Add exFAT support

This commit is contained in:
Dave Allie
2025-12-29 21:23:25 +11:00
parent 071ccb9d1b
commit 6345861b12
47 changed files with 238 additions and 319 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;

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,16 +70,16 @@ 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;
@@ -195,16 +194,16 @@ 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 {
size_t BookMetadataCache::writeSpineEntry(FsFile& file, const SpineEntry& entry) const {
const auto pos = file.position();
serialization::writeString(file, entry.href);
serialization::writePod(file, entry.cumulativeSize);
@@ -212,7 +211,7 @@ size_t BookMetadataCache::writeSpineEntry(File& file, const SpineEntry& entry) c
return pos;
}
size_t BookMetadataCache::writeTocEntry(File& file, const TocEntry& entry) const {
size_t BookMetadataCache::writeTocEntry(FsFile& file, const TocEntry& entry) const {
const auto pos = file.position();
serialization::writeString(file, entry.title);
serialization::writeString(file, entry.href);
@@ -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;
}
@@ -330,7 +329,7 @@ BookMetadataCache::TocEntry BookMetadataCache::getTocEntry(const int index) {
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;
size_t writeSpineEntry(FsFile& file, const SpineEntry& entry) const;
size_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"
@@ -52,7 +51,7 @@ void Section::writeSectionFileHeader(const int fontId, const float lineCompressi
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;
}
@@ -126,12 +125,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 +138,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,7 +156,7 @@ 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);
@@ -169,11 +168,11 @@ 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;
}
@@ -191,7 +190,7 @@ 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;
}
@@ -204,7 +203,7 @@ 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;
}

View File

@@ -12,7 +12,7 @@ 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);

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