refactor: Drop FsFile alias, use HalFile in downstream code (#2141)

This commit is contained in:
Zach Nelson
2026-05-25 16:26:54 -04:00
committed by GitHub
parent d7797baff2
commit e9120888fa
63 changed files with 201 additions and 205 deletions
+4 -5
View File
@@ -152,20 +152,19 @@ These flags in `platformio.ini` fundamentally affect firmware behavior:
#include <HalStorage.h>
// Use Storage singleton (defined via macro)
FsFile file;
HalFile file;
if (Storage.openFileForRead("MODULE", "/path/to/file.bin", file)) {
// Read from file
// No file.close() needed — DESTRUCTOR_CLOSES_FILE=1 handles it at scope exit
}
```
**Usage**: See example above. Uses `FsFile` (SdFat), NOT Arduino `File`. Do NOT add `file.close()` for local variables (see DESTRUCTOR_CLOSES_FILE above).
**Usage**: Use `HalFile` (the mutex-wrapping handle), NOT raw SdFat `FsFile` or Arduino `File`. Do NOT add `file.close()` for local variables (see DESTRUCTOR_CLOSES_FILE above).
**SdFat is not thread-safe; all SD access MUST go through HalStorage**:
- SdFat's `SdSpiCard` tracks SPI bus state with an unsynchronized `m_spiActive` bool. Two tasks calling SdFat concurrently can confuse that state machine and end with one task calling `SPIClass::endTransaction()` against a paramLock the *other* task is holding. That trips FreeRTOS's `xTaskPriorityDisinherit` assert (`tasks.c:5156, pxTCB == pxCurrentTCBs[0]`) and panics the system. See SdFat issue #518.
- `HalStorage` serializes everything via `storageMutex`. Downstream code includes `<HalStorage.h>`, which transparently `using FsFile = HalFile;`; every method call (read, write, seek, close) takes the mutex. `HalFile`'s destructor also takes the mutex before letting the underlying SdFat `FsFile` close.
- **Never** call into `SdFat` / `SdSpiCard` / `FsBaseFile` / `SDCardManager` directly. **Never** define `HAL_STORAGE_IMPL` outside `HalStorage.cpp`; that disables the `FsFile -> HalFile` typedef and you'll get a raw SdFat handle that bypasses the mutex.
- If you're storing a raw `FsFile` in a place that won't transitively include `<HalStorage.h>` (rare), include the header explicitly so the typedef applies.
- `HalStorage` serializes everything via `storageMutex`. Downstream code uses `HalFile` (declared in `<HalStorage.h>`); every method call (read, write, seek, close) takes the mutex. `HalFile`'s destructor also takes the mutex before letting the underlying SdFat `FsFile` close.
- **Never** call into `SdFat` / `SdSpiCard` / `FsBaseFile` / `SDCardManager` / raw `FsFile` directly — that bypasses the mutex.
---
+6 -6
View File
@@ -168,7 +168,7 @@ bool SdCardFont::loadStyleKernLigatureData(PerStyle& s) {
return true;
}
FsFile file;
HalFile file;
if (!Storage.openFileForRead("SDCF", filePath_, file)) {
LOG_ERR("SDCF", "Failed to open .cpfont for kern/lig: %s", filePath_);
return false;
@@ -345,7 +345,7 @@ bool SdCardFont::buildMiniKernMatrix(PerStyle& s, const uint32_t* codepoints, ui
// Step 6: read the full matrix's rows for each used left class, keep only
// columns for used right classes. One SD seek + one read per used left class;
// a row is kernRightClassCount bytes (~200 for Literata).
FsFile file;
HalFile file;
if (!Storage.openFileForRead("SDCF", filePath_, file)) {
LOG_ERR("SDCF", "Failed to open .cpfont for mini kern: %s", filePath_);
freeStyleMiniKern(s);
@@ -425,7 +425,7 @@ bool SdCardFont::load(const char* path) {
strncpy(filePath_, path, sizeof(filePath_) - 1);
filePath_[sizeof(filePath_) - 1] = '\0';
FsFile file;
HalFile file;
if (!Storage.openFileForRead("SDCF", path, file)) {
LOG_ERR("SDCF", "Failed to open .cpfont: %s", path);
return false;
@@ -798,7 +798,7 @@ int SdCardFont::prewarmStyle(uint8_t styleIdx, const uint32_t* codepoints, uint3
std::sort(readOrder, readOrder + validCount,
[&](uint32_t a, uint32_t b) { return mappings[a].globalIndex < mappings[b].globalIndex; });
FsFile file;
HalFile file;
if (!Storage.openFileForRead("SDCF", filePath_, file)) {
LOG_ERR("SDCF", "Failed to reopen .cpfont for prewarm (style %u)", styleIdx);
delete[] readOrder;
@@ -1104,7 +1104,7 @@ int SdCardFont::fetchAdvancesForCodepoints(uint32_t* codepoints, uint32_t cpCoun
[](const CpIdx& a, const CpIdx& b) { return a.glyphIndex < b.glyphIndex; });
// Open file once and read advanceX for each needed glyph.
FsFile file;
HalFile file;
if (!Storage.openFileForRead("SDCF", filePath_, file)) {
LOG_ERR("SDCF", "buildAdvanceTable: failed to open .cpfont for style %u", si);
continue;
@@ -1277,7 +1277,7 @@ const EpdGlyph* SdCardFont::onGlyphMiss(void* ctx, uint32_t codepoint) {
bool wasAtCapacity = (self->overflowCount_ == OVERFLOW_CAPACITY);
// Read glyph metadata into temporary
FsFile file;
HalFile file;
if (!Storage.openFileForRead("SDCF", self->filePath_, file)) {
LOG_ERR("SDCF", "Overflow: failed to open .cpfont");
return nullptr;
+4 -4
View File
@@ -77,12 +77,12 @@ bool SdCardFontRegistry::parseFilename(const char* filename, uint8_t& size, uint
}
void SdCardFontRegistry::scanDirectory(const char* dirPath, SdCardFontFamilyInfo& family) {
FsFile dir = Storage.open(dirPath);
HalFile dir = Storage.open(dirPath);
if (!dir || !dir.isDirectory()) return;
char nameBuffer[128];
while (true) {
FsFile entry = dir.openNextFile();
HalFile entry = dir.openNextFile();
if (!entry) break;
if (entry.isDirectory()) {
entry.close();
@@ -126,7 +126,7 @@ void SdCardFontRegistry::scanDirectory(const char* dirPath, SdCardFontFamilyInfo
// Skips families whose names already exist in `out` (de-duplicates between
// the hidden and visible roots — first scan wins).
void SdCardFontRegistry::scanRoot(const char* rootPath, std::vector<SdCardFontFamilyInfo>& out) {
FsFile root = Storage.open(rootPath);
HalFile root = Storage.open(rootPath);
if (!root) {
LOG_DBG("SDREG", "Fonts directory not found: %s", rootPath);
return;
@@ -138,7 +138,7 @@ void SdCardFontRegistry::scanRoot(const char* rootPath, std::vector<SdCardFontFa
char nameBuffer[128];
while (true) {
FsFile entry = root.openNextFile();
HalFile entry = root.openNextFile();
if (!entry) break;
if (entry.isDirectory()) {
entry.getName(nameBuffer, sizeof(nameBuffer));
+12 -12
View File
@@ -150,7 +150,7 @@ bool Epub::parseTocNcxFile() const {
LOG_DBG("EBP", "Parsing toc ncx file: %s", tocNcxItem.c_str());
const auto tmpNcxPath = getCachePath() + "/toc.ncx";
FsFile tempNcxFile;
HalFile tempNcxFile;
if (!Storage.openFileForWrite("EBP", tmpNcxPath, tempNcxFile)) {
return false;
}
@@ -206,7 +206,7 @@ bool Epub::parseTocNavFile() const {
LOG_DBG("EBP", "Parsing toc nav file: %s", tocNavItem.c_str());
const auto tmpNavPath = getCachePath() + "/toc.nav";
FsFile tempNavFile;
HalFile tempNavFile;
if (!Storage.openFileForWrite("EBP", tmpNavPath, tempNavFile)) {
return false;
}
@@ -297,7 +297,7 @@ void Epub::parseCssFiles() const {
// Extract CSS file to temp location
const auto tmpCssPath = getCachePath() + "/.tmp.css";
FsFile tempCssFile;
HalFile tempCssFile;
if (!Storage.openFileForWrite("EBP", tmpCssPath, tempCssFile)) {
LOG_ERR("EBP", "Could not create temp CSS file");
continue;
@@ -545,7 +545,7 @@ bool Epub::generateCoverBmp(bool cropped) const {
LOG_DBG("EBP", "Generating BMP from JPG cover image (%s mode)", cropped ? "cropped" : "fit");
const auto coverJpgTempPath = getCachePath() + "/.cover.jpg";
FsFile coverJpg;
HalFile coverJpg;
if (!Storage.openFileForWrite("EBP", coverJpgTempPath, coverJpg)) {
return false;
}
@@ -557,7 +557,7 @@ bool Epub::generateCoverBmp(bool cropped) const {
return false;
}
FsFile coverBmp;
HalFile coverBmp;
if (!Storage.openFileForWrite("EBP", getCoverBmpPath(cropped), coverBmp)) {
return false;
}
@@ -579,7 +579,7 @@ bool Epub::generateCoverBmp(bool cropped) const {
LOG_DBG("EBP", "Generating BMP from PNG cover image (%s mode)", cropped ? "cropped" : "fit");
const auto coverPngTempPath = getCachePath() + "/.cover.png";
FsFile coverPng;
HalFile coverPng;
if (!Storage.openFileForWrite("EBP", coverPngTempPath, coverPng)) {
return false;
}
@@ -591,7 +591,7 @@ bool Epub::generateCoverBmp(bool cropped) const {
return false;
}
FsFile coverBmp;
HalFile coverBmp;
if (!Storage.openFileForWrite("EBP", getCoverBmpPath(cropped), coverBmp)) {
return false;
}
@@ -634,7 +634,7 @@ bool Epub::generateThumbBmp(int height) const {
LOG_DBG("EBP", "Generating thumb BMP from JPG cover image");
const auto coverJpgTempPath = getCachePath() + "/.cover.jpg";
FsFile coverJpg;
HalFile coverJpg;
if (!Storage.openFileForWrite("EBP", coverJpgTempPath, coverJpg)) {
return false;
}
@@ -646,7 +646,7 @@ bool Epub::generateThumbBmp(int height) const {
return false;
}
FsFile thumbBmp;
HalFile thumbBmp;
if (!Storage.openFileForWrite("EBP", getThumbBmpPath(height), thumbBmp)) {
return false;
}
@@ -671,7 +671,7 @@ bool Epub::generateThumbBmp(int height) const {
LOG_DBG("EBP", "Generating thumb BMP from PNG cover image");
const auto coverPngTempPath = getCachePath() + "/.cover.png";
FsFile coverPng;
HalFile coverPng;
if (!Storage.openFileForWrite("EBP", coverPngTempPath, coverPng)) {
return false;
}
@@ -683,7 +683,7 @@ bool Epub::generateThumbBmp(int height) const {
return false;
}
FsFile thumbBmp;
HalFile thumbBmp;
if (!Storage.openFileForWrite("EBP", getThumbBmpPath(height), thumbBmp)) {
return false;
}
@@ -707,7 +707,7 @@ bool Epub::generateThumbBmp(int height) const {
}
// Write an empty bmp file to avoid generation attempts in the future
FsFile thumbBmp;
HalFile thumbBmp;
Storage.openFileForWrite("EBP", getThumbBmpPath(height), thumbBmp);
return false;
}
+4 -4
View File
@@ -292,7 +292,7 @@ bool BookMetadataCache::cleanupTmpFiles() const {
return true;
}
uint32_t BookMetadataCache::writeSpineEntry(FsFile& file, const SpineEntry& entry) const {
uint32_t BookMetadataCache::writeSpineEntry(HalFile& file, const SpineEntry& entry) const {
const uint32_t pos = file.position();
serialization::writeString(file, entry.href);
serialization::writePod(file, entry.cumulativeSize);
@@ -300,7 +300,7 @@ uint32_t BookMetadataCache::writeSpineEntry(FsFile& file, const SpineEntry& entr
return pos;
}
uint32_t BookMetadataCache::writeTocEntry(FsFile& file, const TocEntry& entry) const {
uint32_t BookMetadataCache::writeTocEntry(HalFile& file, const TocEntry& entry) const {
const uint32_t pos = file.position();
serialization::writeString(file, entry.title);
serialization::writeString(file, entry.href);
@@ -438,7 +438,7 @@ BookMetadataCache::TocEntry BookMetadataCache::getTocEntry(const int index) {
return readTocEntry(bookFile);
}
BookMetadataCache::SpineEntry BookMetadataCache::readSpineEntry(FsFile& file) const {
BookMetadataCache::SpineEntry BookMetadataCache::readSpineEntry(HalFile& file) const {
SpineEntry entry;
serialization::readString(file, entry.href);
serialization::readPod(file, entry.cumulativeSize);
@@ -446,7 +446,7 @@ BookMetadataCache::SpineEntry BookMetadataCache::readSpineEntry(FsFile& file) co
return entry;
}
BookMetadataCache::TocEntry BookMetadataCache::readTocEntry(FsFile& file) const {
BookMetadataCache::TocEntry BookMetadataCache::readTocEntry(HalFile& file) const {
TocEntry entry;
serialization::readString(file, entry.title);
serialization::readString(file, entry.href);
+7 -7
View File
@@ -50,10 +50,10 @@ class BookMetadataCache {
bool loaded;
bool buildMode;
FsFile bookFile;
HalFile bookFile;
// Temp file handles during build
FsFile spineFile;
FsFile tocFile;
HalFile spineFile;
HalFile tocFile;
// Index for fast href→spineIndex lookup (used only for large EPUBs)
struct SpineHrefIndexEntry {
@@ -76,10 +76,10 @@ class BookMetadataCache {
return hash;
}
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;
uint32_t writeSpineEntry(HalFile& file, const SpineEntry& entry) const;
uint32_t writeTocEntry(HalFile& file, const TocEntry& entry) const;
SpineEntry readSpineEntry(HalFile& file) const;
TocEntry readTocEntry(HalFile& file) const;
public:
BookMetadata coreMetadata;
+8 -8
View File
@@ -10,7 +10,7 @@ void PageLine::render(GfxRenderer& renderer, const int fontId, const int xOffset
block->render(renderer, fontId, xPos + xOffset, yPos + yOffset);
}
bool PageLine::serialize(FsFile& file) {
bool PageLine::serialize(HalFile& file) {
serialization::writePod(file, xPos);
serialization::writePod(file, yPos);
@@ -18,7 +18,7 @@ bool PageLine::serialize(FsFile& file) {
return block->serialize(file);
}
std::unique_ptr<PageLine> PageLine::deserialize(FsFile& file) {
std::unique_ptr<PageLine> PageLine::deserialize(HalFile& file) {
int16_t xPos;
int16_t yPos;
serialization::readPod(file, xPos);
@@ -33,7 +33,7 @@ void PageImage::render(GfxRenderer& renderer, const int fontId, const int xOffse
imageBlock->render(renderer, xPos + xOffset, yPos + yOffset);
}
bool PageImage::serialize(FsFile& file) {
bool PageImage::serialize(HalFile& file) {
serialization::writePod(file, xPos);
serialization::writePod(file, yPos);
@@ -41,7 +41,7 @@ bool PageImage::serialize(FsFile& file) {
return imageBlock->serialize(file);
}
std::unique_ptr<PageImage> PageImage::deserialize(FsFile& file) {
std::unique_ptr<PageImage> PageImage::deserialize(HalFile& file) {
int16_t xPos;
int16_t yPos;
serialization::readPod(file, xPos);
@@ -60,7 +60,7 @@ void PageHorizontalRule::render(GfxRenderer& renderer, const int fontId, const i
renderer.drawLine(xPos + xOffset, yPos + yOffset, xPos + xOffset + width - 1, yPos + yOffset, thickness, true);
}
bool PageHorizontalRule::serialize(FsFile& file) {
bool PageHorizontalRule::serialize(HalFile& file) {
serialization::writePod(file, xPos);
serialization::writePod(file, yPos);
serialization::writePod(file, width);
@@ -68,7 +68,7 @@ bool PageHorizontalRule::serialize(FsFile& file) {
return true;
}
std::unique_ptr<PageHorizontalRule> PageHorizontalRule::deserialize(FsFile& file) {
std::unique_ptr<PageHorizontalRule> PageHorizontalRule::deserialize(HalFile& file) {
int16_t xPos = 0;
int16_t yPos = 0;
uint16_t width = 0;
@@ -98,7 +98,7 @@ void Page::render(GfxRenderer& renderer, const int fontId, const int xOffset, co
}
}
bool Page::serialize(FsFile& file) const {
bool Page::serialize(HalFile& file) const {
const uint16_t count = elements.size();
serialization::writePod(file, count);
@@ -126,7 +126,7 @@ bool Page::serialize(FsFile& file) const {
return true;
}
std::unique_ptr<Page> Page::deserialize(FsFile& file) {
std::unique_ptr<Page> Page::deserialize(HalFile& file) {
auto page = std::unique_ptr<Page>(new Page());
uint16_t count;
+9 -9
View File
@@ -24,7 +24,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(FsFile& file) = 0;
virtual bool serialize(HalFile& file) = 0;
virtual PageElementTag getTag() const = 0; // Add type identification
};
@@ -37,9 +37,9 @@ class PageLine final : public PageElement {
: PageElement(xPos, yPos), block(std::move(block)) {}
const std::shared_ptr<TextBlock>& getBlock() const { return block; }
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) override;
bool serialize(FsFile& file) override;
bool serialize(HalFile& file) override;
PageElementTag getTag() const override { return TAG_PageLine; }
static std::unique_ptr<PageLine> deserialize(FsFile& file);
static std::unique_ptr<PageLine> deserialize(HalFile& file);
};
// New PageImage class
@@ -50,9 +50,9 @@ class PageImage final : public PageElement {
PageImage(std::shared_ptr<ImageBlock> block, const int16_t xPos, const int16_t yPos)
: PageElement(xPos, yPos), imageBlock(std::move(block)) {}
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) override;
bool serialize(FsFile& file) override;
bool serialize(HalFile& file) override;
PageElementTag getTag() const override { return TAG_PageImage; }
static std::unique_ptr<PageImage> deserialize(FsFile& file);
static std::unique_ptr<PageImage> deserialize(HalFile& file);
const ImageBlock& getImageBlock() const { return *imageBlock; }
};
@@ -65,9 +65,9 @@ class PageHorizontalRule final : public PageElement {
: PageElement(xPos, yPos), width(width), thickness(thickness) {}
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) override;
bool serialize(FsFile& file) override;
bool serialize(HalFile& file) override;
PageElementTag getTag() const override { return TAG_PageHorizontalRule; }
static std::unique_ptr<PageHorizontalRule> deserialize(FsFile& file);
static std::unique_ptr<PageHorizontalRule> deserialize(HalFile& file);
};
class Page {
@@ -88,8 +88,8 @@ class Page {
}
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) const;
bool serialize(FsFile& file) const;
static std::unique_ptr<Page> deserialize(FsFile& file);
bool serialize(HalFile& file) const;
static std::unique_ptr<Page> deserialize(HalFile& file);
// Check if page contains any images (used to force full refresh)
bool hasImages() const {
+5 -5
View File
@@ -176,7 +176,7 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c
Storage.remove(tmpHtmlPath.c_str());
}
FsFile tmpHtml;
HalFile tmpHtml;
if (!Storage.openFileForWrite("SCT", tmpHtmlPath, tmpHtml)) {
continue;
}
@@ -317,7 +317,7 @@ std::unique_ptr<Page> Section::loadPageFromSectionFile() {
}
std::optional<uint16_t> Section::getPageForAnchor(const std::string& anchor) const {
FsFile f;
HalFile f;
if (!Storage.openFileForRead("SCT", filePath, f)) {
return std::nullopt;
}
@@ -347,7 +347,7 @@ std::optional<uint16_t> Section::getPageForAnchor(const std::string& anchor) con
}
std::optional<uint16_t> Section::getPageForParagraphIndex(const uint16_t pIndex) const {
FsFile f;
HalFile f;
if (!Storage.openFileForRead("SCT", filePath, f)) {
return std::nullopt;
}
@@ -386,7 +386,7 @@ std::optional<uint16_t> Section::getPageForParagraphIndex(const uint16_t pIndex)
}
std::optional<uint16_t> Section::getParagraphIndexForPage(const uint16_t page) const {
FsFile f;
HalFile f;
if (!Storage.openFileForRead("SCT", filePath, f)) {
return std::nullopt;
}
@@ -418,7 +418,7 @@ std::optional<uint16_t> Section::getParagraphIndexForPage(const uint16_t page) c
}
std::optional<uint16_t> Section::getPageForListItemIndex(const uint16_t liIndex) const {
FsFile f;
HalFile f;
if (!Storage.openFileForRead("SCT", filePath, f)) {
return std::nullopt;
}
+1 -1
View File
@@ -14,7 +14,7 @@ class Section {
const int spineIndex;
GfxRenderer& renderer;
std::string filePath;
FsFile file;
HalFile file;
void writeSectionFileHeader(int fontId, float lineCompression, bool extraParagraphSpacing, uint8_t paragraphAlignment,
uint16_t viewportWidth, uint16_t viewportHeight, bool hyphenationEnabled,
+4 -4
View File
@@ -30,7 +30,7 @@ std::string getCachePath(const std::string& imagePath) {
bool renderFromCache(GfxRenderer& renderer, const std::string& cachePath, int x, int y, int expectedWidth,
int expectedHeight) {
FsFile cacheFile;
HalFile cacheFile;
if (!Storage.openFileForRead("IMG", cachePath, cacheFile)) {
return false;
}
@@ -112,7 +112,7 @@ void ImageBlock::render(GfxRenderer& renderer, const int x, const int y) {
// No cache - need to decode the image
// Check if image file exists
FsFile file;
HalFile file;
if (!Storage.openFileForRead("IMG", imagePath, file)) {
LOG_ERR("IMG", "Image file not found: %s", imagePath.c_str());
return;
@@ -155,14 +155,14 @@ void ImageBlock::render(GfxRenderer& renderer, const int x, const int y) {
LOG_DBG("IMG", "Decode successful");
}
bool ImageBlock::serialize(FsFile& file) {
bool ImageBlock::serialize(HalFile& file) {
serialization::writeString(file, imagePath);
serialization::writePod(file, width);
serialization::writePod(file, height);
return true;
}
std::unique_ptr<ImageBlock> ImageBlock::deserialize(FsFile& file) {
std::unique_ptr<ImageBlock> ImageBlock::deserialize(HalFile& file) {
std::string path;
serialization::readString(file, path);
int16_t w, h;
+2 -2
View File
@@ -21,8 +21,8 @@ class ImageBlock final : public Block {
bool isEmpty() override { return false; }
void render(GfxRenderer& renderer, const int x, const int y);
bool serialize(FsFile& file);
static std::unique_ptr<ImageBlock> deserialize(FsFile& file);
bool serialize(HalFile& file);
static std::unique_ptr<ImageBlock> deserialize(HalFile& file);
private:
std::string imagePath;
+2 -2
View File
@@ -67,7 +67,7 @@ void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int
}
}
bool TextBlock::serialize(FsFile& file) const {
bool TextBlock::serialize(HalFile& file) const {
// Focus annotations are optional; vectors are either empty (no splits in this block)
// or sized in lockstep with words[].
const bool hasFocus = !wordFocusBoundary.empty();
@@ -110,7 +110,7 @@ bool TextBlock::serialize(FsFile& file) const {
return true;
}
std::unique_ptr<TextBlock> TextBlock::deserialize(FsFile& file) {
std::unique_ptr<TextBlock> TextBlock::deserialize(HalFile& file) {
uint16_t wc;
std::vector<std::string> words;
std::vector<int16_t> wordXpos;
+2 -2
View File
@@ -47,6 +47,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(FsFile& file) const;
static std::unique_ptr<TextBlock> deserialize(FsFile& file);
bool serialize(HalFile& file) const;
static std::unique_ptr<TextBlock> deserialize(HalFile& file);
};
@@ -19,7 +19,7 @@ namespace {
// Context struct passed through JPEGDEC callbacks to avoid global mutable state.
// The draw callback receives this via pDraw->pUser (set by setUserPointer()).
// The file I/O callbacks receive the FsFile* via pFile->fHandle (set by jpegOpen()).
// The file I/O callbacks receive the HalFile* via pFile->fHandle (set by jpegOpen()).
struct JpegContext {
GfxRenderer* renderer{nullptr};
const RenderConfig* config{nullptr};
@@ -48,10 +48,10 @@ struct JpegContext {
bool caching{false};
};
// File I/O callbacks use pFile->fHandle to access the FsFile*,
// File I/O callbacks use pFile->fHandle to access the HalFile*,
// avoiding the need for global file state.
void* jpegOpen(const char* filename, int32_t* size) {
FsFile* f = new FsFile();
HalFile* f = new HalFile();
if (!Storage.openFileForRead("JPG", std::string(filename), *f)) {
delete f;
return nullptr;
@@ -61,7 +61,7 @@ void* jpegOpen(const char* filename, int32_t* size) {
}
void jpegClose(void* handle) {
FsFile* f = reinterpret_cast<FsFile*>(handle);
HalFile* f = reinterpret_cast<HalFile*>(handle);
if (f) {
f->close();
delete f;
@@ -73,7 +73,7 @@ void jpegClose(void* handle) {
// MUST maintain iPos to match the actual file position, otherwise progressive
// JPEGs with large headers fail during parsing.
int32_t jpegRead(JPEGFILE* pFile, uint8_t* pBuf, int32_t len) {
FsFile* f = reinterpret_cast<FsFile*>(pFile->fHandle);
HalFile* f = reinterpret_cast<HalFile*>(pFile->fHandle);
if (!f) return 0;
int32_t bytesRead = f->read(pBuf, len);
if (bytesRead < 0) return 0;
@@ -82,7 +82,7 @@ int32_t jpegRead(JPEGFILE* pFile, uint8_t* pBuf, int32_t len) {
}
int32_t jpegSeek(JPEGFILE* pFile, int32_t pos) {
FsFile* f = reinterpret_cast<FsFile*>(pFile->fHandle);
HalFile* f = reinterpret_cast<HalFile*>(pFile->fHandle);
if (!f) return -1;
if (!f->seek(pos)) return -1;
pFile->iPos = pos;
+1 -1
View File
@@ -56,7 +56,7 @@ struct PixelCache {
bool writeToFile(const std::string& cachePath) {
if (!buffer) return false;
FsFile cacheFile;
HalFile cacheFile;
if (!Storage.openFileForWrite("IMG", cachePath, cacheFile)) {
LOG_ERR("IMG", "Failed to open cache file for writing: %s", cachePath.c_str());
return false;
@@ -19,7 +19,7 @@ namespace {
// Context struct passed through PNGdec callbacks to avoid global mutable state.
// The draw callback receives this via pDraw->pUser (set by png.decode()).
// The file I/O callbacks receive the FsFile* via pFile->fHandle (set by pngOpen()).
// The file I/O callbacks receive the HalFile* via pFile->fHandle (set by pngOpen()).
struct PngContext {
GfxRenderer* renderer{nullptr};
const RenderConfig* config{nullptr};
@@ -40,10 +40,10 @@ struct PngContext {
uint8_t* grayLineBuffer{nullptr};
};
// File I/O callbacks use pFile->fHandle to access the FsFile*,
// File I/O callbacks use pFile->fHandle to access the HalFile*,
// avoiding the need for global file state.
void* pngOpenWithHandle(const char* filename, int32_t* size) {
FsFile* f = new FsFile();
HalFile* f = new HalFile();
if (!Storage.openFileForRead("PNG", std::string(filename), *f)) {
delete f;
return nullptr;
@@ -53,7 +53,7 @@ void* pngOpenWithHandle(const char* filename, int32_t* size) {
}
void pngCloseWithHandle(void* handle) {
FsFile* f = reinterpret_cast<FsFile*>(handle);
HalFile* f = reinterpret_cast<HalFile*>(handle);
if (f) {
f->close();
delete f;
@@ -61,13 +61,13 @@ void pngCloseWithHandle(void* handle) {
}
int32_t pngReadWithHandle(PNGFILE* pFile, uint8_t* pBuf, int32_t len) {
FsFile* f = reinterpret_cast<FsFile*>(pFile->fHandle);
HalFile* f = reinterpret_cast<HalFile*>(pFile->fHandle);
if (!f) return 0;
return f->read(pBuf, len);
}
int32_t pngSeekWithHandle(PNGFILE* pFile, int32_t pos) {
FsFile* f = reinterpret_cast<FsFile*>(pFile->fHandle);
HalFile* f = reinterpret_cast<HalFile*>(pFile->fHandle);
if (!f) return -1;
return f->seek(pos);
}
+3 -3
View File
@@ -460,7 +460,7 @@ void CssParser::processRuleBlockWithStyle(const std::string& selectorGroup, cons
// Main parsing entry point
bool CssParser::loadFromStream(FsFile& source) {
bool CssParser::loadFromStream(HalFile& source) {
if (!source) {
LOG_ERR("CSS", "Cannot read from invalid file");
return false;
@@ -676,7 +676,7 @@ bool CssParser::saveToCache() const {
return false;
}
FsFile file;
HalFile file;
if (!Storage.openFileForWrite("CSS", cachePath + rulesCache, file)) {
return false;
}
@@ -751,7 +751,7 @@ bool CssParser::loadFromCache() {
return false;
}
FsFile file;
HalFile file;
if (!Storage.openFileForRead("CSS", cachePath + rulesCache, file)) {
return false;
}
+1 -1
View File
@@ -46,7 +46,7 @@ class CssParser {
* @param source Open file handle to read from
* @return true if parsing completed (even if no rules found)
*/
bool loadFromStream(FsFile& source);
bool loadFromStream(HalFile& source);
/**
* Look up the style for an HTML element, considering tag name and class attributes.
@@ -380,7 +380,7 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
std::string cachedImagePath = self->imageBasePath + std::to_string(self->imageCounter++) + ext;
// Extract image to cache file
FsFile cachedImageFile;
HalFile cachedImageFile;
bool extractSuccess = false;
if (Storage.openFileForWrite("EHP", cachedImagePath, cachedImageFile)) {
extractSuccess = self->epub->readItemContentsToStream(resolvedPath, cachedImageFile, 4096);
@@ -1150,7 +1150,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
// Using DefaultHandlerExpand preserves normal entity expansion from DOCTYPE
XML_SetDefaultHandlerExpand(parser, defaultHandlerExpand);
FsFile file;
HalFile file;
if (!Storage.openFileForRead("EHP", filepath, file)) {
destroyXmlParser(parser);
return false;
+1 -1
View File
@@ -29,7 +29,7 @@ class ContentOpfParser final : public Print {
XML_Parser parser = nullptr;
ParserState state = START;
BookMetadataCache* cache;
FsFile tempItemStore;
HalFile tempItemStore;
std::string coverItemId;
// Index for fast idref→href lookup (used only for large EPUBs)
+2 -2
View File
@@ -21,7 +21,7 @@ Bitmap::~Bitmap() {
delete fsDitherer;
}
uint16_t Bitmap::readLE16(FsFile& f) {
uint16_t Bitmap::readLE16(HalFile& f) {
const int c0 = f.read();
const int c1 = f.read();
const auto b0 = static_cast<uint8_t>(c0 < 0 ? 0 : c0);
@@ -29,7 +29,7 @@ uint16_t Bitmap::readLE16(FsFile& f) {
return static_cast<uint16_t>(b0) | (static_cast<uint16_t>(b1) << 8);
}
uint32_t Bitmap::readLE32(FsFile& f) {
uint32_t Bitmap::readLE32(HalFile& f) {
const int c0 = f.read();
const int c1 = f.read();
const int c2 = f.read();
+4 -4
View File
@@ -64,7 +64,7 @@ class Bitmap {
public:
static const char* errorToString(BmpReaderError err);
explicit Bitmap(FsFile& file, bool dithering = false) : file(file), dithering(dithering) {}
explicit Bitmap(HalFile& file, bool dithering = false) : file(file), dithering(dithering) {}
~Bitmap();
BmpReaderError parseHeaders();
BmpReaderError readNextRow(uint8_t* data, uint8_t* rowBuffer) const;
@@ -78,10 +78,10 @@ class Bitmap {
uint16_t getBpp() const { return bpp; }
private:
static uint16_t readLE16(FsFile& f);
static uint32_t readLE32(FsFile& f);
static uint16_t readLE16(HalFile& f);
static uint32_t readLE32(HalFile& f);
FsFile& file;
HalFile& file;
bool dithering = false;
int width = 0;
int height = 0;
+1 -1
View File
@@ -25,7 +25,7 @@ enum class InflateStatus {
//
// struct MyCtx {
// InflateReader reader; // must be first
// FsFile* file;
// HalFile* file;
// // ...
// };
// static int myCb(struct uzlib_uncomp* u) {
@@ -167,7 +167,7 @@ constexpr size_t MIN_FREE_HEAP = JPEG_DECODER_SIZE + 32 * 1024;
// Static file pointer for JPEGDEC open callback.
// Safe in single-threaded embedded context; never accessed concurrently.
static FsFile* s_jpegFile = nullptr;
static HalFile* s_jpegFile = nullptr;
void* bmpJpegOpen(const char* /*filename*/, int32_t* size) {
if (!s_jpegFile || !*s_jpegFile) return nullptr;
@@ -181,7 +181,7 @@ void bmpJpegClose(void* /*handle*/) {
}
int32_t bmpJpegRead(JPEGFILE* pFile, uint8_t* pBuf, int32_t len) {
auto* f = reinterpret_cast<FsFile*>(pFile->fHandle);
auto* f = reinterpret_cast<HalFile*>(pFile->fHandle);
if (!f) return 0;
int32_t n = f->read(pBuf, len);
if (n < 0) n = 0;
@@ -190,7 +190,7 @@ int32_t bmpJpegRead(JPEGFILE* pFile, uint8_t* pBuf, int32_t len) {
}
int32_t bmpJpegSeek(JPEGFILE* pFile, int32_t pos) {
auto* f = reinterpret_cast<FsFile*>(pFile->fHandle);
auto* f = reinterpret_cast<HalFile*>(pFile->fHandle);
if (!f || !f->seek(pos)) return -1;
pFile->iPos = pos;
return pos;
@@ -376,8 +376,8 @@ int bmpDrawCallback(JPEGDRAW* pDraw) {
} // namespace
// Internal implementation with configurable target size and bit depth
bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bmpOut, int targetWidth, int targetHeight,
bool oneBit, bool crop) {
bool JpegToBmpConverter::jpegFileToBmpStreamInternal(HalFile& jpegFile, Print& bmpOut, int targetWidth,
int targetHeight, bool oneBit, bool crop) {
LOG_DBG("JPG", "Converting JPEG to %s BMP (target: %dx%d)", oneBit ? "1-bit" : "2-bit", targetWidth, targetHeight);
if (ESP.getFreeHeap() < MIN_FREE_HEAP) {
@@ -532,7 +532,7 @@ bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bm
}
// Core function: Convert JPEG file to 2-bit BMP (uses default target size)
bool JpegToBmpConverter::jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut, bool crop) {
bool JpegToBmpConverter::jpegFileToBmpStream(HalFile& jpegFile, Print& bmpOut, bool crop) {
// Use runtime display dimensions (swapped for portrait cover sizing)
const int targetWidth = display.getDisplayHeight();
const int targetHeight = display.getDisplayWidth();
@@ -540,13 +540,13 @@ bool JpegToBmpConverter::jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut, bo
}
// Convert with custom target size (for thumbnails, 2-bit)
bool JpegToBmpConverter::jpegFileToBmpStreamWithSize(FsFile& jpegFile, Print& bmpOut, int targetMaxWidth,
bool JpegToBmpConverter::jpegFileToBmpStreamWithSize(HalFile& jpegFile, Print& bmpOut, int targetMaxWidth,
int targetMaxHeight) {
return jpegFileToBmpStreamInternal(jpegFile, bmpOut, targetMaxWidth, targetMaxHeight, false);
}
// Convert to 1-bit BMP (black and white only, no grays) for fast home screen rendering
bool JpegToBmpConverter::jpegFileTo1BitBmpStreamWithSize(FsFile& jpegFile, Print& bmpOut, int targetMaxWidth,
bool JpegToBmpConverter::jpegFileTo1BitBmpStreamWithSize(HalFile& jpegFile, Print& bmpOut, int targetMaxWidth,
int targetMaxHeight) {
return jpegFileToBmpStreamInternal(jpegFile, bmpOut, targetMaxWidth, targetMaxHeight, true, true);
}
+5 -4
View File
@@ -6,13 +6,14 @@ class Print;
class ZipFile;
class JpegToBmpConverter {
static bool jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bmpOut, int targetWidth, int targetHeight,
static bool jpegFileToBmpStreamInternal(HalFile& jpegFile, Print& bmpOut, int targetWidth, int targetHeight,
bool oneBit, bool crop = true);
public:
static bool jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut, bool crop = true);
static bool jpegFileToBmpStream(HalFile& jpegFile, Print& bmpOut, bool crop = true);
// Convert with custom target size (for thumbnails)
static bool jpegFileToBmpStreamWithSize(FsFile& jpegFile, Print& bmpOut, int targetMaxWidth, int targetMaxHeight);
static bool jpegFileToBmpStreamWithSize(HalFile& jpegFile, Print& bmpOut, int targetMaxWidth, int targetMaxHeight);
// Convert to 1-bit BMP (black and white only, no grays) for fast home screen rendering
static bool jpegFileTo1BitBmpStreamWithSize(FsFile& jpegFile, Print& bmpOut, int targetMaxWidth, int targetMaxHeight);
static bool jpegFileTo1BitBmpStreamWithSize(HalFile& jpegFile, Print& bmpOut, int targetMaxWidth,
int targetMaxHeight);
};
+1 -1
View File
@@ -73,7 +73,7 @@ bool KOReaderCredentialStore::loadFromFile() {
}
bool KOReaderCredentialStore::loadFromBinaryFile() {
FsFile file;
HalFile file;
if (!Storage.openFileForRead("KRS", KOREADER_FILE_BIN, file)) {
return false;
}
+1 -1
View File
@@ -42,7 +42,7 @@ size_t KOReaderDocumentId::getOffset(int i) {
}
std::string KOReaderDocumentId::calculate(const std::string& filePath) {
FsFile file;
HalFile file;
if (!Storage.openFileForRead("KODoc", filePath, file)) {
LOG_DBG("KODoc", "Failed to open file: %s", filePath.c_str());
return "";
+6 -6
View File
@@ -73,7 +73,7 @@ enum PngFilter : uint8_t {
};
// Read a big-endian 32-bit value from file
bool readBE32(FsFile& file, uint32_t& value) {
bool readBE32(HalFile& file, uint32_t& value) {
uint8_t buf[4];
if (file.read(buf, 4) != 4) return false;
value = (static_cast<uint32_t>(buf[0]) << 24) | (static_cast<uint32_t>(buf[1]) << 16) |
@@ -177,7 +177,7 @@ void writeBmpHeader2bit(Print& bmpOut, const int width, const int height) {
// IMPORTANT: reader must be the first field - the uzlib callback casts uzlib_uncomp* to PngDecodeContext*
struct PngDecodeContext {
InflateReader reader; // Must be first — callback casts uzlib_uncomp* to PngDecodeContext*
FsFile* file;
HalFile* file;
// PNG image properties
uint32_t width;
@@ -395,7 +395,7 @@ static void convertScanlineToGray(const PngDecodeContext& ctx, uint8_t* grayRow)
}
}
bool PngToBmpConverter::pngFileToBmpStreamInternal(FsFile& pngFile, Print& bmpOut, int targetWidth, int targetHeight,
bool PngToBmpConverter::pngFileToBmpStreamInternal(HalFile& pngFile, Print& bmpOut, int targetWidth, int targetHeight,
bool oneBit, bool crop) {
LOG_DBG("PNG", "Converting PNG to %s BMP (target: %dx%d)", oneBit ? "1-bit" : "2-bit", targetWidth, targetHeight);
@@ -820,19 +820,19 @@ bool PngToBmpConverter::pngFileToBmpStreamInternal(FsFile& pngFile, Print& bmpOu
return success;
}
bool PngToBmpConverter::pngFileToBmpStream(FsFile& pngFile, Print& bmpOut, bool crop) {
bool PngToBmpConverter::pngFileToBmpStream(HalFile& pngFile, Print& bmpOut, bool crop) {
// Use runtime display dimensions (swapped for portrait cover sizing)
const int targetWidth = display.getDisplayHeight();
const int targetHeight = display.getDisplayWidth();
return pngFileToBmpStreamInternal(pngFile, bmpOut, targetWidth, targetHeight, false, crop);
}
bool PngToBmpConverter::pngFileToBmpStreamWithSize(FsFile& pngFile, Print& bmpOut, int targetMaxWidth,
bool PngToBmpConverter::pngFileToBmpStreamWithSize(HalFile& pngFile, Print& bmpOut, int targetMaxWidth,
int targetMaxHeight) {
return pngFileToBmpStreamInternal(pngFile, bmpOut, targetMaxWidth, targetMaxHeight, false);
}
bool PngToBmpConverter::pngFileTo1BitBmpStreamWithSize(FsFile& pngFile, Print& bmpOut, int targetMaxWidth,
bool PngToBmpConverter::pngFileTo1BitBmpStreamWithSize(HalFile& pngFile, Print& bmpOut, int targetMaxWidth,
int targetMaxHeight) {
return pngFileToBmpStreamInternal(pngFile, bmpOut, targetMaxWidth, targetMaxHeight, true, true);
}
+5 -5
View File
@@ -5,11 +5,11 @@
class Print;
class PngToBmpConverter {
static bool pngFileToBmpStreamInternal(FsFile& pngFile, Print& bmpOut, int targetWidth, int targetHeight, bool oneBit,
bool crop = true);
static bool pngFileToBmpStreamInternal(HalFile& pngFile, Print& bmpOut, int targetWidth, int targetHeight,
bool oneBit, bool crop = true);
public:
static bool pngFileToBmpStream(FsFile& pngFile, Print& bmpOut, bool crop = true);
static bool pngFileToBmpStreamWithSize(FsFile& pngFile, Print& bmpOut, int targetMaxWidth, int targetMaxHeight);
static bool pngFileTo1BitBmpStreamWithSize(FsFile& pngFile, Print& bmpOut, int targetMaxWidth, int targetMaxHeight);
static bool pngFileToBmpStream(HalFile& pngFile, Print& bmpOut, bool crop = true);
static bool pngFileToBmpStreamWithSize(HalFile& pngFile, Print& bmpOut, int targetMaxWidth, int targetMaxHeight);
static bool pngFileTo1BitBmpStreamWithSize(HalFile& pngFile, Print& bmpOut, int targetMaxWidth, int targetMaxHeight);
};
+4 -4
View File
@@ -10,7 +10,7 @@ void writePod(std::ostream& os, const T& value) {
}
template <typename T>
void writePod(FsFile& file, const T& value) {
void writePod(HalFile& file, const T& value) {
file.write(reinterpret_cast<const uint8_t*>(&value), sizeof(T));
}
@@ -20,7 +20,7 @@ void readPod(std::istream& is, T& value) {
}
template <typename T>
void readPod(FsFile& file, T& value) {
void readPod(HalFile& file, T& value) {
file.read(reinterpret_cast<uint8_t*>(&value), sizeof(T));
}
@@ -30,7 +30,7 @@ inline void writeString(std::ostream& os, const std::string& s) {
os.write(s.data(), len);
}
inline void writeString(FsFile& file, const std::string& s) {
inline void writeString(HalFile& 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,7 +43,7 @@ inline void readString(std::istream& is, std::string& s) {
is.read(&s[0], len);
}
inline void readString(FsFile& file, std::string& s) {
inline void readString(HalFile& file, std::string& s) {
uint32_t len;
readPod(file, len);
s.resize(len);
+4 -4
View File
@@ -21,7 +21,7 @@ bool Txt::load() {
return false;
}
FsFile file;
HalFile file;
if (!Storage.openFileForRead("TXT", filepath, file)) {
LOG_ERR("TXT", "Failed to open file: %s", filepath.c_str());
return false;
@@ -115,7 +115,7 @@ bool Txt::generateCoverBmp() const {
if (FsHelpers::hasBmpExtension(coverImagePath)) {
// Copy BMP file to cache
LOG_DBG("TXT", "Copying BMP cover image to cache");
FsFile src, dst;
HalFile src, dst;
if (!Storage.openFileForRead("TXT", coverImagePath, src)) {
return false;
}
@@ -132,7 +132,7 @@ bool Txt::generateCoverBmp() const {
} else if (FsHelpers::hasJpgExtension(coverImagePath)) {
// Convert JPG/JPEG to BMP (same approach as Epub)
LOG_DBG("TXT", "Generating BMP from JPG cover image");
FsFile coverJpg, coverBmp;
HalFile coverJpg, coverBmp;
if (!Storage.openFileForRead("TXT", coverImagePath, coverJpg)) {
return false;
}
@@ -175,7 +175,7 @@ bool Txt::readContent(uint8_t* buffer, size_t offset, size_t length) const {
return false;
}
FsFile file;
HalFile file;
if (!Storage.openFileForRead("TXT", filepath, file)) {
return false;
}
+3 -3
View File
@@ -166,7 +166,7 @@ bool Xtc::generateCoverBmp() const {
}
// Create BMP file
FsFile coverBmp;
HalFile coverBmp;
if (!Storage.openFileForWrite("XTC", getCoverBmpPath(), coverBmp)) {
LOG_DBG("XTC", "Failed to create cover BMP file");
free(pageBuffer);
@@ -306,7 +306,7 @@ bool Xtc::generateThumbBmp(int height) const {
// Page is already small enough, just use cover.bmp
// Copy cover.bmp to thumb.bmp
if (generateCoverBmp()) {
FsFile src, dst;
HalFile src, dst;
if (Storage.openFileForRead("XTC", getCoverBmpPath(), src)) {
if (Storage.openFileForWrite("XTC", getThumbBmpPath(height), dst)) {
uint8_t buffer[512];
@@ -350,7 +350,7 @@ bool Xtc::generateThumbBmp(int height) const {
}
// Create thumbnail BMP file - use 1-bit format for fast home screen rendering (no gray passes)
FsFile thumbBmp;
HalFile thumbBmp;
if (!Storage.openFileForWrite("XTC", getThumbBmpPath(height), thumbBmp)) {
LOG_DBG("XTC", "Failed to create thumb BMP file");
free(pageBuffer);
+1 -1
View File
@@ -530,7 +530,7 @@ XtcError XtcParser::loadPageStreaming(uint32_t pageIndex,
}
bool XtcParser::isValidXtcFile(const char* filepath) {
FsFile file;
HalFile file;
if (!Storage.openFileForRead("XTC", filepath, file)) {
return false;
}
+1 -1
View File
@@ -84,7 +84,7 @@ class XtcParser {
XtcError getLastError() const { return m_lastError; }
private:
FsFile m_file;
HalFile m_file;
std::string m_filepath;
bool m_isOpen;
XtcHeader m_header;
+1 -1
View File
@@ -8,7 +8,7 @@
struct ZipInflateCtx {
InflateReader reader; // Must be first — callback casts uzlib_uncomp* to ZipInflateCtx*
FsFile* file = nullptr;
HalFile* file = nullptr;
size_t fileRemaining = 0;
uint8_t* readBuf = nullptr;
size_t readBufSize = 0;
+1 -1
View File
@@ -39,7 +39,7 @@ class ZipFile {
private:
const std::string& filePath;
FsFile file;
HalFile file;
ZipDetails zipDetails = {0, 0, false};
std::unordered_map<std::string, FileStatSlim> fileStatSlimCache;
-1
View File
@@ -1,4 +1,3 @@
#define HAL_STORAGE_IMPL
#include "HalStorage.h"
#include <FS.h> // need to be included before SdFat.h for compatibility with FS.h's File class
-7
View File
@@ -96,13 +96,6 @@ class HalFile : public Print {
operator bool() const;
};
// Only do renaming FsFile to HalFile if this header is included by downstream code
// The renaming is to allow using the thread-safe HalFile instead of the raw FsFile, without needing to change the
// downstream code
#ifndef HAL_STORAGE_IMPL
using FsFile = HalFile;
#endif
// Downstream code must use Storage instead of SdMan
#ifdef SdMan
#undef SdMan
+3 -3
View File
@@ -14,7 +14,7 @@
// Initialize the static instance
CrossPointSettings CrossPointSettings::instance;
void readAndValidate(FsFile& file, uint8_t& member, const uint8_t maxValue) {
void readAndValidate(HalFile& file, uint8_t& member, const uint8_t maxValue) {
uint8_t tempValue;
serialization::readPod(file, tempValue);
if (tempValue < maxValue) {
@@ -127,7 +127,7 @@ bool CrossPointSettings::migrateLanguageBinaryFile() {
// frozen enum order from 2f969a9.
if (!Storage.exists(LANG_FILE_BIN)) return false;
FsFile f;
HalFile f;
if (Storage.openFileForRead("CPS", LANG_FILE_BIN, f)) {
uint8_t version;
serialization::readPod(f, version);
@@ -146,7 +146,7 @@ bool CrossPointSettings::migrateLanguageBinaryFile() {
}
bool CrossPointSettings::loadFromBinaryFile() {
FsFile inputFile;
HalFile inputFile;
if (!Storage.openFileForRead("CPS", SETTINGS_FILE_BIN, inputFile)) {
return false;
}
+1 -1
View File
@@ -269,7 +269,7 @@ class CrossPointSettings {
int getReaderFontId() const;
// If count_only is true, returns the number of settings items that would be written.
uint8_t writeSettings(FsFile& file, bool count_only = false) const;
uint8_t writeSettings(HalFile& file, bool count_only = false) const;
bool saveToFile() const;
bool loadFromFile();
+1 -1
View File
@@ -63,7 +63,7 @@ bool CrossPointState::loadFromFile() {
}
bool CrossPointState::loadFromBinaryFile() {
FsFile inputFile;
HalFile inputFile;
if (!Storage.openFileForRead("CPS", STATE_FILE_BIN, inputFile)) {
return false;
}
+1 -1
View File
@@ -81,7 +81,7 @@ bool FontInstaller::ensureFamilyDir(const char* familyName) {
}
bool FontInstaller::validateCpfontFile(const char* path) {
FsFile file;
HalFile file;
if (!Storage.openFileForRead("FONT", path, file)) {
LOG_ERR("FONT", "Cannot open for validation: %s", path);
return false;
+1 -1
View File
@@ -148,7 +148,7 @@ bool RecentBooksStore::loadFromFile() {
}
bool RecentBooksStore::loadFromBinaryFile() {
FsFile inputFile;
HalFile inputFile;
if (!Storage.openFileForRead("RBS", RECENT_BOOKS_FILE_BIN, inputFile)) {
return false;
}
+1 -1
View File
@@ -67,7 +67,7 @@ bool WifiCredentialStore::loadFromFile() {
}
bool WifiCredentialStore::loadFromBinaryFile() {
FsFile file;
HalFile file;
if (!Storage.openFileForRead("WCS", WIFI_FILE_BIN, file)) {
return false;
}
+3 -3
View File
@@ -63,7 +63,7 @@ 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.
// This takes priority over the /sleep folder.
FsFile file;
HalFile file;
if (Storage.openFileForRead("SLP", "/sleep.bmp", file)) {
Bitmap bitmap(file, true);
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
@@ -129,7 +129,7 @@ void SleepActivity::renderCustomSleepScreen() const {
APP_STATE.pushRecentSleep(randomFileIndex);
APP_STATE.saveToFile();
const auto filename = std::string(sleepDir) + "/" + files[randomFileIndex];
FsFile randFile;
HalFile randFile;
if (Storage.openFileForRead("SLP", filename, randFile)) {
LOG_DBG("SLP", "Randomly loading: %s/%s", sleepDir, files[randomFileIndex].c_str());
delay(100);
@@ -304,7 +304,7 @@ void SleepActivity::renderCoverSleepScreen() const {
return (this->*renderNoCoverSleepScreen)();
}
FsFile file;
HalFile file;
if (Storage.openFileForRead("SLP", coverBmpPath, file)) {
Bitmap bitmap(file);
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
+1 -1
View File
@@ -123,7 +123,7 @@ void EpubReaderActivity::onEnter() {
epub->setupCacheDir();
FsFile f;
HalFile f;
if (Storage.openFileForRead("ERS", epub->getCachePath() + "/progress.bin", f)) {
uint8_t data[6];
int dataSize = f.read(data, 6);
+1 -1
View File
@@ -13,7 +13,7 @@ inline bool saveProgress(Epub& epub, int spineIndex, int pageNumber, int pageCou
LOG_ERR("ERS", "Progress values out of range: spine=%d page=%d count=%d", spineIndex, pageNumber, pageCount);
return false;
}
FsFile f;
HalFile f;
if (!Storage.openFileForWrite("ERS", epub.getCachePath() + "/progress.bin", f)) {
LOG_ERR("ERS", "Could not open progress file for write!");
return false;
+4 -4
View File
@@ -415,7 +415,7 @@ void TxtReaderActivity::renderStatusBar() const {
}
void TxtReaderActivity::saveProgress() const {
FsFile f;
HalFile f;
if (Storage.openFileForWrite("TRS", txt->getCachePath() + "/progress.bin", f)) {
uint8_t data[4];
data[0] = currentPage & 0xFF;
@@ -427,7 +427,7 @@ void TxtReaderActivity::saveProgress() const {
}
void TxtReaderActivity::loadProgress() {
FsFile f;
HalFile f;
if (Storage.openFileForRead("TRS", txt->getCachePath() + "/progress.bin", f)) {
uint8_t data[4];
if (f.read(data, 4) == 4) {
@@ -457,7 +457,7 @@ bool TxtReaderActivity::loadPageIndexCache() {
// - N * uint32_t: page offsets
std::string cachePath = txt->getCachePath() + "/index.bin";
FsFile f;
HalFile f;
if (!Storage.openFileForRead("TRS", cachePath, f)) {
LOG_DBG("TRS", "No page index cache found");
return false;
@@ -540,7 +540,7 @@ bool TxtReaderActivity::loadPageIndexCache() {
void TxtReaderActivity::savePageIndexCache() const {
std::string cachePath = txt->getCachePath() + "/index.bin";
FsFile f;
HalFile f;
if (!Storage.openFileForWrite("TRS", cachePath, f)) {
LOG_ERR("TRS", "Failed to save page index cache");
return;
+2 -2
View File
@@ -375,7 +375,7 @@ void XtcReaderActivity::renderPage() {
}
void XtcReaderActivity::saveProgress() const {
FsFile f;
HalFile f;
if (Storage.openFileForWrite("XTR", xtc->getCachePath() + "/progress.bin", f)) {
uint8_t data[4];
data[0] = currentPage & 0xFF;
@@ -388,7 +388,7 @@ void XtcReaderActivity::saveProgress() const {
}
void XtcReaderActivity::loadProgress() {
FsFile f;
HalFile f;
if (Storage.openFileForRead("XTR", xtc->getCachePath() + "/progress.bin", f)) {
uint8_t data[4];
if (f.read(data, 4) == 4) {
@@ -82,7 +82,7 @@ bool FontDownloadActivity::fetchAndParseManifest() {
}
// HTTP client is now closed — TLS buffers freed. Parse JSON from file.
FsFile manifestFile;
HalFile manifestFile;
if (!Storage.openFileForRead("FONT", MANIFEST_TMP, manifestFile)) {
LOG_ERR("FONT", "Failed to open temp manifest");
Storage.remove(MANIFEST_TMP);
@@ -149,7 +149,7 @@ bool FontDownloadActivity::fetchAndParseManifest() {
for (const auto& file : family.files) {
char path[128];
FontInstaller::buildFontPath(family.name.c_str(), file.name.c_str(), path, sizeof(path));
FsFile f;
HalFile f;
if (Storage.openFileForRead("FONT", path, f)) {
size_t actual = f.fileSize();
f.close();
@@ -248,7 +248,7 @@ size_t FontDownloadActivity::totalUpdateSize() const {
// Standard CRC32 matching zlib/Python zlib.crc32().
bool FontDownloadActivity::computeFileCrc32(const char* path, uint32_t& outCrc) {
FsFile f;
HalFile f;
if (!Storage.openFileForRead("FONT", path, f)) {
return false;
}
+2 -2
View File
@@ -63,7 +63,7 @@ void BmpViewerActivity::onEnter() {
loadSiblingImages();
}
FsFile file;
HalFile file;
const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight();
@@ -147,7 +147,7 @@ void BmpViewerActivity::doSetSleepCover() {
GUI.drawPopup(renderer, tr(STR_LOADING_POPUP));
bool success = false;
FsFile inFile, outFile;
HalFile inFile, outFile;
if (Storage.openFileForRead("BMP", filePath, inFile)) {
if (Storage.openFileForWrite("BMP", "/sleep.bmp", outFile)) {
char buffer[2048];
+2 -2
View File
@@ -426,7 +426,7 @@ void BaseTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std:
const std::string coverBmpPath =
UITheme::getCoverThumbPath(recentBooks[0].coverBmpPath, BaseMetrics::values.homeCoverHeight);
FsFile file;
HalFile file;
if (Storage.openFileForRead("HOME", coverBmpPath, file)) {
Bitmap bitmap(file);
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
@@ -476,7 +476,7 @@ void BaseTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std:
UITheme::getCoverThumbPath(recentBooks[0].coverBmpPath, BaseMetrics::values.homeCoverHeight);
// First time: load cover from SD and render
FsFile file;
HalFile file;
if (Storage.openFileForRead("HOME", coverBmpPath, file)) {
Bitmap bitmap(file);
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
@@ -42,7 +42,7 @@ void Lyra3CoversTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, con
UITheme::getCoverThumbPath(coverPath, Lyra3CoversMetrics::values.homeCoverHeight);
// First time: load cover from SD and render
FsFile file;
HalFile file;
if (Storage.openFileForRead("HOME", coverBmpPath, file)) {
Bitmap bitmap(file);
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
+1 -1
View File
@@ -423,7 +423,7 @@ void LyraTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std:
const std::string coverBmpPath = UITheme::getCoverThumbPath(coverPath, LyraMetrics::values.homeCoverHeight);
// First time: load cover from SD and render
FsFile file;
HalFile file;
if (Storage.openFileForRead("HOME", coverBmpPath, file)) {
Bitmap bitmap(file);
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
@@ -140,7 +140,7 @@ void RoundedRaffTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, con
UITheme::getCoverThumbPath(coverPath, RoundedRaffMetrics::values.homeCoverHeight);
// First time: load cover from SD and render
FsFile file;
HalFile file;
if (Storage.openFileForRead("HOME", coverBmpPath, file)) {
Bitmap bitmap(file);
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
+2 -2
View File
@@ -237,14 +237,14 @@ void waitForPowerRelease() {
constexpr char SLEEP_FRAME_FILE[] = "/.crosspoint/sleep_frame.bin";
static void saveSleepFrameBuffer() {
FsFile file;
HalFile file;
if (!Storage.openFileForWrite("SLP", SLEEP_FRAME_FILE, file)) return;
file.write(renderer.getFrameBuffer(), renderer.getBufferSize());
file.close();
}
static bool loadSleepFrameBuffer() {
FsFile file;
HalFile file;
if (!Storage.openFileForRead("SLP", SLEEP_FRAME_FILE, file)) return false;
const size_t bufferSize = display.getBufferSize();
const size_t bytesRead = file.read(display.getFrameBuffer(), bufferSize);
+14 -10
View File
@@ -35,7 +35,7 @@ constexpr uint16_t LOCAL_UDP_PORT = 8134;
CrossPointWebServer* wsInstance = nullptr;
// WebSocket upload state
FsFile wsUploadFile;
HalFile wsUploadFile;
String wsUploadFileName;
String wsUploadPath;
size_t wsUploadSize = 0;
@@ -374,7 +374,7 @@ void CrossPointWebServer::handleStatus() const {
}
void CrossPointWebServer::scanFiles(const char* path, const std::function<void(FileInfo)>& callback) const {
FsFile root = Storage.open(path);
HalFile root = Storage.open(path);
if (!root) {
LOG_DBG("WEB", "Failed to open directory: %s", path);
return;
@@ -388,7 +388,7 @@ void CrossPointWebServer::scanFiles(const char* path, const std::function<void(F
LOG_DBG("WEB", "Scanning files in: %s", path);
FsFile file = root.openNextFile();
HalFile file = root.openNextFile();
char name[500];
while (file) {
file.getName(name, sizeof(name));
@@ -519,7 +519,7 @@ void CrossPointWebServer::handleDownload() const {
return;
}
FsFile file = Storage.open(itemPath.c_str());
HalFile file = Storage.open(itemPath.c_str());
if (!file) {
server->send(500, "text/plain", "Failed to open file");
return;
@@ -842,7 +842,7 @@ void CrossPointWebServer::handleRename() const {
return;
}
FsFile file = Storage.open(itemPath.c_str());
HalFile file = Storage.open(itemPath.c_str());
if (!file) {
server->send(500, "text/plain", "Failed to open file");
return;
@@ -918,7 +918,7 @@ void CrossPointWebServer::handleMove() const {
return;
}
FsFile file = Storage.open(itemPath.c_str());
HalFile file = Storage.open(itemPath.c_str());
if (!file) {
server->send(500, "text/plain", "Failed to open file");
return;
@@ -934,7 +934,7 @@ void CrossPointWebServer::handleMove() const {
server->send(404, "text/plain", "Destination not found");
return;
}
FsFile destDir = Storage.open(destPath.c_str());
HalFile destDir = Storage.open(destPath.c_str());
if (!destDir || !destDir.isDirectory()) {
if (destDir) {
destDir.close();
@@ -1064,10 +1064,10 @@ void CrossPointWebServer::handleDelete() const {
// Decide whether it's a directory or file by opening it
bool success = false;
FsFile f = Storage.open(itemPath.c_str());
HalFile f = Storage.open(itemPath.c_str());
if (f && f.isDirectory()) {
// For folders, ensure empty before removing
FsFile entry = f.openNextFile();
HalFile entry = f.openNextFile();
if (entry) {
entry.close();
f.close();
@@ -1741,7 +1741,7 @@ void CrossPointWebServer::handleFontList() const {
fileObj["name"] = name ? name + 1 : file.path.c_str();
// Stat the file for size
FsFile f;
HalFile f;
if (Storage.openFileForRead("WEB", file.path.c_str(), f)) {
fileObj["size"] = static_cast<unsigned long>(f.size());
f.close();
@@ -1848,7 +1848,9 @@ void CrossPointWebServer::handleFontUploadData() {
fontUpload.bytesWritten += fontUpload.bufferPos;
fontUpload.bufferPos = 0;
}
if (fontUpload.file) {
fontUpload.file.close();
}
if (!fontUpload.valid && !fontUpload.filePath.empty()) {
Storage.remove(fontUpload.filePath.c_str());
@@ -1859,7 +1861,9 @@ void CrossPointWebServer::handleFontUploadData() {
}
case UPLOAD_FILE_ABORTED: {
if (fontUpload.file) {
fontUpload.file.close();
}
if (!fontUpload.filePath.empty()) {
Storage.remove(fontUpload.filePath.c_str());
}
+2 -2
View File
@@ -31,7 +31,7 @@ class CrossPointWebServer {
// Used by POST upload handler
struct UploadState {
FsFile file;
HalFile file;
String fileName;
String path = "/";
size_t size = 0;
@@ -117,7 +117,7 @@ class CrossPointWebServer {
// Font upload state
struct FontUploadState {
FsFile file;
HalFile file;
std::string familyName;
std::string filePath;
bool valid = false;
+1 -1
View File
@@ -179,7 +179,7 @@ HttpDownloader::DownloadError HttpDownloader::downloadToFile(const std::string&
if (Storage.exists(destPath.c_str())) {
Storage.remove(destPath.c_str());
}
FsFile file;
HalFile file;
if (!Storage.openFileForWrite("HTTP", destPath.c_str(), file)) {
LOG_ERR("HTTP", "Failed to open file for writing");
return FILE_ERROR;
+11 -11
View File
@@ -67,7 +67,7 @@ void WebDAVHandler::raw(WebServer& server, const String& uri, HTTPRaw& raw) {
_putExisted = Storage.exists(_putPath.c_str());
if (_putExisted) {
FsFile existing = Storage.open(_putPath.c_str());
HalFile existing = Storage.open(_putPath.c_str());
if (existing && existing.isDirectory()) {
existing.close();
_putOk = false;
@@ -96,7 +96,7 @@ void WebDAVHandler::raw(WebServer& server, const String& uri, HTTPRaw& raw) {
if (_putOk) {
String tempPath = _putPath + ".davtmp";
if (_putExisted) Storage.remove(_putPath.c_str());
FsFile tmp = Storage.open(tempPath.c_str());
HalFile tmp = Storage.open(tempPath.c_str());
if (tmp) {
_putOk = tmp.rename(_putPath.c_str());
tmp.close();
@@ -182,7 +182,7 @@ void WebDAVHandler::handlePropfind(WebServer& s) {
return;
}
FsFile root = Storage.open(path.c_str());
HalFile root = Storage.open(path.c_str());
if (!root) {
if (path == "/") {
// Root should always work — send minimal response
@@ -221,7 +221,7 @@ void WebDAVHandler::handlePropfind(WebServer& s) {
// If depth > 0 and it's a directory, list children
if (depth > 0) {
FsFile file = root.openNextFile();
HalFile file = root.openNextFile();
char name[500];
while (file) {
file.getName(name, sizeof(name));
@@ -311,7 +311,7 @@ void WebDAVHandler::handleGet(WebServer& s) {
return;
}
FsFile file = Storage.open(path.c_str());
HalFile file = Storage.open(path.c_str());
if (!file) {
s.send(500, "text/plain", "Failed to open file");
return;
@@ -348,7 +348,7 @@ void WebDAVHandler::handleHead(WebServer& s) {
return;
}
FsFile file = Storage.open(path.c_str());
HalFile file = Storage.open(path.c_str());
if (!file) {
s.send(500, "text/plain", "");
return;
@@ -411,7 +411,7 @@ void WebDAVHandler::handleDelete(WebServer& s) {
return;
}
FsFile file = Storage.open(path.c_str());
HalFile file = Storage.open(path.c_str());
if (!file) {
s.send(500, "text/plain", "Failed to open");
return;
@@ -419,7 +419,7 @@ void WebDAVHandler::handleDelete(WebServer& s) {
if (file.isDirectory()) {
// Check if directory is empty
FsFile entry = file.openNextFile();
HalFile entry = file.openNextFile();
if (entry) {
entry.close();
file.close();
@@ -537,7 +537,7 @@ void WebDAVHandler::handleMove(WebServer& s) {
Storage.remove(dstPath.c_str());
}
FsFile file = Storage.open(srcPath.c_str());
HalFile file = Storage.open(srcPath.c_str());
if (!file) {
s.send(500, "text/plain", "Failed to open source");
return;
@@ -583,7 +583,7 @@ void WebDAVHandler::handleCopy(WebServer& s) {
return;
}
FsFile srcFile = Storage.open(srcPath.c_str());
HalFile srcFile = Storage.open(srcPath.c_str());
if (!srcFile) {
s.send(500, "text/plain", "Failed to open source");
return;
@@ -617,7 +617,7 @@ void WebDAVHandler::handleCopy(WebServer& s) {
Storage.remove(dstPath.c_str());
}
FsFile dstFile;
HalFile dstFile;
if (!Storage.openFileForWrite("DAV", dstPath, dstFile)) {
srcFile.close();
s.send(500, "text/plain", "Failed to create destination");
+1 -1
View File
@@ -13,7 +13,7 @@ class WebDAVHandler : public RequestHandler {
private:
// PUT streaming state (raw() is called in chunks)
FsFile _putFile;
HalFile _putFile;
String _putPath;
bool _putOk = false;
bool _putExisted = false;
+1 -1
View File
@@ -121,7 +121,7 @@ bool ScreenshotUtil::saveFramebufferAsBmp(const char* filename, const uint8_t* f
}
}
FsFile file;
HalFile file;
if (!Storage.openFileForWrite("SCR", filename, file)) {
LOG_ERR("SCR", "Failed to save screenshot");
return false;