Przeglądaj źródła

adding database manager

Bernardo Magri 3 miesięcy temu
rodzic
commit
b41cb68916
2 zmienionych plików z 181 dodań i 0 usunięć
  1. 146 0
      src/DatabaseManager.cpp
  2. 35 0
      src/DatabaseManager.hpp

+ 146 - 0
src/DatabaseManager.cpp

@@ -0,0 +1,146 @@
+#include "DatabaseManager.hpp"
+#include <stdexcept>
+#include <chrono>
+#include <cstring>   // std::strlen
+#include <sys/stat.h>
+
+namespace {
+  inline std::int64_t file_size(const std::string& p) {
+    struct stat st{};
+    return ::stat(p.c_str(), &st) == 0 ? static_cast<std::int64_t>(st.st_size) : 0;
+  }
+  inline std::int64_t file_mtime(const std::string& p) {
+    struct stat st{};
+    return ::stat(p.c_str(), &st) == 0 ? static_cast<std::int64_t>(st.st_mtim.tv_sec) : 0;
+  }
+}
+
+DatabaseManager::DatabaseManager(std::string db_path)
+  : db_path_(std::move(db_path)) {
+  if (sqlite3_open(db_path_.c_str(), &db_) != SQLITE_OK) {
+    throw std::runtime_error(std::string("sqlite open failed: ") + sqlite3_errmsg(db_));
+  }
+  sqlite3_busy_handler(db_, &DatabaseManager::busy_handler, this);
+  ensure_schema();
+}
+
+DatabaseManager::~DatabaseManager() {
+  // if (stmt_upsert_) sqlite3_finalize(stmt_upsert_);
+  // …
+  if (db_) sqlite3_close(db_);
+}
+
+int DatabaseManager::busy_handler(void*, int prev_ms) {
+  // simple backoff up to ~1s total
+  int sleep_ms = std::min(prev_ms + 50, 200);
+  sqlite3_sleep(sleep_ms);
+  return 1; // retry
+}
+
+void DatabaseManager::ensure_schema() {
+  std::lock_guard lk(mtx_);
+  static const char* ddl =
+    "BEGIN;"
+    "CREATE TABLE IF NOT EXISTS books ("
+    " id TEXT PRIMARY KEY,"
+    " title TEXT NOT NULL,"
+    " author TEXT NOT NULL DEFAULT '',"
+    " file_path TEXT NOT NULL,"
+    " cover_path TEXT NOT NULL DEFAULT '',"
+    " size_bytes INTEGER NOT NULL DEFAULT 0,"
+    " mtime_unix INTEGER NOT NULL DEFAULT 0,"
+    " added_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))"
+    ");"
+    "CREATE INDEX IF NOT EXISTS idx_books_title ON books(title);"
+    "COMMIT;";
+  char* err = nullptr;
+  if (sqlite3_exec(db_, ddl, nullptr, nullptr, &err) != SQLITE_OK) {
+    std::string msg = err ? err : "unknown sqlite error";
+    sqlite3_free(err);
+    throw std::runtime_error("schema failed: " + msg);
+  }
+}
+
+bool DatabaseManager::upsert_book(const Book& b) {
+  std::lock_guard lk(mtx_);
+  static const char* sql =
+    "INSERT INTO books(id,title,author,file_path,cover_path,size_bytes,mtime_unix)"
+    " VALUES(?,?,?,?,?,?,?)"
+    " ON CONFLICT(id) DO UPDATE SET"
+    "  title=excluded.title,"
+    "  author=excluded.author,"
+    "  file_path=excluded.file_path,"
+    "  cover_path=excluded.cover_path,"
+    "  size_bytes=excluded.size_bytes,"
+    "  mtime_unix=excluded.mtime_unix;";
+  sqlite3_stmt* st = nullptr;
+  if (sqlite3_prepare_v2(db_, sql, -1, &st, nullptr) != SQLITE_OK) return false;
+
+  sqlite3_bind_text(st, 1, b.id().c_str(),        -1, SQLITE_TRANSIENT);
+  sqlite3_bind_text(st, 2, b.title().c_str(),     -1, SQLITE_TRANSIENT);
+  sqlite3_bind_text(st, 3, b.author().c_str(),    -1, SQLITE_TRANSIENT);
+  sqlite3_bind_text(st, 4, b.filePath().c_str(),  -1, SQLITE_TRANSIENT);
+  sqlite3_bind_text(st, 5, b.coverPath().c_str(), -1, SQLITE_TRANSIENT);
+  sqlite3_bind_int64(st, 6, file_size(b.filePath()));
+  sqlite3_bind_int64(st, 7, file_mtime(b.filePath()));
+
+  bool ok = (sqlite3_step(st) == SQLITE_DONE);
+  sqlite3_finalize(st);
+  return ok;
+}
+
+bool DatabaseManager::remove_book(const std::string& id) {
+  std::lock_guard lk(mtx_);
+  static const char* sql = "DELETE FROM books WHERE id = ?;";
+  sqlite3_stmt* st = nullptr;
+  if (sqlite3_prepare_v2(db_, sql, -1, &st, nullptr) != SQLITE_OK) return false;
+  sqlite3_bind_text(st, 1, id.c_str(), -1, SQLITE_TRANSIENT);
+  bool ok = (sqlite3_step(st) == SQLITE_DONE);
+  sqlite3_finalize(st);
+  return ok;
+}
+
+std::optional<Book> DatabaseManager::get_book(const std::string& id) const {
+  std::lock_guard lk(mtx_);
+  static const char* sql =
+    "SELECT id,title,author,file_path,cover_path FROM books WHERE id = ?;";
+  sqlite3_stmt* st = nullptr;
+  if (sqlite3_prepare_v2(db_, sql, -1, &st, nullptr) != SQLITE_OK) return std::nullopt;
+  sqlite3_bind_text(st, 1, id.c_str(), -1, SQLITE_TRANSIENT);
+
+  std::optional<Book> out;
+  if (sqlite3_step(st) == SQLITE_ROW) {
+    Book b(
+      reinterpret_cast<const char*>(sqlite3_column_text(st,0)),
+      reinterpret_cast<const char*>(sqlite3_column_text(st,1)),
+      reinterpret_cast<const char*>(sqlite3_column_text(st,2)),
+      reinterpret_cast<const char*>(sqlite3_column_text(st,3)),
+      reinterpret_cast<const char*>(sqlite3_column_text(st,4))
+    );
+    out = std::move(b);
+  }
+  sqlite3_finalize(st);
+  return out;
+}
+
+std::vector<Book> DatabaseManager::load_all_books() const {
+  std::lock_guard lk(mtx_);
+  static const char* sql =
+    "SELECT id,title,author,file_path,cover_path FROM books ORDER BY title,author,id;";
+  sqlite3_stmt* st = nullptr;
+  std::vector<Book> rows;
+
+  if (sqlite3_prepare_v2(db_, sql, -1, &st, nullptr) != SQLITE_OK) return rows;
+
+  while (sqlite3_step(st) == SQLITE_ROW) {
+    rows.emplace_back(
+      reinterpret_cast<const char*>(sqlite3_column_text(st,0)),
+      reinterpret_cast<const char*>(sqlite3_column_text(st,1)),
+      reinterpret_cast<const char*>(sqlite3_column_text(st,2)),
+      reinterpret_cast<const char*>(sqlite3_column_text(st,3)),
+      reinterpret_cast<const char*>(sqlite3_column_text(st,4))
+    );
+  }
+  sqlite3_finalize(st);
+  return rows;
+}

+ 35 - 0
src/DatabaseManager.hpp

@@ -0,0 +1,35 @@
+#pragma once
+#include "Book.hpp"
+#include <sqlite3.h>
+#include <string>
+#include <vector>
+#include <mutex>
+#include <optional>
+
+class DatabaseManager {
+public:
+  explicit DatabaseManager(std::string db_path);
+  ~DatabaseManager();
+
+  // non-copyable, movable if you want (omitted for brevity)
+  DatabaseManager(const DatabaseManager&) = delete;
+  DatabaseManager& operator=(const DatabaseManager&) = delete;
+
+  // schema
+  void ensure_schema();
+
+  // CRUD
+  bool upsert_book(const Book& b);             // insert or replace by id
+  bool remove_book(const std::string& id);
+  std::optional<Book> get_book(const std::string& id) const;
+  std::vector<Book>   load_all_books() const;
+
+private:
+  static int busy_handler(void* /*opaque*/, int prev_sleep_ms);
+  void prepare_statements();                   // optional: cache prepared stmts
+
+  std::string db_path_;
+  mutable std::mutex mtx_;
+  sqlite3* db_ = nullptr;
+
+};