Commit graph

89 commits

Author SHA1 Message Date
RumovZ
b7f39cfb93 Add card meta for persisting custom scheduling state (#2040)
* Add card meta for persisting custom scheduling state

* Rename meta -> custom_data

* Enforce limits on size of custom data

Large values will slow down table scans of the cards table, and it's
easier to be strict now and possibly relax things in the future than
the opposite.

* Pack card states and customData into a single message

+ default customData to empty if it can't be parsed

Co-authored-by: Damien Elmes <gpg@ankiweb.net>
2022-09-02 11:22:49 +10:00
Damien Elmes
e734e720b5 Expose backend_proto publicly for AnkiDroid, and rename to pb
We were aliasing it on import half the time anyway
2022-06-27 15:27:53 +10:00
Damien Elmes
56a33fd447 Don't fsync media files on import
I was seeing import speeds of only 10-20 files a second before this
change.
2022-03-19 23:31:11 +10:00
RumovZ
8d05494ad0 Refactor export-import code and resolve fixmes (#1723)
* Write media files in chunks

* Test media file writing

* Add iter `ReadDirFiles`

* Remove ImportMediaError, fail fatally instead

Partially reverts commit e7cac9d1d6.

* Compare hashes of media files to be restored

* Improve `MediaCopier::copy()`

* Restore media files atomically with tempfile

* Make downgrade flag an enum

* Remove SchemaVersion::Latest in favour of Option

* Remove sha1 comparison again

* Remove unnecessary repr(u8) (dae)
2022-03-18 19:31:55 +10:00
Damien Elmes
00f8c448dc Ensure partial colpkg file removed if export fails 2022-03-17 20:58:36 +10:00
RumovZ
643795e383 Backups (#1685)
* Add zstd dep

* Implement backend backup with zstd

* Implement backup thinning

* Write backup meta

* Use new file ending anki21b

* Asynchronously backup on collection close in Rust

* Revert "Add zstd dep"

This reverts commit 3fcb2141d2.

* Add zstd again

* Take backup col path from col struct

* Fix formatting

* Implement backup restoring on backend

* Normalize restored media file names

* Refactor `extract_legacy_data()`

A bit cumbersome due to borrowing rules.

* Refactor

* Make thinning calendar-based and gradual

* Consider last kept backups of previous stages

* Import full apkgs and colpkgs with backend

* Expose new backup settings

* Test `BackupThinner` and make it deterministic

* Mark backup_path when closing optional

* Delete leaky timer

* Add progress updates for restoring media

* Write restored collection to tempfile first

* Do collection compression in the background thread

This has us currently storing an uncompressed and compressed copy of
the collection in memory (not ideal), but means the collection can be
closed without waiting for compression to complete. On a large collection,
this takes a close and reopen from about 0.55s to about 0.07s. The old
backup code for comparison: about 0.35s for compression off, about
8.5s for zip compression.

* Use multithreading in zstd compression

On my system, this reduces the compression time of a large collection
from about 0.55s to 0.08s.

* Stream compressed collection data into zip file

* Tweak backup explanation

+ Fix incorrect tab order for ignore accents option

* Decouple restoring backup and full import

In the first case, no profile is opened, unless the new collection
succeeds to load.
In the second case, either the old collection is reloaded or the new one
is loaded.

* Fix number gap in Progress message

* Don't revert backup when media fails but report it

* Tweak error flow

* Remove native BackupLimits enum

* Fix type annotation

* Add thinning test for whole year

* Satisfy linter

* Await async backup to finish

* Move restart disclaimer out of backup tab

Should be visible regardless of the current tab.

* Write restored collection in chunks

* Refactor

* Write media in chunks and refactor

* Log error if removing file fails

* join_backup_task -> await_backup_completion

* Refactor backup.rs

* Refactor backup meta and collection extraction

* Fix wrong error being returned

* Call sync_all() on new collection

* Add ImportError

* Store logger in Backend, instead of creating one on demand

init_backend() accepts a Logger rather than a log file, to allow other
callers to customize the logger if they wish.

In the future we may want to explore using the tracing crate as an
alternative; it's a bit more ergonomic, as a logger doesn't need to be
passed around, and it plays more nicely with async code.

* Sync file contents prior to rename; sync folder after rename.

* Limit backup creation to once per 30 min

* Use zstd::stream::copy_decode

* Make importing abortable

* Don't revert if backup media is aborted

* Set throttle implicitly

* Change force flag to minimum_backup_interval

* Don't attempt to open folders on Windows

* Join last backup thread before starting new one

Also refactor.

* Disable auto sync and backup when restoring again

* Force backup on full download

* Include the reason why a media file import failed, and the file path

- Introduce a FileIoError that contains a string representation of
the underlying I/O error, and an associated path. There are a few
places in the code where we're currently manually including the filename
in a custom error message, and this is a step towards a more consistent
approach (but we may be better served with a more general approach in
the future similar to Anyhow's .context())
- Move the error message into importing.ftl, as it's a bit neater
when error messages live in the same file as the rest of the messages
associated with some functionality.

* Fix importing of media files

* Minor wording tweaks

* Save an allocation

I18n strings with replacements are already strings, so we can skip the
extra allocation. Not that it matters here at all.

* Terminate import if file missing from archive

If a third-party tool is creating invalid archives, the user should know
about it. This should be rare, so I did not attempt to make it
translatable.

* Skip multithreaded compression on small collections

Co-authored-by: Damien Elmes <gpg@ankiweb.net>
2022-03-07 15:11:31 +10:00
Damien Elmes
9648d7081d Fix an intermittent failure in one of the Rust unit tests 2022-03-07 14:18:17 +10:00
RumovZ
238c159625 Original position (#1677)
* Replace Card.data with .original_position

* Use and update original position in v3

* Show original position in card info

* Revert restoring original position for now

* Fix pb card to/from pylib card

* Try original_position as the last pb field

* minor wording tweaks (dae)
2022-02-22 22:48:21 +10:00
Damien Elmes
594f957bbb add a builder for Collection 2021-11-06 14:43:41 +10:00
Damien Elmes
65f761b55c check for invalid collections in full_upload() 2021-09-13 11:50:13 +10:00
RumovZ
db7916fa11 Skip new notetype checks when importing apkg 2021-07-28 21:46:51 +02:00
Damien Elmes
8870a9c4d5 zero out graves before comparison, instead of at creation
Will allow us to turn the check back on in the future without a client
update
2021-06-30 10:08:52 +10:00
Damien Elmes
c0a1c04c3c ignore graves in sanity check 2021-06-30 09:31:02 +10:00
Damien Elmes
33a9afaa1b add back in missing check for upload size
Compression now happens up-front, so we can tell in advance if the
upload size has been exceeded.
2021-06-29 10:50:03 +10:00
Damien Elmes
ff095ed57b fix clippy lints for latest Rust 2021-06-21 13:09:36 +10:00
Damien Elmes
d89c538c48 in/out -> request/response
The saved characters weren't worth the increased difficulty when
reading, and the fact that we were deviating from protobuf norms.
2021-06-20 15:49:20 +10:00
Damien Elmes
3d0a3a5707 fix exporting of non-default deck configs 2021-05-31 16:27:58 +10:00
Damien Elmes
df068c2a17 update backend to support undoing of notetype changes 2021-04-30 12:54:59 +10:00
Damien Elmes
03ca227fd4 make it more ergonomic to search directly via nodes in Rust 2021-04-30 11:37:55 +10:00
Damien Elmes
d76ef9cde5 update Rust deps
- tokio 1.0
- updated reqwest, thanks to Rumo
- other minor dep updates

the reqwest build file has been split into two, as it was awkward
to manually update the combined file, and the platform gate is now
on the target in rslib/
2021-04-27 22:18:12 +10:00
Damien Elmes
592e13e967 deckconf -> deckconfig 2021-04-20 21:54:24 +10:00
Damien Elmes
363a843d07 tidy up Rust imports
rustfmt can do this automatically, but only when run with a nightly
toolchain, so it needs to be manually done for now - see rslib/rusfmt.toml
2021-04-18 18:38:54 +10:00
Damien Elmes
06dea7aa0a start on making deck config and schema/mod changes undoable
+ move timestamps into a struct in a separate file for convenience
2021-04-18 17:33:12 +10:00
Damien Elmes
eece6125d8 hide NativeName inner value, and require explicit accessors 2021-04-18 09:29:35 +10:00
RumovZ
32edd2b554 Give deck.name the newtype NativeDeckName
The deck name must be constructed by calling associated functions of
NativeDeckName, unless the name is guaranteed to be valid machine
name (like "Default").
NativeDeckName exposes methods to mutate the deck name and return
the human name.
The storage routines take &strs, but those should be slices of
NativeDeckNames to ensure machine form and normalization.
2021-04-17 22:47:04 +02:00
Damien Elmes
085f63ac1b cache scheduling info
Saves us having to recalculate it for each browser row
2021-04-05 17:09:53 +10:00
Damien Elmes
7e285f5ec8 tuple type for IoError 2021-04-01 17:40:35 +10:00
Damien Elmes
8c635f9337 move sync/network errors into separate file 2021-04-01 17:02:54 +10:00
Damien Elmes
cc54a9251e crate::err -> crate::error 2021-04-01 16:07:13 +10:00
Damien Elmes
d793c30526 switch NoteType to Notetype
When used as a variable, we were typically calling it a 'notetype', not
a 'note type'.
2021-03-27 22:03:19 +10:00
Damien Elmes
a90d5aa359 use mixed case for abbreviations in Rust code
So, this is fun. Apparently "DeckId" is considered preferable to the
"DeckID" were were using until now, and the latest clippy will start
warning about it. We could of course disable the warning, but probably
better to bite the bullet and switch to the naming that's generally
considered best.
2021-03-27 19:53:33 +10:00
Damien Elmes
effb0e2ab8 address some new clippy lints 2021-03-27 19:28:19 +10:00
Damien Elmes
9a7f0f9f5d i18n->tr in rslib/ to match Python/TS code 2021-03-27 12:09:51 +10:00
Damien Elmes
0c63a7c8c4 rework translation handling
Instead of generating a fluent.proto file with a giant enum, create
a .json file representing the translations that downstream consumers
can use for code generation.

This enables the generation of a separate method for each translation,
with a docstring that shows the actual text, and any required arguments
listed in the function signature.

The codebase is still using the old enum for now; updating it will need
to come in future commits, and the old enum will need to be kept
around, as add-ons are referencing it.

Other changes:

- move translation code into a separate crate
- store the translations on a per-file/module basis, which will allow
us to avoid sending 1000+ strings on each JS page load in the future
- drop the undocumented support for external .ftl files, that we weren't
using
- duplicate strings in translation files are now checked for at build
time
- fix i18n test failing when run outside Bazel
- drop slog dependency in i18n module
2021-03-26 09:41:32 +10:00
Damien Elmes
42e20461c0 undoable ops now return changes directly; add new *_ops.py files
- Introduced a new transact() method that wraps the return value
in a separate struct that describes the changes that were made.
- Changes are now gathered from the undo log, so we don't need to
guess at what was changed - eg if update_note() is called with identical
note contents, no changes are returned. Card changes will only be set
if cards were actually generated by the update_note() call, and tag
will only be set if a new tag was added.
- mw.perform_op() has been updated to expect the op to return the changes,
or a structure with the changes in it, and it will use them to fire the
change hook, instead of fetching the changes from undo_status(), so there
is no risk of race conditions.
- the various calls to mw.perform_op() have been split into separate
files like card_ops.py. Aside from making the code cleaner, this works
around a rather annoying issue with mypy. Because we run it with
no_strict_optional, mypy is happy to accept an operation that returns None,
despite the type signature saying it requires changes to be returned.
Turning no_strict_optional on for the whole codebase is not practical
at the moment, but we can enable it for individual files.

Still todo:
- The cursor keeps moving back to the start of a field when typing -
we need to ignore the refresh hook when we are the initiator.
- The busy cursor icon should probably be delayed a few hundreds ms.
- Still need to think about a nicer way of handling saveNow()
- op_made_changes(), op_affects_study_queue() might be better embedded
as properties in the object instead
2021-03-19 19:45:21 +10:00
RumovZ
d11e9a5d3f Borrow dids in remove_decks_and_child_decks 2021-03-10 15:56:54 +01:00
RumovZ
c018c31985 Merge branch 'master' into sidebar-tools 2021-03-10 10:34:36 +01:00
Damien Elmes
7e93000e4d undo support for config entries 2021-03-10 11:53:27 +10:00
Damien Elmes
3fcd9f9640 move note undo into separate file 2021-03-10 11:53:27 +10:00
Damien Elmes
8e9169b7c5 fix some clippy lints 2021-03-10 11:53:27 +10:00
Damien Elmes
1b6cc07e63 note deletion undo; refactoring
- transact() now automatically clears card queues unless an op
opts-out (and currently only AnswerCard does). This means there's no
risk of forgetting to clear the queues in an operation, or when undoing/
redoing
- CollectionOp->UndoableOp
- clear queues when redoing "answer card", instead of clearing redo
when clearing queues
2021-03-10 11:47:53 +10:00
Damien Elmes
160c90b840 rework undo
- 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
2021-03-10 11:47:53 +10:00
Damien Elmes
60f7f1ca7d support undoing deck mutations
This required refactoring the deck code a bit to split up the 'update'
and 'add' cases better.
2021-03-10 11:47:53 +10:00
Damien Elmes
e5aeb69728 initial work on undoing reviews+burying siblings
- 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
2021-03-10 11:47:53 +10:00
Damien Elmes
bee7925225 fix sync download failing when temp dir on different mount
https://forums.ankiweb.net/t/problems-with-2-1-41-on-arch-linux/8103
2021-03-09 11:44:49 +10:00
RumovZ
76116174e1 Make backend deck deletion take vec of ids 2021-02-26 19:52:02 +01:00
Damien Elmes
ae1491492f unbury on sync start
https://forums.ankiweb.net/t/unburying-works-differently-on-anki-desktop-ankiweb-and-ios-app/6909
2021-01-28 11:54:48 +10:00
Damien Elmes
028fdb2562 coerce some more invalid decimal values to ints during sync 2021-01-25 09:43:50 +10:00
Damien Elmes
75f06e21bc handle older clients not setting 'done' key when false 2021-01-24 09:05:17 +10:00
Damien Elmes
81b312ddd1 handle decks/notetypes with a duplicate name being sent in a sync
Typically caused by older clients, but could happen if the user added
the same name on different devices without syncing.

Also add an inactive test that was used to try track down this issue
and might be useful in the future.
2021-01-23 12:59:24 +10:00