From 278b056bd06858fdd2cb7ff58d47822971733aff Mon Sep 17 00:00:00 2001 From: Sam Davis Date: Tue, 30 Dec 2025 12:49:18 +1100 Subject: [PATCH 01/10] Add chapter select support to XTC files (#145) ## Summary - **What is the goal of this PR?** Add chapter selection support to the XTC reader activity, including parsing chapter metadata from XTC files. - **What changes are included?** Implemented XTC chapter parsing and exposure in the XTC library, added a chapter selection activity for XTC, integrated it into XtcReaderActivity, and normalized chapter page indices by shifting them to 0-based. ## Additional Context - The reader uses 0-based page indexing (first page = 0), but the XTC chapter table appears to be 1-based (first page = 1), so chapter start/end pages are shifted down by 1 during parsing. --- lib/Xtc/Xtc.cpp | 15 ++ lib/Xtc/Xtc.h | 3 + lib/Xtc/Xtc/XtcParser.cpp | 117 ++++++++++++++ lib/Xtc/Xtc/XtcParser.h | 6 + lib/Xtc/Xtc/XtcTypes.h | 7 + src/activities/reader/XtcReaderActivity.cpp | 31 +++- src/activities/reader/XtcReaderActivity.h | 9 +- .../XtcReaderChapterSelectionActivity.cpp | 152 ++++++++++++++++++ .../XtcReaderChapterSelectionActivity.h | 41 +++++ 9 files changed, 376 insertions(+), 5 deletions(-) create mode 100644 src/activities/reader/XtcReaderChapterSelectionActivity.cpp create mode 100644 src/activities/reader/XtcReaderChapterSelectionActivity.h diff --git a/lib/Xtc/Xtc.cpp b/lib/Xtc/Xtc.cpp index fe0b107..9e948ff 100644 --- a/lib/Xtc/Xtc.cpp +++ b/lib/Xtc/Xtc.cpp @@ -87,6 +87,21 @@ std::string Xtc::getTitle() const { return filepath.substr(lastSlash, lastDot - lastSlash); } +bool Xtc::hasChapters() const { + if (!loaded || !parser) { + return false; + } + return parser->hasChapters(); +} + +const std::vector& Xtc::getChapters() const { + static const std::vector kEmpty; + if (!loaded || !parser) { + return kEmpty; + } + return parser->getChapters(); +} + std::string Xtc::getCoverBmpPath() const { return cachePath + "/cover.bmp"; } bool Xtc::generateCoverBmp() const { diff --git a/lib/Xtc/Xtc.h b/lib/Xtc/Xtc.h index 42e05ef..e5bce10 100644 --- a/lib/Xtc/Xtc.h +++ b/lib/Xtc/Xtc.h @@ -9,6 +9,7 @@ #include #include +#include #include "Xtc/XtcParser.h" #include "Xtc/XtcTypes.h" @@ -55,6 +56,8 @@ class Xtc { // Metadata std::string getTitle() const; + bool hasChapters() const; + const std::vector& getChapters() const; // Cover image support (for sleep screen) std::string getCoverBmpPath() const; diff --git a/lib/Xtc/Xtc/XtcParser.cpp b/lib/Xtc/Xtc/XtcParser.cpp index a443f57..f4bf62f 100644 --- a/lib/Xtc/Xtc/XtcParser.cpp +++ b/lib/Xtc/Xtc/XtcParser.cpp @@ -19,6 +19,7 @@ XtcParser::XtcParser() m_defaultWidth(DISPLAY_WIDTH), m_defaultHeight(DISPLAY_HEIGHT), m_bitDepth(1), + m_hasChapters(false), m_lastError(XtcError::OK) { memset(&m_header, 0, sizeof(m_header)); } @@ -56,6 +57,14 @@ XtcError XtcParser::open(const char* filepath) { return m_lastError; } + // Read chapters if present + m_lastError = readChapters(); + if (m_lastError != XtcError::OK) { + Serial.printf("[%lu] [XTC] Failed to read chapters: %s\n", millis(), errorToString(m_lastError)); + m_file.close(); + return m_lastError; + } + m_isOpen = true; Serial.printf("[%lu] [XTC] Opened file: %s (%u pages, %dx%d)\n", millis(), filepath, m_header.pageCount, m_defaultWidth, m_defaultHeight); @@ -68,7 +77,9 @@ void XtcParser::close() { m_isOpen = false; } m_pageTable.clear(); + m_chapters.clear(); m_title.clear(); + m_hasChapters = false; memset(&m_header, 0, sizeof(m_header)); } @@ -165,6 +176,112 @@ XtcError XtcParser::readPageTable() { return XtcError::OK; } +XtcError XtcParser::readChapters() { + m_hasChapters = false; + m_chapters.clear(); + + uint8_t hasChaptersFlag = 0; + if (!m_file.seek(0x0B)) { + return XtcError::READ_ERROR; + } + if (m_file.read(&hasChaptersFlag, sizeof(hasChaptersFlag)) != sizeof(hasChaptersFlag)) { + return XtcError::READ_ERROR; + } + + if (hasChaptersFlag != 1) { + return XtcError::OK; + } + + uint64_t chapterOffset = 0; + if (!m_file.seek(0x30)) { + return XtcError::READ_ERROR; + } + if (m_file.read(reinterpret_cast(&chapterOffset), sizeof(chapterOffset)) != sizeof(chapterOffset)) { + return XtcError::READ_ERROR; + } + + if (chapterOffset == 0) { + return XtcError::OK; + } + + const uint64_t fileSize = m_file.size(); + if (chapterOffset < sizeof(XtcHeader) || chapterOffset >= fileSize || chapterOffset + 96 > fileSize) { + return XtcError::OK; + } + + uint64_t maxOffset = 0; + if (m_header.pageTableOffset > chapterOffset) { + maxOffset = m_header.pageTableOffset; + } else if (m_header.dataOffset > chapterOffset) { + maxOffset = m_header.dataOffset; + } else { + maxOffset = fileSize; + } + + if (maxOffset <= chapterOffset) { + return XtcError::OK; + } + + constexpr size_t chapterSize = 96; + const uint64_t available = maxOffset - chapterOffset; + const size_t chapterCount = static_cast(available / chapterSize); + if (chapterCount == 0) { + return XtcError::OK; + } + + if (!m_file.seek(chapterOffset)) { + return XtcError::READ_ERROR; + } + + std::vector chapterBuf(chapterSize); + for (size_t i = 0; i < chapterCount; i++) { + if (m_file.read(chapterBuf.data(), chapterSize) != chapterSize) { + return XtcError::READ_ERROR; + } + + char nameBuf[81]; + memcpy(nameBuf, chapterBuf.data(), 80); + nameBuf[80] = '\0'; + const size_t nameLen = strnlen(nameBuf, 80); + std::string name(nameBuf, nameLen); + + uint16_t startPage = 0; + uint16_t endPage = 0; + memcpy(&startPage, chapterBuf.data() + 0x50, sizeof(startPage)); + memcpy(&endPage, chapterBuf.data() + 0x52, sizeof(endPage)); + + if (name.empty() && startPage == 0 && endPage == 0) { + break; + } + + if (startPage > 0) { + startPage--; + } + if (endPage > 0) { + endPage--; + } + + if (startPage >= m_header.pageCount) { + continue; + } + + if (endPage >= m_header.pageCount) { + endPage = m_header.pageCount - 1; + } + + if (startPage > endPage) { + continue; + } + + ChapterInfo chapter{std::move(name), startPage, endPage}; + m_chapters.push_back(std::move(chapter)); + } + + m_hasChapters = !m_chapters.empty(); + Serial.printf("[%lu] [XTC] Chapters: %u\n", millis(), static_cast(m_chapters.size())); + return XtcError::OK; +} + bool XtcParser::getPageInfo(uint32_t pageIndex, PageInfo& info) const { if (pageIndex >= m_pageTable.size()) { return false; diff --git a/lib/Xtc/Xtc/XtcParser.h b/lib/Xtc/Xtc/XtcParser.h index b0a402a..25f1c33 100644 --- a/lib/Xtc/Xtc/XtcParser.h +++ b/lib/Xtc/Xtc/XtcParser.h @@ -70,6 +70,9 @@ class XtcParser { // Get title from metadata std::string getTitle() const { return m_title; } + bool hasChapters() const { return m_hasChapters; } + const std::vector& getChapters() const { return m_chapters; } + // Validation static bool isValidXtcFile(const char* filepath); @@ -81,16 +84,19 @@ class XtcParser { bool m_isOpen; XtcHeader m_header; std::vector m_pageTable; + std::vector m_chapters; std::string m_title; uint16_t m_defaultWidth; uint16_t m_defaultHeight; uint8_t m_bitDepth; // 1 = XTC/XTG (1-bit), 2 = XTCH/XTH (2-bit) + bool m_hasChapters; XtcError m_lastError; // Internal helper functions XtcError readHeader(); XtcError readPageTable(); XtcError readTitle(); + XtcError readChapters(); }; } // namespace xtc diff --git a/lib/Xtc/Xtc/XtcTypes.h b/lib/Xtc/Xtc/XtcTypes.h index 30761d9..eba8b32 100644 --- a/lib/Xtc/Xtc/XtcTypes.h +++ b/lib/Xtc/Xtc/XtcTypes.h @@ -13,6 +13,7 @@ #pragma once #include +#include namespace xtc { @@ -92,6 +93,12 @@ struct PageInfo { uint8_t padding; // Alignment padding }; // 16 bytes total +struct ChapterInfo { + std::string name; + uint16_t startPage; + uint16_t endPage; +}; + // Error codes enum class XtcError { OK = 0, diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index 8933606..317594c 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -13,6 +13,7 @@ #include "CrossPointSettings.h" #include "CrossPointState.h" +#include "XtcReaderChapterSelectionActivity.h" #include "config.h" namespace { @@ -27,7 +28,7 @@ void XtcReaderActivity::taskTrampoline(void* param) { } void XtcReaderActivity::onEnter() { - Activity::onEnter(); + ActivityWithSubactivity::onEnter(); if (!xtc) { return; @@ -56,7 +57,7 @@ void XtcReaderActivity::onEnter() { } void XtcReaderActivity::onExit() { - Activity::onExit(); + ActivityWithSubactivity::onExit(); // Wait until not rendering to delete task xSemaphoreTake(renderingMutex, portMAX_DELAY); @@ -70,6 +71,32 @@ void XtcReaderActivity::onExit() { } void XtcReaderActivity::loop() { + // Pass input responsibility to sub activity if exists + if (subActivity) { + subActivity->loop(); + return; + } + + // Enter chapter selection activity + if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { + if (xtc && xtc->hasChapters() && !xtc->getChapters().empty()) { + xSemaphoreTake(renderingMutex, portMAX_DELAY); + exitActivity(); + enterNewActivity(new XtcReaderChapterSelectionActivity( + this->renderer, this->mappedInput, xtc, currentPage, + [this] { + exitActivity(); + updateRequired = true; + }, + [this](const uint32_t newPage) { + currentPage = newPage; + exitActivity(); + updateRequired = true; + })); + xSemaphoreGive(renderingMutex); + } + } + // Long press BACK (1s+) goes directly to home if (mappedInput.isPressed(MappedInputManager::Button::Back) && mappedInput.getHeldTime() >= goHomeMs) { onGoHome(); diff --git a/src/activities/reader/XtcReaderActivity.h b/src/activities/reader/XtcReaderActivity.h index bf72064..579e177 100644 --- a/src/activities/reader/XtcReaderActivity.h +++ b/src/activities/reader/XtcReaderActivity.h @@ -12,9 +12,9 @@ #include #include -#include "activities/Activity.h" +#include "activities/ActivityWithSubactivity.h" -class XtcReaderActivity final : public Activity { +class XtcReaderActivity final : public ActivityWithSubactivity { std::shared_ptr xtc; TaskHandle_t displayTaskHandle = nullptr; SemaphoreHandle_t renderingMutex = nullptr; @@ -34,7 +34,10 @@ class XtcReaderActivity final : public Activity { public: explicit XtcReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::unique_ptr xtc, const std::function& onGoBack, const std::function& onGoHome) - : Activity("XtcReader", renderer, mappedInput), xtc(std::move(xtc)), onGoBack(onGoBack), onGoHome(onGoHome) {} + : ActivityWithSubactivity("XtcReader", renderer, mappedInput), + xtc(std::move(xtc)), + onGoBack(onGoBack), + onGoHome(onGoHome) {} void onEnter() override; void onExit() override; void loop() override; diff --git a/src/activities/reader/XtcReaderChapterSelectionActivity.cpp b/src/activities/reader/XtcReaderChapterSelectionActivity.cpp new file mode 100644 index 0000000..6c747f0 --- /dev/null +++ b/src/activities/reader/XtcReaderChapterSelectionActivity.cpp @@ -0,0 +1,152 @@ +#include "XtcReaderChapterSelectionActivity.h" + +#include +#include +#include + +#include "config.h" + +namespace { +constexpr int SKIP_PAGE_MS = 700; +} // namespace + +int XtcReaderChapterSelectionActivity::getPageItems() const { + constexpr int startY = 60; + constexpr int lineHeight = 30; + + const int screenHeight = renderer.getScreenHeight(); + const int availableHeight = screenHeight - startY; + int items = availableHeight / lineHeight; + if (items < 1) { + items = 1; + } + return items; +} + +int XtcReaderChapterSelectionActivity::findChapterIndexForPage(uint32_t page) const { + if (!xtc) { + return 0; + } + + const auto& chapters = xtc->getChapters(); + for (size_t i = 0; i < chapters.size(); i++) { + if (page >= chapters[i].startPage && page <= chapters[i].endPage) { + return static_cast(i); + } + } + return 0; +} + +void XtcReaderChapterSelectionActivity::taskTrampoline(void* param) { + auto* self = static_cast(param); + self->displayTaskLoop(); +} + +void XtcReaderChapterSelectionActivity::onEnter() { + Activity::onEnter(); + + if (!xtc) { + return; + } + + renderingMutex = xSemaphoreCreateMutex(); + selectorIndex = findChapterIndexForPage(currentPage); + + updateRequired = true; + xTaskCreate(&XtcReaderChapterSelectionActivity::taskTrampoline, "XtcReaderChapterSelectionActivityTask", + 4096, // Stack size + this, // Parameters + 1, // Priority + &displayTaskHandle // Task handle + ); +} + +void XtcReaderChapterSelectionActivity::onExit() { + Activity::onExit(); + + xSemaphoreTake(renderingMutex, portMAX_DELAY); + if (displayTaskHandle) { + vTaskDelete(displayTaskHandle); + displayTaskHandle = nullptr; + } + vSemaphoreDelete(renderingMutex); + renderingMutex = nullptr; +} + +void XtcReaderChapterSelectionActivity::loop() { + const bool prevReleased = mappedInput.wasReleased(MappedInputManager::Button::Up) || + mappedInput.wasReleased(MappedInputManager::Button::Left); + const bool nextReleased = mappedInput.wasReleased(MappedInputManager::Button::Down) || + mappedInput.wasReleased(MappedInputManager::Button::Right); + + const bool skipPage = mappedInput.getHeldTime() > SKIP_PAGE_MS; + const int pageItems = getPageItems(); + + if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { + const auto& chapters = xtc->getChapters(); + if (!chapters.empty() && selectorIndex >= 0 && selectorIndex < static_cast(chapters.size())) { + onSelectPage(chapters[selectorIndex].startPage); + } + } else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { + onGoBack(); + } else if (prevReleased) { + const int total = static_cast(xtc->getChapters().size()); + if (total == 0) { + return; + } + if (skipPage) { + selectorIndex = ((selectorIndex / pageItems - 1) * pageItems + total) % total; + } else { + selectorIndex = (selectorIndex + total - 1) % total; + } + updateRequired = true; + } else if (nextReleased) { + const int total = static_cast(xtc->getChapters().size()); + if (total == 0) { + return; + } + if (skipPage) { + selectorIndex = ((selectorIndex / pageItems + 1) * pageItems) % total; + } else { + selectorIndex = (selectorIndex + 1) % total; + } + updateRequired = true; + } +} + +void XtcReaderChapterSelectionActivity::displayTaskLoop() { + while (true) { + if (updateRequired) { + updateRequired = false; + xSemaphoreTake(renderingMutex, portMAX_DELAY); + renderScreen(); + xSemaphoreGive(renderingMutex); + } + vTaskDelay(10 / portTICK_PERIOD_MS); + } +} + +void XtcReaderChapterSelectionActivity::renderScreen() { + renderer.clearScreen(); + + const auto pageWidth = renderer.getScreenWidth(); + const int pageItems = getPageItems(); + renderer.drawCenteredText(READER_FONT_ID, 10, "Select Chapter", true, BOLD); + + const auto& chapters = xtc->getChapters(); + if (chapters.empty()) { + renderer.drawCenteredText(UI_FONT_ID, 120, "No chapters", true, REGULAR); + renderer.displayBuffer(); + return; + } + + const auto pageStartIndex = selectorIndex / pageItems * pageItems; + renderer.fillRect(0, 60 + (selectorIndex % pageItems) * 30 - 2, pageWidth - 1, 30); + for (int i = pageStartIndex; i < static_cast(chapters.size()) && i < pageStartIndex + pageItems; i++) { + const auto& chapter = chapters[i]; + const char* title = chapter.name.empty() ? "Unnamed" : chapter.name.c_str(); + renderer.drawText(UI_FONT_ID, 20, 60 + (i % pageItems) * 30, title, i != selectorIndex); + } + + renderer.displayBuffer(); +} diff --git a/src/activities/reader/XtcReaderChapterSelectionActivity.h b/src/activities/reader/XtcReaderChapterSelectionActivity.h new file mode 100644 index 0000000..f0fe06b --- /dev/null +++ b/src/activities/reader/XtcReaderChapterSelectionActivity.h @@ -0,0 +1,41 @@ +#pragma once +#include +#include +#include +#include + +#include + +#include "../Activity.h" + +class XtcReaderChapterSelectionActivity final : public Activity { + std::shared_ptr xtc; + TaskHandle_t displayTaskHandle = nullptr; + SemaphoreHandle_t renderingMutex = nullptr; + uint32_t currentPage = 0; + int selectorIndex = 0; + bool updateRequired = false; + const std::function onGoBack; + const std::function onSelectPage; + + int getPageItems() const; + int findChapterIndexForPage(uint32_t page) const; + + static void taskTrampoline(void* param); + [[noreturn]] void displayTaskLoop(); + void renderScreen(); + + public: + explicit XtcReaderChapterSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, + const std::shared_ptr& xtc, uint32_t currentPage, + const std::function& onGoBack, + const std::function& onSelectPage) + : Activity("XtcReaderChapterSelection", renderer, mappedInput), + xtc(xtc), + currentPage(currentPage), + onGoBack(onGoBack), + onSelectPage(onSelectPage) {} + void onEnter() override; + void onExit() override; + void loop() override; +}; From e4ac90f5c1d1be19378858af7d64b2278a0e460a Mon Sep 17 00:00:00 2001 From: Dave Allie Date: Tue, 30 Dec 2025 12:36:25 +1000 Subject: [PATCH 02/10] Accept big endian version in XTC files (#159) ## Summary * Accept big endian version in XTC files * Recently, two issues (https://github.com/daveallie/crosspoint-reader/issues/157 and https://github.com/daveallie/crosspoint-reader/issues/146) have popped up with XTC files with a big endian encoded version. This is read out as 256. * We should be more lax and accept these values. --- lib/Xtc/Xtc/XtcParser.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Xtc/Xtc/XtcParser.cpp b/lib/Xtc/Xtc/XtcParser.cpp index f4bf62f..6d34b35 100644 --- a/lib/Xtc/Xtc/XtcParser.cpp +++ b/lib/Xtc/Xtc/XtcParser.cpp @@ -101,7 +101,9 @@ XtcError XtcParser::readHeader() { m_bitDepth = (m_header.magic == XTCH_MAGIC) ? 2 : 1; // Check version - if (m_header.version > 1) { + // Currently, version 1 is the only valid version, however some generators are using big endian for the version code + // so we also accept version 256 (0x0100) + if (m_header.version != 1 && m_header.version != 256) { Serial.printf("[%lu] [XTC] Unsupported version: %d\n", millis(), m_header.version); return XtcError::INVALID_VERSION; } From 85d76da96768fd253bf71a3e96c6d88a92395c85 Mon Sep 17 00:00:00 2001 From: Dave Allie Date: Tue, 30 Dec 2025 14:48:20 +1000 Subject: [PATCH 03/10] Split XTC file version into major minor bytes (#161) ## Summary * Split XTC file version into major minor bytes * Continue to support both 1.0 and 0.1 ## Additional Context * See https://github.com/daveallie/crosspoint-reader/issues/146#issuecomment-3698223951 for more detail FYI @treetrum @eunchurn --- lib/Xtc/Xtc/XtcParser.cpp | 15 +++++++++------ lib/Xtc/Xtc/XtcTypes.h | 3 ++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/Xtc/Xtc/XtcParser.cpp b/lib/Xtc/Xtc/XtcParser.cpp index 6d34b35..b1f4f90 100644 --- a/lib/Xtc/Xtc/XtcParser.cpp +++ b/lib/Xtc/Xtc/XtcParser.cpp @@ -101,10 +101,12 @@ XtcError XtcParser::readHeader() { m_bitDepth = (m_header.magic == XTCH_MAGIC) ? 2 : 1; // Check version - // Currently, version 1 is the only valid version, however some generators are using big endian for the version code - // so we also accept version 256 (0x0100) - if (m_header.version != 1 && m_header.version != 256) { - Serial.printf("[%lu] [XTC] Unsupported version: %d\n", millis(), m_header.version); + // Currently, version 1.0 is the only valid version, however some generators are swapping the bytes around, so we + // accept both 1.0 and 0.1 for compatibility + const bool validVersion = m_header.versionMajor == 1 && m_header.versionMinor == 0 || + m_header.versionMajor == 0 && m_header.versionMinor == 1; + if (!validVersion) { + Serial.printf("[%lu] [XTC] Unsupported version: %u.%u\n", millis(), m_header.versionMajor, m_header.versionMinor); return XtcError::INVALID_VERSION; } @@ -113,8 +115,9 @@ XtcError XtcParser::readHeader() { return XtcError::CORRUPTED_HEADER; } - Serial.printf("[%lu] [XTC] Header: magic=0x%08X (%s), ver=%u, pages=%u, bitDepth=%u\n", millis(), m_header.magic, - (m_header.magic == XTCH_MAGIC) ? "XTCH" : "XTC", m_header.version, m_header.pageCount, m_bitDepth); + Serial.printf("[%lu] [XTC] Header: magic=0x%08X (%s), ver=%u.%u, pages=%u, bitDepth=%u\n", millis(), m_header.magic, + (m_header.magic == XTCH_MAGIC) ? "XTCH" : "XTC", m_header.versionMajor, m_header.versionMinor, + m_header.pageCount, m_bitDepth); return XtcError::OK; } diff --git a/lib/Xtc/Xtc/XtcTypes.h b/lib/Xtc/Xtc/XtcTypes.h index eba8b32..08f9c00 100644 --- a/lib/Xtc/Xtc/XtcTypes.h +++ b/lib/Xtc/Xtc/XtcTypes.h @@ -35,7 +35,8 @@ constexpr uint16_t DISPLAY_HEIGHT = 800; #pragma pack(push, 1) struct XtcHeader { uint32_t magic; // 0x00: Magic number "XTC\0" (0x00435458) - uint16_t version; // 0x04: Format version (typically 1) + uint8_t versionMajor; // 0x04: Format version major (typically 1) (together with minor = 1.0) + uint8_t versionMinor; // 0x05: Format version minor (typically 0) uint16_t pageCount; // 0x06: Total page count uint32_t flags; // 0x08: Flags/reserved uint32_t headerSize; // 0x0C: Size of header section (typically 88) From d4bd119950041a1d461748c710ea6b749feb8c5b Mon Sep 17 00:00:00 2001 From: Jonas Diemer Date: Tue, 30 Dec 2025 06:05:06 +0100 Subject: [PATCH 04/10] Add option to apply format fix only on changed files (much faster) (#153) The default version parses a lot of files and takes ~5s on my machine. This adds an option `-g` to run only on files modified/staged in git. --- bin/clang-format-fix | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/bin/clang-format-fix b/bin/clang-format-fix index ba16ec9..92a4051 100755 --- a/bin/clang-format-fix +++ b/bin/clang-format-fix @@ -1,3 +1,26 @@ #!/bin/bash -find src lib \( -name "*.c" -o -name "*.cpp" -o -name "*.h" -o -name "*.hpp" \) -exec clang-format -style=file -i {} + +# Configuration: Standard arguments for clang-format +STYLE_ARGS="-style=file -i" + +# --- Main Logic --- + +if [[ "$1" == "-g" ]]; then + # Mode: Format all modified files (staged and unstaged) + + # Use 'git ls-files' to get a list of all files with pending changes: + # --modified: files tracked by git that have been modified (staged or unstaged) + # --exclude-standard: ignores files in .gitignore + git ls-files --modified --exclude-standard \ + | grep -E '\.(c|cpp|h|hpp)$' \ + | xargs -r clang-format $STYLE_ARGS + + # NOTE: We skip the 'git add' step from before. + # When running on unstaged files, 'clang-format -i' modifies them + # in the working directory, where they remain unstaged (M). + +else + # Executes original working command directly. + find src lib \( -name "*.c" -o -name "*.cpp" -o -name "*.h" -o -name "*.hpp" \) -exec clang-format $STYLE_ARGS {} + + +fi From fb5fc32c5d8afedf24ffeddf155ec0d908fc4763 Mon Sep 17 00:00:00 2001 From: Dave Allie Date: Tue, 30 Dec 2025 15:09:30 +1000 Subject: [PATCH 05/10] Add exFAT support (#150) ## Summary * Swap to updated SDCardManager which uses SdFat * Add exFAT support * Swap to using FsFile everywhere * Use newly exposed `SdMan` macro to get to static instance of SDCardManager * Move a bunch of FsHelpers up to SDCardManager --- lib/Epub/Epub.cpp | 50 +++++++------ lib/Epub/Epub.h | 2 + lib/Epub/Epub/BookMetadataCache.cpp | 61 ++++++++------- lib/Epub/Epub/BookMetadataCache.h | 16 ++-- lib/Epub/Epub/Page.cpp | 8 +- lib/Epub/Epub/Page.h | 12 +-- lib/Epub/Epub/Section.cpp | 69 +++++++++-------- lib/Epub/Epub/Section.h | 4 +- lib/Epub/Epub/blocks/TextBlock.cpp | 4 +- lib/Epub/Epub/blocks/TextBlock.h | 6 +- .../Epub/parsers/ChapterHtmlSlimParser.cpp | 6 +- lib/Epub/Epub/parsers/ContentOpfParser.cpp | 8 +- lib/Epub/Epub/parsers/ContentOpfParser.h | 2 +- lib/FsHelpers/FsHelpers.cpp | 74 ------------------- lib/FsHelpers/FsHelpers.h | 9 +-- lib/GfxRenderer/Bitmap.cpp | 10 +-- lib/GfxRenderer/Bitmap.h | 10 +-- lib/GfxRenderer/GfxRenderer.h | 1 - lib/JpegToBmpConverter/JpegToBmpConverter.cpp | 6 +- lib/JpegToBmpConverter/JpegToBmpConverter.h | 6 +- lib/Serialization/Serialization.h | 12 +-- lib/Xtc/Xtc.cpp | 18 ++--- lib/Xtc/Xtc/XtcParser.cpp | 7 +- lib/Xtc/Xtc/XtcParser.h | 4 +- lib/ZipFile/ZipFile.cpp | 56 +++++++------- lib/ZipFile/ZipFile.h | 4 +- open-x4-sdk | 2 +- platformio.ini | 2 + src/CrossPointSettings.cpp | 13 ++-- src/CrossPointState.cpp | 10 +-- src/MappedInputManager.cpp | 2 + src/MappedInputManager.h | 2 - src/WifiCredentialStore.cpp | 13 ++-- src/activities/Activity.h | 3 +- src/activities/boot_sleep/SleepActivity.cpp | 26 ++++--- src/activities/home/HomeActivity.cpp | 6 +- .../network/CrossPointWebServerActivity.cpp | 2 +- .../network/NetworkModeSelectionActivity.cpp | 2 +- .../network/WifiSelectionActivity.cpp | 1 + src/activities/reader/EpubReaderActivity.cpp | 11 +-- .../EpubReaderChapterSelectionActivity.cpp | 3 +- .../reader/FileSelectionActivity.cpp | 16 ++-- src/activities/reader/ReaderActivity.cpp | 6 +- src/activities/reader/XtcReaderActivity.cpp | 12 +-- .../XtcReaderChapterSelectionActivity.cpp | 3 +- src/activities/settings/OtaUpdateActivity.cpp | 2 +- src/activities/settings/SettingsActivity.cpp | 2 +- src/activities/util/KeyboardEntryActivity.cpp | 1 + src/main.cpp | 5 +- src/network/CrossPointWebServer.cpp | 34 +++++---- 50 files changed, 289 insertions(+), 355 deletions(-) diff --git a/lib/Epub/Epub.cpp b/lib/Epub/Epub.cpp index 941e11b..f4a2bfa 100644 --- a/lib/Epub/Epub.cpp +++ b/lib/Epub/Epub.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include "Epub/parsers/ContainerParser.h" @@ -94,13 +94,13 @@ bool Epub::parseTocNcxFile() const { Serial.printf("[%lu] [EBP] Parsing toc ncx file: %s\n", millis(), tocNcxItem.c_str()); const auto tmpNcxPath = getCachePath() + "/toc.ncx"; - File tempNcxFile; - if (!FsHelpers::openFileForWrite("EBP", tmpNcxPath, tempNcxFile)) { + FsFile tempNcxFile; + if (!SdMan.openFileForWrite("EBP", tmpNcxPath, tempNcxFile)) { return false; } readItemContentsToStream(tocNcxItem, tempNcxFile, 1024); tempNcxFile.close(); - if (!FsHelpers::openFileForRead("EBP", tmpNcxPath, tempNcxFile)) { + if (!SdMan.openFileForRead("EBP", tmpNcxPath, tempNcxFile)) { return false; } const auto ncxSize = tempNcxFile.size(); @@ -132,7 +132,7 @@ bool Epub::parseTocNcxFile() const { free(ncxBuffer); tempNcxFile.close(); - SD.remove(tmpNcxPath.c_str()); + SdMan.remove(tmpNcxPath.c_str()); Serial.printf("[%lu] [EBP] Parsed TOC items\n", millis()); return true; @@ -218,12 +218,12 @@ bool Epub::load() { } bool Epub::clearCache() const { - if (!SD.exists(cachePath.c_str())) { + if (!SdMan.exists(cachePath.c_str())) { Serial.printf("[%lu] [EPB] Cache does not exist, no action needed\n", millis()); return true; } - if (!FsHelpers::removeDir(cachePath.c_str())) { + if (!SdMan.removeDir(cachePath.c_str())) { Serial.printf("[%lu] [EPB] Failed to clear cache\n", millis()); return false; } @@ -233,17 +233,11 @@ bool Epub::clearCache() const { } void Epub::setupCacheDir() const { - if (SD.exists(cachePath.c_str())) { + if (SdMan.exists(cachePath.c_str())) { return; } - // Loop over each segment of the cache path and create directories as needed - for (size_t i = 1; i < cachePath.length(); i++) { - if (cachePath[i] == '/') { - SD.mkdir(cachePath.substr(0, i).c_str()); - } - } - SD.mkdir(cachePath.c_str()); + SdMan.mkdir(cachePath.c_str()); } const std::string& Epub::getCachePath() const { return cachePath; } @@ -263,7 +257,7 @@ std::string Epub::getCoverBmpPath() const { return cachePath + "/cover.bmp"; } bool Epub::generateCoverBmp() const { // Already generated, return true - if (SD.exists(getCoverBmpPath().c_str())) { + if (SdMan.exists(getCoverBmpPath().c_str())) { return true; } @@ -283,30 +277,30 @@ bool Epub::generateCoverBmp() const { Serial.printf("[%lu] [EBP] Generating BMP from JPG cover image\n", millis()); const auto coverJpgTempPath = getCachePath() + "/.cover.jpg"; - File coverJpg; - if (!FsHelpers::openFileForWrite("EBP", coverJpgTempPath, coverJpg)) { + FsFile coverJpg; + if (!SdMan.openFileForWrite("EBP", coverJpgTempPath, coverJpg)) { return false; } readItemContentsToStream(coverImageHref, coverJpg, 1024); coverJpg.close(); - if (!FsHelpers::openFileForRead("EBP", coverJpgTempPath, coverJpg)) { + if (!SdMan.openFileForRead("EBP", coverJpgTempPath, coverJpg)) { return false; } - File coverBmp; - if (!FsHelpers::openFileForWrite("EBP", getCoverBmpPath(), coverBmp)) { + FsFile coverBmp; + if (!SdMan.openFileForWrite("EBP", getCoverBmpPath(), coverBmp)) { coverJpg.close(); return false; } const bool success = JpegToBmpConverter::jpegFileToBmpStream(coverJpg, coverBmp); coverJpg.close(); coverBmp.close(); - SD.remove(coverJpgTempPath.c_str()); + SdMan.remove(coverJpgTempPath.c_str()); if (!success) { Serial.printf("[%lu] [EBP] Failed to generate BMP from JPG cover image\n", millis()); - SD.remove(getCoverBmpPath().c_str()); + SdMan.remove(getCoverBmpPath().c_str()); } Serial.printf("[%lu] [EBP] Generated BMP from JPG cover image, success: %s\n", millis(), success ? "yes" : "no"); return success; @@ -318,6 +312,11 @@ bool Epub::generateCoverBmp() const { } uint8_t* Epub::readItemContentsToBytes(const std::string& itemHref, size_t* size, const bool trailingNullByte) const { + if (itemHref.empty()) { + Serial.printf("[%lu] [EBP] Failed to read item, empty href\n", millis()); + return nullptr; + } + const std::string path = FsHelpers::normalisePath(itemHref); const auto content = ZipFile(filepath).readFileToMemory(path.c_str(), size, trailingNullByte); @@ -330,6 +329,11 @@ uint8_t* Epub::readItemContentsToBytes(const std::string& itemHref, size_t* size } bool Epub::readItemContentsToStream(const std::string& itemHref, Print& out, const size_t chunkSize) const { + if (itemHref.empty()) { + Serial.printf("[%lu] [EBP] Failed to read item, empty href\n", millis()); + return false; + } + const std::string path = FsHelpers::normalisePath(itemHref); return ZipFile(filepath).readFileToStream(path.c_str(), out, chunkSize); } diff --git a/lib/Epub/Epub.h b/lib/Epub/Epub.h index c785008..8ce1843 100644 --- a/lib/Epub/Epub.h +++ b/lib/Epub/Epub.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include diff --git a/lib/Epub/Epub/BookMetadataCache.cpp b/lib/Epub/Epub/BookMetadataCache.cpp index 8fcee28..6272414 100644 --- a/lib/Epub/Epub/BookMetadataCache.cpp +++ b/lib/Epub/Epub/BookMetadataCache.cpp @@ -1,7 +1,6 @@ #include "BookMetadataCache.h" #include -#include #include #include @@ -30,7 +29,7 @@ bool BookMetadataCache::beginContentOpfPass() { Serial.printf("[%lu] [BMC] Beginning content opf pass\n", millis()); // Open spine file for writing - return FsHelpers::openFileForWrite("BMC", cachePath + tmpSpineBinFile, spineFile); + return SdMan.openFileForWrite("BMC", cachePath + tmpSpineBinFile, spineFile); } bool BookMetadataCache::endContentOpfPass() { @@ -42,10 +41,10 @@ bool BookMetadataCache::beginTocPass() { Serial.printf("[%lu] [BMC] Beginning toc pass\n", millis()); // Open spine file for reading - if (!FsHelpers::openFileForRead("BMC", cachePath + tmpSpineBinFile, spineFile)) { + if (!SdMan.openFileForRead("BMC", cachePath + tmpSpineBinFile, spineFile)) { return false; } - if (!FsHelpers::openFileForWrite("BMC", cachePath + tmpTocBinFile, tocFile)) { + if (!SdMan.openFileForWrite("BMC", cachePath + tmpTocBinFile, tocFile)) { spineFile.close(); return false; } @@ -71,27 +70,27 @@ bool BookMetadataCache::endWrite() { bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMetadata& metadata) { // Open all three files, writing to meta, reading from spine and toc - if (!FsHelpers::openFileForWrite("BMC", cachePath + bookBinFile, bookFile)) { + if (!SdMan.openFileForWrite("BMC", cachePath + bookBinFile, bookFile)) { return false; } - if (!FsHelpers::openFileForRead("BMC", cachePath + tmpSpineBinFile, spineFile)) { + if (!SdMan.openFileForRead("BMC", cachePath + tmpSpineBinFile, spineFile)) { bookFile.close(); return false; } - if (!FsHelpers::openFileForRead("BMC", cachePath + tmpTocBinFile, tocFile)) { + if (!SdMan.openFileForRead("BMC", cachePath + tmpTocBinFile, tocFile)) { bookFile.close(); spineFile.close(); return false; } - constexpr size_t headerASize = - sizeof(BOOK_CACHE_VERSION) + /* LUT Offset */ sizeof(size_t) + sizeof(spineCount) + sizeof(tocCount); - const size_t metadataSize = + constexpr uint32_t headerASize = + sizeof(BOOK_CACHE_VERSION) + /* LUT Offset */ sizeof(uint32_t) + sizeof(spineCount) + sizeof(tocCount); + const uint32_t metadataSize = metadata.title.size() + metadata.author.size() + metadata.coverItemHref.size() + sizeof(uint32_t) * 3; - const size_t lutSize = sizeof(size_t) * spineCount + sizeof(size_t) * tocCount; - const size_t lutOffset = headerASize + metadataSize; + const uint32_t lutSize = sizeof(uint32_t) * spineCount + sizeof(uint32_t) * tocCount; + const uint32_t lutOffset = headerASize + metadataSize; // Header A serialization::writePod(bookFile, BOOK_CACHE_VERSION); @@ -106,7 +105,7 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta // Loop through spine entries, writing LUT positions spineFile.seek(0); for (int i = 0; i < spineCount; i++) { - auto pos = spineFile.position(); + uint32_t pos = spineFile.position(); auto spineEntry = readSpineEntry(spineFile); serialization::writePod(bookFile, pos + lutOffset + lutSize); } @@ -114,9 +113,9 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta // Loop through toc entries, writing LUT positions tocFile.seek(0); for (int i = 0; i < tocCount; i++) { - auto pos = tocFile.position(); + uint32_t pos = tocFile.position(); auto tocEntry = readTocEntry(tocFile); - serialization::writePod(bookFile, pos + lutOffset + lutSize + spineFile.position()); + serialization::writePod(bookFile, pos + lutOffset + lutSize + static_cast(spineFile.position())); } // LUTs complete @@ -142,7 +141,7 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta zip.close(); return false; } - size_t cumSize = 0; + uint32_t cumSize = 0; spineFile.seek(0); for (int i = 0; i < spineCount; i++) { auto spineEntry = readSpineEntry(spineFile); @@ -195,25 +194,25 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta } bool BookMetadataCache::cleanupTmpFiles() const { - if (SD.exists((cachePath + tmpSpineBinFile).c_str())) { - SD.remove((cachePath + tmpSpineBinFile).c_str()); + if (SdMan.exists((cachePath + tmpSpineBinFile).c_str())) { + SdMan.remove((cachePath + tmpSpineBinFile).c_str()); } - if (SD.exists((cachePath + tmpTocBinFile).c_str())) { - SD.remove((cachePath + tmpTocBinFile).c_str()); + if (SdMan.exists((cachePath + tmpTocBinFile).c_str())) { + SdMan.remove((cachePath + tmpTocBinFile).c_str()); } return true; } -size_t BookMetadataCache::writeSpineEntry(File& file, const SpineEntry& entry) const { - const auto pos = file.position(); +uint32_t BookMetadataCache::writeSpineEntry(FsFile& file, const SpineEntry& entry) const { + const uint32_t pos = file.position(); serialization::writeString(file, entry.href); serialization::writePod(file, entry.cumulativeSize); serialization::writePod(file, entry.tocIndex); return pos; } -size_t BookMetadataCache::writeTocEntry(File& file, const TocEntry& entry) const { - const auto pos = file.position(); +uint32_t BookMetadataCache::writeTocEntry(FsFile& file, const TocEntry& entry) const { + const uint32_t pos = file.position(); serialization::writeString(file, entry.title); serialization::writeString(file, entry.href); serialization::writeString(file, entry.anchor); @@ -267,7 +266,7 @@ void BookMetadataCache::createTocEntry(const std::string& title, const std::stri /* ============= READING / LOADING FUNCTIONS ================ */ bool BookMetadataCache::load() { - if (!FsHelpers::openFileForRead("BMC", cachePath + bookBinFile, bookFile)) { + if (!SdMan.openFileForRead("BMC", cachePath + bookBinFile, bookFile)) { return false; } @@ -304,8 +303,8 @@ BookMetadataCache::SpineEntry BookMetadataCache::getSpineEntry(const int index) } // Seek to spine LUT item, read from LUT and get out data - bookFile.seek(lutOffset + sizeof(size_t) * index); - size_t spineEntryPos; + bookFile.seek(lutOffset + sizeof(uint32_t) * index); + uint32_t spineEntryPos; serialization::readPod(bookFile, spineEntryPos); bookFile.seek(spineEntryPos); return readSpineEntry(bookFile); @@ -323,14 +322,14 @@ BookMetadataCache::TocEntry BookMetadataCache::getTocEntry(const int index) { } // Seek to TOC LUT item, read from LUT and get out data - bookFile.seek(lutOffset + sizeof(size_t) * spineCount + sizeof(size_t) * index); - size_t tocEntryPos; + bookFile.seek(lutOffset + sizeof(uint32_t) * spineCount + sizeof(uint32_t) * index); + uint32_t tocEntryPos; serialization::readPod(bookFile, tocEntryPos); bookFile.seek(tocEntryPos); return readTocEntry(bookFile); } -BookMetadataCache::SpineEntry BookMetadataCache::readSpineEntry(File& file) const { +BookMetadataCache::SpineEntry BookMetadataCache::readSpineEntry(FsFile& file) const { SpineEntry entry; serialization::readString(file, entry.href); serialization::readPod(file, entry.cumulativeSize); @@ -338,7 +337,7 @@ BookMetadataCache::SpineEntry BookMetadataCache::readSpineEntry(File& file) cons return entry; } -BookMetadataCache::TocEntry BookMetadataCache::readTocEntry(File& file) const { +BookMetadataCache::TocEntry BookMetadataCache::readTocEntry(FsFile& file) const { TocEntry entry; serialization::readString(file, entry.title); serialization::readString(file, entry.href); diff --git a/lib/Epub/Epub/BookMetadataCache.h b/lib/Epub/Epub/BookMetadataCache.h index 7f9f419..a6cf945 100644 --- a/lib/Epub/Epub/BookMetadataCache.h +++ b/lib/Epub/Epub/BookMetadataCache.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include @@ -46,15 +46,15 @@ class BookMetadataCache { bool loaded; bool buildMode; - File bookFile; + FsFile bookFile; // Temp file handles during build - File spineFile; - File tocFile; + FsFile spineFile; + FsFile tocFile; - size_t writeSpineEntry(File& file, const SpineEntry& entry) const; - size_t writeTocEntry(File& file, const TocEntry& entry) const; - SpineEntry readSpineEntry(File& file) const; - TocEntry readTocEntry(File& file) const; + uint32_t writeSpineEntry(FsFile& file, const SpineEntry& entry) const; + uint32_t writeTocEntry(FsFile& file, const TocEntry& entry) const; + SpineEntry readSpineEntry(FsFile& file) const; + TocEntry readTocEntry(FsFile& file) const; public: BookMetadata coreMetadata; diff --git a/lib/Epub/Epub/Page.cpp b/lib/Epub/Epub/Page.cpp index c50fe30..65ce569 100644 --- a/lib/Epub/Epub/Page.cpp +++ b/lib/Epub/Epub/Page.cpp @@ -7,7 +7,7 @@ void PageLine::render(GfxRenderer& renderer, const int fontId, const int xOffset block->render(renderer, fontId, xPos + xOffset, yPos + yOffset); } -bool PageLine::serialize(File& file) { +bool PageLine::serialize(FsFile& file) { serialization::writePod(file, xPos); serialization::writePod(file, yPos); @@ -15,7 +15,7 @@ bool PageLine::serialize(File& file) { return block->serialize(file); } -std::unique_ptr PageLine::deserialize(File& file) { +std::unique_ptr PageLine::deserialize(FsFile& file) { int16_t xPos; int16_t yPos; serialization::readPod(file, xPos); @@ -31,7 +31,7 @@ void Page::render(GfxRenderer& renderer, const int fontId, const int xOffset, co } } -bool Page::serialize(File& file) const { +bool Page::serialize(FsFile& file) const { const uint32_t count = elements.size(); serialization::writePod(file, count); @@ -46,7 +46,7 @@ bool Page::serialize(File& file) const { return true; } -std::unique_ptr Page::deserialize(File& file) { +std::unique_ptr Page::deserialize(FsFile& file) { auto page = std::unique_ptr(new Page()); uint32_t count; diff --git a/lib/Epub/Epub/Page.h b/lib/Epub/Epub/Page.h index 841ef6b..2006194 100644 --- a/lib/Epub/Epub/Page.h +++ b/lib/Epub/Epub/Page.h @@ -1,5 +1,5 @@ #pragma once -#include +#include #include #include @@ -18,7 +18,7 @@ class PageElement { explicit PageElement(const int16_t xPos, const int16_t yPos) : xPos(xPos), yPos(yPos) {} virtual ~PageElement() = default; virtual void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) = 0; - virtual bool serialize(File& file) = 0; + virtual bool serialize(FsFile& file) = 0; }; // a line from a block element @@ -29,8 +29,8 @@ class PageLine final : public PageElement { PageLine(std::shared_ptr block, const int16_t xPos, const int16_t yPos) : PageElement(xPos, yPos), block(std::move(block)) {} void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) override; - bool serialize(File& file) override; - static std::unique_ptr deserialize(File& file); + bool serialize(FsFile& file) override; + static std::unique_ptr deserialize(FsFile& file); }; class Page { @@ -38,6 +38,6 @@ class Page { // the list of block index and line numbers on this page std::vector> elements; void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) const; - bool serialize(File& file) const; - static std::unique_ptr deserialize(File& file); + bool serialize(FsFile& file) const; + static std::unique_ptr deserialize(FsFile& file); }; diff --git a/lib/Epub/Epub/Section.cpp b/lib/Epub/Epub/Section.cpp index 9cc19ea..b153f4f 100644 --- a/lib/Epub/Epub/Section.cpp +++ b/lib/Epub/Epub/Section.cpp @@ -1,7 +1,6 @@ #include "Section.h" -#include -#include +#include #include #include "Page.h" @@ -9,17 +8,17 @@ namespace { constexpr uint8_t SECTION_FILE_VERSION = 7; -constexpr size_t HEADER_SIZE = sizeof(uint8_t) + sizeof(int) + sizeof(float) + sizeof(bool) + sizeof(int) + - sizeof(int) + sizeof(int) + sizeof(size_t); +constexpr uint32_t HEADER_SIZE = sizeof(uint8_t) + sizeof(int) + sizeof(float) + sizeof(bool) + sizeof(int) + + sizeof(int) + sizeof(int) + sizeof(uint32_t); } // namespace -size_t Section::onPageComplete(std::unique_ptr page) { +uint32_t Section::onPageComplete(std::unique_ptr page) { if (!file) { Serial.printf("[%lu] [SCT] File not open for writing page %d\n", millis(), pageCount); return 0; } - const auto position = file.position(); + const uint32_t position = file.position(); if (!page->serialize(file)) { Serial.printf("[%lu] [SCT] Failed to serialize page %d\n", millis(), pageCount); return 0; @@ -38,7 +37,7 @@ void Section::writeSectionFileHeader(const int fontId, const float lineCompressi } static_assert(HEADER_SIZE == sizeof(SECTION_FILE_VERSION) + sizeof(fontId) + sizeof(lineCompression) + sizeof(extraParagraphSpacing) + sizeof(viewportWidth) + sizeof(viewportHeight) + - sizeof(pageCount) + sizeof(size_t), + sizeof(pageCount) + sizeof(uint32_t), "Header size mismatch"); serialization::writePod(file, SECTION_FILE_VERSION); serialization::writePod(file, fontId); @@ -47,12 +46,12 @@ void Section::writeSectionFileHeader(const int fontId, const float lineCompressi serialization::writePod(file, viewportWidth); serialization::writePod(file, viewportHeight); serialization::writePod(file, pageCount); // Placeholder for page count (will be initially 0 when written) - serialization::writePod(file, static_cast(0)); // Placeholder for LUT offset + serialization::writePod(file, static_cast(0)); // Placeholder for LUT offset } bool Section::loadSectionFile(const int fontId, const float lineCompression, const bool extraParagraphSpacing, const int viewportWidth, const int viewportHeight) { - if (!FsHelpers::openFileForRead("SCT", filePath, file)) { + if (!SdMan.openFileForRead("SCT", filePath, file)) { return false; } @@ -94,12 +93,12 @@ bool Section::loadSectionFile(const int fontId, const float lineCompression, con // Your updated class method (assuming you are using the 'SD' object, which is a wrapper for a specific filesystem) bool Section::clearCache() const { - if (!SD.exists(filePath.c_str())) { + if (!SdMan.exists(filePath.c_str())) { Serial.printf("[%lu] [SCT] Cache does not exist, no action needed\n", millis()); return true; } - if (!SD.remove(filePath.c_str())) { + if (!SdMan.remove(filePath.c_str())) { Serial.printf("[%lu] [SCT] Failed to clear cache\n", millis()); return false; } @@ -112,13 +111,19 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c const int viewportWidth, const int viewportHeight, const std::function& progressSetupFn, const std::function& progressFn) { - constexpr size_t MIN_SIZE_FOR_PROGRESS = 50 * 1024; // 50KB + constexpr uint32_t MIN_SIZE_FOR_PROGRESS = 50 * 1024; // 50KB const auto localPath = epub->getSpineItem(spineIndex).href; const auto tmpHtmlPath = epub->getCachePath() + "/.tmp_" + std::to_string(spineIndex) + ".html"; + // Create cache directory if it doesn't exist + { + const auto sectionsDir = epub->getCachePath() + "/sections"; + SdMan.mkdir(sectionsDir.c_str()); + } + // Retry logic for SD card timing issues bool success = false; - size_t fileSize = 0; + uint32_t fileSize = 0; for (int attempt = 0; attempt < 3 && !success; attempt++) { if (attempt > 0) { Serial.printf("[%lu] [SCT] Retrying stream (attempt %d)...\n", millis(), attempt + 1); @@ -126,12 +131,12 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c } // Remove any incomplete file from previous attempt before retrying - if (SD.exists(tmpHtmlPath.c_str())) { - SD.remove(tmpHtmlPath.c_str()); + if (SdMan.exists(tmpHtmlPath.c_str())) { + SdMan.remove(tmpHtmlPath.c_str()); } - File tmpHtml; - if (!FsHelpers::openFileForWrite("SCT", tmpHtmlPath, tmpHtml)) { + FsFile tmpHtml; + if (!SdMan.openFileForWrite("SCT", tmpHtmlPath, tmpHtml)) { continue; } success = epub->readItemContentsToStream(localPath, tmpHtml, 1024); @@ -139,8 +144,8 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c tmpHtml.close(); // If streaming failed, remove the incomplete file immediately - if (!success && SD.exists(tmpHtmlPath.c_str())) { - SD.remove(tmpHtmlPath.c_str()); + if (!success && SdMan.exists(tmpHtmlPath.c_str())) { + SdMan.remove(tmpHtmlPath.c_str()); Serial.printf("[%lu] [SCT] Removed incomplete temp file after failed attempt\n", millis()); } } @@ -157,11 +162,11 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c progressSetupFn(); } - if (!FsHelpers::openFileForWrite("SCT", filePath, file)) { + if (!SdMan.openFileForWrite("SCT", filePath, file)) { return false; } writeSectionFileHeader(fontId, lineCompression, extraParagraphSpacing, viewportWidth, viewportHeight); - std::vector lut = {}; + std::vector lut = {}; ChapterHtmlSlimParser visitor( tmpHtmlPath, renderer, fontId, lineCompression, extraParagraphSpacing, viewportWidth, viewportHeight, @@ -169,18 +174,18 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c progressFn); success = visitor.parseAndBuildPages(); - SD.remove(tmpHtmlPath.c_str()); + SdMan.remove(tmpHtmlPath.c_str()); if (!success) { Serial.printf("[%lu] [SCT] Failed to parse XML and build pages\n", millis()); file.close(); - SD.remove(filePath.c_str()); + SdMan.remove(filePath.c_str()); return false; } - const auto lutOffset = file.position(); + const uint32_t lutOffset = file.position(); bool hasFailedLutRecords = false; // Write LUT - for (const auto& pos : lut) { + for (const uint32_t& pos : lut) { if (pos == 0) { hasFailedLutRecords = true; break; @@ -191,12 +196,12 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c if (hasFailedLutRecords) { Serial.printf("[%lu] [SCT] Failed to write LUT due to invalid page positions\n", millis()); file.close(); - SD.remove(filePath.c_str()); + SdMan.remove(filePath.c_str()); return false; } // Go back and write LUT offset - file.seek(HEADER_SIZE - sizeof(size_t) - sizeof(pageCount)); + file.seek(HEADER_SIZE - sizeof(uint32_t) - sizeof(pageCount)); serialization::writePod(file, pageCount); serialization::writePod(file, lutOffset); file.close(); @@ -204,15 +209,15 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c } std::unique_ptr Section::loadPageFromSectionFile() { - if (!FsHelpers::openFileForRead("SCT", filePath, file)) { + if (!SdMan.openFileForRead("SCT", filePath, file)) { return nullptr; } - file.seek(HEADER_SIZE - sizeof(size_t)); - size_t lutOffset; + file.seek(HEADER_SIZE - sizeof(uint32_t)); + uint32_t lutOffset; serialization::readPod(file, lutOffset); - file.seek(lutOffset + sizeof(size_t) * currentPage); - size_t pagePos; + file.seek(lutOffset + sizeof(uint32_t) * currentPage); + uint32_t pagePos; serialization::readPod(file, pagePos); file.seek(pagePos); diff --git a/lib/Epub/Epub/Section.h b/lib/Epub/Epub/Section.h index 93e0d6c..bc2efab 100644 --- a/lib/Epub/Epub/Section.h +++ b/lib/Epub/Epub/Section.h @@ -12,11 +12,11 @@ class Section { const int spineIndex; GfxRenderer& renderer; std::string filePath; - File file; + FsFile file; void writeSectionFileHeader(int fontId, float lineCompression, bool extraParagraphSpacing, int viewportWidth, int viewportHeight); - size_t onPageComplete(std::unique_ptr page); + uint32_t onPageComplete(std::unique_ptr page); public: int pageCount = 0; diff --git a/lib/Epub/Epub/blocks/TextBlock.cpp b/lib/Epub/Epub/blocks/TextBlock.cpp index c20b37d..3119305 100644 --- a/lib/Epub/Epub/blocks/TextBlock.cpp +++ b/lib/Epub/Epub/blocks/TextBlock.cpp @@ -24,7 +24,7 @@ void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int } } -bool TextBlock::serialize(File& file) const { +bool TextBlock::serialize(FsFile& file) const { if (words.size() != wordXpos.size() || words.size() != wordStyles.size()) { Serial.printf("[%lu] [TXB] Serialization failed: size mismatch (words=%u, xpos=%u, styles=%u)\n", millis(), words.size(), wordXpos.size(), wordStyles.size()); @@ -43,7 +43,7 @@ bool TextBlock::serialize(File& file) const { return true; } -std::unique_ptr TextBlock::deserialize(File& file) { +std::unique_ptr TextBlock::deserialize(FsFile& file) { uint32_t wc; std::list words; std::list wordXpos; diff --git a/lib/Epub/Epub/blocks/TextBlock.h b/lib/Epub/Epub/blocks/TextBlock.h index 9dfde60..95d8884 100644 --- a/lib/Epub/Epub/blocks/TextBlock.h +++ b/lib/Epub/Epub/blocks/TextBlock.h @@ -1,6 +1,6 @@ #pragma once #include -#include +#include #include #include @@ -36,6 +36,6 @@ class TextBlock final : public Block { // given a renderer works out where to break the words into lines void render(const GfxRenderer& renderer, int fontId, int x, int y) const; BlockType getType() override { return TEXT_BLOCK; } - bool serialize(File& file) const; - static std::unique_ptr deserialize(File& file); + bool serialize(FsFile& file) const; + static std::unique_ptr deserialize(FsFile& file); }; diff --git a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp index b2dc2c0..a2b6189 100644 --- a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp +++ b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp @@ -1,8 +1,8 @@ #include "ChapterHtmlSlimParser.h" -#include #include #include +#include #include #include "../Page.h" @@ -218,8 +218,8 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() { return false; } - File file; - if (!FsHelpers::openFileForRead("EHP", filepath, file)) { + FsFile file; + if (!SdMan.openFileForRead("EHP", filepath, file)) { XML_ParserFree(parser); return false; } diff --git a/lib/Epub/Epub/parsers/ContentOpfParser.cpp b/lib/Epub/Epub/parsers/ContentOpfParser.cpp index a62b2d0..ae964ce 100644 --- a/lib/Epub/Epub/parsers/ContentOpfParser.cpp +++ b/lib/Epub/Epub/parsers/ContentOpfParser.cpp @@ -35,8 +35,8 @@ ContentOpfParser::~ContentOpfParser() { if (tempItemStore) { tempItemStore.close(); } - if (SD.exists((cachePath + itemCacheFile).c_str())) { - SD.remove((cachePath + itemCacheFile).c_str()); + if (SdMan.exists((cachePath + itemCacheFile).c_str())) { + SdMan.remove((cachePath + itemCacheFile).c_str()); } } @@ -104,7 +104,7 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name if (self->state == IN_PACKAGE && (strcmp(name, "manifest") == 0 || strcmp(name, "opf:manifest") == 0)) { self->state = IN_MANIFEST; - if (!FsHelpers::openFileForWrite("COF", self->cachePath + itemCacheFile, self->tempItemStore)) { + if (!SdMan.openFileForWrite("COF", self->cachePath + itemCacheFile, self->tempItemStore)) { Serial.printf( "[%lu] [COF] Couldn't open temp items file for writing. This is probably going to be a fatal error.\n", millis()); @@ -114,7 +114,7 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name if (self->state == IN_PACKAGE && (strcmp(name, "spine") == 0 || strcmp(name, "opf:spine") == 0)) { self->state = IN_SPINE; - if (!FsHelpers::openFileForRead("COF", self->cachePath + itemCacheFile, self->tempItemStore)) { + if (!SdMan.openFileForRead("COF", self->cachePath + itemCacheFile, self->tempItemStore)) { Serial.printf( "[%lu] [COF] Couldn't open temp items file for reading. This is probably going to be a fatal error.\n", millis()); diff --git a/lib/Epub/Epub/parsers/ContentOpfParser.h b/lib/Epub/Epub/parsers/ContentOpfParser.h index 5415de6..2a7dd48 100644 --- a/lib/Epub/Epub/parsers/ContentOpfParser.h +++ b/lib/Epub/Epub/parsers/ContentOpfParser.h @@ -22,7 +22,7 @@ class ContentOpfParser final : public Print { XML_Parser parser = nullptr; ParserState state = START; BookMetadataCache* cache; - File tempItemStore; + FsFile tempItemStore; std::string coverItemId; static void startElement(void* userData, const XML_Char* name, const XML_Char** atts); diff --git a/lib/FsHelpers/FsHelpers.cpp b/lib/FsHelpers/FsHelpers.cpp index c8b59ce..4bd8fbf 100644 --- a/lib/FsHelpers/FsHelpers.cpp +++ b/lib/FsHelpers/FsHelpers.cpp @@ -1,81 +1,7 @@ #include "FsHelpers.h" -#include - #include -bool FsHelpers::openFileForRead(const char* moduleName, const char* path, File& file) { - if (!SD.exists(path)) { - Serial.printf("[%lu] [%s] File does not exist: %s\n", millis(), moduleName, path); - return false; - } - - file = SD.open(path, FILE_READ); - if (!file) { - Serial.printf("[%lu] [%s] Failed to open file for reading: %s\n", millis(), moduleName, path); - return false; - } - return true; -} - -bool FsHelpers::openFileForRead(const char* moduleName, const std::string& path, File& file) { - return openFileForRead(moduleName, path.c_str(), file); -} - -bool FsHelpers::openFileForRead(const char* moduleName, const String& path, File& file) { - return openFileForRead(moduleName, path.c_str(), file); -} - -bool FsHelpers::openFileForWrite(const char* moduleName, const char* path, File& file) { - file = SD.open(path, FILE_WRITE, true); - if (!file) { - Serial.printf("[%lu] [%s] Failed to open file for writing: %s\n", millis(), moduleName, path); - return false; - } - return true; -} - -bool FsHelpers::openFileForWrite(const char* moduleName, const std::string& path, File& file) { - return openFileForWrite(moduleName, path.c_str(), file); -} - -bool FsHelpers::openFileForWrite(const char* moduleName, const String& path, File& file) { - return openFileForWrite(moduleName, path.c_str(), file); -} - -bool FsHelpers::removeDir(const char* path) { - // 1. Open the directory - File dir = SD.open(path); - if (!dir) { - return false; - } - if (!dir.isDirectory()) { - return false; - } - - File file = dir.openNextFile(); - while (file) { - String filePath = path; - if (!filePath.endsWith("/")) { - filePath += "/"; - } - filePath += file.name(); - - if (file.isDirectory()) { - if (!removeDir(filePath.c_str())) { - return false; - } - } else { - if (!SD.remove(filePath.c_str())) { - return false; - } - } - file = dir.openNextFile(); - } - - return SD.rmdir(path); -} - std::string FsHelpers::normalisePath(const std::string& path) { std::vector components; std::string component; diff --git a/lib/FsHelpers/FsHelpers.h b/lib/FsHelpers/FsHelpers.h index 0dff145..5bb4218 100644 --- a/lib/FsHelpers/FsHelpers.h +++ b/lib/FsHelpers/FsHelpers.h @@ -1,14 +1,7 @@ #pragma once -#include +#include class FsHelpers { public: - static bool openFileForRead(const char* moduleName, const char* path, File& file); - static bool openFileForRead(const char* moduleName, const std::string& path, File& file); - static bool openFileForRead(const char* moduleName, const String& path, File& file); - static bool openFileForWrite(const char* moduleName, const char* path, File& file); - static bool openFileForWrite(const char* moduleName, const std::string& path, File& file); - static bool openFileForWrite(const char* moduleName, const String& path, File& file); - static bool removeDir(const char* path); static std::string normalisePath(const std::string& path); }; diff --git a/lib/GfxRenderer/Bitmap.cpp b/lib/GfxRenderer/Bitmap.cpp index a034c75..7c46df1 100644 --- a/lib/GfxRenderer/Bitmap.cpp +++ b/lib/GfxRenderer/Bitmap.cpp @@ -123,7 +123,7 @@ Bitmap::~Bitmap() { delete[] errorNextRow; } -uint16_t Bitmap::readLE16(File& f) { +uint16_t Bitmap::readLE16(FsFile& f) { const int c0 = f.read(); const int c1 = f.read(); const auto b0 = static_cast(c0 < 0 ? 0 : c0); @@ -131,7 +131,7 @@ uint16_t Bitmap::readLE16(File& f) { return static_cast(b0) | (static_cast(b1) << 8); } -uint32_t Bitmap::readLE32(File& f) { +uint32_t Bitmap::readLE32(FsFile& f) { const int c0 = f.read(); const int c1 = f.read(); const int c2 = f.read(); @@ -192,7 +192,7 @@ BmpReaderError Bitmap::parseHeaders() { const uint16_t bfType = readLE16(file); if (bfType != 0x4D42) return BmpReaderError::NotBMP; - file.seek(8, SeekCur); + file.seekCur(8); bfOffBits = readLE32(file); // --- DIB HEADER --- @@ -214,10 +214,10 @@ BmpReaderError Bitmap::parseHeaders() { // Allow BI_RGB (0) for all, and BI_BITFIELDS (3) for 32bpp which is common for BGRA masks. if (!(comp == 0 || (bpp == 32 && comp == 3))) return BmpReaderError::UnsupportedCompression; - file.seek(12, SeekCur); // biSizeImage, biXPelsPerMeter, biYPelsPerMeter + file.seekCur(12); // biSizeImage, biXPelsPerMeter, biYPelsPerMeter const uint32_t colorsUsed = readLE32(file); if (colorsUsed > 256u) return BmpReaderError::PaletteTooLarge; - file.seek(4, SeekCur); // biClrImportant + file.seekCur(4); // biClrImportant if (width <= 0 || height <= 0) return BmpReaderError::BadDimensions; diff --git a/lib/GfxRenderer/Bitmap.h b/lib/GfxRenderer/Bitmap.h index 744cb61..7e79964 100644 --- a/lib/GfxRenderer/Bitmap.h +++ b/lib/GfxRenderer/Bitmap.h @@ -1,6 +1,6 @@ #pragma once -#include +#include enum class BmpReaderError : uint8_t { Ok = 0, @@ -28,7 +28,7 @@ class Bitmap { public: static const char* errorToString(BmpReaderError err); - explicit Bitmap(File& file) : file(file) {} + explicit Bitmap(FsFile& file) : file(file) {} ~Bitmap(); BmpReaderError parseHeaders(); BmpReaderError readRow(uint8_t* data, uint8_t* rowBuffer, int rowY) const; @@ -40,10 +40,10 @@ class Bitmap { int getRowBytes() const { return rowBytes; } private: - static uint16_t readLE16(File& f); - static uint32_t readLE32(File& f); + static uint16_t readLE16(FsFile& f); + static uint32_t readLE32(FsFile& f); - File& file; + FsFile& file; int width = 0; int height = 0; bool topDown = false; diff --git a/lib/GfxRenderer/GfxRenderer.h b/lib/GfxRenderer/GfxRenderer.h index 241c76e..f6f5fe0 100644 --- a/lib/GfxRenderer/GfxRenderer.h +++ b/lib/GfxRenderer/GfxRenderer.h @@ -2,7 +2,6 @@ #include #include -#include #include diff --git a/lib/JpegToBmpConverter/JpegToBmpConverter.cpp b/lib/JpegToBmpConverter/JpegToBmpConverter.cpp index 0a19701..9c61ef0 100644 --- a/lib/JpegToBmpConverter/JpegToBmpConverter.cpp +++ b/lib/JpegToBmpConverter/JpegToBmpConverter.cpp @@ -1,5 +1,7 @@ #include "JpegToBmpConverter.h" +#include +#include #include #include @@ -7,7 +9,7 @@ // Context structure for picojpeg callback struct JpegReadContext { - File& file; + FsFile& file; uint8_t buffer[512]; size_t bufferPos; size_t bufferFilled; @@ -426,7 +428,7 @@ unsigned char JpegToBmpConverter::jpegReadCallback(unsigned char* pBuf, const un } // Core function: Convert JPEG file to 2-bit BMP -bool JpegToBmpConverter::jpegFileToBmpStream(File& jpegFile, Print& bmpOut) { +bool JpegToBmpConverter::jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut) { Serial.printf("[%lu] [JPG] Converting JPEG to BMP\n", millis()); // Setup context for picojpeg callback diff --git a/lib/JpegToBmpConverter/JpegToBmpConverter.h b/lib/JpegToBmpConverter/JpegToBmpConverter.h index 1cb76e5..f61bd8e 100644 --- a/lib/JpegToBmpConverter/JpegToBmpConverter.h +++ b/lib/JpegToBmpConverter/JpegToBmpConverter.h @@ -1,7 +1,7 @@ #pragma once -#include - +class FsFile; +class Print; class ZipFile; class JpegToBmpConverter { @@ -11,5 +11,5 @@ class JpegToBmpConverter { unsigned char* pBytes_actually_read, void* pCallback_data); public: - static bool jpegFileToBmpStream(File& jpegFile, Print& bmpOut); + static bool jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut); }; diff --git a/lib/Serialization/Serialization.h b/lib/Serialization/Serialization.h index e6bcbf2..afea564 100644 --- a/lib/Serialization/Serialization.h +++ b/lib/Serialization/Serialization.h @@ -1,5 +1,5 @@ #pragma once -#include +#include #include @@ -10,7 +10,7 @@ static void writePod(std::ostream& os, const T& value) { } template -static void writePod(File& file, const T& value) { +static void writePod(FsFile& file, const T& value) { file.write(reinterpret_cast(&value), sizeof(T)); } @@ -20,7 +20,7 @@ static void readPod(std::istream& is, T& value) { } template -static void readPod(File& file, T& value) { +static void readPod(FsFile& file, T& value) { file.read(reinterpret_cast(&value), sizeof(T)); } @@ -30,7 +30,7 @@ static void writeString(std::ostream& os, const std::string& s) { os.write(s.data(), len); } -static void writeString(File& file, const std::string& s) { +static void writeString(FsFile& file, const std::string& s) { const uint32_t len = s.size(); writePod(file, len); file.write(reinterpret_cast(s.data()), len); @@ -43,10 +43,10 @@ static void readString(std::istream& is, std::string& s) { is.read(&s[0], len); } -static void readString(File& file, std::string& s) { +static void readString(FsFile& file, std::string& s) { uint32_t len; readPod(file, len); s.resize(len); - file.read(reinterpret_cast(&s[0]), len); + file.read(&s[0], len); } } // namespace serialization diff --git a/lib/Xtc/Xtc.cpp b/lib/Xtc/Xtc.cpp index 9e948ff..8f79c9d 100644 --- a/lib/Xtc/Xtc.cpp +++ b/lib/Xtc/Xtc.cpp @@ -9,7 +9,7 @@ #include #include -#include +#include bool Xtc::load() { Serial.printf("[%lu] [XTC] Loading XTC: %s\n", millis(), filepath.c_str()); @@ -31,12 +31,12 @@ bool Xtc::load() { } bool Xtc::clearCache() const { - if (!SD.exists(cachePath.c_str())) { + if (!SdMan.exists(cachePath.c_str())) { Serial.printf("[%lu] [XTC] Cache does not exist, no action needed\n", millis()); return true; } - if (!FsHelpers::removeDir(cachePath.c_str())) { + if (!SdMan.removeDir(cachePath.c_str())) { Serial.printf("[%lu] [XTC] Failed to clear cache\n", millis()); return false; } @@ -46,17 +46,17 @@ bool Xtc::clearCache() const { } void Xtc::setupCacheDir() const { - if (SD.exists(cachePath.c_str())) { + if (SdMan.exists(cachePath.c_str())) { return; } // Create directories recursively for (size_t i = 1; i < cachePath.length(); i++) { if (cachePath[i] == '/') { - SD.mkdir(cachePath.substr(0, i).c_str()); + SdMan.mkdir(cachePath.substr(0, i).c_str()); } } - SD.mkdir(cachePath.c_str()); + SdMan.mkdir(cachePath.c_str()); } std::string Xtc::getTitle() const { @@ -106,7 +106,7 @@ std::string Xtc::getCoverBmpPath() const { return cachePath + "/cover.bmp"; } bool Xtc::generateCoverBmp() const { // Already generated - if (SD.exists(getCoverBmpPath().c_str())) { + if (SdMan.exists(getCoverBmpPath().c_str())) { return true; } @@ -157,8 +157,8 @@ bool Xtc::generateCoverBmp() const { } // Create BMP file - File coverBmp; - if (!FsHelpers::openFileForWrite("XTC", getCoverBmpPath(), coverBmp)) { + FsFile coverBmp; + if (!SdMan.openFileForWrite("XTC", getCoverBmpPath(), coverBmp)) { Serial.printf("[%lu] [XTC] Failed to create cover BMP file\n", millis()); free(pageBuffer); return false; diff --git a/lib/Xtc/Xtc/XtcParser.cpp b/lib/Xtc/Xtc/XtcParser.cpp index b1f4f90..c33e719 100644 --- a/lib/Xtc/Xtc/XtcParser.cpp +++ b/lib/Xtc/Xtc/XtcParser.cpp @@ -9,6 +9,7 @@ #include #include +#include #include @@ -33,7 +34,7 @@ XtcError XtcParser::open(const char* filepath) { } // Open file - if (!FsHelpers::openFileForRead("XTC", filepath, m_file)) { + if (!SdMan.openFileForRead("XTC", filepath, m_file)) { m_lastError = XtcError::FILE_NOT_FOUND; return m_lastError; } @@ -419,8 +420,8 @@ XtcError XtcParser::loadPageStreaming(uint32_t pageIndex, } bool XtcParser::isValidXtcFile(const char* filepath) { - File file = SD.open(filepath, FILE_READ); - if (!file) { + FsFile file; + if (!SdMan.openFileForRead("XTC", filepath, file)) { return false; } diff --git a/lib/Xtc/Xtc/XtcParser.h b/lib/Xtc/Xtc/XtcParser.h index 25f1c33..2d2b780 100644 --- a/lib/Xtc/Xtc/XtcParser.h +++ b/lib/Xtc/Xtc/XtcParser.h @@ -7,7 +7,7 @@ #pragma once -#include +#include #include #include @@ -80,7 +80,7 @@ class XtcParser { XtcError getLastError() const { return m_lastError; } private: - File m_file; + FsFile m_file; bool m_isOpen; XtcHeader m_header; std::vector m_pageTable; diff --git a/lib/ZipFile/ZipFile.cpp b/lib/ZipFile/ZipFile.cpp index 23cf0e8..2a97858 100644 --- a/lib/ZipFile/ZipFile.cpp +++ b/lib/ZipFile/ZipFile.cpp @@ -1,7 +1,7 @@ #include "ZipFile.h" -#include #include +#include #include bool inflateOneShot(const uint8_t* inputBuf, const size_t deflatedSize, uint8_t* outputBuf, const size_t inflatedSize) { @@ -49,29 +49,29 @@ bool ZipFile::loadAllFileStatSlims() { fileStatSlimCache.reserve(zipDetails.totalEntries); while (file.available()) { - file.read(reinterpret_cast(&sig), 4); + file.read(&sig, 4); if (sig != 0x02014b50) break; // End of list FileStatSlim fileStat = {}; - file.seek(6, SeekCur); - file.read(reinterpret_cast(&fileStat.method), 2); - file.seek(8, SeekCur); - file.read(reinterpret_cast(&fileStat.compressedSize), 4); - file.read(reinterpret_cast(&fileStat.uncompressedSize), 4); + file.seekCur(6); + file.read(&fileStat.method, 2); + file.seekCur(8); + file.read(&fileStat.compressedSize, 4); + file.read(&fileStat.uncompressedSize, 4); uint16_t nameLen, m, k; - file.read(reinterpret_cast(&nameLen), 2); - file.read(reinterpret_cast(&m), 2); - file.read(reinterpret_cast(&k), 2); - file.seek(8, SeekCur); - file.read(reinterpret_cast(&fileStat.localHeaderOffset), 4); - file.read(reinterpret_cast(itemName), nameLen); + file.read(&nameLen, 2); + file.read(&m, 2); + file.read(&k, 2); + file.seekCur(8); + file.read(&fileStat.localHeaderOffset, 4); + file.read(itemName, nameLen); itemName[nameLen] = '\0'; fileStatSlimCache.emplace(itemName, fileStat); // Skip the rest of this entry (extra field + comment) - file.seek(m + k, SeekCur); + file.seekCur(m + k); } if (!wasOpen) { @@ -109,21 +109,21 @@ bool ZipFile::loadFileStatSlim(const char* filename, FileStatSlim* fileStat) { bool found = false; while (file.available()) { - file.read(reinterpret_cast(&sig), 4); + file.read(&sig, 4); if (sig != 0x02014b50) break; // End of list - file.seek(6, SeekCur); - file.read(reinterpret_cast(&fileStat->method), 2); - file.seek(8, SeekCur); - file.read(reinterpret_cast(&fileStat->compressedSize), 4); - file.read(reinterpret_cast(&fileStat->uncompressedSize), 4); + file.seekCur(6); + file.read(&fileStat->method, 2); + file.seekCur(8); + file.read(&fileStat->compressedSize, 4); + file.read(&fileStat->uncompressedSize, 4); uint16_t nameLen, m, k; - file.read(reinterpret_cast(&nameLen), 2); - file.read(reinterpret_cast(&m), 2); - file.read(reinterpret_cast(&k), 2); - file.seek(8, SeekCur); - file.read(reinterpret_cast(&fileStat->localHeaderOffset), 4); - file.read(reinterpret_cast(itemName), nameLen); + file.read(&nameLen, 2); + file.read(&m, 2); + file.read(&k, 2); + file.seekCur(8); + file.read(&fileStat->localHeaderOffset, 4); + file.read(itemName, nameLen); itemName[nameLen] = '\0'; if (strcmp(itemName, filename) == 0) { @@ -132,7 +132,7 @@ bool ZipFile::loadFileStatSlim(const char* filename, FileStatSlim* fileStat) { } // Skip the rest of this entry (extra field + comment) - file.seek(m + k, SeekCur); + file.seekCur(m + k); } if (!wasOpen) { @@ -243,7 +243,7 @@ bool ZipFile::loadZipDetails() { } bool ZipFile::open() { - if (!FsHelpers::openFileForRead("ZIP", filePath, file)) { + if (!SdMan.openFileForRead("ZIP", filePath, file)) { return false; } return true; diff --git a/lib/ZipFile/ZipFile.h b/lib/ZipFile/ZipFile.h index 4758f16..0144ed4 100644 --- a/lib/ZipFile/ZipFile.h +++ b/lib/ZipFile/ZipFile.h @@ -1,5 +1,5 @@ #pragma once -#include +#include #include #include @@ -21,7 +21,7 @@ class ZipFile { private: const std::string& filePath; - File file; + FsFile file; ZipDetails zipDetails = {0, 0, false}; std::unordered_map fileStatSlimCache; diff --git a/open-x4-sdk b/open-x4-sdk index 98a5aa1..bd4e670 160000 --- a/open-x4-sdk +++ b/open-x4-sdk @@ -1 +1 @@ -Subproject commit 98a5aa1f8969ccd317c9b45bf0fa84b6c82e167f +Subproject commit bd4e6707503ab9c97d13ee0d8f8c69e9ff03cd12 diff --git a/platformio.ini b/platformio.ini index fb520e5..142307c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -21,6 +21,7 @@ build_flags = -DARDUINO_USB_CDC_ON_BOOT=1 -DMINIZ_NO_ZLIB_COMPATIBLE_NAMES=1 -DEINK_DISPLAY_SINGLE_BUFFER_MODE=1 + -DDISABLE_FS_H_WARNING=1 # https://libexpat.github.io/doc/api/latest/#XML_GE -DXML_GE=0 -DXML_CONTEXT_BYTES=1024 @@ -39,6 +40,7 @@ lib_deps = BatteryMonitor=symlink://open-x4-sdk/libs/hardware/BatteryMonitor InputManager=symlink://open-x4-sdk/libs/hardware/InputManager EInkDisplay=symlink://open-x4-sdk/libs/display/EInkDisplay + SDCardManager=symlink://open-x4-sdk/libs/hardware/SDCardManager ArduinoJson @ 7.4.2 QRCode @ 0.0.1 diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index 41c3322..0f2f13e 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -1,8 +1,7 @@ #include "CrossPointSettings.h" -#include #include -#include +#include #include // Initialize the static instance @@ -17,10 +16,10 @@ constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin"; bool CrossPointSettings::saveToFile() const { // Make sure the directory exists - SD.mkdir("/.crosspoint"); + SdMan.mkdir("/.crosspoint"); - File outputFile; - if (!FsHelpers::openFileForWrite("CPS", SETTINGS_FILE, outputFile)) { + FsFile outputFile; + if (!SdMan.openFileForWrite("CPS", SETTINGS_FILE, outputFile)) { return false; } @@ -40,8 +39,8 @@ bool CrossPointSettings::saveToFile() const { } bool CrossPointSettings::loadFromFile() { - File inputFile; - if (!FsHelpers::openFileForRead("CPS", SETTINGS_FILE, inputFile)) { + FsFile inputFile; + if (!SdMan.openFileForRead("CPS", SETTINGS_FILE, inputFile)) { return false; } diff --git a/src/CrossPointState.cpp b/src/CrossPointState.cpp index 9010822..31cb2ac 100644 --- a/src/CrossPointState.cpp +++ b/src/CrossPointState.cpp @@ -1,7 +1,7 @@ #include "CrossPointState.h" -#include #include +#include #include namespace { @@ -12,8 +12,8 @@ constexpr char STATE_FILE[] = "/.crosspoint/state.bin"; CrossPointState CrossPointState::instance; bool CrossPointState::saveToFile() const { - File outputFile; - if (!FsHelpers::openFileForWrite("CPS", STATE_FILE, outputFile)) { + FsFile outputFile; + if (!SdMan.openFileForWrite("CPS", STATE_FILE, outputFile)) { return false; } @@ -24,8 +24,8 @@ bool CrossPointState::saveToFile() const { } bool CrossPointState::loadFromFile() { - File inputFile; - if (!FsHelpers::openFileForRead("CPS", STATE_FILE, inputFile)) { + FsFile inputFile; + if (!SdMan.openFileForRead("CPS", STATE_FILE, inputFile)) { return false; } diff --git a/src/MappedInputManager.cpp b/src/MappedInputManager.cpp index 7ee2295..17be4cf 100644 --- a/src/MappedInputManager.cpp +++ b/src/MappedInputManager.cpp @@ -1,5 +1,7 @@ #include "MappedInputManager.h" +#include "CrossPointSettings.h" + decltype(InputManager::BTN_BACK) MappedInputManager::mapButton(const Button button) const { const auto frontLayout = static_cast(SETTINGS.frontButtonLayout); const auto sideLayout = static_cast(SETTINGS.sideButtonLayout); diff --git a/src/MappedInputManager.h b/src/MappedInputManager.h index 138d793..62065fe 100644 --- a/src/MappedInputManager.h +++ b/src/MappedInputManager.h @@ -2,8 +2,6 @@ #include -#include "CrossPointSettings.h" - class MappedInputManager { public: enum class Button { Back, Confirm, Left, Right, Up, Down, Power, PageBack, PageForward }; diff --git a/src/WifiCredentialStore.cpp b/src/WifiCredentialStore.cpp index 856098f..be865b8 100644 --- a/src/WifiCredentialStore.cpp +++ b/src/WifiCredentialStore.cpp @@ -1,8 +1,7 @@ #include "WifiCredentialStore.h" -#include #include -#include +#include #include // Initialize the static instance @@ -30,10 +29,10 @@ void WifiCredentialStore::obfuscate(std::string& data) const { bool WifiCredentialStore::saveToFile() const { // Make sure the directory exists - SD.mkdir("/.crosspoint"); + SdMan.mkdir("/.crosspoint"); - File file; - if (!FsHelpers::openFileForWrite("WCS", WIFI_FILE, file)) { + FsFile file; + if (!SdMan.openFileForWrite("WCS", WIFI_FILE, file)) { return false; } @@ -60,8 +59,8 @@ bool WifiCredentialStore::saveToFile() const { } bool WifiCredentialStore::loadFromFile() { - File file; - if (!FsHelpers::openFileForRead("WCS", WIFI_FILE, file)) { + FsFile file; + if (!SdMan.openFileForRead("WCS", WIFI_FILE, file)) { return false; } diff --git a/src/activities/Activity.h b/src/activities/Activity.h index 66dfde1..aad5591 100644 --- a/src/activities/Activity.h +++ b/src/activities/Activity.h @@ -5,8 +5,7 @@ #include #include -#include "../MappedInputManager.h" - +class MappedInputManager; class GfxRenderer; class Activity { diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index fdf84b6..fa60523 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include @@ -58,29 +58,31 @@ void SleepActivity::renderPopup(const char* message) const { void SleepActivity::renderCustomSleepScreen() const { // Check if we have a /sleep directory - auto dir = SD.open("/sleep"); + auto dir = SdMan.open("/sleep"); if (dir && dir.isDirectory()) { std::vector files; + char name[128]; // collect all valid BMP files - for (File file = dir.openNextFile(); file; file = dir.openNextFile()) { + for (auto file = dir.openNextFile(); file; file = dir.openNextFile()) { if (file.isDirectory()) { file.close(); continue; } - auto filename = std::string(file.name()); + file.getName(name, sizeof(name)); + auto filename = std::string(name); if (filename[0] == '.') { file.close(); continue; } if (filename.substr(filename.length() - 4) != ".bmp") { - Serial.printf("[%lu] [SLP] Skipping non-.bmp file name: %s\n", millis(), file.name()); + Serial.printf("[%lu] [SLP] Skipping non-.bmp file name: %s\n", millis(), name); file.close(); continue; } Bitmap bitmap(file); if (bitmap.parseHeaders() != BmpReaderError::Ok) { - Serial.printf("[%lu] [SLP] Skipping invalid BMP file: %s\n", millis(), file.name()); + Serial.printf("[%lu] [SLP] Skipping invalid BMP file: %s\n", millis(), name); file.close(); continue; } @@ -92,8 +94,8 @@ void SleepActivity::renderCustomSleepScreen() const { // Generate a random number between 1 and numFiles const auto randomFileIndex = random(numFiles); const auto filename = "/sleep/" + files[randomFileIndex]; - File file; - if (FsHelpers::openFileForRead("SLP", filename, file)) { + FsFile file; + if (SdMan.openFileForRead("SLP", filename, file)) { Serial.printf("[%lu] [SLP] Randomly loading: /sleep/%s\n", millis(), files[randomFileIndex].c_str()); delay(100); Bitmap bitmap(file); @@ -109,8 +111,8 @@ void SleepActivity::renderCustomSleepScreen() const { // Look for sleep.bmp on the root of the sd card to determine if we should // render a custom sleep screen instead of the default. - File file; - if (FsHelpers::openFileForRead("SLP", "/sleep.bmp", file)) { + FsFile file; + if (SdMan.openFileForRead("SLP", "/sleep.bmp", file)) { Bitmap bitmap(file); if (bitmap.parseHeaders() == BmpReaderError::Ok) { Serial.printf("[%lu] [SLP] Loading: /sleep.bmp\n", millis()); @@ -224,8 +226,8 @@ void SleepActivity::renderCoverSleepScreen() const { coverBmpPath = lastEpub.getCoverBmpPath(); } - File file; - if (FsHelpers::openFileForRead("SLP", coverBmpPath, file)) { + FsFile file; + if (SdMan.openFileForRead("SLP", coverBmpPath, file)) { Bitmap bitmap(file); if (bitmap.parseHeaders() == BmpReaderError::Ok) { renderBitmapSleepScreen(bitmap); diff --git a/src/activities/home/HomeActivity.cpp b/src/activities/home/HomeActivity.cpp index 1dee518..8291826 100644 --- a/src/activities/home/HomeActivity.cpp +++ b/src/activities/home/HomeActivity.cpp @@ -1,10 +1,10 @@ #include "HomeActivity.h" #include -#include -#include +#include #include "CrossPointState.h" +#include "MappedInputManager.h" #include "config.h" void HomeActivity::taskTrampoline(void* param) { @@ -20,7 +20,7 @@ void HomeActivity::onEnter() { renderingMutex = xSemaphoreCreateMutex(); // Check if we have a book to continue reading - hasContinueReading = !APP_STATE.openEpubPath.empty() && SD.exists(APP_STATE.openEpubPath.c_str()); + hasContinueReading = !APP_STATE.openEpubPath.empty() && SdMan.exists(APP_STATE.openEpubPath.c_str()); selectorIndex = 0; diff --git a/src/activities/network/CrossPointWebServerActivity.cpp b/src/activities/network/CrossPointWebServerActivity.cpp index eda20a8..a0db3c3 100644 --- a/src/activities/network/CrossPointWebServerActivity.cpp +++ b/src/activities/network/CrossPointWebServerActivity.cpp @@ -3,12 +3,12 @@ #include #include #include -#include #include #include #include +#include "MappedInputManager.h" #include "NetworkModeSelectionActivity.h" #include "WifiSelectionActivity.h" #include "config.h" diff --git a/src/activities/network/NetworkModeSelectionActivity.cpp b/src/activities/network/NetworkModeSelectionActivity.cpp index 0cc3594..57afc6e 100644 --- a/src/activities/network/NetworkModeSelectionActivity.cpp +++ b/src/activities/network/NetworkModeSelectionActivity.cpp @@ -1,8 +1,8 @@ #include "NetworkModeSelectionActivity.h" #include -#include +#include "MappedInputManager.h" #include "config.h" namespace { diff --git a/src/activities/network/WifiSelectionActivity.cpp b/src/activities/network/WifiSelectionActivity.cpp index 9f0e502..8977a78 100644 --- a/src/activities/network/WifiSelectionActivity.cpp +++ b/src/activities/network/WifiSelectionActivity.cpp @@ -5,6 +5,7 @@ #include +#include "MappedInputManager.h" #include "WifiCredentialStore.h" #include "activities/util/KeyboardEntryActivity.h" #include "config.h" diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 6864a56..ab03550 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -3,12 +3,13 @@ #include #include #include -#include +#include #include "Battery.h" #include "CrossPointSettings.h" #include "CrossPointState.h" #include "EpubReaderChapterSelectionActivity.h" +#include "MappedInputManager.h" #include "config.h" namespace { @@ -54,8 +55,8 @@ void EpubReaderActivity::onEnter() { epub->setupCacheDir(); - File f; - if (FsHelpers::openFileForRead("ERS", epub->getCachePath() + "/progress.bin", f)) { + FsFile f; + if (SdMan.openFileForRead("ERS", epub->getCachePath() + "/progress.bin", f)) { uint8_t data[4]; if (f.read(data, 4) == 4) { currentSpineIndex = data[0] + (data[1] << 8); @@ -346,8 +347,8 @@ void EpubReaderActivity::renderScreen() { Serial.printf("[%lu] [ERS] Rendered page in %dms\n", millis(), millis() - start); } - File f; - if (FsHelpers::openFileForWrite("ERS", epub->getCachePath() + "/progress.bin", f)) { + FsFile f; + if (SdMan.openFileForWrite("ERS", epub->getCachePath() + "/progress.bin", f)) { uint8_t data[4]; data[0] = currentSpineIndex & 0xFF; data[1] = (currentSpineIndex >> 8) & 0xFF; diff --git a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp index dc0bee5..8245ed2 100644 --- a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp +++ b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp @@ -1,9 +1,8 @@ #include "EpubReaderChapterSelectionActivity.h" #include -#include -#include +#include "MappedInputManager.h" #include "config.h" namespace { diff --git a/src/activities/reader/FileSelectionActivity.cpp b/src/activities/reader/FileSelectionActivity.cpp index 447db6d..45ef6ef 100644 --- a/src/activities/reader/FileSelectionActivity.cpp +++ b/src/activities/reader/FileSelectionActivity.cpp @@ -1,9 +1,9 @@ #include "FileSelectionActivity.h" #include -#include -#include +#include +#include "MappedInputManager.h" #include "config.h" namespace { @@ -30,17 +30,19 @@ void FileSelectionActivity::taskTrampoline(void* param) { void FileSelectionActivity::loadFiles() { files.clear(); selectorIndex = 0; - auto root = SD.open(basepath.c_str()); - for (File file = root.openNextFile(); file; file = root.openNextFile()) { - auto filename = std::string(file.name()); - if (filename[0] == '.') { + auto root = SdMan.open(basepath.c_str()); + char name[128]; + for (auto file = root.openNextFile(); file; file = root.openNextFile()) { + file.getName(name, sizeof(name)); + if (name[0] == '.') { file.close(); continue; } if (file.isDirectory()) { - files.emplace_back(filename + "/"); + files.emplace_back(std::string(name) + "/"); } else { + auto filename = std::string(name); std::string ext4 = filename.length() >= 4 ? filename.substr(filename.length() - 4) : ""; std::string ext5 = filename.length() >= 5 ? filename.substr(filename.length() - 5) : ""; if (ext5 == ".epub" || ext5 == ".xtch" || ext4 == ".xtc") { diff --git a/src/activities/reader/ReaderActivity.cpp b/src/activities/reader/ReaderActivity.cpp index d98e167..d6a3aa6 100644 --- a/src/activities/reader/ReaderActivity.cpp +++ b/src/activities/reader/ReaderActivity.cpp @@ -1,7 +1,5 @@ #include "ReaderActivity.h" -#include - #include "Epub.h" #include "EpubReaderActivity.h" #include "FileSelectionActivity.h" @@ -29,7 +27,7 @@ bool ReaderActivity::isXtcFile(const std::string& path) { } std::unique_ptr ReaderActivity::loadEpub(const std::string& path) { - if (!SD.exists(path.c_str())) { + if (!SdMan.exists(path.c_str())) { Serial.printf("[%lu] [ ] File does not exist: %s\n", millis(), path.c_str()); return nullptr; } @@ -44,7 +42,7 @@ std::unique_ptr ReaderActivity::loadEpub(const std::string& path) { } std::unique_ptr ReaderActivity::loadXtc(const std::string& path) { - if (!SD.exists(path.c_str())) { + if (!SdMan.exists(path.c_str())) { Serial.printf("[%lu] [ ] File does not exist: %s\n", millis(), path.c_str()); return nullptr; } diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index 317594c..421b789 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -9,10 +9,10 @@ #include #include -#include +#include -#include "CrossPointSettings.h" #include "CrossPointState.h" +#include "MappedInputManager.h" #include "XtcReaderChapterSelectionActivity.h" #include "config.h" @@ -357,8 +357,8 @@ void XtcReaderActivity::renderPage() { } void XtcReaderActivity::saveProgress() const { - File f; - if (FsHelpers::openFileForWrite("XTR", xtc->getCachePath() + "/progress.bin", f)) { + FsFile f; + if (SdMan.openFileForWrite("XTR", xtc->getCachePath() + "/progress.bin", f)) { uint8_t data[4]; data[0] = currentPage & 0xFF; data[1] = (currentPage >> 8) & 0xFF; @@ -370,8 +370,8 @@ void XtcReaderActivity::saveProgress() const { } void XtcReaderActivity::loadProgress() { - File f; - if (FsHelpers::openFileForRead("XTR", xtc->getCachePath() + "/progress.bin", f)) { + FsFile f; + if (SdMan.openFileForRead("XTR", xtc->getCachePath() + "/progress.bin", f)) { uint8_t data[4]; if (f.read(data, 4) == 4) { currentPage = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); diff --git a/src/activities/reader/XtcReaderChapterSelectionActivity.cpp b/src/activities/reader/XtcReaderChapterSelectionActivity.cpp index 6c747f0..d00fc59 100644 --- a/src/activities/reader/XtcReaderChapterSelectionActivity.cpp +++ b/src/activities/reader/XtcReaderChapterSelectionActivity.cpp @@ -1,9 +1,8 @@ #include "XtcReaderChapterSelectionActivity.h" #include -#include -#include +#include "MappedInputManager.h" #include "config.h" namespace { diff --git a/src/activities/settings/OtaUpdateActivity.cpp b/src/activities/settings/OtaUpdateActivity.cpp index 846438b..8a04578 100644 --- a/src/activities/settings/OtaUpdateActivity.cpp +++ b/src/activities/settings/OtaUpdateActivity.cpp @@ -1,9 +1,9 @@ #include "OtaUpdateActivity.h" #include -#include #include +#include "MappedInputManager.h" #include "activities/network/WifiSelectionActivity.h" #include "config.h" #include "network/OtaUpdater.h" diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index 6a66ede..eea7a47 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -1,9 +1,9 @@ #include "SettingsActivity.h" #include -#include #include "CrossPointSettings.h" +#include "MappedInputManager.h" #include "OtaUpdateActivity.h" #include "config.h" diff --git a/src/activities/util/KeyboardEntryActivity.cpp b/src/activities/util/KeyboardEntryActivity.cpp index 7a13aab..dbfd109 100644 --- a/src/activities/util/KeyboardEntryActivity.cpp +++ b/src/activities/util/KeyboardEntryActivity.cpp @@ -1,6 +1,7 @@ #include "KeyboardEntryActivity.h" #include "../../config.h" +#include "MappedInputManager.h" // Keyboard layouts - lowercase const char* const KeyboardEntryActivity::keyboard[NUM_ROWS] = { diff --git a/src/main.cpp b/src/main.cpp index b617ef8..e1bba59 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include #include @@ -37,7 +37,6 @@ #define UART0_RXD 20 // Used for USB connection detection -#define SD_SPI_CS 12 #define SD_SPI_MISO 7 EInkDisplay einkDisplay(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_RST, EPD_BUSY); @@ -189,7 +188,7 @@ void setup() { // SD Card Initialization // We need 6 open files concurrently when parsing a new chapter - if (!SD.begin(SD_SPI_CS, SPI, SPI_FQ, "/sd", 6)) { + if (!SdMan.begin()) { Serial.printf("[%lu] [ ] SD card initialization failed\n", millis()); setupDisplayAndFonts(); exitActivity(); diff --git a/src/network/CrossPointWebServer.cpp b/src/network/CrossPointWebServer.cpp index 041273f..916f6a2 100644 --- a/src/network/CrossPointWebServer.cpp +++ b/src/network/CrossPointWebServer.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include @@ -170,7 +170,7 @@ void CrossPointWebServer::handleStatus() const { } void CrossPointWebServer::scanFiles(const char* path, const std::function& callback) const { - File root = SD.open(path); + FsFile root = SdMan.open(path); if (!root) { Serial.printf("[%lu] [WEB] Failed to open directory: %s\n", millis(), path); return; @@ -184,9 +184,11 @@ void CrossPointWebServer::scanFiles(const char* path, const std::function