#include "RecentBooksStore.h" #include #include #include #include #include #include #include void RecentBooksStore::toJson(JsonDocument& doc) const { JsonArray arr = doc["books"].to(); for (const auto& book : recentBooks) { JsonObject obj = arr.add(); obj["path"] = book.path; obj["title"] = book.title; obj["author"] = book.author; obj["coverBmpPath"] = book.coverBmpPath; } } bool RecentBooksStore::fromJson(JsonVariantConst doc) { // Tolerate a missing/invalid 'books' key (treat as empty list); only a // JSON parse error is fatal. A null JsonArray iterates zero times. recentBooks.clear(); JsonArrayConst arr = doc["books"].as(); recentBooks.reserve(std::min(arr.size(), static_cast(MAX_RECENT_BOOKS))); for (JsonObjectConst obj : arr) { if (getCount() >= MAX_RECENT_BOOKS) break; RecentBook book; book.path = obj["path"] | ""; book.title = obj["title"] | ""; book.author = obj["author"] | ""; book.coverBmpPath = obj["coverBmpPath"] | ""; recentBooks.push_back(book); } LOG_DBG("RBS", "Recent books loaded from file (%d entries)", getCount()); return true; } void RecentBooksStore::addBook(const std::string& path, const std::string& title, const std::string& author, const std::string& coverBmpPath) { // Drop stale entries first so a new add can't evict a valid book in their stead. pruneMissing(); // Remove existing entry if present auto it = std::find_if(recentBooks.begin(), recentBooks.end(), [&](const RecentBook& book) { return book.path == path; }); if (it != recentBooks.end()) { recentBooks.erase(it); } // Add to front recentBooks.insert(recentBooks.begin(), {path, title, author, coverBmpPath}); // Trim to max size if (recentBooks.size() > MAX_RECENT_BOOKS) { recentBooks.resize(MAX_RECENT_BOOKS); } saveToFile(); } void RecentBooksStore::updateBook(const std::string& path, const std::string& title, const std::string& author, const std::string& coverBmpPath) { auto it = std::find_if(recentBooks.begin(), recentBooks.end(), [&](const RecentBook& book) { return book.path == path; }); if (it != recentBooks.end()) { RecentBook& book = *it; book.title = title; book.author = author; book.coverBmpPath = coverBmpPath; saveToFile(); } } bool RecentBooksStore::removeByPath(const std::string& path) { auto it = std::find_if(recentBooks.begin(), recentBooks.end(), [&](const RecentBook& book) { return book.path == path; }); if (it == recentBooks.end()) { return false; } recentBooks.erase(it); if (!saveToFile()) { LOG_ERR("RBS", "Failed to persist removal of recent book: %s", path.c_str()); } return true; } void RecentBooksStore::updatePath(const std::string& oldPath, const std::string& newPath, const std::string& oldCachePath, const std::string& newCachePath) { auto it = std::find_if(recentBooks.begin(), recentBooks.end(), [&](const RecentBook& book) { return book.path == oldPath; }); if (it == recentBooks.end()) { return; } it->path = newPath; if (!oldCachePath.empty() && !it->coverBmpPath.empty() && it->coverBmpPath.rfind(oldCachePath, 0) == 0) { it->coverBmpPath = newCachePath + it->coverBmpPath.substr(oldCachePath.size()); } saveToFile(); } bool RecentBooksStore::isMissing(const RecentBook& book) { return !Storage.exists(book.path.c_str()); } bool RecentBooksStore::pruneMissing() { const size_t before = recentBooks.size(); recentBooks.erase(std::remove_if(recentBooks.begin(), recentBooks.end(), &isMissing), recentBooks.end()); return recentBooks.size() != before; } RecentBook RecentBooksStore::getDataFromBook(std::string path) const { std::string lastBookFileName = ""; const size_t lastSlash = path.find_last_of('/'); if (lastSlash != std::string::npos) { lastBookFileName = path.substr(lastSlash + 1); } LOG_DBG("RBS", "Loading recent book: %s", path.c_str()); // If epub, try to load the metadata for title/author and cover. // Use buildIfMissing=false to avoid heavy epub loading on boot; getTitle()/getAuthor() may be // blank until the book is opened, and entries with missing title are omitted from recent list. if (FsHelpers::hasEpubExtension(lastBookFileName)) { Epub epub(path, "/.crosspoint"); epub.load(false, true); return RecentBook{path, epub.getTitle(), epub.getAuthor(), epub.getThumbBmpPath()}; } else if (FsHelpers::hasXtcExtension(lastBookFileName)) { // Handle XTC file Xtc xtc(path, "/.crosspoint"); if (xtc.load()) { return RecentBook{path, xtc.getTitle(), xtc.getAuthor(), xtc.getThumbBmpPath()}; } } else if (FsHelpers::hasTxtExtension(lastBookFileName) || FsHelpers::hasMarkdownExtension(lastBookFileName)) { return RecentBook{path, lastBookFileName, "", ""}; } return RecentBook{path, "", "", ""}; }