feat: Add central logging pragma (#843)
## Summary
* Definition and use of a central LOG function, that can later be
extended or completely be removed (for public use where debugging
information may not be required) to save flash by suppressing the
-DENABLE_SERIAL_LOG like in the slim branch
* **What changes are included?**
## Additional Context
* By using the central logger the usual:
```
#include <HardwareSerial.h>
...
Serial.printf("[%lu] [WCS] Obfuscating/deobfuscating %zu bytes\n", millis(), data.size());
```
would then become
```
#include <Logging.h>
...
LOG_DBG("WCS", "Obfuscating/deobfuscating %zu bytes", data.size());
```
You do have ``LOG_DBG`` for debug messages, ``LOG_ERR`` for error
messages and ``LOG_INF`` for informational messages. Depending on the
verbosity level defined (see below) soe of these message types will be
suppressed/not-compiled.
* The normal compilation (default) will create a firmware.elf file of
42.194.356 bytes, the same code via slim will create 42.024.048 bytes -
170.308 bytes less
* Firmware.bin : 6.469.984 bytes for default, 6.418.672 bytes for slim -
51.312 bytes less
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _NO_
---------
Co-authored-by: Xuan Son Nguyen <son@huggingface.co>
This commit is contained in:
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
#include <FsHelpers.h>
|
#include <FsHelpers.h>
|
||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
#include <HardwareSerial.h>
|
|
||||||
#include <JpegToBmpConverter.h>
|
#include <JpegToBmpConverter.h>
|
||||||
|
#include <Logging.h>
|
||||||
#include <ZipFile.h>
|
#include <ZipFile.h>
|
||||||
|
|
||||||
#include "Epub/parsers/ContainerParser.h"
|
#include "Epub/parsers/ContainerParser.h"
|
||||||
@@ -17,7 +17,7 @@ bool Epub::findContentOpfFile(std::string* contentOpfFile) const {
|
|||||||
|
|
||||||
// Get file size without loading it all into heap
|
// Get file size without loading it all into heap
|
||||||
if (!getItemSize(containerPath, &containerSize)) {
|
if (!getItemSize(containerPath, &containerSize)) {
|
||||||
Serial.printf("[%lu] [EBP] Could not find or size META-INF/container.xml\n", millis());
|
LOG_ERR("EBP", "Could not find or size META-INF/container.xml");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,13 +29,13 @@ bool Epub::findContentOpfFile(std::string* contentOpfFile) const {
|
|||||||
|
|
||||||
// Stream read (reusing your existing stream logic)
|
// Stream read (reusing your existing stream logic)
|
||||||
if (!readItemContentsToStream(containerPath, containerParser, 512)) {
|
if (!readItemContentsToStream(containerPath, containerParser, 512)) {
|
||||||
Serial.printf("[%lu] [EBP] Could not read META-INF/container.xml\n", millis());
|
LOG_ERR("EBP", "Could not read META-INF/container.xml");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract the result
|
// Extract the result
|
||||||
if (containerParser.fullPath.empty()) {
|
if (containerParser.fullPath.empty()) {
|
||||||
Serial.printf("[%lu] [EBP] Could not find valid rootfile in container.xml\n", millis());
|
LOG_ERR("EBP", "Could not find valid rootfile in container.xml");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,28 +46,28 @@ bool Epub::findContentOpfFile(std::string* contentOpfFile) const {
|
|||||||
bool Epub::parseContentOpf(BookMetadataCache::BookMetadata& bookMetadata) {
|
bool Epub::parseContentOpf(BookMetadataCache::BookMetadata& bookMetadata) {
|
||||||
std::string contentOpfFilePath;
|
std::string contentOpfFilePath;
|
||||||
if (!findContentOpfFile(&contentOpfFilePath)) {
|
if (!findContentOpfFile(&contentOpfFilePath)) {
|
||||||
Serial.printf("[%lu] [EBP] Could not find content.opf in zip\n", millis());
|
LOG_ERR("EBP", "Could not find content.opf in zip");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
contentBasePath = contentOpfFilePath.substr(0, contentOpfFilePath.find_last_of('/') + 1);
|
contentBasePath = contentOpfFilePath.substr(0, contentOpfFilePath.find_last_of('/') + 1);
|
||||||
|
|
||||||
Serial.printf("[%lu] [EBP] Parsing content.opf: %s\n", millis(), contentOpfFilePath.c_str());
|
LOG_DBG("EBP", "Parsing content.opf: %s", contentOpfFilePath.c_str());
|
||||||
|
|
||||||
size_t contentOpfSize;
|
size_t contentOpfSize;
|
||||||
if (!getItemSize(contentOpfFilePath, &contentOpfSize)) {
|
if (!getItemSize(contentOpfFilePath, &contentOpfSize)) {
|
||||||
Serial.printf("[%lu] [EBP] Could not get size of content.opf\n", millis());
|
LOG_ERR("EBP", "Could not get size of content.opf");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentOpfParser opfParser(getCachePath(), getBasePath(), contentOpfSize, bookMetadataCache.get());
|
ContentOpfParser opfParser(getCachePath(), getBasePath(), contentOpfSize, bookMetadataCache.get());
|
||||||
if (!opfParser.setup()) {
|
if (!opfParser.setup()) {
|
||||||
Serial.printf("[%lu] [EBP] Could not setup content.opf parser\n", millis());
|
LOG_ERR("EBP", "Could not setup content.opf parser");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!readItemContentsToStream(contentOpfFilePath, opfParser, 1024)) {
|
if (!readItemContentsToStream(contentOpfFilePath, opfParser, 1024)) {
|
||||||
Serial.printf("[%lu] [EBP] Could not read content.opf\n", millis());
|
LOG_ERR("EBP", "Could not read content.opf");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,18 +90,18 @@ bool Epub::parseContentOpf(BookMetadataCache::BookMetadata& bookMetadata) {
|
|||||||
cssFiles = opfParser.cssFiles;
|
cssFiles = opfParser.cssFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [EBP] Successfully parsed content.opf\n", millis());
|
LOG_DBG("EBP", "Successfully parsed content.opf");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Epub::parseTocNcxFile() const {
|
bool Epub::parseTocNcxFile() const {
|
||||||
// the ncx file should have been specified in the content.opf file
|
// the ncx file should have been specified in the content.opf file
|
||||||
if (tocNcxItem.empty()) {
|
if (tocNcxItem.empty()) {
|
||||||
Serial.printf("[%lu] [EBP] No ncx file specified\n", millis());
|
LOG_DBG("EBP", "No ncx file specified");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [EBP] Parsing toc ncx file: %s\n", millis(), tocNcxItem.c_str());
|
LOG_DBG("EBP", "Parsing toc ncx file: %s", tocNcxItem.c_str());
|
||||||
|
|
||||||
const auto tmpNcxPath = getCachePath() + "/toc.ncx";
|
const auto tmpNcxPath = getCachePath() + "/toc.ncx";
|
||||||
FsFile tempNcxFile;
|
FsFile tempNcxFile;
|
||||||
@@ -118,14 +118,14 @@ bool Epub::parseTocNcxFile() const {
|
|||||||
TocNcxParser ncxParser(contentBasePath, ncxSize, bookMetadataCache.get());
|
TocNcxParser ncxParser(contentBasePath, ncxSize, bookMetadataCache.get());
|
||||||
|
|
||||||
if (!ncxParser.setup()) {
|
if (!ncxParser.setup()) {
|
||||||
Serial.printf("[%lu] [EBP] Could not setup toc ncx parser\n", millis());
|
LOG_ERR("EBP", "Could not setup toc ncx parser");
|
||||||
tempNcxFile.close();
|
tempNcxFile.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto ncxBuffer = static_cast<uint8_t*>(malloc(1024));
|
const auto ncxBuffer = static_cast<uint8_t*>(malloc(1024));
|
||||||
if (!ncxBuffer) {
|
if (!ncxBuffer) {
|
||||||
Serial.printf("[%lu] [EBP] Could not allocate memory for toc ncx parser\n", millis());
|
LOG_ERR("EBP", "Could not allocate memory for toc ncx parser");
|
||||||
tempNcxFile.close();
|
tempNcxFile.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@ bool Epub::parseTocNcxFile() const {
|
|||||||
const auto processedSize = ncxParser.write(ncxBuffer, readSize);
|
const auto processedSize = ncxParser.write(ncxBuffer, readSize);
|
||||||
|
|
||||||
if (processedSize != readSize) {
|
if (processedSize != readSize) {
|
||||||
Serial.printf("[%lu] [EBP] Could not process all toc ncx data\n", millis());
|
LOG_ERR("EBP", "Could not process all toc ncx data");
|
||||||
free(ncxBuffer);
|
free(ncxBuffer);
|
||||||
tempNcxFile.close();
|
tempNcxFile.close();
|
||||||
return false;
|
return false;
|
||||||
@@ -147,18 +147,18 @@ bool Epub::parseTocNcxFile() const {
|
|||||||
tempNcxFile.close();
|
tempNcxFile.close();
|
||||||
Storage.remove(tmpNcxPath.c_str());
|
Storage.remove(tmpNcxPath.c_str());
|
||||||
|
|
||||||
Serial.printf("[%lu] [EBP] Parsed TOC items\n", millis());
|
LOG_DBG("EBP", "Parsed TOC items");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Epub::parseTocNavFile() const {
|
bool Epub::parseTocNavFile() const {
|
||||||
// the nav file should have been specified in the content.opf file (EPUB 3)
|
// the nav file should have been specified in the content.opf file (EPUB 3)
|
||||||
if (tocNavItem.empty()) {
|
if (tocNavItem.empty()) {
|
||||||
Serial.printf("[%lu] [EBP] No nav file specified\n", millis());
|
LOG_DBG("EBP", "No nav file specified");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [EBP] Parsing toc nav file: %s\n", millis(), tocNavItem.c_str());
|
LOG_DBG("EBP", "Parsing toc nav file: %s", tocNavItem.c_str());
|
||||||
|
|
||||||
const auto tmpNavPath = getCachePath() + "/toc.nav";
|
const auto tmpNavPath = getCachePath() + "/toc.nav";
|
||||||
FsFile tempNavFile;
|
FsFile tempNavFile;
|
||||||
@@ -178,13 +178,13 @@ bool Epub::parseTocNavFile() const {
|
|||||||
TocNavParser navParser(navContentBasePath, navSize, bookMetadataCache.get());
|
TocNavParser navParser(navContentBasePath, navSize, bookMetadataCache.get());
|
||||||
|
|
||||||
if (!navParser.setup()) {
|
if (!navParser.setup()) {
|
||||||
Serial.printf("[%lu] [EBP] Could not setup toc nav parser\n", millis());
|
LOG_ERR("EBP", "Could not setup toc nav parser");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto navBuffer = static_cast<uint8_t*>(malloc(1024));
|
const auto navBuffer = static_cast<uint8_t*>(malloc(1024));
|
||||||
if (!navBuffer) {
|
if (!navBuffer) {
|
||||||
Serial.printf("[%lu] [EBP] Could not allocate memory for toc nav parser\n", millis());
|
LOG_ERR("EBP", "Could not allocate memory for toc nav parser");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,7 +193,7 @@ bool Epub::parseTocNavFile() const {
|
|||||||
const auto processedSize = navParser.write(navBuffer, readSize);
|
const auto processedSize = navParser.write(navBuffer, readSize);
|
||||||
|
|
||||||
if (processedSize != readSize) {
|
if (processedSize != readSize) {
|
||||||
Serial.printf("[%lu] [EBP] Could not process all toc nav data\n", millis());
|
LOG_ERR("EBP", "Could not process all toc nav data");
|
||||||
free(navBuffer);
|
free(navBuffer);
|
||||||
tempNavFile.close();
|
tempNavFile.close();
|
||||||
return false;
|
return false;
|
||||||
@@ -204,7 +204,7 @@ bool Epub::parseTocNavFile() const {
|
|||||||
tempNavFile.close();
|
tempNavFile.close();
|
||||||
Storage.remove(tmpNavPath.c_str());
|
Storage.remove(tmpNavPath.c_str());
|
||||||
|
|
||||||
Serial.printf("[%lu] [EBP] Parsed TOC nav items\n", millis());
|
LOG_DBG("EBP", "Parsed TOC nav items");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,35 +215,35 @@ bool Epub::loadCssRulesFromCache() const {
|
|||||||
if (Storage.openFileForRead("EBP", getCssRulesCache(), cssCacheFile)) {
|
if (Storage.openFileForRead("EBP", getCssRulesCache(), cssCacheFile)) {
|
||||||
if (cssParser->loadFromCache(cssCacheFile)) {
|
if (cssParser->loadFromCache(cssCacheFile)) {
|
||||||
cssCacheFile.close();
|
cssCacheFile.close();
|
||||||
Serial.printf("[%lu] [EBP] Loaded CSS rules from cache\n", millis());
|
LOG_DBG("EBP", "Loaded CSS rules from cache");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
cssCacheFile.close();
|
cssCacheFile.close();
|
||||||
Serial.printf("[%lu] [EBP] CSS cache invalid, reparsing\n", millis());
|
LOG_DBG("EBP", "CSS cache invalid, reparsing");
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Epub::parseCssFiles() const {
|
void Epub::parseCssFiles() const {
|
||||||
if (cssFiles.empty()) {
|
if (cssFiles.empty()) {
|
||||||
Serial.printf("[%lu] [EBP] No CSS files to parse, but CssParser created for inline styles\n", millis());
|
LOG_DBG("EBP", "No CSS files to parse, but CssParser created for inline styles");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to load from CSS cache first
|
// Try to load from CSS cache first
|
||||||
if (!loadCssRulesFromCache()) {
|
if (!loadCssRulesFromCache()) {
|
||||||
// Cache miss - parse CSS files
|
// Cache miss - parse CSS files
|
||||||
for (const auto& cssPath : cssFiles) {
|
for (const auto& cssPath : cssFiles) {
|
||||||
Serial.printf("[%lu] [EBP] Parsing CSS file: %s\n", millis(), cssPath.c_str());
|
LOG_DBG("EBP", "Parsing CSS file: %s", cssPath.c_str());
|
||||||
|
|
||||||
// Extract CSS file to temp location
|
// Extract CSS file to temp location
|
||||||
const auto tmpCssPath = getCachePath() + "/.tmp.css";
|
const auto tmpCssPath = getCachePath() + "/.tmp.css";
|
||||||
FsFile tempCssFile;
|
FsFile tempCssFile;
|
||||||
if (!Storage.openFileForWrite("EBP", tmpCssPath, tempCssFile)) {
|
if (!Storage.openFileForWrite("EBP", tmpCssPath, tempCssFile)) {
|
||||||
Serial.printf("[%lu] [EBP] Could not create temp CSS file\n", millis());
|
LOG_ERR("EBP", "Could not create temp CSS file");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!readItemContentsToStream(cssPath, tempCssFile, 1024)) {
|
if (!readItemContentsToStream(cssPath, tempCssFile, 1024)) {
|
||||||
Serial.printf("[%lu] [EBP] Could not read CSS file: %s\n", millis(), cssPath.c_str());
|
LOG_ERR("EBP", "Could not read CSS file: %s", cssPath.c_str());
|
||||||
tempCssFile.close();
|
tempCssFile.close();
|
||||||
Storage.remove(tmpCssPath.c_str());
|
Storage.remove(tmpCssPath.c_str());
|
||||||
continue;
|
continue;
|
||||||
@@ -252,7 +252,7 @@ void Epub::parseCssFiles() const {
|
|||||||
|
|
||||||
// Parse the CSS file
|
// Parse the CSS file
|
||||||
if (!Storage.openFileForRead("EBP", tmpCssPath, tempCssFile)) {
|
if (!Storage.openFileForRead("EBP", tmpCssPath, tempCssFile)) {
|
||||||
Serial.printf("[%lu] [EBP] Could not open temp CSS file for reading\n", millis());
|
LOG_ERR("EBP", "Could not open temp CSS file for reading");
|
||||||
Storage.remove(tmpCssPath.c_str());
|
Storage.remove(tmpCssPath.c_str());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -268,14 +268,13 @@ void Epub::parseCssFiles() const {
|
|||||||
cssCacheFile.close();
|
cssCacheFile.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [EBP] Loaded %zu CSS style rules from %zu files\n", millis(), cssParser->ruleCount(),
|
LOG_DBG("EBP", "Loaded %zu CSS style rules from %zu files", cssParser->ruleCount(), cssFiles.size());
|
||||||
cssFiles.size());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// load in the meta data for the epub file
|
// load in the meta data for the epub file
|
||||||
bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) {
|
bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) {
|
||||||
Serial.printf("[%lu] [EBP] Loading ePub: %s\n", millis(), filepath.c_str());
|
LOG_DBG("EBP", "Loading ePub: %s", filepath.c_str());
|
||||||
|
|
||||||
// Initialize spine/TOC cache
|
// Initialize spine/TOC cache
|
||||||
bookMetadataCache.reset(new BookMetadataCache(cachePath));
|
bookMetadataCache.reset(new BookMetadataCache(cachePath));
|
||||||
@@ -285,15 +284,15 @@ bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) {
|
|||||||
// Try to load existing cache first
|
// Try to load existing cache first
|
||||||
if (bookMetadataCache->load()) {
|
if (bookMetadataCache->load()) {
|
||||||
if (!skipLoadingCss && !loadCssRulesFromCache()) {
|
if (!skipLoadingCss && !loadCssRulesFromCache()) {
|
||||||
Serial.printf("[%lu] [EBP] Warning: CSS rules cache not found, attempting to parse CSS files\n", millis());
|
LOG_DBG("EBP", "Warning: CSS rules cache not found, attempting to parse CSS files");
|
||||||
// to get CSS file list
|
// to get CSS file list
|
||||||
if (!parseContentOpf(bookMetadataCache->coreMetadata)) {
|
if (!parseContentOpf(bookMetadataCache->coreMetadata)) {
|
||||||
Serial.printf("[%lu] [EBP] Could not parse content.opf from cached bookMetadata for CSS files\n", millis());
|
LOG_ERR("EBP", "Could not parse content.opf from cached bookMetadata for CSS files");
|
||||||
// continue anyway - book will work without CSS and we'll still load any inline style CSS
|
// continue anyway - book will work without CSS and we'll still load any inline style CSS
|
||||||
}
|
}
|
||||||
parseCssFiles();
|
parseCssFiles();
|
||||||
}
|
}
|
||||||
Serial.printf("[%lu] [EBP] Loaded ePub: %s\n", millis(), filepath.c_str());
|
LOG_DBG("EBP", "Loaded ePub: %s", filepath.c_str());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,14 +302,14 @@ bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cache doesn't exist or is invalid, build it
|
// Cache doesn't exist or is invalid, build it
|
||||||
Serial.printf("[%lu] [EBP] Cache not found, building spine/TOC cache\n", millis());
|
LOG_DBG("EBP", "Cache not found, building spine/TOC cache");
|
||||||
setupCacheDir();
|
setupCacheDir();
|
||||||
|
|
||||||
const uint32_t indexingStart = millis();
|
const uint32_t indexingStart = millis();
|
||||||
|
|
||||||
// Begin building cache - stream entries to disk immediately
|
// Begin building cache - stream entries to disk immediately
|
||||||
if (!bookMetadataCache->beginWrite()) {
|
if (!bookMetadataCache->beginWrite()) {
|
||||||
Serial.printf("[%lu] [EBP] Could not begin writing cache\n", millis());
|
LOG_ERR("EBP", "Could not begin writing cache");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,23 +317,23 @@ bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) {
|
|||||||
const uint32_t opfStart = millis();
|
const uint32_t opfStart = millis();
|
||||||
BookMetadataCache::BookMetadata bookMetadata;
|
BookMetadataCache::BookMetadata bookMetadata;
|
||||||
if (!bookMetadataCache->beginContentOpfPass()) {
|
if (!bookMetadataCache->beginContentOpfPass()) {
|
||||||
Serial.printf("[%lu] [EBP] Could not begin writing content.opf pass\n", millis());
|
LOG_ERR("EBP", "Could not begin writing content.opf pass");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!parseContentOpf(bookMetadata)) {
|
if (!parseContentOpf(bookMetadata)) {
|
||||||
Serial.printf("[%lu] [EBP] Could not parse content.opf\n", millis());
|
LOG_ERR("EBP", "Could not parse content.opf");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!bookMetadataCache->endContentOpfPass()) {
|
if (!bookMetadataCache->endContentOpfPass()) {
|
||||||
Serial.printf("[%lu] [EBP] Could not end writing content.opf pass\n", millis());
|
LOG_ERR("EBP", "Could not end writing content.opf pass");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Serial.printf("[%lu] [EBP] OPF pass completed in %lu ms\n", millis(), millis() - opfStart);
|
LOG_DBG("EBP", "OPF pass completed in %lu ms", millis() - opfStart);
|
||||||
|
|
||||||
// TOC Pass - try EPUB 3 nav first, fall back to NCX
|
// TOC Pass - try EPUB 3 nav first, fall back to NCX
|
||||||
const uint32_t tocStart = millis();
|
const uint32_t tocStart = millis();
|
||||||
if (!bookMetadataCache->beginTocPass()) {
|
if (!bookMetadataCache->beginTocPass()) {
|
||||||
Serial.printf("[%lu] [EBP] Could not begin writing toc pass\n", millis());
|
LOG_ERR("EBP", "Could not begin writing toc pass");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,50 +341,50 @@ bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) {
|
|||||||
|
|
||||||
// Try EPUB 3 nav document first (preferred)
|
// Try EPUB 3 nav document first (preferred)
|
||||||
if (!tocNavItem.empty()) {
|
if (!tocNavItem.empty()) {
|
||||||
Serial.printf("[%lu] [EBP] Attempting to parse EPUB 3 nav document\n", millis());
|
LOG_DBG("EBP", "Attempting to parse EPUB 3 nav document");
|
||||||
tocParsed = parseTocNavFile();
|
tocParsed = parseTocNavFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fall back to NCX if nav parsing failed or wasn't available
|
// Fall back to NCX if nav parsing failed or wasn't available
|
||||||
if (!tocParsed && !tocNcxItem.empty()) {
|
if (!tocParsed && !tocNcxItem.empty()) {
|
||||||
Serial.printf("[%lu] [EBP] Falling back to NCX TOC\n", millis());
|
LOG_DBG("EBP", "Falling back to NCX TOC");
|
||||||
tocParsed = parseTocNcxFile();
|
tocParsed = parseTocNcxFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tocParsed) {
|
if (!tocParsed) {
|
||||||
Serial.printf("[%lu] [EBP] Warning: Could not parse any TOC format\n", millis());
|
LOG_ERR("EBP", "Warning: Could not parse any TOC format");
|
||||||
// Continue anyway - book will work without TOC
|
// Continue anyway - book will work without TOC
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bookMetadataCache->endTocPass()) {
|
if (!bookMetadataCache->endTocPass()) {
|
||||||
Serial.printf("[%lu] [EBP] Could not end writing toc pass\n", millis());
|
LOG_ERR("EBP", "Could not end writing toc pass");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Serial.printf("[%lu] [EBP] TOC pass completed in %lu ms\n", millis(), millis() - tocStart);
|
LOG_DBG("EBP", "TOC pass completed in %lu ms", millis() - tocStart);
|
||||||
|
|
||||||
// Close the cache files
|
// Close the cache files
|
||||||
if (!bookMetadataCache->endWrite()) {
|
if (!bookMetadataCache->endWrite()) {
|
||||||
Serial.printf("[%lu] [EBP] Could not end writing cache\n", millis());
|
LOG_ERR("EBP", "Could not end writing cache");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build final book.bin
|
// Build final book.bin
|
||||||
const uint32_t buildStart = millis();
|
const uint32_t buildStart = millis();
|
||||||
if (!bookMetadataCache->buildBookBin(filepath, bookMetadata)) {
|
if (!bookMetadataCache->buildBookBin(filepath, bookMetadata)) {
|
||||||
Serial.printf("[%lu] [EBP] Could not update mappings and sizes\n", millis());
|
LOG_ERR("EBP", "Could not update mappings and sizes");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Serial.printf("[%lu] [EBP] buildBookBin completed in %lu ms\n", millis(), millis() - buildStart);
|
LOG_DBG("EBP", "buildBookBin completed in %lu ms", millis() - buildStart);
|
||||||
Serial.printf("[%lu] [EBP] Total indexing completed in %lu ms\n", millis(), millis() - indexingStart);
|
LOG_DBG("EBP", "Total indexing completed in %lu ms", millis() - indexingStart);
|
||||||
|
|
||||||
if (!bookMetadataCache->cleanupTmpFiles()) {
|
if (!bookMetadataCache->cleanupTmpFiles()) {
|
||||||
Serial.printf("[%lu] [EBP] Could not cleanup tmp files - ignoring\n", millis());
|
LOG_DBG("EBP", "Could not cleanup tmp files - ignoring");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload the cache from disk so it's in the correct state
|
// Reload the cache from disk so it's in the correct state
|
||||||
bookMetadataCache.reset(new BookMetadataCache(cachePath));
|
bookMetadataCache.reset(new BookMetadataCache(cachePath));
|
||||||
if (!bookMetadataCache->load()) {
|
if (!bookMetadataCache->load()) {
|
||||||
Serial.printf("[%lu] [EBP] Failed to reload cache after writing\n", millis());
|
LOG_ERR("EBP", "Failed to reload cache after writing");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,22 +393,22 @@ bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) {
|
|||||||
parseCssFiles();
|
parseCssFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [EBP] Loaded ePub: %s\n", millis(), filepath.c_str());
|
LOG_DBG("EBP", "Loaded ePub: %s", filepath.c_str());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Epub::clearCache() const {
|
bool Epub::clearCache() const {
|
||||||
if (!Storage.exists(cachePath.c_str())) {
|
if (!Storage.exists(cachePath.c_str())) {
|
||||||
Serial.printf("[%lu] [EPB] Cache does not exist, no action needed\n", millis());
|
LOG_DBG("EPB", "Cache does not exist, no action needed");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Storage.removeDir(cachePath.c_str())) {
|
if (!Storage.removeDir(cachePath.c_str())) {
|
||||||
Serial.printf("[%lu] [EPB] Failed to clear cache\n", millis());
|
LOG_ERR("EPB", "Failed to clear cache");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [EPB] Cache cleared successfully\n", millis());
|
LOG_DBG("EPB", "Cache cleared successfully");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,19 +463,19 @@ bool Epub::generateCoverBmp(bool cropped) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
||||||
Serial.printf("[%lu] [EBP] Cannot generate cover BMP, cache not loaded\n", millis());
|
LOG_ERR("EBP", "Cannot generate cover BMP, cache not loaded");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto coverImageHref = bookMetadataCache->coreMetadata.coverItemHref;
|
const auto coverImageHref = bookMetadataCache->coreMetadata.coverItemHref;
|
||||||
if (coverImageHref.empty()) {
|
if (coverImageHref.empty()) {
|
||||||
Serial.printf("[%lu] [EBP] No known cover image\n", millis());
|
LOG_ERR("EBP", "No known cover image");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (coverImageHref.substr(coverImageHref.length() - 4) == ".jpg" ||
|
if (coverImageHref.substr(coverImageHref.length() - 4) == ".jpg" ||
|
||||||
coverImageHref.substr(coverImageHref.length() - 5) == ".jpeg") {
|
coverImageHref.substr(coverImageHref.length() - 5) == ".jpeg") {
|
||||||
Serial.printf("[%lu] [EBP] Generating BMP from JPG cover image (%s mode)\n", millis(), cropped ? "cropped" : "fit");
|
LOG_DBG("EBP", "Generating BMP from JPG cover image (%s mode)", cropped ? "cropped" : "fit");
|
||||||
const auto coverJpgTempPath = getCachePath() + "/.cover.jpg";
|
const auto coverJpgTempPath = getCachePath() + "/.cover.jpg";
|
||||||
|
|
||||||
FsFile coverJpg;
|
FsFile coverJpg;
|
||||||
@@ -501,13 +500,13 @@ bool Epub::generateCoverBmp(bool cropped) const {
|
|||||||
Storage.remove(coverJpgTempPath.c_str());
|
Storage.remove(coverJpgTempPath.c_str());
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
Serial.printf("[%lu] [EBP] Failed to generate BMP from JPG cover image\n", millis());
|
LOG_ERR("EBP", "Failed to generate BMP from cover image");
|
||||||
Storage.remove(getCoverBmpPath(cropped).c_str());
|
Storage.remove(getCoverBmpPath(cropped).c_str());
|
||||||
}
|
}
|
||||||
Serial.printf("[%lu] [EBP] Generated BMP from JPG cover image, success: %s\n", millis(), success ? "yes" : "no");
|
LOG_DBG("EBP", "Generated BMP from cover image, success: %s", success ? "yes" : "no");
|
||||||
return success;
|
return success;
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("[%lu] [EBP] Cover image is not a JPG, skipping\n", millis());
|
LOG_ERR("EBP", "Cover image is not a supported format, skipping");
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -523,16 +522,16 @@ bool Epub::generateThumbBmp(int height) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
||||||
Serial.printf("[%lu] [EBP] Cannot generate thumb BMP, cache not loaded\n", millis());
|
LOG_ERR("EBP", "Cannot generate thumb BMP, cache not loaded");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto coverImageHref = bookMetadataCache->coreMetadata.coverItemHref;
|
const auto coverImageHref = bookMetadataCache->coreMetadata.coverItemHref;
|
||||||
if (coverImageHref.empty()) {
|
if (coverImageHref.empty()) {
|
||||||
Serial.printf("[%lu] [EBP] No known cover image for thumbnail\n", millis());
|
LOG_DBG("EBP", "No known cover image for thumbnail");
|
||||||
} else if (coverImageHref.substr(coverImageHref.length() - 4) == ".jpg" ||
|
} else if (coverImageHref.substr(coverImageHref.length() - 4) == ".jpg" ||
|
||||||
coverImageHref.substr(coverImageHref.length() - 5) == ".jpeg") {
|
coverImageHref.substr(coverImageHref.length() - 5) == ".jpeg") {
|
||||||
Serial.printf("[%lu] [EBP] Generating thumb BMP from JPG cover image\n", millis());
|
LOG_DBG("EBP", "Generating thumb BMP from JPG cover image");
|
||||||
const auto coverJpgTempPath = getCachePath() + "/.cover.jpg";
|
const auto coverJpgTempPath = getCachePath() + "/.cover.jpg";
|
||||||
|
|
||||||
FsFile coverJpg;
|
FsFile coverJpg;
|
||||||
@@ -562,14 +561,13 @@ bool Epub::generateThumbBmp(int height) const {
|
|||||||
Storage.remove(coverJpgTempPath.c_str());
|
Storage.remove(coverJpgTempPath.c_str());
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
Serial.printf("[%lu] [EBP] Failed to generate thumb BMP from JPG cover image\n", millis());
|
LOG_ERR("EBP", "Failed to generate thumb BMP from JPG cover image");
|
||||||
Storage.remove(getThumbBmpPath(height).c_str());
|
Storage.remove(getThumbBmpPath(height).c_str());
|
||||||
}
|
}
|
||||||
Serial.printf("[%lu] [EBP] Generated thumb BMP from JPG cover image, success: %s\n", millis(),
|
LOG_DBG("EBP", "Generated thumb BMP from JPG cover image, success: %s", success ? "yes" : "no");
|
||||||
success ? "yes" : "no");
|
|
||||||
return success;
|
return success;
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("[%lu] [EBP] Cover image is not a JPG, skipping thumbnail\n", millis());
|
LOG_ERR("EBP", "Cover image is not a supported format, skipping thumbnail");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write an empty bmp file to avoid generation attempts in the future
|
// Write an empty bmp file to avoid generation attempts in the future
|
||||||
@@ -581,7 +579,7 @@ bool Epub::generateThumbBmp(int height) const {
|
|||||||
|
|
||||||
uint8_t* Epub::readItemContentsToBytes(const std::string& itemHref, size_t* size, const bool trailingNullByte) const {
|
uint8_t* Epub::readItemContentsToBytes(const std::string& itemHref, size_t* size, const bool trailingNullByte) const {
|
||||||
if (itemHref.empty()) {
|
if (itemHref.empty()) {
|
||||||
Serial.printf("[%lu] [EBP] Failed to read item, empty href\n", millis());
|
LOG_DBG("EBP", "Failed to read item, empty href");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -589,7 +587,7 @@ uint8_t* Epub::readItemContentsToBytes(const std::string& itemHref, size_t* size
|
|||||||
|
|
||||||
const auto content = ZipFile(filepath).readFileToMemory(path.c_str(), size, trailingNullByte);
|
const auto content = ZipFile(filepath).readFileToMemory(path.c_str(), size, trailingNullByte);
|
||||||
if (!content) {
|
if (!content) {
|
||||||
Serial.printf("[%lu] [EBP] Failed to read item %s\n", millis(), path.c_str());
|
LOG_DBG("EBP", "Failed to read item %s", path.c_str());
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -598,7 +596,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 {
|
||||||
if (itemHref.empty()) {
|
if (itemHref.empty()) {
|
||||||
Serial.printf("[%lu] [EBP] Failed to read item, empty href\n", millis());
|
LOG_DBG("EBP", "Failed to read item, empty href");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -622,12 +620,12 @@ size_t Epub::getCumulativeSpineItemSize(const int spineIndex) const { return get
|
|||||||
|
|
||||||
BookMetadataCache::SpineEntry Epub::getSpineItem(const int spineIndex) const {
|
BookMetadataCache::SpineEntry Epub::getSpineItem(const int spineIndex) const {
|
||||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
||||||
Serial.printf("[%lu] [EBP] getSpineItem called but cache not loaded\n", millis());
|
LOG_ERR("EBP", "getSpineItem called but cache not loaded");
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spineIndex < 0 || spineIndex >= bookMetadataCache->getSpineCount()) {
|
if (spineIndex < 0 || spineIndex >= bookMetadataCache->getSpineCount()) {
|
||||||
Serial.printf("[%lu] [EBP] getSpineItem index:%d is out of range\n", millis(), spineIndex);
|
LOG_ERR("EBP", "getSpineItem index:%d is out of range", spineIndex);
|
||||||
return bookMetadataCache->getSpineEntry(0);
|
return bookMetadataCache->getSpineEntry(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -636,12 +634,12 @@ BookMetadataCache::SpineEntry Epub::getSpineItem(const int spineIndex) const {
|
|||||||
|
|
||||||
BookMetadataCache::TocEntry Epub::getTocItem(const int tocIndex) const {
|
BookMetadataCache::TocEntry Epub::getTocItem(const int tocIndex) const {
|
||||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
||||||
Serial.printf("[%lu] [EBP] getTocItem called but cache not loaded\n", millis());
|
LOG_DBG("EBP", "getTocItem called but cache not loaded");
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tocIndex < 0 || tocIndex >= bookMetadataCache->getTocCount()) {
|
if (tocIndex < 0 || tocIndex >= bookMetadataCache->getTocCount()) {
|
||||||
Serial.printf("[%lu] [EBP] getTocItem index:%d is out of range\n", millis(), tocIndex);
|
LOG_DBG("EBP", "getTocItem index:%d is out of range", tocIndex);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -659,18 +657,18 @@ int Epub::getTocItemsCount() const {
|
|||||||
// work out the section index for a toc index
|
// work out the section index for a toc index
|
||||||
int Epub::getSpineIndexForTocIndex(const int tocIndex) const {
|
int Epub::getSpineIndexForTocIndex(const int tocIndex) const {
|
||||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
||||||
Serial.printf("[%lu] [EBP] getSpineIndexForTocIndex called but cache not loaded\n", millis());
|
LOG_ERR("EBP", "getSpineIndexForTocIndex called but cache not loaded");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tocIndex < 0 || tocIndex >= bookMetadataCache->getTocCount()) {
|
if (tocIndex < 0 || tocIndex >= bookMetadataCache->getTocCount()) {
|
||||||
Serial.printf("[%lu] [EBP] getSpineIndexForTocIndex: tocIndex %d out of range\n", millis(), tocIndex);
|
LOG_ERR("EBP", "getSpineIndexForTocIndex: tocIndex %d out of range", tocIndex);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int spineIndex = bookMetadataCache->getTocEntry(tocIndex).spineIndex;
|
const int spineIndex = bookMetadataCache->getTocEntry(tocIndex).spineIndex;
|
||||||
if (spineIndex < 0) {
|
if (spineIndex < 0) {
|
||||||
Serial.printf("[%lu] [EBP] Section not found for TOC index %d\n", millis(), tocIndex);
|
LOG_DBG("EBP", "Section not found for TOC index %d", tocIndex);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -688,14 +686,13 @@ size_t Epub::getBookSize() const {
|
|||||||
|
|
||||||
int Epub::getSpineIndexForTextReference() const {
|
int Epub::getSpineIndexForTextReference() const {
|
||||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
||||||
Serial.printf("[%lu] [EBP] getSpineIndexForTextReference called but cache not loaded\n", millis());
|
LOG_ERR("EBP", "getSpineIndexForTextReference called but cache not loaded");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
Serial.printf("[%lu] [ERS] Core Metadata: cover(%d)=%s, textReference(%d)=%s\n", millis(),
|
LOG_DBG("EBP", "Core Metadata: cover(%d)=%s, textReference(%d)=%s",
|
||||||
bookMetadataCache->coreMetadata.coverItemHref.size(),
|
bookMetadataCache->coreMetadata.coverItemHref.size(), bookMetadataCache->coreMetadata.coverItemHref.c_str(),
|
||||||
bookMetadataCache->coreMetadata.coverItemHref.c_str(),
|
bookMetadataCache->coreMetadata.textReferenceHref.size(),
|
||||||
bookMetadataCache->coreMetadata.textReferenceHref.size(),
|
bookMetadataCache->coreMetadata.textReferenceHref.c_str());
|
||||||
bookMetadataCache->coreMetadata.textReferenceHref.c_str());
|
|
||||||
|
|
||||||
if (bookMetadataCache->coreMetadata.textReferenceHref.empty()) {
|
if (bookMetadataCache->coreMetadata.textReferenceHref.empty()) {
|
||||||
// there was no textReference in epub, so we return 0 (the first chapter)
|
// there was no textReference in epub, so we return 0 (the first chapter)
|
||||||
@@ -705,13 +702,13 @@ int Epub::getSpineIndexForTextReference() const {
|
|||||||
// loop through spine items to get the correct index matching the text href
|
// loop through spine items to get the correct index matching the text href
|
||||||
for (size_t i = 0; i < getSpineItemsCount(); i++) {
|
for (size_t i = 0; i < getSpineItemsCount(); i++) {
|
||||||
if (getSpineItem(i).href == bookMetadataCache->coreMetadata.textReferenceHref) {
|
if (getSpineItem(i).href == bookMetadataCache->coreMetadata.textReferenceHref) {
|
||||||
Serial.printf("[%lu] [ERS] Text reference %s found at index %d\n", millis(),
|
LOG_DBG("EBP", "Text reference %s found at index %d", bookMetadataCache->coreMetadata.textReferenceHref.c_str(),
|
||||||
bookMetadataCache->coreMetadata.textReferenceHref.c_str(), i);
|
i);
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// This should not happen, as we checked for empty textReferenceHref earlier
|
// This should not happen, as we checked for empty textReferenceHref earlier
|
||||||
Serial.printf("[%lu] [EBP] Section not found for text reference\n", millis());
|
LOG_DBG("EBP", "Section not found for text reference");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "BookMetadataCache.h"
|
#include "BookMetadataCache.h"
|
||||||
|
|
||||||
#include <HardwareSerial.h>
|
#include <Logging.h>
|
||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
#include <ZipFile.h>
|
#include <ZipFile.h>
|
||||||
|
|
||||||
@@ -21,12 +21,12 @@ bool BookMetadataCache::beginWrite() {
|
|||||||
buildMode = true;
|
buildMode = true;
|
||||||
spineCount = 0;
|
spineCount = 0;
|
||||||
tocCount = 0;
|
tocCount = 0;
|
||||||
Serial.printf("[%lu] [BMC] Entering write mode\n", millis());
|
LOG_DBG("BMC", "Entering write mode");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BookMetadataCache::beginContentOpfPass() {
|
bool BookMetadataCache::beginContentOpfPass() {
|
||||||
Serial.printf("[%lu] [BMC] Beginning content opf pass\n", millis());
|
LOG_DBG("BMC", "Beginning content opf pass");
|
||||||
|
|
||||||
// Open spine file for writing
|
// Open spine file for writing
|
||||||
return Storage.openFileForWrite("BMC", cachePath + tmpSpineBinFile, spineFile);
|
return Storage.openFileForWrite("BMC", cachePath + tmpSpineBinFile, spineFile);
|
||||||
@@ -38,7 +38,7 @@ bool BookMetadataCache::endContentOpfPass() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool BookMetadataCache::beginTocPass() {
|
bool BookMetadataCache::beginTocPass() {
|
||||||
Serial.printf("[%lu] [BMC] Beginning toc pass\n", millis());
|
LOG_DBG("BMC", "Beginning toc pass");
|
||||||
|
|
||||||
if (!Storage.openFileForRead("BMC", cachePath + tmpSpineBinFile, spineFile)) {
|
if (!Storage.openFileForRead("BMC", cachePath + tmpSpineBinFile, spineFile)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -66,7 +66,7 @@ bool BookMetadataCache::beginTocPass() {
|
|||||||
});
|
});
|
||||||
spineFile.seek(0);
|
spineFile.seek(0);
|
||||||
useSpineHrefIndex = true;
|
useSpineHrefIndex = true;
|
||||||
Serial.printf("[%lu] [BMC] Using fast index for %d spine items\n", millis(), spineCount);
|
LOG_DBG("BMC", "Using fast index for %d spine items", spineCount);
|
||||||
} else {
|
} else {
|
||||||
useSpineHrefIndex = false;
|
useSpineHrefIndex = false;
|
||||||
}
|
}
|
||||||
@@ -87,12 +87,12 @@ bool BookMetadataCache::endTocPass() {
|
|||||||
|
|
||||||
bool BookMetadataCache::endWrite() {
|
bool BookMetadataCache::endWrite() {
|
||||||
if (!buildMode) {
|
if (!buildMode) {
|
||||||
Serial.printf("[%lu] [BMC] endWrite called but not in build mode\n", millis());
|
LOG_DBG("BMC", "endWrite called but not in build mode");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
buildMode = false;
|
buildMode = false;
|
||||||
Serial.printf("[%lu] [BMC] Wrote %d spine, %d TOC entries\n", millis(), spineCount, tocCount);
|
LOG_DBG("BMC", "Wrote %d spine, %d TOC entries", spineCount, tocCount);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta
|
|||||||
ZipFile zip(epubPath);
|
ZipFile zip(epubPath);
|
||||||
// Pre-open zip file to speed up size calculations
|
// Pre-open zip file to speed up size calculations
|
||||||
if (!zip.open()) {
|
if (!zip.open()) {
|
||||||
Serial.printf("[%lu] [BMC] Could not open EPUB zip for size calculations\n", millis());
|
LOG_ERR("BMC", "Could not open EPUB zip for size calculations");
|
||||||
bookFile.close();
|
bookFile.close();
|
||||||
spineFile.close();
|
spineFile.close();
|
||||||
tocFile.close();
|
tocFile.close();
|
||||||
@@ -185,7 +185,7 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta
|
|||||||
bool useBatchSizes = false;
|
bool useBatchSizes = false;
|
||||||
|
|
||||||
if (spineCount >= LARGE_SPINE_THRESHOLD) {
|
if (spineCount >= LARGE_SPINE_THRESHOLD) {
|
||||||
Serial.printf("[%lu] [BMC] Using batch size lookup for %d spine items\n", millis(), spineCount);
|
LOG_DBG("BMC", "Using batch size lookup for %d spine items", spineCount);
|
||||||
|
|
||||||
std::vector<ZipFile::SizeTarget> targets;
|
std::vector<ZipFile::SizeTarget> targets;
|
||||||
targets.reserve(spineCount);
|
targets.reserve(spineCount);
|
||||||
@@ -208,7 +208,7 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta
|
|||||||
|
|
||||||
spineSizes.resize(spineCount, 0);
|
spineSizes.resize(spineCount, 0);
|
||||||
int matched = zip.fillUncompressedSizes(targets, spineSizes);
|
int matched = zip.fillUncompressedSizes(targets, spineSizes);
|
||||||
Serial.printf("[%lu] [BMC] Batch lookup matched %d/%d spine items\n", millis(), matched, spineCount);
|
LOG_DBG("BMC", "Batch lookup matched %d/%d spine items", matched, spineCount);
|
||||||
|
|
||||||
targets.clear();
|
targets.clear();
|
||||||
targets.shrink_to_fit();
|
targets.shrink_to_fit();
|
||||||
@@ -227,9 +227,8 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta
|
|||||||
// Not a huge deal if we don't fine a TOC entry for the spine entry, this is expected behaviour for EPUBs
|
// Not a huge deal if we don't fine a TOC entry for the spine entry, this is expected behaviour for EPUBs
|
||||||
// Logging here is for debugging
|
// Logging here is for debugging
|
||||||
if (spineEntry.tocIndex == -1) {
|
if (spineEntry.tocIndex == -1) {
|
||||||
Serial.printf(
|
LOG_DBG("BMC", "Warning: Could not find TOC entry for spine item %d: %s, using title from last section", i,
|
||||||
"[%lu] [BMC] Warning: Could not find TOC entry for spine item %d: %s, using title from last section\n",
|
spineEntry.href.c_str());
|
||||||
millis(), i, spineEntry.href.c_str());
|
|
||||||
spineEntry.tocIndex = lastSpineTocIndex;
|
spineEntry.tocIndex = lastSpineTocIndex;
|
||||||
}
|
}
|
||||||
lastSpineTocIndex = spineEntry.tocIndex;
|
lastSpineTocIndex = spineEntry.tocIndex;
|
||||||
@@ -240,13 +239,13 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta
|
|||||||
if (itemSize == 0) {
|
if (itemSize == 0) {
|
||||||
const std::string path = FsHelpers::normalisePath(spineEntry.href);
|
const std::string path = FsHelpers::normalisePath(spineEntry.href);
|
||||||
if (!zip.getInflatedFileSize(path.c_str(), &itemSize)) {
|
if (!zip.getInflatedFileSize(path.c_str(), &itemSize)) {
|
||||||
Serial.printf("[%lu] [BMC] Warning: Could not get size for spine item: %s\n", millis(), path.c_str());
|
LOG_ERR("BMC", "Warning: Could not get size for spine item: %s", path.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const std::string path = FsHelpers::normalisePath(spineEntry.href);
|
const std::string path = FsHelpers::normalisePath(spineEntry.href);
|
||||||
if (!zip.getInflatedFileSize(path.c_str(), &itemSize)) {
|
if (!zip.getInflatedFileSize(path.c_str(), &itemSize)) {
|
||||||
Serial.printf("[%lu] [BMC] Warning: Could not get size for spine item: %s\n", millis(), path.c_str());
|
LOG_ERR("BMC", "Warning: Could not get size for spine item: %s", path.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,7 +269,7 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta
|
|||||||
spineFile.close();
|
spineFile.close();
|
||||||
tocFile.close();
|
tocFile.close();
|
||||||
|
|
||||||
Serial.printf("[%lu] [BMC] Successfully built book.bin\n", millis());
|
LOG_DBG("BMC", "Successfully built book.bin");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,7 +305,7 @@ uint32_t BookMetadataCache::writeTocEntry(FsFile& file, const TocEntry& entry) c
|
|||||||
// this is because in this function we're marking positions of the items
|
// this is because in this function we're marking positions of the items
|
||||||
void BookMetadataCache::createSpineEntry(const std::string& href) {
|
void BookMetadataCache::createSpineEntry(const std::string& href) {
|
||||||
if (!buildMode || !spineFile) {
|
if (!buildMode || !spineFile) {
|
||||||
Serial.printf("[%lu] [BMC] createSpineEntry called but not in build mode\n", millis());
|
LOG_DBG("BMC", "createSpineEntry called but not in build mode");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,7 +317,7 @@ void BookMetadataCache::createSpineEntry(const std::string& href) {
|
|||||||
void BookMetadataCache::createTocEntry(const std::string& title, const std::string& href, const std::string& anchor,
|
void BookMetadataCache::createTocEntry(const std::string& title, const std::string& href, const std::string& anchor,
|
||||||
const uint8_t level) {
|
const uint8_t level) {
|
||||||
if (!buildMode || !tocFile || !spineFile) {
|
if (!buildMode || !tocFile || !spineFile) {
|
||||||
Serial.printf("[%lu] [BMC] createTocEntry called but not in build mode\n", millis());
|
LOG_DBG("BMC", "createTocEntry called but not in build mode");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,7 +339,7 @@ void BookMetadataCache::createTocEntry(const std::string& title, const std::stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (spineIndex == -1) {
|
if (spineIndex == -1) {
|
||||||
Serial.printf("[%lu] [BMC] createTocEntry: Could not find spine item for TOC href %s\n", millis(), href.c_str());
|
LOG_DBG("BMC", "createTocEntry: Could not find spine item for TOC href %s", href.c_str());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
spineFile.seek(0);
|
spineFile.seek(0);
|
||||||
@@ -352,7 +351,7 @@ void BookMetadataCache::createTocEntry(const std::string& title, const std::stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (spineIndex == -1) {
|
if (spineIndex == -1) {
|
||||||
Serial.printf("[%lu] [BMC] createTocEntry: Could not find spine item for TOC href %s\n", millis(), href.c_str());
|
LOG_DBG("BMC", "createTocEntry: Could not find spine item for TOC href %s", href.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,7 +370,7 @@ bool BookMetadataCache::load() {
|
|||||||
uint8_t version;
|
uint8_t version;
|
||||||
serialization::readPod(bookFile, version);
|
serialization::readPod(bookFile, version);
|
||||||
if (version != BOOK_CACHE_VERSION) {
|
if (version != BOOK_CACHE_VERSION) {
|
||||||
Serial.printf("[%lu] [BMC] Cache version mismatch: expected %d, got %d\n", millis(), BOOK_CACHE_VERSION, version);
|
LOG_DBG("BMC", "Cache version mismatch: expected %d, got %d", BOOK_CACHE_VERSION, version);
|
||||||
bookFile.close();
|
bookFile.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -387,18 +386,18 @@ bool BookMetadataCache::load() {
|
|||||||
serialization::readString(bookFile, coreMetadata.textReferenceHref);
|
serialization::readString(bookFile, coreMetadata.textReferenceHref);
|
||||||
|
|
||||||
loaded = true;
|
loaded = true;
|
||||||
Serial.printf("[%lu] [BMC] Loaded cache data: %d spine, %d TOC entries\n", millis(), spineCount, tocCount);
|
LOG_DBG("BMC", "Loaded cache data: %d spine, %d TOC entries", spineCount, tocCount);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
BookMetadataCache::SpineEntry BookMetadataCache::getSpineEntry(const int index) {
|
BookMetadataCache::SpineEntry BookMetadataCache::getSpineEntry(const int index) {
|
||||||
if (!loaded) {
|
if (!loaded) {
|
||||||
Serial.printf("[%lu] [BMC] getSpineEntry called but cache not loaded\n", millis());
|
LOG_ERR("BMC", "getSpineEntry called but cache not loaded");
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index < 0 || index >= static_cast<int>(spineCount)) {
|
if (index < 0 || index >= static_cast<int>(spineCount)) {
|
||||||
Serial.printf("[%lu] [BMC] getSpineEntry index %d out of range\n", millis(), index);
|
LOG_ERR("BMC", "getSpineEntry index %d out of range", index);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,12 +411,12 @@ BookMetadataCache::SpineEntry BookMetadataCache::getSpineEntry(const int index)
|
|||||||
|
|
||||||
BookMetadataCache::TocEntry BookMetadataCache::getTocEntry(const int index) {
|
BookMetadataCache::TocEntry BookMetadataCache::getTocEntry(const int index) {
|
||||||
if (!loaded) {
|
if (!loaded) {
|
||||||
Serial.printf("[%lu] [BMC] getTocEntry called but cache not loaded\n", millis());
|
LOG_ERR("BMC", "getTocEntry called but cache not loaded");
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index < 0 || index >= static_cast<int>(tocCount)) {
|
if (index < 0 || index >= static_cast<int>(tocCount)) {
|
||||||
Serial.printf("[%lu] [BMC] getTocEntry index %d out of range\n", millis(), index);
|
LOG_ERR("BMC", "getTocEntry index %d out of range", index);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "Page.h"
|
#include "Page.h"
|
||||||
|
|
||||||
#include <HardwareSerial.h>
|
#include <Logging.h>
|
||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
|
|
||||||
void PageLine::render(GfxRenderer& renderer, const int fontId, const int xOffset, const int yOffset) {
|
void PageLine::render(GfxRenderer& renderer, const int fontId, const int xOffset, const int yOffset) {
|
||||||
@@ -60,7 +60,7 @@ std::unique_ptr<Page> Page::deserialize(FsFile& file) {
|
|||||||
auto pl = PageLine::deserialize(file);
|
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);
|
LOG_ERR("PGE", "Deserialization failed: Unknown tag %u", tag);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "Section.h"
|
#include "Section.h"
|
||||||
|
|
||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
|
#include <Logging.h>
|
||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
|
|
||||||
#include "Page.h"
|
#include "Page.h"
|
||||||
@@ -16,16 +17,16 @@ constexpr uint32_t HEADER_SIZE = sizeof(uint8_t) + sizeof(int) + sizeof(float) +
|
|||||||
|
|
||||||
uint32_t Section::onPageComplete(std::unique_ptr<Page> page) {
|
uint32_t Section::onPageComplete(std::unique_ptr<Page> page) {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
Serial.printf("[%lu] [SCT] File not open for writing page %d\n", millis(), pageCount);
|
LOG_ERR("SCT", "File not open for writing page %d", pageCount);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint32_t position = file.position();
|
const uint32_t position = file.position();
|
||||||
if (!page->serialize(file)) {
|
if (!page->serialize(file)) {
|
||||||
Serial.printf("[%lu] [SCT] Failed to serialize page %d\n", millis(), pageCount);
|
LOG_ERR("SCT", "Failed to serialize page %d", pageCount);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
Serial.printf("[%lu] [SCT] Page %d processed\n", millis(), pageCount);
|
LOG_DBG("SCT", "Page %d processed", pageCount);
|
||||||
|
|
||||||
pageCount++;
|
pageCount++;
|
||||||
return position;
|
return position;
|
||||||
@@ -36,7 +37,7 @@ void Section::writeSectionFileHeader(const int fontId, const float lineCompressi
|
|||||||
const uint16_t viewportHeight, const bool hyphenationEnabled,
|
const uint16_t viewportHeight, const bool hyphenationEnabled,
|
||||||
const bool embeddedStyle) {
|
const bool embeddedStyle) {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
Serial.printf("[%lu] [SCT] File not open for writing header\n", millis());
|
LOG_DBG("SCT", "File not open for writing header");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
static_assert(HEADER_SIZE == sizeof(SECTION_FILE_VERSION) + sizeof(fontId) + sizeof(lineCompression) +
|
static_assert(HEADER_SIZE == sizeof(SECTION_FILE_VERSION) + sizeof(fontId) + sizeof(lineCompression) +
|
||||||
@@ -70,7 +71,7 @@ bool Section::loadSectionFile(const int fontId, const float lineCompression, con
|
|||||||
serialization::readPod(file, version);
|
serialization::readPod(file, version);
|
||||||
if (version != SECTION_FILE_VERSION) {
|
if (version != SECTION_FILE_VERSION) {
|
||||||
file.close();
|
file.close();
|
||||||
Serial.printf("[%lu] [SCT] Deserialization failed: Unknown version %u\n", millis(), version);
|
LOG_ERR("SCT", "Deserialization failed: Unknown version %u", version);
|
||||||
clearCache();
|
clearCache();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -96,7 +97,7 @@ bool Section::loadSectionFile(const int fontId, const float lineCompression, con
|
|||||||
viewportWidth != fileViewportWidth || viewportHeight != fileViewportHeight ||
|
viewportWidth != fileViewportWidth || viewportHeight != fileViewportHeight ||
|
||||||
hyphenationEnabled != fileHyphenationEnabled || embeddedStyle != fileEmbeddedStyle) {
|
hyphenationEnabled != fileHyphenationEnabled || embeddedStyle != fileEmbeddedStyle) {
|
||||||
file.close();
|
file.close();
|
||||||
Serial.printf("[%lu] [SCT] Deserialization failed: Parameters do not match\n", millis());
|
LOG_ERR("SCT", "Deserialization failed: Parameters do not match");
|
||||||
clearCache();
|
clearCache();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -104,23 +105,23 @@ bool Section::loadSectionFile(const int fontId, const float lineCompression, con
|
|||||||
|
|
||||||
serialization::readPod(file, pageCount);
|
serialization::readPod(file, pageCount);
|
||||||
file.close();
|
file.close();
|
||||||
Serial.printf("[%lu] [SCT] Deserialization succeeded: %d pages\n", millis(), pageCount);
|
LOG_DBG("SCT", "Deserialization succeeded: %d pages", pageCount);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Your updated class method (assuming you are using the 'SD' object, which is a wrapper for a specific filesystem)
|
// Your updated class method (assuming you are using the 'SD' object, which is a wrapper for a specific filesystem)
|
||||||
bool Section::clearCache() const {
|
bool Section::clearCache() const {
|
||||||
if (!Storage.exists(filePath.c_str())) {
|
if (!Storage.exists(filePath.c_str())) {
|
||||||
Serial.printf("[%lu] [SCT] Cache does not exist, no action needed\n", millis());
|
LOG_DBG("SCT", "Cache does not exist, no action needed");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Storage.remove(filePath.c_str())) {
|
if (!Storage.remove(filePath.c_str())) {
|
||||||
Serial.printf("[%lu] [SCT] Failed to clear cache\n", millis());
|
LOG_ERR("SCT", "Failed to clear cache");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [SCT] Cache cleared successfully\n", millis());
|
LOG_DBG("SCT", "Cache cleared successfully");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +143,7 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c
|
|||||||
uint32_t fileSize = 0;
|
uint32_t fileSize = 0;
|
||||||
for (int attempt = 0; attempt < 3 && !success; attempt++) {
|
for (int attempt = 0; attempt < 3 && !success; attempt++) {
|
||||||
if (attempt > 0) {
|
if (attempt > 0) {
|
||||||
Serial.printf("[%lu] [SCT] Retrying stream (attempt %d)...\n", millis(), attempt + 1);
|
LOG_DBG("SCT", "Retrying stream (attempt %d)...", attempt + 1);
|
||||||
delay(50); // Brief delay before retry
|
delay(50); // Brief delay before retry
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,16 +163,16 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c
|
|||||||
// If streaming failed, remove the incomplete file immediately
|
// If streaming failed, remove the incomplete file immediately
|
||||||
if (!success && Storage.exists(tmpHtmlPath.c_str())) {
|
if (!success && Storage.exists(tmpHtmlPath.c_str())) {
|
||||||
Storage.remove(tmpHtmlPath.c_str());
|
Storage.remove(tmpHtmlPath.c_str());
|
||||||
Serial.printf("[%lu] [SCT] Removed incomplete temp file after failed attempt\n", millis());
|
LOG_DBG("SCT", "Removed incomplete temp file after failed attempt");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
Serial.printf("[%lu] [SCT] Failed to stream item contents to temp file after retries\n", millis());
|
LOG_ERR("SCT", "Failed to stream item contents to temp file after retries");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [SCT] Streamed temp HTML to %s (%d bytes)\n", millis(), tmpHtmlPath.c_str(), fileSize);
|
LOG_DBG("SCT", "Streamed temp HTML to %s (%d bytes)", tmpHtmlPath.c_str(), fileSize);
|
||||||
|
|
||||||
if (!Storage.openFileForWrite("SCT", filePath, file)) {
|
if (!Storage.openFileForWrite("SCT", filePath, file)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -190,7 +191,7 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c
|
|||||||
|
|
||||||
Storage.remove(tmpHtmlPath.c_str());
|
Storage.remove(tmpHtmlPath.c_str());
|
||||||
if (!success) {
|
if (!success) {
|
||||||
Serial.printf("[%lu] [SCT] Failed to parse XML and build pages\n", millis());
|
LOG_ERR("SCT", "Failed to parse XML and build pages");
|
||||||
file.close();
|
file.close();
|
||||||
Storage.remove(filePath.c_str());
|
Storage.remove(filePath.c_str());
|
||||||
return false;
|
return false;
|
||||||
@@ -208,7 +209,7 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hasFailedLutRecords) {
|
if (hasFailedLutRecords) {
|
||||||
Serial.printf("[%lu] [SCT] Failed to write LUT due to invalid page positions\n", millis());
|
LOG_ERR("SCT", "Failed to write LUT due to invalid page positions");
|
||||||
file.close();
|
file.close();
|
||||||
Storage.remove(filePath.c_str());
|
Storage.remove(filePath.c_str());
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
#include "TextBlock.h"
|
#include "TextBlock.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
#include <Logging.h>
|
||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
|
|
||||||
void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int x, const int y) const {
|
void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int x, const int y) const {
|
||||||
// Validate iterator bounds before rendering
|
// Validate iterator bounds before rendering
|
||||||
if (words.size() != wordXpos.size() || words.size() != wordStyles.size()) {
|
if (words.size() != wordXpos.size() || words.size() != wordStyles.size()) {
|
||||||
Serial.printf("[%lu] [TXB] Render skipped: size mismatch (words=%u, xpos=%u, styles=%u)\n", millis(),
|
LOG_ERR("TXB", "Render skipped: size mismatch (words=%u, xpos=%u, styles=%u)\n", (uint32_t)words.size(),
|
||||||
(uint32_t)words.size(), (uint32_t)wordXpos.size(), (uint32_t)wordStyles.size());
|
(uint32_t)wordXpos.size(), (uint32_t)wordStyles.size());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,8 +50,8 @@ void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int
|
|||||||
|
|
||||||
bool TextBlock::serialize(FsFile& file) const {
|
bool TextBlock::serialize(FsFile& file) const {
|
||||||
if (words.size() != wordXpos.size() || words.size() != wordStyles.size()) {
|
if (words.size() != wordXpos.size() || words.size() != wordStyles.size()) {
|
||||||
Serial.printf("[%lu] [TXB] Serialization failed: size mismatch (words=%u, xpos=%u, styles=%u)\n", millis(),
|
LOG_ERR("TXB", "Serialization failed: size mismatch (words=%u, xpos=%u, styles=%u)\n", words.size(),
|
||||||
words.size(), wordXpos.size(), wordStyles.size());
|
wordXpos.size(), wordStyles.size());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +90,7 @@ std::unique_ptr<TextBlock> TextBlock::deserialize(FsFile& file) {
|
|||||||
|
|
||||||
// Sanity check: prevent allocation of unreasonably large lists (max 10000 words per block)
|
// Sanity check: prevent allocation of unreasonably large lists (max 10000 words per block)
|
||||||
if (wc > 10000) {
|
if (wc > 10000) {
|
||||||
Serial.printf("[%lu] [TXB] Deserialization failed: word count %u exceeds maximum\n", millis(), wc);
|
LOG_ERR("TXB", "Deserialization failed: word count %u exceeds maximum", wc);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "CssParser.h"
|
#include "CssParser.h"
|
||||||
|
|
||||||
#include <HardwareSerial.h>
|
#include <Logging.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
@@ -449,7 +449,7 @@ void CssParser::processRuleBlock(const std::string& selectorGroup, const std::st
|
|||||||
|
|
||||||
bool CssParser::loadFromStream(FsFile& source) {
|
bool CssParser::loadFromStream(FsFile& source) {
|
||||||
if (!source) {
|
if (!source) {
|
||||||
Serial.printf("[%lu] [CSS] Cannot read from invalid file\n", millis());
|
LOG_ERR("CSS", "Cannot read from invalid file");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -470,7 +470,7 @@ bool CssParser::loadFromStream(FsFile& source) {
|
|||||||
processRuleBlock(selector, body);
|
processRuleBlock(selector, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [CSS] Parsed %zu rules\n", millis(), rulesBySelector_.size());
|
LOG_DBG("CSS", "Parsed %zu rules", rulesBySelector_.size());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -582,7 +582,7 @@ bool CssParser::saveToCache(FsFile& file) const {
|
|||||||
file.write(reinterpret_cast<const uint8_t*>(&definedBits), sizeof(definedBits));
|
file.write(reinterpret_cast<const uint8_t*>(&definedBits), sizeof(definedBits));
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [CSS] Saved %u rules to cache\n", millis(), ruleCount);
|
LOG_DBG("CSS", "Saved %u rules to cache", ruleCount);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -597,7 +597,7 @@ bool CssParser::loadFromCache(FsFile& file) {
|
|||||||
// Read and verify version
|
// Read and verify version
|
||||||
uint8_t version = 0;
|
uint8_t version = 0;
|
||||||
if (file.read(&version, 1) != 1 || version != CSS_CACHE_VERSION) {
|
if (file.read(&version, 1) != 1 || version != CSS_CACHE_VERSION) {
|
||||||
Serial.printf("[%lu] [CSS] Cache version mismatch (got %u, expected %u)\n", millis(), version, CSS_CACHE_VERSION);
|
LOG_DBG("CSS", "Cache version mismatch (got %u, expected %u)", version, CSS_CACHE_VERSION);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -694,6 +694,6 @@ bool CssParser::loadFromCache(FsFile& file) {
|
|||||||
rulesBySelector_[selector] = style;
|
rulesBySelector_[selector] = style;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [CSS] Loaded %u rules from cache\n", millis(), ruleCount);
|
LOG_DBG("CSS", "Loaded %u rules from cache", ruleCount);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
#include <HardwareSerial.h>
|
#include <Logging.h>
|
||||||
#include <expat.h>
|
#include <expat.h>
|
||||||
|
|
||||||
#include "../Page.h"
|
#include "../Page.h"
|
||||||
@@ -168,7 +168,7 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [EHP] Image alt: %s\n", millis(), alt.c_str());
|
LOG_DBG("EHP", "Image alt: %s", alt.c_str());
|
||||||
|
|
||||||
self->startNewTextBlock(centeredBlockStyle);
|
self->startNewTextBlock(centeredBlockStyle);
|
||||||
self->italicUntilDepth = min(self->italicUntilDepth, self->depth);
|
self->italicUntilDepth = min(self->italicUntilDepth, self->depth);
|
||||||
@@ -386,7 +386,7 @@ void XMLCALL ChapterHtmlSlimParser::characterData(void* userData, const XML_Char
|
|||||||
// memory.
|
// memory.
|
||||||
// Spotted when reading Intermezzo, there are some really long text blocks in there.
|
// Spotted when reading Intermezzo, there are some really long text blocks in there.
|
||||||
if (self->currentTextBlock->size() > 750) {
|
if (self->currentTextBlock->size() > 750) {
|
||||||
Serial.printf("[%lu] [EHP] Text block too long, splitting into multiple pages\n", millis());
|
LOG_DBG("EHP", "Text block too long, splitting into multiple pages");
|
||||||
self->currentTextBlock->layoutAndExtractLines(
|
self->currentTextBlock->layoutAndExtractLines(
|
||||||
self->renderer, self->fontId, self->viewportWidth,
|
self->renderer, self->fontId, self->viewportWidth,
|
||||||
[self](const std::shared_ptr<TextBlock>& textBlock) { self->addLineToPage(textBlock); }, false);
|
[self](const std::shared_ptr<TextBlock>& textBlock) { self->addLineToPage(textBlock); }, false);
|
||||||
@@ -477,7 +477,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
|
|||||||
int done;
|
int done;
|
||||||
|
|
||||||
if (!parser) {
|
if (!parser) {
|
||||||
Serial.printf("[%lu] [EHP] Couldn't allocate memory for parser\n", millis());
|
LOG_ERR("EHP", "Couldn't allocate memory for parser");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -499,7 +499,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
|
|||||||
do {
|
do {
|
||||||
void* const buf = XML_GetBuffer(parser, 1024);
|
void* const buf = XML_GetBuffer(parser, 1024);
|
||||||
if (!buf) {
|
if (!buf) {
|
||||||
Serial.printf("[%lu] [EHP] Couldn't allocate memory for buffer\n", millis());
|
LOG_ERR("EHP", "Couldn't allocate memory for buffer");
|
||||||
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);
|
||||||
@@ -511,7 +511,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
|
|||||||
const size_t len = file.read(buf, 1024);
|
const size_t len = file.read(buf, 1024);
|
||||||
|
|
||||||
if (len == 0 && file.available() > 0) {
|
if (len == 0 && file.available() > 0) {
|
||||||
Serial.printf("[%lu] [EHP] File read error\n", millis());
|
LOG_ERR("EHP", "File read error");
|
||||||
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);
|
||||||
@@ -523,8 +523,8 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
|
|||||||
done = file.available() == 0;
|
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),
|
LOG_ERR("EHP", "Parse error at line %lu:\n%s", XML_GetCurrentLineNumber(parser),
|
||||||
XML_ErrorString(XML_GetErrorCode(parser)));
|
XML_ErrorString(XML_GetErrorCode(parser)));
|
||||||
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);
|
||||||
@@ -568,7 +568,7 @@ void ChapterHtmlSlimParser::addLineToPage(std::shared_ptr<TextBlock> line) {
|
|||||||
|
|
||||||
void ChapterHtmlSlimParser::makePages() {
|
void ChapterHtmlSlimParser::makePages() {
|
||||||
if (!currentTextBlock) {
|
if (!currentTextBlock) {
|
||||||
Serial.printf("[%lu] [EHP] !! No text block to make pages for !!\n", millis());
|
LOG_ERR("EHP", "!! No text block to make pages for !!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
#include "ContainerParser.h"
|
#include "ContainerParser.h"
|
||||||
|
|
||||||
#include <HardwareSerial.h>
|
#include <Logging.h>
|
||||||
|
|
||||||
bool ContainerParser::setup() {
|
bool ContainerParser::setup() {
|
||||||
parser = XML_ParserCreate(nullptr);
|
parser = XML_ParserCreate(nullptr);
|
||||||
if (!parser) {
|
if (!parser) {
|
||||||
Serial.printf("[%lu] [CTR] Couldn't allocate memory for parser\n", millis());
|
LOG_ERR("CTR", "Couldn't allocate memory for parser");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ size_t ContainerParser::write(const uint8_t* buffer, const size_t size) {
|
|||||||
while (remainingInBuffer > 0) {
|
while (remainingInBuffer > 0) {
|
||||||
void* const buf = XML_GetBuffer(parser, 1024);
|
void* const buf = XML_GetBuffer(parser, 1024);
|
||||||
if (!buf) {
|
if (!buf) {
|
||||||
Serial.printf("[%lu] [CTR] Couldn't allocate buffer\n", millis());
|
LOG_DBG("CTR", "Couldn't allocate buffer");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ size_t ContainerParser::write(const uint8_t* buffer, const size_t size) {
|
|||||||
memcpy(buf, currentBufferPos, toRead);
|
memcpy(buf, currentBufferPos, toRead);
|
||||||
|
|
||||||
if (XML_ParseBuffer(parser, static_cast<int>(toRead), remainingSize == toRead) == XML_STATUS_ERROR) {
|
if (XML_ParseBuffer(parser, static_cast<int>(toRead), remainingSize == toRead) == XML_STATUS_ERROR) {
|
||||||
Serial.printf("[%lu] [CTR] Parse error: %s\n", millis(), XML_ErrorString(XML_GetErrorCode(parser)));
|
LOG_ERR("CTR", "Parse error: %s", XML_ErrorString(XML_GetErrorCode(parser)));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "ContentOpfParser.h"
|
#include "ContentOpfParser.h"
|
||||||
|
|
||||||
#include <FsHelpers.h>
|
#include <FsHelpers.h>
|
||||||
#include <HardwareSerial.h>
|
#include <Logging.h>
|
||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
|
|
||||||
#include "../BookMetadataCache.h"
|
#include "../BookMetadataCache.h"
|
||||||
@@ -15,7 +15,7 @@ constexpr char itemCacheFile[] = "/.items.bin";
|
|||||||
bool ContentOpfParser::setup() {
|
bool ContentOpfParser::setup() {
|
||||||
parser = XML_ParserCreate(nullptr);
|
parser = XML_ParserCreate(nullptr);
|
||||||
if (!parser) {
|
if (!parser) {
|
||||||
Serial.printf("[%lu] [COF] Couldn't allocate memory for parser\n", millis());
|
LOG_DBG("COF", "Couldn't allocate memory for parser");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ size_t ContentOpfParser::write(const uint8_t* buffer, const size_t size) {
|
|||||||
void* const buf = XML_GetBuffer(parser, 1024);
|
void* const buf = XML_GetBuffer(parser, 1024);
|
||||||
|
|
||||||
if (!buf) {
|
if (!buf) {
|
||||||
Serial.printf("[%lu] [COF] Couldn't allocate memory for buffer\n", millis());
|
LOG_ERR("COF", "Couldn't allocate memory for buffer");
|
||||||
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);
|
||||||
@@ -69,8 +69,8 @@ size_t ContentOpfParser::write(const uint8_t* buffer, const size_t size) {
|
|||||||
memcpy(buf, currentBufferPos, toRead);
|
memcpy(buf, currentBufferPos, toRead);
|
||||||
|
|
||||||
if (XML_ParseBuffer(parser, static_cast<int>(toRead), remainingSize == toRead) == XML_STATUS_ERROR) {
|
if (XML_ParseBuffer(parser, static_cast<int>(toRead), remainingSize == toRead) == XML_STATUS_ERROR) {
|
||||||
Serial.printf("[%lu] [COF] Parse error at line %lu: %s\n", millis(), XML_GetCurrentLineNumber(parser),
|
LOG_DBG("COF", "Parse error at line %lu: %s", XML_GetCurrentLineNumber(parser),
|
||||||
XML_ErrorString(XML_GetErrorCode(parser)));
|
XML_ErrorString(XML_GetErrorCode(parser)));
|
||||||
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);
|
||||||
@@ -119,9 +119,7 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
|
|||||||
if (self->state == IN_PACKAGE && (strcmp(name, "manifest") == 0 || strcmp(name, "opf:manifest") == 0)) {
|
if (self->state == IN_PACKAGE && (strcmp(name, "manifest") == 0 || strcmp(name, "opf:manifest") == 0)) {
|
||||||
self->state = IN_MANIFEST;
|
self->state = IN_MANIFEST;
|
||||||
if (!Storage.openFileForWrite("COF", self->cachePath + itemCacheFile, self->tempItemStore)) {
|
if (!Storage.openFileForWrite("COF", self->cachePath + itemCacheFile, self->tempItemStore)) {
|
||||||
Serial.printf(
|
LOG_ERR("COF", "Couldn't open temp items file for writing. This is probably going to be a fatal error.");
|
||||||
"[%lu] [COF] Couldn't open temp items file for writing. This is probably going to be a fatal error.\n",
|
|
||||||
millis());
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -129,9 +127,7 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
|
|||||||
if (self->state == IN_PACKAGE && (strcmp(name, "spine") == 0 || strcmp(name, "opf:spine") == 0)) {
|
if (self->state == IN_PACKAGE && (strcmp(name, "spine") == 0 || strcmp(name, "opf:spine") == 0)) {
|
||||||
self->state = IN_SPINE;
|
self->state = IN_SPINE;
|
||||||
if (!Storage.openFileForRead("COF", self->cachePath + itemCacheFile, self->tempItemStore)) {
|
if (!Storage.openFileForRead("COF", self->cachePath + itemCacheFile, self->tempItemStore)) {
|
||||||
Serial.printf(
|
LOG_ERR("COF", "Couldn't open temp items file for reading. This is probably going to be a fatal error.");
|
||||||
"[%lu] [COF] Couldn't open temp items file for reading. This is probably going to be a fatal error.\n",
|
|
||||||
millis());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort item index for binary search if we have enough items
|
// Sort item index for binary search if we have enough items
|
||||||
@@ -140,7 +136,7 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
|
|||||||
return a.idHash < b.idHash || (a.idHash == b.idHash && a.idLen < b.idLen);
|
return a.idHash < b.idHash || (a.idHash == b.idHash && a.idLen < b.idLen);
|
||||||
});
|
});
|
||||||
self->useItemIndex = true;
|
self->useItemIndex = true;
|
||||||
Serial.printf("[%lu] [COF] Using fast index for %zu manifest items\n", millis(), self->itemIndex.size());
|
LOG_DBG("COF", "Using fast index for %zu manifest items", self->itemIndex.size());
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -148,11 +144,9 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
|
|||||||
if (self->state == IN_PACKAGE && (strcmp(name, "guide") == 0 || strcmp(name, "opf:guide") == 0)) {
|
if (self->state == IN_PACKAGE && (strcmp(name, "guide") == 0 || strcmp(name, "opf:guide") == 0)) {
|
||||||
self->state = IN_GUIDE;
|
self->state = IN_GUIDE;
|
||||||
// TODO Remove print
|
// TODO Remove print
|
||||||
Serial.printf("[%lu] [COF] Entering guide state.\n", millis());
|
LOG_DBG("COF", "Entering guide state.");
|
||||||
if (!Storage.openFileForRead("COF", self->cachePath + itemCacheFile, self->tempItemStore)) {
|
if (!Storage.openFileForRead("COF", self->cachePath + itemCacheFile, self->tempItemStore)) {
|
||||||
Serial.printf(
|
LOG_ERR("COF", "Couldn't open temp items file for reading. This is probably going to be a fatal error.");
|
||||||
"[%lu] [COF] Couldn't open temp items file for reading. This is probably going to be a fatal error.\n",
|
|
||||||
millis());
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -214,8 +208,7 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
|
|||||||
if (self->tocNcxPath.empty()) {
|
if (self->tocNcxPath.empty()) {
|
||||||
self->tocNcxPath = href;
|
self->tocNcxPath = href;
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("[%lu] [COF] Warning: Multiple NCX files found in manifest. Ignoring duplicate: %s\n", millis(),
|
LOG_DBG("COF", "Warning: Multiple NCX files found in manifest. Ignoring duplicate: %s", href.c_str());
|
||||||
href.c_str());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,7 +222,7 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
|
|||||||
// Properties is space-separated, check if "nav" is present as a word
|
// Properties is space-separated, check if "nav" is present as a word
|
||||||
if (properties == "nav" || properties.find("nav ") == 0 || properties.find(" nav") != std::string::npos) {
|
if (properties == "nav" || properties.find("nav ") == 0 || properties.find(" nav") != std::string::npos) {
|
||||||
self->tocNavPath = href;
|
self->tocNavPath = href;
|
||||||
Serial.printf("[%lu] [COF] Found EPUB 3 nav document: %s\n", millis(), href.c_str());
|
LOG_DBG("COF", "Found EPUB 3 nav document: %s", href.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,7 +303,7 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
|
|||||||
if (type == "text" || type == "start") {
|
if (type == "text" || type == "start") {
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("[%lu] [COF] Skipping non-text reference in guide: %s\n", millis(), type.c_str());
|
LOG_DBG("COF", "Skipping non-text reference in guide: %s", type.c_str());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if (strcmp(atts[i], "href") == 0) {
|
} else if (strcmp(atts[i], "href") == 0) {
|
||||||
@@ -318,7 +311,7 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((type == "text" || (type == "start" && !self->textReferenceHref.empty())) && (textHref.length() > 0)) {
|
if ((type == "text" || (type == "start" && !self->textReferenceHref.empty())) && (textHref.length() > 0)) {
|
||||||
Serial.printf("[%lu] [COF] Found %s reference in guide: %s.\n", millis(), type.c_str(), textHref.c_str());
|
LOG_DBG("COF", "Found %s reference in guide: %s.", type.c_str(), textHref.c_str());
|
||||||
self->textReferenceHref = textHref;
|
self->textReferenceHref = textHref;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
#include "TocNavParser.h"
|
#include "TocNavParser.h"
|
||||||
|
|
||||||
#include <FsHelpers.h>
|
#include <FsHelpers.h>
|
||||||
#include <HardwareSerial.h>
|
#include <Logging.h>
|
||||||
|
|
||||||
#include "../BookMetadataCache.h"
|
#include "../BookMetadataCache.h"
|
||||||
|
|
||||||
bool TocNavParser::setup() {
|
bool TocNavParser::setup() {
|
||||||
parser = XML_ParserCreate(nullptr);
|
parser = XML_ParserCreate(nullptr);
|
||||||
if (!parser) {
|
if (!parser) {
|
||||||
Serial.printf("[%lu] [NAV] Couldn't allocate memory for parser\n", millis());
|
LOG_DBG("NAV", "Couldn't allocate memory for parser");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ size_t TocNavParser::write(const uint8_t* buffer, const size_t size) {
|
|||||||
while (remainingInBuffer > 0) {
|
while (remainingInBuffer > 0) {
|
||||||
void* const buf = XML_GetBuffer(parser, 1024);
|
void* const buf = XML_GetBuffer(parser, 1024);
|
||||||
if (!buf) {
|
if (!buf) {
|
||||||
Serial.printf("[%lu] [NAV] Couldn't allocate memory for buffer\n", millis());
|
LOG_DBG("NAV", "Couldn't allocate memory for buffer");
|
||||||
XML_StopParser(parser, XML_FALSE);
|
XML_StopParser(parser, XML_FALSE);
|
||||||
XML_SetElementHandler(parser, nullptr, nullptr);
|
XML_SetElementHandler(parser, nullptr, nullptr);
|
||||||
XML_SetCharacterDataHandler(parser, nullptr);
|
XML_SetCharacterDataHandler(parser, nullptr);
|
||||||
@@ -52,8 +52,8 @@ size_t TocNavParser::write(const uint8_t* buffer, const size_t size) {
|
|||||||
memcpy(buf, currentBufferPos, toRead);
|
memcpy(buf, currentBufferPos, toRead);
|
||||||
|
|
||||||
if (XML_ParseBuffer(parser, static_cast<int>(toRead), remainingSize == toRead) == XML_STATUS_ERROR) {
|
if (XML_ParseBuffer(parser, static_cast<int>(toRead), remainingSize == toRead) == XML_STATUS_ERROR) {
|
||||||
Serial.printf("[%lu] [NAV] Parse error at line %lu: %s\n", millis(), XML_GetCurrentLineNumber(parser),
|
LOG_DBG("NAV", "Parse error at line %lu: %s", XML_GetCurrentLineNumber(parser),
|
||||||
XML_ErrorString(XML_GetErrorCode(parser)));
|
XML_ErrorString(XML_GetErrorCode(parser)));
|
||||||
XML_StopParser(parser, XML_FALSE);
|
XML_StopParser(parser, XML_FALSE);
|
||||||
XML_SetElementHandler(parser, nullptr, nullptr);
|
XML_SetElementHandler(parser, nullptr, nullptr);
|
||||||
XML_SetCharacterDataHandler(parser, nullptr);
|
XML_SetCharacterDataHandler(parser, nullptr);
|
||||||
@@ -88,7 +88,7 @@ void XMLCALL TocNavParser::startElement(void* userData, const XML_Char* name, co
|
|||||||
for (int i = 0; atts[i]; i += 2) {
|
for (int i = 0; atts[i]; i += 2) {
|
||||||
if ((strcmp(atts[i], "epub:type") == 0 || strcmp(atts[i], "type") == 0) && strcmp(atts[i + 1], "toc") == 0) {
|
if ((strcmp(atts[i], "epub:type") == 0 || strcmp(atts[i], "type") == 0) && strcmp(atts[i + 1], "toc") == 0) {
|
||||||
self->state = IN_NAV_TOC;
|
self->state = IN_NAV_TOC;
|
||||||
Serial.printf("[%lu] [NAV] Found nav toc element\n", millis());
|
LOG_DBG("NAV", "Found nav toc element");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,7 +179,7 @@ void XMLCALL TocNavParser::endElement(void* userData, const XML_Char* name) {
|
|||||||
|
|
||||||
if (strcmp(name, "nav") == 0 && self->state >= IN_NAV_TOC) {
|
if (strcmp(name, "nav") == 0 && self->state >= IN_NAV_TOC) {
|
||||||
self->state = IN_BODY;
|
self->state = IN_BODY;
|
||||||
Serial.printf("[%lu] [NAV] Finished parsing nav toc\n", millis());
|
LOG_DBG("NAV", "Finished parsing nav toc");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
#include "TocNcxParser.h"
|
#include "TocNcxParser.h"
|
||||||
|
|
||||||
#include <FsHelpers.h>
|
#include <FsHelpers.h>
|
||||||
#include <HardwareSerial.h>
|
#include <Logging.h>
|
||||||
|
|
||||||
#include "../BookMetadataCache.h"
|
#include "../BookMetadataCache.h"
|
||||||
|
|
||||||
bool TocNcxParser::setup() {
|
bool TocNcxParser::setup() {
|
||||||
parser = XML_ParserCreate(nullptr);
|
parser = XML_ParserCreate(nullptr);
|
||||||
if (!parser) {
|
if (!parser) {
|
||||||
Serial.printf("[%lu] [TOC] Couldn't allocate memory for parser\n", millis());
|
LOG_DBG("TOC", "Couldn't allocate memory for parser");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ size_t TocNcxParser::write(const uint8_t* buffer, const size_t size) {
|
|||||||
while (remainingInBuffer > 0) {
|
while (remainingInBuffer > 0) {
|
||||||
void* const buf = XML_GetBuffer(parser, 1024);
|
void* const buf = XML_GetBuffer(parser, 1024);
|
||||||
if (!buf) {
|
if (!buf) {
|
||||||
Serial.printf("[%lu] [TOC] Couldn't allocate memory for buffer\n", millis());
|
LOG_DBG("TOC", "Couldn't allocate memory for buffer");
|
||||||
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);
|
||||||
@@ -52,8 +52,8 @@ size_t TocNcxParser::write(const uint8_t* buffer, const size_t size) {
|
|||||||
memcpy(buf, currentBufferPos, toRead);
|
memcpy(buf, currentBufferPos, toRead);
|
||||||
|
|
||||||
if (XML_ParseBuffer(parser, static_cast<int>(toRead), remainingSize == toRead) == XML_STATUS_ERROR) {
|
if (XML_ParseBuffer(parser, static_cast<int>(toRead), remainingSize == toRead) == XML_STATUS_ERROR) {
|
||||||
Serial.printf("[%lu] [TOC] Parse error at line %lu: %s\n", millis(), XML_GetCurrentLineNumber(parser),
|
LOG_DBG("TOC", "Parse error at line %lu: %s", XML_GetCurrentLineNumber(parser),
|
||||||
XML_ErrorString(XML_GetErrorCode(parser)));
|
XML_ErrorString(XML_GetErrorCode(parser)));
|
||||||
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);
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
#include "GfxRenderer.h"
|
#include "GfxRenderer.h"
|
||||||
|
|
||||||
|
#include <Logging.h>
|
||||||
#include <Utf8.h>
|
#include <Utf8.h>
|
||||||
|
|
||||||
void GfxRenderer::begin() {
|
void GfxRenderer::begin() {
|
||||||
frameBuffer = display.getFrameBuffer();
|
frameBuffer = display.getFrameBuffer();
|
||||||
if (!frameBuffer) {
|
if (!frameBuffer) {
|
||||||
Serial.printf("[%lu] [GFX] !! No framebuffer\n", millis());
|
LOG_ERR("GFX", "!! No framebuffer");
|
||||||
assert(false);
|
assert(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,7 +58,7 @@ void GfxRenderer::drawPixel(const int x, const int y, const bool state) const {
|
|||||||
|
|
||||||
// Bounds checking against physical panel dimensions
|
// Bounds checking against physical panel dimensions
|
||||||
if (phyX < 0 || phyX >= HalDisplay::DISPLAY_WIDTH || phyY < 0 || phyY >= HalDisplay::DISPLAY_HEIGHT) {
|
if (phyX < 0 || phyX >= HalDisplay::DISPLAY_WIDTH || phyY < 0 || phyY >= HalDisplay::DISPLAY_HEIGHT) {
|
||||||
Serial.printf("[%lu] [GFX] !! Outside range (%d, %d) -> (%d, %d)\n", millis(), x, y, phyX, phyY);
|
LOG_ERR("GFX", "!! Outside range (%d, %d) -> (%d, %d)", x, y, phyX, phyY);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +75,7 @@ void GfxRenderer::drawPixel(const int x, const int y, const bool state) const {
|
|||||||
|
|
||||||
int GfxRenderer::getTextWidth(const int fontId, const char* text, const EpdFontFamily::Style style) const {
|
int GfxRenderer::getTextWidth(const int fontId, const char* text, const EpdFontFamily::Style style) const {
|
||||||
if (fontMap.count(fontId) == 0) {
|
if (fontMap.count(fontId) == 0) {
|
||||||
Serial.printf("[%lu] [GFX] Font %d not found\n", millis(), fontId);
|
LOG_ERR("GFX", "Font %d not found", fontId);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +101,7 @@ void GfxRenderer::drawText(const int fontId, const int x, const int y, const cha
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fontMap.count(fontId) == 0) {
|
if (fontMap.count(fontId) == 0) {
|
||||||
Serial.printf("[%lu] [GFX] Font %d not found\n", millis(), fontId);
|
LOG_ERR("GFX", "Font %d not found", fontId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto font = fontMap.at(fontId);
|
const auto font = fontMap.at(fontId);
|
||||||
@@ -133,7 +134,7 @@ void GfxRenderer::drawLine(int x1, int y1, int x2, int y2, const bool state) con
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// TODO: Implement
|
// TODO: Implement
|
||||||
Serial.printf("[%lu] [GFX] Line drawing not supported\n", millis());
|
LOG_ERR("GFX", "Line drawing not supported");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,8 +420,8 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con
|
|||||||
bool isScaled = false;
|
bool isScaled = false;
|
||||||
int cropPixX = std::floor(bitmap.getWidth() * cropX / 2.0f);
|
int cropPixX = std::floor(bitmap.getWidth() * cropX / 2.0f);
|
||||||
int cropPixY = std::floor(bitmap.getHeight() * cropY / 2.0f);
|
int cropPixY = std::floor(bitmap.getHeight() * cropY / 2.0f);
|
||||||
Serial.printf("[%lu] [GFX] Cropping %dx%d by %dx%d pix, is %s\n", millis(), bitmap.getWidth(), bitmap.getHeight(),
|
LOG_DBG("GFX", "Cropping %dx%d by %dx%d pix, is %s", bitmap.getWidth(), bitmap.getHeight(), cropPixX, cropPixY,
|
||||||
cropPixX, cropPixY, bitmap.isTopDown() ? "top-down" : "bottom-up");
|
bitmap.isTopDown() ? "top-down" : "bottom-up");
|
||||||
|
|
||||||
if (maxWidth > 0 && (1.0f - cropX) * bitmap.getWidth() > maxWidth) {
|
if (maxWidth > 0 && (1.0f - cropX) * bitmap.getWidth() > maxWidth) {
|
||||||
scale = static_cast<float>(maxWidth) / static_cast<float>((1.0f - cropX) * bitmap.getWidth());
|
scale = static_cast<float>(maxWidth) / static_cast<float>((1.0f - cropX) * bitmap.getWidth());
|
||||||
@@ -430,7 +431,7 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con
|
|||||||
scale = std::min(scale, static_cast<float>(maxHeight) / static_cast<float>((1.0f - cropY) * bitmap.getHeight()));
|
scale = std::min(scale, static_cast<float>(maxHeight) / static_cast<float>((1.0f - cropY) * bitmap.getHeight()));
|
||||||
isScaled = true;
|
isScaled = true;
|
||||||
}
|
}
|
||||||
Serial.printf("[%lu] [GFX] Scaling by %f - %s\n", millis(), scale, isScaled ? "scaled" : "not scaled");
|
LOG_DBG("GFX", "Scaling by %f - %s", scale, isScaled ? "scaled" : "not scaled");
|
||||||
|
|
||||||
// Calculate output row size (2 bits per pixel, packed into bytes)
|
// Calculate output row size (2 bits per pixel, packed into bytes)
|
||||||
// IMPORTANT: Use int, not uint8_t, to avoid overflow for images > 1020 pixels wide
|
// IMPORTANT: Use int, not uint8_t, to avoid overflow for images > 1020 pixels wide
|
||||||
@@ -439,7 +440,7 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con
|
|||||||
auto* rowBytes = static_cast<uint8_t*>(malloc(bitmap.getRowBytes()));
|
auto* rowBytes = static_cast<uint8_t*>(malloc(bitmap.getRowBytes()));
|
||||||
|
|
||||||
if (!outputRow || !rowBytes) {
|
if (!outputRow || !rowBytes) {
|
||||||
Serial.printf("[%lu] [GFX] !! Failed to allocate BMP row buffers\n", millis());
|
LOG_ERR("GFX", "!! Failed to allocate BMP row buffers");
|
||||||
free(outputRow);
|
free(outputRow);
|
||||||
free(rowBytes);
|
free(rowBytes);
|
||||||
return;
|
return;
|
||||||
@@ -458,7 +459,7 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (bitmap.readNextRow(outputRow, rowBytes) != BmpReaderError::Ok) {
|
if (bitmap.readNextRow(outputRow, rowBytes) != BmpReaderError::Ok) {
|
||||||
Serial.printf("[%lu] [GFX] Failed to read row %d from bitmap\n", millis(), bmpY);
|
LOG_ERR("GFX", "Failed to read row %d from bitmap", bmpY);
|
||||||
free(outputRow);
|
free(outputRow);
|
||||||
free(rowBytes);
|
free(rowBytes);
|
||||||
return;
|
return;
|
||||||
@@ -521,7 +522,7 @@ void GfxRenderer::drawBitmap1Bit(const Bitmap& bitmap, const int x, const int y,
|
|||||||
auto* rowBytes = static_cast<uint8_t*>(malloc(bitmap.getRowBytes()));
|
auto* rowBytes = static_cast<uint8_t*>(malloc(bitmap.getRowBytes()));
|
||||||
|
|
||||||
if (!outputRow || !rowBytes) {
|
if (!outputRow || !rowBytes) {
|
||||||
Serial.printf("[%lu] [GFX] !! Failed to allocate 1-bit BMP row buffers\n", millis());
|
LOG_ERR("GFX", "!! Failed to allocate 1-bit BMP row buffers");
|
||||||
free(outputRow);
|
free(outputRow);
|
||||||
free(rowBytes);
|
free(rowBytes);
|
||||||
return;
|
return;
|
||||||
@@ -530,7 +531,7 @@ void GfxRenderer::drawBitmap1Bit(const Bitmap& bitmap, const int x, const int y,
|
|||||||
for (int bmpY = 0; bmpY < bitmap.getHeight(); bmpY++) {
|
for (int bmpY = 0; bmpY < bitmap.getHeight(); bmpY++) {
|
||||||
// Read rows sequentially using readNextRow
|
// Read rows sequentially using readNextRow
|
||||||
if (bitmap.readNextRow(outputRow, rowBytes) != BmpReaderError::Ok) {
|
if (bitmap.readNextRow(outputRow, rowBytes) != BmpReaderError::Ok) {
|
||||||
Serial.printf("[%lu] [GFX] Failed to read row %d from 1-bit bitmap\n", millis(), bmpY);
|
LOG_ERR("GFX", "Failed to read row %d from 1-bit bitmap", bmpY);
|
||||||
free(outputRow);
|
free(outputRow);
|
||||||
free(rowBytes);
|
free(rowBytes);
|
||||||
return;
|
return;
|
||||||
@@ -588,7 +589,7 @@ void GfxRenderer::fillPolygon(const int* xPoints, const int* yPoints, int numPoi
|
|||||||
// Allocate node buffer for scanline algorithm
|
// Allocate node buffer for scanline algorithm
|
||||||
auto* nodeX = static_cast<int*>(malloc(numPoints * sizeof(int)));
|
auto* nodeX = static_cast<int*>(malloc(numPoints * sizeof(int)));
|
||||||
if (!nodeX) {
|
if (!nodeX) {
|
||||||
Serial.printf("[%lu] [GFX] !! Failed to allocate polygon node buffer\n", millis());
|
LOG_ERR("GFX", "!! Failed to allocate polygon node buffer");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -655,7 +656,7 @@ void GfxRenderer::invertScreen() const {
|
|||||||
|
|
||||||
void GfxRenderer::displayBuffer(const HalDisplay::RefreshMode refreshMode) const {
|
void GfxRenderer::displayBuffer(const HalDisplay::RefreshMode refreshMode) const {
|
||||||
auto elapsed = millis() - start_ms;
|
auto elapsed = millis() - start_ms;
|
||||||
Serial.printf("[%lu] [GFX] Time = %lu ms from clearScreen to displayBuffer\n", millis(), elapsed);
|
LOG_DBG("GFX", "Time = %lu ms from clearScreen to displayBuffer", elapsed);
|
||||||
display.displayBuffer(refreshMode, fadingFix);
|
display.displayBuffer(refreshMode, fadingFix);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -709,7 +710,7 @@ int GfxRenderer::getScreenHeight() const {
|
|||||||
|
|
||||||
int GfxRenderer::getSpaceWidth(const int fontId) const {
|
int GfxRenderer::getSpaceWidth(const int fontId) const {
|
||||||
if (fontMap.count(fontId) == 0) {
|
if (fontMap.count(fontId) == 0) {
|
||||||
Serial.printf("[%lu] [GFX] Font %d not found\n", millis(), fontId);
|
LOG_ERR("GFX", "Font %d not found", fontId);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -718,7 +719,7 @@ int GfxRenderer::getSpaceWidth(const int fontId) const {
|
|||||||
|
|
||||||
int GfxRenderer::getTextAdvanceX(const int fontId, const char* text) const {
|
int GfxRenderer::getTextAdvanceX(const int fontId, const char* text) const {
|
||||||
if (fontMap.count(fontId) == 0) {
|
if (fontMap.count(fontId) == 0) {
|
||||||
Serial.printf("[%lu] [GFX] Font %d not found\n", millis(), fontId);
|
LOG_ERR("GFX", "Font %d not found", fontId);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -732,7 +733,7 @@ int GfxRenderer::getTextAdvanceX(const int fontId, const char* text) const {
|
|||||||
|
|
||||||
int GfxRenderer::getFontAscenderSize(const int fontId) const {
|
int GfxRenderer::getFontAscenderSize(const int fontId) const {
|
||||||
if (fontMap.count(fontId) == 0) {
|
if (fontMap.count(fontId) == 0) {
|
||||||
Serial.printf("[%lu] [GFX] Font %d not found\n", millis(), fontId);
|
LOG_ERR("GFX", "Font %d not found", fontId);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -741,7 +742,7 @@ int GfxRenderer::getFontAscenderSize(const int fontId) const {
|
|||||||
|
|
||||||
int GfxRenderer::getLineHeight(const int fontId) const {
|
int GfxRenderer::getLineHeight(const int fontId) const {
|
||||||
if (fontMap.count(fontId) == 0) {
|
if (fontMap.count(fontId) == 0) {
|
||||||
Serial.printf("[%lu] [GFX] Font %d not found\n", millis(), fontId);
|
LOG_ERR("GFX", "Font %d not found", fontId);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -750,7 +751,7 @@ int GfxRenderer::getLineHeight(const int fontId) const {
|
|||||||
|
|
||||||
int GfxRenderer::getTextHeight(const int fontId) const {
|
int GfxRenderer::getTextHeight(const int fontId) const {
|
||||||
if (fontMap.count(fontId) == 0) {
|
if (fontMap.count(fontId) == 0) {
|
||||||
Serial.printf("[%lu] [GFX] Font %d not found\n", millis(), fontId);
|
LOG_ERR("GFX", "Font %d not found", fontId);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return fontMap.at(fontId).getData(EpdFontFamily::REGULAR)->ascender;
|
return fontMap.at(fontId).getData(EpdFontFamily::REGULAR)->ascender;
|
||||||
@@ -764,7 +765,7 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fontMap.count(fontId) == 0) {
|
if (fontMap.count(fontId) == 0) {
|
||||||
Serial.printf("[%lu] [GFX] Font %d not found\n", millis(), fontId);
|
LOG_ERR("GFX", "Font %d not found", fontId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto font = fontMap.at(fontId);
|
const auto font = fontMap.at(fontId);
|
||||||
@@ -872,8 +873,7 @@ bool GfxRenderer::storeBwBuffer() {
|
|||||||
for (size_t i = 0; i < BW_BUFFER_NUM_CHUNKS; i++) {
|
for (size_t i = 0; i < BW_BUFFER_NUM_CHUNKS; i++) {
|
||||||
// Check if any chunks are already allocated
|
// Check if any chunks are already allocated
|
||||||
if (bwBufferChunks[i]) {
|
if (bwBufferChunks[i]) {
|
||||||
Serial.printf("[%lu] [GFX] !! BW buffer chunk %zu already stored - this is likely a bug, freeing chunk\n",
|
LOG_ERR("GFX", "!! BW buffer chunk %zu already stored - this is likely a bug, freeing chunk", i);
|
||||||
millis(), i);
|
|
||||||
free(bwBufferChunks[i]);
|
free(bwBufferChunks[i]);
|
||||||
bwBufferChunks[i] = nullptr;
|
bwBufferChunks[i] = nullptr;
|
||||||
}
|
}
|
||||||
@@ -882,8 +882,7 @@ bool GfxRenderer::storeBwBuffer() {
|
|||||||
bwBufferChunks[i] = static_cast<uint8_t*>(malloc(BW_BUFFER_CHUNK_SIZE));
|
bwBufferChunks[i] = static_cast<uint8_t*>(malloc(BW_BUFFER_CHUNK_SIZE));
|
||||||
|
|
||||||
if (!bwBufferChunks[i]) {
|
if (!bwBufferChunks[i]) {
|
||||||
Serial.printf("[%lu] [GFX] !! Failed to allocate BW buffer chunk %zu (%zu bytes)\n", millis(), i,
|
LOG_ERR("GFX", "!! Failed to allocate BW buffer chunk %zu (%zu bytes)", i, BW_BUFFER_CHUNK_SIZE);
|
||||||
BW_BUFFER_CHUNK_SIZE);
|
|
||||||
// Free previously allocated chunks
|
// Free previously allocated chunks
|
||||||
freeBwBufferChunks();
|
freeBwBufferChunks();
|
||||||
return false;
|
return false;
|
||||||
@@ -892,8 +891,7 @@ bool GfxRenderer::storeBwBuffer() {
|
|||||||
memcpy(bwBufferChunks[i], frameBuffer + offset, BW_BUFFER_CHUNK_SIZE);
|
memcpy(bwBufferChunks[i], frameBuffer + offset, BW_BUFFER_CHUNK_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [GFX] Stored BW buffer in %zu chunks (%zu bytes each)\n", millis(), BW_BUFFER_NUM_CHUNKS,
|
LOG_DBG("GFX", "Stored BW buffer in %zu chunks (%zu bytes each)", BW_BUFFER_NUM_CHUNKS, BW_BUFFER_CHUNK_SIZE);
|
||||||
BW_BUFFER_CHUNK_SIZE);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -920,7 +918,7 @@ void GfxRenderer::restoreBwBuffer() {
|
|||||||
for (size_t i = 0; i < BW_BUFFER_NUM_CHUNKS; i++) {
|
for (size_t i = 0; i < BW_BUFFER_NUM_CHUNKS; i++) {
|
||||||
// Check if chunk is missing
|
// Check if chunk is missing
|
||||||
if (!bwBufferChunks[i]) {
|
if (!bwBufferChunks[i]) {
|
||||||
Serial.printf("[%lu] [GFX] !! BW buffer chunks not stored - this is likely a bug\n", millis());
|
LOG_ERR("GFX", "!! BW buffer chunks not stored - this is likely a bug");
|
||||||
freeBwBufferChunks();
|
freeBwBufferChunks();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -932,7 +930,7 @@ void GfxRenderer::restoreBwBuffer() {
|
|||||||
display.cleanupGrayscaleBuffers(frameBuffer);
|
display.cleanupGrayscaleBuffers(frameBuffer);
|
||||||
|
|
||||||
freeBwBufferChunks();
|
freeBwBufferChunks();
|
||||||
Serial.printf("[%lu] [GFX] Restored and freed BW buffer chunks\n", millis());
|
LOG_DBG("GFX", "Restored and freed BW buffer chunks");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -954,7 +952,7 @@ void GfxRenderer::renderChar(const EpdFontFamily& fontFamily, const uint32_t cp,
|
|||||||
|
|
||||||
// no glyph?
|
// no glyph?
|
||||||
if (!glyph) {
|
if (!glyph) {
|
||||||
Serial.printf("[%lu] [GFX] No glyph for codepoint %d\n", millis(), cp);
|
LOG_ERR("GFX", "No glyph for codepoint %d", cp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "JpegToBmpConverter.h"
|
#include "JpegToBmpConverter.h"
|
||||||
|
|
||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
#include <HardwareSerial.h>
|
#include <Logging.h>
|
||||||
#include <picojpeg.h>
|
#include <picojpeg.h>
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
@@ -201,8 +201,7 @@ unsigned char JpegToBmpConverter::jpegReadCallback(unsigned char* pBuf, const un
|
|||||||
// Internal implementation with configurable target size and bit depth
|
// Internal implementation with configurable target size and bit depth
|
||||||
bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bmpOut, int targetWidth, int targetHeight,
|
bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bmpOut, int targetWidth, int targetHeight,
|
||||||
bool oneBit, bool crop) {
|
bool oneBit, bool crop) {
|
||||||
Serial.printf("[%lu] [JPG] Converting JPEG to %s BMP (target: %dx%d)\n", millis(), oneBit ? "1-bit" : "2-bit",
|
LOG_DBG("JPG", "Converting JPEG to %s BMP (target: %dx%d)", oneBit ? "1-bit" : "2-bit", targetWidth, targetHeight);
|
||||||
targetWidth, targetHeight);
|
|
||||||
|
|
||||||
// Setup context for picojpeg callback
|
// Setup context for picojpeg callback
|
||||||
JpegReadContext context = {.file = jpegFile, .bufferPos = 0, .bufferFilled = 0};
|
JpegReadContext context = {.file = jpegFile, .bufferPos = 0, .bufferFilled = 0};
|
||||||
@@ -211,12 +210,12 @@ bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bm
|
|||||||
pjpeg_image_info_t imageInfo;
|
pjpeg_image_info_t imageInfo;
|
||||||
const unsigned char status = pjpeg_decode_init(&imageInfo, jpegReadCallback, &context, 0);
|
const unsigned char status = pjpeg_decode_init(&imageInfo, jpegReadCallback, &context, 0);
|
||||||
if (status != 0) {
|
if (status != 0) {
|
||||||
Serial.printf("[%lu] [JPG] JPEG decode init failed with error code: %d\n", millis(), status);
|
LOG_ERR("JPG", "JPEG decode init failed with error code: %d", status);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [JPG] JPEG dimensions: %dx%d, components: %d, MCUs: %dx%d\n", millis(), imageInfo.m_width,
|
LOG_DBG("JPG", "JPEG dimensions: %dx%d, components: %d, MCUs: %dx%d", imageInfo.m_width, imageInfo.m_height,
|
||||||
imageInfo.m_height, imageInfo.m_comps, imageInfo.m_MCUSPerRow, imageInfo.m_MCUSPerCol);
|
imageInfo.m_comps, imageInfo.m_MCUSPerRow, imageInfo.m_MCUSPerCol);
|
||||||
|
|
||||||
// Safety limits to prevent memory issues on ESP32
|
// Safety limits to prevent memory issues on ESP32
|
||||||
constexpr int MAX_IMAGE_WIDTH = 2048;
|
constexpr int MAX_IMAGE_WIDTH = 2048;
|
||||||
@@ -224,8 +223,8 @@ bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bm
|
|||||||
constexpr int MAX_MCU_ROW_BYTES = 65536;
|
constexpr int MAX_MCU_ROW_BYTES = 65536;
|
||||||
|
|
||||||
if (imageInfo.m_width > MAX_IMAGE_WIDTH || imageInfo.m_height > MAX_IMAGE_HEIGHT) {
|
if (imageInfo.m_width > MAX_IMAGE_WIDTH || imageInfo.m_height > MAX_IMAGE_HEIGHT) {
|
||||||
Serial.printf("[%lu] [JPG] Image too large (%dx%d), max supported: %dx%d\n", millis(), imageInfo.m_width,
|
LOG_DBG("JPG", "Image too large (%dx%d), max supported: %dx%d", imageInfo.m_width, imageInfo.m_height,
|
||||||
imageInfo.m_height, MAX_IMAGE_WIDTH, MAX_IMAGE_HEIGHT);
|
MAX_IMAGE_WIDTH, MAX_IMAGE_HEIGHT);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,8 +261,8 @@ bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bm
|
|||||||
scaleY_fp = (static_cast<uint32_t>(imageInfo.m_height) << 16) / outHeight;
|
scaleY_fp = (static_cast<uint32_t>(imageInfo.m_height) << 16) / outHeight;
|
||||||
needsScaling = true;
|
needsScaling = true;
|
||||||
|
|
||||||
Serial.printf("[%lu] [JPG] Pre-scaling %dx%d -> %dx%d (fit to %dx%d)\n", millis(), imageInfo.m_width,
|
LOG_DBG("JPG", "Pre-scaling %dx%d -> %dx%d (fit to %dx%d)", imageInfo.m_width, imageInfo.m_height, outWidth,
|
||||||
imageInfo.m_height, outWidth, outHeight, targetWidth, targetHeight);
|
outHeight, targetWidth, targetHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write BMP header with output dimensions
|
// Write BMP header with output dimensions
|
||||||
@@ -282,7 +281,7 @@ bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bm
|
|||||||
// Allocate row buffer
|
// Allocate row buffer
|
||||||
auto* rowBuffer = static_cast<uint8_t*>(malloc(bytesPerRow));
|
auto* rowBuffer = static_cast<uint8_t*>(malloc(bytesPerRow));
|
||||||
if (!rowBuffer) {
|
if (!rowBuffer) {
|
||||||
Serial.printf("[%lu] [JPG] Failed to allocate row buffer\n", millis());
|
LOG_ERR("JPG", "Failed to allocate row buffer");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,15 +292,14 @@ bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bm
|
|||||||
|
|
||||||
// Validate MCU row buffer size before allocation
|
// Validate MCU row buffer size before allocation
|
||||||
if (mcuRowPixels > MAX_MCU_ROW_BYTES) {
|
if (mcuRowPixels > MAX_MCU_ROW_BYTES) {
|
||||||
Serial.printf("[%lu] [JPG] MCU row buffer too large (%d bytes), max: %d\n", millis(), mcuRowPixels,
|
LOG_DBG("JPG", "MCU row buffer too large (%d bytes), max: %d", mcuRowPixels, MAX_MCU_ROW_BYTES);
|
||||||
MAX_MCU_ROW_BYTES);
|
|
||||||
free(rowBuffer);
|
free(rowBuffer);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* mcuRowBuffer = static_cast<uint8_t*>(malloc(mcuRowPixels));
|
auto* mcuRowBuffer = static_cast<uint8_t*>(malloc(mcuRowPixels));
|
||||||
if (!mcuRowBuffer) {
|
if (!mcuRowBuffer) {
|
||||||
Serial.printf("[%lu] [JPG] Failed to allocate MCU row buffer (%d bytes)\n", millis(), mcuRowPixels);
|
LOG_ERR("JPG", "Failed to allocate MCU row buffer (%d bytes)", mcuRowPixels);
|
||||||
free(rowBuffer);
|
free(rowBuffer);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -349,10 +347,9 @@ bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bm
|
|||||||
const unsigned char mcuStatus = pjpeg_decode_mcu();
|
const unsigned char mcuStatus = pjpeg_decode_mcu();
|
||||||
if (mcuStatus != 0) {
|
if (mcuStatus != 0) {
|
||||||
if (mcuStatus == PJPG_NO_MORE_BLOCKS) {
|
if (mcuStatus == PJPG_NO_MORE_BLOCKS) {
|
||||||
Serial.printf("[%lu] [JPG] Unexpected end of blocks at MCU (%d, %d)\n", millis(), mcuX, mcuY);
|
LOG_ERR("JPG", "Unexpected end of blocks at MCU (%d, %d)", mcuX, mcuY);
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("[%lu] [JPG] JPEG decode MCU failed at (%d, %d) with error code: %d\n", millis(), mcuX, mcuY,
|
LOG_ERR("JPG", "JPEG decode MCU failed at (%d, %d) with error code: %d", mcuX, mcuY, mcuStatus);
|
||||||
mcuStatus);
|
|
||||||
}
|
}
|
||||||
free(mcuRowBuffer);
|
free(mcuRowBuffer);
|
||||||
free(rowBuffer);
|
free(rowBuffer);
|
||||||
@@ -549,7 +546,7 @@ bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bm
|
|||||||
free(mcuRowBuffer);
|
free(mcuRowBuffer);
|
||||||
free(rowBuffer);
|
free(rowBuffer);
|
||||||
|
|
||||||
Serial.printf("[%lu] [JPG] Successfully converted JPEG to BMP\n", millis());
|
LOG_DBG("JPG", "Successfully converted JPEG to BMP");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "KOReaderCredentialStore.h"
|
#include "KOReaderCredentialStore.h"
|
||||||
|
|
||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
#include <HardwareSerial.h>
|
#include <Logging.h>
|
||||||
#include <MD5Builder.h>
|
#include <MD5Builder.h>
|
||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ bool KOReaderCredentialStore::saveToFile() const {
|
|||||||
|
|
||||||
// Write username (plaintext - not particularly sensitive)
|
// Write username (plaintext - not particularly sensitive)
|
||||||
serialization::writeString(file, username);
|
serialization::writeString(file, username);
|
||||||
Serial.printf("[%lu] [KRS] Saving username: %s\n", millis(), username.c_str());
|
LOG_DBG("KRS", "Saving username: %s", username.c_str());
|
||||||
|
|
||||||
// Write password (obfuscated)
|
// Write password (obfuscated)
|
||||||
std::string obfuscatedPwd = password;
|
std::string obfuscatedPwd = password;
|
||||||
@@ -58,14 +58,14 @@ bool KOReaderCredentialStore::saveToFile() const {
|
|||||||
serialization::writePod(file, static_cast<uint8_t>(matchMethod));
|
serialization::writePod(file, static_cast<uint8_t>(matchMethod));
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
Serial.printf("[%lu] [KRS] Saved KOReader credentials to file\n", millis());
|
LOG_DBG("KRS", "Saved KOReader credentials to file");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool KOReaderCredentialStore::loadFromFile() {
|
bool KOReaderCredentialStore::loadFromFile() {
|
||||||
FsFile file;
|
FsFile file;
|
||||||
if (!Storage.openFileForRead("KRS", KOREADER_FILE, file)) {
|
if (!Storage.openFileForRead("KRS", KOREADER_FILE, file)) {
|
||||||
Serial.printf("[%lu] [KRS] No credentials file found\n", millis());
|
LOG_DBG("KRS", "No credentials file found");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ bool KOReaderCredentialStore::loadFromFile() {
|
|||||||
uint8_t version;
|
uint8_t version;
|
||||||
serialization::readPod(file, version);
|
serialization::readPod(file, version);
|
||||||
if (version != KOREADER_FILE_VERSION) {
|
if (version != KOREADER_FILE_VERSION) {
|
||||||
Serial.printf("[%lu] [KRS] Unknown file version: %u\n", millis(), version);
|
LOG_DBG("KRS", "Unknown file version: %u", version);
|
||||||
file.close();
|
file.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -110,14 +110,14 @@ bool KOReaderCredentialStore::loadFromFile() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
Serial.printf("[%lu] [KRS] Loaded KOReader credentials for user: %s\n", millis(), username.c_str());
|
LOG_DBG("KRS", "Loaded KOReader credentials for user: %s", username.c_str());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void KOReaderCredentialStore::setCredentials(const std::string& user, const std::string& pass) {
|
void KOReaderCredentialStore::setCredentials(const std::string& user, const std::string& pass) {
|
||||||
username = user;
|
username = user;
|
||||||
password = pass;
|
password = pass;
|
||||||
Serial.printf("[%lu] [KRS] Set credentials for user: %s\n", millis(), user.c_str());
|
LOG_DBG("KRS", "Set credentials for user: %s", user.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string KOReaderCredentialStore::getMd5Password() const {
|
std::string KOReaderCredentialStore::getMd5Password() const {
|
||||||
@@ -140,12 +140,12 @@ void KOReaderCredentialStore::clearCredentials() {
|
|||||||
username.clear();
|
username.clear();
|
||||||
password.clear();
|
password.clear();
|
||||||
saveToFile();
|
saveToFile();
|
||||||
Serial.printf("[%lu] [KRS] Cleared KOReader credentials\n", millis());
|
LOG_DBG("KRS", "Cleared KOReader credentials");
|
||||||
}
|
}
|
||||||
|
|
||||||
void KOReaderCredentialStore::setServerUrl(const std::string& url) {
|
void KOReaderCredentialStore::setServerUrl(const std::string& url) {
|
||||||
serverUrl = url;
|
serverUrl = url;
|
||||||
Serial.printf("[%lu] [KRS] Set server URL: %s\n", millis(), url.empty() ? "(default)" : url.c_str());
|
LOG_DBG("KRS", "Set server URL: %s", url.empty() ? "(default)" : url.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string KOReaderCredentialStore::getBaseUrl() const {
|
std::string KOReaderCredentialStore::getBaseUrl() const {
|
||||||
@@ -163,6 +163,5 @@ std::string KOReaderCredentialStore::getBaseUrl() const {
|
|||||||
|
|
||||||
void KOReaderCredentialStore::setMatchMethod(DocumentMatchMethod method) {
|
void KOReaderCredentialStore::setMatchMethod(DocumentMatchMethod method) {
|
||||||
matchMethod = method;
|
matchMethod = method;
|
||||||
Serial.printf("[%lu] [KRS] Set match method: %s\n", millis(),
|
LOG_DBG("KRS", "Set match method: %s", method == DocumentMatchMethod::FILENAME ? "Filename" : "Binary");
|
||||||
method == DocumentMatchMethod::FILENAME ? "Filename" : "Binary");
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "KOReaderDocumentId.h"
|
#include "KOReaderDocumentId.h"
|
||||||
|
|
||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
#include <HardwareSerial.h>
|
#include <Logging.h>
|
||||||
#include <MD5Builder.h>
|
#include <MD5Builder.h>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -27,7 +27,7 @@ std::string KOReaderDocumentId::calculateFromFilename(const std::string& filePat
|
|||||||
md5.calculate();
|
md5.calculate();
|
||||||
|
|
||||||
std::string result = md5.toString().c_str();
|
std::string result = md5.toString().c_str();
|
||||||
Serial.printf("[%lu] [KODoc] Filename hash: %s (from '%s')\n", millis(), result.c_str(), filename.c_str());
|
LOG_DBG("KODoc", "Filename hash: %s (from '%s')", result.c_str(), filename.c_str());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,12 +44,12 @@ size_t KOReaderDocumentId::getOffset(int i) {
|
|||||||
std::string KOReaderDocumentId::calculate(const std::string& filePath) {
|
std::string KOReaderDocumentId::calculate(const std::string& filePath) {
|
||||||
FsFile file;
|
FsFile file;
|
||||||
if (!Storage.openFileForRead("KODoc", filePath, file)) {
|
if (!Storage.openFileForRead("KODoc", filePath, file)) {
|
||||||
Serial.printf("[%lu] [KODoc] Failed to open file: %s\n", millis(), filePath.c_str());
|
LOG_DBG("KODoc", "Failed to open file: %s", filePath.c_str());
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const size_t fileSize = file.fileSize();
|
const size_t fileSize = file.fileSize();
|
||||||
Serial.printf("[%lu] [KODoc] Calculating hash for file: %s (size: %zu)\n", millis(), filePath.c_str(), fileSize);
|
LOG_DBG("KODoc", "Calculating hash for file: %s (size: %zu)", filePath.c_str(), fileSize);
|
||||||
|
|
||||||
// Initialize MD5 builder
|
// Initialize MD5 builder
|
||||||
MD5Builder md5;
|
MD5Builder md5;
|
||||||
@@ -70,7 +70,7 @@ std::string KOReaderDocumentId::calculate(const std::string& filePath) {
|
|||||||
|
|
||||||
// Seek to offset
|
// Seek to offset
|
||||||
if (!file.seekSet(offset)) {
|
if (!file.seekSet(offset)) {
|
||||||
Serial.printf("[%lu] [KODoc] Failed to seek to offset %zu\n", millis(), offset);
|
LOG_DBG("KODoc", "Failed to seek to offset %zu", offset);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ std::string KOReaderDocumentId::calculate(const std::string& filePath) {
|
|||||||
md5.calculate();
|
md5.calculate();
|
||||||
std::string result = md5.toString().c_str();
|
std::string result = md5.toString().c_str();
|
||||||
|
|
||||||
Serial.printf("[%lu] [KODoc] Hash calculated: %s (from %zu bytes)\n", millis(), result.c_str(), totalBytesRead);
|
LOG_DBG("KODoc", "Hash calculated: %s (from %zu bytes)", result.c_str(), totalBytesRead);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include <HTTPClient.h>
|
#include <HTTPClient.h>
|
||||||
#include <HardwareSerial.h>
|
#include <Logging.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <WiFiClientSecure.h>
|
#include <WiFiClientSecure.h>
|
||||||
|
|
||||||
@@ -30,12 +30,12 @@ bool isHttpsUrl(const std::string& url) { return url.rfind("https://", 0) == 0;
|
|||||||
|
|
||||||
KOReaderSyncClient::Error KOReaderSyncClient::authenticate() {
|
KOReaderSyncClient::Error KOReaderSyncClient::authenticate() {
|
||||||
if (!KOREADER_STORE.hasCredentials()) {
|
if (!KOREADER_STORE.hasCredentials()) {
|
||||||
Serial.printf("[%lu] [KOSync] No credentials configured\n", millis());
|
LOG_DBG("KOSync", "No credentials configured");
|
||||||
return NO_CREDENTIALS;
|
return NO_CREDENTIALS;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string url = KOREADER_STORE.getBaseUrl() + "/users/auth";
|
std::string url = KOREADER_STORE.getBaseUrl() + "/users/auth";
|
||||||
Serial.printf("[%lu] [KOSync] Authenticating: %s\n", millis(), url.c_str());
|
LOG_DBG("KOSync", "Authenticating: %s", url.c_str());
|
||||||
|
|
||||||
HTTPClient http;
|
HTTPClient http;
|
||||||
std::unique_ptr<WiFiClientSecure> secureClient;
|
std::unique_ptr<WiFiClientSecure> secureClient;
|
||||||
@@ -53,7 +53,7 @@ KOReaderSyncClient::Error KOReaderSyncClient::authenticate() {
|
|||||||
const int httpCode = http.GET();
|
const int httpCode = http.GET();
|
||||||
http.end();
|
http.end();
|
||||||
|
|
||||||
Serial.printf("[%lu] [KOSync] Auth response: %d\n", millis(), httpCode);
|
LOG_DBG("KOSync", "Auth response: %d", httpCode);
|
||||||
|
|
||||||
if (httpCode == 200) {
|
if (httpCode == 200) {
|
||||||
return OK;
|
return OK;
|
||||||
@@ -68,12 +68,12 @@ KOReaderSyncClient::Error KOReaderSyncClient::authenticate() {
|
|||||||
KOReaderSyncClient::Error KOReaderSyncClient::getProgress(const std::string& documentHash,
|
KOReaderSyncClient::Error KOReaderSyncClient::getProgress(const std::string& documentHash,
|
||||||
KOReaderProgress& outProgress) {
|
KOReaderProgress& outProgress) {
|
||||||
if (!KOREADER_STORE.hasCredentials()) {
|
if (!KOREADER_STORE.hasCredentials()) {
|
||||||
Serial.printf("[%lu] [KOSync] No credentials configured\n", millis());
|
LOG_DBG("KOSync", "No credentials configured");
|
||||||
return NO_CREDENTIALS;
|
return NO_CREDENTIALS;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string url = KOREADER_STORE.getBaseUrl() + "/syncs/progress/" + documentHash;
|
std::string url = KOREADER_STORE.getBaseUrl() + "/syncs/progress/" + documentHash;
|
||||||
Serial.printf("[%lu] [KOSync] Getting progress: %s\n", millis(), url.c_str());
|
LOG_DBG("KOSync", "Getting progress: %s", url.c_str());
|
||||||
|
|
||||||
HTTPClient http;
|
HTTPClient http;
|
||||||
std::unique_ptr<WiFiClientSecure> secureClient;
|
std::unique_ptr<WiFiClientSecure> secureClient;
|
||||||
@@ -99,7 +99,7 @@ KOReaderSyncClient::Error KOReaderSyncClient::getProgress(const std::string& doc
|
|||||||
const DeserializationError error = deserializeJson(doc, responseBody);
|
const DeserializationError error = deserializeJson(doc, responseBody);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
Serial.printf("[%lu] [KOSync] JSON parse failed: %s\n", millis(), error.c_str());
|
LOG_ERR("KOSync", "JSON parse failed: %s", error.c_str());
|
||||||
return JSON_ERROR;
|
return JSON_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,14 +110,13 @@ KOReaderSyncClient::Error KOReaderSyncClient::getProgress(const std::string& doc
|
|||||||
outProgress.deviceId = doc["device_id"].as<std::string>();
|
outProgress.deviceId = doc["device_id"].as<std::string>();
|
||||||
outProgress.timestamp = doc["timestamp"].as<int64_t>();
|
outProgress.timestamp = doc["timestamp"].as<int64_t>();
|
||||||
|
|
||||||
Serial.printf("[%lu] [KOSync] Got progress: %.2f%% at %s\n", millis(), outProgress.percentage * 100,
|
LOG_DBG("KOSync", "Got progress: %.2f%% at %s", outProgress.percentage * 100, outProgress.progress.c_str());
|
||||||
outProgress.progress.c_str());
|
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
http.end();
|
http.end();
|
||||||
|
|
||||||
Serial.printf("[%lu] [KOSync] Get progress response: %d\n", millis(), httpCode);
|
LOG_DBG("KOSync", "Get progress response: %d", httpCode);
|
||||||
|
|
||||||
if (httpCode == 401) {
|
if (httpCode == 401) {
|
||||||
return AUTH_FAILED;
|
return AUTH_FAILED;
|
||||||
@@ -131,12 +130,12 @@ KOReaderSyncClient::Error KOReaderSyncClient::getProgress(const std::string& doc
|
|||||||
|
|
||||||
KOReaderSyncClient::Error KOReaderSyncClient::updateProgress(const KOReaderProgress& progress) {
|
KOReaderSyncClient::Error KOReaderSyncClient::updateProgress(const KOReaderProgress& progress) {
|
||||||
if (!KOREADER_STORE.hasCredentials()) {
|
if (!KOREADER_STORE.hasCredentials()) {
|
||||||
Serial.printf("[%lu] [KOSync] No credentials configured\n", millis());
|
LOG_DBG("KOSync", "No credentials configured");
|
||||||
return NO_CREDENTIALS;
|
return NO_CREDENTIALS;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string url = KOREADER_STORE.getBaseUrl() + "/syncs/progress";
|
std::string url = KOREADER_STORE.getBaseUrl() + "/syncs/progress";
|
||||||
Serial.printf("[%lu] [KOSync] Updating progress: %s\n", millis(), url.c_str());
|
LOG_DBG("KOSync", "Updating progress: %s", url.c_str());
|
||||||
|
|
||||||
HTTPClient http;
|
HTTPClient http;
|
||||||
std::unique_ptr<WiFiClientSecure> secureClient;
|
std::unique_ptr<WiFiClientSecure> secureClient;
|
||||||
@@ -163,12 +162,12 @@ KOReaderSyncClient::Error KOReaderSyncClient::updateProgress(const KOReaderProgr
|
|||||||
std::string body;
|
std::string body;
|
||||||
serializeJson(doc, body);
|
serializeJson(doc, body);
|
||||||
|
|
||||||
Serial.printf("[%lu] [KOSync] Request body: %s\n", millis(), body.c_str());
|
LOG_DBG("KOSync", "Request body: %s", body.c_str());
|
||||||
|
|
||||||
const int httpCode = http.PUT(body.c_str());
|
const int httpCode = http.PUT(body.c_str());
|
||||||
http.end();
|
http.end();
|
||||||
|
|
||||||
Serial.printf("[%lu] [KOSync] Update progress response: %d\n", millis(), httpCode);
|
LOG_DBG("KOSync", "Update progress response: %d", httpCode);
|
||||||
|
|
||||||
if (httpCode == 200 || httpCode == 202) {
|
if (httpCode == 200 || httpCode == 202) {
|
||||||
return OK;
|
return OK;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "ProgressMapper.h"
|
#include "ProgressMapper.h"
|
||||||
|
|
||||||
#include <HardwareSerial.h>
|
#include <Logging.h>
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
@@ -23,8 +23,8 @@ KOReaderPosition ProgressMapper::toKOReader(const std::shared_ptr<Epub>& epub, c
|
|||||||
const int tocIndex = epub->getTocIndexForSpineIndex(pos.spineIndex);
|
const int tocIndex = epub->getTocIndexForSpineIndex(pos.spineIndex);
|
||||||
const std::string chapterName = (tocIndex >= 0) ? epub->getTocItem(tocIndex).title : "unknown";
|
const std::string chapterName = (tocIndex >= 0) ? epub->getTocItem(tocIndex).title : "unknown";
|
||||||
|
|
||||||
Serial.printf("[%lu] [ProgressMapper] CrossPoint -> KOReader: chapter='%s', page=%d/%d -> %.2f%% at %s\n", millis(),
|
LOG_DBG("ProgressMapper", "CrossPoint -> KOReader: chapter='%s', page=%d/%d -> %.2f%% at %s", chapterName.c_str(),
|
||||||
chapterName.c_str(), pos.pageNumber, pos.totalPages, result.percentage * 100, result.xpath.c_str());
|
pos.pageNumber, pos.totalPages, result.percentage * 100, result.xpath.c_str());
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -76,8 +76,8 @@ CrossPointPosition ProgressMapper::toCrossPoint(const std::shared_ptr<Epub>& epu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [ProgressMapper] KOReader -> CrossPoint: %.2f%% at %s -> spine=%d, page=%d\n", millis(),
|
LOG_DBG("ProgressMapper", "KOReader -> CrossPoint: %.2f%% at %s -> spine=%d, page=%d", koPos.percentage * 100,
|
||||||
koPos.percentage * 100, koPos.xpath.c_str(), result.spineIndex, result.pageNumber);
|
koPos.xpath.c_str(), result.spineIndex, result.pageNumber);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
47
lib/Logging/Logging.cpp
Normal file
47
lib/Logging/Logging.cpp
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#include "Logging.h"
|
||||||
|
|
||||||
|
// Since logging can take a large amount of flash, we want to make the format string as short as possible.
|
||||||
|
// This logPrintf prepend the timestamp, level and origin to the user-provided message, so that the user only needs to
|
||||||
|
// provide the format string for the message itself.
|
||||||
|
void logPrintf(const char* level, const char* origin, const char* format, ...) {
|
||||||
|
if (!logSerial) {
|
||||||
|
return; // Serial not initialized, skip logging
|
||||||
|
}
|
||||||
|
va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
char buf[256];
|
||||||
|
char* c = buf;
|
||||||
|
// add the timestamp
|
||||||
|
{
|
||||||
|
unsigned long ms = millis();
|
||||||
|
int len = snprintf(c, sizeof(buf), "[%lu] ", ms);
|
||||||
|
if (len < 0) {
|
||||||
|
return; // encoding error, skip logging
|
||||||
|
}
|
||||||
|
c += len;
|
||||||
|
}
|
||||||
|
// add the level
|
||||||
|
{
|
||||||
|
const char* p = level;
|
||||||
|
size_t remaining = sizeof(buf) - (c - buf);
|
||||||
|
while (*p && remaining > 1) {
|
||||||
|
*c++ = *p++;
|
||||||
|
remaining--;
|
||||||
|
}
|
||||||
|
if (remaining > 1) {
|
||||||
|
*c++ = ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// add the origin
|
||||||
|
{
|
||||||
|
int len = snprintf(c, sizeof(buf) - (c - buf), "[%s] ", origin);
|
||||||
|
if (len < 0) {
|
||||||
|
return; // encoding error, skip logging
|
||||||
|
}
|
||||||
|
c += len;
|
||||||
|
}
|
||||||
|
// add the user message
|
||||||
|
vsnprintf(c, sizeof(buf) - (c - buf), format, args);
|
||||||
|
va_end(args);
|
||||||
|
logSerial.print(buf);
|
||||||
|
}
|
||||||
71
lib/Logging/Logging.h
Normal file
71
lib/Logging/Logging.h
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <HardwareSerial.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
Define ENABLE_SERIAL_LOG to enable logging
|
||||||
|
Can be set in platformio.ini build_flags or as a compile definition
|
||||||
|
|
||||||
|
Define LOG_LEVEL to control log verbosity:
|
||||||
|
0 = ERR only
|
||||||
|
1 = ERR + INF
|
||||||
|
2 = ERR + INF + DBG
|
||||||
|
If not defined, defaults to 0
|
||||||
|
|
||||||
|
If you have a legitimate need for raw Serial access (e.g., binary data,
|
||||||
|
special formatting), use the underlying logSerial object directly:
|
||||||
|
logSerial.printf("Special case: %d\n", value);
|
||||||
|
logSerial.write(binaryData, length);
|
||||||
|
|
||||||
|
The logSerial reference (defined below) points to the real Serial object and
|
||||||
|
won't trigger deprecation warnings.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LOG_LEVEL
|
||||||
|
#define LOG_LEVEL 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static HWCDC& logSerial = Serial;
|
||||||
|
|
||||||
|
void logPrintf(const char* level, const char* origin, const char* format, ...);
|
||||||
|
|
||||||
|
#ifdef ENABLE_SERIAL_LOG
|
||||||
|
#if LOG_LEVEL >= 0
|
||||||
|
#define LOG_ERR(origin, format, ...) logPrintf("[ERR]", origin, format "\n", ##__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define LOG_ERR(origin, format, ...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if LOG_LEVEL >= 1
|
||||||
|
#define LOG_INF(origin, format, ...) logPrintf("[INF]", origin, format "\n", ##__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define LOG_INF(origin, format, ...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if LOG_LEVEL >= 2
|
||||||
|
#define LOG_DBG(origin, format, ...) logPrintf("[DBG]", origin, format "\n", ##__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define LOG_DBG(origin, format, ...)
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#define LOG_DBG(origin, format, ...)
|
||||||
|
#define LOG_ERR(origin, format, ...)
|
||||||
|
#define LOG_INF(origin, format, ...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class MySerialImpl : public Print {
|
||||||
|
public:
|
||||||
|
void begin(unsigned long baud) { logSerial.begin(baud); }
|
||||||
|
|
||||||
|
// Support boolean conversion for compatibility with code like:
|
||||||
|
// if (Serial) or while (!Serial)
|
||||||
|
operator bool() const { return logSerial; }
|
||||||
|
|
||||||
|
__attribute__((deprecated("Use LOG_* macro instead"))) size_t printf(const char* format, ...);
|
||||||
|
size_t write(uint8_t b) override;
|
||||||
|
size_t write(const uint8_t* buffer, size_t size) override;
|
||||||
|
void flush() override;
|
||||||
|
static MySerialImpl instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define Serial MySerialImpl::instance
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "OpdsParser.h"
|
#include "OpdsParser.h"
|
||||||
|
|
||||||
#include <HardwareSerial.h>
|
#include <Logging.h>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ OpdsParser::OpdsParser() {
|
|||||||
parser = XML_ParserCreate(nullptr);
|
parser = XML_ParserCreate(nullptr);
|
||||||
if (!parser) {
|
if (!parser) {
|
||||||
errorOccured = true;
|
errorOccured = true;
|
||||||
Serial.printf("[%lu] [OPDS] Couldn't allocate memory for parser\n", millis());
|
LOG_DBG("OPDS", "Couldn't allocate memory for parser");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ size_t OpdsParser::write(const uint8_t* xmlData, const size_t length) {
|
|||||||
void* const buf = XML_GetBuffer(parser, chunkSize);
|
void* const buf = XML_GetBuffer(parser, chunkSize);
|
||||||
if (!buf) {
|
if (!buf) {
|
||||||
errorOccured = true;
|
errorOccured = true;
|
||||||
Serial.printf("[%lu] [OPDS] Couldn't allocate memory for buffer\n", millis());
|
LOG_DBG("OPDS", "Couldn't allocate memory for buffer");
|
||||||
XML_ParserFree(parser);
|
XML_ParserFree(parser);
|
||||||
parser = nullptr;
|
parser = nullptr;
|
||||||
return length;
|
return length;
|
||||||
@@ -53,8 +53,8 @@ size_t OpdsParser::write(const uint8_t* xmlData, const size_t length) {
|
|||||||
|
|
||||||
if (XML_ParseBuffer(parser, static_cast<int>(toRead), 0) == XML_STATUS_ERROR) {
|
if (XML_ParseBuffer(parser, static_cast<int>(toRead), 0) == XML_STATUS_ERROR) {
|
||||||
errorOccured = true;
|
errorOccured = true;
|
||||||
Serial.printf("[%lu] [OPDS] Parse error at line %lu: %s\n", millis(), XML_GetCurrentLineNumber(parser),
|
LOG_DBG("OPDS", "Parse error at line %lu: %s", XML_GetCurrentLineNumber(parser),
|
||||||
XML_ErrorString(XML_GetErrorCode(parser)));
|
XML_ErrorString(XML_GetErrorCode(parser)));
|
||||||
XML_ParserFree(parser);
|
XML_ParserFree(parser);
|
||||||
parser = nullptr;
|
parser = nullptr;
|
||||||
return length;
|
return length;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <FsHelpers.h>
|
#include <FsHelpers.h>
|
||||||
#include <JpegToBmpConverter.h>
|
#include <JpegToBmpConverter.h>
|
||||||
|
#include <Logging.h>
|
||||||
|
|
||||||
Txt::Txt(std::string path, std::string cacheBasePath)
|
Txt::Txt(std::string path, std::string cacheBasePath)
|
||||||
: filepath(std::move(path)), cacheBasePath(std::move(cacheBasePath)) {
|
: filepath(std::move(path)), cacheBasePath(std::move(cacheBasePath)) {
|
||||||
@@ -16,13 +17,13 @@ bool Txt::load() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!Storage.exists(filepath.c_str())) {
|
if (!Storage.exists(filepath.c_str())) {
|
||||||
Serial.printf("[%lu] [TXT] File does not exist: %s\n", millis(), filepath.c_str());
|
LOG_ERR("TXT", "File does not exist: %s", filepath.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
FsFile file;
|
FsFile file;
|
||||||
if (!Storage.openFileForRead("TXT", filepath, file)) {
|
if (!Storage.openFileForRead("TXT", filepath, file)) {
|
||||||
Serial.printf("[%lu] [TXT] Failed to open file: %s\n", millis(), filepath.c_str());
|
LOG_ERR("TXT", "Failed to open file: %s", filepath.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +31,7 @@ bool Txt::load() {
|
|||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
loaded = true;
|
loaded = true;
|
||||||
Serial.printf("[%lu] [TXT] Loaded TXT file: %s (%zu bytes)\n", millis(), filepath.c_str(), fileSize);
|
LOG_DBG("TXT", "Loaded TXT file: %s (%zu bytes)", filepath.c_str(), fileSize);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +75,7 @@ std::string Txt::findCoverImage() const {
|
|||||||
for (const auto& ext : extensions) {
|
for (const auto& ext : extensions) {
|
||||||
std::string coverPath = folder + "/" + baseName + ext;
|
std::string coverPath = folder + "/" + baseName + ext;
|
||||||
if (Storage.exists(coverPath.c_str())) {
|
if (Storage.exists(coverPath.c_str())) {
|
||||||
Serial.printf("[%lu] [TXT] Found matching cover image: %s\n", millis(), coverPath.c_str());
|
LOG_DBG("TXT", "Found matching cover image: %s", coverPath.c_str());
|
||||||
return coverPath;
|
return coverPath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,7 +86,7 @@ std::string Txt::findCoverImage() const {
|
|||||||
for (const auto& ext : extensions) {
|
for (const auto& ext : extensions) {
|
||||||
std::string coverPath = folder + "/" + std::string(name) + ext;
|
std::string coverPath = folder + "/" + std::string(name) + ext;
|
||||||
if (Storage.exists(coverPath.c_str())) {
|
if (Storage.exists(coverPath.c_str())) {
|
||||||
Serial.printf("[%lu] [TXT] Found fallback cover image: %s\n", millis(), coverPath.c_str());
|
LOG_DBG("TXT", "Found fallback cover image: %s", coverPath.c_str());
|
||||||
return coverPath;
|
return coverPath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,7 +105,7 @@ bool Txt::generateCoverBmp() const {
|
|||||||
|
|
||||||
std::string coverImagePath = findCoverImage();
|
std::string coverImagePath = findCoverImage();
|
||||||
if (coverImagePath.empty()) {
|
if (coverImagePath.empty()) {
|
||||||
Serial.printf("[%lu] [TXT] No cover image found for TXT file\n", millis());
|
LOG_DBG("TXT", "No cover image found for TXT file");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +121,7 @@ bool Txt::generateCoverBmp() const {
|
|||||||
|
|
||||||
if (isBmp) {
|
if (isBmp) {
|
||||||
// Copy BMP file to cache
|
// Copy BMP file to cache
|
||||||
Serial.printf("[%lu] [TXT] Copying BMP cover image to cache\n", millis());
|
LOG_DBG("TXT", "Copying BMP cover image to cache");
|
||||||
FsFile src, dst;
|
FsFile src, dst;
|
||||||
if (!Storage.openFileForRead("TXT", coverImagePath, src)) {
|
if (!Storage.openFileForRead("TXT", coverImagePath, src)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -136,13 +137,13 @@ bool Txt::generateCoverBmp() const {
|
|||||||
}
|
}
|
||||||
src.close();
|
src.close();
|
||||||
dst.close();
|
dst.close();
|
||||||
Serial.printf("[%lu] [TXT] Copied BMP cover to cache\n", millis());
|
LOG_DBG("TXT", "Copied BMP cover to cache");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isJpg) {
|
if (isJpg) {
|
||||||
// Convert JPG/JPEG to BMP (same approach as Epub)
|
// Convert JPG/JPEG to BMP (same approach as Epub)
|
||||||
Serial.printf("[%lu] [TXT] Generating BMP from JPG cover image\n", millis());
|
LOG_DBG("TXT", "Generating BMP from JPG cover image");
|
||||||
FsFile coverJpg, coverBmp;
|
FsFile coverJpg, coverBmp;
|
||||||
if (!Storage.openFileForRead("TXT", coverImagePath, coverJpg)) {
|
if (!Storage.openFileForRead("TXT", coverImagePath, coverJpg)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -156,16 +157,16 @@ bool Txt::generateCoverBmp() const {
|
|||||||
coverBmp.close();
|
coverBmp.close();
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
Serial.printf("[%lu] [TXT] Failed to generate BMP from JPG cover image\n", millis());
|
LOG_ERR("TXT", "Failed to generate BMP from JPG cover image");
|
||||||
Storage.remove(getCoverBmpPath().c_str());
|
Storage.remove(getCoverBmpPath().c_str());
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("[%lu] [TXT] Generated BMP from JPG cover image\n", millis());
|
LOG_DBG("TXT", "Generated BMP from JPG cover image");
|
||||||
}
|
}
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
// PNG files are not supported (would need a PNG decoder)
|
// PNG files are not supported (would need a PNG decoder)
|
||||||
Serial.printf("[%lu] [TXT] Cover image format not supported (only BMP/JPG/JPEG)\n", millis());
|
LOG_ERR("TXT", "Cover image format not supported (only BMP/JPG/JPEG)");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
#include "Xtc.h"
|
#include "Xtc.h"
|
||||||
|
|
||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
#include <HardwareSerial.h>
|
#include <Logging.h>
|
||||||
|
|
||||||
bool Xtc::load() {
|
bool Xtc::load() {
|
||||||
Serial.printf("[%lu] [XTC] Loading XTC: %s\n", millis(), filepath.c_str());
|
LOG_DBG("XTC", "Loading XTC: %s", filepath.c_str());
|
||||||
|
|
||||||
// Initialize parser
|
// Initialize parser
|
||||||
parser.reset(new xtc::XtcParser());
|
parser.reset(new xtc::XtcParser());
|
||||||
@@ -19,28 +19,28 @@ bool Xtc::load() {
|
|||||||
// Open XTC file
|
// Open XTC file
|
||||||
xtc::XtcError err = parser->open(filepath.c_str());
|
xtc::XtcError err = parser->open(filepath.c_str());
|
||||||
if (err != xtc::XtcError::OK) {
|
if (err != xtc::XtcError::OK) {
|
||||||
Serial.printf("[%lu] [XTC] Failed to load: %s\n", millis(), xtc::errorToString(err));
|
LOG_ERR("XTC", "Failed to load: %s", xtc::errorToString(err));
|
||||||
parser.reset();
|
parser.reset();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
loaded = true;
|
loaded = true;
|
||||||
Serial.printf("[%lu] [XTC] Loaded XTC: %s (%lu pages)\n", millis(), filepath.c_str(), parser->getPageCount());
|
LOG_DBG("XTC", "Loaded XTC: %s (%lu pages)", filepath.c_str(), parser->getPageCount());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Xtc::clearCache() const {
|
bool Xtc::clearCache() const {
|
||||||
if (!Storage.exists(cachePath.c_str())) {
|
if (!Storage.exists(cachePath.c_str())) {
|
||||||
Serial.printf("[%lu] [XTC] Cache does not exist, no action needed\n", millis());
|
LOG_DBG("XTC", "Cache does not exist, no action needed");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Storage.removeDir(cachePath.c_str())) {
|
if (!Storage.removeDir(cachePath.c_str())) {
|
||||||
Serial.printf("[%lu] [XTC] Failed to clear cache\n", millis());
|
LOG_ERR("XTC", "Failed to clear cache");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [XTC] Cache cleared successfully\n", millis());
|
LOG_DBG("XTC", "Cache cleared successfully");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,12 +119,12 @@ bool Xtc::generateCoverBmp() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!loaded || !parser) {
|
if (!loaded || !parser) {
|
||||||
Serial.printf("[%lu] [XTC] Cannot generate cover BMP, file not loaded\n", millis());
|
LOG_ERR("XTC", "Cannot generate cover BMP, file not loaded");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parser->getPageCount() == 0) {
|
if (parser->getPageCount() == 0) {
|
||||||
Serial.printf("[%lu] [XTC] No pages in XTC file\n", millis());
|
LOG_ERR("XTC", "No pages in XTC file");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,7 +134,7 @@ bool Xtc::generateCoverBmp() const {
|
|||||||
// Get first page info for cover
|
// Get first page info for cover
|
||||||
xtc::PageInfo pageInfo;
|
xtc::PageInfo pageInfo;
|
||||||
if (!parser->getPageInfo(0, pageInfo)) {
|
if (!parser->getPageInfo(0, pageInfo)) {
|
||||||
Serial.printf("[%lu] [XTC] Failed to get first page info\n", millis());
|
LOG_DBG("XTC", "Failed to get first page info");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,14 +152,14 @@ bool Xtc::generateCoverBmp() const {
|
|||||||
}
|
}
|
||||||
uint8_t* pageBuffer = static_cast<uint8_t*>(malloc(bitmapSize));
|
uint8_t* pageBuffer = static_cast<uint8_t*>(malloc(bitmapSize));
|
||||||
if (!pageBuffer) {
|
if (!pageBuffer) {
|
||||||
Serial.printf("[%lu] [XTC] Failed to allocate page buffer (%lu bytes)\n", millis(), bitmapSize);
|
LOG_ERR("XTC", "Failed to allocate page buffer (%lu bytes)", bitmapSize);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load first page (cover)
|
// Load first page (cover)
|
||||||
size_t bytesRead = const_cast<xtc::XtcParser*>(parser.get())->loadPage(0, pageBuffer, bitmapSize);
|
size_t bytesRead = const_cast<xtc::XtcParser*>(parser.get())->loadPage(0, pageBuffer, bitmapSize);
|
||||||
if (bytesRead == 0) {
|
if (bytesRead == 0) {
|
||||||
Serial.printf("[%lu] [XTC] Failed to load cover page\n", millis());
|
LOG_ERR("XTC", "Failed to load cover page");
|
||||||
free(pageBuffer);
|
free(pageBuffer);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -167,7 +167,7 @@ bool Xtc::generateCoverBmp() const {
|
|||||||
// Create BMP file
|
// Create BMP file
|
||||||
FsFile coverBmp;
|
FsFile coverBmp;
|
||||||
if (!Storage.openFileForWrite("XTC", getCoverBmpPath(), coverBmp)) {
|
if (!Storage.openFileForWrite("XTC", getCoverBmpPath(), coverBmp)) {
|
||||||
Serial.printf("[%lu] [XTC] Failed to create cover BMP file\n", millis());
|
LOG_DBG("XTC", "Failed to create cover BMP file");
|
||||||
free(pageBuffer);
|
free(pageBuffer);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -297,7 +297,7 @@ bool Xtc::generateCoverBmp() const {
|
|||||||
coverBmp.close();
|
coverBmp.close();
|
||||||
free(pageBuffer);
|
free(pageBuffer);
|
||||||
|
|
||||||
Serial.printf("[%lu] [XTC] Generated cover BMP: %s\n", millis(), getCoverBmpPath().c_str());
|
LOG_DBG("XTC", "Generated cover BMP: %s", getCoverBmpPath().c_str());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,12 +311,12 @@ bool Xtc::generateThumbBmp(int height) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!loaded || !parser) {
|
if (!loaded || !parser) {
|
||||||
Serial.printf("[%lu] [XTC] Cannot generate thumb BMP, file not loaded\n", millis());
|
LOG_ERR("XTC", "Cannot generate thumb BMP, file not loaded");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parser->getPageCount() == 0) {
|
if (parser->getPageCount() == 0) {
|
||||||
Serial.printf("[%lu] [XTC] No pages in XTC file\n", millis());
|
LOG_ERR("XTC", "No pages in XTC file");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,7 +326,7 @@ bool Xtc::generateThumbBmp(int height) const {
|
|||||||
// Get first page info for cover
|
// Get first page info for cover
|
||||||
xtc::PageInfo pageInfo;
|
xtc::PageInfo pageInfo;
|
||||||
if (!parser->getPageInfo(0, pageInfo)) {
|
if (!parser->getPageInfo(0, pageInfo)) {
|
||||||
Serial.printf("[%lu] [XTC] Failed to get first page info\n", millis());
|
LOG_DBG("XTC", "Failed to get first page info");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,7 +359,7 @@ bool Xtc::generateThumbBmp(int height) const {
|
|||||||
}
|
}
|
||||||
src.close();
|
src.close();
|
||||||
}
|
}
|
||||||
Serial.printf("[%lu] [XTC] Copied cover to thumb (no scaling needed)\n", millis());
|
LOG_DBG("XTC", "Copied cover to thumb (no scaling needed)");
|
||||||
return Storage.exists(getThumbBmpPath(height).c_str());
|
return Storage.exists(getThumbBmpPath(height).c_str());
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -368,8 +368,8 @@ bool Xtc::generateThumbBmp(int height) const {
|
|||||||
uint16_t thumbWidth = static_cast<uint16_t>(pageInfo.width * scale);
|
uint16_t thumbWidth = static_cast<uint16_t>(pageInfo.width * scale);
|
||||||
uint16_t thumbHeight = static_cast<uint16_t>(pageInfo.height * scale);
|
uint16_t thumbHeight = static_cast<uint16_t>(pageInfo.height * scale);
|
||||||
|
|
||||||
Serial.printf("[%lu] [XTC] Generating thumb BMP: %dx%d -> %dx%d (scale: %.3f)\n", millis(), pageInfo.width,
|
LOG_DBG("XTC", "Generating thumb BMP: %dx%d -> %dx%d (scale: %.3f)", pageInfo.width, pageInfo.height, thumbWidth,
|
||||||
pageInfo.height, thumbWidth, thumbHeight, scale);
|
thumbHeight, scale);
|
||||||
|
|
||||||
// Allocate buffer for page data
|
// Allocate buffer for page data
|
||||||
size_t bitmapSize;
|
size_t bitmapSize;
|
||||||
@@ -380,14 +380,14 @@ bool Xtc::generateThumbBmp(int height) const {
|
|||||||
}
|
}
|
||||||
uint8_t* pageBuffer = static_cast<uint8_t*>(malloc(bitmapSize));
|
uint8_t* pageBuffer = static_cast<uint8_t*>(malloc(bitmapSize));
|
||||||
if (!pageBuffer) {
|
if (!pageBuffer) {
|
||||||
Serial.printf("[%lu] [XTC] Failed to allocate page buffer (%lu bytes)\n", millis(), bitmapSize);
|
LOG_ERR("XTC", "Failed to allocate page buffer (%lu bytes)", bitmapSize);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load first page (cover)
|
// Load first page (cover)
|
||||||
size_t bytesRead = const_cast<xtc::XtcParser*>(parser.get())->loadPage(0, pageBuffer, bitmapSize);
|
size_t bytesRead = const_cast<xtc::XtcParser*>(parser.get())->loadPage(0, pageBuffer, bitmapSize);
|
||||||
if (bytesRead == 0) {
|
if (bytesRead == 0) {
|
||||||
Serial.printf("[%lu] [XTC] Failed to load cover page for thumb\n", millis());
|
LOG_ERR("XTC", "Failed to load cover page for thumb");
|
||||||
free(pageBuffer);
|
free(pageBuffer);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -395,7 +395,7 @@ bool Xtc::generateThumbBmp(int height) const {
|
|||||||
// Create thumbnail BMP file - use 1-bit format for fast home screen rendering (no gray passes)
|
// Create thumbnail BMP file - use 1-bit format for fast home screen rendering (no gray passes)
|
||||||
FsFile thumbBmp;
|
FsFile thumbBmp;
|
||||||
if (!Storage.openFileForWrite("XTC", getThumbBmpPath(height), thumbBmp)) {
|
if (!Storage.openFileForWrite("XTC", getThumbBmpPath(height), thumbBmp)) {
|
||||||
Serial.printf("[%lu] [XTC] Failed to create thumb BMP file\n", millis());
|
LOG_DBG("XTC", "Failed to create thumb BMP file");
|
||||||
free(pageBuffer);
|
free(pageBuffer);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -558,8 +558,7 @@ bool Xtc::generateThumbBmp(int height) const {
|
|||||||
thumbBmp.close();
|
thumbBmp.close();
|
||||||
free(pageBuffer);
|
free(pageBuffer);
|
||||||
|
|
||||||
Serial.printf("[%lu] [XTC] Generated thumb BMP (%dx%d): %s\n", millis(), thumbWidth, thumbHeight,
|
LOG_DBG("XTC", "Generated thumb BMP (%dx%d): %s", thumbWidth, thumbHeight, getThumbBmpPath(height).c_str());
|
||||||
getThumbBmpPath(height).c_str());
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
#include <FsHelpers.h>
|
#include <FsHelpers.h>
|
||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
#include <HardwareSerial.h>
|
#include <Logging.h>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ XtcError XtcParser::open(const char* filepath) {
|
|||||||
// Read header
|
// Read header
|
||||||
m_lastError = readHeader();
|
m_lastError = readHeader();
|
||||||
if (m_lastError != XtcError::OK) {
|
if (m_lastError != XtcError::OK) {
|
||||||
Serial.printf("[%lu] [XTC] Failed to read header: %s\n", millis(), errorToString(m_lastError));
|
LOG_DBG("XTC", "Failed to read header: %s", errorToString(m_lastError));
|
||||||
m_file.close();
|
m_file.close();
|
||||||
return m_lastError;
|
return m_lastError;
|
||||||
}
|
}
|
||||||
@@ -51,13 +51,13 @@ XtcError XtcParser::open(const char* filepath) {
|
|||||||
if (m_header.hasMetadata) {
|
if (m_header.hasMetadata) {
|
||||||
m_lastError = readTitle();
|
m_lastError = readTitle();
|
||||||
if (m_lastError != XtcError::OK) {
|
if (m_lastError != XtcError::OK) {
|
||||||
Serial.printf("[%lu] [XTC] Failed to read title: %s\n", millis(), errorToString(m_lastError));
|
LOG_DBG("XTC", "Failed to read title: %s", errorToString(m_lastError));
|
||||||
m_file.close();
|
m_file.close();
|
||||||
return m_lastError;
|
return m_lastError;
|
||||||
}
|
}
|
||||||
m_lastError = readAuthor();
|
m_lastError = readAuthor();
|
||||||
if (m_lastError != XtcError::OK) {
|
if (m_lastError != XtcError::OK) {
|
||||||
Serial.printf("[%lu] [XTC] Failed to read author: %s\n", millis(), errorToString(m_lastError));
|
LOG_DBG("XTC", "Failed to read author: %s", errorToString(m_lastError));
|
||||||
m_file.close();
|
m_file.close();
|
||||||
return m_lastError;
|
return m_lastError;
|
||||||
}
|
}
|
||||||
@@ -66,7 +66,7 @@ XtcError XtcParser::open(const char* filepath) {
|
|||||||
// Read page table
|
// Read page table
|
||||||
m_lastError = readPageTable();
|
m_lastError = readPageTable();
|
||||||
if (m_lastError != XtcError::OK) {
|
if (m_lastError != XtcError::OK) {
|
||||||
Serial.printf("[%lu] [XTC] Failed to read page table: %s\n", millis(), errorToString(m_lastError));
|
LOG_DBG("XTC", "Failed to read page table: %s", errorToString(m_lastError));
|
||||||
m_file.close();
|
m_file.close();
|
||||||
return m_lastError;
|
return m_lastError;
|
||||||
}
|
}
|
||||||
@@ -74,14 +74,13 @@ XtcError XtcParser::open(const char* filepath) {
|
|||||||
// Read chapters if present
|
// Read chapters if present
|
||||||
m_lastError = readChapters();
|
m_lastError = readChapters();
|
||||||
if (m_lastError != XtcError::OK) {
|
if (m_lastError != XtcError::OK) {
|
||||||
Serial.printf("[%lu] [XTC] Failed to read chapters: %s\n", millis(), errorToString(m_lastError));
|
LOG_DBG("XTC", "Failed to read chapters: %s", errorToString(m_lastError));
|
||||||
m_file.close();
|
m_file.close();
|
||||||
return m_lastError;
|
return m_lastError;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_isOpen = true;
|
m_isOpen = true;
|
||||||
Serial.printf("[%lu] [XTC] Opened file: %s (%u pages, %dx%d)\n", millis(), filepath, m_header.pageCount,
|
LOG_DBG("XTC", "Opened file: %s (%u pages, %dx%d)", filepath, m_header.pageCount, m_defaultWidth, m_defaultHeight);
|
||||||
m_defaultWidth, m_defaultHeight);
|
|
||||||
return XtcError::OK;
|
return XtcError::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,8 +105,7 @@ XtcError XtcParser::readHeader() {
|
|||||||
|
|
||||||
// Verify magic number (accept both XTC and XTCH)
|
// Verify magic number (accept both XTC and XTCH)
|
||||||
if (m_header.magic != XTC_MAGIC && m_header.magic != XTCH_MAGIC) {
|
if (m_header.magic != XTC_MAGIC && m_header.magic != XTCH_MAGIC) {
|
||||||
Serial.printf("[%lu] [XTC] Invalid magic: 0x%08X (expected 0x%08X or 0x%08X)\n", millis(), m_header.magic,
|
LOG_DBG("XTC", "Invalid magic: 0x%08X (expected 0x%08X or 0x%08X)", m_header.magic, XTC_MAGIC, XTCH_MAGIC);
|
||||||
XTC_MAGIC, XTCH_MAGIC);
|
|
||||||
return XtcError::INVALID_MAGIC;
|
return XtcError::INVALID_MAGIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +118,7 @@ XtcError XtcParser::readHeader() {
|
|||||||
const bool validVersion = m_header.versionMajor == 1 && m_header.versionMinor == 0 ||
|
const bool validVersion = m_header.versionMajor == 1 && m_header.versionMinor == 0 ||
|
||||||
m_header.versionMajor == 0 && m_header.versionMinor == 1;
|
m_header.versionMajor == 0 && m_header.versionMinor == 1;
|
||||||
if (!validVersion) {
|
if (!validVersion) {
|
||||||
Serial.printf("[%lu] [XTC] Unsupported version: %u.%u\n", millis(), m_header.versionMajor, m_header.versionMinor);
|
LOG_DBG("XTC", "Unsupported version: %u.%u", m_header.versionMajor, m_header.versionMinor);
|
||||||
return XtcError::INVALID_VERSION;
|
return XtcError::INVALID_VERSION;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,9 +127,9 @@ XtcError XtcParser::readHeader() {
|
|||||||
return XtcError::CORRUPTED_HEADER;
|
return XtcError::CORRUPTED_HEADER;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [XTC] Header: magic=0x%08X (%s), ver=%u.%u, pages=%u, bitDepth=%u\n", millis(), m_header.magic,
|
LOG_DBG("XTC", "Header: magic=0x%08X (%s), ver=%u.%u, pages=%u, bitDepth=%u", m_header.magic,
|
||||||
(m_header.magic == XTCH_MAGIC) ? "XTCH" : "XTC", m_header.versionMajor, m_header.versionMinor,
|
(m_header.magic == XTCH_MAGIC) ? "XTCH" : "XTC", m_header.versionMajor, m_header.versionMinor,
|
||||||
m_header.pageCount, m_bitDepth);
|
m_header.pageCount, m_bitDepth);
|
||||||
|
|
||||||
return XtcError::OK;
|
return XtcError::OK;
|
||||||
}
|
}
|
||||||
@@ -146,7 +144,7 @@ XtcError XtcParser::readTitle() {
|
|||||||
m_file.read(titleBuf, sizeof(titleBuf) - 1);
|
m_file.read(titleBuf, sizeof(titleBuf) - 1);
|
||||||
m_title = titleBuf;
|
m_title = titleBuf;
|
||||||
|
|
||||||
Serial.printf("[%lu] [XTC] Title: %s\n", millis(), m_title.c_str());
|
LOG_DBG("XTC", "Title: %s", m_title.c_str());
|
||||||
return XtcError::OK;
|
return XtcError::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,19 +159,19 @@ XtcError XtcParser::readAuthor() {
|
|||||||
m_file.read(authorBuf, sizeof(authorBuf) - 1);
|
m_file.read(authorBuf, sizeof(authorBuf) - 1);
|
||||||
m_author = authorBuf;
|
m_author = authorBuf;
|
||||||
|
|
||||||
Serial.printf("[%lu] [XTC] Author: %s\n", millis(), m_author.c_str());
|
LOG_DBG("XTC", "Author: %s", m_author.c_str());
|
||||||
return XtcError::OK;
|
return XtcError::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
XtcError XtcParser::readPageTable() {
|
XtcError XtcParser::readPageTable() {
|
||||||
if (m_header.pageTableOffset == 0) {
|
if (m_header.pageTableOffset == 0) {
|
||||||
Serial.printf("[%lu] [XTC] Page table offset is 0, cannot read\n", millis());
|
LOG_DBG("XTC", "Page table offset is 0, cannot read");
|
||||||
return XtcError::CORRUPTED_HEADER;
|
return XtcError::CORRUPTED_HEADER;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seek to page table
|
// Seek to page table
|
||||||
if (!m_file.seek(m_header.pageTableOffset)) {
|
if (!m_file.seek(m_header.pageTableOffset)) {
|
||||||
Serial.printf("[%lu] [XTC] Failed to seek to page table at %llu\n", millis(), m_header.pageTableOffset);
|
LOG_DBG("XTC", "Failed to seek to page table at %llu", m_header.pageTableOffset);
|
||||||
return XtcError::READ_ERROR;
|
return XtcError::READ_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +182,7 @@ XtcError XtcParser::readPageTable() {
|
|||||||
PageTableEntry entry;
|
PageTableEntry entry;
|
||||||
size_t bytesRead = m_file.read(reinterpret_cast<uint8_t*>(&entry), sizeof(PageTableEntry));
|
size_t bytesRead = m_file.read(reinterpret_cast<uint8_t*>(&entry), sizeof(PageTableEntry));
|
||||||
if (bytesRead != sizeof(PageTableEntry)) {
|
if (bytesRead != sizeof(PageTableEntry)) {
|
||||||
Serial.printf("[%lu] [XTC] Failed to read page table entry %u\n", millis(), i);
|
LOG_DBG("XTC", "Failed to read page table entry %u", i);
|
||||||
return XtcError::READ_ERROR;
|
return XtcError::READ_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +199,7 @@ XtcError XtcParser::readPageTable() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [XTC] Read %u page table entries\n", millis(), m_header.pageCount);
|
LOG_DBG("XTC", "Read %u page table entries", m_header.pageCount);
|
||||||
return XtcError::OK;
|
return XtcError::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,7 +305,7 @@ XtcError XtcParser::readChapters() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_hasChapters = !m_chapters.empty();
|
m_hasChapters = !m_chapters.empty();
|
||||||
Serial.printf("[%lu] [XTC] Chapters: %u\n", millis(), static_cast<unsigned int>(m_chapters.size()));
|
LOG_DBG("XTC", "Chapters: %u", static_cast<unsigned int>(m_chapters.size()));
|
||||||
return XtcError::OK;
|
return XtcError::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,7 +332,7 @@ size_t XtcParser::loadPage(uint32_t pageIndex, uint8_t* buffer, size_t bufferSiz
|
|||||||
|
|
||||||
// Seek to page data
|
// Seek to page data
|
||||||
if (!m_file.seek(page.offset)) {
|
if (!m_file.seek(page.offset)) {
|
||||||
Serial.printf("[%lu] [XTC] Failed to seek to page %u at offset %lu\n", millis(), pageIndex, page.offset);
|
LOG_DBG("XTC", "Failed to seek to page %u at offset %lu", pageIndex, page.offset);
|
||||||
m_lastError = XtcError::READ_ERROR;
|
m_lastError = XtcError::READ_ERROR;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -343,7 +341,7 @@ size_t XtcParser::loadPage(uint32_t pageIndex, uint8_t* buffer, size_t bufferSiz
|
|||||||
XtgPageHeader pageHeader;
|
XtgPageHeader pageHeader;
|
||||||
size_t headerRead = m_file.read(reinterpret_cast<uint8_t*>(&pageHeader), sizeof(XtgPageHeader));
|
size_t headerRead = m_file.read(reinterpret_cast<uint8_t*>(&pageHeader), sizeof(XtgPageHeader));
|
||||||
if (headerRead != sizeof(XtgPageHeader)) {
|
if (headerRead != sizeof(XtgPageHeader)) {
|
||||||
Serial.printf("[%lu] [XTC] Failed to read page header for page %u\n", millis(), pageIndex);
|
LOG_DBG("XTC", "Failed to read page header for page %u", pageIndex);
|
||||||
m_lastError = XtcError::READ_ERROR;
|
m_lastError = XtcError::READ_ERROR;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -351,8 +349,8 @@ size_t XtcParser::loadPage(uint32_t pageIndex, uint8_t* buffer, size_t bufferSiz
|
|||||||
// Verify page magic (XTG for 1-bit, XTH for 2-bit)
|
// Verify page magic (XTG for 1-bit, XTH for 2-bit)
|
||||||
const uint32_t expectedMagic = (m_bitDepth == 2) ? XTH_MAGIC : XTG_MAGIC;
|
const uint32_t expectedMagic = (m_bitDepth == 2) ? XTH_MAGIC : XTG_MAGIC;
|
||||||
if (pageHeader.magic != expectedMagic) {
|
if (pageHeader.magic != expectedMagic) {
|
||||||
Serial.printf("[%lu] [XTC] Invalid page magic for page %u: 0x%08X (expected 0x%08X)\n", millis(), pageIndex,
|
LOG_DBG("XTC", "Invalid page magic for page %u: 0x%08X (expected 0x%08X)", pageIndex, pageHeader.magic,
|
||||||
pageHeader.magic, expectedMagic);
|
expectedMagic);
|
||||||
m_lastError = XtcError::INVALID_MAGIC;
|
m_lastError = XtcError::INVALID_MAGIC;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -370,7 +368,7 @@ size_t XtcParser::loadPage(uint32_t pageIndex, uint8_t* buffer, size_t bufferSiz
|
|||||||
|
|
||||||
// Check buffer size
|
// Check buffer size
|
||||||
if (bufferSize < bitmapSize) {
|
if (bufferSize < bitmapSize) {
|
||||||
Serial.printf("[%lu] [XTC] Buffer too small: need %u, have %u\n", millis(), bitmapSize, bufferSize);
|
LOG_DBG("XTC", "Buffer too small: need %u, have %u", bitmapSize, bufferSize);
|
||||||
m_lastError = XtcError::MEMORY_ERROR;
|
m_lastError = XtcError::MEMORY_ERROR;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -378,7 +376,7 @@ size_t XtcParser::loadPage(uint32_t pageIndex, uint8_t* buffer, size_t bufferSiz
|
|||||||
// Read bitmap data
|
// Read bitmap data
|
||||||
size_t bytesRead = m_file.read(buffer, bitmapSize);
|
size_t bytesRead = m_file.read(buffer, bitmapSize);
|
||||||
if (bytesRead != bitmapSize) {
|
if (bytesRead != bitmapSize) {
|
||||||
Serial.printf("[%lu] [XTC] Page read error: expected %u, got %u\n", millis(), bitmapSize, bytesRead);
|
LOG_DBG("XTC", "Page read error: expected %u, got %u", bitmapSize, bytesRead);
|
||||||
m_lastError = XtcError::READ_ERROR;
|
m_lastError = XtcError::READ_ERROR;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "ZipFile.h"
|
#include "ZipFile.h"
|
||||||
|
|
||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
#include <HardwareSerial.h>
|
#include <Logging.h>
|
||||||
#include <miniz.h>
|
#include <miniz.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -10,7 +10,7 @@ bool inflateOneShot(const uint8_t* inputBuf, const size_t deflatedSize, uint8_t*
|
|||||||
// Setup inflator
|
// Setup inflator
|
||||||
const auto inflator = static_cast<tinfl_decompressor*>(malloc(sizeof(tinfl_decompressor)));
|
const auto inflator = static_cast<tinfl_decompressor*>(malloc(sizeof(tinfl_decompressor)));
|
||||||
if (!inflator) {
|
if (!inflator) {
|
||||||
Serial.printf("[%lu] [ZIP] Failed to allocate memory for inflator\n", millis());
|
LOG_ERR("ZIP", "Failed to allocate memory for inflator");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
memset(inflator, 0, sizeof(tinfl_decompressor));
|
memset(inflator, 0, sizeof(tinfl_decompressor));
|
||||||
@@ -23,7 +23,7 @@ bool inflateOneShot(const uint8_t* inputBuf, const size_t deflatedSize, uint8_t*
|
|||||||
free(inflator);
|
free(inflator);
|
||||||
|
|
||||||
if (status != TINFL_STATUS_DONE) {
|
if (status != TINFL_STATUS_DONE) {
|
||||||
Serial.printf("[%lu] [ZIP] tinfl_decompress() failed with status %d\n", millis(), status);
|
LOG_ERR("ZIP", "tinfl_decompress() failed with status %d", status);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,13 +195,13 @@ long ZipFile::getDataOffset(const FileStatSlim& fileStat) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (read != localHeaderSize) {
|
if (read != localHeaderSize) {
|
||||||
Serial.printf("[%lu] [ZIP] Something went wrong reading the local header\n", millis());
|
LOG_ERR("ZIP", "Something went wrong reading the local header");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pLocalHeader[0] + (pLocalHeader[1] << 8) + (pLocalHeader[2] << 16) + (pLocalHeader[3] << 24) !=
|
if (pLocalHeader[0] + (pLocalHeader[1] << 8) + (pLocalHeader[2] << 16) + (pLocalHeader[3] << 24) !=
|
||||||
0x04034b50 /* MZ_ZIP_LOCAL_DIR_HEADER_SIG */) {
|
0x04034b50 /* MZ_ZIP_LOCAL_DIR_HEADER_SIG */) {
|
||||||
Serial.printf("[%lu] [ZIP] Not a valid zip file header\n", millis());
|
LOG_ERR("ZIP", "Not a valid zip file header");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +222,7 @@ bool ZipFile::loadZipDetails() {
|
|||||||
|
|
||||||
const size_t fileSize = file.size();
|
const size_t fileSize = file.size();
|
||||||
if (fileSize < 22) {
|
if (fileSize < 22) {
|
||||||
Serial.printf("[%lu] [ZIP] File too small to be a valid zip\n", millis());
|
LOG_ERR("ZIP", "File too small to be a valid zip");
|
||||||
if (!wasOpen) {
|
if (!wasOpen) {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
@@ -234,7 +234,7 @@ bool ZipFile::loadZipDetails() {
|
|||||||
const int scanRange = fileSize > 1024 ? 1024 : fileSize;
|
const int scanRange = fileSize > 1024 ? 1024 : fileSize;
|
||||||
const auto buffer = static_cast<uint8_t*>(malloc(scanRange));
|
const auto buffer = static_cast<uint8_t*>(malloc(scanRange));
|
||||||
if (!buffer) {
|
if (!buffer) {
|
||||||
Serial.printf("[%lu] [ZIP] Failed to allocate memory for EOCD scan buffer\n", millis());
|
LOG_ERR("ZIP", "Failed to allocate memory for EOCD scan buffer");
|
||||||
if (!wasOpen) {
|
if (!wasOpen) {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
@@ -255,7 +255,7 @@ bool ZipFile::loadZipDetails() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (foundOffset == -1) {
|
if (foundOffset == -1) {
|
||||||
Serial.printf("[%lu] [ZIP] EOCD signature not found in zip file\n", millis());
|
LOG_ERR("ZIP", "EOCD signature not found in zip file");
|
||||||
free(buffer);
|
free(buffer);
|
||||||
if (!wasOpen) {
|
if (!wasOpen) {
|
||||||
close();
|
close();
|
||||||
@@ -407,7 +407,7 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo
|
|||||||
const auto dataSize = trailingNullByte ? inflatedDataSize + 1 : inflatedDataSize;
|
const auto dataSize = trailingNullByte ? inflatedDataSize + 1 : inflatedDataSize;
|
||||||
const auto data = static_cast<uint8_t*>(malloc(dataSize));
|
const auto data = static_cast<uint8_t*>(malloc(dataSize));
|
||||||
if (data == nullptr) {
|
if (data == nullptr) {
|
||||||
Serial.printf("[%lu] [ZIP] Failed to allocate memory for output buffer (%zu bytes)\n", millis(), dataSize);
|
LOG_ERR("ZIP", "Failed to allocate memory for output buffer (%zu bytes)", dataSize);
|
||||||
if (!wasOpen) {
|
if (!wasOpen) {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
@@ -422,7 +422,7 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dataRead != inflatedDataSize) {
|
if (dataRead != inflatedDataSize) {
|
||||||
Serial.printf("[%lu] [ZIP] Failed to read data\n", millis());
|
LOG_ERR("ZIP", "Failed to read data");
|
||||||
free(data);
|
free(data);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -432,7 +432,7 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo
|
|||||||
// Read out deflated content from file
|
// Read out deflated content from file
|
||||||
const auto deflatedData = static_cast<uint8_t*>(malloc(deflatedDataSize));
|
const auto deflatedData = static_cast<uint8_t*>(malloc(deflatedDataSize));
|
||||||
if (deflatedData == nullptr) {
|
if (deflatedData == nullptr) {
|
||||||
Serial.printf("[%lu] [ZIP] Failed to allocate memory for decompression buffer\n", millis());
|
LOG_ERR("ZIP", "Failed to allocate memory for decompression buffer");
|
||||||
if (!wasOpen) {
|
if (!wasOpen) {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
@@ -445,7 +445,7 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dataRead != deflatedDataSize) {
|
if (dataRead != deflatedDataSize) {
|
||||||
Serial.printf("[%lu] [ZIP] Failed to read data, expected %d got %d\n", millis(), deflatedDataSize, dataRead);
|
LOG_ERR("ZIP", "Failed to read data, expected %d got %d", deflatedDataSize, dataRead);
|
||||||
free(deflatedData);
|
free(deflatedData);
|
||||||
free(data);
|
free(data);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -455,14 +455,14 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo
|
|||||||
free(deflatedData);
|
free(deflatedData);
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
Serial.printf("[%lu] [ZIP] Failed to inflate file\n", millis());
|
LOG_ERR("ZIP", "Failed to inflate file");
|
||||||
free(data);
|
free(data);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Continue out of block with data set
|
// Continue out of block with data set
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("[%lu] [ZIP] Unsupported compression method\n", millis());
|
LOG_ERR("ZIP", "Unsupported compression method");
|
||||||
if (!wasOpen) {
|
if (!wasOpen) {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
@@ -498,7 +498,7 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
|
|||||||
// no deflation, just read content
|
// no deflation, just read content
|
||||||
const auto buffer = static_cast<uint8_t*>(malloc(chunkSize));
|
const auto buffer = static_cast<uint8_t*>(malloc(chunkSize));
|
||||||
if (!buffer) {
|
if (!buffer) {
|
||||||
Serial.printf("[%lu] [ZIP] Failed to allocate memory for buffer\n", millis());
|
LOG_ERR("ZIP", "Failed to allocate memory for buffer");
|
||||||
if (!wasOpen) {
|
if (!wasOpen) {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
@@ -509,7 +509,7 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
|
|||||||
while (remaining > 0) {
|
while (remaining > 0) {
|
||||||
const size_t dataRead = file.read(buffer, remaining < chunkSize ? remaining : chunkSize);
|
const size_t dataRead = file.read(buffer, remaining < chunkSize ? remaining : chunkSize);
|
||||||
if (dataRead == 0) {
|
if (dataRead == 0) {
|
||||||
Serial.printf("[%lu] [ZIP] Could not read more bytes\n", millis());
|
LOG_ERR("ZIP", "Could not read more bytes");
|
||||||
free(buffer);
|
free(buffer);
|
||||||
if (!wasOpen) {
|
if (!wasOpen) {
|
||||||
close();
|
close();
|
||||||
@@ -532,7 +532,7 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
|
|||||||
// Setup inflator
|
// Setup inflator
|
||||||
const auto inflator = static_cast<tinfl_decompressor*>(malloc(sizeof(tinfl_decompressor)));
|
const auto inflator = static_cast<tinfl_decompressor*>(malloc(sizeof(tinfl_decompressor)));
|
||||||
if (!inflator) {
|
if (!inflator) {
|
||||||
Serial.printf("[%lu] [ZIP] Failed to allocate memory for inflator\n", millis());
|
LOG_ERR("ZIP", "Failed to allocate memory for inflator");
|
||||||
if (!wasOpen) {
|
if (!wasOpen) {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
@@ -544,7 +544,7 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
|
|||||||
// Setup file read buffer
|
// Setup file read buffer
|
||||||
const auto fileReadBuffer = static_cast<uint8_t*>(malloc(chunkSize));
|
const auto fileReadBuffer = static_cast<uint8_t*>(malloc(chunkSize));
|
||||||
if (!fileReadBuffer) {
|
if (!fileReadBuffer) {
|
||||||
Serial.printf("[%lu] [ZIP] Failed to allocate memory for zip file read buffer\n", millis());
|
LOG_ERR("ZIP", "Failed to allocate memory for zip file read buffer");
|
||||||
free(inflator);
|
free(inflator);
|
||||||
if (!wasOpen) {
|
if (!wasOpen) {
|
||||||
close();
|
close();
|
||||||
@@ -554,7 +554,7 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
|
|||||||
|
|
||||||
const auto outputBuffer = static_cast<uint8_t*>(malloc(TINFL_LZ_DICT_SIZE));
|
const auto outputBuffer = static_cast<uint8_t*>(malloc(TINFL_LZ_DICT_SIZE));
|
||||||
if (!outputBuffer) {
|
if (!outputBuffer) {
|
||||||
Serial.printf("[%lu] [ZIP] Failed to allocate memory for dictionary\n", millis());
|
LOG_ERR("ZIP", "Failed to allocate memory for dictionary");
|
||||||
free(inflator);
|
free(inflator);
|
||||||
free(fileReadBuffer);
|
free(fileReadBuffer);
|
||||||
if (!wasOpen) {
|
if (!wasOpen) {
|
||||||
@@ -605,7 +605,7 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
|
|||||||
if (outBytes > 0) {
|
if (outBytes > 0) {
|
||||||
processedOutputBytes += outBytes;
|
processedOutputBytes += outBytes;
|
||||||
if (out.write(outputBuffer + outputCursor, outBytes) != outBytes) {
|
if (out.write(outputBuffer + outputCursor, outBytes) != outBytes) {
|
||||||
Serial.printf("[%lu] [ZIP] Failed to write all output bytes to stream\n", millis());
|
LOG_ERR("ZIP", "Failed to write all output bytes to stream");
|
||||||
if (!wasOpen) {
|
if (!wasOpen) {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
@@ -619,7 +619,7 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (status < 0) {
|
if (status < 0) {
|
||||||
Serial.printf("[%lu] [ZIP] tinfl_decompress() failed with status %d\n", millis(), status);
|
LOG_ERR("ZIP", "tinfl_decompress() failed with status %d", status);
|
||||||
if (!wasOpen) {
|
if (!wasOpen) {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
@@ -630,8 +630,7 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (status == TINFL_STATUS_DONE) {
|
if (status == TINFL_STATUS_DONE) {
|
||||||
Serial.printf("[%lu] [ZIP] Decompressed %d bytes into %d bytes\n", millis(), deflatedDataSize,
|
LOG_ERR("ZIP", "Decompressed %d bytes into %d bytes", deflatedDataSize, inflatedDataSize);
|
||||||
inflatedDataSize);
|
|
||||||
if (!wasOpen) {
|
if (!wasOpen) {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
@@ -643,7 +642,7 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we get here, EOF reached without TINFL_STATUS_DONE
|
// If we get here, EOF reached without TINFL_STATUS_DONE
|
||||||
Serial.printf("[%lu] [ZIP] Unexpected EOF\n", millis());
|
LOG_ERR("ZIP", "Unexpected EOF");
|
||||||
if (!wasOpen) {
|
if (!wasOpen) {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
@@ -657,6 +656,6 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
|
|||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [ZIP] Unsupported compression method\n", millis());
|
LOG_ERR("ZIP", "Unsupported compression method");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,15 +54,31 @@ extends = base
|
|||||||
build_flags =
|
build_flags =
|
||||||
${base.build_flags}
|
${base.build_flags}
|
||||||
-DCROSSPOINT_VERSION=\"${crosspoint.version}-dev\"
|
-DCROSSPOINT_VERSION=\"${crosspoint.version}-dev\"
|
||||||
|
-DENABLE_SERIAL_LOG
|
||||||
|
-DLOG_LEVEL=2 ; Set log level to debug for development builds
|
||||||
|
|
||||||
|
|
||||||
[env:gh_release]
|
[env:gh_release]
|
||||||
extends = base
|
extends = base
|
||||||
build_flags =
|
build_flags =
|
||||||
${base.build_flags}
|
${base.build_flags}
|
||||||
-DCROSSPOINT_VERSION=\"${crosspoint.version}\"
|
-DCROSSPOINT_VERSION=\"${crosspoint.version}\"
|
||||||
|
-DENABLE_SERIAL_LOG
|
||||||
|
-DLOG_LEVEL=0 ; Set log level to error for release builds
|
||||||
|
|
||||||
[env:gh_release_rc]
|
[env:gh_release_rc]
|
||||||
extends = base
|
extends = base
|
||||||
build_flags =
|
build_flags =
|
||||||
${base.build_flags}
|
${base.build_flags}
|
||||||
-DCROSSPOINT_VERSION=\"${crosspoint.version}-rc+${sysenv.CROSSPOINT_RC_HASH}\"
|
-DCROSSPOINT_VERSION=\"${crosspoint.version}-rc+${sysenv.CROSSPOINT_RC_HASH}\"
|
||||||
|
-DENABLE_SERIAL_LOG
|
||||||
|
-DLOG_LEVEL=1 ; Set log level to info for release candidate builds
|
||||||
|
|
||||||
|
[env:slim]
|
||||||
|
extends = base
|
||||||
|
build_flags =
|
||||||
|
${base.build_flags}
|
||||||
|
-DCROSSPOINT_VERSION=\"${crosspoint.version}-slim\"
|
||||||
|
; serial output is disabled in slim builds to save space
|
||||||
|
-UENABLE_SERIAL_LOG
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
|
|
||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
#include <HardwareSerial.h>
|
#include <Logging.h>
|
||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -121,7 +121,7 @@ bool CrossPointSettings::saveToFile() const {
|
|||||||
// New fields added at end for backward compatibility
|
// New fields added at end for backward compatibility
|
||||||
outputFile.close();
|
outputFile.close();
|
||||||
|
|
||||||
Serial.printf("[%lu] [CPS] Settings saved to file\n", millis());
|
LOG_DBG("CPS", "Settings saved to file");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,7 +134,7 @@ bool CrossPointSettings::loadFromFile() {
|
|||||||
uint8_t version;
|
uint8_t version;
|
||||||
serialization::readPod(inputFile, version);
|
serialization::readPod(inputFile, version);
|
||||||
if (version != SETTINGS_FILE_VERSION) {
|
if (version != SETTINGS_FILE_VERSION) {
|
||||||
Serial.printf("[%lu] [CPS] Deserialization failed: Unknown version %u\n", millis(), version);
|
LOG_ERR("CPS", "Deserialization failed: Unknown version %u", version);
|
||||||
inputFile.close();
|
inputFile.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -233,7 +233,7 @@ bool CrossPointSettings::loadFromFile() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inputFile.close();
|
inputFile.close();
|
||||||
Serial.printf("[%lu] [CPS] Settings loaded from file\n", millis());
|
LOG_DBG("CPS", "Settings loaded from file");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "CrossPointState.h"
|
#include "CrossPointState.h"
|
||||||
|
|
||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
#include <HardwareSerial.h>
|
#include <Logging.h>
|
||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -35,7 +35,7 @@ bool CrossPointState::loadFromFile() {
|
|||||||
uint8_t version;
|
uint8_t version;
|
||||||
serialization::readPod(inputFile, version);
|
serialization::readPod(inputFile, version);
|
||||||
if (version > STATE_FILE_VERSION) {
|
if (version > STATE_FILE_VERSION) {
|
||||||
Serial.printf("[%lu] [CPS] Deserialization failed: Unknown version %u\n", millis(), version);
|
LOG_ERR("CPS", "Deserialization failed: Unknown version %u", version);
|
||||||
inputFile.close();
|
inputFile.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#include <Epub.h>
|
#include <Epub.h>
|
||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
#include <HardwareSerial.h>
|
#include <Logging.h>
|
||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
#include <Xtc.h>
|
#include <Xtc.h>
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ bool RecentBooksStore::saveToFile() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
outputFile.close();
|
outputFile.close();
|
||||||
Serial.printf("[%lu] [RBS] Recent books saved to file (%d entries)\n", millis(), count);
|
LOG_DBG("RBS", "Recent books saved to file (%d entries)", count);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ RecentBook RecentBooksStore::getDataFromBook(std::string path) const {
|
|||||||
lastBookFileName = path.substr(lastSlash + 1);
|
lastBookFileName = path.substr(lastSlash + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [RBS] Loading recent book: %s\n", millis(), path.c_str());
|
LOG_DBG("RBS", "Loading recent book: %s", path.c_str());
|
||||||
|
|
||||||
// If epub, try to load the metadata for title/author and cover
|
// If epub, try to load the metadata for title/author and cover
|
||||||
if (StringUtils::checkFileExtension(lastBookFileName, ".epub")) {
|
if (StringUtils::checkFileExtension(lastBookFileName, ".epub")) {
|
||||||
@@ -136,7 +136,7 @@ bool RecentBooksStore::loadFromFile() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("[%lu] [RBS] Deserialization failed: Unknown version %u\n", millis(), version);
|
LOG_ERR("RBS", "Deserialization failed: Unknown version %u", version);
|
||||||
inputFile.close();
|
inputFile.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -158,6 +158,6 @@ bool RecentBooksStore::loadFromFile() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inputFile.close();
|
inputFile.close();
|
||||||
Serial.printf("[%lu] [RBS] Recent books loaded from file (%d entries)\n", millis(), recentBooks.size());
|
LOG_DBG("RBS", "Recent books loaded from file (%d entries)", recentBooks.size());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "WifiCredentialStore.h"
|
#include "WifiCredentialStore.h"
|
||||||
|
|
||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
#include <HardwareSerial.h>
|
#include <Logging.h>
|
||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
|
|
||||||
// Initialize the static instance
|
// Initialize the static instance
|
||||||
@@ -21,7 +21,7 @@ constexpr size_t KEY_LENGTH = sizeof(OBFUSCATION_KEY);
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void WifiCredentialStore::obfuscate(std::string& data) const {
|
void WifiCredentialStore::obfuscate(std::string& data) const {
|
||||||
Serial.printf("[%lu] [WCS] Obfuscating/deobfuscating %zu bytes\n", millis(), data.size());
|
LOG_DBG("WCS", "Obfuscating/deobfuscating %zu bytes", data.size());
|
||||||
for (size_t i = 0; i < data.size(); i++) {
|
for (size_t i = 0; i < data.size(); i++) {
|
||||||
data[i] ^= OBFUSCATION_KEY[i % KEY_LENGTH];
|
data[i] ^= OBFUSCATION_KEY[i % KEY_LENGTH];
|
||||||
}
|
}
|
||||||
@@ -45,8 +45,7 @@ bool WifiCredentialStore::saveToFile() const {
|
|||||||
for (const auto& cred : credentials) {
|
for (const auto& cred : credentials) {
|
||||||
// Write SSID (plaintext - not sensitive)
|
// Write SSID (plaintext - not sensitive)
|
||||||
serialization::writeString(file, cred.ssid);
|
serialization::writeString(file, cred.ssid);
|
||||||
Serial.printf("[%lu] [WCS] Saving SSID: %s, password length: %zu\n", millis(), cred.ssid.c_str(),
|
LOG_DBG("WCS", "Saving SSID: %s, password length: %zu", cred.ssid.c_str(), cred.password.size());
|
||||||
cred.password.size());
|
|
||||||
|
|
||||||
// Write password (obfuscated)
|
// Write password (obfuscated)
|
||||||
std::string obfuscatedPwd = cred.password;
|
std::string obfuscatedPwd = cred.password;
|
||||||
@@ -55,7 +54,7 @@ bool WifiCredentialStore::saveToFile() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
Serial.printf("[%lu] [WCS] Saved %zu WiFi credentials to file\n", millis(), credentials.size());
|
LOG_DBG("WCS", "Saved %zu WiFi credentials to file", credentials.size());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +68,7 @@ bool WifiCredentialStore::loadFromFile() {
|
|||||||
uint8_t version;
|
uint8_t version;
|
||||||
serialization::readPod(file, version);
|
serialization::readPod(file, version);
|
||||||
if (version > WIFI_FILE_VERSION) {
|
if (version > WIFI_FILE_VERSION) {
|
||||||
Serial.printf("[%lu] [WCS] Unknown file version: %u\n", millis(), version);
|
LOG_DBG("WCS", "Unknown file version: %u", version);
|
||||||
file.close();
|
file.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -94,16 +93,15 @@ bool WifiCredentialStore::loadFromFile() {
|
|||||||
|
|
||||||
// Read and deobfuscate password
|
// Read and deobfuscate password
|
||||||
serialization::readString(file, cred.password);
|
serialization::readString(file, cred.password);
|
||||||
Serial.printf("[%lu] [WCS] Loaded SSID: %s, obfuscated password length: %zu\n", millis(), cred.ssid.c_str(),
|
LOG_DBG("WCS", "Loaded SSID: %s, obfuscated password length: %zu", cred.ssid.c_str(), cred.password.size());
|
||||||
cred.password.size());
|
|
||||||
obfuscate(cred.password); // XOR is symmetric, so same function deobfuscates
|
obfuscate(cred.password); // XOR is symmetric, so same function deobfuscates
|
||||||
Serial.printf("[%lu] [WCS] After deobfuscation, password length: %zu\n", millis(), cred.password.size());
|
LOG_DBG("WCS", "After deobfuscation, password length: %zu", cred.password.size());
|
||||||
|
|
||||||
credentials.push_back(cred);
|
credentials.push_back(cred);
|
||||||
}
|
}
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
Serial.printf("[%lu] [WCS] Loaded %zu WiFi credentials from file\n", millis(), credentials.size());
|
LOG_DBG("WCS", "Loaded %zu WiFi credentials from file", credentials.size());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,19 +111,19 @@ bool WifiCredentialStore::addCredential(const std::string& ssid, const std::stri
|
|||||||
[&ssid](const WifiCredential& cred) { return cred.ssid == ssid; });
|
[&ssid](const WifiCredential& cred) { return cred.ssid == ssid; });
|
||||||
if (cred != credentials.end()) {
|
if (cred != credentials.end()) {
|
||||||
cred->password = password;
|
cred->password = password;
|
||||||
Serial.printf("[%lu] [WCS] Updated credentials for: %s\n", millis(), ssid.c_str());
|
LOG_DBG("WCS", "Updated credentials for: %s", ssid.c_str());
|
||||||
return saveToFile();
|
return saveToFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we've reached the limit
|
// Check if we've reached the limit
|
||||||
if (credentials.size() >= MAX_NETWORKS) {
|
if (credentials.size() >= MAX_NETWORKS) {
|
||||||
Serial.printf("[%lu] [WCS] Cannot add more networks, limit of %zu reached\n", millis(), MAX_NETWORKS);
|
LOG_DBG("WCS", "Cannot add more networks, limit of %zu reached", MAX_NETWORKS);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add new credential
|
// Add new credential
|
||||||
credentials.push_back({ssid, password});
|
credentials.push_back({ssid, password});
|
||||||
Serial.printf("[%lu] [WCS] Added credentials for: %s\n", millis(), ssid.c_str());
|
LOG_DBG("WCS", "Added credentials for: %s", ssid.c_str());
|
||||||
return saveToFile();
|
return saveToFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,7 +132,7 @@ bool WifiCredentialStore::removeCredential(const std::string& ssid) {
|
|||||||
[&ssid](const WifiCredential& cred) { return cred.ssid == ssid; });
|
[&ssid](const WifiCredential& cred) { return cred.ssid == ssid; });
|
||||||
if (cred != credentials.end()) {
|
if (cred != credentials.end()) {
|
||||||
credentials.erase(cred);
|
credentials.erase(cred);
|
||||||
Serial.printf("[%lu] [WCS] Removed credentials for: %s\n", millis(), ssid.c_str());
|
LOG_DBG("WCS", "Removed credentials for: %s", ssid.c_str());
|
||||||
if (ssid == lastConnectedSsid) {
|
if (ssid == lastConnectedSsid) {
|
||||||
clearLastConnectedSsid();
|
clearLastConnectedSsid();
|
||||||
}
|
}
|
||||||
@@ -176,5 +174,5 @@ void WifiCredentialStore::clearAll() {
|
|||||||
credentials.clear();
|
credentials.clear();
|
||||||
lastConnectedSsid.clear();
|
lastConnectedSsid.clear();
|
||||||
saveToFile();
|
saveToFile();
|
||||||
Serial.printf("[%lu] [WCS] Cleared all WiFi credentials\n", millis());
|
LOG_DBG("WCS", "Cleared all WiFi credentials");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <HardwareSerial.h>
|
#include <Logging.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -18,8 +18,8 @@ class Activity {
|
|||||||
explicit Activity(std::string name, GfxRenderer& renderer, MappedInputManager& mappedInput)
|
explicit Activity(std::string name, GfxRenderer& renderer, MappedInputManager& mappedInput)
|
||||||
: name(std::move(name)), renderer(renderer), mappedInput(mappedInput) {}
|
: name(std::move(name)), renderer(renderer), mappedInput(mappedInput) {}
|
||||||
virtual ~Activity() = default;
|
virtual ~Activity() = default;
|
||||||
virtual void onEnter() { Serial.printf("[%lu] [ACT] Entering activity: %s\n", millis(), name.c_str()); }
|
virtual void onEnter() { LOG_DBG("ACT", "Entering activity: %s", name.c_str()); }
|
||||||
virtual void onExit() { Serial.printf("[%lu] [ACT] Exiting activity: %s\n", millis(), name.c_str()); }
|
virtual void onExit() { LOG_DBG("ACT", "Exiting activity: %s", name.c_str()); }
|
||||||
virtual void loop() {}
|
virtual void loop() {}
|
||||||
virtual bool skipLoopDelay() { return false; }
|
virtual bool skipLoopDelay() { return false; }
|
||||||
virtual bool preventAutoSleep() { return false; }
|
virtual bool preventAutoSleep() { return false; }
|
||||||
|
|||||||
@@ -50,13 +50,13 @@ void SleepActivity::renderCustomSleepScreen() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (filename.substr(filename.length() - 4) != ".bmp") {
|
if (filename.substr(filename.length() - 4) != ".bmp") {
|
||||||
Serial.printf("[%lu] [SLP] Skipping non-.bmp file name: %s\n", millis(), name);
|
LOG_DBG("SLP", "Skipping non-.bmp file name: %s", name);
|
||||||
file.close();
|
file.close();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Bitmap bitmap(file);
|
Bitmap bitmap(file);
|
||||||
if (bitmap.parseHeaders() != BmpReaderError::Ok) {
|
if (bitmap.parseHeaders() != BmpReaderError::Ok) {
|
||||||
Serial.printf("[%lu] [SLP] Skipping invalid BMP file: %s\n", millis(), name);
|
LOG_DBG("SLP", "Skipping invalid BMP file: %s", name);
|
||||||
file.close();
|
file.close();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,7 @@ void SleepActivity::renderCustomSleepScreen() const {
|
|||||||
const auto filename = "/sleep/" + files[randomFileIndex];
|
const auto filename = "/sleep/" + files[randomFileIndex];
|
||||||
FsFile file;
|
FsFile file;
|
||||||
if (Storage.openFileForRead("SLP", filename, file)) {
|
if (Storage.openFileForRead("SLP", filename, file)) {
|
||||||
Serial.printf("[%lu] [SLP] Randomly loading: /sleep/%s\n", millis(), files[randomFileIndex].c_str());
|
LOG_DBG("SLP", "Randomly loading: /sleep/%s", files[randomFileIndex].c_str());
|
||||||
delay(100);
|
delay(100);
|
||||||
Bitmap bitmap(file, true);
|
Bitmap bitmap(file, true);
|
||||||
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
||||||
@@ -95,7 +95,7 @@ void SleepActivity::renderCustomSleepScreen() const {
|
|||||||
if (Storage.openFileForRead("SLP", "/sleep.bmp", file)) {
|
if (Storage.openFileForRead("SLP", "/sleep.bmp", file)) {
|
||||||
Bitmap bitmap(file, true);
|
Bitmap bitmap(file, true);
|
||||||
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
||||||
Serial.printf("[%lu] [SLP] Loading: /sleep.bmp\n", millis());
|
LOG_DBG("SLP", "Loading: /sleep.bmp");
|
||||||
renderBitmapSleepScreen(bitmap);
|
renderBitmapSleepScreen(bitmap);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -127,34 +127,33 @@ void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const {
|
|||||||
const auto pageHeight = renderer.getScreenHeight();
|
const auto pageHeight = renderer.getScreenHeight();
|
||||||
float cropX = 0, cropY = 0;
|
float cropX = 0, cropY = 0;
|
||||||
|
|
||||||
Serial.printf("[%lu] [SLP] bitmap %d x %d, screen %d x %d\n", millis(), bitmap.getWidth(), bitmap.getHeight(),
|
LOG_DBG("SLP", "bitmap %d x %d, screen %d x %d", bitmap.getWidth(), bitmap.getHeight(), pageWidth, pageHeight);
|
||||||
pageWidth, pageHeight);
|
|
||||||
if (bitmap.getWidth() > pageWidth || bitmap.getHeight() > pageHeight) {
|
if (bitmap.getWidth() > pageWidth || bitmap.getHeight() > pageHeight) {
|
||||||
// image will scale, make sure placement is right
|
// image will scale, make sure placement is right
|
||||||
float ratio = static_cast<float>(bitmap.getWidth()) / static_cast<float>(bitmap.getHeight());
|
float ratio = static_cast<float>(bitmap.getWidth()) / static_cast<float>(bitmap.getHeight());
|
||||||
const float screenRatio = static_cast<float>(pageWidth) / static_cast<float>(pageHeight);
|
const float screenRatio = static_cast<float>(pageWidth) / static_cast<float>(pageHeight);
|
||||||
|
|
||||||
Serial.printf("[%lu] [SLP] bitmap ratio: %f, screen ratio: %f\n", millis(), ratio, screenRatio);
|
LOG_DBG("SLP", "bitmap ratio: %f, screen ratio: %f", ratio, screenRatio);
|
||||||
if (ratio > screenRatio) {
|
if (ratio > screenRatio) {
|
||||||
// image wider than viewport ratio, scaled down image needs to be centered vertically
|
// image wider than viewport ratio, scaled down image needs to be centered vertically
|
||||||
if (SETTINGS.sleepScreenCoverMode == CrossPointSettings::SLEEP_SCREEN_COVER_MODE::CROP) {
|
if (SETTINGS.sleepScreenCoverMode == CrossPointSettings::SLEEP_SCREEN_COVER_MODE::CROP) {
|
||||||
cropX = 1.0f - (screenRatio / ratio);
|
cropX = 1.0f - (screenRatio / ratio);
|
||||||
Serial.printf("[%lu] [SLP] Cropping bitmap x: %f\n", millis(), cropX);
|
LOG_DBG("SLP", "Cropping bitmap x: %f", cropX);
|
||||||
ratio = (1.0f - cropX) * static_cast<float>(bitmap.getWidth()) / static_cast<float>(bitmap.getHeight());
|
ratio = (1.0f - cropX) * static_cast<float>(bitmap.getWidth()) / static_cast<float>(bitmap.getHeight());
|
||||||
}
|
}
|
||||||
x = 0;
|
x = 0;
|
||||||
y = std::round((static_cast<float>(pageHeight) - static_cast<float>(pageWidth) / ratio) / 2);
|
y = std::round((static_cast<float>(pageHeight) - static_cast<float>(pageWidth) / ratio) / 2);
|
||||||
Serial.printf("[%lu] [SLP] Centering with ratio %f to y=%d\n", millis(), ratio, y);
|
LOG_DBG("SLP", "Centering with ratio %f to y=%d", ratio, y);
|
||||||
} else {
|
} else {
|
||||||
// image taller than viewport ratio, scaled down image needs to be centered horizontally
|
// image taller than viewport ratio, scaled down image needs to be centered horizontally
|
||||||
if (SETTINGS.sleepScreenCoverMode == CrossPointSettings::SLEEP_SCREEN_COVER_MODE::CROP) {
|
if (SETTINGS.sleepScreenCoverMode == CrossPointSettings::SLEEP_SCREEN_COVER_MODE::CROP) {
|
||||||
cropY = 1.0f - (ratio / screenRatio);
|
cropY = 1.0f - (ratio / screenRatio);
|
||||||
Serial.printf("[%lu] [SLP] Cropping bitmap y: %f\n", millis(), cropY);
|
LOG_DBG("SLP", "Cropping bitmap y: %f", cropY);
|
||||||
ratio = static_cast<float>(bitmap.getWidth()) / ((1.0f - cropY) * static_cast<float>(bitmap.getHeight()));
|
ratio = static_cast<float>(bitmap.getWidth()) / ((1.0f - cropY) * static_cast<float>(bitmap.getHeight()));
|
||||||
}
|
}
|
||||||
x = std::round((static_cast<float>(pageWidth) - static_cast<float>(pageHeight) * ratio) / 2);
|
x = std::round((static_cast<float>(pageWidth) - static_cast<float>(pageHeight) * ratio) / 2);
|
||||||
y = 0;
|
y = 0;
|
||||||
Serial.printf("[%lu] [SLP] Centering with ratio %f to x=%d\n", millis(), ratio, x);
|
LOG_DBG("SLP", "Centering with ratio %f to x=%d", ratio, x);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// center the image
|
// center the image
|
||||||
@@ -162,7 +161,7 @@ void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const {
|
|||||||
y = (pageHeight - bitmap.getHeight()) / 2;
|
y = (pageHeight - bitmap.getHeight()) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [SLP] drawing to %d x %d\n", millis(), x, y);
|
LOG_DBG("SLP", "drawing to %d x %d", x, y);
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
|
|
||||||
const bool hasGreyscale = bitmap.hasGreyscale() &&
|
const bool hasGreyscale = bitmap.hasGreyscale() &&
|
||||||
@@ -218,12 +217,12 @@ void SleepActivity::renderCoverSleepScreen() const {
|
|||||||
// Handle XTC file
|
// Handle XTC file
|
||||||
Xtc lastXtc(APP_STATE.openEpubPath, "/.crosspoint");
|
Xtc lastXtc(APP_STATE.openEpubPath, "/.crosspoint");
|
||||||
if (!lastXtc.load()) {
|
if (!lastXtc.load()) {
|
||||||
Serial.printf("[%lu] [SLP] Failed to load last XTC\n", millis());
|
LOG_ERR("SLP", "Failed to load last XTC");
|
||||||
return (this->*renderNoCoverSleepScreen)();
|
return (this->*renderNoCoverSleepScreen)();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!lastXtc.generateCoverBmp()) {
|
if (!lastXtc.generateCoverBmp()) {
|
||||||
Serial.printf("[%lu] [SLP] Failed to generate XTC cover bmp\n", millis());
|
LOG_ERR("SLP", "Failed to generate XTC cover bmp");
|
||||||
return (this->*renderNoCoverSleepScreen)();
|
return (this->*renderNoCoverSleepScreen)();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,12 +231,12 @@ void SleepActivity::renderCoverSleepScreen() const {
|
|||||||
// Handle TXT file - looks for cover image in the same folder
|
// Handle TXT file - looks for cover image in the same folder
|
||||||
Txt lastTxt(APP_STATE.openEpubPath, "/.crosspoint");
|
Txt lastTxt(APP_STATE.openEpubPath, "/.crosspoint");
|
||||||
if (!lastTxt.load()) {
|
if (!lastTxt.load()) {
|
||||||
Serial.printf("[%lu] [SLP] Failed to load last TXT\n", millis());
|
LOG_ERR("SLP", "Failed to load last TXT");
|
||||||
return (this->*renderNoCoverSleepScreen)();
|
return (this->*renderNoCoverSleepScreen)();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!lastTxt.generateCoverBmp()) {
|
if (!lastTxt.generateCoverBmp()) {
|
||||||
Serial.printf("[%lu] [SLP] No cover image found for TXT file\n", millis());
|
LOG_ERR("SLP", "No cover image found for TXT file");
|
||||||
return (this->*renderNoCoverSleepScreen)();
|
return (this->*renderNoCoverSleepScreen)();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,12 +246,12 @@ void SleepActivity::renderCoverSleepScreen() const {
|
|||||||
Epub lastEpub(APP_STATE.openEpubPath, "/.crosspoint");
|
Epub lastEpub(APP_STATE.openEpubPath, "/.crosspoint");
|
||||||
// Skip loading css since we only need metadata here
|
// Skip loading css since we only need metadata here
|
||||||
if (!lastEpub.load(true, true)) {
|
if (!lastEpub.load(true, true)) {
|
||||||
Serial.printf("[%lu] [SLP] Failed to load last epub\n", millis());
|
LOG_ERR("SLP", "Failed to load last epub");
|
||||||
return (this->*renderNoCoverSleepScreen)();
|
return (this->*renderNoCoverSleepScreen)();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!lastEpub.generateCoverBmp(cropped)) {
|
if (!lastEpub.generateCoverBmp(cropped)) {
|
||||||
Serial.printf("[%lu] [SLP] Failed to generate cover bmp\n", millis());
|
LOG_ERR("SLP", "Failed to generate cover bmp");
|
||||||
return (this->*renderNoCoverSleepScreen)();
|
return (this->*renderNoCoverSleepScreen)();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,7 +264,7 @@ void SleepActivity::renderCoverSleepScreen() const {
|
|||||||
if (Storage.openFileForRead("SLP", coverBmpPath, file)) {
|
if (Storage.openFileForRead("SLP", coverBmpPath, file)) {
|
||||||
Bitmap bitmap(file);
|
Bitmap bitmap(file);
|
||||||
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
||||||
Serial.printf("[%lu] [SLP] Rendering sleep cover: %s\n", millis(), coverBmpPath.c_str());
|
LOG_DBG("SLP", "Rendering sleep cover: %s", coverBmpPath.c_str());
|
||||||
renderBitmapSleepScreen(bitmap);
|
renderBitmapSleepScreen(bitmap);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#include <Epub.h>
|
#include <Epub.h>
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <HardwareSerial.h>
|
#include <Logging.h>
|
||||||
#include <OpdsStream.h>
|
#include <OpdsStream.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
|
|
||||||
@@ -78,14 +78,14 @@ void OpdsBookBrowserActivity::loop() {
|
|||||||
// Check if WiFi is still connected
|
// Check if WiFi is still connected
|
||||||
if (WiFi.status() == WL_CONNECTED && WiFi.localIP() != IPAddress(0, 0, 0, 0)) {
|
if (WiFi.status() == WL_CONNECTED && WiFi.localIP() != IPAddress(0, 0, 0, 0)) {
|
||||||
// WiFi connected - just retry fetching the feed
|
// WiFi connected - just retry fetching the feed
|
||||||
Serial.printf("[%lu] [OPDS] Retry: WiFi connected, retrying fetch\n", millis());
|
LOG_DBG("OPDS", "Retry: WiFi connected, retrying fetch");
|
||||||
state = BrowserState::LOADING;
|
state = BrowserState::LOADING;
|
||||||
statusMessage = "Loading...";
|
statusMessage = "Loading...";
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
fetchFeed(currentPath);
|
fetchFeed(currentPath);
|
||||||
} else {
|
} else {
|
||||||
// WiFi not connected - launch WiFi selection
|
// WiFi not connected - launch WiFi selection
|
||||||
Serial.printf("[%lu] [OPDS] Retry: WiFi not connected, launching selection\n", millis());
|
LOG_DBG("OPDS", "Retry: WiFi not connected, launching selection");
|
||||||
launchWifiSelection();
|
launchWifiSelection();
|
||||||
}
|
}
|
||||||
} else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
} else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
||||||
@@ -265,7 +265,7 @@ void OpdsBookBrowserActivity::fetchFeed(const std::string& path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string url = UrlUtils::buildUrl(serverUrl, path);
|
std::string url = UrlUtils::buildUrl(serverUrl, path);
|
||||||
Serial.printf("[%lu] [OPDS] Fetching: %s\n", millis(), url.c_str());
|
LOG_DBG("OPDS", "Fetching: %s", url.c_str());
|
||||||
|
|
||||||
OpdsParser parser;
|
OpdsParser parser;
|
||||||
|
|
||||||
@@ -287,7 +287,7 @@ void OpdsBookBrowserActivity::fetchFeed(const std::string& path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
entries = std::move(parser).getEntries();
|
entries = std::move(parser).getEntries();
|
||||||
Serial.printf("[%lu] [OPDS] Found %d entries\n", millis(), entries.size());
|
LOG_DBG("OPDS", "Found %d entries", entries.size());
|
||||||
selectorIndex = 0;
|
selectorIndex = 0;
|
||||||
|
|
||||||
if (entries.empty()) {
|
if (entries.empty()) {
|
||||||
@@ -351,7 +351,7 @@ void OpdsBookBrowserActivity::downloadBook(const OpdsEntry& book) {
|
|||||||
}
|
}
|
||||||
std::string filename = "/" + StringUtils::sanitizeFilename(baseName) + ".epub";
|
std::string filename = "/" + StringUtils::sanitizeFilename(baseName) + ".epub";
|
||||||
|
|
||||||
Serial.printf("[%lu] [OPDS] Downloading: %s -> %s\n", millis(), downloadUrl.c_str(), filename.c_str());
|
LOG_DBG("OPDS", "Downloading: %s -> %s", downloadUrl.c_str(), filename.c_str());
|
||||||
|
|
||||||
const auto result =
|
const auto result =
|
||||||
HttpDownloader::downloadToFile(downloadUrl, filename, [this](const size_t downloaded, const size_t total) {
|
HttpDownloader::downloadToFile(downloadUrl, filename, [this](const size_t downloaded, const size_t total) {
|
||||||
@@ -361,12 +361,12 @@ void OpdsBookBrowserActivity::downloadBook(const OpdsEntry& book) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (result == HttpDownloader::OK) {
|
if (result == HttpDownloader::OK) {
|
||||||
Serial.printf("[%lu] [OPDS] Download complete: %s\n", millis(), filename.c_str());
|
LOG_DBG("OPDS", "Download complete: %s", filename.c_str());
|
||||||
|
|
||||||
// Invalidate any existing cache for this file to prevent stale metadata issues
|
// Invalidate any existing cache for this file to prevent stale metadata issues
|
||||||
Epub epub(filename, "/.crosspoint");
|
Epub epub(filename, "/.crosspoint");
|
||||||
epub.clearCache();
|
epub.clearCache();
|
||||||
Serial.printf("[%lu] [OPDS] Cleared cache for: %s\n", millis(), filename.c_str());
|
LOG_DBG("OPDS", "Cleared cache for: %s", filename.c_str());
|
||||||
|
|
||||||
state = BrowserState::BROWSING;
|
state = BrowserState::BROWSING;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
@@ -403,13 +403,13 @@ void OpdsBookBrowserActivity::onWifiSelectionComplete(const bool connected) {
|
|||||||
exitActivity();
|
exitActivity();
|
||||||
|
|
||||||
if (connected) {
|
if (connected) {
|
||||||
Serial.printf("[%lu] [OPDS] WiFi connected via selection, fetching feed\n", millis());
|
LOG_DBG("OPDS", "WiFi connected via selection, fetching feed");
|
||||||
state = BrowserState::LOADING;
|
state = BrowserState::LOADING;
|
||||||
statusMessage = "Loading...";
|
statusMessage = "Loading...";
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
fetchFeed(currentPath);
|
fetchFeed(currentPath);
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("[%lu] [OPDS] WiFi selection cancelled/failed\n", millis());
|
LOG_DBG("OPDS", "WiFi selection cancelled/failed");
|
||||||
// Force disconnect to ensure clean state for next retry
|
// Force disconnect to ensure clean state for next retry
|
||||||
// This prevents stale connection status from interfering
|
// This prevents stale connection status from interfering
|
||||||
WiFi.disconnect();
|
WiFi.disconnect();
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ void RecentBooksActivity::loop() {
|
|||||||
|
|
||||||
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||||
if (!recentBooks.empty() && selectorIndex < static_cast<int>(recentBooks.size())) {
|
if (!recentBooks.empty() && selectorIndex < static_cast<int>(recentBooks.size())) {
|
||||||
Serial.printf("[%lu] [RBA] Selected recent book: %s\n", millis(), recentBooks[selectorIndex].path.c_str());
|
LOG_DBG("RBA", "Selected recent book: %s", recentBooks[selectorIndex].path.c_str());
|
||||||
onSelectBook(recentBooks[selectorIndex].path);
|
onSelectBook(recentBooks[selectorIndex].path);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ void CalibreConnectActivity::startWebServer() {
|
|||||||
|
|
||||||
if (MDNS.begin(HOSTNAME)) {
|
if (MDNS.begin(HOSTNAME)) {
|
||||||
// mDNS is optional for the Calibre plugin but still helpful for users.
|
// mDNS is optional for the Calibre plugin but still helpful for users.
|
||||||
Serial.printf("[%lu] [CAL] mDNS started: http://%s.local/\n", millis(), HOSTNAME);
|
LOG_DBG("CAL", "mDNS started: http://%s.local/", HOSTNAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
webServer.reset(new CrossPointWebServer());
|
webServer.reset(new CrossPointWebServer());
|
||||||
@@ -131,7 +131,7 @@ void CalibreConnectActivity::loop() {
|
|||||||
if (webServer && webServer->isRunning()) {
|
if (webServer && webServer->isRunning()) {
|
||||||
const unsigned long timeSinceLastHandleClient = millis() - lastHandleClientTime;
|
const unsigned long timeSinceLastHandleClient = millis() - lastHandleClientTime;
|
||||||
if (lastHandleClientTime > 0 && timeSinceLastHandleClient > 100) {
|
if (lastHandleClientTime > 0 && timeSinceLastHandleClient > 100) {
|
||||||
Serial.printf("[%lu] [CAL] WARNING: %lu ms gap since last handleClient\n", millis(), timeSinceLastHandleClient);
|
LOG_DBG("CAL", "WARNING: %lu ms gap since last handleClient", timeSinceLastHandleClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_task_wdt_reset();
|
esp_task_wdt_reset();
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ void CrossPointWebServerActivity::taskTrampoline(void* param) {
|
|||||||
void CrossPointWebServerActivity::onEnter() {
|
void CrossPointWebServerActivity::onEnter() {
|
||||||
ActivityWithSubactivity::onEnter();
|
ActivityWithSubactivity::onEnter();
|
||||||
|
|
||||||
Serial.printf("[%lu] [WEBACT] [MEM] Free heap at onEnter: %d bytes\n", millis(), ESP.getFreeHeap());
|
LOG_DBG("WEBACT] [MEM", "Free heap at onEnter: %d bytes", ESP.getFreeHeap());
|
||||||
|
|
||||||
renderingMutex = xSemaphoreCreateMutex();
|
renderingMutex = xSemaphoreCreateMutex();
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ void CrossPointWebServerActivity::onEnter() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Launch network mode selection subactivity
|
// Launch network mode selection subactivity
|
||||||
Serial.printf("[%lu] [WEBACT] Launching NetworkModeSelectionActivity...\n", millis());
|
LOG_DBG("WEBACT", "Launching NetworkModeSelectionActivity...");
|
||||||
enterNewActivity(new NetworkModeSelectionActivity(
|
enterNewActivity(new NetworkModeSelectionActivity(
|
||||||
renderer, mappedInput, [this](const NetworkMode mode) { onNetworkModeSelected(mode); },
|
renderer, mappedInput, [this](const NetworkMode mode) { onNetworkModeSelected(mode); },
|
||||||
[this]() { onGoBack(); } // Cancel goes back to home
|
[this]() { onGoBack(); } // Cancel goes back to home
|
||||||
@@ -68,7 +68,7 @@ void CrossPointWebServerActivity::onEnter() {
|
|||||||
void CrossPointWebServerActivity::onExit() {
|
void CrossPointWebServerActivity::onExit() {
|
||||||
ActivityWithSubactivity::onExit();
|
ActivityWithSubactivity::onExit();
|
||||||
|
|
||||||
Serial.printf("[%lu] [WEBACT] [MEM] Free heap at onExit start: %d bytes\n", millis(), ESP.getFreeHeap());
|
LOG_DBG("WEBACT] [MEM", "Free heap at onExit start: %d bytes", ESP.getFreeHeap());
|
||||||
|
|
||||||
state = WebServerActivityState::SHUTTING_DOWN;
|
state = WebServerActivityState::SHUTTING_DOWN;
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ void CrossPointWebServerActivity::onExit() {
|
|||||||
|
|
||||||
// Stop DNS server if running (AP mode)
|
// Stop DNS server if running (AP mode)
|
||||||
if (dnsServer) {
|
if (dnsServer) {
|
||||||
Serial.printf("[%lu] [WEBACT] Stopping DNS server...\n", millis());
|
LOG_DBG("WEBACT", "Stopping DNS server...");
|
||||||
dnsServer->stop();
|
dnsServer->stop();
|
||||||
delete dnsServer;
|
delete dnsServer;
|
||||||
dnsServer = nullptr;
|
dnsServer = nullptr;
|
||||||
@@ -91,39 +91,39 @@ void CrossPointWebServerActivity::onExit() {
|
|||||||
|
|
||||||
// Disconnect WiFi gracefully
|
// Disconnect WiFi gracefully
|
||||||
if (isApMode) {
|
if (isApMode) {
|
||||||
Serial.printf("[%lu] [WEBACT] Stopping WiFi AP...\n", millis());
|
LOG_DBG("WEBACT", "Stopping WiFi AP...");
|
||||||
WiFi.softAPdisconnect(true);
|
WiFi.softAPdisconnect(true);
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("[%lu] [WEBACT] Disconnecting WiFi (graceful)...\n", millis());
|
LOG_DBG("WEBACT", "Disconnecting WiFi (graceful)...");
|
||||||
WiFi.disconnect(false); // false = don't erase credentials, send disconnect frame
|
WiFi.disconnect(false); // false = don't erase credentials, send disconnect frame
|
||||||
}
|
}
|
||||||
delay(30); // Allow disconnect frame to be sent
|
delay(30); // Allow disconnect frame to be sent
|
||||||
|
|
||||||
Serial.printf("[%lu] [WEBACT] Setting WiFi mode OFF...\n", millis());
|
LOG_DBG("WEBACT", "Setting WiFi mode OFF...");
|
||||||
WiFi.mode(WIFI_OFF);
|
WiFi.mode(WIFI_OFF);
|
||||||
delay(30); // Allow WiFi hardware to power down
|
delay(30); // Allow WiFi hardware to power down
|
||||||
|
|
||||||
Serial.printf("[%lu] [WEBACT] [MEM] Free heap after WiFi disconnect: %d bytes\n", millis(), ESP.getFreeHeap());
|
LOG_DBG("WEBACT] [MEM", "Free heap after WiFi disconnect: %d bytes", ESP.getFreeHeap());
|
||||||
|
|
||||||
// Acquire mutex before deleting task
|
// Acquire mutex before deleting task
|
||||||
Serial.printf("[%lu] [WEBACT] Acquiring rendering mutex before task deletion...\n", millis());
|
LOG_DBG("WEBACT", "Acquiring rendering mutex before task deletion...");
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
|
|
||||||
// Delete the display task
|
// Delete the display task
|
||||||
Serial.printf("[%lu] [WEBACT] Deleting display task...\n", millis());
|
LOG_DBG("WEBACT", "Deleting display task...");
|
||||||
if (displayTaskHandle) {
|
if (displayTaskHandle) {
|
||||||
vTaskDelete(displayTaskHandle);
|
vTaskDelete(displayTaskHandle);
|
||||||
displayTaskHandle = nullptr;
|
displayTaskHandle = nullptr;
|
||||||
Serial.printf("[%lu] [WEBACT] Display task deleted\n", millis());
|
LOG_DBG("WEBACT", "Display task deleted");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the mutex
|
// Delete the mutex
|
||||||
Serial.printf("[%lu] [WEBACT] Deleting mutex...\n", millis());
|
LOG_DBG("WEBACT", "Deleting mutex...");
|
||||||
vSemaphoreDelete(renderingMutex);
|
vSemaphoreDelete(renderingMutex);
|
||||||
renderingMutex = nullptr;
|
renderingMutex = nullptr;
|
||||||
Serial.printf("[%lu] [WEBACT] Mutex deleted\n", millis());
|
LOG_DBG("WEBACT", "Mutex deleted");
|
||||||
|
|
||||||
Serial.printf("[%lu] [WEBACT] [MEM] Free heap at onExit end: %d bytes\n", millis(), ESP.getFreeHeap());
|
LOG_DBG("WEBACT] [MEM", "Free heap at onExit end: %d bytes", ESP.getFreeHeap());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CrossPointWebServerActivity::onNetworkModeSelected(const NetworkMode mode) {
|
void CrossPointWebServerActivity::onNetworkModeSelected(const NetworkMode mode) {
|
||||||
@@ -133,7 +133,7 @@ void CrossPointWebServerActivity::onNetworkModeSelected(const NetworkMode mode)
|
|||||||
} else if (mode == NetworkMode::CREATE_HOTSPOT) {
|
} else if (mode == NetworkMode::CREATE_HOTSPOT) {
|
||||||
modeName = "Create Hotspot";
|
modeName = "Create Hotspot";
|
||||||
}
|
}
|
||||||
Serial.printf("[%lu] [WEBACT] Network mode selected: %s\n", millis(), modeName);
|
LOG_DBG("WEBACT", "Network mode selected: %s", modeName);
|
||||||
|
|
||||||
networkMode = mode;
|
networkMode = mode;
|
||||||
isApMode = (mode == NetworkMode::CREATE_HOTSPOT);
|
isApMode = (mode == NetworkMode::CREATE_HOTSPOT);
|
||||||
@@ -155,11 +155,11 @@ void CrossPointWebServerActivity::onNetworkModeSelected(const NetworkMode mode)
|
|||||||
|
|
||||||
if (mode == NetworkMode::JOIN_NETWORK) {
|
if (mode == NetworkMode::JOIN_NETWORK) {
|
||||||
// STA mode - launch WiFi selection
|
// STA mode - launch WiFi selection
|
||||||
Serial.printf("[%lu] [WEBACT] Turning on WiFi (STA mode)...\n", millis());
|
LOG_DBG("WEBACT", "Turning on WiFi (STA mode)...");
|
||||||
WiFi.mode(WIFI_STA);
|
WiFi.mode(WIFI_STA);
|
||||||
|
|
||||||
state = WebServerActivityState::WIFI_SELECTION;
|
state = WebServerActivityState::WIFI_SELECTION;
|
||||||
Serial.printf("[%lu] [WEBACT] Launching WifiSelectionActivity...\n", millis());
|
LOG_DBG("WEBACT", "Launching WifiSelectionActivity...");
|
||||||
enterNewActivity(new WifiSelectionActivity(renderer, mappedInput,
|
enterNewActivity(new WifiSelectionActivity(renderer, mappedInput,
|
||||||
[this](const bool connected) { onWifiSelectionComplete(connected); }));
|
[this](const bool connected) { onWifiSelectionComplete(connected); }));
|
||||||
} else {
|
} else {
|
||||||
@@ -171,7 +171,7 @@ void CrossPointWebServerActivity::onNetworkModeSelected(const NetworkMode mode)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CrossPointWebServerActivity::onWifiSelectionComplete(const bool connected) {
|
void CrossPointWebServerActivity::onWifiSelectionComplete(const bool connected) {
|
||||||
Serial.printf("[%lu] [WEBACT] WifiSelectionActivity completed, connected=%d\n", millis(), connected);
|
LOG_DBG("WEBACT", "WifiSelectionActivity completed, connected=%d", connected);
|
||||||
|
|
||||||
if (connected) {
|
if (connected) {
|
||||||
// Get connection info before exiting subactivity
|
// Get connection info before exiting subactivity
|
||||||
@@ -183,7 +183,7 @@ void CrossPointWebServerActivity::onWifiSelectionComplete(const bool connected)
|
|||||||
|
|
||||||
// Start mDNS for hostname resolution
|
// Start mDNS for hostname resolution
|
||||||
if (MDNS.begin(AP_HOSTNAME)) {
|
if (MDNS.begin(AP_HOSTNAME)) {
|
||||||
Serial.printf("[%lu] [WEBACT] mDNS started: http://%s.local/\n", millis(), AP_HOSTNAME);
|
LOG_DBG("WEBACT", "mDNS started: http://%s.local/", AP_HOSTNAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the web server
|
// Start the web server
|
||||||
@@ -199,8 +199,8 @@ void CrossPointWebServerActivity::onWifiSelectionComplete(const bool connected)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CrossPointWebServerActivity::startAccessPoint() {
|
void CrossPointWebServerActivity::startAccessPoint() {
|
||||||
Serial.printf("[%lu] [WEBACT] Starting Access Point mode...\n", millis());
|
LOG_DBG("WEBACT", "Starting Access Point mode...");
|
||||||
Serial.printf("[%lu] [WEBACT] [MEM] Free heap before AP start: %d bytes\n", millis(), ESP.getFreeHeap());
|
LOG_DBG("WEBACT] [MEM", "Free heap before AP start: %d bytes", ESP.getFreeHeap());
|
||||||
|
|
||||||
// Configure and start the AP
|
// Configure and start the AP
|
||||||
WiFi.mode(WIFI_AP);
|
WiFi.mode(WIFI_AP);
|
||||||
@@ -216,7 +216,7 @@ void CrossPointWebServerActivity::startAccessPoint() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!apStarted) {
|
if (!apStarted) {
|
||||||
Serial.printf("[%lu] [WEBACT] ERROR: Failed to start Access Point!\n", millis());
|
LOG_ERR("WEBACT", "ERROR: Failed to start Access Point!");
|
||||||
onGoBack();
|
onGoBack();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -230,15 +230,15 @@ void CrossPointWebServerActivity::startAccessPoint() {
|
|||||||
connectedIP = ipStr;
|
connectedIP = ipStr;
|
||||||
connectedSSID = AP_SSID;
|
connectedSSID = AP_SSID;
|
||||||
|
|
||||||
Serial.printf("[%lu] [WEBACT] Access Point started!\n", millis());
|
LOG_DBG("WEBACT", "Access Point started!");
|
||||||
Serial.printf("[%lu] [WEBACT] SSID: %s\n", millis(), AP_SSID);
|
LOG_DBG("WEBACT", "SSID: %s", AP_SSID);
|
||||||
Serial.printf("[%lu] [WEBACT] IP: %s\n", millis(), connectedIP.c_str());
|
LOG_DBG("WEBACT", "IP: %s", connectedIP.c_str());
|
||||||
|
|
||||||
// Start mDNS for hostname resolution
|
// Start mDNS for hostname resolution
|
||||||
if (MDNS.begin(AP_HOSTNAME)) {
|
if (MDNS.begin(AP_HOSTNAME)) {
|
||||||
Serial.printf("[%lu] [WEBACT] mDNS started: http://%s.local/\n", millis(), AP_HOSTNAME);
|
LOG_DBG("WEBACT", "mDNS started: http://%s.local/", AP_HOSTNAME);
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("[%lu] [WEBACT] WARNING: mDNS failed to start\n", millis());
|
LOG_DBG("WEBACT", "WARNING: mDNS failed to start");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start DNS server for captive portal behavior
|
// Start DNS server for captive portal behavior
|
||||||
@@ -246,16 +246,16 @@ void CrossPointWebServerActivity::startAccessPoint() {
|
|||||||
dnsServer = new DNSServer();
|
dnsServer = new DNSServer();
|
||||||
dnsServer->setErrorReplyCode(DNSReplyCode::NoError);
|
dnsServer->setErrorReplyCode(DNSReplyCode::NoError);
|
||||||
dnsServer->start(DNS_PORT, "*", apIP);
|
dnsServer->start(DNS_PORT, "*", apIP);
|
||||||
Serial.printf("[%lu] [WEBACT] DNS server started for captive portal\n", millis());
|
LOG_DBG("WEBACT", "DNS server started for captive portal");
|
||||||
|
|
||||||
Serial.printf("[%lu] [WEBACT] [MEM] Free heap after AP start: %d bytes\n", millis(), ESP.getFreeHeap());
|
LOG_DBG("WEBACT] [MEM", "Free heap after AP start: %d bytes", ESP.getFreeHeap());
|
||||||
|
|
||||||
// Start the web server
|
// Start the web server
|
||||||
startWebServer();
|
startWebServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CrossPointWebServerActivity::startWebServer() {
|
void CrossPointWebServerActivity::startWebServer() {
|
||||||
Serial.printf("[%lu] [WEBACT] Starting web server...\n", millis());
|
LOG_DBG("WEBACT", "Starting web server...");
|
||||||
|
|
||||||
// Create the web server instance
|
// Create the web server instance
|
||||||
webServer.reset(new CrossPointWebServer());
|
webServer.reset(new CrossPointWebServer());
|
||||||
@@ -263,16 +263,16 @@ void CrossPointWebServerActivity::startWebServer() {
|
|||||||
|
|
||||||
if (webServer->isRunning()) {
|
if (webServer->isRunning()) {
|
||||||
state = WebServerActivityState::SERVER_RUNNING;
|
state = WebServerActivityState::SERVER_RUNNING;
|
||||||
Serial.printf("[%lu] [WEBACT] Web server started successfully\n", millis());
|
LOG_DBG("WEBACT", "Web server started successfully");
|
||||||
|
|
||||||
// Force an immediate render since we're transitioning from a subactivity
|
// Force an immediate render since we're transitioning from a subactivity
|
||||||
// that had its own rendering task. We need to make sure our display is shown.
|
// that had its own rendering task. We need to make sure our display is shown.
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
render();
|
render();
|
||||||
xSemaphoreGive(renderingMutex);
|
xSemaphoreGive(renderingMutex);
|
||||||
Serial.printf("[%lu] [WEBACT] Rendered File Transfer screen\n", millis());
|
LOG_DBG("WEBACT", "Rendered File Transfer screen");
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("[%lu] [WEBACT] ERROR: Failed to start web server!\n", millis());
|
LOG_ERR("WEBACT", "ERROR: Failed to start web server!");
|
||||||
webServer.reset();
|
webServer.reset();
|
||||||
// Go back on error
|
// Go back on error
|
||||||
onGoBack();
|
onGoBack();
|
||||||
@@ -281,9 +281,9 @@ void CrossPointWebServerActivity::startWebServer() {
|
|||||||
|
|
||||||
void CrossPointWebServerActivity::stopWebServer() {
|
void CrossPointWebServerActivity::stopWebServer() {
|
||||||
if (webServer && webServer->isRunning()) {
|
if (webServer && webServer->isRunning()) {
|
||||||
Serial.printf("[%lu] [WEBACT] Stopping web server...\n", millis());
|
LOG_DBG("WEBACT", "Stopping web server...");
|
||||||
webServer->stop();
|
webServer->stop();
|
||||||
Serial.printf("[%lu] [WEBACT] Web server stopped\n", millis());
|
LOG_DBG("WEBACT", "Web server stopped");
|
||||||
}
|
}
|
||||||
webServer.reset();
|
webServer.reset();
|
||||||
}
|
}
|
||||||
@@ -309,7 +309,7 @@ void CrossPointWebServerActivity::loop() {
|
|||||||
lastWifiCheck = millis();
|
lastWifiCheck = millis();
|
||||||
const wl_status_t wifiStatus = WiFi.status();
|
const wl_status_t wifiStatus = WiFi.status();
|
||||||
if (wifiStatus != WL_CONNECTED) {
|
if (wifiStatus != WL_CONNECTED) {
|
||||||
Serial.printf("[%lu] [WEBACT] WiFi disconnected! Status: %d\n", millis(), wifiStatus);
|
LOG_DBG("WEBACT", "WiFi disconnected! Status: %d", wifiStatus);
|
||||||
// Show error and exit gracefully
|
// Show error and exit gracefully
|
||||||
state = WebServerActivityState::SHUTTING_DOWN;
|
state = WebServerActivityState::SHUTTING_DOWN;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
@@ -318,7 +318,7 @@ void CrossPointWebServerActivity::loop() {
|
|||||||
// Log weak signal warnings
|
// Log weak signal warnings
|
||||||
const int rssi = WiFi.RSSI();
|
const int rssi = WiFi.RSSI();
|
||||||
if (rssi < -75) {
|
if (rssi < -75) {
|
||||||
Serial.printf("[%lu] [WEBACT] Warning: Weak WiFi signal: %d dBm\n", millis(), rssi);
|
LOG_DBG("WEBACT", "Warning: Weak WiFi signal: %d dBm", rssi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -329,8 +329,7 @@ void CrossPointWebServerActivity::loop() {
|
|||||||
|
|
||||||
// Log if there's a significant gap between handleClient calls (>100ms)
|
// Log if there's a significant gap between handleClient calls (>100ms)
|
||||||
if (lastHandleClientTime > 0 && timeSinceLastHandleClient > 100) {
|
if (lastHandleClientTime > 0 && timeSinceLastHandleClient > 100) {
|
||||||
Serial.printf("[%lu] [WEBACT] WARNING: %lu ms gap since last handleClient\n", millis(),
|
LOG_DBG("WEBACT", "WARNING: %lu ms gap since last handleClient", timeSinceLastHandleClient);
|
||||||
timeSinceLastHandleClient);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset watchdog BEFORE processing - HTTP header parsing can be slow
|
// Reset watchdog BEFORE processing - HTTP header parsing can be slow
|
||||||
@@ -401,7 +400,7 @@ void drawQRCode(const GfxRenderer& renderer, const int x, const int y, const std
|
|||||||
// The structure to manage the QR code
|
// The structure to manage the QR code
|
||||||
QRCode qrcode;
|
QRCode qrcode;
|
||||||
uint8_t qrcodeBytes[qrcode_getBufferSize(4)];
|
uint8_t qrcodeBytes[qrcode_getBufferSize(4)];
|
||||||
Serial.printf("[%lu] [WEBACT] QR Code (%lu): %s\n", millis(), data.length(), data.c_str());
|
LOG_DBG("WEBACT", "QR Code (%lu): %s", data.length(), data.c_str());
|
||||||
|
|
||||||
qrcode_initText(&qrcode, qrcodeBytes, 4, ECC_LOW, data.c_str());
|
qrcode_initText(&qrcode, qrcodeBytes, 4, ECC_LOW, data.c_str());
|
||||||
const uint8_t px = 6; // pixels per module
|
const uint8_t px = 6; // pixels per module
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "WifiSelectionActivity.h"
|
#include "WifiSelectionActivity.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
#include <Logging.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
@@ -62,7 +63,7 @@ void WifiSelectionActivity::onEnter() {
|
|||||||
if (!lastSsid.empty()) {
|
if (!lastSsid.empty()) {
|
||||||
const auto* cred = WIFI_STORE.findCredential(lastSsid);
|
const auto* cred = WIFI_STORE.findCredential(lastSsid);
|
||||||
if (cred) {
|
if (cred) {
|
||||||
Serial.printf("[%lu] [WIFI] Attempting to auto-connect to %s\n", millis(), lastSsid.c_str());
|
LOG_DBG("WIFI", "Attempting to auto-connect to %s", lastSsid.c_str());
|
||||||
selectedSSID = cred->ssid;
|
selectedSSID = cred->ssid;
|
||||||
enteredPassword = cred->password;
|
enteredPassword = cred->password;
|
||||||
selectedRequiresPassword = !cred->password.empty();
|
selectedRequiresPassword = !cred->password.empty();
|
||||||
@@ -82,12 +83,12 @@ void WifiSelectionActivity::onEnter() {
|
|||||||
void WifiSelectionActivity::onExit() {
|
void WifiSelectionActivity::onExit() {
|
||||||
Activity::onExit();
|
Activity::onExit();
|
||||||
|
|
||||||
Serial.printf("[%lu] [WIFI] [MEM] Free heap at onExit start: %d bytes\n", millis(), ESP.getFreeHeap());
|
LOG_DBG("WIFI] [MEM", "Free heap at onExit start: %d bytes", ESP.getFreeHeap());
|
||||||
|
|
||||||
// Stop any ongoing WiFi scan
|
// Stop any ongoing WiFi scan
|
||||||
Serial.printf("[%lu] [WIFI] Deleting WiFi scan...\n", millis());
|
LOG_DBG("WIFI", "Deleting WiFi scan...");
|
||||||
WiFi.scanDelete();
|
WiFi.scanDelete();
|
||||||
Serial.printf("[%lu] [WIFI] [MEM] Free heap after scanDelete: %d bytes\n", millis(), ESP.getFreeHeap());
|
LOG_DBG("WIFI] [MEM", "Free heap after scanDelete: %d bytes", ESP.getFreeHeap());
|
||||||
|
|
||||||
// Note: We do NOT disconnect WiFi here - the parent activity
|
// Note: We do NOT disconnect WiFi here - the parent activity
|
||||||
// (CrossPointWebServerActivity) manages WiFi connection state. We just clean
|
// (CrossPointWebServerActivity) manages WiFi connection state. We just clean
|
||||||
@@ -95,25 +96,25 @@ void WifiSelectionActivity::onExit() {
|
|||||||
|
|
||||||
// Acquire mutex before deleting task to ensure task isn't using it
|
// Acquire mutex before deleting task to ensure task isn't using it
|
||||||
// This prevents hangs/crashes if the task holds the mutex when deleted
|
// This prevents hangs/crashes if the task holds the mutex when deleted
|
||||||
Serial.printf("[%lu] [WIFI] Acquiring rendering mutex before task deletion...\n", millis());
|
LOG_DBG("WIFI", "Acquiring rendering mutex before task deletion...");
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
|
|
||||||
// Delete the display task (we now hold the mutex, so task is blocked if it
|
// Delete the display task (we now hold the mutex, so task is blocked if it
|
||||||
// needs it)
|
// needs it)
|
||||||
Serial.printf("[%lu] [WIFI] Deleting display task...\n", millis());
|
LOG_DBG("WIFI", "Deleting display task...");
|
||||||
if (displayTaskHandle) {
|
if (displayTaskHandle) {
|
||||||
vTaskDelete(displayTaskHandle);
|
vTaskDelete(displayTaskHandle);
|
||||||
displayTaskHandle = nullptr;
|
displayTaskHandle = nullptr;
|
||||||
Serial.printf("[%lu] [WIFI] Display task deleted\n", millis());
|
LOG_DBG("WIFI", "Display task deleted");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now safe to delete the mutex since we own it
|
// Now safe to delete the mutex since we own it
|
||||||
Serial.printf("[%lu] [WIFI] Deleting mutex...\n", millis());
|
LOG_DBG("WIFI", "Deleting mutex...");
|
||||||
vSemaphoreDelete(renderingMutex);
|
vSemaphoreDelete(renderingMutex);
|
||||||
renderingMutex = nullptr;
|
renderingMutex = nullptr;
|
||||||
Serial.printf("[%lu] [WIFI] Mutex deleted\n", millis());
|
LOG_DBG("WIFI", "Mutex deleted");
|
||||||
|
|
||||||
Serial.printf("[%lu] [WIFI] [MEM] Free heap at onExit end: %d bytes\n", millis(), ESP.getFreeHeap());
|
LOG_DBG("WIFI] [MEM", "Free heap at onExit end: %d bytes", ESP.getFreeHeap());
|
||||||
}
|
}
|
||||||
|
|
||||||
void WifiSelectionActivity::startWifiScan() {
|
void WifiSelectionActivity::startWifiScan() {
|
||||||
@@ -211,8 +212,7 @@ void WifiSelectionActivity::selectNetwork(const int index) {
|
|||||||
// Use saved password - connect directly
|
// Use saved password - connect directly
|
||||||
enteredPassword = savedCred->password;
|
enteredPassword = savedCred->password;
|
||||||
usedSavedPassword = true;
|
usedSavedPassword = true;
|
||||||
Serial.printf("[%lu] [WiFi] Using saved password for %s, length: %zu\n", millis(), selectedSSID.c_str(),
|
LOG_DBG("WiFi", "Using saved password for %s, length: %zu", selectedSSID.c_str(), enteredPassword.size());
|
||||||
enteredPassword.size());
|
|
||||||
attemptConnection();
|
attemptConnection();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -290,10 +290,9 @@ void WifiSelectionActivity::checkConnectionStatus() {
|
|||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
} else {
|
} else {
|
||||||
// Using saved password or open network - complete immediately
|
// Using saved password or open network - complete immediately
|
||||||
Serial.printf(
|
LOG_DBG("WIFI",
|
||||||
"[%lu] [WIFI] Connected with saved/open credentials, "
|
"Connected with saved/open credentials, "
|
||||||
"completing immediately\n",
|
"completing immediately");
|
||||||
millis());
|
|
||||||
onComplete(true);
|
onComplete(true);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <FsHelpers.h>
|
#include <FsHelpers.h>
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
|
#include <Logging.h>
|
||||||
|
|
||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
#include "CrossPointState.h"
|
#include "CrossPointState.h"
|
||||||
@@ -84,7 +85,7 @@ void EpubReaderActivity::onEnter() {
|
|||||||
currentSpineIndex = data[0] + (data[1] << 8);
|
currentSpineIndex = data[0] + (data[1] << 8);
|
||||||
nextPageNumber = data[2] + (data[3] << 8);
|
nextPageNumber = data[2] + (data[3] << 8);
|
||||||
cachedSpineIndex = currentSpineIndex;
|
cachedSpineIndex = currentSpineIndex;
|
||||||
Serial.printf("[%lu] [ERS] Loaded cache: %d, %d\n", millis(), currentSpineIndex, nextPageNumber);
|
LOG_DBG("ERS", "Loaded cache: %d, %d", currentSpineIndex, nextPageNumber);
|
||||||
}
|
}
|
||||||
if (dataSize == 6) {
|
if (dataSize == 6) {
|
||||||
cachedChapterTotalPageCount = data[4] + (data[5] << 8);
|
cachedChapterTotalPageCount = data[4] + (data[5] << 8);
|
||||||
@@ -97,8 +98,7 @@ void EpubReaderActivity::onEnter() {
|
|||||||
int textSpineIndex = epub->getSpineIndexForTextReference();
|
int textSpineIndex = epub->getSpineIndexForTextReference();
|
||||||
if (textSpineIndex != 0) {
|
if (textSpineIndex != 0) {
|
||||||
currentSpineIndex = textSpineIndex;
|
currentSpineIndex = textSpineIndex;
|
||||||
Serial.printf("[%lu] [ERS] Opened for first time, navigating to text reference at index %d\n", millis(),
|
LOG_DBG("ERS", "Opened for first time, navigating to text reference at index %d", textSpineIndex);
|
||||||
textSpineIndex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -567,7 +567,7 @@ void EpubReaderActivity::renderScreen() {
|
|||||||
|
|
||||||
if (!section) {
|
if (!section) {
|
||||||
const auto filepath = epub->getSpineItem(currentSpineIndex).href;
|
const auto filepath = epub->getSpineItem(currentSpineIndex).href;
|
||||||
Serial.printf("[%lu] [ERS] Loading file: %s, index: %d\n", millis(), filepath.c_str(), currentSpineIndex);
|
LOG_DBG("ERS", "Loading file: %s, index: %d", filepath.c_str(), currentSpineIndex);
|
||||||
section = std::unique_ptr<Section>(new Section(epub, currentSpineIndex, renderer));
|
section = std::unique_ptr<Section>(new Section(epub, currentSpineIndex, renderer));
|
||||||
|
|
||||||
const uint16_t viewportWidth = renderer.getScreenWidth() - orientedMarginLeft - orientedMarginRight;
|
const uint16_t viewportWidth = renderer.getScreenWidth() - orientedMarginLeft - orientedMarginRight;
|
||||||
@@ -576,19 +576,19 @@ void EpubReaderActivity::renderScreen() {
|
|||||||
if (!section->loadSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(),
|
if (!section->loadSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(),
|
||||||
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
|
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
|
||||||
viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle)) {
|
viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle)) {
|
||||||
Serial.printf("[%lu] [ERS] Cache not found, building...\n", millis());
|
LOG_DBG("ERS", "Cache not found, building...");
|
||||||
|
|
||||||
const auto popupFn = [this]() { GUI.drawPopup(renderer, "Indexing..."); };
|
const auto popupFn = [this]() { GUI.drawPopup(renderer, "Indexing..."); };
|
||||||
|
|
||||||
if (!section->createSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(),
|
if (!section->createSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(),
|
||||||
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
|
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
|
||||||
viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle, popupFn)) {
|
viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle, popupFn)) {
|
||||||
Serial.printf("[%lu] [ERS] Failed to persist page data to SD\n", millis());
|
LOG_ERR("ERS", "Failed to persist page data to SD");
|
||||||
section.reset();
|
section.reset();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("[%lu] [ERS] Cache found, skipping build...\n", millis());
|
LOG_DBG("ERS", "Cache found, skipping build...");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextPageNumber == UINT16_MAX) {
|
if (nextPageNumber == UINT16_MAX) {
|
||||||
@@ -622,7 +622,7 @@ void EpubReaderActivity::renderScreen() {
|
|||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
|
|
||||||
if (section->pageCount == 0) {
|
if (section->pageCount == 0) {
|
||||||
Serial.printf("[%lu] [ERS] No pages to render\n", millis());
|
LOG_DBG("ERS", "No pages to render");
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, "Empty chapter", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, 300, "Empty chapter", true, EpdFontFamily::BOLD);
|
||||||
renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
|
renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
@@ -630,7 +630,7 @@ void EpubReaderActivity::renderScreen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (section->currentPage < 0 || section->currentPage >= section->pageCount) {
|
if (section->currentPage < 0 || section->currentPage >= section->pageCount) {
|
||||||
Serial.printf("[%lu] [ERS] Page out of bounds: %d (max %d)\n", millis(), section->currentPage, section->pageCount);
|
LOG_DBG("ERS", "Page out of bounds: %d (max %d)", section->currentPage, section->pageCount);
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, "Out of bounds", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, 300, "Out of bounds", true, EpdFontFamily::BOLD);
|
||||||
renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
|
renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
@@ -640,14 +640,14 @@ void EpubReaderActivity::renderScreen() {
|
|||||||
{
|
{
|
||||||
auto p = section->loadPageFromSectionFile();
|
auto p = section->loadPageFromSectionFile();
|
||||||
if (!p) {
|
if (!p) {
|
||||||
Serial.printf("[%lu] [ERS] Failed to load page from SD - clearing section cache\n", millis());
|
LOG_ERR("ERS", "Failed to load page from SD - clearing section cache");
|
||||||
section->clearCache();
|
section->clearCache();
|
||||||
section.reset();
|
section.reset();
|
||||||
return renderScreen();
|
return renderScreen();
|
||||||
}
|
}
|
||||||
const auto start = millis();
|
const auto start = millis();
|
||||||
renderContents(std::move(p), orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
|
renderContents(std::move(p), orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
|
||||||
Serial.printf("[%lu] [ERS] Rendered page in %dms\n", millis(), millis() - start);
|
LOG_DBG("ERS", "Rendered page in %dms", millis() - start);
|
||||||
}
|
}
|
||||||
saveProgress(currentSpineIndex, section->currentPage, section->pageCount);
|
saveProgress(currentSpineIndex, section->currentPage, section->pageCount);
|
||||||
}
|
}
|
||||||
@@ -664,9 +664,9 @@ void EpubReaderActivity::saveProgress(int spineIndex, int currentPage, int pageC
|
|||||||
data[5] = (pageCount >> 8) & 0xFF;
|
data[5] = (pageCount >> 8) & 0xFF;
|
||||||
f.write(data, 6);
|
f.write(data, 6);
|
||||||
f.close();
|
f.close();
|
||||||
Serial.printf("[%lu] [ERS] Progress saved: Chapter %d, Page %d\n", millis(), spineIndex, currentPage);
|
LOG_DBG("ERS", "Progress saved: Chapter %d, Page %d", spineIndex, currentPage);
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("[%lu] [ERS] Could not save progress!\n", millis());
|
LOG_ERR("ERS", "Could not save progress!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int orientedMarginTop,
|
void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int orientedMarginTop,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "KOReaderSyncActivity.h"
|
#include "KOReaderSyncActivity.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
#include <Logging.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <esp_sntp.h>
|
#include <esp_sntp.h>
|
||||||
|
|
||||||
@@ -32,9 +33,9 @@ void syncTimeWithNTP() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (retry < maxRetries) {
|
if (retry < maxRetries) {
|
||||||
Serial.printf("[%lu] [KOSync] NTP time synced\n", millis());
|
LOG_DBG("KOSync", "NTP time synced");
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("[%lu] [KOSync] NTP sync timeout, using fallback\n", millis());
|
LOG_DBG("KOSync", "NTP sync timeout, using fallback");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -48,12 +49,12 @@ void KOReaderSyncActivity::onWifiSelectionComplete(const bool success) {
|
|||||||
exitActivity();
|
exitActivity();
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
Serial.printf("[%lu] [KOSync] WiFi connection failed, exiting\n", millis());
|
LOG_DBG("KOSync", "WiFi connection failed, exiting");
|
||||||
onCancel();
|
onCancel();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [KOSync] WiFi connected, starting sync\n", millis());
|
LOG_DBG("KOSync", "WiFi connected, starting sync");
|
||||||
|
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
state = SYNCING;
|
state = SYNCING;
|
||||||
@@ -88,7 +89,7 @@ void KOReaderSyncActivity::performSync() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [KOSync] Document hash: %s\n", millis(), documentHash.c_str());
|
LOG_DBG("KOSync", "Document hash: %s", documentHash.c_str());
|
||||||
|
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
statusMessage = "Fetching remote progress...";
|
statusMessage = "Fetching remote progress...";
|
||||||
@@ -188,12 +189,12 @@ void KOReaderSyncActivity::onEnter() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Turn on WiFi
|
// Turn on WiFi
|
||||||
Serial.printf("[%lu] [KOSync] Turning on WiFi...\n", millis());
|
LOG_DBG("KOSync", "Turning on WiFi...");
|
||||||
WiFi.mode(WIFI_STA);
|
WiFi.mode(WIFI_STA);
|
||||||
|
|
||||||
// Check if already connected
|
// Check if already connected
|
||||||
if (WiFi.status() == WL_CONNECTED) {
|
if (WiFi.status() == WL_CONNECTED) {
|
||||||
Serial.printf("[%lu] [KOSync] Already connected to WiFi\n", millis());
|
LOG_DBG("KOSync", "Already connected to WiFi");
|
||||||
state = SYNCING;
|
state = SYNCING;
|
||||||
statusMessage = "Syncing time...";
|
statusMessage = "Syncing time...";
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
@@ -216,7 +217,7 @@ void KOReaderSyncActivity::onEnter() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Launch WiFi selection subactivity
|
// Launch WiFi selection subactivity
|
||||||
Serial.printf("[%lu] [KOSync] Launching WifiSelectionActivity...\n", millis());
|
LOG_DBG("KOSync", "Launching WifiSelectionActivity...");
|
||||||
enterNewActivity(new WifiSelectionActivity(renderer, mappedInput,
|
enterNewActivity(new WifiSelectionActivity(renderer, mappedInput,
|
||||||
[this](const bool connected) { onWifiSelectionComplete(connected); }));
|
[this](const bool connected) { onWifiSelectionComplete(connected); }));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ bool ReaderActivity::isTxtFile(const std::string& path) {
|
|||||||
|
|
||||||
std::unique_ptr<Epub> ReaderActivity::loadEpub(const std::string& path) {
|
std::unique_ptr<Epub> ReaderActivity::loadEpub(const std::string& path) {
|
||||||
if (!Storage.exists(path.c_str())) {
|
if (!Storage.exists(path.c_str())) {
|
||||||
Serial.printf("[%lu] [ ] File does not exist: %s\n", millis(), path.c_str());
|
LOG_ERR("READER", "File does not exist: %s", path.c_str());
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,13 +39,13 @@ std::unique_ptr<Epub> ReaderActivity::loadEpub(const std::string& path) {
|
|||||||
return epub;
|
return epub;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [ ] Failed to load epub\n", millis());
|
LOG_ERR("READER", "Failed to load epub");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Xtc> ReaderActivity::loadXtc(const std::string& path) {
|
std::unique_ptr<Xtc> ReaderActivity::loadXtc(const std::string& path) {
|
||||||
if (!Storage.exists(path.c_str())) {
|
if (!Storage.exists(path.c_str())) {
|
||||||
Serial.printf("[%lu] [ ] File does not exist: %s\n", millis(), path.c_str());
|
LOG_ERR("READER", "File does not exist: %s", path.c_str());
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,13 +54,13 @@ std::unique_ptr<Xtc> ReaderActivity::loadXtc(const std::string& path) {
|
|||||||
return xtc;
|
return xtc;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [ ] Failed to load XTC\n", millis());
|
LOG_ERR("READER", "Failed to load XTC");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Txt> ReaderActivity::loadTxt(const std::string& path) {
|
std::unique_ptr<Txt> ReaderActivity::loadTxt(const std::string& path) {
|
||||||
if (!Storage.exists(path.c_str())) {
|
if (!Storage.exists(path.c_str())) {
|
||||||
Serial.printf("[%lu] [ ] File does not exist: %s\n", millis(), path.c_str());
|
LOG_ERR("READER", "File does not exist: %s", path.c_str());
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ std::unique_ptr<Txt> ReaderActivity::loadTxt(const std::string& path) {
|
|||||||
return txt;
|
return txt;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [ ] Failed to load TXT\n", millis());
|
LOG_ERR("READER", "Failed to load TXT");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -191,8 +191,7 @@ void TxtReaderActivity::initializeReader() {
|
|||||||
linesPerPage = viewportHeight / lineHeight;
|
linesPerPage = viewportHeight / lineHeight;
|
||||||
if (linesPerPage < 1) linesPerPage = 1;
|
if (linesPerPage < 1) linesPerPage = 1;
|
||||||
|
|
||||||
Serial.printf("[%lu] [TRS] Viewport: %dx%d, lines per page: %d\n", millis(), viewportWidth, viewportHeight,
|
LOG_DBG("TRS", "Viewport: %dx%d, lines per page: %d", viewportWidth, viewportHeight, linesPerPage);
|
||||||
linesPerPage);
|
|
||||||
|
|
||||||
// Try to load cached page index first
|
// Try to load cached page index first
|
||||||
if (!loadPageIndexCache()) {
|
if (!loadPageIndexCache()) {
|
||||||
@@ -215,7 +214,7 @@ void TxtReaderActivity::buildPageIndex() {
|
|||||||
size_t offset = 0;
|
size_t offset = 0;
|
||||||
const size_t fileSize = txt->getFileSize();
|
const size_t fileSize = txt->getFileSize();
|
||||||
|
|
||||||
Serial.printf("[%lu] [TRS] Building page index for %zu bytes...\n", millis(), fileSize);
|
LOG_DBG("TRS", "Building page index for %zu bytes...", fileSize);
|
||||||
|
|
||||||
GUI.drawPopup(renderer, "Indexing...");
|
GUI.drawPopup(renderer, "Indexing...");
|
||||||
|
|
||||||
@@ -244,7 +243,7 @@ void TxtReaderActivity::buildPageIndex() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
totalPages = pageOffsets.size();
|
totalPages = pageOffsets.size();
|
||||||
Serial.printf("[%lu] [TRS] Built page index: %d pages\n", millis(), totalPages);
|
LOG_DBG("TRS", "Built page index: %d pages", totalPages);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TxtReaderActivity::loadPageAtOffset(size_t offset, std::vector<std::string>& outLines, size_t& nextOffset) {
|
bool TxtReaderActivity::loadPageAtOffset(size_t offset, std::vector<std::string>& outLines, size_t& nextOffset) {
|
||||||
@@ -259,7 +258,7 @@ bool TxtReaderActivity::loadPageAtOffset(size_t offset, std::vector<std::string>
|
|||||||
size_t chunkSize = std::min(CHUNK_SIZE, fileSize - offset);
|
size_t chunkSize = std::min(CHUNK_SIZE, fileSize - offset);
|
||||||
auto* buffer = static_cast<uint8_t*>(malloc(chunkSize + 1));
|
auto* buffer = static_cast<uint8_t*>(malloc(chunkSize + 1));
|
||||||
if (!buffer) {
|
if (!buffer) {
|
||||||
Serial.printf("[%lu] [TRS] Failed to allocate %zu bytes\n", millis(), chunkSize);
|
LOG_ERR("TRS", "Failed to allocate %zu bytes", chunkSize);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -588,7 +587,7 @@ void TxtReaderActivity::loadProgress() {
|
|||||||
if (currentPage < 0) {
|
if (currentPage < 0) {
|
||||||
currentPage = 0;
|
currentPage = 0;
|
||||||
}
|
}
|
||||||
Serial.printf("[%lu] [TRS] Loaded progress: page %d/%d\n", millis(), currentPage, totalPages);
|
LOG_DBG("TRS", "Loaded progress: page %d/%d", currentPage, totalPages);
|
||||||
}
|
}
|
||||||
f.close();
|
f.close();
|
||||||
}
|
}
|
||||||
@@ -610,7 +609,7 @@ bool TxtReaderActivity::loadPageIndexCache() {
|
|||||||
std::string cachePath = txt->getCachePath() + "/index.bin";
|
std::string cachePath = txt->getCachePath() + "/index.bin";
|
||||||
FsFile f;
|
FsFile f;
|
||||||
if (!Storage.openFileForRead("TRS", cachePath, f)) {
|
if (!Storage.openFileForRead("TRS", cachePath, f)) {
|
||||||
Serial.printf("[%lu] [TRS] No page index cache found\n", millis());
|
LOG_DBG("TRS", "No page index cache found");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -618,7 +617,7 @@ bool TxtReaderActivity::loadPageIndexCache() {
|
|||||||
uint32_t magic;
|
uint32_t magic;
|
||||||
serialization::readPod(f, magic);
|
serialization::readPod(f, magic);
|
||||||
if (magic != CACHE_MAGIC) {
|
if (magic != CACHE_MAGIC) {
|
||||||
Serial.printf("[%lu] [TRS] Cache magic mismatch, rebuilding\n", millis());
|
LOG_DBG("TRS", "Cache magic mismatch, rebuilding");
|
||||||
f.close();
|
f.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -626,7 +625,7 @@ bool TxtReaderActivity::loadPageIndexCache() {
|
|||||||
uint8_t version;
|
uint8_t version;
|
||||||
serialization::readPod(f, version);
|
serialization::readPod(f, version);
|
||||||
if (version != CACHE_VERSION) {
|
if (version != CACHE_VERSION) {
|
||||||
Serial.printf("[%lu] [TRS] Cache version mismatch (%d != %d), rebuilding\n", millis(), version, CACHE_VERSION);
|
LOG_DBG("TRS", "Cache version mismatch (%d != %d), rebuilding", version, CACHE_VERSION);
|
||||||
f.close();
|
f.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -634,7 +633,7 @@ bool TxtReaderActivity::loadPageIndexCache() {
|
|||||||
uint32_t fileSize;
|
uint32_t fileSize;
|
||||||
serialization::readPod(f, fileSize);
|
serialization::readPod(f, fileSize);
|
||||||
if (fileSize != txt->getFileSize()) {
|
if (fileSize != txt->getFileSize()) {
|
||||||
Serial.printf("[%lu] [TRS] Cache file size mismatch, rebuilding\n", millis());
|
LOG_DBG("TRS", "Cache file size mismatch, rebuilding");
|
||||||
f.close();
|
f.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -642,7 +641,7 @@ bool TxtReaderActivity::loadPageIndexCache() {
|
|||||||
int32_t cachedWidth;
|
int32_t cachedWidth;
|
||||||
serialization::readPod(f, cachedWidth);
|
serialization::readPod(f, cachedWidth);
|
||||||
if (cachedWidth != viewportWidth) {
|
if (cachedWidth != viewportWidth) {
|
||||||
Serial.printf("[%lu] [TRS] Cache viewport width mismatch, rebuilding\n", millis());
|
LOG_DBG("TRS", "Cache viewport width mismatch, rebuilding");
|
||||||
f.close();
|
f.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -650,7 +649,7 @@ bool TxtReaderActivity::loadPageIndexCache() {
|
|||||||
int32_t cachedLines;
|
int32_t cachedLines;
|
||||||
serialization::readPod(f, cachedLines);
|
serialization::readPod(f, cachedLines);
|
||||||
if (cachedLines != linesPerPage) {
|
if (cachedLines != linesPerPage) {
|
||||||
Serial.printf("[%lu] [TRS] Cache lines per page mismatch, rebuilding\n", millis());
|
LOG_DBG("TRS", "Cache lines per page mismatch, rebuilding");
|
||||||
f.close();
|
f.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -658,7 +657,7 @@ bool TxtReaderActivity::loadPageIndexCache() {
|
|||||||
int32_t fontId;
|
int32_t fontId;
|
||||||
serialization::readPod(f, fontId);
|
serialization::readPod(f, fontId);
|
||||||
if (fontId != cachedFontId) {
|
if (fontId != cachedFontId) {
|
||||||
Serial.printf("[%lu] [TRS] Cache font ID mismatch (%d != %d), rebuilding\n", millis(), fontId, cachedFontId);
|
LOG_DBG("TRS", "Cache font ID mismatch (%d != %d), rebuilding", fontId, cachedFontId);
|
||||||
f.close();
|
f.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -666,7 +665,7 @@ bool TxtReaderActivity::loadPageIndexCache() {
|
|||||||
int32_t margin;
|
int32_t margin;
|
||||||
serialization::readPod(f, margin);
|
serialization::readPod(f, margin);
|
||||||
if (margin != cachedScreenMargin) {
|
if (margin != cachedScreenMargin) {
|
||||||
Serial.printf("[%lu] [TRS] Cache screen margin mismatch, rebuilding\n", millis());
|
LOG_DBG("TRS", "Cache screen margin mismatch, rebuilding");
|
||||||
f.close();
|
f.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -674,7 +673,7 @@ bool TxtReaderActivity::loadPageIndexCache() {
|
|||||||
uint8_t alignment;
|
uint8_t alignment;
|
||||||
serialization::readPod(f, alignment);
|
serialization::readPod(f, alignment);
|
||||||
if (alignment != cachedParagraphAlignment) {
|
if (alignment != cachedParagraphAlignment) {
|
||||||
Serial.printf("[%lu] [TRS] Cache paragraph alignment mismatch, rebuilding\n", millis());
|
LOG_DBG("TRS", "Cache paragraph alignment mismatch, rebuilding");
|
||||||
f.close();
|
f.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -694,7 +693,7 @@ bool TxtReaderActivity::loadPageIndexCache() {
|
|||||||
|
|
||||||
f.close();
|
f.close();
|
||||||
totalPages = pageOffsets.size();
|
totalPages = pageOffsets.size();
|
||||||
Serial.printf("[%lu] [TRS] Loaded page index cache: %d pages\n", millis(), totalPages);
|
LOG_DBG("TRS", "Loaded page index cache: %d pages", totalPages);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -702,7 +701,7 @@ void TxtReaderActivity::savePageIndexCache() const {
|
|||||||
std::string cachePath = txt->getCachePath() + "/index.bin";
|
std::string cachePath = txt->getCachePath() + "/index.bin";
|
||||||
FsFile f;
|
FsFile f;
|
||||||
if (!Storage.openFileForWrite("TRS", cachePath, f)) {
|
if (!Storage.openFileForWrite("TRS", cachePath, f)) {
|
||||||
Serial.printf("[%lu] [TRS] Failed to save page index cache\n", millis());
|
LOG_ERR("TRS", "Failed to save page index cache");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -723,5 +722,5 @@ void TxtReaderActivity::savePageIndexCache() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
f.close();
|
f.close();
|
||||||
Serial.printf("[%lu] [TRS] Saved page index cache: %d pages\n", millis(), totalPages);
|
LOG_DBG("TRS", "Saved page index cache: %d pages", totalPages);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ void XtcReaderActivity::renderPage() {
|
|||||||
// Allocate page buffer
|
// Allocate page buffer
|
||||||
uint8_t* pageBuffer = static_cast<uint8_t*>(malloc(pageBufferSize));
|
uint8_t* pageBuffer = static_cast<uint8_t*>(malloc(pageBufferSize));
|
||||||
if (!pageBuffer) {
|
if (!pageBuffer) {
|
||||||
Serial.printf("[%lu] [XTR] Failed to allocate page buffer (%lu bytes)\n", millis(), pageBufferSize);
|
LOG_ERR("XTR", "Failed to allocate page buffer (%lu bytes)", pageBufferSize);
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, "Memory error", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, 300, "Memory error", true, EpdFontFamily::BOLD);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
@@ -216,7 +216,7 @@ void XtcReaderActivity::renderPage() {
|
|||||||
// Load page data
|
// Load page data
|
||||||
size_t bytesRead = xtc->loadPage(currentPage, pageBuffer, pageBufferSize);
|
size_t bytesRead = xtc->loadPage(currentPage, pageBuffer, pageBufferSize);
|
||||||
if (bytesRead == 0) {
|
if (bytesRead == 0) {
|
||||||
Serial.printf("[%lu] [XTR] Failed to load page %lu\n", millis(), currentPage);
|
LOG_ERR("XTR", "Failed to load page %lu", currentPage);
|
||||||
free(pageBuffer);
|
free(pageBuffer);
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, "Page load error", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, 300, "Page load error", true, EpdFontFamily::BOLD);
|
||||||
@@ -265,8 +265,8 @@ void XtcReaderActivity::renderPage() {
|
|||||||
pixelCounts[getPixelValue(x, y)]++;
|
pixelCounts[getPixelValue(x, y)]++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Serial.printf("[%lu] [XTR] Pixel distribution: White=%lu, DarkGrey=%lu, LightGrey=%lu, Black=%lu\n", millis(),
|
LOG_DBG("XTR", "Pixel distribution: White=%lu, DarkGrey=%lu, LightGrey=%lu, Black=%lu", pixelCounts[0],
|
||||||
pixelCounts[0], pixelCounts[1], pixelCounts[2], pixelCounts[3]);
|
pixelCounts[1], pixelCounts[2], pixelCounts[3]);
|
||||||
|
|
||||||
// Pass 1: BW buffer - draw all non-white pixels as black
|
// Pass 1: BW buffer - draw all non-white pixels as black
|
||||||
for (uint16_t y = 0; y < pageHeight; y++) {
|
for (uint16_t y = 0; y < pageHeight; y++) {
|
||||||
@@ -329,8 +329,7 @@ void XtcReaderActivity::renderPage() {
|
|||||||
|
|
||||||
free(pageBuffer);
|
free(pageBuffer);
|
||||||
|
|
||||||
Serial.printf("[%lu] [XTR] Rendered page %lu/%lu (2-bit grayscale)\n", millis(), currentPage + 1,
|
LOG_DBG("XTR", "Rendered page %lu/%lu (2-bit grayscale)", currentPage + 1, xtc->getPageCount());
|
||||||
xtc->getPageCount());
|
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
// 1-bit mode: 8 pixels per byte, MSB first
|
// 1-bit mode: 8 pixels per byte, MSB first
|
||||||
@@ -366,8 +365,7 @@ void XtcReaderActivity::renderPage() {
|
|||||||
pagesUntilFullRefresh--;
|
pagesUntilFullRefresh--;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [XTR] Rendered page %lu/%lu (%u-bit)\n", millis(), currentPage + 1, xtc->getPageCount(),
|
LOG_DBG("XTR", "Rendered page %lu/%lu (%u-bit)", currentPage + 1, xtc->getPageCount(), bitDepth);
|
||||||
bitDepth);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void XtcReaderActivity::saveProgress() const {
|
void XtcReaderActivity::saveProgress() const {
|
||||||
@@ -389,7 +387,7 @@ void XtcReaderActivity::loadProgress() {
|
|||||||
uint8_t data[4];
|
uint8_t data[4];
|
||||||
if (f.read(data, 4) == 4) {
|
if (f.read(data, 4) == 4) {
|
||||||
currentPage = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
|
currentPage = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
|
||||||
Serial.printf("[%lu] [XTR] Loaded progress: page %lu\n", millis(), currentPage);
|
LOG_DBG("XTR", "Loaded progress: page %lu", currentPage);
|
||||||
|
|
||||||
// Validate page number
|
// Validate page number
|
||||||
if (currentPage >= xtc->getPageCount()) {
|
if (currentPage >= xtc->getPageCount()) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
#include <HardwareSerial.h>
|
#include <Logging.h>
|
||||||
|
|
||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
#include "components/UITheme.h"
|
#include "components/UITheme.h"
|
||||||
@@ -104,12 +104,12 @@ void ClearCacheActivity::render() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ClearCacheActivity::clearCache() {
|
void ClearCacheActivity::clearCache() {
|
||||||
Serial.printf("[%lu] [CLEAR_CACHE] Clearing cache...\n", millis());
|
LOG_DBG("CLEAR_CACHE", "Clearing cache...");
|
||||||
|
|
||||||
// Open .crosspoint directory
|
// Open .crosspoint directory
|
||||||
auto root = Storage.open("/.crosspoint");
|
auto root = Storage.open("/.crosspoint");
|
||||||
if (!root || !root.isDirectory()) {
|
if (!root || !root.isDirectory()) {
|
||||||
Serial.printf("[%lu] [CLEAR_CACHE] Failed to open cache directory\n", millis());
|
LOG_DBG("CLEAR_CACHE", "Failed to open cache directory");
|
||||||
if (root) root.close();
|
if (root) root.close();
|
||||||
state = FAILED;
|
state = FAILED;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
@@ -128,14 +128,14 @@ void ClearCacheActivity::clearCache() {
|
|||||||
// Only delete directories starting with epub_ or xtc_
|
// Only delete directories starting with epub_ or xtc_
|
||||||
if (file.isDirectory() && (itemName.startsWith("epub_") || itemName.startsWith("xtc_"))) {
|
if (file.isDirectory() && (itemName.startsWith("epub_") || itemName.startsWith("xtc_"))) {
|
||||||
String fullPath = "/.crosspoint/" + itemName;
|
String fullPath = "/.crosspoint/" + itemName;
|
||||||
Serial.printf("[%lu] [CLEAR_CACHE] Removing cache: %s\n", millis(), fullPath.c_str());
|
LOG_DBG("CLEAR_CACHE", "Removing cache: %s", fullPath.c_str());
|
||||||
|
|
||||||
file.close(); // Close before attempting to delete
|
file.close(); // Close before attempting to delete
|
||||||
|
|
||||||
if (Storage.removeDir(fullPath.c_str())) {
|
if (Storage.removeDir(fullPath.c_str())) {
|
||||||
clearedCount++;
|
clearedCount++;
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("[%lu] [CLEAR_CACHE] Failed to remove: %s\n", millis(), fullPath.c_str());
|
LOG_ERR("CLEAR_CACHE", "Failed to remove: %s", fullPath.c_str());
|
||||||
failedCount++;
|
failedCount++;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -144,7 +144,7 @@ void ClearCacheActivity::clearCache() {
|
|||||||
}
|
}
|
||||||
root.close();
|
root.close();
|
||||||
|
|
||||||
Serial.printf("[%lu] [CLEAR_CACHE] Cache cleared: %d removed, %d failed\n", millis(), clearedCount, failedCount);
|
LOG_DBG("CLEAR_CACHE", "Cache cleared: %d removed, %d failed", clearedCount, failedCount);
|
||||||
|
|
||||||
state = SUCCESS;
|
state = SUCCESS;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
@@ -153,7 +153,7 @@ void ClearCacheActivity::clearCache() {
|
|||||||
void ClearCacheActivity::loop() {
|
void ClearCacheActivity::loop() {
|
||||||
if (state == WARNING) {
|
if (state == WARNING) {
|
||||||
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||||
Serial.printf("[%lu] [CLEAR_CACHE] User confirmed, starting cache clear\n", millis());
|
LOG_DBG("CLEAR_CACHE", "User confirmed, starting cache clear");
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
state = CLEARING;
|
state = CLEARING;
|
||||||
xSemaphoreGive(renderingMutex);
|
xSemaphoreGive(renderingMutex);
|
||||||
@@ -164,7 +164,7 @@ void ClearCacheActivity::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||||
Serial.printf("[%lu] [CLEAR_CACHE] User cancelled\n", millis());
|
LOG_DBG("CLEAR_CACHE", "User cancelled");
|
||||||
goBack();
|
goBack();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ void OtaUpdateActivity::onWifiSelectionComplete(const bool success) {
|
|||||||
exitActivity();
|
exitActivity();
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
Serial.printf("[%lu] [OTA] WiFi connection failed, exiting\n", millis());
|
LOG_ERR("OTA", "WiFi connection failed, exiting");
|
||||||
goBack();
|
goBack();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [OTA] WiFi connected, checking for update\n", millis());
|
LOG_DBG("OTA", "WiFi connected, checking for update");
|
||||||
|
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
state = CHECKING_FOR_UPDATE;
|
state = CHECKING_FOR_UPDATE;
|
||||||
@@ -32,7 +32,7 @@ void OtaUpdateActivity::onWifiSelectionComplete(const bool success) {
|
|||||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||||
const auto res = updater.checkForUpdate();
|
const auto res = updater.checkForUpdate();
|
||||||
if (res != OtaUpdater::OK) {
|
if (res != OtaUpdater::OK) {
|
||||||
Serial.printf("[%lu] [OTA] Update check failed: %d\n", millis(), res);
|
LOG_DBG("OTA", "Update check failed: %d", res);
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
state = FAILED;
|
state = FAILED;
|
||||||
xSemaphoreGive(renderingMutex);
|
xSemaphoreGive(renderingMutex);
|
||||||
@@ -41,7 +41,7 @@ void OtaUpdateActivity::onWifiSelectionComplete(const bool success) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!updater.isUpdateNewer()) {
|
if (!updater.isUpdateNewer()) {
|
||||||
Serial.printf("[%lu] [OTA] No new update available\n", millis());
|
LOG_DBG("OTA", "No new update available");
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
state = NO_UPDATE;
|
state = NO_UPDATE;
|
||||||
xSemaphoreGive(renderingMutex);
|
xSemaphoreGive(renderingMutex);
|
||||||
@@ -68,11 +68,11 @@ void OtaUpdateActivity::onEnter() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Turn on WiFi immediately
|
// Turn on WiFi immediately
|
||||||
Serial.printf("[%lu] [OTA] Turning on WiFi...\n", millis());
|
LOG_DBG("OTA", "Turning on WiFi...");
|
||||||
WiFi.mode(WIFI_STA);
|
WiFi.mode(WIFI_STA);
|
||||||
|
|
||||||
// Launch WiFi selection subactivity
|
// Launch WiFi selection subactivity
|
||||||
Serial.printf("[%lu] [OTA] Launching WifiSelectionActivity...\n", millis());
|
LOG_DBG("OTA", "Launching WifiSelectionActivity...");
|
||||||
enterNewActivity(new WifiSelectionActivity(renderer, mappedInput,
|
enterNewActivity(new WifiSelectionActivity(renderer, mappedInput,
|
||||||
[this](const bool connected) { onWifiSelectionComplete(connected); }));
|
[this](const bool connected) { onWifiSelectionComplete(connected); }));
|
||||||
}
|
}
|
||||||
@@ -116,8 +116,7 @@ void OtaUpdateActivity::render() {
|
|||||||
|
|
||||||
float updaterProgress = 0;
|
float updaterProgress = 0;
|
||||||
if (state == UPDATE_IN_PROGRESS) {
|
if (state == UPDATE_IN_PROGRESS) {
|
||||||
Serial.printf("[%lu] [OTA] Update progress: %d / %d\n", millis(), updater.getProcessedSize(),
|
LOG_DBG("OTA", "Update progress: %d / %d", updater.getProcessedSize(), updater.getTotalSize());
|
||||||
updater.getTotalSize());
|
|
||||||
updaterProgress = static_cast<float>(updater.getProcessedSize()) / static_cast<float>(updater.getTotalSize());
|
updaterProgress = static_cast<float>(updater.getProcessedSize()) / static_cast<float>(updater.getTotalSize());
|
||||||
// Only update every 2% at the most
|
// Only update every 2% at the most
|
||||||
if (static_cast<int>(updaterProgress * 50) == lastUpdaterPercentage / 2) {
|
if (static_cast<int>(updaterProgress * 50) == lastUpdaterPercentage / 2) {
|
||||||
@@ -190,7 +189,7 @@ void OtaUpdateActivity::loop() {
|
|||||||
|
|
||||||
if (state == WAITING_CONFIRMATION) {
|
if (state == WAITING_CONFIRMATION) {
|
||||||
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||||
Serial.printf("[%lu] [OTA] New update available, starting download...\n", millis());
|
LOG_DBG("OTA", "New update available, starting download...");
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
state = UPDATE_IN_PROGRESS;
|
state = UPDATE_IN_PROGRESS;
|
||||||
xSemaphoreGive(renderingMutex);
|
xSemaphoreGive(renderingMutex);
|
||||||
@@ -199,7 +198,7 @@ void OtaUpdateActivity::loop() {
|
|||||||
const auto res = updater.installUpdate();
|
const auto res = updater.installUpdate();
|
||||||
|
|
||||||
if (res != OtaUpdater::OK) {
|
if (res != OtaUpdater::OK) {
|
||||||
Serial.printf("[%lu] [OTA] Update failed: %d\n", millis(), res);
|
LOG_DBG("OTA", "Update failed: %d", res);
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
state = FAILED;
|
state = FAILED;
|
||||||
xSemaphoreGive(renderingMutex);
|
xSemaphoreGive(renderingMutex);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "SettingsActivity.h"
|
#include "SettingsActivity.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <HardwareSerial.h>
|
#include <Logging.h>
|
||||||
|
|
||||||
#include "ButtonRemapActivity.h"
|
#include "ButtonRemapActivity.h"
|
||||||
#include "CalibreSettingsActivity.h"
|
#include "CalibreSettingsActivity.h"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "UITheme.h"
|
#include "UITheme.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
#include <Logging.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
@@ -23,12 +24,12 @@ void UITheme::reload() {
|
|||||||
void UITheme::setTheme(CrossPointSettings::UI_THEME type) {
|
void UITheme::setTheme(CrossPointSettings::UI_THEME type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case CrossPointSettings::UI_THEME::CLASSIC:
|
case CrossPointSettings::UI_THEME::CLASSIC:
|
||||||
Serial.printf("[%lu] [UI] Using Classic theme\n", millis());
|
LOG_DBG("UI", "Using Classic theme");
|
||||||
currentTheme = new BaseTheme();
|
currentTheme = new BaseTheme();
|
||||||
currentMetrics = &BaseMetrics::values;
|
currentMetrics = &BaseMetrics::values;
|
||||||
break;
|
break;
|
||||||
case CrossPointSettings::UI_THEME::LYRA:
|
case CrossPointSettings::UI_THEME::LYRA:
|
||||||
Serial.printf("[%lu] [UI] Using Lyra theme\n", millis());
|
LOG_DBG("UI", "Using Lyra theme");
|
||||||
currentTheme = new LyraTheme();
|
currentTheme = new LyraTheme();
|
||||||
currentMetrics = &LyraMetrics::values;
|
currentMetrics = &LyraMetrics::values;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
|
#include <Logging.h>
|
||||||
#include <Utf8.h>
|
#include <Utf8.h>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -311,7 +312,7 @@ void BaseTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std:
|
|||||||
if (Storage.openFileForRead("HOME", coverBmpPath, file)) {
|
if (Storage.openFileForRead("HOME", coverBmpPath, file)) {
|
||||||
Bitmap bitmap(file);
|
Bitmap bitmap(file);
|
||||||
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
||||||
Serial.printf("Rendering bmp\n");
|
LOG_DBG("THEME", "Rendering bmp");
|
||||||
// Calculate position to center image within the book card
|
// Calculate position to center image within the book card
|
||||||
int coverX, coverY;
|
int coverX, coverY;
|
||||||
|
|
||||||
@@ -345,7 +346,7 @@ void BaseTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std:
|
|||||||
|
|
||||||
// First render: if selected, draw selection indicators now
|
// First render: if selected, draw selection indicators now
|
||||||
if (bookSelected) {
|
if (bookSelected) {
|
||||||
Serial.printf("Drawing selection\n");
|
LOG_DBG("THEME", "Drawing selection");
|
||||||
renderer.drawRect(bookX + 1, bookY + 1, bookWidth - 2, bookHeight - 2);
|
renderer.drawRect(bookX + 1, bookY + 1, bookWidth - 2, bookHeight - 2);
|
||||||
renderer.drawRect(bookX + 2, bookY + 2, bookWidth - 4, bookHeight - 4);
|
renderer.drawRect(bookX + 2, bookY + 2, bookWidth - 4, bookHeight - 4);
|
||||||
}
|
}
|
||||||
|
|||||||
39
src/main.cpp
39
src/main.cpp
@@ -4,6 +4,7 @@
|
|||||||
#include <HalDisplay.h>
|
#include <HalDisplay.h>
|
||||||
#include <HalGPIO.h>
|
#include <HalGPIO.h>
|
||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
|
#include <Logging.h>
|
||||||
#include <SPI.h>
|
#include <SPI.h>
|
||||||
#include <builtinFonts/all.h>
|
#include <builtinFonts/all.h>
|
||||||
|
|
||||||
@@ -201,8 +202,8 @@ void enterDeepSleep() {
|
|||||||
enterNewActivity(new SleepActivity(renderer, mappedInputManager));
|
enterNewActivity(new SleepActivity(renderer, mappedInputManager));
|
||||||
|
|
||||||
display.deepSleep();
|
display.deepSleep();
|
||||||
Serial.printf("[%lu] [ ] Power button press calibration value: %lu ms\n", millis(), t2 - t1);
|
LOG_DBG("MAIN", "Power button press calibration value: %lu ms", t2 - t1);
|
||||||
Serial.printf("[%lu] [ ] Entering deep sleep.\n", millis());
|
LOG_DBG("MAIN", "Entering deep sleep");
|
||||||
|
|
||||||
gpio.startDeepSleep();
|
gpio.startDeepSleep();
|
||||||
}
|
}
|
||||||
@@ -255,7 +256,7 @@ void onGoHome() {
|
|||||||
void setupDisplayAndFonts() {
|
void setupDisplayAndFonts() {
|
||||||
display.begin();
|
display.begin();
|
||||||
renderer.begin();
|
renderer.begin();
|
||||||
Serial.printf("[%lu] [ ] Display initialized\n", millis());
|
LOG_DBG("MAIN", "Display initialized");
|
||||||
renderer.insertFont(BOOKERLY_14_FONT_ID, bookerly14FontFamily);
|
renderer.insertFont(BOOKERLY_14_FONT_ID, bookerly14FontFamily);
|
||||||
#ifndef OMIT_FONTS
|
#ifndef OMIT_FONTS
|
||||||
renderer.insertFont(BOOKERLY_12_FONT_ID, bookerly12FontFamily);
|
renderer.insertFont(BOOKERLY_12_FONT_ID, bookerly12FontFamily);
|
||||||
@@ -274,7 +275,7 @@ void setupDisplayAndFonts() {
|
|||||||
renderer.insertFont(UI_10_FONT_ID, ui10FontFamily);
|
renderer.insertFont(UI_10_FONT_ID, ui10FontFamily);
|
||||||
renderer.insertFont(UI_12_FONT_ID, ui12FontFamily);
|
renderer.insertFont(UI_12_FONT_ID, ui12FontFamily);
|
||||||
renderer.insertFont(SMALL_FONT_ID, smallFontFamily);
|
renderer.insertFont(SMALL_FONT_ID, smallFontFamily);
|
||||||
Serial.printf("[%lu] [ ] Fonts setup\n", millis());
|
LOG_DBG("MAIN", "Fonts setup");
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
@@ -295,7 +296,7 @@ void setup() {
|
|||||||
// SD Card Initialization
|
// SD Card Initialization
|
||||||
// We need 6 open files concurrently when parsing a new chapter
|
// We need 6 open files concurrently when parsing a new chapter
|
||||||
if (!Storage.begin()) {
|
if (!Storage.begin()) {
|
||||||
Serial.printf("[%lu] [ ] SD card initialization failed\n", millis());
|
LOG_ERR("MAIN", "SD card initialization failed");
|
||||||
setupDisplayAndFonts();
|
setupDisplayAndFonts();
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new FullScreenMessageActivity(renderer, mappedInputManager, "SD card error", EpdFontFamily::BOLD));
|
enterNewActivity(new FullScreenMessageActivity(renderer, mappedInputManager, "SD card error", EpdFontFamily::BOLD));
|
||||||
@@ -310,12 +311,12 @@ void setup() {
|
|||||||
switch (gpio.getWakeupReason()) {
|
switch (gpio.getWakeupReason()) {
|
||||||
case HalGPIO::WakeupReason::PowerButton:
|
case HalGPIO::WakeupReason::PowerButton:
|
||||||
// For normal wakeups, verify power button press duration
|
// For normal wakeups, verify power button press duration
|
||||||
Serial.printf("[%lu] [ ] Verifying power button press duration\n", millis());
|
LOG_DBG("MAIN", "Verifying power button press duration");
|
||||||
verifyPowerButtonDuration();
|
verifyPowerButtonDuration();
|
||||||
break;
|
break;
|
||||||
case HalGPIO::WakeupReason::AfterUSBPower:
|
case HalGPIO::WakeupReason::AfterUSBPower:
|
||||||
// If USB power caused a cold boot, go back to sleep
|
// If USB power caused a cold boot, go back to sleep
|
||||||
Serial.printf("[%lu] [ ] Wakeup reason: After USB Power\n", millis());
|
LOG_DBG("MAIN", "Wakeup reason: After USB Power");
|
||||||
gpio.startDeepSleep();
|
gpio.startDeepSleep();
|
||||||
break;
|
break;
|
||||||
case HalGPIO::WakeupReason::AfterFlash:
|
case HalGPIO::WakeupReason::AfterFlash:
|
||||||
@@ -326,7 +327,7 @@ void setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// First serial output only here to avoid timing inconsistencies for power button press duration verification
|
// First serial output only here to avoid timing inconsistencies for power button press duration verification
|
||||||
Serial.printf("[%lu] [ ] Starting CrossPoint version " CROSSPOINT_VERSION "\n", millis());
|
LOG_DBG("MAIN", "Starting CrossPoint version " CROSSPOINT_VERSION);
|
||||||
|
|
||||||
setupDisplayAndFonts();
|
setupDisplayAndFonts();
|
||||||
|
|
||||||
@@ -364,22 +365,23 @@ void loop() {
|
|||||||
renderer.setFadingFix(SETTINGS.fadingFix);
|
renderer.setFadingFix(SETTINGS.fadingFix);
|
||||||
|
|
||||||
if (Serial && millis() - lastMemPrint >= 10000) {
|
if (Serial && millis() - lastMemPrint >= 10000) {
|
||||||
Serial.printf("[%lu] [MEM] Free: %d bytes, Total: %d bytes, Min Free: %d bytes\n", millis(), ESP.getFreeHeap(),
|
LOG_INF("MEM", "Free: %d bytes, Total: %d bytes, Min Free: %d bytes", ESP.getFreeHeap(), ESP.getHeapSize(),
|
||||||
ESP.getHeapSize(), ESP.getMinFreeHeap());
|
ESP.getMinFreeHeap());
|
||||||
lastMemPrint = millis();
|
lastMemPrint = millis();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle incoming serial commands
|
// Handle incoming serial commands,
|
||||||
if (Serial.available() > 0) {
|
// nb: we use logSerial from logging to avoid deprecation warnings
|
||||||
String line = Serial.readStringUntil('\n');
|
if (logSerial.available() > 0) {
|
||||||
|
String line = logSerial.readStringUntil('\n');
|
||||||
if (line.startsWith("CMD:")) {
|
if (line.startsWith("CMD:")) {
|
||||||
String cmd = line.substring(4);
|
String cmd = line.substring(4);
|
||||||
cmd.trim();
|
cmd.trim();
|
||||||
if (cmd == "SCREENSHOT") {
|
if (cmd == "SCREENSHOT") {
|
||||||
Serial.printf("SCREENSHOT_START:%d\n", HalDisplay::BUFFER_SIZE);
|
logSerial.printf("SCREENSHOT_START:%d\n", HalDisplay::BUFFER_SIZE);
|
||||||
uint8_t* buf = display.getFrameBuffer();
|
uint8_t* buf = display.getFrameBuffer();
|
||||||
Serial.write(buf, HalDisplay::BUFFER_SIZE);
|
logSerial.write(buf, HalDisplay::BUFFER_SIZE);
|
||||||
Serial.printf("SCREENSHOT_END\n");
|
logSerial.printf("SCREENSHOT_END\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -392,7 +394,7 @@ void loop() {
|
|||||||
|
|
||||||
const unsigned long sleepTimeoutMs = SETTINGS.getSleepTimeoutMs();
|
const unsigned long sleepTimeoutMs = SETTINGS.getSleepTimeoutMs();
|
||||||
if (millis() - lastActivityTime >= sleepTimeoutMs) {
|
if (millis() - lastActivityTime >= sleepTimeoutMs) {
|
||||||
Serial.printf("[%lu] [SLP] Auto-sleep triggered after %lu ms of inactivity\n", millis(), sleepTimeoutMs);
|
LOG_DBG("SLP", "Auto-sleep triggered after %lu ms of inactivity", sleepTimeoutMs);
|
||||||
enterDeepSleep();
|
enterDeepSleep();
|
||||||
// This should never be hit as `enterDeepSleep` calls esp_deep_sleep_start
|
// This should never be hit as `enterDeepSleep` calls esp_deep_sleep_start
|
||||||
return;
|
return;
|
||||||
@@ -414,8 +416,7 @@ void loop() {
|
|||||||
if (loopDuration > maxLoopDuration) {
|
if (loopDuration > maxLoopDuration) {
|
||||||
maxLoopDuration = loopDuration;
|
maxLoopDuration = loopDuration;
|
||||||
if (maxLoopDuration > 50) {
|
if (maxLoopDuration > 50) {
|
||||||
Serial.printf("[%lu] [LOOP] New max loop duration: %lu ms (activity: %lu ms)\n", millis(), maxLoopDuration,
|
LOG_DBG("LOOP", "New max loop duration: %lu ms (activity: %lu ms)", maxLoopDuration, activityDuration);
|
||||||
activityDuration);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <Epub.h>
|
#include <Epub.h>
|
||||||
#include <FsHelpers.h>
|
#include <FsHelpers.h>
|
||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
|
#include <Logging.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <esp_task_wdt.h>
|
#include <esp_task_wdt.h>
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@ void clearEpubCacheIfNeeded(const String& filePath) {
|
|||||||
// Only clear cache for .epub files
|
// Only clear cache for .epub files
|
||||||
if (StringUtils::checkFileExtension(filePath, ".epub")) {
|
if (StringUtils::checkFileExtension(filePath, ".epub")) {
|
||||||
Epub(filePath.c_str(), "/.crosspoint").clearCache();
|
Epub(filePath.c_str(), "/.crosspoint").clearCache();
|
||||||
Serial.printf("[%lu] [WEB] Cleared epub cache for: %s\n", millis(), filePath.c_str());
|
LOG_DBG("WEB", "Cleared epub cache for: %s", filePath.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +90,7 @@ CrossPointWebServer::~CrossPointWebServer() { stop(); }
|
|||||||
|
|
||||||
void CrossPointWebServer::begin() {
|
void CrossPointWebServer::begin() {
|
||||||
if (running) {
|
if (running) {
|
||||||
Serial.printf("[%lu] [WEB] Web server already running\n", millis());
|
LOG_DBG("WEB", "Web server already running");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,18 +100,17 @@ void CrossPointWebServer::begin() {
|
|||||||
const bool isInApMode = (wifiMode & WIFI_MODE_AP) && (WiFi.softAPgetStationNum() >= 0); // AP is running
|
const bool isInApMode = (wifiMode & WIFI_MODE_AP) && (WiFi.softAPgetStationNum() >= 0); // AP is running
|
||||||
|
|
||||||
if (!isStaConnected && !isInApMode) {
|
if (!isStaConnected && !isInApMode) {
|
||||||
Serial.printf("[%lu] [WEB] Cannot start webserver - no valid network (mode=%d, status=%d)\n", millis(), wifiMode,
|
LOG_DBG("WEB", "Cannot start webserver - no valid network (mode=%d, status=%d)", wifiMode, WiFi.status());
|
||||||
WiFi.status());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store AP mode flag for later use (e.g., in handleStatus)
|
// Store AP mode flag for later use (e.g., in handleStatus)
|
||||||
apMode = isInApMode;
|
apMode = isInApMode;
|
||||||
|
|
||||||
Serial.printf("[%lu] [WEB] [MEM] Free heap before begin: %d bytes\n", millis(), ESP.getFreeHeap());
|
LOG_DBG("WEB", "[MEM] Free heap before begin: %d bytes", ESP.getFreeHeap());
|
||||||
Serial.printf("[%lu] [WEB] Network mode: %s\n", millis(), apMode ? "AP" : "STA");
|
LOG_DBG("WEB", "Network mode: %s", apMode ? "AP" : "STA");
|
||||||
|
|
||||||
Serial.printf("[%lu] [WEB] Creating web server on port %d...\n", millis(), port);
|
LOG_DBG("WEB", "Creating web server on port %d...", port);
|
||||||
server.reset(new WebServer(port));
|
server.reset(new WebServer(port));
|
||||||
|
|
||||||
// Disable WiFi sleep to improve responsiveness and prevent 'unreachable' errors.
|
// Disable WiFi sleep to improve responsiveness and prevent 'unreachable' errors.
|
||||||
@@ -120,15 +120,15 @@ void CrossPointWebServer::begin() {
|
|||||||
// Note: WebServer class doesn't have setNoDelay() in the standard ESP32 library.
|
// Note: WebServer class doesn't have setNoDelay() in the standard ESP32 library.
|
||||||
// We rely on disabling WiFi sleep for responsiveness.
|
// We rely on disabling WiFi sleep for responsiveness.
|
||||||
|
|
||||||
Serial.printf("[%lu] [WEB] [MEM] Free heap after WebServer allocation: %d bytes\n", millis(), ESP.getFreeHeap());
|
LOG_DBG("WEB", "[MEM] Free heap after WebServer allocation: %d bytes", ESP.getFreeHeap());
|
||||||
|
|
||||||
if (!server) {
|
if (!server) {
|
||||||
Serial.printf("[%lu] [WEB] Failed to create WebServer!\n", millis());
|
LOG_ERR("WEB", "Failed to create WebServer!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup routes
|
// Setup routes
|
||||||
Serial.printf("[%lu] [WEB] Setting up routes...\n", millis());
|
LOG_DBG("WEB", "Setting up routes...");
|
||||||
server->on("/", HTTP_GET, [this] { handleRoot(); });
|
server->on("/", HTTP_GET, [this] { handleRoot(); });
|
||||||
server->on("/files", HTTP_GET, [this] { handleFileList(); });
|
server->on("/files", HTTP_GET, [this] { handleFileList(); });
|
||||||
|
|
||||||
@@ -157,43 +157,41 @@ void CrossPointWebServer::begin() {
|
|||||||
server->on("/api/settings", HTTP_POST, [this] { handlePostSettings(); });
|
server->on("/api/settings", HTTP_POST, [this] { handlePostSettings(); });
|
||||||
|
|
||||||
server->onNotFound([this] { handleNotFound(); });
|
server->onNotFound([this] { handleNotFound(); });
|
||||||
Serial.printf("[%lu] [WEB] [MEM] Free heap after route setup: %d bytes\n", millis(), ESP.getFreeHeap());
|
LOG_DBG("WEB", "[MEM] Free heap after route setup: %d bytes", ESP.getFreeHeap());
|
||||||
|
|
||||||
server->begin();
|
server->begin();
|
||||||
|
|
||||||
// Start WebSocket server for fast binary uploads
|
// Start WebSocket server for fast binary uploads
|
||||||
Serial.printf("[%lu] [WEB] Starting WebSocket server on port %d...\n", millis(), wsPort);
|
LOG_DBG("WEB", "Starting WebSocket server on port %d...", wsPort);
|
||||||
wsServer.reset(new WebSocketsServer(wsPort));
|
wsServer.reset(new WebSocketsServer(wsPort));
|
||||||
wsInstance = const_cast<CrossPointWebServer*>(this);
|
wsInstance = const_cast<CrossPointWebServer*>(this);
|
||||||
wsServer->begin();
|
wsServer->begin();
|
||||||
wsServer->onEvent(wsEventCallback);
|
wsServer->onEvent(wsEventCallback);
|
||||||
Serial.printf("[%lu] [WEB] WebSocket server started\n", millis());
|
LOG_DBG("WEB", "WebSocket server started");
|
||||||
|
|
||||||
udpActive = udp.begin(LOCAL_UDP_PORT);
|
udpActive = udp.begin(LOCAL_UDP_PORT);
|
||||||
Serial.printf("[%lu] [WEB] Discovery UDP %s on port %d\n", millis(), udpActive ? "enabled" : "failed",
|
LOG_DBG("WEB", "Discovery UDP %s on port %d", udpActive ? "enabled" : "failed", LOCAL_UDP_PORT);
|
||||||
LOCAL_UDP_PORT);
|
|
||||||
|
|
||||||
running = true;
|
running = true;
|
||||||
|
|
||||||
Serial.printf("[%lu] [WEB] Web server started on port %d\n", millis(), port);
|
LOG_DBG("WEB", "Web server started on port %d", port);
|
||||||
// Show the correct IP based on network mode
|
// Show the correct IP based on network mode
|
||||||
const String ipAddr = apMode ? WiFi.softAPIP().toString() : WiFi.localIP().toString();
|
const String ipAddr = apMode ? WiFi.softAPIP().toString() : WiFi.localIP().toString();
|
||||||
Serial.printf("[%lu] [WEB] Access at http://%s/\n", millis(), ipAddr.c_str());
|
LOG_DBG("WEB", "Access at http://%s/", ipAddr.c_str());
|
||||||
Serial.printf("[%lu] [WEB] WebSocket at ws://%s:%d/\n", millis(), ipAddr.c_str(), wsPort);
|
LOG_DBG("WEB", "WebSocket at ws://%s:%d/", ipAddr.c_str(), wsPort);
|
||||||
Serial.printf("[%lu] [WEB] [MEM] Free heap after server.begin(): %d bytes\n", millis(), ESP.getFreeHeap());
|
LOG_DBG("WEB", "[MEM] Free heap after server.begin(): %d bytes", ESP.getFreeHeap());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CrossPointWebServer::stop() {
|
void CrossPointWebServer::stop() {
|
||||||
if (!running || !server) {
|
if (!running || !server) {
|
||||||
Serial.printf("[%lu] [WEB] stop() called but already stopped (running=%d, server=%p)\n", millis(), running,
|
LOG_DBG("WEB", "stop() called but already stopped (running=%d, server=%p)", running, server.get());
|
||||||
server.get());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [WEB] STOP INITIATED - setting running=false first\n", millis());
|
LOG_DBG("WEB", "STOP INITIATED - setting running=false first");
|
||||||
running = false; // Set this FIRST to prevent handleClient from using server
|
running = false; // Set this FIRST to prevent handleClient from using server
|
||||||
|
|
||||||
Serial.printf("[%lu] [WEB] [MEM] Free heap before stop: %d bytes\n", millis(), ESP.getFreeHeap());
|
LOG_DBG("WEB", "[MEM] Free heap before stop: %d bytes", ESP.getFreeHeap());
|
||||||
|
|
||||||
// Close any in-progress WebSocket upload
|
// Close any in-progress WebSocket upload
|
||||||
if (wsUploadInProgress && wsUploadFile) {
|
if (wsUploadInProgress && wsUploadFile) {
|
||||||
@@ -203,11 +201,11 @@ void CrossPointWebServer::stop() {
|
|||||||
|
|
||||||
// Stop WebSocket server
|
// Stop WebSocket server
|
||||||
if (wsServer) {
|
if (wsServer) {
|
||||||
Serial.printf("[%lu] [WEB] Stopping WebSocket server...\n", millis());
|
LOG_DBG("WEB", "Stopping WebSocket server...");
|
||||||
wsServer->close();
|
wsServer->close();
|
||||||
wsServer.reset();
|
wsServer.reset();
|
||||||
wsInstance = nullptr;
|
wsInstance = nullptr;
|
||||||
Serial.printf("[%lu] [WEB] WebSocket server stopped\n", millis());
|
LOG_DBG("WEB", "WebSocket server stopped");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (udpActive) {
|
if (udpActive) {
|
||||||
@@ -219,18 +217,18 @@ void CrossPointWebServer::stop() {
|
|||||||
delay(20);
|
delay(20);
|
||||||
|
|
||||||
server->stop();
|
server->stop();
|
||||||
Serial.printf("[%lu] [WEB] [MEM] Free heap after server->stop(): %d bytes\n", millis(), ESP.getFreeHeap());
|
LOG_DBG("WEB", "[MEM] Free heap after server->stop(): %d bytes", ESP.getFreeHeap());
|
||||||
|
|
||||||
// Brief delay before deletion
|
// Brief delay before deletion
|
||||||
delay(10);
|
delay(10);
|
||||||
|
|
||||||
server.reset();
|
server.reset();
|
||||||
Serial.printf("[%lu] [WEB] Web server stopped and deleted\n", millis());
|
LOG_DBG("WEB", "Web server stopped and deleted");
|
||||||
Serial.printf("[%lu] [WEB] [MEM] Free heap after delete server: %d bytes\n", millis(), ESP.getFreeHeap());
|
LOG_DBG("WEB", "[MEM] Free heap after delete server: %d bytes", ESP.getFreeHeap());
|
||||||
|
|
||||||
// Note: Static upload variables (uploadFileName, uploadPath, uploadError) are declared
|
// Note: Static upload variables (uploadFileName, uploadPath, uploadError) are declared
|
||||||
// later in the file and will be cleared when they go out of scope or on next upload
|
// later in the file and will be cleared when they go out of scope or on next upload
|
||||||
Serial.printf("[%lu] [WEB] [MEM] Free heap final: %d bytes\n", millis(), ESP.getFreeHeap());
|
LOG_DBG("WEB", "[MEM] Free heap final: %d bytes", ESP.getFreeHeap());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CrossPointWebServer::handleClient() {
|
void CrossPointWebServer::handleClient() {
|
||||||
@@ -243,13 +241,13 @@ void CrossPointWebServer::handleClient() {
|
|||||||
|
|
||||||
// Double-check server pointer is valid
|
// Double-check server pointer is valid
|
||||||
if (!server) {
|
if (!server) {
|
||||||
Serial.printf("[%lu] [WEB] WARNING: handleClient called with null server!\n", millis());
|
LOG_DBG("WEB", "WARNING: handleClient called with null server!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print debug every 10 seconds to confirm handleClient is being called
|
// Print debug every 10 seconds to confirm handleClient is being called
|
||||||
if (millis() - lastDebugPrint > 10000) {
|
if (millis() - lastDebugPrint > 10000) {
|
||||||
Serial.printf("[%lu] [WEB] handleClient active, server running on port %d\n", millis(), port);
|
LOG_DBG("WEB", "handleClient active, server running on port %d", port);
|
||||||
lastDebugPrint = millis();
|
lastDebugPrint = millis();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,7 +295,7 @@ CrossPointWebServer::WsUploadStatus CrossPointWebServer::getWsUploadStatus() con
|
|||||||
|
|
||||||
void CrossPointWebServer::handleRoot() const {
|
void CrossPointWebServer::handleRoot() const {
|
||||||
server->send(200, "text/html", HomePageHtml);
|
server->send(200, "text/html", HomePageHtml);
|
||||||
Serial.printf("[%lu] [WEB] Served root page\n", millis());
|
LOG_DBG("WEB", "Served root page");
|
||||||
}
|
}
|
||||||
|
|
||||||
void CrossPointWebServer::handleNotFound() const {
|
void CrossPointWebServer::handleNotFound() const {
|
||||||
@@ -326,17 +324,17 @@ void CrossPointWebServer::handleStatus() const {
|
|||||||
void CrossPointWebServer::scanFiles(const char* path, const std::function<void(FileInfo)>& callback) const {
|
void CrossPointWebServer::scanFiles(const char* path, const std::function<void(FileInfo)>& callback) const {
|
||||||
FsFile root = Storage.open(path);
|
FsFile root = Storage.open(path);
|
||||||
if (!root) {
|
if (!root) {
|
||||||
Serial.printf("[%lu] [WEB] Failed to open directory: %s\n", millis(), path);
|
LOG_DBG("WEB", "Failed to open directory: %s", path);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!root.isDirectory()) {
|
if (!root.isDirectory()) {
|
||||||
Serial.printf("[%lu] [WEB] Not a directory: %s\n", millis(), path);
|
LOG_DBG("WEB", "Not a directory: %s", path);
|
||||||
root.close();
|
root.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [WEB] Scanning files in: %s\n", millis(), path);
|
LOG_DBG("WEB", "Scanning files in: %s", path);
|
||||||
|
|
||||||
FsFile file = root.openNextFile();
|
FsFile file = root.openNextFile();
|
||||||
char name[500];
|
char name[500];
|
||||||
@@ -422,7 +420,7 @@ void CrossPointWebServer::handleFileListData() const {
|
|||||||
const size_t written = serializeJson(doc, output, outputSize);
|
const size_t written = serializeJson(doc, output, outputSize);
|
||||||
if (written >= outputSize) {
|
if (written >= outputSize) {
|
||||||
// JSON output truncated; skip this entry to avoid sending malformed JSON
|
// JSON output truncated; skip this entry to avoid sending malformed JSON
|
||||||
Serial.printf("[%lu] [WEB] Skipping file entry with oversized JSON for name: %s\n", millis(), info.name.c_str());
|
LOG_DBG("WEB", "Skipping file entry with oversized JSON for name: %s", info.name.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -436,7 +434,7 @@ void CrossPointWebServer::handleFileListData() const {
|
|||||||
server->sendContent("]");
|
server->sendContent("]");
|
||||||
// End of streamed response, empty chunk to signal client
|
// End of streamed response, empty chunk to signal client
|
||||||
server->sendContent("");
|
server->sendContent("");
|
||||||
Serial.printf("[%lu] [WEB] Served file listing page for path: %s\n", millis(), currentPath.c_str());
|
LOG_DBG("WEB", "Served file listing page for path: %s", currentPath.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CrossPointWebServer::handleDownload() const {
|
void CrossPointWebServer::handleDownload() const {
|
||||||
@@ -517,8 +515,7 @@ static bool flushUploadBuffer(CrossPointWebServer::UploadState& state) {
|
|||||||
esp_task_wdt_reset(); // Reset watchdog after SD write
|
esp_task_wdt_reset(); // Reset watchdog after SD write
|
||||||
|
|
||||||
if (written != state.bufferPos) {
|
if (written != state.bufferPos) {
|
||||||
Serial.printf("[%lu] [WEB] [UPLOAD] Buffer flush failed: expected %d, wrote %d\n", millis(), state.bufferPos,
|
LOG_DBG("WEB", "[UPLOAD] Buffer flush failed: expected %d, wrote %d", state.bufferPos, written);
|
||||||
written);
|
|
||||||
state.bufferPos = 0;
|
state.bufferPos = 0;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -535,7 +532,7 @@ void CrossPointWebServer::handleUpload(UploadState& state) const {
|
|||||||
|
|
||||||
// Safety check: ensure server is still valid
|
// Safety check: ensure server is still valid
|
||||||
if (!running || !server) {
|
if (!running || !server) {
|
||||||
Serial.printf("[%lu] [WEB] [UPLOAD] ERROR: handleUpload called but server not running!\n", millis());
|
LOG_DBG("WEB", "[UPLOAD] ERROR: handleUpload called but server not running!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,8 +569,8 @@ void CrossPointWebServer::handleUpload(UploadState& state) const {
|
|||||||
state.path = "/";
|
state.path = "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [WEB] [UPLOAD] START: %s to path: %s\n", millis(), state.fileName.c_str(), state.path.c_str());
|
LOG_DBG("WEB", "[UPLOAD] START: %s to path: %s", state.fileName.c_str(), state.path.c_str());
|
||||||
Serial.printf("[%lu] [WEB] [UPLOAD] Free heap: %d bytes\n", millis(), ESP.getFreeHeap());
|
LOG_DBG("WEB", "[UPLOAD] Free heap: %d bytes", ESP.getFreeHeap());
|
||||||
|
|
||||||
// Create file path
|
// Create file path
|
||||||
String filePath = state.path;
|
String filePath = state.path;
|
||||||
@@ -583,7 +580,7 @@ void CrossPointWebServer::handleUpload(UploadState& state) const {
|
|||||||
// Check if file already exists - SD operations can be slow
|
// Check if file already exists - SD operations can be slow
|
||||||
esp_task_wdt_reset();
|
esp_task_wdt_reset();
|
||||||
if (Storage.exists(filePath.c_str())) {
|
if (Storage.exists(filePath.c_str())) {
|
||||||
Serial.printf("[%lu] [WEB] [UPLOAD] Overwriting existing file: %s\n", millis(), filePath.c_str());
|
LOG_DBG("WEB", "[UPLOAD] Overwriting existing file: %s", filePath.c_str());
|
||||||
esp_task_wdt_reset();
|
esp_task_wdt_reset();
|
||||||
Storage.remove(filePath.c_str());
|
Storage.remove(filePath.c_str());
|
||||||
}
|
}
|
||||||
@@ -592,12 +589,12 @@ void CrossPointWebServer::handleUpload(UploadState& state) const {
|
|||||||
esp_task_wdt_reset();
|
esp_task_wdt_reset();
|
||||||
if (!Storage.openFileForWrite("WEB", filePath, state.file)) {
|
if (!Storage.openFileForWrite("WEB", filePath, state.file)) {
|
||||||
state.error = "Failed to create file on SD card";
|
state.error = "Failed to create file on SD card";
|
||||||
Serial.printf("[%lu] [WEB] [UPLOAD] FAILED to create file: %s\n", millis(), filePath.c_str());
|
LOG_DBG("WEB", "[UPLOAD] FAILED to create file: %s", filePath.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
esp_task_wdt_reset();
|
esp_task_wdt_reset();
|
||||||
|
|
||||||
Serial.printf("[%lu] [WEB] [UPLOAD] File created successfully: %s\n", millis(), filePath.c_str());
|
LOG_DBG("WEB", "[UPLOAD] File created successfully: %s", filePath.c_str());
|
||||||
} else if (upload.status == UPLOAD_FILE_WRITE) {
|
} else if (upload.status == UPLOAD_FILE_WRITE) {
|
||||||
if (state.file && state.error.isEmpty()) {
|
if (state.file && state.error.isEmpty()) {
|
||||||
// Buffer incoming data and flush when buffer is full
|
// Buffer incoming data and flush when buffer is full
|
||||||
@@ -630,8 +627,8 @@ void CrossPointWebServer::handleUpload(UploadState& state) const {
|
|||||||
if (state.size - lastLoggedSize >= 102400) {
|
if (state.size - lastLoggedSize >= 102400) {
|
||||||
const unsigned long elapsed = millis() - uploadStartTime;
|
const unsigned long elapsed = millis() - uploadStartTime;
|
||||||
const float kbps = (elapsed > 0) ? (state.size / 1024.0) / (elapsed / 1000.0) : 0;
|
const float kbps = (elapsed > 0) ? (state.size / 1024.0) / (elapsed / 1000.0) : 0;
|
||||||
Serial.printf("[%lu] [WEB] [UPLOAD] %d bytes (%.1f KB), %.1f KB/s, %d writes\n", millis(), state.size,
|
LOG_DBG("WEB", "[UPLOAD] %d bytes (%.1f KB), %.1f KB/s, %d writes", state.size, state.size / 1024.0, kbps,
|
||||||
state.size / 1024.0, kbps, writeCount);
|
writeCount);
|
||||||
lastLoggedSize = state.size;
|
lastLoggedSize = state.size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -648,10 +645,10 @@ void CrossPointWebServer::handleUpload(UploadState& state) const {
|
|||||||
const unsigned long elapsed = millis() - uploadStartTime;
|
const unsigned long elapsed = millis() - uploadStartTime;
|
||||||
const float avgKbps = (elapsed > 0) ? (state.size / 1024.0) / (elapsed / 1000.0) : 0;
|
const float avgKbps = (elapsed > 0) ? (state.size / 1024.0) / (elapsed / 1000.0) : 0;
|
||||||
const float writePercent = (elapsed > 0) ? (totalWriteTime * 100.0 / elapsed) : 0;
|
const float writePercent = (elapsed > 0) ? (totalWriteTime * 100.0 / elapsed) : 0;
|
||||||
Serial.printf("[%lu] [WEB] [UPLOAD] Complete: %s (%d bytes in %lu ms, avg %.1f KB/s)\n", millis(),
|
LOG_DBG("WEB", "[UPLOAD] Complete: %s (%d bytes in %lu ms, avg %.1f KB/s)", state.fileName.c_str(), state.size,
|
||||||
state.fileName.c_str(), state.size, elapsed, avgKbps);
|
elapsed, avgKbps);
|
||||||
Serial.printf("[%lu] [WEB] [UPLOAD] Diagnostics: %d writes, total write time: %lu ms (%.1f%%)\n", millis(),
|
LOG_DBG("WEB", "[UPLOAD] Diagnostics: %d writes, total write time: %lu ms (%.1f%%)", writeCount, totalWriteTime,
|
||||||
writeCount, totalWriteTime, writePercent);
|
writePercent);
|
||||||
|
|
||||||
// Clear epub cache to prevent stale metadata issues when overwriting files
|
// Clear epub cache to prevent stale metadata issues when overwriting files
|
||||||
String filePath = state.path;
|
String filePath = state.path;
|
||||||
@@ -671,7 +668,7 @@ void CrossPointWebServer::handleUpload(UploadState& state) const {
|
|||||||
Storage.remove(filePath.c_str());
|
Storage.remove(filePath.c_str());
|
||||||
}
|
}
|
||||||
state.error = "Upload aborted";
|
state.error = "Upload aborted";
|
||||||
Serial.printf("[%lu] [WEB] Upload aborted\n", millis());
|
LOG_DBG("WEB", "Upload aborted");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -716,7 +713,7 @@ void CrossPointWebServer::handleCreateFolder() const {
|
|||||||
if (!folderPath.endsWith("/")) folderPath += "/";
|
if (!folderPath.endsWith("/")) folderPath += "/";
|
||||||
folderPath += folderName;
|
folderPath += folderName;
|
||||||
|
|
||||||
Serial.printf("[%lu] [WEB] Creating folder: %s\n", millis(), folderPath.c_str());
|
LOG_DBG("WEB", "Creating folder: %s", folderPath.c_str());
|
||||||
|
|
||||||
// Check if already exists
|
// Check if already exists
|
||||||
if (Storage.exists(folderPath.c_str())) {
|
if (Storage.exists(folderPath.c_str())) {
|
||||||
@@ -726,10 +723,10 @@ void CrossPointWebServer::handleCreateFolder() const {
|
|||||||
|
|
||||||
// Create the folder
|
// Create the folder
|
||||||
if (Storage.mkdir(folderPath.c_str())) {
|
if (Storage.mkdir(folderPath.c_str())) {
|
||||||
Serial.printf("[%lu] [WEB] Folder created successfully: %s\n", millis(), folderPath.c_str());
|
LOG_DBG("WEB", "Folder created successfully: %s", folderPath.c_str());
|
||||||
server->send(200, "text/plain", "Folder created: " + folderName);
|
server->send(200, "text/plain", "Folder created: " + folderName);
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("[%lu] [WEB] Failed to create folder: %s\n", millis(), folderPath.c_str());
|
LOG_DBG("WEB", "Failed to create folder: %s", folderPath.c_str());
|
||||||
server->send(500, "text/plain", "Failed to create folder");
|
server->send(500, "text/plain", "Failed to create folder");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -808,10 +805,10 @@ void CrossPointWebServer::handleRename() const {
|
|||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
Serial.printf("[%lu] [WEB] Renamed file: %s -> %s\n", millis(), itemPath.c_str(), newPath.c_str());
|
LOG_DBG("WEB", "Renamed file: %s -> %s", itemPath.c_str(), newPath.c_str());
|
||||||
server->send(200, "text/plain", "Renamed successfully");
|
server->send(200, "text/plain", "Renamed successfully");
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("[%lu] [WEB] Failed to rename file: %s -> %s\n", millis(), itemPath.c_str(), newPath.c_str());
|
LOG_ERR("WEB", "Failed to rename file: %s -> %s", itemPath.c_str(), newPath.c_str());
|
||||||
server->send(500, "text/plain", "Failed to rename file");
|
server->send(500, "text/plain", "Failed to rename file");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -901,10 +898,10 @@ void CrossPointWebServer::handleMove() const {
|
|||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
Serial.printf("[%lu] [WEB] Moved file: %s -> %s\n", millis(), itemPath.c_str(), newPath.c_str());
|
LOG_DBG("WEB", "Moved file: %s -> %s", itemPath.c_str(), newPath.c_str());
|
||||||
server->send(200, "text/plain", "Moved successfully");
|
server->send(200, "text/plain", "Moved successfully");
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("[%lu] [WEB] Failed to move file: %s -> %s\n", millis(), itemPath.c_str(), newPath.c_str());
|
LOG_ERR("WEB", "Failed to move file: %s -> %s", itemPath.c_str(), newPath.c_str());
|
||||||
server->send(500, "text/plain", "Failed to move file");
|
server->send(500, "text/plain", "Failed to move file");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -935,7 +932,7 @@ void CrossPointWebServer::handleDelete() const {
|
|||||||
|
|
||||||
// Check if item starts with a dot (hidden/system file)
|
// Check if item starts with a dot (hidden/system file)
|
||||||
if (itemName.startsWith(".")) {
|
if (itemName.startsWith(".")) {
|
||||||
Serial.printf("[%lu] [WEB] Delete rejected - hidden/system item: %s\n", millis(), itemPath.c_str());
|
LOG_DBG("WEB", "Delete rejected - hidden/system item: %s", itemPath.c_str());
|
||||||
server->send(403, "text/plain", "Cannot delete system files");
|
server->send(403, "text/plain", "Cannot delete system files");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -943,7 +940,7 @@ void CrossPointWebServer::handleDelete() const {
|
|||||||
// Check against explicitly protected items
|
// Check against explicitly protected items
|
||||||
for (size_t i = 0; i < HIDDEN_ITEMS_COUNT; i++) {
|
for (size_t i = 0; i < HIDDEN_ITEMS_COUNT; i++) {
|
||||||
if (itemName.equals(HIDDEN_ITEMS[i])) {
|
if (itemName.equals(HIDDEN_ITEMS[i])) {
|
||||||
Serial.printf("[%lu] [WEB] Delete rejected - protected item: %s\n", millis(), itemPath.c_str());
|
LOG_DBG("WEB", "Delete rejected - protected item: %s", itemPath.c_str());
|
||||||
server->send(403, "text/plain", "Cannot delete protected items");
|
server->send(403, "text/plain", "Cannot delete protected items");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -951,12 +948,12 @@ void CrossPointWebServer::handleDelete() const {
|
|||||||
|
|
||||||
// Check if item exists
|
// Check if item exists
|
||||||
if (!Storage.exists(itemPath.c_str())) {
|
if (!Storage.exists(itemPath.c_str())) {
|
||||||
Serial.printf("[%lu] [WEB] Delete failed - item not found: %s\n", millis(), itemPath.c_str());
|
LOG_DBG("WEB", "Delete failed - item not found: %s", itemPath.c_str());
|
||||||
server->send(404, "text/plain", "Item not found");
|
server->send(404, "text/plain", "Item not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [WEB] Attempting to delete %s: %s\n", millis(), itemType.c_str(), itemPath.c_str());
|
LOG_DBG("WEB", "Attempting to delete %s: %s", itemType.c_str(), itemPath.c_str());
|
||||||
|
|
||||||
bool success = false;
|
bool success = false;
|
||||||
|
|
||||||
@@ -970,7 +967,7 @@ void CrossPointWebServer::handleDelete() const {
|
|||||||
// Folder is not empty
|
// Folder is not empty
|
||||||
entry.close();
|
entry.close();
|
||||||
dir.close();
|
dir.close();
|
||||||
Serial.printf("[%lu] [WEB] Delete failed - folder not empty: %s\n", millis(), itemPath.c_str());
|
LOG_DBG("WEB", "Delete failed - folder not empty: %s", itemPath.c_str());
|
||||||
server->send(400, "text/plain", "Folder is not empty. Delete contents first.");
|
server->send(400, "text/plain", "Folder is not empty. Delete contents first.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -983,17 +980,17 @@ void CrossPointWebServer::handleDelete() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
Serial.printf("[%lu] [WEB] Successfully deleted: %s\n", millis(), itemPath.c_str());
|
LOG_DBG("WEB", "Successfully deleted: %s", itemPath.c_str());
|
||||||
server->send(200, "text/plain", "Deleted successfully");
|
server->send(200, "text/plain", "Deleted successfully");
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("[%lu] [WEB] Failed to delete: %s\n", millis(), itemPath.c_str());
|
LOG_ERR("WEB", "Failed to delete: %s", itemPath.c_str());
|
||||||
server->send(500, "text/plain", "Failed to delete item");
|
server->send(500, "text/plain", "Failed to delete item");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CrossPointWebServer::handleSettingsPage() const {
|
void CrossPointWebServer::handleSettingsPage() const {
|
||||||
server->send(200, "text/html", SettingsPageHtml);
|
server->send(200, "text/html", SettingsPageHtml);
|
||||||
Serial.printf("[%lu] [WEB] Served settings page\n", millis());
|
LOG_DBG("WEB", "Served settings page");
|
||||||
}
|
}
|
||||||
|
|
||||||
void CrossPointWebServer::handleGetSettings() const {
|
void CrossPointWebServer::handleGetSettings() const {
|
||||||
@@ -1062,7 +1059,7 @@ void CrossPointWebServer::handleGetSettings() const {
|
|||||||
|
|
||||||
const size_t written = serializeJson(doc, output, outputSize);
|
const size_t written = serializeJson(doc, output, outputSize);
|
||||||
if (written >= outputSize) {
|
if (written >= outputSize) {
|
||||||
Serial.printf("[%lu] [WEB] Skipping oversized setting JSON for: %s\n", millis(), s.key);
|
LOG_DBG("WEB", "Skipping oversized setting JSON for: %s", s.key);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1076,7 +1073,7 @@ void CrossPointWebServer::handleGetSettings() const {
|
|||||||
|
|
||||||
server->sendContent("]");
|
server->sendContent("]");
|
||||||
server->sendContent("");
|
server->sendContent("");
|
||||||
Serial.printf("[%lu] [WEB] Served settings API\n", millis());
|
LOG_DBG("WEB", "Served settings API");
|
||||||
}
|
}
|
||||||
|
|
||||||
void CrossPointWebServer::handlePostSettings() {
|
void CrossPointWebServer::handlePostSettings() {
|
||||||
@@ -1149,7 +1146,7 @@ void CrossPointWebServer::handlePostSettings() {
|
|||||||
|
|
||||||
SETTINGS.saveToFile();
|
SETTINGS.saveToFile();
|
||||||
|
|
||||||
Serial.printf("[%lu] [WEB] Applied %d setting(s)\n", millis(), applied);
|
LOG_DBG("WEB", "Applied %d setting(s)", applied);
|
||||||
server->send(200, "text/plain", String("Applied ") + String(applied) + " setting(s)");
|
server->send(200, "text/plain", String("Applied ") + String(applied) + " setting(s)");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1169,7 +1166,7 @@ void CrossPointWebServer::wsEventCallback(uint8_t num, WStype_t type, uint8_t* p
|
|||||||
void CrossPointWebServer::onWebSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) {
|
void CrossPointWebServer::onWebSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case WStype_DISCONNECTED:
|
case WStype_DISCONNECTED:
|
||||||
Serial.printf("[%lu] [WS] Client %u disconnected\n", millis(), num);
|
LOG_DBG("WS", "Client %u disconnected", num);
|
||||||
// Clean up any in-progress upload
|
// Clean up any in-progress upload
|
||||||
if (wsUploadInProgress && wsUploadFile) {
|
if (wsUploadInProgress && wsUploadFile) {
|
||||||
wsUploadFile.close();
|
wsUploadFile.close();
|
||||||
@@ -1178,20 +1175,20 @@ void CrossPointWebServer::onWebSocketEvent(uint8_t num, WStype_t type, uint8_t*
|
|||||||
if (!filePath.endsWith("/")) filePath += "/";
|
if (!filePath.endsWith("/")) filePath += "/";
|
||||||
filePath += wsUploadFileName;
|
filePath += wsUploadFileName;
|
||||||
Storage.remove(filePath.c_str());
|
Storage.remove(filePath.c_str());
|
||||||
Serial.printf("[%lu] [WS] Deleted incomplete upload: %s\n", millis(), filePath.c_str());
|
LOG_DBG("WS", "Deleted incomplete upload: %s", filePath.c_str());
|
||||||
}
|
}
|
||||||
wsUploadInProgress = false;
|
wsUploadInProgress = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WStype_CONNECTED: {
|
case WStype_CONNECTED: {
|
||||||
Serial.printf("[%lu] [WS] Client %u connected\n", millis(), num);
|
LOG_DBG("WS", "Client %u connected", num);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case WStype_TEXT: {
|
case WStype_TEXT: {
|
||||||
// Parse control messages
|
// Parse control messages
|
||||||
String msg = String((char*)payload);
|
String msg = String((char*)payload);
|
||||||
Serial.printf("[%lu] [WS] Text from client %u: %s\n", millis(), num, msg.c_str());
|
LOG_DBG("WS", "Text from client %u: %s", num, msg.c_str());
|
||||||
|
|
||||||
if (msg.startsWith("START:")) {
|
if (msg.startsWith("START:")) {
|
||||||
// Parse: START:<filename>:<size>:<path>
|
// Parse: START:<filename>:<size>:<path>
|
||||||
@@ -1216,8 +1213,8 @@ void CrossPointWebServer::onWebSocketEvent(uint8_t num, WStype_t type, uint8_t*
|
|||||||
if (!filePath.endsWith("/")) filePath += "/";
|
if (!filePath.endsWith("/")) filePath += "/";
|
||||||
filePath += wsUploadFileName;
|
filePath += wsUploadFileName;
|
||||||
|
|
||||||
Serial.printf("[%lu] [WS] Starting upload: %s (%d bytes) to %s\n", millis(), wsUploadFileName.c_str(),
|
LOG_DBG("WS", "Starting upload: %s (%d bytes) to %s", wsUploadFileName.c_str(), wsUploadSize,
|
||||||
wsUploadSize, filePath.c_str());
|
filePath.c_str());
|
||||||
|
|
||||||
// Check if file exists and remove it
|
// Check if file exists and remove it
|
||||||
esp_task_wdt_reset();
|
esp_task_wdt_reset();
|
||||||
@@ -1283,8 +1280,8 @@ void CrossPointWebServer::onWebSocketEvent(uint8_t num, WStype_t type, uint8_t*
|
|||||||
unsigned long elapsed = millis() - wsUploadStartTime;
|
unsigned long elapsed = millis() - wsUploadStartTime;
|
||||||
float kbps = (elapsed > 0) ? (wsUploadSize / 1024.0) / (elapsed / 1000.0) : 0;
|
float kbps = (elapsed > 0) ? (wsUploadSize / 1024.0) / (elapsed / 1000.0) : 0;
|
||||||
|
|
||||||
Serial.printf("[%lu] [WS] Upload complete: %s (%d bytes in %lu ms, %.1f KB/s)\n", millis(),
|
LOG_DBG("WS", "Upload complete: %s (%d bytes in %lu ms, %.1f KB/s)", wsUploadFileName.c_str(), wsUploadSize,
|
||||||
wsUploadFileName.c_str(), wsUploadSize, elapsed, kbps);
|
elapsed, kbps);
|
||||||
|
|
||||||
// Clear epub cache to prevent stale metadata issues when overwriting files
|
// Clear epub cache to prevent stale metadata issues when overwriting files
|
||||||
String filePath = wsUploadPath;
|
String filePath = wsUploadPath;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "HttpDownloader.h"
|
#include "HttpDownloader.h"
|
||||||
|
|
||||||
#include <HTTPClient.h>
|
#include <HTTPClient.h>
|
||||||
#include <HardwareSerial.h>
|
#include <Logging.h>
|
||||||
#include <StreamString.h>
|
#include <StreamString.h>
|
||||||
#include <WiFiClient.h>
|
#include <WiFiClient.h>
|
||||||
#include <WiFiClientSecure.h>
|
#include <WiFiClientSecure.h>
|
||||||
@@ -25,7 +25,7 @@ bool HttpDownloader::fetchUrl(const std::string& url, Stream& outContent) {
|
|||||||
}
|
}
|
||||||
HTTPClient http;
|
HTTPClient http;
|
||||||
|
|
||||||
Serial.printf("[%lu] [HTTP] Fetching: %s\n", millis(), url.c_str());
|
LOG_DBG("HTTP", "Fetching: %s", url.c_str());
|
||||||
|
|
||||||
http.begin(*client, url.c_str());
|
http.begin(*client, url.c_str());
|
||||||
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||||
@@ -40,7 +40,7 @@ bool HttpDownloader::fetchUrl(const std::string& url, Stream& outContent) {
|
|||||||
|
|
||||||
const int httpCode = http.GET();
|
const int httpCode = http.GET();
|
||||||
if (httpCode != HTTP_CODE_OK) {
|
if (httpCode != HTTP_CODE_OK) {
|
||||||
Serial.printf("[%lu] [HTTP] Fetch failed: %d\n", millis(), httpCode);
|
LOG_ERR("HTTP", "Fetch failed: %d", httpCode);
|
||||||
http.end();
|
http.end();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -49,7 +49,7 @@ bool HttpDownloader::fetchUrl(const std::string& url, Stream& outContent) {
|
|||||||
|
|
||||||
http.end();
|
http.end();
|
||||||
|
|
||||||
Serial.printf("[%lu] [HTTP] Fetch success\n", millis());
|
LOG_DBG("HTTP", "Fetch success");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,8 +75,8 @@ HttpDownloader::DownloadError HttpDownloader::downloadToFile(const std::string&
|
|||||||
}
|
}
|
||||||
HTTPClient http;
|
HTTPClient http;
|
||||||
|
|
||||||
Serial.printf("[%lu] [HTTP] Downloading: %s\n", millis(), url.c_str());
|
LOG_DBG("HTTP", "Downloading: %s", url.c_str());
|
||||||
Serial.printf("[%lu] [HTTP] Destination: %s\n", millis(), destPath.c_str());
|
LOG_DBG("HTTP", "Destination: %s", destPath.c_str());
|
||||||
|
|
||||||
http.begin(*client, url.c_str());
|
http.begin(*client, url.c_str());
|
||||||
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||||
@@ -91,13 +91,13 @@ HttpDownloader::DownloadError HttpDownloader::downloadToFile(const std::string&
|
|||||||
|
|
||||||
const int httpCode = http.GET();
|
const int httpCode = http.GET();
|
||||||
if (httpCode != HTTP_CODE_OK) {
|
if (httpCode != HTTP_CODE_OK) {
|
||||||
Serial.printf("[%lu] [HTTP] Download failed: %d\n", millis(), httpCode);
|
LOG_ERR("HTTP", "Download failed: %d", httpCode);
|
||||||
http.end();
|
http.end();
|
||||||
return HTTP_ERROR;
|
return HTTP_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
const size_t contentLength = http.getSize();
|
const size_t contentLength = http.getSize();
|
||||||
Serial.printf("[%lu] [HTTP] Content-Length: %zu\n", millis(), contentLength);
|
LOG_DBG("HTTP", "Content-Length: %zu", contentLength);
|
||||||
|
|
||||||
// Remove existing file if present
|
// Remove existing file if present
|
||||||
if (Storage.exists(destPath.c_str())) {
|
if (Storage.exists(destPath.c_str())) {
|
||||||
@@ -107,7 +107,7 @@ HttpDownloader::DownloadError HttpDownloader::downloadToFile(const std::string&
|
|||||||
// Open file for writing
|
// Open file for writing
|
||||||
FsFile file;
|
FsFile file;
|
||||||
if (!Storage.openFileForWrite("HTTP", destPath.c_str(), file)) {
|
if (!Storage.openFileForWrite("HTTP", destPath.c_str(), file)) {
|
||||||
Serial.printf("[%lu] [HTTP] Failed to open file for writing\n", millis());
|
LOG_ERR("HTTP", "Failed to open file for writing");
|
||||||
http.end();
|
http.end();
|
||||||
return FILE_ERROR;
|
return FILE_ERROR;
|
||||||
}
|
}
|
||||||
@@ -115,7 +115,7 @@ HttpDownloader::DownloadError HttpDownloader::downloadToFile(const std::string&
|
|||||||
// Get the stream for chunked reading
|
// Get the stream for chunked reading
|
||||||
WiFiClient* stream = http.getStreamPtr();
|
WiFiClient* stream = http.getStreamPtr();
|
||||||
if (!stream) {
|
if (!stream) {
|
||||||
Serial.printf("[%lu] [HTTP] Failed to get stream\n", millis());
|
LOG_ERR("HTTP", "Failed to get stream");
|
||||||
file.close();
|
file.close();
|
||||||
Storage.remove(destPath.c_str());
|
Storage.remove(destPath.c_str());
|
||||||
http.end();
|
http.end();
|
||||||
@@ -143,7 +143,7 @@ HttpDownloader::DownloadError HttpDownloader::downloadToFile(const std::string&
|
|||||||
|
|
||||||
const size_t written = file.write(buffer, bytesRead);
|
const size_t written = file.write(buffer, bytesRead);
|
||||||
if (written != bytesRead) {
|
if (written != bytesRead) {
|
||||||
Serial.printf("[%lu] [HTTP] Write failed: wrote %zu of %zu bytes\n", millis(), written, bytesRead);
|
LOG_ERR("HTTP", "Write failed: wrote %zu of %zu bytes", written, bytesRead);
|
||||||
file.close();
|
file.close();
|
||||||
Storage.remove(destPath.c_str());
|
Storage.remove(destPath.c_str());
|
||||||
http.end();
|
http.end();
|
||||||
@@ -160,11 +160,11 @@ HttpDownloader::DownloadError HttpDownloader::downloadToFile(const std::string&
|
|||||||
file.close();
|
file.close();
|
||||||
http.end();
|
http.end();
|
||||||
|
|
||||||
Serial.printf("[%lu] [HTTP] Downloaded %zu bytes\n", millis(), downloaded);
|
LOG_DBG("HTTP", "Downloaded %zu bytes", downloaded);
|
||||||
|
|
||||||
// Verify download size if known
|
// Verify download size if known
|
||||||
if (contentLength > 0 && downloaded != contentLength) {
|
if (contentLength > 0 && downloaded != contentLength) {
|
||||||
Serial.printf("[%lu] [HTTP] Size mismatch: got %zu, expected %zu\n", millis(), downloaded, contentLength);
|
LOG_ERR("HTTP", "Size mismatch: got %zu, expected %zu", downloaded, contentLength);
|
||||||
Storage.remove(destPath.c_str());
|
Storage.remove(destPath.c_str());
|
||||||
return HTTP_ERROR;
|
return HTTP_ERROR;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "OtaUpdater.h"
|
#include "OtaUpdater.h"
|
||||||
|
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
|
#include <Logging.h>
|
||||||
|
|
||||||
#include "esp_http_client.h"
|
#include "esp_http_client.h"
|
||||||
#include "esp_https_ota.h"
|
#include "esp_https_ota.h"
|
||||||
@@ -39,7 +40,7 @@ esp_err_t event_handler(esp_http_client_event_t* event) {
|
|||||||
local_buf = static_cast<char*>(calloc(content_len + 1, sizeof(char)));
|
local_buf = static_cast<char*>(calloc(content_len + 1, sizeof(char)));
|
||||||
output_len = 0;
|
output_len = 0;
|
||||||
if (local_buf == NULL) {
|
if (local_buf == NULL) {
|
||||||
Serial.printf("[%lu] [OTA] HTTP Client Out of Memory Failed, Allocation %d\n", millis(), content_len);
|
LOG_ERR("OTA", "HTTP Client Out of Memory Failed, Allocation %d", content_len);
|
||||||
return ESP_ERR_NO_MEM;
|
return ESP_ERR_NO_MEM;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,7 +53,7 @@ esp_err_t event_handler(esp_http_client_event_t* event) {
|
|||||||
/* Code might be hits here, It happened once (for version checking) but I need more logs to handle that */
|
/* Code might be hits here, It happened once (for version checking) but I need more logs to handle that */
|
||||||
int chunked_len;
|
int chunked_len;
|
||||||
esp_http_client_get_chunk_length(event->client, &chunked_len);
|
esp_http_client_get_chunk_length(event->client, &chunked_len);
|
||||||
Serial.printf("[%lu] [OTA] esp_http_client_is_chunked_response failed, chunked_len: %d\n", millis(), chunked_len);
|
LOG_DBG("OTA", "esp_http_client_is_chunked_response failed, chunked_len: %d", chunked_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
@@ -88,20 +89,20 @@ OtaUpdater::OtaUpdaterError OtaUpdater::checkForUpdate() {
|
|||||||
|
|
||||||
esp_http_client_handle_t client_handle = esp_http_client_init(&client_config);
|
esp_http_client_handle_t client_handle = esp_http_client_init(&client_config);
|
||||||
if (!client_handle) {
|
if (!client_handle) {
|
||||||
Serial.printf("[%lu] [OTA] HTTP Client Handle Failed\n", millis());
|
LOG_ERR("OTA", "HTTP Client Handle Failed");
|
||||||
return INTERNAL_UPDATE_ERROR;
|
return INTERNAL_UPDATE_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err = esp_http_client_set_header(client_handle, "User-Agent", "CrossPoint-ESP32-" CROSSPOINT_VERSION);
|
esp_err = esp_http_client_set_header(client_handle, "User-Agent", "CrossPoint-ESP32-" CROSSPOINT_VERSION);
|
||||||
if (esp_err != ESP_OK) {
|
if (esp_err != ESP_OK) {
|
||||||
Serial.printf("[%lu] [OTA] esp_http_client_set_header Failed : %s\n", millis(), esp_err_to_name(esp_err));
|
LOG_ERR("OTA", "esp_http_client_set_header Failed : %s", esp_err_to_name(esp_err));
|
||||||
esp_http_client_cleanup(client_handle);
|
esp_http_client_cleanup(client_handle);
|
||||||
return INTERNAL_UPDATE_ERROR;
|
return INTERNAL_UPDATE_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err = esp_http_client_perform(client_handle);
|
esp_err = esp_http_client_perform(client_handle);
|
||||||
if (esp_err != ESP_OK) {
|
if (esp_err != ESP_OK) {
|
||||||
Serial.printf("[%lu] [OTA] esp_http_client_perform Failed : %s\n", millis(), esp_err_to_name(esp_err));
|
LOG_ERR("OTA", "esp_http_client_perform Failed : %s", esp_err_to_name(esp_err));
|
||||||
esp_http_client_cleanup(client_handle);
|
esp_http_client_cleanup(client_handle);
|
||||||
return HTTP_ERROR;
|
return HTTP_ERROR;
|
||||||
}
|
}
|
||||||
@@ -109,7 +110,7 @@ OtaUpdater::OtaUpdaterError OtaUpdater::checkForUpdate() {
|
|||||||
/* esp_http_client_close will be called inside cleanup as well*/
|
/* esp_http_client_close will be called inside cleanup as well*/
|
||||||
esp_err = esp_http_client_cleanup(client_handle);
|
esp_err = esp_http_client_cleanup(client_handle);
|
||||||
if (esp_err != ESP_OK) {
|
if (esp_err != ESP_OK) {
|
||||||
Serial.printf("[%lu] [OTA] esp_http_client_cleanupp Failed : %s\n", millis(), esp_err_to_name(esp_err));
|
LOG_ERR("OTA", "esp_http_client_cleanup Failed : %s", esp_err_to_name(esp_err));
|
||||||
return INTERNAL_UPDATE_ERROR;
|
return INTERNAL_UPDATE_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,17 +120,17 @@ OtaUpdater::OtaUpdaterError OtaUpdater::checkForUpdate() {
|
|||||||
filter["assets"][0]["size"] = true;
|
filter["assets"][0]["size"] = true;
|
||||||
const DeserializationError error = deserializeJson(doc, local_buf, DeserializationOption::Filter(filter));
|
const DeserializationError error = deserializeJson(doc, local_buf, DeserializationOption::Filter(filter));
|
||||||
if (error) {
|
if (error) {
|
||||||
Serial.printf("[%lu] [OTA] JSON parse failed: %s\n", millis(), error.c_str());
|
LOG_ERR("OTA", "JSON parse failed: %s", error.c_str());
|
||||||
return JSON_PARSE_ERROR;
|
return JSON_PARSE_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!doc["tag_name"].is<std::string>()) {
|
if (!doc["tag_name"].is<std::string>()) {
|
||||||
Serial.printf("[%lu] [OTA] No tag_name found\n", millis());
|
LOG_ERR("OTA", "No tag_name found");
|
||||||
return JSON_PARSE_ERROR;
|
return JSON_PARSE_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!doc["assets"].is<JsonArray>()) {
|
if (!doc["assets"].is<JsonArray>()) {
|
||||||
Serial.printf("[%lu] [OTA] No assets found\n", millis());
|
LOG_ERR("OTA", "No assets found");
|
||||||
return JSON_PARSE_ERROR;
|
return JSON_PARSE_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,11 +147,11 @@ OtaUpdater::OtaUpdaterError OtaUpdater::checkForUpdate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!updateAvailable) {
|
if (!updateAvailable) {
|
||||||
Serial.printf("[%lu] [OTA] No firmware.bin asset found\n", millis());
|
LOG_ERR("OTA", "No firmware.bin asset found");
|
||||||
return NO_UPDATE;
|
return NO_UPDATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [OTA] Found update: %s\n", millis(), latestVersion.c_str());
|
LOG_DBG("OTA", "Found update: %s", latestVersion.c_str());
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,7 +234,7 @@ OtaUpdater::OtaUpdaterError OtaUpdater::installUpdate() {
|
|||||||
|
|
||||||
esp_err = esp_https_ota_begin(&ota_config, &ota_handle);
|
esp_err = esp_https_ota_begin(&ota_config, &ota_handle);
|
||||||
if (esp_err != ESP_OK) {
|
if (esp_err != ESP_OK) {
|
||||||
Serial.printf("[%lu] [OTA] HTTP OTA Begin Failed: %s\n", millis(), esp_err_to_name(esp_err));
|
LOG_DBG("OTA", "HTTP OTA Begin Failed: %s", esp_err_to_name(esp_err));
|
||||||
return INTERNAL_UPDATE_ERROR;
|
return INTERNAL_UPDATE_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,24 +250,23 @@ OtaUpdater::OtaUpdaterError OtaUpdater::installUpdate() {
|
|||||||
esp_wifi_set_ps(WIFI_PS_MIN_MODEM);
|
esp_wifi_set_ps(WIFI_PS_MIN_MODEM);
|
||||||
|
|
||||||
if (esp_err != ESP_OK) {
|
if (esp_err != ESP_OK) {
|
||||||
Serial.printf("[%lu] [OTA] esp_https_ota_perform Failed: %s\n", millis(), esp_err_to_name(esp_err));
|
LOG_ERR("OTA", "esp_https_ota_perform Failed: %s", esp_err_to_name(esp_err));
|
||||||
esp_https_ota_finish(ota_handle);
|
esp_https_ota_finish(ota_handle);
|
||||||
return HTTP_ERROR;
|
return HTTP_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!esp_https_ota_is_complete_data_received(ota_handle)) {
|
if (!esp_https_ota_is_complete_data_received(ota_handle)) {
|
||||||
Serial.printf("[%lu] [OTA] esp_https_ota_is_complete_data_received Failed: %s\n", millis(),
|
LOG_ERR("OTA", "esp_https_ota_is_complete_data_received Failed: %s", esp_err_to_name(esp_err));
|
||||||
esp_err_to_name(esp_err));
|
|
||||||
esp_https_ota_finish(ota_handle);
|
esp_https_ota_finish(ota_handle);
|
||||||
return INTERNAL_UPDATE_ERROR;
|
return INTERNAL_UPDATE_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err = esp_https_ota_finish(ota_handle);
|
esp_err = esp_https_ota_finish(ota_handle);
|
||||||
if (esp_err != ESP_OK) {
|
if (esp_err != ESP_OK) {
|
||||||
Serial.printf("[%lu] [OTA] esp_https_ota_finish Failed: %s\n", millis(), esp_err_to_name(esp_err));
|
LOG_ERR("OTA", "esp_https_ota_finish Failed: %s", esp_err_to_name(esp_err));
|
||||||
return INTERNAL_UPDATE_ERROR;
|
return INTERNAL_UPDATE_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [OTA] Update completed\n", millis());
|
LOG_INF("OTA", "Update completed");
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user