Reviews and operations on the backend that support undoing can now be
committed immediately, so they will not be lost in the event of a crash.
This required tweaks to a few places:
- don't set collection mtime on save() unless changes were made in
Python, as otherwise we end up accidentally clearing the backend undo
queue
- autosave() is now run on every reset()
- garbage collection now runs in a timer, instead of relying on
autosave() to be run periodically
- use dataclasses for the review/checkpoint undo cases, instead of the
nasty ad-hoc list structure
- expose backend review undo to Python, and hook it into GUI
- redo is not currently exposed on the GUI, and the backend can only
cope with reviews done by the new scheduler at the moment
- the initial undo prototype code was bumping mtime/usn on undo, but
that was not ideal, as it was breaking the queue handling which expected
the mtime to match. The original rationale for bumping mtime/usn was
to avoid problems with syncing, but various operations like removing
a revlog can't be synced anyway - so we just need to ensure we clear the
undo queue prior to syncing
- fetch sfld and csum when fetching notes, to make it cheaper
to write them back out unmodified
- make `fields` private, and access it via accessors, so we can
still catch when fields have been mutated without calling
prepare_for_update()
- fix python importing code passing a string in as the checksum
https://forums.ankiweb.net/t/anki-2-1-41-beta/7305/59
When originally implemented in 21023ed3e5,
a given deck's limit was bound by its parents. This lead to a deck list
that seemed more logical in the parent limit < child limit case, as
child counts couldn't exceed a parent's, but it obscured the fact that
child decks could still be clicked on to show cards. And in the parent
limit > child limit case, the count shown for the child on the deck list
did not reflect how many cards were actually available and would be
delivered.
This change updates the reviewer to ignore parent limits when getting
review counts for the deck, which makes the behaviour consistent with
the deck list, which was recently changed to ignore parent limits.
Neither solution is ideal - this was a tradeoff v2 made in order to keep
fetching of review cards from multiple decks reasonably performant. The
experimental scheduling work moves back to respecting limits on
individual children, so this should hopefully improve in the future.
Also removed _revForDeck(), which was unused.
- Currently we just use 1.5x and 2x the normal preview delay; we could
change this in the future.
- Don't try to capture the current state; just use a flag to denote
exit status.
- Show (end) when exiting
Notes:
- The fuzz seed is now derived from the card id and # of reps, so
if a card is undone and done again, the same fuzz will be used.
- The intervals shown on the answer buttons now include the fuzz, instead
of hiding it from the user. This will prevent questions about due dates
being different to what was shown on the buttons, but will create
questions about due dates being different for cards with the same
interval, and some people may find it distracting for learning cards.
The new approach is easier to reason about, but time will tell
whether it's a net gain or not.
- The env var we were using to shift the clock away from rollover for
unit tests has been repurposed to also disable fuzzing, which simplifies
the tests.
- Cards in filtered decks without scheduling now have the preview delay
fuzzed.
- Sub-day learning cards are mostly fuzzed like before, but will apply
the up-to-5-minutes of fuzz regardless of the time of day.
- The answer buttons now round minute values, as the fuzz on short
intervals is distracting.
This is not the way the code is intended to be used, but making it
conform to the existing API allows us to exercise the existing unit
tests and provides partial backwards compatibility.
- Leech handling is currently broken
- Fix answered_at in wrong units, and not being used
This reverts commit 8372931b9b.
I fear this will be too disruptive - let's give AnkiDroid a bit more
time to catch up. Reverting this will mean new users are presented with
an upgrade notice on first startup, which looks a bit silly, but it's
probably the lesser of two evils.
- In corner cases, enabling the new timezone handling later can cause
reviews to shift forward or back a day, so it's best to have it on
by default.
- https://github.com/ankidroid/Anki-Android/issues/5805 has not landed
in a stable release yet, but will hopefully not be too far off by the
time 2.1.41 is released.
- Existing users will be unaffected, as the upgrade prompt in the previous
commit asks them if they use AnkiDroid.
- Users starting on AnkiDroid will be unaffected, as their collections
will still be on V1.
- The error message AnkiWeb gives when syncing an older AnkiDroid
with the new timezone enabled has been updated to direct users to the
preferences screen.
- Rework V2 upgrade so that it no longer resets cards in learning,
or empties filtered decks.
- V1 users will receive a message at the top of the deck list
encouraging them to upgrade, and they can upgrade directly from that
screen.
- The setting in the preferences screen has been removed, so users
will need to use an older Anki version if they wish to switch back to
V1.
- Prevent V2 exports with scheduling from being importable into a V1
collection - the code was previously allowing this when it shouldn't
have been.
- New collections still default to v1 at the moment.
Also add helper to get map of decks and deck configs, as there were
a few places in the codebase where that was required.
- SearchTerm -> SearchNode
- Operator -> Joiner; share between messages
- build_search_string() supports specifying AND/OR as a convenience
- group_searches() makes it easier to negate
While implementing the overdue search, I realised it would be nice to
be able to construct a search string with OR and NOT searches without
having to construct each part individually with build_search_string().
Changes:
- Extends SearchTerm to support a text search, which will be parsed
by the backend. This allows us to do things like wrap text in a group
or NOT node.
- Because SearchTerm->Node conversion can now fail with a parsing error,
it's switched over to TryFrom
- Switch concatenate_searches and replace_search_term to use SearchTerms,
so that they too don't require separate string building steps.
- Remove the unused normalize_search()
- Remove negate_search, as this is now an operation on a Node, and
users can wrap their search in SearchTerm(negated=...)
- Remove the match_any and negate args from build_search_string
Having done all this work, I've just realised that perhaps the original
JSON idea was more feasible than I first thought - if we wrote it out
to a string and re-parsed it, we would be able to leverage the existing
checks that occur at parsing stage.
The progress messages are only really intended to be consumed by Anki.
If consumption by add-ons was expected, we'd be better off keeping the
wrapper, as the API for oneofs in Python is quite awkward to use.
The old rescheduling dialog's two options have been split into two
separate menu items, "Forget", and "Set Due Date"
For cards that are not review cards, "Set Due Date" behaves like the
old reschedule option, changing the cards into a review card, and
and setting both the interval and due date to the provided number of
days.
When "Set Due Date" is applied to a review card, it no longer resets
the card's interval. Instead, it looks at how much the provided number
of days will change the original interval, and adjusts the interval by
that amount, so that cards that are answered earlier receive a smaller
next interval, and cards that are answered after a longer delay receive
a bonus.
For example, imagine a card was answered on day 5, and given an interval
of 10 days, so it has a due date of day 15.
- if on day 10 the due date is changed to day 12 (today+2), the card
is being scheduled 3 days earlier than it was supposed to be, so the
interval will be adjusted to 7 days.
- and if on day 10 the due date is changed to day 20, the interval will
be changed from 10 days to 15 days.
There is no separate option to reset the interval of a review card, but
it can be accomplished by forgetting the card(s), and then setting the
desired due date.
Other notes:
- Added the action to the review screen as well.
- Set the shortcut to Ctrl+Shift+D, and changed the existing Delete
Tags shortcut to Ctrl+Alt+Shift+A.
While mypy can understand nested references like ConfigBool.Key.COLLAPSE_RECENT,
PyCharm doesn't understand the metaclass syntax, and shows the definitions
as invalid.
- Closes#976
- Added helper to apply arbitrary colour to an icon.
- Fix#979 - low res icons in night mode.
- The icons and colours are not perfect - please feel free to send
through a PR if you can improve them.
- Convert colors dictionary into module consts, so we can
use code completion.
- Added "Edited Today" and "Due Tomorrow"
- Rename camelCase attribute to snake_case and tweak the wording
of some enum constants. We've already broken compatibility with the
major sidebar add-ons, so we may as well make these changes while we
can.
- Removed Filter button. Currently there is no exposed way to toggle
the Sidebar off - wonder if we still need it?
QTextEdit() will pin the CPU at 100% for seconds to minutes when
fed a large string to display - work around it by switching to
QPlainTextEdit().
Also strip HTML before showing the user - easier to read, and less
text to display. And turn off word wrap, as it makes it easier to skim,
and further reduces the work the widget needs to do.
https://forums.ankiweb.net/t/big-issue-where-anki-gets-slow-when-you-import-this-deck/7050
- anki._backend stores the protobuf files and rsbackend.py code
- pylib modules import protobuf messages directly from the
_pb2 files, and explicitly export any will be returned or consumed
by public pylib functions, so that calling code can import from pylib
- the "rsbackend" no longer imports and re-exports protobuf messages
- pylib can just consume them directly.
- move errors to errors.py
Still todo:
- rsbridge
- finishing the work on rsbackend, and check what we need to add
back to the original file location to avoid breaking add-ons
- IdList could be re-used for a cids: search in the future if required.
- Embedding the message means it's easy to access from Python as
an attribute of SearchTerm.
This was the behaviour before, but got lost when ._reset_counts() was
added. Also added the check back to schedv2:fillRev(), which also
appears to have gotten lost in the move to ._reset_counts()
Just a stop-gap fix until this code can get a proper rewrite.
- use the TimestampSecs newtype instead of raw i64s
- use FixedOffset instead of a minutes_west offset
- check localOffset each time the timing is calculated, and set it
if it's stale - even for v1.
- check for and fix missing rollover when calculating timing
- stop explicitly passing localOffset in the sync/start call
Because the logic is in rename_tag() now, it means we create a
checkpoint even if the tag is orphaned. This is because currently
checkpointing is a GUI responsibility. In the future we need to introduce
multi-level undo, and should move responsibility for managing it
to the backend.
Cast col.decks.selected() to int so the return type fits the annotation.
Thus, fix the comparison in col.decks.select() which was leading to
a superfluous db modification and in turn to a false indication of a
necessary sync right after another one in certain cases.
If the client's clock is behind AnkiWeb's, even by a few seconds,
we can end up with a situation where last_begin_at is updated after
the sync to a value less than the mtime we received from AnkiWeb,
causing the collection to be saved, which bumps the modtime.
Work around this by recording mtime at begin() time, and seeing if it
has changed in either direction.
Thanks to Rumo, who did the hard work looking into it:
https://forums.ankiweb.net/t/why-is-my-sync-button-blue/2078/21
Running and testing should be working on the three platforms, but
there's still a fair bit that needs to be done:
- Wheel building + testing in a venv still needs to be implemented.
- Python requirements still need to be compiled with piptool and pinned;
need to compile on all platforms then merge
- Cargo deps in cargo/ and rslib/ need to be cleaned up, and ideally
unified into one place
- Currently using rustls to work around openssl compilation issues
on Linux, but this will break corporate proxies with custom SSL
authorities; need to conditionally use openssl or use
https://github.com/seanmonstar/reqwest/pull/1058
- Makefiles and docs still need cleaning up
- It may make sense to reparent ts/* to the top level, as we don't
nest the other modules under a specific language.
- rspy and pylib must always be updated in lock-step, so merging
rspy into pylib as a private module would simplify things.
- Merging desktop-ftl and mobile-ftl into the core ftl would make
managing and updating translations easier.
- Obsolete scripts need removing.
- And probably more.
Makes the media check recognize files in <audio> and <object> tags as used.
They've been observed/supported by the WebView (checked: Anki, AnkiDroid) since just about forever already and are extremely useful if one knows a thing about web dev.
We want to avoid unburied != self.today, because the unburied time
is synchronized as part of the collection at the moment, and we don't
want a client with an older unburied time that was modified more
recently to cause cards to be unburied twice - so we only unbury
if today is more than 7 days earlier than the last unbury time.
Unsure what caused these users to end up in that state in the first
place:
https://anki.tenderapp.com/discussions/ankidesktop/41335-cards-remain-buried-forever-unless-manually-unburied
- blue for normal sync, red for full sync required
- refactor status fetching code so we don't hold a collection lock
during the network request, which slows things down
- fix sync spinner restarting when returning to deck list
- Use a separate abort handle, as the media sync is running
in the background and we need to be able to target them separately.
The current progress handling is going to need a rethink if we introduce
any other background tasks in the future.
- Roll back the transaction when interrupting.
Also:
- provide a way for the progress handler to skip the throttling so that
we can ensure progress is updated at the end of a stage
- show 'checking' at the end of full sync
the change that caused it:
https://github.com/dropbox/mypy-protobuf/issues/118
This is more awkward to handle now, as the types are only available
at type-checking time. Python's static typing is such a mess :-(
- .update() should update a single deck and preserve usn by default,
as that's what existing code expects
- decks are automatically renamed when they conflict with an existing
name
- wins back the performance lost by the decks and dconf not being
in memory, and the overhead of serializing data for DB calls
- card counts are no longer capped to 1000
- learn counts are currently still calculated separately - can't merge
v1 counts without changing the existing behaviour
- partially rendering the tree may yield more savings
- the old "mutate but don't save" approach to resetting the "done today"
counts no longer works, and was inefficient anyway - now we just check
the day when returning the count
- remove separate implementation for v1 scheduler
This is a stop-gap solution - a bigger refactor will need to wait
until the deck/note type changes have stabilized.
The progress bar add-ons appear to be the only active users of it;
they can switch their old code from iterating over the list to
simply locating the selected deck in deckDueTree(), as its counts should
summarize all the child decks.
- front/back/css shown in tabs
- front/back preview switchable; only one webview needs to be loaded
- dropdown to select cloze number in preview
- search box to search in front/back/css
Like adding individual cards, we now support importing material even
if it wouldn't generate any cards, and the old availOrds check can't
handle negated conditionals.
- changes are now committed in bulk when closing the dialog,
and can be canceled
- it's not necessary to save the note to the database to preview it
- duplicate fields are now shown as duplicates in the top list
- redraw preview more quickly
- use + instead of _ when deduplicating names, as the latter is a
glob character
Saves having to serialize the note fields and q/a templates, which
is particularly a win when rendering question/answer in the browse
screen.
Also some work towards being able to preview notes without having to
commit them to the database.
- notes with wrong field count are now recovered instead of
being deleted
- notes with missing note types are now recovered
- notes with missing cards are now recovered
- recover_missing_deck() still needs implementing
- checks required
- field changes are now applied when user closes dialog with save
button, in bulk
- models diag now fetches note type and saves it as required, instead
of holding on top a copy that can grow stale as changes are made in
subdialogs
- both dialogs now perform operations in the backend
- note.model() now fetches the note type on the fly, instead of
holding on to a copy that may become stale
Previously if the sum of child counts was above the parent's limit,
the deck tree would show a parent count above the parent's limit, and
clicking on the parent would show a lower count. The new deck tree
code never shows a parent count above the parent's limit, so this
part of the test is no longer valid.
- notetypes are fetched from the DB as needed, and cached in Python
- handle note type changes in the backend. Multiple operations can now
be performed in one go, but this is not currently exposed in the GUI.
- extra methods to grab sorted note type names quickly, and fetch by
name
- col.models.save() without a provided notetype is now a no-op
- note loading/saving handled in the backend
- notes with no valid cards can now be added
- templates can now be deleted even if they would previously
orphan notes
a number of fixmes have been left in notes.py and models.py