forked from Mirrorlandia_minetest/minetest
Use a database for mod storage (#11763)
This commit is contained in:
parent
b81948a14c
commit
bf22569019
@ -112,6 +112,10 @@ leveldb, and files.
|
|||||||
Migrate from current players backend to another. Possible values are sqlite3,
|
Migrate from current players backend to another. Possible values are sqlite3,
|
||||||
leveldb, postgresql, dummy, and files.
|
leveldb, postgresql, dummy, and files.
|
||||||
.TP
|
.TP
|
||||||
|
.B \-\-migrate-mod-storage <value>
|
||||||
|
Migrate from current mod storage backend to another. Possible values are
|
||||||
|
sqlite3, dummy, and files.
|
||||||
|
.TP
|
||||||
.B \-\-terminal
|
.B \-\-terminal
|
||||||
Display an interactive terminal over ncurses during execution.
|
Display an interactive terminal over ncurses during execution.
|
||||||
|
|
||||||
|
@ -128,6 +128,11 @@ Client::Client(
|
|||||||
// Add local player
|
// Add local player
|
||||||
m_env.setLocalPlayer(new LocalPlayer(this, playername));
|
m_env.setLocalPlayer(new LocalPlayer(this, playername));
|
||||||
|
|
||||||
|
// Make the mod storage database and begin the save for later
|
||||||
|
m_mod_storage_database =
|
||||||
|
new ModMetadataDatabaseSQLite3(porting::path_user + DIR_DELIM + "client");
|
||||||
|
m_mod_storage_database->beginSave();
|
||||||
|
|
||||||
if (g_settings->getBool("enable_minimap")) {
|
if (g_settings->getBool("enable_minimap")) {
|
||||||
m_minimap = new Minimap(this);
|
m_minimap = new Minimap(this);
|
||||||
}
|
}
|
||||||
@ -305,6 +310,11 @@ Client::~Client()
|
|||||||
m_minimap = nullptr;
|
m_minimap = nullptr;
|
||||||
|
|
||||||
delete m_media_downloader;
|
delete m_media_downloader;
|
||||||
|
|
||||||
|
// Write the changes and delete
|
||||||
|
if (m_mod_storage_database)
|
||||||
|
m_mod_storage_database->endSave();
|
||||||
|
delete m_mod_storage_database;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::connect(Address address, bool is_local_server)
|
void Client::connect(Address address, bool is_local_server)
|
||||||
@ -641,19 +651,12 @@ void Client::step(float dtime)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write changes to the mod storage
|
||||||
m_mod_storage_save_timer -= dtime;
|
m_mod_storage_save_timer -= dtime;
|
||||||
if (m_mod_storage_save_timer <= 0.0f) {
|
if (m_mod_storage_save_timer <= 0.0f) {
|
||||||
m_mod_storage_save_timer = g_settings->getFloat("server_map_save_interval");
|
m_mod_storage_save_timer = g_settings->getFloat("server_map_save_interval");
|
||||||
int n = 0;
|
m_mod_storage_database->endSave();
|
||||||
for (std::unordered_map<std::string, ModMetadata *>::const_iterator
|
m_mod_storage_database->beginSave();
|
||||||
it = m_mod_storages.begin(); it != m_mod_storages.end(); ++it) {
|
|
||||||
if (it->second->isModified()) {
|
|
||||||
it->second->save(getModStoragePath());
|
|
||||||
n++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (n > 0)
|
|
||||||
infostream << "Saved " << n << " modified mod storages." << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write server map
|
// Write server map
|
||||||
@ -1960,16 +1963,8 @@ void Client::unregisterModStorage(const std::string &name)
|
|||||||
{
|
{
|
||||||
std::unordered_map<std::string, ModMetadata *>::const_iterator it =
|
std::unordered_map<std::string, ModMetadata *>::const_iterator it =
|
||||||
m_mod_storages.find(name);
|
m_mod_storages.find(name);
|
||||||
if (it != m_mod_storages.end()) {
|
if (it != m_mod_storages.end())
|
||||||
// Save unconditionaly on unregistration
|
|
||||||
it->second->save(getModStoragePath());
|
|
||||||
m_mod_storages.erase(name);
|
m_mod_storages.erase(name);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Client::getModStoragePath() const
|
|
||||||
{
|
|
||||||
return porting::path_user + DIR_DELIM + "client" + DIR_DELIM + "mod_storage";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -380,8 +380,8 @@ public:
|
|||||||
{ return checkPrivilege(priv); }
|
{ return checkPrivilege(priv); }
|
||||||
virtual scene::IAnimatedMesh* getMesh(const std::string &filename, bool cache = false);
|
virtual scene::IAnimatedMesh* getMesh(const std::string &filename, bool cache = false);
|
||||||
const std::string* getModFile(std::string filename);
|
const std::string* getModFile(std::string filename);
|
||||||
|
ModMetadataDatabase *getModStorageDatabase() override { return m_mod_storage_database; }
|
||||||
|
|
||||||
std::string getModStoragePath() const override;
|
|
||||||
bool registerModStorage(ModMetadata *meta) override;
|
bool registerModStorage(ModMetadata *meta) override;
|
||||||
void unregisterModStorage(const std::string &name) override;
|
void unregisterModStorage(const std::string &name) override;
|
||||||
|
|
||||||
@ -590,6 +590,7 @@ private:
|
|||||||
// Client modding
|
// Client modding
|
||||||
ClientScripting *m_script = nullptr;
|
ClientScripting *m_script = nullptr;
|
||||||
std::unordered_map<std::string, ModMetadata *> m_mod_storages;
|
std::unordered_map<std::string, ModMetadata *> m_mod_storages;
|
||||||
|
ModMetadataDatabase *m_mod_storage_database = nullptr;
|
||||||
float m_mod_storage_save_timer = 10.0f;
|
float m_mod_storage_save_timer = 10.0f;
|
||||||
std::vector<ModSpec> m_mods;
|
std::vector<ModSpec> m_mods;
|
||||||
StringMap m_mod_vfs;
|
StringMap m_mod_vfs;
|
||||||
|
@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
#include <json/json.h>
|
#include <json/json.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include "content/mods.h"
|
#include "content/mods.h"
|
||||||
|
#include "database/database.h"
|
||||||
#include "filesys.h"
|
#include "filesys.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "content/subgames.h"
|
#include "content/subgames.h"
|
||||||
@ -422,83 +423,29 @@ ClientModConfiguration::ClientModConfiguration(const std::string &path) :
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ModMetadata::ModMetadata(const std::string &mod_name) : m_mod_name(mod_name)
|
ModMetadata::ModMetadata(const std::string &mod_name, ModMetadataDatabase *database):
|
||||||
|
m_mod_name(mod_name), m_database(database)
|
||||||
{
|
{
|
||||||
|
m_database->getModEntries(m_mod_name, &m_stringvars);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModMetadata::clear()
|
void ModMetadata::clear()
|
||||||
{
|
{
|
||||||
|
for (const auto &pair : m_stringvars) {
|
||||||
|
m_database->removeModEntry(m_mod_name, pair.first);
|
||||||
|
}
|
||||||
Metadata::clear();
|
Metadata::clear();
|
||||||
m_modified = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModMetadata::save(const std::string &root_path)
|
|
||||||
{
|
|
||||||
Json::Value json;
|
|
||||||
for (StringMap::const_iterator it = m_stringvars.begin();
|
|
||||||
it != m_stringvars.end(); ++it) {
|
|
||||||
json[it->first] = it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fs::PathExists(root_path)) {
|
|
||||||
if (!fs::CreateAllDirs(root_path)) {
|
|
||||||
errorstream << "ModMetadata[" << m_mod_name
|
|
||||||
<< "]: Unable to save. '" << root_path
|
|
||||||
<< "' tree cannot be created." << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!fs::IsDir(root_path)) {
|
|
||||||
errorstream << "ModMetadata[" << m_mod_name << "]: Unable to save. '"
|
|
||||||
<< root_path << "' is not a directory." << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool w_ok = fs::safeWriteToFile(
|
|
||||||
root_path + DIR_DELIM + m_mod_name, fastWriteJson(json));
|
|
||||||
|
|
||||||
if (w_ok) {
|
|
||||||
m_modified = false;
|
|
||||||
} else {
|
|
||||||
errorstream << "ModMetadata[" << m_mod_name << "]: failed write file."
|
|
||||||
<< std::endl;
|
|
||||||
}
|
|
||||||
return w_ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModMetadata::load(const std::string &root_path)
|
|
||||||
{
|
|
||||||
m_stringvars.clear();
|
|
||||||
|
|
||||||
std::ifstream is((root_path + DIR_DELIM + m_mod_name).c_str(),
|
|
||||||
std::ios_base::binary);
|
|
||||||
if (!is.good()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Json::Value root;
|
|
||||||
Json::CharReaderBuilder builder;
|
|
||||||
builder.settings_["collectComments"] = false;
|
|
||||||
std::string errs;
|
|
||||||
|
|
||||||
if (!Json::parseFromStream(builder, is, &root, &errs)) {
|
|
||||||
errorstream << "ModMetadata[" << m_mod_name
|
|
||||||
<< "]: failed read data "
|
|
||||||
"(Json decoding failure). Message: "
|
|
||||||
<< errs << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Json::Value::Members attr_list = root.getMemberNames();
|
|
||||||
for (const auto &it : attr_list) {
|
|
||||||
Json::Value attr_value = root[it];
|
|
||||||
m_stringvars[it] = attr_value.asString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ModMetadata::setString(const std::string &name, const std::string &var)
|
bool ModMetadata::setString(const std::string &name, const std::string &var)
|
||||||
{
|
{
|
||||||
m_modified = Metadata::setString(name, var);
|
if (Metadata::setString(name, var)) {
|
||||||
return m_modified;
|
if (var.empty()) {
|
||||||
|
m_database->removeModEntry(m_mod_name, name);
|
||||||
|
} else {
|
||||||
|
m_database->setModEntry(m_mod_name, name, var);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "metadata.h"
|
#include "metadata.h"
|
||||||
|
|
||||||
|
class ModMetadataDatabase;
|
||||||
|
|
||||||
#define MODNAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_"
|
#define MODNAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_"
|
||||||
|
|
||||||
struct ModSpec
|
struct ModSpec
|
||||||
@ -149,20 +151,16 @@ class ModMetadata : public Metadata
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ModMetadata() = delete;
|
ModMetadata() = delete;
|
||||||
ModMetadata(const std::string &mod_name);
|
ModMetadata(const std::string &mod_name, ModMetadataDatabase *database);
|
||||||
~ModMetadata() = default;
|
~ModMetadata() = default;
|
||||||
|
|
||||||
virtual void clear();
|
virtual void clear();
|
||||||
|
|
||||||
bool save(const std::string &root_path);
|
|
||||||
bool load(const std::string &root_path);
|
|
||||||
|
|
||||||
bool isModified() const { return m_modified; }
|
|
||||||
const std::string &getModName() const { return m_mod_name; }
|
const std::string &getModName() const { return m_mod_name; }
|
||||||
|
|
||||||
virtual bool setString(const std::string &name, const std::string &var);
|
virtual bool setString(const std::string &name, const std::string &var);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string m_mod_name;
|
std::string m_mod_name;
|
||||||
bool m_modified = false;
|
ModMetadataDatabase *m_database;
|
||||||
};
|
};
|
||||||
|
@ -358,6 +358,7 @@ void loadGameConfAndInitWorld(const std::string &path, const std::string &name,
|
|||||||
conf.set("backend", "sqlite3");
|
conf.set("backend", "sqlite3");
|
||||||
conf.set("player_backend", "sqlite3");
|
conf.set("player_backend", "sqlite3");
|
||||||
conf.set("auth_backend", "sqlite3");
|
conf.set("auth_backend", "sqlite3");
|
||||||
|
conf.set("mod_storage_backend", "sqlite3");
|
||||||
conf.setBool("creative_mode", g_settings->getBool("creative_mode"));
|
conf.setBool("creative_mode", g_settings->getBool("creative_mode"));
|
||||||
conf.setBool("enable_damage", g_settings->getBool("enable_damage"));
|
conf.setBool("enable_damage", g_settings->getBool("enable_damage"));
|
||||||
|
|
||||||
|
@ -80,3 +80,41 @@ void Database_Dummy::listPlayers(std::vector<std::string> &res)
|
|||||||
res.emplace_back(player);
|
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 "database.h"
|
||||||
#include "irrlichttypes.h"
|
#include "irrlichttypes.h"
|
||||||
|
|
||||||
class Database_Dummy : public MapDatabase, public PlayerDatabase
|
class Database_Dummy : public MapDatabase, public PlayerDatabase, public ModMetadataDatabase
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
bool saveBlock(const v3s16 &pos, const std::string &data);
|
bool saveBlock(const v3s16 &pos, const std::string &data);
|
||||||
@ -37,10 +37,17 @@ public:
|
|||||||
bool removePlayer(const std::string &name);
|
bool removePlayer(const std::string &name);
|
||||||
void listPlayers(std::vector<std::string> &res);
|
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 beginSave() {}
|
||||||
void endSave() {}
|
void endSave() {}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::map<s64, std::string> m_database;
|
std::map<s64, std::string> m_database;
|
||||||
std::set<std::string> m_player_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 <cassert>
|
||||||
#include <json/json.h>
|
|
||||||
#include "convert_json.h"
|
#include "convert_json.h"
|
||||||
#include "database-files.h"
|
#include "database-files.h"
|
||||||
#include "remoteplayer.h"
|
#include "remoteplayer.h"
|
||||||
@ -376,3 +375,138 @@ bool AuthDatabaseFiles::writeAuthFile()
|
|||||||
}
|
}
|
||||||
return true;
|
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 "database.h"
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <json/json.h>
|
||||||
|
|
||||||
class PlayerDatabaseFiles : public PlayerDatabase
|
class PlayerDatabaseFiles : public PlayerDatabase
|
||||||
{
|
{
|
||||||
@ -69,3 +71,27 @@ private:
|
|||||||
bool readAuthFile();
|
bool readAuthFile();
|
||||||
bool writeAuthFile();
|
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);
|
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_delete_privs = nullptr;
|
||||||
sqlite3_stmt *m_stmt_last_insert_rowid = 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 "irr_v3d.h"
|
||||||
#include "irrlichttypes.h"
|
#include "irrlichttypes.h"
|
||||||
#include "util/basic_macros.h"
|
#include "util/basic_macros.h"
|
||||||
|
#include "util/string.h"
|
||||||
|
|
||||||
class Database
|
class Database
|
||||||
{
|
{
|
||||||
@ -84,3 +85,15 @@ public:
|
|||||||
virtual void listNames(std::vector<std::string> &res) = 0;
|
virtual void listNames(std::vector<std::string> &res) = 0;
|
||||||
virtual void reload() = 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;
|
||||||
|
};
|
||||||
|
@ -33,6 +33,7 @@ class EmergeManager;
|
|||||||
class Camera;
|
class Camera;
|
||||||
class ModChannel;
|
class ModChannel;
|
||||||
class ModMetadata;
|
class ModMetadata;
|
||||||
|
class ModMetadataDatabase;
|
||||||
|
|
||||||
namespace irr { namespace scene {
|
namespace irr { namespace scene {
|
||||||
class IAnimatedMesh;
|
class IAnimatedMesh;
|
||||||
@ -70,9 +71,9 @@ public:
|
|||||||
virtual const std::vector<ModSpec> &getMods() const = 0;
|
virtual const std::vector<ModSpec> &getMods() const = 0;
|
||||||
virtual const ModSpec* getModSpec(const std::string &modname) const = 0;
|
virtual const ModSpec* getModSpec(const std::string &modname) const = 0;
|
||||||
virtual std::string getWorldPath() const { return ""; }
|
virtual std::string getWorldPath() const { return ""; }
|
||||||
virtual std::string getModStoragePath() const = 0;
|
|
||||||
virtual bool registerModStorage(ModMetadata *storage) = 0;
|
virtual bool registerModStorage(ModMetadata *storage) = 0;
|
||||||
virtual void unregisterModStorage(const std::string &name) = 0;
|
virtual void unregisterModStorage(const std::string &name) = 0;
|
||||||
|
virtual ModMetadataDatabase *getModStorageDatabase() = 0;
|
||||||
|
|
||||||
virtual bool joinModChannel(const std::string &channel) = 0;
|
virtual bool joinModChannel(const std::string &channel) = 0;
|
||||||
virtual bool leaveModChannel(const std::string &channel) = 0;
|
virtual bool leaveModChannel(const std::string &channel) = 0;
|
||||||
|
@ -299,6 +299,8 @@ static void set_allowed_options(OptionList *allowed_options)
|
|||||||
_("Migrate from current players backend to another (Only works when using minetestserver or with --server)"))));
|
_("Migrate from current players backend to another (Only works when using minetestserver or with --server)"))));
|
||||||
allowed_options->insert(std::make_pair("migrate-auth", ValueSpec(VALUETYPE_STRING,
|
allowed_options->insert(std::make_pair("migrate-auth", ValueSpec(VALUETYPE_STRING,
|
||||||
_("Migrate from current auth backend to another (Only works when using minetestserver or with --server)"))));
|
_("Migrate from current auth backend to another (Only works when using minetestserver or with --server)"))));
|
||||||
|
allowed_options->insert(std::make_pair("migrate-mod-storage", ValueSpec(VALUETYPE_STRING,
|
||||||
|
_("Migrate from current mod storage backend to another (Only works when using minetestserver or with --server)"))));
|
||||||
allowed_options->insert(std::make_pair("terminal", ValueSpec(VALUETYPE_FLAG,
|
allowed_options->insert(std::make_pair("terminal", ValueSpec(VALUETYPE_FLAG,
|
||||||
_("Feature an interactive terminal (Only works when using minetestserver or with --server)"))));
|
_("Feature an interactive terminal (Only works when using minetestserver or with --server)"))));
|
||||||
allowed_options->insert(std::make_pair("recompress", ValueSpec(VALUETYPE_FLAG,
|
allowed_options->insert(std::make_pair("recompress", ValueSpec(VALUETYPE_FLAG,
|
||||||
@ -886,6 +888,9 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings &
|
|||||||
if (cmd_args.exists("migrate-auth"))
|
if (cmd_args.exists("migrate-auth"))
|
||||||
return ServerEnvironment::migrateAuthDatabase(game_params, cmd_args);
|
return ServerEnvironment::migrateAuthDatabase(game_params, cmd_args);
|
||||||
|
|
||||||
|
if (cmd_args.exists("migrate-mod-storage"))
|
||||||
|
return Server::migrateModStorageDatabase(game_params, cmd_args);
|
||||||
|
|
||||||
if (cmd_args.getFlag("recompress"))
|
if (cmd_args.getFlag("recompress"))
|
||||||
return recompress_map_database(game_params, cmd_args, bind_addr);
|
return recompress_map_database(game_params, cmd_args, bind_addr);
|
||||||
|
|
||||||
|
@ -32,19 +32,23 @@ int ModApiStorage::l_get_mod_storage(lua_State *L)
|
|||||||
|
|
||||||
std::string mod_name = readParam<std::string>(L, -1);
|
std::string mod_name = readParam<std::string>(L, -1);
|
||||||
|
|
||||||
ModMetadata *store = new ModMetadata(mod_name);
|
ModMetadata *store = nullptr;
|
||||||
|
|
||||||
if (IGameDef *gamedef = getGameDef(L)) {
|
if (IGameDef *gamedef = getGameDef(L)) {
|
||||||
store->load(gamedef->getModStoragePath());
|
store = new ModMetadata(mod_name, gamedef->getModStorageDatabase());
|
||||||
gamedef->registerModStorage(store);
|
if (gamedef->registerModStorage(store)) {
|
||||||
|
StorageRef::create(L, store);
|
||||||
|
int object = lua_gettop(L);
|
||||||
|
lua_pushvalue(L, object);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
delete store;
|
|
||||||
assert(false); // this should not happen
|
assert(false); // this should not happen
|
||||||
}
|
}
|
||||||
|
|
||||||
StorageRef::create(L, store);
|
delete store;
|
||||||
int object = lua_gettop(L);
|
|
||||||
|
|
||||||
lua_pushvalue(L, object);
|
lua_pushnil(L);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
140
src/server.cpp
140
src/server.cpp
@ -66,6 +66,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
#include "server/player_sao.h"
|
#include "server/player_sao.h"
|
||||||
#include "server/serverinventorymgr.h"
|
#include "server/serverinventorymgr.h"
|
||||||
#include "translation.h"
|
#include "translation.h"
|
||||||
|
#include "database/database-sqlite3.h"
|
||||||
|
#include "database/database-files.h"
|
||||||
|
#include "database/database-dummy.h"
|
||||||
|
#include "gameparams.h"
|
||||||
|
|
||||||
class ClientNotFoundException : public BaseException
|
class ClientNotFoundException : public BaseException
|
||||||
{
|
{
|
||||||
@ -344,10 +348,15 @@ Server::~Server()
|
|||||||
delete m_thread;
|
delete m_thread;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write any changes before deletion.
|
||||||
|
if (m_mod_storage_database)
|
||||||
|
m_mod_storage_database->endSave();
|
||||||
|
|
||||||
// Delete things in the reverse order of creation
|
// Delete things in the reverse order of creation
|
||||||
delete m_emerge;
|
delete m_emerge;
|
||||||
delete m_env;
|
delete m_env;
|
||||||
delete m_rollback;
|
delete m_rollback;
|
||||||
|
delete m_mod_storage_database;
|
||||||
delete m_banmanager;
|
delete m_banmanager;
|
||||||
delete m_itemdef;
|
delete m_itemdef;
|
||||||
delete m_nodedef;
|
delete m_nodedef;
|
||||||
@ -393,6 +402,10 @@ void Server::init()
|
|||||||
std::string ban_path = m_path_world + DIR_DELIM "ipban.txt";
|
std::string ban_path = m_path_world + DIR_DELIM "ipban.txt";
|
||||||
m_banmanager = new BanManager(ban_path);
|
m_banmanager = new BanManager(ban_path);
|
||||||
|
|
||||||
|
// Create mod storage database and begin a save for later
|
||||||
|
m_mod_storage_database = openModStorageDatabase(m_path_world);
|
||||||
|
m_mod_storage_database->beginSave();
|
||||||
|
|
||||||
m_modmgr = std::unique_ptr<ServerModManager>(new ServerModManager(m_path_world));
|
m_modmgr = std::unique_ptr<ServerModManager>(new ServerModManager(m_path_world));
|
||||||
std::vector<ModSpec> unsatisfied_mods = m_modmgr->getUnsatisfiedMods();
|
std::vector<ModSpec> unsatisfied_mods = m_modmgr->getUnsatisfiedMods();
|
||||||
// complain about mods with unsatisfied dependencies
|
// complain about mods with unsatisfied dependencies
|
||||||
@ -733,20 +746,12 @@ void Server::AsyncRunStep(bool initial_step)
|
|||||||
}
|
}
|
||||||
m_clients.unlock();
|
m_clients.unlock();
|
||||||
|
|
||||||
// Save mod storages if modified
|
// Write changes to the mod storage
|
||||||
m_mod_storage_save_timer -= dtime;
|
m_mod_storage_save_timer -= dtime;
|
||||||
if (m_mod_storage_save_timer <= 0.0f) {
|
if (m_mod_storage_save_timer <= 0.0f) {
|
||||||
m_mod_storage_save_timer = g_settings->getFloat("server_map_save_interval");
|
m_mod_storage_save_timer = g_settings->getFloat("server_map_save_interval");
|
||||||
int n = 0;
|
m_mod_storage_database->endSave();
|
||||||
for (std::unordered_map<std::string, ModMetadata *>::const_iterator
|
m_mod_storage_database->beginSave();
|
||||||
it = m_mod_storages.begin(); it != m_mod_storages.end(); ++it) {
|
|
||||||
if (it->second->isModified()) {
|
|
||||||
it->second->save(getModStoragePath());
|
|
||||||
n++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (n > 0)
|
|
||||||
infostream << "Saved " << n << " modified mod storages." << std::endl;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3689,11 +3694,6 @@ std::string Server::getBuiltinLuaPath()
|
|||||||
return porting::path_share + DIR_DELIM + "builtin";
|
return porting::path_share + DIR_DELIM + "builtin";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Server::getModStoragePath() const
|
|
||||||
{
|
|
||||||
return m_path_world + DIR_DELIM + "mod_storage";
|
|
||||||
}
|
|
||||||
|
|
||||||
v3f Server::findSpawnPos()
|
v3f Server::findSpawnPos()
|
||||||
{
|
{
|
||||||
ServerMap &map = m_env->getServerMap();
|
ServerMap &map = m_env->getServerMap();
|
||||||
@ -3857,11 +3857,8 @@ bool Server::registerModStorage(ModMetadata *storage)
|
|||||||
void Server::unregisterModStorage(const std::string &name)
|
void Server::unregisterModStorage(const std::string &name)
|
||||||
{
|
{
|
||||||
std::unordered_map<std::string, ModMetadata *>::const_iterator it = m_mod_storages.find(name);
|
std::unordered_map<std::string, ModMetadata *>::const_iterator it = m_mod_storages.find(name);
|
||||||
if (it != m_mod_storages.end()) {
|
if (it != m_mod_storages.end())
|
||||||
// Save unconditionaly on unregistration
|
|
||||||
it->second->save(getModStoragePath());
|
|
||||||
m_mod_storages.erase(name);
|
m_mod_storages.erase(name);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void dedicated_server_loop(Server &server, bool &kill)
|
void dedicated_server_loop(Server &server, bool &kill)
|
||||||
@ -3999,3 +3996,106 @@ Translations *Server::getTranslationLanguage(const std::string &lang_code)
|
|||||||
|
|
||||||
return translations;
|
return translations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ModMetadataDatabase *Server::openModStorageDatabase(const std::string &world_path)
|
||||||
|
{
|
||||||
|
std::string world_mt_path = world_path + DIR_DELIM + "world.mt";
|
||||||
|
Settings world_mt;
|
||||||
|
if (!world_mt.readConfigFile(world_mt_path.c_str()))
|
||||||
|
throw BaseException("Cannot read world.mt!");
|
||||||
|
|
||||||
|
std::string backend = world_mt.exists("mod_storage_backend") ?
|
||||||
|
world_mt.get("mod_storage_backend") : "files";
|
||||||
|
if (backend == "files")
|
||||||
|
warningstream << "/!\\ You are using the old mod storage files backend. "
|
||||||
|
<< "This backend is deprecated and may be removed in a future release /!\\"
|
||||||
|
<< std::endl << "Switching to SQLite3 is advised, "
|
||||||
|
<< "please read http://wiki.minetest.net/Database_backends." << std::endl;
|
||||||
|
|
||||||
|
return openModStorageDatabase(backend, world_path, world_mt);
|
||||||
|
}
|
||||||
|
|
||||||
|
ModMetadataDatabase *Server::openModStorageDatabase(const std::string &backend,
|
||||||
|
const std::string &world_path, const Settings &world_mt)
|
||||||
|
{
|
||||||
|
if (backend == "sqlite3")
|
||||||
|
return new ModMetadataDatabaseSQLite3(world_path);
|
||||||
|
|
||||||
|
if (backend == "files")
|
||||||
|
return new ModMetadataDatabaseFiles(world_path);
|
||||||
|
|
||||||
|
if (backend == "dummy")
|
||||||
|
return new Database_Dummy();
|
||||||
|
|
||||||
|
throw BaseException("Mod storage database backend " + backend + " not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Server::migrateModStorageDatabase(const GameParams &game_params, const Settings &cmd_args)
|
||||||
|
{
|
||||||
|
std::string migrate_to = cmd_args.get("migrate-mod-storage");
|
||||||
|
Settings world_mt;
|
||||||
|
std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
|
||||||
|
if (!world_mt.readConfigFile(world_mt_path.c_str())) {
|
||||||
|
errorstream << "Cannot read world.mt!" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string backend = world_mt.exists("mod_storage_backend") ?
|
||||||
|
world_mt.get("mod_storage_backend") : "files";
|
||||||
|
if (backend == migrate_to) {
|
||||||
|
errorstream << "Cannot migrate: new backend is same"
|
||||||
|
<< " as the old one" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModMetadataDatabase *srcdb = nullptr;
|
||||||
|
ModMetadataDatabase *dstdb = nullptr;
|
||||||
|
|
||||||
|
bool succeeded = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
srcdb = Server::openModStorageDatabase(backend, game_params.world_path, world_mt);
|
||||||
|
dstdb = Server::openModStorageDatabase(migrate_to, game_params.world_path, world_mt);
|
||||||
|
|
||||||
|
dstdb->beginSave();
|
||||||
|
|
||||||
|
std::vector<std::string> mod_list;
|
||||||
|
srcdb->listMods(&mod_list);
|
||||||
|
for (const std::string &modname : mod_list) {
|
||||||
|
StringMap meta;
|
||||||
|
srcdb->getModEntries(modname, &meta);
|
||||||
|
for (const auto &pair : meta) {
|
||||||
|
dstdb->setModEntry(modname, pair.first, pair.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dstdb->endSave();
|
||||||
|
|
||||||
|
succeeded = true;
|
||||||
|
|
||||||
|
actionstream << "Successfully migrated the metadata of "
|
||||||
|
<< mod_list.size() << " mods" << std::endl;
|
||||||
|
world_mt.set("mod_storage_backend", migrate_to);
|
||||||
|
if (!world_mt.updateConfigFile(world_mt_path.c_str()))
|
||||||
|
errorstream << "Failed to update world.mt!" << std::endl;
|
||||||
|
else
|
||||||
|
actionstream << "world.mt updated" << std::endl;
|
||||||
|
|
||||||
|
} catch (BaseException &e) {
|
||||||
|
errorstream << "An error occurred during migration: " << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete srcdb;
|
||||||
|
delete dstdb;
|
||||||
|
|
||||||
|
if (succeeded && backend == "files") {
|
||||||
|
// Back up files
|
||||||
|
const std::string storage_path = game_params.world_path + DIR_DELIM + "mod_storage";
|
||||||
|
const std::string backup_path = game_params.world_path + DIR_DELIM + "mod_storage.bak";
|
||||||
|
if (!fs::Rename(storage_path, backup_path))
|
||||||
|
warningstream << "After migration, " << storage_path
|
||||||
|
<< " could not be renamed to " << backup_path << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return succeeded;
|
||||||
|
}
|
||||||
|
11
src/server.h
11
src/server.h
@ -283,6 +283,7 @@ public:
|
|||||||
virtual u16 allocateUnknownNodeId(const std::string &name);
|
virtual u16 allocateUnknownNodeId(const std::string &name);
|
||||||
IRollbackManager *getRollbackManager() { return m_rollback; }
|
IRollbackManager *getRollbackManager() { return m_rollback; }
|
||||||
virtual EmergeManager *getEmergeManager() { return m_emerge; }
|
virtual EmergeManager *getEmergeManager() { return m_emerge; }
|
||||||
|
virtual ModMetadataDatabase *getModStorageDatabase() { return m_mod_storage_database; }
|
||||||
|
|
||||||
IWritableItemDefManager* getWritableItemDefManager();
|
IWritableItemDefManager* getWritableItemDefManager();
|
||||||
NodeDefManager* getWritableNodeDefManager();
|
NodeDefManager* getWritableNodeDefManager();
|
||||||
@ -293,7 +294,6 @@ public:
|
|||||||
void getModNames(std::vector<std::string> &modlist);
|
void getModNames(std::vector<std::string> &modlist);
|
||||||
std::string getBuiltinLuaPath();
|
std::string getBuiltinLuaPath();
|
||||||
virtual std::string getWorldPath() const { return m_path_world; }
|
virtual std::string getWorldPath() const { return m_path_world; }
|
||||||
virtual std::string getModStoragePath() const;
|
|
||||||
|
|
||||||
inline bool isSingleplayer()
|
inline bool isSingleplayer()
|
||||||
{ return m_simple_singleplayer_mode; }
|
{ return m_simple_singleplayer_mode; }
|
||||||
@ -377,6 +377,14 @@ public:
|
|||||||
// Get or load translations for a language
|
// Get or load translations for a language
|
||||||
Translations *getTranslationLanguage(const std::string &lang_code);
|
Translations *getTranslationLanguage(const std::string &lang_code);
|
||||||
|
|
||||||
|
static ModMetadataDatabase *openModStorageDatabase(const std::string &world_path);
|
||||||
|
|
||||||
|
static ModMetadataDatabase *openModStorageDatabase(const std::string &backend,
|
||||||
|
const std::string &world_path, const Settings &world_mt);
|
||||||
|
|
||||||
|
static bool migrateModStorageDatabase(const GameParams &game_params,
|
||||||
|
const Settings &cmd_args);
|
||||||
|
|
||||||
// Bind address
|
// Bind address
|
||||||
Address m_bind_addr;
|
Address m_bind_addr;
|
||||||
|
|
||||||
@ -678,6 +686,7 @@ private:
|
|||||||
s32 nextSoundId();
|
s32 nextSoundId();
|
||||||
|
|
||||||
std::unordered_map<std::string, ModMetadata *> m_mod_storages;
|
std::unordered_map<std::string, ModMetadata *> m_mod_storages;
|
||||||
|
ModMetadataDatabase *m_mod_storage_database = nullptr;
|
||||||
float m_mod_storage_save_timer = 10.0f;
|
float m_mod_storage_save_timer = 10.0f;
|
||||||
|
|
||||||
// CSM restrictions byteflag
|
// CSM restrictions byteflag
|
||||||
|
@ -14,6 +14,7 @@ set (UNITTEST_SRCS
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/test_map_settings_manager.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test_map_settings_manager.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test_mapnode.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test_mapnode.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test_modchannels.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test_modchannels.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test_modmetadatadatabase.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test_nodedef.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test_nodedef.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test_noderesolver.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test_noderesolver.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test_noise.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test_noise.cpp
|
||||||
|
@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
#include "gamedef.h"
|
#include "gamedef.h"
|
||||||
#include "modchannels.h"
|
#include "modchannels.h"
|
||||||
#include "content/mods.h"
|
#include "content/mods.h"
|
||||||
|
#include "database/database-dummy.h"
|
||||||
#include "util/numeric.h"
|
#include "util/numeric.h"
|
||||||
#include "porting.h"
|
#include "porting.h"
|
||||||
|
|
||||||
@ -55,6 +56,7 @@ public:
|
|||||||
scene::ISceneManager *getSceneManager() { return m_scenemgr; }
|
scene::ISceneManager *getSceneManager() { return m_scenemgr; }
|
||||||
IRollbackManager *getRollbackManager() { return m_rollbackmgr; }
|
IRollbackManager *getRollbackManager() { return m_rollbackmgr; }
|
||||||
EmergeManager *getEmergeManager() { return m_emergemgr; }
|
EmergeManager *getEmergeManager() { return m_emergemgr; }
|
||||||
|
ModMetadataDatabase *getModStorageDatabase() { return m_mod_storage_database; }
|
||||||
|
|
||||||
scene::IAnimatedMesh *getMesh(const std::string &filename) { return NULL; }
|
scene::IAnimatedMesh *getMesh(const std::string &filename) { return NULL; }
|
||||||
bool checkLocalPrivilege(const std::string &priv) { return false; }
|
bool checkLocalPrivilege(const std::string &priv) { return false; }
|
||||||
@ -68,7 +70,6 @@ public:
|
|||||||
return testmodspec;
|
return testmodspec;
|
||||||
}
|
}
|
||||||
virtual const ModSpec* getModSpec(const std::string &modname) const { return NULL; }
|
virtual const ModSpec* getModSpec(const std::string &modname) const { return NULL; }
|
||||||
virtual std::string getModStoragePath() const { return "."; }
|
|
||||||
virtual bool registerModStorage(ModMetadata *meta) { return true; }
|
virtual bool registerModStorage(ModMetadata *meta) { return true; }
|
||||||
virtual void unregisterModStorage(const std::string &name) {}
|
virtual void unregisterModStorage(const std::string &name) {}
|
||||||
bool joinModChannel(const std::string &channel);
|
bool joinModChannel(const std::string &channel);
|
||||||
@ -89,11 +90,13 @@ private:
|
|||||||
scene::ISceneManager *m_scenemgr = nullptr;
|
scene::ISceneManager *m_scenemgr = nullptr;
|
||||||
IRollbackManager *m_rollbackmgr = nullptr;
|
IRollbackManager *m_rollbackmgr = nullptr;
|
||||||
EmergeManager *m_emergemgr = nullptr;
|
EmergeManager *m_emergemgr = nullptr;
|
||||||
|
ModMetadataDatabase *m_mod_storage_database = nullptr;
|
||||||
std::unique_ptr<ModChannelMgr> m_modchannel_mgr;
|
std::unique_ptr<ModChannelMgr> m_modchannel_mgr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
TestGameDef::TestGameDef() :
|
TestGameDef::TestGameDef() :
|
||||||
|
m_mod_storage_database(new Database_Dummy()),
|
||||||
m_modchannel_mgr(new ModChannelMgr())
|
m_modchannel_mgr(new ModChannelMgr())
|
||||||
{
|
{
|
||||||
m_itemdef = createItemDefManager();
|
m_itemdef = createItemDefManager();
|
||||||
@ -107,6 +110,7 @@ TestGameDef::~TestGameDef()
|
|||||||
{
|
{
|
||||||
delete m_itemdef;
|
delete m_itemdef;
|
||||||
delete m_nodedef;
|
delete m_nodedef;
|
||||||
|
delete m_mod_storage_database;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
253
src/unittest/test_modmetadatadatabase.cpp
Normal file
253
src/unittest/test_modmetadatadatabase.cpp
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
/*
|
||||||
|
Minetest
|
||||||
|
Copyright (C) 2018 bendeutsch, Ben Deutsch <ben@bendeutsch.de>
|
||||||
|
Copyright (C) 2021 TurkeyMcMac, Jude Melton-Houghton <jwmhjwmh@gmail.com>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This file is an edited copy of test_authdatabase.cpp
|
||||||
|
|
||||||
|
#include "test.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include "database/database-files.h"
|
||||||
|
#include "database/database-sqlite3.h"
|
||||||
|
#include "filesys.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// Anonymous namespace to create classes that are only
|
||||||
|
// visible to this file
|
||||||
|
//
|
||||||
|
// These are helpers that return a *ModMetadataDatabase and
|
||||||
|
// allow us to run the same tests on different databases and
|
||||||
|
// database acquisition strategies.
|
||||||
|
|
||||||
|
class ModMetadataDatabaseProvider
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~ModMetadataDatabaseProvider() = default;
|
||||||
|
virtual ModMetadataDatabase *getModMetadataDatabase() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FixedProvider : public ModMetadataDatabaseProvider
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FixedProvider(ModMetadataDatabase *mod_meta_db) : mod_meta_db(mod_meta_db){};
|
||||||
|
virtual ~FixedProvider(){};
|
||||||
|
virtual ModMetadataDatabase *getModMetadataDatabase() { return mod_meta_db; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
ModMetadataDatabase *mod_meta_db;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FilesProvider : public ModMetadataDatabaseProvider
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FilesProvider(const std::string &dir) : dir(dir){};
|
||||||
|
virtual ~FilesProvider()
|
||||||
|
{
|
||||||
|
if (mod_meta_db)
|
||||||
|
mod_meta_db->endSave();
|
||||||
|
delete mod_meta_db;
|
||||||
|
}
|
||||||
|
virtual ModMetadataDatabase *getModMetadataDatabase()
|
||||||
|
{
|
||||||
|
if (mod_meta_db)
|
||||||
|
mod_meta_db->endSave();
|
||||||
|
delete mod_meta_db;
|
||||||
|
mod_meta_db = new ModMetadataDatabaseFiles(dir);
|
||||||
|
mod_meta_db->beginSave();
|
||||||
|
return mod_meta_db;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string dir;
|
||||||
|
ModMetadataDatabase *mod_meta_db = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SQLite3Provider : public ModMetadataDatabaseProvider
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SQLite3Provider(const std::string &dir) : dir(dir){};
|
||||||
|
virtual ~SQLite3Provider()
|
||||||
|
{
|
||||||
|
if (mod_meta_db)
|
||||||
|
mod_meta_db->endSave();
|
||||||
|
delete mod_meta_db;
|
||||||
|
}
|
||||||
|
virtual ModMetadataDatabase *getModMetadataDatabase()
|
||||||
|
{
|
||||||
|
if (mod_meta_db)
|
||||||
|
mod_meta_db->endSave();
|
||||||
|
delete mod_meta_db;
|
||||||
|
mod_meta_db = new ModMetadataDatabaseSQLite3(dir);
|
||||||
|
mod_meta_db->beginSave();
|
||||||
|
return mod_meta_db;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string dir;
|
||||||
|
ModMetadataDatabase *mod_meta_db = nullptr;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestModMetadataDatabase : public TestBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TestModMetadataDatabase() { TestManager::registerTestModule(this); }
|
||||||
|
const char *getName() { return "TestModMetadataDatabase"; }
|
||||||
|
|
||||||
|
void runTests(IGameDef *gamedef);
|
||||||
|
void runTestsForCurrentDB();
|
||||||
|
|
||||||
|
void testRecallFail();
|
||||||
|
void testCreate();
|
||||||
|
void testRecall();
|
||||||
|
void testChange();
|
||||||
|
void testRecallChanged();
|
||||||
|
void testListMods();
|
||||||
|
void testRemove();
|
||||||
|
|
||||||
|
private:
|
||||||
|
ModMetadataDatabaseProvider *mod_meta_provider;
|
||||||
|
};
|
||||||
|
|
||||||
|
static TestModMetadataDatabase g_test_instance;
|
||||||
|
|
||||||
|
void TestModMetadataDatabase::runTests(IGameDef *gamedef)
|
||||||
|
{
|
||||||
|
// fixed directory, for persistence
|
||||||
|
thread_local const std::string test_dir = getTestTempDirectory();
|
||||||
|
|
||||||
|
// Each set of tests is run twice for each database type:
|
||||||
|
// one where we reuse the same ModMetadataDatabase object (to test local caching),
|
||||||
|
// and one where we create a new ModMetadataDatabase object for each call
|
||||||
|
// (to test actual persistence).
|
||||||
|
|
||||||
|
rawstream << "-------- Files database (same object)" << std::endl;
|
||||||
|
|
||||||
|
ModMetadataDatabase *mod_meta_db = new ModMetadataDatabaseFiles(test_dir);
|
||||||
|
mod_meta_provider = new FixedProvider(mod_meta_db);
|
||||||
|
|
||||||
|
runTestsForCurrentDB();
|
||||||
|
|
||||||
|
delete mod_meta_db;
|
||||||
|
delete mod_meta_provider;
|
||||||
|
|
||||||
|
// reset database
|
||||||
|
fs::RecursiveDelete(test_dir + DIR_DELIM + "mod_storage");
|
||||||
|
|
||||||
|
rawstream << "-------- Files database (new objects)" << std::endl;
|
||||||
|
|
||||||
|
mod_meta_provider = new FilesProvider(test_dir);
|
||||||
|
|
||||||
|
runTestsForCurrentDB();
|
||||||
|
|
||||||
|
delete mod_meta_provider;
|
||||||
|
|
||||||
|
rawstream << "-------- SQLite3 database (same object)" << std::endl;
|
||||||
|
|
||||||
|
mod_meta_db = new ModMetadataDatabaseSQLite3(test_dir);
|
||||||
|
mod_meta_provider = new FixedProvider(mod_meta_db);
|
||||||
|
|
||||||
|
runTestsForCurrentDB();
|
||||||
|
|
||||||
|
delete mod_meta_db;
|
||||||
|
delete mod_meta_provider;
|
||||||
|
|
||||||
|
// reset database
|
||||||
|
fs::DeleteSingleFileOrEmptyDirectory(test_dir + DIR_DELIM + "mod_storage.sqlite");
|
||||||
|
|
||||||
|
rawstream << "-------- SQLite3 database (new objects)" << std::endl;
|
||||||
|
|
||||||
|
mod_meta_provider = new SQLite3Provider(test_dir);
|
||||||
|
|
||||||
|
runTestsForCurrentDB();
|
||||||
|
|
||||||
|
delete mod_meta_provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
void TestModMetadataDatabase::runTestsForCurrentDB()
|
||||||
|
{
|
||||||
|
TEST(testRecallFail);
|
||||||
|
TEST(testCreate);
|
||||||
|
TEST(testRecall);
|
||||||
|
TEST(testChange);
|
||||||
|
TEST(testRecallChanged);
|
||||||
|
TEST(testListMods);
|
||||||
|
TEST(testRemove);
|
||||||
|
TEST(testRecallFail);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestModMetadataDatabase::testRecallFail()
|
||||||
|
{
|
||||||
|
ModMetadataDatabase *mod_meta_db = mod_meta_provider->getModMetadataDatabase();
|
||||||
|
StringMap recalled;
|
||||||
|
mod_meta_db->getModEntries("mod1", &recalled);
|
||||||
|
UASSERT(recalled.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestModMetadataDatabase::testCreate()
|
||||||
|
{
|
||||||
|
ModMetadataDatabase *mod_meta_db = mod_meta_provider->getModMetadataDatabase();
|
||||||
|
StringMap recalled;
|
||||||
|
UASSERT(mod_meta_db->setModEntry("mod1", "key1", "value1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestModMetadataDatabase::testRecall()
|
||||||
|
{
|
||||||
|
ModMetadataDatabase *mod_meta_db = mod_meta_provider->getModMetadataDatabase();
|
||||||
|
StringMap recalled;
|
||||||
|
mod_meta_db->getModEntries("mod1", &recalled);
|
||||||
|
UASSERT(recalled.size() == 1);
|
||||||
|
UASSERT(recalled["key1"] == "value1");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestModMetadataDatabase::testChange()
|
||||||
|
{
|
||||||
|
ModMetadataDatabase *mod_meta_db = mod_meta_provider->getModMetadataDatabase();
|
||||||
|
StringMap recalled;
|
||||||
|
UASSERT(mod_meta_db->setModEntry("mod1", "key1", "value2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestModMetadataDatabase::testRecallChanged()
|
||||||
|
{
|
||||||
|
ModMetadataDatabase *mod_meta_db = mod_meta_provider->getModMetadataDatabase();
|
||||||
|
StringMap recalled;
|
||||||
|
mod_meta_db->getModEntries("mod1", &recalled);
|
||||||
|
UASSERT(recalled.size() == 1);
|
||||||
|
UASSERT(recalled["key1"] == "value2");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestModMetadataDatabase::testListMods()
|
||||||
|
{
|
||||||
|
ModMetadataDatabase *mod_meta_db = mod_meta_provider->getModMetadataDatabase();
|
||||||
|
UASSERT(mod_meta_db->setModEntry("mod2", "key1", "value1"));
|
||||||
|
std::vector<std::string> mod_list;
|
||||||
|
mod_meta_db->listMods(&mod_list);
|
||||||
|
UASSERT(mod_list.size() == 2);
|
||||||
|
UASSERT(std::find(mod_list.cbegin(), mod_list.cend(), "mod1") != mod_list.cend());
|
||||||
|
UASSERT(std::find(mod_list.cbegin(), mod_list.cend(), "mod2") != mod_list.cend());
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestModMetadataDatabase::testRemove()
|
||||||
|
{
|
||||||
|
ModMetadataDatabase *mod_meta_db = mod_meta_provider->getModMetadataDatabase();
|
||||||
|
UASSERT(mod_meta_db->removeModEntry("mod1", "key1"));
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user