mirror of
https://github.com/ankitects/anki.git
synced 2025-12-11 13:56:55 -05:00
update auto-sync code
This commit is contained in:
parent
058ff1b71a
commit
7e221f0acf
5 changed files with 90 additions and 83 deletions
|
|
@ -42,6 +42,7 @@ from aqt.mediasync import MediaSyncer
|
||||||
from aqt.profiles import ProfileManager as ProfileManagerType
|
from aqt.profiles import ProfileManager as ProfileManagerType
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.qt import sip
|
from aqt.qt import sip
|
||||||
|
from aqt.sync import sync_collection, sync_login
|
||||||
from aqt.taskman import TaskManager
|
from aqt.taskman import TaskManager
|
||||||
from aqt.theme import theme_manager
|
from aqt.theme import theme_manager
|
||||||
from aqt.utils import (
|
from aqt.utils import (
|
||||||
|
|
@ -380,13 +381,9 @@ close the profile or restart Anki."""
|
||||||
self.taskman.run_in_background(downgrade, on_done)
|
self.taskman.run_in_background(downgrade, on_done)
|
||||||
|
|
||||||
def loadProfile(self, onsuccess: Optional[Callable] = None) -> None:
|
def loadProfile(self, onsuccess: Optional[Callable] = None) -> None:
|
||||||
self.maybeAutoSync()
|
|
||||||
|
|
||||||
if not self.loadCollection():
|
if not self.loadCollection():
|
||||||
return
|
return
|
||||||
|
|
||||||
self.maybe_auto_sync_media()
|
|
||||||
|
|
||||||
self.pm.apply_profile_options()
|
self.pm.apply_profile_options()
|
||||||
|
|
||||||
# show main window
|
# show main window
|
||||||
|
|
@ -408,17 +405,16 @@ close the profile or restart Anki."""
|
||||||
self.handleImport(self.pendingImport)
|
self.handleImport(self.pendingImport)
|
||||||
self.pendingImport = None
|
self.pendingImport = None
|
||||||
gui_hooks.profile_did_open()
|
gui_hooks.profile_did_open()
|
||||||
if onsuccess:
|
|
||||||
onsuccess()
|
if onsuccess is None:
|
||||||
|
onsuccess = lambda: None
|
||||||
|
self.maybe_auto_sync_on_open_close(onsuccess)
|
||||||
|
|
||||||
def unloadProfile(self, onsuccess: Callable) -> None:
|
def unloadProfile(self, onsuccess: Callable) -> None:
|
||||||
def callback():
|
def callback():
|
||||||
self._unloadProfile()
|
self._unloadProfile()
|
||||||
onsuccess()
|
onsuccess()
|
||||||
|
|
||||||
# start media sync if not already running
|
|
||||||
self.maybe_auto_sync_media()
|
|
||||||
|
|
||||||
gui_hooks.profile_will_close()
|
gui_hooks.profile_will_close()
|
||||||
self.unloadCollection(callback)
|
self.unloadCollection(callback)
|
||||||
|
|
||||||
|
|
@ -433,8 +429,6 @@ close the profile or restart Anki."""
|
||||||
# at this point there should be no windows left
|
# at this point there should be no windows left
|
||||||
self._checkForUnclosedWidgets()
|
self._checkForUnclosedWidgets()
|
||||||
|
|
||||||
self.maybeAutoSync()
|
|
||||||
|
|
||||||
def _checkForUnclosedWidgets(self) -> None:
|
def _checkForUnclosedWidgets(self) -> None:
|
||||||
for w in self.app.topLevelWidgets():
|
for w in self.app.topLevelWidgets():
|
||||||
if w.isVisible():
|
if w.isVisible():
|
||||||
|
|
@ -520,13 +514,16 @@ close the profile or restart Anki."""
|
||||||
self.col.reopen()
|
self.col.reopen()
|
||||||
|
|
||||||
def unloadCollection(self, onsuccess: Callable) -> None:
|
def unloadCollection(self, onsuccess: Callable) -> None:
|
||||||
def callback():
|
def after_sync():
|
||||||
self.setEnabled(False)
|
|
||||||
self.media_syncer.show_diag_until_finished()
|
self.media_syncer.show_diag_until_finished()
|
||||||
self._unloadCollection()
|
self._unloadCollection()
|
||||||
onsuccess()
|
onsuccess()
|
||||||
|
|
||||||
self.closeAllWindows(callback)
|
def before_sync():
|
||||||
|
self.setEnabled(False)
|
||||||
|
self.maybe_auto_sync_on_open_close(after_sync)
|
||||||
|
|
||||||
|
self.closeAllWindows(before_sync)
|
||||||
|
|
||||||
def _unloadCollection(self) -> None:
|
def _unloadCollection(self) -> None:
|
||||||
if not self.col:
|
if not self.col:
|
||||||
|
|
@ -869,52 +866,51 @@ title="%s" %s>%s</button>""" % (
|
||||||
# Syncing
|
# Syncing
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
# expects a current profile and a loaded collection; reloads
|
def on_sync_button_clicked(self):
|
||||||
# collection after sync completes
|
|
||||||
def onSync(self):
|
|
||||||
if self.media_syncer.is_syncing():
|
if self.media_syncer.is_syncing():
|
||||||
self.media_syncer.show_sync_log()
|
self.media_syncer.show_sync_log()
|
||||||
else:
|
else:
|
||||||
self.temp_sync()
|
auth = self.pm.sync_auth()
|
||||||
# self.unloadCollection(self._onSync)
|
if not auth:
|
||||||
|
sync_login(self, self._sync_collection_and_media)
|
||||||
|
else:
|
||||||
|
self._sync_collection_and_media(lambda: None)
|
||||||
|
|
||||||
def _onSync(self):
|
def _sync_collection_and_media(self, after_sync: Callable[[], None]):
|
||||||
self._sync()
|
"Caller should ensure auth available."
|
||||||
if not self.loadCollection():
|
# start media sync if not already running
|
||||||
return
|
if not self.media_syncer.is_syncing():
|
||||||
self.media_syncer.start()
|
self.media_syncer.start()
|
||||||
|
|
||||||
# expects a current profile, but no collection loaded
|
def on_collection_sync_finished():
|
||||||
def maybeAutoSync(self) -> None:
|
self.reset()
|
||||||
if (
|
after_sync()
|
||||||
not self.pm.profile["syncKey"]
|
|
||||||
or not self.pm.profile["autoSync"]
|
|
||||||
or self.safeMode
|
|
||||||
or self.restoringBackup
|
|
||||||
):
|
|
||||||
return
|
|
||||||
|
|
||||||
# ok to sync
|
sync_collection(self, on_done=on_collection_sync_finished)
|
||||||
self._sync()
|
|
||||||
|
def maybe_auto_sync_on_open_close(self, after_sync: Callable[[], None]) -> None:
|
||||||
|
"If disabled, after_sync() is called immediately."
|
||||||
|
if self.can_auto_sync():
|
||||||
|
self._sync_collection_and_media(after_sync)
|
||||||
|
else:
|
||||||
|
after_sync()
|
||||||
|
|
||||||
def maybe_auto_sync_media(self) -> None:
|
def maybe_auto_sync_media(self) -> None:
|
||||||
if not self.pm.profile["autoSync"] or self.safeMode or self.restoringBackup:
|
if self.can_auto_sync():
|
||||||
return
|
return
|
||||||
|
# media_syncer takes care of media syncing preference check
|
||||||
self.media_syncer.start()
|
self.media_syncer.start()
|
||||||
|
|
||||||
|
def can_auto_sync(self) -> bool:
|
||||||
|
return (self.pm.auto_syncing_enabled()
|
||||||
|
and self.pm.sync_auth()
|
||||||
|
and not self.safeMode
|
||||||
|
and not self.restoringBackup)
|
||||||
|
|
||||||
|
# legacy
|
||||||
def _sync(self):
|
def _sync(self):
|
||||||
from aqt.sync import SyncManager
|
pass
|
||||||
|
onSync = on_sync_button_clicked
|
||||||
self.state = "sync"
|
|
||||||
self.app.setQuitOnLastWindowClosed(False)
|
|
||||||
self.syncer = SyncManager(self, self.pm)
|
|
||||||
self.syncer.sync()
|
|
||||||
self.app.setQuitOnLastWindowClosed(True)
|
|
||||||
|
|
||||||
def temp_sync(self):
|
|
||||||
from aqt.sync import sync
|
|
||||||
|
|
||||||
sync(self)
|
|
||||||
|
|
||||||
# Tools
|
# Tools
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
@ -940,7 +936,7 @@ title="%s" %s>%s</button>""" % (
|
||||||
("a", self.onAddCard),
|
("a", self.onAddCard),
|
||||||
("b", self.onBrowse),
|
("b", self.onBrowse),
|
||||||
("t", self.onStats),
|
("t", self.onStats),
|
||||||
("y", self.onSync),
|
("y", self.on_sync_button_clicked),
|
||||||
]
|
]
|
||||||
self.applyShortcuts(globalShortcuts)
|
self.applyShortcuts(globalShortcuts)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ class MediaSyncer:
|
||||||
|
|
||||||
self._log_and_notify(tr(TR.SYNC_MEDIA_STARTING))
|
self._log_and_notify(tr(TR.SYNC_MEDIA_STARTING))
|
||||||
self._syncing = True
|
self._syncing = True
|
||||||
self._progress_timer = self.mw.progress.timer(1000, self._on_progress, True)
|
self._progress_timer = self.mw.progress.timer(1000, self._on_progress, False)
|
||||||
gui_hooks.media_sync_did_start_or_stop(True)
|
gui_hooks.media_sync_did_start_or_stop(True)
|
||||||
|
|
||||||
def run() -> None:
|
def run() -> None:
|
||||||
|
|
|
||||||
|
|
@ -618,6 +618,9 @@ create table if not exists profiles
|
||||||
def media_syncing_enabled(self) -> bool:
|
def media_syncing_enabled(self) -> bool:
|
||||||
return self.profile["syncMedia"]
|
return self.profile["syncMedia"]
|
||||||
|
|
||||||
|
def auto_syncing_enabled(self) -> bool:
|
||||||
|
return self.profile["autoSync"]
|
||||||
|
|
||||||
def sync_auth(self) -> Optional[SyncAuth]:
|
def sync_auth(self) -> Optional[SyncAuth]:
|
||||||
hkey = self.profile.get("syncKey")
|
hkey = self.profile.get("syncKey")
|
||||||
if not hkey:
|
if not hkey:
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,10 @@ from aqt.qt import (
|
||||||
)
|
)
|
||||||
from aqt.utils import askUser, askUserDialog, showText, showWarning, tr
|
from aqt.utils import askUser, askUserDialog, showText, showWarning, tr
|
||||||
|
|
||||||
|
# fixme: catch auth error in other routines, clear sync auth
|
||||||
|
# fixme: sync progress
|
||||||
|
# fixme: curDeck marking collection modified
|
||||||
|
# fixme: show progress immediately
|
||||||
|
|
||||||
class FullSyncChoice(enum.Enum):
|
class FullSyncChoice(enum.Enum):
|
||||||
CANCEL = 0
|
CANCEL = 0
|
||||||
|
|
@ -40,69 +44,73 @@ def get_sync_status(mw: aqt.main.AnkiQt, callback: Callable[[SyncOutput], None])
|
||||||
if not auth:
|
if not auth:
|
||||||
return
|
return
|
||||||
|
|
||||||
def on_done(fut):
|
def on_future_done(fut):
|
||||||
callback(fut.result())
|
callback(fut.result())
|
||||||
|
|
||||||
mw.taskman.run_in_background(lambda: mw.col.backend.sync_status(auth), on_done)
|
mw.taskman.run_in_background(lambda: mw.col.backend.sync_status(auth), on_future_done)
|
||||||
|
|
||||||
|
|
||||||
def sync(mw: aqt.main.AnkiQt) -> None:
|
def sync_collection(mw: aqt.main.AnkiQt, on_done: Callable[[], None]) -> None:
|
||||||
auth = mw.pm.sync_auth()
|
auth = mw.pm.sync_auth()
|
||||||
if not auth:
|
if not auth:
|
||||||
login(mw, on_success=lambda: sync(mw))
|
sync_login(mw, on_success=lambda: sync_collection(mw))
|
||||||
return
|
return
|
||||||
|
|
||||||
def on_done(fut):
|
def on_future_done(fut):
|
||||||
mw.col.db.begin()
|
mw.col.db.begin()
|
||||||
try:
|
try:
|
||||||
out: SyncOutput = fut.result()
|
out: SyncOutput = fut.result()
|
||||||
except InterruptedError:
|
except InterruptedError:
|
||||||
return
|
return on_done()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
showWarning(str(e))
|
showWarning(str(e))
|
||||||
return
|
return on_done()
|
||||||
|
|
||||||
mw.pm.set_host_number(out.host_number)
|
mw.pm.set_host_number(out.host_number)
|
||||||
if out.server_message:
|
if out.server_message:
|
||||||
showText(out.server_message)
|
showText(out.server_message)
|
||||||
if out.required == out.NO_CHANGES:
|
if out.required == out.NO_CHANGES:
|
||||||
# all done
|
# all done
|
||||||
return
|
return on_done()
|
||||||
else:
|
else:
|
||||||
full_sync(mw, out)
|
full_sync(mw, out, on_done)
|
||||||
|
|
||||||
if not mw.col.basicCheck():
|
if not mw.col.basicCheck():
|
||||||
showWarning("Please use Tools>Check Database")
|
showWarning("Please use Tools>Check Database")
|
||||||
return
|
return on_done()
|
||||||
|
|
||||||
mw.col.save(trx=False)
|
mw.col.save(trx=False)
|
||||||
mw.taskman.with_progress(
|
mw.taskman.with_progress(
|
||||||
lambda: mw.col.backend.sync_collection(auth),
|
lambda: mw.col.backend.sync_collection(auth),
|
||||||
on_done,
|
on_future_done,
|
||||||
label=tr(TR.SYNC_CHECKING),
|
label=tr(TR.SYNC_CHECKING),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def full_sync(mw: aqt.main.AnkiQt, out: SyncOutput) -> None:
|
def full_sync(
|
||||||
|
mw: aqt.main.AnkiQt, out: SyncOutput, on_done: Callable[[], None]
|
||||||
|
) -> None:
|
||||||
if out.required == out.FULL_DOWNLOAD:
|
if out.required == out.FULL_DOWNLOAD:
|
||||||
confirm_full_download(mw)
|
confirm_full_download(mw, on_done)
|
||||||
elif out.required == out.FULL_UPLOAD:
|
elif out.required == out.FULL_UPLOAD:
|
||||||
full_upload(mw)
|
full_upload(mw, on_done)
|
||||||
else:
|
else:
|
||||||
choice = ask_user_to_decide_direction()
|
choice = ask_user_to_decide_direction()
|
||||||
if choice == FullSyncChoice.UPLOAD:
|
if choice == FullSyncChoice.UPLOAD:
|
||||||
full_upload(mw)
|
full_upload(mw, on_done)
|
||||||
elif choice == FullSyncChoice.DOWNLOAD:
|
elif choice == FullSyncChoice.DOWNLOAD:
|
||||||
full_download(mw)
|
full_download(mw, on_done)
|
||||||
|
else:
|
||||||
|
on_done()
|
||||||
|
|
||||||
|
|
||||||
def confirm_full_download(mw: aqt.main.AnkiQt) -> None:
|
def confirm_full_download(mw: aqt.main.AnkiQt, on_done: Callable[[], None]) -> None:
|
||||||
# confirmation step required, as some users customize their notetypes
|
# confirmation step required, as some users customize their notetypes
|
||||||
# in an empty collection, then want to upload them
|
# in an empty collection, then want to upload them
|
||||||
if not askUser(tr(TR.SYNC_CONFIRM_EMPTY_DOWNLOAD)):
|
if not askUser(tr(TR.SYNC_CONFIRM_EMPTY_DOWNLOAD)):
|
||||||
return
|
return on_done()
|
||||||
else:
|
else:
|
||||||
mw.closeAllWindows(lambda: full_download(mw))
|
mw.closeAllWindows(lambda: full_download(mw, on_done))
|
||||||
|
|
||||||
|
|
||||||
def on_full_sync_timer(mw: aqt.main.AnkiQt) -> None:
|
def on_full_sync_timer(mw: aqt.main.AnkiQt) -> None:
|
||||||
|
|
@ -119,7 +127,7 @@ def on_full_sync_timer(mw: aqt.main.AnkiQt) -> None:
|
||||||
mw.col.backend.abort_sync()
|
mw.col.backend.abort_sync()
|
||||||
|
|
||||||
|
|
||||||
def full_download(mw: aqt.main.AnkiQt) -> None:
|
def full_download(mw: aqt.main.AnkiQt, on_done: Callable[[], None]) -> None:
|
||||||
mw.col.close_for_full_sync()
|
mw.col.close_for_full_sync()
|
||||||
|
|
||||||
def on_timer():
|
def on_timer():
|
||||||
|
|
@ -129,7 +137,7 @@ def full_download(mw: aqt.main.AnkiQt) -> None:
|
||||||
qconnect(timer.timeout, on_timer)
|
qconnect(timer.timeout, on_timer)
|
||||||
timer.start(150)
|
timer.start(150)
|
||||||
|
|
||||||
def on_done(fut):
|
def on_future_done(fut):
|
||||||
timer.stop()
|
timer.stop()
|
||||||
mw.col.reopen(after_full_sync=True)
|
mw.col.reopen(after_full_sync=True)
|
||||||
mw.reset()
|
mw.reset()
|
||||||
|
|
@ -137,16 +145,16 @@ def full_download(mw: aqt.main.AnkiQt) -> None:
|
||||||
fut.result()
|
fut.result()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
showWarning(str(e))
|
showWarning(str(e))
|
||||||
return
|
return on_done()
|
||||||
|
|
||||||
mw.taskman.with_progress(
|
mw.taskman.with_progress(
|
||||||
lambda: mw.col.backend.full_download(mw.pm.sync_auth()),
|
lambda: mw.col.backend.full_download(mw.pm.sync_auth()),
|
||||||
on_done,
|
on_future_done,
|
||||||
label=tr(TR.SYNC_DOWNLOADING_FROM_ANKIWEB),
|
label=tr(TR.SYNC_DOWNLOADING_FROM_ANKIWEB),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def full_upload(mw: aqt.main.AnkiQt) -> None:
|
def full_upload(mw: aqt.main.AnkiQt, on_done: Callable[[], None]) -> None:
|
||||||
mw.col.close_for_full_sync()
|
mw.col.close_for_full_sync()
|
||||||
|
|
||||||
def on_timer():
|
def on_timer():
|
||||||
|
|
@ -156,7 +164,7 @@ def full_upload(mw: aqt.main.AnkiQt) -> None:
|
||||||
qconnect(timer.timeout, on_timer)
|
qconnect(timer.timeout, on_timer)
|
||||||
timer.start(150)
|
timer.start(150)
|
||||||
|
|
||||||
def on_done(fut):
|
def on_future_done(fut):
|
||||||
timer.stop()
|
timer.stop()
|
||||||
mw.col.reopen(after_full_sync=True)
|
mw.col.reopen(after_full_sync=True)
|
||||||
mw.reset()
|
mw.reset()
|
||||||
|
|
@ -164,16 +172,15 @@ def full_upload(mw: aqt.main.AnkiQt) -> None:
|
||||||
fut.result()
|
fut.result()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
showWarning(str(e))
|
showWarning(str(e))
|
||||||
return
|
return on_done()
|
||||||
|
|
||||||
mw.taskman.with_progress(
|
mw.taskman.with_progress(
|
||||||
lambda: mw.col.backend.full_upload(mw.pm.sync_auth()),
|
lambda: mw.col.backend.full_upload(mw.pm.sync_auth()),
|
||||||
on_done,
|
on_future_done,
|
||||||
label=tr(TR.SYNC_UPLOADING_TO_ANKIWEB),
|
label=tr(TR.SYNC_UPLOADING_TO_ANKIWEB),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def sync_login(
|
||||||
def login(
|
|
||||||
mw: aqt.main.AnkiQt, on_success: Callable[[], None], username="", password=""
|
mw: aqt.main.AnkiQt, on_success: Callable[[], None], username="", password=""
|
||||||
) -> None:
|
) -> None:
|
||||||
while True:
|
while True:
|
||||||
|
|
@ -183,13 +190,13 @@ def login(
|
||||||
if username and password:
|
if username and password:
|
||||||
break
|
break
|
||||||
|
|
||||||
def on_done(fut):
|
def on_future_done(fut):
|
||||||
try:
|
try:
|
||||||
auth = fut.result()
|
auth = fut.result()
|
||||||
except SyncError as e:
|
except SyncError as e:
|
||||||
if e.kind() == SyncErrorKind.AUTH_FAILED:
|
if e.kind() == SyncErrorKind.AUTH_FAILED:
|
||||||
showWarning(str(e))
|
showWarning(str(e))
|
||||||
login(mw, on_success, username, password)
|
sync_login(mw, on_success, username, password)
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
showWarning(str(e))
|
showWarning(str(e))
|
||||||
|
|
@ -202,7 +209,8 @@ def login(
|
||||||
on_success()
|
on_success()
|
||||||
|
|
||||||
mw.taskman.with_progress(
|
mw.taskman.with_progress(
|
||||||
lambda: mw.col.backend.sync_login(username=username, password=password), on_done
|
lambda: mw.col.backend.sync_login(username=username, password=password),
|
||||||
|
on_future_done,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -175,7 +175,7 @@ class Toolbar:
|
||||||
self.mw.onStats()
|
self.mw.onStats()
|
||||||
|
|
||||||
def _syncLinkHandler(self) -> None:
|
def _syncLinkHandler(self) -> None:
|
||||||
self.mw.onSync()
|
self.mw.on_sync_button_clicked()
|
||||||
|
|
||||||
# HTML & CSS
|
# HTML & CSS
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue