Merge pull request #1286 from ankitects/proto

Split backend.proto into multiple files
This commit is contained in:
Damien Elmes 2021-07-11 19:53:04 +10:00 committed by GitHub
commit 0b9fbd9050
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
151 changed files with 2634 additions and 2208 deletions

View File

@ -1,5 +1,5 @@
workspace(
name = "net_ankiweb_anki",
name = "ankidesktop",
managed_directories = {"@npm": [
"ts/node_modules",
]},

View File

@ -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()

View File

@ -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",
+ )

0
proto/.top_level Normal file
View File

32
proto/BUILD.bazel Normal file
View File

@ -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",
])

63
proto/anki/backend.proto Normal file
View File

@ -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;
}

View File

@ -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;
}

64
proto/anki/cards.proto Normal file
View File

@ -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;
}

111
proto/anki/collection.proto Normal file
View File

@ -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;
}
}

118
proto/anki/config.proto Normal file
View File

@ -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;
}

145
proto/anki/deckconfig.proto Normal file
View File

@ -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;
}

188
proto/anki/decks.proto Normal file
View File

@ -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;
}

44
proto/anki/generic.proto Normal file
View File

@ -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;
}

42
proto/anki/i18n.proto Normal file
View File

@ -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<string, TranslateArgValue> 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;
}

32
proto/anki/media.proto Normal file
View File

@ -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;
}

106
proto/anki/notes.proto Normal file
View File

@ -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;
}

176
proto/anki/notetypes.proto Normal file
View File

@ -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;
}

219
proto/anki/scheduler.proto Normal file
View File

@ -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;
}

177
proto/anki/search.proto Normal file
View File

@ -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;
}

64
proto/anki/stats.proto Normal file
View File

@ -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;
}

76
proto/anki/sync.proto Normal file
View File

@ -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;
}

60
proto/anki/tags.proto Normal file
View File

@ -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;
}

View File

@ -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],

76
proto/format.bzl Normal file
View File

@ -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
)

View File

@ -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"]
)

View File

@ -14,6 +14,7 @@ ignored-classes=
NoteFieldsCheckResponse,
BackendError,
SetDeckCollapsedRequest,
ConfigKey,
[REPORTS]
output-format=colorized

View File

@ -31,6 +31,7 @@ py_test(
main = "tests/run_mypy.py",
deps = [
"//pylib/anki",
"//pylib/anki:proto",
requirement("mypy"),
],
)

View File

@ -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"],
)

View File

@ -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"]

View File

@ -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",

View File

@ -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()

View File

@ -1 +0,0 @@
../../../bazel-bin/pylib/anki/_backend/backend_pb2.pyi

View File

@ -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:

1
pylib/anki/backend_pb2.pyi Symbolic link
View File

@ -0,0 +1 @@
../../bazel-bin/pylib/anki/backend_pb2.pyi

View File

@ -0,0 +1 @@
../../bazel-bin/pylib/anki/card_rendering_pb2.pyi

View File

@ -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,

1
pylib/anki/cards_pb2.pyi Symbolic link
View File

@ -0,0 +1 @@
../../bazel-bin/pylib/anki/cards_pb2.pyi

View File

@ -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)

View File

@ -0,0 +1 @@
../../bazel-bin/pylib/anki/collection_pb2.pyi

View File

@ -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:

1
pylib/anki/config_pb2.pyi Symbolic link
View File

@ -0,0 +1 @@
../../bazel-bin/pylib/anki/config_pb2.pyi

View File

@ -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:

View File

@ -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

View File

@ -0,0 +1 @@
../../bazel-bin/pylib/anki/deckconfig_pb2.pyi

View File

@ -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]

1
pylib/anki/decks_pb2.pyi Symbolic link
View File

@ -0,0 +1 @@
../../bazel-bin/pylib/anki/decks_pb2.pyi

1
pylib/anki/generic_pb2.pyi Symbolic link
View File

