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
This commit is contained in:
Dave Allie
2025-12-30 15:09:30 +10:00
committed by GitHub
parent d4bd119950
commit fb5fc32c5d
50 changed files with 289 additions and 355 deletions

View File

@@ -3,7 +3,7 @@
#include <FsHelpers.h> #include <FsHelpers.h>
#include <HardwareSerial.h> #include <HardwareSerial.h>
#include <JpegToBmpConverter.h> #include <JpegToBmpConverter.h>
#include <SD.h> #include <SDCardManager.h>
#include <ZipFile.h> #include <ZipFile.h>
#include "Epub/parsers/ContainerParser.h" #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()); Serial.printf("[%lu] [EBP] Parsing toc ncx file: %s\n", millis(), tocNcxItem.c_str());
const auto tmpNcxPath = getCachePath() + "/toc.ncx"; const auto tmpNcxPath = getCachePath() + "/toc.ncx";
File tempNcxFile; FsFile tempNcxFile;
if (!FsHelpers::openFileForWrite("EBP", tmpNcxPath, tempNcxFile)) { if (!SdMan.openFileForWrite("EBP", tmpNcxPath, tempNcxFile)) {
return false; return false;
} }
readItemContentsToStream(tocNcxItem, tempNcxFile, 1024); readItemContentsToStream(tocNcxItem, tempNcxFile, 1024);
tempNcxFile.close(); tempNcxFile.close();
if (!FsHelpers::openFileForRead("EBP", tmpNcxPath, tempNcxFile)) { if (!SdMan.openFileForRead("EBP", tmpNcxPath, tempNcxFile)) {
return false; return false;
} }
const auto ncxSize = tempNcxFile.size(); const auto ncxSize = tempNcxFile.size();
@@ -132,7 +132,7 @@ bool Epub::parseTocNcxFile() const {
free(ncxBuffer); free(ncxBuffer);
tempNcxFile.close(); tempNcxFile.close();
SD.remove(tmpNcxPath.c_str()); SdMan.remove(tmpNcxPath.c_str());
Serial.printf("[%lu] [EBP] Parsed TOC items\n", millis()); Serial.printf("[%lu] [EBP] Parsed TOC items\n", millis());
return true; return true;
@@ -218,12 +218,12 @@ bool Epub::load() {
} }
bool Epub::clearCache() const { 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()); Serial.printf("[%lu] [EPB] Cache does not exist, no action needed\n", millis());
return true; return true;
} }
if (!FsHelpers::removeDir(cachePath.c_str())) { if (!SdMan.removeDir(cachePath.c_str())) {
Serial.printf("[%lu] [EPB] Failed to clear cache\n", millis()); Serial.printf("[%lu] [EPB] Failed to clear cache\n", millis());
return false; return false;
} }
@@ -233,17 +233,11 @@ bool Epub::clearCache() const {
} }
void Epub::setupCacheDir() const { void Epub::setupCacheDir() const {
if (SD.exists(cachePath.c_str())) { if (SdMan.exists(cachePath.c_str())) {
return; return;
} }
// Loop over each segment of the cache path and create directories as needed SdMan.mkdir(cachePath.c_str());
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());
} }
const std::string& Epub::getCachePath() const { return cachePath; } 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 { bool Epub::generateCoverBmp() const {
// Already generated, return true // Already generated, return true
if (SD.exists(getCoverBmpPath().c_str())) { if (SdMan.exists(getCoverBmpPath().c_str())) {
return true; return true;
} }
@@ -283,30 +277,30 @@ bool Epub::generateCoverBmp() const {
Serial.printf("[%lu] [EBP] Generating BMP from JPG cover image\n", millis()); Serial.printf("[%lu] [EBP] Generating BMP from JPG cover image\n", millis());
const auto coverJpgTempPath = getCachePath() + "/.cover.jpg"; const auto coverJpgTempPath = getCachePath() + "/.cover.jpg";
File coverJpg; FsFile coverJpg;
if (!FsHelpers::openFileForWrite("EBP", coverJpgTempPath, coverJpg)) { if (!SdMan.openFileForWrite("EBP", coverJpgTempPath, coverJpg)) {
return false; return false;
} }
readItemContentsToStream(coverImageHref, coverJpg, 1024); readItemContentsToStream(coverImageHref, coverJpg, 1024);
coverJpg.close(); coverJpg.close();
if (!FsHelpers::openFileForRead("EBP", coverJpgTempPath, coverJpg)) { if (!SdMan.openFileForRead("EBP", coverJpgTempPath, coverJpg)) {
return false; return false;
} }
File coverBmp; FsFile coverBmp;
if (!FsHelpers::openFileForWrite("EBP", getCoverBmpPath(), coverBmp)) { if (!SdMan.openFileForWrite("EBP", getCoverBmpPath(), coverBmp)) {
coverJpg.close(); coverJpg.close();
return false; return false;
} }
const bool success = JpegToBmpConverter::jpegFileToBmpStream(coverJpg, coverBmp); const bool success = JpegToBmpConverter::jpegFileToBmpStream(coverJpg, coverBmp);
coverJpg.close(); coverJpg.close();
coverBmp.close(); coverBmp.close();
SD.remove(coverJpgTempPath.c_str()); SdMan.remove(coverJpgTempPath.c_str());
if (!success) { if (!success) {
Serial.printf("[%lu] [EBP] Failed to generate BMP from JPG cover image\n", millis()); 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"); Serial.printf("[%lu] [EBP] Generated BMP from JPG cover image, success: %s\n", millis(), success ? "yes" : "no");
return success; 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 { 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 std::string path = FsHelpers::normalisePath(itemHref);
const auto content = ZipFile(filepath).readFileToMemory(path.c_str(), size, trailingNullByte); 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 { 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); const std::string path = FsHelpers::normalisePath(itemHref);
return ZipFile(filepath).readFileToStream(path.c_str(), out, chunkSize); return ZipFile(filepath).readFileToStream(path.c_str(), out, chunkSize);
} }

View File

@@ -1,5 +1,7 @@
#pragma once #pragma once
#include <Print.h>
#include <memory> #include <memory>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>

View File

@@ -1,7 +1,6 @@
#include "BookMetadataCache.h" #include "BookMetadataCache.h"
#include <HardwareSerial.h> #include <HardwareSerial.h>
#include <SD.h>
#include <Serialization.h> #include <Serialization.h>
#include <ZipFile.h> #include <ZipFile.h>
@@ -30,7 +29,7 @@ bool BookMetadataCache::beginContentOpfPass() {
Serial.printf("[%lu] [BMC] Beginning content opf pass\n", millis()); Serial.printf("[%lu] [BMC] Beginning content opf pass\n", millis());
// Open spine file for writing // Open spine file for writing
return FsHelpers::openFileForWrite("BMC", cachePath + tmpSpineBinFile, spineFile); return SdMan.openFileForWrite("BMC", cachePath + tmpSpineBinFile, spineFile);
} }
bool BookMetadataCache::endContentOpfPass() { bool BookMetadataCache::endContentOpfPass() {
@@ -42,10 +41,10 @@ bool BookMetadataCache::beginTocPass() {
Serial.printf("[%lu] [BMC] Beginning toc pass\n", millis()); Serial.printf("[%lu] [BMC] Beginning toc pass\n", millis());
// Open spine file for reading // Open spine file for reading
if (!FsHelpers::openFileForRead("BMC", cachePath + tmpSpineBinFile, spineFile)) { if (!SdMan.openFileForRead("BMC", cachePath + tmpSpineBinFile, spineFile)) {
return false; return false;
} }
if (!FsHelpers::openFileForWrite("BMC", cachePath + tmpTocBinFile, tocFile)) { if (!SdMan.openFileForWrite("BMC", cachePath + tmpTocBinFile, tocFile)) {
spineFile.close(); spineFile.close();
return false; return false;
} }
@@ -71,27 +70,27 @@ bool BookMetadataCache::endWrite() {
bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMetadata& metadata) { bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMetadata& metadata) {
// Open all three files, writing to meta, reading from spine and toc // 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; return false;
} }
if (!FsHelpers::openFileForRead("BMC", cachePath + tmpSpineBinFile, spineFile)) { if (!SdMan.openFileForRead("BMC", cachePath + tmpSpineBinFile, spineFile)) {
bookFile.close(); bookFile.close();
return false; return false;
} }
if (!FsHelpers::openFileForRead("BMC", cachePath + tmpTocBinFile, tocFile)) { if (!SdMan.openFileForRead("BMC", cachePath + tmpTocBinFile, tocFile)) {
bookFile.close(); bookFile.close();
spineFile.close(); spineFile.close();
return false; return false;
} }
constexpr size_t headerASize = constexpr uint32_t headerASize =
sizeof(BOOK_CACHE_VERSION) + /* LUT Offset */ sizeof(size_t) + sizeof(spineCount) + sizeof(tocCount); sizeof(BOOK_CACHE_VERSION) + /* LUT Offset */ sizeof(uint32_t) + sizeof(spineCount) + sizeof(tocCount);
const size_t metadataSize = const uint32_t metadataSize =
metadata.title.size() + metadata.author.size() + metadata.coverItemHref.size() + sizeof(uint32_t) * 3; 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 uint32_t lutSize = sizeof(uint32_t) * spineCount + sizeof(uint32_t) * tocCount;
const size_t lutOffset = headerASize + metadataSize; const uint32_t lutOffset = headerASize + metadataSize;
// Header A // Header A
serialization::writePod(bookFile, BOOK_CACHE_VERSION); 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 // Loop through spine entries, writing LUT positions
spineFile.seek(0); spineFile.seek(0);
for (int i = 0; i < spineCount; i++) { for (int i = 0; i < spineCount; i++) {
auto pos = spineFile.position(); uint32_t pos = spineFile.position();
auto spineEntry = readSpineEntry(spineFile); auto spineEntry = readSpineEntry(spineFile);
serialization::writePod(bookFile, pos + lutOffset + lutSize); 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 // Loop through toc entries, writing LUT positions
tocFile.seek(0); tocFile.seek(0);
for (int i = 0; i < tocCount; i++) { for (int i = 0; i < tocCount; i++) {
auto pos = tocFile.position(); uint32_t pos = tocFile.position();
auto tocEntry = readTocEntry(tocFile); auto tocEntry = readTocEntry(tocFile);
serialization::writePod(bookFile, pos + lutOffset + lutSize + spineFile.position()); serialization::writePod(bookFile, pos + lutOffset + lutSize + static_cast<uint32_t>(spineFile.position()));
} }
// LUTs complete // LUTs complete
@@ -142,7 +141,7 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta
zip.close(); zip.close();
return false; return false;
} }
size_t cumSize = 0; uint32_t cumSize = 0;
spineFile.seek(0); spineFile.seek(0);
for (int i = 0; i < spineCount; i++) { for (int i = 0; i < spineCount; i++) {
auto spineEntry = readSpineEntry(spineFile); auto spineEntry = readSpineEntry(spineFile);
@@ -195,25 +194,25 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta
} }
bool BookMetadataCache::cleanupTmpFiles() const { bool BookMetadataCache::cleanupTmpFiles() const {
if (SD.exists((cachePath + tmpSpineBinFile).c_str())) { if (SdMan.exists((cachePath + tmpSpineBinFile).c_str())) {
SD.remove((cachePath + tmpSpineBinFile).c_str()); SdMan.remove((cachePath + tmpSpineBinFile).c_str());
} }
if (SD.exists((cachePath + tmpTocBinFile).c_str())) { if (SdMan.exists((cachePath + tmpTocBinFile).c_str())) {
SD.remove((cachePath + tmpTocBinFile).c_str()); SdMan.remove((cachePath + tmpTocBinFile).c_str());
} }
return true; return true;
} }
size_t BookMetadataCache::writeSpineEntry(File& file, const SpineEntry& entry) const { uint32_t BookMetadataCache::writeSpineEntry(FsFile& file, const SpineEntry& entry) const {
const auto pos = file.position(); const uint32_t pos = file.position();
serialization::writeString(file, entry.href); serialization::writeString(file, entry.href);
serialization::writePod(file, entry.cumulativeSize); serialization::writePod(file, entry.cumulativeSize);
serialization::writePod(file, entry.tocIndex); serialization::writePod(file, entry.tocIndex);
return pos; return pos;
} }
size_t BookMetadataCache::writeTocEntry(File& file, const TocEntry& entry) const { uint32_t BookMetadataCache::writeTocEntry(FsFile& file, const TocEntry& entry) const {
const auto pos = file.position(); const uint32_t pos = file.position();
serialization::writeString(file, entry.title); serialization::writeString(file, entry.title);
serialization::writeString(file, entry.href); serialization::writeString(file, entry.href);
serialization::writeString(file, entry.anchor); serialization::writeString(file, entry.anchor);
@@ -267,7 +266,7 @@ void BookMetadataCache::createTocEntry(const std::string& title, const std::stri
/* ============= READING / LOADING FUNCTIONS ================ */ /* ============= READING / LOADING FUNCTIONS ================ */
bool BookMetadataCache::load() { bool BookMetadataCache::load() {
if (!FsHelpers::openFileForRead("BMC", cachePath + bookBinFile, bookFile)) { if (!SdMan.openFileForRead("BMC", cachePath + bookBinFile, bookFile)) {
return false; 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 // Seek to spine LUT item, read from LUT and get out data
bookFile.seek(lutOffset + sizeof(size_t) * index); bookFile.seek(lutOffset + sizeof(uint32_t) * index);
size_t spineEntryPos; uint32_t spineEntryPos;
serialization::readPod(bookFile, spineEntryPos); serialization::readPod(bookFile, spineEntryPos);
bookFile.seek(spineEntryPos); bookFile.seek(spineEntryPos);
return readSpineEntry(bookFile); 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 // Seek to TOC LUT item, read from LUT and get out data
bookFile.seek(lutOffset + sizeof(size_t) * spineCount + sizeof(size_t) * index); bookFile.seek(lutOffset + sizeof(uint32_t) * spineCount + sizeof(uint32_t) * index);
size_t tocEntryPos; uint32_t tocEntryPos;
serialization::readPod(bookFile, tocEntryPos); serialization::readPod(bookFile, tocEntryPos);
bookFile.seek(tocEntryPos); bookFile.seek(tocEntryPos);
return readTocEntry(bookFile); return readTocEntry(bookFile);
} }
BookMetadataCache::SpineEntry BookMetadataCache::readSpineEntry(File& file) const { BookMetadataCache::SpineEntry BookMetadataCache::readSpineEntry(FsFile& file) const {
SpineEntry entry; SpineEntry entry;
serialization::readString(file, entry.href); serialization::readString(file, entry.href);
serialization::readPod(file, entry.cumulativeSize); serialization::readPod(file, entry.cumulativeSize);
@@ -338,7 +337,7 @@ BookMetadataCache::SpineEntry BookMetadataCache::readSpineEntry(File& file) cons
return entry; return entry;
} }
BookMetadataCache::TocEntry BookMetadataCache::readTocEntry(File& file) const { BookMetadataCache::TocEntry BookMetadataCache::readTocEntry(FsFile& file) const {
TocEntry entry; TocEntry entry;
serialization::readString(file, entry.title); serialization::readString(file, entry.title);
serialization::readString(file, entry.href); serialization::readString(file, entry.href);

View File

@@ -1,6 +1,6 @@
#pragma once #pragma once
#include <SD.h> #include <SDCardManager.h>
#include <string> #include <string>
@@ -46,15 +46,15 @@ class BookMetadataCache {
bool loaded; bool loaded;
bool buildMode; bool buildMode;
File bookFile; FsFile bookFile;
// Temp file handles during build // Temp file handles during build
File spineFile; FsFile spineFile;
File tocFile; FsFile tocFile;
size_t writeSpineEntry(File& file, const SpineEntry& entry) const; uint32_t writeSpineEntry(FsFile& file, const SpineEntry& entry) const;
size_t writeTocEntry(File& file, const TocEntry& entry) const; uint32_t writeTocEntry(FsFile& file, const TocEntry& entry) const;
SpineEntry readSpineEntry(File& file) const; SpineEntry readSpineEntry(FsFile& file) const;
TocEntry readTocEntry(File& file) const; TocEntry readTocEntry(FsFile& file) const;
public: public:
BookMetadata coreMetadata; BookMetadata coreMetadata;

View File

@@ -7,7 +7,7 @@ void PageLine::render(GfxRenderer& renderer, const int fontId, const int xOffset
block->render(renderer, fontId, xPos + xOffset, yPos + yOffset); 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, xPos);
serialization::writePod(file, yPos); serialization::writePod(file, yPos);
@@ -15,7 +15,7 @@ bool PageLine::serialize(File& file) {
return block->serialize(file); return block->serialize(file);
} }
std::unique_ptr<PageLine> PageLine::deserialize(File& file) { std::unique_ptr<PageLine> PageLine::deserialize(FsFile& file) {
int16_t xPos; int16_t xPos;
int16_t yPos; int16_t yPos;
serialization::readPod(file, xPos); 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(); const uint32_t count = elements.size();
serialization::writePod(file, count); serialization::writePod(file, count);
@@ -46,7 +46,7 @@ bool Page::serialize(File& file) const {
return true; return true;
} }
std::unique_ptr<Page> Page::deserialize(File& file) { std::unique_ptr<Page> Page::deserialize(FsFile& file) {
auto page = std::unique_ptr<Page>(new Page()); auto page = std::unique_ptr<Page>(new Page());
uint32_t count; uint32_t count;

View File

@@ -1,5 +1,5 @@
#pragma once #pragma once
#include <FS.h> #include <SdFat.h>
#include <utility> #include <utility>
#include <vector> #include <vector>
@@ -18,7 +18,7 @@ class PageElement {
explicit PageElement(const int16_t xPos, const int16_t yPos) : xPos(xPos), yPos(yPos) {} explicit PageElement(const int16_t xPos, const int16_t yPos) : xPos(xPos), yPos(yPos) {}
virtual ~PageElement() = default; virtual ~PageElement() = default;
virtual void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) = 0; 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 // a line from a block element
@@ -29,8 +29,8 @@ class PageLine final : public PageElement {
PageLine(std::shared_ptr<TextBlock> block, const int16_t xPos, const int16_t yPos) PageLine(std::shared_ptr<TextBlock> block, const int16_t xPos, const int16_t yPos)
: PageElement(xPos, yPos), block(std::move(block)) {} : PageElement(xPos, yPos), block(std::move(block)) {}
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) override; void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) override;
bool serialize(File& file) override; bool serialize(FsFile& file) override;
static std::unique_ptr<PageLine> deserialize(File& file); static std::unique_ptr<PageLine> deserialize(FsFile& file);
}; };
class Page { class Page {
@@ -38,6 +38,6 @@ class Page {
// the list of block index and line numbers on this page // the list of block index and line numbers on this page
std::vector<std::shared_ptr<PageElement>> elements; std::vector<std::shared_ptr<PageElement>> elements;
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) const; void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) const;
bool serialize(File& file) const; bool serialize(FsFile& file) const;
static std::unique_ptr<Page> deserialize(File& file); static std::unique_ptr<Page> deserialize(FsFile& file);
}; };

View File

@@ -1,7 +1,6 @@
#include "Section.h" #include "Section.h"
#include <FsHelpers.h> #include <SDCardManager.h>
#include <SD.h>
#include <Serialization.h> #include <Serialization.h>
#include "Page.h" #include "Page.h"
@@ -9,17 +8,17 @@
namespace { namespace {
constexpr uint8_t SECTION_FILE_VERSION = 7; constexpr uint8_t SECTION_FILE_VERSION = 7;
constexpr size_t HEADER_SIZE = sizeof(uint8_t) + sizeof(int) + sizeof(float) + sizeof(bool) + sizeof(int) + constexpr uint32_t HEADER_SIZE = sizeof(uint8_t) + sizeof(int) + sizeof(float) + sizeof(bool) + sizeof(int) +
sizeof(int) + sizeof(int) + sizeof(size_t); sizeof(int) + sizeof(int) + sizeof(uint32_t);
} // namespace } // namespace
size_t Section::onPageComplete(std::unique_ptr<Page> page) { uint32_t Section::onPageComplete(std::unique_ptr<Page> page) {
if (!file) { if (!file) {
Serial.printf("[%lu] [SCT] File not open for writing page %d\n", millis(), pageCount); Serial.printf("[%lu] [SCT] File not open for writing page %d\n", millis(), pageCount);
return 0; return 0;
} }
const auto position = file.position(); const uint32_t position = file.position();
if (!page->serialize(file)) { if (!page->serialize(file)) {
Serial.printf("[%lu] [SCT] Failed to serialize page %d\n", millis(), pageCount); Serial.printf("[%lu] [SCT] Failed to serialize page %d\n", millis(), pageCount);
return 0; 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) + static_assert(HEADER_SIZE == sizeof(SECTION_FILE_VERSION) + sizeof(fontId) + sizeof(lineCompression) +
sizeof(extraParagraphSpacing) + sizeof(viewportWidth) + sizeof(viewportHeight) + sizeof(extraParagraphSpacing) + sizeof(viewportWidth) + sizeof(viewportHeight) +
sizeof(pageCount) + sizeof(size_t), sizeof(pageCount) + sizeof(uint32_t),
"Header size mismatch"); "Header size mismatch");
serialization::writePod(file, SECTION_FILE_VERSION); serialization::writePod(file, SECTION_FILE_VERSION);
serialization::writePod(file, fontId); serialization::writePod(file, fontId);
@@ -47,12 +46,12 @@ void Section::writeSectionFileHeader(const int fontId, const float lineCompressi
serialization::writePod(file, viewportWidth); serialization::writePod(file, viewportWidth);
serialization::writePod(file, viewportHeight); serialization::writePod(file, viewportHeight);
serialization::writePod(file, pageCount); // Placeholder for page count (will be initially 0 when written) serialization::writePod(file, pageCount); // Placeholder for page count (will be initially 0 when written)
serialization::writePod(file, static_cast<size_t>(0)); // Placeholder for LUT offset serialization::writePod(file, static_cast<uint32_t>(0)); // Placeholder for LUT offset
} }
bool Section::loadSectionFile(const int fontId, const float lineCompression, const bool extraParagraphSpacing, bool Section::loadSectionFile(const int fontId, const float lineCompression, const bool extraParagraphSpacing,
const int viewportWidth, const int viewportHeight) { const int viewportWidth, const int viewportHeight) {
if (!FsHelpers::openFileForRead("SCT", filePath, file)) { if (!SdMan.openFileForRead("SCT", filePath, file)) {
return false; 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) // Your updated class method (assuming you are using the 'SD' object, which is a wrapper for a specific filesystem)
bool Section::clearCache() const { 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()); Serial.printf("[%lu] [SCT] Cache does not exist, no action needed\n", millis());
return true; return true;
} }
if (!SD.remove(filePath.c_str())) { if (!SdMan.remove(filePath.c_str())) {
Serial.printf("[%lu] [SCT] Failed to clear cache\n", millis()); Serial.printf("[%lu] [SCT] Failed to clear cache\n", millis());
return false; return false;
} }
@@ -112,13 +111,19 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c
const int viewportWidth, const int viewportHeight, const int viewportWidth, const int viewportHeight,
const std::function<void()>& progressSetupFn, const std::function<void()>& progressSetupFn,
const std::function<void(int)>& progressFn) { const std::function<void(int)>& 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 localPath = epub->getSpineItem(spineIndex).href;
const auto tmpHtmlPath = epub->getCachePath() + "/.tmp_" + std::to_string(spineIndex) + ".html"; 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 // Retry logic for SD card timing issues
bool success = false; bool success = false;
size_t fileSize = 0; uint32_t fileSize = 0;
for (int attempt = 0; attempt < 3 && !success; attempt++) { for (int attempt = 0; attempt < 3 && !success; attempt++) {
if (attempt > 0) { if (attempt > 0) {
Serial.printf("[%lu] [SCT] Retrying stream (attempt %d)...\n", millis(), attempt + 1); 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 // Remove any incomplete file from previous attempt before retrying
if (SD.exists(tmpHtmlPath.c_str())) { if (SdMan.exists(tmpHtmlPath.c_str())) {
SD.remove(tmpHtmlPath.c_str()); SdMan.remove(tmpHtmlPath.c_str());
} }
File tmpHtml; FsFile tmpHtml;
if (!FsHelpers::openFileForWrite("SCT", tmpHtmlPath, tmpHtml)) { if (!SdMan.openFileForWrite("SCT", tmpHtmlPath, tmpHtml)) {
continue; continue;
} }
success = epub->readItemContentsToStream(localPath, tmpHtml, 1024); success = epub->readItemContentsToStream(localPath, tmpHtml, 1024);
@@ -139,8 +144,8 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c
tmpHtml.close(); tmpHtml.close();
// If streaming failed, remove the incomplete file immediately // If streaming failed, remove the incomplete file immediately
if (!success && SD.exists(tmpHtmlPath.c_str())) { if (!success && SdMan.exists(tmpHtmlPath.c_str())) {
SD.remove(tmpHtmlPath.c_str()); SdMan.remove(tmpHtmlPath.c_str());
Serial.printf("[%lu] [SCT] Removed incomplete temp file after failed attempt\n", millis()); 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(); progressSetupFn();
} }
if (!FsHelpers::openFileForWrite("SCT", filePath, file)) { if (!SdMan.openFileForWrite("SCT", filePath, file)) {
return false; return false;
} }
writeSectionFileHeader(fontId, lineCompression, extraParagraphSpacing, viewportWidth, viewportHeight); writeSectionFileHeader(fontId, lineCompression, extraParagraphSpacing, viewportWidth, viewportHeight);
std::vector<size_t> lut = {}; std::vector<uint32_t> lut = {};
ChapterHtmlSlimParser visitor( ChapterHtmlSlimParser visitor(
tmpHtmlPath, renderer, fontId, lineCompression, extraParagraphSpacing, viewportWidth, viewportHeight, tmpHtmlPath, renderer, fontId, lineCompression, extraParagraphSpacing, viewportWidth, viewportHeight,
@@ -169,18 +174,18 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c
progressFn); progressFn);
success = visitor.parseAndBuildPages(); success = visitor.parseAndBuildPages();
SD.remove(tmpHtmlPath.c_str()); SdMan.remove(tmpHtmlPath.c_str());
if (!success) { if (!success) {
Serial.printf("[%lu] [SCT] Failed to parse XML and build pages\n", millis()); Serial.printf("[%lu] [SCT] Failed to parse XML and build pages\n", millis());
file.close(); file.close();
SD.remove(filePath.c_str()); SdMan.remove(filePath.c_str());
return false; return false;
} }
const auto lutOffset = file.position(); const uint32_t lutOffset = file.position();
bool hasFailedLutRecords = false; bool hasFailedLutRecords = false;
// Write LUT // Write LUT
for (const auto& pos : lut) { for (const uint32_t& pos : lut) {
if (pos == 0) { if (pos == 0) {
hasFailedLutRecords = true; hasFailedLutRecords = true;
break; break;
@@ -191,12 +196,12 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c
if (hasFailedLutRecords) { if (hasFailedLutRecords) {
Serial.printf("[%lu] [SCT] Failed to write LUT due to invalid page positions\n", millis()); Serial.printf("[%lu] [SCT] Failed to write LUT due to invalid page positions\n", millis());
file.close(); file.close();
SD.remove(filePath.c_str()); SdMan.remove(filePath.c_str());
return false; return false;
} }
// Go back and write LUT offset // 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, pageCount);
serialization::writePod(file, lutOffset); serialization::writePod(file, lutOffset);
file.close(); file.close();
@@ -204,15 +209,15 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c
} }
std::unique_ptr<Page> Section::loadPageFromSectionFile() { std::unique_ptr<Page> Section::loadPageFromSectionFile() {
if (!FsHelpers::openFileForRead("SCT", filePath, file)) { if (!SdMan.openFileForRead("SCT", filePath, file)) {
return nullptr; return nullptr;
} }
file.seek(HEADER_SIZE - sizeof(size_t)); file.seek(HEADER_SIZE - sizeof(uint32_t));
size_t lutOffset; uint32_t lutOffset;
serialization::readPod(file, lutOffset); serialization::readPod(file, lutOffset);
file.seek(lutOffset + sizeof(size_t) * currentPage); file.seek(lutOffset + sizeof(uint32_t) * currentPage);
size_t pagePos; uint32_t pagePos;
serialization::readPod(file, pagePos); serialization::readPod(file, pagePos);
file.seek(pagePos); file.seek(pagePos);

View File

@@ -12,11 +12,11 @@ class Section {
const int spineIndex; const int spineIndex;
GfxRenderer& renderer; GfxRenderer& renderer;
std::string filePath; std::string filePath;
File file; FsFile file;
void writeSectionFileHeader(int fontId, float lineCompression, bool extraParagraphSpacing, int viewportWidth, void writeSectionFileHeader(int fontId, float lineCompression, bool extraParagraphSpacing, int viewportWidth,
int viewportHeight); int viewportHeight);
size_t onPageComplete(std::unique_ptr<Page> page); uint32_t onPageComplete(std::unique_ptr<Page> page);
public: public:
int pageCount = 0; int pageCount = 0;

View File

@@ -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()) { 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(), Serial.printf("[%lu] [TXB] Serialization failed: size mismatch (words=%u, xpos=%u, styles=%u)\n", millis(),
words.size(), wordXpos.size(), wordStyles.size()); words.size(), wordXpos.size(), wordStyles.size());
@@ -43,7 +43,7 @@ bool TextBlock::serialize(File& file) const {
return true; return true;
} }
std::unique_ptr<TextBlock> TextBlock::deserialize(File& file) { std::unique_ptr<TextBlock> TextBlock::deserialize(FsFile& file) {
uint32_t wc; uint32_t wc;
std::list<std::string> words; std::list<std::string> words;
std::list<uint16_t> wordXpos; std::list<uint16_t> wordXpos;

View File

@@ -1,6 +1,6 @@
#pragma once #pragma once
#include <EpdFontFamily.h> #include <EpdFontFamily.h>
#include <FS.h> #include <SdFat.h>
#include <list> #include <list>
#include <memory> #include <memory>
@@ -36,6 +36,6 @@ class TextBlock final : public Block {
// given a renderer works out where to break the words into lines // given a renderer works out where to break the words into lines
void render(const GfxRenderer& renderer, int fontId, int x, int y) const; void render(const GfxRenderer& renderer, int fontId, int x, int y) const;
BlockType getType() override { return TEXT_BLOCK; } BlockType getType() override { return TEXT_BLOCK; }
bool serialize(File& file) const; bool serialize(FsFile& file) const;
static std::unique_ptr<TextBlock> deserialize(File& file); static std::unique_ptr<TextBlock> deserialize(FsFile& file);
}; };

View File

@@ -1,8 +1,8 @@
#include "ChapterHtmlSlimParser.h" #include "ChapterHtmlSlimParser.h"
#include <FsHelpers.h>
#include <GfxRenderer.h> #include <GfxRenderer.h>
#include <HardwareSerial.h> #include <HardwareSerial.h>
#include <SDCardManager.h>
#include <expat.h> #include <expat.h>
#include "../Page.h" #include "../Page.h"
@@ -218,8 +218,8 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
return false; return false;
} }
File file; FsFile file;
if (!FsHelpers::openFileForRead("EHP", filepath, file)) { if (!SdMan.openFileForRead("EHP", filepath, file)) {
XML_ParserFree(parser); XML_ParserFree(parser);
return false; return false;
} }

View File

@@ -35,8 +35,8 @@ ContentOpfParser::~ContentOpfParser() {
if (tempItemStore) { if (tempItemStore) {
tempItemStore.close(); tempItemStore.close();
} }
if (SD.exists((cachePath + itemCacheFile).c_str())) { if (SdMan.exists((cachePath + itemCacheFile).c_str())) {
SD.remove((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)) { if (self->state == IN_PACKAGE && (strcmp(name, "manifest") == 0 || strcmp(name, "opf:manifest") == 0)) {
self->state = IN_MANIFEST; self->state = IN_MANIFEST;
if (!FsHelpers::openFileForWrite("COF", self->cachePath + itemCacheFile, self->tempItemStore)) { if (!SdMan.openFileForWrite("COF", self->cachePath + itemCacheFile, self->tempItemStore)) {
Serial.printf( Serial.printf(
"[%lu] [COF] Couldn't open temp items file for writing. This is probably going to be a fatal error.\n", "[%lu] [COF] Couldn't open temp items file for writing. This is probably going to be a fatal error.\n",
millis()); 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)) { if (self->state == IN_PACKAGE && (strcmp(name, "spine") == 0 || strcmp(name, "opf:spine") == 0)) {
self->state = IN_SPINE; self->state = IN_SPINE;
if (!FsHelpers::openFileForRead("COF", self->cachePath + itemCacheFile, self->tempItemStore)) { if (!SdMan.openFileForRead("COF", self->cachePath + itemCacheFile, self->tempItemStore)) {
Serial.printf( Serial.printf(
"[%lu] [COF] Couldn't open temp items file for reading. This is probably going to be a fatal error.\n", "[%lu] [COF] Couldn't open temp items file for reading. This is probably going to be a fatal error.\n",
millis()); millis());

View File

@@ -22,7 +22,7 @@ class ContentOpfParser final : public Print {
XML_Parser parser = nullptr; XML_Parser parser = nullptr;
ParserState state = START; ParserState state = START;
BookMetadataCache* cache; BookMetadataCache* cache;
File tempItemStore; FsFile tempItemStore;
std::string coverItemId; std::string coverItemId;
static void startElement(void* userData, const XML_Char* name, const XML_Char** atts); static void startElement(void* userData, const XML_Char* name, const XML_Char** atts);

View File

@@ -1,81 +1,7 @@
#include "FsHelpers.h" #include "FsHelpers.h"
#include <SD.h>
#include <vector> #include <vector>
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::string FsHelpers::normalisePath(const std::string& path) {
std::vector<std::string> components; std::vector<std::string> components;
std::string component; std::string component;

View File

@@ -1,14 +1,7 @@
#pragma once #pragma once
#include <FS.h> #include <string>
class FsHelpers { class FsHelpers {
public: 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); static std::string normalisePath(const std::string& path);
}; };

View File

@@ -123,7 +123,7 @@ Bitmap::~Bitmap() {
delete[] errorNextRow; delete[] errorNextRow;
} }
uint16_t Bitmap::readLE16(File& f) { uint16_t Bitmap::readLE16(FsFile& f) {
const int c0 = f.read(); const int c0 = f.read();
const int c1 = f.read(); const int c1 = f.read();
const auto b0 = static_cast<uint8_t>(c0 < 0 ? 0 : c0); const auto b0 = static_cast<uint8_t>(c0 < 0 ? 0 : c0);
@@ -131,7 +131,7 @@ uint16_t Bitmap::readLE16(File& f) {
return static_cast<uint16_t>(b0) | (static_cast<uint16_t>(b1) << 8); return static_cast<uint16_t>(b0) | (static_cast<uint16_t>(b1) << 8);
} }
uint32_t Bitmap::readLE32(File& f) { uint32_t Bitmap::readLE32(FsFile& f) {
const int c0 = f.read(); const int c0 = f.read();
const int c1 = f.read(); const int c1 = f.read();
const int c2 = f.read(); const int c2 = f.read();
@@ -192,7 +192,7 @@ BmpReaderError Bitmap::parseHeaders() {
const uint16_t bfType = readLE16(file); const uint16_t bfType = readLE16(file);
if (bfType != 0x4D42) return BmpReaderError::NotBMP; if (bfType != 0x4D42) return BmpReaderError::NotBMP;
file.seek(8, SeekCur); file.seekCur(8);
bfOffBits = readLE32(file); bfOffBits = readLE32(file);
// --- DIB HEADER --- // --- 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. // 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; 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); const uint32_t colorsUsed = readLE32(file);
if (colorsUsed > 256u) return BmpReaderError::PaletteTooLarge; if (colorsUsed > 256u) return BmpReaderError::PaletteTooLarge;
file.seek(4, SeekCur); // biClrImportant file.seekCur(4); // biClrImportant
if (width <= 0 || height <= 0) return BmpReaderError::BadDimensions; if (width <= 0 || height <= 0) return BmpReaderError::BadDimensions;

View File

@@ -1,6 +1,6 @@
#pragma once #pragma once
#include <FS.h> #include <SdFat.h>
enum class BmpReaderError : uint8_t { enum class BmpReaderError : uint8_t {
Ok = 0, Ok = 0,
@@ -28,7 +28,7 @@ class Bitmap {
public: public:
static const char* errorToString(BmpReaderError err); static const char* errorToString(BmpReaderError err);
explicit Bitmap(File& file) : file(file) {} explicit Bitmap(FsFile& file) : file(file) {}
~Bitmap(); ~Bitmap();
BmpReaderError parseHeaders(); BmpReaderError parseHeaders();
BmpReaderError readRow(uint8_t* data, uint8_t* rowBuffer, int rowY) const; BmpReaderError readRow(uint8_t* data, uint8_t* rowBuffer, int rowY) const;
@@ -40,10 +40,10 @@ class Bitmap {
int getRowBytes() const { return rowBytes; } int getRowBytes() const { return rowBytes; }
private: private:
static uint16_t readLE16(File& f); static uint16_t readLE16(FsFile& f);
static uint32_t readLE32(File& f); static uint32_t readLE32(FsFile& f);
File& file; FsFile& file;
int width = 0; int width = 0;
int height = 0; int height = 0;
bool topDown = false; bool topDown = false;

View File

@@ -2,7 +2,6 @@
#include <EInkDisplay.h> #include <EInkDisplay.h>
#include <EpdFontFamily.h> #include <EpdFontFamily.h>
#include <FS.h>
#include <map> #include <map>

View File

@@ -1,5 +1,7 @@
#include "JpegToBmpConverter.h" #include "JpegToBmpConverter.h"
#include <HardwareSerial.h>
#include <SdFat.h>
#include <picojpeg.h> #include <picojpeg.h>
#include <cstdio> #include <cstdio>
@@ -7,7 +9,7 @@
// Context structure for picojpeg callback // Context structure for picojpeg callback
struct JpegReadContext { struct JpegReadContext {
File& file; FsFile& file;
uint8_t buffer[512]; uint8_t buffer[512];
size_t bufferPos; size_t bufferPos;
size_t bufferFilled; 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 // 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()); Serial.printf("[%lu] [JPG] Converting JPEG to BMP\n", millis());
// Setup context for picojpeg callback // Setup context for picojpeg callback

View File

@@ -1,7 +1,7 @@
#pragma once #pragma once
#include <FS.h> class FsFile;
class Print;
class ZipFile; class ZipFile;
class JpegToBmpConverter { class JpegToBmpConverter {
@@ -11,5 +11,5 @@ class JpegToBmpConverter {
unsigned char* pBytes_actually_read, void* pCallback_data); unsigned char* pBytes_actually_read, void* pCallback_data);
public: public:
static bool jpegFileToBmpStream(File& jpegFile, Print& bmpOut); static bool jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut);
}; };

View File

@@ -1,5 +1,5 @@
#pragma once #pragma once
#include <FS.h> #include <SdFat.h>
#include <iostream> #include <iostream>
@@ -10,7 +10,7 @@ static void writePod(std::ostream& os, const T& value) {
} }
template <typename T> template <typename T>
static void writePod(File& file, const T& value) { static void writePod(FsFile& file, const T& value) {
file.write(reinterpret_cast<const uint8_t*>(&value), sizeof(T)); file.write(reinterpret_cast<const uint8_t*>(&value), sizeof(T));
} }
@@ -20,7 +20,7 @@ static void readPod(std::istream& is, T& value) {
} }
template <typename T> template <typename T>
static void readPod(File& file, T& value) { static void readPod(FsFile& file, T& value) {
file.read(reinterpret_cast<uint8_t*>(&value), sizeof(T)); file.read(reinterpret_cast<uint8_t*>(&value), sizeof(T));
} }
@@ -30,7 +30,7 @@ static void writeString(std::ostream& os, const std::string& s) {
os.write(s.data(), len); 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(); const uint32_t len = s.size();
writePod(file, len); writePod(file, len);
file.write(reinterpret_cast<const uint8_t*>(s.data()), len); file.write(reinterpret_cast<const uint8_t*>(s.data()), len);
@@ -43,10 +43,10 @@ static void readString(std::istream& is, std::string& s) {
is.read(&s[0], len); is.read(&s[0], len);
} }
static void readString(File& file, std::string& s) { static void readString(FsFile& file, std::string& s) {
uint32_t len; uint32_t len;
readPod(file, len); readPod(file, len);
s.resize(len); s.resize(len);
file.read(reinterpret_cast<uint8_t*>(&s[0]), len); file.read(&s[0], len);
} }
} // namespace serialization } // namespace serialization

View File

@@ -9,7 +9,7 @@
#include <FsHelpers.h> #include <FsHelpers.h>
#include <HardwareSerial.h> #include <HardwareSerial.h>
#include <SD.h> #include <SDCardManager.h>
bool Xtc::load() { bool Xtc::load() {
Serial.printf("[%lu] [XTC] Loading XTC: %s\n", millis(), filepath.c_str()); Serial.printf("[%lu] [XTC] Loading XTC: %s\n", millis(), filepath.c_str());
@@ -31,12 +31,12 @@ bool Xtc::load() {
} }
bool Xtc::clearCache() const { 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()); Serial.printf("[%lu] [XTC] Cache does not exist, no action needed\n", millis());
return true; return true;
} }
if (!FsHelpers::removeDir(cachePath.c_str())) { if (!SdMan.removeDir(cachePath.c_str())) {
Serial.printf("[%lu] [XTC] Failed to clear cache\n", millis()); Serial.printf("[%lu] [XTC] Failed to clear cache\n", millis());
return false; return false;
} }
@@ -46,17 +46,17 @@ bool Xtc::clearCache() const {
} }
void Xtc::setupCacheDir() const { void Xtc::setupCacheDir() const {
if (SD.exists(cachePath.c_str())) { if (SdMan.exists(cachePath.c_str())) {
return; return;
} }
// Create directories recursively // Create directories recursively
for (size_t i = 1; i < cachePath.length(); i++) { for (size_t i = 1; i < cachePath.length(); i++) {
if (cachePath[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 { std::string Xtc::getTitle() const {
@@ -106,7 +106,7 @@ std::string Xtc::getCoverBmpPath() const { return cachePath + "/cover.bmp"; }
bool Xtc::generateCoverBmp() const { bool Xtc::generateCoverBmp() const {
// Already generated // Already generated
if (SD.exists(getCoverBmpPath().c_str())) { if (SdMan.exists(getCoverBmpPath().c_str())) {
return true; return true;
} }
@@ -157,8 +157,8 @@ bool Xtc::generateCoverBmp() const {
} }
// Create BMP file // Create BMP file
File coverBmp; FsFile coverBmp;
if (!FsHelpers::openFileForWrite("XTC", getCoverBmpPath(), coverBmp)) { if (!SdMan.openFileForWrite("XTC", getCoverBmpPath(), coverBmp)) {
Serial.printf("[%lu] [XTC] Failed to create cover BMP file\n", millis()); Serial.printf("[%lu] [XTC] Failed to create cover BMP file\n", millis());
free(pageBuffer); free(pageBuffer);
return false; return false;

View File

@@ -9,6 +9,7 @@
#include <FsHelpers.h> #include <FsHelpers.h>
#include <HardwareSerial.h> #include <HardwareSerial.h>
#include <SDCardManager.h>
#include <cstring> #include <cstring>
@@ -33,7 +34,7 @@ XtcError XtcParser::open(const char* filepath) {
} }
// Open file // Open file
if (!FsHelpers::openFileForRead("XTC", filepath, m_file)) { if (!SdMan.openFileForRead("XTC", filepath, m_file)) {
m_lastError = XtcError::FILE_NOT_FOUND; m_lastError = XtcError::FILE_NOT_FOUND;
return m_lastError; return m_lastError;
} }
@@ -419,8 +420,8 @@ XtcError XtcParser::loadPageStreaming(uint32_t pageIndex,
} }
bool XtcParser::isValidXtcFile(const char* filepath) { bool XtcParser::isValidXtcFile(const char* filepath) {
File file = SD.open(filepath, FILE_READ); FsFile file;
if (!file) { if (!SdMan.openFileForRead("XTC", filepath, file)) {
return false; return false;
} }

View File

@@ -7,7 +7,7 @@
#pragma once #pragma once
#include <SD.h> #include <SdFat.h>
#include <functional> #include <functional>
#include <memory> #include <memory>
@@ -80,7 +80,7 @@ class XtcParser {
XtcError getLastError() const { return m_lastError; } XtcError getLastError() const { return m_lastError; }
private: private:
File m_file; FsFile m_file;
bool m_isOpen; bool m_isOpen;
XtcHeader m_header; XtcHeader m_header;
std::vector<PageInfo> m_pageTable; std::vector<PageInfo> m_pageTable;

View File

@@ -1,7 +1,7 @@
#include "ZipFile.h" #include "ZipFile.h"
#include <FsHelpers.h>
#include <HardwareSerial.h> #include <HardwareSerial.h>
#include <SDCardManager.h>
#include <miniz.h> #include <miniz.h>
bool inflateOneShot(const uint8_t* inputBuf, const size_t deflatedSize, uint8_t* outputBuf, const size_t inflatedSize) { 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); fileStatSlimCache.reserve(zipDetails.totalEntries);
while (file.available()) { while (file.available()) {
file.read(reinterpret_cast<uint8_t*>(&sig), 4); file.read(&sig, 4);
if (sig != 0x02014b50) break; // End of list if (sig != 0x02014b50) break; // End of list
FileStatSlim fileStat = {}; FileStatSlim fileStat = {};
file.seek(6, SeekCur); file.seekCur(6);
file.read(reinterpret_cast<uint8_t*>(&fileStat.method), 2); file.read(&fileStat.method, 2);
file.seek(8, SeekCur); file.seekCur(8);
file.read(reinterpret_cast<uint8_t*>(&fileStat.compressedSize), 4); file.read(&fileStat.compressedSize, 4);
file.read(reinterpret_cast<uint8_t*>(&fileStat.uncompressedSize), 4); file.read(&fileStat.uncompressedSize, 4);
uint16_t nameLen, m, k; uint16_t nameLen, m, k;
file.read(reinterpret_cast<uint8_t*>(&nameLen), 2); file.read(&nameLen, 2);
file.read(reinterpret_cast<uint8_t*>(&m), 2); file.read(&m, 2);
file.read(reinterpret_cast<uint8_t*>(&k), 2); file.read(&k, 2);
file.seek(8, SeekCur); file.seekCur(8);
file.read(reinterpret_cast<uint8_t*>(&fileStat.localHeaderOffset), 4); file.read(&fileStat.localHeaderOffset, 4);
file.read(reinterpret_cast<uint8_t*>(itemName), nameLen); file.read(itemName, nameLen);
itemName[nameLen] = '\0'; itemName[nameLen] = '\0';
fileStatSlimCache.emplace(itemName, fileStat); fileStatSlimCache.emplace(itemName, fileStat);
// Skip the rest of this entry (extra field + comment) // Skip the rest of this entry (extra field + comment)
file.seek(m + k, SeekCur); file.seekCur(m + k);
} }
if (!wasOpen) { if (!wasOpen) {
@@ -109,21 +109,21 @@ bool ZipFile::loadFileStatSlim(const char* filename, FileStatSlim* fileStat) {
bool found = false; bool found = false;
while (file.available()) { while (file.available()) {
file.read(reinterpret_cast<uint8_t*>(&sig), 4); file.read(&sig, 4);
if (sig != 0x02014b50) break; // End of list if (sig != 0x02014b50) break; // End of list
file.seek(6, SeekCur); file.seekCur(6);
file.read(reinterpret_cast<uint8_t*>(&fileStat->method), 2); file.read(&fileStat->method, 2);
file.seek(8, SeekCur); file.seekCur(8);
file.read(reinterpret_cast<uint8_t*>(&fileStat->compressedSize), 4); file.read(&fileStat->compressedSize, 4);
file.read(reinterpret_cast<uint8_t*>(&fileStat->uncompressedSize), 4); file.read(&fileStat->uncompressedSize, 4);
uint16_t nameLen, m, k; uint16_t nameLen, m, k;
file.read(reinterpret_cast<uint8_t*>(&nameLen), 2); file.read(&nameLen, 2);
file.read(reinterpret_cast<uint8_t*>(&m), 2); file.read(&m, 2);
file.read(reinterpret_cast<uint8_t*>(&k), 2); file.read(&k, 2);
file.seek(8, SeekCur); file.seekCur(8);
file.read(reinterpret_cast<uint8_t*>(&fileStat->localHeaderOffset), 4); file.read(&fileStat->localHeaderOffset, 4);
file.read(reinterpret_cast<uint8_t*>(itemName), nameLen); file.read(itemName, nameLen);
itemName[nameLen] = '\0'; itemName[nameLen] = '\0';
if (strcmp(itemName, filename) == 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) // Skip the rest of this entry (extra field + comment)
file.seek(m + k, SeekCur); file.seekCur(m + k);
} }
if (!wasOpen) { if (!wasOpen) {
@@ -243,7 +243,7 @@ bool ZipFile::loadZipDetails() {
} }
bool ZipFile::open() { bool ZipFile::open() {
if (!FsHelpers::openFileForRead("ZIP", filePath, file)) { if (!SdMan.openFileForRead("ZIP", filePath, file)) {
return false; return false;
} }
return true; return true;

View File

@@ -1,5 +1,5 @@
#pragma once #pragma once
#include <FS.h> #include <SdFat.h>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
@@ -21,7 +21,7 @@ class ZipFile {
private: private:
const std::string& filePath; const std::string& filePath;
File file; FsFile file;
ZipDetails zipDetails = {0, 0, false}; ZipDetails zipDetails = {0, 0, false};
std::unordered_map<std::string, FileStatSlim> fileStatSlimCache; std::unordered_map<std::string, FileStatSlim> fileStatSlimCache;

View File

@@ -21,6 +21,7 @@ build_flags =
-DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_CDC_ON_BOOT=1
-DMINIZ_NO_ZLIB_COMPATIBLE_NAMES=1 -DMINIZ_NO_ZLIB_COMPATIBLE_NAMES=1
-DEINK_DISPLAY_SINGLE_BUFFER_MODE=1 -DEINK_DISPLAY_SINGLE_BUFFER_MODE=1
-DDISABLE_FS_H_WARNING=1
# https://libexpat.github.io/doc/api/latest/#XML_GE # https://libexpat.github.io/doc/api/latest/#XML_GE
-DXML_GE=0 -DXML_GE=0
-DXML_CONTEXT_BYTES=1024 -DXML_CONTEXT_BYTES=1024
@@ -39,6 +40,7 @@ lib_deps =
BatteryMonitor=symlink://open-x4-sdk/libs/hardware/BatteryMonitor BatteryMonitor=symlink://open-x4-sdk/libs/hardware/BatteryMonitor
InputManager=symlink://open-x4-sdk/libs/hardware/InputManager InputManager=symlink://open-x4-sdk/libs/hardware/InputManager
EInkDisplay=symlink://open-x4-sdk/libs/display/EInkDisplay EInkDisplay=symlink://open-x4-sdk/libs/display/EInkDisplay
SDCardManager=symlink://open-x4-sdk/libs/hardware/SDCardManager
ArduinoJson @ 7.4.2 ArduinoJson @ 7.4.2
QRCode @ 0.0.1 QRCode @ 0.0.1

View File

@@ -1,8 +1,7 @@
#include "CrossPointSettings.h" #include "CrossPointSettings.h"
#include <FsHelpers.h>
#include <HardwareSerial.h> #include <HardwareSerial.h>
#include <SD.h> #include <SDCardManager.h>
#include <Serialization.h> #include <Serialization.h>
// Initialize the static instance // Initialize the static instance
@@ -17,10 +16,10 @@ constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin";
bool CrossPointSettings::saveToFile() const { bool CrossPointSettings::saveToFile() const {
// Make sure the directory exists // Make sure the directory exists
SD.mkdir("/.crosspoint"); SdMan.mkdir("/.crosspoint");
File outputFile; FsFile outputFile;
if (!FsHelpers::openFileForWrite("CPS", SETTINGS_FILE, outputFile)) { if (!SdMan.openFileForWrite("CPS", SETTINGS_FILE, outputFile)) {
return false; return false;
} }
@@ -40,8 +39,8 @@ bool CrossPointSettings::saveToFile() const {
} }
bool CrossPointSettings::loadFromFile() { bool CrossPointSettings::loadFromFile() {
File inputFile; FsFile inputFile;
if (!FsHelpers::openFileForRead("CPS", SETTINGS_FILE, inputFile)) { if (!SdMan.openFileForRead("CPS", SETTINGS_FILE, inputFile)) {
return false; return false;
} }

View File

@@ -1,7 +1,7 @@
#include "CrossPointState.h" #include "CrossPointState.h"
#include <FsHelpers.h>
#include <HardwareSerial.h> #include <HardwareSerial.h>
#include <SDCardManager.h>
#include <Serialization.h> #include <Serialization.h>
namespace { namespace {
@@ -12,8 +12,8 @@ constexpr char STATE_FILE[] = "/.crosspoint/state.bin";
CrossPointState CrossPointState::instance; CrossPointState CrossPointState::instance;
bool CrossPointState::saveToFile() const { bool CrossPointState::saveToFile() const {
File outputFile; FsFile outputFile;
if (!FsHelpers::openFileForWrite("CPS", STATE_FILE, outputFile)) { if (!SdMan.openFileForWrite("CPS", STATE_FILE, outputFile)) {
return false; return false;
} }
@@ -24,8 +24,8 @@ bool CrossPointState::saveToFile() const {
} }
bool CrossPointState::loadFromFile() { bool CrossPointState::loadFromFile() {
File inputFile; FsFile inputFile;
if (!FsHelpers::openFileForRead("CPS", STATE_FILE, inputFile)) { if (!SdMan.openFileForRead("CPS", STATE_FILE, inputFile)) {
return false; return false;
} }

View File

@@ -1,5 +1,7 @@
#include "MappedInputManager.h" #include "MappedInputManager.h"
#include "CrossPointSettings.h"
decltype(InputManager::BTN_BACK) MappedInputManager::mapButton(const Button button) const { decltype(InputManager::BTN_BACK) MappedInputManager::mapButton(const Button button) const {
const auto frontLayout = static_cast<CrossPointSettings::FRONT_BUTTON_LAYOUT>(SETTINGS.frontButtonLayout); const auto frontLayout = static_cast<CrossPointSettings::FRONT_BUTTON_LAYOUT>(SETTINGS.frontButtonLayout);
const auto sideLayout = static_cast<CrossPointSettings::SIDE_BUTTON_LAYOUT>(SETTINGS.sideButtonLayout); const auto sideLayout = static_cast<CrossPointSettings::SIDE_BUTTON_LAYOUT>(SETTINGS.sideButtonLayout);

View File

@@ -2,8 +2,6 @@
#include <InputManager.h> #include <InputManager.h>
#include "CrossPointSettings.h"
class MappedInputManager { class MappedInputManager {
public: public:
enum class Button { Back, Confirm, Left, Right, Up, Down, Power, PageBack, PageForward }; enum class Button { Back, Confirm, Left, Right, Up, Down, Power, PageBack, PageForward };

View File

@@ -1,8 +1,7 @@
#include "WifiCredentialStore.h" #include "WifiCredentialStore.h"
#include <FsHelpers.h>
#include <HardwareSerial.h> #include <HardwareSerial.h>
#include <SD.h> #include <SDCardManager.h>
#include <Serialization.h> #include <Serialization.h>
// Initialize the static instance // Initialize the static instance
@@ -30,10 +29,10 @@ void WifiCredentialStore::obfuscate(std::string& data) const {
bool WifiCredentialStore::saveToFile() const { bool WifiCredentialStore::saveToFile() const {
// Make sure the directory exists // Make sure the directory exists
SD.mkdir("/.crosspoint"); SdMan.mkdir("/.crosspoint");
File file; FsFile file;
if (!FsHelpers::openFileForWrite("WCS", WIFI_FILE, file)) { if (!SdMan.openFileForWrite("WCS", WIFI_FILE, file)) {
return false; return false;
} }
@@ -60,8 +59,8 @@ bool WifiCredentialStore::saveToFile() const {
} }
bool WifiCredentialStore::loadFromFile() { bool WifiCredentialStore::loadFromFile() {
File file; FsFile file;
if (!FsHelpers::openFileForRead("WCS", WIFI_FILE, file)) { if (!SdMan.openFileForRead("WCS", WIFI_FILE, file)) {
return false; return false;
} }

View File

@@ -5,8 +5,7 @@
#include <string> #include <string>
#include <utility> #include <utility>
#include "../MappedInputManager.h" class MappedInputManager;
class GfxRenderer; class GfxRenderer;
class Activity { class Activity {

View File

@@ -3,7 +3,7 @@
#include <Epub.h> #include <Epub.h>
#include <FsHelpers.h> #include <FsHelpers.h>
#include <GfxRenderer.h> #include <GfxRenderer.h>
#include <SD.h> #include <SDCardManager.h>
#include <Xtc.h> #include <Xtc.h>
#include <vector> #include <vector>
@@ -58,29 +58,31 @@ void SleepActivity::renderPopup(const char* message) const {
void SleepActivity::renderCustomSleepScreen() const { void SleepActivity::renderCustomSleepScreen() const {
// Check if we have a /sleep directory // Check if we have a /sleep directory
auto dir = SD.open("/sleep"); auto dir = SdMan.open("/sleep");
if (dir && dir.isDirectory()) { if (dir && dir.isDirectory()) {
std::vector<std::string> files; std::vector<std::string> files;
char name[128];
// collect all valid BMP files // 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()) { if (file.isDirectory()) {
file.close(); file.close();
continue; continue;
} }
auto filename = std::string(file.name()); file.getName(name, sizeof(name));
auto filename = std::string(name);
if (filename[0] == '.') { if (filename[0] == '.') {
file.close(); file.close();
continue; continue;
} }
if (filename.substr(filename.length() - 4) != ".bmp") { 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(); file.close();
continue; continue;
} }
Bitmap bitmap(file); Bitmap bitmap(file);
if (bitmap.parseHeaders() != BmpReaderError::Ok) { 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(); file.close();
continue; continue;
} }
@@ -92,8 +94,8 @@ void SleepActivity::renderCustomSleepScreen() const {
// Generate a random number between 1 and numFiles // Generate a random number between 1 and numFiles
const auto randomFileIndex = random(numFiles); const auto randomFileIndex = random(numFiles);
const auto filename = "/sleep/" + files[randomFileIndex]; const auto filename = "/sleep/" + files[randomFileIndex];
File file; FsFile file;
if (FsHelpers::openFileForRead("SLP", filename, file)) { if (SdMan.openFileForRead("SLP", filename, file)) {
Serial.printf("[%lu] [SLP] Randomly loading: /sleep/%s\n", millis(), files[randomFileIndex].c_str()); Serial.printf("[%lu] [SLP] Randomly loading: /sleep/%s\n", millis(), files[randomFileIndex].c_str());
delay(100); delay(100);
Bitmap bitmap(file); 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 // 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. // render a custom sleep screen instead of the default.
File file; FsFile file;
if (FsHelpers::openFileForRead("SLP", "/sleep.bmp", file)) { if (SdMan.openFileForRead("SLP", "/sleep.bmp", file)) {
Bitmap bitmap(file); Bitmap bitmap(file);
if (bitmap.parseHeaders() == BmpReaderError::Ok) { if (bitmap.parseHeaders() == BmpReaderError::Ok) {
Serial.printf("[%lu] [SLP] Loading: /sleep.bmp\n", millis()); Serial.printf("[%lu] [SLP] Loading: /sleep.bmp\n", millis());
@@ -224,8 +226,8 @@ void SleepActivity::renderCoverSleepScreen() const {
coverBmpPath = lastEpub.getCoverBmpPath(); coverBmpPath = lastEpub.getCoverBmpPath();
} }
File file; FsFile file;
if (FsHelpers::openFileForRead("SLP", coverBmpPath, file)) { if (SdMan.openFileForRead("SLP", coverBmpPath, file)) {
Bitmap bitmap(file); Bitmap bitmap(file);
if (bitmap.parseHeaders() == BmpReaderError::Ok) { if (bitmap.parseHeaders() == BmpReaderError::Ok) {
renderBitmapSleepScreen(bitmap); renderBitmapSleepScreen(bitmap);

View File

@@ -1,10 +1,10 @@
#include "HomeActivity.h" #include "HomeActivity.h"
#include <GfxRenderer.h> #include <GfxRenderer.h>
#include <InputManager.h> #include <SDCardManager.h>
#include <SD.h>
#include "CrossPointState.h" #include "CrossPointState.h"
#include "MappedInputManager.h"
#include "config.h" #include "config.h"
void HomeActivity::taskTrampoline(void* param) { void HomeActivity::taskTrampoline(void* param) {
@@ -20,7 +20,7 @@ void HomeActivity::onEnter() {
renderingMutex = xSemaphoreCreateMutex(); renderingMutex = xSemaphoreCreateMutex();
// Check if we have a book to continue reading // 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; selectorIndex = 0;

View File

@@ -3,12 +3,12 @@
#include <DNSServer.h> #include <DNSServer.h>
#include <ESPmDNS.h> #include <ESPmDNS.h>
#include <GfxRenderer.h> #include <GfxRenderer.h>
#include <InputManager.h>
#include <WiFi.h> #include <WiFi.h>
#include <qrcode.h> #include <qrcode.h>
#include <cstddef> #include <cstddef>
#include "MappedInputManager.h"
#include "NetworkModeSelectionActivity.h" #include "NetworkModeSelectionActivity.h"
#include "WifiSelectionActivity.h" #include "WifiSelectionActivity.h"
#include "config.h" #include "config.h"

View File

@@ -1,8 +1,8 @@
#include "NetworkModeSelectionActivity.h" #include "NetworkModeSelectionActivity.h"
#include <GfxRenderer.h> #include <GfxRenderer.h>
#include <InputManager.h>
#include "MappedInputManager.h"
#include "config.h" #include "config.h"
namespace { namespace {

View File

@@ -5,6 +5,7 @@
#include <map> #include <map>
#include "MappedInputManager.h"
#include "WifiCredentialStore.h" #include "WifiCredentialStore.h"
#include "activities/util/KeyboardEntryActivity.h" #include "activities/util/KeyboardEntryActivity.h"
#include "config.h" #include "config.h"

View File

@@ -3,12 +3,13 @@
#include <Epub/Page.h> #include <Epub/Page.h>
#include <FsHelpers.h> #include <FsHelpers.h>
#include <GfxRenderer.h> #include <GfxRenderer.h>
#include <InputManager.h> #include <SDCardManager.h>
#include "Battery.h" #include "Battery.h"
#include "CrossPointSettings.h" #include "CrossPointSettings.h"
#include "CrossPointState.h" #include "CrossPointState.h"
#include "EpubReaderChapterSelectionActivity.h" #include "EpubReaderChapterSelectionActivity.h"
#include "MappedInputManager.h"
#include "config.h" #include "config.h"
namespace { namespace {
@@ -54,8 +55,8 @@ void EpubReaderActivity::onEnter() {
epub->setupCacheDir(); epub->setupCacheDir();
File f; FsFile f;
if (FsHelpers::openFileForRead("ERS", epub->getCachePath() + "/progress.bin", f)) { if (SdMan.openFileForRead("ERS", epub->getCachePath() + "/progress.bin", f)) {
uint8_t data[4]; uint8_t data[4];
if (f.read(data, 4) == 4) { if (f.read(data, 4) == 4) {
currentSpineIndex = data[0] + (data[1] << 8); 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); Serial.printf("[%lu] [ERS] Rendered page in %dms\n", millis(), millis() - start);
} }
File f; FsFile f;
if (FsHelpers::openFileForWrite("ERS", epub->getCachePath() + "/progress.bin", f)) { if (SdMan.openFileForWrite("ERS", epub->getCachePath() + "/progress.bin", f)) {
uint8_t data[4]; uint8_t data[4];
data[0] = currentSpineIndex & 0xFF; data[0] = currentSpineIndex & 0xFF;
data[1] = (currentSpineIndex >> 8) & 0xFF; data[1] = (currentSpineIndex >> 8) & 0xFF;

View File

@@ -1,9 +1,8 @@
#include "EpubReaderChapterSelectionActivity.h" #include "EpubReaderChapterSelectionActivity.h"
#include <GfxRenderer.h> #include <GfxRenderer.h>
#include <InputManager.h>
#include <SD.h>
#include "MappedInputManager.h"
#include "config.h" #include "config.h"
namespace { namespace {

View File

@@ -1,9 +1,9 @@
#include "FileSelectionActivity.h" #include "FileSelectionActivity.h"
#include <GfxRenderer.h> #include <GfxRenderer.h>
#include <InputManager.h> #include <SDCardManager.h>
#include <SD.h>
#include "MappedInputManager.h"
#include "config.h" #include "config.h"
namespace { namespace {
@@ -30,17 +30,19 @@ void FileSelectionActivity::taskTrampoline(void* param) {
void FileSelectionActivity::loadFiles() { void FileSelectionActivity::loadFiles() {
files.clear(); files.clear();
selectorIndex = 0; selectorIndex = 0;
auto root = SD.open(basepath.c_str()); auto root = SdMan.open(basepath.c_str());
for (File file = root.openNextFile(); file; file = root.openNextFile()) { char name[128];
auto filename = std::string(file.name()); for (auto file = root.openNextFile(); file; file = root.openNextFile()) {
if (filename[0] == '.') { file.getName(name, sizeof(name));
if (name[0] == '.') {
file.close(); file.close();
continue; continue;
} }
if (file.isDirectory()) { if (file.isDirectory()) {
files.emplace_back(filename + "/"); files.emplace_back(std::string(name) + "/");
} else { } else {
auto filename = std::string(name);
std::string ext4 = filename.length() >= 4 ? filename.substr(filename.length() - 4) : ""; std::string ext4 = filename.length() >= 4 ? filename.substr(filename.length() - 4) : "";
std::string ext5 = filename.length() >= 5 ? filename.substr(filename.length() - 5) : ""; std::string ext5 = filename.length() >= 5 ? filename.substr(filename.length() - 5) : "";
if (ext5 == ".epub" || ext5 == ".xtch" || ext4 == ".xtc") { if (ext5 == ".epub" || ext5 == ".xtch" || ext4 == ".xtc") {

View File

@@ -1,7 +1,5 @@
#include "ReaderActivity.h" #include "ReaderActivity.h"
#include <SD.h>
#include "Epub.h" #include "Epub.h"
#include "EpubReaderActivity.h" #include "EpubReaderActivity.h"
#include "FileSelectionActivity.h" #include "FileSelectionActivity.h"
@@ -29,7 +27,7 @@ bool ReaderActivity::isXtcFile(const std::string& path) {
} }
std::unique_ptr<Epub> ReaderActivity::loadEpub(const std::string& path) { std::unique_ptr<Epub> 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()); Serial.printf("[%lu] [ ] File does not exist: %s\n", millis(), path.c_str());
return nullptr; return nullptr;
} }
@@ -44,7 +42,7 @@ std::unique_ptr<Epub> ReaderActivity::loadEpub(const std::string& path) {
} }
std::unique_ptr<Xtc> ReaderActivity::loadXtc(const std::string& path) { std::unique_ptr<Xtc> 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()); Serial.printf("[%lu] [ ] File does not exist: %s\n", millis(), path.c_str());
return nullptr; return nullptr;
} }

View File

@@ -9,10 +9,10 @@
#include <FsHelpers.h> #include <FsHelpers.h>
#include <GfxRenderer.h> #include <GfxRenderer.h>
#include <InputManager.h> #include <SDCardManager.h>
#include "CrossPointSettings.h"
#include "CrossPointState.h" #include "CrossPointState.h"
#include "MappedInputManager.h"
#include "XtcReaderChapterSelectionActivity.h" #include "XtcReaderChapterSelectionActivity.h"
#include "config.h" #include "config.h"
@@ -357,8 +357,8 @@ void XtcReaderActivity::renderPage() {
} }
void XtcReaderActivity::saveProgress() const { void XtcReaderActivity::saveProgress() const {
File f; FsFile f;
if (FsHelpers::openFileForWrite("XTR", xtc->getCachePath() + "/progress.bin", f)) { if (SdMan.openFileForWrite("XTR", xtc->getCachePath() + "/progress.bin", f)) {
uint8_t data[4]; uint8_t data[4];
data[0] = currentPage & 0xFF; data[0] = currentPage & 0xFF;
data[1] = (currentPage >> 8) & 0xFF; data[1] = (currentPage >> 8) & 0xFF;
@@ -370,8 +370,8 @@ void XtcReaderActivity::saveProgress() const {
} }
void XtcReaderActivity::loadProgress() { void XtcReaderActivity::loadProgress() {
File f; FsFile f;
if (FsHelpers::openFileForRead("XTR", xtc->getCachePath() + "/progress.bin", f)) { if (SdMan.openFileForRead("XTR", xtc->getCachePath() + "/progress.bin", f)) {
uint8_t data[4]; uint8_t data[4];
if (f.read(data, 4) == 4) { if (f.read(data, 4) == 4) {
currentPage = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); currentPage = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);

View File

@@ -1,9 +1,8 @@
#include "XtcReaderChapterSelectionActivity.h" #include "XtcReaderChapterSelectionActivity.h"
#include <GfxRenderer.h> #include <GfxRenderer.h>
#include <InputManager.h>
#include <SD.h>
#include "MappedInputManager.h"
#include "config.h" #include "config.h"
namespace { namespace {

View File

@@ -1,9 +1,9 @@
#include "OtaUpdateActivity.h" #include "OtaUpdateActivity.h"
#include <GfxRenderer.h> #include <GfxRenderer.h>
#include <InputManager.h>
#include <WiFi.h> #include <WiFi.h>
#include "MappedInputManager.h"
#include "activities/network/WifiSelectionActivity.h" #include "activities/network/WifiSelectionActivity.h"
#include "config.h" #include "config.h"
#include "network/OtaUpdater.h" #include "network/OtaUpdater.h"

View File

@@ -1,9 +1,9 @@
#include "SettingsActivity.h" #include "SettingsActivity.h"
#include <GfxRenderer.h> #include <GfxRenderer.h>
#include <InputManager.h>
#include "CrossPointSettings.h" #include "CrossPointSettings.h"
#include "MappedInputManager.h"
#include "OtaUpdateActivity.h" #include "OtaUpdateActivity.h"
#include "config.h" #include "config.h"

View File

@@ -1,6 +1,7 @@
#include "KeyboardEntryActivity.h" #include "KeyboardEntryActivity.h"
#include "../../config.h" #include "../../config.h"
#include "MappedInputManager.h"
// Keyboard layouts - lowercase // Keyboard layouts - lowercase
const char* const KeyboardEntryActivity::keyboard[NUM_ROWS] = { const char* const KeyboardEntryActivity::keyboard[NUM_ROWS] = {

View File

@@ -3,7 +3,7 @@
#include <Epub.h> #include <Epub.h>
#include <GfxRenderer.h> #include <GfxRenderer.h>
#include <InputManager.h> #include <InputManager.h>
#include <SD.h> #include <SDCardManager.h>
#include <SPI.h> #include <SPI.h>
#include <builtinFonts/bookerly_2b.h> #include <builtinFonts/bookerly_2b.h>
#include <builtinFonts/bookerly_bold_2b.h> #include <builtinFonts/bookerly_bold_2b.h>
@@ -37,7 +37,6 @@
#define UART0_RXD 20 // Used for USB connection detection #define UART0_RXD 20 // Used for USB connection detection
#define SD_SPI_CS 12
#define SD_SPI_MISO 7 #define SD_SPI_MISO 7
EInkDisplay einkDisplay(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_RST, EPD_BUSY); EInkDisplay einkDisplay(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_RST, EPD_BUSY);
@@ -189,7 +188,7 @@ void setup() {
// SD Card Initialization // SD Card Initialization
// We need 6 open files concurrently when parsing a new chapter // 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()); Serial.printf("[%lu] [ ] SD card initialization failed\n", millis());
setupDisplayAndFonts(); setupDisplayAndFonts();
exitActivity(); exitActivity();

View File

@@ -2,7 +2,7 @@
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <FsHelpers.h> #include <FsHelpers.h>
#include <SD.h> #include <SDCardManager.h>
#include <WiFi.h> #include <WiFi.h>
#include <algorithm> #include <algorithm>
@@ -170,7 +170,7 @@ void CrossPointWebServer::handleStatus() const {
} }
void CrossPointWebServer::scanFiles(const char* path, const std::function<void(FileInfo)>& callback) const { void CrossPointWebServer::scanFiles(const char* path, const std::function<void(FileInfo)>& callback) const {
File root = SD.open(path); FsFile root = SdMan.open(path);
if (!root) { if (!root) {
Serial.printf("[%lu] [WEB] Failed to open directory: %s\n", millis(), path); Serial.printf("[%lu] [WEB] Failed to open directory: %s\n", millis(), path);
return; return;
@@ -184,9 +184,11 @@ void CrossPointWebServer::scanFiles(const char* path, const std::function<void(F
Serial.printf("[%lu] [WEB] Scanning files in: %s\n", millis(), path); Serial.printf("[%lu] [WEB] Scanning files in: %s\n", millis(), path);
File file = root.openNextFile(); FsFile file = root.openNextFile();
char name[128];
while (file) { while (file) {
auto fileName = String(file.name()); file.getName(name, sizeof(name));
auto fileName = String(name);
// Skip hidden items (starting with ".") // Skip hidden items (starting with ".")
bool shouldHide = fileName.startsWith("."); bool shouldHide = fileName.startsWith(".");
@@ -279,7 +281,7 @@ void CrossPointWebServer::handleFileListData() const {
} }
// Static variables for upload handling // Static variables for upload handling
static File uploadFile; static FsFile uploadFile;
static String uploadFileName; static String uploadFileName;
static String uploadPath = "/"; static String uploadPath = "/";
static size_t uploadSize = 0; static size_t uploadSize = 0;
@@ -334,13 +336,13 @@ void CrossPointWebServer::handleUpload() const {
filePath += uploadFileName; filePath += uploadFileName;
// Check if file already exists // Check if file already exists
if (SD.exists(filePath.c_str())) { if (SdMan.exists(filePath.c_str())) {
Serial.printf("[%lu] [WEB] [UPLOAD] Overwriting existing file: %s\n", millis(), filePath.c_str()); Serial.printf("[%lu] [WEB] [UPLOAD] Overwriting existing file: %s\n", millis(), filePath.c_str());
SD.remove(filePath.c_str()); SdMan.remove(filePath.c_str());
} }
// Open file for writing // Open file for writing
if (!FsHelpers::openFileForWrite("WEB", filePath, uploadFile)) { if (!SdMan.openFileForWrite("WEB", filePath, uploadFile)) {
uploadError = "Failed to create file on SD card"; uploadError = "Failed to create file on SD card";
Serial.printf("[%lu] [WEB] [UPLOAD] FAILED to create file: %s\n", millis(), filePath.c_str()); Serial.printf("[%lu] [WEB] [UPLOAD] FAILED to create file: %s\n", millis(), filePath.c_str());
return; return;
@@ -393,7 +395,7 @@ void CrossPointWebServer::handleUpload() const {
String filePath = uploadPath; String filePath = uploadPath;
if (!filePath.endsWith("/")) filePath += "/"; if (!filePath.endsWith("/")) filePath += "/";
filePath += uploadFileName; filePath += uploadFileName;
SD.remove(filePath.c_str()); SdMan.remove(filePath.c_str());
} }
uploadError = "Upload aborted"; uploadError = "Upload aborted";
Serial.printf("[%lu] [WEB] Upload aborted\n", millis()); Serial.printf("[%lu] [WEB] Upload aborted\n", millis());
@@ -444,13 +446,13 @@ void CrossPointWebServer::handleCreateFolder() const {
Serial.printf("[%lu] [WEB] Creating folder: %s\n", millis(), folderPath.c_str()); Serial.printf("[%lu] [WEB] Creating folder: %s\n", millis(), folderPath.c_str());
// Check if already exists // Check if already exists
if (SD.exists(folderPath.c_str())) { if (SdMan.exists(folderPath.c_str())) {
server->send(400, "text/plain", "Folder already exists"); server->send(400, "text/plain", "Folder already exists");
return; return;
} }
// Create the folder // Create the folder
if (SD.mkdir(folderPath.c_str())) { if (SdMan.mkdir(folderPath.c_str())) {
Serial.printf("[%lu] [WEB] Folder created successfully: %s\n", millis(), folderPath.c_str()); Serial.printf("[%lu] [WEB] Folder created successfully: %s\n", millis(), folderPath.c_str());
server->send(200, "text/plain", "Folder created: " + folderName); server->send(200, "text/plain", "Folder created: " + folderName);
} else { } else {
@@ -500,7 +502,7 @@ void CrossPointWebServer::handleDelete() const {
} }
// Check if item exists // Check if item exists
if (!SD.exists(itemPath.c_str())) { if (!SdMan.exists(itemPath.c_str())) {
Serial.printf("[%lu] [WEB] Delete failed - item not found: %s\n", millis(), itemPath.c_str()); Serial.printf("[%lu] [WEB] Delete failed - item not found: %s\n", millis(), itemPath.c_str());
server->send(404, "text/plain", "Item not found"); server->send(404, "text/plain", "Item not found");
return; return;
@@ -512,10 +514,10 @@ void CrossPointWebServer::handleDelete() const {
if (itemType == "folder") { if (itemType == "folder") {
// For folders, try to remove (will fail if not empty) // For folders, try to remove (will fail if not empty)
File dir = SD.open(itemPath.c_str()); FsFile dir = SdMan.open(itemPath.c_str());
if (dir && dir.isDirectory()) { if (dir && dir.isDirectory()) {
// Check if folder is empty // Check if folder is empty
File entry = dir.openNextFile(); FsFile entry = dir.openNextFile();
if (entry) { if (entry) {
// Folder is not empty // Folder is not empty
entry.close(); entry.close();
@@ -526,10 +528,10 @@ void CrossPointWebServer::handleDelete() const {
} }
dir.close(); dir.close();
} }
success = SD.rmdir(itemPath.c_str()); success = SdMan.rmdir(itemPath.c_str());
} else { } else {
// For files, use remove // For files, use remove
success = SD.remove(itemPath.c_str()); success = SdMan.remove(itemPath.c_str());
} }
if (success) { if (success) {