From 93cf929f29dea490ed60e5300cacdd99886c988e Mon Sep 17 00:00:00 2001 From: Chris Xiong Date: Sat, 9 Sep 2023 20:09:50 -0400 Subject: Add the standalone portion of mapman. --- mapman/src/library.cpp | 329 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 329 insertions(+) create mode 100644 mapman/src/library.cpp (limited to 'mapman/src/library.cpp') 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 +#include +#include + +const int MAPDB_VERSION = 1; + +map_library::map_library() : db(nullptr) {} + +map_library::~map_library() +{ + if (db) + sqlite3_close(db); +} + +std::vector map_library::map_ids() const +{ + std::vector 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 map_library::groups() const +{ + std::vector 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 &tally, + std::vector &a_b, + std::vector &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; +} -- cgit v1.2.3