Last active
December 8, 2025 21:11
-
-
Save mallomar/67349ac3c88e1950754ba39a4913c029 to your computer and use it in GitHub Desktop.
Automatically sync Reading Statistics, Vocabulary Builder and Highlight Sync at user configurable events (e.g. open, close, suspend) - requires HighlightSync plugin (https://github.com/gitalexcampos/koreader-Highlight-Sync)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| -- 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 |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The version below should work for you: