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 <HardwareSerial.h>
#include <JpegToBmpConverter.h>
#include <SD.h>
#include <SDCardManager.h>
#include <ZipFile.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());
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);
}

View File

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

View File

@@ -1,7 +1,6 @@
#include "BookMetadataCache.h"
#include <HardwareSerial.h>
#include <SD.h>
#include <Serialization.h>
#include <ZipFile.h>
@@ -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<uint32_t>(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);

View File

@@ -1,6 +1,6 @@
#pragma once
#include <SD.h>
#include <SDCardManager.h>
#include <string>
@@ -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;

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);
}
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> PageLine::deserialize(File& file) {
std::unique_ptr<PageLine> 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> Page::deserialize(File& file) {
std::unique_ptr<Page> Page::deserialize(FsFile& file) {
auto page = std::unique_ptr<Page>(new Page());
uint32_t count;

View File

@@ -1,5 +1,5 @@
#pragma once
#include <FS.h>
#include <SdFat.h>
#include <utility>
#include <vector>
@@ -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<TextBlock> 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<PageLine> deserialize(File& file);
bool serialize(FsFile& file) override;
static std::unique_ptr<PageLine> deserialize(FsFile& file);
};
class Page {
@@ -38,6 +38,6 @@ class Page {
// the list of block index and line numbers on this page
std::vector<std::shared_ptr<PageElement>> elements;
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) const;
bool serialize(File& file) const;
static std::unique_ptr<Page> deserialize(File& file);
bool serialize(FsFile& file) const;
static std::unique_ptr<Page> deserialize(FsFile& file);
};

View File

@@ -1,7 +1,6 @@
#include "Section.h"
#include <FsHelpers.h>
#include <SD.h>
#include <SDCardManager.h>
#include <Serialization.h>
#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> page) {
uint32_t Section::onPageComplete(std::unique_ptr<Page> 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<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,
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<void()>& progressSetupFn,
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 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<size_t> lut = {};
std::vector<uint32_t> 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<Page> 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);

View File

@@ -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> page);
uint32_t onPageComplete(std::unique_ptr<Page> page);
public:
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()) {
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> TextBlock::deserialize(File& file) {
std::unique_ptr<TextBlock> TextBlock::deserialize(FsFile& file) {
uint32_t wc;
std::list<std::string> words;
std::list<uint16_t> wordXpos;

View File

@@ -1,6 +1,6 @@
#pragma once
#include <EpdFontFamily.h>
#include <FS.h>
#include <SdFat.h>
#include <list>
#include <memory>
@@ -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<TextBlock> deserialize(File& file);
bool serialize(FsFile& file) const;
static std::unique_ptr<TextBlock> deserialize(FsFile& file);
};

View File

@@ -1,8 +1,8 @@
#include "ChapterHtmlSlimParser.h"
#include <FsHelpers.h>
#include <GfxRenderer.h>
#include <HardwareSerial.h>
#include <SDCardManager.h>
#include <expat.h>
#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;
}

View File

@@ -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());

View File

@@ -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);

View File

@@ -1,81 +1,7 @@
#include "FsHelpers.h"
#include <SD.h>
#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::vector<std::string> components;
std::string component;

View File

@@ -1,14 +1,7 @@
#pragma once
#include <FS.h>
#include <string>
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);
};

View File

@@ -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<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);
}
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;

View File

@@ -1,6 +1,6 @@
#pragma once
#include <FS.h>
#include <SdFat.h>
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;

View File

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

View File

@@ -1,5 +1,7 @@
#include "JpegToBmpConverter.h"
#include <HardwareSerial.h>
#include <SdFat.h>
#include <picojpeg.h>
#include <cstdio>
@@ -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

View File

@@ -1,7 +1,7 @@
#pragma once
#include <FS.h>
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);
};

View File

@@ -1,5 +1,5 @@
#pragma once
#include <FS.h>
#include <SdFat.h>
#include <iostream>
@@ -10,7 +10,7 @@ static void writePod(std::ostream& os, const T& value) {
}
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));
}
@@ -20,7 +20,7 @@ static void readPod(std::istream& is, T& value) {
}
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));
}
@@ -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<const uint8_t*>(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<uint8_t*>(&s[0]), len);
file.read(&s[0], len);
}
} // namespace serialization

View File

@@ -9,7 +9,7 @@
#include <FsHelpers.h>
#include <HardwareSerial.h>
#include <SD.h>
#include <SDCardManager.h>
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;

View File

@@ -9,6 +9,7 @@
#include <FsHelpers.h>
#include <HardwareSerial.h>
#include <SDCardManager.h>
#include <cstring>
@@ -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;
}

View File

@@ -7,7 +7,7 @@
#pragma once
#include <SD.h>
#include <SdFat.h>
#include <functional>
#include <memory>
@@ -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<PageInfo> m_pageTable;

