-
-
Save mallomar/67349ac3c88e1950754ba39a4913c029 to your computer and use it in GitHub Desktop.
| -- 2-unified-autosync.lua - KOReader Auto Sync Patch | |
| -- Place in /storage/emulated/0/koreader/patches/ | |
| -- | |
| -- Features: | |
| -- - Syncs Highlightsync, Statistics, and Vocabulary Builder automatically | |
| -- - Robust Highlightsync detection with fallback (from v2.5) | |
| -- - Global cooldown prevents double-syncing (from v2.5) | |
| -- - Stable Vocabulary sync (from v3.2) | |
| -- - Smart reload detection prevents infinite loops | |
| -- - Configurable triggers (open, close, suspend) | |
| -- | |
| -- Version: 3.3 (Stable + Complete) | |
| local log_file = "/storage/emulated/0/koreader/settings/unified-autosync-debug.txt" | |
| local function log(msg) | |
| local file = io.open(log_file, "a") | |
| if file then | |
| file:write(os.date("%Y-%m-%d %H:%M:%S") .. " - " .. msg .. "\n") | |
| file:close() | |
| end | |
| end | |
| log("========== UNIFIED AUTO SYNC PATCH LOADING (v3.3) ==========") | |
| local UIManager = require("ui/uimanager") | |
| local InfoMessage = require("ui/widget/infomessage") | |
| local ReaderUI = require("apps/reader/readerui") | |
| local ReaderMenu = require("apps/reader/modules/readermenu") | |
| -- --- STATE TRACKING (from v2.5) --- | |
| local sync_state = { | |
| is_syncing = false, | |
| last_sync_time = 0, | |
| cooldown_seconds = 30, -- Global cooldown for ALL sync types | |
| in_highlightsync_reload = false, | |
| reload_timeout = nil, | |
| } | |
| -- Check if enough time has passed since last sync (applies to ALL sync types) | |
| local function canSync() | |
| -- Check if we're in a Highlightsync-triggered reload | |
| if sync_state.in_highlightsync_reload then | |
| log("Inside Highlightsync reload cycle, skipping sync") | |
| return false | |
| end | |
| local current_time = os.time() | |
| local time_since_last = current_time - sync_state.last_sync_time | |
| if sync_state.is_syncing then | |
| log("Sync already in progress, skipping") | |
| return false | |
| end | |
| if time_since_last < sync_state.cooldown_seconds then | |
| log(string.format("Cooldown active (%ds remaining), skipping", | |
| sync_state.cooldown_seconds - time_since_last)) | |
| return false | |
| end | |
| return true | |
| end | |
| local function showMessage(text, timeout) | |
| pcall(function() | |
| UIManager:show(InfoMessage:new{ text = text, timeout = timeout or 3 }) | |
| end) | |
| end | |
| -- --- PLUGIN CONFIGURATION CHECKS --- | |
| local function isStatsConfigured() | |
| local settings = G_reader_settings:readSetting("statistics") or {} | |
| return settings.sync_server ~= nil | |
| end | |
| local function isVocabConfigured() | |
| local settings = G_reader_settings:readSetting("vocabulary_builder") or {} | |
| return settings.server ~= nil | |
| end | |
| local function isHighlightConfigured() | |
| local settings = G_reader_settings:readSetting("highlight_sync") or {} | |
| return settings.sync_server ~= nil | |
| end | |
| -- --- SYNC FUNCTIONS --- | |
| local function syncStatistics(immediate) | |
| log("Attempting to sync Statistics...") | |
| if not isStatsConfigured() then | |
| log("Statistics not configured") | |
| return false | |
| end | |
| local sync_func = function() | |
| if ReaderUI.instance and ReaderUI.instance.statistics then | |
| local success = pcall(function() | |
| ReaderUI.instance.statistics:onSyncBookStats(true) | |
| end) | |
| if success then | |
| log("✓ Statistics sync completed") | |
| else | |
| log("✗ Statistics sync failed") | |
| end | |
| end | |
| end | |
| if immediate then | |
| sync_func() | |
| else | |
| UIManager:scheduleIn(0.1, sync_func) | |
| end | |
| log("Statistics sync " .. (immediate and "executed" or "scheduled")) | |
| return true | |
| end | |
| local function syncVocabularyBuilder(immediate) | |
| log("Attempting to sync Vocabulary Builder...") | |
| if not isVocabConfigured() then | |
| log("VocabBuilder not configured") | |
| return false | |
| end | |
| -- Always schedule (safer than immediate execution) | |
| UIManager:scheduleIn(0.2, function() | |
| log("VocabBuilder: Starting...") | |
| local success = pcall(function() | |
| local SyncService = require("frontend/apps/cloudstorage/syncservice") | |
| local DataStorage = require("datastorage") | |
| -- Load DB using loadfile (v3.2 approach - more stable) | |
| local vocab_db_path = "plugins/vocabbuilder.koplugin/db.lua" | |
| local chunk = loadfile(vocab_db_path) | |
| if not chunk then | |
| log("✗ VocabBuilder: Could not load db.lua") | |
| return | |
| end | |
| local ok, VocabDB = pcall(chunk) | |
| if not ok or not VocabDB then | |
| log("✗ VocabBuilder: Failed to execute db.lua") | |
| return | |
| end | |
| -- Save pending words (v3.2's safer approach) | |
| if ReaderUI.instance and ReaderUI.instance.vocabulary_builder then | |
| local vb = ReaderUI.instance.vocabulary_builder | |
| -- v3.2 checks for .widget, v2.5 didn't - this prevents crashes | |
| if vb.widget and vb.widget.item_table then | |
| pcall(VocabDB.batchUpdateItems, VocabDB, vb.widget.item_table) | |
| log("VocabBuilder: Saved pending words") | |
| elseif vb.item_table then | |
| -- Fallback for different widget structure | |
| pcall(VocabDB.batchUpdateItems, VocabDB, vb.item_table) | |
| log("VocabBuilder: Saved pending words (fallback)") | |
| end | |
| end | |
| local settings = G_reader_settings:readSetting("vocabulary_builder") or {} | |
| local db_path = DataStorage:getSettingsDir() .. "/vocabulary_builder.sqlite3" | |
| -- Sync without blocking | |
| SyncService.sync(settings.server, db_path, VocabDB.onSync, true) | |
| log("✓ Vocabulary sync completed") | |
| end) | |
| if not success then | |
| log("✗ Vocabulary sync failed") | |
| end | |
| end) | |
| log("Vocabulary sync scheduled") | |
| return true | |
| end | |
| local function syncHighlightsync(immediate) | |
| log("Attempting to sync Highlightsync...") | |
| if G_reader_settings:readSetting("unified_autosync_include_highlights") == false then | |
| log("Highlightsync disabled by user") | |
| return false | |
| end | |
| if not isHighlightConfigured() then | |
| log("Highlightsync not configured") | |
| return false | |
| end | |
| if not ReaderUI.instance then | |
| log("ReaderUI not ready") | |
| return false | |
| end | |
| -- v2.5's ROBUST detection - try THREE variants | |
| local highlightsync = ReaderUI.instance.highlight_sync or | |
| ReaderUI.instance.highlightsync or | |
| ReaderUI.instance.Highlightsync | |
| if not (highlightsync and highlightsync.onSyncBookHighlights) then | |
| -- v2.5's FALLBACK - try loading plugin directly | |
| log("Highlightsync not in ReaderUI, trying direct load...") | |
| local ok, plugin = pcall(require, "plugins/highlightsync.koplugin/main") | |
| if ok and plugin and plugin.onSyncBookHighlights then | |
| highlightsync = plugin | |
| log("Highlightsync loaded directly") | |
| else | |
| log("✗ Highlightsync plugin not found") | |
| return false | |
| end | |
| end | |
| -- Set reload flag BEFORE triggering sync | |
| sync_state.in_highlightsync_reload = true | |
| log("Reload flag SET - will ignore next close/open events") | |
| -- Clear any existing timeout | |
| if sync_state.reload_timeout then | |
| UIManager:unschedule(sync_state.reload_timeout) | |
| end | |
| -- Schedule flag clearing (safety timeout) | |
| sync_state.reload_timeout = function() | |
| sync_state.in_highlightsync_reload = false | |
| sync_state.reload_timeout = nil | |
| log("Reload flag CLEARED (timeout)") | |
| end | |
| UIManager:scheduleIn(8, sync_state.reload_timeout) | |
| local sync_func = function() | |
| local success = pcall(function() | |
| highlightsync:onSyncBookHighlights(true) | |
| end) | |
| if success then | |
| log("✓ Highlightsync completed") | |
| -- Clear flag after successful sync | |
| UIManager:scheduleIn(3, function() | |
| if sync_state.in_highlightsync_reload then | |
| sync_state.in_highlightsync_reload = false | |
| log("Reload flag CLEARED (after successful sync)") | |
| end | |
| end) | |
| else | |
| log("✗ Highlightsync failed") | |
| sync_state.in_highlightsync_reload = false | |
| log("Reload flag CLEARED (sync failed)") | |
| end | |
| end | |
| if immediate then | |
| sync_func() | |
| else | |
| UIManager:scheduleIn(0.3, sync_func) | |
| end | |
| log("Highlightsync " .. (immediate and "executed" or "scheduled") .. " with reload protection") | |
| return true | |
| end | |
| -- --- MAIN SYNC FUNCTION --- | |
| local function performUnifiedSync(sync_type) | |
| log("========== UNIFIED SYNC START (" .. sync_type .. ") ==========") | |
| if not G_reader_settings:isTrue("unified_autosync_enabled") then | |
| log("Auto sync disabled") | |
| return | |
| end | |
| -- GLOBAL cooldown check (applies to ALL sync types) | |
| if not canSync() then | |
| return | |
| end | |
| -- Mark sync as in progress | |
| sync_state.is_syncing = true | |
| sync_state.last_sync_time = os.time() | |
| -- Determine if we need immediate execution | |
| local immediate = (sync_type == "on_close" or sync_type == "on_suspend" or sync_type == "manual") | |
| log("Sync mode: " .. (immediate and "IMMEDIATE" or "SCHEDULED")) | |
| local sync_scheduled = {} | |
| -- Sync all plugins | |
| if syncHighlightsync(immediate) then | |
| table.insert(sync_scheduled, "Highlights") | |
| end | |
| if syncStatistics(immediate) then | |
| table.insert(sync_scheduled, "Statistics") | |
| end | |
| if syncVocabularyBuilder(immediate) then | |
| table.insert(sync_scheduled, "Vocabulary") | |
| end | |
| if #sync_scheduled > 0 then | |
| log("Synced: " .. table.concat(sync_scheduled, ", ")) | |
| G_reader_settings:saveSetting("unified_autosync_last_sync", os.date("%H:%M:%S")) | |
| if G_reader_settings:readSetting("unified_autosync_show_notifications") then | |
| local notification_text = immediate and "Synced: " or "Syncing: " | |
| UIManager:scheduleIn(0.5, function() | |
| showMessage(notification_text .. table.concat(sync_scheduled, ", "), 2) | |
| end) | |
| end | |
| -- Mark sync as complete | |
| if immediate then | |
| sync_state.is_syncing = false | |
| log("Sync cycle completed (immediate)") | |
| else | |
| UIManager:scheduleIn(5, function() | |
| sync_state.is_syncing = false | |
| log("Sync cycle completed (scheduled)") | |
| end) | |
| end | |
| else | |
| sync_state.is_syncing = false | |
| log("No plugins to sync") | |
| end | |
| log("========== UNIFIED SYNC " .. (immediate and "EXECUTED" or "SCHEDULED") .. " ==========") | |
| end | |
| -- --- MENU --- | |
| local function createMenuItem() | |
| return { | |
| text = "Unified Auto Sync", | |
| sub_item_table = { | |
| { | |
| text = "Enable Auto Sync", | |
| checked_func = function() | |
| return G_reader_settings:isTrue("unified_autosync_enabled") | |
| end, | |
| callback = function() | |
| local val = not G_reader_settings:isTrue("unified_autosync_enabled") | |
| G_reader_settings:saveSetting("unified_autosync_enabled", val) | |
| showMessage("Auto-sync " .. (val and "enabled" or "disabled")) | |
| end, | |
| separator = true, | |
| }, | |
| { | |
| text = "Sync Now", | |
| callback = function() | |
| -- Clear cooldown for manual sync | |
| sync_state.last_sync_time = 0 | |
| sync_state.is_syncing = false | |
| showMessage("Starting sync...", 1) | |
| performUnifiedSync("manual") | |
| end | |
| }, | |
| { | |
| text = "Clear Cooldown", | |
| keep_menu_open = true, | |
| callback = function() | |
| sync_state.last_sync_time = 0 | |
| sync_state.is_syncing = false | |
| showMessage("Cooldown cleared - ready to sync") | |
| end, | |
| separator = true, | |
| }, | |
| { | |
| text = "Sync on Open", | |
| checked_func = function() | |
| return G_reader_settings:readSetting("unified_autosync_on_open") ~= false | |
| end, | |
| callback = function() | |
| local val = G_reader_settings:readSetting("unified_autosync_on_open") ~= false | |
| G_reader_settings:saveSetting("unified_autosync_on_open", not val) | |
| showMessage("Sync on open " .. (not val and "enabled" or "disabled")) | |
| end, | |
| }, | |
| { | |
| text = "Open Delay", | |
| keep_menu_open = true, | |
| sub_item_table = { | |
| { | |
| text = "1 second", | |
| checked_func = function() | |
| return G_reader_settings:readSetting("unified_autosync_open_delay") == 1 | |
| end, | |
| callback = function() | |
| G_reader_settings:saveSetting("unified_autosync_open_delay", 1) | |
| showMessage("Open delay: 1 second") | |
| end, | |
| }, | |
| { | |
| text = "3 seconds (default)", | |
| checked_func = function() | |
| local delay = G_reader_settings:readSetting("unified_autosync_open_delay") | |
| return delay == nil or delay == 3 | |
| end, | |
| callback = function() | |
| G_reader_settings:saveSetting("unified_autosync_open_delay", 3) | |
| showMessage("Open delay: 3 seconds") | |
| end, | |
| }, | |
| { | |
| text = "5 seconds", | |
| checked_func = function() | |
| return G_reader_settings:readSetting("unified_autosync_open_delay") == 5 | |
| end, | |
| callback = function() | |
| G_reader_settings:saveSetting("unified_autosync_open_delay", 5) | |
| showMessage("Open delay: 5 seconds") | |
| end, | |
| }, | |
| }, | |
| }, | |
| { | |
| text = "Sync on Close", | |
| checked_func = function() | |
| return G_reader_settings:readSetting("unified_autosync_on_close") ~= false | |
| end, | |
| callback = function() | |
| local val = G_reader_settings:readSetting("unified_autosync_on_close") ~= false | |
| G_reader_settings:saveSetting("unified_autosync_on_close", not val) | |
| showMessage("Sync on close " .. (not val and "enabled" or "disabled")) | |
| end, | |
| }, | |
| { | |
| text = "Sync on Suspend", | |
| checked_func = function() | |
| return G_reader_settings:readSetting("unified_autosync_on_suspend") or false | |
| end, | |
| callback = function() | |
| local val = G_reader_settings:readSetting("unified_autosync_on_suspend") or false | |
| G_reader_settings:saveSetting("unified_autosync_on_suspend", not val) | |
| showMessage("Sync on suspend " .. (not val and "enabled" or "disabled")) | |
| end, | |
| separator = true, | |
| }, | |
| { | |
| text = "Cooldown Duration", | |
| keep_menu_open = true, | |
| sub_item_table = { | |
| { | |
| text = "15 seconds", | |
| checked_func = function() return sync_state.cooldown_seconds == 15 end, | |
| callback = function() | |
| sync_state.cooldown_seconds = 15 | |
| showMessage("Cooldown: 15 seconds") | |
| end, | |
| }, | |
| { | |
| text = "30 seconds (default)", | |
| checked_func = function() return sync_state.cooldown_seconds == 30 end, | |
| callback = function() | |
| sync_state.cooldown_seconds = 30 | |
| showMessage("Cooldown: 30 seconds") | |
| end, | |
| }, | |
| { | |
| text = "60 seconds", | |
| checked_func = function() return sync_state.cooldown_seconds == 60 end, | |
| callback = function() | |
| sync_state.cooldown_seconds = 60 | |
| showMessage("Cooldown: 60 seconds") | |
| end, | |
| }, | |
| { | |
| text = "2 minutes", | |
| checked_func = function() return sync_state.cooldown_seconds == 120 end, | |
| callback = function() | |
| sync_state.cooldown_seconds = 120 | |
| showMessage("Cooldown: 2 minutes") | |
| end, | |
| }, | |
| }, | |
| }, | |
| { | |
| text = "Include Highlightsync", | |
| checked_func = function() | |
| return G_reader_settings:readSetting("unified_autosync_include_highlights") ~= false | |
| end, | |
| callback = function() | |
| local val = G_reader_settings:readSetting("unified_autosync_include_highlights") ~= false | |
| G_reader_settings:saveSetting("unified_autosync_include_highlights", not val) | |
| showMessage("Highlightsync " .. (not val and "enabled" or "disabled")) | |
| end, | |
| help_text = "Enable/disable automatic Highlightsync. Uses smart reload detection.", | |
| separator = true, | |
| }, | |
| { | |
| text = "Show Notifications", | |
| checked_func = function() | |
| return G_reader_settings:readSetting("unified_autosync_show_notifications") or false | |
| end, | |
| callback = function() | |
| local val = G_reader_settings:readSetting("unified_autosync_show_notifications") or false | |
| G_reader_settings:saveSetting("unified_autosync_show_notifications", not val) | |
| showMessage("Notifications " .. (not val and "enabled" or "disabled")) | |
| end, | |
| }, | |
| { | |
| text = "Show Startup Message", | |
| checked_func = function() | |
| return G_reader_settings:readSetting("unified_autosync_show_startup_message") or false | |
| end, | |
| callback = function() | |
| local val = G_reader_settings:readSetting("unified_autosync_show_startup_message") or false | |
| G_reader_settings:saveSetting("unified_autosync_show_startup_message", not val) | |
| showMessage("Startup message " .. (not val and "enabled" or "disabled")) | |
| end, | |
| help_text = "Show a notification when the patch loads (for debugging)", | |
| separator = true, | |
| }, | |
| { | |
| text = "View Status", | |
| keep_menu_open = true, | |
| callback = function() | |
| local enabled = G_reader_settings:isTrue("unified_autosync_enabled") and "Enabled" or "Disabled" | |
| local last_sync = G_reader_settings:readSetting("unified_autosync_last_sync") or "Never" | |
| local on_open = G_reader_settings:readSetting("unified_autosync_on_open") ~= false | |
| local on_close = G_reader_settings:readSetting("unified_autosync_on_close") ~= false | |
| local on_suspend = G_reader_settings:readSetting("unified_autosync_on_suspend") or false | |
| local stats_status = isStatsConfigured() and "✓" or "✗" | |
| local vocab_status = isVocabConfigured() and "✓" or "✗" | |
| local hl_enabled = G_reader_settings:readSetting("unified_autosync_include_highlights") ~= false | |
| local hl_status = hl_enabled and (isHighlightConfigured() and "✓" or "✗") or "⊘" | |
| local sync_status = sync_state.is_syncing and "Syncing..." or "Idle" | |
| if sync_state.in_highlightsync_reload then | |
| sync_status = "In reload (protected)" | |
| end | |
| local time_since_last = os.time() - sync_state.last_sync_time | |
| local cooldown_remaining = math.max(0, sync_state.cooldown_seconds - time_since_last) | |
| local cooldown_text = cooldown_remaining > 0 | |
| and string.format("cooldown: %ds", cooldown_remaining) | |
| or "ready" | |
| local status = string.format( | |
| "Status: %s\nState: %s (%s)\nLast sync: %s\n\nTriggers:\n• Open: %s\n• Close: %s\n• Suspend: %s\n\nPlugins:\n• Highlights: %s\n• Statistics: %s\n• Vocabulary: %s", | |
| enabled, sync_status, cooldown_text, last_sync, | |
| on_open and "Yes" or "No", | |
| on_close and "Yes" or "No", | |
| on_suspend and "Yes" or "No", | |
| hl_status, stats_status, vocab_status | |
| ) | |
| showMessage(status, 8) | |
| end | |
| } | |
| } | |
| } | |
| end | |
| -- --- MENU HOOK --- | |
| log("Attempting to hook ReaderMenu...") | |
| local menu_hook_success, menu_hook_error = pcall(function() | |
| log("ReaderMenu module available") | |
| local original_setUpdateItemTable = ReaderMenu.setUpdateItemTable | |
| if not original_setUpdateItemTable then | |
| log("WARNING: ReaderMenu.setUpdateItemTable not found") | |
| return | |
| end | |
| ReaderMenu.setUpdateItemTable = function(self) | |
| log("setUpdateItemTable called") | |
| -- Add our menu item BEFORE calling original function (critical!) | |
| local add_success, add_error = pcall(function() | |
| -- Try to get the menu order to insert into proper section | |
| local ok_order, reader_order = pcall(require, "ui/elements/reader_menu_order") | |
| self.menu_items = self.menu_items or {} | |
| self.menu_items.unified_autosync = createMenuItem() | |
| log("Menu item created and added to menu_items") | |
| -- Add to menu order (try tools, then more_tools as fallback) | |
| if ok_order and reader_order then | |
| if reader_order.tools then | |
| table.insert(reader_order.tools, "unified_autosync") | |
| log("Added to reader_order.tools") | |
| elseif reader_order.more_tools then | |
| table.insert(reader_order.more_tools, "unified_autosync") | |
| log("Added to reader_order.more_tools") | |
| end | |
| else | |
| -- Fallback: add directly to self.menu_order | |
| self.menu_order = self.menu_order or {} | |
| self.menu_order.tools = self.menu_order.tools or {} | |
| table.insert(self.menu_order.tools, "unified_autosync") | |
| log("Added to self.menu_order.tools (fallback)") | |
| end | |
| end) | |
| if not add_success then | |
| log("ERROR adding menu item: " .. tostring(add_error)) | |
| end | |
| -- NOW call the original function | |
| original_setUpdateItemTable(self) | |
| log("Original setUpdateItemTable called") | |
| end | |
| log("ReaderMenu hook installed") | |
| end) | |
| if not menu_hook_success then | |
| log("ERROR hooking ReaderMenu: " .. tostring(menu_hook_error)) | |
| end | |
| -- --- EVENT HOOKS --- | |
| local original_onReaderReady = ReaderUI.onReaderReady | |
| ReaderUI.onReaderReady = function(self, ...) | |
| log("onReaderReady triggered") | |
| if original_onReaderReady then | |
| original_onReaderReady(self, ...) | |
| end | |
| if G_reader_settings:isTrue("unified_autosync_enabled") and | |
| G_reader_settings:readSetting("unified_autosync_on_open") ~= false then | |
| local delay = G_reader_settings:readSetting("unified_autosync_open_delay") or 3 | |
| log(string.format("Scheduling sync on document open (%ds delay)", delay)) | |
| UIManager:scheduleIn(delay, function() | |
| performUnifiedSync("on_open") | |
| end) | |
| else | |
| log("Sync on open disabled") | |
| end | |
| end | |
| local original_onCloseDocument = ReaderUI.onCloseDocument | |
| ReaderUI.onCloseDocument = function(self, ...) | |
| log("onCloseDocument triggered") | |
| if G_reader_settings:isTrue("unified_autosync_enabled") and | |
| G_reader_settings:readSetting("unified_autosync_on_close") ~= false then | |
| log("Triggering sync on document close") | |
| performUnifiedSync("on_close") | |
| else | |
| log("Sync on close disabled") | |
| end | |
| if original_onCloseDocument then | |
| original_onCloseDocument(self, ...) | |
| end | |
| end | |
| local original_onSuspend = ReaderUI.onSuspend | |
| ReaderUI.onSuspend = function(self, ...) | |
| log("onSuspend triggered") | |
| if G_reader_settings:isTrue("unified_autosync_enabled") and | |
| G_reader_settings:readSetting("unified_autosync_on_suspend") then | |
| log("Triggering sync on suspend") | |
| performUnifiedSync("on_suspend") | |
| else | |
| log("Sync on suspend disabled") | |
| end | |
| if original_onSuspend then | |
| original_onSuspend(self, ...) | |
| end | |
| end | |
| -- --- SET DEFAULTS --- | |
| if G_reader_settings:readSetting("unified_autosync_enabled") == nil then | |
| log("First run: setting defaults") | |
| G_reader_settings:saveSetting("unified_autosync_enabled", true) | |
| G_reader_settings:saveSetting("unified_autosync_on_open", true) | |
| G_reader_settings:saveSetting("unified_autosync_on_close", true) | |
| G_reader_settings:saveSetting("unified_autosync_on_suspend", false) | |
| G_reader_settings:saveSetting("unified_autosync_show_notifications", false) | |
| G_reader_settings:saveSetting("unified_autosync_show_startup_message", false) | |
| G_reader_settings:saveSetting("unified_autosync_open_delay", 3) | |
| G_reader_settings:saveSetting("unified_autosync_include_highlights", true) | |
| end | |
| log("========== UNIFIED AUTO SYNC PATCH READY (v3.3) ==========") | |
| -- Show a startup notification if enabled (for debugging) | |
| if G_reader_settings:readSetting("unified_autosync_show_startup_message") then | |
| log("Showing startup notification") | |
| UIManager:scheduleIn(2, function() | |
| showMessage("Unified Auto Sync loaded ✓", 2) | |
| end) | |
| end | |
| return true |
Obrigado pelo cuidado em responder no meu idioma... Me refiro a tela de "Histórico" onde aparecem os livros que estou lendo. Queria deixar a mesma entre o kindle e o meu koreader web. Outra questão, coloquei o seu patche e ele parece bem estruturado e funcional, porém o meu aparece que os "destaques" aparecem como "Não configurados".
O Histórico que me refiro, é a tela abaixo:
Opa, Eduardo!
Obrigado pelo feedback! Fico feliz que o patch parece útil. Deixa eu esclarecer esses dois pontos:
Sobre o "Histórico" (a lista de livros recentes):
Essa tela de "Histórico" (a lista de livros abertos recentemente) é uma função do próprio KOReader. Nenhum plugin sincroniza essa lista, nem o "KOSync (Progress Sync)" que vem com o KOReader, nem o meu patch.
O motivo é que os caminhos dos arquivos são diferentes em cada aparelho (no seu Kindle é /mnt/us/..., no seu Raspberry Pi é outro). Se ele sincronizasse a lista, um aparelho não conseguiria abrir os livros do outro. O que você está vendo (listas de histórico diferentes) é o comportamento normal e esperado.
Sobre "Highlights: Não configurados":
Isso é o meu patch funcionando corretamente! Ele está te avisando que o outro plugin, o "HighlightSync" (o plugin original dos highlights), não está configurado.
(Primeiro, como o HighlightSync é um plugin da comunidade, você precisa ter certeza de que instalou ele manualmente, já que ele não vem com o KOReader.)
Depois de instalado, para arrumar o aviso, você precisa ir no menu de plugins, achar o "HighlightSync" (o de highlights, não o meu patch) e configurar o seu servidor WebDAV dentro dele. Assim que você salvar, o meu patch vai achar essa configuração e a mensagem "Não configurados" vai sumir.
Muito obrigado pelo seu retorno.
Hi @mallomar , I love your patch. Is there a way to make it sync daily? For instance the sync clock resets at midnight, and the next time you open koreader it syncs. My Kindle is quite slow, so syncing on every open and close takes a lot of time.
Would adding
text = "Cooldown Duration",
keep_menu_open = true,
sub_item_table = {
{
text = "24 hours",
checked_func = function() return sync_state.cooldown_hours == 24 end,
callback = function()
sync_state.cooldown_hours = 24
showMessage("Cooldown: 24 hours")
end,
},
},To the cooldown table work?
Would adding
text = "Cooldown Duration", keep_menu_open = true, sub_item_table = { { text = "24 hours", checked_func = function() return sync_state.cooldown_hours == 24 end, callback = function() sync_state.cooldown_hours = 24 showMessage("Cooldown: 24 hours") end, }, },To the cooldown table work?
The version below should work for you:
-- 2-unified-autosync.lua - KOReader Auto Sync Patch
-- Place in /storage/emulated/0/koreader/patches/
--
-- Features:
-- - Syncs Statistics and Vocabulary Builder automatically (Highlightsync optional)
-- - Configurable cooldown: 15s, 30s, 1min, 2min, 1h, 6h, or 24h (daily)
-- - Robust Highlightsync detection with fallback (disabled by default for mobile hotspot)
-- - Global cooldown prevents double-syncing
-- - Stable Vocabulary sync (skipped on close/suspend for slow connections)
-- - Smart reload detection prevents infinite loops
-- - Configurable triggers (open, close, suspend)
--
-- MOBILE HOTSPOT SAFE: Highlightsync disabled by default (enable in menu if on WiFi)
--
-- Version: 3.7 (Daily Sync Support)
local log_file = "/storage/emulated/0/koreader/settings/unified-autosync-debug.txt"
local function log(msg)
local file = io.open(log_file, "a")
if file then
file:write(os.date("%Y-%m-%d %H:%M:%S") .. " - " .. msg .. "\n")
file:close()
end
end
log("========== UNIFIED AUTO SYNC PATCH LOADING (v3.7) ==========")
local UIManager = require("ui/uimanager")
local InfoMessage = require("ui/widget/infomessage")
local ReaderUI = require("apps/reader/readerui")
local ReaderMenu = require("apps/reader/modules/readermenu")
-- --- STATE TRACKING ---
local sync_state = {
is_syncing = false,
last_sync_time = 0,
cooldown_seconds = 30, -- Default: 30s. Can be changed to 86400 for daily sync
in_highlightsync_reload = false,
reload_timeout = nil,
}
-- Check if enough time has passed since last sync
local function canSync()
if sync_state.in_highlightsync_reload then
log("Inside Highlightsync reload cycle, skipping sync")
return false
end
local current_time = os.time()
local time_since_last = current_time - sync_state.last_sync_time
if sync_state.is_syncing then
log("Sync already in progress, skipping")
return false
end
if time_since_last < sync_state.cooldown_seconds then
local remaining = sync_state.cooldown_seconds - time_since_last
if remaining >= 3600 then
log(string.format("Cooldown active (%.1fh remaining), skipping", remaining / 3600))
elseif remaining >= 60 then
log(string.format("Cooldown active (%dm remaining), skipping", math.floor(remaining / 60)))
else
log(string.format("Cooldown active (%ds remaining), skipping", remaining))
end
return false
end
return true
end
local function showMessage(text, timeout)
pcall(function()
UIManager:show(InfoMessage:new{ text = text, timeout = timeout or 3 })
end)
end
-- --- PLUGIN CONFIGURATION CHECKS ---
local function isStatsConfigured()
local settings = G_reader_settings:readSetting("statistics") or {}
return settings.sync_server ~= nil
end
local function isVocabConfigured()
local settings = G_reader_settings:readSetting("vocabulary_builder") or {}
return settings.server ~= nil
end
local function isHighlightConfigured()
local settings = G_reader_settings:readSetting("highlight_sync") or {}
return settings.sync_server ~= nil
end
-- --- SYNC FUNCTIONS ---
local function syncStatistics(immediate)
log("Attempting to sync Statistics...")
if not isStatsConfigured() then
log("Statistics not configured")
return false
end
local sync_func = function()
if ReaderUI.instance and ReaderUI.instance.statistics then
local success = pcall(function()
ReaderUI.instance.statistics:onSyncBookStats(true)
end)
if success then
log("✓ Statistics sync completed")
else
log("✗ Statistics sync failed")
end
end
end
if immediate then
sync_func()
else
UIManager:scheduleIn(0.1, sync_func)
end
log("Statistics sync " .. (immediate and "executed" or "scheduled"))
return true
end
local function syncVocabularyBuilder(immediate)
log("Attempting to sync Vocabulary Builder...")
if not isVocabConfigured() then
log("VocabBuilder not configured")
return false
end
-- MOBILE HOTSPOT FIX: Skip VocabBuilder on immediate syncs (close/suspend)
if immediate then
log("VocabBuilder: Skipping on immediate sync (prevents crashes on slow connections)")
return false
end
UIManager:scheduleIn(0.2, function()
log("VocabBuilder: Starting...")
local success = pcall(function()
local SyncService = require("frontend/apps/cloudstorage/syncservice")
local DataStorage = require("datastorage")
local vocab_db_path = "plugins/vocabbuilder.koplugin/db.lua"
local chunk = loadfile(vocab_db_path)
if not chunk then
log("✗ VocabBuilder: Could not load db.lua")
return
end
local ok, VocabDB = pcall(chunk)
if not ok or not VocabDB then
log("✗ VocabBuilder: Failed to execute db.lua")
return
end
if ReaderUI.instance and ReaderUI.instance.vocabulary_builder then
local vb = ReaderUI.instance.vocabulary_builder
if vb.widget and vb.widget.item_table then
pcall(VocabDB.batchUpdateItems, VocabDB, vb.widget.item_table)
log("VocabBuilder: Saved pending words")
elseif vb.item_table then
pcall(VocabDB.batchUpdateItems, VocabDB, vb.item_table)
log("VocabBuilder: Saved pending words (fallback)")
end
end
local settings = G_reader_settings:readSetting("vocabulary_builder") or {}
local db_path = DataStorage:getSettingsDir() .. "/vocabulary_builder.sqlite3"
SyncService.sync(settings.server, db_path, VocabDB.onSync, true)
log("✓ Vocabulary sync completed")
end)
if not success then
log("✗ Vocabulary sync failed")
end
end)
log("Vocabulary sync scheduled")
return true
end
local function syncHighlightsync(immediate)
log("Attempting to sync Highlightsync...")
if G_reader_settings:readSetting("unified_autosync_include_highlights") == false then
log("Highlightsync disabled by user")
return false
end
if not isHighlightConfigured() then
log("Highlightsync not configured")
return false
end
if not ReaderUI.instance then
log("ReaderUI not ready")
return false
end
local highlightsync = ReaderUI.instance.highlight_sync or
ReaderUI.instance.highlightsync or
ReaderUI.instance.Highlightsync
if not (highlightsync and highlightsync.onSyncBookHighlights) then
log("Highlightsync not in ReaderUI, trying direct load...")
local ok, plugin = pcall(require, "plugins/highlightsync.koplugin/main")
if ok and plugin and plugin.onSyncBookHighlights then
highlightsync = plugin
log("Highlightsync loaded directly")
else
log("✗ Highlightsync plugin not found")
return false
end
end
sync_state.in_highlightsync_reload = true
log("Reload flag SET - will ignore next close/open events")
if sync_state.reload_timeout then
UIManager:unschedule(sync_state.reload_timeout)
end
sync_state.reload_timeout = function()
sync_state.in_highlightsync_reload = false
sync_state.reload_timeout = nil
log("Reload flag CLEARED (timeout)")
end
UIManager:scheduleIn(8, sync_state.reload_timeout)
local sync_func = function()
local success = pcall(function()
highlightsync:onSyncBookHighlights(true)
end)
if success then
log("✓ Highlightsync completed")
UIManager:scheduleIn(3, function()
if sync_state.in_highlightsync_reload then
sync_state.in_highlightsync_reload = false
log("Reload flag CLEARED (after successful sync)")
end
end)
else
log("✗ Highlightsync failed")
sync_state.in_highlightsync_reload = false
log("Reload flag CLEARED (sync failed)")
end
end
if immediate then
sync_func()
else
UIManager:scheduleIn(0.3, sync_func)
end
log("Highlightsync " .. (immediate and "executed" or "scheduled") .. " with reload protection")
return true
end
-- --- MAIN SYNC FUNCTION ---
local function performUnifiedSync(sync_type)
log("========== UNIFIED SYNC START (" .. sync_type .. ") ==========")
if not G_reader_settings:isTrue("unified_autosync_enabled") then
log("Auto sync disabled")
return
end
if not canSync() then
return
end
sync_state.is_syncing = true
sync_state.last_sync_time = os.time()
local immediate = (sync_type == "on_close" or sync_type == "on_suspend" or sync_type == "manual")
log("Sync mode: " .. (immediate and "IMMEDIATE" or "SCHEDULED"))
local sync_scheduled = {}
if syncHighlightsync(immediate) then
table.insert(sync_scheduled, "Highlights")
end
if syncStatistics(immediate) then
table.insert(sync_scheduled, "Statistics")
end
if syncVocabularyBuilder(immediate) then
table.insert(sync_scheduled, "Vocabulary")
end
if #sync_scheduled > 0 then
log("Synced: " .. table.concat(sync_scheduled, ", "))
G_reader_settings:saveSetting("unified_autosync_last_sync", os.date("%Y-%m-%d %H:%M:%S"))
if G_reader_settings:readSetting("unified_autosync_show_notifications") then
local notification_text = immediate and "Synced: " or "Syncing: "
UIManager:scheduleIn(0.5, function()
showMessage(notification_text .. table.concat(sync_scheduled, ", "), 2)
end)
end
if immediate then
sync_state.is_syncing = false
log("Sync cycle completed (immediate)")
else
UIManager:scheduleIn(5, function()
sync_state.is_syncing = false
log("Sync cycle completed (scheduled)")
end)
end
else
sync_state.is_syncing = false
log("No plugins to sync")
end
log("========== UNIFIED SYNC " .. (immediate and "EXECUTED" or "SCHEDULED") .. " ==========")
end
-- --- MENU ---
local function createMenuItem()
return {
text = "Unified Auto Sync",
sub_item_table = {
{
text = "Enable Auto Sync",
checked_func = function()
return G_reader_settings:isTrue("unified_autosync_enabled")
end,
callback = function()
local val = not G_reader_settings:isTrue("unified_autosync_enabled")
G_reader_settings:saveSetting("unified_autosync_enabled", val)
showMessage("Auto-sync " .. (val and "enabled" or "disabled"))
end,
separator = true,
},
{
text = "Sync Now",
callback = function()
sync_state.last_sync_time = 0
sync_state.is_syncing = false
showMessage("Starting sync...", 1)
performUnifiedSync("manual")
end
},
{
text = "Clear Cooldown",
keep_menu_open = true,
callback = function()
sync_state.last_sync_time = 0
sync_state.is_syncing = false
showMessage("Cooldown cleared - ready to sync")
end,
separator = true,
},
{
text = "Sync on Open",
checked_func = function()
return G_reader_settings:readSetting("unified_autosync_on_open") ~= false
end,
callback = function()
local val = G_reader_settings:readSetting("unified_autosync_on_open") ~= false
G_reader_settings:saveSetting("unified_autosync_on_open", not val)
showMessage("Sync on open " .. (not val and "enabled" or "disabled"))
end,
},
{
text = "Open Delay",
keep_menu_open = true,
sub_item_table = {
{
text = "1 second",
checked_func = function()
return G_reader_settings:readSetting("unified_autosync_open_delay") == 1
end,
callback = function()
G_reader_settings:saveSetting("unified_autosync_open_delay", 1)
showMessage("Open delay: 1 second")
end,
},
{
text = "3 seconds (default)",
checked_func = function()
local delay = G_reader_settings:readSetting("unified_autosync_open_delay")
return delay == nil or delay == 3
end,
callback = function()
G_reader_settings:saveSetting("unified_autosync_open_delay", 3)
showMessage("Open delay: 3 seconds")
end,
},
{
text = "5 seconds",
checked_func = function()
return G_reader_settings:readSetting("unified_autosync_open_delay") == 5
end,
callback = function()
G_reader_settings:saveSetting("unified_autosync_open_delay", 5)
showMessage("Open delay: 5 seconds")
end,
},
},
},
{
text = "Sync on Close",
checked_func = function()
return G_reader_settings:readSetting("unified_autosync_on_close") ~= false
end,
callback = function()
local val = G_reader_settings:readSetting("unified_autosync_on_close") ~= false
G_reader_settings:saveSetting("unified_autosync_on_close", not val)
showMessage("Sync on close " .. (not val and "enabled" or "disabled"))
end,
},
{
text = "Sync on Suspend",
checked_func = function()
return G_reader_settings:readSetting("unified_autosync_on_suspend") or false
end,
callback = function()
local val = G_reader_settings:readSetting("unified_autosync_on_suspend") or false
G_reader_settings:saveSetting("unified_autosync_on_suspend", not val)
showMessage("Sync on suspend " .. (not val and "enabled" or "disabled"))
end,
separator = true,
},
{
text = "Cooldown Duration",
keep_menu_open = true,
sub_item_table = {
{
text = "15 seconds",
checked_func = function() return sync_state.cooldown_seconds == 15 end,
callback = function()
sync_state.cooldown_seconds = 15
showMessage("Cooldown: 15 seconds")
end,
},
{
text = "30 seconds (default)",
checked_func = function() return sync_state.cooldown_seconds == 30 end,
callback = function()
sync_state.cooldown_seconds = 30
showMessage("Cooldown: 30 seconds")
end,
},
{
text = "1 minute",
checked_func = function() return sync_state.cooldown_seconds == 60 end,
callback = function()
sync_state.cooldown_seconds = 60
showMessage("Cooldown: 1 minute")
end,
},
{
text = "2 minutes",
checked_func = function() return sync_state.cooldown_seconds == 120 end,
callback = function()
sync_state.cooldown_seconds = 120
showMessage("Cooldown: 2 minutes")
end,
},
{
text = "1 hour",
checked_func = function() return sync_state.cooldown_seconds == 3600 end,
callback = function()
sync_state.cooldown_seconds = 3600
showMessage("Cooldown: 1 hour")
end,
},
{
text = "6 hours",
checked_func = function() return sync_state.cooldown_seconds == 21600 end,
callback = function()
sync_state.cooldown_seconds = 21600
showMessage("Cooldown: 6 hours")
end,
},
{
text = "24 hours (daily sync)",
checked_func = function() return sync_state.cooldown_seconds == 86400 end,
callback = function()
sync_state.cooldown_seconds = 86400
showMessage("Cooldown: 24 hours\nSyncs once per day")
end,
},
},
help_text = "Minimum time between syncs. Set to 24 hours for daily sync.",
},
{
text = "Include Highlightsync",
checked_func = function()
return G_reader_settings:readSetting("unified_autosync_include_highlights") ~= false
end,
callback = function()
local val = G_reader_settings:readSetting("unified_autosync_include_highlights") ~= false
G_reader_settings:saveSetting("unified_autosync_include_highlights", not val)
showMessage("Highlightsync " .. (not val and "enabled" or "disabled"))
end,
help_text = "Enable/disable automatic Highlightsync. Uses smart reload detection.",
separator = true,
},
{
text = "Show Notifications",
checked_func = function()
return G_reader_settings:readSetting("unified_autosync_show_notifications") or false
end,
callback = function()
local val = G_reader_settings:readSetting("unified_autosync_show_notifications") or false
G_reader_settings:saveSetting("unified_autosync_show_notifications", not val)
showMessage("Notifications " .. (not val and "enabled" or "disabled"))
end,
},
{
text = "Show Startup Message",
checked_func = function()
return G_reader_settings:readSetting("unified_autosync_show_startup_message") or false
end,
callback = function()
local val = G_reader_settings:readSetting("unified_autosync_show_startup_message") or false
G_reader_settings:saveSetting("unified_autosync_show_startup_message", not val)
showMessage("Startup message " .. (not val and "enabled" or "disabled"))
end,
help_text = "Show a notification when the patch loads (for debugging)",
separator = true,
},
{
text = "View Status",
keep_menu_open = true,
callback = function()
local enabled = G_reader_settings:isTrue("unified_autosync_enabled") and "Enabled" or "Disabled"
local last_sync = G_reader_settings:readSetting("unified_autosync_last_sync") or "Never"
local on_open = G_reader_settings:readSetting("unified_autosync_on_open") ~= false
local on_close = G_reader_settings:readSetting("unified_autosync_on_close") ~= false
local on_suspend = G_reader_settings:readSetting("unified_autosync_on_suspend") or false
local stats_status = isStatsConfigured() and "✓" or "✗"
local vocab_status = isVocabConfigured() and "✓" or "✗"
local hl_enabled = G_reader_settings:readSetting("unified_autosync_include_highlights") ~= false
local hl_status = hl_enabled and (isHighlightConfigured() and "✓" or "✗") or "⊘"
local sync_status = sync_state.is_syncing and "Syncing..." or "Idle"
if sync_state.in_highlightsync_reload then
sync_status = "In reload (protected)"
end
local time_since_last = os.time() - sync_state.last_sync_time
local cooldown_remaining = math.max(0, sync_state.cooldown_seconds - time_since_last)
local cooldown_text
if cooldown_remaining >= 3600 then
cooldown_text = string.format("cooldown: %.1fh", cooldown_remaining / 3600)
elseif cooldown_remaining >= 60 then
cooldown_text = string.format("cooldown: %dm", math.floor(cooldown_remaining / 60))
elseif cooldown_remaining > 0 then
cooldown_text = string.format("cooldown: %ds", cooldown_remaining)
else
cooldown_text = "ready"
end
local status = string.format(
"Status: %s\nState: %s (%s)\nLast sync: %s\n\nTriggers:\n• Open: %s\n• Close: %s\n• Suspend: %s\n\nPlugins:\n• Highlights: %s\n• Statistics: %s\n• Vocabulary: %s",
enabled, sync_status, cooldown_text, last_sync,
on_open and "Yes" or "No",
on_close and "Yes" or "No",
on_suspend and "Yes" or "No",
hl_status, stats_status, vocab_status
)
showMessage(status, 8)
end
}
}
}
end
-- --- MENU HOOK ---
log("Attempting to hook ReaderMenu...")
local menu_hook_success, menu_hook_error = pcall(function()
log("ReaderMenu module available")
local original_setUpdateItemTable = ReaderMenu.setUpdateItemTable
if not original_setUpdateItemTable then
log("WARNING: ReaderMenu.setUpdateItemTable not found")
return
end
ReaderMenu.setUpdateItemTable = function(self)
log("setUpdateItemTable called")
local add_success, add_error = pcall(function()
local ok_order, reader_order = pcall(require, "ui/elements/reader_menu_order")
self.menu_items = self.menu_items or {}
self.menu_items.unified_autosync = createMenuItem()
log("Menu item created and added to menu_items")
if ok_order and reader_order then
if reader_order.tools then
table.insert(reader_order.tools, "unified_autosync")
log("Added to reader_order.tools")
elseif reader_order.more_tools then
table.insert(reader_order.more_tools, "unified_autosync")
log("Added to reader_order.more_tools")
end
else
self.menu_order = self.menu_order or {}
self.menu_order.tools = self.menu_order.tools or {}
table.insert(self.menu_order.tools, "unified_autosync")
log("Added to self.menu_order.tools (fallback)")
end
end)
if not add_success then
log("ERROR adding menu item: " .. tostring(add_error))
end
original_setUpdateItemTable(self)
log("Original setUpdateItemTable called")
end
log("ReaderMenu hook installed")
end)
if not menu_hook_success then
log("ERROR hooking ReaderMenu: " .. tostring(menu_hook_error))
end
-- --- EVENT HOOKS ---
local original_onReaderReady = ReaderUI.onReaderReady
ReaderUI.onReaderReady = function(self, ...)
log("========== onReaderReady triggered ==========")
log(string.format("Reload flag state: %s", tostring(sync_state.in_highlightsync_reload)))
if original_onReaderReady then
log("Calling original onReaderReady")
original_onReaderReady(self, ...)
log("Original onReaderReady completed")
end
if G_reader_settings:isTrue("unified_autosync_enabled") and
G_reader_settings:readSetting("unified_autosync_on_open") ~= false then
local delay = G_reader_settings:readSetting("unified_autosync_open_delay") or 3
log(string.format("Scheduling sync on document open (%ds delay)", delay))
UIManager:scheduleIn(delay, function()
performUnifiedSync("on_open")
end)
else
log("Sync on open disabled")
end
end
local original_onCloseDocument = ReaderUI.onCloseDocument
ReaderUI.onCloseDocument = function(self, ...)
log("========== onCloseDocument triggered ==========")
log(string.format("Reload flag state: %s", tostring(sync_state.in_highlightsync_reload)))
log(string.format("Time since last sync: %ds", os.time() - sync_state.last_sync_time))
local debug_info = debug.getinfo(2, "Sl")
if debug_info then
log(string.format("Called from: %s:%d", debug_info.short_src or "unknown", debug_info.currentline or 0))
else
log("Could not get caller info")
end
if G_reader_settings:isTrue("unified_autosync_enabled") and
G_reader_settings:readSetting("unified_autosync_on_close") ~= false then
log("Triggering sync on document close")
performUnifiedSync("on_close")
else
log("Sync on close disabled")
end
if original_onCloseDocument then
log("Calling original onCloseDocument")
original_onCloseDocument(self, ...)
log("Original onCloseDocument completed")
end
end
local original_onSuspend = ReaderUI.onSuspend
ReaderUI.onSuspend = function(self, ...)
log("========== onSuspend triggered ==========")
log(string.format("Reload flag state: %s", tostring(sync_state.in_highlightsync_reload)))
log(string.format("Time since last sync: %ds", os.time() - sync_state.last_sync_time))
if G_reader_settings:isTrue("unified_autosync_enabled") and
G_reader_settings:readSetting("unified_autosync_on_suspend") then
log("Triggering sync on suspend")
performUnifiedSync("on_suspend")
else
log("Sync on suspend disabled")
end
if original_onSuspend then
log("Calling original onSuspend")
original_onSuspend(self, ...)
log("Original onSuspend completed")
end
end
-- --- SET DEFAULTS ---
if G_reader_settings:readSetting("unified_autosync_enabled") == nil then
log("First run: setting defaults")
G_reader_settings:saveSetting("unified_autosync_enabled", true)
G_reader_settings:saveSetting("unified_autosync_on_open", true)
G_reader_settings:saveSetting("unified_autosync_on_close", true)
G_reader_settings:saveSetting("unified_autosync_on_suspend", false)
G_reader_settings:saveSetting("unified_autosync_show_notifications", false)
G_reader_settings:saveSetting("unified_autosync_show_startup_message", false)
G_reader_settings:saveSetting("unified_autosync_open_delay", 3)
G_reader_settings:saveSetting("unified_autosync_include_highlights", false)
end
if G_reader_settings:readSetting("unified_autosync_include_highlights") == nil then
log("Highlightsync not configured, disabling by default for stability")
G_reader_settings:saveSetting("unified_autosync_include_highlights", false)
end
log("========== UNIFIED AUTO SYNC PATCH READY (v3.7) ==========")
if G_reader_settings:readSetting("unified_autosync_show_startup_message") then
log("Showing startup notification")
UIManager:scheduleIn(2, function()
showMessage("Unified Auto Sync loaded ✓", 2)
end)
end
return true
Hi, I see you're Brazilian, so I'll switch to Portuguese. Valeu @eduardorodrigues08 pelas palavras gentis! Acho que isso faz parte do patch. Os livros que você lê aparecem nas Reading Statistics, então imagino que isso faça parte das Reading Statistics também. Ou você quer dizer outra coisa?