+167
-21
main.lua
+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)