#include "RecentBooksStore.h" #include #include #include #include #include #include #include "util/StringUtils.h" namespace { constexpr uint8_t RECENT_BOOKS_FILE_VERSION = 3; constexpr char RECENT_BOOKS_FILE[] = "/.crosspoint/recent.bin"; constexpr int MAX_RECENT_BOOKS = 10; } // namespace RecentBooksStore RecentBooksStore::instance; void RecentBooksStore::addBook(const std::string& path, const std::string& title, const std::string& author, const std::string& coverBmpPath) { // 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(); } bool RecentBooksStore::saveToFile() const { // Make sure the directory exists SdMan.mkdir("/.crosspoint"); FsFile outputFile; if (!SdMan.openFileForWrite("RBS", RECENT_BOOKS_FILE, outputFile)) { return false; } serialization::writePod(outputFile, RECENT_BOOKS_FILE_VERSION); const uint8_t count = static_cast(recentBooks.size()); serialization::writePod(outputFile, count); for (const auto& book : recentBooks) { serialization::writeString(outputFile, book.path); serialization::writeString(outputFile, book.title); serialization::writeString(outputFile, book.author); serialization::writeString(outputFile, book.coverBmpPath); } outputFile.close(); Serial.printf("[%lu] [RBS] Recent books saved to file (%d entries)\n", millis(), count); return true; } 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); } Serial.printf("Loading recent book: %s\n", path.c_str()); // If epub, try to load the metadata for title/author and cover if (StringUtils::checkFileExtension(lastBookFileName, ".epub")) { Epub epub(path, "/.crosspoint"); epub.load(false); return RecentBook{path, epub.getTitle(), epub.getAuthor(), epub.getThumbBmpPath()}; } else if (StringUtils::checkFileExtension(lastBookFileName, ".xtch") || StringUtils::checkFileExtension(lastBookFileName, ".xtc")) { // Handle XTC file Xtc xtc(path, "/.crosspoint"); if (xtc.load()) { return RecentBook{path, xtc.getTitle(), xtc.getAuthor(), xtc.getThumbBmpPath()}; } } else if (StringUtils::checkFileExtension(lastBookFileName, ".txt") || StringUtils::checkFileExtension(lastBookFileName, ".md")) { return RecentBook{path, lastBookFileName, "", ""}; } return RecentBook{path, "", "", ""}; } bool RecentBooksStore::loadFromFile() { FsFile inputFile; if (!SdMan.openFileForRead("RBS", RECENT_BOOKS_FILE, inputFile)) { return false; } uint8_t version; serialization::readPod(inputFile, version); if (version != RECENT_BOOKS_FILE_VERSION) { if (version == 1 || version == 2) { // Old version, just read paths uint8_t count; serialization::readPod(inputFile, count); recentBooks.clear(); recentBooks.reserve(count); for (uint8_t i = 0; i < count; i++) { std::string path; serialization::readString(inputFile, path); // load book to get missing data RecentBook book = getDataFromBook(path); if (book.title.empty() && book.author.empty() && version == 2) { // Fall back to loading what we can from the store std::string title, author; serialization::readString(inputFile, title); serialization::readString(inputFile, author); recentBooks.push_back({path, title, author, ""}); } else { recentBooks.push_back(book); } } } else { Serial.printf("[%lu] [RBS] Deserialization failed: Unknown version %u\n", millis(), version); inputFile.close(); return false; } } else { uint8_t count; serialization::readPod(inputFile, count); recentBooks.clear(); recentBooks.reserve(count); for (uint8_t i = 0; i < count; i++) { std::string path, title, author, coverBmpPath; serialization::readString(inputFile, path); serialization::readString(inputFile, title); serialization::readString(inputFile, author); serialization::readString(inputFile, coverBmpPath); recentBooks.push_back({path, title, author, coverBmpPath}); } } inputFile.close(); Serial.printf("[%lu] [RBS] Recent books loaded from file (%d entries)\n", millis(), recentBooks.size()); return true; }