aboutsummaryrefslogblamecommitdiff
path: root/mapman/src/library.cpp
blob: 4a51272a0fdcc3b34ff1f66de0217ec13a56adc7 (plain) (tree)
1
2
3
4
5
6
7
8






                      
                            






































                                                                            
                                                                                                                  
                                                                                                   


                                                                                          



                         
                                                                                                                   

                                                                                                   

                                                                                          







                                        
                                                                                                   
                                
                                                      


                                                                         

                                                                                     

































































































































































































































                                                                                                                                   
                           




























                                                                                           
#include "library.hpp"
#include "mapdump.hpp"

#include <cstring>
#include <filesystem>
#include <sqlite3.h>

const int MAPDB_VERSION = 2;

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 = ?, 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<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,
            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;
}