#include "hosts_db.h" #include #include namespace pex { static const char* k_schema = R"SQL( CREATE TABLE IF NOT EXISTS hosts ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, url TEXT NOT NULL, token TEXT NOT NULL DEFAULT '', os TEXT NOT NULL DEFAULT '', last_seen_unix INTEGER NOT NULL DEFAULT 0, is_local INTEGER NOT NULL DEFAULT 0 ); )SQL"; HostsDb::HostsDb() = default; HostsDb::~HostsDb() { close(); } bool HostsDb::open(const std::string& path) { if (sqlite3_open(path.c_str(), &db_) != SQLITE_OK) { db_ = nullptr; return false; } char* err = nullptr; if (sqlite3_exec(db_, k_schema, nullptr, nullptr, &err) != SQLITE_OK) { sqlite3_free(err); close(); return false; } return true; } void HostsDb::close() { if (db_) { sqlite3_close(db_); db_ = nullptr; } } std::vector HostsDb::list() { std::vector out; if (!db_) return out; const char* sql = "SELECT id, name, url, token, os, last_seen_unix, is_local FROM hosts ORDER BY name"; sqlite3_stmt* st = nullptr; if (sqlite3_prepare_v2(db_, sql, -1, &st, nullptr) != SQLITE_OK) return out; while (sqlite3_step(st) == SQLITE_ROW) { Host h; h.id = sqlite3_column_int64(st, 0); h.name = reinterpret_cast(sqlite3_column_text(st, 1)); h.url = reinterpret_cast(sqlite3_column_text(st, 2)); h.token = reinterpret_cast(sqlite3_column_text(st, 3)); h.os = reinterpret_cast(sqlite3_column_text(st, 4)); h.last_seen_unix = sqlite3_column_int64(st, 5); h.is_local = sqlite3_column_int(st, 6) != 0; out.push_back(std::move(h)); } sqlite3_finalize(st); return out; } int64_t HostsDb::upsert(const Host& h) { if (!db_) return -1; const char* sql = "INSERT INTO hosts(name,url,token,os,last_seen_unix,is_local) VALUES(?,?,?,?,?,?) " "ON CONFLICT(name) DO UPDATE SET url=excluded.url, token=excluded.token, " "os=excluded.os, last_seen_unix=excluded.last_seen_unix, is_local=excluded.is_local"; sqlite3_stmt* st = nullptr; if (sqlite3_prepare_v2(db_, sql, -1, &st, nullptr) != SQLITE_OK) return -1; sqlite3_bind_text(st, 1, h.name.c_str(), -1, SQLITE_TRANSIENT); sqlite3_bind_text(st, 2, h.url.c_str(), -1, SQLITE_TRANSIENT); sqlite3_bind_text(st, 3, h.token.c_str(), -1, SQLITE_TRANSIENT); sqlite3_bind_text(st, 4, h.os.c_str(), -1, SQLITE_TRANSIENT); sqlite3_bind_int64(st, 5, h.last_seen_unix); sqlite3_bind_int(st, 6, h.is_local ? 1 : 0); int rc = sqlite3_step(st); sqlite3_finalize(st); if (rc != SQLITE_DONE) return -1; return sqlite3_last_insert_rowid(db_); } bool HostsDb::remove(int64_t id) { if (!db_) return false; sqlite3_stmt* st = nullptr; if (sqlite3_prepare_v2(db_, "DELETE FROM hosts WHERE id=?", -1, &st, nullptr) != SQLITE_OK) return false; sqlite3_bind_int64(st, 1, id); bool ok = sqlite3_step(st) == SQLITE_DONE; sqlite3_finalize(st); return ok; } bool HostsDb::touch_last_seen(int64_t id, int64_t unix_ts) { if (!db_) return false; sqlite3_stmt* st = nullptr; if (sqlite3_prepare_v2(db_, "UPDATE hosts SET last_seen_unix=? WHERE id=?", -1, &st, nullptr) != SQLITE_OK) return false; sqlite3_bind_int64(st, 1, unix_ts); sqlite3_bind_int64(st, 2, id); bool ok = sqlite3_step(st) == SQLITE_DONE; sqlite3_finalize(st); return ok; } } // namespace pex