mirror of
https://github.com/ankitects/anki.git
synced 2025-11-30 08:27:12 -05:00
Add QueryOpWithBackendProgress
Also support backend exporting progress.
This commit is contained in:
parent
4137b1e6e6
commit
c237d4c969
1 changed files with 57 additions and 32 deletions
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from abc import ABC
|
||||||
from concurrent.futures._base import Future
|
from concurrent.futures._base import Future
|
||||||
from typing import Any, Callable, Generic, Literal, Protocol, TypeVar, Union
|
from typing import Any, Callable, Generic, Literal, Protocol, TypeVar, Union
|
||||||
|
|
||||||
|
|
@ -16,10 +17,11 @@ from anki.collection import (
|
||||||
OpChangesAfterUndo,
|
OpChangesAfterUndo,
|
||||||
OpChangesWithCount,
|
OpChangesWithCount,
|
||||||
OpChangesWithId,
|
OpChangesWithId,
|
||||||
|
Progress,
|
||||||
)
|
)
|
||||||
from aqt.errors import show_exception
|
from aqt.errors import show_exception
|
||||||
from aqt.qt import QTimer, QWidget, qconnect
|
from aqt.qt import QTimer, QWidget, qconnect
|
||||||
from aqt.utils import showWarning
|
from aqt.utils import showWarning, tr
|
||||||
|
|
||||||
|
|
||||||
class HasChangesProperty(Protocol):
|
class HasChangesProperty(Protocol):
|
||||||
|
|
@ -133,7 +135,6 @@ class CollectionOp(Generic[ResultWithChanges]):
|
||||||
) -> None:
|
) -> None:
|
||||||
mw.update_undo_actions()
|
mw.update_undo_actions()
|
||||||
mw.autosave()
|
mw.autosave()
|
||||||
# fire change hooks
|
|
||||||
self._fire_change_hooks_after_op_performed(result, initiator)
|
self._fire_change_hooks_after_op_performed(result, initiator)
|
||||||
|
|
||||||
def _fire_change_hooks_after_op_performed(
|
def _fire_change_hooks_after_op_performed(
|
||||||
|
|
@ -252,61 +253,85 @@ class QueryOp(Generic[T]):
|
||||||
|
|
||||||
self._success(future.result())
|
self._success(future.result())
|
||||||
|
|
||||||
mw.taskman.run_in_background(wrapped_op, wrapped_done)
|
self._run(mw, wrapped_op, wrapped_done)
|
||||||
|
|
||||||
|
|
||||||
class CollectionOpWithBackendProgress(CollectionOp):
|
|
||||||
"""Periodically queries the backend for progress updates, and enables abortion.
|
|
||||||
|
|
||||||
Requires a key for a string value on the `Progress` proto message."""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
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, *args, **kwargs)
|
|
||||||
|
|
||||||
def _run(
|
def _run(
|
||||||
self,
|
self,
|
||||||
mw: aqt.main.AnkiQt,
|
mw: aqt.main.AnkiQt,
|
||||||
op: Callable[[], ResultWithChanges],
|
op: Callable[[], T],
|
||||||
on_done: Callable[[Future], None],
|
on_done: Callable[[Future], None],
|
||||||
) -> None:
|
) -> None:
|
||||||
if not (dialog := mw.progress.start(immediate=True)):
|
mw.taskman.run_in_background(op, on_done)
|
||||||
|
|
||||||
|
|
||||||
|
class OpWithBackendProgress(ABC):
|
||||||
|
"""Periodically queries the backend for progress updates, and enables abortion.
|
||||||
|
|
||||||
|
Requires a key for a value on the `Progress` proto message."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*args: Any,
|
||||||
|
key: Literal["importing", "exporting"],
|
||||||
|
**kwargs: Any,
|
||||||
|
):
|
||||||
|
self._key = key
|
||||||
|
self._timer = QTimer()
|
||||||
|
self._timer.setSingleShot(False)
|
||||||
|
self._timer.setInterval(100)
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def _run(
|
||||||
|
self,
|
||||||
|
mw: aqt.main.AnkiQt,
|
||||||
|
op: Callable,
|
||||||
|
on_done: Callable[[Future], None],
|
||||||
|
) -> None:
|
||||||
|
if not (dialog := mw.progress.start(immediate=True, parent=mw)):
|
||||||
print("Progress dialog already running; aborting will not work")
|
print("Progress dialog already running; aborting will not work")
|
||||||
|
|
||||||
def on_progress() -> None:
|
def on_progress() -> None:
|
||||||
assert mw
|
assert mw
|
||||||
|
|
||||||
progress = mw.backend.latest_progress()
|
progress = mw.backend.latest_progress()
|
||||||
if not progress.HasField(self._key):
|
if not (label := label_from_progress(progress, self._key)):
|
||||||
return
|
return
|
||||||
label = getattr(progress, self._key)
|
|
||||||
|
|
||||||
if dialog and dialog.wantCancel:
|
if dialog and dialog.wantCancel:
|
||||||
mw.backend.set_wants_abort()
|
mw.backend.set_wants_abort()
|
||||||
|
|
||||||
mw.taskman.run_on_main(lambda: mw.progress.update(label=label))
|
mw.taskman.run_on_main(lambda: mw.progress.update(label=label))
|
||||||
|
|
||||||
def wrapped_on_done(future: Future) -> None:
|
def wrapped_on_done(future: Future) -> None:
|
||||||
self.timer.deleteLater()
|
self._timer.deleteLater()
|
||||||
assert mw
|
assert mw
|
||||||
mw.progress.finish()
|
mw.progress.finish()
|
||||||
on_done(future)
|
on_done(future)
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
|
||||||
|
def label_from_progress(
|
||||||
|
progress: Progress,
|
||||||
|
key: Literal["importing", "exporting"],
|
||||||
|
) -> str | None:
|
||||||
|
if not progress.HasField(key):
|
||||||
|
return None
|
||||||
|
if key == "importing":
|
||||||
|
return progress.importing
|
||||||
|
if key == "exporting":
|
||||||
|
return tr.exporting_exported_media_file(count=progress.exporting)
|
||||||
|
|
||||||
|
|
||||||
|
class CollectionOpWithBackendProgress(OpWithBackendProgress, CollectionOp):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class QueryOpWithBackendProgress(OpWithBackendProgress, QueryOp):
|
||||||
|
def with_progress(self, *_args: Any) -> Any:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class ClosedCollectionOp(CollectionOp):
|
class ClosedCollectionOp(CollectionOp):
|
||||||
"""For CollectionOps that need to be run on a closed collection.
|
"""For CollectionOps that need to be run on a closed collection.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue