mirror of
https://github.com/luanti-org/luanti.git
synced 2025-06-27 16:36:03 +00:00
Use a database for mod storage (#11763)
This commit is contained in:
parent
b81948a14c
commit
bf22569019
21 changed files with 798 additions and 127 deletions
|
@ -80,3 +80,41 @@ void Database_Dummy::listPlayers(std::vector<std::string> &res)
|
|||
res.emplace_back(player);
|
||||
}
|
||||
}
|
||||
|
||||
bool Database_Dummy::getModEntries(const std::string &modname, StringMap *storage)
|
||||
{
|
||||
const auto mod_pair = m_mod_meta_database.find(modname);
|
||||
if (mod_pair != m_mod_meta_database.cend()) {
|
||||
for (const auto &pair : mod_pair->second) {
|
||||
(*storage)[pair.first] = pair.second;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Database_Dummy::setModEntry(const std::string &modname,
|
||||
const std::string &key, const std::string &value)
|
||||
{
|
||||
auto mod_pair = m_mod_meta_database.find(modname);
|
||||
if (mod_pair == m_mod_meta_database.end()) {
|
||||
m_mod_meta_database[modname] = StringMap({{key, value}});
|
||||
} else {
|
||||
mod_pair->second[key] = value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Database_Dummy::removeModEntry(const std::string &modname, const std::string &key)
|
||||
{
|
||||
auto mod_pair = m_mod_meta_database.find(modname);
|
||||
if (mod_pair != m_mod_meta_database.end())
|
||||
return mod_pair->second.erase(key) > 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
void Database_Dummy::listMods(std::vector<std::string> *res)
|
||||
{
|
||||
for (const auto &pair : m_mod_meta_database) {
|
||||
res->push_back(pair.first);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
#include "database.h"
|
||||
#include "irrlichttypes.h"
|
||||
|
||||
class Database_Dummy : public MapDatabase, public PlayerDatabase
|
||||
class Database_Dummy : public MapDatabase, public PlayerDatabase, public ModMetadataDatabase
|
||||
{
|
||||
public:
|
||||
bool saveBlock(const v3s16 &pos, const std::string &data);
|
||||
|
@ -37,10 +37,17 @@ public:
|
|||
bool removePlayer(const std::string &name);
|
||||
void listPlayers(std::vector<std::string> &res);
|
||||
|
||||
bool getModEntries(const std::string &modname, StringMap *storage);
|
||||
bool setModEntry(const std::string &modname,
|
||||
const std::string &key, const std::string &value);
|
||||
bool removeModEntry(const std::string &modname, const std::string &key);
|
||||
void listMods(std::vector<std::string> *res);
|
||||
|
||||
void beginSave() {}
|
||||
void endSave() {}
|
||||
|
||||
private:
|
||||
std::map<s64, std::string> m_database;
|
||||
std::set<std::string> m_player_database;
|
||||
std::unordered_map<std::string, StringMap> m_mod_meta_database;
|
||||
};
|
||||
|
|
|
@ -18,7 +18,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <json/json.h>
|
||||
#include "convert_json.h"
|
||||
#include "database-files.h"
|
||||
#include "remoteplayer.h"
|
||||
|
@ -376,3 +375,138 @@ bool AuthDatabaseFiles::writeAuthFile()
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ModMetadataDatabaseFiles::ModMetadataDatabaseFiles(const std::string &savedir):
|
||||
m_storage_dir(savedir + DIR_DELIM + "mod_storage")
|
||||
{
|
||||
}
|
||||
|
||||
bool ModMetadataDatabaseFiles::getModEntries(const std::string &modname, StringMap *storage)
|
||||
{
|
||||
Json::Value *meta = getOrCreateJson(modname);
|
||||
if (!meta)
|
||||
return false;
|
||||
|
||||
const Json::Value::Members attr_list = meta->getMemberNames();
|
||||
for (const auto &it : attr_list) {
|
||||
Json::Value attr_value = (*meta)[it];
|
||||
(*storage)[it] = attr_value.asString();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ModMetadataDatabaseFiles::setModEntry(const std::string &modname,
|
||||
const std::string &key, const std::string &value)
|
||||
{
|
||||
Json::Value *meta = getOrCreateJson(modname);
|
||||
if (!meta)
|
||||
return false;
|
||||
|
||||
(*meta)[key] = Json::Value(value);
|
||||
m_modified.insert(modname);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ModMetadataDatabaseFiles::removeModEntry(const std::string &modname,
|
||||
const std::string &key)
|
||||
{
|
||||
Json::Value *meta = getOrCreateJson(modname);
|
||||
if (!meta)
|
||||
return false;
|
||||
|
||||
Json::Value removed;
|
||||
if (meta->removeMember(key, &removed)) {
|
||||
m_modified.insert(modname);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ModMetadataDatabaseFiles::beginSave()
|
||||
{
|
||||
}
|
||||
|
||||
void ModMetadataDatabaseFiles::endSave()
|
||||
{
|
||||
if (!fs::CreateAllDirs(m_storage_dir)) {
|
||||
errorstream << "ModMetadataDatabaseFiles: Unable to save. '" << m_storage_dir
|
||||
<< "' tree cannot be created." << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto it = m_modified.begin(); it != m_modified.end();) {
|
||||
const std::string &modname = *it;
|
||||
|
||||
if (!fs::PathExists(m_storage_dir)) {
|
||||
if (!fs::CreateAllDirs(m_storage_dir)) {
|
||||
errorstream << "ModMetadataDatabaseFiles[" << modname
|
||||
<< "]: Unable to save. '" << m_storage_dir
|
||||
<< "' tree cannot be created." << std::endl;
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
} else if (!fs::IsDir(m_storage_dir)) {
|
||||
errorstream << "ModMetadataDatabaseFiles[" << modname << "]: Unable to save. '"
|
||||
<< m_storage_dir << "' is not a directory." << std::endl;
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
||||
const Json::Value &json = m_mod_meta[modname];
|
||||
|
||||
if (!fs::safeWriteToFile(m_storage_dir + DIR_DELIM + modname, fastWriteJson(json))) {
|
||||
errorstream << "ModMetadataDatabaseFiles[" << modname
|
||||
<< "]: failed write file." << std::endl;
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
||||
it = m_modified.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void ModMetadataDatabaseFiles::listMods(std::vector<std::string> *res)
|
||||
{
|
||||
// List in-memory metadata first.
|
||||
for (const auto &pair : m_mod_meta) {
|
||||
res->push_back(pair.first);
|
||||
}
|
||||
|
||||
// List other metadata present in the filesystem.
|
||||
for (const auto &entry : fs::GetDirListing(m_storage_dir)) {
|
||||
if (!entry.dir && m_mod_meta.count(entry.name) == 0)
|
||||
res->push_back(entry.name);
|
||||
}
|
||||
}
|
||||
|
||||
Json::Value *ModMetadataDatabaseFiles::getOrCreateJson(const std::string &modname)
|
||||
{
|
||||
auto found = m_mod_meta.find(modname);
|
||||
if (found == m_mod_meta.end()) {
|
||||
fs::CreateAllDirs(m_storage_dir);
|
||||
|
||||
Json::Value meta(Json::objectValue);
|
||||
|
||||
std::string path = m_storage_dir + DIR_DELIM + modname;
|
||||
if (fs::PathExists(path)) {
|
||||
std::ifstream is(path.c_str(), std::ios_base::binary);
|
||||
|
||||
Json::CharReaderBuilder builder;
|
||||
builder.settings_["collectComments"] = false;
|
||||
std::string errs;
|
||||
|
||||
if (!Json::parseFromStream(builder, is, &meta, &errs)) {
|
||||
errorstream << "ModMetadataDatabaseFiles[" << modname
|
||||
<< "]: failed read data (Json decoding failure). Message: "
|
||||
<< errs << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return &(m_mod_meta[modname] = meta);
|
||||
} else {
|
||||
return &found->second;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
#include "database.h"
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <json/json.h>
|
||||
|
||||
class PlayerDatabaseFiles : public PlayerDatabase
|
||||
{
|
||||
|
@ -69,3 +71,27 @@ private:
|
|||
bool readAuthFile();
|
||||
bool writeAuthFile();
|
||||
};
|
||||
|
||||
class ModMetadataDatabaseFiles : public ModMetadataDatabase
|
||||
{
|
||||
public:
|
||||
ModMetadataDatabaseFiles(const std::string &savedir);
|
||||
virtual ~ModMetadataDatabaseFiles() = default;
|
||||
|
||||
virtual bool getModEntries(const std::string &modname, StringMap *storage);
|
||||
virtual bool setModEntry(const std::string &modname,
|
||||
const std::string &key, const std::string &value);
|
||||
virtual bool removeModEntry(const std::string &modname, const std::string &key);
|
||||
virtual void listMods(std::vector<std::string> *res);
|
||||
|
||||
virtual void beginSave();
|
||||
virtual void endSave();
|
||||
|
||||
private:
|
||||
Json::Value *getOrCreateJson(const std::string &modname);
|
||||
bool writeJson(const std::string &modname, const Json::Value &json);
|
||||
|
||||
std::string m_storage_dir;
|
||||
std::unordered_map<std::string, Json::Value> m_mod_meta;
|
||||
std::unordered_set<std::string> m_modified;
|
||||
};
|
||||
|
|
|
@ -779,3 +779,108 @@ void AuthDatabaseSQLite3::writePrivileges(const AuthEntry &authEntry)
|
|||
sqlite3_reset(m_stmt_write_privs);
|
||||
}
|
||||
}
|
||||
|
||||
ModMetadataDatabaseSQLite3::ModMetadataDatabaseSQLite3(const std::string &savedir):
|
||||
Database_SQLite3(savedir, "mod_storage"), ModMetadataDatabase()
|
||||
{
|
||||
}
|
||||
|
||||
ModMetadataDatabaseSQLite3::~ModMetadataDatabaseSQLite3()
|
||||
{
|
||||
FINALIZE_STATEMENT(m_stmt_remove)
|
||||
FINALIZE_STATEMENT(m_stmt_set)
|
||||
FINALIZE_STATEMENT(m_stmt_get)
|
||||
}
|
||||
|
||||
void ModMetadataDatabaseSQLite3::createDatabase()
|
||||
{
|
||||
assert(m_database); // Pre-condition
|
||||
|
||||
SQLOK(sqlite3_exec(m_database,
|
||||
"CREATE TABLE IF NOT EXISTS `entries` (\n"
|
||||
" `modname` TEXT NOT NULL,\n"
|
||||
" `key` BLOB NOT NULL,\n"
|
||||
" `value` BLOB NOT NULL,\n"
|
||||
" PRIMARY KEY (`modname`, `key`)\n"
|
||||
");\n",
|
||||
NULL, NULL, NULL),
|
||||
"Failed to create database table");
|
||||
}
|
||||
|
||||
void ModMetadataDatabaseSQLite3::initStatements()
|
||||
{
|
||||
PREPARE_STATEMENT(get, "SELECT `key`, `value` FROM `entries` WHERE `modname` = ?");
|
||||
PREPARE_STATEMENT(set,
|
||||
"REPLACE INTO `entries` (`modname`, `key`, `value`) VALUES (?, ?, ?)");
|
||||
PREPARE_STATEMENT(remove, "DELETE FROM `entries` WHERE `modname` = ? AND `key` = ?");
|
||||
}
|
||||
|
||||
bool ModMetadataDatabaseSQLite3::getModEntries(const std::string &modname, StringMap *storage)
|
||||
{
|
||||
verifyDatabase();
|
||||
|
||||
str_to_sqlite(m_stmt_get, 1, modname);
|
||||
while (sqlite3_step(m_stmt_get) == SQLITE_ROW) {
|
||||
const char *key_data = (const char *) sqlite3_column_blob(m_stmt_get, 0);
|
||||
size_t key_len = sqlite3_column_bytes(m_stmt_get, 0);
|
||||
const char *value_data = (const char *) sqlite3_column_blob(m_stmt_get, 1);
|
||||
size_t value_len = sqlite3_column_bytes(m_stmt_get, 1);
|
||||
(*storage)[std::string(key_data, key_len)] = std::string(value_data, value_len);
|
||||
}
|
||||
sqlite3_vrfy(sqlite3_errcode(m_database), SQLITE_DONE);
|
||||
|
||||
sqlite3_reset(m_stmt_get);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ModMetadataDatabaseSQLite3::setModEntry(const std::string &modname,
|
||||
const std::string &key, const std::string &value)
|
||||
{
|
||||
verifyDatabase();
|
||||
|
||||
str_to_sqlite(m_stmt_set, 1, modname);
|
||||
SQLOK(sqlite3_bind_blob(m_stmt_set, 2, key.data(), key.size(), NULL),
|
||||
"Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__));
|
||||
SQLOK(sqlite3_bind_blob(m_stmt_set, 3, value.data(), value.size(), NULL),
|
||||
"Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__));
|
||||
SQLRES(sqlite3_step(m_stmt_set), SQLITE_DONE, "Failed to set mod entry")
|
||||
|
||||
sqlite3_reset(m_stmt_set);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ModMetadataDatabaseSQLite3::removeModEntry(const std::string &modname,
|
||||
const std::string &key)
|
||||
{
|
||||
verifyDatabase();
|
||||
|
||||
str_to_sqlite(m_stmt_remove, 1, modname);
|
||||
SQLOK(sqlite3_bind_blob(m_stmt_remove, 2, key.data(), key.size(), NULL),
|
||||
"Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__));
|
||||
sqlite3_vrfy(sqlite3_step(m_stmt_remove), SQLITE_DONE);
|
||||
int changes = sqlite3_changes(m_database);
|
||||
|
||||
sqlite3_reset(m_stmt_remove);
|
||||
|
||||
return changes > 0;
|
||||
}
|
||||
|
||||
void ModMetadataDatabaseSQLite3::listMods(std::vector<std::string> *res)
|
||||
{
|
||||
verifyDatabase();
|
||||
|
||||
char *errmsg;
|
||||
int status = sqlite3_exec(m_database,
|
||||
"SELECT `modname` FROM `entries` GROUP BY `modname`;",
|
||||
[](void *res_vp, int n_col, char **cols, char **col_names) -> int {
|
||||
((decltype(res)) res_vp)->emplace_back(cols[0]);
|
||||
return 0;
|
||||
}, (void *) res, &errmsg);
|
||||
if (status != SQLITE_OK) {
|
||||
DatabaseException e(std::string("Error trying to list mods with metadata: ") + errmsg);
|
||||
sqlite3_free(errmsg);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -232,3 +232,28 @@ private:
|
|||
sqlite3_stmt *m_stmt_delete_privs = nullptr;
|
||||
sqlite3_stmt *m_stmt_last_insert_rowid = nullptr;
|
||||
};
|
||||
|
||||
class ModMetadataDatabaseSQLite3 : private Database_SQLite3, public ModMetadataDatabase
|
||||
{
|
||||
public:
|
||||
ModMetadataDatabaseSQLite3(const std::string &savedir);
|
||||
virtual ~ModMetadataDatabaseSQLite3();
|
||||
|
||||
virtual bool getModEntries(const std::string &modname, StringMap *storage);
|
||||
virtual bool setModEntry(const std::string &modname,
|
||||
const std::string &key, const std::string &value);
|
||||
virtual bool removeModEntry(const std::string &modname, const std::string &key);
|
||||
virtual void listMods(std::vector<std::string> *res);
|
||||
|
||||
virtual void beginSave() { Database_SQLite3::beginSave(); }
|
||||
virtual void endSave() { Database_SQLite3::endSave(); }
|
||||
|
||||
protected:
|
||||
virtual void createDatabase();
|
||||
virtual void initStatements();
|
||||
|
||||
private:
|
||||
sqlite3_stmt *m_stmt_get = nullptr;
|
||||
sqlite3_stmt *m_stmt_set = nullptr;
|
||||
sqlite3_stmt *m_stmt_remove = nullptr;
|
||||
};
|
||||
|
|
|
@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
#include "irr_v3d.h"
|
||||
#include "irrlichttypes.h"
|
||||
#include "util/basic_macros.h"
|
||||
#include "util/string.h"
|
||||
|
||||
class Database
|
||||
{
|
||||
|
@ -84,3 +85,15 @@ public:
|
|||
virtual void listNames(std::vector<std::string> &res) = 0;
|
||||
virtual void reload() = 0;
|
||||
};
|
||||
|
||||
class ModMetadataDatabase : public Database
|
||||
{
|
||||
public:
|
||||
virtual ~ModMetadataDatabase() = default;
|
||||
|
||||
virtual bool getModEntries(const std::string &modname, StringMap *storage) = 0;
|
||||
virtual bool setModEntry(const std::string &modname,
|
||||
const std::string &key, const std::string &value) = 0;
|
||||
virtual bool removeModEntry(const std::string &modname, const std::string &key) = 0;
|
||||
virtual void listMods(std::vector<std::string> *res) = 0;
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue