diff --git a/lib/I18n/translations/belarusian.yaml b/lib/I18n/translations/belarusian.yaml index 8f0cf220..e907950d 100644 --- a/lib/I18n/translations/belarusian.yaml +++ b/lib/I18n/translations/belarusian.yaml @@ -268,6 +268,7 @@ STR_IMAGES_DISPLAY: "Паказваць" STR_IMAGES_PLACEHOLDER: "Запаўняльнік" STR_IMAGES_SUPPRESS: "Хаваць" STR_SHOW_HIDDEN_FILES: "Паказваць схаваныя файлы" +STR_MOVE_FINISHED_TO_READ: "Перамяшчаць прачытаныя кнігі ў тэчку Read" STR_SELECTED: "Выбрана" STR_SHOW: "Паказаць" STR_HIDE: "Схаваць" diff --git a/lib/I18n/translations/catalan.yaml b/lib/I18n/translations/catalan.yaml index 7e6b5593..0961626c 100644 --- a/lib/I18n/translations/catalan.yaml +++ b/lib/I18n/translations/catalan.yaml @@ -85,6 +85,7 @@ STR_PARA_ALIGNMENT: "Alineació de paràgrafs del lector" STR_HYPHENATION: "Partició de mots" STR_TIME_TO_SLEEP: "Temps per entrar en repòs" STR_SHOW_HIDDEN_FILES: "Mostra fitxers ocults" +STR_MOVE_FINISHED_TO_READ: "Mou els llibres acabats a la carpeta Read" STR_REFRESH_FREQ: "Freqüència de refresc" STR_KOREADER_SYNC: "Sincronització del KOReader" STR_CHECK_UPDATES: "Comprova si hi ha actualitzacions" diff --git a/lib/I18n/translations/czech.yaml b/lib/I18n/translations/czech.yaml index 286ded40..e03ea8af 100644 --- a/lib/I18n/translations/czech.yaml +++ b/lib/I18n/translations/czech.yaml @@ -80,6 +80,7 @@ STR_SCREEN_MARGIN: "Okraj obrazovky čtečky" STR_PARA_ALIGNMENT: "Zarovnání odstavců čtečky" STR_HYPHENATION: "Dělení slov" STR_TIME_TO_SLEEP: "Čas do uspání" +STR_MOVE_FINISHED_TO_READ: "Přesunout přečtené knihy do složky Read" STR_REFRESH_FREQ: "Frekvence obnovení" STR_KOREADER_SYNC: "KOReaderu Sync" STR_CHECK_UPDATES: "Zkontrolovat aktualizace" diff --git a/lib/I18n/translations/danish.yaml b/lib/I18n/translations/danish.yaml index 206eca03..56f3a05f 100644 --- a/lib/I18n/translations/danish.yaml +++ b/lib/I18n/translations/danish.yaml @@ -85,6 +85,7 @@ STR_PARA_ALIGNMENT: "Afsnitsjustering" STR_HYPHENATION: "Orddeling" STR_TIME_TO_SLEEP: "Tid til hvile" STR_SHOW_HIDDEN_FILES: "Vis skjulte filer" +STR_MOVE_FINISHED_TO_READ: "Flyt læste bøger til Read-mappen" STR_REFRESH_FREQ: "Opdateringsfrekvens" STR_KOREADER_SYNC: "KOReader Sync" STR_CHECK_UPDATES: "Søg efter opdateringer" diff --git a/lib/I18n/translations/dutch.yaml b/lib/I18n/translations/dutch.yaml index 07a39cce..0f0c42ab 100644 --- a/lib/I18n/translations/dutch.yaml +++ b/lib/I18n/translations/dutch.yaml @@ -85,6 +85,7 @@ STR_PARA_ALIGNMENT: "Uitlijning alinea lezer" STR_HYPHENATION: "Woordafbreking" STR_TIME_TO_SLEEP: "Tijd tot slaapstand" STR_SHOW_HIDDEN_FILES: "Toon verborgen bestanden" +STR_MOVE_FINISHED_TO_READ: "Voltooide boeken naar de Read-map verplaatsen" STR_REFRESH_FREQ: "Verversingsfrequentie" STR_KOREADER_SYNC: "KOReader Sync" STR_CHECK_UPDATES: "Controleren op updates" diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index c24ce193..e7027349 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -85,6 +85,7 @@ STR_PARA_ALIGNMENT: "Reader Paragraph Alignment" STR_HYPHENATION: "Hyphenation" STR_TIME_TO_SLEEP: "Time to Sleep" STR_SHOW_HIDDEN_FILES: "Show Hidden Files" +STR_MOVE_FINISHED_TO_READ: "Move Finished Books to Read Folder" STR_REFRESH_FREQ: "Refresh Frequency" STR_KOREADER_SYNC: "KOReader Sync" STR_CHECK_UPDATES: "Check for updates" diff --git a/lib/I18n/translations/finnish.yaml b/lib/I18n/translations/finnish.yaml index 958ab044..6d625a07 100644 --- a/lib/I18n/translations/finnish.yaml +++ b/lib/I18n/translations/finnish.yaml @@ -80,6 +80,7 @@ STR_SCREEN_MARGIN: "Lukijan näyttömarginaali" STR_PARA_ALIGNMENT: "Lukijan kappaletasaus" STR_HYPHENATION: "Tavutus" STR_TIME_TO_SLEEP: "Aika lepotilaan" +STR_MOVE_FINISHED_TO_READ: "Siirrä luetut kirjat Read-kansioon" STR_REFRESH_FREQ: "Päivitystaajuus" STR_KOREADER_SYNC: "KOReader-synkronointi" STR_CHECK_UPDATES: "Tarkista päivitykset" diff --git a/lib/I18n/translations/french.yaml b/lib/I18n/translations/french.yaml index 6e090314..8e335f76 100644 --- a/lib/I18n/translations/french.yaml +++ b/lib/I18n/translations/french.yaml @@ -85,6 +85,7 @@ STR_PARA_ALIGNMENT: "Alignement du texte" STR_HYPHENATION: "Césure" STR_TIME_TO_SLEEP: "Mise en veille auto" STR_SHOW_HIDDEN_FILES: "Afficher les fichiers cachés" +STR_MOVE_FINISHED_TO_READ: "Déplacer les livres terminés vers le dossier Read" STR_REFRESH_FREQ: "Fréquence rafraîchissement" STR_KOREADER_SYNC: "Synchro KOReader" STR_CHECK_UPDATES: "Mise à jour" diff --git a/lib/I18n/translations/german.yaml b/lib/I18n/translations/german.yaml index 0edf8be5..0f98eb6c 100644 --- a/lib/I18n/translations/german.yaml +++ b/lib/I18n/translations/german.yaml @@ -303,6 +303,7 @@ STR_IMAGES_DISPLAY: "Darstellung" STR_IMAGES_PLACEHOLDER: "Platzhalter" STR_IMAGES_SUPPRESS: "Unterdrücken" STR_SHOW_HIDDEN_FILES: "Versteckte Dateien anzeigen" +STR_MOVE_FINISHED_TO_READ: "Gelesene Bücher in den Read-Ordner verschieben" STR_PAGE_OVERLAY: "Seiten-Overlay" STR_RECENTS: "Zuletzt" STR_MAPPING_REMOTE: "Externe Position zuordnen..." diff --git a/lib/I18n/translations/hungarian.yaml b/lib/I18n/translations/hungarian.yaml index 4e0fca09..b5eb19f7 100644 --- a/lib/I18n/translations/hungarian.yaml +++ b/lib/I18n/translations/hungarian.yaml @@ -82,6 +82,7 @@ STR_PARA_ALIGNMENT: "Olvasó szövegigazítás" STR_HYPHENATION: "Elválasztás" STR_TIME_TO_SLEEP: "Alvó mód aktiválása" STR_SHOW_HIDDEN_FILES: "Rejtett fájlok megjelenítése" +STR_MOVE_FINISHED_TO_READ: "Befejezett könyvek áthelyezése a Read mappába" STR_REFRESH_FREQ: "Frissítési frekvencia" STR_KOREADER_SYNC: "KOReader szinkronizálás" STR_CHECK_UPDATES: "Frissítések keresése" diff --git a/lib/I18n/translations/italian.yaml b/lib/I18n/translations/italian.yaml index dc652992..611aeab4 100644 --- a/lib/I18n/translations/italian.yaml +++ b/lib/I18n/translations/italian.yaml @@ -85,6 +85,7 @@ STR_PARA_ALIGNMENT: "Allineamento paragrafo" STR_HYPHENATION: "Sillabazione" STR_TIME_TO_SLEEP: "Timeout sospensione" STR_SHOW_HIDDEN_FILES: "Mostra file nascosti" +STR_MOVE_FINISHED_TO_READ: "Sposta i libri finiti nella cartella Read" STR_REFRESH_FREQ: "Frequenza di aggiornamento" STR_KOREADER_SYNC: "Sincronizzazione KOReader" STR_CHECK_UPDATES: "Cerca aggiornamenti" diff --git a/lib/I18n/translations/kazakh.yaml b/lib/I18n/translations/kazakh.yaml index a7f2603b..624bf197 100644 --- a/lib/I18n/translations/kazakh.yaml +++ b/lib/I18n/translations/kazakh.yaml @@ -76,6 +76,7 @@ STR_SCREEN_MARGIN: "Оқырман экран жиегі" STR_PARA_ALIGNMENT: "Оқырман абзац туралануы" STR_HYPHENATION: "Буын бөлу" STR_TIME_TO_SLEEP: "Ұйқы уақыты" +STR_MOVE_FINISHED_TO_READ: "Оқылған кітаптарды Read қалтасына жылжыту" STR_REFRESH_FREQ: "Жаңарту жиілігі" STR_KOREADER_SYNC: "KOReader синхронизациясы" STR_CHECK_UPDATES: "Жаңартуларды тексеру" diff --git a/lib/I18n/translations/lithuanian.yaml b/lib/I18n/translations/lithuanian.yaml index aaa68943..b73c86e7 100644 --- a/lib/I18n/translations/lithuanian.yaml +++ b/lib/I18n/translations/lithuanian.yaml @@ -82,6 +82,7 @@ STR_PARA_ALIGNMENT: "Lygiavimas" STR_HYPHENATION: "Skiemenuoti" STR_TIME_TO_SLEEP: "Užmigti po" STR_SHOW_HIDDEN_FILES: "Rod. paslėptus failus" +STR_MOVE_FINISHED_TO_READ: "Perkelti perskaitytas knygas į aplanką Read" STR_REFRESH_FREQ: "Atnaujinimo dažnis" STR_KOREADER_SYNC: "KOReader sinchr." STR_CHECK_UPDATES: "Atnaujinimai" diff --git a/lib/I18n/translations/polish.yaml b/lib/I18n/translations/polish.yaml index 6ac52c9c..fffdb114 100644 --- a/lib/I18n/translations/polish.yaml +++ b/lib/I18n/translations/polish.yaml @@ -85,6 +85,7 @@ STR_PARA_ALIGNMENT: "Wyjustowanie paragrafów" STR_HYPHENATION: "Dzielenie wyrazów" STR_TIME_TO_SLEEP: "Czas do uśpienia" STR_SHOW_HIDDEN_FILES: "Pokaż ukryte pliki" +STR_MOVE_FINISHED_TO_READ: "Przenoś przeczytane książki do folderu Read" STR_REFRESH_FREQ: "Częstotliwość odświeżania" STR_KOREADER_SYNC: "KOReader Sync" STR_CHECK_UPDATES: "Sprawdź uaktualnia" diff --git a/lib/I18n/translations/portuguese.yaml b/lib/I18n/translations/portuguese.yaml index f5466307..a0efb68c 100644 --- a/lib/I18n/translations/portuguese.yaml +++ b/lib/I18n/translations/portuguese.yaml @@ -80,6 +80,7 @@ STR_SCREEN_MARGIN: "Margens da tela" STR_PARA_ALIGNMENT: "Alinhamento parágrafo" STR_HYPHENATION: "Hifenização" STR_TIME_TO_SLEEP: "Tempo para repousar" +STR_MOVE_FINISHED_TO_READ: "Mover livros lidos para a pasta Read" STR_REFRESH_FREQ: "Frequência atualização" STR_KOREADER_SYNC: "Sincronização KOReader" STR_CHECK_UPDATES: "Verificar atualizações" diff --git a/lib/I18n/translations/romanian.yaml b/lib/I18n/translations/romanian.yaml index 1ac1aca5..d5670467 100644 --- a/lib/I18n/translations/romanian.yaml +++ b/lib/I18n/translations/romanian.yaml @@ -85,6 +85,7 @@ STR_PARA_ALIGNMENT: "Aliniere paragrafe reader" STR_HYPHENATION: "Silabisire" STR_TIME_TO_SLEEP: "Timp până la repaus" STR_SHOW_HIDDEN_FILES: "Afişează fişierele ascunse" +STR_MOVE_FINISHED_TO_READ: "Mută cărţile terminate în folderul Read" STR_REFRESH_FREQ: "Frecvenţă reîmprospătare" STR_KOREADER_SYNC: "Sincronizare KOReader" STR_CHECK_UPDATES: "Căutaţi actualizări" diff --git a/lib/I18n/translations/russian.yaml b/lib/I18n/translations/russian.yaml index c3c9abad..eadbc60b 100644 --- a/lib/I18n/translations/russian.yaml +++ b/lib/I18n/translations/russian.yaml @@ -85,6 +85,7 @@ STR_PARA_ALIGNMENT: "Выравнивание абзаца" STR_HYPHENATION: "Перенос слов" STR_TIME_TO_SLEEP: "Сон через" STR_SHOW_HIDDEN_FILES: "Показать скрытые файлы" +STR_MOVE_FINISHED_TO_READ: "Перемещать прочитанные книги в папку Read" STR_REFRESH_FREQ: "Частота обновления" STR_KOREADER_SYNC: "Синхронизация KOReader" STR_CHECK_UPDATES: "Проверить обновления" diff --git a/lib/I18n/translations/slovenian.yaml b/lib/I18n/translations/slovenian.yaml index 0a0c3c8c..87d96817 100644 --- a/lib/I18n/translations/slovenian.yaml +++ b/lib/I18n/translations/slovenian.yaml @@ -82,6 +82,7 @@ STR_PARA_ALIGNMENT: "Poravnava odstavkov" STR_HYPHENATION: "Deljenje besed" STR_TIME_TO_SLEEP: "Čas do spanja" STR_SHOW_HIDDEN_FILES: "Prikaži skrite datoteke" +STR_MOVE_FINISHED_TO_READ: "Premakni prebrane knjige v mapo Read" STR_REFRESH_FREQ: "Pogostost osveževanja" STR_KOREADER_SYNC: "KOReader sinhronizacija" STR_CHECK_UPDATES: "Preveri posodobitve" diff --git a/lib/I18n/translations/spanish.yaml b/lib/I18n/translations/spanish.yaml index e528ebce..cb7723a2 100644 --- a/lib/I18n/translations/spanish.yaml +++ b/lib/I18n/translations/spanish.yaml @@ -85,6 +85,7 @@ STR_PARA_ALIGNMENT: "Ajuste de párrafo" STR_HYPHENATION: "División de palabras" STR_TIME_TO_SLEEP: "Auto suspensión" STR_SHOW_HIDDEN_FILES: "Mostrar archivos ocultos" +STR_MOVE_FINISHED_TO_READ: "Mover libros terminados a la carpeta Read" STR_REFRESH_FREQ: "Frecuencia de refresco" STR_KOREADER_SYNC: "Sincronización de KOReader" STR_CHECK_UPDATES: "Verificar actualizaciones" diff --git a/lib/I18n/translations/swedish.yaml b/lib/I18n/translations/swedish.yaml index 0ebf33ce..8d0957d3 100644 --- a/lib/I18n/translations/swedish.yaml +++ b/lib/I18n/translations/swedish.yaml @@ -85,6 +85,7 @@ STR_PARA_ALIGNMENT: "Eboksläsarens paragrafjustering" STR_HYPHENATION: "Avstavning" STR_TIME_TO_SLEEP: "Tid för att gå i vila" STR_SHOW_HIDDEN_FILES: "Visa dolda filer" +STR_MOVE_FINISHED_TO_READ: "Flytta lästa böcker till Read-mappen" STR_REFRESH_FREQ: "Uppdateringsfrekvens" STR_KOREADER_SYNC: "KOReader-synkronisering" STR_CHECK_UPDATES: "Kolla efter uppdateringar" diff --git a/lib/I18n/translations/turkish.yaml b/lib/I18n/translations/turkish.yaml index cb52eb9d..64a58285 100644 --- a/lib/I18n/translations/turkish.yaml +++ b/lib/I18n/translations/turkish.yaml @@ -80,6 +80,7 @@ STR_PARA_ALIGNMENT: "Okuyucu Paragraf Hizalaması" STR_HYPHENATION: "Hecelerden Ayırma" STR_TIME_TO_SLEEP: "Uykuya Geçme Süresi" STR_SHOW_HIDDEN_FILES: "Gizli Dosyaları Göster" +STR_MOVE_FINISHED_TO_READ: "Biten kitapları Read klasörüne taşı" STR_REFRESH_FREQ: "Yenileme Sıklığı" STR_KOREADER_SYNC: "KOReader Senkronizasyonu" STR_CHECK_UPDATES: "Güncellemeleri denetle" diff --git a/lib/I18n/translations/ukrainian.yaml b/lib/I18n/translations/ukrainian.yaml index 21a20caf..bc2299a9 100644 --- a/lib/I18n/translations/ukrainian.yaml +++ b/lib/I18n/translations/ukrainian.yaml @@ -85,6 +85,7 @@ STR_PARA_ALIGNMENT: "Вирівнювання тексту" STR_HYPHENATION: "Перенесення слів" STR_TIME_TO_SLEEP: "Перехід в режим сну" STR_SHOW_HIDDEN_FILES: "Показати приховані файли" +STR_MOVE_FINISHED_TO_READ: "Переміщати прочитані книги до теки Read" STR_REFRESH_FREQ: "Частота оновлення екрану" STR_KOREADER_SYNC: "Синхронізація KOReader" STR_CHECK_UPDATES: "Перевірити оновлення системи" diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 9302d2a2..372a1a53 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -220,6 +220,8 @@ class CrossPointSettings { char sdFontFamilyName[32] = ""; // Show hidden files/directories (starting with '.') in the file browser (0 = hidden, 1 = show) uint8_t showHiddenFiles = 0; + // Move epub to /Read/ folder on SD card when finished (0 = disabled, 1 = enabled) + uint8_t moveFinishedToReadFolder = 0; // Image rendering mode in EPUB reader uint8_t imageRendering = IMAGES_DISPLAY; // Tilt-based page turning (X3 only — requires QMI8658 IMU) diff --git a/src/RecentBooksStore.cpp b/src/RecentBooksStore.cpp index fc0d89a4..e4c819b9 100644 --- a/src/RecentBooksStore.cpp +++ b/src/RecentBooksStore.cpp @@ -56,6 +56,20 @@ void RecentBooksStore::updateBook(const std::string& path, const std::string& ti } } +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() { diff --git a/src/RecentBooksStore.h b/src/RecentBooksStore.h index 293d4f6f..d1111a9f 100644 --- a/src/RecentBooksStore.h +++ b/src/RecentBooksStore.h @@ -37,6 +37,12 @@ class RecentBooksStore { void updateBook(const std::string& path, const std::string& title, const std::string& author, const std::string& coverBmpPath); + // Repoint an entry's path (and coverBmpPath, if it lived under the old cache dir) after the + // backing file and cache dir were moved on disk. No-op if no entry matches oldPath. + // Persists on success. Keeps the entry's list position (does not reorder). + void updatePath(const std::string& oldPath, const std::string& newPath, const std::string& oldCachePath, + const std::string& newCachePath); + // True if the book's backing file is no longer present on the SD card. static bool isMissing(const RecentBook& book); diff --git a/src/SettingsList.h b/src/SettingsList.h index 492ce755..aae73477 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -177,6 +177,8 @@ inline std::vector getSettingsList(const SdCardFontRegistry* regist "sleepTimeout", StrId::STR_CAT_SYSTEM), SettingInfo::Toggle(StrId::STR_SHOW_HIDDEN_FILES, &CrossPointSettings::showHiddenFiles, "showHiddenFiles", StrId::STR_CAT_SYSTEM), + SettingInfo::Toggle(StrId::STR_MOVE_FINISHED_TO_READ, &CrossPointSettings::moveFinishedToReadFolder, + "moveFinishedToReadFolder", StrId::STR_CAT_SYSTEM), // --- KOReader Sync (web-only, uses KOReaderCredentialStore) --- SettingInfo::DynamicString( diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 7732762b..e30d1959 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -45,6 +46,68 @@ int clampPercent(int percent) { return percent; } +// SD card folder finished books are moved into. Single source of truth for the path. +// constexpr ⇒ lives in flash .rodata, no DRAM cost. +constexpr char READ_FOLDER[] = "/read"; + +// True if path is inside READ_FOLDER (starts with "/"). Non-allocating so +// it is cheap to call from loop(), and avoids reintroducing a separate "/Read/" literal. +bool isInReadFolder(const std::string& path) { + constexpr size_t n = sizeof(READ_FOLDER) - 1; // length of "/Read" (excludes NUL) + return path.size() > n && path.compare(0, n, READ_FOLDER) == 0 && path[n] == '/'; +} + +// Pick a non-colliding destination path inside /Read/ for a finished book. +// Mirrors the suffixing scheme used elsewhere: "name.epub" -> "name (2).epub", etc. +std::string buildReadFolderDestination(const std::string& srcPath) { + const size_t lastSlash = srcPath.rfind('/'); + const std::string filename = (lastSlash != std::string::npos) ? srcPath.substr(lastSlash + 1) : srcPath; + + Storage.mkdir(READ_FOLDER); + std::string dstPath = std::string(READ_FOLDER) + "/" + filename; + if (!Storage.exists(dstPath.c_str())) { + return dstPath; + } + + const size_t dotPos = filename.rfind('.'); + const std::string base = (dotPos != std::string::npos) ? filename.substr(0, dotPos) : filename; + const std::string ext = (dotPos != std::string::npos) ? filename.substr(dotPos) : ""; + int suffix = 2; + do { + dstPath = std::string(READ_FOLDER) + "/" + base + " (" + std::to_string(suffix) + ")" + ext; + suffix++; + } while (Storage.exists(dstPath.c_str()) && suffix < 100); + return dstPath; +} + +// Relocate a finished book and its cache dir into /read/, keep it in recents by +// repointing its entry to the new path, and repoint the resume pointer too. +// On rename failure: LOG_ERR and leave everything in place (no UI alert subsystem here). +void moveFinishedBookToReadFolder(const std::string& srcPath, const std::string& dstPath, + const std::string& oldCachePath) { + LOG_INF("ERS", "Moving finished epub: %s -> %s", srcPath.c_str(), dstPath.c_str()); + if (!Storage.rename(srcPath.c_str(), dstPath.c_str())) { + LOG_ERR("ERS", "Failed to move finished book to '/Read' folder"); + return; + } + + // Cache dir is keyed by hash of the epub path (see Epub ctor), so it must be re-keyed. + const std::string newCachePath = "/.crosspoint/epub_" + std::to_string(std::hash{}(dstPath)); + if (!oldCachePath.empty() && Storage.exists(oldCachePath.c_str())) { + if (!Storage.rename(oldCachePath.c_str(), newCachePath.c_str())) { + LOG_ERR("ERS", "Failed to rename cache dir %s -> %s (non-fatal)", oldCachePath.c_str(), newCachePath.c_str()); + } + } + + // Keep the book in recents (crossink behavior): repoint the entry to its new + // location instead of dropping it. updatePath persists on success. + RECENT_BOOKS.updatePath(srcPath, dstPath, oldCachePath, newCachePath); + if (APP_STATE.openEpubPath == srcPath) { + APP_STATE.openEpubPath = dstPath; + APP_STATE.saveToFile(); + } +} + } // namespace void EpubReaderActivity::onEnter() { @@ -109,7 +172,15 @@ void EpubReaderActivity::onExit() { APP_STATE.readerActivityLoadCount = 0; APP_STATE.saveToFile(); section.reset(); - epub.reset(); + if (pendingReadFolderMove && epub) { + const std::string srcPath = epub->getPath(); + const std::string oldCachePath = epub->getCachePath(); + const std::string dstPath = buildReadFolderDestination(srcPath); + epub.reset(); // release the Epub (and any open handles) before renaming on the SD card + moveFinishedBookToReadFolder(srcPath, dstPath, oldCachePath); + } else { + epub.reset(); + } } void EpubReaderActivity::loop() { @@ -119,6 +190,15 @@ void EpubReaderActivity::loop() { return; } + // Being on the "End of Book" screen (currentSpineIndex == spine count) means the book is + // finished. Arm the move here so ANY exit path (Back, Home, file browser) relocates the + // book in onExit(); paging back off the end screen disarms it (book not actually finished). + if (currentSpineIndex > 0 && currentSpineIndex >= epub->getSpineItemsCount()) { + pendingReadFolderMove = SETTINGS.moveFinishedToReadFolder && !isInReadFolder(epub->getPath()); + } else { + pendingReadFolderMove = false; + } + if (automaticPageTurnActive) { if (mappedInput.wasReleased(MappedInputManager::Button::Confirm) || mappedInput.wasReleased(MappedInputManager::Button::Back)) { diff --git a/src/activities/reader/EpubReaderActivity.h b/src/activities/reader/EpubReaderActivity.h index 01de3fde..38ecc2a1 100644 --- a/src/activities/reader/EpubReaderActivity.h +++ b/src/activities/reader/EpubReaderActivity.h @@ -31,6 +31,9 @@ class EpubReaderActivity final : public Activity { bool pendingSyncSaveError = false; bool skipNextButtonCheck = false; // Skip button processing for one frame after subactivity exit bool automaticPageTurnActive = false; + // Set when the reader is left at end-of-book and SETTINGS.moveFinishedToReadFolder is on. + // Consumed in onExit() to relocate the finished book into /Read/. + bool pendingReadFolderMove = false; // Footnote support std::vector currentPageFootnotes;