## Summary Reduce/fix the lag on the home screen before recent book covers are rendered ## Additional Context We were previously rendering the screen in two steps, delaying the recent book covers render to avoid a lag before the screen loads. In this PR, we are now doing that only if at least one book doesn't have the cover thumbnail generated yet. If all thumbs are already generated, we load and display them right away, with no lag. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**NO **_
164 lines
5.3 KiB
C++
164 lines
5.3 KiB
C++
#include "RecentBooksStore.h"
|
|
|
|
#include <Epub.h>
|
|
#include <HardwareSerial.h>
|
|
#include <SDCardManager.h>
|
|
#include <Serialization.h>
|
|
#include <Xtc.h>
|
|
|
|
#include <algorithm>
|
|
|
|
#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();
|
|
}
|
|
|
|
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::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<uint8_t>(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;
|
|
}
|