input files. Use it for all the databases, at least all the ones managed by database.cc. Gets rid of 150 lines of code :)
Also, fix 1910634 by adding insult.txt to the shout db. It results in some duplication in the dbs on disk, but it's an OK fix for now, I think.
The DBs are now in the subdirectory saves/db/ ; is this a problem for distribution? I poke around in that directory occasionally and it's convenient to have the DBs out of the way.
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@3680 c06c8d41-db1a-0410-9941-cceddc491573
7C62IQ3PLAE7RLZ2ZNA3G6Z7LPXWAMK2OEHSBNY4WEKJ42BPZYQAC
32RFWHFR63CXAG4TXTANBTV35GC4EZHZDCR6LKWT5NBUTVSARH7QC
FUAUWKK3UMB5ZGMXFPEUTXO7VFX5XUB4IQWN434PBLBRXUKIMPPAC
Y56C5OMUQ5XF2G6DKDV4R5MED44UOIUPTBBQVWQBUHYIXYA5MOZAC
J6APXOT4QOGQFONWB7G546VTVF6QG42HVOROMHF7YBDJPR4K26OAC
QHUJATUWL3I7TJOLOY55LPZSAU3EB5X2AKRKTBVN7VSZD527VAXQC
3ZNI2YMHYXRVEONY5CGWXSRMFSLOATZMKU7H6HRY3CC2W6OZAM7QC
KR655YT3I3U5DMN5NS3FPUGMDNT4BI56K3SFF2FNJ77C45NFKL5AC
EJKHYV2Z6UPRVYUAL4WRW33GBNHYBFPMPA57HMBX2LQKXHIUO5VQC
G7CTMQ3VNTAB73ZI3LNZHKTAJ5LEQEGG772MVFQQ5XXLCMJVORTQC
I7QLYOTE6DLQZM7YWUWYLKHRJRB2A3STQ42ALSRGQICEWKD2QTEQC
RN2DSYJRJ55J7S2VBEYCOAINFU4VDBMWDJIYJG326BSLYOFDNHGAC
ZIFFVCQ72K35WGIUMZYN3KOXIUXF2CNXWKG6ZWEZ6LT3NSF3XOQAC
VRFQK6S2TXOFFO5K5HRDXPR7QEKKAZAVCASSIJVPWQ4GE26UOGTQC
PAYI4UTJCR3XZSFOX5L35EURHRXQ6STO4Z7AQ3525QPNL3QYLNBAC
VCG3BRIYRTNNWYC3LOXD6KFGXOX37HAFW2HNV7WXVG2V7EUHLDZQC
W2KRIXSCRJPS6WDIYTHVF5IRMF3V5DWECRAWUPVTB7VZ6A2BLD4QC
// Convenience functions for (read-only) access to generic
// berkeley DB databases.
// TextDB handles dependency checking the db vs text files, creating the
// db, loading, and destroying the DB.
class TextDB
{
public:
// db_name is the savedir-relative name of the db file,
// minus the "db" extension.
TextDB(const char* db_name, ...);
~TextDB() { shutdown(); }
void init();
void shutdown();
DBM* get() { return _db; }
// shout and speak databases are all generated from a single
// text file in the data directory and stored as .db files in the
// save directory. New databases that follow this same pattern should
// add themselves to the db_id enum and the singleFileDBs array below.
enum db_id
{
DB_SHOUT,
DB_HELP,
MAX_DBID
private:
const char* const _db_name; // relative to savedir
std::vector<std::string> _input_files; // relative to datafile dirs
DBM* _db;
SingleFileDB("shout"),
SingleFileDB("help")
TextDB( "db/descriptions",
"descript/features.txt",
"descript/items.txt",
"descript/unident.txt",
"descript/monsters.txt",
"descript/spells.txt",
"descript/gods.txt",
"descript/branches.txt",
0),
TextDB( "db/randart",
"database/randname.txt",
"database/rand_wpn.txt", // mostly weapons
"database/rand_arm.txt", // mostly armour
"database/rand_all.txt", // jewellery and general
0),
TextDB( "db/speak",
"database/monspeak.txt", // monster speech
"database/wpnnoise.txt", // noisy weapon speech
"database/insult.txt", // imp/demon taunts
0),
TextDB( "db/shout",
"database/shout.txt",
"database/insult.txt", // imp/demon taunts, again
0),
TextDB( "db/help", "database/help.txt", 0),
#define DESC_BASE_NAME "descript"
#define DESC_TXT_DIR "descript"
#define DESC_DB (DESC_BASE_NAME ".db")
static TextDB& DescriptionDB = AllDBs[0];
static TextDB& RandartDB = AllDBs[1];
static TextDB& SpeakDB = AllDBs[2];
static TextDB& ShoutDB = AllDBs[3];
static TextDB& HelpDB = AllDBs[4];
#define DATABASE_TXT_DIR "database"
#define RANDART_BASE_NAME "randart"
#define SPEAK_BASE_NAME "speak"
#define RANDART_DB (RANDART_BASE_NAME ".db")
#define SPEAK_DB (SPEAK_BASE_NAME ".db")
// ----------------------------------------------------------------------
// TextDB
// ----------------------------------------------------------------------
static std::vector<std::string> description_txt_paths();
static std::vector<std::string> randart_txt_paths();
static std::vector<std::string> speak_txt_paths();
static void generate_description_db();
static void generate_randart_db();
static void generate_speak_db();
static void store_text_db(const std::string &in, const std::string &out);
static DBM *get_dbm(db_id id);
void databaseSystemInit()
TextDB::TextDB(const char* db_name, ...)
: _db_name(db_name),
_db(NULL)
std::string descriptionPath = get_savedir_path(DESC_DB);
std::vector<std::string> textPaths = description_txt_paths();
const char* input_file = va_arg(args, const char *);
if (input_file == 0) break;
ASSERT( strstr(input_file, ".txt") != 0 ); // probably forgot the terminating 0
_input_files.push_back(input_file);
}
va_end(args);
}
// If any of the description text files are newer then
// aggregated description db, then regenerate the whole db
for (int i = 0, size = textPaths.size(); i < size; i++)
if (is_newer(textPaths[i], descriptionPath))
{
generate_description_db();
break;
}
void TextDB::init()
{
if (_needs_update())
_regenerate_db();
const std::string full_db_path = get_savedir_path(_db_name);
_db = dbm_open(full_db_path.c_str(), O_RDONLY, 0660);
if (_db == NULL)
end(1, true, "Failed to open DB: %s", full_db_path.c_str());
}
descriptionPath.erase(descriptionPath.length() - 3);
if (!(DescriptionDB = openDB(descriptionPath.c_str())))
end(1, true, "Failed to open DB: %s", descriptionPath.c_str());
void TextDB::shutdown()
{
if (_db)
{
dbm_close(_db);
_db = NULL;
std::string randartPath = get_savedir_path(RANDART_DB);
std::vector<std::string> textPaths = randart_txt_paths();
// If any of the randart text files are newer then
// aggregated randart db, then regenerate the whole db
for (int i = 0, size = textPaths.size(); i < size; i++)
if (is_newer(textPaths[i], randartPath))
{
generate_randart_db();
break;
}
std::string full_input_path = datafile_path(_input_files[i], true);
if (is_newer(full_input_path, full_db_path))
return true;
}
return false;
}
randartPath.erase(randartPath.length() - 3);
if (!(RandartDB = openDB(randartPath.c_str())))
end(1, true, "Failed to open DB: %s", randartPath.c_str());
}
void TextDB::_regenerate_db()
{
std::string db_path = get_savedir_path(_db_name);
std::string full_db_path = db_path + ".db";
std::string speakPath = get_savedir_path(SPEAK_DB);
std::vector<std::string> textPaths = speak_txt_paths();
// If any of the speech text files are newer then
// aggregated speak db, then regenerate the whole db
for (int i = 0, size = textPaths.size(); i < size; i++)
if (is_newer(textPaths[i], speakPath))
{
generate_speak_db();
break;
}
speakPath.erase(speakPath.length() - 3);
if (!(SpeakDB = openDB(speakPath.c_str())))
end(1, true, "Failed to open DB: %s", speakPath.c_str());
std::string output_dir = get_parent_directory(db_path);
if (!check_dir("DB directory", output_dir))
end(1);
std::string filename = DATABASE_TXT_DIR;
filename += FILE_SEPARATOR;
filename += singleFileDBs[i].base_name;
filename += ".txt";
std::string dbText = datafile_path(filename);
std::string dbBase = get_savedir_path(
singleFileDBs[i].base_name);
for (unsigned int i=0; i<_input_files.size(); i++)
{
std::string full_input_path = datafile_path(_input_files[i], true);
store_text_db(full_input_path, db_path);
}
if (!(singleFileDBs[i].db = openDB(dbBase.c_str())))
end(1, true, "Failed to open DB: %s", dbBase.c_str());
}
void databaseSystemInit()
{
for (unsigned int i=0; i < ARRAYSIZE(AllDBs); i++)
AllDBs[i].init();
for (db_list::iterator i = OpenDBList.begin();
i != OpenDBList.end(); ++i)
{
dbm_close(*i);
}
OpenDBList.clear();
DescriptionDB = NULL;
RandartDB = NULL;
SpeakDB = NULL;
for (unsigned int i=0; i < ARRAYSIZE(AllDBs); i++)
AllDBs[i].shutdown();
// This is here, and is external, just for future expansion -- if we
// want to allow external modules to manage their own DB, they can
// use this for the sake of convenience. It's arguable that it's
// morally wrong to have the database module manage the memory here.
// But hey, life is hard and you can write your own berkeley DB
// calls if you like.
DBM *openDB(const char *dbFilename)
{
DBM *dbToReturn = dbm_open(dbFilename, O_RDONLY, 0660);
if (dbToReturn)
OpenDBList.push_front(dbToReturn);
}
return database_find_bodies(DescriptionDB, regex, true, filter);
}
static std::vector<std::string> description_txt_paths()
{
std::vector<std::string> txt_file_names;
std::vector<std::string> paths;
txt_file_names.push_back("features");
txt_file_names.push_back("items");
txt_file_names.push_back("unident");
txt_file_names.push_back("monsters");
txt_file_names.push_back("spells");
txt_file_names.push_back("gods");
txt_file_names.push_back("branches");
for (int i = 0, size = txt_file_names.size(); i < size; i++)
{
std::string name = DESC_TXT_DIR;
name += FILE_SEPARATOR;
name += txt_file_names[i];
name += ".txt";
std::string txt_path = datafile_path(name);
if (!txt_path.empty())
paths.push_back(txt_path);
static void generate_description_db()
{
std::string db_path = get_savedir_path(DESC_BASE_NAME);
std::string full_db_path = get_savedir_path(DESC_DB);
std::vector<std::string> txt_paths = description_txt_paths();
file_lock lock(get_savedir_path(DESC_BASE_NAME ".lk"), "wb");
unlink( full_db_path.c_str() );
for (int i = 0, size = txt_paths.size(); i < size; i++)
store_text_db(txt_paths[i], db_path);
DO_CHMOD_PRIVATE(full_db_path.c_str());
}
static std::vector<std::string> randart_txt_paths()
{
std::vector<std::string> txt_file_names;
std::vector<std::string> paths;
txt_file_names.push_back("randname");
txt_file_names.push_back("rand_wpn"); // mostly weapons
txt_file_names.push_back("rand_arm"); // mostly armour
txt_file_names.push_back("rand_all"); // jewellery and general
for (int i = 0, size = txt_file_names.size(); i < size; i++)
{
std::string name = DATABASE_TXT_DIR;
name += FILE_SEPARATOR;
name += txt_file_names[i];
name += ".txt";
std::string txt_path = datafile_path(name);
if (!txt_path.empty())
paths.push_back(txt_path);
}
return (paths);
}
static void generate_randart_db()
{
std::string db_path = get_savedir_path(RANDART_BASE_NAME);
std::string full_db_path = get_savedir_path(RANDART_DB);
std::vector<std::string> txt_paths = randart_txt_paths();
file_lock lock(get_savedir_path(RANDART_BASE_NAME ".lk"), "wb");
unlink( full_db_path.c_str() );
for (int i = 0, size = txt_paths.size(); i < size; i++)
store_text_db(txt_paths[i], db_path);
DO_CHMOD_PRIVATE(full_db_path.c_str());
}
static std::vector<std::string> speak_txt_paths()
{
std::vector<std::string> txt_file_names;
std::vector<std::string> paths;
txt_file_names.push_back("monspeak"); // monster speech
txt_file_names.push_back("wpnnoise"); // noisy weapon speech
txt_file_names.push_back("insult"); // imp/demon taunts
for (int i = 0, size = txt_file_names.size(); i < size; i++)
{
std::string name = DATABASE_TXT_DIR;
name += FILE_SEPARATOR;
name += txt_file_names[i];
name += ".txt";
std::string txt_path = datafile_path(name);
if (!txt_path.empty())
paths.push_back(txt_path);
}
return (paths);
}
static void generate_speak_db()
{
std::string db_path = get_savedir_path(SPEAK_BASE_NAME);
std::string full_db_path = get_savedir_path(SPEAK_DB);
std::vector<std::string> txt_paths = speak_txt_paths();
file_lock lock(get_savedir_path(SPEAK_BASE_NAME ".lk"), "wb");
unlink( full_db_path.c_str() );
for (int i = 0, size = txt_paths.size(); i < size; i++)
store_text_db(txt_paths[i], db_path);
DO_CHMOD_PRIVATE(full_db_path.c_str());
}
static DBM *get_dbm(db_id id)
{
DBM *ret = singleFileDBs[id].db;
// If this assertion fires, the database hasn't been initialized
// properly in databaseSystemInit().
ASSERT(ret);
return ret;
}