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