Loading bookloresync.koplugin/main.lua +249 −62 Original line number Diff line number Diff line Loading @@ -392,8 +392,21 @@ function BookloreSync:init() self.progress_sync_last_push_ts = 0 self.progress_sync_page_counter = 0 self.progress_sync_last_page = -1 self.progress_sync_page_update_primed = false self.progress_sync_last_page_turn_timestamp = 0 self.progress_sync_ignore_next_page_event = false self.progress_sync_debounce_seconds = tonumber(self.settings:readSetting("progress_sync_debounce_seconds")) if self.progress_sync_debounce_seconds == nil then self.progress_sync_debounce_seconds = PROGRESS_API_DEBOUNCE_SECONDS end if self.progress_sync_debounce_seconds < 0 then self.progress_sync_debounce_seconds = 0 end self.progress_sync_fasttrack_in_flight = false self.progress_sync_background_flush_scheduled = false self.progress_sync_background_flush_running = false self.progress_sync_background_idle_seconds = 2 self.progress_sync_background_interval_seconds = 2 self.progress_sync_device_id = G_reader_settings:readSetting("device_id") self.progress_pull_on_open = self.settings:readSetting("progress_pull_on_open") if self.progress_pull_on_open == nil then Loading Loading @@ -3547,8 +3560,15 @@ function BookloreSync:fileDialogShowStoredData(file_path) table.insert(lines, "") if is_matched then table.insert(lines, _("Server ID:") .. " " .. tostring(book.book_id)) local server_pagecount = tonumber(book.server_pagecount) if server_pagecount and server_pagecount > 0 then table.insert(lines, _("Server pagecount:") .. " " .. tostring(server_pagecount)) else table.insert(lines, _("Server pagecount:") .. " " .. _("(unknown)")) end else table.insert(lines, _("Server ID:") .. " " .. _("(not matched)")) table.insert(lines, _("Server pagecount:") .. " " .. _("(not matched)")) end table.insert(lines, "") table.insert(lines, "── " .. _("Reading Sessions") .. " ──") Loading Loading @@ -4085,11 +4105,11 @@ function BookloreSync:addToMainMenu(menu_items) }, keep_menu_open = true, }, { }, } local what_to_sync_advanced_menu = { text = _("Advanced"), enabled_func = function() return self.progress_sync_enabled end, sub_item_table = { { text = _("Normalize session locations"), Loading Loading @@ -4128,9 +4148,6 @@ function BookloreSync:addToMainMenu(menu_items) keep_menu_open = true, }, }, keep_menu_open = true, }, }, } local annotations_menu = Settings:buildAnnotationsMenu(self) Loading Loading @@ -4194,6 +4211,11 @@ function BookloreSync:addToMainMenu(menu_items) progress_sync_menu, Settings:buildRatingMenu(self), annotations_and_bookmarks_menu, { text = "────────────────", enabled = false, }, what_to_sync_advanced_menu, }, hold_callback = function() showSectionHelp( Loading Loading @@ -5309,8 +5331,6 @@ function BookloreSync:pushCurrentKoreaderProgressDirect(payload) return false, "WiFi is not connected" end self.api:init(self.server_url, self.username, self.password, self.db) local ok, body_or_err = self.api:updateKoReaderProgress(payload) if not ok then return false, body_or_err Loading @@ -5332,6 +5352,96 @@ function BookloreSync:pushCurrentKoreaderProgressDirect(payload) return true, body_or_err end ---BookloreSync:requestFastTrackProgressPush. function BookloreSync:requestFastTrackProgressPush(payload) if self.progress_sync_fasttrack_in_flight then self:logDbg("BookloreSync: Fast-track progress push skipped - in flight") return false end self.progress_sync_fasttrack_in_flight = true UIManager:scheduleIn(0, function() local ok, err = pcall(function() local done = self:pushCurrentKoreaderProgressFastTrack(payload) if done then self.progress_sync_last_push_ts = os.time() end end) if not ok then self:logWarn("BookloreSync: Fast-track progress push background task failed:", tostring(err)) end self.progress_sync_fasttrack_in_flight = false end) return true end ---BookloreSync:scheduleBackgroundProgressFlush. function BookloreSync:scheduleBackgroundProgressFlush(reason) if self.progress_sync_background_flush_scheduled then self:logDbg("BookloreSync: Progress background flush already scheduled, reason:", tostring(reason or "unknown")) return false end self.progress_sync_background_flush_scheduled = true local interval = tonumber(self.progress_sync_background_interval_seconds) or 2 if interval < 1 then interval = 1 end self:logInfo("BookloreSync: Scheduling background progress flush in", tostring(interval), "s; reason:", tostring(reason or "unknown")) UIManager:scheduleIn(interval, function() self.progress_sync_background_flush_scheduled = false self:runBackgroundProgressFlush(reason) end) return true end ---BookloreSync:runBackgroundProgressFlush. function BookloreSync:runBackgroundProgressFlush(reason) if self.progress_sync_background_flush_running then self:logDbg("BookloreSync: Background progress flush skipped - already running") return false end if not self.progress_sync_enabled or self.manual_sync_only then return false end if not NetworkMgr:isConnected() then self:logDbg("BookloreSync: Background progress flush skipped - offline") return false end local now = os.time() local idle_for = now - (tonumber(self.progress_sync_last_page_turn_timestamp) or now) local idle_needed = tonumber(self.progress_sync_background_idle_seconds) or 2 if idle_for < idle_needed then self:logDbg("BookloreSync: Background progress flush deferred - reader active (idle", tostring(idle_for), "s)") self:scheduleBackgroundProgressFlush("reader_active") return false end self.progress_sync_background_flush_running = true UIManager:scheduleIn(0, function() local ok, synced, failed = pcall(function() return self:syncPendingKoreaderProgress(true, 1) end) if ok then self:logInfo("BookloreSync: Background progress flush done; reason=", tostring(reason or "unknown"), "synced=", tostring(synced or 0), "failed=", tostring(failed or 0)) if (tonumber(synced) or 0) > 0 then self.progress_sync_last_push_ts = os.time() end if self.db and (self.db:getPendingKoreaderProgressCount() or 0) > 0 then self:scheduleBackgroundProgressFlush("pending_remaining") end else self:logWarn("BookloreSync: Background progress flush crashed:", tostring(synced)) end self.progress_sync_background_flush_running = false end) return true end --[[-- Push current KOReader progress immediately (no full pending sync pipeline). Loading Loading @@ -5475,6 +5585,32 @@ function BookloreSync:queueCurrentKoreaderProgress() return ok end ---BookloreSync:queueKoreaderProgressPayload. function BookloreSync:queueKoreaderProgressPayload(payload) if not self.progress_sync_enabled then return false end if not self.db or type(payload) ~= "table" then return false end local queued = self.db:addPendingKoreaderProgress({ book_cache_id = payload.book_cache_id, book_hash = tostring(payload.book_hash or payload.document or ""), progress = tostring(payload.progress or payload.location or "0"), percentage = tonumber(payload.percentage) or 0, location = tostring(payload.location or payload.progress or "0"), device = tostring(payload.device or (Device and Device.model) or "KOReader"), device_id = tostring(payload.device_id or self.progress_sync_device_id or "unknown"), timestamp = tonumber(payload.timestamp) or os.time(), }) if queued then self:logInfo("BookloreSync: Queued periodic progress for background flush; hash:", tostring(payload.book_hash or payload.document), "percentage:", tostring(payload.percentage)) end return queued end --[[-- Queue a forced "read" progress state (100%) for the current book. Loading Loading @@ -6177,6 +6313,12 @@ function BookloreSync:startSession() } self:logInfo("BookloreSync: Session started for '", book_title, "' at", start_progress, "% (location:", start_location, ")") -- Re-initialize API client at session start so periodic progress pushes do -- not pay init cost on every page-based sync trigger. if self.api then self.api:init(self.server_url, self.username, self.password, self.db) end end --[[-- Loading Loading @@ -6305,6 +6447,7 @@ function BookloreSync:onReaderReady() else self.progress_sync_last_page = -1 end self.progress_sync_page_update_primed = false self.progress_sync_last_page_turn_timestamp = os.time() -- Pull-only on open: allow remote catch-up without recording local state. Loading Loading @@ -6334,6 +6477,14 @@ function BookloreSync:onPageUpdate(page) return false end -- Prime the page-update baseline once per open so initial render updates -- do not count as a user page turn or trigger a progress push. if not self.progress_sync_page_update_primed then self.progress_sync_page_update_primed = true self.progress_sync_last_page = page return false end if self.progress_sync_last_page ~= page then self.progress_sync_last_page = page self.progress_sync_last_page_turn_timestamp = os.time() Loading Loading @@ -6362,30 +6513,32 @@ function BookloreSync:onPageUpdate(page) payload = self:buildKoreaderProgressPayloadForPage(page, os.time()) end local debounce_seconds = PROGRESS_API_DEBOUNCE_SECONDS if threshold <= 1 then local debounce_seconds = tonumber(self.progress_sync_debounce_seconds) if debounce_seconds == nil then debounce_seconds = PROGRESS_API_DEBOUNCE_SECONDS end if debounce_seconds < 0 then debounce_seconds = 0 end local now = os.time() if debounce_seconds > 0 and now - (self.progress_sync_last_push_ts or 0) < debounce_seconds then return false end if self.manual_sync_only then local queued = self:queueCurrentKoreaderProgress() local queued = payload and self:queueKoreaderProgressPayload(payload) or self:queueCurrentKoreaderProgress() if queued then self.progress_sync_last_push_ts = now end else self:_requestWifi(_("sync reading progress"), function() local done = self:pushCurrentKoreaderProgressFastTrack(payload) if done then self.progress_sync_last_push_ts = os.time() local queued = payload and self:queueKoreaderProgressPayload(payload) or self:queueCurrentKoreaderProgress() if queued then self:scheduleBackgroundProgressFlush("page_update") end end, function() self:logInfo("BookloreSync: WiFi request declined on page-update progress sync - queueing") local queued = self:queueCurrentKoreaderProgress(payload) local queued = payload and self:queueKoreaderProgressPayload(payload) or self:queueCurrentKoreaderProgress() if queued then self.progress_sync_last_push_ts = os.time() end Loading Loading @@ -7243,17 +7396,32 @@ function BookloreSync:onResume() self:logInfo("BookloreSync: Device resuming") -- Cooldown guard: skip auto-sync if we ran one less than 5 minutes ago. -- Prevents hammering the server on rapid suspend/resume cycles (e.g. when -- the user is navigating menus with sleep-timer enabled). -- Exception: if sessions/progress are pending, force a resume sync to avoid -- leaving deferred close data queued until a later trigger. local totals = self:getPendingSyncTotals() local pending_sessions = tonumber(totals and totals.sessions) or 0 local pending_progress = tonumber(totals and totals.progress) or 0 local has_pending_resume_critical = (pending_sessions > 0) or (pending_progress > 0) local now = os.time() if self.last_auto_sync_time and (now - self.last_auto_sync_time) < 300 then local cooldown_active = self.last_auto_sync_time and (now - self.last_auto_sync_time) < 300 if cooldown_active and not has_pending_resume_critical then self:logInfo("BookloreSync: Skipping resume auto-sync (cooldown active, last run", (now - self.last_auto_sync_time), "s ago)") else self.last_auto_sync_time = now if not self.manual_sync_only then if cooldown_active and has_pending_resume_critical then self:logInfo( "BookloreSync: Cooldown overridden on resume due to pending items", "sessions=", tostring(pending_sessions), "progress=", tostring(pending_progress) ) else self:logInfo("BookloreSync: Attempting background sync on resume") end -- Use _requestWifi so that if the ask_wifi_enable option is on the -- user is prompted before we try to connect. On deny the sync is -- silently skipped (data remains queued for the next opportunity). Loading Loading @@ -9647,8 +9815,9 @@ On failure we increment retry_count and keep it queued. @return integer synced_count, integer failed_count --]] ---BookloreSync:syncPendingKoreaderProgress. function BookloreSync:syncPendingKoreaderProgress(silent) function BookloreSync:syncPendingKoreaderProgress(silent, max_rows) silent = silent or false max_rows = tonumber(max_rows) or 0 if not self.db then return 0, 0 Loading @@ -9662,7 +9831,12 @@ function BookloreSync:syncPendingKoreaderProgress(silent) local synced_count = 0 local failed_count = 0 local processed = 0 for _, row in ipairs(rows) do if max_rows > 0 and processed >= max_rows then break end processed = processed + 1 local payload = { timestamp = tonumber(row.timestamp) or os.time(), document = tostring(row.book_hash), Loading Loading @@ -10174,6 +10348,19 @@ function BookloreSync:_saveBookCacheMatch(book, selected_result) local book_title = type(selected_result) == "table" and selected_result.title or nil local isbn10 = type(selected_result) == "table" and selected_result.isbn10 or nil local isbn13 = type(selected_result) == "table" and selected_result.isbn13 or nil local server_pagecount = nil if type(selected_result) == "table" then local raw_pagecount = selected_result.pagecount if raw_pagecount == nil then raw_pagecount = selected_result.server_pagecount end local parsed_pagecount = tonumber(raw_pagecount) if parsed_pagecount and parsed_pagecount > 0 then -- Keep parsing lightweight and defensive here; saveBookCache() -- performs final normalization/rounding/validation. server_pagecount = parsed_pagecount end end if not book_id then UIManager:show(InfoMessage:new{ Loading @@ -10187,7 +10374,7 @@ function BookloreSync:_saveBookCacheMatch(book, selected_result) local ok = self.db:saveBookCache( book.file_path, book.file_hash or "", book_id, book_title or book.title, book.author, isbn10, isbn13 isbn10, isbn13, server_pagecount ) if not ok then Loading @@ -10201,17 +10388,17 @@ function BookloreSync:_saveBookCacheMatch(book, selected_result) self:logInfo("BookloreSync: Saved manual match - file:", book.file_path, "book_id:", book_id) -- Advance to the next unmatched book self.bk_matching_index = self.bk_matching_index + 1 -- Kick off a sync so pending annotations/ratings/sessions for this book -- are uploaded right away. Run silently so the UI just shows the next -- book-match dialog without interruption. self:syncPendingSessions(true) -- Show the next match -- Continue batch matching flow only when that state is active. if self.bk_unmatched_books and self.bk_matching_index then self.bk_matching_index = self.bk_matching_index + 1 self:_showNextBookCacheMatch() end end --[[-- Resolve book IDs for cached books that don't have them yet Loading Loading
bookloresync.koplugin/main.lua +249 −62 Original line number Diff line number Diff line Loading @@ -392,8 +392,21 @@ function BookloreSync:init() self.progress_sync_last_push_ts = 0 self.progress_sync_page_counter = 0 self.progress_sync_last_page = -1 self.progress_sync_page_update_primed = false self.progress_sync_last_page_turn_timestamp = 0 self.progress_sync_ignore_next_page_event = false self.progress_sync_debounce_seconds = tonumber(self.settings:readSetting("progress_sync_debounce_seconds")) if self.progress_sync_debounce_seconds == nil then self.progress_sync_debounce_seconds = PROGRESS_API_DEBOUNCE_SECONDS end if self.progress_sync_debounce_seconds < 0 then self.progress_sync_debounce_seconds = 0 end self.progress_sync_fasttrack_in_flight = false self.progress_sync_background_flush_scheduled = false self.progress_sync_background_flush_running = false self.progress_sync_background_idle_seconds = 2 self.progress_sync_background_interval_seconds = 2 self.progress_sync_device_id = G_reader_settings:readSetting("device_id") self.progress_pull_on_open = self.settings:readSetting("progress_pull_on_open") if self.progress_pull_on_open == nil then Loading Loading @@ -3547,8 +3560,15 @@ function BookloreSync:fileDialogShowStoredData(file_path) table.insert(lines, "") if is_matched then table.insert(lines, _("Server ID:") .. " " .. tostring(book.book_id)) local server_pagecount = tonumber(book.server_pagecount) if server_pagecount and server_pagecount > 0 then table.insert(lines, _("Server pagecount:") .. " " .. tostring(server_pagecount)) else table.insert(lines, _("Server pagecount:") .. " " .. _("(unknown)")) end else table.insert(lines, _("Server ID:") .. " " .. _("(not matched)")) table.insert(lines, _("Server pagecount:") .. " " .. _("(not matched)")) end table.insert(lines, "") table.insert(lines, "── " .. _("Reading Sessions") .. " ──") Loading Loading @@ -4085,11 +4105,11 @@ function BookloreSync:addToMainMenu(menu_items) }, keep_menu_open = true, }, { }, } local what_to_sync_advanced_menu = { text = _("Advanced"), enabled_func = function() return self.progress_sync_enabled end, sub_item_table = { { text = _("Normalize session locations"), Loading Loading @@ -4128,9 +4148,6 @@ function BookloreSync:addToMainMenu(menu_items) keep_menu_open = true, }, }, keep_menu_open = true, }, }, } local annotations_menu = Settings:buildAnnotationsMenu(self) Loading Loading @@ -4194,6 +4211,11 @@ function BookloreSync:addToMainMenu(menu_items) progress_sync_menu, Settings:buildRatingMenu(self), annotations_and_bookmarks_menu, { text = "────────────────", enabled = false, }, what_to_sync_advanced_menu, }, hold_callback = function() showSectionHelp( Loading Loading @@ -5309,8 +5331,6 @@ function BookloreSync:pushCurrentKoreaderProgressDirect(payload) return false, "WiFi is not connected" end self.api:init(self.server_url, self.username, self.password, self.db) local ok, body_or_err = self.api:updateKoReaderProgress(payload) if not ok then return false, body_or_err Loading @@ -5332,6 +5352,96 @@ function BookloreSync:pushCurrentKoreaderProgressDirect(payload) return true, body_or_err end ---BookloreSync:requestFastTrackProgressPush. function BookloreSync:requestFastTrackProgressPush(payload) if self.progress_sync_fasttrack_in_flight then self:logDbg("BookloreSync: Fast-track progress push skipped - in flight") return false end self.progress_sync_fasttrack_in_flight = true UIManager:scheduleIn(0, function() local ok, err = pcall(function() local done = self:pushCurrentKoreaderProgressFastTrack(payload) if done then self.progress_sync_last_push_ts = os.time() end end) if not ok then self:logWarn("BookloreSync: Fast-track progress push background task failed:", tostring(err)) end self.progress_sync_fasttrack_in_flight = false end) return true end ---BookloreSync:scheduleBackgroundProgressFlush. function BookloreSync:scheduleBackgroundProgressFlush(reason) if self.progress_sync_background_flush_scheduled then self:logDbg("BookloreSync: Progress background flush already scheduled, reason:", tostring(reason or "unknown")) return false end self.progress_sync_background_flush_scheduled = true local interval = tonumber(self.progress_sync_background_interval_seconds) or 2 if interval < 1 then interval = 1 end self:logInfo("BookloreSync: Scheduling background progress flush in", tostring(interval), "s; reason:", tostring(reason or "unknown")) UIManager:scheduleIn(interval, function() self.progress_sync_background_flush_scheduled = false self:runBackgroundProgressFlush(reason) end) return true end ---BookloreSync:runBackgroundProgressFlush. function BookloreSync:runBackgroundProgressFlush(reason) if self.progress_sync_background_flush_running then self:logDbg("BookloreSync: Background progress flush skipped - already running") return false end if not self.progress_sync_enabled or self.manual_sync_only then return false end if not NetworkMgr:isConnected() then self:logDbg("BookloreSync: Background progress flush skipped - offline") return false end local now = os.time() local idle_for = now - (tonumber(self.progress_sync_last_page_turn_timestamp) or now) local idle_needed = tonumber(self.progress_sync_background_idle_seconds) or 2 if idle_for < idle_needed then self:logDbg("BookloreSync: Background progress flush deferred - reader active (idle", tostring(idle_for), "s)") self:scheduleBackgroundProgressFlush("reader_active") return false end self.progress_sync_background_flush_running = true UIManager:scheduleIn(0, function() local ok, synced, failed = pcall(function() return self:syncPendingKoreaderProgress(true, 1) end) if ok then self:logInfo("BookloreSync: Background progress flush done; reason=", tostring(reason or "unknown"), "synced=", tostring(synced or 0), "failed=", tostring(failed or 0)) if (tonumber(synced) or 0) > 0 then self.progress_sync_last_push_ts = os.time() end if self.db and (self.db:getPendingKoreaderProgressCount() or 0) > 0 then self:scheduleBackgroundProgressFlush("pending_remaining") end else self:logWarn("BookloreSync: Background progress flush crashed:", tostring(synced)) end self.progress_sync_background_flush_running = false end) return true end --[[-- Push current KOReader progress immediately (no full pending sync pipeline). Loading Loading @@ -5475,6 +5585,32 @@ function BookloreSync:queueCurrentKoreaderProgress() return ok end ---BookloreSync:queueKoreaderProgressPayload. function BookloreSync:queueKoreaderProgressPayload(payload) if not self.progress_sync_enabled then return false end if not self.db or type(payload) ~= "table" then return false end local queued = self.db:addPendingKoreaderProgress({ book_cache_id = payload.book_cache_id, book_hash = tostring(payload.book_hash or payload.document or ""), progress = tostring(payload.progress or payload.location or "0"), percentage = tonumber(payload.percentage) or 0, location = tostring(payload.location or payload.progress or "0"), device = tostring(payload.device or (Device and Device.model) or "KOReader"), device_id = tostring(payload.device_id or self.progress_sync_device_id or "unknown"), timestamp = tonumber(payload.timestamp) or os.time(), }) if queued then self:logInfo("BookloreSync: Queued periodic progress for background flush; hash:", tostring(payload.book_hash or payload.document), "percentage:", tostring(payload.percentage)) end return queued end --[[-- Queue a forced "read" progress state (100%) for the current book. Loading Loading @@ -6177,6 +6313,12 @@ function BookloreSync:startSession() } self:logInfo("BookloreSync: Session started for '", book_title, "' at", start_progress, "% (location:", start_location, ")") -- Re-initialize API client at session start so periodic progress pushes do -- not pay init cost on every page-based sync trigger. if self.api then self.api:init(self.server_url, self.username, self.password, self.db) end end --[[-- Loading Loading @@ -6305,6 +6447,7 @@ function BookloreSync:onReaderReady() else self.progress_sync_last_page = -1 end self.progress_sync_page_update_primed = false self.progress_sync_last_page_turn_timestamp = os.time() -- Pull-only on open: allow remote catch-up without recording local state. Loading Loading @@ -6334,6 +6477,14 @@ function BookloreSync:onPageUpdate(page) return false end -- Prime the page-update baseline once per open so initial render updates -- do not count as a user page turn or trigger a progress push. if not self.progress_sync_page_update_primed then self.progress_sync_page_update_primed = true self.progress_sync_last_page = page return false end if self.progress_sync_last_page ~= page then self.progress_sync_last_page = page self.progress_sync_last_page_turn_timestamp = os.time() Loading Loading @@ -6362,30 +6513,32 @@ function BookloreSync:onPageUpdate(page) payload = self:buildKoreaderProgressPayloadForPage(page, os.time()) end local debounce_seconds = PROGRESS_API_DEBOUNCE_SECONDS if threshold <= 1 then local debounce_seconds = tonumber(self.progress_sync_debounce_seconds) if debounce_seconds == nil then debounce_seconds = PROGRESS_API_DEBOUNCE_SECONDS end if debounce_seconds < 0 then debounce_seconds = 0 end local now = os.time() if debounce_seconds > 0 and now - (self.progress_sync_last_push_ts or 0) < debounce_seconds then return false end if self.manual_sync_only then local queued = self:queueCurrentKoreaderProgress() local queued = payload and self:queueKoreaderProgressPayload(payload) or self:queueCurrentKoreaderProgress() if queued then self.progress_sync_last_push_ts = now end else self:_requestWifi(_("sync reading progress"), function() local done = self:pushCurrentKoreaderProgressFastTrack(payload) if done then self.progress_sync_last_push_ts = os.time() local queued = payload and self:queueKoreaderProgressPayload(payload) or self:queueCurrentKoreaderProgress() if queued then self:scheduleBackgroundProgressFlush("page_update") end end, function() self:logInfo("BookloreSync: WiFi request declined on page-update progress sync - queueing") local queued = self:queueCurrentKoreaderProgress(payload) local queued = payload and self:queueKoreaderProgressPayload(payload) or self:queueCurrentKoreaderProgress() if queued then self.progress_sync_last_push_ts = os.time() end Loading Loading @@ -7243,17 +7396,32 @@ function BookloreSync:onResume() self:logInfo("BookloreSync: Device resuming") -- Cooldown guard: skip auto-sync if we ran one less than 5 minutes ago. -- Prevents hammering the server on rapid suspend/resume cycles (e.g. when -- the user is navigating menus with sleep-timer enabled). -- Exception: if sessions/progress are pending, force a resume sync to avoid -- leaving deferred close data queued until a later trigger. local totals = self:getPendingSyncTotals() local pending_sessions = tonumber(totals and totals.sessions) or 0 local pending_progress = tonumber(totals and totals.progress) or 0 local has_pending_resume_critical = (pending_sessions > 0) or (pending_progress > 0) local now = os.time() if self.last_auto_sync_time and (now - self.last_auto_sync_time) < 300 then local cooldown_active = self.last_auto_sync_time and (now - self.last_auto_sync_time) < 300 if cooldown_active and not has_pending_resume_critical then self:logInfo("BookloreSync: Skipping resume auto-sync (cooldown active, last run", (now - self.last_auto_sync_time), "s ago)") else self.last_auto_sync_time = now if not self.manual_sync_only then if cooldown_active and has_pending_resume_critical then self:logInfo( "BookloreSync: Cooldown overridden on resume due to pending items", "sessions=", tostring(pending_sessions), "progress=", tostring(pending_progress) ) else self:logInfo("BookloreSync: Attempting background sync on resume") end -- Use _requestWifi so that if the ask_wifi_enable option is on the -- user is prompted before we try to connect. On deny the sync is -- silently skipped (data remains queued for the next opportunity). Loading Loading @@ -9647,8 +9815,9 @@ On failure we increment retry_count and keep it queued. @return integer synced_count, integer failed_count --]] ---BookloreSync:syncPendingKoreaderProgress. function BookloreSync:syncPendingKoreaderProgress(silent) function BookloreSync:syncPendingKoreaderProgress(silent, max_rows) silent = silent or false max_rows = tonumber(max_rows) or 0 if not self.db then return 0, 0 Loading @@ -9662,7 +9831,12 @@ function BookloreSync:syncPendingKoreaderProgress(silent) local synced_count = 0 local failed_count = 0 local processed = 0 for _, row in ipairs(rows) do if max_rows > 0 and processed >= max_rows then break end processed = processed + 1 local payload = { timestamp = tonumber(row.timestamp) or os.time(), document = tostring(row.book_hash), Loading Loading @@ -10174,6 +10348,19 @@ function BookloreSync:_saveBookCacheMatch(book, selected_result) local book_title = type(selected_result) == "table" and selected_result.title or nil local isbn10 = type(selected_result) == "table" and selected_result.isbn10 or nil local isbn13 = type(selected_result) == "table" and selected_result.isbn13 or nil local server_pagecount = nil if type(selected_result) == "table" then local raw_pagecount = selected_result.pagecount if raw_pagecount == nil then raw_pagecount = selected_result.server_pagecount end local parsed_pagecount = tonumber(raw_pagecount) if parsed_pagecount and parsed_pagecount > 0 then -- Keep parsing lightweight and defensive here; saveBookCache() -- performs final normalization/rounding/validation. server_pagecount = parsed_pagecount end end if not book_id then UIManager:show(InfoMessage:new{ Loading @@ -10187,7 +10374,7 @@ function BookloreSync:_saveBookCacheMatch(book, selected_result) local ok = self.db:saveBookCache( book.file_path, book.file_hash or "", book_id, book_title or book.title, book.author, isbn10, isbn13 isbn10, isbn13, server_pagecount ) if not ok then Loading @@ -10201,17 +10388,17 @@ function BookloreSync:_saveBookCacheMatch(book, selected_result) self:logInfo("BookloreSync: Saved manual match - file:", book.file_path, "book_id:", book_id) -- Advance to the next unmatched book self.bk_matching_index = self.bk_matching_index + 1 -- Kick off a sync so pending annotations/ratings/sessions for this book -- are uploaded right away. Run silently so the UI just shows the next -- book-match dialog without interruption. self:syncPendingSessions(true) -- Show the next match -- Continue batch matching flow only when that state is active. if self.bk_unmatched_books and self.bk_matching_index then self.bk_matching_index = self.bk_matching_index + 1 self:_showNextBookCacheMatch() end end --[[-- Resolve book IDs for cached books that don't have them yet Loading