Add ClosedCollectionOp

This commit is contained in:
RumovZ 2022-04-22 16:54:02 +02:00
parent 2491b966b5
commit 50c8267c15
3 changed files with 95 additions and 82 deletions

View file

@ -15,7 +15,7 @@ from anki.errors import Interrupted
from anki.importing.anki2 import V2ImportIntoV1 from anki.importing.anki2 import V2ImportIntoV1
from anki.importing.apkg import AnkiPackageImporter from anki.importing.apkg import AnkiPackageImporter
from aqt.main import AnkiQt, gui_hooks from aqt.main import AnkiQt, gui_hooks
from aqt.operations import QueryOp from aqt.operations import ClosedCollectionOpWithBackendProgress
from aqt.qt import * from aqt.qt import *
from aqt.utils import ( from aqt.utils import (
HelpPage, HelpPage,
@ -434,78 +434,36 @@ def setupApkgImport(mw: AnkiQt, importer: AnkiPackageImporter) -> bool:
if not full: if not full:
# adding # adding
return True return True
if not askUser( if askUser(
tr.importing_this_will_delete_your_existing_collection(), tr.importing_this_will_delete_your_existing_collection(),
msgfunc=QMessageBox.warning, msgfunc=QMessageBox.warning,
defaultno=True, defaultno=True,
): ):
return False run_full_apkg_import(mw, importer.file)
full_apkg_import(mw, importer.file)
return False return False
def full_apkg_import(mw: AnkiQt, file: str) -> None: def run_full_apkg_import(mw: AnkiQt, file: str) -> None:
def on_done(success: bool) -> None: def on_success(_future: Future) -> None:
mw.loadCollection() mw.loadCollection()
if success: tooltip(tr.importing_importing_complete())
tooltip(tr.importing_importing_complete())
def after_backup(created: bool) -> None: def on_failure(err: Exception) -> None:
mw.unloadCollection(lambda: replace_with_apkg(mw, file, on_done)) mw.loadCollection()
if not isinstance(err, Interrupted):
showWarning(str(err))
QueryOp( ClosedCollectionOpWithBackendProgress(
parent=mw, op=lambda _: mw.create_backup_now(), success=after_backup parent=mw,
).with_progress().run_in_background() op=lambda: full_apkg_import(mw, file),
key="importing",
).success(on_success).failure(on_failure).run_in_background()
def replace_with_apkg( def full_apkg_import(mw: AnkiQt, file: str) -> None:
mw: AnkiQt, filename: str, callback: Callable[[bool], None] col_path = mw.pm.collectionPath()
) -> None: media_folder = os.path.join(mw.pm.profileFolder(), "collection.media")
"""Tries to replace the provided collection with the provided backup, mw.backend.import_collection_package(
then calls the callback. True if success. col_path=col_path, backup_path=file, media_folder=media_folder
""" )
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)

View file

@ -50,7 +50,7 @@ from aqt.flags import FlagManager
from aqt.legacy import install_pylib_legacy from aqt.legacy import install_pylib_legacy
from aqt.mediacheck import check_media_db from aqt.mediacheck import check_media_db
from aqt.mediasync import MediaSyncer 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.collection import redo, undo
from aqt.operations.deck import set_current_deck from aqt.operations.deck import set_current_deck
from aqt.profiles import ProfileManager as ProfileManagerType from aqt.profiles import ProfileManager as ProfileManagerType
@ -402,15 +402,20 @@ class AnkiQt(QMainWindow):
) )
def _openBackup(self, path: str) -> None: 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 import aqt.importing
self.restoring_backup = True self.restoring_backup = True
showInfo(tr.qt_misc_automatic_syncing_and_backups_have_been()) 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: def _on_downgrade(self) -> None:
self.progress.start() self.progress.start()

View file

@ -17,7 +17,7 @@ from anki.collection import (
OpChangesWithId, OpChangesWithId,
) )
from aqt.errors import show_exception 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 from aqt.utils import showWarning
@ -114,10 +114,7 @@ class CollectionOp(Generic[ResultWithChanges]):
if self._success: if self._success:
self._success(result) self._success(result)
finally: finally:
mw.update_undo_actions() self._finish_op(mw, result, initiator)
mw.autosave()
# fire change hooks
self._fire_change_hooks_after_op_performed(result, initiator)
self._run(mw, wrapped_op, wrapped_done) self._run(mw, wrapped_op, wrapped_done)
@ -129,6 +126,14 @@ class CollectionOp(Generic[ResultWithChanges]):
) -> None: ) -> None:
mw.taskman.with_progress(op, on_done) 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( def _fire_change_hooks_after_op_performed(
self, self,
result: ResultWithChanges, result: ResultWithChanges,
@ -257,14 +262,15 @@ class CollectionOpWithBackendProgress(CollectionOp):
self, self,
parent: QWidget, parent: QWidget,
op: Callable[[Collection], ResultWithChanges], op: Callable[[Collection], ResultWithChanges],
*, *args: Any,
key: Literal["importing"], key: Literal["importing"],
**kwargs: Any,
): ):
self._key = key self._key = key
self.timer = QTimer() self.timer = QTimer()
self.timer.setSingleShot(False) self.timer.setSingleShot(False)
self.timer.setInterval(100) self.timer.setInterval(100)
super().__init__(parent, op) super().__init__(parent, op, *args, **kwargs)
def _run( def _run(
self, self,
@ -283,12 +289,8 @@ class CollectionOpWithBackendProgress(CollectionOp):
return return
label = getattr(progress, self._key) label = getattr(progress, self._key)
try: if dialog and dialog.wantCancel:
if dialog.wantCancel: mw.backend.set_wants_abort()
mw.backend.set_wants_abort()
except AttributeError:
# dialog may not be active
pass
mw.taskman.run_on_main(lambda: mw.progress.update(label=label)) mw.taskman.run_on_main(lambda: mw.progress.update(label=label))
@ -301,3 +303,51 @@ class CollectionOpWithBackendProgress(CollectionOp):
qconnect(self.timer.timeout, on_progress) qconnect(self.timer.timeout, on_progress)
self.timer.start() self.timer.start()
mw.taskman.run_in_background(task=op, on_done=wrapped_on_done) 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."""