move more logic into mediasync.py, handle auth errors

This commit is contained in:
Damien Elmes 2020-02-04 12:26:10 +10:00
parent 347ac80086
commit 93c768cab9
3 changed files with 69 additions and 75 deletions

View File

@ -84,7 +84,7 @@ class AnkiQt(QMainWindow):
self.opts = opts self.opts = opts
self.col: Optional[_Collection] = None self.col: Optional[_Collection] = None
self.taskman = TaskManager() self.taskman = TaskManager()
self.media_syncer = MediaSyncer(self.taskman, self._on_media_sync_start_stop) self.media_syncer = MediaSyncer(self)
aqt.mw = self aqt.mw = self
self.app = app self.app = app
self.pm = profileManager self.pm = profileManager
@ -833,7 +833,7 @@ title="%s" %s>%s</button>""" % (
# collection after sync completes # collection after sync completes
def onSync(self): def onSync(self):
if self.media_syncer.is_syncing(): if self.media_syncer.is_syncing():
self._show_sync_log() self.media_syncer.show_sync_log()
else: else:
self.unloadCollection(self._onSync) self.unloadCollection(self._onSync)
@ -841,7 +841,7 @@ title="%s" %s>%s</button>""" % (
self._sync() self._sync()
if not self.loadCollection(): if not self.loadCollection():
return return
self._sync_media() self.media_syncer.start()
# expects a current profile, but no collection loaded # expects a current profile, but no collection loaded
def maybeAutoSync(self) -> None: def maybeAutoSync(self) -> None:
@ -863,31 +863,6 @@ title="%s" %s>%s</button>""" % (
self.syncer = SyncManager(self, self.pm) self.syncer = SyncManager(self, self.pm)
self.syncer.sync() self.syncer.sync()
# fixme: self.pm.profile["syncMedia"]
# fixme: mediaSanity
# fixme: corruptMediaDB
# fixme: hkey
# fixme: shard
# fixme: dialog
# fixme: autosync
# elif evt == "mediaSanity":
# showWarning(
# _(
# """\
# A problem occurred while syncing media. Please use Tools>Check Media, then \
# sync again to correct the issue."""
# )
# )
def _sync_media(self):
self.media_syncer.start(self.col, self.pm.sync_key(), None)
def _on_media_sync_start_stop(self):
self.toolbar.set_sync_active(self.media_syncer.is_syncing())
def _show_sync_log(self):
aqt.dialogs.open("sync_log", self, self.media_syncer)
# Tools # Tools
########################################################################## ##########################################################################

View File

@ -7,14 +7,14 @@ import time
from concurrent.futures import Future from concurrent.futures import Future
from copy import copy from copy import copy
from dataclasses import dataclass from dataclasses import dataclass
from typing import Callable, List, Optional, Union from typing import List, Optional, Union
import anki
import aqt import aqt
from anki import hooks from anki import hooks
from anki.lang import _ from anki.lang import _
from anki.media import media_paths_from_col_path from anki.media import media_paths_from_col_path
from anki.rsbackend import ( from anki.rsbackend import (
AnkiWebAuthFailed,
Interrupted, Interrupted,
MediaSyncDownloadedChanges, MediaSyncDownloadedChanges,
MediaSyncDownloadedFiles, MediaSyncDownloadedFiles,
@ -28,7 +28,7 @@ from anki.types import assert_impossible
from anki.utils import intTime from anki.utils import intTime
from aqt import gui_hooks from aqt import gui_hooks
from aqt.qt import QDialog, QDialogButtonBox, QPushButton from aqt.qt import QDialog, QDialogButtonBox, QPushButton
from aqt.taskman import TaskManager from aqt.utils import showInfo
@dataclass @dataclass
@ -40,30 +40,24 @@ class MediaSyncState:
removed_files: int = 0 removed_files: int = 0
# fixme: make sure we don't run twice
# fixme: handle auth errors
# fixme: handle network errors # fixme: handle network errors
# fixme: show progress in UI
# fixme: abort when closing collection/app # fixme: abort when closing collection/app
# fixme: handle no hkey
# fixme: shards # fixme: shards
# fixme: dialog should be a singleton # fixme: concurrent modifications during upload step
# fixme: abort button should not be default # fixme: mediaSanity
# fixme: corruptMediaDB
# fixme: autosync
# elif evt == "mediaSanity":
# showWarning(
# _(
# """\
# A problem occurred while syncing media. Please use Tools>Check Media, then \
# sync again to correct the issue."""
# )
# )
class SyncBegun: LogEntry = Union[MediaSyncState, str]
pass
class SyncEnded:
pass
class SyncAborted:
pass
LogEntry = Union[MediaSyncState, SyncBegun, SyncEnded, SyncAborted]
@dataclass @dataclass
@ -73,12 +67,11 @@ class LogEntryWithTime:
class MediaSyncer: class MediaSyncer:
def __init__(self, taskman: TaskManager, on_start_stop: Callable[[], None]): def __init__(self, mw: aqt.main.AnkiQt):
self._taskman = taskman self.mw = mw
self._sync_state: Optional[MediaSyncState] = None self._sync_state: Optional[MediaSyncState] = None
self._log: List[LogEntryWithTime] = [] self._log: List[LogEntryWithTime] = []
self._want_stop = False self._want_stop = False
self._on_start_stop = on_start_stop
hooks.rust_progress_callback.append(self._on_rust_progress) hooks.rust_progress_callback.append(self._on_rust_progress)
def _on_rust_progress(self, proceed: bool, progress: Progress) -> bool: def _on_rust_progress(self, proceed: bool, progress: Progress) -> bool:
@ -104,14 +97,22 @@ class MediaSyncer:
elif isinstance(progress, MediaSyncRemovedFiles): elif isinstance(progress, MediaSyncRemovedFiles):
self._sync_state.removed_files += progress.files self._sync_state.removed_files += progress.files
def start( def start(self) -> None:
self, col: anki.storage._Collection, hkey: str, shard: Optional[int]
) -> None:
"Start media syncing in the background, if it's not already running." "Start media syncing in the background, if it's not already running."
if self._sync_state is not None: if self._sync_state is not None:
return return
self._log_and_notify(SyncBegun()) hkey = self.mw.pm.sync_key()
if hkey is None:
return
if not self.mw.pm.media_syncing_enabled():
self._log_and_notify(_("Media syncing disabled."))
return
shard = None
self._log_and_notify(_("Media sync starting..."))
self._sync_state = MediaSyncState() self._sync_state = MediaSyncState()
self._want_stop = False self._want_stop = False
self._on_start_stop() self._on_start_stop()
@ -122,17 +123,17 @@ class MediaSyncer:
shard_str = "" shard_str = ""
endpoint = f"https://sync{shard_str}ankiweb.net" endpoint = f"https://sync{shard_str}ankiweb.net"
(media_folder, media_db) = media_paths_from_col_path(col.path) (media_folder, media_db) = media_paths_from_col_path(self.mw.col.path)
def run() -> None: def run() -> None:
col.backend.sync_media(hkey, media_folder, media_db, endpoint) self.mw.col.backend.sync_media(hkey, media_folder, media_db, endpoint)
self._taskman.run_in_background(run, self._on_finished) self.mw.taskman.run_in_background(run, self._on_finished)
def _log_and_notify(self, entry: LogEntry) -> None: def _log_and_notify(self, entry: LogEntry) -> None:
entry_with_time = LogEntryWithTime(time=intTime(), entry=entry) entry_with_time = LogEntryWithTime(time=intTime(), entry=entry)
self._log.append(entry_with_time) self._log.append(entry_with_time)
self._taskman.run_on_main( self.mw.taskman.run_on_main(
lambda: gui_hooks.media_sync_did_progress(entry_with_time) lambda: gui_hooks.media_sync_did_progress(entry_with_time)
) )
@ -142,22 +143,38 @@ class MediaSyncer:
exc = future.exception() exc = future.exception()
if exc is not None: if exc is not None:
if isinstance(exc, Interrupted): self._handle_sync_error(exc)
self._log_and_notify(SyncAborted())
else:
raise exc
else: else:
self._log_and_notify(SyncEnded()) self._log_and_notify(_("Media sync complete."))
def _handle_sync_error(self, exc: BaseException):
if isinstance(exc, AnkiWebAuthFailed):
self.mw.pm.set_sync_key(None)
self._log_and_notify(_("Authentication failed."))
showInfo(_("AnkiWeb ID or password was incorrect; please try again."))
elif isinstance(exc, Interrupted):
self._log_and_notify(_("Media sync aborted."))
else:
raise exc
def entries(self) -> List[LogEntryWithTime]: def entries(self) -> List[LogEntryWithTime]:
return self._log return self._log
def abort(self) -> None: def abort(self) -> None:
if not self.is_syncing():
return
self._log_and_notify(_("Media sync aborting..."))
self._want_stop = True self._want_stop = True
def is_syncing(self) -> bool: def is_syncing(self) -> bool:
return self._sync_state is not None return self._sync_state is not None
def _on_start_stop(self):
self.mw.toolbar.set_sync_active(self.is_syncing())
def show_sync_log(self):
aqt.dialogs.open("sync_log", self.mw, self)
class MediaSyncDialog(QDialog): class MediaSyncDialog(QDialog):
silentlyClose = True silentlyClose = True
@ -170,6 +187,7 @@ class MediaSyncDialog(QDialog):
self.form.setupUi(self) self.form.setupUi(self)
self.abort_button = QPushButton(_("Abort")) self.abort_button = QPushButton(_("Abort"))
self.abort_button.clicked.connect(self._on_abort) # type: ignore self.abort_button.clicked.connect(self._on_abort) # type: ignore
self.abort_button.setAutoDefault(False)
self.form.buttonBox.addButton(self.abort_button, QDialogButtonBox.ActionRole) self.form.buttonBox.addButton(self.abort_button, QDialogButtonBox.ActionRole)
gui_hooks.media_sync_did_progress.append(self._on_log_entry) gui_hooks.media_sync_did_progress.append(self._on_log_entry)
@ -191,9 +209,6 @@ class MediaSyncDialog(QDialog):
self.show() self.show()
def _on_abort(self, *args) -> None: def _on_abort(self, *args) -> None:
self.form.plainTextEdit.appendPlainText(
self._time_and_text(intTime(), _("Aborting..."))
)
self._syncer.abort() self._syncer.abort()
self.abort_button.setHidden(True) self.abort_button.setHidden(True)
@ -202,12 +217,8 @@ class MediaSyncDialog(QDialog):
return f"{asctime}: {text}" return f"{asctime}: {text}"
def _entry_to_text(self, entry: LogEntryWithTime): def _entry_to_text(self, entry: LogEntryWithTime):
if isinstance(entry.entry, SyncBegun): if isinstance(entry.entry, str):
txt = _("Media sync starting...") txt = entry.entry
elif isinstance(entry.entry, SyncEnded):
txt = _("Media sync complete.")
elif isinstance(entry.entry, SyncAborted):
txt = _("Aborted.")
elif isinstance(entry.entry, MediaSyncState): elif isinstance(entry.entry, MediaSyncState):
txt = self._logentry_to_text(entry.entry) txt = self._logentry_to_text(entry.entry)
else: else:
@ -227,3 +238,5 @@ class MediaSyncDialog(QDialog):
def _on_log_entry(self, entry: LogEntryWithTime): def _on_log_entry(self, entry: LogEntryWithTime):
self.form.plainTextEdit.appendPlainText(self._entry_to_text(entry)) self.form.plainTextEdit.appendPlainText(self._entry_to_text(entry))
if not self._syncer.is_syncing():
self.abort_button.setHidden(True)

View File

@ -515,6 +515,12 @@ please see:
def sync_key(self) -> Optional[str]: def sync_key(self) -> Optional[str]:
return self.profile.get("syncKey") return self.profile.get("syncKey")
def set_sync_key(self, val: Optional[str]) -> None:
self.profile["syncKey"] = val
def media_syncing_enabled(self) -> bool:
return self.profile["syncMedia"]
###################################################################### ######################################################################
def apply_profile_options(self) -> None: def apply_profile_options(self) -> None: