mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-04 14:47:37 +03:00
Standardize File handling with FsHelpers (#110)
Some checks failed
CI / build (push) Has been cancelled
Some checks failed
CI / build (push) Has been cancelled
## Summary * Standardize File handling with FsHelpers * Better central place to manage to logic of if files exist/open for reading/writing
This commit is contained in:
parent
66ddb52103
commit
1107590b56
@ -1,5 +1,6 @@
|
|||||||
#include "Epub.h"
|
#include "Epub.h"
|
||||||
|
|
||||||
|
#include <FsHelpers.h>
|
||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
#include <JpegToBmpConverter.h>
|
#include <JpegToBmpConverter.h>
|
||||||
#include <SD.h>
|
#include <SD.h>
|
||||||
@ -7,7 +8,6 @@
|
|||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
#include "Epub/FsHelpers.h"
|
|
||||||
#include "Epub/parsers/ContainerParser.h"
|
#include "Epub/parsers/ContainerParser.h"
|
||||||
#include "Epub/parsers/ContentOpfParser.h"
|
#include "Epub/parsers/ContentOpfParser.h"
|
||||||
#include "Epub/parsers/TocNcxParser.h"
|
#include "Epub/parsers/TocNcxParser.h"
|
||||||
@ -95,10 +95,15 @@ bool Epub::parseTocNcxFile() {
|
|||||||
Serial.printf("[%lu] [EBP] Parsing toc ncx file: %s\n", millis(), tocNcxItem.c_str());
|
Serial.printf("[%lu] [EBP] Parsing toc ncx file: %s\n", millis(), tocNcxItem.c_str());
|
||||||
|
|
||||||
const auto tmpNcxPath = getCachePath() + "/toc.ncx";
|
const auto tmpNcxPath = getCachePath() + "/toc.ncx";
|
||||||
File tempNcxFile = SD.open(tmpNcxPath.c_str(), FILE_WRITE);
|
File tempNcxFile;
|
||||||
|
if (!FsHelpers::openFileForWrite("EBP", tmpNcxPath, tempNcxFile)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
readItemContentsToStream(tocNcxItem, tempNcxFile, 1024);
|
readItemContentsToStream(tocNcxItem, tempNcxFile, 1024);
|
||||||
tempNcxFile.close();
|
tempNcxFile.close();
|
||||||
tempNcxFile = SD.open(tmpNcxPath.c_str(), FILE_READ);
|
if (!FsHelpers::openFileForRead("EBP", tmpNcxPath, tempNcxFile)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const auto ncxSize = tempNcxFile.size();
|
const auto ncxSize = tempNcxFile.size();
|
||||||
|
|
||||||
TocNcxParser ncxParser(contentBasePath, ncxSize);
|
TocNcxParser ncxParser(contentBasePath, ncxSize);
|
||||||
@ -235,16 +240,28 @@ bool Epub::generateCoverBmp() const {
|
|||||||
if (coverImageItem.substr(coverImageItem.length() - 4) == ".jpg" ||
|
if (coverImageItem.substr(coverImageItem.length() - 4) == ".jpg" ||
|
||||||
coverImageItem.substr(coverImageItem.length() - 5) == ".jpeg") {
|
coverImageItem.substr(coverImageItem.length() - 5) == ".jpeg") {
|
||||||
Serial.printf("[%lu] [EBP] Generating BMP from JPG cover image\n", millis());
|
Serial.printf("[%lu] [EBP] Generating BMP from JPG cover image\n", millis());
|
||||||
File coverJpg = SD.open((getCachePath() + "/.cover.jpg").c_str(), FILE_WRITE, true);
|
const auto coverJpgTempPath = getCachePath() + "/.cover.jpg";
|
||||||
|
|
||||||
|
File coverJpg;
|
||||||
|
if (!FsHelpers::openFileForWrite("EBP", coverJpgTempPath, coverJpg)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
readItemContentsToStream(coverImageItem, coverJpg, 1024);
|
readItemContentsToStream(coverImageItem, coverJpg, 1024);
|
||||||
coverJpg.close();
|
coverJpg.close();
|
||||||
|
|
||||||
coverJpg = SD.open((getCachePath() + "/.cover.jpg").c_str(), FILE_READ);
|
if (!FsHelpers::openFileForRead("EBP", coverJpgTempPath, coverJpg)) {
|
||||||
File coverBmp = SD.open(getCoverBmpPath().c_str(), FILE_WRITE, true);
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
File coverBmp;
|
||||||
|
if (!FsHelpers::openFileForWrite("EBP", getCoverBmpPath(), coverBmp)) {
|
||||||
|
coverJpg.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const bool success = JpegToBmpConverter::jpegFileToBmpStream(coverJpg, coverBmp);
|
const bool success = JpegToBmpConverter::jpegFileToBmpStream(coverJpg, coverBmp);
|
||||||
coverJpg.close();
|
coverJpg.close();
|
||||||
coverBmp.close();
|
coverBmp.close();
|
||||||
SD.remove((getCachePath() + "/.cover.jpg").c_str());
|
SD.remove(coverJpgTempPath.c_str());
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
Serial.printf("[%lu] [EBP] Failed to generate BMP from JPG cover image\n", millis());
|
Serial.printf("[%lu] [EBP] Failed to generate BMP from JPG cover image\n", millis());
|
||||||
@ -259,45 +276,9 @@ bool Epub::generateCoverBmp() const {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string normalisePath(const std::string& path) {
|
|
||||||
std::vector<std::string> components;
|
|
||||||
std::string component;
|
|
||||||
|
|
||||||
for (const auto c : path) {
|
|
||||||
if (c == '/') {
|
|
||||||
if (!component.empty()) {
|
|
||||||
if (component == "..") {
|
|
||||||
if (!components.empty()) {
|
|
||||||
components.pop_back();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
components.push_back(component);
|
|
||||||
}
|
|
||||||
component.clear();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
component += c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!component.empty()) {
|
|
||||||
components.push_back(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string result;
|
|
||||||
for (const auto& c : components) {
|
|
||||||
if (!result.empty()) {
|
|
||||||
result += "/";
|
|
||||||
}
|
|
||||||
result += c;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t* Epub::readItemContentsToBytes(const std::string& itemHref, size_t* size, bool trailingNullByte) const {
|
uint8_t* Epub::readItemContentsToBytes(const std::string& itemHref, size_t* size, bool trailingNullByte) const {
|
||||||
const ZipFile zip("/sd" + filepath);
|
const ZipFile zip("/sd" + filepath);
|
||||||
const std::string path = normalisePath(itemHref);
|
const std::string path = FsHelpers::normalisePath(itemHref);
|
||||||
|
|
||||||
const auto content = zip.readFileToMemory(path.c_str(), size, trailingNullByte);
|
const auto content = zip.readFileToMemory(path.c_str(), size, trailingNullByte);
|
||||||
if (!content) {
|
if (!content) {
|
||||||
@ -310,7 +291,7 @@ uint8_t* Epub::readItemContentsToBytes(const std::string& itemHref, size_t* size
|
|||||||
|
|
||||||
bool Epub::readItemContentsToStream(const std::string& itemHref, Print& out, const size_t chunkSize) const {
|
bool Epub::readItemContentsToStream(const std::string& itemHref, Print& out, const size_t chunkSize) const {
|
||||||
const ZipFile zip("/sd" + filepath);
|
const ZipFile zip("/sd" + filepath);
|
||||||
const std::string path = normalisePath(itemHref);
|
const std::string path = FsHelpers::normalisePath(itemHref);
|
||||||
|
|
||||||
return zip.readFileToStream(path.c_str(), out, chunkSize);
|
return zip.readFileToStream(path.c_str(), out, chunkSize);
|
||||||
}
|
}
|
||||||
@ -321,7 +302,7 @@ bool Epub::getItemSize(const std::string& itemHref, size_t* size) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Epub::getItemSize(const ZipFile& zip, const std::string& itemHref, size_t* size) {
|
bool Epub::getItemSize(const ZipFile& zip, const std::string& itemHref, size_t* size) {
|
||||||
const std::string path = normalisePath(itemHref);
|
const std::string path = FsHelpers::normalisePath(itemHref);
|
||||||
return zip.getInflatedFileSize(path.c_str(), size);
|
return zip.getInflatedFileSize(path.c_str(), size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -349,18 +330,18 @@ std::string& Epub::getSpineItem(const int spineIndex) {
|
|||||||
return spine.at(spineIndex).second;
|
return spine.at(spineIndex).second;
|
||||||
}
|
}
|
||||||
|
|
||||||
EpubTocEntry& Epub::getTocItem(const int tocTndex) {
|
EpubTocEntry& Epub::getTocItem(const int tocIndex) {
|
||||||
static EpubTocEntry emptyEntry = {};
|
static EpubTocEntry emptyEntry = {};
|
||||||
if (toc.empty()) {
|
if (toc.empty()) {
|
||||||
Serial.printf("[%lu] [EBP] getTocItem called but toc is empty\n", millis());
|
Serial.printf("[%lu] [EBP] getTocItem called but toc is empty\n", millis());
|
||||||
return emptyEntry;
|
return emptyEntry;
|
||||||
}
|
}
|
||||||
if (tocTndex < 0 || tocTndex >= static_cast<int>(toc.size())) {
|
if (tocIndex < 0 || tocIndex >= static_cast<int>(toc.size())) {
|
||||||
Serial.printf("[%lu] [EBP] getTocItem index:%d is out of range\n", millis(), tocTndex);
|
Serial.printf("[%lu] [EBP] getTocItem index:%d is out of range\n", millis(), tocIndex);
|
||||||
return toc.at(0);
|
return toc.at(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return toc.at(tocTndex);
|
return toc.at(tocIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
int Epub::getTocItemsCount() const { return toc.size(); }
|
int Epub::getTocItemsCount() const { return toc.size(); }
|
||||||
|
|||||||
@ -1,36 +0,0 @@
|
|||||||
#include "FsHelpers.h"
|
|
||||||
|
|
||||||
#include <SD.h>
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
class FsHelpers {
|
|
||||||
public:
|
|
||||||
static bool removeDir(const char* path);
|
|
||||||
};
|
|
||||||
@ -9,21 +9,21 @@ constexpr uint8_t PAGE_FILE_VERSION = 3;
|
|||||||
|
|
||||||
void PageLine::render(GfxRenderer& renderer, const int fontId) { block->render(renderer, fontId, xPos, yPos); }
|
void PageLine::render(GfxRenderer& renderer, const int fontId) { block->render(renderer, fontId, xPos, yPos); }
|
||||||
|
|
||||||
void PageLine::serialize(std::ostream& os) {
|
void PageLine::serialize(File& file) {
|
||||||
serialization::writePod(os, xPos);
|
serialization::writePod(file, xPos);
|
||||||
serialization::writePod(os, yPos);
|
serialization::writePod(file, yPos);
|
||||||
|
|
||||||
// serialize TextBlock pointed to by PageLine
|
// serialize TextBlock pointed to by PageLine
|
||||||
block->serialize(os);
|
block->serialize(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<PageLine> PageLine::deserialize(std::istream& is) {
|
std::unique_ptr<PageLine> PageLine::deserialize(File& file) {
|
||||||
int16_t xPos;
|
int16_t xPos;
|
||||||
int16_t yPos;
|
int16_t yPos;
|
||||||
serialization::readPod(is, xPos);
|
serialization::readPod(file, xPos);
|
||||||
serialization::readPod(is, yPos);
|
serialization::readPod(file, yPos);
|
||||||
|
|
||||||
auto tb = TextBlock::deserialize(is);
|
auto tb = TextBlock::deserialize(file);
|
||||||
return std::unique_ptr<PageLine>(new PageLine(std::move(tb), xPos, yPos));
|
return std::unique_ptr<PageLine>(new PageLine(std::move(tb), xPos, yPos));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,22 +33,22 @@ void Page::render(GfxRenderer& renderer, const int fontId) const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Page::serialize(std::ostream& os) const {
|
void Page::serialize(File& file) const {
|
||||||
serialization::writePod(os, PAGE_FILE_VERSION);
|
serialization::writePod(file, PAGE_FILE_VERSION);
|
||||||
|
|
||||||
const uint32_t count = elements.size();
|
const uint32_t count = elements.size();
|
||||||
serialization::writePod(os, count);
|
serialization::writePod(file, count);
|
||||||
|
|
||||||
for (const auto& el : elements) {
|
for (const auto& el : elements) {
|
||||||
// Only PageLine exists currently
|
// Only PageLine exists currently
|
||||||
serialization::writePod(os, static_cast<uint8_t>(TAG_PageLine));
|
serialization::writePod(file, static_cast<uint8_t>(TAG_PageLine));
|
||||||
el->serialize(os);
|
el->serialize(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Page> Page::deserialize(std::istream& is) {
|
std::unique_ptr<Page> Page::deserialize(File& file) {
|
||||||
uint8_t version;
|
uint8_t version;
|
||||||
serialization::readPod(is, version);
|
serialization::readPod(file, version);
|
||||||
if (version != PAGE_FILE_VERSION) {
|
if (version != PAGE_FILE_VERSION) {
|
||||||
Serial.printf("[%lu] [PGE] Deserialization failed: Unknown version %u\n", millis(), version);
|
Serial.printf("[%lu] [PGE] Deserialization failed: Unknown version %u\n", millis(), version);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@ -57,14 +57,14 @@ std::unique_ptr<Page> Page::deserialize(std::istream& is) {
|
|||||||
auto page = std::unique_ptr<Page>(new Page());
|
auto page = std::unique_ptr<Page>(new Page());
|
||||||
|
|
||||||
uint32_t count;
|
uint32_t count;
|
||||||
serialization::readPod(is, count);
|
serialization::readPod(file, count);
|
||||||
|
|
||||||
for (uint32_t i = 0; i < count; i++) {
|
for (uint32_t i = 0; i < count; i++) {
|
||||||
uint8_t tag;
|
uint8_t tag;
|
||||||
serialization::readPod(is, tag);
|
serialization::readPod(file, tag);
|
||||||
|
|
||||||
if (tag == TAG_PageLine) {
|
if (tag == TAG_PageLine) {
|
||||||
auto pl = PageLine::deserialize(is);
|
auto pl = PageLine::deserialize(file);
|
||||||
page->elements.push_back(std::move(pl));
|
page->elements.push_back(std::move(pl));
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("[%lu] [PGE] Deserialization failed: Unknown tag %u\n", millis(), tag);
|
Serial.printf("[%lu] [PGE] Deserialization failed: Unknown tag %u\n", millis(), tag);
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include <FS.h>
|
||||||
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@ -16,7 +18,7 @@ class PageElement {
|
|||||||
explicit PageElement(const int16_t xPos, const int16_t yPos) : xPos(xPos), yPos(yPos) {}
|
explicit PageElement(const int16_t xPos, const int16_t yPos) : xPos(xPos), yPos(yPos) {}
|
||||||
virtual ~PageElement() = default;
|
virtual ~PageElement() = default;
|
||||||
virtual void render(GfxRenderer& renderer, int fontId) = 0;
|
virtual void render(GfxRenderer& renderer, int fontId) = 0;
|
||||||
virtual void serialize(std::ostream& os) = 0;
|
virtual void serialize(File& file) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// a line from a block element
|
// a line from a block element
|
||||||
@ -27,8 +29,8 @@ class PageLine final : public PageElement {
|
|||||||
PageLine(std::shared_ptr<TextBlock> block, const int16_t xPos, const int16_t yPos)
|
PageLine(std::shared_ptr<TextBlock> block, const int16_t xPos, const int16_t yPos)
|
||||||
: PageElement(xPos, yPos), block(std::move(block)) {}
|
: PageElement(xPos, yPos), block(std::move(block)) {}
|
||||||
void render(GfxRenderer& renderer, int fontId) override;
|
void render(GfxRenderer& renderer, int fontId) override;
|
||||||
void serialize(std::ostream& os) override;
|
void serialize(File& file) override;
|
||||||
static std::unique_ptr<PageLine> deserialize(std::istream& is);
|
static std::unique_ptr<PageLine> deserialize(File& file);
|
||||||
};
|
};
|
||||||
|
|
||||||
class Page {
|
class Page {
|
||||||
@ -36,6 +38,6 @@ class Page {
|
|||||||
// the list of block index and line numbers on this page
|
// the list of block index and line numbers on this page
|
||||||
std::vector<std::shared_ptr<PageElement>> elements;
|
std::vector<std::shared_ptr<PageElement>> elements;
|
||||||
void render(GfxRenderer& renderer, int fontId) const;
|
void render(GfxRenderer& renderer, int fontId) const;
|
||||||
void serialize(std::ostream& os) const;
|
void serialize(File& file) const;
|
||||||
static std::unique_ptr<Page> deserialize(std::istream& is);
|
static std::unique_ptr<Page> deserialize(File& file);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
#include "Section.h"
|
#include "Section.h"
|
||||||
|
|
||||||
|
#include <FsHelpers.h>
|
||||||
#include <SD.h>
|
#include <SD.h>
|
||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
#include "FsHelpers.h"
|
|
||||||
#include "Page.h"
|
#include "Page.h"
|
||||||
#include "parsers/ChapterHtmlSlimParser.h"
|
#include "parsers/ChapterHtmlSlimParser.h"
|
||||||
|
|
||||||
@ -16,7 +14,10 @@ constexpr uint8_t SECTION_FILE_VERSION = 5;
|
|||||||
void Section::onPageComplete(std::unique_ptr<Page> page) {
|
void Section::onPageComplete(std::unique_ptr<Page> page) {
|
||||||
const auto filePath = cachePath + "/page_" + std::to_string(pageCount) + ".bin";
|
const auto filePath = cachePath + "/page_" + std::to_string(pageCount) + ".bin";
|
||||||
|
|
||||||
std::ofstream outputFile("/sd" + filePath);
|
File outputFile;
|
||||||
|
if (!FsHelpers::openFileForWrite("SCT", filePath, outputFile)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
page->serialize(outputFile);
|
page->serialize(outputFile);
|
||||||
outputFile.close();
|
outputFile.close();
|
||||||
|
|
||||||
@ -28,7 +29,10 @@ void Section::onPageComplete(std::unique_ptr<Page> page) {
|
|||||||
void Section::writeCacheMetadata(const int fontId, const float lineCompression, const int marginTop,
|
void Section::writeCacheMetadata(const int fontId, const float lineCompression, const int marginTop,
|
||||||
const int marginRight, const int marginBottom, const int marginLeft,
|
const int marginRight, const int marginBottom, const int marginLeft,
|
||||||
const bool extraParagraphSpacing) const {
|
const bool extraParagraphSpacing) const {
|
||||||
std::ofstream outputFile(("/sd" + cachePath + "/section.bin").c_str());
|
File outputFile;
|
||||||
|
if (!FsHelpers::openFileForWrite("SCT", cachePath + "/section.bin", outputFile)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
serialization::writePod(outputFile, SECTION_FILE_VERSION);
|
serialization::writePod(outputFile, SECTION_FILE_VERSION);
|
||||||
serialization::writePod(outputFile, fontId);
|
serialization::writePod(outputFile, fontId);
|
||||||
serialization::writePod(outputFile, lineCompression);
|
serialization::writePod(outputFile, lineCompression);
|
||||||
@ -44,17 +48,12 @@ void Section::writeCacheMetadata(const int fontId, const float lineCompression,
|
|||||||
bool Section::loadCacheMetadata(const int fontId, const float lineCompression, const int marginTop,
|
bool Section::loadCacheMetadata(const int fontId, const float lineCompression, const int marginTop,
|
||||||
const int marginRight, const int marginBottom, const int marginLeft,
|
const int marginRight, const int marginBottom, const int marginLeft,
|
||||||
const bool extraParagraphSpacing) {
|
const bool extraParagraphSpacing) {
|
||||||
if (!SD.exists(cachePath.c_str())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto sectionFilePath = cachePath + "/section.bin";
|
const auto sectionFilePath = cachePath + "/section.bin";
|
||||||
if (!SD.exists(sectionFilePath.c_str())) {
|
File inputFile;
|
||||||
|
if (!FsHelpers::openFileForRead("SCT", sectionFilePath, inputFile)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ifstream inputFile(("/sd" + sectionFilePath).c_str());
|
|
||||||
|
|
||||||
// Match parameters
|
// Match parameters
|
||||||
{
|
{
|
||||||
uint8_t version;
|
uint8_t version;
|
||||||
@ -119,13 +118,13 @@ bool Section::persistPageDataToSD(const int fontId, const float lineCompression,
|
|||||||
const bool extraParagraphSpacing) {
|
const bool extraParagraphSpacing) {
|
||||||
const auto localPath = epub->getSpineItem(spineIndex);
|
const auto localPath = epub->getSpineItem(spineIndex);
|
||||||
|
|
||||||
// TODO: Should we get rid of this file all together?
|
|
||||||
// It currently saves us a bit of memory by allowing for all the inflation bits to be released
|
|
||||||
// before loading the XML parser
|
|
||||||
const auto tmpHtmlPath = epub->getCachePath() + "/.tmp_" + std::to_string(spineIndex) + ".html";
|
const auto tmpHtmlPath = epub->getCachePath() + "/.tmp_" + std::to_string(spineIndex) + ".html";
|
||||||
File f = SD.open(tmpHtmlPath.c_str(), FILE_WRITE, true);
|
File tmpHtml;
|
||||||
bool success = epub->readItemContentsToStream(localPath, f, 1024);
|
if (!FsHelpers::openFileForWrite("SCT", tmpHtmlPath, tmpHtml)) {
|
||||||
f.close();
|
return false;
|
||||||
|
}
|
||||||
|
bool success = epub->readItemContentsToStream(localPath, tmpHtml, 1024);
|
||||||
|
tmpHtml.close();
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
Serial.printf("[%lu] [SCT] Failed to stream item contents to temp file\n", millis());
|
Serial.printf("[%lu] [SCT] Failed to stream item contents to temp file\n", millis());
|
||||||
@ -134,10 +133,8 @@ bool Section::persistPageDataToSD(const int fontId, const float lineCompression,
|
|||||||
|
|
||||||
Serial.printf("[%lu] [SCT] Streamed temp HTML to %s\n", millis(), tmpHtmlPath.c_str());
|
Serial.printf("[%lu] [SCT] Streamed temp HTML to %s\n", millis(), tmpHtmlPath.c_str());
|
||||||
|
|
||||||
const auto sdTmpHtmlPath = "/sd" + tmpHtmlPath;
|
ChapterHtmlSlimParser visitor(tmpHtmlPath, renderer, fontId, lineCompression, marginTop, marginRight, marginBottom,
|
||||||
|
marginLeft, extraParagraphSpacing,
|
||||||
ChapterHtmlSlimParser visitor(sdTmpHtmlPath.c_str(), renderer, fontId, lineCompression, marginTop, marginRight,
|
|
||||||
marginBottom, marginLeft, extraParagraphSpacing,
|
|
||||||
[this](std::unique_ptr<Page> page) { this->onPageComplete(std::move(page)); });
|
[this](std::unique_ptr<Page> page) { this->onPageComplete(std::move(page)); });
|
||||||
success = visitor.parseAndBuildPages();
|
success = visitor.parseAndBuildPages();
|
||||||
|
|
||||||
@ -153,13 +150,12 @@ bool Section::persistPageDataToSD(const int fontId, const float lineCompression,
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Page> Section::loadPageFromSD() const {
|
std::unique_ptr<Page> Section::loadPageFromSD() const {
|
||||||
const auto filePath = "/sd" + cachePath + "/page_" + std::to_string(currentPage) + ".bin";
|
const auto filePath = cachePath + "/page_" + std::to_string(currentPage) + ".bin";
|
||||||
if (!SD.exists(filePath.c_str() + 3)) {
|
|
||||||
Serial.printf("[%lu] [SCT] Page file does not exist: %s\n", millis(), filePath.c_str());
|
File inputFile;
|
||||||
|
if (!FsHelpers::openFileForRead("SCT", filePath, inputFile)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ifstream inputFile(filePath);
|
|
||||||
auto page = Page::deserialize(inputFile);
|
auto page = Page::deserialize(inputFile);
|
||||||
inputFile.close();
|
inputFile.close();
|
||||||
return page;
|
return page;
|
||||||
|
|||||||
@ -17,27 +17,27 @@ void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextBlock::serialize(std::ostream& os) const {
|
void TextBlock::serialize(File& file) const {
|
||||||
// words
|
// words
|
||||||
const uint32_t wc = words.size();
|
const uint32_t wc = words.size();
|
||||||
serialization::writePod(os, wc);
|
serialization::writePod(file, wc);
|
||||||
for (const auto& w : words) serialization::writeString(os, w);
|
for (const auto& w : words) serialization::writeString(file, w);
|
||||||
|
|
||||||
// wordXpos
|
// wordXpos
|
||||||
const uint32_t xc = wordXpos.size();
|
const uint32_t xc = wordXpos.size();
|
||||||
serialization::writePod(os, xc);
|
serialization::writePod(file, xc);
|
||||||
for (auto x : wordXpos) serialization::writePod(os, x);
|
for (auto x : wordXpos) serialization::writePod(file, x);
|
||||||
|
|
||||||
// wordStyles
|
// wordStyles
|
||||||
const uint32_t sc = wordStyles.size();
|
const uint32_t sc = wordStyles.size();
|
||||||
serialization::writePod(os, sc);
|
serialization::writePod(file, sc);
|
||||||
for (auto s : wordStyles) serialization::writePod(os, s);
|
for (auto s : wordStyles) serialization::writePod(file, s);
|
||||||
|
|
||||||
// style
|
// style
|
||||||
serialization::writePod(os, style);
|
serialization::writePod(file, style);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<TextBlock> TextBlock::deserialize(std::istream& is) {
|
std::unique_ptr<TextBlock> TextBlock::deserialize(File& file) {
|
||||||
uint32_t wc, xc, sc;
|
uint32_t wc, xc, sc;
|
||||||
std::list<std::string> words;
|
std::list<std::string> words;
|
||||||
std::list<uint16_t> wordXpos;
|
std::list<uint16_t> wordXpos;
|
||||||
@ -45,22 +45,22 @@ std::unique_ptr<TextBlock> TextBlock::deserialize(std::istream& is) {
|
|||||||
BLOCK_STYLE style;
|
BLOCK_STYLE style;
|
||||||
|
|
||||||
// words
|
// words
|
||||||
serialization::readPod(is, wc);
|
serialization::readPod(file, wc);
|
||||||
words.resize(wc);
|
words.resize(wc);
|
||||||
for (auto& w : words) serialization::readString(is, w);
|
for (auto& w : words) serialization::readString(file, w);
|
||||||
|
|
||||||
// wordXpos
|
// wordXpos
|
||||||
serialization::readPod(is, xc);
|
serialization::readPod(file, xc);
|
||||||
wordXpos.resize(xc);
|
wordXpos.resize(xc);
|
||||||
for (auto& x : wordXpos) serialization::readPod(is, x);
|
for (auto& x : wordXpos) serialization::readPod(file, x);
|
||||||
|
|
||||||
// wordStyles
|
// wordStyles
|
||||||
serialization::readPod(is, sc);
|
serialization::readPod(file, sc);
|
||||||
wordStyles.resize(sc);
|
wordStyles.resize(sc);
|
||||||
for (auto& s : wordStyles) serialization::readPod(is, s);
|
for (auto& s : wordStyles) serialization::readPod(file, s);
|
||||||
|
|
||||||
// style
|
// style
|
||||||
serialization::readPod(is, style);
|
serialization::readPod(file, style);
|
||||||
|
|
||||||
return std::unique_ptr<TextBlock>(new TextBlock(std::move(words), std::move(wordXpos), std::move(wordStyles), style));
|
return std::unique_ptr<TextBlock>(new TextBlock(std::move(words), std::move(wordXpos), std::move(wordStyles), style));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <EpdFontFamily.h>
|
#include <EpdFontFamily.h>
|
||||||
|
#include <FS.h>
|
||||||
|
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@ -35,6 +36,6 @@ class TextBlock final : public Block {
|
|||||||
// given a renderer works out where to break the words into lines
|
// given a renderer works out where to break the words into lines
|
||||||
void render(const GfxRenderer& renderer, int fontId, int x, int y) const;
|
void render(const GfxRenderer& renderer, int fontId, int x, int y) const;
|
||||||
BlockType getType() override { return TEXT_BLOCK; }
|
BlockType getType() override { return TEXT_BLOCK; }
|
||||||
void serialize(std::ostream& os) const;
|
void serialize(File& file) const;
|
||||||
static std::unique_ptr<TextBlock> deserialize(std::istream& is);
|
static std::unique_ptr<TextBlock> deserialize(File& file);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
#include "ChapterHtmlSlimParser.h"
|
#include "ChapterHtmlSlimParser.h"
|
||||||
|
|
||||||
|
#include <FsHelpers.h>
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
#include <expat.h>
|
#include <expat.h>
|
||||||
@ -214,9 +215,8 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
FILE* file = fopen(filepath, "r");
|
File file;
|
||||||
if (!file) {
|
if (!FsHelpers::openFileForRead("EHP", filepath, file)) {
|
||||||
Serial.printf("[%lu] [EHP] Couldn't open file %s\n", millis(), filepath);
|
|
||||||
XML_ParserFree(parser);
|
XML_ParserFree(parser);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -233,23 +233,23 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
|
|||||||
XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks
|
XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks
|
||||||
XML_SetCharacterDataHandler(parser, nullptr);
|
XML_SetCharacterDataHandler(parser, nullptr);
|
||||||
XML_ParserFree(parser);
|
XML_ParserFree(parser);
|
||||||
fclose(file);
|
file.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const size_t len = fread(buf, 1, 1024, file);
|
const size_t len = file.read(static_cast<uint8_t*>(buf), 1024);
|
||||||
|
|
||||||
if (ferror(file)) {
|
if (len == 0) {
|
||||||
Serial.printf("[%lu] [EHP] File read error\n", millis());
|
Serial.printf("[%lu] [EHP] File read error\n", millis());
|
||||||
XML_StopParser(parser, XML_FALSE); // Stop any pending processing
|
XML_StopParser(parser, XML_FALSE); // Stop any pending processing
|
||||||
XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks
|
XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks
|
||||||
XML_SetCharacterDataHandler(parser, nullptr);
|
XML_SetCharacterDataHandler(parser, nullptr);
|
||||||
XML_ParserFree(parser);
|
XML_ParserFree(parser);
|
||||||
fclose(file);
|
file.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
done = feof(file);
|
done = file.available() == 0;
|
||||||
|
|
||||||
if (XML_ParseBuffer(parser, static_cast<int>(len), done) == XML_STATUS_ERROR) {
|
if (XML_ParseBuffer(parser, static_cast<int>(len), done) == XML_STATUS_ERROR) {
|
||||||
Serial.printf("[%lu] [EHP] Parse error at line %lu:\n%s\n", millis(), XML_GetCurrentLineNumber(parser),
|
Serial.printf("[%lu] [EHP] Parse error at line %lu:\n%s\n", millis(), XML_GetCurrentLineNumber(parser),
|
||||||
@ -258,7 +258,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
|
|||||||
XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks
|
XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks
|
||||||
XML_SetCharacterDataHandler(parser, nullptr);
|
XML_SetCharacterDataHandler(parser, nullptr);
|
||||||
XML_ParserFree(parser);
|
XML_ParserFree(parser);
|
||||||
fclose(file);
|
file.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} while (!done);
|
} while (!done);
|
||||||
@ -267,7 +267,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
|
|||||||
XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks
|
XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks
|
||||||
XML_SetCharacterDataHandler(parser, nullptr);
|
XML_SetCharacterDataHandler(parser, nullptr);
|
||||||
XML_ParserFree(parser);
|
XML_ParserFree(parser);
|
||||||
fclose(file);
|
file.close();
|
||||||
|
|
||||||
// Process last page if there is still text
|
// Process last page if there is still text
|
||||||
if (currentTextBlock) {
|
if (currentTextBlock) {
|
||||||
|
|||||||
@ -15,7 +15,7 @@ class GfxRenderer;
|
|||||||
#define MAX_WORD_SIZE 200
|
#define MAX_WORD_SIZE 200
|
||||||
|
|
||||||
class ChapterHtmlSlimParser {
|
class ChapterHtmlSlimParser {
|
||||||
const char* filepath;
|
const std::string& filepath;
|
||||||
GfxRenderer& renderer;
|
GfxRenderer& renderer;
|
||||||
std::function<void(std::unique_ptr<Page>)> completePageFn;
|
std::function<void(std::unique_ptr<Page>)> completePageFn;
|
||||||
int depth = 0;
|
int depth = 0;
|
||||||
@ -45,7 +45,7 @@ class ChapterHtmlSlimParser {
|
|||||||
static void XMLCALL endElement(void* userData, const XML_Char* name);
|
static void XMLCALL endElement(void* userData, const XML_Char* name);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ChapterHtmlSlimParser(const char* filepath, GfxRenderer& renderer, const int fontId,
|
explicit ChapterHtmlSlimParser(const std::string& filepath, GfxRenderer& renderer, const int fontId,
|
||||||
const float lineCompression, const int marginTop, const int marginRight,
|
const float lineCompression, const int marginTop, const int marginRight,
|
||||||
const int marginBottom, const int marginLeft, const bool extraParagraphSpacing,
|
const int marginBottom, const int marginLeft, const bool extraParagraphSpacing,
|
||||||
const std::function<void(std::unique_ptr<Page>)>& completePageFn)
|
const std::function<void(std::unique_ptr<Page>)>& completePageFn)
|
||||||
|
|||||||
112
lib/FsHelpers/FsHelpers.cpp
Normal file
112
lib/FsHelpers/FsHelpers.cpp
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
#include "FsHelpers.h"
|
||||||
|
|
||||||
|
#include <SD.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
bool FsHelpers::openFileForRead(const char* moduleName, const char* path, File& file) {
|
||||||
|
if (!SD.exists(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;
|
||||||
|
|
||||||
|
for (const auto c : path) {
|
||||||
|
if (c == '/') {
|
||||||
|
if (!component.empty()) {
|
||||||
|
if (component == "..") {
|
||||||
|
if (!components.empty()) {
|
||||||
|
components.pop_back();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
components.push_back(component);
|
||||||
|
}
|
||||||
|
component.clear();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
component += c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!component.empty()) {
|
||||||
|
components.push_back(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string result;
|
||||||
|
for (const auto& c : components) {
|
||||||
|
if (!result.empty()) {
|
||||||
|
result += "/";
|
||||||
|
}
|
||||||
|
result += c;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
14
lib/FsHelpers/FsHelpers.h
Normal file
14
lib/FsHelpers/FsHelpers.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <FS.h>
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
@ -1,4 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include <FS.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
namespace serialization {
|
namespace serialization {
|
||||||
@ -7,21 +9,44 @@ static void writePod(std::ostream& os, const T& value) {
|
|||||||
os.write(reinterpret_cast<const char*>(&value), sizeof(T));
|
os.write(reinterpret_cast<const char*>(&value), sizeof(T));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static void writePod(File& file, const T& value) {
|
||||||
|
file.write(reinterpret_cast<const uint8_t*>(&value), sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
static void readPod(std::istream& is, T& value) {
|
static void readPod(std::istream& is, T& value) {
|
||||||
is.read(reinterpret_cast<char*>(&value), sizeof(T));
|
is.read(reinterpret_cast<char*>(&value), sizeof(T));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static void readPod(File& file, T& value) {
|
||||||
|
file.read(reinterpret_cast<uint8_t*>(&value), sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
static void writeString(std::ostream& os, const std::string& s) {
|
static void writeString(std::ostream& os, const std::string& s) {
|
||||||
const uint32_t len = s.size();
|
const uint32_t len = s.size();
|
||||||
writePod(os, len);
|
writePod(os, len);
|
||||||
os.write(s.data(), len);
|
os.write(s.data(), len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void writeString(File& file, const std::string& s) {
|
||||||
|
const uint32_t len = s.size();
|
||||||
|
writePod(file, len);
|
||||||
|
file.write(reinterpret_cast<const uint8_t*>(s.data()), len);
|
||||||
|
}
|
||||||
|
|
||||||
static void readString(std::istream& is, std::string& s) {
|
static void readString(std::istream& is, std::string& s) {
|
||||||
uint32_t len;
|
uint32_t len;
|
||||||
readPod(is, len);
|
readPod(is, len);
|
||||||
s.resize(len);
|
s.resize(len);
|
||||||
is.read(&s[0], len);
|
is.read(&s[0], len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void readString(File& file, std::string& s) {
|
||||||
|
uint32_t len;
|
||||||
|
readPod(file, len);
|
||||||
|
s.resize(len);
|
||||||
|
file.read(reinterpret_cast<uint8_t*>(&s[0]), len);
|
||||||
|
}
|
||||||
} // namespace serialization
|
} // namespace serialization
|
||||||
|
|||||||
@ -1,26 +1,28 @@
|
|||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
|
|
||||||
|
#include <FsHelpers.h>
|
||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
#include <SD.h>
|
#include <SD.h>
|
||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
// Initialize the static instance
|
// Initialize the static instance
|
||||||
CrossPointSettings CrossPointSettings::instance;
|
CrossPointSettings CrossPointSettings::instance;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr uint8_t SETTINGS_FILE_VERSION = 1;
|
constexpr uint8_t SETTINGS_FILE_VERSION = 1;
|
||||||
constexpr uint8_t SETTINGS_COUNT = 3;
|
constexpr uint8_t SETTINGS_COUNT = 3;
|
||||||
constexpr char SETTINGS_FILE[] = "/sd/.crosspoint/settings.bin";
|
constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin";
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool CrossPointSettings::saveToFile() const {
|
bool CrossPointSettings::saveToFile() const {
|
||||||
// Make sure the directory exists
|
// Make sure the directory exists
|
||||||
SD.mkdir("/.crosspoint");
|
SD.mkdir("/.crosspoint");
|
||||||
|
|
||||||
std::ofstream outputFile(SETTINGS_FILE);
|
File outputFile;
|
||||||
|
if (!FsHelpers::openFileForWrite("CPS", SETTINGS_FILE, outputFile)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
serialization::writePod(outputFile, SETTINGS_FILE_VERSION);
|
serialization::writePod(outputFile, SETTINGS_FILE_VERSION);
|
||||||
serialization::writePod(outputFile, SETTINGS_COUNT);
|
serialization::writePod(outputFile, SETTINGS_COUNT);
|
||||||
serialization::writePod(outputFile, sleepScreen);
|
serialization::writePod(outputFile, sleepScreen);
|
||||||
@ -33,13 +35,11 @@ bool CrossPointSettings::saveToFile() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool CrossPointSettings::loadFromFile() {
|
bool CrossPointSettings::loadFromFile() {
|
||||||
if (!SD.exists(SETTINGS_FILE + 3)) { // +3 to skip "/sd" prefix
|
File inputFile;
|
||||||
Serial.printf("[%lu] [CPS] Settings file does not exist, using defaults\n", millis());
|
if (!FsHelpers::openFileForRead("CPS", SETTINGS_FILE, inputFile)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ifstream inputFile(SETTINGS_FILE);
|
|
||||||
|
|
||||||
uint8_t version;
|
uint8_t version;
|
||||||
serialization::readPod(inputFile, version);
|
serialization::readPod(inputFile, version);
|
||||||
if (version != SETTINGS_FILE_VERSION) {
|
if (version != SETTINGS_FILE_VERSION) {
|
||||||
|
|||||||
@ -1,20 +1,22 @@
|
|||||||
#include "CrossPointState.h"
|
#include "CrossPointState.h"
|
||||||
|
|
||||||
|
#include <FsHelpers.h>
|
||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
#include <SD.h>
|
|
||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr uint8_t STATE_FILE_VERSION = 1;
|
constexpr uint8_t STATE_FILE_VERSION = 1;
|
||||||
constexpr char STATE_FILE[] = "/sd/.crosspoint/state.bin";
|
constexpr char STATE_FILE[] = "/.crosspoint/state.bin";
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
CrossPointState CrossPointState::instance;
|
CrossPointState CrossPointState::instance;
|
||||||
|
|
||||||
bool CrossPointState::saveToFile() const {
|
bool CrossPointState::saveToFile() const {
|
||||||
std::ofstream outputFile(STATE_FILE);
|
File outputFile;
|
||||||
|
if (!FsHelpers::openFileForWrite("CPS", STATE_FILE, outputFile)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
serialization::writePod(outputFile, STATE_FILE_VERSION);
|
serialization::writePod(outputFile, STATE_FILE_VERSION);
|
||||||
serialization::writeString(outputFile, openEpubPath);
|
serialization::writeString(outputFile, openEpubPath);
|
||||||
outputFile.close();
|
outputFile.close();
|
||||||
@ -22,7 +24,10 @@ bool CrossPointState::saveToFile() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool CrossPointState::loadFromFile() {
|
bool CrossPointState::loadFromFile() {
|
||||||
std::ifstream inputFile(STATE_FILE);
|
File inputFile;
|
||||||
|
if (!FsHelpers::openFileForRead("CPS", STATE_FILE, inputFile)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t version;
|
uint8_t version;
|
||||||
serialization::readPod(inputFile, version);
|
serialization::readPod(inputFile, version);
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
#include "WifiCredentialStore.h"
|
#include "WifiCredentialStore.h"
|
||||||
|
|
||||||
|
#include <FsHelpers.h>
|
||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
#include <SD.h>
|
#include <SD.h>
|
||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
// Initialize the static instance
|
// Initialize the static instance
|
||||||
WifiCredentialStore WifiCredentialStore::instance;
|
WifiCredentialStore WifiCredentialStore::instance;
|
||||||
|
|
||||||
@ -14,7 +13,7 @@ namespace {
|
|||||||
constexpr uint8_t WIFI_FILE_VERSION = 1;
|
constexpr uint8_t WIFI_FILE_VERSION = 1;
|
||||||
|
|
||||||
// WiFi credentials file path
|
// WiFi credentials file path
|
||||||
constexpr char WIFI_FILE[] = "/sd/.crosspoint/wifi.bin";
|
constexpr char WIFI_FILE[] = "/.crosspoint/wifi.bin";
|
||||||
|
|
||||||
// Obfuscation key - "CrossPoint" in ASCII
|
// Obfuscation key - "CrossPoint" in ASCII
|
||||||
// This is NOT cryptographic security, just prevents casual file reading
|
// This is NOT cryptographic security, just prevents casual file reading
|
||||||
@ -33,9 +32,8 @@ bool WifiCredentialStore::saveToFile() const {
|
|||||||
// Make sure the directory exists
|
// Make sure the directory exists
|
||||||
SD.mkdir("/.crosspoint");
|
SD.mkdir("/.crosspoint");
|
||||||
|
|
||||||
std::ofstream file(WIFI_FILE, std::ios::binary);
|
File file;
|
||||||
if (!file) {
|
if (!FsHelpers::openFileForWrite("WCS", WIFI_FILE, file)) {
|
||||||
Serial.printf("[%lu] [WCS] Failed to open wifi.bin for writing\n", millis());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,14 +60,8 @@ bool WifiCredentialStore::saveToFile() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool WifiCredentialStore::loadFromFile() {
|
bool WifiCredentialStore::loadFromFile() {
|
||||||
if (!SD.exists(WIFI_FILE + 3)) { // +3 to skip "/sd" prefix
|
File file;
|
||||||
Serial.printf("[%lu] [WCS] WiFi credentials file does not exist\n", millis());
|
if (!FsHelpers::openFileForRead("WCS", WIFI_FILE, file)) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ifstream file(WIFI_FILE, std::ios::binary);
|
|
||||||
if (!file) {
|
|
||||||
Serial.printf("[%lu] [WCS] Failed to open wifi.bin for reading\n", millis());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#include "SleepActivity.h"
|
#include "SleepActivity.h"
|
||||||
|
|
||||||
#include <Epub.h>
|
#include <Epub.h>
|
||||||
|
#include <FsHelpers.h>
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <SD.h>
|
#include <SD.h>
|
||||||
|
|
||||||
@ -76,8 +77,8 @@ void SleepActivity::renderCustomSleepScreen() const {
|
|||||||
// Generate a random number between 1 and numFiles
|
// Generate a random number between 1 and numFiles
|
||||||
const auto randomFileIndex = random(numFiles);
|
const auto randomFileIndex = random(numFiles);
|
||||||
const auto filename = "/sleep/" + files[randomFileIndex];
|
const auto filename = "/sleep/" + files[randomFileIndex];
|
||||||
auto file = SD.open(filename.c_str());
|
File file;
|
||||||
if (file) {
|
if (FsHelpers::openFileForRead("SLP", filename, file)) {
|
||||||
Serial.printf("[%lu] [SLP] Randomly loading: /sleep/%s\n", millis(), files[randomFileIndex].c_str());
|
Serial.printf("[%lu] [SLP] Randomly loading: /sleep/%s\n", millis(), files[randomFileIndex].c_str());
|
||||||
delay(100);
|
delay(100);
|
||||||
Bitmap bitmap(file);
|
Bitmap bitmap(file);
|
||||||
@ -93,8 +94,8 @@ void SleepActivity::renderCustomSleepScreen() const {
|
|||||||
|
|
||||||
// Look for sleep.bmp on the root of the sd card to determine if we should
|
// Look for sleep.bmp on the root of the sd card to determine if we should
|
||||||
// render a custom sleep screen instead of the default.
|
// render a custom sleep screen instead of the default.
|
||||||
auto file = SD.open("/sleep.bmp");
|
File file;
|
||||||
if (file) {
|
if (FsHelpers::openFileForRead("SLP", "/sleep.bmp", file)) {
|
||||||
Bitmap bitmap(file);
|
Bitmap bitmap(file);
|
||||||
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
||||||
Serial.printf("[%lu] [SLP] Loading: /sleep.bmp\n", millis());
|
Serial.printf("[%lu] [SLP] Loading: /sleep.bmp\n", millis());
|
||||||
@ -186,8 +187,8 @@ void SleepActivity::renderCoverSleepScreen() const {
|
|||||||
return renderDefaultSleepScreen();
|
return renderDefaultSleepScreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto file = SD.open(lastEpub.getCoverBmpPath().c_str(), FILE_READ);
|
File file;
|
||||||
if (file) {
|
if (FsHelpers::openFileForRead("SLP", lastEpub.getCoverBmpPath(), file)) {
|
||||||
Bitmap bitmap(file);
|
Bitmap bitmap(file);
|
||||||
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
||||||
renderBitmapSleepScreen(bitmap);
|
renderBitmapSleepScreen(bitmap);
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
#include "EpubReaderActivity.h"
|
#include "EpubReaderActivity.h"
|
||||||
|
|
||||||
#include <Epub/Page.h>
|
#include <Epub/Page.h>
|
||||||
|
#include <FsHelpers.h>
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <InputManager.h>
|
#include <InputManager.h>
|
||||||
#include <SD.h>
|
|
||||||
|
|
||||||
#include "Battery.h"
|
#include "Battery.h"
|
||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
@ -37,8 +37,8 @@ void EpubReaderActivity::onEnter() {
|
|||||||
|
|
||||||
epub->setupCacheDir();
|
epub->setupCacheDir();
|
||||||
|
|
||||||
File f = SD.open((epub->getCachePath() + "/progress.bin").c_str());
|
File f;
|
||||||
if (f) {
|
if (FsHelpers::openFileForRead("ERS", epub->getCachePath() + "/progress.bin", f)) {
|
||||||
uint8_t data[4];
|
uint8_t data[4];
|
||||||
if (f.read(data, 4) == 4) {
|
if (f.read(data, 4) == 4) {
|
||||||
currentSpineIndex = data[0] + (data[1] << 8);
|
currentSpineIndex = data[0] + (data[1] << 8);
|
||||||
@ -282,7 +282,8 @@ void EpubReaderActivity::renderScreen() {
|
|||||||
Serial.printf("[%lu] [ERS] Rendered page in %dms\n", millis(), millis() - start);
|
Serial.printf("[%lu] [ERS] Rendered page in %dms\n", millis(), millis() - start);
|
||||||
}
|
}
|
||||||
|
|
||||||
File f = SD.open((epub->getCachePath() + "/progress.bin").c_str(), FILE_WRITE);
|
File f;
|
||||||
|
if (FsHelpers::openFileForWrite("ERS", epub->getCachePath() + "/progress.bin", f)) {
|
||||||
uint8_t data[4];
|
uint8_t data[4];
|
||||||
data[0] = currentSpineIndex & 0xFF;
|
data[0] = currentSpineIndex & 0xFF;
|
||||||
data[1] = (currentSpineIndex >> 8) & 0xFF;
|
data[1] = (currentSpineIndex >> 8) & 0xFF;
|
||||||
@ -290,6 +291,7 @@ void EpubReaderActivity::renderScreen() {
|
|||||||
data[3] = (section->currentPage >> 8) & 0xFF;
|
data[3] = (section->currentPage >> 8) & 0xFF;
|
||||||
f.write(data, 4);
|
f.write(data, 4);
|
||||||
f.close();
|
f.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderActivity::renderContents(std::unique_ptr<Page> page) {
|
void EpubReaderActivity::renderContents(std::unique_ptr<Page> page) {
|
||||||
|
|||||||
@ -160,7 +160,12 @@ void onGoHome() {
|
|||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
t1 = millis();
|
t1 = millis();
|
||||||
|
|
||||||
|
// Only start serial if USB connected
|
||||||
|
pinMode(UART0_RXD, INPUT);
|
||||||
|
if (digitalRead(UART0_RXD) == HIGH) {
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [ ] Starting CrossPoint version " CROSSPOINT_VERSION "\n", millis());
|
Serial.printf("[%lu] [ ] Starting CrossPoint version " CROSSPOINT_VERSION "\n", millis());
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#include "CrossPointWebServer.h"
|
#include "CrossPointWebServer.h"
|
||||||
|
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
|
#include <FsHelpers.h>
|
||||||
#include <SD.h>
|
#include <SD.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
|
|
||||||
@ -339,8 +340,7 @@ void CrossPointWebServer::handleUpload() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Open file for writing
|
// Open file for writing
|
||||||
uploadFile = SD.open(filePath.c_str(), FILE_WRITE);
|
if (!FsHelpers::openFileForWrite("WEB", filePath, uploadFile)) {
|
||||||
if (!uploadFile) {
|
|
||||||
uploadError = "Failed to create file on SD card";
|
uploadError = "Failed to create file on SD card";
|
||||||
Serial.printf("[%lu] [WEB] [UPLOAD] FAILED to create file: %s\n", millis(), filePath.c_str());
|
Serial.printf("[%lu] [WEB] [UPLOAD] FAILED to create file: %s\n", millis(), filePath.c_str());
|
||||||
return;
|
return;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user