Anki/proto/anki/import_export.proto
RumovZ 14de8451dc
Merging Notetypes on Import (#2612)
* Remember original id when importing notetype

* Reuse notetypes with matching original id

* Add field and template ids

* Enable merging imported notetypes

* Fix test

Note should be updated if the incoming note's notetype is
remapped to the existing note's notetype.
On the other hand, it should be skipped if its notetype id is mapped
to some new notetype.

* Change field and template ids to i32

* Add merge notetypes flag to proto message

* Add dialog for apkg import

* Move HelpModal into components

* Generalize import dialog

* Move SettingTitle into components

* Add help modal to ImportAnkiPackagePage

* Move SwitchRow into components

* Fix backend method import

* Make testable in browser

* Fix broken modal

* Wrap in container and fix margins

* Update commented Anki version of new proto fields

* Check ids when comparing notetype schemas

* Add tooltip for merging notetypes.

* Allow updating notes regardless of mtime

* Gitignore yarn-error.log

* Allow updating notetypes regardless of mtime

* Fix apkg help carousel

* Use i64s for template and field ids

* Add option to omit importing scheduling info

* Restore last settings in apkg import dialog

* Display error when getting metadata in webview

* Update manual links for apkg importing

* Apply suggestions from code review

Co-authored-by: Damien Elmes <dae@users.noreply.github.com>

* Omit schduling -> Import all cards as new cards

* Tweak importing-update-notes-help

* UpdateCondition → ImportAnkiPackageUpdateCondition

* Load keyboard.ftl

* Skip updating dupes in 'update alwyas' case

* Explain more when merging notetypes is required

* "omit scheduling" → "with scheduling"

* Skip updating notetype dupes if 'update always'

* Merge duplicated notetypes from previous imports

* Fix rebase aftermath

* Fix panic when merging

* Clarify 'update notetypes' help

* Mention 'merge notetypes' in the log

* Add a test which covers the previously panicking path

* Use nested ftl messages to ensure consistency

* Make order of merged fields deterministic

* Rewrite test to trigger panic

* Update version comment on new fields
2023-09-09 09:00:55 +10:00

221 lines
5.8 KiB
Protocol Buffer

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
syntax = "proto3";
option java_multiple_files = true;
package anki.import_export;
import "anki/cards.proto";
import "anki/collection.proto";
import "anki/notes.proto";
import "anki/generic.proto";
service ImportExportService {
rpc ImportAnkiPackage(ImportAnkiPackageRequest) returns (ImportResponse);
rpc GetImportAnkiPackagePresets(generic.Empty)
returns (ImportAnkiPackageOptions);
rpc ExportAnkiPackage(ExportAnkiPackageRequest) returns (generic.UInt32);
rpc GetCsvMetadata(CsvMetadataRequest) returns (CsvMetadata);
rpc ImportCsv(ImportCsvRequest) returns (ImportResponse);
rpc ExportNoteCsv(ExportNoteCsvRequest) returns (generic.UInt32);
rpc ExportCardCsv(ExportCardCsvRequest) returns (generic.UInt32);
rpc ImportJsonFile(generic.String) returns (ImportResponse);
rpc ImportJsonString(generic.String) returns (ImportResponse);
}
// Implicitly includes any of the above methods that are not listed in the
// backend service.
service BackendImportExportService {
rpc ImportCollectionPackage(ImportCollectionPackageRequest)
returns (generic.Empty);
rpc ExportCollectionPackage(ExportCollectionPackageRequest)
returns (generic.Empty);
}
message ImportCollectionPackageRequest {
string col_path = 1;
string backup_path = 2;
string media_folder = 3;
string media_db = 4;
}
message ExportCollectionPackageRequest {
string out_path = 1;
bool include_media = 2;
bool legacy = 3;
}
enum ImportAnkiPackageUpdateCondition {
IMPORT_ANKI_PACKAGE_UPDATE_CONDITION_IF_NEWER = 0;
IMPORT_ANKI_PACKAGE_UPDATE_CONDITION_ALWAYS = 1;
IMPORT_ANKI_PACKAGE_UPDATE_CONDITION_NEVER = 2;
}
message ImportAnkiPackageOptions {
bool merge_notetypes = 1;
ImportAnkiPackageUpdateCondition update_notes = 2;
ImportAnkiPackageUpdateCondition update_notetypes = 3;
bool with_scheduling = 4;
}
message ImportAnkiPackageRequest {
string package_path = 1;
ImportAnkiPackageOptions options = 2;
}
message ImportResponse {
message Note {
notes.NoteId id = 1;
repeated string fields = 2;
}
message Log {
repeated Note new = 1;
repeated Note updated = 2;
repeated Note duplicate = 3;
repeated Note conflicting = 4;
repeated Note first_field_match = 5;
repeated Note missing_notetype = 6;
repeated Note missing_deck = 7;
repeated Note empty_first_field = 8;
CsvMetadata.DupeResolution dupe_resolution = 9;
uint32 found_notes = 10;
}
collection.OpChanges changes = 1;
Log log = 2;
}
message ExportAnkiPackageRequest {
string out_path = 1;
bool with_scheduling = 2;
bool with_media = 3;
bool legacy = 4;
ExportLimit limit = 5;
}
message PackageMetadata {
enum Version {
VERSION_UNKNOWN = 0;
// When `meta` missing, and collection.anki2 file present.
VERSION_LEGACY_1 = 1;
// When `meta` missing, and collection.anki21 file present.
VERSION_LEGACY_2 = 2;
// Implies MediaEntry media map, and zstd compression.
// collection.21b file
VERSION_LATEST = 3;
}
Version version = 1;
}
message MediaEntries {
message MediaEntry {
string name = 1;
uint32 size = 2;
bytes sha1 = 3;
/// Legacy media maps may include gaps in the media list, so the original
/// file index is recorded when importing from a HashMap. This field is not
/// set when exporting.
optional uint32 legacy_zip_filename = 255;
}
repeated MediaEntry entries = 1;
}
message ImportCsvRequest {
string path = 1;
CsvMetadata metadata = 2;
}
message CsvMetadataRequest {
string path = 1;
optional CsvMetadata.Delimiter delimiter = 2;
optional int64 notetype_id = 3;
optional int64 deck_id = 4;
optional bool is_html = 5;
}
// Column indices are 1-based to make working with them in TS easier, where
// unset numerical fields default to 0.
message CsvMetadata {
enum DupeResolution {
UPDATE = 0;
PRESERVE = 1;
DUPLICATE = 2;
// UPDATE_IF_NEWER = 3;
}
// Order roughly in ascending expected frequency in note text, because the
// delimiter detection algorithm is stupidly picking the first one it
// encounters.
enum Delimiter {
TAB = 0;
PIPE = 1;
SEMICOLON = 2;
COLON = 3;
COMMA = 4;
SPACE = 5;
}
message MappedNotetype {
int64 id = 1;
// Source column indices for note fields. One-based. 0 means n/a.
repeated uint32 field_columns = 2;
}
Delimiter delimiter = 1;
bool is_html = 2;
repeated string global_tags = 3;
repeated string updated_tags = 4;
// Column names as defined by the file or empty strings otherwise. Also used
// to determine the number of columns.
repeated string column_labels = 5;
oneof deck {
int64 deck_id = 6;
// One-based. 0 means n/a.
uint32 deck_column = 7;
}
oneof notetype {
// One notetype for all rows with given column mapping.
MappedNotetype global_notetype = 8;
// Row-specific notetypes with automatic mapping by index.
// One-based. 0 means n/a.
uint32 notetype_column = 9;
}
enum MatchScope {
NOTETYPE = 0;
NOTETYPE_AND_DECK = 1;
}
// One-based. 0 means n/a.
uint32 tags_column = 10;
bool force_delimiter = 11;
bool force_is_html = 12;
repeated generic.StringList preview = 13;
uint32 guid_column = 14;
DupeResolution dupe_resolution = 15;
MatchScope match_scope = 16;
}
message ExportCardCsvRequest {
string out_path = 1;
bool with_html = 2;
ExportLimit limit = 3;
}
message ExportNoteCsvRequest {
string out_path = 1;
bool with_html = 2;
bool with_tags = 3;
bool with_deck = 4;
bool with_notetype = 5;
bool with_guid = 6;
ExportLimit limit = 7;
}
message ExportLimit {
oneof limit {
generic.Empty whole_collection = 1;
int64 deck_id = 2;
notes.NoteIds note_ids = 3;
cards.CardIds card_ids = 4;
}
}