From 50c8267c15c61667335db4ba9ec289bb84f27035 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Fri, 22 Apr 2022 16:54:02 +0200 Subject: [PATCH] Add ClosedCollectionOp --- qt/aqt/importing.py | 84 +++++++++-------------------------- qt/aqt/main.py | 17 ++++--- qt/aqt/operations/__init__.py | 76 +++++++++++++++++++++++++------ 3 files changed, 95 insertions(+), 82 deletions(-) diff --git a/qt/aqt/importing.py b/qt/aqt/importing.py index 0168d2c9c..1e94ac740 100644 --- a/qt/aqt/importing.py +++ b/qt/aqt/importing.py @@ -15,7 +15,7 @@ from anki.errors import Interrupted from anki.importing.anki2 import V2ImportIntoV1 from anki.importing.apkg import AnkiPackageImporter from aqt.main import AnkiQt, gui_hooks -from aqt.operations import QueryOp +from aqt.operations import ClosedCollectionOpWithBackendProgress from aqt.qt import * from aqt.utils import ( HelpPage, @@ -434,78 +434,36 @@ def setupApkgImport(mw: AnkiQt, importer: AnkiPackageImporter) -> bool: if not full: # adding return True - if not askUser( + if askUser( tr.importing_this_will_delete_your_existing_collection(), msgfunc=QMessageBox.warning, defaultno=True, ): - return False + run_full_apkg_import(mw, importer.file) - full_apkg_import(mw, importer.file) return False -def full_apkg_import(mw: AnkiQt, file: str) -> None: - def on_done(success: bool) -> None: +def run_full_apkg_import(mw: AnkiQt, file: str) -> None: + def on_success(_future: Future) -> None: mw.loadCollection() - if success: - tooltip(tr.importing_importing_complete()) + tooltip(tr.importing_importing_complete()) - def after_backup(created: bool) -> None: - mw.unloadCollection(lambda: replace_with_apkg(mw, file, on_done)) + def on_failure(err: Exception) -> None: + mw.loadCollection() + if not isinstance(err, Interrupted): + showWarning(str(err)) - QueryOp( - parent=mw, op=lambda _: mw.create_backup_now(), success=after_backup - ).with_progress().run_in_background() + ClosedCollectionOpWithBackendProgress( + parent=mw, + op=lambda: full_apkg_import(mw, file), + key="importing", + ).success(on_success).failure(on_failure).run_in_background() -def replace_with_apkg( - mw: AnkiQt, filename: str, callback: Callable[[bool], None] -) -> None: - """Tries to replace the provided collection with the provided backup, - then calls the callback. True if success. - """ - if not (dialog := mw.progress.start(immediate=True)): - print("No progress dialog during import; aborting will not work") - timer = QTimer() - timer.setSingleShot(False) - timer.setInterval(100) - - def on_progress() -> None: - progress = mw.backend.latest_progress() - if not progress.HasField("importing"): - return - label = progress.importing - - try: - if dialog.wantCancel: - mw.backend.set_wants_abort() - except AttributeError: - # dialog may not be active - pass - - mw.taskman.run_on_main(lambda: mw.progress.update(label=label)) - - def do_import() -> None: - col_path = mw.pm.collectionPath() - media_folder = os.path.join(mw.pm.profileFolder(), "collection.media") - mw.backend.import_collection_package( - col_path=col_path, backup_path=filename, media_folder=media_folder - ) - - def on_done(future: Future) -> None: - mw.progress.finish() - timer.deleteLater() - - try: - future.result() - except Exception as error: - if not isinstance(error, Interrupted): - showWarning(str(error)) - callback(False) - else: - callback(True) - - qconnect(timer.timeout, on_progress) - timer.start() - mw.taskman.run_in_background(do_import, on_done) +def full_apkg_import(mw: AnkiQt, file: str) -> None: + col_path = mw.pm.collectionPath() + media_folder = os.path.join(mw.pm.profileFolder(), "collection.media") + mw.backend.import_collection_package( + col_path=col_path, backup_path=file, media_folder=media_folder + ) diff --git a/qt/aqt/main.py b/qt/aqt/main.py index e3b1c5a2c..8b530c07a 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -50,7 +50,7 @@ from aqt.flags import FlagManager from aqt.legacy import install_pylib_legacy from aqt.mediacheck import check_media_db from aqt.mediasync import MediaSyncer -from aqt.operations import QueryOp +from aqt.operations import ClosedCollectionOpWithBackendProgress, QueryOp from aqt.operations.collection import redo, undo from aqt.operations.deck import set_current_deck from aqt.profiles import ProfileManager as ProfileManagerType @@ -402,15 +402,20 @@ class AnkiQt(QMainWindow): ) def _openBackup(self, path: str) -> None: - def on_done(success: bool) -> None: - if success: - self.onOpenProfile(callback=lambda: self.col.mod_schema(check=False)) - import aqt.importing self.restoring_backup = True showInfo(tr.qt_misc_automatic_syncing_and_backups_have_been()) - aqt.importing.replace_with_apkg(self, path, on_done) + + ClosedCollectionOpWithBackendProgress( + parent=self, + op=lambda: aqt.importing.full_apkg_import(self, path), + key="importing", + ).success( + lambda _: self.onOpenProfile( + callback=lambda: self.col.mod_schema(check=False) + ) + ).run_in_background() def _on_downgrade(self) -> None: self.progress.start() diff --git a/qt/aqt/operations/__init__.py b/qt/aqt/operations/__init__.py index 675f49691..95d270f97 100644 --- a/qt/aqt/operations/__init__.py +++ b/qt/aqt/operations/__init__.py @@ -17,7 +17,7 @@ from anki.collection import ( OpChangesWithId, ) from aqt.errors import show_exception -from aqt.qt import QWidget, QTimer, qconnect +from aqt.qt import QTimer, QWidget, qconnect from aqt.utils import showWarning @@ -114,10 +114,7 @@ class CollectionOp(Generic[ResultWithChanges]): if self._success: self._success(result) finally: - mw.update_undo_actions() - mw.autosave() - # fire change hooks - self._fire_change_hooks_after_op_performed(result, initiator) + self._finish_op(mw, result, initiator) self._run(mw, wrapped_op, wrapped_done) @@ -129,6 +126,14 @@ class CollectionOp(Generic[ResultWithChanges]): ) -> None: mw.taskman.with_progress(op, on_done) + def _finish_op( + self, mw: aqt.main.AnkiQt, result: ResultWithChanges, initiator: object | None + ) -> None: + mw.update_undo_actions() + mw.autosave() + # fire change hooks + self._fire_change_hooks_after_op_performed(result, initiator) + def _fire_change_hooks_after_op_performed( self, result: ResultWithChanges, @@ -257,14 +262,15 @@ class CollectionOpWithBackendProgress(CollectionOp): self, parent: QWidget, op: Callable[[Collection], ResultWithChanges], - *, + *args: Any, key: Literal["importing"], + **kwargs: Any, ): self._key = key self.timer = QTimer() self.timer.setSingleShot(False) self.timer.setInterval(100) - super().__init__(parent, op) + super().__init__(parent, op, *args, **kwargs) def _run( self, @@ -283,12 +289,8 @@ class CollectionOpWithBackendProgress(CollectionOp): return label = getattr(progress, self._key) - try: - if dialog.wantCancel: - mw.backend.set_wants_abort() - except AttributeError: - # dialog may not be active - pass + if dialog and dialog.wantCancel: + mw.backend.set_wants_abort() mw.taskman.run_on_main(lambda: mw.progress.update(label=label)) @@ -301,3 +303,51 @@ class CollectionOpWithBackendProgress(CollectionOp): qconnect(self.timer.timeout, on_progress) self.timer.start() mw.taskman.run_in_background(task=op, on_done=wrapped_on_done) + + +class ClosedCollectionOp(CollectionOp): + """For CollectionOps that need to be run on a closed collection. + + If a collection is open, backs it up and unloads it, before running the op. + Reloads it, if that has not been done by a callback, yet. + """ + + def __init__( + self, + parent: QWidget, + op: Callable[[], ResultWithChanges], + *args: Any, + **kwargs: Any, + ): + super().__init__(parent, lambda _: op(), *args, **kwargs) + + def _run( + self, + mw: aqt.main.AnkiQt, + op: Callable[[], ResultWithChanges], + on_done: Callable[[Future], None], + ) -> None: + if mw.col: + QueryOp( + parent=mw, + op=lambda _: mw.create_backup_now(), + success=lambda _: mw.unloadCollection( + lambda: super(ClosedCollectionOp, self)._run(mw, op, on_done) + ), + ).with_progress().run_in_background() + else: + super()._run(mw, op, on_done) + + def _finish_op( + self, + _mw: aqt.main.AnkiQt, + _result: ResultWithChanges, + _initiator: object | None, + ) -> None: + pass + + +class ClosedCollectionOpWithBackendProgress( + ClosedCollectionOp, CollectionOpWithBackendProgress +): + """See ClosedCollectionOp and CollectionOpWithBackendProgress."""