@ -0,0 +1 @@
../../bazel-bin/pylib/anki/generic_pb2.pyi

View File

@ -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}"

1
pylib/anki/i18n_pb2.pyi Symbolic link
View File

@ -0,0 +1 @@
../../bazel-bin/pylib/anki/i18n_pb2.pyi

View File

@ -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

View File

@ -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

View File

@ -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=[

View File

@ -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

1
pylib/anki/media_pb2.pyi Symbolic link
View File

@ -0,0 +1 @@
../../bazel-bin/pylib/anki/media_pb2.pyi

View File

@ -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)

View File

@ -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,

1
pylib/anki/notes_pb2.pyi Symbolic link
View File

@ -0,0 +1 @@
../../bazel-bin/pylib/anki/notes_pb2.pyi

View File

@ -0,0 +1 @@
../../bazel-bin/pylib/anki/notetypes_pb2.pyi

View File

@ -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(

View File

@ -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]

View File

@ -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":

View File

@ -0,0 +1 @@
../../bazel-bin/pylib/anki/scheduler_pb2.pyi

1
pylib/anki/search_pb2.pyi Symbolic link
View File

@ -0,0 +1 @@
../../bazel-bin/pylib/anki/search_pb2.pyi

View File

@ -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

1
pylib/anki/stats_pb2.pyi Symbolic link
View File

@ -0,0 +1 @@
../../bazel-bin/pylib/anki/stats_pb2.pyi

View File

@ -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

View File

@ -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

1
pylib/anki/sync_pb2.pyi Symbolic link
View File

@ -0,0 +1 @@
../../bazel-bin/pylib/anki/sync_pb2.pyi

View File

@ -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

View File

@ -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"

1
pylib/anki/tags_pb2.pyi Symbolic link
View File

@ -0,0 +1 @@
../../bazel-bin/pylib/anki/tags_pb2.pyi

View File

@ -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))

View File

@ -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",

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)))

View File

@ -9,7 +9,7 @@ ignored-classes=
BrowserColumns,
BrowserRow,
SearchNode,
Config,
ConfigKey,
OpChanges,
UnburyDeckRequest,
CardAnswer,

View File

@ -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:

View File

@ -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:

View File

@ -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",
]

View File

@ -137,7 +137,6 @@ class Editor:
],
js=[
"js/vendor/jquery.min.js",
"js/vendor/protobuf.min.js",
"js/editor.js",
],
context=self,

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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,

View File

@ -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 *

View File

@ -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"),
],

View File

@ -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"])

File diff suppressed because it is too large Load Diff

View File

@ -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<super::{output_type}>;\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<dyn prost_build::ServiceGenerator> {
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();
}

View File

@ -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<StringKeyProto> for StringKey {
}
}
impl From<pb::config::String> 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<pb::Json> {
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<pb::Bool> {
fn get_config_bool(&self, input: pb::GetConfigBoolRequest) -> Result<pb::Bool> {
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<pb::String> {
fn get_config_string(&self, input: pb::GetConfigStringRequest) -> Result<pb::String> {
self.with_col(|col| {
Ok(pb::String {
val: col.get_config_string(input.key().into()),

View File

@ -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),

View File

@ -143,6 +143,13 @@ impl NotesService for Backend {
})
})
}
fn get_single_notetype_of_notes(&self, input: pb::NoteIds) -> Result<pb::NotetypeId> {
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<i64>) -> Vec<NoteId> {

View File

@ -159,13 +159,6 @@ impl NotetypesService for Backend {
})
}
fn get_single_notetype_of_notes(&self, input: pb::NoteIds) -> Result<pb::NotetypeId> {
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,

View File

@ -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<pb::SchedTimingTodayResponse> {
@ -117,7 +117,7 @@ impl SchedulingService for Backend {
}
fn set_due_date(&self, input: pb::SetDueDateRequest) -> Result<pb::OpChanges> {
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))

View File

@ -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);

Some files were not shown because too many files have changed in this diff Show More