aboutsummaryrefslogtreecommitdiff
path: root/mapman/src/library.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mapman/src/library.cpp')
-rw-r--r--mapman/src/library.cpp329
1 files changed, 329 insertions, 0 deletions
diff --git a/mapman/src/library.cpp b/mapman/src/library.cpp
new file mode 100644
index 0000000..2361441
--- /dev/null
+++ b/mapman/src/library.cpp
@@ -0,0 +1,329 @@
+#include "library.hpp"
+#include "mapdump.hpp"
+
+#include <cstring>
+#include <filesystem>
+#include <sqlite3.h>
+
+const int MAPDB_VERSION = 1;
+
+map_library::map_library() : db(nullptr) {}
+
+map_library::~map_library()
+{
+ if (db)
+ sqlite3_close(db);
+}
+
+std::vector<int> map_library::map_ids() const
+{
+ std::vector<int> ret;
+ sqlite3_stmt *st = nullptr;
+ sqlite3_prepare_v2(db, "select id from maps;", -1, &st, 0);
+ while (1)
+ {
+ int r = sqlite3_step(st);
+ if (r != SQLITE_ROW) break;
+ ret.push_back(sqlite3_column_int(st, 0));
+ }
+ sqlite3_finalize(st);
+ return ret;
+}
+
+bool map_library::has_map(int id) const
+{
+ sqlite3_stmt *st = nullptr;
+ sqlite3_prepare_v2(db, "select id from maps where id = ?;", -1, &st, 0);
+ sqlite3_bind_int(st, 1, id);
+ int r = sqlite3_step(st);
+ sqlite3_finalize(st);
+ return r == SQLITE_ROW;
+}
+
+void map_library::set_map(const map_t &map)
+{
+ sqlite3_stmt *st = nullptr;
+ if (has_map(map.id))
+ {
+ sqlite3_prepare_v2(db, "update maps set custom_name = ?, data = ? where id = ?;", -1, &st, 0);
+ sqlite3_bind_text(st, 1, map.custom_name.c_str(), map.custom_name.length(), SQLITE_STATIC);
+ sqlite3_bind_blob(st, 2, map.map_data.data(), map.map_data.size(), SQLITE_STATIC);
+ sqlite3_bind_int(st, 3, map.id);
+ sqlite3_step(st);
+ }
+ else
+ {
+ sqlite3_prepare_v2(db, "insert into maps (id, custom_name, data) values(?, ?, ?);", -1, &st, 0);
+ sqlite3_bind_int(st, 1, map.id);
+ sqlite3_bind_text(st, 2, map.custom_name.c_str(), map.custom_name.length(), SQLITE_STATIC);
+ sqlite3_bind_blob(st, 3, map.map_data.data(), map.map_data.size(), SQLITE_STATIC);
+ sqlite3_step(st);
+ }
+ sqlite3_finalize(st);
+}
+
+map_t map_library::get_map(int id) const
+{
+ sqlite3_stmt *st = nullptr;
+ sqlite3_prepare_v2(db, "select custom_name, data from maps where id = ?;", -1, &st, 0);
+ sqlite3_bind_int(st, 1, id);
+ map_t ret{id, std::string(), map_data_t()};
+ if (sqlite3_step(st) == SQLITE_ROW)
+ {
+ ret.custom_name = std::string((char*)sqlite3_column_text(st, 0));
+ memcpy(ret.map_data.data(), sqlite3_column_blob(st, 1), ret.map_data.size());
+ }
+ sqlite3_finalize(st);
+ return ret;
+}
+
+std::vector<int64_t> map_library::groups() const
+{
+ std::vector<int64_t> ret;
+ sqlite3_stmt *st = nullptr;
+ sqlite3_prepare_v2(db, "select rowid from groups;", -1, &st, 0);
+ while (1)
+ {
+ int r = sqlite3_step(st);
+ if (r != SQLITE_ROW) break;
+ ret.push_back(sqlite3_column_int64(st, 0));
+ }
+ sqlite3_finalize(st);
+ return ret;
+}
+
+int64_t map_library::new_group(const map_group_t &g)
+{
+ sqlite3_stmt *st = nullptr;
+ sqlite3_prepare_v2(db, "insert into groups (title, author, horizontal_count, vertical_count) values(?, ?, ?, ?);", -1, &st, 0);
+ sqlite3_bind_text(st, 1, g.title.c_str(), g.title.length(), SQLITE_STATIC);
+ sqlite3_bind_text(st, 2, g.author.c_str(), g.author.length(), SQLITE_STATIC);
+ sqlite3_bind_int(st, 3, g.hc);
+ sqlite3_bind_int(st, 4, g.vc);
+ sqlite3_step(st);
+ sqlite3_finalize(st);
+ int64_t id = sqlite3_last_insert_rowid(db);
+ sqlite3_prepare_v2(db, "insert into group_maps (gid, pos, map_id) values(?, ?, ?);", -1, &st, 0);
+ sqlite3_bind_int64(st, 1, id);
+ for (size_t i = 0; i < g.ids.size(); ++i)
+ {
+ if (!g.populated[i])
+ continue;
+ sqlite3_bind_int(st, 2, i);
+ sqlite3_bind_int(st, 3, g.ids[i]);
+ sqlite3_step(st);
+ if (i + 1 < g.ids.size())
+ sqlite3_reset(st);
+ }
+ sqlite3_finalize(st);
+ return id;
+}
+
+bool map_library::has_group(int64_t gid) const
+{
+ sqlite3_stmt *st = nullptr;
+ sqlite3_prepare_v2(db, "select rowid from groups where rowid = ?;", -1, &st, 0);
+ sqlite3_bind_int(st, 1, gid);
+ int r = sqlite3_step(st);
+ sqlite3_finalize(st);
+ return r == SQLITE_ROW;
+}
+
+void map_library::set_group(int64_t gid, const map_group_t &g)
+{
+ if (!has_group(gid))
+ return;
+ sqlite3_stmt *st = nullptr;
+ sqlite3_prepare_v2(db, R"sql(
+ update groups set title = ?, author = ?, horizontal_count = ?, vertical_count = ? where rowid = ?;
+ )sql", -1, &st, 0);
+ sqlite3_bind_text(st, 1, g.title.c_str(), g.title.length(), SQLITE_STATIC);
+ sqlite3_bind_text(st, 2, g.author.c_str(), g.author.length(), SQLITE_STATIC);
+ sqlite3_bind_int(st, 3, g.hc);
+ sqlite3_bind_int(st, 4, g.vc);
+ sqlite3_bind_int64(st, 5, gid);
+ sqlite3_step(st);
+ sqlite3_finalize(st);
+ sqlite3_prepare_v2(db, R"sql(
+ delete from group_maps where gid = ?;
+ )sql", -1, &st, 0);
+ sqlite3_bind_int64(st, 1, gid);
+ sqlite3_step(st);
+ sqlite3_finalize(st);
+ sqlite3_prepare_v2(db, "insert into group_maps (gid, pos, map_id) values(?, ?, ?);", -1, &st, 0);
+ sqlite3_bind_int(st, 1, gid);
+ for (size_t i = 0; i < g.ids.size(); ++i)
+ {
+ if (!g.populated[i])
+ continue;
+ sqlite3_bind_int(st, 2, i);
+ sqlite3_bind_int(st, 3, g.ids[i]);
+ sqlite3_step(st);
+ if (i + 1 < g.ids.size())
+ sqlite3_reset(st);
+ }
+ sqlite3_finalize(st);
+}
+
+map_group_t map_library::get_group(int64_t gid)
+{
+ map_group_t ret{};
+ if (!has_group(gid))
+ return ret;
+ sqlite3_stmt *st = nullptr;
+ sqlite3_prepare_v2(db, "select title, author, horizontal_count, vertical_count from groups where rowid = ?;", -1, &st, 0);
+ sqlite3_bind_int64(st, 1, gid);
+ if (sqlite3_step(st) == SQLITE_ROW)
+ {
+ ret.title = std::string((char*)sqlite3_column_text(st, 0));
+ ret.author = std::string((char*)sqlite3_column_text(st, 1));
+ ret.hc = sqlite3_column_int(st, 2);
+ ret.vc = sqlite3_column_int(st, 3);
+ }
+ sqlite3_finalize(st);
+ ret.ids.resize(ret.hc * ret.vc);
+ ret.populated.resize(ret.hc * ret.vc, false);
+ sqlite3_prepare_v2(db, "select pos, map_id from group_maps where gid = ?;", -1, &st, 0);
+ sqlite3_bind_int(st, 1, gid);
+ while (sqlite3_step(st) == SQLITE_ROW)
+ {
+ int pos = sqlite3_column_int(st, 0);
+ int id = sqlite3_column_int(st, 1);
+ if (pos < ret.hc * ret.vc)
+ {
+ ret.ids[pos] = id;
+ ret.populated[pos] = true;
+ }
+ }
+ sqlite3_finalize(st);
+ return ret;
+}
+
+void map_library::remove_group(int64_t gid) const
+{
+ if (!has_group(gid))
+ return;
+ sqlite3_stmt *st = nullptr;
+ sqlite3_prepare_v2(db, R"sql(
+ delete from groups where rowid = ?;
+ delete from group_maps where gid = ?1;
+ )sql", -1, &st, 0);
+ sqlite3_bind_int64(st, 1, gid);
+ sqlite3_step(st);
+ sqlite3_finalize(st);
+}
+
+/*
+ * tally (in): list of maps in storage containers
+ * a_b (out): maps that are in the library but not in containers
+ * b_a (out): maps that are in containers but not in the library
+ */
+void map_library::tally_diff(const std::vector<int> &tally,
+ std::vector<int> &a_b,
+ std::vector<int> &b_a) const
+{
+ sqlite3_exec(db, "create table temp_tally(id int);", nullptr, nullptr, nullptr);
+ sqlite3_stmt *st = nullptr;
+ sqlite3_prepare_v2(db, "insert into temp_tally (id) values(?);", -1, &st, 0);
+ for (auto &id : tally)
+ {
+ sqlite3_bind_int(st, 1, id);
+ sqlite3_step(st);
+ sqlite3_reset(st);
+ }
+ sqlite3_finalize(st);
+ sqlite3_prepare_v2(db, R"sql(
+ select maps.id as mid, temp_tally.id as tid
+ from maps full outer join temp_tally on maps.id = temp_tally.id
+ where mid is NULL or tid is NULL;
+ )sql", -1, &st, 0);
+ a_b.clear();
+ b_a.clear();
+ while (sqlite3_step(st) == SQLITE_ROW)
+ {
+ int mid = sqlite3_column_int(st, 0);
+ int tid = sqlite3_column_int(st, 1);
+ bool mid_null = sqlite3_column_type(st, 0) == SQLITE_NULL;
+ bool tid_null = sqlite3_column_type(st, 1) == SQLITE_NULL;
+ if (mid_null)
+ b_a.push_back(tid);
+ if (tid_null)
+ a_b.push_back(mid);
+ }
+ sqlite3_finalize(st);
+ sqlite3_exec(db, "drop table temp_tally;", nullptr, nullptr, nullptr);
+ std::sort(a_b.begin(), a_b.end());
+ std::sort(b_a.begin(), b_a.end());
+}
+
+
+bool map_library::open_db(const std::filesystem::path &p)
+{
+ bool needs_creation = !std::filesystem::is_regular_file(p);
+#if PATH_VALSIZE == 2
+ sqlite3_open16(p.c_str(), &db);
+#else
+ sqlite3_open(p.c_str(), &db);
+#endif
+ if (needs_creation)
+ init_db();
+ else
+ {
+ if (!verify_db())
+ {
+ sqlite3_close(db);
+ db = nullptr;
+ return false;
+ }
+ }
+ return true;
+}
+
+void map_library::init_db()
+{
+ sqlite3_exec(db, R"sql(
+ create table mapdbinfo(
+ version int,
+ name text
+ );
+ )sql", nullptr, nullptr, nullptr);
+
+ sqlite3_stmt *vst;
+ sqlite3_prepare_v2(db, "insert into mapdbinfo (version) values(?);", -1, &vst, 0);
+ sqlite3_bind_int(vst, 1, MAPDB_VERSION);
+ sqlite3_step(vst);
+ sqlite3_finalize(vst);
+
+ sqlite3_exec(db, R"sql(
+ create table maps(
+ id integer primary key,
+ custom_name text,
+ data blob
+ );
+ )sql", nullptr, nullptr, nullptr);
+ sqlite3_exec(db, R"sql(
+ create table groups(
+ title text,
+ author text,
+ horizontal_count integer,
+ vertical_count integer
+ );
+ )sql", nullptr, nullptr, nullptr);
+ sqlite3_exec(db, R"sql(
+ create table group_maps(
+ gid integer,
+ pos integer,
+ map_id integer
+ );
+ )sql", nullptr, nullptr, nullptr);
+}
+
+bool map_library::verify_db()
+{
+ sqlite3_stmt *vst;
+ sqlite3_prepare_v2(db, "select version from mapdbinfo;", -1, &vst, 0);
+ if (sqlite3_step(vst) != SQLITE_ROW) {sqlite3_finalize(vst); return false;}
+ if (MAPDB_VERSION != sqlite3_column_int(vst, 0)) {sqlite3_finalize(vst); return false;}
+ sqlite3_finalize(vst);
+ return true;
+}