refactor: consolidate Epub blank strings, simplify BookInfo buildLayout
Replace 13 per-accessor static std::string blank locals with a single file-scope kBlank (~384 bytes DRAM saved). Add Epub::getMetadata() returning the full BookMetadata struct. Refactor buildLayout from 14 individual parameters to a single BookMetadata const ref + fileSize. Made-with: Cursor
This commit is contained in:
46
chat-summaries/2026-03-08_23-00-summary.md
Normal file
46
chat-summaries/2026-03-08_23-00-summary.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Parse and Display All Available EPUB Metadata Fields + Cleanup Refactor
|
||||
|
||||
## Task
|
||||
1. Extend the OPF parser, metadata cache, Epub accessors, i18n, and BookInfo screen to parse and display all standard Dublin Core metadata fields plus Calibre rating.
|
||||
2. Refactor for code quality: consolidate duplicate static blank strings, add `getMetadata()` accessor, and simplify `buildLayout` to accept `BookMetadata` struct.
|
||||
|
||||
## Changes Made
|
||||
|
||||
### 1. ContentOpfParser (`lib/Epub/Epub/parsers/ContentOpfParser.h/.cpp`)
|
||||
- Added 6 new `ParserState` entries: `IN_BOOK_PUBLISHER`, `IN_BOOK_DATE`, `IN_BOOK_SUBJECT`, `IN_BOOK_RIGHTS`, `IN_BOOK_CONTRIBUTOR`, `IN_BOOK_IDENTIFIER`
|
||||
- Added 7 new public string members: `publisher`, `date`, `subjects`, `rights`, `contributor`, `identifier`, `rating`
|
||||
- Added `identifierIsIsbn` flag for preferring ISBN identifiers over generic ones
|
||||
- `startElement`: handles `dc:publisher`, `dc:date`, `dc:subject` (multi-tag, comma-separated), `dc:rights`, `dc:contributor` (multi-tag, comma-separated), `dc:identifier` (prefers `opf:scheme="ISBN"`), and `calibre:rating` meta tag
|
||||
- `characterData`: appends text for all new states
|
||||
- `endElement`: transitions back to `IN_METADATA` for all new `dc:*` elements
|
||||
|
||||
### 2. BookMetadataCache (`lib/Epub/Epub/BookMetadataCache.h/.cpp`)
|
||||
- Added 7 new fields to `BookMetadata` struct
|
||||
- Bumped `BOOK_CACHE_VERSION` from 6 to 7
|
||||
- Updated `metadataSize` calculation (8 → 15 string fields)
|
||||
- Added `writeString`/`readString` calls for all new fields in serialization/deserialization
|
||||
|
||||
### 3. Epub Accessors (`lib/Epub/Epub.h/.cpp`)
|
||||
- Added 7 new accessor methods: `getPublisher()`, `getDate()`, `getSubjects()`, `getRights()`, `getContributor()`, `getIdentifier()`, `getRating()`
|
||||
- Added `getMetadata()` returning full `BookMetadataCache::BookMetadata` const ref
|
||||
- Consolidated 13 duplicate `static std::string blank` locals into single file-scope `kBlank` (saves ~384 bytes DRAM)
|
||||
- Propagated new fields from `opfParser` to `bookMetadata` in `parseContentOpf()`
|
||||
|
||||
### 4. I18n (`lib/I18n/translations/english.yaml`, `lib/I18n/I18nKeys.h`)
|
||||
- Added 7 new translation keys: `STR_PUBLISHER`, `STR_DATE`, `STR_SUBJECTS`, `STR_RATING`, `STR_ISBN`, `STR_RIGHTS`, `STR_CONTRIBUTOR`
|
||||
- Regenerated I18n headers
|
||||
|
||||
### 5. BookInfoActivity (`src/activities/home/BookInfoActivity.h/.cpp`)
|
||||
- Refactored `buildLayout()` from 14 individual parameters to single `BookMetadataCache::BookMetadata` struct + `fileSize`
|
||||
- `onEnter()` EPUB path uses `epub.getMetadata()` directly; XTC path builds a local `BookMetadata`
|
||||
- Display order: Title, Author, Series, Publisher, Date, Subjects, Rating (N/5), Language, ISBN, Contributor, File Size, Rights, Description
|
||||
- Rating displayed as `N / 5` (Calibre stores 0-10, divided by 2)
|
||||
|
||||
## Commits
|
||||
- `8025e6f` — `feat: parse and display all available EPUB metadata fields`
|
||||
- (pending) — `refactor: consolidate Epub blank strings, simplify BookInfo buildLayout`
|
||||
|
||||
## Follow-up
|
||||
- Existing book caches will auto-invalidate (version 6 → 7) and regenerate on next load
|
||||
- Users must delete `.crosspoint/` or let it regenerate to see new metadata for previously cached books
|
||||
- Hardware testing needed to verify rendering of all new fields in all orientations
|
||||
@@ -13,6 +13,11 @@
|
||||
#include "Epub/parsers/TocNavParser.h"
|
||||
#include "Epub/parsers/TocNcxParser.h"
|
||||
|
||||
namespace {
|
||||
const std::string kBlank;
|
||||
const BookMetadataCache::BookMetadata kBlankMetadata;
|
||||
} // namespace
|
||||
|
||||
bool Epub::findContentOpfFile(std::string* contentOpfFile) const {
|
||||
const auto containerPath = "META-INF/container.xml";
|
||||
size_t containerSize;
|
||||
@@ -501,101 +506,75 @@ const std::string& Epub::getCachePath() const { return cachePath; }
|
||||
const std::string& Epub::getPath() const { return filepath; }
|
||||
|
||||
const std::string& Epub::getTitle() const {
|
||||
static std::string blank;
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
||||
return blank;
|
||||
}
|
||||
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) return kBlank;
|
||||
return bookMetadataCache->coreMetadata.title;
|
||||
}
|
||||
|
||||
const std::string& Epub::getAuthor() const {
|
||||
static std::string blank;
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
||||
return blank;
|
||||
}
|
||||
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) return kBlank;
|
||||
return bookMetadataCache->coreMetadata.author;
|
||||
}
|
||||
|
||||
const std::string& Epub::getLanguage() const {
|
||||
static std::string blank;
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
||||
return blank;
|
||||
}
|
||||
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) return kBlank;
|
||||
return bookMetadataCache->coreMetadata.language;
|
||||
}
|
||||
|
||||
const std::string& Epub::getSeries() const {
|
||||
static std::string blank;
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
||||
return blank;
|
||||
}
|
||||
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) return kBlank;
|
||||
return bookMetadataCache->coreMetadata.series;
|
||||
}
|
||||
|
||||
const std::string& Epub::getSeriesIndex() const {
|
||||
static std::string blank;
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
||||
return blank;
|
||||
}
|
||||
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) return kBlank;
|
||||
return bookMetadataCache->coreMetadata.seriesIndex;
|
||||
}
|
||||
|
||||
const std::string& Epub::getDescription() const {
|
||||
static std::string blank;
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
||||
return blank;
|
||||
}
|
||||
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) return kBlank;
|
||||
return bookMetadataCache->coreMetadata.description;
|
||||
}
|
||||
|
||||
const std::string& Epub::getPublisher() const {
|
||||
static std::string blank;
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) return blank;
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) return kBlank;
|
||||
return bookMetadataCache->coreMetadata.publisher;
|
||||
}
|
||||
|
||||
const std::string& Epub::getDate() const {
|
||||
static std::string blank;
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) return blank;
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) return kBlank;
|
||||
return bookMetadataCache->coreMetadata.date;
|
||||
}
|
||||
|
||||
const std::string& Epub::getSubjects() const {
|
||||
static std::string blank;
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) return blank;
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) return kBlank;
|
||||
return bookMetadataCache->coreMetadata.subjects;
|
||||
}
|
||||
|
||||
const std::string& Epub::getRights() const {
|
||||
static std::string blank;
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) return blank;
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) return kBlank;
|
||||
return bookMetadataCache->coreMetadata.rights;
|
||||
}
|
||||
|
||||
const std::string& Epub::getContributor() const {
|
||||
static std::string blank;
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) return blank;
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) return kBlank;
|
||||
return bookMetadataCache->coreMetadata.contributor;
|
||||
}
|
||||
|
||||
const std::string& Epub::getIdentifier() const {
|
||||
static std::string blank;
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) return blank;
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) return kBlank;
|
||||
return bookMetadataCache->coreMetadata.identifier;
|
||||
}
|
||||
|
||||
const std::string& Epub::getRating() const {
|
||||
static std::string blank;
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) return blank;
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) return kBlank;
|
||||
return bookMetadataCache->coreMetadata.rating;
|
||||
}
|
||||
|
||||
const BookMetadataCache::BookMetadata& Epub::getMetadata() const {
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) return kBlankMetadata;
|
||||
return bookMetadataCache->coreMetadata;
|
||||
}
|
||||
|
||||
std::string Epub::getCoverBmpPath(bool cropped) const {
|
||||
const auto coverFileName = std::string("cover") + (cropped ? "_crop" : "");
|
||||
return cachePath + "/" + coverFileName + ".bmp";
|
||||
|
||||
@@ -61,6 +61,7 @@ class Epub {
|
||||
const std::string& getContributor() const;
|
||||
const std::string& getIdentifier() const;
|
||||
const std::string& getRating() const;
|
||||
const BookMetadataCache::BookMetadata& getMetadata() const;
|
||||
std::string getCoverBmpPath(bool cropped = false) const;
|
||||
bool generateCoverBmp(bool cropped = false) const;
|
||||
std::string getThumbBmpPath() const;
|
||||
|
||||
@@ -59,8 +59,7 @@ void BookInfoActivity::onEnter() {
|
||||
}
|
||||
}
|
||||
|
||||
std::string title, author, series, seriesIndex, description, language;
|
||||
std::string publisher, date, subjects, rights, contributor, identifier, rating;
|
||||
BookMetadataCache::BookMetadata meta;
|
||||
|
||||
if (FsHelpers::hasEpubExtension(fileName)) {
|
||||
Epub epub(filePath, "/.crosspoint");
|
||||
@@ -73,19 +72,8 @@ void BookInfoActivity::onEnter() {
|
||||
GUI.fillPopupProgress(renderer, popupRect, 50);
|
||||
}
|
||||
|
||||
title = epub.getTitle();
|
||||
author = epub.getAuthor();
|
||||
series = epub.getSeries();
|
||||
seriesIndex = epub.getSeriesIndex();
|
||||
description = normalizeWhitespace(epub.getDescription());
|
||||
language = epub.getLanguage();
|
||||
publisher = epub.getPublisher();
|
||||
date = epub.getDate();
|
||||
subjects = epub.getSubjects();
|
||||
rights = epub.getRights();
|
||||
contributor = epub.getContributor();
|
||||
identifier = epub.getIdentifier();
|
||||
rating = epub.getRating();
|
||||
meta = epub.getMetadata();
|
||||
meta.description = normalizeWhitespace(meta.description);
|
||||
|
||||
const int coverH = renderer.getScreenHeight() * 2 / 5;
|
||||
if (epub.generateThumbBmp(coverH)) {
|
||||
@@ -93,8 +81,8 @@ void BookInfoActivity::onEnter() {
|
||||
} else {
|
||||
const int thumbW = static_cast<int>(coverH * 0.6);
|
||||
const std::string placeholderPath = epub.getCachePath() + "/placeholder_" + std::to_string(coverH) + ".bmp";
|
||||
if (PlaceholderCoverGenerator::generate(placeholderPath, title.empty() ? fileName : title, author, thumbW,
|
||||
coverH)) {
|
||||
if (PlaceholderCoverGenerator::generate(placeholderPath, meta.title.empty() ? fileName : meta.title, meta.author,
|
||||
thumbW, coverH)) {
|
||||
coverBmpPath = placeholderPath;
|
||||
}
|
||||
}
|
||||
@@ -113,8 +101,8 @@ void BookInfoActivity::onEnter() {
|
||||
if (needsBuild) {
|
||||
GUI.fillPopupProgress(renderer, popupRect, 50);
|
||||
}
|
||||
title = xtc.getTitle();
|
||||
author = xtc.getAuthor();
|
||||
meta.title = xtc.getTitle();
|
||||
meta.author = xtc.getAuthor();
|
||||
|
||||
const int coverH = renderer.getScreenHeight() * 2 / 5;
|
||||
if (xtc.generateThumbBmp(coverH)) {
|
||||
@@ -122,8 +110,8 @@ void BookInfoActivity::onEnter() {
|
||||
} else {
|
||||
const int thumbW = static_cast<int>(coverH * 0.6);
|
||||
const std::string placeholderPath = xtc.getCachePath() + "/placeholder_" + std::to_string(coverH) + ".bmp";
|
||||
if (PlaceholderCoverGenerator::generate(placeholderPath, title.empty() ? fileName : title, author, thumbW,
|
||||
coverH)) {
|
||||
if (PlaceholderCoverGenerator::generate(placeholderPath, meta.title.empty() ? fileName : meta.title, meta.author,
|
||||
thumbW, coverH)) {
|
||||
coverBmpPath = placeholderPath;
|
||||
}
|
||||
}
|
||||
@@ -133,23 +121,17 @@ void BookInfoActivity::onEnter() {
|
||||
}
|
||||
}
|
||||
|
||||
if (title.empty()) {
|
||||
title = fileName;
|
||||
if (meta.title.empty()) {
|
||||
meta.title = fileName;
|
||||
}
|
||||
|
||||
buildLayout(title, author, series, seriesIndex, description, language, fileSize, publisher, date, subjects, rights,
|
||||
contributor, identifier, rating);
|
||||
buildLayout(meta, fileSize);
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
void BookInfoActivity::onExit() { Activity::onExit(); }
|
||||
|
||||
void BookInfoActivity::buildLayout(const std::string& title, const std::string& author, const std::string& series,
|
||||
const std::string& seriesIndex, const std::string& description,
|
||||
const std::string& language, size_t fileSize, const std::string& publisher,
|
||||
const std::string& date, const std::string& subjects, const std::string& rights,
|
||||
const std::string& contributor, const std::string& identifier,
|
||||
const std::string& rating) {
|
||||
void BookInfoActivity::buildLayout(const BookMetadataCache::BookMetadata& meta, size_t fileSize) {
|
||||
const int contentW = renderer.getScreenWidth() - MARGIN * 2;
|
||||
fields.reserve(13);
|
||||
|
||||
@@ -162,23 +144,23 @@ void BookInfoActivity::buildLayout(const std::string& title, const std::string&
|
||||
fields.push_back(std::move(field));
|
||||
};
|
||||
|
||||
addField(nullptr, title, true, EpdFontFamily::BOLD);
|
||||
addField(tr(STR_AUTHOR), author, false, EpdFontFamily::REGULAR);
|
||||
addField(nullptr, meta.title, true, EpdFontFamily::BOLD);
|
||||
addField(tr(STR_AUTHOR), meta.author, false, EpdFontFamily::REGULAR);
|
||||
|
||||
if (!series.empty()) {
|
||||
std::string seriesStr = series;
|
||||
if (!seriesIndex.empty()) {
|
||||
seriesStr += " #" + seriesIndex;
|
||||
if (!meta.series.empty()) {
|
||||
std::string seriesStr = meta.series;
|
||||
if (!meta.seriesIndex.empty()) {
|
||||
seriesStr += " #" + meta.seriesIndex;
|
||||
}
|
||||
addField(tr(STR_SERIES), seriesStr, false, EpdFontFamily::REGULAR);
|
||||
}
|
||||
|
||||
addField(tr(STR_PUBLISHER), publisher, false, EpdFontFamily::REGULAR);
|
||||
addField(tr(STR_DATE), date, false, EpdFontFamily::REGULAR);
|
||||
addField(tr(STR_SUBJECTS), subjects, false, EpdFontFamily::REGULAR);
|
||||
addField(tr(STR_PUBLISHER), meta.publisher, false, EpdFontFamily::REGULAR);
|
||||
addField(tr(STR_DATE), meta.date, false, EpdFontFamily::REGULAR);
|
||||
addField(tr(STR_SUBJECTS), meta.subjects, false, EpdFontFamily::REGULAR);
|
||||
|
||||
if (!rating.empty()) {
|
||||
int ratingVal = atoi(rating.c_str());
|
||||
if (!meta.rating.empty()) {
|
||||
int ratingVal = atoi(meta.rating.c_str());
|
||||
if (ratingVal > 0 && ratingVal <= 10) {
|
||||
char ratingBuf[8];
|
||||
snprintf(ratingBuf, sizeof(ratingBuf), "%d / 5", (ratingVal + 1) / 2);
|
||||
@@ -186,16 +168,16 @@ void BookInfoActivity::buildLayout(const std::string& title, const std::string&
|
||||
}
|
||||
}
|
||||
|
||||
addField(tr(STR_LANGUAGE), language, false, EpdFontFamily::REGULAR);
|
||||
addField(tr(STR_ISBN), identifier, false, EpdFontFamily::REGULAR);
|
||||
addField(tr(STR_CONTRIBUTOR), contributor, false, EpdFontFamily::REGULAR);
|
||||
addField(tr(STR_LANGUAGE), meta.language, false, EpdFontFamily::REGULAR);
|
||||
addField(tr(STR_ISBN), meta.identifier, false, EpdFontFamily::REGULAR);
|
||||
addField(tr(STR_CONTRIBUTOR), meta.contributor, false, EpdFontFamily::REGULAR);
|
||||
|
||||
if (fileSize > 0) {
|
||||
addField(tr(STR_FILE_SIZE), formatFileSize(fileSize), false, EpdFontFamily::REGULAR);
|
||||
}
|
||||
|
||||
addField(tr(STR_RIGHTS), rights, false, EpdFontFamily::REGULAR);
|
||||
addField(tr(STR_DESCRIPTION), description, false, EpdFontFamily::REGULAR);
|
||||
addField(tr(STR_RIGHTS), meta.rights, false, EpdFontFamily::REGULAR);
|
||||
addField(tr(STR_DESCRIPTION), meta.description, false, EpdFontFamily::REGULAR);
|
||||
|
||||
const int lineH10 = renderer.getLineHeight(UI_10_FONT_ID);
|
||||
const int lineH12 = renderer.getLineHeight(UI_12_FONT_ID);
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <Epub/BookMetadataCache.h>
|
||||
|
||||
#include "../Activity.h"
|
||||
|
||||
class BookInfoActivity final : public Activity {
|
||||
@@ -31,10 +33,6 @@ class BookInfoActivity final : public Activity {
|
||||
int coverDisplayHeight = 0;
|
||||
int coverDisplayWidth = 0;
|
||||
|
||||
void buildLayout(const std::string& title, const std::string& author, const std::string& series,
|
||||
const std::string& seriesIndex, const std::string& description, const std::string& language,
|
||||
size_t fileSize, const std::string& publisher, const std::string& date,
|
||||
const std::string& subjects, const std::string& rights, const std::string& contributor,
|
||||
const std::string& identifier, const std::string& rating);
|
||||
void buildLayout(const BookMetadataCache::BookMetadata& meta, size_t fileSize);
|
||||
static std::string formatFileSize(size_t bytes);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user