Files

175 lines
4.9 KiB
C++
Raw Permalink Normal View History

2026-01-14 19:36:40 +09:00
#include "Txt.h"
#include <FsHelpers.h>
#include <JpegToBmpConverter.h>
2026-02-13 12:16:39 +01:00
#include <Logging.h>
2026-01-14 19:36:40 +09:00
Txt::Txt(std::string path, std::string cacheBasePath)
: filepath(std::move(path)), cacheBasePath(std::move(cacheBasePath)) {
// Generate cache path from file path hash
const size_t hash = std::hash<std::string>{}(filepath);
cachePath = this->cacheBasePath + "/txt_" + std::to_string(hash);
}
bool Txt::load() {
if (loaded) {
return true;
}
2026-02-08 21:29:14 +01:00
if (!Storage.exists(filepath.c_str())) {
2026-02-13 12:16:39 +01:00
LOG_ERR("TXT", "File does not exist: %s", filepath.c_str());
2026-01-14 19:36:40 +09:00
return false;
}
FsFile file;
2026-02-08 21:29:14 +01:00
if (!Storage.openFileForRead("TXT", filepath, file)) {
2026-02-13 12:16:39 +01:00
LOG_ERR("TXT", "Failed to open file: %s", filepath.c_str());
2026-01-14 19:36:40 +09:00
return false;
}
fileSize = file.size();
file.close();
loaded = true;
2026-02-13 12:16:39 +01:00
LOG_DBG("TXT", "Loaded TXT file: %s (%zu bytes)", filepath.c_str(), fileSize);
2026-01-14 19:36:40 +09:00
return true;
}
std::string Txt::getTitle() const {
// Extract filename without path and extension
size_t lastSlash = filepath.find_last_of('/');
std::string filename = (lastSlash != std::string::npos) ? filepath.substr(lastSlash + 1) : filepath;
// Remove .txt extension
if (FsHelpers::hasTxtExtension(filename)) {
2026-01-14 19:36:40 +09:00
filename = filename.substr(0, filename.length() - 4);
}
return filename;
}
void Txt::setupCacheDir() const {
2026-02-08 21:29:14 +01:00
if (!Storage.exists(cacheBasePath.c_str())) {
Storage.mkdir(cacheBasePath.c_str());
2026-01-14 19:36:40 +09:00
}
2026-02-08 21:29:14 +01:00
if (!Storage.exists(cachePath.c_str())) {
Storage.mkdir(cachePath.c_str());
2026-01-14 19:36:40 +09:00
}
}
std::string Txt::findCoverImage() const {
// Get the folder containing the txt file
size_t lastSlash = filepath.find_last_of('/');
std::string folder = (lastSlash != std::string::npos) ? filepath.substr(0, lastSlash) : "";
if (folder.empty()) {
folder = "/";
}
// Get the base filename without extension (e.g., "mybook" from "/books/mybook.txt")
std::string baseName = getTitle();
// Image extensions to try
const char* extensions[] = {".bmp", ".jpg", ".jpeg", ".png", ".BMP", ".JPG", ".JPEG", ".PNG"};
// First priority: look for image with same name as txt file (e.g., mybook.jpg)
for (const auto& ext : extensions) {
std::string coverPath = folder + "/" + baseName + ext;
2026-02-08 21:29:14 +01:00
if (Storage.exists(coverPath.c_str())) {
2026-02-13 12:16:39 +01:00
LOG_DBG("TXT", "Found matching cover image: %s", coverPath.c_str());
2026-01-14 19:36:40 +09:00
return coverPath;
}
}
// Fallback: look for cover image files
const char* coverNames[] = {"cover", "Cover", "COVER"};
for (const auto& name : coverNames) {
for (const auto& ext : extensions) {
std::string coverPath = folder + "/" + std::string(name) + ext;
2026-02-08 21:29:14 +01:00
if (Storage.exists(coverPath.c_str())) {
2026-02-13 12:16:39 +01:00
LOG_DBG("TXT", "Found fallback cover image: %s", coverPath.c_str());
2026-01-14 19:36:40 +09:00
return coverPath;
}
}
}
return "";
}
std::string Txt::getCoverBmpPath() const { return cachePath + "/cover.bmp"; }
bool Txt::generateCoverBmp() const {
// Already generated, return true
2026-02-08 21:29:14 +01:00
if (Storage.exists(getCoverBmpPath().c_str())) {
2026-01-14 19:36:40 +09:00
return true;
}
std::string coverImagePath = findCoverImage();
if (coverImagePath.empty()) {
2026-02-13 12:16:39 +01:00
LOG_DBG("TXT", "No cover image found for TXT file");
2026-01-14 19:36:40 +09:00
return false;
}
// Setup cache directory
setupCacheDir();
if (FsHelpers::hasBmpExtension(coverImagePath)) {
2026-01-14 19:36:40 +09:00
// Copy BMP file to cache
2026-02-13 12:16:39 +01:00
LOG_DBG("TXT", "Copying BMP cover image to cache");
2026-01-14 19:36:40 +09:00
FsFile src, dst;
2026-02-08 21:29:14 +01:00
if (!Storage.openFileForRead("TXT", coverImagePath, src)) {
2026-01-14 19:36:40 +09:00
return false;
}
2026-02-08 21:29:14 +01:00
if (!Storage.openFileForWrite("TXT", getCoverBmpPath(), dst)) {
2026-01-14 19:36:40 +09:00
return false;
}
uint8_t buffer[1024];
while (src.available()) {
size_t bytesRead = src.read(buffer, sizeof(buffer));
dst.write(buffer, bytesRead);
}
2026-02-13 12:16:39 +01:00
LOG_DBG("TXT", "Copied BMP cover to cache");
2026-01-14 19:36:40 +09:00
return true;
} else if (FsHelpers::hasJpgExtension(coverImagePath)) {
2026-01-14 19:36:40 +09:00
// Convert JPG/JPEG to BMP (same approach as Epub)
2026-02-13 12:16:39 +01:00
LOG_DBG("TXT", "Generating BMP from JPG cover image");
2026-01-14 19:36:40 +09:00
FsFile coverJpg, coverBmp;
2026-02-08 21:29:14 +01:00
if (!Storage.openFileForRead("TXT", coverImagePath, coverJpg)) {
2026-01-14 19:36:40 +09:00
return false;
}
2026-02-08 21:29:14 +01:00
if (!Storage.openFileForWrite("TXT", getCoverBmpPath(), coverBmp)) {
2026-01-14 19:36:40 +09:00
return false;
}
const bool success = JpegToBmpConverter::jpegFileToBmpStream(coverJpg, coverBmp);
if (!success) {
2026-02-13 12:16:39 +01:00
LOG_ERR("TXT", "Failed to generate BMP from JPG cover image");
2026-02-08 21:29:14 +01:00
Storage.remove(getCoverBmpPath().c_str());
2026-01-14 19:36:40 +09:00
} else {
2026-02-13 12:16:39 +01:00
LOG_DBG("TXT", "Generated BMP from JPG cover image");
2026-01-14 19:36:40 +09:00
}
return success;
}
// PNG files are not supported (would need a PNG decoder)
2026-02-13 12:16:39 +01:00
LOG_ERR("TXT", "Cover image format not supported (only BMP/JPG/JPEG)");
2026-01-14 19:36:40 +09:00
return false;
}
bool Txt::readContent(uint8_t* buffer, size_t offset, size_t length) const {
if (!loaded) {
return false;
}
FsFile file;
2026-02-08 21:29:14 +01:00
if (!Storage.openFileForRead("TXT", filepath, file)) {
2026-01-14 19:36:40 +09:00
return false;
}
if (!file.seek(offset)) {
return false;
}
size_t bytesRead = file.read(buffer, length);
return bytesRead > 0;
}