#include "BookmarkStore.h" #include #include std::string BookmarkStore::filePath(const std::string& cachePath) { return cachePath + "/bookmarks.bin"; } std::vector BookmarkStore::load(const std::string& cachePath) { std::vector bookmarks; FsFile f; if (!Storage.openFileForRead("BKM", filePath(cachePath), f)) { return bookmarks; } // File format v2: [version(1)] [count(2)] [entries...] // Each entry: [spine(2)] [page(2)] [snippetLen(1)] [snippet(snippetLen)] // v1 (no version byte): [count(2)] [entries of 4 bytes each] // We detect v1 by checking if the first byte could be a version marker (0xFF). uint8_t firstByte; if (f.read(&firstByte, 1) != 1) { f.close(); return bookmarks; } uint16_t count; bool hasSnippets; if (firstByte == 0xFF) { // v2 format: version marker was 0xFF hasSnippets = true; uint8_t countBytes[2]; if (f.read(countBytes, 2) != 2) { f.close(); return bookmarks; } count = static_cast(countBytes[0]) | (static_cast(countBytes[1]) << 8); } else { // v1 format: first byte was part of the count hasSnippets = false; uint8_t secondByte; if (f.read(&secondByte, 1) != 1) { f.close(); return bookmarks; } count = static_cast(firstByte) | (static_cast(secondByte) << 8); } if (count > MAX_BOOKMARKS) { count = MAX_BOOKMARKS; } for (uint16_t i = 0; i < count; i++) { uint8_t entry[4]; if (f.read(entry, 4) != 4) break; Bookmark b; b.spineIndex = static_cast(static_cast(entry[0]) | (static_cast(entry[1]) << 8)); b.pageNumber = static_cast(static_cast(entry[2]) | (static_cast(entry[3]) << 8)); if (hasSnippets) { uint8_t snippetLen; if (f.read(&snippetLen, 1) != 1) break; if (snippetLen > 0) { std::vector buf(snippetLen); if (f.read(buf.data(), snippetLen) != snippetLen) break; b.snippet = std::string(buf.begin(), buf.end()); } } bookmarks.push_back(b); } f.close(); return bookmarks; } bool BookmarkStore::save(const std::string& cachePath, const std::vector& bookmarks) { FsFile f; if (!Storage.openFileForWrite("BKM", filePath(cachePath), f)) { Serial.printf("[%lu] [BKM] Could not save bookmarks!\n", millis()); return false; } // Write v2 format: version marker + count + entries with snippets uint8_t version = 0xFF; f.write(&version, 1); uint16_t count = static_cast(bookmarks.size()); uint8_t header[2] = {static_cast(count & 0xFF), static_cast((count >> 8) & 0xFF)}; f.write(header, 2); for (const auto& b : bookmarks) { uint8_t entry[4]; entry[0] = static_cast(b.spineIndex & 0xFF); entry[1] = static_cast((b.spineIndex >> 8) & 0xFF); entry[2] = static_cast(b.pageNumber & 0xFF); entry[3] = static_cast((b.pageNumber >> 8) & 0xFF); f.write(entry, 4); // Write snippet: length byte + string data uint8_t snippetLen = static_cast(std::min(static_cast(b.snippet.size()), MAX_SNIPPET_LENGTH)); f.write(&snippetLen, 1); if (snippetLen > 0) { f.write(reinterpret_cast(b.snippet.c_str()), snippetLen); } } f.close(); Serial.printf("[%lu] [BKM] Saved %d bookmarks\n", millis(), count); return true; } bool BookmarkStore::addBookmark(const std::string& cachePath, int spineIndex, int page, const std::string& snippet) { auto bookmarks = load(cachePath); // Check for duplicate for (const auto& b : bookmarks) { if (b.spineIndex == spineIndex && b.pageNumber == page) { return true; // Already bookmarked } } if (static_cast(bookmarks.size()) >= MAX_BOOKMARKS) { return false; } Bookmark b; b.spineIndex = static_cast(spineIndex); b.pageNumber = static_cast(page); b.snippet = snippet.substr(0, MAX_SNIPPET_LENGTH); bookmarks.push_back(b); return save(cachePath, bookmarks); } bool BookmarkStore::removeBookmark(const std::string& cachePath, int spineIndex, int page) { auto bookmarks = load(cachePath); auto it = std::remove_if(bookmarks.begin(), bookmarks.end(), [spineIndex, page](const Bookmark& b) { return b.spineIndex == spineIndex && b.pageNumber == page; }); if (it == bookmarks.end()) { return false; // Not found } bookmarks.erase(it, bookmarks.end()); return save(cachePath, bookmarks); } bool BookmarkStore::hasBookmark(const std::string& cachePath, int spineIndex, int page) { auto bookmarks = load(cachePath); return std::any_of(bookmarks.begin(), bookmarks.end(), [spineIndex, page](const Bookmark& b) { return b.spineIndex == spineIndex && b.pageNumber == page; }); }