View File

@@ -1,7 +1,7 @@
#include "ZipFile.h"
#include <FsHelpers.h>
#include <HardwareSerial.h>
#include <SDCardManager.h>
#include <miniz.h>
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<uint8_t*>(&sig), 4);
file.read(&sig, 4);
if (sig != 0x02014b50) break; // End of list
FileStatSlim fileStat = {};
file.seek(6, SeekCur);
file.read(reinterpret_cast<uint8_t*>(&fileStat.method), 2);
file.seek(8, SeekCur);
file.read(reinterpret_cast<uint8_t*>(&fileStat.compressedSize), 4);
file.read(reinterpret_cast<uint8_t*>(&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<uint8_t*>(&nameLen), 2);
file.read(reinterpret_cast<uint8_t*>(&m), 2);
file.read(reinterpret_cast<uint8_t*>(&k), 2);
file.seek(8, SeekCur);
file.read(reinterpret_cast<uint8_t*>(&fileStat.localHeaderOffset), 4);
file.read(reinterpret_cast<uint8_t*>(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<uint8_t*>(&sig), 4);
file.read(&sig, 4);
if (sig != 0x02014b50) break; // End of list
file.seek(6, SeekCur);
file.read(reinterpret_cast<uint8_t*>(&fileStat->method), 2);
file.seek(8, SeekCur);
file.read(reinterpret_cast<uint8_t*>(&fileStat->compressedSize), 4);
file.read(reinterpret_cast<uint8_t*>(&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<uint8_t*>(&nameLen), 2);
file.read(reinterpret_cast<uint8_t*>(&m), 2);
file.read(reinterpret_cast<uint8_t*>(&k), 2);
file.seek(8, SeekCur);
file.read(reinterpret_cast<uint8_t*>(&fileStat->localHeaderOffset), 4);
file.read(reinterpret_cast<uint8_t*>(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;

View File

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

View File

@@ -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

View File

@@ -1,8 +1,7 @@
#include "CrossPointSettings.h"
#include <FsHelpers.h>
#include <HardwareSerial.h>
#include <SD.h>
#include <SDCardManager.h>
#include <Serialization.h>
// 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;
}

View File

@@ -1,7 +1,7 @@
#include "CrossPointState.h"
#include <FsHelpers.h>
#include <HardwareSerial.h>
#include <SDCardManager.h>
#include <Serialization.h>
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;
}

View File

@@ -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<CrossPointSettings::FRONT_BUTTON_LAYOUT>(SETTINGS.frontButtonLayout);
const auto sideLayout = static_cast<CrossPointSettings::SIDE_BUTTON_LAYOUT>(SETTINGS.sideButtonLayout);

View File

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

View File

@@ -1,8 +1,7 @@
#include "WifiCredentialStore.h"
#include <FsHelpers.h>
#include <HardwareSerial.h>
#include <SD.h>
#include <SDCardManager.h>
#include <Serialization.h>
// 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;
}

View File

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

View File

@@ -3,7 +3,7 @@
#include <Epub.h>
#include <FsHelpers.h>
#include <GfxRenderer.h>
#include <SD.h>
#include <SDCardManager.h>
#include <Xtc.h>
#include <vector>
@@ -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<std::string> 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);

View File

@@ -1,10 +1,10 @@
#include "HomeActivity.h"
#include <GfxRenderer.h>
#include <InputManager.h>
#include <SD.h>
#include <SDCardManager.h>
#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;

View File

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

View File

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

View File

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

View File

@@ -3,12 +3,13 @@
#include <Epub/Page.h>
#include <FsHelpers.h>
#include <GfxRenderer.h>
#include <InputManager.h>
#include <SDCardManager.h>
#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;

View File

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

View File

@@ -1,9 +1,9 @@
#include "FileSelectionActivity.h"
#include <GfxRenderer.h>
#include <InputManager.h>
#include <SD.h>
#include <SDCardManager.h>
#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") {

View File

@@ -1,7 +1,5 @@
#include "ReaderActivity.h"
#include <SD.h>
#include "Epub.h"
#include "EpubReaderActivity.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) {
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<Epub> ReaderActivity::loadEpub(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());
return nullptr;
}

View File

@@ -9,10 +9,10 @@
#include <FsHelpers.h>
#include <GfxRenderer.h>
#include <InputManager.h>
#include <SDCardManager.h>
#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);

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
#include <Epub.h>
#include <GfxRenderer.h>
#include <InputManager.h>
#include <SD.h>
#include <SDCardManager.h>
#include <SPI.h>
#include <builtinFonts/bookerly_2b.h>
#include <builtinFonts/bookerly_bold_2b.h>
@@ -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();

View File

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