| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 |
- #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;
- }
|