aboutsummaryrefslogtreecommitdiff
path: root/src/database
diff options
context:
space:
mode:
authorJude Melton-Houghton <jwmhjwmh@gmail.com>2022-01-07 13:28:49 -0500
committerGitHub <noreply@github.com>2022-01-07 20:28:49 +0200
commitbf22569019749e421e8ffe0a73cff988a9a9c846 (patch)
tree9e6910c1faf0ddce191ad4b3110f08b0201fc482 /src/database
parentb81948a14c138517f6a227dac5b71f0b2facb33c (diff)
downloadminetest-bf22569019749e421e8ffe0a73cff988a9a9c846.tar.gz
minetest-bf22569019749e421e8ffe0a73cff988a9a9c846.tar.bz2
minetest-bf22569019749e421e8ffe0a73cff988a9a9c846.zip
Use a database for mod storage (#11763)
Diffstat (limited to 'src/database')
-rw-r--r--src/database/database-dummy.cpp38
-rw-r--r--src/database/database-dummy.h9
-rw-r--r--src/database/database-files.cpp136
-rw-r--r--src/database/database-files.h26
-rw-r--r--src/database/database-sqlite3.cpp105
-rw-r--r--src/database/database-sqlite3.h25
-rw-r--r--src/database/database.h13
7 files changed, 350 insertions, 2 deletions
diff --git a/src/database/database-dummy.cpp b/src/database/database-dummy.cpp
index b56f341c5..629b2fb04 100644
--- a/src/database/database-dummy.cpp
+++ b/src/database/database-dummy.cpp
@@ -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);
+ }
+}
diff --git a/src/database/database-dummy.h b/src/database/database-dummy.h
index b69919f84..44b9e8d68 100644
--- a/src/database/database-dummy.h
+++ b/src/database/database-dummy.h
@@ -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;
};
diff --git a/src/database/database-files.cpp b/src/database/database-files.cpp
index d9d113b4e..9021ae61b 100644
--- a/src/database/database-files.cpp
+++ b/src/database/database-files.cpp
@@ -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;
+ }
+}
diff --git a/src/database/database-files.h b/src/database/database-files.h
index e647a2e24..962e4d7bb 100644
--- a/src/database/database-files.h
+++ b/src/database/database-files.h
@@ -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;
+};
diff --git a/src/database/database-sqlite3.cpp b/src/database/database-sqlite3.cpp
index 898acc265..e9442118e 100644
--- a/src/database/database-sqlite3.cpp
+++ b/src/database/database-sqlite3.cpp
@@ -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;
+ }
+}
diff --git a/src/database/database-sqlite3.h b/src/database/database-sqlite3.h
index d7202a918..5e3d7c96c 100644
--- a/src/database/database-sqlite3.h
+++ b/src/database/database-sqlite3.h
@@ -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;
+};
diff --git a/src/database/database.h b/src/database/database.h
index b7d551935..fbb5befea 100644
--- a/src/database/database.h
+++ b/src/database/database.h
@@ -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;
+};