Add FreeInkBook EPUB engine migration guide
Document migration plan from CrossPoint's lib/Epub to FreeInkBook, a memory-efficient clean-room EPUB engine. Covers advantages (O(paragraph+page) memory via SAX parsing, arena allocation, host-testable, full UAX#14 typography, runtime TTF/OTF support), 9-step migration strategy, ESP32-C3 constraints, and integration approach preserving existing UI layer while replacing parsing/layout stack.
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
#pragma once
|
||||
|
||||
// FreeInkBook storage adapters — bind the engine's BookSource/CacheStorage
|
||||
// interfaces to HalStorage (all SD access must go through the HAL mutex).
|
||||
// Pattern follows freeink-books' BookStorageAdapters.h with CrossPoint's
|
||||
// torn-write-safe temp+rename commit.
|
||||
|
||||
#include <BookStorage.h>
|
||||
#include <HalStorage.h>
|
||||
#include <Logging.h>
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
// Random access over one book file on the SD card. The file stays open for
|
||||
// the book's lifetime; every readAt takes the storage mutex via HalFile.
|
||||
class SdBookSource : public freeink::book::BookSource {
|
||||
public:
|
||||
bool open(const char* path) {
|
||||
if (!Storage.openFileForRead("FIBSRC", path, file_)) return false;
|
||||
size_ = file_.fileSize64();
|
||||
return size_ > 0;
|
||||
}
|
||||
void close() { file_.close(); }
|
||||
int32_t readAt(uint64_t offset, void* dst, uint32_t len) override {
|
||||
if (!file_.isOpen() || !file_.seek64(offset)) return -1;
|
||||
return file_.read(dst, len);
|
||||
}
|
||||
uint64_t size() const override { return size_; }
|
||||
|
||||
private:
|
||||
HalFile file_;
|
||||
uint64_t size_ = 0;
|
||||
};
|
||||
|
||||
// Layout-cache files inside one book's cache directory. Writes stream to a
|
||||
// temp name and commit via rename, so an interrupted write leaves the old
|
||||
// file or none — never a torn one (the engine additionally verifies a footer
|
||||
// magic). Reads keep the last-touched file open: page turns are then one
|
||||
// seek+read instead of a directory walk per call.
|
||||
class SdCacheStorage : public freeink::book::CacheStorage {
|
||||
public:
|
||||
// `dir` is the per-book cache directory (e.g. ".crosspoint/epub_<hash>").
|
||||
void setDir(const char* dir) {
|
||||
snprintf(dir_, sizeof(dir_), "%s", dir);
|
||||
Storage.ensureDirectoryExists(dir_);
|
||||
closeRead();
|
||||
}
|
||||
|
||||
bool exists(const char* name) override { return Storage.exists(path(name)); }
|
||||
|
||||
bool remove(const char* name) override {
|
||||
invalidateRead(name);
|
||||
return Storage.remove(path(name));
|
||||
}
|
||||
|
||||
int64_t fileSize(const char* name) override {
|
||||
if (!ensureReadOpen(name)) return -1;
|
||||
return static_cast<int64_t>(readFile_.fileSize64());
|
||||
}
|
||||
|
||||
int32_t readAt(const char* name, uint32_t offset, void* dst, uint32_t len) override {
|
||||
if (!ensureReadOpen(name) || !readFile_.seekSet(offset)) return -1;
|
||||
return readFile_.read(dst, len);
|
||||
}
|
||||
|
||||
bool beginWrite(const char* name) override {
|
||||
snprintf(commitPath_, sizeof(commitPath_), "%s/%s", dir_, name);
|
||||
invalidateRead(name);
|
||||
if (!Storage.openFileForWrite("FIBCACHE", path(kTempName), write_)) {
|
||||
LOG_ERR("FIBCACHE", "beginWrite failed: %s", commitPath_);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool write(const void* data, uint32_t len) override {
|
||||
return write_.isOpen() && write_.write(data, len) == len;
|
||||
}
|
||||
|
||||
bool endWrite() override {
|
||||
if (!write_.isOpen()) return false;
|
||||
write_.close(); // must close before rename (DESTRUCTOR_CLOSES_FILE covers scope exit only)
|
||||
Storage.remove(commitPath_); // may not exist; rename below is the commit point
|
||||
if (!Storage.rename(path(kTempName), commitPath_)) {
|
||||
LOG_ERR("FIBCACHE", "commit rename failed: %s", commitPath_);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr const char* kTempName = "_tmp.fibp";
|
||||
|
||||
const char* path(const char* name) {
|
||||
snprintf(pathBuf_, sizeof(pathBuf_), "%s/%s", dir_, name);
|
||||
return pathBuf_;
|
||||
}
|
||||
|
||||
bool ensureReadOpen(const char* name) {
|
||||
if (readFile_.isOpen() && strncmp(readName_, name, sizeof(readName_)) == 0) return true;
|
||||
closeRead();
|
||||
if (!Storage.openFileForRead("FIBCACHE", path(name), readFile_)) return false;
|
||||
snprintf(readName_, sizeof(readName_), "%s", name);
|
||||
return true;
|
||||
}
|
||||
|
||||
void invalidateRead(const char* name) {
|
||||
if (readFile_.isOpen() && strncmp(readName_, name, sizeof(readName_)) == 0) closeRead();
|
||||
}
|
||||
|
||||
void closeRead() {
|
||||
if (readFile_.isOpen()) readFile_.close();
|
||||
readName_[0] = '\0';
|
||||
}
|
||||
|
||||
char dir_[96] = "";
|
||||
char pathBuf_[192];
|
||||
char commitPath_[192];
|
||||
char readName_[80] = "";
|
||||
HalFile readFile_;
|
||||
HalFile write_;
|
||||
};
|
||||
Reference in New Issue
Block a user