Files
Crosspoint/src/RecentBooksStore.cpp
T

141 lines
5.0 KiB
C++

#include "RecentBooksStore.h"
#include <Epub.h>
#include <FsHelpers.h>
#include <HalStorage.h>
#include <Logging.h>
#include <Xtc.h>
#include <algorithm>
#include <iterator>
void RecentBooksStore::toJson(JsonDocument& doc) const {
JsonArray arr = doc["books"].to<JsonArray>();
for (const auto& book : recentBooks) {
JsonObject obj = arr.add<JsonObject>();
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<JsonArrayConst>();
recentBooks.reserve(std::min(arr.size(), static_cast<size_t>(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, "", "", ""};
}