diff --git a/WORKSPACE b/WORKSPACE index 3d3ecc885..26a551657 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -1,5 +1,5 @@ workspace( - name = "net_ankiweb_anki", + name = "ankidesktop", managed_directories = {"@npm": [ "ts/node_modules", ]}, diff --git a/defs.bzl b/defs.bzl index b3f5441bc..f65f0b02a 100644 --- a/defs.bzl +++ b/defs.bzl @@ -1,10 +1,10 @@ load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") load("@bazel_skylib//lib:versions.bzl", "versions") load("@rules_rust//rust:repositories.bzl", "rust_repositories") -load("@net_ankiweb_anki//cargo:crates.bzl", "raze_fetch_remote_crates") +load("@ankidesktop//cargo:crates.bzl", "raze_fetch_remote_crates") load(":python.bzl", "setup_local_python") -load(":protobuf.bzl", "setup_protobuf_binary") -load("//rslib:clang_format.bzl", "setup_clang_format") +load("//proto:protobuf.bzl", "setup_protobuf_binary") +load("//proto:format.bzl", "setup_clang_format") load("@build_bazel_rules_nodejs//:index.bzl", "node_repositories", "yarn_install") load("@io_bazel_rules_sass//:defs.bzl", "sass_repositories") load("@com_github_ali5h_rules_pip//:defs.bzl", "pip_import") @@ -35,7 +35,7 @@ def setup_deps(): pip_import( name = "py_deps", - requirements = "@net_ankiweb_anki//pip:requirements.txt", + requirements = "@ankidesktop//pip:requirements.txt", python_runtime = "@python//:python", ) @@ -44,12 +44,12 @@ def setup_deps(): python_runtime = "@python//:python", ) - node_repositories(package_json = ["@net_ankiweb_anki//ts:package.json"]) + node_repositories(package_json = ["@ankidesktop//ts:package.json"]) yarn_install( name = "npm", - package_json = "@net_ankiweb_anki//ts:package.json", - yarn_lock = "@net_ankiweb_anki//ts:yarn.lock", + package_json = "@ankidesktop//ts:package.json", + yarn_lock = "@ankidesktop//ts:yarn.lock", ) sass_repositories() diff --git a/docs/new-platform.md b/docs/new-platform.md index 20767cc84..ffdb67bb1 100644 --- a/docs/new-platform.md +++ b/docs/new-platform.md @@ -74,14 +74,14 @@ index eff3d9df2..fb2e9f7fe 100644 python_runtime = "@python//:python", ) -- node_repositories(package_json = ["@net_ankiweb_anki//ts:package.json"]) +- node_repositories(package_json = ["@ankidesktop//ts:package.json"]) + native.local_repository( + name = "local_node", + path = "local_node", + ) + + node_repositories( -+ package_json = ["@net_ankiweb_anki//ts:package.json"], ++ package_json = ["@ankidesktop//ts:package.json"], + vendored_node = "@local_node//:node", + ) diff --git a/rslib/.clang-format b/proto/.clang-format similarity index 100% rename from rslib/.clang-format rename to proto/.clang-format diff --git a/proto/.top_level b/proto/.top_level new file mode 100644 index 000000000..e69de29bb diff --git a/proto/BUILD.bazel b/proto/BUILD.bazel new file mode 100644 index 000000000..ad81b1cad --- /dev/null +++ b/proto/BUILD.bazel @@ -0,0 +1,32 @@ +# Copyright: Ankitects Pty Ltd and contributors +# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +load("@rules_proto//proto:defs.bzl", "proto_library") +load("//proto:clang_format.bzl", "proto_format") + +proto_format( + name = "format", + srcs = glob(["**/*.proto"]), + visibility = ["//visibility:public"], +) + +proto_library( + name = "backend_proto_lib", + srcs = glob(["**/*.proto"]), + # "" removes the "proto/" prefix + strip_import_prefix = "", + visibility = ["//visibility:public"], +) + +filegroup( + name = "proto", + srcs = glob(["**/*.proto"]), + visibility = ["//visibility:public"], +) + +exports_files([ + # for external workspace use + "format.py", + # an empty file we use to get the root proto/ path in the Rust build + ".top_level", +]) diff --git a/proto/anki/backend.proto b/proto/anki/backend.proto new file mode 100644 index 000000000..b69fb2120 --- /dev/null +++ b/proto/anki/backend.proto @@ -0,0 +1,63 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +syntax = "proto3"; + +package anki.backend; + +/// while the protobuf descriptors expose the order services are defined in, +/// that information is not available in prost, so we define an enum to make +/// sure all clients agree on the service index +enum ServiceIndex { + SERVICE_INDEX_SCHEDULER = 0; + SERVICE_INDEX_DECKS = 1; + SERVICE_INDEX_NOTES = 2; + SERVICE_INDEX_SYNC = 3; + SERVICE_INDEX_NOTETYPES = 4; + SERVICE_INDEX_CONFIG = 5; + SERVICE_INDEX_CARD_RENDERING = 6; + SERVICE_INDEX_DECK_CONFIG = 7; + SERVICE_INDEX_TAGS = 8; + SERVICE_INDEX_SEARCH = 9; + SERVICE_INDEX_STATS = 10; + SERVICE_INDEX_MEDIA = 11; + SERVICE_INDEX_I18N = 12; + SERVICE_INDEX_COLLECTION = 13; + SERVICE_INDEX_CARDS = 14; +} + +message BackendInit { + repeated string preferred_langs = 1; + string locale_folder_path = 2; + bool server = 3; +} + +message I18nBackendInit { + repeated string preferred_langs = 4; + string locale_folder_path = 5; +} + +message BackendError { + enum Kind { + INVALID_INPUT = 0; + UNDO_EMPTY = 1; + INTERRUPTED = 2; + TEMPLATE_PARSE = 3; + IO_ERROR = 4; + DB_ERROR = 5; + NETWORK_ERROR = 6; + SYNC_AUTH_ERROR = 7; + SYNC_OTHER_ERROR = 8; + JSON_ERROR = 9; + PROTO_ERROR = 10; + NOT_FOUND_ERROR = 11; + EXISTS = 12; + FILTERED_DECK_ERROR = 13; + SEARCH_ERROR = 14; + } + + // localized error description suitable for displaying to the user + string localized = 1; + // the error subtype + Kind kind = 2; +} diff --git a/proto/anki/card_rendering.proto b/proto/anki/card_rendering.proto new file mode 100644 index 000000000..589293086 --- /dev/null +++ b/proto/anki/card_rendering.proto @@ -0,0 +1,119 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +syntax = "proto3"; + +package anki.card_rendering; + +import "anki/generic.proto"; +import "anki/notes.proto"; +import "anki/notetypes.proto"; + +service CardRenderingService { + rpc ExtractAVTags(ExtractAVTagsRequest) returns (ExtractAVTagsResponse); + rpc ExtractLatex(ExtractLatexRequest) returns (ExtractLatexResponse); + rpc GetEmptyCards(generic.Empty) returns (EmptyCardsReport); + rpc RenderExistingCard(RenderExistingCardRequest) + returns (RenderCardResponse); + rpc RenderUncommittedCard(RenderUncommittedCardRequest) + returns (RenderCardResponse); + rpc RenderUncommittedCardLegacy(RenderUncommittedCardLegacyRequest) + returns (RenderCardResponse); + rpc StripAVTags(generic.String) returns (generic.String); + rpc RenderMarkdown(RenderMarkdownRequest) returns (generic.String); +} + +message ExtractAVTagsRequest { + string text = 1; + bool question_side = 2; +} + +message ExtractAVTagsResponse { + string text = 1; + repeated AVTag av_tags = 2; +} + +message AVTag { + oneof value { + string sound_or_video = 1; + TTSTag tts = 2; + } +} + +message TTSTag { + string field_text = 1; + string lang = 2; + repeated string voices = 3; + float speed = 4; + repeated string other_args = 5; +} + +message ExtractLatexRequest { + string text = 1; + bool svg = 2; + bool expand_clozes = 3; +} + +message ExtractLatexResponse { + string text = 1; + repeated ExtractedLatex latex = 2; +} + +message ExtractedLatex { + string filename = 1; + string latex_body = 2; +} + +message EmptyCardsReport { + message NoteWithEmptyCards { + int64 note_id = 1; + repeated int64 card_ids = 2; + bool will_delete_note = 3; + } + string report = 1; + repeated NoteWithEmptyCards notes = 2; +} + +message RenderExistingCardRequest { + int64 card_id = 1; + bool browser = 2; +} + +message RenderUncommittedCardRequest { + notes.Note note = 1; + uint32 card_ord = 2; + notetypes.Notetype.Template template = 3; + bool fill_empty = 4; +} + +message RenderUncommittedCardLegacyRequest { + notes.Note note = 1; + uint32 card_ord = 2; + bytes template = 3; + bool fill_empty = 4; +} + +message RenderCardResponse { + repeated RenderedTemplateNode question_nodes = 1; + repeated RenderedTemplateNode answer_nodes = 2; + string css = 3; + bool latex_svg = 4; +} + +message RenderedTemplateNode { + oneof value { + string text = 1; + RenderedTemplateReplacement replacement = 2; + } +} + +message RenderedTemplateReplacement { + string field_name = 1; + string current_text = 2; + repeated string filters = 3; +} + +message RenderMarkdownRequest { + string markdown = 1; + bool sanitize = 2; +} diff --git a/proto/anki/cards.proto b/proto/anki/cards.proto new file mode 100644 index 000000000..26b260982 --- /dev/null +++ b/proto/anki/cards.proto @@ -0,0 +1,64 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +syntax = "proto3"; + +package anki.cards; + +import "anki/generic.proto"; +import "anki/collection.proto"; + +service CardsService { + rpc GetCard(CardId) returns (Card); + rpc UpdateCard(UpdateCardRequest) returns (collection.OpChanges); + rpc RemoveCards(RemoveCardsRequest) returns (generic.Empty); + rpc SetDeck(SetDeckRequest) returns (collection.OpChangesWithCount); + rpc SetFlag(SetFlagRequest) returns (collection.OpChangesWithCount); +} +message CardId { + int64 cid = 1; +} + +message CardIds { + repeated int64 cids = 1; +} + +message Card { + int64 id = 1; + int64 note_id = 2; + int64 deck_id = 3; + uint32 template_idx = 4; + int64 mtime_secs = 5; + sint32 usn = 6; + uint32 ctype = 7; + sint32 queue = 8; + sint32 due = 9; + uint32 interval = 10; + uint32 ease_factor = 11; + uint32 reps = 12; + uint32 lapses = 13; + uint32 remaining_steps = 14; + sint32 original_due = 15; + int64 original_deck_id = 16; + uint32 flags = 17; + string data = 18; +} + +message UpdateCardRequest { + Card card = 1; + bool skip_undo_entry = 2; +} + +message RemoveCardsRequest { + repeated int64 card_ids = 1; +} + +message SetDeckRequest { + repeated int64 card_ids = 1; + int64 deck_id = 2; +} + +message SetFlagRequest { + repeated int64 card_ids = 1; + uint32 flag = 2; +} diff --git a/proto/anki/collection.proto b/proto/anki/collection.proto new file mode 100644 index 000000000..21465371d --- /dev/null +++ b/proto/anki/collection.proto @@ -0,0 +1,111 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +syntax = "proto3"; + +package anki.collection; + +import "anki/generic.proto"; + +service CollectionService { + rpc OpenCollection(OpenCollectionRequest) returns (generic.Empty); + rpc CloseCollection(CloseCollectionRequest) returns (generic.Empty); + rpc CheckDatabase(generic.Empty) returns (CheckDatabaseResponse); + rpc GetUndoStatus(generic.Empty) returns (UndoStatus); + rpc Undo(generic.Empty) returns (OpChangesAfterUndo); + rpc Redo(generic.Empty) returns (OpChangesAfterUndo); + rpc AddCustomUndoEntry(generic.String) returns (generic.UInt32); + rpc MergeUndoEntries(generic.UInt32) returns (OpChanges); + rpc LatestProgress(generic.Empty) returns (Progress); + rpc SetWantsAbort(generic.Empty) returns (generic.Empty); +} + +message OpenCollectionRequest { + string collection_path = 1; + string media_folder_path = 2; + string media_db_path = 3; + string log_path = 4; +} + +message CloseCollectionRequest { + bool downgrade_to_schema11 = 1; +} + +message CheckDatabaseResponse { + repeated string problems = 1; +} + +message OpChanges { + bool card = 1; + bool note = 2; + bool deck = 3; + bool tag = 4; + bool notetype = 5; + bool config = 6; + bool deck_config = 11; + bool mtime = 12; + + bool browser_table = 7; + bool browser_sidebar = 8; + // editor and displayed card in review screen + bool note_text = 9; + // whether to call .reset() and getCard() + bool study_queues = 10; +} + +message OpChangesWithCount { + uint32 count = 1; + OpChanges changes = 2; +} + +message OpChangesWithId { + int64 id = 1; + OpChanges changes = 2; +} + +message UndoStatus { + string undo = 1; + string redo = 2; + uint32 last_step = 3; +} + +message OpChangesAfterUndo { + OpChanges changes = 1; + string operation = 2; + int64 reverted_to_timestamp = 3; + UndoStatus new_status = 4; + uint32 counter = 5; +} + +message Progress { + message MediaSync { + string checked = 1; + string added = 2; + string removed = 3; + } + + message FullSync { + uint32 transferred = 1; + uint32 total = 2; + } + + message NormalSync { + string stage = 1; + string added = 2; + string removed = 3; + } + + message DatabaseCheck { + string stage = 1; + uint32 stage_total = 2; + uint32 stage_current = 3; + } + oneof value { + generic.Empty none = 1; + MediaSync media_sync = 2; + string media_check = 3; + FullSync full_sync = 4; + NormalSync normal_sync = 5; + DatabaseCheck database_check = 6; + } +} diff --git a/proto/anki/config.proto b/proto/anki/config.proto new file mode 100644 index 000000000..40f632664 --- /dev/null +++ b/proto/anki/config.proto @@ -0,0 +1,118 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +syntax = "proto3"; + +package anki.config; + +import "anki/generic.proto"; +import "anki/collection.proto"; + +service ConfigService { + rpc GetConfigJson(generic.String) returns (generic.Json); + rpc SetConfigJson(SetConfigJsonRequest) returns (collection.OpChanges); + rpc SetConfigJsonNoUndo(SetConfigJsonRequest) returns (generic.Empty); + rpc RemoveConfig(generic.String) returns (collection.OpChanges); + rpc GetAllConfig(generic.Empty) returns (generic.Json); + rpc GetConfigBool(GetConfigBoolRequest) returns (generic.Bool); + rpc SetConfigBool(SetConfigBoolRequest) returns (collection.OpChanges); + rpc GetConfigString(GetConfigStringRequest) returns (generic.String); + rpc SetConfigString(SetConfigStringRequest) returns (collection.OpChanges); + rpc GetPreferences(generic.Empty) returns (Preferences); + rpc SetPreferences(Preferences) returns (collection.OpChanges); +} + +message ConfigKey { + enum Bool { + BROWSER_TABLE_SHOW_NOTES_MODE = 0; + PREVIEW_BOTH_SIDES = 3; + COLLAPSE_TAGS = 4; + COLLAPSE_NOTETYPES = 5; + COLLAPSE_DECKS = 6; + COLLAPSE_SAVED_SEARCHES = 7; + COLLAPSE_TODAY = 8; + COLLAPSE_CARD_STATE = 9; + COLLAPSE_FLAGS = 10; + SCHED_2021 = 11; + ADDING_DEFAULTS_TO_CURRENT_DECK = 12; + HIDE_AUDIO_PLAY_BUTTONS = 13; + INTERRUPT_AUDIO_WHEN_ANSWERING = 14; + PASTE_IMAGES_AS_PNG = 15; + PASTE_STRIPS_FORMATTING = 16; + NORMALIZE_NOTE_TEXT = 17; + } + enum String { + SET_DUE_BROWSER = 0; + SET_DUE_REVIEWER = 1; + DEFAULT_SEARCH_TEXT = 2; + CARD_STATE_CUSTOMIZER = 3; + } +} + +message GetConfigBoolRequest { + ConfigKey.Bool key = 1; +} + +message SetConfigBoolRequest { + ConfigKey.Bool key = 1; + bool value = 2; + bool undoable = 3; +} + +message GetConfigStringRequest { + ConfigKey.String key = 1; +} + +message SetConfigStringRequest { + ConfigKey.String key = 1; + string value = 2; + bool undoable = 3; +} + +message OptionalStringConfigKey { + ConfigKey.String key = 1; +} + +message SetConfigJsonRequest { + string key = 1; + bytes value_json = 2; + bool undoable = 3; +} + +message Preferences { + message Scheduling { + enum NewReviewMix { + DISTRIBUTE = 0; + REVIEWS_FIRST = 1; + NEW_FIRST = 2; + } + + // read only; 1-3 + uint32 scheduler_version = 1; + + uint32 rollover = 2; + uint32 learn_ahead_secs = 3; + NewReviewMix new_review_mix = 4; + + // v2 only + bool new_timezone = 5; + bool day_learn_first = 6; + } + message Reviewing { + bool hide_audio_play_buttons = 1; + bool interrupt_audio_when_answering = 2; + bool show_remaining_due_counts = 3; + bool show_intervals_on_buttons = 4; + uint32 time_limit_secs = 5; + } + message Editing { + bool adding_defaults_to_current_deck = 1; + bool paste_images_as_png = 2; + bool paste_strips_formatting = 3; + string default_search_text = 4; + } + + Scheduling scheduling = 1; + Reviewing reviewing = 2; + Editing editing = 3; +} diff --git a/proto/anki/deckconfig.proto b/proto/anki/deckconfig.proto new file mode 100644 index 000000000..224ddad87 --- /dev/null +++ b/proto/anki/deckconfig.proto @@ -0,0 +1,145 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +syntax = "proto3"; + +package anki.deckconfig; + +import "anki/generic.proto"; +import "anki/collection.proto"; +import "anki/decks.proto"; + +service DeckConfigService { + rpc AddOrUpdateDeckConfigLegacy(generic.Json) returns (DeckConfigId); + rpc GetDeckConfig(DeckConfigId) returns (DeckConfig); + rpc AllDeckConfigLegacy(generic.Empty) returns (generic.Json); + rpc GetDeckConfigLegacy(DeckConfigId) returns (generic.Json); + rpc NewDeckConfigLegacy(generic.Empty) returns (generic.Json); + rpc RemoveDeckConfig(DeckConfigId) returns (generic.Empty); + rpc GetDeckConfigsForUpdate(decks.DeckId) returns (DeckConfigsForUpdate); + rpc UpdateDeckConfigs(UpdateDeckConfigsRequest) + returns (collection.OpChanges); +} + +message DeckConfigId { + int64 dcid = 1; +} + +message DeckConfig { + message Config { + enum NewCardInsertOrder { + NEW_CARD_INSERT_ORDER_DUE = 0; + NEW_CARD_INSERT_ORDER_RANDOM = 1; + } + enum NewCardGatherPriority { + NEW_CARD_GATHER_PRIORITY_DECK = 0; + NEW_CARD_GATHER_PRIORITY_LOWEST_POSITION = 1; + NEW_CARD_GATHER_PRIORITY_HIGHEST_POSITION = 2; + } + enum NewCardSortOrder { + NEW_CARD_SORT_ORDER_TEMPLATE_THEN_LOWEST_POSITION = 0; + NEW_CARD_SORT_ORDER_TEMPLATE_THEN_HIGHEST_POSITION = 1; + NEW_CARD_SORT_ORDER_TEMPLATE_THEN_RANDOM = 2; + NEW_CARD_SORT_ORDER_LOWEST_POSITION = 3; + NEW_CARD_SORT_ORDER_HIGHEST_POSITION = 4; + NEW_CARD_SORT_ORDER_RANDOM = 5; + } + enum ReviewCardOrder { + REVIEW_CARD_ORDER_DAY = 0; + REVIEW_CARD_ORDER_DAY_THEN_DECK = 1; + REVIEW_CARD_ORDER_DECK_THEN_DAY = 2; + REVIEW_CARD_ORDER_INTERVALS_ASCENDING = 3; + REVIEW_CARD_ORDER_INTERVALS_DESCENDING = 4; + // REVIEW_CARD_ORDER_RELATIVE_OVERDUE = 3; + } + enum ReviewMix { + REVIEW_MIX_MIX_WITH_REVIEWS = 0; + REVIEW_MIX_AFTER_REVIEWS = 1; + REVIEW_MIX_BEFORE_REVIEWS = 2; + } + enum LeechAction { + LEECH_ACTION_SUSPEND = 0; + LEECH_ACTION_TAG_ONLY = 1; + } + + repeated float learn_steps = 1; + repeated float relearn_steps = 2; + + reserved 3 to 8; + + uint32 new_per_day = 9; + uint32 reviews_per_day = 10; + uint32 new_per_day_minimum = 29; + + float initial_ease = 11; + float easy_multiplier = 12; + float hard_multiplier = 13; + float lapse_multiplier = 14; + float interval_multiplier = 15; + + uint32 maximum_review_interval = 16; + uint32 minimum_lapse_interval = 17; + + uint32 graduating_interval_good = 18; + uint32 graduating_interval_easy = 19; + + NewCardInsertOrder new_card_insert_order = 20; + NewCardGatherPriority new_card_gather_priority = 34; + NewCardSortOrder new_card_sort_order = 32; + ReviewMix new_mix = 30; + + ReviewCardOrder review_order = 33; + + ReviewMix interday_learning_mix = 31; + + LeechAction leech_action = 21; + uint32 leech_threshold = 22; + + bool disable_autoplay = 23; + uint32 cap_answer_time_to_secs = 24; + bool show_timer = 25; + bool skip_question_when_replaying_answer = 26; + + bool bury_new = 27; + bool bury_reviews = 28; + + bytes other = 255; + } + + int64 id = 1; + string name = 2; + int64 mtime_secs = 3; + int32 usn = 4; + Config config = 5; +} + +message DeckConfigsForUpdate { + message ConfigWithExtra { + DeckConfig config = 1; + uint32 use_count = 2; + } + message CurrentDeck { + string name = 1; + int64 config_id = 2; + repeated int64 parent_config_ids = 3; + } + + repeated ConfigWithExtra all_config = 1; + CurrentDeck current_deck = 2; + DeckConfig defaults = 3; + bool schema_modified = 4; + bool v3_scheduler = 5; + bool have_addons = 6; + // only applies to v3 scheduler + string card_state_customizer = 7; +} + +message UpdateDeckConfigsRequest { + int64 target_deck_id = 1; + /// Unchanged, non-selected configs can be omitted. Deck will + /// be set to whichever entry comes last. + repeated DeckConfig configs = 2; + repeated int64 removed_config_ids = 3; + bool apply_to_children = 4; + string card_state_customizer = 5; +} diff --git a/proto/anki/decks.proto b/proto/anki/decks.proto new file mode 100644 index 000000000..5e24ea1cc --- /dev/null +++ b/proto/anki/decks.proto @@ -0,0 +1,188 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +syntax = "proto3"; + +package anki.decks; + +import "anki/generic.proto"; +import "anki/collection.proto"; + +service DecksService { + rpc AddDeckLegacy(generic.Json) returns (collection.OpChangesWithId); + rpc AddOrUpdateDeckLegacy(AddOrUpdateDeckLegacyRequest) returns (DeckId); + rpc DeckTree(DeckTreeRequest) returns (DeckTreeNode); + rpc DeckTreeLegacy(generic.Empty) returns (generic.Json); + rpc GetAllDecksLegacy(generic.Empty) returns (generic.Json); + rpc GetDeckIdByName(generic.String) returns (DeckId); + rpc GetDeck(DeckId) returns (Deck); + rpc UpdateDeck(Deck) returns (collection.OpChanges); + rpc UpdateDeckLegacy(generic.Json) returns (collection.OpChanges); + rpc SetDeckCollapsed(SetDeckCollapsedRequest) returns (collection.OpChanges); + rpc GetDeckLegacy(DeckId) returns (generic.Json); + rpc GetDeckNames(GetDeckNamesRequest) returns (DeckNames); + rpc NewDeckLegacy(generic.Bool) returns (generic.Json); + rpc RemoveDecks(DeckIds) returns (collection.OpChangesWithCount); + rpc ReparentDecks(ReparentDecksRequest) + returns (collection.OpChangesWithCount); + rpc RenameDeck(RenameDeckRequest) returns (collection.OpChanges); + rpc GetOrCreateFilteredDeck(DeckId) returns (FilteredDeckForUpdate); + rpc AddOrUpdateFilteredDeck(FilteredDeckForUpdate) + returns (collection.OpChangesWithId); + rpc FilteredDeckOrderLabels(generic.Empty) returns (generic.StringList); + rpc SetCurrentDeck(DeckId) returns (collection.OpChanges); + rpc GetCurrentDeck(generic.Empty) returns (Deck); +} + +message DeckId { + int64 did = 1; +} + +message DeckIds { + repeated int64 dids = 1; +} + +message Deck { + message Common { + bool study_collapsed = 1; + bool browser_collapsed = 2; + + uint32 last_day_studied = 3; + int32 new_studied = 4; + int32 review_studied = 5; + int32 milliseconds_studied = 7; + + // previously set in the v1 scheduler, + // but not currently used for anything + int32 learning_studied = 6; + + reserved 8 to 13; + + bytes other = 255; + } + message Normal { + int64 config_id = 1; + uint32 extend_new = 2; + uint32 extend_review = 3; + string description = 4; + bool markdown_description = 5; + + reserved 6 to 11; + } + message Filtered { + message SearchTerm { + enum Order { + OLDEST_REVIEWED_FIRST = 0; + RANDOM = 1; + INTERVALS_ASCENDING = 2; + INTERVALS_DESCENDING = 3; + LAPSES = 4; + ADDED = 5; + DUE = 6; + REVERSE_ADDED = 7; + DUE_PRIORITY = 8; + } + + string search = 1; + uint32 limit = 2; + Order order = 3; + } + + bool reschedule = 1; + repeated SearchTerm search_terms = 2; + // v1 scheduler only + repeated float delays = 3; + // v2 scheduler only + uint32 preview_delay = 4; + } + // a container to store the deck specifics in the DB + // as a tagged enum + message KindContainer { + oneof kind { + Normal normal = 1; + Filtered filtered = 2; + } + } + + int64 id = 1; + string name = 2; + int64 mtime_secs = 3; + int32 usn = 4; + Common common = 5; + // the specifics are inlined here when sending data to clients, + // as otherwise an extra level of indirection would be required + oneof kind { + Normal normal = 6; + Filtered filtered = 7; + } +} + +message AddOrUpdateDeckLegacyRequest { + bytes deck = 1; + bool preserve_usn_and_mtime = 2; +} + +message DeckTreeRequest { + // if non-zero, counts for the provided timestamp will be included + int64 now = 1; + int64 top_deck_id = 2; +} + +message DeckTreeNode { + int64 deck_id = 1; + string name = 2; + uint32 level = 4; + bool collapsed = 5; + + uint32 review_count = 6; + uint32 learn_count = 7; + uint32 new_count = 8; + + bool filtered = 16; + + // low index so key can be packed into a byte, but at bottom + // to make debug output easier to read + repeated DeckTreeNode children = 3; +} + +message SetDeckCollapsedRequest { + enum Scope { + REVIEWER = 0; + BROWSER = 1; + } + + int64 deck_id = 1; + bool collapsed = 2; + Scope scope = 3; +} + +message GetDeckNamesRequest { + bool skip_empty_default = 1; + // if unset, implies skip_empty_default + bool include_filtered = 2; +} + +message DeckNames { + repeated DeckNameId entries = 1; +} + +message DeckNameId { + int64 id = 1; + string name = 2; +} + +message ReparentDecksRequest { + repeated int64 deck_ids = 1; + int64 new_parent = 2; +} + +message RenameDeckRequest { + int64 deck_id = 1; + string new_name = 2; +} + +message FilteredDeckForUpdate { + int64 id = 1; + string name = 2; + Deck.Filtered config = 3; +} diff --git a/proto/anki/generic.proto b/proto/anki/generic.proto new file mode 100644 index 000000000..5156e48d6 --- /dev/null +++ b/proto/anki/generic.proto @@ -0,0 +1,44 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +syntax = "proto3"; + +package anki.generic; + +message Empty {} + +message OptionalInt32 { + sint32 val = 1; +} + +message OptionalUInt32 { + uint32 val = 1; +} + +message Int32 { + sint32 val = 1; +} + +message UInt32 { + uint32 val = 1; +} + +message Int64 { + int64 val = 1; +} + +message String { + string val = 1; +} + +message Json { + bytes json = 1; +} + +message Bool { + bool val = 1; +} + +message StringList { + repeated string vals = 1; +} diff --git a/proto/anki/i18n.proto b/proto/anki/i18n.proto new file mode 100644 index 000000000..1f135287b --- /dev/null +++ b/proto/anki/i18n.proto @@ -0,0 +1,42 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +syntax = "proto3"; + +package anki.i18n; + +import "anki/generic.proto"; + +service I18nService { + rpc TranslateString(TranslateStringRequest) returns (generic.String); + rpc FormatTimespan(FormatTimespanRequest) returns (generic.String); + rpc I18nResources(I18nResourcesRequest) returns (generic.Json); +} + +message TranslateStringRequest { + uint32 module_index = 1; + uint32 message_index = 2; + map args = 3; +} + +message TranslateArgValue { + oneof value { + string str = 1; + double number = 2; + } +} + +message FormatTimespanRequest { + enum Context { + PRECISE = 0; + ANSWER_BUTTONS = 1; + INTERVALS = 2; + } + + float seconds = 1; + Context context = 2; +} + +message I18nResourcesRequest { + repeated string modules = 1; +} diff --git a/proto/anki/media.proto b/proto/anki/media.proto new file mode 100644 index 000000000..f506615b8 --- /dev/null +++ b/proto/anki/media.proto @@ -0,0 +1,32 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +syntax = "proto3"; + +package anki.media; + +import "anki/generic.proto"; + +service MediaService { + rpc CheckMedia(generic.Empty) returns (CheckMediaResponse); + rpc TrashMediaFiles(TrashMediaFilesRequest) returns (generic.Empty); + rpc AddMediaFile(AddMediaFileRequest) returns (generic.String); + rpc EmptyTrash(generic.Empty) returns (generic.Empty); + rpc RestoreTrash(generic.Empty) returns (generic.Empty); +} + +message CheckMediaResponse { + repeated string unused = 1; + repeated string missing = 2; + string report = 3; + bool have_trash = 4; +} + +message TrashMediaFilesRequest { + repeated string fnames = 1; +} + +message AddMediaFileRequest { + string desired_name = 1; + bytes data = 2; +} diff --git a/proto/anki/notes.proto b/proto/anki/notes.proto new file mode 100644 index 000000000..3c5fc7759 --- /dev/null +++ b/proto/anki/notes.proto @@ -0,0 +1,106 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +syntax = "proto3"; + +package anki.notes; + +import "anki/notetypes.proto"; +import "anki/collection.proto"; +import "anki/decks.proto"; +import "anki/cards.proto"; + +service NotesService { + rpc NewNote(notetypes.NotetypeId) returns (Note); + rpc AddNote(AddNoteRequest) returns (AddNoteResponse); + rpc DefaultsForAdding(DefaultsForAddingRequest) returns (DeckAndNotetype); + rpc DefaultDeckForNotetype(notetypes.NotetypeId) returns (decks.DeckId); + rpc UpdateNote(UpdateNoteRequest) returns (collection.OpChanges); + rpc GetNote(NoteId) returns (Note); + rpc RemoveNotes(RemoveNotesRequest) returns (collection.OpChangesWithCount); + rpc ClozeNumbersInNote(Note) returns (ClozeNumbersInNoteResponse); + rpc AfterNoteUpdates(AfterNoteUpdatesRequest) + returns (collection.OpChangesWithCount); + rpc FieldNamesForNotes(FieldNamesForNotesRequest) + returns (FieldNamesForNotesResponse); + rpc NoteFieldsCheck(Note) returns (NoteFieldsCheckResponse); + rpc CardsOfNote(NoteId) returns (cards.CardIds); + rpc GetSingleNotetypeOfNotes(notes.NoteIds) returns (notetypes.NotetypeId); +} + +message NoteId { + int64 nid = 1; +} + +message NoteIds { + repeated int64 note_ids = 1; +} + +message Note { + int64 id = 1; + string guid = 2; + int64 notetype_id = 3; + uint32 mtime_secs = 4; + int32 usn = 5; + repeated string tags = 6; + repeated string fields = 7; +} + +message AddNoteRequest { + Note note = 1; + int64 deck_id = 2; +} + +message AddNoteResponse { + int64 note_id = 1; + collection.OpChanges changes = 2; +} + +message UpdateNoteRequest { + Note note = 1; + bool skip_undo_entry = 2; +} + +message DefaultsForAddingRequest { + int64 home_deck_of_current_review_card = 1; +} + +message DeckAndNotetype { + int64 deck_id = 1; + int64 notetype_id = 2; +} + +message RemoveNotesRequest { + repeated int64 note_ids = 1; + repeated int64 card_ids = 2; +} + +message ClozeNumbersInNoteResponse { + repeated uint32 numbers = 1; +} + +message AfterNoteUpdatesRequest { + repeated int64 nids = 1; + bool mark_notes_modified = 2; + bool generate_cards = 3; +} + +message FieldNamesForNotesRequest { + repeated int64 nids = 1; +} + +message FieldNamesForNotesResponse { + repeated string fields = 1; +} + +message NoteFieldsCheckResponse { + enum State { + NORMAL = 0; + EMPTY = 1; + DUPLICATE = 2; + MISSING_CLOZE = 3; + NOTETYPE_NOT_CLOZE = 4; + FIELD_NOT_CLOZE = 5; + } + State state = 1; +} diff --git a/proto/anki/notetypes.proto b/proto/anki/notetypes.proto new file mode 100644 index 000000000..4065e34ab --- /dev/null +++ b/proto/anki/notetypes.proto @@ -0,0 +1,176 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +syntax = "proto3"; + +package anki.notetypes; + +import "anki/generic.proto"; +import "anki/collection.proto"; + +service NotetypesService { + rpc AddNotetype(Notetype) returns (collection.OpChangesWithId); + rpc UpdateNotetype(Notetype) returns (collection.OpChanges); + rpc AddNotetypeLegacy(generic.Json) returns (collection.OpChangesWithId); + rpc UpdateNotetypeLegacy(generic.Json) returns (collection.OpChanges); + rpc AddOrUpdateNotetype(AddOrUpdateNotetypeRequest) returns (NotetypeId); + rpc GetStockNotetypeLegacy(StockNotetype) returns (generic.Json); + rpc GetNotetype(NotetypeId) returns (Notetype); + rpc GetNotetypeLegacy(NotetypeId) returns (generic.Json); + rpc GetNotetypeNames(generic.Empty) returns (NotetypeNames); + rpc GetNotetypeNamesAndCounts(generic.Empty) returns (NotetypeUseCounts); + rpc GetNotetypeIdByName(generic.String) returns (NotetypeId); + rpc RemoveNotetype(NotetypeId) returns (collection.OpChanges); + rpc GetAuxNotetypeConfigKey(GetAuxConfigKeyRequest) returns (generic.String); + rpc GetAuxTemplateConfigKey(GetAuxTemplateConfigKeyRequest) + returns (generic.String); + rpc GetChangeNotetypeInfo(GetChangeNotetypeInfoRequest) + returns (ChangeNotetypeInfo); + rpc ChangeNotetype(ChangeNotetypeRequest) returns (collection.OpChanges); +} + +message NotetypeId { + int64 ntid = 1; +} + +message Notetype { + message Config { + enum Kind { + KIND_NORMAL = 0; + KIND_CLOZE = 1; + } + message CardRequirement { + enum Kind { + KIND_NONE = 0; + KIND_ANY = 1; + KIND_ALL = 2; + } + uint32 card_ord = 1; + Kind kind = 2; + repeated uint32 field_ords = 3; + } + + Kind kind = 1; + uint32 sort_field_idx = 2; + string css = 3; + /// This is now stored separately; retrieve with DefaultsForAdding() + int64 target_deck_id_unused = 4; + string latex_pre = 5; + string latex_post = 6; + bool latex_svg = 7; + repeated CardRequirement reqs = 8; + + bytes other = 255; + } + message Field { + message Config { + bool sticky = 1; + bool rtl = 2; + string font_name = 3; + uint32 font_size = 4; + + bytes other = 255; + } + generic.OptionalUInt32 ord = 1; + string name = 2; + Config config = 5; + } + message Template { + message Config { + string q_format = 1; + string a_format = 2; + string q_format_browser = 3; + string a_format_browser = 4; + int64 target_deck_id = 5; + string browser_font_name = 6; + uint32 browser_font_size = 7; + + bytes other = 255; + } + + generic.OptionalUInt32 ord = 1; + string name = 2; + int64 mtime_secs = 3; + sint32 usn = 4; + Config config = 5; + } + + int64 id = 1; + string name = 2; + int64 mtime_secs = 3; + sint32 usn = 4; + Config config = 7; + repeated Field fields = 8; + repeated Template templates = 9; +} + +message AddOrUpdateNotetypeRequest { + bytes json = 1; + bool preserve_usn_and_mtime = 2; +} + +message StockNotetype { + enum Kind { + BASIC = 0; + BASIC_AND_REVERSED = 1; + BASIC_OPTIONAL_REVERSED = 2; + BASIC_TYPING = 3; + CLOZE = 4; + } + + Kind kind = 1; +} + +message NotetypeNames { + repeated NotetypeNameId entries = 1; +} + +message NotetypeUseCounts { + repeated NotetypeNameIdUseCount entries = 1; +} + +message NotetypeNameId { + int64 id = 1; + string name = 2; +} + +message NotetypeNameIdUseCount { + int64 id = 1; + string name = 2; + uint32 use_count = 3; +} + +message GetAuxConfigKeyRequest { + int64 id = 1; + string key = 2; +} + +message GetAuxTemplateConfigKeyRequest { + int64 notetype_id = 1; + uint32 card_ordinal = 2; + string key = 3; +} + +message GetChangeNotetypeInfoRequest { + int64 old_notetype_id = 1; + int64 new_notetype_id = 2; +} + +message ChangeNotetypeRequest { + repeated int64 note_ids = 1; + // -1 is used to represent null, as nullable repeated fields + // are unwieldy in protobuf + repeated int32 new_fields = 2; + repeated int32 new_templates = 3; + int64 old_notetype_id = 4; + int64 new_notetype_id = 5; + int64 current_schema = 6; +} + +message ChangeNotetypeInfo { + repeated string old_field_names = 1; + repeated string old_template_names = 2; + repeated string new_field_names = 3; + repeated string new_template_names = 4; + ChangeNotetypeRequest input = 5; +} diff --git a/proto/anki/scheduler.proto b/proto/anki/scheduler.proto new file mode 100644 index 000000000..7876e3554 --- /dev/null +++ b/proto/anki/scheduler.proto @@ -0,0 +1,219 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +syntax = "proto3"; + +package anki.scheduler; + +import "anki/generic.proto"; +import "anki/cards.proto"; +import "anki/decks.proto"; +import "anki/collection.proto"; +import "anki/config.proto"; + +service SchedulerService { + rpc GetQueuedCards(GetQueuedCardsRequest) returns (QueuedCards); + rpc AnswerCard(CardAnswer) returns (collection.OpChanges); + rpc SchedTimingToday(generic.Empty) returns (SchedTimingTodayResponse); + rpc StudiedToday(generic.Empty) returns (generic.String); + rpc StudiedTodayMessage(StudiedTodayMessageRequest) returns (generic.String); + rpc UpdateStats(UpdateStatsRequest) returns (generic.Empty); + rpc ExtendLimits(ExtendLimitsRequest) returns (generic.Empty); + rpc CountsForDeckToday(decks.DeckId) returns (CountsForDeckTodayResponse); + rpc CongratsInfo(generic.Empty) returns (CongratsInfoResponse); + rpc RestoreBuriedAndSuspendedCards(cards.CardIds) + returns (collection.OpChanges); + rpc UnburyDeck(UnburyDeckRequest) returns (collection.OpChanges); + rpc BuryOrSuspendCards(BuryOrSuspendCardsRequest) + returns (collection.OpChangesWithCount); + rpc EmptyFilteredDeck(decks.DeckId) returns (collection.OpChanges); + rpc RebuildFilteredDeck(decks.DeckId) returns (collection.OpChangesWithCount); + rpc ScheduleCardsAsNew(ScheduleCardsAsNewRequest) + returns (collection.OpChanges); + rpc SetDueDate(SetDueDateRequest) returns (collection.OpChanges); + rpc SortCards(SortCardsRequest) returns (collection.OpChangesWithCount); + rpc SortDeck(SortDeckRequest) returns (collection.OpChangesWithCount); + rpc GetNextCardStates(cards.CardId) returns (NextCardStates); + rpc DescribeNextStates(NextCardStates) returns (generic.StringList); + rpc StateIsLeech(SchedulingState) returns (generic.Bool); + rpc UpgradeScheduler(generic.Empty) returns (generic.Empty); +} + +message SchedulingState { + message New { + uint32 position = 1; + } + message Learning { + uint32 remaining_steps = 1; + uint32 scheduled_secs = 2; + } + message Review { + uint32 scheduled_days = 1; + uint32 elapsed_days = 2; + float ease_factor = 3; + uint32 lapses = 4; + bool leeched = 5; + } + message Relearning { + Review review = 1; + Learning learning = 2; + } + message Normal { + oneof value { + New new = 1; + Learning learning = 2; + Review review = 3; + Relearning relearning = 4; + } + } + message Preview { + uint32 scheduled_secs = 1; + bool finished = 2; + } + message ReschedulingFilter { + Normal original_state = 1; + } + message Filtered { + oneof value { + Preview preview = 1; + ReschedulingFilter rescheduling = 2; + } + } + + oneof value { + Normal normal = 1; + Filtered filtered = 2; + } +} + +message QueuedCards { + enum Queue { + NEW = 0; + LEARNING = 1; + REVIEW = 2; + } + message QueuedCard { + cards.Card card = 1; + Queue queue = 2; + NextCardStates next_states = 3; + } + + repeated QueuedCard cards = 1; + uint32 new_count = 2; + uint32 learning_count = 3; + uint32 review_count = 4; +} + +message GetQueuedCardsRequest { + uint32 fetch_limit = 1; + bool intraday_learning_only = 2; +} + +message SchedTimingTodayResponse { + uint32 days_elapsed = 1; + int64 next_day_at = 2; +} + +message StudiedTodayMessageRequest { + uint32 cards = 1; + double seconds = 2; +} + +message UpdateStatsRequest { + int64 deck_id = 1; + int32 new_delta = 2; + int32 review_delta = 4; + int32 millisecond_delta = 5; +} + +message ExtendLimitsRequest { + int64 deck_id = 1; + int32 new_delta = 2; + int32 review_delta = 3; +} + +message CountsForDeckTodayResponse { + int32 new = 1; + int32 review = 2; +} + +message CongratsInfoResponse { + uint32 learn_remaining = 1; + uint32 secs_until_next_learn = 2; + bool review_remaining = 3; + bool new_remaining = 4; + bool have_sched_buried = 5; + bool have_user_buried = 6; + bool is_filtered_deck = 7; + bool bridge_commands_supported = 8; + string deck_description = 9; +} + +message UnburyDeckRequest { + enum Mode { + ALL = 0; + SCHED_ONLY = 1; + USER_ONLY = 2; + } + int64 deck_id = 1; + Mode mode = 2; +} + +message BuryOrSuspendCardsRequest { + enum Mode { + SUSPEND = 0; + BURY_SCHED = 1; + BURY_USER = 2; + } + repeated int64 card_ids = 1; + repeated int64 note_ids = 2; + Mode mode = 3; +} + +message ScheduleCardsAsNewRequest { + repeated int64 card_ids = 1; + bool log = 2; +} + +message SetDueDateRequest { + repeated int64 card_ids = 1; + string days = 2; + config.OptionalStringConfigKey config_key = 3; +} + +message SortCardsRequest { + repeated int64 card_ids = 1; + uint32 starting_from = 2; + uint32 step_size = 3; + bool randomize = 4; + bool shift_existing = 5; +} + +message SortDeckRequest { + int64 deck_id = 1; + bool randomize = 2; +} + +message NextCardStates { + SchedulingState current = 1; + SchedulingState again = 2; + SchedulingState hard = 3; + SchedulingState good = 4; + SchedulingState easy = 5; +} + +message CardAnswer { + enum Rating { + AGAIN = 0; + HARD = 1; + GOOD = 2; + EASY = 3; + } + + int64 card_id = 1; + SchedulingState current_state = 2; + SchedulingState new_state = 3; + Rating rating = 4; + int64 answered_at_millis = 5; + uint32 milliseconds_taken = 6; +} diff --git a/proto/anki/search.proto b/proto/anki/search.proto new file mode 100644 index 000000000..91a7d9148 --- /dev/null +++ b/proto/anki/search.proto @@ -0,0 +1,177 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +syntax = "proto3"; + +package anki.search; + +import "anki/generic.proto"; +import "anki/collection.proto"; + +service SearchService { + rpc BuildSearchString(SearchNode) returns (generic.String); + rpc SearchCards(SearchRequest) returns (SearchResponse); + rpc SearchNotes(SearchRequest) returns (SearchResponse); + rpc JoinSearchNodes(JoinSearchNodesRequest) returns (generic.String); + rpc ReplaceSearchNode(ReplaceSearchNodeRequest) returns (generic.String); + rpc FindAndReplace(FindAndReplaceRequest) + returns (collection.OpChangesWithCount); + rpc AllBrowserColumns(generic.Empty) returns (BrowserColumns); + rpc BrowserRowForId(generic.Int64) returns (BrowserRow); + rpc SetActiveBrowserColumns(generic.StringList) returns (generic.Empty); +} + +message SearchNode { + message Dupe { + int64 notetype_id = 1; + string first_field = 2; + } + enum Flag { + FLAG_NONE = 0; + FLAG_ANY = 1; + FLAG_RED = 2; + FLAG_ORANGE = 3; + FLAG_GREEN = 4; + FLAG_BLUE = 5; + FLAG_PINK = 6; + FLAG_TURQUOISE = 7; + FLAG_PURPLE = 8; + } + enum Rating { + RATING_ANY = 0; + RATING_AGAIN = 1; + RATING_HARD = 2; + RATING_GOOD = 3; + RATING_EASY = 4; + RATING_BY_RESCHEDULE = 5; + } + message Rated { + uint32 days = 1; + Rating rating = 2; + } + enum CardState { + CARD_STATE_NEW = 0; + CARD_STATE_LEARN = 1; + CARD_STATE_REVIEW = 2; + CARD_STATE_DUE = 3; + CARD_STATE_SUSPENDED = 4; + CARD_STATE_BURIED = 5; + } + message IdList { + repeated int64 ids = 1; + } + message Group { + enum Joiner { + AND = 0; + OR = 1; + } + repeated SearchNode nodes = 1; + Joiner joiner = 2; + } + oneof filter { + Group group = 1; + SearchNode negated = 2; + string parsable_text = 3; + uint32 template = 4; + int64 nid = 5; + Dupe dupe = 6; + string field_name = 7; + Rated rated = 8; + uint32 added_in_days = 9; + int32 due_in_days = 10; + Flag flag = 11; + CardState card_state = 12; + IdList nids = 13; + uint32 edited_in_days = 14; + string deck = 15; + int32 due_on_day = 16; + string tag = 17; + string note = 18; + uint32 introduced_in_days = 19; + } +} + +message SearchRequest { + string search = 1; + SortOrder order = 2; +} + +message SearchResponse { + repeated int64 ids = 1; +} + +message SortOrder { + message Builtin { + string column = 1; + bool reverse = 2; + } + oneof value { + generic.Empty none = 1; + string custom = 2; + Builtin builtin = 3; + } +} + +message JoinSearchNodesRequest { + SearchNode.Group.Joiner joiner = 1; + SearchNode existing_node = 2; + SearchNode additional_node = 3; +} + +message ReplaceSearchNodeRequest { + SearchNode existing_node = 1; + SearchNode replacement_node = 2; +} + +message FindAndReplaceRequest { + repeated int64 nids = 1; + string search = 2; + string replacement = 3; + bool regex = 4; + bool match_case = 5; + string field_name = 6; +} + +message BrowserColumns { + enum Sorting { + SORTING_NONE = 0; + SORTING_NORMAL = 1; + SORTING_REVERSED = 2; + } + enum Alignment { + ALIGNMENT_START = 0; + ALIGNMENT_CENTER = 1; + } + message Column { + string key = 1; + string cards_mode_label = 2; + string notes_mode_label = 3; + Sorting sorting = 4; + bool uses_cell_font = 5; + Alignment alignment = 6; + } + repeated Column columns = 1; +} + +message BrowserRow { + message Cell { + string text = 1; + bool is_rtl = 2; + } + enum Color { + COLOR_DEFAULT = 0; + COLOR_MARKED = 1; + COLOR_SUSPENDED = 2; + COLOR_FLAG_RED = 3; + COLOR_FLAG_ORANGE = 4; + COLOR_FLAG_GREEN = 5; + COLOR_FLAG_BLUE = 6; + COLOR_FLAG_PINK = 7; + COLOR_FLAG_TURQUOISE = 8; + COLOR_FLAG_PURPLE = 9; + } + repeated Cell cells = 1; + Color color = 2; + string font_name = 3; + uint32 font_size = 4; +} diff --git a/proto/anki/stats.proto b/proto/anki/stats.proto new file mode 100644 index 000000000..21f69170e --- /dev/null +++ b/proto/anki/stats.proto @@ -0,0 +1,64 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +syntax = "proto3"; + +package anki.stats; + +import "anki/generic.proto"; +import "anki/cards.proto"; + +service StatsService { + rpc CardStats(cards.CardId) returns (generic.String); + rpc Graphs(GraphsRequest) returns (GraphsResponse); + rpc GetGraphPreferences(generic.Empty) returns (GraphPreferences); + rpc SetGraphPreferences(GraphPreferences) returns (generic.Empty); +} + +message GraphsRequest { + string search = 1; + uint32 days = 2; +} + +message GraphsResponse { + repeated cards.Card cards = 1; + repeated RevlogEntry revlog = 2; + uint32 days_elapsed = 3; + // Based on rollover hour + uint32 next_day_at_secs = 4; + uint32 scheduler_version = 5; + /// Seconds to add to UTC timestamps to get local time. + int32 local_offset_secs = 7; +} + +message GraphPreferences { + enum Weekday { + SUNDAY = 0; + MONDAY = 1; + FRIDAY = 5; + SATURDAY = 6; + } + Weekday calendar_first_day_of_week = 1; + bool card_counts_separate_inactive = 2; + bool browser_links_supported = 3; + bool future_due_show_backlog = 4; +} + +message RevlogEntry { + enum ReviewKind { + LEARNING = 0; + REVIEW = 1; + RELEARNING = 2; + EARLY_REVIEW = 3; + MANUAL = 4; + } + int64 id = 1; + int64 cid = 2; + int32 usn = 3; + uint32 button_chosen = 4; + int32 interval = 5; + int32 last_interval = 6; + uint32 ease_factor = 7; + uint32 taken_millis = 8; + ReviewKind review_kind = 9; +} diff --git a/proto/anki/sync.proto b/proto/anki/sync.proto new file mode 100644 index 000000000..67553884f --- /dev/null +++ b/proto/anki/sync.proto @@ -0,0 +1,76 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +syntax = "proto3"; + +package anki.sync; + +import "anki/generic.proto"; + +service SyncService { + rpc SyncMedia(SyncAuth) returns (generic.Empty); + rpc AbortSync(generic.Empty) returns (generic.Empty); + rpc AbortMediaSync(generic.Empty) returns (generic.Empty); + rpc BeforeUpload(generic.Empty) returns (generic.Empty); + rpc SyncLogin(SyncLoginRequest) returns (SyncAuth); + rpc SyncStatus(SyncAuth) returns (SyncStatusResponse); + rpc SyncCollection(SyncAuth) returns (SyncCollectionResponse); + rpc FullUpload(SyncAuth) returns (generic.Empty); + rpc FullDownload(SyncAuth) returns (generic.Empty); + rpc SyncServerMethod(SyncServerMethodRequest) returns (generic.Json); +} + +message SyncAuth { + string hkey = 1; + uint32 host_number = 2; +} + +message SyncLoginRequest { + string username = 1; + string password = 2; +} + +message SyncStatusResponse { + enum Required { + NO_CHANGES = 0; + NORMAL_SYNC = 1; + FULL_SYNC = 2; + } + Required required = 1; +} + +message SyncCollectionResponse { + enum ChangesRequired { + NO_CHANGES = 0; + NORMAL_SYNC = 1; + FULL_SYNC = 2; + // local collection has no cards; upload not an option + FULL_DOWNLOAD = 3; + // remote collection has no cards; download not an option + FULL_UPLOAD = 4; + } + + uint32 host_number = 1; + string server_message = 2; + ChangesRequired required = 3; +} + +message SyncServerMethodRequest { + enum Method { + HOST_KEY = 0; + META = 1; + START = 2; + APPLY_GRAVES = 3; + APPLY_CHANGES = 4; + CHUNK = 5; + APPLY_CHUNK = 6; + SANITY_CHECK = 7; + FINISH = 8; + ABORT = 9; + // caller must reopen after these two are called + FULL_UPLOAD = 10; + FULL_DOWNLOAD = 11; + } + Method method = 1; + bytes data = 2; +} diff --git a/proto/anki/tags.proto b/proto/anki/tags.proto new file mode 100644 index 000000000..a1597f7d6 --- /dev/null +++ b/proto/anki/tags.proto @@ -0,0 +1,60 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +syntax = "proto3"; + +package anki.tags; + +import "anki/generic.proto"; +import "anki/collection.proto"; + +service TagsService { + rpc ClearUnusedTags(generic.Empty) returns (collection.OpChangesWithCount); + rpc AllTags(generic.Empty) returns (generic.StringList); + rpc RemoveTags(generic.String) returns (collection.OpChangesWithCount); + rpc SetTagCollapsed(SetTagCollapsedRequest) returns (collection.OpChanges); + rpc TagTree(generic.Empty) returns (TagTreeNode); + rpc ReparentTags(ReparentTagsRequest) returns (collection.OpChangesWithCount); + rpc RenameTags(RenameTagsRequest) returns (collection.OpChangesWithCount); + rpc AddNoteTags(NoteIdsAndTagsRequest) + returns (collection.OpChangesWithCount); + rpc RemoveNoteTags(NoteIdsAndTagsRequest) + returns (collection.OpChangesWithCount); + rpc FindAndReplaceTag(FindAndReplaceTagRequest) + returns (collection.OpChangesWithCount); +} + +message SetTagCollapsedRequest { + string name = 1; + bool collapsed = 2; +} + +message TagTreeNode { + string name = 1; + repeated TagTreeNode children = 2; + uint32 level = 3; + bool collapsed = 4; +} + +message ReparentTagsRequest { + repeated string tags = 1; + string new_parent = 2; +} + +message RenameTagsRequest { + string current_prefix = 1; + string new_prefix = 2; +} + +message NoteIdsAndTagsRequest { + repeated int64 note_ids = 1; + string tags = 2; +} + +message FindAndReplaceTagRequest { + repeated int64 note_ids = 1; + string search = 2; + string replacement = 3; + bool regex = 4; + bool match_case = 5; +} diff --git a/rslib/clang_format.bzl b/proto/clang_format.bzl similarity index 87% rename from rslib/clang_format.bzl rename to proto/clang_format.bzl index 3591a82c0..164fcb8b1 100644 --- a/rslib/clang_format.bzl +++ b/proto/clang_format.bzl @@ -14,9 +14,9 @@ def _impl(rctx): alias( name = "clang_format", actual = select({ - "@net_ankiweb_anki//platforms:windows_x86_64": "@clang_format_windows_x86_64//:clang-format.exe", - "@net_ankiweb_anki//platforms:macos_x86_64": "@clang_format_macos_x86_64//:clang-format", - "@net_ankiweb_anki//platforms:linux_x86_64": "@clang_format_linux_x86_64//:clang-format", + "@ankidesktop//platforms:windows_x86_64": "@clang_format_windows_x86_64//:clang-format.exe", + "@ankidesktop//platforms:macos_x86_64": "@clang_format_macos_x86_64//:clang-format", + "@ankidesktop//platforms:linux_x86_64": "@clang_format_linux_x86_64//:clang-format", }), visibility = ["//visibility:public"] ) @@ -68,7 +68,7 @@ def proto_format(name, srcs, **kwargs): py_test( name = name, srcs = [ - "proto_format.py", + "@ankidesktop//proto:format.py", ], data = ["@clang_format//:clang_format"] + srcs, args = ["$(location @clang_format//:clang_format)"] + [native.package_name() + "/" + f for f in srcs], diff --git a/proto/format.bzl b/proto/format.bzl new file mode 100644 index 000000000..fd91d5fe7 --- /dev/null +++ b/proto/format.bzl @@ -0,0 +1,76 @@ +# Copyright: Ankitects Pty Ltd and contributors +# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +""" +Exposes a clang-format binary for formatting protobuf. +""" + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") +load("@rules_python//python:defs.bzl", "py_test") + +def _impl(rctx): + rctx.file("BUILD.bazel", """ +alias( + name = "clang_format", + actual = select({ + "@ankidesktop//platforms:windows_x86_64": "@clang_format_windows_x86_64//:clang-format.exe", + "@ankidesktop//platforms:macos_x86_64": "@clang_format_macos_x86_64//:clang-format", + "@ankidesktop//platforms:linux_x86_64": "@clang_format_linux_x86_64//:clang-format", + }), + visibility = ["//visibility:public"] +) +""") + +_setup_clang_format = repository_rule( + attrs = {}, + local = True, + implementation = _impl, +) + +def setup_clang_format(name): + maybe( + http_archive, + name = "clang_format_macos_x86_64", + build_file_content = """exports_files(["clang-format"])""", + sha256 = "238be68d9478163a945754f06a213483473044f5a004c4125d3d9d8d3556466e", + urls = [ + "https://github.com/ankitects/clang-format-binaries/releases/download/anki-2021-01-09/clang-format_macos_x86_64.zip", + ], + ) + + maybe( + http_archive, + name = "clang_format_linux_x86_64", + build_file_content = """exports_files(["clang-format"])""", + sha256 = "64060bc4dbca30d0d96aab9344e2783008b16e1cae019a2532f1126ca5ec5449", + urls = [ + "https://github.com/ankitects/clang-format-binaries/releases/download/anki-2021-01-09/clang-format_linux_x86_64.zip", + ], + ) + + maybe( + http_archive, + name = "clang_format_windows_x86_64", + build_file_content = """exports_files(["clang-format.exe"])""", + sha256 = "7d9f6915e3f0fb72407830f0fc37141308d2e6915daba72987a52f309fbeaccc", + urls = [ + "https://github.com/ankitects/clang-format-binaries/releases/download/anki-2021-01-09/clang-format_windows_x86_64.zip", + ], + ) + + if not native.existing_rule(name): + _setup_clang_format( + name = name, + ) + +def proto_format(name, srcs, **kwargs): + py_test( + name = name, + srcs = [ + "@ankidesktop//format.py", + ], + data = ["@clang_format//:clang_format"] + srcs, + args = ["$(location @clang_format//:clang_format)"] + [native.package_name() + "/" + f for f in srcs], + **kwargs + ) diff --git a/rslib/proto_format.py b/proto/format.py similarity index 100% rename from rslib/proto_format.py rename to proto/format.py diff --git a/protobuf.bzl b/proto/protobuf.bzl similarity index 85% rename from protobuf.bzl rename to proto/protobuf.bzl index b46af971b..2e42c5511 100644 --- a/protobuf.bzl +++ b/proto/protobuf.bzl @@ -12,10 +12,10 @@ def _impl(rctx): alias( name = "protoc", actual = select({ - "@net_ankiweb_anki//platforms:windows_x86_64": "@protoc_bin_windows//:bin/protoc.exe", - "@net_ankiweb_anki//platforms:macos_x86_64": "@protoc_bin_macos//:bin/protoc", - "@net_ankiweb_anki//platforms:linux_x86_64": "@protoc_bin_linux_x86_64//:bin/protoc", - "@net_ankiweb_anki//platforms:linux_arm64": "@protoc_bin_linux_arm64//:bin/protoc" + "@ankidesktop//platforms:windows_x86_64": "@protoc_bin_windows//:bin/protoc.exe", + "@ankidesktop//platforms:macos_x86_64": "@protoc_bin_macos//:bin/protoc", + "@ankidesktop//platforms:linux_x86_64": "@protoc_bin_linux_x86_64//:bin/protoc", + "@ankidesktop//platforms:linux_arm64": "@protoc_bin_linux_arm64//:bin/protoc" }), visibility = ["//visibility:public"] ) diff --git a/pylib/.pylintrc b/pylib/.pylintrc index 0ab9d4fc0..1fc1205de 100644 --- a/pylib/.pylintrc +++ b/pylib/.pylintrc @@ -14,6 +14,7 @@ ignored-classes= NoteFieldsCheckResponse, BackendError, SetDeckCollapsedRequest, + ConfigKey, [REPORTS] output-format=colorized diff --git a/pylib/BUILD.bazel b/pylib/BUILD.bazel index 098d24a83..8c791286f 100644 --- a/pylib/BUILD.bazel +++ b/pylib/BUILD.bazel @@ -31,6 +31,7 @@ py_test( main = "tests/run_mypy.py", deps = [ "//pylib/anki", + "//pylib/anki:proto", requirement("mypy"), ], ) diff --git a/pylib/anki/BUILD.bazel b/pylib/anki/BUILD.bazel index 056449dbc..872110c05 100644 --- a/pylib/anki/BUILD.bazel +++ b/pylib/anki/BUILD.bazel @@ -4,6 +4,7 @@ load("@py_deps//:requirements.bzl", "requirement") load("@rules_python//experimental/python:wheel.bzl", "py_package", "py_wheel") load("//:defs.bzl", "anki_version") load("//pylib:orjson.bzl", "orjson_if_available") +load("//pylib:protobuf.bzl", "py_proto") copy_file( name = "buildinfo", @@ -39,6 +40,7 @@ py_library( ], visibility = ["//visibility:public"], deps = [ + ":proto", requirement("beautifulsoup4"), requirement("decorator"), requirement("distro"), @@ -105,3 +107,24 @@ filegroup( "//pylib:__subpackages__", ], ) + +py_proto( + name = "proto_py", + srcs = ["//proto"], + visibility = [ + "//visibility:public", + ], +) + +py_library( + name = "proto", + srcs = [ + "__init__.py", + # includes the .py files + ":proto_py", + ], + # includes the .pyi files + data = [":proto_py", "py.typed"], + imports = [".."], + visibility = ["//visibility:public"], +) diff --git a/pylib/anki/__init__.py b/pylib/anki/__init__.py index d958c513c..e3d8b5638 100644 --- a/pylib/anki/__init__.py +++ b/pylib/anki/__init__.py @@ -1,18 +1,2 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - -import sys - -from anki.buildinfo import version -from anki.collection import Collection - -if sys.version_info[0] < 3 or sys.version_info[1] < 7: - raise Exception("Anki requires Python 3.7+") - -# ensure unicode filenames are supported -try: - "ใƒ†ใ‚นใƒˆ".encode(sys.getfilesystemencoding()) -except UnicodeEncodeError as exc: - raise Exception("Anki requires a UTF-8 locale.") from exc - -__all__ = ["Collection"] diff --git a/pylib/anki/_backend/BUILD.bazel b/pylib/anki/_backend/BUILD.bazel index 2cba311d5..e60346b55 100644 --- a/pylib/anki/_backend/BUILD.bazel +++ b/pylib/anki/_backend/BUILD.bazel @@ -1,27 +1,18 @@ load("@rules_python//python:defs.bzl", "py_binary") load("@py_deps//:requirements.bzl", "requirement") -load("//pylib:protobuf.bzl", "py_proto_library_typed") load("@bazel_skylib//rules:copy_file.bzl", "copy_file") load("@bazel_skylib//lib:selects.bzl", "selects") -py_proto_library_typed( - name = "backend_pb2", - src = "//rslib:backend.proto", - visibility = [ - "//visibility:public", - ], -) - py_binary( name = "genbackend", srcs = [ - "backend_pb2", "genbackend.py", ], deps = [ requirement("black"), requirement("stringcase"), requirement("protobuf"), + "//pylib/anki:proto", ], ) @@ -94,7 +85,6 @@ filegroup( srcs = [ "__init__.py", "rsbridge.pyi", - ":backend_pb2", ":fluent_gen", ":rsbackend_gen", ":rsbridge", diff --git a/pylib/anki/_backend/__init__.py b/pylib/anki/_backend/__init__.py index 83cd3a2d7..d0c23045d 100644 --- a/pylib/anki/_backend/__init__.py +++ b/pylib/anki/_backend/__init__.py @@ -11,6 +11,8 @@ from weakref import ref from markdown import markdown import anki.buildinfo +import anki.lang +from anki import backend_pb2, i18n_pb2 from anki._backend.generated import RustBackendGenerated from anki.dbproxy import Row as DBRow from anki.dbproxy import ValueForDB @@ -32,7 +34,6 @@ from ..errors import ( TemplateError, UndoEmpty, ) -from . import backend_pb2 as pb from . import rsbridge from .fluent import GeneratedTranslations, LegacyTranslationEnum @@ -65,7 +66,7 @@ class RustBackend(RustBackendGenerated): if langs is None: langs = [anki.lang.currentLang] - init_msg = pb.BackendInit( + init_msg = backend_pb2.BackendInit( preferred_langs=langs, server=server, ) @@ -95,7 +96,7 @@ class RustBackend(RustBackendGenerated): return from_json_bytes(self._backend.db_command(to_json_bytes(input))) except Exception as e: err_bytes = bytes(e.args[0]) - err = pb.BackendError() + err = backend_pb2.BackendError() err.ParseFromString(err_bytes) raise backend_exception_to_pylib(err) @@ -125,21 +126,21 @@ class RustBackend(RustBackendGenerated): return self._backend.command(service, method, input_bytes) except Exception as e: err_bytes = bytes(e.args[0]) - err = pb.BackendError() + err = backend_pb2.BackendError() err.ParseFromString(err_bytes) raise backend_exception_to_pylib(err) def translate_string_in( module_index: int, message_index: int, **kwargs: Union[str, int, float] -) -> pb.TranslateStringRequest: +) -> i18n_pb2.TranslateStringRequest: args = {} for (k, v) in kwargs.items(): if isinstance(v, str): - args[k] = pb.TranslateArgValue(str=v) + args[k] = i18n_pb2.TranslateArgValue(str=v) else: - args[k] = pb.TranslateArgValue(number=v) - return pb.TranslateStringRequest( + args[k] = i18n_pb2.TranslateArgValue(number=v) + return i18n_pb2.TranslateStringRequest( module_index=module_index, message_index=message_index, args=args ) @@ -167,8 +168,8 @@ class Translations(GeneratedTranslations): ) -def backend_exception_to_pylib(err: pb.BackendError) -> Exception: - kind = pb.BackendError +def backend_exception_to_pylib(err: backend_pb2.BackendError) -> Exception: + kind = backend_pb2.BackendError val = err.kind if val == kind.INTERRUPTED: return Interrupted() diff --git a/pylib/anki/_backend/backend_pb2.pyi b/pylib/anki/_backend/backend_pb2.pyi deleted file mode 120000 index fb507905b..000000000 --- a/pylib/anki/_backend/backend_pb2.pyi +++ /dev/null @@ -1 +0,0 @@ -../../../bazel-bin/pylib/anki/_backend/backend_pb2.pyi \ No newline at end of file diff --git a/pylib/anki/_backend/genbackend.py b/pylib/anki/_backend/genbackend.py index d86e6ceb9..b5cb0964a 100755 --- a/pylib/anki/_backend/genbackend.py +++ b/pylib/anki/_backend/genbackend.py @@ -7,7 +7,24 @@ import re import sys import google.protobuf.descriptor -import pylib.anki._backend.backend_pb2 as pb + +import anki.backend_pb2 +import anki.i18n_pb2 +import anki.cards_pb2 +import anki.collection_pb2 +import anki.decks_pb2 +import anki.deckconfig_pb2 +import anki.notes_pb2 +import anki.notetypes_pb2 +import anki.scheduler_pb2 +import anki.sync_pb2 +import anki.config_pb2 +import anki.search_pb2 +import anki.stats_pb2 +import anki.card_rendering_pb2 +import anki.tags_pb2 +import anki.media_pb2 + import stringcase TYPE_DOUBLE = 1 @@ -73,11 +90,11 @@ def python_type_inner(field): raise Exception(f"unknown type: {type}") -def fullname(fullname): - if "FluentString" in fullname: - return fullname.replace("BackendProto", "anki.fluent_pb2") - else: - return fullname.replace("BackendProto", "pb") +def fullname(fullname: str) -> str: + # eg anki.generic.Empty -> anki.generic_pb2.Empty + components = fullname.split(".") + components[1] += "_pb2" + return ".".join(components) # get_deck_i_d -> get_deck_id etc @@ -131,7 +148,7 @@ def render_method(service_idx, method_idx, method): return_type = python_type(f) else: single_field = "" - return_type = f"pb.{method.output_type.name}" + return_type = fullname(method.output_type.full_name) if method.name in SKIP_DECODE: return_type = "bytes" @@ -144,7 +161,7 @@ def render_method(service_idx, method_idx, method): buf += f"""return self._run_command({service_idx}, {method_idx}, input) """ else: - buf += f"""output = pb.{method.output_type.name}() + buf += f"""output = {fullname(method.output_type.full_name)}() output.ParseFromString(self._run_command({service_idx}, {method_idx}, input)) return output{single_field} """ @@ -162,12 +179,30 @@ def render_service( out.append(render_method(service_index, method_index, method)) -for service in pb.ServiceIndex.DESCRIPTOR.values: +service_modules = dict( + I18N=anki.i18n_pb2, + COLLECTION=anki.collection_pb2, + CARDS=anki.cards_pb2, + NOTES=anki.notes_pb2, + DECKS=anki.decks_pb2, + DECK_CONFIG=anki.deckconfig_pb2, + NOTETYPES=anki.notetypes_pb2, + SCHEDULER=anki.scheduler_pb2, + SYNC=anki.sync_pb2, + CONFIG=anki.config_pb2, + SEARCH=anki.search_pb2, + STATS=anki.stats_pb2, + CARD_RENDERING=anki.card_rendering_pb2, + TAGS=anki.tags_pb2, + MEDIA=anki.media_pb2, +) + +for service in anki.backend_pb2.ServiceIndex.DESCRIPTOR.values: # SERVICE_INDEX_TEST -> _TESTSERVICE - service_var = ( - "_" + service.name.replace("SERVICE_INDEX", "").replace("_", "") + "SERVICE" - ) - service_obj = getattr(pb, service_var) + base = service.name.replace("SERVICE_INDEX_", "") + service_pkg = service_modules.get(base) + service_var = "_" + base.replace("_", "") + "SERVICE" + service_obj = getattr(service_pkg, service_var) service_index = service.number render_service(service_obj, service_index) @@ -194,7 +229,23 @@ col.decks.all_config() from typing import * -import anki._backend.backend_pb2 as pb +import anki +import anki.backend_pb2 +import anki.i18n_pb2 +import anki.cards_pb2 +import anki.collection_pb2 +import anki.decks_pb2 +import anki.deckconfig_pb2 +import anki.notes_pb2 +import anki.notetypes_pb2 +import anki.scheduler_pb2 +import anki.sync_pb2 +import anki.config_pb2 +import anki.search_pb2 +import anki.stats_pb2 +import anki.card_rendering_pb2 +import anki.tags_pb2 +import anki.media_pb2 class RustBackendGenerated: def _run_command(self, service: int, method: int, input: Any) -> bytes: diff --git a/pylib/anki/backend_pb2.pyi b/pylib/anki/backend_pb2.pyi new file mode 120000 index 000000000..d3937d76d --- /dev/null +++ b/pylib/anki/backend_pb2.pyi @@ -0,0 +1 @@ +../../bazel-bin/pylib/anki/backend_pb2.pyi \ No newline at end of file diff --git a/pylib/anki/card_rendering_pb2.pyi b/pylib/anki/card_rendering_pb2.pyi new file mode 120000 index 000000000..02d63405a --- /dev/null +++ b/pylib/anki/card_rendering_pb2.pyi @@ -0,0 +1 @@ +../../bazel-bin/pylib/anki/card_rendering_pb2.pyi \ No newline at end of file diff --git a/pylib/anki/cards.py b/pylib/anki/cards.py index 60f15c42b..769332f8d 100644 --- a/pylib/anki/cards.py +++ b/pylib/anki/cards.py @@ -10,8 +10,7 @@ import time from typing import List, NewType, Optional import anki # pylint: disable=unused-import -import anki._backend.backend_pb2 as _pb -from anki import hooks +from anki import cards_pb2, hooks from anki._legacy import DeprecatedNamesMixin, deprecated from anki.consts import * from anki.models import NotetypeDict, TemplateDict @@ -31,7 +30,7 @@ from anki.sound import AVTag # types CardId = NewType("CardId", int) -BackendCard = _pb.Card +BackendCard = cards_pb2.Card class Card(DeprecatedNamesMixin): @@ -62,14 +61,14 @@ class Card(DeprecatedNamesMixin): self._load_from_backend_card(backend_card) else: # new card with defaults - self._load_from_backend_card(_pb.Card()) + self._load_from_backend_card(cards_pb2.Card()) def load(self) -> None: card = self.col._backend.get_card(self.id) assert card self._load_from_backend_card(card) - def _load_from_backend_card(self, card: _pb.Card) -> None: + def _load_from_backend_card(self, card: cards_pb2.Card) -> None: self._render_output = None self._note = None self.id = CardId(card.id) @@ -91,9 +90,9 @@ class Card(DeprecatedNamesMixin): self.flags = card.flags self.data = card.data - def _to_backend_card(self) -> _pb.Card: + def _to_backend_card(self) -> cards_pb2.Card: # mtime & usn are set by backend - return _pb.Card( + return cards_pb2.Card( id=self.id, note_id=self.nid, deck_id=self.did, diff --git a/pylib/anki/cards_pb2.pyi b/pylib/anki/cards_pb2.pyi new file mode 120000 index 000000000..f503a0f31 --- /dev/null +++ b/pylib/anki/cards_pb2.pyi @@ -0,0 +1 @@ +../../bazel-bin/pylib/anki/cards_pb2.pyi \ No newline at end of file diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index dc7cfaeb2..757170257 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -7,24 +7,29 @@ from __future__ import annotations from typing import Any, Generator, List, Literal, Optional, Sequence, Tuple, Union, cast -import anki._backend.backend_pb2 as _pb - -# protobuf we publicly export - listed first to avoid circular imports +from anki import ( + card_rendering_pb2, + collection_pb2, + config_pb2, + generic_pb2, + search_pb2, + stats_pb2, +) from anki._legacy import DeprecatedNamesMixin, deprecated -SearchNode = _pb.SearchNode -Progress = _pb.Progress -EmptyCardsReport = _pb.EmptyCardsReport -GraphPreferences = _pb.GraphPreferences -Preferences = _pb.Preferences -UndoStatus = _pb.UndoStatus -OpChanges = _pb.OpChanges -OpChangesWithCount = _pb.OpChangesWithCount -OpChangesWithId = _pb.OpChangesWithId -OpChangesAfterUndo = _pb.OpChangesAfterUndo -DefaultsForAdding = _pb.DeckAndNotetype -BrowserRow = _pb.BrowserRow -BrowserColumns = _pb.BrowserColumns +# protobuf we publicly export - listed first to avoid circular imports +SearchNode = search_pb2.SearchNode +Progress = collection_pb2.Progress +EmptyCardsReport = card_rendering_pb2.EmptyCardsReport +GraphPreferences = stats_pb2.GraphPreferences +Preferences = config_pb2.Preferences +UndoStatus = collection_pb2.UndoStatus +OpChanges = collection_pb2.OpChanges +OpChangesWithCount = collection_pb2.OpChangesWithCount +OpChangesWithId = collection_pb2.OpChangesWithId +OpChangesAfterUndo = collection_pb2.OpChangesAfterUndo +BrowserRow = search_pb2.BrowserRow +BrowserColumns = search_pb2.BrowserColumns import copy import os @@ -370,7 +375,7 @@ class Collection(DeprecatedNamesMixin): def defaults_for_adding( self, *, current_review_card: Optional[Card] - ) -> DefaultsForAdding: + ) -> anki.notes.DefaultsForAdding: """Get starting deck and notetype for add screen. An option in the preferences controls whether this will be based on the current deck or current notetype. @@ -487,12 +492,12 @@ class Collection(DeprecatedNamesMixin): order: Union[bool, str, BrowserColumns.Column], reverse: bool, finding_notes: bool, - ) -> _pb.SortOrder: + ) -> search_pb2.SortOrder: if isinstance(order, str): - return _pb.SortOrder(custom=order) + return search_pb2.SortOrder(custom=order) if isinstance(order, bool): if order is False: - return _pb.SortOrder(none=_pb.Empty()) + return search_pb2.SortOrder(none=generic_pb2.Empty()) # order=True: set args to sort column and reverse from config sort_key = BrowserConfig.sort_column_key(finding_notes) order = self.get_browser_column(self.get_config(sort_key)) @@ -500,13 +505,15 @@ class Collection(DeprecatedNamesMixin): reverse = self.get_config(reverse_key) if isinstance(order, BrowserColumns.Column): if order.sorting != BrowserColumns.SORTING_NONE: - return _pb.SortOrder( - builtin=_pb.SortOrder.Builtin(column=order.key, reverse=reverse) + return search_pb2.SortOrder( + builtin=search_pb2.SortOrder.Builtin( + column=order.key, reverse=reverse + ) ) # eg, user is ordering on an add-on field with the add-on not installed print(f"{order} is not a valid sort order.") - return _pb.SortOrder(none=_pb.Empty()) + return search_pb2.SortOrder(none=generic_pb2.Empty()) def find_and_replace( self, @@ -725,19 +732,19 @@ class Collection(DeprecatedNamesMixin): "This is a debugging aid. Prefer .get_config() when you know the key you need." return from_json_bytes(self._backend.get_all_config()) - def get_config_bool(self, key: Config.Bool.Key.V) -> bool: + def get_config_bool(self, key: Config.Bool.V) -> bool: return self._backend.get_config_bool(key) def set_config_bool( - self, key: Config.Bool.Key.V, value: bool, *, undoable: bool = False + self, key: Config.Bool.V, value: bool, *, undoable: bool = False ) -> OpChanges: return self._backend.set_config_bool(key=key, value=value, undoable=undoable) - def get_config_string(self, key: Config.String.Key.V) -> str: + def get_config_string(self, key: Config.String.V) -> str: return self._backend.get_config_string(key) def set_config_string( - self, key: Config.String.Key.V, value: str, undoable: bool = False + self, key: Config.String.V, value: str, undoable: bool = False ) -> OpChanges: return self._backend.set_config_string(key=key, value=value, undoable=undoable) diff --git a/pylib/anki/collection_pb2.pyi b/pylib/anki/collection_pb2.pyi new file mode 120000 index 000000000..1c9fc3872 --- /dev/null +++ b/pylib/anki/collection_pb2.pyi @@ -0,0 +1 @@ +../../bazel-bin/pylib/anki/collection_pb2.pyi \ No newline at end of file diff --git a/pylib/anki/config.py b/pylib/anki/config.py index af71eeaf5..61d3e9357 100644 --- a/pylib/anki/config.py +++ b/pylib/anki/config.py @@ -25,12 +25,12 @@ from typing import Any from weakref import ref import anki -from anki._backend import backend_pb2 as _pb +from anki import config_pb2 from anki.collection import OpChanges from anki.errors import NotFoundError from anki.utils import from_json_bytes, to_json_bytes -Config = _pb.Config +Config = config_pb2.ConfigKey class ConfigManager: diff --git a/pylib/anki/config_pb2.pyi b/pylib/anki/config_pb2.pyi new file mode 120000 index 000000000..4c6057427 --- /dev/null +++ b/pylib/anki/config_pb2.pyi @@ -0,0 +1 @@ +../../bazel-bin/pylib/anki/config_pb2.pyi \ No newline at end of file diff --git a/pylib/anki/consts.py b/pylib/anki/consts.py index d57a93b00..f26388245 100644 --- a/pylib/anki/consts.py +++ b/pylib/anki/consts.py @@ -6,8 +6,6 @@ from __future__ import annotations import sys from typing import Any, Dict, NewType, Optional -import anki - # whether new cards should be mixed with reviews, or shown first or last NEW_CARDS_DISTRIBUTE = 0 NEW_CARDS_LAST = 1 @@ -93,6 +91,8 @@ REVLOG_RESCHED = 4 # Labels ########################################################################## +import anki.collection + def _tr(col: Optional[anki.collection.Collection]) -> Any: if col: diff --git a/pylib/anki/dbproxy.py b/pylib/anki/dbproxy.py index 3561613c5..45c391383 100644 --- a/pylib/anki/dbproxy.py +++ b/pylib/anki/dbproxy.py @@ -7,7 +7,7 @@ import re from re import Match from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple, Union -import anki +import anki._backend # 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 diff --git a/pylib/anki/deckconfig_pb2.pyi b/pylib/anki/deckconfig_pb2.pyi new file mode 120000 index 000000000..9cd723191 --- /dev/null +++ b/pylib/anki/deckconfig_pb2.pyi @@ -0,0 +1 @@ +../../bazel-bin/pylib/anki/deckconfig_pb2.pyi \ No newline at end of file diff --git a/pylib/anki/decks.py b/pylib/anki/decks.py index bda0c1e2e..bb32c074f 100644 --- a/pylib/anki/decks.py +++ b/pylib/anki/decks.py @@ -23,7 +23,7 @@ from typing import ( if TYPE_CHECKING: import anki -import anki._backend.backend_pb2 as _pb +from anki import deckconfig_pb2, decks_pb2 from anki._legacy import DeprecatedNamesMixin, deprecated, print_deprecation_warning from anki.cards import CardId from anki.collection import OpChanges, OpChangesWithCount, OpChangesWithId @@ -32,12 +32,12 @@ from anki.errors import NotFoundError from anki.utils import from_json_bytes, ids2str, intTime, to_json_bytes # public exports -DeckTreeNode = _pb.DeckTreeNode -DeckNameId = _pb.DeckNameId -FilteredDeckConfig = _pb.Deck.Filtered -DeckCollapseScope = _pb.SetDeckCollapsedRequest.Scope -DeckConfigsForUpdate = _pb.DeckConfigsForUpdate -UpdateDeckConfigs = _pb.UpdateDeckConfigsRequest +DeckTreeNode = decks_pb2.DeckTreeNode +DeckNameId = decks_pb2.DeckNameId +FilteredDeckConfig = decks_pb2.Deck.Filtered +DeckCollapseScope = decks_pb2.SetDeckCollapsedRequest.Scope +DeckConfigsForUpdate = deckconfig_pb2.DeckConfigsForUpdate +UpdateDeckConfigs = deckconfig_pb2.UpdateDeckConfigsRequest # type aliases until we can move away from dicts DeckDict = Dict[str, Any] diff --git a/pylib/anki/decks_pb2.pyi b/pylib/anki/decks_pb2.pyi new file mode 120000 index 000000000..dc097aa9f --- /dev/null +++ b/pylib/anki/decks_pb2.pyi @@ -0,0 +1 @@ +../../bazel-bin/pylib/anki/decks_pb2.pyi \ No newline at end of file diff --git a/pylib/anki/generic_pb2.pyi b/pylib/anki/generic_pb2.pyi new file mode 120000 index 000000000..77017f778 --- /dev/null +++ b/pylib/anki/generic_pb2.pyi @@ -0,0 +1 @@ +../../bazel-bin/pylib/anki/generic_pb2.pyi \ No newline at end of file diff --git a/pylib/anki/httpclient.py b/pylib/anki/httpclient.py index 55ca971b6..a26536496 100644 --- a/pylib/anki/httpclient.py +++ b/pylib/anki/httpclient.py @@ -76,7 +76,7 @@ class HttpClient: return buf.getvalue() def _agentName(self) -> str: - from anki import version + from anki.buildinfo import version return f"Anki {version}" diff --git a/pylib/anki/i18n_pb2.pyi b/pylib/anki/i18n_pb2.pyi new file mode 120000 index 000000000..d496aec75 --- /dev/null +++ b/pylib/anki/i18n_pb2.pyi @@ -0,0 +1 @@ +../../bazel-bin/pylib/anki/i18n_pb2.pyi \ No newline at end of file diff --git a/pylib/anki/importing/__init__.py b/pylib/anki/importing/__init__.py index d2f434938..7c35ba545 100644 --- a/pylib/anki/importing/__init__.py +++ b/pylib/anki/importing/__init__.py @@ -3,7 +3,7 @@ from typing import Any, Callable, Sequence, Tuple, Type, Union -from anki import Collection +from anki.collection import Collection from anki.importing.anki2 import Anki2Importer from anki.importing.apkg import AnkiPackageImporter from anki.importing.base import Importer diff --git a/pylib/anki/lang.py b/pylib/anki/lang.py index 020712444..87ccee947 100644 --- a/pylib/anki/lang.py +++ b/pylib/anki/lang.py @@ -9,7 +9,8 @@ import weakref from typing import Optional, Tuple import anki -import anki._backend.backend_pb2 as _pb +import anki._backend +import anki.i18n_pb2 as _pb # public exports TR = anki._backend.LegacyTranslationEnum diff --git a/pylib/anki/latex.py b/pylib/anki/latex.py index 2132d5972..e1e31f228 100644 --- a/pylib/anki/latex.py +++ b/pylib/anki/latex.py @@ -10,8 +10,7 @@ from dataclasses import dataclass from typing import Any, List, Optional, Tuple import anki -import anki._backend.backend_pb2 as _pb -from anki import hooks +from anki import card_rendering_pb2, hooks from anki.models import NotetypeDict from anki.template import TemplateRenderContext, TemplateRenderOutput from anki.utils import call, isMac, namedtmp, tmpdir @@ -45,7 +44,9 @@ class ExtractedLatexOutput: latex: List[ExtractedLatex] @staticmethod - def from_proto(proto: _pb.ExtractLatexResponse) -> ExtractedLatexOutput: + def from_proto( + proto: card_rendering_pb2.ExtractLatexResponse, + ) -> ExtractedLatexOutput: return ExtractedLatexOutput( html=proto.text, latex=[ diff --git a/pylib/anki/media.py b/pylib/anki/media.py index e54f43485..df9097a59 100644 --- a/pylib/anki/media.py +++ b/pylib/anki/media.py @@ -10,8 +10,7 @@ import sys import time from typing import Any, Callable, List, Optional, Tuple -import anki -import anki._backend.backend_pb2 as _pb +from anki import media_pb2 from anki._legacy import deprecated from anki.consts import * from anki.latex import render_latex, render_latex_returning_errors @@ -27,7 +26,7 @@ def media_paths_from_col_path(col_path: str) -> Tuple[str, str]: return (media_folder, media_db) -CheckMediaResponse = _pb.CheckMediaResponse +CheckMediaResponse = media_pb2.CheckMediaResponse # fixme: look into whether we can drop chdir() below diff --git a/pylib/anki/media_pb2.pyi b/pylib/anki/media_pb2.pyi new file mode 120000 index 000000000..f86cbf1aa --- /dev/null +++ b/pylib/anki/media_pb2.pyi @@ -0,0 +1 @@ +../../bazel-bin/pylib/anki/media_pb2.pyi \ No newline at end of file diff --git a/pylib/anki/models.py b/pylib/anki/models.py index 8a3bd13f6..889d749bd 100644 --- a/pylib/anki/models.py +++ b/pylib/anki/models.py @@ -12,7 +12,7 @@ import time from typing import Any, Dict, List, NewType, Optional, Sequence, Tuple, Union import anki # pylint: disable=unused-import -import anki._backend.backend_pb2 as _pb +from anki import notetypes_pb2 from anki._legacy import DeprecatedNamesMixin, deprecated, print_deprecation_warning from anki.collection import OpChanges, OpChangesWithId from anki.consts import * @@ -22,11 +22,11 @@ from anki.stdmodels import StockNotetypeKind from anki.utils import checksum, from_json_bytes, to_json_bytes # public exports -NotetypeNameId = _pb.NotetypeNameId -NotetypeNameIdUseCount = _pb.NotetypeNameIdUseCount -NotetypeNames = _pb.NotetypeNames -ChangeNotetypeInfo = _pb.ChangeNotetypeInfo -ChangeNotetypeRequest = _pb.ChangeNotetypeRequest +NotetypeNameId = notetypes_pb2.NotetypeNameId +NotetypeNameIdUseCount = notetypes_pb2.NotetypeNameIdUseCount +NotetypeNames = notetypes_pb2.NotetypeNames +ChangeNotetypeInfo = notetypes_pb2.ChangeNotetypeInfo +ChangeNotetypeRequest = notetypes_pb2.ChangeNotetypeRequest # legacy types NotetypeDict = Dict[str, Any] @@ -459,7 +459,9 @@ and notes.mid = ? and cards.ord = ?""", def _availClozeOrds( self, notetype: NotetypeDict, flds: str, allow_empty: bool = True ) -> List[int]: - note = _pb.Note(fields=[flds]) + import anki.notes_pb2 + + note = anki.notes_pb2.Note(fields=[flds]) return list(self.col._backend.cloze_numbers_in_note(note)) # @deprecated(replaced_by=add_template) diff --git a/pylib/anki/notes.py b/pylib/anki/notes.py index 356c34ff5..f63da1083 100644 --- a/pylib/anki/notes.py +++ b/pylib/anki/notes.py @@ -9,15 +9,15 @@ import copy from typing import Any, List, NewType, Optional, Sequence, Tuple, Union import anki # pylint: disable=unused-import -import anki._backend.backend_pb2 as _pb -from anki import hooks +from anki import hooks, notes_pb2 from anki._legacy import DeprecatedNamesMixin from anki.consts import MODEL_STD from anki.models import NotetypeDict, NotetypeId, TemplateDict from anki.utils import joinFields -DuplicateOrEmptyResult = _pb.NoteFieldsCheckResponse.State -NoteFieldsCheckResult = _pb.NoteFieldsCheckResponse.State +DuplicateOrEmptyResult = notes_pb2.NoteFieldsCheckResponse.State +NoteFieldsCheckResult = notes_pb2.NoteFieldsCheckResponse.State +DefaultsForAdding = notes_pb2.DeckAndNotetype # types NoteId = NewType("NoteId", int) @@ -53,7 +53,7 @@ class Note(DeprecatedNamesMixin): assert note self._load_from_backend_note(note) - def _load_from_backend_note(self, note: _pb.Note) -> None: + def _load_from_backend_note(self, note: notes_pb2.Note) -> None: self.id = NoteId(note.id) self.guid = note.guid self.mid = NotetypeId(note.notetype_id) @@ -63,9 +63,9 @@ class Note(DeprecatedNamesMixin): self.fields = list(note.fields) self._fmap = self.col.models.field_map(self.note_type()) - def _to_backend_note(self) -> _pb.Note: + def _to_backend_note(self) -> notes_pb2.Note: hooks.note_will_flush(self) - return _pb.Note( + return notes_pb2.Note( id=self.id, guid=self.guid, notetype_id=self.mid, diff --git a/pylib/anki/notes_pb2.pyi b/pylib/anki/notes_pb2.pyi new file mode 120000 index 000000000..3728ff344 --- /dev/null +++ b/pylib/anki/notes_pb2.pyi @@ -0,0 +1 @@ +../../bazel-bin/pylib/anki/notes_pb2.pyi \ No newline at end of file diff --git a/pylib/anki/notetypes_pb2.pyi b/pylib/anki/notetypes_pb2.pyi new file mode 120000 index 000000000..7d08b284d --- /dev/null +++ b/pylib/anki/notetypes_pb2.pyi @@ -0,0 +1 @@ +../../bazel-bin/pylib/anki/notetypes_pb2.pyi \ No newline at end of file diff --git a/pylib/anki/scheduler/base.py b/pylib/anki/scheduler/base.py index a4b082623..defd25599 100644 --- a/pylib/anki/scheduler/base.py +++ b/pylib/anki/scheduler/base.py @@ -4,26 +4,26 @@ from __future__ import annotations import anki -import anki._backend.backend_pb2 as _pb +from anki import decks_pb2, scheduler_pb2 from anki.collection import OpChanges, OpChangesWithCount, OpChangesWithId from anki.config import Config -SchedTimingToday = _pb.SchedTimingTodayResponse +SchedTimingToday = scheduler_pb2.SchedTimingTodayResponse +CongratsInfo = scheduler_pb2.CongratsInfoResponse +UnburyDeck = scheduler_pb2.UnburyDeckRequest +BuryOrSuspend = scheduler_pb2.BuryOrSuspendCardsRequest +FilteredDeckForUpdate = decks_pb2.FilteredDeckForUpdate from typing import List, Optional, Sequence +from anki import config_pb2 from anki.cards import CardId from anki.consts import CARD_TYPE_NEW, NEW_CARDS_RANDOM, QUEUE_TYPE_NEW, QUEUE_TYPE_REV from anki.decks import DeckConfigDict, DeckId, DeckTreeNode from anki.notes import NoteId from anki.utils import ids2str, intTime -CongratsInfo = _pb.CongratsInfoResponse -UnburyDeck = _pb.UnburyDeckRequest -BuryOrSuspend = _pb.BuryOrSuspendCardsRequest -FilteredDeckForUpdate = _pb.FilteredDeckForUpdate - class SchedulerBase: "Actions shared between schedulers." @@ -162,14 +162,14 @@ select id from cards where did in %s and queue = {QUEUE_TYPE_REV} and due <= ? l self, card_ids: Sequence[CardId], days: str, - config_key: Optional[Config.String.Key.V] = None, + config_key: Optional[Config.String.V] = None, ) -> OpChanges: """Set cards to be due in `days`, turning them into review cards if necessary. `days` can be of the form '5' or '5..7' If `config_key` is provided, provided days will be remembered in config.""" - key: Optional[Config.String] + key: Optional[config_pb2.OptionalStringConfigKey] if config_key is not None: - key = Config.String(key=config_key) + key = config_pb2.OptionalStringConfigKey(key=config_key) else: key = None return self.col._backend.set_due_date( diff --git a/pylib/anki/scheduler/v2.py b/pylib/anki/scheduler/v2.py index 713a9e15d..43500c4c1 100644 --- a/pylib/anki/scheduler/v2.py +++ b/pylib/anki/scheduler/v2.py @@ -11,8 +11,7 @@ from heapq import * from typing import Any, Callable, Dict, List, Optional, Tuple, Union import anki # pylint: disable=unused-import -import anki._backend.backend_pb2 as _pb -from anki import hooks +from anki import hooks, scheduler_pb2 from anki.cards import Card, CardId from anki.consts import * from anki.decks import DeckConfigDict, DeckDict, DeckId @@ -20,8 +19,8 @@ from anki.lang import FormatTimeSpan from anki.scheduler.legacy import SchedulerBaseWithLegacy from anki.utils import ids2str, intTime -CountsForDeckToday = _pb.CountsForDeckTodayResponse -SchedTimingToday = _pb.SchedTimingTodayResponse +CountsForDeckToday = scheduler_pb2.CountsForDeckTodayResponse +SchedTimingToday = scheduler_pb2.SchedTimingTodayResponse # legacy type alias QueueConfig = Dict[str, Any] diff --git a/pylib/anki/scheduler/v3.py b/pylib/anki/scheduler/v3.py index 4986e279a..60463dbce 100644 --- a/pylib/anki/scheduler/v3.py +++ b/pylib/anki/scheduler/v3.py @@ -14,7 +14,7 @@ from __future__ import annotations from typing import List, Literal, Sequence, Tuple -import anki._backend.backend_pb2 as _pb +from anki import scheduler_pb2 from anki.cards import Card from anki.collection import OpChanges from anki.consts import * @@ -24,10 +24,10 @@ from anki.scheduler.legacy import SchedulerBaseWithLegacy from anki.types import assert_exhaustive from anki.utils import intTime -QueuedCards = _pb.QueuedCards -SchedulingState = _pb.SchedulingState -NextStates = _pb.NextCardStates -CardAnswer = _pb.CardAnswer +QueuedCards = scheduler_pb2.QueuedCards +SchedulingState = scheduler_pb2.SchedulingState +NextStates = scheduler_pb2.NextCardStates +CardAnswer = scheduler_pb2.CardAnswer class Scheduler(SchedulerBaseWithLegacy): @@ -171,7 +171,7 @@ class Scheduler(SchedulerBaseWithLegacy): ########################################################################## # fixme: move these into tests_schedv2 in the future - def _interval_for_state(self, state: _pb.SchedulingState) -> int: + def _interval_for_state(self, state: scheduler_pb2.SchedulingState) -> int: kind = state.WhichOneof("value") if kind == "normal": return self._interval_for_normal_state(state.normal) @@ -181,7 +181,9 @@ class Scheduler(SchedulerBaseWithLegacy): assert_exhaustive(kind) return 0 # unreachable - def _interval_for_normal_state(self, normal: _pb.SchedulingState.Normal) -> int: + def _interval_for_normal_state( + self, normal: scheduler_pb2.SchedulingState.Normal + ) -> int: kind = normal.WhichOneof("value") if kind == "new": return 0 @@ -196,7 +198,7 @@ class Scheduler(SchedulerBaseWithLegacy): return 0 # unreachable def _interval_for_filtered_state( - self, filtered: _pb.SchedulingState.Filtered + self, filtered: scheduler_pb2.SchedulingState.Filtered ) -> int: kind = filtered.WhichOneof("value") if kind == "preview": diff --git a/pylib/anki/scheduler_pb2.pyi b/pylib/anki/scheduler_pb2.pyi new file mode 120000 index 000000000..c09399793 --- /dev/null +++ b/pylib/anki/scheduler_pb2.pyi @@ -0,0 +1 @@ +../../bazel-bin/pylib/anki/scheduler_pb2.pyi \ No newline at end of file diff --git a/pylib/anki/search_pb2.pyi b/pylib/anki/search_pb2.pyi new file mode 120000 index 000000000..5161a8d44 --- /dev/null +++ b/pylib/anki/search_pb2.pyi @@ -0,0 +1 @@ +../../bazel-bin/pylib/anki/search_pb2.pyi \ No newline at end of file diff --git a/pylib/anki/stats.py b/pylib/anki/stats.py index 9bd302ed2..9ef02c026 100644 --- a/pylib/anki/stats.py +++ b/pylib/anki/stats.py @@ -10,7 +10,8 @@ import json import time from typing import Any, Dict, List, Optional, Sequence, Tuple, Union -import anki +import anki.cards +import anki.collection from anki.consts import * from anki.lang import FormatTimeSpan from anki.utils import ids2str diff --git a/pylib/anki/stats_pb2.pyi b/pylib/anki/stats_pb2.pyi new file mode 120000 index 000000000..b18a9af51 --- /dev/null +++ b/pylib/anki/stats_pb2.pyi @@ -0,0 +1 @@ +../../bazel-bin/pylib/anki/stats_pb2.pyi \ No newline at end of file diff --git a/pylib/anki/stdmodels.py b/pylib/anki/stdmodels.py index a3da07a74..eb5dad1a2 100644 --- a/pylib/anki/stdmodels.py +++ b/pylib/anki/stdmodels.py @@ -5,12 +5,13 @@ from __future__ import annotations from typing import Any, Callable, List, Tuple -import anki -import anki._backend.backend_pb2 as _pb +import anki.collection +import anki.models +from anki import notetypes_pb2 from anki.utils import from_json_bytes # pylint: disable=no-member -StockNotetypeKind = _pb.StockNotetype.Kind +StockNotetypeKind = notetypes_pb2.StockNotetype.Kind # add-on authors can add ("note type name", function) # to this list to have it shown in the add/clone note type screen diff --git a/pylib/anki/sync.py b/pylib/anki/sync.py index f81b7d972..e52611d30 100644 --- a/pylib/anki/sync.py +++ b/pylib/anki/sync.py @@ -1,12 +1,12 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -import anki._backend.backend_pb2 as _pb +from anki import sync_pb2 # public exports -SyncAuth = _pb.SyncAuth -SyncOutput = _pb.SyncCollectionResponse -SyncStatus = _pb.SyncStatusResponse +SyncAuth = sync_pb2.SyncAuth +SyncOutput = sync_pb2.SyncCollectionResponse +SyncStatus = sync_pb2.SyncStatusResponse # Legacy attributes some add-ons may be using diff --git a/pylib/anki/sync_pb2.pyi b/pylib/anki/sync_pb2.pyi new file mode 120000 index 000000000..c6c8de862 --- /dev/null +++ b/pylib/anki/sync_pb2.pyi @@ -0,0 +1 @@ +../../bazel-bin/pylib/anki/sync_pb2.pyi \ No newline at end of file diff --git a/pylib/anki/syncserver/__init__.py b/pylib/anki/syncserver/__init__.py index c1d60d1e8..9e6852eda 100644 --- a/pylib/anki/syncserver/__init__.py +++ b/pylib/anki/syncserver/__init__.py @@ -26,8 +26,8 @@ except ImportError as e: from flask import Response -from anki import Collection -from anki._backend.backend_pb2 import SyncServerMethodRequest +from anki.collection import Collection +from anki.sync_pb2 import SyncServerMethodRequest Method = SyncServerMethodRequest.Method # pylint: disable=no-member diff --git a/pylib/anki/tags.py b/pylib/anki/tags.py index 89bb5e158..757d102b9 100644 --- a/pylib/anki/tags.py +++ b/pylib/anki/tags.py @@ -16,15 +16,15 @@ import re from typing import Collection, List, Match, Optional, Sequence import anki # pylint: disable=unused-import -import anki._backend.backend_pb2 as _pb import anki.collection +from anki import tags_pb2 from anki.collection import OpChanges, OpChangesWithCount from anki.decks import DeckId from anki.notes import NoteId from anki.utils import ids2str # public exports -TagTreeNode = _pb.TagTreeNode +TagTreeNode = tags_pb2.TagTreeNode MARKED_TAG = "marked" diff --git a/pylib/anki/tags_pb2.pyi b/pylib/anki/tags_pb2.pyi new file mode 120000 index 000000000..c9ec6d18f --- /dev/null +++ b/pylib/anki/tags_pb2.pyi @@ -0,0 +1 @@ +../../bazel-bin/pylib/anki/tags_pb2.pyi \ No newline at end of file diff --git a/pylib/anki/template.py b/pylib/anki/template.py index 332420f7d..ab4a57a4b 100644 --- a/pylib/anki/template.py +++ b/pylib/anki/template.py @@ -32,8 +32,7 @@ from dataclasses import dataclass from typing import Any, Dict, List, Optional, Sequence, Tuple, Union import anki -import anki._backend.backend_pb2 as _pb -from anki import hooks +from anki import card_rendering_pb2, hooks from anki.cards import Card from anki.decks import DeckManager from anki.errors import TemplateError @@ -65,7 +64,9 @@ class PartiallyRenderedCard: latex_svg: bool @classmethod - def from_proto(cls, out: _pb.RenderCardResponse) -> PartiallyRenderedCard: + def from_proto( + cls, out: card_rendering_pb2.RenderCardResponse + ) -> PartiallyRenderedCard: qnodes = cls.nodes_from_proto(out.question_nodes) anodes = cls.nodes_from_proto(out.answer_nodes) @@ -73,7 +74,7 @@ class PartiallyRenderedCard: @staticmethod def nodes_from_proto( - nodes: Sequence[_pb.RenderedTemplateNode], + nodes: Sequence[card_rendering_pb2.RenderedTemplateNode], ) -> TemplateReplacementList: results: TemplateReplacementList = [] for node in nodes: @@ -90,7 +91,7 @@ class PartiallyRenderedCard: return results -def av_tag_to_native(tag: _pb.AVTag) -> AVTag: +def av_tag_to_native(tag: card_rendering_pb2.AVTag) -> AVTag: val = tag.WhichOneof("value") if val == "sound_or_video": return SoundOrVideoTag(filename=tag.sound_or_video) @@ -104,7 +105,7 @@ def av_tag_to_native(tag: _pb.AVTag) -> AVTag: ) -def av_tags_to_native(tags: Sequence[_pb.AVTag]) -> List[AVTag]: +def av_tags_to_native(tags: Sequence[card_rendering_pb2.AVTag]) -> List[AVTag]: return list(map(av_tag_to_native, tags)) diff --git a/pylib/protobuf.bzl b/pylib/protobuf.bzl index 7dc321a12..4ba3b65f4 100644 --- a/pylib/protobuf.bzl +++ b/pylib/protobuf.bzl @@ -1,21 +1,21 @@ load("@bazel_skylib//lib:paths.bzl", "paths") -def _py_proto_library_impl(ctx): - basename = ctx.file.src.basename - outs = [ - ctx.actions.declare_file(paths.replace_extension(basename, "_pb2.py")), - ctx.actions.declare_file(paths.replace_extension(basename, "_pb2.pyi")), - ] +def _py_proto_impl(ctx): + outs = [] + for src in ctx.files.srcs: + base = paths.basename(src.path) + outs.append(ctx.actions.declare_file(paths.replace_extension(base, "_pb2.py"))) + outs.append(ctx.actions.declare_file(paths.replace_extension(base, "_pb2.pyi"))) + ctx.actions.run( outputs = outs, - inputs = [ctx.file.src], + inputs = ctx.files.srcs, executable = ctx.executable.protoc_wrapper, arguments = [ ctx.executable.protoc.path, ctx.executable.mypy_protobuf.path, - ctx.file.src.path, paths.dirname(outs[0].path), - ], + ] + [file.path for file in ctx.files.srcs], tools = [ ctx.executable.protoc, ctx.executable.mypy_protobuf, @@ -26,10 +26,10 @@ def _py_proto_library_impl(ctx): DefaultInfo(files = depset(direct = outs), data_runfiles = ctx.runfiles(files = outs)), ] -py_proto_library_typed = rule( - implementation = _py_proto_library_impl, +py_proto = rule( + implementation = _py_proto_impl, attrs = { - "src": attr.label(allow_single_file = [".proto"]), + "srcs": attr.label_list(allow_files = [".proto"]), "protoc_wrapper": attr.label( executable = True, cfg = "exec", diff --git a/pylib/tests/shared.py b/pylib/tests/shared.py index b64599329..ce6fe9d8f 100644 --- a/pylib/tests/shared.py +++ b/pylib/tests/shared.py @@ -6,7 +6,7 @@ import shutil import tempfile import time -from anki import Collection as aopen +from anki.collection import Collection as aopen # Between 2-4AM, shift the time back so test assumptions hold. lt = time.localtime() diff --git a/pylib/tests/test_collection.py b/pylib/tests/test_collection.py index a2200c734..6588bc592 100644 --- a/pylib/tests/test_collection.py +++ b/pylib/tests/test_collection.py @@ -6,7 +6,7 @@ import os import tempfile -from anki import Collection as aopen +from anki.collection import Collection as aopen from anki.dbproxy import emulate_named_args from anki.lang import TR, without_unicode_isolation from anki.stdmodels import addBasicModel, get_stock_notetypes diff --git a/pylib/tests/test_exporting.py b/pylib/tests/test_exporting.py index 30a3acfd9..ef3309fae 100644 --- a/pylib/tests/test_exporting.py +++ b/pylib/tests/test_exporting.py @@ -6,7 +6,7 @@ import os import tempfile -from anki import Collection as aopen +from anki.collection import Collection as aopen from anki.exporting import * from anki.importing import Anki2Importer from tests.shared import errorsAfterMidnight diff --git a/pylib/tests/test_schedv1.py b/pylib/tests/test_schedv1.py index ef5073fb2..056d49c69 100644 --- a/pylib/tests/test_schedv1.py +++ b/pylib/tests/test_schedv1.py @@ -4,7 +4,7 @@ import copy import time -from anki import Collection +from anki.collection import Collection from anki.consts import * from anki.lang import without_unicode_isolation from anki.utils import intTime diff --git a/pylib/tools/protoc_wrapper.py b/pylib/tools/protoc_wrapper.py index a7a86fcfd..f10af3464 100644 --- a/pylib/tools/protoc_wrapper.py +++ b/pylib/tools/protoc_wrapper.py @@ -10,16 +10,12 @@ import shutil import subprocess import sys -(protoc, mypy_protobuf, proto, outdir) = sys.argv[1:] +(protoc, mypy_protobuf, outdir, *protos) = sys.argv[1:] -# copy to current dir -basename = os.path.basename(proto) -shutil.copyfile(proto, basename) - -# output filenames -without_ext = os.path.splitext(basename)[0] -pb2_py = without_ext + "_pb2.py" -pb2_pyi = without_ext + "_pb2.pyi" +if protos[0].startswith("external"): + prefix = "external/ankidesktop/proto/" +else: + prefix = "proto/" # invoke protoc subprocess.run( @@ -28,13 +24,17 @@ subprocess.run( "--plugin=protoc-gen-mypy=" + mypy_protobuf, "--python_out=.", "--mypy_out=.", - basename, + "-I" + prefix, + "-Iexternal/ankidesktop/" + prefix, + *protos, ], # mypy prints to stderr on success :-( stderr=subprocess.DEVNULL, check=True, ) -# move files into output -shutil.move(pb2_py, outdir + "/" + pb2_py) -shutil.move(pb2_pyi, outdir + "/" + pb2_pyi) +for proto in protos: + without_prefix_and_ext, _ = os.path.splitext(proto[len(prefix) :]) + for ext in "_pb2.py", "_pb2.pyi": + path = without_prefix_and_ext + ext + shutil.move(path, os.path.join(outdir, os.path.basename(path))) diff --git a/qt/.pylintrc b/qt/.pylintrc index 545e7e07f..ea7a8d48d 100644 --- a/qt/.pylintrc +++ b/qt/.pylintrc @@ -9,7 +9,7 @@ ignored-classes= BrowserColumns, BrowserRow, SearchNode, - Config, + ConfigKey, OpChanges, UnburyDeckRequest, CardAnswer, diff --git a/qt/aqt/__init__.py b/qt/aqt/__init__.py index 54b12ba93..e617eaa09 100644 --- a/qt/aqt/__init__.py +++ b/qt/aqt/__init__.py @@ -13,13 +13,27 @@ import traceback from typing import Any, Callable, Dict, List, Optional, Tuple, Union, cast import anki.lang -from anki import version as _version from anki._backend import RustBackend +from anki.buildinfo import version as _version +from anki.collection import Collection from anki.consts import HELP_SITE from anki.utils import checksum, isLin, isMac from aqt.qt import * from aqt.utils import TR, locale_dir, tr +if sys.version_info[0] < 3 or sys.version_info[1] < 7: + raise Exception("Anki requires Python 3.7+") + +# ensure unicode filenames are supported +try: + "ใƒ†ใ‚นใƒˆ".encode(sys.getfilesystemencoding()) +except UnicodeEncodeError as exc: + raise Exception("Anki requires a UTF-8 locale.") from exc + +# compat aliases +anki.version = _version # type: ignore +anki.Collection = Collection # type: ignore + # we want to be able to print unicode debug info to console without # fear of a traceback on systems with the console set to ASCII try: diff --git a/qt/aqt/browser/sidebar/tree.py b/qt/aqt/browser/sidebar/tree.py index 157538633..be0544a0a 100644 --- a/qt/aqt/browser/sidebar/tree.py +++ b/qt/aqt/browser/sidebar/tree.py @@ -502,7 +502,7 @@ class SidebarTreeView(QTreeView): root: SidebarItem, name: str, icon: Union[str, ColoredIcon], - collapse_key: Config.Bool.Key.V, + collapse_key: Config.Bool.V, type: Optional[SidebarItemType] = None, ) -> SidebarItem: def update(expanded: bool) -> None: diff --git a/qt/aqt/data/web/js/vendor/BUILD.bazel b/qt/aqt/data/web/js/vendor/BUILD.bazel index fbdae540a..19e6e8c21 100644 --- a/qt/aqt/data/web/js/vendor/BUILD.bazel +++ b/qt/aqt/data/web/js/vendor/BUILD.bazel @@ -1,18 +1,15 @@ load( "//ts:vendor.bzl", + "copy_bootstrap_js", "copy_css_browser_selector", "copy_jquery", "copy_jquery_ui", - "copy_protobufjs", - "copy_bootstrap_js", ) copy_jquery(name = "jquery") copy_jquery_ui(name = "jquery-ui") -copy_protobufjs(name = "protobufjs") - copy_css_browser_selector(name = "css-browser-selector") copy_bootstrap_js(name = "bootstrap") @@ -20,7 +17,6 @@ copy_bootstrap_js(name = "bootstrap") files = [ "jquery", "jquery-ui", - "protobufjs", "css-browser-selector", "bootstrap", ] diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index 174fa18b7..21764547d 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -137,7 +137,6 @@ class Editor: ], js=[ "js/vendor/jquery.min.js", - "js/vendor/protobuf.min.js", "js/editor.js", ], context=self, diff --git a/qt/aqt/models.py b/qt/aqt/models.py index 8e0beb677..b65be9d34 100644 --- a/qt/aqt/models.py +++ b/qt/aqt/models.py @@ -6,7 +6,8 @@ from operator import itemgetter from typing import Any, List, Optional, Sequence import aqt.clayout -from anki import Collection, stdmodels +from anki import stdmodels +from anki.collection import Collection from anki.lang import without_unicode_isolation from anki.models import NotetypeDict, NotetypeId, NotetypeNameIdUseCount from anki.notes import Note diff --git a/qt/aqt/operations/scheduling.py b/qt/aqt/operations/scheduling.py index 087b1475d..3e8eb6c89 100644 --- a/qt/aqt/operations/scheduling.py +++ b/qt/aqt/operations/scheduling.py @@ -29,7 +29,7 @@ def set_due_date_dialog( *, parent: QWidget, card_ids: Sequence[CardId], - config_key: Optional[Config.String.Key.V], + config_key: Optional[Config.String.V], ) -> Optional[CollectionOp[OpChanges]]: assert aqt.mw if not card_ids: diff --git a/qt/aqt/profiles.py b/qt/aqt/profiles.py index 8e47ca2b1..2fce8ec77 100644 --- a/qt/aqt/profiles.py +++ b/qt/aqt/profiles.py @@ -17,7 +17,7 @@ from send2trash import send2trash import anki.lang import aqt.forms import aqt.sound -from anki import Collection +from anki.collection import Collection from anki.db import DB from anki.lang import without_unicode_isolation from anki.sync import SyncAuth diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index d2804a9de..fa540c55b 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -293,7 +293,6 @@ class Reviewer: "js/mathjax.js", "js/vendor/mathjax/tex-chtml.js", "js/reviewer.js", - "js/vendor/protobuf.min.js", "js/reviewer_extras.js", ], context=self, diff --git a/qt/aqt/utils.py b/qt/aqt/utils.py index 0b674dbd2..e44958c25 100644 --- a/qt/aqt/utils.py +++ b/qt/aqt/utils.py @@ -34,8 +34,8 @@ from PyQt5.QtWidgets import ( ) import aqt -from anki import Collection from anki._legacy import deprecated +from anki.collection import Collection from anki.lang import TR, tr_legacyglobal # pylint: disable=unused-import from anki.utils import invalidFilename, isMac, isWin, noBundledLibs, versionWithBuild from aqt.qt import * diff --git a/qt/dmypy.py b/qt/dmypy.py index 5e0b277a5..968061146 100755 --- a/qt/dmypy.py +++ b/qt/dmypy.py @@ -30,8 +30,8 @@ if subprocess.run( "--", "--config-file", "qt/mypy.ini", - "bazel-bin/qt/dmypy.runfiles/net_ankiweb_anki/pylib/anki", - "bazel-bin/qt/dmypy.runfiles/net_ankiweb_anki/qt/aqt", + "bazel-bin/qt/dmypy.runfiles/ankidesktop/pylib/anki", + "bazel-bin/qt/dmypy.runfiles/ankidesktop/qt/aqt", "--python-executable", os.path.abspath("pip/stubs/extendsitepkgs"), ], diff --git a/rslib/BUILD.bazel b/rslib/BUILD.bazel index 988f9f836..27db1eaf8 100644 --- a/rslib/BUILD.bazel +++ b/rslib/BUILD.bazel @@ -1,11 +1,9 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -load("@rules_proto//proto:defs.bzl", "proto_library") load("@rules_rust//rust:rust.bzl", "rust_binary", "rust_library", "rust_test") load("@rules_rust//cargo:cargo_build_script.bzl", "cargo_build_script") load(":rustfmt.bzl", "rustfmt_fix", "rustfmt_test") -load(":clang_format.bzl", "proto_format") load("//ts:sql_format.bzl", "sql_format") # Build script @@ -15,7 +13,7 @@ cargo_build_script( name = "build_script", srcs = glob(["build/*.rs"]), build_script_env = { - "BACKEND_PROTO": "$(location backend.proto)", + "PROTO_TOP": "$(location //proto:.top_level)", "PROTOC": "$(location @com_google_protobuf//:protoc)", "RSLIB_FTL_ROOT": "$(location @rslib_ftl//:l10n.toml)", "EXTRA_FTL_ROOT": "$(location @extra_ftl//:l10n.toml)", @@ -24,9 +22,10 @@ cargo_build_script( crate_root = "build/main.rs", data = [ "//ftl", - "backend.proto", + "//proto", "@com_google_protobuf//:protoc", # bazel requires us to list these out separately + "//proto:.top_level", "@rslib_ftl//:l10n.toml", "@extra_ftl//:l10n.toml", ], @@ -163,19 +162,3 @@ sql_format( name = "sql_format", srcs = glob(["**/*.sql"]), ) - -proto_format( - name = "proto_format", - srcs = ["backend.proto"], -) - -# backend.proto -####################### - -proto_library( - name = "backend_proto_lib", - srcs = ["backend.proto"], - visibility = ["//visibility:public"], -) - -exports_files(["backend.proto"]) diff --git a/rslib/backend.proto b/rslib/backend.proto deleted file mode 100644 index 978c9d353..000000000 --- a/rslib/backend.proto +++ /dev/null @@ -1,1681 +0,0 @@ -// Copyright: Ankitects Pty Ltd and contributors -// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -syntax = "proto3"; - -package BackendProto; - -// Generic containers -/////////////////////////////////////////////////////////// - -message Empty {} - -message OptionalInt32 { - sint32 val = 1; -} - -message OptionalUInt32 { - uint32 val = 1; -} - -message Int32 { - sint32 val = 1; -} - -message UInt32 { - uint32 val = 1; -} - -message Int64 { - int64 val = 1; -} - -message String { - string val = 1; -} - -message Json { - bytes json = 1; -} - -message Bool { - bool val = 1; -} - -message StringList { - repeated string vals = 1; -} - -message OpChangesWithCount { - uint32 count = 1; - OpChanges changes = 2; -} - -message OpChangesWithId { - int64 id = 1; - OpChanges changes = 2; -} - -// IDs used in RPC calls -/////////////////////////////////////////////////////////// - -message NotetypeId { - int64 ntid = 1; -} - -message NoteId { - int64 nid = 1; -} - -message NoteIds { - repeated int64 note_ids = 1; -} - -message CardId { - int64 cid = 1; -} - -message CardIds { - repeated int64 cids = 1; -} - -message DeckId { - int64 did = 1; -} - -message DeckIds { - repeated int64 dids = 1; -} - -message DeckConfigId { - int64 dcid = 1; -} - -// Backend methods -/////////////////////////////////////////////////////////// - -/// while the protobuf descriptors expose the order services are defined in, -/// that information is not available in prost, so we define an enum to make -/// sure all clients agree on the service index -enum ServiceIndex { - SERVICE_INDEX_SCHEDULING = 0; - SERVICE_INDEX_DECKS = 1; - SERVICE_INDEX_NOTES = 2; - SERVICE_INDEX_SYNC = 3; - SERVICE_INDEX_NOTETYPES = 4; - SERVICE_INDEX_CONFIG = 5; - SERVICE_INDEX_CARD_RENDERING = 6; - SERVICE_INDEX_DECK_CONFIG = 7; - SERVICE_INDEX_TAGS = 8; - SERVICE_INDEX_SEARCH = 9; - SERVICE_INDEX_STATS = 10; - SERVICE_INDEX_MEDIA = 11; - SERVICE_INDEX_I18N = 12; - SERVICE_INDEX_COLLECTION = 13; - SERVICE_INDEX_CARDS = 14; -} - -service SchedulingService { - rpc SchedTimingToday(Empty) returns (SchedTimingTodayResponse); - rpc StudiedToday(Empty) returns (String); - rpc StudiedTodayMessage(StudiedTodayMessageRequest) returns (String); - rpc UpdateStats(UpdateStatsRequest) returns (Empty); - rpc ExtendLimits(ExtendLimitsRequest) returns (Empty); - rpc CountsForDeckToday(DeckId) returns (CountsForDeckTodayResponse); - rpc CongratsInfo(Empty) returns (CongratsInfoResponse); - rpc RestoreBuriedAndSuspendedCards(CardIds) returns (OpChanges); - rpc UnburyDeck(UnburyDeckRequest) returns (OpChanges); - rpc BuryOrSuspendCards(BuryOrSuspendCardsRequest) - returns (OpChangesWithCount); - rpc EmptyFilteredDeck(DeckId) returns (OpChanges); - rpc RebuildFilteredDeck(DeckId) returns (OpChangesWithCount); - rpc ScheduleCardsAsNew(ScheduleCardsAsNewRequest) returns (OpChanges); - rpc SetDueDate(SetDueDateRequest) returns (OpChanges); - rpc SortCards(SortCardsRequest) returns (OpChangesWithCount); - rpc SortDeck(SortDeckRequest) returns (OpChangesWithCount); - rpc GetNextCardStates(CardId) returns (NextCardStates); - rpc DescribeNextStates(NextCardStates) returns (StringList); - rpc StateIsLeech(SchedulingState) returns (Bool); - rpc AnswerCard(CardAnswer) returns (OpChanges); - rpc UpgradeScheduler(Empty) returns (Empty); - rpc GetQueuedCards(GetQueuedCardsRequest) returns (QueuedCards); -} - -service DecksService { - rpc AddDeckLegacy(Json) returns (OpChangesWithId); - rpc AddOrUpdateDeckLegacy(AddOrUpdateDeckLegacyRequest) returns (DeckId); - rpc DeckTree(DeckTreeRequest) returns (DeckTreeNode); - rpc DeckTreeLegacy(Empty) returns (Json); - rpc GetAllDecksLegacy(Empty) returns (Json); - rpc GetDeckIdByName(String) returns (DeckId); - rpc GetDeck(DeckId) returns (Deck); - rpc UpdateDeck(Deck) returns (OpChanges); - rpc UpdateDeckLegacy(Json) returns (OpChanges); - rpc SetDeckCollapsed(SetDeckCollapsedRequest) returns (OpChanges); - rpc GetDeckLegacy(DeckId) returns (Json); - rpc GetDeckNames(GetDeckNamesRequest) returns (DeckNames); - rpc NewDeckLegacy(Bool) returns (Json); - rpc RemoveDecks(DeckIds) returns (OpChangesWithCount); - rpc ReparentDecks(ReparentDecksRequest) returns (OpChangesWithCount); - rpc RenameDeck(RenameDeckRequest) returns (OpChanges); - rpc GetOrCreateFilteredDeck(DeckId) returns (FilteredDeckForUpdate); - rpc AddOrUpdateFilteredDeck(FilteredDeckForUpdate) returns (OpChangesWithId); - rpc FilteredDeckOrderLabels(Empty) returns (StringList); - rpc SetCurrentDeck(DeckId) returns (OpChanges); - rpc GetCurrentDeck(Empty) returns (Deck); -} - -service NotesService { - rpc NewNote(NotetypeId) returns (Note); - rpc AddNote(AddNoteRequest) returns (AddNoteResponse); - rpc DefaultsForAdding(DefaultsForAddingRequest) returns (DeckAndNotetype); - rpc DefaultDeckForNotetype(NotetypeId) returns (DeckId); - rpc UpdateNote(UpdateNoteRequest) returns (OpChanges); - rpc GetNote(NoteId) returns (Note); - rpc RemoveNotes(RemoveNotesRequest) returns (OpChangesWithCount); - rpc ClozeNumbersInNote(Note) returns (ClozeNumbersInNoteResponse); - rpc AfterNoteUpdates(AfterNoteUpdatesRequest) returns (OpChangesWithCount); - rpc FieldNamesForNotes(FieldNamesForNotesRequest) - returns (FieldNamesForNotesResponse); - rpc NoteFieldsCheck(Note) returns (NoteFieldsCheckResponse); - rpc CardsOfNote(NoteId) returns (CardIds); -} - -service SyncService { - rpc SyncMedia(SyncAuth) returns (Empty); - rpc AbortSync(Empty) returns (Empty); - rpc AbortMediaSync(Empty) returns (Empty); - rpc BeforeUpload(Empty) returns (Empty); - rpc SyncLogin(SyncLoginRequest) returns (SyncAuth); - rpc SyncStatus(SyncAuth) returns (SyncStatusResponse); - rpc SyncCollection(SyncAuth) returns (SyncCollectionResponse); - rpc FullUpload(SyncAuth) returns (Empty); - rpc FullDownload(SyncAuth) returns (Empty); - rpc SyncServerMethod(SyncServerMethodRequest) returns (Json); -} - -service ConfigService { - rpc GetConfigJson(String) returns (Json); - rpc SetConfigJson(SetConfigJsonRequest) returns (OpChanges); - rpc SetConfigJsonNoUndo(SetConfigJsonRequest) returns (Empty); - rpc RemoveConfig(String) returns (OpChanges); - rpc GetAllConfig(Empty) returns (Json); - rpc GetConfigBool(Config.Bool) returns (Bool); - rpc SetConfigBool(SetConfigBoolRequest) returns (OpChanges); - rpc GetConfigString(Config.String) returns (String); - rpc SetConfigString(SetConfigStringRequest) returns (OpChanges); - rpc GetPreferences(Empty) returns (Preferences); - rpc SetPreferences(Preferences) returns (OpChanges); -} - -service NotetypesService { - rpc AddNotetype(Notetype) returns (OpChangesWithId); - rpc UpdateNotetype(Notetype) returns (OpChanges); - rpc AddNotetypeLegacy(Json) returns (OpChangesWithId); - rpc UpdateNotetypeLegacy(Json) returns (OpChanges); - rpc AddOrUpdateNotetype(AddOrUpdateNotetypeRequest) returns (NotetypeId); - rpc GetStockNotetypeLegacy(StockNotetype) returns (Json); - rpc GetNotetype(NotetypeId) returns (Notetype); - rpc GetNotetypeLegacy(NotetypeId) returns (Json); - rpc GetNotetypeNames(Empty) returns (NotetypeNames); - rpc GetNotetypeNamesAndCounts(Empty) returns (NotetypeUseCounts); - rpc GetNotetypeIdByName(String) returns (NotetypeId); - rpc RemoveNotetype(NotetypeId) returns (OpChanges); - rpc GetAuxNotetypeConfigKey(GetAuxConfigKeyRequest) returns (String); - rpc GetAuxTemplateConfigKey(GetAuxTemplateConfigKeyRequest) returns (String); - rpc GetSingleNotetypeOfNotes(NoteIds) returns (NotetypeId); - rpc GetChangeNotetypeInfo(GetChangeNotetypeInfoRequest) - returns (ChangeNotetypeInfo); - rpc ChangeNotetype(ChangeNotetypeRequest) returns (OpChanges); -} - -service CardRenderingService { - rpc ExtractAVTags(ExtractAVTagsRequest) returns (ExtractAVTagsResponse); - rpc ExtractLatex(ExtractLatexRequest) returns (ExtractLatexResponse); - rpc GetEmptyCards(Empty) returns (EmptyCardsReport); - rpc RenderExistingCard(RenderExistingCardRequest) - returns (RenderCardResponse); - rpc RenderUncommittedCard(RenderUncommittedCardRequest) - returns (RenderCardResponse); - rpc RenderUncommittedCardLegacy(RenderUncommittedCardLegacyRequest) - returns (RenderCardResponse); - rpc StripAVTags(String) returns (String); - rpc RenderMarkdown(RenderMarkdownRequest) returns (String); -} - -service DeckConfigService { - rpc AddOrUpdateDeckConfigLegacy(Json) returns (DeckConfigId); - rpc GetDeckConfig(DeckConfigId) returns (DeckConfig); - rpc AllDeckConfigLegacy(Empty) returns (Json); - rpc GetDeckConfigLegacy(DeckConfigId) returns (Json); - rpc NewDeckConfigLegacy(Empty) returns (Json); - rpc RemoveDeckConfig(DeckConfigId) returns (Empty); - rpc GetDeckConfigsForUpdate(DeckId) returns (DeckConfigsForUpdate); - rpc UpdateDeckConfigs(UpdateDeckConfigsRequest) returns (OpChanges); -} - -service TagsService { - rpc ClearUnusedTags(Empty) returns (OpChangesWithCount); - rpc AllTags(Empty) returns (StringList); - rpc RemoveTags(String) returns (OpChangesWithCount); - rpc SetTagCollapsed(SetTagCollapsedRequest) returns (OpChanges); - rpc TagTree(Empty) returns (TagTreeNode); - rpc ReparentTags(ReparentTagsRequest) returns (OpChangesWithCount); - rpc RenameTags(RenameTagsRequest) returns (OpChangesWithCount); - rpc AddNoteTags(NoteIdsAndTagsRequest) returns (OpChangesWithCount); - rpc RemoveNoteTags(NoteIdsAndTagsRequest) returns (OpChangesWithCount); - rpc FindAndReplaceTag(FindAndReplaceTagRequest) returns (OpChangesWithCount); -} - -service SearchService { - rpc BuildSearchString(SearchNode) returns (String); - rpc SearchCards(SearchRequest) returns (SearchResponse); - rpc SearchNotes(SearchRequest) returns (SearchResponse); - rpc JoinSearchNodes(JoinSearchNodesRequest) returns (String); - rpc ReplaceSearchNode(ReplaceSearchNodeRequest) returns (String); - rpc FindAndReplace(FindAndReplaceRequest) returns (OpChangesWithCount); - rpc AllBrowserColumns(Empty) returns (BrowserColumns); - rpc BrowserRowForId(Int64) returns (BrowserRow); - rpc SetActiveBrowserColumns(StringList) returns (Empty); -} - -service StatsService { - rpc CardStats(CardId) returns (String); - rpc Graphs(GraphsRequest) returns (GraphsResponse); - rpc GetGraphPreferences(Empty) returns (GraphPreferences); - rpc SetGraphPreferences(GraphPreferences) returns (Empty); -} - -service MediaService { - rpc CheckMedia(Empty) returns (CheckMediaResponse); - rpc TrashMediaFiles(TrashMediaFilesRequest) returns (Empty); - rpc AddMediaFile(AddMediaFileRequest) returns (String); - rpc EmptyTrash(Empty) returns (Empty); - rpc RestoreTrash(Empty) returns (Empty); -} - -service I18nService { - rpc TranslateString(TranslateStringRequest) returns (String); - rpc FormatTimespan(FormatTimespanRequest) returns (String); - rpc I18nResources(I18nResourcesRequest) returns (Json); -} - -service CollectionService { - rpc OpenCollection(OpenCollectionRequest) returns (Empty); - rpc CloseCollection(CloseCollectionRequest) returns (Empty); - rpc CheckDatabase(Empty) returns (CheckDatabaseResponse); - rpc GetUndoStatus(Empty) returns (UndoStatus); - rpc Undo(Empty) returns (OpChangesAfterUndo); - rpc Redo(Empty) returns (OpChangesAfterUndo); - rpc AddCustomUndoEntry(String) returns (UInt32); - rpc MergeUndoEntries(UInt32) returns (OpChanges); - rpc LatestProgress(Empty) returns (Progress); - rpc SetWantsAbort(Empty) returns (Empty); -} - -service CardsService { - rpc GetCard(CardId) returns (Card); - rpc UpdateCard(UpdateCardRequest) returns (OpChanges); - rpc RemoveCards(RemoveCardsRequest) returns (Empty); - rpc SetDeck(SetDeckRequest) returns (OpChangesWithCount); - rpc SetFlag(SetFlagRequest) returns (OpChangesWithCount); -} - -// Protobuf stored in .anki2 files -// These should be moved to a separate file in the future -/////////////////////////////////////////////////////////// - -message DeckConfig { - message Config { - enum NewCardInsertOrder { - NEW_CARD_INSERT_ORDER_DUE = 0; - NEW_CARD_INSERT_ORDER_RANDOM = 1; - } - enum NewCardGatherPriority { - NEW_CARD_GATHER_PRIORITY_DECK = 0; - NEW_CARD_GATHER_PRIORITY_LOWEST_POSITION = 1; - NEW_CARD_GATHER_PRIORITY_HIGHEST_POSITION = 2; - } - enum NewCardSortOrder { - NEW_CARD_SORT_ORDER_TEMPLATE_THEN_LOWEST_POSITION = 0; - NEW_CARD_SORT_ORDER_TEMPLATE_THEN_HIGHEST_POSITION = 1; - NEW_CARD_SORT_ORDER_TEMPLATE_THEN_RANDOM = 2; - NEW_CARD_SORT_ORDER_LOWEST_POSITION = 3; - NEW_CARD_SORT_ORDER_HIGHEST_POSITION = 4; - NEW_CARD_SORT_ORDER_RANDOM = 5; - } - enum ReviewCardOrder { - REVIEW_CARD_ORDER_DAY = 0; - REVIEW_CARD_ORDER_DAY_THEN_DECK = 1; - REVIEW_CARD_ORDER_DECK_THEN_DAY = 2; - REVIEW_CARD_ORDER_INTERVALS_ASCENDING = 3; - REVIEW_CARD_ORDER_INTERVALS_DESCENDING = 4; - // REVIEW_CARD_ORDER_RELATIVE_OVERDUE = 3; - } - enum ReviewMix { - REVIEW_MIX_MIX_WITH_REVIEWS = 0; - REVIEW_MIX_AFTER_REVIEWS = 1; - REVIEW_MIX_BEFORE_REVIEWS = 2; - } - enum LeechAction { - LEECH_ACTION_SUSPEND = 0; - LEECH_ACTION_TAG_ONLY = 1; - } - - repeated float learn_steps = 1; - repeated float relearn_steps = 2; - - reserved 3 to 8; - - uint32 new_per_day = 9; - uint32 reviews_per_day = 10; - uint32 new_per_day_minimum = 29; - - float initial_ease = 11; - float easy_multiplier = 12; - float hard_multiplier = 13; - float lapse_multiplier = 14; - float interval_multiplier = 15; - - uint32 maximum_review_interval = 16; - uint32 minimum_lapse_interval = 17; - - uint32 graduating_interval_good = 18; - uint32 graduating_interval_easy = 19; - - NewCardInsertOrder new_card_insert_order = 20; - NewCardGatherPriority new_card_gather_priority = 34; - NewCardSortOrder new_card_sort_order = 32; - ReviewMix new_mix = 30; - - ReviewCardOrder review_order = 33; - - ReviewMix interday_learning_mix = 31; - - LeechAction leech_action = 21; - uint32 leech_threshold = 22; - - bool disable_autoplay = 23; - uint32 cap_answer_time_to_secs = 24; - bool show_timer = 25; - bool skip_question_when_replaying_answer = 26; - - bool bury_new = 27; - bool bury_reviews = 28; - - bytes other = 255; - } - - int64 id = 1; - string name = 2; - int64 mtime_secs = 3; - int32 usn = 4; - Config config = 5; -} - -message Deck { - message Common { - bool study_collapsed = 1; - bool browser_collapsed = 2; - - uint32 last_day_studied = 3; - int32 new_studied = 4; - int32 review_studied = 5; - int32 milliseconds_studied = 7; - - // previously set in the v1 scheduler, - // but not currently used for anything - int32 learning_studied = 6; - - reserved 8 to 13; - - bytes other = 255; - } - message Normal { - int64 config_id = 1; - uint32 extend_new = 2; - uint32 extend_review = 3; - string description = 4; - bool markdown_description = 5; - - reserved 6 to 11; - } - message Filtered { - message SearchTerm { - enum Order { - OLDEST_REVIEWED_FIRST = 0; - RANDOM = 1; - INTERVALS_ASCENDING = 2; - INTERVALS_DESCENDING = 3; - LAPSES = 4; - ADDED = 5; - DUE = 6; - REVERSE_ADDED = 7; - DUE_PRIORITY = 8; - } - - string search = 1; - uint32 limit = 2; - Order order = 3; - } - - bool reschedule = 1; - repeated SearchTerm search_terms = 2; - // v1 scheduler only - repeated float delays = 3; - // v2 scheduler only - uint32 preview_delay = 4; - } - // a container to store the deck specifics in the DB - // as a tagged enum - message KindContainer { - oneof kind { - Normal normal = 1; - Filtered filtered = 2; - } - } - - int64 id = 1; - string name = 2; - int64 mtime_secs = 3; - int32 usn = 4; - Common common = 5; - // the specifics are inlined here when sending data to clients, - // as otherwise an extra level of indirection would be required - oneof kind { - Normal normal = 6; - Filtered filtered = 7; - } -} - -message Notetype { - message Config { - enum Kind { - KIND_NORMAL = 0; - KIND_CLOZE = 1; - } - message CardRequirement { - enum Kind { - KIND_NONE = 0; - KIND_ANY = 1; - KIND_ALL = 2; - } - uint32 card_ord = 1; - Kind kind = 2; - repeated uint32 field_ords = 3; - } - - Kind kind = 1; - uint32 sort_field_idx = 2; - string css = 3; - /// This is now stored separately; retrieve with DefaultsForAdding() - int64 target_deck_id_unused = 4; - string latex_pre = 5; - string latex_post = 6; - bool latex_svg = 7; - repeated CardRequirement reqs = 8; - - bytes other = 255; - } - message Field { - message Config { - bool sticky = 1; - bool rtl = 2; - string font_name = 3; - uint32 font_size = 4; - - bytes other = 255; - } - OptionalUInt32 ord = 1; - string name = 2; - Config config = 5; - } - message Template { - message Config { - string q_format = 1; - string a_format = 2; - string q_format_browser = 3; - string a_format_browser = 4; - int64 target_deck_id = 5; - string browser_font_name = 6; - uint32 browser_font_size = 7; - - bytes other = 255; - } - - OptionalUInt32 ord = 1; - string name = 2; - int64 mtime_secs = 3; - sint32 usn = 4; - Config config = 5; - } - - int64 id = 1; - string name = 2; - int64 mtime_secs = 3; - sint32 usn = 4; - Config config = 7; - repeated Field fields = 8; - repeated Template templates = 9; -} - -// Database objects -/////////////////////////////////////////////////////////// - -message Note { - int64 id = 1; - string guid = 2; - int64 notetype_id = 3; - uint32 mtime_secs = 4; - int32 usn = 5; - repeated string tags = 6; - repeated string fields = 7; -} - -message Card { - int64 id = 1; - int64 note_id = 2; - int64 deck_id = 3; - uint32 template_idx = 4; - int64 mtime_secs = 5; - sint32 usn = 6; - uint32 ctype = 7; - sint32 queue = 8; - sint32 due = 9; - uint32 interval = 10; - uint32 ease_factor = 11; - uint32 reps = 12; - uint32 lapses = 13; - uint32 remaining_steps = 14; - sint32 original_due = 15; - int64 original_deck_id = 16; - uint32 flags = 17; - string data = 18; -} - -// Backend -/////////////////////////////////////////////////////////// - -message BackendInit { - repeated string preferred_langs = 1; - string locale_folder_path = 2; - bool server = 3; -} - -message I18nBackendInit { - repeated string preferred_langs = 4; - string locale_folder_path = 5; -} - -// Errors -/////////////////////////////////////////////////////////// - -message BackendError { - enum Kind { - INVALID_INPUT = 0; - UNDO_EMPTY = 1; - INTERRUPTED = 2; - TEMPLATE_PARSE = 3; - IO_ERROR = 4; - DB_ERROR = 5; - NETWORK_ERROR = 6; - SYNC_AUTH_ERROR = 7; - SYNC_OTHER_ERROR = 8; - JSON_ERROR = 9; - PROTO_ERROR = 10; - NOT_FOUND_ERROR = 11; - EXISTS = 12; - FILTERED_DECK_ERROR = 13; - SEARCH_ERROR = 14; - } - - // localized error description suitable for displaying to the user - string localized = 1; - // the error subtype - Kind kind = 2; -} - -// Progress -/////////////////////////////////////////////////////////// - -message Progress { - message MediaSync { - string checked = 1; - string added = 2; - string removed = 3; - } - - message FullSync { - uint32 transferred = 1; - uint32 total = 2; - } - - message NormalSync { - string stage = 1; - string added = 2; - string removed = 3; - } - - message DatabaseCheck { - string stage = 1; - uint32 stage_total = 2; - uint32 stage_current = 3; - } - oneof value { - Empty none = 1; - MediaSync media_sync = 2; - string media_check = 3; - FullSync full_sync = 4; - NormalSync normal_sync = 5; - DatabaseCheck database_check = 6; - } -} - -// Messages -/////////////////////////////////////////////////////////// - -message SchedTimingTodayResponse { - uint32 days_elapsed = 1; - int64 next_day_at = 2; -} - -message DeckTreeRequest { - // if non-zero, counts for the provided timestamp will be included - int64 now = 1; - int64 top_deck_id = 2; -} - -message DeckTreeNode { - int64 deck_id = 1; - string name = 2; - uint32 level = 4; - bool collapsed = 5; - - uint32 review_count = 6; - uint32 learn_count = 7; - uint32 new_count = 8; - - bool filtered = 16; - - // low index so key can be packed into a byte, but at bottom - // to make debug output easier to read - repeated DeckTreeNode children = 3; -} - -message RenderExistingCardRequest { - int64 card_id = 1; - bool browser = 2; -} - -message RenderUncommittedCardRequest { - Note note = 1; - uint32 card_ord = 2; - Notetype.Template template = 3; - bool fill_empty = 4; -} - -message RenderUncommittedCardLegacyRequest { - Note note = 1; - uint32 card_ord = 2; - bytes template = 3; - bool fill_empty = 4; -} - -message RenderCardResponse { - repeated RenderedTemplateNode question_nodes = 1; - repeated RenderedTemplateNode answer_nodes = 2; - string css = 3; - bool latex_svg = 4; -} - -message RenderedTemplateNode { - oneof value { - string text = 1; - RenderedTemplateReplacement replacement = 2; - } -} - -message RenderedTemplateReplacement { - string field_name = 1; - string current_text = 2; - repeated string filters = 3; -} - -message ExtractAVTagsRequest { - string text = 1; - bool question_side = 2; -} - -message ExtractAVTagsResponse { - string text = 1; - repeated AVTag av_tags = 2; -} - -message AVTag { - oneof value { - string sound_or_video = 1; - TTSTag tts = 2; - } -} - -message TTSTag { - string field_text = 1; - string lang = 2; - repeated string voices = 3; - float speed = 4; - repeated string other_args = 5; -} - -message ExtractLatexRequest { - string text = 1; - bool svg = 2; - bool expand_clozes = 3; -} - -message ExtractLatexResponse { - string text = 1; - repeated ExtractedLatex latex = 2; -} - -message ExtractedLatex { - string filename = 1; - string latex_body = 2; -} - -message AddMediaFileRequest { - string desired_name = 1; - bytes data = 2; -} - -message CheckMediaResponse { - repeated string unused = 1; - repeated string missing = 2; - string report = 3; - bool have_trash = 4; -} - -message TrashMediaFilesRequest { - repeated string fnames = 1; -} - -message TranslateStringRequest { - uint32 module_index = 1; - uint32 message_index = 2; - map args = 3; -} - -message TranslateArgValue { - oneof value { - string str = 1; - double number = 2; - } -} - -message FormatTimespanRequest { - enum Context { - PRECISE = 0; - ANSWER_BUTTONS = 1; - INTERVALS = 2; - } - - float seconds = 1; - Context context = 2; -} - -message I18nResourcesRequest { - repeated string modules = 1; -} - -message StudiedTodayMessageRequest { - uint32 cards = 1; - double seconds = 2; -} - -message CongratsLearnMessageRequest { - float next_due = 1; - uint32 remaining = 2; -} - -message OpenCollectionRequest { - string collection_path = 1; - string media_folder_path = 2; - string media_db_path = 3; - string log_path = 4; -} - -message SearchRequest { - string search = 1; - SortOrder order = 2; -} - -message SearchResponse { - repeated int64 ids = 1; -} - -message SortOrder { - message Builtin { - string column = 1; - bool reverse = 2; - } - oneof value { - Empty none = 1; - string custom = 2; - Builtin builtin = 3; - } -} - -message SearchNode { - message Dupe { - int64 notetype_id = 1; - string first_field = 2; - } - enum Flag { - FLAG_NONE = 0; - FLAG_ANY = 1; - FLAG_RED = 2; - FLAG_ORANGE = 3; - FLAG_GREEN = 4; - FLAG_BLUE = 5; - FLAG_PINK = 6; - FLAG_TURQUOISE = 7; - FLAG_PURPLE = 8; - } - enum Rating { - RATING_ANY = 0; - RATING_AGAIN = 1; - RATING_HARD = 2; - RATING_GOOD = 3; - RATING_EASY = 4; - RATING_BY_RESCHEDULE = 5; - } - message Rated { - uint32 days = 1; - Rating rating = 2; - } - enum CardState { - CARD_STATE_NEW = 0; - CARD_STATE_LEARN = 1; - CARD_STATE_REVIEW = 2; - CARD_STATE_DUE = 3; - CARD_STATE_SUSPENDED = 4; - CARD_STATE_BURIED = 5; - } - message IdList { - repeated int64 ids = 1; - } - message Group { - enum Joiner { - AND = 0; - OR = 1; - } - repeated SearchNode nodes = 1; - Joiner joiner = 2; - } - oneof filter { - Group group = 1; - SearchNode negated = 2; - string parsable_text = 3; - uint32 template = 4; - int64 nid = 5; - Dupe dupe = 6; - string field_name = 7; - Rated rated = 8; - uint32 added_in_days = 9; - int32 due_in_days = 10; - Flag flag = 11; - CardState card_state = 12; - IdList nids = 13; - uint32 edited_in_days = 14; - string deck = 15; - int32 due_on_day = 16; - string tag = 17; - string note = 18; - uint32 introduced_in_days = 19; - } -} - -message JoinSearchNodesRequest { - SearchNode.Group.Joiner joiner = 1; - SearchNode existing_node = 2; - SearchNode additional_node = 3; -} - -message ReplaceSearchNodeRequest { - SearchNode existing_node = 1; - SearchNode replacement_node = 2; -} - -message CloseCollectionRequest { - bool downgrade_to_schema11 = 1; -} - -message DeckConfigsForUpdate { - message ConfigWithExtra { - DeckConfig config = 1; - uint32 use_count = 2; - } - message CurrentDeck { - string name = 1; - int64 config_id = 2; - repeated int64 parent_config_ids = 3; - } - - repeated ConfigWithExtra all_config = 1; - CurrentDeck current_deck = 2; - DeckConfig defaults = 3; - bool schema_modified = 4; - bool v3_scheduler = 5; - bool have_addons = 6; - // only applies to v3 scheduler - string card_state_customizer = 7; -} - -message UpdateDeckConfigsRequest { - int64 target_deck_id = 1; - /// Unchanged, non-selected configs can be omitted. Deck will - /// be set to whichever entry comes last. - repeated DeckConfig configs = 2; - repeated int64 removed_config_ids = 3; - bool apply_to_children = 4; - string card_state_customizer = 5; -} - -message SetTagCollapsedRequest { - string name = 1; - bool collapsed = 2; -} - -message SetDeckCollapsedRequest { - enum Scope { - REVIEWER = 0; - BROWSER = 1; - } - - int64 deck_id = 1; - bool collapsed = 2; - Scope scope = 3; -} - -message GetChangedTagsResponse { - repeated string tags = 1; -} - -message TagTreeNode { - string name = 1; - repeated TagTreeNode children = 2; - uint32 level = 3; - bool collapsed = 4; -} - -message ReparentTagsRequest { - repeated string tags = 1; - string new_parent = 2; -} - -message RenameTagsRequest { - string current_prefix = 1; - string new_prefix = 2; -} - -message SetConfigJsonRequest { - string key = 1; - bytes value_json = 2; - bool undoable = 3; -} - -message StockNotetype { - enum Kind { - BASIC = 0; - BASIC_AND_REVERSED = 1; - BASIC_OPTIONAL_REVERSED = 2; - BASIC_TYPING = 3; - CLOZE = 4; - } - - Kind kind = 1; -} - -message NotetypeNames { - repeated NotetypeNameId entries = 1; -} - -message NotetypeUseCounts { - repeated NotetypeNameIdUseCount entries = 1; -} - -message NotetypeNameId { - int64 id = 1; - string name = 2; -} - -message NotetypeNameIdUseCount { - int64 id = 1; - string name = 2; - uint32 use_count = 3; -} - -message AddOrUpdateNotetypeRequest { - bytes json = 1; - bool preserve_usn_and_mtime = 2; -} - -message AddNoteRequest { - Note note = 1; - int64 deck_id = 2; -} - -message AddNoteResponse { - int64 note_id = 1; - OpChanges changes = 2; -} - -message UpdateNoteRequest { - Note note = 1; - bool skip_undo_entry = 2; -} - -message UpdateCardRequest { - Card card = 1; - bool skip_undo_entry = 2; -} - -message EmptyCardsReport { - message NoteWithEmptyCards { - int64 note_id = 1; - repeated int64 card_ids = 2; - bool will_delete_note = 3; - } - string report = 1; - repeated NoteWithEmptyCards notes = 2; -} - -message DeckNames { - repeated DeckNameId entries = 1; -} - -message DeckNameId { - int64 id = 1; - string name = 2; -} - -message AddOrUpdateDeckLegacyRequest { - bytes deck = 1; - bool preserve_usn_and_mtime = 2; -} - -message FieldNamesForNotesRequest { - repeated int64 nids = 1; -} - -message FieldNamesForNotesResponse { - repeated string fields = 1; -} - -message FindAndReplaceRequest { - repeated int64 nids = 1; - string search = 2; - string replacement = 3; - bool regex = 4; - bool match_case = 5; - string field_name = 6; -} - -message BrowserColumns { - enum Sorting { - SORTING_NONE = 0; - SORTING_NORMAL = 1; - SORTING_REVERSED = 2; - } - enum Alignment { - ALIGNMENT_START = 0; - ALIGNMENT_CENTER = 1; - } - message Column { - string key = 1; - string cards_mode_label = 2; - string notes_mode_label = 3; - Sorting sorting = 4; - bool uses_cell_font = 5; - Alignment alignment = 6; - } - repeated Column columns = 1; -} - -message BrowserRow { - message Cell { - string text = 1; - bool is_rtl = 2; - } - enum Color { - COLOR_DEFAULT = 0; - COLOR_MARKED = 1; - COLOR_SUSPENDED = 2; - COLOR_FLAG_RED = 3; - COLOR_FLAG_ORANGE = 4; - COLOR_FLAG_GREEN = 5; - COLOR_FLAG_BLUE = 6; - COLOR_FLAG_PINK = 7; - COLOR_FLAG_TURQUOISE = 8; - COLOR_FLAG_PURPLE = 9; - } - repeated Cell cells = 1; - Color color = 2; - string font_name = 3; - uint32 font_size = 4; -} - -message AfterNoteUpdatesRequest { - repeated int64 nids = 1; - bool mark_notes_modified = 2; - bool generate_cards = 3; -} - -message NoteIdsAndTagsRequest { - repeated int64 note_ids = 1; - string tags = 2; -} - -message FindAndReplaceTagRequest { - repeated int64 note_ids = 1; - string search = 2; - string replacement = 3; - bool regex = 4; - bool match_case = 5; -} - -message CheckDatabaseResponse { - repeated string problems = 1; -} - -message Preferences { - message Scheduling { - enum NewReviewMix { - DISTRIBUTE = 0; - REVIEWS_FIRST = 1; - NEW_FIRST = 2; - } - - // read only; 1-3 - uint32 scheduler_version = 1; - - uint32 rollover = 2; - uint32 learn_ahead_secs = 3; - NewReviewMix new_review_mix = 4; - - // v2 only - bool new_timezone = 5; - bool day_learn_first = 6; - } - message Reviewing { - bool hide_audio_play_buttons = 1; - bool interrupt_audio_when_answering = 2; - bool show_remaining_due_counts = 3; - bool show_intervals_on_buttons = 4; - uint32 time_limit_secs = 5; - } - message Editing { - bool adding_defaults_to_current_deck = 1; - bool paste_images_as_png = 2; - bool paste_strips_formatting = 3; - string default_search_text = 4; - } - - Scheduling scheduling = 1; - Reviewing reviewing = 2; - Editing editing = 3; -} - -message ClozeNumbersInNoteResponse { - repeated uint32 numbers = 1; -} - -message GetDeckNamesRequest { - bool skip_empty_default = 1; - // if unset, implies skip_empty_default - bool include_filtered = 2; -} - -message ReparentDecksRequest { - repeated int64 deck_ids = 1; - int64 new_parent = 2; -} - -message NoteFieldsCheckResponse { - enum State { - NORMAL = 0; - EMPTY = 1; - DUPLICATE = 2; - MISSING_CLOZE = 3; - NOTETYPE_NOT_CLOZE = 4; - FIELD_NOT_CLOZE = 5; - } - State state = 1; -} - -message SyncLoginRequest { - string username = 1; - string password = 2; -} - -message SyncStatusResponse { - enum Required { - NO_CHANGES = 0; - NORMAL_SYNC = 1; - FULL_SYNC = 2; - } - Required required = 1; -} - -message SyncCollectionResponse { - enum ChangesRequired { - NO_CHANGES = 0; - NORMAL_SYNC = 1; - FULL_SYNC = 2; - // local collection has no cards; upload not an option - FULL_DOWNLOAD = 3; - // remote collection has no cards; download not an option - FULL_UPLOAD = 4; - } - - uint32 host_number = 1; - string server_message = 2; - ChangesRequired required = 3; -} - -message SyncAuth { - string hkey = 1; - uint32 host_number = 2; -} - -message SyncServerMethodRequest { - enum Method { - HOST_KEY = 0; - META = 1; - START = 2; - APPLY_GRAVES = 3; - APPLY_CHANGES = 4; - CHUNK = 5; - APPLY_CHUNK = 6; - SANITY_CHECK = 7; - FINISH = 8; - ABORT = 9; - // caller must reopen after these two are called - FULL_UPLOAD = 10; - FULL_DOWNLOAD = 11; - } - Method method = 1; - bytes data = 2; -} - -message RemoveNotesRequest { - repeated int64 note_ids = 1; - repeated int64 card_ids = 2; -} - -message RemoveCardsRequest { - repeated int64 card_ids = 1; -} - -message UpdateStatsRequest { - int64 deck_id = 1; - int32 new_delta = 2; - int32 review_delta = 4; - int32 millisecond_delta = 5; -} - -message ExtendLimitsRequest { - int64 deck_id = 1; - int32 new_delta = 2; - int32 review_delta = 3; -} - -message CountsForDeckTodayResponse { - int32 new = 1; - int32 review = 2; -} - -message GraphsRequest { - string search = 1; - uint32 days = 2; -} - -message GraphsResponse { - repeated Card cards = 1; - repeated RevlogEntry revlog = 2; - uint32 days_elapsed = 3; - // Based on rollover hour - uint32 next_day_at_secs = 4; - uint32 scheduler_version = 5; - /// Seconds to add to UTC timestamps to get local time. - int32 local_offset_secs = 7; -} - -message GraphPreferences { - enum Weekday { - SUNDAY = 0; - MONDAY = 1; - FRIDAY = 5; - SATURDAY = 6; - } - Weekday calendar_first_day_of_week = 1; - bool card_counts_separate_inactive = 2; - bool browser_links_supported = 3; - bool future_due_show_backlog = 4; -} - -message RevlogEntry { - enum ReviewKind { - LEARNING = 0; - REVIEW = 1; - RELEARNING = 2; - EARLY_REVIEW = 3; - MANUAL = 4; - } - int64 id = 1; - int64 cid = 2; - int32 usn = 3; - uint32 button_chosen = 4; - int32 interval = 5; - int32 last_interval = 6; - uint32 ease_factor = 7; - uint32 taken_millis = 8; - ReviewKind review_kind = 9; -} - -message CongratsInfoResponse { - uint32 learn_remaining = 1; - uint32 secs_until_next_learn = 2; - bool review_remaining = 3; - bool new_remaining = 4; - bool have_sched_buried = 5; - bool have_user_buried = 6; - bool is_filtered_deck = 7; - bool bridge_commands_supported = 8; - string deck_description = 9; -} - -message UnburyDeckRequest { - enum Mode { - ALL = 0; - SCHED_ONLY = 1; - USER_ONLY = 2; - } - int64 deck_id = 1; - Mode mode = 2; -} - -message BuryOrSuspendCardsRequest { - enum Mode { - SUSPEND = 0; - BURY_SCHED = 1; - BURY_USER = 2; - } - repeated int64 card_ids = 1; - repeated int64 note_ids = 2; - Mode mode = 3; -} - -message ScheduleCardsAsNewRequest { - repeated int64 card_ids = 1; - bool log = 2; -} - -message SetDueDateRequest { - repeated int64 card_ids = 1; - string days = 2; - Config.String config_key = 3; -} - -message SortCardsRequest { - repeated int64 card_ids = 1; - uint32 starting_from = 2; - uint32 step_size = 3; - bool randomize = 4; - bool shift_existing = 5; -} - -message SortDeckRequest { - int64 deck_id = 1; - bool randomize = 2; -} - -message SetDeckRequest { - repeated int64 card_ids = 1; - int64 deck_id = 2; -} - -message Config { - message Bool { - enum Key { - BROWSER_TABLE_SHOW_NOTES_MODE = 0; - PREVIEW_BOTH_SIDES = 3; - COLLAPSE_TAGS = 4; - COLLAPSE_NOTETYPES = 5; - COLLAPSE_DECKS = 6; - COLLAPSE_SAVED_SEARCHES = 7; - COLLAPSE_TODAY = 8; - COLLAPSE_CARD_STATE = 9; - COLLAPSE_FLAGS = 10; - SCHED_2021 = 11; - ADDING_DEFAULTS_TO_CURRENT_DECK = 12; - HIDE_AUDIO_PLAY_BUTTONS = 13; - INTERRUPT_AUDIO_WHEN_ANSWERING = 14; - PASTE_IMAGES_AS_PNG = 15; - PASTE_STRIPS_FORMATTING = 16; - NORMALIZE_NOTE_TEXT = 17; - } - Key key = 1; - } - - message String { - enum Key { - SET_DUE_BROWSER = 0; - SET_DUE_REVIEWER = 1; - DEFAULT_SEARCH_TEXT = 2; - CARD_STATE_CUSTOMIZER = 3; - } - Key key = 1; - } -} - -message SetConfigBoolRequest { - Config.Bool.Key key = 1; - bool value = 2; - bool undoable = 3; -} - -message SetConfigStringRequest { - Config.String.Key key = 1; - string value = 2; - bool undoable = 3; -} - -message RenderMarkdownRequest { - string markdown = 1; - bool sanitize = 2; -} - -message SchedulingState { - message New { - uint32 position = 1; - } - message Learning { - uint32 remaining_steps = 1; - uint32 scheduled_secs = 2; - } - message Review { - uint32 scheduled_days = 1; - uint32 elapsed_days = 2; - float ease_factor = 3; - uint32 lapses = 4; - bool leeched = 5; - } - message Relearning { - Review review = 1; - Learning learning = 2; - } - message Normal { - oneof value { - New new = 1; - Learning learning = 2; - Review review = 3; - Relearning relearning = 4; - } - } - message Preview { - uint32 scheduled_secs = 1; - bool finished = 2; - } - message ReschedulingFilter { - Normal original_state = 1; - } - message Filtered { - oneof value { - Preview preview = 1; - ReschedulingFilter rescheduling = 2; - } - } - - oneof value { - Normal normal = 1; - Filtered filtered = 2; - } -} - -message NextCardStates { - SchedulingState current = 1; - SchedulingState again = 2; - SchedulingState hard = 3; - SchedulingState good = 4; - SchedulingState easy = 5; -} - -message CardAnswer { - enum Rating { - AGAIN = 0; - HARD = 1; - GOOD = 2; - EASY = 3; - } - - int64 card_id = 1; - SchedulingState current_state = 2; - SchedulingState new_state = 3; - Rating rating = 4; - int64 answered_at_millis = 5; - uint32 milliseconds_taken = 6; -} - -message GetQueuedCardsRequest { - uint32 fetch_limit = 1; - bool intraday_learning_only = 2; -} - -message QueuedCards { - enum Queue { - NEW = 0; - LEARNING = 1; - REVIEW = 2; - } - message QueuedCard { - Card card = 1; - Queue queue = 2; - NextCardStates next_states = 3; - } - - repeated QueuedCard cards = 1; - uint32 new_count = 2; - uint32 learning_count = 3; - uint32 review_count = 4; -} - -message OpChanges { - bool card = 1; - bool note = 2; - bool deck = 3; - bool tag = 4; - bool notetype = 5; - bool config = 6; - bool deck_config = 11; - bool mtime = 12; - - bool browser_table = 7; - bool browser_sidebar = 8; - // editor and displayed card in review screen - bool note_text = 9; - // whether to call .reset() and getCard() - bool study_queues = 10; -} - -message UndoStatus { - string undo = 1; - string redo = 2; - uint32 last_step = 3; -} - -message OpChangesAfterUndo { - OpChanges changes = 1; - string operation = 2; - int64 reverted_to_timestamp = 3; - UndoStatus new_status = 4; - uint32 counter = 5; -} - -message DefaultsForAddingRequest { - int64 home_deck_of_current_review_card = 1; -} - -message DeckAndNotetype { - int64 deck_id = 1; - int64 notetype_id = 2; -} - -message RenameDeckRequest { - int64 deck_id = 1; - string new_name = 2; -} - -message FilteredDeckForUpdate { - int64 id = 1; - string name = 2; - Deck.Filtered config = 3; -} - -message SetFlagRequest { - repeated int64 card_ids = 1; - uint32 flag = 2; -} - -message GetAuxConfigKeyRequest { - int64 id = 1; - string key = 2; -} - -message GetAuxTemplateConfigKeyRequest { - int64 notetype_id = 1; - uint32 card_ordinal = 2; - string key = 3; -} - -message GetChangeNotetypeInfoRequest { - int64 old_notetype_id = 1; - int64 new_notetype_id = 2; -} - -message ChangeNotetypeRequest { - repeated int64 note_ids = 1; - // -1 is used to represent null, as nullable repeated fields - // are unwieldy in protobuf - repeated int32 new_fields = 2; - repeated int32 new_templates = 3; - int64 old_notetype_id = 4; - int64 new_notetype_id = 5; - int64 current_schema = 6; -} - -message ChangeNotetypeInfo { - repeated string old_field_names = 1; - repeated string old_template_names = 2; - repeated string new_field_names = 3; - repeated string new_template_names = 4; - ChangeNotetypeRequest input = 5; -} diff --git a/rslib/build/protobuf.rs b/rslib/build/protobuf.rs index 42c0440a3..5ea84ad7d 100644 --- a/rslib/build/protobuf.rs +++ b/rslib/build/protobuf.rs @@ -17,7 +17,7 @@ pub trait Service { write!( buf, concat!(" ", - "{idx} => {{ let input = {input_type}::decode(input)?;\n", + "{idx} => {{ let input = super::{input_type}::decode(input)?;\n", "let output = self.{rust_method}(input)?;\n", "let mut out_bytes = Vec::new(); output.encode(&mut out_bytes)?; Ok(out_bytes) }}, "), idx = idx, @@ -38,8 +38,8 @@ pub trait Service { write!( buf, concat!( - " fn {method_name}(&self, input: {input_type}) -> ", - "Result<{output_type}>;\n" + " fn {method_name}(&self, input: super::{input_type}) -> ", + "Result;\n" ), method_name = method.name, input_type = method.input_type, @@ -55,7 +55,6 @@ impl prost_build::ServiceGenerator for CustomGenerator { write!( buf, "pub mod {name}_service {{ - use super::*; use prost::Message; use crate::error::Result; ", @@ -73,16 +72,32 @@ fn service_generator() -> Box { pub fn write_backend_proto_rs() { let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); - let backend_proto; - let proto_dir; - if let Ok(proto) = env::var("BACKEND_PROTO") { - backend_proto = PathBuf::from(proto); - proto_dir = backend_proto.parent().unwrap().to_owned(); + let proto_dir = if let Ok(proto) = env::var("PROTO_TOP") { + let backend_proto = PathBuf::from(proto); + backend_proto.parent().unwrap().to_owned() } else { - backend_proto = PathBuf::from("backend.proto"); - proto_dir = PathBuf::from("."); + PathBuf::from("../proto") + }; + + let subfolders = &["anki"]; + let mut paths = vec![]; + + for subfolder in subfolders { + for entry in proto_dir.join(subfolder).read_dir().unwrap() { + let entry = entry.unwrap(); + let path = entry.path(); + if path + .file_name() + .unwrap() + .to_str() + .unwrap() + .ends_with(".proto") + { + println!("cargo:rerun-if-changed={}", path.to_str().unwrap()); + paths.push(path); + } + } } - println!("cargo:rerun-if-changed={}", backend_proto.to_str().unwrap()); let mut config = prost_build::Config::new(); config @@ -92,6 +107,6 @@ pub fn write_backend_proto_rs() { "Deck.Filtered.SearchTerm.Order", "#[derive(strum::EnumIter)]", ) - .compile_protos(&[&backend_proto], &[&proto_dir, &out_dir]) + .compile_protos(paths.as_slice(), &[proto_dir, out_dir]) .unwrap(); } diff --git a/rslib/src/backend/config.rs b/rslib/src/backend/config.rs index e4c2222da..a992de29d 100644 --- a/rslib/src/backend/config.rs +++ b/rslib/src/backend/config.rs @@ -7,7 +7,7 @@ use super::Backend; pub(super) use crate::backend_proto::config_service::Service as ConfigService; use crate::{ backend_proto as pb, - backend_proto::config::{bool::Key as BoolKeyProto, string::Key as StringKeyProto}, + backend_proto::config_key::{Bool as BoolKeyProto, String as StringKeyProto}, config::{BoolKey, StringKey}, prelude::*, }; @@ -46,12 +46,6 @@ impl From for StringKey { } } -impl From for StringKey { - fn from(key: pb::config::String) -> Self { - key.key().into() - } -} - impl ConfigService for Backend { fn get_config_json(&self, input: pb::String) -> Result { self.with_col(|col| { @@ -91,7 +85,7 @@ impl ConfigService for Backend { .map(Into::into) } - fn get_config_bool(&self, input: pb::config::Bool) -> Result { + fn get_config_bool(&self, input: pb::GetConfigBoolRequest) -> Result { self.with_col(|col| { Ok(pb::Bool { val: col.get_config_bool(input.key().into()), @@ -104,7 +98,7 @@ impl ConfigService for Backend { .map(Into::into) } - fn get_config_string(&self, input: pb::config::String) -> Result { + fn get_config_string(&self, input: pb::GetConfigStringRequest) -> Result { self.with_col(|col| { Ok(pb::String { val: col.get_config_string(input.key().into()), diff --git a/rslib/src/backend/mod.rs b/rslib/src/backend/mod.rs index 074ee23a7..013d5104d 100644 --- a/rslib/src/backend/mod.rs +++ b/rslib/src/backend/mod.rs @@ -48,7 +48,7 @@ use self::{ notes::NotesService, notetypes::NotetypesService, progress::ProgressState, - scheduler::SchedulingService, + scheduler::SchedulerService, search::SearchService, stats::StatsService, sync::{SyncService, SyncState}, @@ -117,7 +117,7 @@ impl Backend { pb::ServiceIndex::from_i32(service as i32) .ok_or_else(|| AnkiError::invalid_input("invalid service")) .and_then(|service| match service { - pb::ServiceIndex::Scheduling => SchedulingService::run_method(self, method, input), + pb::ServiceIndex::Scheduler => SchedulerService::run_method(self, method, input), pb::ServiceIndex::Decks => DecksService::run_method(self, method, input), pb::ServiceIndex::Notes => NotesService::run_method(self, method, input), pb::ServiceIndex::Notetypes => NotetypesService::run_method(self, method, input), diff --git a/rslib/src/backend/notes.rs b/rslib/src/backend/notes.rs index d3b8c93a2..e54123910 100644 --- a/rslib/src/backend/notes.rs +++ b/rslib/src/backend/notes.rs @@ -143,6 +143,13 @@ impl NotesService for Backend { }) }) } + + fn get_single_notetype_of_notes(&self, input: pb::NoteIds) -> Result { + self.with_col(|col| { + col.get_single_notetype_of_notes(&input.note_ids.into_newtype(NoteId)) + .map(Into::into) + }) + } } pub(super) fn to_note_ids(ids: Vec) -> Vec { diff --git a/rslib/src/backend/notetypes.rs b/rslib/src/backend/notetypes.rs index e47f1d484..9dfe9ab44 100644 --- a/rslib/src/backend/notetypes.rs +++ b/rslib/src/backend/notetypes.rs @@ -159,13 +159,6 @@ impl NotetypesService for Backend { }) } - fn get_single_notetype_of_notes(&self, input: pb::NoteIds) -> Result { - self.with_col(|col| { - col.get_single_notetype_of_notes(&input.note_ids.into_newtype(NoteId)) - .map(Into::into) - }) - } - fn get_change_notetype_info( &self, input: pb::GetChangeNotetypeInfoRequest, diff --git a/rslib/src/backend/scheduler/mod.rs b/rslib/src/backend/scheduler/mod.rs index 176a0d12c..05d4dbdda 100644 --- a/rslib/src/backend/scheduler/mod.rs +++ b/rslib/src/backend/scheduler/mod.rs @@ -5,7 +5,7 @@ mod answering; mod states; use super::Backend; -pub(super) use crate::backend_proto::scheduling_service::Service as SchedulingService; +pub(super) use crate::backend_proto::scheduler_service::Service as SchedulerService; use crate::{ backend_proto::{self as pb}, prelude::*, @@ -16,7 +16,7 @@ use crate::{ stats::studied_today, }; -impl SchedulingService for Backend { +impl SchedulerService for Backend { /// This behaves like _updateCutoff() in older code - it also unburies at the start of /// a new day. fn sched_timing_today(&self, _input: pb::Empty) -> Result { @@ -117,7 +117,7 @@ impl SchedulingService for Backend { } fn set_due_date(&self, input: pb::SetDueDateRequest) -> Result { - let config = input.config_key.map(Into::into); + let config = input.config_key.map(|v| v.key().into()); let days = input.days; let cids = input.card_ids.into_newtype(CardId); self.with_col(|col| col.set_due_date(&cids, &days, config).map(Into::into)) diff --git a/rslib/src/backend_proto.rs b/rslib/src/backend_proto.rs index 751d575cc..0fc6be831 100644 --- a/rslib/src/backend_proto.rs +++ b/rslib/src/backend_proto.rs @@ -1,4 +1,32 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -include!(concat!(env!("OUT_DIR"), "/backend_proto.rs")); +macro_rules! protobuf { + ($ident:ident) => { + pub mod $ident { + include!(concat!( + env!("OUT_DIR"), + concat!("/anki.", stringify!($ident), ".rs") + )); + } + pub use $ident::*; + }; +} + +protobuf!(backend); +protobuf!(card_rendering); +protobuf!(cards); +protobuf!(collection); +protobuf!(config); +protobuf!(deckconfig); +protobuf!(decks); +protobuf!(generic); +protobuf!(i18n); +protobuf!(media); +protobuf!(notes); +protobuf!(notetypes); +protobuf!(scheduler); +protobuf!(search); +protobuf!(stats); +protobuf!(sync); +protobuf!(tags); diff --git a/ts/BUILD.bazel b/ts/BUILD.bazel index 5233fc3c5..3ca8c216b 100644 --- a/ts/BUILD.bazel +++ b/ts/BUILD.bazel @@ -29,7 +29,6 @@ exports_files([ ".eslintrc.js", "licenses.json", "sql_format.ts", - "protobuf-shim.js", "jest.config.js", "package.json", "protobuf-no-long.js", diff --git a/ts/change-notetype/BUILD.bazel b/ts/change-notetype/BUILD.bazel index 697ae9685..017d5c46e 100644 --- a/ts/change-notetype/BUILD.bazel +++ b/ts/change-notetype/BUILD.bazel @@ -52,7 +52,6 @@ ts_library( deps = [ "//ts/components", "//ts/lib", - "//ts/lib:backend_proto", "//ts/sveltelib", "@npm//lodash-es", "@npm//svelte", @@ -61,20 +60,13 @@ ts_library( esbuild( name = "change-notetype", - srcs = [ - "//ts:protobuf-shim.js", - ], args = [ "--global-name=anki", - "--inject:$(location //ts:protobuf-shim.js)", "--resolve-extensions=.mjs,.js", "--log-level=warning", "--loader:.svg=text", ], entry_point = "index.ts", - external = [ - "protobufjs/light", - ], output_css = "change-notetype.css", visibility = ["//visibility:public"], deps = [ @@ -83,10 +75,10 @@ esbuild( "@npm//bootstrap", "@npm//marked", "//ts/lib", - "//ts/lib:backend_proto", "//ts/sveltelib", "//ts/components", "//ts/components:svelte_components", + "@npm//protobufjs", ] + svelte_names, ) @@ -129,7 +121,7 @@ jest_test( protobuf = True, deps = [ ":lib", - "//ts/lib:backend_proto", + "//ts/lib", "@npm//protobufjs", "@npm//svelte", ], diff --git a/ts/change-notetype/change-notetype.html b/ts/change-notetype/change-notetype.html index 244e1cf81..90e18ca94 100644 --- a/ts/change-notetype/change-notetype.html +++ b/ts/change-notetype/change-notetype.html @@ -5,7 +5,6 @@ - diff --git a/ts/change-notetype/lib.test.ts b/ts/change-notetype/lib.test.ts index b9597debe..bcda9781c 100644 --- a/ts/change-notetype/lib.test.ts +++ b/ts/change-notetype/lib.test.ts @@ -5,7 +5,7 @@ @typescript-eslint/no-explicit-any: "off", */ -import * as pb from "lib/backend_proto"; +import { Notetypes } from "lib/proto"; import { ChangeNotetypeState, negativeOneToNull, MapContext } from "./lib"; import { get } from "svelte/store"; @@ -64,15 +64,15 @@ const exampleInfoSame = { function differentState(): ChangeNotetypeState { return new ChangeNotetypeState( - pb.BackendProto.NotetypeNames.fromObject(exampleNames), - pb.BackendProto.ChangeNotetypeInfo.fromObject(exampleInfoDifferent) + Notetypes.NotetypeNames.fromObject(exampleNames), + Notetypes.ChangeNotetypeInfo.fromObject(exampleInfoDifferent) ); } function sameState(): ChangeNotetypeState { return new ChangeNotetypeState( - pb.BackendProto.NotetypeNames.fromObject(exampleNames), - pb.BackendProto.ChangeNotetypeInfo.fromObject(exampleInfoSame) + Notetypes.NotetypeNames.fromObject(exampleNames), + Notetypes.ChangeNotetypeInfo.fromObject(exampleInfoSame) ); } diff --git a/ts/change-notetype/lib.ts b/ts/change-notetype/lib.ts index 5e7fe578d..84661db38 100644 --- a/ts/change-notetype/lib.ts +++ b/ts/change-notetype/lib.ts @@ -5,13 +5,13 @@ @typescript-eslint/no-non-null-assertion: "off", */ -import pb from "lib/backend_proto"; +import { Notetypes } from "lib/proto"; import { postRequest } from "lib/postrequest"; import { readable, Readable } from "svelte/store"; import { isEqual } from "lodash-es"; -export async function getNotetypeNames(): Promise { - return pb.BackendProto.NotetypeNames.decode( +export async function getNotetypeNames(): Promise { + return Notetypes.NotetypeNames.decode( await postRequest("/_anki/notetypeNames", "") ); } @@ -19,8 +19,8 @@ export async function getNotetypeNames(): Promise export async function getChangeNotetypeInfo( oldNotetypeId: number, newNotetypeId: number -): Promise { - return pb.BackendProto.ChangeNotetypeInfo.decode( +): Promise { + return Notetypes.ChangeNotetypeInfo.decode( await postRequest( "/_anki/changeNotetypeInfo", JSON.stringify({ oldNotetypeId, newNotetypeId }) @@ -29,10 +29,9 @@ export async function getChangeNotetypeInfo( } export async function changeNotetype( - input: pb.BackendProto.ChangeNotetypeRequest + input: Notetypes.ChangeNotetypeRequest ): Promise { - const data: Uint8Array = - pb.BackendProto.ChangeNotetypeRequest.encode(input).finish(); + const data: Uint8Array = Notetypes.ChangeNotetypeRequest.encode(input).finish(); await postRequest("/_anki/changeNotetype", data); return; } @@ -50,9 +49,9 @@ export function negativeOneToNull(list: number[]): (number | null)[] { export class ChangeNotetypeInfoWrapper { fields: (number | null)[]; templates?: (number | null)[]; - readonly info: pb.BackendProto.ChangeNotetypeInfo; + readonly info: Notetypes.ChangeNotetypeInfo; - constructor(info: pb.BackendProto.ChangeNotetypeInfo) { + constructor(info: Notetypes.ChangeNotetypeInfo) { this.info = info; const templates = info.input!.newTemplates!; if (templates.length > 0) { @@ -114,13 +113,13 @@ export class ChangeNotetypeInfoWrapper { ); } - input(): pb.BackendProto.ChangeNotetypeRequest { - return this.info.input as pb.BackendProto.ChangeNotetypeRequest; + input(): Notetypes.ChangeNotetypeRequest { + return this.info.input as Notetypes.ChangeNotetypeRequest; } /// Pack changes back into input message for saving. - intoInput(): pb.BackendProto.ChangeNotetypeRequest { - const input = this.info.input as pb.BackendProto.ChangeNotetypeRequest; + intoInput(): Notetypes.ChangeNotetypeRequest { + const input = this.info.input as Notetypes.ChangeNotetypeRequest; input.newFields = nullToNegativeOne(this.fields); if (this.templates) { input.newTemplates = nullToNegativeOne(this.templates); @@ -146,12 +145,12 @@ export class ChangeNotetypeState { private info_: ChangeNotetypeInfoWrapper; private infoSetter!: (val: ChangeNotetypeInfoWrapper) => void; - private notetypeNames: pb.BackendProto.NotetypeNames; + private notetypeNames: Notetypes.NotetypeNames; private notetypesSetter!: (val: NotetypeListEntry[]) => void; constructor( - notetypes: pb.BackendProto.NotetypeNames, - info: pb.BackendProto.ChangeNotetypeInfo + notetypes: Notetypes.NotetypeNames, + info: Notetypes.ChangeNotetypeInfo ) { this.info_ = new ChangeNotetypeInfoWrapper(info); this.info = readable(this.info_, (set) => { @@ -203,7 +202,7 @@ export class ChangeNotetypeState { await changeNotetype(this.dataForSaving()); } - dataForSaving(): pb.BackendProto.ChangeNotetypeRequest { + dataForSaving(): Notetypes.ChangeNotetypeRequest { return this.info_.intoInput(); } diff --git a/ts/compile_sass.bzl b/ts/compile_sass.bzl index c8d96cfb3..7ab1aba6a 100644 --- a/ts/compile_sass.bzl +++ b/ts/compile_sass.bzl @@ -14,7 +14,7 @@ def compile_sass(group, srcs, deps = [], visibility = ["//visibility:private"]): sourcemap = False, deps = deps, visibility = visibility, - include_paths = ["external/net_ankiweb_anki"], + include_paths = ["external/ankidesktop"], ) native.filegroup( diff --git a/ts/components/BUILD.bazel b/ts/components/BUILD.bazel index 4eaa56dbd..167e4089a 100644 --- a/ts/components/BUILD.bazel +++ b/ts/components/BUILD.bazel @@ -17,25 +17,24 @@ filegroup( compile_svelte( name = "svelte", srcs = svelte_files, + visibility = ["//visibility:public"], deps = [ "//ts/sass:button_mixins_lib", "//ts/sass/bootstrap", ], - visibility = ["//visibility:public"], ) ts_library( name = "components", - module_name = "components", srcs = glob( ["*.ts"], exclude = ["*.test.ts"], ), + module_name = "components", tsconfig = "//ts:tsconfig.json", visibility = ["//visibility:public"], deps = [ "//ts/lib", - "//ts/lib:backend_proto", "//ts/sveltelib", "@npm//@popperjs/core", "@npm//@types/bootstrap", diff --git a/ts/congrats/BUILD.bazel b/ts/congrats/BUILD.bazel index 1148e3ae0..8cba0ec3d 100644 --- a/ts/congrats/BUILD.bazel +++ b/ts/congrats/BUILD.bazel @@ -35,27 +35,17 @@ ts_library( ts_library( name = "lib", srcs = ["lib.ts"], - deps = [ - "//ts/lib", - "//ts/lib:backend_proto", - ], + deps = ["//ts/lib"], ) esbuild( name = "congrats", - srcs = [ - "//ts:protobuf-shim.js", - ], args = [ "--global-name=anki", - "--inject:$(location //ts:protobuf-shim.js)", "--resolve-extensions=.mjs,.js", "--log-level=warning", ], entry_point = "index.ts", - external = [ - "protobufjs/light", - ], output_css = "congrats.css", visibility = ["//visibility:public"], deps = [ @@ -63,7 +53,7 @@ esbuild( ":base_css", ":index", "//ts/lib", - "//ts/lib:backend_proto", + "@npm//protobufjs", ], ) diff --git a/ts/congrats/CongratsPage.svelte b/ts/congrats/CongratsPage.svelte index df5b59ab7..122d42cb8 100644 --- a/ts/congrats/CongratsPage.svelte +++ b/ts/congrats/CongratsPage.svelte @@ -3,11 +3,11 @@ Copyright: Ankitects Pty Ltd and contributors License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html --> diff --git a/ts/congrats/lib.ts b/ts/congrats/lib.ts index fb001ae9a..9ab67c171 100644 --- a/ts/congrats/lib.ts +++ b/ts/congrats/lib.ts @@ -1,19 +1,19 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -import pb from "lib/backend_proto"; +import { Scheduler } from "lib/proto"; import { postRequest } from "lib/postrequest"; import { naturalUnit, unitAmount, unitName } from "lib/time"; import * as tr from "lib/i18n"; -export async function getCongratsInfo(): Promise { - return pb.BackendProto.CongratsInfoResponse.decode( +export async function getCongratsInfo(): Promise { + return Scheduler.CongratsInfoResponse.decode( await postRequest("/_anki/congratsInfo", "") ); } -export function buildNextLearnMsg(info: pb.BackendProto.CongratsInfoResponse): string { +export function buildNextLearnMsg(info: Scheduler.CongratsInfoResponse): string { const secsUntil = info.secsUntilNextLearn; // next learning card not due (/ until tomorrow)? if (secsUntil == 0 || secsUntil > 86_400) { diff --git a/ts/deck-options/BUILD.bazel b/ts/deck-options/BUILD.bazel index 6703cb8b5..d2c589472 100644 --- a/ts/deck-options/BUILD.bazel +++ b/ts/deck-options/BUILD.bazel @@ -26,8 +26,8 @@ compile_svelte( name = "svelte", srcs = svelte_files, deps = [ - "//ts/sveltelib", "//ts/components", + "//ts/sveltelib", "@npm//@types/bootstrap", "@npm//@types/marked", ], @@ -48,8 +48,8 @@ ts_library( deps = [ "DeckOptionsPage", "lib", - "//ts/lib", "//ts/components", + "//ts/lib", "@npm//@popperjs", "@npm//svelte2tsx", ], @@ -66,10 +66,9 @@ ts_library( module_name = "deckoptions", deps = [ "//ts:image_module_support", - "//ts/lib", - "//ts/lib:backend_proto", - "//ts/sveltelib", "//ts/components", + "//ts/lib", + "//ts/sveltelib", "@npm//lodash-es", "@npm//svelte", ], @@ -77,20 +76,13 @@ ts_library( esbuild( name = "deck-options", - srcs = [ - "//ts:protobuf-shim.js", - ], args = [ "--global-name=anki", - "--inject:$(location //ts:protobuf-shim.js)", "--resolve-extensions=.mjs,.js", "--log-level=warning", "--loader:.svg=text", ], entry_point = "index.ts", - external = [ - "protobufjs/light", - ], output_css = "deck-options.css", visibility = ["//visibility:public"], deps = [ @@ -99,8 +91,8 @@ esbuild( ":base_css", "@npm//bootstrap", "@npm//marked", + "@npm//protobufjs", "//ts/lib", - "//ts/lib:backend_proto", "//ts/sveltelib", "//ts/components", "//ts/components:svelte_components", @@ -147,7 +139,7 @@ jest_test( protobuf = True, deps = [ ":lib", - "//ts/lib:backend_proto", + "//ts/lib", "@npm//protobufjs", "@npm//svelte", ], diff --git a/ts/deck-options/deck-options.html b/ts/deck-options/deck-options.html index ecbbb4def..0b7c3915e 100644 --- a/ts/deck-options/deck-options.html +++ b/ts/deck-options/deck-options.html @@ -5,7 +5,6 @@ - diff --git a/ts/deck-options/lib.test.ts b/ts/deck-options/lib.test.ts index 05f85b880..eb9168896 100644 --- a/ts/deck-options/lib.test.ts +++ b/ts/deck-options/lib.test.ts @@ -5,7 +5,7 @@ @typescript-eslint/no-explicit-any: "off", */ -import * as pb from "lib/backend_proto"; +import { DeckConfig } from "lib/proto"; import { DeckOptionsState } from "./lib"; import { get } from "svelte/store"; @@ -94,7 +94,7 @@ const exampleData = { function startingState(): DeckOptionsState { return new DeckOptionsState( 123, - pb.BackendProto.DeckConfigsForUpdate.fromObject(exampleData) + DeckConfig.DeckConfigsForUpdate.fromObject(exampleData) ); } diff --git a/ts/deck-options/lib.ts b/ts/deck-options/lib.ts index 3ca866df6..1775d4815 100644 --- a/ts/deck-options/lib.ts +++ b/ts/deck-options/lib.ts @@ -5,7 +5,7 @@ @typescript-eslint/no-non-null-assertion: "off", */ -import pb from "lib/backend_proto"; +import { DeckConfig } from "lib/proto"; import { postRequest } from "lib/postrequest"; import { Writable, writable, get, Readable, readable } from "svelte/store"; import { isEqual, cloneDeep } from "lodash-es"; @@ -14,17 +14,16 @@ import type { DynamicSvelteComponent } from "sveltelib/dynamicComponent"; export async function getDeckOptionsInfo( deckId: number -): Promise { - return pb.BackendProto.DeckConfigsForUpdate.decode( +): Promise { + return DeckConfig.DeckConfigsForUpdate.decode( await postRequest("/_anki/deckConfigsForUpdate", JSON.stringify({ deckId })) ); } export async function saveDeckOptions( - input: pb.BackendProto.UpdateDeckConfigsRequest + input: DeckConfig.UpdateDeckConfigsRequest ): Promise { - const data: Uint8Array = - pb.BackendProto.UpdateDeckConfigsRequest.encode(input).finish(); + const data: Uint8Array = DeckConfig.UpdateDeckConfigsRequest.encode(input).finish(); await postRequest("/_anki/updateDeckConfigs", data); return; } @@ -32,7 +31,7 @@ export async function saveDeckOptions( export type DeckOptionsId = number; export interface ConfigWithCount { - config: pb.BackendProto.DeckConfig; + config: DeckConfig.DeckConfig; useCount: number; } @@ -49,14 +48,14 @@ export interface ConfigListEntry { current: boolean; } -type ConfigInner = pb.BackendProto.DeckConfig.Config; +type ConfigInner = DeckConfig.DeckConfig.Config; export class DeckOptionsState { readonly currentConfig: Writable; readonly currentAuxData: Writable>; readonly configList: Readable; readonly parentLimits: Readable; readonly cardStateCustomizer: Writable; - readonly currentDeck: pb.BackendProto.DeckConfigsForUpdate.CurrentDeck; + readonly currentDeck: DeckConfig.DeckConfigsForUpdate.CurrentDeck; readonly defaults: ConfigInner; readonly addonComponents: Writable; readonly v3Scheduler: boolean; @@ -71,13 +70,13 @@ export class DeckOptionsState { private removedConfigs: DeckOptionsId[] = []; private schemaModified: boolean; - constructor(targetDeckId: number, data: pb.BackendProto.DeckConfigsForUpdate) { + constructor(targetDeckId: number, data: DeckConfig.DeckConfigsForUpdate) { this.targetDeckId = targetDeckId; this.currentDeck = - data.currentDeck as pb.BackendProto.DeckConfigsForUpdate.CurrentDeck; + data.currentDeck as DeckConfig.DeckConfigsForUpdate.CurrentDeck; this.defaults = data.defaults!.config! as ConfigInner; this.configs = data.allConfig.map((config) => { - const configInner = config.config as pb.BackendProto.DeckConfig; + const configInner = config.config as DeckConfig.DeckConfig; return { config: configInner, useCount: config.useCount!, @@ -152,12 +151,9 @@ export class DeckOptionsState { } /// Clone the current config, making it current. - private addConfigFrom( - name: string, - source: pb.BackendProto.DeckConfig.IConfig - ): void { + private addConfigFrom(name: string, source: DeckConfig.DeckConfig.IConfig): void { const uniqueName = this.ensureNewNameUnique(name); - const config = pb.BackendProto.DeckConfig.create({ + const config = DeckConfig.DeckConfig.create({ id: 0, name: uniqueName, config: cloneDeep(source), @@ -193,7 +189,7 @@ export class DeckOptionsState { this.updateConfigList(); } - dataForSaving(applyToChildren: boolean): pb.BackendProto.UpdateDeckConfigsRequest { + dataForSaving(applyToChildren: boolean): DeckConfig.UpdateDeckConfigsRequest { const modifiedConfigsExcludingCurrent = this.configs .map((c) => c.config) .filter((c, idx) => { @@ -207,7 +203,7 @@ export class DeckOptionsState { // current must come last, even if unmodified this.configs[this.selectedIdx].config, ]; - return pb.BackendProto.UpdateDeckConfigsRequest.create({ + return DeckConfig.UpdateDeckConfigsRequest.create({ targetDeckId: this.targetDeckId, removedConfigIds: this.removedConfigs, configs, diff --git a/ts/editor/BUILD.bazel b/ts/editor/BUILD.bazel index d0f1aa91d..15946689b 100644 --- a/ts/editor/BUILD.bazel +++ b/ts/editor/BUILD.bazel @@ -114,7 +114,6 @@ copy_mdi_icons( "function-variant.svg", "contain.svg", "xml.svg", - "format-color-text.svg", "format-color-highlight.svg", "color-helper.svg", @@ -124,19 +123,12 @@ copy_mdi_icons( esbuild( name = "editor", - srcs = [ - "//ts:protobuf-shim.js", - ], args = [ "--loader:.svg=text", - "--inject:$(location //ts:protobuf-shim.js)", "--resolve-extensions=.mjs,.js", "--log-level=warning", ], entry_point = "index_wrapper.ts", - external = [ - "protobufjs/light", - ], output_css = "editor.css", visibility = ["//visibility:public"], deps = [ @@ -148,6 +140,7 @@ esbuild( "svelte_components", "//ts/components", "//ts/components:svelte_components", + "@npm//protobufjs", ], ) diff --git a/ts/graphs/AddedGraph.svelte b/ts/graphs/AddedGraph.svelte index 99ebccf71..c3f56c64f 100644 --- a/ts/graphs/AddedGraph.svelte +++ b/ts/graphs/AddedGraph.svelte @@ -3,7 +3,7 @@ Copyright: Ankitects Pty Ltd and contributors License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html --> diff --git a/ts/graphs/hours.ts b/ts/graphs/hours.ts index a5ff7ebd1..743f4d0e5 100644 --- a/ts/graphs/hours.ts +++ b/ts/graphs/hours.ts @@ -6,7 +6,7 @@ @typescript-eslint/no-explicit-any: "off", */ -import pb from "lib/backend_proto"; +import { Stats } from "lib/proto"; import { interpolateBlues, select, @@ -37,15 +37,15 @@ interface Hour { correctCount: number; } -const ReviewKind = pb.BackendProto.RevlogEntry.ReviewKind; +const ReviewKind = Stats.RevlogEntry.ReviewKind; -function gatherData(data: pb.BackendProto.GraphsResponse, range: GraphRange): Hour[] { +function gatherData(data: Stats.GraphsResponse, range: GraphRange): Hour[] { const hours = [...Array(24)].map((_n, idx: number) => { return { hour: idx, totalCount: 0, correctCount: 0 } as Hour; }); const cutoff = millisecondCutoffForRange(range, data.nextDayAtSecs); - for (const review of data.revlog as pb.BackendProto.RevlogEntry[]) { + for (const review of data.revlog as Stats.RevlogEntry[]) { switch (review.reviewKind) { case ReviewKind.LEARNING: case ReviewKind.REVIEW: @@ -74,7 +74,7 @@ function gatherData(data: pb.BackendProto.GraphsResponse, range: GraphRange): Ho export function renderHours( svgElem: SVGElement, bounds: GraphBounds, - origData: pb.BackendProto.GraphsResponse, + origData: Stats.GraphsResponse, range: GraphRange ): void { const data = gatherData(origData, range); diff --git a/ts/graphs/intervals.ts b/ts/graphs/intervals.ts index f69ced872..81000b1c9 100644 --- a/ts/graphs/intervals.ts +++ b/ts/graphs/intervals.ts @@ -6,7 +6,7 @@ @typescript-eslint/no-explicit-any: "off", */ -import type pb from "lib/backend_proto"; +import type { Stats, Cards } from "lib/proto"; import { extent, histogram, @@ -36,10 +36,8 @@ export enum IntervalRange { All = 3, } -export function gatherIntervalData( - data: pb.BackendProto.GraphsResponse -): IntervalGraphData { - const intervals = (data.cards as pb.BackendProto.Card[]) +export function gatherIntervalData(data: Stats.GraphsResponse): IntervalGraphData { + const intervals = (data.cards as Cards.Card[]) .filter((c) => [CardType.Review, CardType.Relearn].includes(c.ctype)) .map((c) => c.interval); return { intervals }; diff --git a/ts/graphs/reviews.ts b/ts/graphs/reviews.ts index a01d19c7b..9ab89e4a5 100644 --- a/ts/graphs/reviews.ts +++ b/ts/graphs/reviews.ts @@ -6,7 +6,7 @@ @typescript-eslint/no-explicit-any: "off", */ -import pb from "lib/backend_proto"; +import { Stats } from "lib/proto"; import { timeSpan, dayLabel } from "lib/time"; import { @@ -50,15 +50,15 @@ export interface GraphData { reviewTime: Map; } -const ReviewKind = pb.BackendProto.RevlogEntry.ReviewKind; +const ReviewKind = Stats.RevlogEntry.ReviewKind; type BinType = Bin, number>; -export function gatherData(data: pb.BackendProto.GraphsResponse): GraphData { +export function gatherData(data: Stats.GraphsResponse): GraphData { const reviewCount = new Map(); const reviewTime = new Map(); const empty = { mature: 0, young: 0, learn: 0, relearn: 0, early: 0 }; - for (const review of data.revlog as pb.BackendProto.RevlogEntry[]) { + for (const review of data.revlog as Stats.RevlogEntry[]) { if (review.reviewKind == ReviewKind.MANUAL) { // don't count days with only manual scheduling continue; diff --git a/ts/graphs/today.ts b/ts/graphs/today.ts index b0f8d13cb..f12b23e13 100644 --- a/ts/graphs/today.ts +++ b/ts/graphs/today.ts @@ -1,7 +1,7 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -import pb from "lib/backend_proto"; +import { Stats } from "lib/proto"; import { studiedToday } from "lib/time"; import * as tr from "lib/i18n"; @@ -11,9 +11,9 @@ export interface TodayData { lines: string[]; } -const ReviewKind = pb.BackendProto.RevlogEntry.ReviewKind; +const ReviewKind = Stats.RevlogEntry.ReviewKind; -export function gatherData(data: pb.BackendProto.GraphsResponse): TodayData { +export function gatherData(data: Stats.GraphsResponse): TodayData { let answerCount = 0; let answerMillis = 0; let correctCount = 0; @@ -26,7 +26,7 @@ export function gatherData(data: pb.BackendProto.GraphsResponse): TodayData { const startOfTodayMillis = (data.nextDayAtSecs - 86400) * 1000; - for (const review of data.revlog as pb.BackendProto.RevlogEntry[]) { + for (const review of data.revlog as Stats.RevlogEntry[]) { if (review.id < startOfTodayMillis) { continue; } diff --git a/ts/lib/BUILD.bazel b/ts/lib/BUILD.bazel index 6f0f91366..ba27d66af 100644 --- a/ts/lib/BUILD.bazel +++ b/ts/lib/BUILD.bazel @@ -9,7 +9,7 @@ load("//ts:jest.bzl", "jest_test") protobufjs_library( name = "backend_proto", - proto = "//rslib:backend_proto_lib", + proto = "//proto:backend_proto_lib", visibility = ["//visibility:public"], ) diff --git a/ts/lib/proto.ts b/ts/lib/proto.ts new file mode 100644 index 000000000..dbba70dbb --- /dev/null +++ b/ts/lib/proto.ts @@ -0,0 +1,10 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +import { anki } from "./backend_proto"; +import Cards = anki.cards; +import DeckConfig = anki.deckconfig; +import Notetypes = anki.notetypes; +import Scheduler = anki.scheduler; +import Stats = anki.stats; +export { Stats, Cards, DeckConfig, Notetypes, Scheduler }; diff --git a/ts/package.json b/ts/package.json index 779513746..1b68e2934 100644 --- a/ts/package.json +++ b/ts/package.json @@ -37,6 +37,7 @@ "jsdoc": "^3.6.6", "license-checker-rseidelsohn": "^1.2.2", "minimist": "^1.2.5", + "patch-package": "^6.4.7", "prettier": "=2.3.0", "prettier-plugin-svelte": "=2.3.0", "sass": "=1.32.12", @@ -51,7 +52,8 @@ "uglify-js": "^3.13.1" }, "scripts": { - "fix": "prettier --write */*.ts */*.svelte" + "fix": "prettier --write */*.ts */*.svelte", + "postinstall": "patch-package" }, "dependencies": { "@fluent/bundle": "^0.15.1", diff --git a/ts/patches/protobufjs+6.11.2.patch b/ts/patches/protobufjs+6.11.2.patch new file mode 100644 index 000000000..cb9fcb718 --- /dev/null +++ b/ts/patches/protobufjs+6.11.2.patch @@ -0,0 +1,13 @@ +diff --git a/node_modules/protobufjs/src/root.js b/node_modules/protobufjs/src/root.js +index df6f11f..112f9e8 100644 +--- a/node_modules/protobufjs/src/root.js ++++ b/node_modules/protobufjs/src/root.js +@@ -259,7 +259,7 @@ Root.prototype.resolveAll = function resolveAll() { + }; + + // only uppercased (and thus conflict-free) children are exposed, see below +-var exposeRe = /^[A-Z]/; ++var exposeRe = /^[A-Za-z]/; + + /** + * Handles a deferred declaring extension field by creating a sister field to represent it within its extended type. diff --git a/ts/protobuf-shim.js b/ts/protobuf-shim.js deleted file mode 100644 index 3f4f213ef..000000000 --- a/ts/protobuf-shim.js +++ /dev/null @@ -1,4 +0,0 @@ -window.require = (name) => { - if (name === "protobufjs/light") return window.protobuf; - else throw new Error(`Cannot require ${name}`); -}; diff --git a/ts/reviewer/BUILD.bazel b/ts/reviewer/BUILD.bazel index 4b2217a48..76bec83ff 100644 --- a/ts/reviewer/BUILD.bazel +++ b/ts/reviewer/BUILD.bazel @@ -8,31 +8,21 @@ load("//ts:compile_sass.bzl", "compile_sass") ts_library( name = "lib", srcs = glob(["*.ts"]), - deps = [ - "//ts/lib", - "//ts/lib:backend_proto", - ], + deps = ["//ts/lib"], ) esbuild( name = "reviewer_extras", - srcs = [ - "//ts:protobuf-shim.js", - ], args = [ - "--inject:$(location //ts:protobuf-shim.js)", "--resolve-extensions=.mjs,.js", "--log-level=warning", ], entry_point = "index.ts", - external = [ - "protobufjs/light", - ], visibility = ["//visibility:public"], deps = [ ":lib", "//ts/lib", - "//ts/lib:backend_proto", + "@npm//protobufjs", ], ) diff --git a/ts/reviewer/answering.ts b/ts/reviewer/answering.ts index 6f7960ff1..27e3b5807 100644 --- a/ts/reviewer/answering.ts +++ b/ts/reviewer/answering.ts @@ -1,26 +1,26 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -import * as pb from "lib/backend_proto"; +import { Scheduler } from "lib/proto"; import { postRequest } from "lib/postrequest"; -async function getNextStates(): Promise { - return pb.BackendProto.NextCardStates.decode( +async function getNextStates(): Promise { + return Scheduler.NextCardStates.decode( await postRequest("/_anki/nextCardStates", "") ); } async function setNextStates( key: string, - states: pb.BackendProto.NextCardStates + states: Scheduler.NextCardStates ): Promise { - const data: Uint8Array = pb.BackendProto.NextCardStates.encode(states).finish(); + const data: Uint8Array = Scheduler.NextCardStates.encode(states).finish(); await postRequest("/_anki/setNextCardStates", data, { key }); } export async function mutateNextCardStates( key: string, - mutator: (states: pb.BackendProto.NextCardStates) => void + mutator: (states: Scheduler.NextCardStates) => void ): Promise { const states = await getNextStates(); mutator(states); diff --git a/ts/svelte/svelte.bzl b/ts/svelte/svelte.bzl index 27610ad88..eb99acd6b 100644 --- a/ts/svelte/svelte.bzl +++ b/ts/svelte/svelte.bzl @@ -91,7 +91,6 @@ def svelte_check(name = "svelte_check", srcs = []): "//ts:tsconfig.json", "//ts/sveltelib", "//ts/lib", - "//ts/lib:backend_proto", "@npm//sass", ] + srcs, env = {"SASS_PATH": "$(rootpath //ts:tsconfig.json)/../.."}, diff --git a/ts/svelte/svelte.ts b/ts/svelte/svelte.ts index 440e14b78..0f05831bc 100644 --- a/ts/svelte/svelte.ts +++ b/ts/svelte/svelte.ts @@ -157,9 +157,9 @@ async function writeJs( genDir, // a nasty hack to ensure ts/sass/... resolves correctly // when invoked from an external workspace - binDir + "/external/net_ankiweb_anki", - genDir + "/external/net_ankiweb_anki", - binDir + "/../../../external/net_ankiweb_anki", + binDir + "/external/ankidesktop", + genDir + "/external/ankidesktop", + binDir + "/../../../external/ankidesktop", ], }, }); diff --git a/ts/vendor.bzl b/ts/vendor.bzl index 13b948adc..578d95e3e 100644 --- a/ts/vendor.bzl +++ b/ts/vendor.bzl @@ -60,17 +60,6 @@ def copy_jquery_ui(name = "jquery-ui", visibility = ["//visibility:public"]): visibility = visibility, ) -def copy_protobufjs(name = "protobufjs", visibility = ["//visibility:public"]): - vendor_js_lib( - name = name, - pkg = pkg_from_name(name), - include = [ - "dist/protobuf.min.js", - ], - strip_prefix = "dist/", - visibility = visibility, - ) - def copy_mathjax(name = "mathjax", visibility = ["//visibility:public"]): vendor_js_lib( name = name, @@ -140,4 +129,3 @@ def copy_mdi_icons(name = "mdi-icons", icons = [], visibility = ["//visibility:p strip_prefix = "svg/", visibility = visibility, ) - diff --git a/ts/yarn.lock b/ts/yarn.lock index 50c723e2b..fc5719c20 100644 --- a/ts/yarn.lock +++ b/ts/yarn.lock @@ -1177,6 +1177,11 @@ "@typescript-eslint/types" "4.27.0" eslint-visitor-keys "^2.0.0" +"@yarnpkg/lockfile@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" + integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== + abab@^2.0.3, abab@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" @@ -1474,7 +1479,7 @@ catharsis@^0.9.0: dependencies: lodash "^4.17.15" -chalk@^2.0.0: +chalk@^2.0.0, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -1511,6 +1516,11 @@ char-regex@^1.0.2: optionalDependencies: fsevents "~2.3.2" +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + ci-info@^3.1.1: version "3.2.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6" @@ -1605,6 +1615,17 @@ cross-env@^7.0.2: dependencies: cross-spawn "^7.0.1" +cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -2271,6 +2292,13 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" +find-yarn-workspace-root@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd" + integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ== + dependencies: + micromatch "^4.0.2" + flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" @@ -2293,6 +2321,15 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +fs-extra@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -2376,7 +2413,7 @@ globby@^11.0.3: merge2 "^1.3.0" slash "^3.0.0" -graceful-fs@^4.1.2, graceful-fs@^4.1.9, graceful-fs@^4.2.4: +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.4: version "4.2.6" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== @@ -2512,6 +2549,13 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" +is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" + integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== + dependencies: + ci-info "^2.0.0" + is-ci@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.0.tgz#c7e7be3c9d8eef7d0fa144390bd1e4b88dc4c994" @@ -2526,6 +2570,11 @@ is-core-module@^2.2.0: dependencies: has "^1.0.3" +is-docker@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -2568,6 +2617,13 @@ is-typedarray@^1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= +is-wsl@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -3144,6 +3200,20 @@ json5@^2.1.2: dependencies: minimist "^1.2.5" +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + optionalDependencies: + graceful-fs "^4.1.6" + +klaw-sync@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c" + integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ== + dependencies: + graceful-fs "^4.1.11" + klaw@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/klaw/-/klaw-3.0.0.tgz#b11bec9cf2492f06756d6e809ab73a2910259146" @@ -3343,7 +3413,7 @@ minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -minimist@^1.2.5: +minimist@^1.2.0, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== @@ -3368,6 +3438,11 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + no-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" @@ -3444,6 +3519,14 @@ onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" +open@^7.4.2: + version "7.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" + integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== + dependencies: + is-docker "^2.0.0" + is-wsl "^2.1.1" + optionator@^0.8.1: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" @@ -3468,6 +3551,11 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + p-each-series@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a" @@ -3512,6 +3600,25 @@ pascal-case@^3.1.1: no-case "^3.0.4" tslib "^2.0.3" +patch-package@^6.4.7: + version "6.4.7" + resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-6.4.7.tgz#2282d53c397909a0d9ef92dae3fdeb558382b148" + integrity sha512-S0vh/ZEafZ17hbhgqdnpunKDfzHQibQizx9g8yEf5dcVk3KOflOfdufRXQX8CSEkyOQwuM/bNz1GwKvFj54kaQ== + dependencies: + "@yarnpkg/lockfile" "^1.1.0" + chalk "^2.4.2" + cross-spawn "^6.0.5" + find-yarn-workspace-root "^2.0.0" + fs-extra "^7.0.1" + is-ci "^2.0.0" + klaw-sync "^6.0.0" + minimist "^1.2.0" + open "^7.4.2" + rimraf "^2.6.3" + semver "^5.6.0" + slash "^2.0.0" + tmp "^0.0.33" + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -3522,6 +3629,11 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= +path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" @@ -3760,6 +3872,13 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +rimraf@^2.6.3: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -3815,7 +3934,7 @@ saxes@^5.0.1: dependencies: xmlchars "^2.2.0" -"semver@2 || 3 || 4 || 5": +"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -3837,6 +3956,13 @@ semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: dependencies: lru-cache "^6.0.0" +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -3844,6 +3970,11 @@ shebang-command@^2.0.0: dependencies: shebang-regex "^3.0.0" +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + shebang-regex@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" @@ -3859,6 +3990,11 @@ sisteransi@^1.0.5: resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== +slash@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" + integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -4132,6 +4268,13 @@ throat@^6.0.1: resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w== +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + tmp@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" @@ -4257,7 +4400,7 @@ underscore@~1.13.1: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.1.tgz#0c1c6bd2df54b6b69f2314066d65b6cde6fcf9d1" integrity sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g== -universalify@^0.1.2: +universalify@^0.1.0, universalify@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== @@ -4348,6 +4491,13 @@ whatwg-url@^8.0.0, whatwg-url@^8.5.0: tr46 "^2.1.0" webidl-conversions "^6.1.0" +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"