Unofficial Paperbnd/Popfeed plugin for KOReader

queue updates when offline

graham.systems ef64004a 3dc4756a

verified
Changed files
+167 -21
+167 -21
main.lua
··· 26 26 self.refresh_token = self.settings:readSetting("refresh_token") 27 27 self.did = self.settings:readSetting("did") 28 28 self.document_mappings = self.settings:readSetting("document_mappings") or {} 29 + self.offline_queue = self.settings:readSetting("offline_queue") or {} 29 30 self.auto_sync_enabled = self.settings:readSetting("auto_sync_enabled") 30 31 if self.auto_sync_enabled == nil then 31 32 self.auto_sync_enabled = false -- Default OFF for safety ··· 56 57 -- Validate session at startup if we have credentials 57 58 if self:isAuthenticated() then 58 59 self:validateSessionAtStartup() 60 + 61 + -- Try to flush any queued syncs from previous sessions 62 + self:flushOfflineQueue() 59 63 end 60 64 end 61 65 ··· 67 71 self.settings:saveSetting("refresh_token", self.refresh_token) 68 72 self.settings:saveSetting("did", self.did) 69 73 self.settings:saveSetting("document_mappings", self.document_mappings) 74 + self.settings:saveSetting("offline_queue", self.offline_queue) 70 75 self.settings:saveSetting("auto_sync_enabled", self.auto_sync_enabled) 71 76 self.settings:flush() 72 77 end ··· 155 160 { 156 161 text_func = function() 157 162 local doc_path = self:getCurrentDocumentPath() 163 + 164 + -- Check if there's a queued sync for this document 165 + if doc_path and self.offline_queue[doc_path] then 166 + local queued = self.offline_queue[doc_path] 167 + return T(_("Last synced: Queued - offline (%1%)"), queued.percent) 168 + end 169 + 158 170 if 159 171 doc_path 160 172 and self.document_mappings[doc_path] ··· 475 487 return 476 488 end 477 489 478 - local mapping = self.document_mappings[doc_path] 490 + -- Cancel any pending auto-sync for current document (manual sync takes priority) 491 + if self.auto_sync_task then 492 + UIManager:unschedule(self.auto_sync_task) 493 + self.auto_sync_task = nil 494 + end 479 495 480 496 -- Get document statistics 481 497 local pages = self.ui.document:getPageCount() ··· 491 507 end 492 508 local percent = math.floor((current_page / pages) * 100) 493 509 510 + -- Try to sync 511 + local success = self:attemptSync(doc_path, percent, current_page, pages) 512 + 513 + if success then 514 + -- Show success notification (silent for auto-sync) 515 + if not use_notification then 516 + -- Use InfoMessage for manual sync and document close 517 + UIManager:show(InfoMessage:new({ 518 + text = T(_("Synced: %1% (%2/%3)"), percent, current_page, pages), 519 + timeout = 2, 520 + })) 521 + end 522 + 523 + -- On success, try to flush any queued syncs 524 + self:flushOfflineQueue() 525 + else 526 + -- Queue this sync for later (silent) 527 + self:queueOfflineSync(doc_path, percent, current_page, pages) 528 + end 529 + end 530 + 531 + -- Helper function to detect network errors 532 + function Paperbnd:isNetworkError(error_msg) 533 + if not error_msg then 534 + return false 535 + end 536 + local error_lower = string.lower(error_msg) 537 + return string.find(error_lower, "connection") ~= nil 538 + or string.find(error_lower, "timeout") ~= nil 539 + or string.find(error_lower, "unreachable") ~= nil 540 + or string.find(error_lower, "network") ~= nil 541 + or string.find(error_lower, "no address associated") ~= nil 542 + end 543 + 544 + -- Attempt to sync progress, returns true on success, false on network error 545 + function Paperbnd:attemptSync(doc_path, percent, current_page, pages) 546 + local mapping = self.document_mappings[doc_path] 547 + if not mapping then 548 + return false 549 + end 550 + 494 551 -- Fetch current record 495 552 local record_response, err = self.xrpc:getRecord(self.did, "social.popfeed.feed.listItem", mapping.rkey) 496 553 497 554 if err then 498 - UIManager:show(InfoMessage:new({ 499 - text = T(_("Failed to fetch record: %1"), err), 500 - })) 501 - return 555 + if self:isNetworkError(err) then 556 + -- Network error - return false to trigger queuing 557 + return false 558 + else 559 + -- Other error (auth, data, etc.) - show error and return false 560 + UIManager:show(InfoMessage:new({ 561 + text = T(_("Failed to fetch record: %1"), err), 562 + })) 563 + return false 564 + end 502 565 end 503 566 504 567 local record = record_response.value ··· 512 575 updatedAt = os.date("!%Y-%m-%dT%H:%M:%S.000Z"), 513 576 } 514 577 515 - -- If not already in currently_reading, update listType 516 - -- if record.listType ~= "currently_reading_books" then 517 - -- record.listType = "currently_reading_books" 518 - -- TODO: Also update the listUri to point to currently_reading list 519 - -- end 520 - 521 578 -- Put updated record 522 579 local _res, put_err = self.xrpc:putRecord(self.did, "social.popfeed.feed.listItem", mapping.rkey, record) 523 580 524 581 if put_err then 525 - UIManager:show(InfoMessage:new({ 526 - text = T(_("Failed to sync progress: %1"), put_err), 527 - })) 528 - return 582 + if self:isNetworkError(put_err) then 583 + -- Network error - return false to trigger queuing 584 + return false 585 + else 586 + -- Other error - show error and return false 587 + UIManager:show(InfoMessage:new({ 588 + text = T(_("Failed to sync progress: %1"), put_err), 589 + })) 590 + return false 591 + end 529 592 end 530 593 531 - -- Record last sync info for status display (per-document) 594 + -- Success! Update last sync info 532 595 self.document_mappings[doc_path].last_sync_time = os.time() 533 596 self.document_mappings[doc_path].last_sync_percent = percent 534 597 self:saveSettings() 535 598 536 - -- Show success notification (silent for auto-sync) 537 - if not use_notification then 538 - -- Use InfoMessage for manual sync and document close 599 + return true 600 + end 601 + 602 + -- Queue a sync for offline processing 603 + function Paperbnd:queueOfflineSync(doc_path, percent, current_page, pages) 604 + local mapping = self.document_mappings[doc_path] 605 + if not mapping then 606 + return 607 + end 608 + 609 + -- Cancel any pending auto-sync (this queued sync is newer) 610 + if self.auto_sync_task then 611 + UIManager:unschedule(self.auto_sync_task) 612 + self.auto_sync_task = nil 613 + end 614 + 615 + -- Store or update queue entry (latest only per document) 616 + self.offline_queue[doc_path] = { 617 + percent = percent, 618 + current_page = current_page, 619 + total_pages = pages, 620 + timestamp = os.time(), 621 + rkey = mapping.rkey, 622 + title = mapping.title, 623 + author = mapping.author, 624 + } 625 + 626 + self:saveSettings() 627 + end 628 + 629 + -- Flush all queued syncs (called after successful sync) 630 + function Paperbnd:flushOfflineQueue() 631 + if not self:isAuthenticated() then 632 + return 633 + end 634 + 635 + local queue_count = 0 636 + for _ in pairs(self.offline_queue) do 637 + queue_count = queue_count + 1 638 + end 639 + 640 + if queue_count == 0 then 641 + return 642 + end 643 + 644 + local flushed_count = 0 645 + local failed_paths = {} 646 + local current_doc_path = self:getCurrentDocumentPath() 647 + 648 + -- Try to sync each queued item 649 + for doc_path, queue_entry in pairs(self.offline_queue) do 650 + local percent, current_page, pages 651 + 652 + -- If this is the current document, use latest progress instead of queued 653 + if doc_path == current_doc_path and self.ui.document then 654 + pages = self.ui.document:getPageCount() 655 + if self.ui.paging then 656 + current_page = self.ui.paging.current_page 657 + elseif self.ui.rolling then 658 + current_page = self.ui.rolling.current_page 659 + else 660 + current_page = 1 661 + end 662 + percent = math.floor((current_page / pages) * 100) 663 + else 664 + -- Use queued progress for other documents 665 + percent = queue_entry.percent 666 + current_page = queue_entry.current_page 667 + pages = queue_entry.total_pages 668 + end 669 + 670 + local success = self:attemptSync(doc_path, percent, current_page, pages) 671 + if success then 672 + -- Remove from queue on success 673 + self.offline_queue[doc_path] = nil 674 + flushed_count = flushed_count + 1 675 + else 676 + -- Keep in queue on failure 677 + table.insert(failed_paths, doc_path) 678 + end 679 + end 680 + 681 + -- Save updated queue 682 + if flushed_count > 0 then 683 + self:saveSettings() 684 + 685 + -- Show notification for flushed syncs 539 686 UIManager:show(InfoMessage:new({ 540 - text = T(_("Synced: %1% (%2/%3)"), percent, current_page, pages), 687 + text = T(_("Flushed %1 queued sync(s)"), flushed_count), 541 688 timeout = 2, 542 689 })) 543 690 end 544 - -- Auto-sync is completely silent (use_notification = true) 545 691 end 546 692 547 693 function Paperbnd:scheduleDebouncedSync(pageno)