DatabaseManager.cpp 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. #include "DatabaseManager.hpp"
  2. #include <stdexcept>
  3. #include <chrono>
  4. #include <cstring> // std::strlen
  5. #include <sys/stat.h>
  6. namespace {
  7. inline std::int64_t file_size(const std::string& p) {
  8. struct stat st{};
  9. return ::stat(p.c_str(), &st) == 0 ? static_cast<std::int64_t>(st.st_size) : 0;
  10. }
  11. inline std::int64_t file_mtime(const std::string& p) {
  12. struct stat st{};
  13. return ::stat(p.c_str(), &st) == 0 ? static_cast<std::int64_t>(st.st_mtim.tv_sec) : 0;
  14. }
  15. }
  16. DatabaseManager::DatabaseManager(std::string db_path)
  17. : db_path_(std::move(db_path)) {
  18. if (sqlite3_open(db_path_.c_str(), &db_) != SQLITE_OK) {
  19. throw std::runtime_error(std::string("sqlite open failed: ") + sqlite3_errmsg(db_));
  20. }
  21. sqlite3_busy_handler(db_, &DatabaseManager::busy_handler, this);
  22. ensure_schema();
  23. }
  24. DatabaseManager::~DatabaseManager() {
  25. // if (stmt_upsert_) sqlite3_finalize(stmt_upsert_);
  26. // …
  27. if (db_) sqlite3_close(db_);
  28. }
  29. int DatabaseManager::busy_handler(void*, int prev_ms) {
  30. // simple backoff up to ~1s total
  31. int sleep_ms = std::min(prev_ms + 50, 200);
  32. sqlite3_sleep(sleep_ms);
  33. return 1; // retry
  34. }
  35. void DatabaseManager::ensure_schema() {
  36. std::lock_guard lk(mtx_);
  37. static const char* ddl =
  38. "BEGIN;"
  39. "CREATE TABLE IF NOT EXISTS books ("
  40. " id TEXT PRIMARY KEY,"
  41. " title TEXT NOT NULL,"
  42. " author TEXT NOT NULL DEFAULT '',"
  43. " file_path TEXT NOT NULL,"
  44. " cover_path TEXT NOT NULL DEFAULT '',"
  45. " size_bytes INTEGER NOT NULL DEFAULT 0,"
  46. " mtime_unix INTEGER NOT NULL DEFAULT 0,"
  47. " added_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))"
  48. ");"
  49. "CREATE INDEX IF NOT EXISTS idx_books_title ON books(title);"
  50. "COMMIT;";
  51. char* err = nullptr;
  52. if (sqlite3_exec(db_, ddl, nullptr, nullptr, &err) != SQLITE_OK) {
  53. std::string msg = err ? err : "unknown sqlite error";
  54. sqlite3_free(err);
  55. throw std::runtime_error("schema failed: " + msg);
  56. }
  57. }
  58. bool DatabaseManager::upsert_book(const Book& b) {
  59. std::lock_guard lk(mtx_);
  60. static const char* sql =
  61. "INSERT INTO books(id,title,author,file_path,cover_path,size_bytes,mtime_unix)"
  62. " VALUES(?,?,?,?,?,?,?)"
  63. " ON CONFLICT(id) DO UPDATE SET"
  64. " title=excluded.title,"
  65. " author=excluded.author,"
  66. " file_path=excluded.file_path,"
  67. " cover_path=excluded.cover_path,"
  68. " size_bytes=excluded.size_bytes,"
  69. " mtime_unix=excluded.mtime_unix;";
  70. sqlite3_stmt* st = nullptr;
  71. if (sqlite3_prepare_v2(db_, sql, -1, &st, nullptr) != SQLITE_OK) return false;
  72. sqlite3_bind_text(st, 1, b.id().c_str(), -1, SQLITE_TRANSIENT);
  73. sqlite3_bind_text(st, 2, b.title().c_str(), -1, SQLITE_TRANSIENT);
  74. sqlite3_bind_text(st, 3, b.author().c_str(), -1, SQLITE_TRANSIENT);
  75. sqlite3_bind_text(st, 4, b.filePath().c_str(), -1, SQLITE_TRANSIENT);
  76. sqlite3_bind_text(st, 5, b.coverPath().c_str(), -1, SQLITE_TRANSIENT);
  77. sqlite3_bind_int64(st, 6, file_size(b.filePath()));
  78. sqlite3_bind_int64(st, 7, file_mtime(b.filePath()));
  79. bool ok = (sqlite3_step(st) == SQLITE_DONE);
  80. sqlite3_finalize(st);
  81. return ok;
  82. }
  83. bool DatabaseManager::remove_book(const std::string& id) {
  84. std::lock_guard lk(mtx_);
  85. static const char* sql = "DELETE FROM books WHERE id = ?;";
  86. sqlite3_stmt* st = nullptr;
  87. if (sqlite3_prepare_v2(db_, sql, -1, &st, nullptr) != SQLITE_OK) return false;
  88. sqlite3_bind_text(st, 1, id.c_str(), -1, SQLITE_TRANSIENT);
  89. bool ok = (sqlite3_step(st) == SQLITE_DONE);
  90. sqlite3_finalize(st);
  91. return ok;
  92. }
  93. std::optional<Book> DatabaseManager::get_book(const std::string& id) const {
  94. std::lock_guard lk(mtx_);
  95. static const char* sql =
  96. "SELECT id,title,author,file_path,cover_path FROM books WHERE id = ?;";
  97. sqlite3_stmt* st = nullptr;
  98. if (sqlite3_prepare_v2(db_, sql, -1, &st, nullptr) != SQLITE_OK) return std::nullopt;
  99. sqlite3_bind_text(st, 1, id.c_str(), -1, SQLITE_TRANSIENT);
  100. std::optional<Book> out;
  101. if (sqlite3_step(st) == SQLITE_ROW) {
  102. Book b(
  103. reinterpret_cast<const char*>(sqlite3_column_text(st,0)),
  104. reinterpret_cast<const char*>(sqlite3_column_text(st,1)),
  105. reinterpret_cast<const char*>(sqlite3_column_text(st,2)),
  106. reinterpret_cast<const char*>(sqlite3_column_text(st,3)),
  107. reinterpret_cast<const char*>(sqlite3_column_text(st,4))
  108. );
  109. out = std::move(b);
  110. }
  111. sqlite3_finalize(st);
  112. return out;
  113. }
  114. std::vector<Book> DatabaseManager::load_all_books() const {
  115. std::lock_guard lk(mtx_);
  116. static const char* sql =
  117. "SELECT id,title,author,file_path,cover_path FROM books ORDER BY title,author,id;";
  118. sqlite3_stmt* st = nullptr;
  119. std::vector<Book> rows;
  120. if (sqlite3_prepare_v2(db_, sql, -1, &st, nullptr) != SQLITE_OK) return rows;
  121. while (sqlite3_step(st) == SQLITE_ROW) {
  122. rows.emplace_back(
  123. reinterpret_cast<const char*>(sqlite3_column_text(st,0)),
  124. reinterpret_cast<const char*>(sqlite3_column_text(st,1)),
  125. reinterpret_cast<const char*>(sqlite3_column_text(st,2)),
  126. reinterpret_cast<const char*>(sqlite3_column_text(st,3)),
  127. reinterpret_cast<const char*>(sqlite3_column_text(st,4))
  128. );
  129. }
  130. sqlite3_finalize(st);
  131. return rows;
  132. }