mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
Drop support for checkpoints (#2742)
* Drop support for checkpoints * Deprecate .flush() * Remove .begin/.commit * Remove rollback() and deprecate save/autosave/reset() There's no need to commit anymore, as the Rust code is handling transactions for us. * Add safer transact() method This will ensure add-on authors can't accidentally leave a transaction open, leading to data loss. --------- Co-authored-by: Damien Elmes <gpg@ankiweb.net>
This commit is contained in:
parent
4c17a6964e
commit
b23f17df27
25 changed files with 49 additions and 319 deletions
|
@ -125,6 +125,7 @@ class Card(DeprecatedNamesMixin):
|
||||||
desired_retention=self.desired_retention,
|
desired_retention=self.desired_retention,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@deprecated(info="please use col.update_card()")
|
||||||
def flush(self) -> None:
|
def flush(self) -> None:
|
||||||
hooks.card_will_flush(self)
|
hooks.card_will_flush(self)
|
||||||
if self.id != 0:
|
if self.id != 0:
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, Generator, Iterable, Literal, Optional, Sequence, Union, cast
|
from typing import Any, Generator, Iterable, Literal, Sequence, Union, cast
|
||||||
|
|
||||||
from anki import (
|
from anki import (
|
||||||
ankiweb_pb2,
|
ankiweb_pb2,
|
||||||
|
@ -81,7 +81,6 @@ from anki.scheduler.dummy import DummyScheduler
|
||||||
from anki.scheduler.v3 import Scheduler as V3Scheduler
|
from anki.scheduler.v3 import Scheduler as V3Scheduler
|
||||||
from anki.sync import SyncAuth, SyncOutput, SyncStatus
|
from anki.sync import SyncAuth, SyncOutput, SyncStatus
|
||||||
from anki.tags import TagManager
|
from anki.tags import TagManager
|
||||||
from anki.types import assert_exhaustive
|
|
||||||
from anki.utils import (
|
from anki.utils import (
|
||||||
from_json_bytes,
|
from_json_bytes,
|
||||||
ids2str,
|
ids2str,
|
||||||
|
@ -97,14 +96,6 @@ anki.latex.setup_hook()
|
||||||
SearchJoiner = Literal["AND", "OR"]
|
SearchJoiner = Literal["AND", "OR"]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class LegacyCheckpoint:
|
|
||||||
name: str
|
|
||||||
|
|
||||||
|
|
||||||
LegacyUndoResult = Optional[LegacyCheckpoint]
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class DeckIdLimit:
|
class DeckIdLimit:
|
||||||
deck_id: DeckId
|
deck_id: DeckId
|
||||||
|
@ -229,7 +220,6 @@ class Collection(DeprecatedNamesMixin):
|
||||||
|
|
||||||
def upgrade_to_v2_scheduler(self) -> None:
|
def upgrade_to_v2_scheduler(self) -> None:
|
||||||
self._backend.upgrade_scheduler()
|
self._backend.upgrade_scheduler()
|
||||||
self.clear_python_undo()
|
|
||||||
self._load_scheduler()
|
self._load_scheduler()
|
||||||
|
|
||||||
def v3_scheduler(self) -> bool:
|
def v3_scheduler(self) -> bool:
|
||||||
|
@ -259,47 +249,20 @@ class Collection(DeprecatedNamesMixin):
|
||||||
def mod(self) -> int:
|
def mod(self) -> int:
|
||||||
return self.db.scalar("select mod from col")
|
return self.db.scalar("select mod from col")
|
||||||
|
|
||||||
def modified_by_backend(self) -> bool:
|
@deprecated(info="saving is automatic")
|
||||||
# Until we can move away from long-running transactions, the Python
|
def save(self, **args: Any) -> None:
|
||||||
# code needs to know if the transaction should be committed, so we need
|
pass
|
||||||
# to check if the backend updated the modification time.
|
|
||||||
return self.db.last_begin_at != self.mod
|
|
||||||
|
|
||||||
def save(self, name: str | None = None, trx: bool = True) -> None:
|
|
||||||
"Flush, commit DB, and take out another write lock if trx=True."
|
|
||||||
# commit needed?
|
|
||||||
if self.db.modified_in_python or self.modified_by_backend():
|
|
||||||
self.db.modified_in_python = False
|
|
||||||
self.db.commit()
|
|
||||||
if trx:
|
|
||||||
self.db.begin()
|
|
||||||
elif not trx:
|
|
||||||
# if no changes were pending but calling code expects to be
|
|
||||||
# outside of a transaction, we need to roll back
|
|
||||||
self.db.rollback()
|
|
||||||
|
|
||||||
self._save_checkpoint(name)
|
|
||||||
|
|
||||||
|
@deprecated(info="saving is automatic")
|
||||||
def autosave(self) -> None:
|
def autosave(self) -> None:
|
||||||
"""Save any pending changes.
|
pass
|
||||||
If a checkpoint was taken in the last 5 minutes, don't save."""
|
|
||||||
if not self._have_outstanding_checkpoint():
|
|
||||||
# if there's no active checkpoint, we can save immediately
|
|
||||||
self.save()
|
|
||||||
elif time.time() - self._last_checkpoint_at > 300:
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
def close(
|
def close(
|
||||||
self,
|
self,
|
||||||
save: bool = True,
|
|
||||||
downgrade: bool = False,
|
downgrade: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"Disconnect from DB."
|
"Disconnect from DB."
|
||||||
if self.db:
|
if self.db:
|
||||||
if save:
|
|
||||||
self.save(trx=False)
|
|
||||||
else:
|
|
||||||
self.db.rollback()
|
|
||||||
self._clear_caches()
|
self._clear_caches()
|
||||||
self._backend.close_collection(
|
self._backend.close_collection(
|
||||||
downgrade_to_schema11=downgrade,
|
downgrade_to_schema11=downgrade,
|
||||||
|
@ -309,15 +272,9 @@ class Collection(DeprecatedNamesMixin):
|
||||||
def close_for_full_sync(self) -> None:
|
def close_for_full_sync(self) -> None:
|
||||||
# save and cleanup, but backend will take care of collection close
|
# save and cleanup, but backend will take care of collection close
|
||||||
if self.db:
|
if self.db:
|
||||||
self.save(trx=False)
|
|
||||||
self._clear_caches()
|
self._clear_caches()
|
||||||
self.db = None
|
self.db = None
|
||||||
|
|
||||||
def rollback(self) -> None:
|
|
||||||
self._clear_caches()
|
|
||||||
self.db.rollback()
|
|
||||||
self.db.begin()
|
|
||||||
|
|
||||||
def _clear_caches(self) -> None:
|
def _clear_caches(self) -> None:
|
||||||
self.models._clear_cache()
|
self.models._clear_cache()
|
||||||
|
|
||||||
|
@ -325,9 +282,6 @@ class Collection(DeprecatedNamesMixin):
|
||||||
if self.db:
|
if self.db:
|
||||||
raise Exception("reopen() called with open db")
|
raise Exception("reopen() called with open db")
|
||||||
|
|
||||||
self._last_checkpoint_at = time.time()
|
|
||||||
self._undo: _UndoInfo = None
|
|
||||||
|
|
||||||
(media_dir, media_db) = media_paths_from_col_path(self.path)
|
(media_dir, media_db) = media_paths_from_col_path(self.path)
|
||||||
|
|
||||||
# connect
|
# connect
|
||||||
|
@ -338,13 +292,11 @@ class Collection(DeprecatedNamesMixin):
|
||||||
media_db_path=media_db,
|
media_db_path=media_db,
|
||||||
)
|
)
|
||||||
self.db = DBProxy(weakref.proxy(self._backend))
|
self.db = DBProxy(weakref.proxy(self._backend))
|
||||||
self.db.begin()
|
|
||||||
if after_full_sync:
|
if after_full_sync:
|
||||||
self._load_scheduler()
|
self._load_scheduler()
|
||||||
|
|
||||||
def set_schema_modified(self) -> None:
|
def set_schema_modified(self) -> None:
|
||||||
self.db.execute("update col set scm=?", int_time(1000))
|
self.db.execute("update col set scm=?", int_time(1000))
|
||||||
self.save()
|
|
||||||
|
|
||||||
def mod_schema(self, check: bool) -> None:
|
def mod_schema(self, check: bool) -> None:
|
||||||
"Mark schema modified. GUI catches this and will ask user if required."
|
"Mark schema modified. GUI catches this and will ask user if required."
|
||||||
|
@ -363,12 +315,6 @@ class Collection(DeprecatedNamesMixin):
|
||||||
else:
|
else:
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
def legacy_checkpoint_pending(self) -> bool:
|
|
||||||
return (
|
|
||||||
self._have_outstanding_checkpoint()
|
|
||||||
and time.time() - self._last_checkpoint_at < 300
|
|
||||||
)
|
|
||||||
|
|
||||||
# Import/export
|
# Import/export
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
@ -385,19 +331,15 @@ class Collection(DeprecatedNamesMixin):
|
||||||
Returns true if backup created. This may be false in the force=True case,
|
Returns true if backup created. This may be false in the force=True case,
|
||||||
if no changes have been made to the collection.
|
if no changes have been made to the collection.
|
||||||
|
|
||||||
Commits any outstanding changes, which clears any active legacy checkpoint.
|
|
||||||
|
|
||||||
Throws on failure of current backup, or the previous backup if it was not
|
Throws on failure of current backup, or the previous backup if it was not
|
||||||
awaited.
|
awaited.
|
||||||
"""
|
"""
|
||||||
# ensure any pending transaction from legacy code/add-ons has been committed
|
# ensure any pending transaction from legacy code/add-ons has been committed
|
||||||
self.save(trx=False)
|
|
||||||
created = self._backend.create_backup(
|
created = self._backend.create_backup(
|
||||||
backup_folder=backup_folder,
|
backup_folder=backup_folder,
|
||||||
force=force,
|
force=force,
|
||||||
wait_for_completion=wait_for_completion,
|
wait_for_completion=wait_for_completion,
|
||||||
)
|
)
|
||||||
self.db.begin()
|
|
||||||
return created
|
return created
|
||||||
|
|
||||||
def await_backup_completion(self) -> None:
|
def await_backup_completion(self) -> None:
|
||||||
|
@ -539,30 +481,26 @@ class Collection(DeprecatedNamesMixin):
|
||||||
return Card(self, id)
|
return Card(self, id)
|
||||||
|
|
||||||
def update_cards(self, cards: Sequence[Card]) -> OpChanges:
|
def update_cards(self, cards: Sequence[Card]) -> OpChanges:
|
||||||
"""Save card changes to database, and add an undo entry.
|
"""Save card changes to database, and add an undo entry."""
|
||||||
Unlike card.flush(), this will invalidate any current checkpoint."""
|
|
||||||
return self._backend.update_cards(
|
return self._backend.update_cards(
|
||||||
cards=[c._to_backend_card() for c in cards], skip_undo_entry=False
|
cards=[c._to_backend_card() for c in cards], skip_undo_entry=False
|
||||||
)
|
)
|
||||||
|
|
||||||
def update_card(self, card: Card) -> OpChanges:
|
def update_card(self, card: Card) -> OpChanges:
|
||||||
"""Save card changes to database, and add an undo entry.
|
"""Save card changes to database, and add an undo entry."""
|
||||||
Unlike card.flush(), this will invalidate any current checkpoint."""
|
|
||||||
return self.update_cards([card])
|
return self.update_cards([card])
|
||||||
|
|
||||||
def get_note(self, id: NoteId) -> Note:
|
def get_note(self, id: NoteId) -> Note:
|
||||||
return Note(self, id=id)
|
return Note(self, id=id)
|
||||||
|
|
||||||
def update_notes(self, notes: Sequence[Note]) -> OpChanges:
|
def update_notes(self, notes: Sequence[Note]) -> OpChanges:
|
||||||
"""Save note changes to database, and add an undo entry.
|
"""Save note changes to database, and add an undo entry."""
|
||||||
Unlike note.flush(), this will invalidate any current checkpoint."""
|
|
||||||
return self._backend.update_notes(
|
return self._backend.update_notes(
|
||||||
notes=[n._to_backend_note() for n in notes], skip_undo_entry=False
|
notes=[n._to_backend_note() for n in notes], skip_undo_entry=False
|
||||||
)
|
)
|
||||||
|
|
||||||
def update_note(self, note: Note) -> OpChanges:
|
def update_note(self, note: Note) -> OpChanges:
|
||||||
"""Save note changes to database, and add an undo entry.
|
"""Save note changes to database, and add an undo entry."""
|
||||||
Unlike note.flush(), this will invalidate any current checkpoint."""
|
|
||||||
return self.update_notes([note])
|
return self.update_notes([note])
|
||||||
|
|
||||||
# Utils
|
# Utils
|
||||||
|
@ -577,10 +515,9 @@ class Collection(DeprecatedNamesMixin):
|
||||||
self.conf[type] = id + 1
|
self.conf[type] = id + 1
|
||||||
return id
|
return id
|
||||||
|
|
||||||
|
@deprecated(info="no longer required")
|
||||||
def reset(self) -> None:
|
def reset(self) -> None:
|
||||||
"Rebuild the queue and reload data after DB modified."
|
pass
|
||||||
self.autosave()
|
|
||||||
self.sched.reset()
|
|
||||||
|
|
||||||
# Notes
|
# Notes
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
@ -1061,18 +998,7 @@ class Collection(DeprecatedNamesMixin):
|
||||||
|
|
||||||
def undo_status(self) -> UndoStatus:
|
def undo_status(self) -> UndoStatus:
|
||||||
"Return the undo status."
|
"Return the undo status."
|
||||||
# check backend first
|
return self._check_backend_undo_status() or UndoStatus()
|
||||||
if status := self._check_backend_undo_status():
|
|
||||||
return status
|
|
||||||
|
|
||||||
if not self._undo:
|
|
||||||
return UndoStatus()
|
|
||||||
|
|
||||||
if isinstance(self._undo, LegacyCheckpoint):
|
|
||||||
return UndoStatus(undo=self._undo.name)
|
|
||||||
else:
|
|
||||||
assert_exhaustive(self._undo)
|
|
||||||
assert False
|
|
||||||
|
|
||||||
def add_custom_undo_entry(self, name: str) -> int:
|
def add_custom_undo_entry(self, name: str) -> int:
|
||||||
"""Add an empty undo entry with the given name.
|
"""Add an empty undo entry with the given name.
|
||||||
|
@ -1096,18 +1022,9 @@ class Collection(DeprecatedNamesMixin):
|
||||||
"""
|
"""
|
||||||
return self._backend.merge_undo_entries(target)
|
return self._backend.merge_undo_entries(target)
|
||||||
|
|
||||||
def clear_python_undo(self) -> None:
|
|
||||||
"""Clear the Python undo state.
|
|
||||||
The backend will automatically clear backend undo state when
|
|
||||||
any SQL DML is executed, or an operation that doesn't support undo
|
|
||||||
is run."""
|
|
||||||
self._undo = None
|
|
||||||
|
|
||||||
def undo(self) -> OpChangesAfterUndo:
|
def undo(self) -> OpChangesAfterUndo:
|
||||||
"""Returns result of backend undo operation, or throws UndoEmpty.
|
"""Returns result of backend undo operation, or throws UndoEmpty."""
|
||||||
If UndoEmpty is received, caller should try undo_legacy()."""
|
|
||||||
out = self._backend.undo()
|
out = self._backend.undo()
|
||||||
self.clear_python_undo()
|
|
||||||
if out.changes.notetype:
|
if out.changes.notetype:
|
||||||
self.models._clear_cache()
|
self.models._clear_cache()
|
||||||
return out
|
return out
|
||||||
|
@ -1115,21 +1032,10 @@ class Collection(DeprecatedNamesMixin):
|
||||||
def redo(self) -> OpChangesAfterUndo:
|
def redo(self) -> OpChangesAfterUndo:
|
||||||
"""Returns result of backend redo operation, or throws UndoEmpty."""
|
"""Returns result of backend redo operation, or throws UndoEmpty."""
|
||||||
out = self._backend.redo()
|
out = self._backend.redo()
|
||||||
self.clear_python_undo()
|
|
||||||
if out.changes.notetype:
|
if out.changes.notetype:
|
||||||
self.models._clear_cache()
|
self.models._clear_cache()
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def undo_legacy(self) -> LegacyUndoResult:
|
|
||||||
"Returns None if the legacy undo queue is empty."
|
|
||||||
if isinstance(self._undo, LegacyCheckpoint):
|
|
||||||
return self._undo_checkpoint()
|
|
||||||
elif self._undo is None:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
assert_exhaustive(self._undo)
|
|
||||||
assert False
|
|
||||||
|
|
||||||
def op_made_changes(self, changes: OpChanges) -> bool:
|
def op_made_changes(self, changes: OpChanges) -> bool:
|
||||||
for field in changes.DESCRIPTOR.fields:
|
for field in changes.DESCRIPTOR.fields:
|
||||||
if field.name != "kind":
|
if field.name != "kind":
|
||||||
|
@ -1142,30 +1048,10 @@ class Collection(DeprecatedNamesMixin):
|
||||||
If backend has undo available, clear the Python undo state."""
|
If backend has undo available, clear the Python undo state."""
|
||||||
status = self._backend.get_undo_status()
|
status = self._backend.get_undo_status()
|
||||||
if status.undo or status.redo:
|
if status.undo or status.redo:
|
||||||
self.clear_python_undo()
|
|
||||||
return status
|
return status
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _have_outstanding_checkpoint(self) -> bool:
|
|
||||||
self._check_backend_undo_status()
|
|
||||||
return isinstance(self._undo, LegacyCheckpoint)
|
|
||||||
|
|
||||||
def _undo_checkpoint(self) -> LegacyCheckpoint:
|
|
||||||
assert isinstance(self._undo, LegacyCheckpoint)
|
|
||||||
self.rollback()
|
|
||||||
undo = self._undo
|
|
||||||
self.clear_python_undo()
|
|
||||||
return undo
|
|
||||||
|
|
||||||
def _save_checkpoint(self, name: str | None) -> None:
|
|
||||||
"Call via .save(). If name not provided, clear any existing checkpoint."
|
|
||||||
self._last_checkpoint_at = time.time()
|
|
||||||
if name:
|
|
||||||
self._undo = LegacyCheckpoint(name=name)
|
|
||||||
else:
|
|
||||||
self.clear_python_undo()
|
|
||||||
|
|
||||||
# DB maintenance
|
# DB maintenance
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
@ -1175,7 +1061,6 @@ class Collection(DeprecatedNamesMixin):
|
||||||
Returns tuple of (error: str, ok: bool). 'ok' will be true if no
|
Returns tuple of (error: str, ok: bool). 'ok' will be true if no
|
||||||
problems were found.
|
problems were found.
|
||||||
"""
|
"""
|
||||||
self.save(trx=False)
|
|
||||||
try:
|
try:
|
||||||
problems = list(self._backend.check_database())
|
problems = list(self._backend.check_database())
|
||||||
ok = not problems
|
ok = not problems
|
||||||
|
@ -1183,19 +1068,11 @@ class Collection(DeprecatedNamesMixin):
|
||||||
except DBError as err:
|
except DBError as err:
|
||||||
problems = [str(err)]
|
problems = [str(err)]
|
||||||
ok = False
|
ok = False
|
||||||
finally:
|
|
||||||
try:
|
|
||||||
self.db.begin()
|
|
||||||
except:
|
|
||||||
# may fail if the DB is very corrupt
|
|
||||||
pass
|
|
||||||
return ("\n".join(problems), ok)
|
return ("\n".join(problems), ok)
|
||||||
|
|
||||||
def optimize(self) -> None:
|
def optimize(self) -> None:
|
||||||
self.save(trx=False)
|
|
||||||
self.db.execute("vacuum")
|
self.db.execute("vacuum")
|
||||||
self.db.execute("analyze")
|
self.db.execute("analyze")
|
||||||
self.db.begin()
|
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
@ -1372,7 +1249,6 @@ class Collection(DeprecatedNamesMixin):
|
||||||
|
|
||||||
|
|
||||||
Collection.register_deprecated_aliases(
|
Collection.register_deprecated_aliases(
|
||||||
clearUndo=Collection.clear_python_undo,
|
|
||||||
findReplace=Collection.find_and_replace,
|
findReplace=Collection.find_and_replace,
|
||||||
remCards=Collection.remove_cards_and_orphaned_notes,
|
remCards=Collection.remove_cards_and_orphaned_notes,
|
||||||
)
|
)
|
||||||
|
@ -1381,8 +1257,6 @@ Collection.register_deprecated_aliases(
|
||||||
# legacy name
|
# legacy name
|
||||||
_Collection = Collection
|
_Collection = Collection
|
||||||
|
|
||||||
_UndoInfo = Union[LegacyCheckpoint, None]
|
|
||||||
|
|
||||||
|
|
||||||
def pb_export_limit(limit: ExportLimit) -> import_export_pb2.ExportLimit:
|
def pb_export_limit(limit: ExportLimit) -> import_export_pb2.ExportLimit:
|
||||||
message = import_export_pb2.ExportLimit()
|
message = import_export_pb2.ExportLimit()
|
||||||
|
|
|
@ -5,10 +5,11 @@ from __future__ import annotations
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from re import Match
|
from re import Match
|
||||||
from typing import TYPE_CHECKING, Any, Iterable, Sequence, Union
|
from typing import TYPE_CHECKING, Any, Callable, Iterable, Sequence, Union
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import anki._backend
|
import anki._backend
|
||||||
|
from anki.collection import Collection
|
||||||
|
|
||||||
# DBValue is actually Union[str, int, float, None], but if defined
|
# DBValue is actually Union[str, int, float, None], but if defined
|
||||||
# that way, every call site needs to do a type check prior to using
|
# that way, every call site needs to do a type check prior to using
|
||||||
|
@ -25,21 +26,29 @@ class DBProxy:
|
||||||
|
|
||||||
def __init__(self, backend: anki._backend.RustBackend) -> None:
|
def __init__(self, backend: anki._backend.RustBackend) -> None:
|
||||||
self._backend = backend
|
self._backend = backend
|
||||||
self.modified_in_python = False
|
|
||||||
self.last_begin_at = 0
|
|
||||||
|
|
||||||
# Transactions
|
# Transactions
|
||||||
###############
|
###############
|
||||||
|
|
||||||
def begin(self) -> None:
|
def transact(self, op: Callable[[], None]) -> None:
|
||||||
self.last_begin_at = self.scalar("select mod from col")
|
"""Run the provided operation inside a transaction.
|
||||||
self._backend.db_begin()
|
|
||||||
|
|
||||||
def commit(self) -> None:
|
Please note that all backend methods automatically wrap changes in a transaction,
|
||||||
self._backend.db_commit()
|
so there is no need to use this when calling methods like update_cards(), unless
|
||||||
|
you are making other changes at the same time and want to ensure they are applied
|
||||||
|
completely or not at all.
|
||||||
|
|
||||||
def rollback(self) -> None:
|
If the operation throws an exception, the changes will be automatically rolled
|
||||||
self._backend.db_rollback()
|
back.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._backend.db_begin()
|
||||||
|
op()
|
||||||
|
self._backend.db_commit()
|
||||||
|
except BaseException as e:
|
||||||
|
self._backend.db_rollback()
|
||||||
|
raise e
|
||||||
|
|
||||||
# Querying
|
# Querying
|
||||||
################
|
################
|
||||||
|
@ -51,11 +60,6 @@ class DBProxy:
|
||||||
first_row_only: bool = False,
|
first_row_only: bool = False,
|
||||||
**kwargs: ValueForDB,
|
**kwargs: ValueForDB,
|
||||||
) -> list[Row]:
|
) -> list[Row]:
|
||||||
# mark modified?
|
|
||||||
cananoized = sql.strip().lower()
|
|
||||||
for stmt in "insert", "update", "delete":
|
|
||||||
if cananoized.startswith(stmt):
|
|
||||||
self.modified_in_python = True
|
|
||||||
sql, args2 = emulate_named_args(sql, args, kwargs)
|
sql, args2 = emulate_named_args(sql, args, kwargs)
|
||||||
# fetch rows
|
# fetch rows
|
||||||
return self._backend.db_query(sql, args2, first_row_only)
|
return self._backend.db_query(sql, args2, first_row_only)
|
||||||
|
@ -93,7 +97,6 @@ class DBProxy:
|
||||||
################
|
################
|
||||||
|
|
||||||
def executemany(self, sql: str, args: Iterable[Sequence[ValueForDB]]) -> None:
|
def executemany(self, sql: str, args: Iterable[Sequence[ValueForDB]]) -> None:
|
||||||
self.modified_in_python = True
|
|
||||||
if isinstance(args, list):
|
if isinstance(args, list):
|
||||||
list_args = args
|
list_args = args
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -424,7 +424,6 @@ class DeckManager(DeprecatedNamesMixin):
|
||||||
# make sure arg is an int; legacy callers may be passing in a string
|
# make sure arg is an int; legacy callers may be passing in a string
|
||||||
did = DeckId(did)
|
did = DeckId(did)
|
||||||
self.set_current(did)
|
self.set_current(did)
|
||||||
self.col.reset()
|
|
||||||
|
|
||||||
selected = get_current_id
|
selected = get_current_id
|
||||||
|
|
||||||
|
|
|
@ -396,7 +396,6 @@ class AnkiPackageExporter(AnkiExporter):
|
||||||
n = c.newNote()
|
n = c.newNote()
|
||||||
n.fields[0] = "This file requires a newer version of Anki."
|
n.fields[0] = "This file requires a newer version of Anki."
|
||||||
c.addNote(n)
|
c.addNote(n)
|
||||||
c.save()
|
|
||||||
c.close(downgrade=True)
|
c.close(downgrade=True)
|
||||||
|
|
||||||
zip.write(path, "collection.anki2")
|
zip.write(path, "collection.anki2")
|
||||||
|
|
|
@ -52,7 +52,7 @@ class Anki2Importer(Importer):
|
||||||
try:
|
try:
|
||||||
self._import()
|
self._import()
|
||||||
finally:
|
finally:
|
||||||
self.src.close(save=False, downgrade=False)
|
self.src.close(downgrade=False)
|
||||||
|
|
||||||
def _prepareFiles(self) -> None:
|
def _prepareFiles(self) -> None:
|
||||||
self.source_needs_upgrade = False
|
self.source_needs_upgrade = False
|
||||||
|
|
|
@ -179,9 +179,6 @@ class MediaManager(DeprecatedNamesMixin):
|
||||||
|
|
||||||
def check(self) -> CheckMediaResponse:
|
def check(self) -> CheckMediaResponse:
|
||||||
output = self.col._backend.check_media()
|
output = self.col._backend.check_media()
|
||||||
# files may have been renamed on disk, so an undo at this point could
|
|
||||||
# break file references
|
|
||||||
self.col.save()
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def render_all_latex(
|
def render_all_latex(
|
||||||
|
|
|
@ -12,7 +12,7 @@ import anki.collection
|
||||||
import anki.decks
|
import anki.decks
|
||||||
import anki.template
|
import anki.template
|
||||||
from anki import hooks, notes_pb2
|
from anki import hooks, notes_pb2
|
||||||
from anki._legacy import DeprecatedNamesMixin
|
from anki._legacy import DeprecatedNamesMixin, deprecated
|
||||||
from anki.consts import MODEL_STD
|
from anki.consts import MODEL_STD
|
||||||
from anki.models import NotetypeDict, NotetypeId, TemplateDict
|
from anki.models import NotetypeDict, NotetypeId, TemplateDict
|
||||||
from anki.utils import join_fields
|
from anki.utils import join_fields
|
||||||
|
@ -78,9 +78,9 @@ class Note(DeprecatedNamesMixin):
|
||||||
fields=self.fields,
|
fields=self.fields,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@deprecated(info="please use col.update_note()")
|
||||||
def flush(self) -> None:
|
def flush(self) -> None:
|
||||||
"""This preserves any current checkpoint.
|
"""For an undo entry, use col.update_note() instead."""
|
||||||
For an undo entry, use col.update_note() instead."""
|
|
||||||
if self.id == 0:
|
if self.id == 0:
|
||||||
raise Exception("can't flush a new note")
|
raise Exception("can't flush a new note")
|
||||||
self.col._backend.update_notes(
|
self.col._backend.update_notes(
|
||||||
|
|
|
@ -17,6 +17,7 @@ from __future__ import annotations
|
||||||
from typing import Literal, Optional, Sequence
|
from typing import Literal, Optional, Sequence
|
||||||
|
|
||||||
from anki import frontend_pb2, scheduler_pb2
|
from anki import frontend_pb2, scheduler_pb2
|
||||||
|
from anki._legacy import deprecated
|
||||||
from anki.cards import Card
|
from anki.cards import Card
|
||||||
from anki.collection import OpChanges
|
from anki.collection import OpChanges
|
||||||
from anki.consts import *
|
from anki.consts import *
|
||||||
|
@ -103,6 +104,7 @@ class Scheduler(SchedulerBaseWithLegacy):
|
||||||
# Fetching the next card (legacy API)
|
# Fetching the next card (legacy API)
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
@deprecated(info="no longer required")
|
||||||
def reset(self) -> None:
|
def reset(self) -> None:
|
||||||
# backend automatically resets queues as operations are performed
|
# backend automatically resets queues as operations are performed
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -13,7 +13,6 @@ def test_delete():
|
||||||
note["Back"] = "2"
|
note["Back"] = "2"
|
||||||
col.addNote(note)
|
col.addNote(note)
|
||||||
cid = note.cards()[0].id
|
cid = note.cards()[0].id
|
||||||
col.reset()
|
|
||||||
col.sched.answerCard(col.sched.getCard(), 2)
|
col.sched.answerCard(col.sched.getCard(), 2)
|
||||||
col.remove_cards_and_orphaned_notes([cid])
|
col.remove_cards_and_orphaned_notes([cid])
|
||||||
assert col.card_count() == 0
|
assert col.card_count() == 0
|
||||||
|
|
|
@ -22,13 +22,11 @@ def test_basic():
|
||||||
assert col.decks.id("new deck") == parentId
|
assert col.decks.id("new deck") == parentId
|
||||||
# we start with the default col selected
|
# we start with the default col selected
|
||||||
assert col.decks.selected() == 1
|
assert col.decks.selected() == 1
|
||||||
col.reset()
|
|
||||||
# we can select a different col
|
# we can select a different col
|
||||||
col.decks.select(parentId)
|
col.decks.select(parentId)
|
||||||
assert col.decks.selected() == parentId
|
assert col.decks.selected() == parentId
|
||||||
# let's create a child
|
# let's create a child
|
||||||
childId = col.decks.id("new deck::child")
|
childId = col.decks.id("new deck::child")
|
||||||
col.sched.reset()
|
|
||||||
# it should have been added to the active list
|
# it should have been added to the active list
|
||||||
assert col.decks.selected() == parentId
|
assert col.decks.selected() == parentId
|
||||||
# we can select the child individually too
|
# we can select the child individually too
|
||||||
|
|
|
@ -108,7 +108,6 @@ def test_export_anki_due():
|
||||||
note["Front"] = "foo"
|
note["Front"] = "foo"
|
||||||
col.addNote(note)
|
col.addNote(note)
|
||||||
col.crt -= 86400 * 10
|
col.crt -= 86400 * 10
|
||||||
col.sched.reset()
|
|
||||||
c = col.sched.getCard()
|
c = col.sched.getCard()
|
||||||
col.sched.answerCard(c, 3)
|
col.sched.answerCard(c, 3)
|
||||||
col.sched.answerCard(c, 3)
|
col.sched.answerCard(c, 3)
|
||||||
|
@ -130,7 +129,6 @@ def test_export_anki_due():
|
||||||
imp = Anki2Importer(col2, newname)
|
imp = Anki2Importer(col2, newname)
|
||||||
imp.run()
|
imp.run()
|
||||||
c = col2.getCard(c.id)
|
c = col2.getCard(c.id)
|
||||||
col2.sched.reset()
|
|
||||||
assert c.due - col2.sched.today == 1
|
assert c.due - col2.sched.today == 1
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,6 @@ def test_find_cards():
|
||||||
note["Front"] = "test"
|
note["Front"] = "test"
|
||||||
note["Back"] = "foo bar"
|
note["Back"] = "foo bar"
|
||||||
col.addNote(note)
|
col.addNote(note)
|
||||||
col.save()
|
|
||||||
latestCardIds = [c.id for c in note.cards()]
|
latestCardIds = [c.id for c in note.cards()]
|
||||||
# tag searches
|
# tag searches
|
||||||
assert len(col.find_cards("tag:*")) == 5
|
assert len(col.find_cards("tag:*")) == 5
|
||||||
|
@ -164,7 +163,6 @@ def test_find_cards():
|
||||||
col.db.execute(
|
col.db.execute(
|
||||||
"update cards set did = ? where id = ?", col.decks.id("Default::Child"), id
|
"update cards set did = ? where id = ?", col.decks.id("Default::Child"), id
|
||||||
)
|
)
|
||||||
col.save()
|
|
||||||
assert len(col.find_cards("deck:default")) == 7
|
assert len(col.find_cards("deck:default")) == 7
|
||||||
assert len(col.find_cards("deck:default::child")) == 1
|
assert len(col.find_cards("deck:default::child")) == 1
|
||||||
assert len(col.find_cards("deck:default -deck:default::*")) == 6
|
assert len(col.find_cards("deck:default -deck:default::*")) == 6
|
||||||
|
|
|
@ -29,20 +29,17 @@ def test_clock():
|
||||||
|
|
||||||
def test_basics():
|
def test_basics():
|
||||||
col = getEmptyCol()
|
col = getEmptyCol()
|
||||||
col.reset()
|
|
||||||
assert not col.sched.getCard()
|
assert not col.sched.getCard()
|
||||||
|
|
||||||
|
|
||||||
def test_new():
|
def test_new():
|
||||||
col = getEmptyCol()
|
col = getEmptyCol()
|
||||||
col.reset()
|
|
||||||
assert col.sched.newCount == 0
|
assert col.sched.newCount == 0
|
||||||
# add a note
|
# add a note
|
||||||
note = col.newNote()
|
note = col.newNote()
|
||||||
note["Front"] = "one"
|
note["Front"] = "one"
|
||||||
note["Back"] = "two"
|
note["Back"] = "two"
|
||||||
col.addNote(note)
|
col.addNote(note)
|
||||||
col.reset()
|
|
||||||
assert col.sched.newCount == 1
|
assert col.sched.newCount == 1
|
||||||
# fetch it
|
# fetch it
|
||||||
c = col.sched.getCard()
|
c = col.sched.getCard()
|
||||||
|
@ -92,7 +89,6 @@ def test_newLimits():
|
||||||
# give the child deck a different configuration
|
# give the child deck a different configuration
|
||||||
c2 = col.decks.add_config_returning_id("new conf")
|
c2 = col.decks.add_config_returning_id("new conf")
|
||||||
col.decks.set_config_id_for_deck_dict(col.decks.get(deck2), c2)
|
col.decks.set_config_id_for_deck_dict(col.decks.get(deck2), c2)
|
||||||
col.reset()
|
|
||||||
# both confs have defaulted to a limit of 20
|
# both confs have defaulted to a limit of 20
|
||||||
assert col.sched.newCount == 20
|
assert col.sched.newCount == 20
|
||||||
# first card we get comes from parent
|
# first card we get comes from parent
|
||||||
|
@ -102,13 +98,11 @@ def test_newLimits():
|
||||||
conf1 = col.decks.config_dict_for_deck_id(1)
|
conf1 = col.decks.config_dict_for_deck_id(1)
|
||||||
conf1["new"]["perDay"] = 10
|
conf1["new"]["perDay"] = 10
|
||||||
col.decks.save(conf1)
|
col.decks.save(conf1)
|
||||||
col.reset()
|
|
||||||
assert col.sched.newCount == 10
|
assert col.sched.newCount == 10
|
||||||
# if we limit child to 4, we should get 9
|
# if we limit child to 4, we should get 9
|
||||||
conf2 = col.decks.config_dict_for_deck_id(deck2)
|
conf2 = col.decks.config_dict_for_deck_id(deck2)
|
||||||
conf2["new"]["perDay"] = 4
|
conf2["new"]["perDay"] = 4
|
||||||
col.decks.save(conf2)
|
col.decks.save(conf2)
|
||||||
col.reset()
|
|
||||||
assert col.sched.newCount == 9
|
assert col.sched.newCount == 9
|
||||||
|
|
||||||
|
|
||||||
|
@ -117,7 +111,6 @@ def test_newBoxes():
|
||||||
note = col.newNote()
|
note = col.newNote()
|
||||||
note["Front"] = "one"
|
note["Front"] = "one"
|
||||||
col.addNote(note)
|
col.addNote(note)
|
||||||
col.reset()
|
|
||||||
c = col.sched.getCard()
|
c = col.sched.getCard()
|
||||||
conf = col.sched._cardConf(c)
|
conf = col.sched._cardConf(c)
|
||||||
conf["new"]["delays"] = [1, 2, 3, 4, 5]
|
conf["new"]["delays"] = [1, 2, 3, 4, 5]
|
||||||
|
@ -138,7 +131,6 @@ def test_learn():
|
||||||
col.addNote(note)
|
col.addNote(note)
|
||||||
# set as a new card and rebuild queues
|
# set as a new card and rebuild queues
|
||||||
col.db.execute(f"update cards set queue={QUEUE_TYPE_NEW}, type={CARD_TYPE_NEW}")
|
col.db.execute(f"update cards set queue={QUEUE_TYPE_NEW}, type={CARD_TYPE_NEW}")
|
||||||
col.reset()
|
|
||||||
# sched.getCard should return it, since it's due in the past
|
# sched.getCard should return it, since it's due in the past
|
||||||
c = col.sched.getCard()
|
c = col.sched.getCard()
|
||||||
assert c
|
assert c
|
||||||
|
@ -182,7 +174,6 @@ def test_learn():
|
||||||
c.type = CARD_TYPE_NEW
|
c.type = CARD_TYPE_NEW
|
||||||
c.queue = QUEUE_TYPE_LRN
|
c.queue = QUEUE_TYPE_LRN
|
||||||
c.flush()
|
c.flush()
|
||||||
col.sched.reset()
|
|
||||||
col.sched.answerCard(c, 4)
|
col.sched.answerCard(c, 4)
|
||||||
assert c.type == CARD_TYPE_REV
|
assert c.type == CARD_TYPE_REV
|
||||||
assert c.queue == QUEUE_TYPE_REV
|
assert c.queue == QUEUE_TYPE_REV
|
||||||
|
@ -203,7 +194,6 @@ def test_relearn():
|
||||||
c.flush()
|
c.flush()
|
||||||
|
|
||||||
# fail the card
|
# fail the card
|
||||||
col.reset()
|
|
||||||
c = col.sched.getCard()
|
c = col.sched.getCard()
|
||||||
col.sched.answerCard(c, 1)
|
col.sched.answerCard(c, 1)
|
||||||
assert c.queue == QUEUE_TYPE_LRN
|
assert c.queue == QUEUE_TYPE_LRN
|
||||||
|
@ -234,7 +224,6 @@ def test_relearn_no_steps():
|
||||||
col.decks.save(conf)
|
col.decks.save(conf)
|
||||||
|
|
||||||
# fail the card
|
# fail the card
|
||||||
col.reset()
|
|
||||||
c = col.sched.getCard()
|
c = col.sched.getCard()
|
||||||
col.sched.answerCard(c, 1)
|
col.sched.answerCard(c, 1)
|
||||||
assert c.queue == CARD_TYPE_REV and c.type == QUEUE_TYPE_REV
|
assert c.queue == CARD_TYPE_REV and c.type == QUEUE_TYPE_REV
|
||||||
|
@ -251,7 +240,6 @@ def test_learn_collapsed():
|
||||||
col.addNote(note)
|
col.addNote(note)
|
||||||
# set as a new card and rebuild queues
|
# set as a new card and rebuild queues
|
||||||
col.db.execute(f"update cards set queue={QUEUE_TYPE_NEW}, type={CARD_TYPE_NEW}")
|
col.db.execute(f"update cards set queue={QUEUE_TYPE_NEW}, type={CARD_TYPE_NEW}")
|
||||||
col.reset()
|
|
||||||
# should get '1' first
|
# should get '1' first
|
||||||
c = col.sched.getCard()
|
c = col.sched.getCard()
|
||||||
assert c.question().endswith("1")
|
assert c.question().endswith("1")
|
||||||
|
@ -276,7 +264,6 @@ def test_learn_day():
|
||||||
note = col.newNote()
|
note = col.newNote()
|
||||||
note["Front"] = "two"
|
note["Front"] = "two"
|
||||||
col.addNote(note)
|
col.addNote(note)
|
||||||
col.sched.reset()
|
|
||||||
c = col.sched.getCard()
|
c = col.sched.getCard()
|
||||||
conf = col.sched._cardConf(c)
|
conf = col.sched._cardConf(c)
|
||||||
conf["new"]["delays"] = [1, 10, 1440, 2880]
|
conf["new"]["delays"] = [1, 10, 1440, 2880]
|
||||||
|
@ -300,7 +287,6 @@ def test_learn_day():
|
||||||
# for testing, move it back a day
|
# for testing, move it back a day
|
||||||
c.due -= 1
|
c.due -= 1
|
||||||
c.flush()
|
c.flush()
|
||||||
col.reset()
|
|
||||||
assert col.sched.counts() == (0, 1, 0)
|
assert col.sched.counts() == (0, 1, 0)
|
||||||
c = col.sched.getCard()
|
c = col.sched.getCard()
|
||||||
# nextIvl should work
|
# nextIvl should work
|
||||||
|
@ -309,13 +295,11 @@ def test_learn_day():
|
||||||
col.sched.answerCard(c, 1)
|
col.sched.answerCard(c, 1)
|
||||||
assert c.queue == QUEUE_TYPE_LRN
|
assert c.queue == QUEUE_TYPE_LRN
|
||||||
col.undo()
|
col.undo()
|
||||||
col.reset()
|
|
||||||
c = col.sched.getCard()
|
c = col.sched.getCard()
|
||||||
col.sched.answerCard(c, 3)
|
col.sched.answerCard(c, 3)
|
||||||
# simulate the passing of another two days
|
# simulate the passing of another two days
|
||||||
c.due -= 2
|
c.due -= 2
|
||||||
c.flush()
|
c.flush()
|
||||||
col.reset()
|
|
||||||
# the last pass should graduate it into a review card
|
# the last pass should graduate it into a review card
|
||||||
assert ni(c, 3) == 86400
|
assert ni(c, 3) == 86400
|
||||||
col.sched.answerCard(c, 3)
|
col.sched.answerCard(c, 3)
|
||||||
|
@ -324,7 +308,6 @@ def test_learn_day():
|
||||||
# correctly
|
# correctly
|
||||||
c.due = 0
|
c.due = 0
|
||||||
c.flush()
|
c.flush()
|
||||||
col.reset()
|
|
||||||
assert col.sched.counts() == (0, 0, 1)
|
assert col.sched.counts() == (0, 0, 1)
|
||||||
conf = col.sched._cardConf(c)
|
conf = col.sched._cardConf(c)
|
||||||
conf["lapse"]["delays"] = [1440]
|
conf["lapse"]["delays"] = [1440]
|
||||||
|
@ -359,7 +342,6 @@ def test_reviews():
|
||||||
##################################################
|
##################################################
|
||||||
c = copy.copy(cardcopy)
|
c = copy.copy(cardcopy)
|
||||||
c.flush()
|
c.flush()
|
||||||
col.reset()
|
|
||||||
col.sched.answerCard(c, 2)
|
col.sched.answerCard(c, 2)
|
||||||
assert c.queue == QUEUE_TYPE_REV
|
assert c.queue == QUEUE_TYPE_REV
|
||||||
# the new interval should be (100) * 1.2 = 120
|
# the new interval should be (100) * 1.2 = 120
|
||||||
|
@ -477,7 +459,6 @@ def test_button_spacing():
|
||||||
c.ivl = 1
|
c.ivl = 1
|
||||||
c.start_timer()
|
c.start_timer()
|
||||||
c.flush()
|
c.flush()
|
||||||
col.reset()
|
|
||||||
ni = col.sched.nextIvlStr
|
ni = col.sched.nextIvlStr
|
||||||
wo = without_unicode_isolation
|
wo = without_unicode_isolation
|
||||||
assert wo(ni(c, 2)) == "2d"
|
assert wo(ni(c, 2)) == "2d"
|
||||||
|
@ -491,47 +472,12 @@ def test_button_spacing():
|
||||||
assert wo(ni(c, 2)) == "1d"
|
assert wo(ni(c, 2)) == "1d"
|
||||||
|
|
||||||
|
|
||||||
def test_overdue_lapse():
|
|
||||||
# disabled in commit 3069729776990980f34c25be66410e947e9d51a2
|
|
||||||
return
|
|
||||||
col = getEmptyCol() # pylint: disable=unreachable
|
|
||||||
# add a note
|
|
||||||
note = col.newNote()
|
|
||||||
note["Front"] = "one"
|
|
||||||
col.addNote(note)
|
|
||||||
# simulate a review that was lapsed and is now due for its normal review
|
|
||||||
c = note.cards()[0]
|
|
||||||
c.type = CARD_TYPE_REV
|
|
||||||
c.queue = QUEUE_TYPE_LRN
|
|
||||||
c.due = -1
|
|
||||||
c.odue = -1
|
|
||||||
c.factor = STARTING_FACTOR
|
|
||||||
c.left = 2002
|
|
||||||
c.ivl = 0
|
|
||||||
c.flush()
|
|
||||||
# checkpoint
|
|
||||||
col.save()
|
|
||||||
col.sched.reset()
|
|
||||||
assert col.sched.counts() == (0, 2, 0)
|
|
||||||
c = col.sched.getCard()
|
|
||||||
col.sched.answerCard(c, 3)
|
|
||||||
# it should be due tomorrow
|
|
||||||
assert c.due == col.sched.today + 1
|
|
||||||
# revert to before
|
|
||||||
col.rollback()
|
|
||||||
# with the default settings, the overdue card should be removed from the
|
|
||||||
# learning queue
|
|
||||||
col.sched.reset()
|
|
||||||
assert col.sched.counts() == (0, 0, 1)
|
|
||||||
|
|
||||||
|
|
||||||
def test_nextIvl():
|
def test_nextIvl():
|
||||||
col = getEmptyCol()
|
col = getEmptyCol()
|
||||||
note = col.newNote()
|
note = col.newNote()
|
||||||
note["Front"] = "one"
|
note["Front"] = "one"
|
||||||
note["Back"] = "two"
|
note["Back"] = "two"
|
||||||
col.addNote(note)
|
col.addNote(note)
|
||||||
col.reset()
|
|
||||||
conf = col.decks.config_dict_for_deck_id(1)
|
conf = col.decks.config_dict_for_deck_id(1)
|
||||||
conf["new"]["delays"] = [0.5, 3, 10]
|
conf["new"]["delays"] = [0.5, 3, 10]
|
||||||
conf["lapse"]["delays"] = [1, 5, 9]
|
conf["lapse"]["delays"] = [1, 5, 9]
|
||||||
|
@ -611,7 +557,6 @@ def test_bury():
|
||||||
c2.load()
|
c2.load()
|
||||||
assert c2.queue == QUEUE_TYPE_SIBLING_BURIED
|
assert c2.queue == QUEUE_TYPE_SIBLING_BURIED
|
||||||
|
|
||||||
col.reset()
|
|
||||||
assert not col.sched.getCard()
|
assert not col.sched.getCard()
|
||||||
|
|
||||||
col.sched.unbury_deck(deck_id=col.decks.get_current_id(), mode=UnburyDeck.USER_ONLY)
|
col.sched.unbury_deck(deck_id=col.decks.get_current_id(), mode=UnburyDeck.USER_ONLY)
|
||||||
|
@ -629,8 +574,6 @@ def test_bury():
|
||||||
col.sched.bury_cards([c.id, c2.id])
|
col.sched.bury_cards([c.id, c2.id])
|
||||||
col.sched.unbury_deck(deck_id=col.decks.get_current_id())
|
col.sched.unbury_deck(deck_id=col.decks.get_current_id())
|
||||||
|
|
||||||
col.reset()
|
|
||||||
|
|
||||||
assert col.sched.counts() == (2, 0, 0)
|
assert col.sched.counts() == (2, 0, 0)
|
||||||
|
|
||||||
|
|
||||||
|
@ -641,14 +584,11 @@ def test_suspend():
|
||||||
col.addNote(note)
|
col.addNote(note)
|
||||||
c = note.cards()[0]
|
c = note.cards()[0]
|
||||||
# suspending
|
# suspending
|
||||||
col.reset()
|
|
||||||
assert col.sched.getCard()
|
assert col.sched.getCard()
|
||||||
col.sched.suspend_cards([c.id])
|
col.sched.suspend_cards([c.id])
|
||||||
col.reset()
|
|
||||||
assert not col.sched.getCard()
|
assert not col.sched.getCard()
|
||||||
# unsuspending
|
# unsuspending
|
||||||
col.sched.unsuspend_cards([c.id])
|
col.sched.unsuspend_cards([c.id])
|
||||||
col.reset()
|
|
||||||
assert col.sched.getCard()
|
assert col.sched.getCard()
|
||||||
# should cope with rev cards being relearnt
|
# should cope with rev cards being relearnt
|
||||||
c.due = 0
|
c.due = 0
|
||||||
|
@ -656,7 +596,6 @@ def test_suspend():
|
||||||
c.type = CARD_TYPE_REV
|
c.type = CARD_TYPE_REV
|
||||||
c.queue = QUEUE_TYPE_REV
|
c.queue = QUEUE_TYPE_REV
|
||||||
c.flush()
|
c.flush()
|
||||||
col.reset()
|
|
||||||
c = col.sched.getCard()
|
c = col.sched.getCard()
|
||||||
col.sched.answerCard(c, 1)
|
col.sched.answerCard(c, 1)
|
||||||
assert c.due >= time.time()
|
assert c.due >= time.time()
|
||||||
|
@ -699,12 +638,10 @@ def test_filt_reviewing_early_normal():
|
||||||
c.factor = STARTING_FACTOR
|
c.factor = STARTING_FACTOR
|
||||||
c.start_timer()
|
c.start_timer()
|
||||||
c.flush()
|
c.flush()
|
||||||
col.reset()
|
|
||||||
assert col.sched.counts() == (0, 0, 0)
|
assert col.sched.counts() == (0, 0, 0)
|
||||||
# create a dynamic deck and refresh it
|
# create a dynamic deck and refresh it
|
||||||
did = col.decks.new_filtered("Cram")
|
did = col.decks.new_filtered("Cram")
|
||||||
col.sched.rebuild_filtered_deck(did)
|
col.sched.rebuild_filtered_deck(did)
|
||||||
col.reset()
|
|
||||||
# should appear as normal in the deck list
|
# should appear as normal in the deck list
|
||||||
assert sorted(col.sched.deck_due_tree().children)[0].review_count == 1
|
assert sorted(col.sched.deck_due_tree().children)[0].review_count == 1
|
||||||
# and should appear in the counts
|
# and should appear in the counts
|
||||||
|
@ -731,7 +668,6 @@ def test_filt_reviewing_early_normal():
|
||||||
c.due = col.sched.today + 75
|
c.due = col.sched.today + 75
|
||||||
c.flush()
|
c.flush()
|
||||||
col.sched.rebuild_filtered_deck(did)
|
col.sched.rebuild_filtered_deck(did)
|
||||||
col.reset()
|
|
||||||
c = col.sched.getCard()
|
c = col.sched.getCard()
|
||||||
|
|
||||||
assert col.sched.nextIvl(c, 2) == 100 * 1.2 / 2 * 86400
|
assert col.sched.nextIvl(c, 2) == 100 * 1.2 / 2 * 86400
|
||||||
|
@ -763,7 +699,6 @@ def test_filt_keep_lrn_state():
|
||||||
# create a dynamic deck and refresh it
|
# create a dynamic deck and refresh it
|
||||||
did = col.decks.new_filtered("Cram")
|
did = col.decks.new_filtered("Cram")
|
||||||
col.sched.rebuild_filtered_deck(did)
|
col.sched.rebuild_filtered_deck(did)
|
||||||
col.reset()
|
|
||||||
|
|
||||||
# card should still be in learning state
|
# card should still be in learning state
|
||||||
c.load()
|
c.load()
|
||||||
|
@ -800,7 +735,6 @@ def test_preview():
|
||||||
cram["resched"] = False
|
cram["resched"] = False
|
||||||
col.decks.save(cram)
|
col.decks.save(cram)
|
||||||
col.sched.rebuild_filtered_deck(did)
|
col.sched.rebuild_filtered_deck(did)
|
||||||
col.reset()
|
|
||||||
# grab the first card
|
# grab the first card
|
||||||
c = col.sched.getCard()
|
c = col.sched.getCard()
|
||||||
|
|
||||||
|
@ -860,7 +794,6 @@ def test_ordcycle():
|
||||||
conf = col.decks.get_config(1)
|
conf = col.decks.get_config(1)
|
||||||
conf["new"]["bury"] = False
|
conf["new"]["bury"] = False
|
||||||
col.decks.save(conf)
|
col.decks.save(conf)
|
||||||
col.reset()
|
|
||||||
|
|
||||||
# ordinals should arrive in order
|
# ordinals should arrive in order
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
|
@ -879,7 +812,6 @@ def test_counts_idx_new():
|
||||||
note["Front"] = "two"
|
note["Front"] = "two"
|
||||||
note["Back"] = "two"
|
note["Back"] = "two"
|
||||||
col.addNote(note)
|
col.addNote(note)
|
||||||
col.reset()
|
|
||||||
assert col.sched.counts() == (2, 0, 0)
|
assert col.sched.counts() == (2, 0, 0)
|
||||||
c = col.sched.getCard()
|
c = col.sched.getCard()
|
||||||
# getCard does not decrement counts
|
# getCard does not decrement counts
|
||||||
|
@ -903,7 +835,6 @@ def test_repCounts():
|
||||||
note = col.newNote()
|
note = col.newNote()
|
||||||
note["Front"] = "two"
|
note["Front"] = "two"
|
||||||
col.addNote(note)
|
col.addNote(note)
|
||||||
col.reset()
|
|
||||||
# lrnReps should be accurate on pass/fail
|
# lrnReps should be accurate on pass/fail
|
||||||
assert col.sched.counts() == (2, 0, 0)
|
assert col.sched.counts() == (2, 0, 0)
|
||||||
col.sched.answerCard(col.sched.getCard(), 1)
|
col.sched.answerCard(col.sched.getCard(), 1)
|
||||||
|
@ -924,7 +855,6 @@ def test_repCounts():
|
||||||
note = col.newNote()
|
note = col.newNote()
|
||||||
note["Front"] = "four"
|
note["Front"] = "four"
|
||||||
col.addNote(note)
|
col.addNote(note)
|
||||||
col.reset()
|
|
||||||
# initial pass and immediate graduate should be correct too
|
# initial pass and immediate graduate should be correct too
|
||||||
assert col.sched.counts() == (2, 0, 0)
|
assert col.sched.counts() == (2, 0, 0)
|
||||||
col.sched.answerCard(col.sched.getCard(), 3)
|
col.sched.answerCard(col.sched.getCard(), 3)
|
||||||
|
@ -945,7 +875,6 @@ def test_repCounts():
|
||||||
note = col.newNote()
|
note = col.newNote()
|
||||||
note["Front"] = "six"
|
note["Front"] = "six"
|
||||||
col.addNote(note)
|
col.addNote(note)
|
||||||
col.reset()
|
|
||||||
assert col.sched.counts() == (1, 0, 1)
|
assert col.sched.counts() == (1, 0, 1)
|
||||||
col.sched.answerCard(col.sched.getCard(), 1)
|
col.sched.answerCard(col.sched.getCard(), 1)
|
||||||
assert col.sched.counts() == (1, 1, 0)
|
assert col.sched.counts() == (1, 1, 0)
|
||||||
|
@ -964,7 +893,6 @@ def test_timing():
|
||||||
c.due = 0
|
c.due = 0
|
||||||
c.flush()
|
c.flush()
|
||||||
# fail the first one
|
# fail the first one
|
||||||
col.reset()
|
|
||||||
c = col.sched.getCard()
|
c = col.sched.getCard()
|
||||||
col.sched.answerCard(c, 1)
|
col.sched.answerCard(c, 1)
|
||||||
# the next card should be another review
|
# the next card should be another review
|
||||||
|
@ -973,7 +901,6 @@ def test_timing():
|
||||||
# if the failed card becomes due, it should show first
|
# if the failed card becomes due, it should show first
|
||||||
c.due = int_time() - 1
|
c.due = int_time() - 1
|
||||||
c.flush()
|
c.flush()
|
||||||
col.reset()
|
|
||||||
c = col.sched.getCard()
|
c = col.sched.getCard()
|
||||||
assert c.queue == QUEUE_TYPE_LRN
|
assert c.queue == QUEUE_TYPE_LRN
|
||||||
|
|
||||||
|
@ -988,7 +915,6 @@ def test_collapse():
|
||||||
note = col.newNote()
|
note = col.newNote()
|
||||||
note["Front"] = "two"
|
note["Front"] = "two"
|
||||||
col.addNote(note)
|
col.addNote(note)
|
||||||
col.reset()
|
|
||||||
# first note
|
# first note
|
||||||
c = col.sched.getCard()
|
c = col.sched.getCard()
|
||||||
col.sched.answerCard(c, 1)
|
col.sched.answerCard(c, 1)
|
||||||
|
@ -1032,7 +958,6 @@ def test_deckDue():
|
||||||
note["Front"] = "three"
|
note["Front"] = "three"
|
||||||
note.note_type()["did"] = col.decks.id("foo::baz")
|
note.note_type()["did"] = col.decks.id("foo::baz")
|
||||||
col.addNote(note)
|
col.addNote(note)
|
||||||
col.reset()
|
|
||||||
assert len(col.decks.all_names_and_ids()) == 5
|
assert len(col.decks.all_names_and_ids()) == 5
|
||||||
tree = col.sched.deck_due_tree().children
|
tree = col.sched.deck_due_tree().children
|
||||||
assert tree[0].name == "Default"
|
assert tree[0].name == "Default"
|
||||||
|
@ -1078,7 +1003,6 @@ def test_deckFlow():
|
||||||
note["Front"] = "three"
|
note["Front"] = "three"
|
||||||
default1 = note.note_type()["did"] = col.decks.id("Default::1")
|
default1 = note.note_type()["did"] = col.decks.id("Default::1")
|
||||||
col.addNote(note)
|
col.addNote(note)
|
||||||
col.reset()
|
|
||||||
assert col.sched.counts() == (3, 0, 0)
|
assert col.sched.counts() == (3, 0, 0)
|
||||||
# should get top level one first, then ::1, then ::2
|
# should get top level one first, then ::1, then ::2
|
||||||
for i in "one", "three", "two":
|
for i in "one", "three", "two":
|
||||||
|
@ -1142,10 +1066,8 @@ def test_forget():
|
||||||
c.ivl = 100
|
c.ivl = 100
|
||||||
c.due = 0
|
c.due = 0
|
||||||
c.flush()
|
c.flush()
|
||||||
col.reset()
|
|
||||||
assert col.sched.counts() == (0, 0, 1)
|
assert col.sched.counts() == (0, 0, 1)
|
||||||
col.sched.forgetCards([c.id])
|
col.sched.forgetCards([c.id])
|
||||||
col.reset()
|
|
||||||
assert col.sched.counts() == (1, 0, 0)
|
assert col.sched.counts() == (1, 0, 0)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1183,7 +1105,6 @@ def test_norelearn():
|
||||||
c.ivl = 100
|
c.ivl = 100
|
||||||
c.start_timer()
|
c.start_timer()
|
||||||
c.flush()
|
c.flush()
|
||||||
col.reset()
|
|
||||||
col.sched.answerCard(c, 1)
|
col.sched.answerCard(c, 1)
|
||||||
col.sched._cardConf(c)["lapse"]["delays"] = []
|
col.sched._cardConf(c)["lapse"]["delays"] = []
|
||||||
col.sched.answerCard(c, 1)
|
col.sched.answerCard(c, 1)
|
||||||
|
@ -1235,7 +1156,6 @@ def test_negativeDueFilter():
|
||||||
did = col.decks.new_filtered("Cram")
|
did = col.decks.new_filtered("Cram")
|
||||||
col.sched.rebuild_filtered_deck(did)
|
col.sched.rebuild_filtered_deck(did)
|
||||||
col.sched.empty_filtered_deck(did)
|
col.sched.empty_filtered_deck(did)
|
||||||
col.reset()
|
|
||||||
|
|
||||||
c.load()
|
c.load()
|
||||||
assert c.due == -5
|
assert c.due == -5
|
||||||
|
@ -1250,7 +1170,6 @@ def test_initial_repeat():
|
||||||
note["Back"] = "two"
|
note["Back"] = "two"
|
||||||
col.addNote(note)
|
col.addNote(note)
|
||||||
|
|
||||||
col.reset()
|
|
||||||
c = col.sched.getCard()
|
c = col.sched.getCard()
|
||||||
col.sched.answerCard(c, 2)
|
col.sched.answerCard(c, 2)
|
||||||
# should be due in ~ 5.5 mins
|
# should be due in ~ 5.5 mins
|
||||||
|
|
|
@ -17,7 +17,6 @@ def test_stats():
|
||||||
# card stats
|
# card stats
|
||||||
card_stats = col.card_stats_data(c.id)
|
card_stats = col.card_stats_data(c.id)
|
||||||
assert card_stats.note_id == note.id
|
assert card_stats.note_id == note.id
|
||||||
col.reset()
|
|
||||||
c = col.sched.getCard()
|
c = col.sched.getCard()
|
||||||
col.sched.answerCard(c, 3)
|
col.sched.answerCard(c, 3)
|
||||||
col.sched.answerCard(c, 2)
|
col.sched.answerCard(c, 2)
|
||||||
|
|
|
@ -38,7 +38,6 @@ class DeckConf(QDialog):
|
||||||
self.form = aqt.forms.dconf.Ui_Dialog()
|
self.form = aqt.forms.dconf.Ui_Dialog()
|
||||||
self.form.setupUi(self)
|
self.form.setupUi(self)
|
||||||
gui_hooks.deck_conf_did_setup_ui_form(self)
|
gui_hooks.deck_conf_did_setup_ui_form(self)
|
||||||
self.mw.checkpoint(tr.actions_options())
|
|
||||||
self.setupCombos()
|
self.setupCombos()
|
||||||
self.setupConfs()
|
self.setupConfs()
|
||||||
qconnect(
|
qconnect(
|
||||||
|
|
|
@ -193,7 +193,6 @@ class ImportDialog(QDialog):
|
||||||
)
|
)
|
||||||
self.mw.col.models.save(self.importer.model, updateReqs=False)
|
self.mw.col.models.save(self.importer.model, updateReqs=False)
|
||||||
self.mw.progress.start()
|
self.mw.progress.start()
|
||||||
self.mw.checkpoint(tr.actions_import())
|
|
||||||
|
|
||||||
def on_done(future: Future) -> None:
|
def on_done(future: Future) -> None:
|
||||||
self.mw.progress.finish()
|
self.mw.progress.finish()
|
||||||
|
|
|
@ -28,6 +28,7 @@ import aqt.toolbar
|
||||||
import aqt.webview
|
import aqt.webview
|
||||||
from anki import hooks
|
from anki import hooks
|
||||||
from anki._backend import RustBackend as _RustBackend
|
from anki._backend import RustBackend as _RustBackend
|
||||||
|
from anki._legacy import deprecated
|
||||||
from anki.collection import Collection, Config, OpChanges, UndoStatus
|
from anki.collection import Collection, Config, OpChanges, UndoStatus
|
||||||
from anki.decks import DeckDict, DeckId
|
from anki.decks import DeckDict, DeckId
|
||||||
from anki.hooks import runHook
|
from anki.hooks import runHook
|
||||||
|
@ -918,9 +919,7 @@ title="{}" {}>{}</button>""".format(
|
||||||
signal.signal(signal.SIGTERM, self.onUnixSignal)
|
signal.signal(signal.SIGTERM, self.onUnixSignal)
|
||||||
|
|
||||||
def onUnixSignal(self, signum: Any, frame: Any) -> None:
|
def onUnixSignal(self, signum: Any, frame: Any) -> None:
|
||||||
# schedule a rollback & quit
|
|
||||||
def quit() -> None:
|
def quit() -> None:
|
||||||
self.col.db.rollback()
|
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
self.progress.single_shot(100, quit)
|
self.progress.single_shot(100, quit)
|
||||||
|
@ -1021,7 +1020,6 @@ title="{}" {}>{}</button>""".format(
|
||||||
"Caller should ensure auth available."
|
"Caller should ensure auth available."
|
||||||
|
|
||||||
def on_collection_sync_finished() -> None:
|
def on_collection_sync_finished() -> None:
|
||||||
self.col.clear_python_undo()
|
|
||||||
self.col.models._clear_cache()
|
self.col.models._clear_cache()
|
||||||
gui_hooks.sync_did_finish()
|
gui_hooks.sync_did_finish()
|
||||||
self.reset()
|
self.reset()
|
||||||
|
@ -1189,15 +1187,14 @@ title="{}" {}>{}</button>""".format(
|
||||||
self.form.actionRedo.setVisible(info.show_redo)
|
self.form.actionRedo.setVisible(info.show_redo)
|
||||||
gui_hooks.undo_state_did_change(info)
|
gui_hooks.undo_state_did_change(info)
|
||||||
|
|
||||||
|
@deprecated(info="checkpoints are no longer supported")
|
||||||
def checkpoint(self, name: str) -> None:
|
def checkpoint(self, name: str) -> None:
|
||||||
self.col.save(name)
|
pass
|
||||||
self.update_undo_actions()
|
|
||||||
|
|
||||||
|
@deprecated(info="saving is automatic")
|
||||||
def autosave(self) -> None:
|
def autosave(self) -> None:
|
||||||
self.col.autosave()
|
pass
|
||||||
self.update_undo_actions()
|
|
||||||
|
|
||||||
maybeEnableUndo = update_undo_actions
|
|
||||||
onUndo = undo
|
onUndo = undo
|
||||||
|
|
||||||
# Other menu operations
|
# Other menu operations
|
||||||
|
@ -1213,7 +1210,6 @@ title="{}" {}>{}</button>""".format(
|
||||||
aqt.dialogs.open("EditCurrent", self)
|
aqt.dialogs.open("EditCurrent", self)
|
||||||
|
|
||||||
def onOverview(self) -> None:
|
def onOverview(self) -> None:
|
||||||
self.col.reset()
|
|
||||||
self.moveToState("overview")
|
self.moveToState("overview")
|
||||||
|
|
||||||
def onStats(self) -> None:
|
def onStats(self) -> None:
|
||||||
|
@ -1461,10 +1457,6 @@ title="{}" {}>{}</button>""".format(
|
||||||
)
|
)
|
||||||
|
|
||||||
def _create_backup_with_progress(self, user_initiated: bool) -> None:
|
def _create_backup_with_progress(self, user_initiated: bool) -> None:
|
||||||
# if there's a legacy undo op, try again later
|
|
||||||
if not user_initiated and self.col.legacy_checkpoint_pending():
|
|
||||||
return
|
|
||||||
|
|
||||||
# The initial copy will display a progress window if it takes too long
|
# The initial copy will display a progress window if it takes too long
|
||||||
def backup(col: Collection) -> bool:
|
def backup(col: Collection) -> bool:
|
||||||
return col.create_backup(
|
return col.create_backup(
|
||||||
|
@ -1483,7 +1475,6 @@ title="{}" {}>{}</button>""".format(
|
||||||
)
|
)
|
||||||
|
|
||||||
def after_backup_started(created: bool) -> None:
|
def after_backup_started(created: bool) -> None:
|
||||||
# Legacy checkpoint may have expired.
|
|
||||||
self.update_undo_actions()
|
self.update_undo_actions()
|
||||||
|
|
||||||
if user_initiated and not created:
|
if user_initiated and not created:
|
||||||
|
|
|
@ -148,7 +148,6 @@ def on_op_finished(
|
||||||
mw: aqt.main.AnkiQt, result: ResultWithChanges, initiator: object | None
|
mw: aqt.main.AnkiQt, result: ResultWithChanges, initiator: object | None
|
||||||
) -> None:
|
) -> None:
|
||||||
mw.update_undo_actions()
|
mw.update_undo_actions()
|
||||||
mw.autosave()
|
|
||||||
|
|
||||||
if isinstance(result, OpChanges):
|
if isinstance(result, OpChanges):
|
||||||
changes = result
|
changes = result
|
||||||
|
|
|
@ -3,13 +3,12 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from anki.collection import LegacyCheckpoint, OpChanges, OpChangesAfterUndo, Preferences
|
from anki.collection import OpChanges, OpChangesAfterUndo, Preferences
|
||||||
from anki.errors import UndoEmpty
|
from anki.errors import UndoEmpty
|
||||||
from anki.types import assert_exhaustive
|
|
||||||
from aqt import gui_hooks
|
from aqt import gui_hooks
|
||||||
from aqt.operations import CollectionOp
|
from aqt.operations import CollectionOp
|
||||||
from aqt.qt import QWidget
|
from aqt.qt import QWidget
|
||||||
from aqt.utils import showInfo, showWarning, tooltip, tr
|
from aqt.utils import showWarning, tooltip, tr
|
||||||
|
|
||||||
|
|
||||||
def undo(*, parent: QWidget) -> None:
|
def undo(*, parent: QWidget) -> None:
|
||||||
|
@ -20,10 +19,7 @@ def undo(*, parent: QWidget) -> None:
|
||||||
tooltip(tr.undo_action_undone(action=out.operation), parent=parent)
|
tooltip(tr.undo_action_undone(action=out.operation), parent=parent)
|
||||||
|
|
||||||
def on_failure(exc: Exception) -> None:
|
def on_failure(exc: Exception) -> None:
|
||||||
if isinstance(exc, UndoEmpty):
|
if not isinstance(exc, UndoEmpty):
|
||||||
# backend has no undo, but there may be a checkpoint waiting
|
|
||||||
_legacy_undo(parent=parent)
|
|
||||||
else:
|
|
||||||
showWarning(str(exc), parent=parent)
|
showWarning(str(exc), parent=parent)
|
||||||
|
|
||||||
CollectionOp(parent, lambda col: col.undo()).success(on_success).failure(
|
CollectionOp(parent, lambda col: col.undo()).success(on_success).failure(
|
||||||
|
@ -40,35 +36,6 @@ def redo(*, parent: QWidget) -> None:
|
||||||
CollectionOp(parent, lambda col: col.redo()).success(on_success).run_in_background()
|
CollectionOp(parent, lambda col: col.redo()).success(on_success).run_in_background()
|
||||||
|
|
||||||
|
|
||||||
def _legacy_undo(*, parent: QWidget) -> None:
|
|
||||||
from aqt import mw
|
|
||||||
|
|
||||||
assert mw
|
|
||||||
assert mw.col
|
|
||||||
|
|
||||||
result = mw.col.undo_legacy()
|
|
||||||
|
|
||||||
if result is None:
|
|
||||||
# should not happen
|
|
||||||
showInfo(tr.actions_nothing_to_undo(), parent=parent)
|
|
||||||
mw.update_undo_actions()
|
|
||||||
return
|
|
||||||
|
|
||||||
elif isinstance(result, LegacyCheckpoint):
|
|
||||||
name = result.name
|
|
||||||
|
|
||||||
else:
|
|
||||||
assert_exhaustive(result)
|
|
||||||
assert False
|
|
||||||
|
|
||||||
# full queue+gui reset required
|
|
||||||
mw.reset()
|
|
||||||
|
|
||||||
tooltip(tr.undo_action_undone(action=name), parent=parent)
|
|
||||||
gui_hooks.state_did_revert(name)
|
|
||||||
mw.update_undo_actions()
|
|
||||||
|
|
||||||
|
|
||||||
def set_preferences(
|
def set_preferences(
|
||||||
*, parent: QWidget, preferences: Preferences
|
*, parent: QWidget, preferences: Preferences
|
||||||
) -> CollectionOp[OpChanges]:
|
) -> CollectionOp[OpChanges]:
|
||||||
|
|
|
@ -64,7 +64,6 @@ class Overview:
|
||||||
def refresh(self) -> None:
|
def refresh(self) -> None:
|
||||||
def success(_counts: tuple) -> None:
|
def success(_counts: tuple) -> None:
|
||||||
self._refresh_needed = False
|
self._refresh_needed = False
|
||||||
self.mw.col.reset()
|
|
||||||
self._renderPage()
|
self._renderPage()
|
||||||
self._renderBottom()
|
self._renderBottom()
|
||||||
self.mw.web.setFocus()
|
self.mw.web.setFocus()
|
||||||
|
|
|
@ -325,7 +325,7 @@ class ProfileManager:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
c = Collection(path)
|
c = Collection(path)
|
||||||
c.close(save=False, downgrade=True)
|
c.close(downgrade=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
problem_profiles.append(name)
|
problem_profiles.append(name)
|
||||||
|
|
|
@ -178,7 +178,6 @@ class Reviewer:
|
||||||
|
|
||||||
def refresh_if_needed(self) -> None:
|
def refresh_if_needed(self) -> None:
|
||||||
if self._refresh_needed is RefreshNeeded.QUEUES:
|
if self._refresh_needed is RefreshNeeded.QUEUES:
|
||||||
self.mw.col.reset()
|
|
||||||
self.nextCard()
|
self.nextCard()
|
||||||
self.mw.fade_in_webview()
|
self.mw.fade_in_webview()
|
||||||
self._refresh_needed = None
|
self._refresh_needed = None
|
||||||
|
@ -444,7 +443,6 @@ class Reviewer:
|
||||||
def _after_answering(self, ease: Literal[1, 2, 3, 4]) -> None:
|
def _after_answering(self, ease: Literal[1, 2, 3, 4]) -> None:
|
||||||
gui_hooks.reviewer_did_answer_card(self, self.card, ease)
|
gui_hooks.reviewer_did_answer_card(self, self.card, ease)
|
||||||
self._answeredIds.append(self.card.id)
|
self._answeredIds.append(self.card.id)
|
||||||
self.mw.autosave()
|
|
||||||
if not self.check_timebox():
|
if not self.check_timebox():
|
||||||
self.nextCard()
|
self.nextCard()
|
||||||
|
|
||||||
|
|
|
@ -98,7 +98,6 @@ def sync_collection(mw: aqt.main.AnkiQt, on_done: Callable[[], None]) -> None:
|
||||||
timer.start(150)
|
timer.start(150)
|
||||||
|
|
||||||
def on_future_done(fut: Future[SyncOutput]) -> None:
|
def on_future_done(fut: Future[SyncOutput]) -> None:
|
||||||
mw.col.db.begin()
|
|
||||||
# scheduler version may have changed
|
# scheduler version may have changed
|
||||||
mw.col._load_scheduler()
|
mw.col._load_scheduler()
|
||||||
timer.stop()
|
timer.stop()
|
||||||
|
@ -121,7 +120,6 @@ def sync_collection(mw: aqt.main.AnkiQt, on_done: Callable[[], None]) -> None:
|
||||||
else:
|
else:
|
||||||
full_sync(mw, out, on_done)
|
full_sync(mw, out, on_done)
|
||||||
|
|
||||||
mw.col.save(trx=False)
|
|
||||||
mw.taskman.with_progress(
|
mw.taskman.with_progress(
|
||||||
lambda: mw.col.sync_collection(auth, mw.pm.media_syncing_enabled()),
|
lambda: mw.col.sync_collection(auth, mw.pm.media_syncing_enabled()),
|
||||||
on_future_done,
|
on_future_done,
|
||||||
|
|
|
@ -602,12 +602,6 @@ hooks = [
|
||||||
),
|
),
|
||||||
# UI state/refreshing
|
# UI state/refreshing
|
||||||
###################
|
###################
|
||||||
Hook(
|
|
||||||
name="state_did_revert",
|
|
||||||
args=["action: str"],
|
|
||||||
legacy_hook="revertedState",
|
|
||||||
doc="Legacy hook, called after undoing.",
|
|
||||||
),
|
|
||||||
Hook(
|
Hook(
|
||||||
name="state_did_undo",
|
name="state_did_undo",
|
||||||
args=["changes: OpChangesAfterUndo"],
|
args=["changes: OpChangesAfterUndo"],
|
||||||
|
|
Loading…
Reference in a new issue