#include "JsonSettingsIO.h" #include #include #include #include #include #include "CrossPointSettings.h" #include "CrossPointState.h" #include "KOReaderCredentialStore.h" #include "RecentBooksStore.h" #include "WifiCredentialStore.h" // ---- CrossPointState ---- bool JsonSettingsIO::saveState(const CrossPointState& s, const char* path) { JsonDocument doc; doc["openEpubPath"] = s.openEpubPath; doc["lastSleepImage"] = s.lastSleepImage; doc["readerActivityLoadCount"] = s.readerActivityLoadCount; doc["lastSleepFromReader"] = s.lastSleepFromReader; String json; serializeJson(doc, json); return Storage.writeFile(path, json); } bool JsonSettingsIO::loadState(CrossPointState& s, const char* json) { JsonDocument doc; auto error = deserializeJson(doc, json); if (error) { LOG_ERR("CPS", "JSON parse error: %s", error.c_str()); return false; } s.openEpubPath = doc["openEpubPath"] | std::string(""); s.lastSleepImage = doc["lastSleepImage"] | (uint8_t)0; s.readerActivityLoadCount = doc["readerActivityLoadCount"] | (uint8_t)0; s.lastSleepFromReader = doc["lastSleepFromReader"] | false; return true; } // ---- CrossPointSettings ---- bool JsonSettingsIO::saveSettings(const CrossPointSettings& s, const char* path) { JsonDocument doc; doc["sleepScreen"] = s.sleepScreen; doc["sleepScreenCoverMode"] = s.sleepScreenCoverMode; doc["sleepScreenCoverFilter"] = s.sleepScreenCoverFilter; doc["statusBar"] = s.statusBar; doc["extraParagraphSpacing"] = s.extraParagraphSpacing; doc["textAntiAliasing"] = s.textAntiAliasing; doc["shortPwrBtn"] = s.shortPwrBtn; doc["orientation"] = s.orientation; doc["sideButtonLayout"] = s.sideButtonLayout; doc["frontButtonBack"] = s.frontButtonBack; doc["frontButtonConfirm"] = s.frontButtonConfirm; doc["frontButtonLeft"] = s.frontButtonLeft; doc["frontButtonRight"] = s.frontButtonRight; doc["fontFamily"] = s.fontFamily; doc["fontSize"] = s.fontSize; doc["lineSpacing"] = s.lineSpacing; doc["paragraphAlignment"] = s.paragraphAlignment; doc["sleepTimeout"] = s.sleepTimeout; doc["refreshFrequency"] = s.refreshFrequency; doc["screenMargin"] = s.screenMargin; doc["opdsServerUrl"] = s.opdsServerUrl; doc["opdsUsername"] = s.opdsUsername; doc["opdsPassword_obf"] = obfuscation::obfuscateToBase64(s.opdsPassword); doc["hideBatteryPercentage"] = s.hideBatteryPercentage; doc["longPressChapterSkip"] = s.longPressChapterSkip; doc["hyphenationEnabled"] = s.hyphenationEnabled; doc["uiTheme"] = s.uiTheme; doc["fadingFix"] = s.fadingFix; doc["embeddedStyle"] = s.embeddedStyle; String json; serializeJson(doc, json); return Storage.writeFile(path, json); } bool JsonSettingsIO::loadSettings(CrossPointSettings& s, const char* json, bool* needsResave) { if (needsResave) *needsResave = false; JsonDocument doc; auto error = deserializeJson(doc, json); if (error) { LOG_ERR("CPS", "JSON parse error: %s", error.c_str()); return false; } using S = CrossPointSettings; auto clamp = [](uint8_t val, uint8_t maxVal, uint8_t def) -> uint8_t { return val < maxVal ? val : def; }; s.sleepScreen = clamp(doc["sleepScreen"] | (uint8_t)S::DARK, S::SLEEP_SCREEN_MODE_COUNT, S::DARK); s.sleepScreenCoverMode = clamp(doc["sleepScreenCoverMode"] | (uint8_t)S::FIT, S::SLEEP_SCREEN_COVER_MODE_COUNT, S::FIT); s.sleepScreenCoverFilter = clamp(doc["sleepScreenCoverFilter"] | (uint8_t)S::NO_FILTER, S::SLEEP_SCREEN_COVER_FILTER_COUNT, S::NO_FILTER); s.statusBar = clamp(doc["statusBar"] | (uint8_t)S::FULL, S::STATUS_BAR_MODE_COUNT, S::FULL); s.extraParagraphSpacing = doc["extraParagraphSpacing"] | (uint8_t)1; s.textAntiAliasing = doc["textAntiAliasing"] | (uint8_t)1; s.shortPwrBtn = clamp(doc["shortPwrBtn"] | (uint8_t)S::IGNORE, S::SHORT_PWRBTN_COUNT, S::IGNORE); s.orientation = clamp(doc["orientation"] | (uint8_t)S::PORTRAIT, S::ORIENTATION_COUNT, S::PORTRAIT); s.sideButtonLayout = clamp(doc["sideButtonLayout"] | (uint8_t)S::PREV_NEXT, S::SIDE_BUTTON_LAYOUT_COUNT, S::PREV_NEXT); s.frontButtonBack = clamp(doc["frontButtonBack"] | (uint8_t)S::FRONT_HW_BACK, S::FRONT_BUTTON_HARDWARE_COUNT, S::FRONT_HW_BACK); s.frontButtonConfirm = clamp(doc["frontButtonConfirm"] | (uint8_t)S::FRONT_HW_CONFIRM, S::FRONT_BUTTON_HARDWARE_COUNT, S::FRONT_HW_CONFIRM); s.frontButtonLeft = clamp(doc["frontButtonLeft"] | (uint8_t)S::FRONT_HW_LEFT, S::FRONT_BUTTON_HARDWARE_COUNT, S::FRONT_HW_LEFT); s.frontButtonRight = clamp(doc["frontButtonRight"] | (uint8_t)S::FRONT_HW_RIGHT, S::FRONT_BUTTON_HARDWARE_COUNT, S::FRONT_HW_RIGHT); CrossPointSettings::validateFrontButtonMapping(s); s.fontFamily = clamp(doc["fontFamily"] | (uint8_t)S::BOOKERLY, S::FONT_FAMILY_COUNT, S::BOOKERLY); s.fontSize = clamp(doc["fontSize"] | (uint8_t)S::MEDIUM, S::FONT_SIZE_COUNT, S::MEDIUM); s.lineSpacing = clamp(doc["lineSpacing"] | (uint8_t)S::NORMAL, S::LINE_COMPRESSION_COUNT, S::NORMAL); s.paragraphAlignment = clamp(doc["paragraphAlignment"] | (uint8_t)S::JUSTIFIED, S::PARAGRAPH_ALIGNMENT_COUNT, S::JUSTIFIED); s.sleepTimeout = clamp(doc["sleepTimeout"] | (uint8_t)S::SLEEP_10_MIN, S::SLEEP_TIMEOUT_COUNT, S::SLEEP_10_MIN); s.refreshFrequency = clamp(doc["refreshFrequency"] | (uint8_t)S::REFRESH_15, S::REFRESH_FREQUENCY_COUNT, S::REFRESH_15); s.screenMargin = doc["screenMargin"] | (uint8_t)5; s.hideBatteryPercentage = clamp(doc["hideBatteryPercentage"] | (uint8_t)S::HIDE_NEVER, S::HIDE_BATTERY_PERCENTAGE_COUNT, S::HIDE_NEVER); s.longPressChapterSkip = doc["longPressChapterSkip"] | (uint8_t)1; s.hyphenationEnabled = doc["hyphenationEnabled"] | (uint8_t)0; s.uiTheme = doc["uiTheme"] | (uint8_t)S::LYRA; s.fadingFix = doc["fadingFix"] | (uint8_t)0; s.embeddedStyle = doc["embeddedStyle"] | (uint8_t)1; const char* url = doc["opdsServerUrl"] | ""; strncpy(s.opdsServerUrl, url, sizeof(s.opdsServerUrl) - 1); s.opdsServerUrl[sizeof(s.opdsServerUrl) - 1] = '\0'; const char* user = doc["opdsUsername"] | ""; strncpy(s.opdsUsername, user, sizeof(s.opdsUsername) - 1); s.opdsUsername[sizeof(s.opdsUsername) - 1] = '\0'; bool passOk = false; std::string pass = obfuscation::deobfuscateFromBase64(doc["opdsPassword_obf"] | "", &passOk); if (!passOk || pass.empty()) { pass = doc["opdsPassword"] | ""; if (!pass.empty() && needsResave) *needsResave = true; } strncpy(s.opdsPassword, pass.c_str(), sizeof(s.opdsPassword) - 1); s.opdsPassword[sizeof(s.opdsPassword) - 1] = '\0'; LOG_DBG("CPS", "Settings loaded from file"); return true; } // ---- KOReaderCredentialStore ---- bool JsonSettingsIO::saveKOReader(const KOReaderCredentialStore& store, const char* path) { JsonDocument doc; doc["username"] = store.getUsername(); doc["password_obf"] = obfuscation::obfuscateToBase64(store.getPassword()); doc["serverUrl"] = store.getServerUrl(); doc["matchMethod"] = static_cast(store.getMatchMethod()); String json; serializeJson(doc, json); return Storage.writeFile(path, json); } bool JsonSettingsIO::loadKOReader(KOReaderCredentialStore& store, const char* json, bool* needsResave) { if (needsResave) *needsResave = false; JsonDocument doc; auto error = deserializeJson(doc, json); if (error) { LOG_ERR("KRS", "JSON parse error: %s", error.c_str()); return false; } store.username = doc["username"] | std::string(""); bool ok = false; store.password = obfuscation::deobfuscateFromBase64(doc["password_obf"] | "", &ok); if (!ok || store.password.empty()) { store.password = doc["password"] | std::string(""); if (!store.password.empty() && needsResave) *needsResave = true; } store.serverUrl = doc["serverUrl"] | std::string(""); uint8_t method = doc["matchMethod"] | (uint8_t)0; store.matchMethod = static_cast(method); LOG_DBG("KRS", "Loaded KOReader credentials for user: %s", store.username.c_str()); return true; } // ---- WifiCredentialStore ---- bool JsonSettingsIO::saveWifi(const WifiCredentialStore& store, const char* path) { JsonDocument doc; doc["lastConnectedSsid"] = store.getLastConnectedSsid(); JsonArray arr = doc["credentials"].to(); for (const auto& cred : store.getCredentials()) { JsonObject obj = arr.add(); obj["ssid"] = cred.ssid; obj["password_obf"] = obfuscation::obfuscateToBase64(cred.password); } String json; serializeJson(doc, json); return Storage.writeFile(path, json); } bool JsonSettingsIO::loadWifi(WifiCredentialStore& store, const char* json, bool* needsResave) { if (needsResave) *needsResave = false; JsonDocument doc; auto error = deserializeJson(doc, json); if (error) { LOG_ERR("WCS", "JSON parse error: %s", error.c_str()); return false; } store.lastConnectedSsid = doc["lastConnectedSsid"] | std::string(""); store.credentials.clear(); JsonArray arr = doc["credentials"].as(); for (JsonObject obj : arr) { if (store.credentials.size() >= store.MAX_NETWORKS) break; WifiCredential cred; cred.ssid = obj["ssid"] | std::string(""); bool ok = false; cred.password = obfuscation::deobfuscateFromBase64(obj["password_obf"] | "", &ok); if (!ok || cred.password.empty()) { cred.password = obj["password"] | std::string(""); if (!cred.password.empty() && needsResave) *needsResave = true; } store.credentials.push_back(cred); } LOG_DBG("WCS", "Loaded %zu WiFi credentials from file", store.credentials.size()); return true; } // ---- RecentBooksStore ---- bool JsonSettingsIO::saveRecentBooks(const RecentBooksStore& store, const char* path) { JsonDocument doc; JsonArray arr = doc["books"].to(); for (const auto& book : store.getBooks()) { JsonObject obj = arr.add(); obj["path"] = book.path; obj["title"] = book.title; obj["author"] = book.author; obj["coverBmpPath"] = book.coverBmpPath; } String json; serializeJson(doc, json); return Storage.writeFile(path, json); } bool JsonSettingsIO::loadRecentBooks(RecentBooksStore& store, const char* json) { JsonDocument doc; auto error = deserializeJson(doc, json); if (error) { LOG_ERR("RBS", "JSON parse error: %s", error.c_str()); return false; } store.recentBooks.clear(); JsonArray arr = doc["books"].as(); for (JsonObject obj : arr) { if (store.getCount() >= 10) break; RecentBook book; book.path = obj["path"] | std::string(""); book.title = obj["title"] | std::string(""); book.author = obj["author"] | std::string(""); book.coverBmpPath = obj["coverBmpPath"] | std::string(""); store.recentBooks.push_back(book); } LOG_DBG("RBS", "Recent books loaded from file (%d entries)", store.getCount()); return true; }