diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index 1f2abb41..6a822063 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -170,6 +170,7 @@ STR_FILENAME: "Filename" STR_BINARY: "Binary" STR_MENU_KOSYNC_BEHAVIOR: "Sync Behavior" STR_KO_SYNC_ON_BOOK_CLOSE: "Auto-Push on Book Close" +STR_SEND_METADATA: "Send Document Metadata" STR_KO_AUTO_SYNC_SKIPPED: "Remote ahead, skipping" STR_KO_LONGPRESS_HINT: "Hold Confirm to open with sync" STR_SET_CREDENTIALS_FIRST: "Set credentials first" diff --git a/lib/KOReaderSync/KOReaderCredentialStore.cpp b/lib/KOReaderSync/KOReaderCredentialStore.cpp index 05a2b3ab..a0655690 100644 --- a/lib/KOReaderSync/KOReaderCredentialStore.cpp +++ b/lib/KOReaderSync/KOReaderCredentialStore.cpp @@ -175,3 +175,8 @@ void KOReaderCredentialStore::setMatchMethod(DocumentMatchMethod method) { matchMethod = method; LOG_DBG("KRS", "Set match method: %s", method == DocumentMatchMethod::FILENAME ? "Filename" : "Binary"); } + +void KOReaderCredentialStore::setSendMetadata(bool value) { + sendMetadata = value; + LOG_DBG("KRS", "Send metadata: %s", value ? "enabled" : "disabled"); +} diff --git a/lib/KOReaderSync/KOReaderCredentialStore.h b/lib/KOReaderSync/KOReaderCredentialStore.h index 89e008d0..4ed55a28 100644 --- a/lib/KOReaderSync/KOReaderCredentialStore.h +++ b/lib/KOReaderSync/KOReaderCredentialStore.h @@ -27,6 +27,7 @@ class KOReaderCredentialStore { std::string password; std::string serverUrl; // Custom sync server URL (empty = default) DocumentMatchMethod matchMethod = DocumentMatchMethod::FILENAME; // Default to filename for compatibility + bool sendMetadata = false; // Private constructor for singleton KOReaderCredentialStore() = default; @@ -72,6 +73,10 @@ class KOReaderCredentialStore { // Document matching method void setMatchMethod(DocumentMatchMethod method); DocumentMatchMethod getMatchMethod() const { return matchMethod; } + + // Send document metadata (filename, title, authors) with progress uploads + void setSendMetadata(bool value); + bool getSendMetadata() const { return sendMetadata; } }; // Helper macro to access credential store diff --git a/lib/KOReaderSync/KOReaderSyncClient.cpp b/lib/KOReaderSync/KOReaderSyncClient.cpp index 9bfc1087..7030488e 100644 --- a/lib/KOReaderSync/KOReaderSyncClient.cpp +++ b/lib/KOReaderSync/KOReaderSyncClient.cpp @@ -555,6 +555,13 @@ KOReaderSyncClient::Error KOReaderSyncClient::updateProgress(const KOReaderProgr doc["device"] = DEVICE_NAME; doc["device_id"] = DEVICE_ID; + if (progress.metadata) { + JsonObject meta = doc["metadata"].to(); + meta["filename"] = progress.metadata->filename; + meta["title"] = progress.metadata->title; + meta["authors"] = progress.metadata->authors; + } + std::string body; serializeJson(doc, body); diff --git a/lib/KOReaderSync/KOReaderSyncClient.h b/lib/KOReaderSync/KOReaderSyncClient.h index 8ffa0203..faecb5bb 100644 --- a/lib/KOReaderSync/KOReaderSyncClient.h +++ b/lib/KOReaderSync/KOReaderSyncClient.h @@ -1,16 +1,29 @@ #pragma once +#include #include +/** + * Optional document metadata sent alongside progress uploads. + * Only populated when KOReaderCredentialStore::getSendMetadata() is true. + * The official sync server ignores this field; custom servers may use it. + */ +struct KOReaderMetadata { + std::string filename; + std::string title; + std::string authors; +}; + /** * Progress data from KOReader sync server. */ struct KOReaderProgress { - std::string document; // Document hash - std::string progress; // XPath-like progress string - float percentage; // Progress percentage (0.0 to 1.0) - std::string device; // Device name - std::string deviceId; // Device ID - int64_t timestamp; // Unix timestamp of last update + std::string document; // Document hash + std::string progress; // XPath-like progress string + float percentage; // Progress percentage (0.0 to 1.0) + std::string device; // Device name + std::string deviceId; // Device ID + int64_t timestamp; // Unix timestamp of last update + std::optional metadata; // Optional document metadata }; /** diff --git a/src/JsonSettingsIO.cpp b/src/JsonSettingsIO.cpp index aaa1fa87..2723d325 100644 --- a/src/JsonSettingsIO.cpp +++ b/src/JsonSettingsIO.cpp @@ -296,6 +296,7 @@ bool JsonSettingsIO::saveKOReader(const KOReaderCredentialStore& store, const ch doc["password_obf"] = obfuscation::obfuscateToBase64(store.getPassword()); doc["serverUrl"] = store.getServerUrl(); doc["matchMethod"] = static_cast(store.getMatchMethod()); + doc["sendMetadata"] = store.getSendMetadata(); String json; serializeJson(doc, json); @@ -321,6 +322,7 @@ bool JsonSettingsIO::loadKOReader(KOReaderCredentialStore& store, const char* js store.serverUrl = doc["serverUrl"] | std::string(""); uint8_t method = doc["matchMethod"] | (uint8_t)0; store.matchMethod = static_cast(method); + store.sendMetadata = doc["sendMetadata"] | false; LOG_DBG("KRS", "Loaded KOReader credentials for user: %s", store.username.c_str()); return true; diff --git a/src/SettingsList.h b/src/SettingsList.h index c2ad45b0..0e8f548d 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -276,6 +276,19 @@ inline const std::vector list = { "koMatchMethod", StrId::STR_KOREADER_SYNC), SettingInfo::Toggle(StrId::STR_KO_SYNC_ON_BOOK_CLOSE, &CrossPointSettings::koSyncOnBookClose, "koSyncOnBookClose", StrId::STR_KOREADER_SYNC), + []() { + SettingInfo s; + s.nameId = StrId::STR_SEND_METADATA; + s.type = SettingType::TOGGLE; + s.key = "koSendMetadata"; + s.category = StrId::STR_KOREADER_SYNC; + s.valueGetter = [](const void*) -> uint8_t { return KOREADER_STORE.getSendMetadata() ? 1u : 0u; }; + s.valueSetter = [](void*, uint8_t v) { + KOREADER_STORE.setSendMetadata(v != 0); + KOREADER_STORE.saveToFile(); + }; + return s; + }(), // --- Status Bar Settings (web-only, uses StatusBarSettingsActivity) --- SettingInfo::Toggle(StrId::STR_CHAPTER_PAGE_COUNT, &CrossPointSettings::statusBarChapterPageCount, diff --git a/src/activities/reader/KOReaderSyncActivity.cpp b/src/activities/reader/KOReaderSyncActivity.cpp index 99602f72..2d86d6b5 100644 --- a/src/activities/reader/KOReaderSyncActivity.cpp +++ b/src/activities/reader/KOReaderSyncActivity.cpp @@ -405,6 +405,7 @@ void KOReaderSyncActivity::performUpload() { progress.document = documentHash; progress.progress = localProgress.xpath; progress.percentage = localProgress.percentage; + progress.metadata = localDocumentMetadata; const auto result = KOReaderSyncClient::updateProgress(progress); KOReaderSyncClient::endPersistentSession(); @@ -717,6 +718,18 @@ bool KOReaderSyncActivity::computeLocalProgressAndChapter() { localChapterLabel = (localTocIndex >= 0) ? epub->getTocItem(localTocIndex).title : (std::string(tr(STR_SECTION_PREFIX)) + std::to_string(currentSpineIndex + 1)); + + if (KOREADER_STORE.getSendMetadata()) { + const size_t slash = epubPath.rfind('/'); + KOReaderMetadata meta; + meta.filename = (slash != std::string::npos) ? epubPath.substr(slash + 1) : epubPath; + meta.title = epub->getTitle(); + meta.authors = epub->getAuthor(); + localDocumentMetadata = std::move(meta); + } else { + localDocumentMetadata.reset(); + } + return true; } diff --git a/src/activities/reader/KOReaderSyncActivity.h b/src/activities/reader/KOReaderSyncActivity.h index 003b8ead..0985ac22 100644 --- a/src/activities/reader/KOReaderSyncActivity.h +++ b/src/activities/reader/KOReaderSyncActivity.h @@ -93,6 +93,7 @@ class KOReaderSyncActivity final : public Activity { KOReaderPosition localProgress; std::string remoteChapterLabel; std::string localChapterLabel; + std::optional localDocumentMetadata; // Selection in result screen (0=Apply, 1=Upload) int selectedOption = 0; diff --git a/src/activities/settings/KOReaderSettingsActivity.cpp b/src/activities/settings/KOReaderSettingsActivity.cpp index 091e1020..c88487fb 100644 --- a/src/activities/settings/KOReaderSettingsActivity.cpp +++ b/src/activities/settings/KOReaderSettingsActivity.cpp @@ -17,7 +17,7 @@ KOReaderSettingsActivity::KOReaderSettingsActivity(GfxRenderer& renderer, Mapped } void KOReaderSettingsActivity::buildMenuItems() { - menuItems.reserve(7); + menuItems.reserve(8); // Username, Password, Server URL: ACTION items with custom value display menuItems.push_back(SettingInfo::Action(StrId::STR_SYNC_SERVER_URL, SettingAction::None) .withSubcategory(StrId::STR_MENU_KOSYNC_SERVER)); @@ -35,6 +35,17 @@ void KOReaderSettingsActivity::buildMenuItems() { .withSubcategory(StrId::STR_MENU_KOSYNC_BEHAVIOR)); menuItems.push_back(SettingInfo::Toggle(StrId::STR_KO_SYNC_ON_BOOK_CLOSE, &CrossPointSettings::koSyncOnBookClose, "koSyncOnBookClose")); + { + SettingInfo s; + s.nameId = StrId::STR_SEND_METADATA; + s.type = SettingType::TOGGLE; + s.valueGetter = [](const void*) -> uint8_t { return KOREADER_STORE.getSendMetadata() ? 1u : 0u; }; + s.valueSetter = [](void*, uint8_t v) { + KOREADER_STORE.setSendMetadata(v != 0); + KOREADER_STORE.saveToFile(); + }; + menuItems.push_back(std::move(s)); + } // Authenticate and Register: ACTION items menuItems.push_back(