Send optional document metadata with KOSync progress uploads
This commit is contained in:
@@ -170,6 +170,7 @@ STR_FILENAME: "Filename"
|
|||||||
STR_BINARY: "Binary"
|
STR_BINARY: "Binary"
|
||||||
STR_MENU_KOSYNC_BEHAVIOR: "Sync Behavior"
|
STR_MENU_KOSYNC_BEHAVIOR: "Sync Behavior"
|
||||||
STR_KO_SYNC_ON_BOOK_CLOSE: "Auto-Push on Book Close"
|
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_AUTO_SYNC_SKIPPED: "Remote ahead, skipping"
|
||||||
STR_KO_LONGPRESS_HINT: "Hold Confirm to open with sync"
|
STR_KO_LONGPRESS_HINT: "Hold Confirm to open with sync"
|
||||||
STR_SET_CREDENTIALS_FIRST: "Set credentials first"
|
STR_SET_CREDENTIALS_FIRST: "Set credentials first"
|
||||||
|
|||||||
@@ -175,3 +175,8 @@ void KOReaderCredentialStore::setMatchMethod(DocumentMatchMethod method) {
|
|||||||
matchMethod = method;
|
matchMethod = method;
|
||||||
LOG_DBG("KRS", "Set match method: %s", method == DocumentMatchMethod::FILENAME ? "Filename" : "Binary");
|
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");
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ class KOReaderCredentialStore {
|
|||||||
std::string password;
|
std::string password;
|
||||||
std::string serverUrl; // Custom sync server URL (empty = default)
|
std::string serverUrl; // Custom sync server URL (empty = default)
|
||||||
DocumentMatchMethod matchMethod = DocumentMatchMethod::FILENAME; // Default to filename for compatibility
|
DocumentMatchMethod matchMethod = DocumentMatchMethod::FILENAME; // Default to filename for compatibility
|
||||||
|
bool sendMetadata = false;
|
||||||
|
|
||||||
// Private constructor for singleton
|
// Private constructor for singleton
|
||||||
KOReaderCredentialStore() = default;
|
KOReaderCredentialStore() = default;
|
||||||
@@ -72,6 +73,10 @@ class KOReaderCredentialStore {
|
|||||||
// Document matching method
|
// Document matching method
|
||||||
void setMatchMethod(DocumentMatchMethod method);
|
void setMatchMethod(DocumentMatchMethod method);
|
||||||
DocumentMatchMethod getMatchMethod() const { return matchMethod; }
|
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
|
// Helper macro to access credential store
|
||||||
|
|||||||
@@ -555,6 +555,13 @@ KOReaderSyncClient::Error KOReaderSyncClient::updateProgress(const KOReaderProgr
|
|||||||
doc["device"] = DEVICE_NAME;
|
doc["device"] = DEVICE_NAME;
|
||||||
doc["device_id"] = DEVICE_ID;
|
doc["device_id"] = DEVICE_ID;
|
||||||
|
|
||||||
|
if (progress.metadata) {
|
||||||
|
JsonObject meta = doc["metadata"].to<JsonObject>();
|
||||||
|
meta["filename"] = progress.metadata->filename;
|
||||||
|
meta["title"] = progress.metadata->title;
|
||||||
|
meta["authors"] = progress.metadata->authors;
|
||||||
|
}
|
||||||
|
|
||||||
std::string body;
|
std::string body;
|
||||||
serializeJson(doc, body);
|
serializeJson(doc, body);
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,29 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
* Progress data from KOReader sync server.
|
||||||
*/
|
*/
|
||||||
struct KOReaderProgress {
|
struct KOReaderProgress {
|
||||||
std::string document; // Document hash
|
std::string document; // Document hash
|
||||||
std::string progress; // XPath-like progress string
|
std::string progress; // XPath-like progress string
|
||||||
float percentage; // Progress percentage (0.0 to 1.0)
|
float percentage; // Progress percentage (0.0 to 1.0)
|
||||||
std::string device; // Device name
|
std::string device; // Device name
|
||||||
std::string deviceId; // Device ID
|
std::string deviceId; // Device ID
|
||||||
int64_t timestamp; // Unix timestamp of last update
|
int64_t timestamp; // Unix timestamp of last update
|
||||||
|
std::optional<KOReaderMetadata> metadata; // Optional document metadata
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -296,6 +296,7 @@ bool JsonSettingsIO::saveKOReader(const KOReaderCredentialStore& store, const ch
|
|||||||
doc["password_obf"] = obfuscation::obfuscateToBase64(store.getPassword());
|
doc["password_obf"] = obfuscation::obfuscateToBase64(store.getPassword());
|
||||||
doc["serverUrl"] = store.getServerUrl();
|
doc["serverUrl"] = store.getServerUrl();
|
||||||
doc["matchMethod"] = static_cast<uint8_t>(store.getMatchMethod());
|
doc["matchMethod"] = static_cast<uint8_t>(store.getMatchMethod());
|
||||||
|
doc["sendMetadata"] = store.getSendMetadata();
|
||||||
|
|
||||||
String json;
|
String json;
|
||||||
serializeJson(doc, json);
|
serializeJson(doc, json);
|
||||||
@@ -321,6 +322,7 @@ bool JsonSettingsIO::loadKOReader(KOReaderCredentialStore& store, const char* js
|
|||||||
store.serverUrl = doc["serverUrl"] | std::string("");
|
store.serverUrl = doc["serverUrl"] | std::string("");
|
||||||
uint8_t method = doc["matchMethod"] | (uint8_t)0;
|
uint8_t method = doc["matchMethod"] | (uint8_t)0;
|
||||||
store.matchMethod = static_cast<DocumentMatchMethod>(method);
|
store.matchMethod = static_cast<DocumentMatchMethod>(method);
|
||||||
|
store.sendMetadata = doc["sendMetadata"] | false;
|
||||||
|
|
||||||
LOG_DBG("KRS", "Loaded KOReader credentials for user: %s", store.username.c_str());
|
LOG_DBG("KRS", "Loaded KOReader credentials for user: %s", store.username.c_str());
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -276,6 +276,19 @@ inline const std::vector<SettingInfo> list = {
|
|||||||
"koMatchMethod", StrId::STR_KOREADER_SYNC),
|
"koMatchMethod", StrId::STR_KOREADER_SYNC),
|
||||||
SettingInfo::Toggle(StrId::STR_KO_SYNC_ON_BOOK_CLOSE, &CrossPointSettings::koSyncOnBookClose, "koSyncOnBookClose",
|
SettingInfo::Toggle(StrId::STR_KO_SYNC_ON_BOOK_CLOSE, &CrossPointSettings::koSyncOnBookClose, "koSyncOnBookClose",
|
||||||
StrId::STR_KOREADER_SYNC),
|
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) ---
|
// --- Status Bar Settings (web-only, uses StatusBarSettingsActivity) ---
|
||||||
SettingInfo::Toggle(StrId::STR_CHAPTER_PAGE_COUNT, &CrossPointSettings::statusBarChapterPageCount,
|
SettingInfo::Toggle(StrId::STR_CHAPTER_PAGE_COUNT, &CrossPointSettings::statusBarChapterPageCount,
|
||||||
|
|||||||
@@ -405,6 +405,7 @@ void KOReaderSyncActivity::performUpload() {
|
|||||||
progress.document = documentHash;
|
progress.document = documentHash;
|
||||||
progress.progress = localProgress.xpath;
|
progress.progress = localProgress.xpath;
|
||||||
progress.percentage = localProgress.percentage;
|
progress.percentage = localProgress.percentage;
|
||||||
|
progress.metadata = localDocumentMetadata;
|
||||||
|
|
||||||
const auto result = KOReaderSyncClient::updateProgress(progress);
|
const auto result = KOReaderSyncClient::updateProgress(progress);
|
||||||
KOReaderSyncClient::endPersistentSession();
|
KOReaderSyncClient::endPersistentSession();
|
||||||
@@ -717,6 +718,18 @@ bool KOReaderSyncActivity::computeLocalProgressAndChapter() {
|
|||||||
localChapterLabel = (localTocIndex >= 0)
|
localChapterLabel = (localTocIndex >= 0)
|
||||||
? epub->getTocItem(localTocIndex).title
|
? epub->getTocItem(localTocIndex).title
|
||||||
: (std::string(tr(STR_SECTION_PREFIX)) + std::to_string(currentSpineIndex + 1));
|
: (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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ class KOReaderSyncActivity final : public Activity {
|
|||||||
KOReaderPosition localProgress;
|
KOReaderPosition localProgress;
|
||||||
std::string remoteChapterLabel;
|
std::string remoteChapterLabel;
|
||||||
std::string localChapterLabel;
|
std::string localChapterLabel;
|
||||||
|
std::optional<KOReaderMetadata> localDocumentMetadata;
|
||||||
|
|
||||||
// Selection in result screen (0=Apply, 1=Upload)
|
// Selection in result screen (0=Apply, 1=Upload)
|
||||||
int selectedOption = 0;
|
int selectedOption = 0;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ KOReaderSettingsActivity::KOReaderSettingsActivity(GfxRenderer& renderer, Mapped
|
|||||||
}
|
}
|
||||||
|
|
||||||
void KOReaderSettingsActivity::buildMenuItems() {
|
void KOReaderSettingsActivity::buildMenuItems() {
|
||||||
menuItems.reserve(7);
|
menuItems.reserve(8);
|
||||||
// Username, Password, Server URL: ACTION items with custom value display
|
// Username, Password, Server URL: ACTION items with custom value display
|
||||||
menuItems.push_back(SettingInfo::Action(StrId::STR_SYNC_SERVER_URL, SettingAction::None)
|
menuItems.push_back(SettingInfo::Action(StrId::STR_SYNC_SERVER_URL, SettingAction::None)
|
||||||
.withSubcategory(StrId::STR_MENU_KOSYNC_SERVER));
|
.withSubcategory(StrId::STR_MENU_KOSYNC_SERVER));
|
||||||
@@ -35,6 +35,17 @@ void KOReaderSettingsActivity::buildMenuItems() {
|
|||||||
.withSubcategory(StrId::STR_MENU_KOSYNC_BEHAVIOR));
|
.withSubcategory(StrId::STR_MENU_KOSYNC_BEHAVIOR));
|
||||||
menuItems.push_back(SettingInfo::Toggle(StrId::STR_KO_SYNC_ON_BOOK_CLOSE, &CrossPointSettings::koSyncOnBookClose,
|
menuItems.push_back(SettingInfo::Toggle(StrId::STR_KO_SYNC_ON_BOOK_CLOSE, &CrossPointSettings::koSyncOnBookClose,
|
||||||
"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
|
// Authenticate and Register: ACTION items
|
||||||
menuItems.push_back(
|
menuItems.push_back(
|
||||||
|
|||||||
Reference in New Issue
Block a user