mirror of
https://github.com/kovidgoyal/calibre.git
synced 2026-04-03 15:51:58 -04:00
E-book viewer: track pending annotation uploads per-book in IDB
Previously a single 'pending-annot-upload' IDB key was used, so only
the last-annotated book's pending upload survived an app kill. With
multiple books annotated offline (or across multiple tabs), earlier
books' uploads were silently dropped from the queue. A related bug
caused stale in-memory state from a previous book to be used on Sync
after navigating between books in the same tab, potentially sending the
wrong annotations to the wrong book endpoint.
Changes:
- IDB key is now 'pending-annot-upload:{library_id}/{book_id}/{fmt}',
one entry per book, so all books' pending uploads survive independently
- New get_all_pending_annot_uploads() uses an IDB cursor range query to
retrieve every pending entry
- clear_pending_annot_upload() now takes book identity params and a
completion callback so the next upload starts only after the IDB
delete has committed
- _make_annot_upload_done() returns a per-book closure used as the ajax
callback, replacing the single _annot_upload_done method
- After each successful upload, _upload_next_from_idb() fetches and
uploads the next pending entry, draining the queue sequentially
- _on_network_restored() no longer requires a book to be open, so
pending uploads from other books are flushed even from the homepage
- load_book() clears unsynced_amap and the indicator timer/state so
stale in-memory state from the previous book is never used
This commit is contained in:
parent
215caf3525
commit
ba5555303e
@ -203,18 +203,32 @@ class DB:
|
||||
self.display_error(error_msg, event)
|
||||
|
||||
def set_pending_annot_upload(self, library_id, book_id, fmt, amap):
|
||||
entry = {'key': 'pending-annot-upload', 'library_id': library_id,
|
||||
'book_id': book_id, 'fmt': fmt, 'amap': amap}
|
||||
key = f'pending-annot-upload:{library_id}/{book_id}/{fmt}'
|
||||
entry = {'key': key, 'library_id': library_id, 'book_id': book_id, 'fmt': fmt, 'amap': amap}
|
||||
self.do_op(['objects'], entry, _('Failed to save pending annotation upload'),
|
||||
def(): None;, op='put')
|
||||
|
||||
def get_pending_annot_upload(self, proceed):
|
||||
self.do_op(['objects'], 'pending-annot-upload',
|
||||
_('Failed to read pending annotation upload'), proceed)
|
||||
def get_all_pending_annot_uploads(self, proceed):
|
||||
transaction = self.idb.transaction(['objects'])
|
||||
store = transaction.objectStore('objects')
|
||||
key_range = IDBKeyRange.bound('pending-annot-upload:', 'pending-annot-upload:\uffff')
|
||||
entries = v'[]'
|
||||
req = store.openCursor(key_range)
|
||||
req.onsuccess = def(event):
|
||||
cursor = event.target.result
|
||||
if cursor:
|
||||
entries.push(cursor.value)
|
||||
cursor.continue()
|
||||
else:
|
||||
proceed(entries)
|
||||
req.onerror = def(event):
|
||||
self.display_error(_('Failed to read pending annotation uploads'), event)
|
||||
proceed(v'[]')
|
||||
|
||||
def clear_pending_annot_upload(self):
|
||||
self.do_op(['objects'], 'pending-annot-upload',
|
||||
_('Failed to clear pending annotation upload'), def(): None;, op='delete')
|
||||
def clear_pending_annot_upload(self, library_id, book_id, fmt, proceed):
|
||||
key = f'pending-annot-upload:{library_id}/{book_id}/{fmt}'
|
||||
self.do_op(['objects'], key,
|
||||
_('Failed to clear pending annotation upload'), proceed or def(): None;, op='delete')
|
||||
|
||||
def get_book(self, library_id, book_id, fmt, metadata, proceed):
|
||||
fmt = fmt.toUpperCase()
|
||||
|
||||
@ -253,6 +253,13 @@ class ReadUI:
|
||||
)
|
||||
|
||||
def load_book(self, library_id, book_id, fmt, metadata, force_reload):
|
||||
self.unsynced_amap = None
|
||||
if self.unsynced_indicator_timer:
|
||||
window.clearTimeout(self.unsynced_indicator_timer)
|
||||
self.unsynced_indicator_timer = None
|
||||
if self.unsynced_indicator_active:
|
||||
self.unsynced_indicator_active = False
|
||||
self.view.show_unsynced_indicator(False)
|
||||
self.base_url_data = {'library_id': library_id, 'book_id':book_id, 'fmt':fmt}
|
||||
if not self.db.initialized:
|
||||
self.pending_load = [book_id, fmt, metadata, force_reload]
|
||||
@ -314,18 +321,42 @@ class ReadUI:
|
||||
self.unsynced_amap = amap
|
||||
self.db.set_pending_annot_upload(library_id, book_id, fmt, amap)
|
||||
self._reschedule_unsynced_indicator()
|
||||
ajax_send(f'book-update-annotations/{library_id}/{book_id}/{fmt}', amap, self._annot_upload_done.bind(self))
|
||||
ajax_send(f'book-update-annotations/{library_id}/{book_id}/{fmt}', amap,
|
||||
self._make_annot_upload_done(library_id, book_id, fmt))
|
||||
|
||||
def _annot_upload_done(self, end_type, xhr, ev):
|
||||
if end_type is 'load':
|
||||
self.unsynced_amap = None
|
||||
self.db.clear_pending_annot_upload()
|
||||
if self.unsynced_indicator_timer:
|
||||
window.clearTimeout(self.unsynced_indicator_timer)
|
||||
self.unsynced_indicator_timer = None
|
||||
if self.unsynced_indicator_active:
|
||||
self.unsynced_indicator_active = False
|
||||
self.view.show_unsynced_indicator(False)
|
||||
def _make_annot_upload_done(self, library_id, book_id, fmt):
|
||||
def done(end_type, xhr, ev):
|
||||
if end_type is 'load':
|
||||
def on_cleared():
|
||||
if (self.unsynced_amap is not None and self.base_url_data
|
||||
and self.base_url_data.library_id is library_id
|
||||
and str(self.base_url_data.book_id) is str(book_id)
|
||||
and self.base_url_data.fmt is fmt):
|
||||
self.unsynced_amap = None
|
||||
if self.unsynced_indicator_timer:
|
||||
window.clearTimeout(self.unsynced_indicator_timer)
|
||||
self.unsynced_indicator_timer = None
|
||||
if self.unsynced_indicator_active:
|
||||
self.unsynced_indicator_active = False
|
||||
self.view.show_unsynced_indicator(False)
|
||||
self._upload_next_from_idb()
|
||||
self.db.clear_pending_annot_upload(library_id, book_id, fmt, on_cleared)
|
||||
return done
|
||||
|
||||
def _upload_next_from_idb(self):
|
||||
self.db.get_all_pending_annot_uploads(def(entries):
|
||||
if not entries.length:
|
||||
return
|
||||
e = entries[0]
|
||||
if (self.base_url_data
|
||||
and self.base_url_data.library_id is e.library_id
|
||||
and str(self.base_url_data.book_id) is str(e.book_id)
|
||||
and self.base_url_data.fmt is e.fmt):
|
||||
self.unsynced_amap = e.amap
|
||||
self._reschedule_unsynced_indicator()
|
||||
ajax_send(f'book-update-annotations/{e.library_id}/{e.book_id}/{e.fmt}', e.amap,
|
||||
self._make_annot_upload_done(e.library_id, e.book_id, e.fmt))
|
||||
)
|
||||
|
||||
def has_unsynced_changes(self):
|
||||
return self.unsynced_indicator_active
|
||||
@ -344,24 +375,30 @@ class ReadUI:
|
||||
book_id = self.base_url_data.book_id
|
||||
fmt = self.base_url_data.fmt
|
||||
amap = self.unsynced_amap
|
||||
def done(end_type, xhr, ev):
|
||||
self._annot_upload_done(end_type, xhr, ev)
|
||||
callback(end_type is 'load')
|
||||
ajax_send(f'book-update-annotations/{library_id}/{book_id}/{fmt}', amap, done)
|
||||
ajax_send(f'book-update-annotations/{library_id}/{book_id}/{fmt}', amap,
|
||||
self._make_annot_upload_done(library_id, book_id, fmt))
|
||||
callback(True)
|
||||
return
|
||||
# unsynced_amap is None — page may have been killed; check IDB for a surviving entry
|
||||
self.db.get_pending_annot_upload(def(entry):
|
||||
if not entry:
|
||||
# unsynced_amap is None — page may have been killed or this is another book;
|
||||
# check IDB for surviving entries
|
||||
self.db.get_all_pending_annot_uploads(def(entries):
|
||||
if not entries.length:
|
||||
callback(True)
|
||||
return
|
||||
self.unsynced_amap = entry.amap
|
||||
self._reschedule_unsynced_indicator()
|
||||
self.upload_pending_annotations(callback)
|
||||
e = entries[0]
|
||||
if (self.base_url_data
|
||||
and self.base_url_data.library_id is e.library_id
|
||||
and str(self.base_url_data.book_id) is str(e.book_id)
|
||||
and self.base_url_data.fmt is e.fmt):
|
||||
self.unsynced_amap = e.amap
|
||||
self._reschedule_unsynced_indicator()
|
||||
ajax_send(f'book-update-annotations/{e.library_id}/{e.book_id}/{e.fmt}', e.amap,
|
||||
self._make_annot_upload_done(e.library_id, e.book_id, e.fmt))
|
||||
callback(True)
|
||||
)
|
||||
|
||||
def _on_network_restored(self):
|
||||
if self.base_url_data:
|
||||
self.upload_pending_annotations(def(ok): None;)
|
||||
self.upload_pending_annotations(def(ok): None;)
|
||||
|
||||
def annotations_synced(self, amap):
|
||||
library_id = self.base_url_data.library_id
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user