#include "library.hpp" #include "mapdump.hpp" #include #include #include const int MAPDB_VERSION = 2; 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 = ?, locked = ?, data = ? where id = ?;", -1, &st, 0); sqlite3_bind_text(st, 1, map.custom_name.c_str(), map.custom_name.length(), SQLITE_STATIC); sqlite3_bind_int(st, 2, map.locked); sqlite3_bind_blob(st, 3, map.map_data.data(), map.map_data.size(), SQLITE_STATIC); sqlite3_bind_int(st, 4, map.id); sqlite3_step(st); } else { sqlite3_prepare_v2(db, "insert into maps (id, custom_name, locked, 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_int(st, 3, map.locked); sqlite3_bind_blob(st, 4, 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, locked, data from maps where id = ?;", -1, &st, 0); sqlite3_bind_int(st, 1, id); map_t ret{id, std::string(), false, map_data_t()}; if (sqlite3_step(st) == SQLITE_ROW) { ret.custom_name = std::string((char*)sqlite3_column_text(st, 0)); ret.locked = sqlite3_column_int(st, 1) ? true : false; memcpy(ret.map_data.data(), sqlite3_column_blob(st, 2), 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, locked integer, 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; }