Compare commits

...

3 Commits

Author SHA1 Message Date
CaptainFrito
7ba522ed9e
Merge 9572fc3fb2 into f67c544e16 2026-02-02 22:26:39 +07:00
CaptainFrito
9572fc3fb2 Fixed recent books migration 2026-02-02 22:26:23 +07:00
Aaron Cunliffe
f67c544e16
fix: webserver folder creation regex change (#653)
Some checks failed
CI / build (push) Has been cancelled
## Summary

Resolves #562 

Implements regex change to support valid characters discussed by
@daveallie in issue
[here](https://github.com/crosspoint-reader/crosspoint-reader/issues/562#issuecomment-3830809156).

Also rejects `.` and `..` as folder names which are invalid in FAT32 and
exFAT filesystems

## Additional Context
- Unsure on the wording for the alert, it feels overly explicit, but
that might be a good thing. Happy to change.

---

### 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? _**< PARTIALLY >**_
2026-02-02 21:27:02 +11:00
5 changed files with 61 additions and 36 deletions

View File

@ -153,7 +153,7 @@ Click **File Manager** to access file management features.
1. Click the **+ Add** button in the top-right corner
2. Select **New Folder** from the dropdown menu
3. Enter a folder name (letters, numbers, underscores, and hyphens only)
3. Enter a folder name (must not contain characters \" * : < > ? / \\ | and must not be . or ..)
4. Click **Create Folder**
This is useful for organizing your ebooks by genre, author, or series.

View File

@ -79,7 +79,8 @@ bool RecentBooksStore::loadFromFile() {
serialization::readString(inputFile, path);
// Title and author will be empty, they will be filled when the book is
// opened again
recentBooks.push_back({path, "", "", ""});
std::string fileName = path.substr(path.rfind('/') + 1);
recentBooks.push_back({path, fileName, "", "-"});
}
} else if (version == 2) {
// Old version, just read paths, titles and authors
@ -94,7 +95,7 @@ bool RecentBooksStore::loadFromFile() {
serialization::readString(inputFile, author);
// Title and author will be empty, they will be filled when the book is
// opened again
recentBooks.push_back({path, title, author, ""});
recentBooks.push_back({path, title, author, "-"});
}
} else {
Serial.printf("[%lu] [RBS] Deserialization failed: Unknown version %u\n", millis(), version);

View File

@ -44,8 +44,11 @@ void HomeActivity::loadRecentBooks(int maxBooks, int coverHeight) {
const auto& books = RECENT_BOOKS.getBooks();
recentBooks.reserve(std::min(static_cast<int>(books.size()), maxBooks));
bool mustReloadRecents = false; // We're migrating from an older version of recent books
int progress = 0;
for (const RecentBook& book : books) {
bool mustLoadCover = false;
// Limit to maximum number of recent books
if (recentBooks.size() >= maxBooks) {
break;
@ -56,51 +59,70 @@ void HomeActivity::loadRecentBooks(int maxBooks, int coverHeight) {
continue;
}
if (!book.coverBmpPath.empty()) {
if (book.coverBmpPath == "-") {
mustReloadRecents = true;
} else {
std::string coverPath = UITheme::getCoverThumbPath(book.coverBmpPath, coverHeight);
if (!SdMan.exists(coverPath.c_str())) {
std::string lastBookFileName = "";
const size_t lastSlash = book.path.find_last_of('/');
if (lastSlash != std::string::npos) {
lastBookFileName = book.path.substr(lastSlash + 1);
mustLoadCover = true;
}
}
if (mustReloadRecents || mustLoadCover) {
std::string lastBookFileName = "";
const size_t lastSlash = book.path.find_last_of('/');
if (lastSlash != std::string::npos) {
lastBookFileName = book.path.substr(lastSlash + 1);
}
Serial.printf("Loading recent book: %s\n", book.path.c_str());
// If epub, try to load the metadata for title/author and cover
if (StringUtils::checkFileExtension(lastBookFileName, ".epub")) {
Epub epub(book.path, "/.crosspoint");
epub.load(false);
// Try to generate thumbnail image for Continue Reading card
if (!showingLoading) {
showingLoading = true;
popupRect = GUI.drawPopup(renderer, "Loading...");
}
GUI.fillPopupProgress(renderer, popupRect, progress * (100 / maxBooks));
epub.generateThumbBmp(coverHeight);
Serial.printf("Loading recent book: %s\n", book.path.c_str());
// If epub, try to load the metadata for title/author and cover
if (StringUtils::checkFileExtension(lastBookFileName, ".epub")) {
Epub epub(book.path, "/.crosspoint");
epub.load(false);
if (mustReloadRecents) {
// Update recent book entry with title/author/cover path
RECENT_BOOKS.addBook(book.path, epub.getTitle(), epub.getAuthor(), epub.getThumbBmpPath());
recentBooks.push_back({book.path, epub.getTitle(), epub.getAuthor(), epub.getThumbBmpPath()});
}
} else if (StringUtils::checkFileExtension(lastBookFileName, ".xtch") ||
StringUtils::checkFileExtension(lastBookFileName, ".xtc")) {
// Handle XTC file
Xtc xtc(book.path, "/.crosspoint");
if (xtc.load()) {
// Try to generate thumbnail image for Continue Reading card
if (!showingLoading) {
showingLoading = true;
popupRect = GUI.drawPopup(renderer, "Loading...");
}
GUI.fillPopupProgress(renderer, popupRect, progress * 30);
epub.generateThumbBmp(coverHeight);
} else if (StringUtils::checkFileExtension(lastBookFileName, ".xtch") ||
StringUtils::checkFileExtension(lastBookFileName, ".xtc")) {
// Handle XTC file
Xtc xtc(book.path, "/.crosspoint");
if (xtc.load()) {
// Try to generate thumbnail image for Continue Reading card
if (!showingLoading) {
showingLoading = true;
popupRect = GUI.drawPopup(renderer, "Loading...");
}
GUI.fillPopupProgress(renderer, popupRect, progress * 30);
xtc.generateThumbBmp(coverHeight);
GUI.fillPopupProgress(renderer, popupRect, progress * (100 / maxBooks));
xtc.generateThumbBmp(coverHeight);
if (mustReloadRecents) {
// Update recent book entry with title/author/cover path
RECENT_BOOKS.addBook(book.path, xtc.getTitle(), xtc.getAuthor(), xtc.getThumbBmpPath());
recentBooks.push_back({book.path, xtc.getTitle(), xtc.getAuthor(), xtc.getThumbBmpPath()});
}
}
}
}
recentBooks.push_back(book);
if (!mustReloadRecents) {
recentBooks.push_back(book);
}
progress++;
}
Serial.printf("Recent books loaded: %d\n", recentBooks.size());
recentsLoaded = true;
recentsLoading = false;
updateRequired = true;

View File

@ -58,9 +58,11 @@ void TxtReaderActivity::onEnter() {
txt->setupCacheDir();
// Save current txt as last opened file and add to recent books
APP_STATE.openEpubPath = txt->getPath();
auto filePath = txt->getPath();
auto fileName = filePath.substr(filePath.rfind('/') + 1);
APP_STATE.openEpubPath = filePath;
APP_STATE.saveToFile();
RECENT_BOOKS.addBook(txt->getPath(), "", "", "");
RECENT_BOOKS.addBook(filePath, fileName, "", "");
// Trigger first update
updateRequired = true;

View File

@ -1146,10 +1146,10 @@ function retryAllFailedUploads() {
return;
}
// Validate folder name (no special characters except underscore and hyphen)
const validName = /^[a-zA-Z0-9_\-]+$/.test(folderName);
// Validate folder name
const validName = /^(?!\.{1,2}$)[^"*:<>?\/\\|]+$/.test(folderName);
if (!validName) {
alert('Folder name can only contain letters, numbers, underscores, and hyphens.');
alert('Folder name cannot contain \" * : < > ? / \\ | and must not be . or ..');
return;
}