split out decks, deckconfig, notes, notetypes

This commit is contained in:
Damien Elmes 2021-07-10 20:44:22 +10:00
parent 18851ace47
commit 9e0a295ab9
20 changed files with 733 additions and 683 deletions

View file

@ -7,34 +7,10 @@ package anki.backend;
import "anki/generic.proto"; import "anki/generic.proto";
import "anki/cards.proto"; import "anki/cards.proto";
import "anki/decks.proto";
import "anki/collection.proto"; import "anki/collection.proto";
import "anki/notes.proto";
// IDs used in RPC calls import "anki/notetypes.proto";
///////////////////////////////////////////////////////////
message NotetypeId {
int64 ntid = 1;
}
message NoteId {
int64 nid = 1;
}
message NoteIds {
repeated int64 note_ids = 1;
}
message DeckId {
int64 did = 1;
}
message DeckIds {
repeated int64 dids = 1;
}
message DeckConfigId {
int64 dcid = 1;
}
// Backend methods // Backend methods
/////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////
@ -66,15 +42,15 @@ service SchedulingService {
rpc StudiedTodayMessage(StudiedTodayMessageRequest) returns (generic.String); rpc StudiedTodayMessage(StudiedTodayMessageRequest) returns (generic.String);
rpc UpdateStats(UpdateStatsRequest) returns (generic.Empty); rpc UpdateStats(UpdateStatsRequest) returns (generic.Empty);
rpc ExtendLimits(ExtendLimitsRequest) returns (generic.Empty); rpc ExtendLimits(ExtendLimitsRequest) returns (generic.Empty);
rpc CountsForDeckToday(DeckId) returns (CountsForDeckTodayResponse); rpc CountsForDeckToday(decks.DeckId) returns (CountsForDeckTodayResponse);
rpc CongratsInfo(generic.Empty) returns (CongratsInfoResponse); rpc CongratsInfo(generic.Empty) returns (CongratsInfoResponse);
rpc RestoreBuriedAndSuspendedCards(cards.CardIds) rpc RestoreBuriedAndSuspendedCards(cards.CardIds)
returns (collection.OpChanges); returns (collection.OpChanges);
rpc UnburyDeck(UnburyDeckRequest) returns (collection.OpChanges); rpc UnburyDeck(UnburyDeckRequest) returns (collection.OpChanges);
rpc BuryOrSuspendCards(BuryOrSuspendCardsRequest) rpc BuryOrSuspendCards(BuryOrSuspendCardsRequest)
returns (collection.OpChangesWithCount); returns (collection.OpChangesWithCount);
rpc EmptyFilteredDeck(DeckId) returns (collection.OpChanges); rpc EmptyFilteredDeck(decks.DeckId) returns (collection.OpChanges);
rpc RebuildFilteredDeck(DeckId) returns (collection.OpChangesWithCount); rpc RebuildFilteredDeck(decks.DeckId) returns (collection.OpChangesWithCount);
rpc ScheduleCardsAsNew(ScheduleCardsAsNewRequest) rpc ScheduleCardsAsNew(ScheduleCardsAsNewRequest)
returns (collection.OpChanges); returns (collection.OpChanges);
rpc SetDueDate(SetDueDateRequest) returns (collection.OpChanges); rpc SetDueDate(SetDueDateRequest) returns (collection.OpChanges);
@ -88,49 +64,6 @@ service SchedulingService {
rpc GetQueuedCards(GetQueuedCardsRequest) returns (QueuedCards); rpc GetQueuedCards(GetQueuedCardsRequest) returns (QueuedCards);
} }
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);
}
service NotesService {
rpc NewNote(NotetypeId) returns (Note);
rpc AddNote(AddNoteRequest) returns (AddNoteResponse);
rpc DefaultsForAdding(DefaultsForAddingRequest) returns (DeckAndNotetype);
rpc DefaultDeckForNotetype(NotetypeId) returns (DeckId);
rpc UpdateNote(UpdateNoteRequest) returns (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);
}
service SyncService { service SyncService {
rpc SyncMedia(SyncAuth) returns (generic.Empty); rpc SyncMedia(SyncAuth) returns (generic.Empty);
rpc AbortSync(generic.Empty) returns (generic.Empty); rpc AbortSync(generic.Empty) returns (generic.Empty);
@ -158,28 +91,6 @@ service ConfigService {
rpc SetPreferences(Preferences) returns (collection.OpChanges); rpc SetPreferences(Preferences) returns (collection.OpChanges);
} }
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 GetSingleNotetypeOfNotes(NoteIds) returns (NotetypeId);
rpc GetChangeNotetypeInfo(GetChangeNotetypeInfoRequest)
returns (ChangeNotetypeInfo);
rpc ChangeNotetype(ChangeNotetypeRequest) returns (collection.OpChanges);
}
service CardRenderingService { service CardRenderingService {
rpc ExtractAVTags(ExtractAVTagsRequest) returns (ExtractAVTagsResponse); rpc ExtractAVTags(ExtractAVTagsRequest) returns (ExtractAVTagsResponse);
rpc ExtractLatex(ExtractLatexRequest) returns (ExtractLatexResponse); rpc ExtractLatex(ExtractLatexRequest) returns (ExtractLatexResponse);
@ -194,18 +105,6 @@ service CardRenderingService {
rpc RenderMarkdown(RenderMarkdownRequest) returns (generic.String); rpc RenderMarkdown(RenderMarkdownRequest) returns (generic.String);
} }
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(DeckId) returns (DeckConfigsForUpdate);
rpc UpdateDeckConfigs(UpdateDeckConfigsRequest)
returns (collection.OpChanges);
}
service TagsService { service TagsService {
rpc ClearUnusedTags(generic.Empty) returns (collection.OpChangesWithCount); rpc ClearUnusedTags(generic.Empty) returns (collection.OpChangesWithCount);
rpc AllTags(generic.Empty) returns (generic.StringList); rpc AllTags(generic.Empty) returns (generic.StringList);
@ -250,257 +149,6 @@ service MediaService {
rpc RestoreTrash(generic.Empty) returns (generic.Empty); rpc RestoreTrash(generic.Empty) returns (generic.Empty);
} }
// Protobuf stored in .anki2 files
// These should be moved to a separate file in the future
///////////////////////////////////////////////////////////
message DeckConfig {
message Config {
enum NewCardInsertOrder {
NEW_CARD_INSERT_ORDER_DUE = 0;
NEW_CARD_INSERT_ORDER_RANDOM = 1;
}
enum NewCardGatherPriority {
NEW_CARD_GATHER_PRIORITY_DECK = 0;
NEW_CARD_GATHER_PRIORITY_LOWEST_POSITION = 1;
NEW_CARD_GATHER_PRIORITY_HIGHEST_POSITION = 2;
}
enum NewCardSortOrder {
NEW_CARD_SORT_ORDER_TEMPLATE_THEN_LOWEST_POSITION = 0;
NEW_CARD_SORT_ORDER_TEMPLATE_THEN_HIGHEST_POSITION = 1;
NEW_CARD_SORT_ORDER_TEMPLATE_THEN_RANDOM = 2;
NEW_CARD_SORT_ORDER_LOWEST_POSITION = 3;
NEW_CARD_SORT_ORDER_HIGHEST_POSITION = 4;
NEW_CARD_SORT_ORDER_RANDOM = 5;
}
enum ReviewCardOrder {
REVIEW_CARD_ORDER_DAY = 0;
REVIEW_CARD_ORDER_DAY_THEN_DECK = 1;
REVIEW_CARD_ORDER_DECK_THEN_DAY = 2;
REVIEW_CARD_ORDER_INTERVALS_ASCENDING = 3;
REVIEW_CARD_ORDER_INTERVALS_DESCENDING = 4;
// REVIEW_CARD_ORDER_RELATIVE_OVERDUE = 3;
}
enum ReviewMix {
REVIEW_MIX_MIX_WITH_REVIEWS = 0;
REVIEW_MIX_AFTER_REVIEWS = 1;
REVIEW_MIX_BEFORE_REVIEWS = 2;
}
enum LeechAction {
LEECH_ACTION_SUSPEND = 0;
LEECH_ACTION_TAG_ONLY = 1;
}
repeated float learn_steps = 1;
repeated float relearn_steps = 2;
reserved 3 to 8;
uint32 new_per_day = 9;
uint32 reviews_per_day = 10;
uint32 new_per_day_minimum = 29;
float initial_ease = 11;
float easy_multiplier = 12;
float hard_multiplier = 13;
float lapse_multiplier = 14;
float interval_multiplier = 15;
uint32 maximum_review_interval = 16;
uint32 minimum_lapse_interval = 17;
uint32 graduating_interval_good = 18;
uint32 graduating_interval_easy = 19;
NewCardInsertOrder new_card_insert_order = 20;
NewCardGatherPriority new_card_gather_priority = 34;
NewCardSortOrder new_card_sort_order = 32;
ReviewMix new_mix = 30;
ReviewCardOrder review_order = 33;
ReviewMix interday_learning_mix = 31;
LeechAction leech_action = 21;
uint32 leech_threshold = 22;
bool disable_autoplay = 23;
uint32 cap_answer_time_to_secs = 24;
bool show_timer = 25;
bool skip_question_when_replaying_answer = 26;
bool bury_new = 27;
bool bury_reviews = 28;
bytes other = 255;
}
int64 id = 1;
string name = 2;
int64 mtime_secs = 3;
int32 usn = 4;
Config config = 5;
}
message Deck {
message Common {
bool study_collapsed = 1;
bool browser_collapsed = 2;
uint32 last_day_studied = 3;
int32 new_studied = 4;
int32 review_studied = 5;
int32 milliseconds_studied = 7;
// previously set in the v1 scheduler,
// but not currently used for anything
int32 learning_studied = 6;
reserved 8 to 13;
bytes other = 255;
}
message Normal {
int64 config_id = 1;
uint32 extend_new = 2;
uint32 extend_review = 3;
string description = 4;
bool markdown_description = 5;
reserved 6 to 11;
}
message Filtered {
message SearchTerm {
enum Order {
OLDEST_REVIEWED_FIRST = 0;
RANDOM = 1;
INTERVALS_ASCENDING = 2;
INTERVALS_DESCENDING = 3;
LAPSES = 4;
ADDED = 5;
DUE = 6;
REVERSE_ADDED = 7;
DUE_PRIORITY = 8;
}
string search = 1;
uint32 limit = 2;
Order order = 3;
}
bool reschedule = 1;
repeated SearchTerm search_terms = 2;
// v1 scheduler only
repeated float delays = 3;
// v2 scheduler only
uint32 preview_delay = 4;
}
// a container to store the deck specifics in the DB
// as a tagged enum
message KindContainer {
oneof kind {
Normal normal = 1;
Filtered filtered = 2;
}
}
int64 id = 1;
string name = 2;
int64 mtime_secs = 3;
int32 usn = 4;
Common common = 5;
// the specifics are inlined here when sending data to clients,
// as otherwise an extra level of indirection would be required
oneof kind {
Normal normal = 6;
Filtered filtered = 7;
}
}
message Notetype {
message Config {
enum Kind {
KIND_NORMAL = 0;
KIND_CLOZE = 1;
}
message CardRequirement {
enum Kind {
KIND_NONE = 0;
KIND_ANY = 1;
KIND_ALL = 2;
}
uint32 card_ord = 1;
Kind kind = 2;
repeated uint32 field_ords = 3;
}
Kind kind = 1;
uint32 sort_field_idx = 2;
string css = 3;
/// This is now stored separately; retrieve with DefaultsForAdding()
int64 target_deck_id_unused = 4;
string latex_pre = 5;
string latex_post = 6;
bool latex_svg = 7;
repeated CardRequirement reqs = 8;
bytes other = 255;
}
message Field {
message Config {
bool sticky = 1;
bool rtl = 2;
string font_name = 3;
uint32 font_size = 4;
bytes other = 255;
}
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;
}
// Database objects
///////////////////////////////////////////////////////////
message Note {
int64 id = 1;
string guid = 2;
int64 notetype_id = 3;
uint32 mtime_secs = 4;
int32 usn = 5;
repeated string tags = 6;
repeated string fields = 7;
}
// Backend // Backend
/////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////
@ -551,43 +199,20 @@ message SchedTimingTodayResponse {
int64 next_day_at = 2; int64 next_day_at = 2;
} }
message DeckTreeRequest {
// if non-zero, counts for the provided timestamp will be included
int64 now = 1;
int64 top_deck_id = 2;
}
message DeckTreeNode {
int64 deck_id = 1;
string name = 2;
uint32 level = 4;
bool collapsed = 5;
uint32 review_count = 6;
uint32 learn_count = 7;
uint32 new_count = 8;
bool filtered = 16;
// low index so key can be packed into a byte, but at bottom
// to make debug output easier to read
repeated DeckTreeNode children = 3;
}
message RenderExistingCardRequest { message RenderExistingCardRequest {
int64 card_id = 1; int64 card_id = 1;
bool browser = 2; bool browser = 2;
} }
message RenderUncommittedCardRequest { message RenderUncommittedCardRequest {
Note note = 1; notes.Note note = 1;
uint32 card_ord = 2; uint32 card_ord = 2;
Notetype.Template template = 3; notetypes.Notetype.Template template = 3;
bool fill_empty = 4; bool fill_empty = 4;
} }
message RenderUncommittedCardLegacyRequest { message RenderUncommittedCardLegacyRequest {
Note note = 1; notes.Note note = 1;
uint32 card_ord = 2; uint32 card_ord = 2;
bytes template = 3; bytes template = 3;
bool fill_empty = 4; bool fill_empty = 4;
@ -782,53 +407,11 @@ message ReplaceSearchNodeRequest {
SearchNode replacement_node = 2; SearchNode replacement_node = 2;
} }
message DeckConfigsForUpdate {
message ConfigWithExtra {
DeckConfig config = 1;
uint32 use_count = 2;
}
message CurrentDeck {
string name = 1;
int64 config_id = 2;
repeated int64 parent_config_ids = 3;
}
repeated ConfigWithExtra all_config = 1;
CurrentDeck current_deck = 2;
DeckConfig defaults = 3;
bool schema_modified = 4;
bool v3_scheduler = 5;
bool have_addons = 6;
// only applies to v3 scheduler
string card_state_customizer = 7;
}
message UpdateDeckConfigsRequest {
int64 target_deck_id = 1;
/// Unchanged, non-selected configs can be omitted. Deck will
/// be set to whichever entry comes last.
repeated DeckConfig configs = 2;
repeated int64 removed_config_ids = 3;
bool apply_to_children = 4;
string card_state_customizer = 5;
}
message SetTagCollapsedRequest { message SetTagCollapsedRequest {
string name = 1; string name = 1;
bool collapsed = 2; bool collapsed = 2;
} }
message SetDeckCollapsedRequest {
enum Scope {
REVIEWER = 0;
BROWSER = 1;
}
int64 deck_id = 1;
bool collapsed = 2;
Scope scope = 3;
}
message GetChangedTagsResponse { message GetChangedTagsResponse {
repeated string tags = 1; repeated string tags = 1;
} }
@ -856,57 +439,6 @@ message SetConfigJsonRequest {
bool undoable = 3; bool undoable = 3;
} }
message StockNotetype {
enum Kind {
BASIC = 0;
BASIC_AND_REVERSED = 1;
BASIC_OPTIONAL_REVERSED = 2;
BASIC_TYPING = 3;
CLOZE = 4;
}
Kind kind = 1;
}
message NotetypeNames {
repeated NotetypeNameId entries = 1;
}
message NotetypeUseCounts {
repeated NotetypeNameIdUseCount entries = 1;
}
message NotetypeNameId {
int64 id = 1;
string name = 2;
}
message NotetypeNameIdUseCount {
int64 id = 1;
string name = 2;
uint32 use_count = 3;
}
message AddOrUpdateNotetypeRequest {
bytes json = 1;
bool preserve_usn_and_mtime = 2;
}
message AddNoteRequest {
Note note = 1;
int64 deck_id = 2;
}
message AddNoteResponse {
int64 note_id = 1;
collection.OpChanges changes = 2;
}
message UpdateNoteRequest {
Note note = 1;
bool skip_undo_entry = 2;
}
message EmptyCardsReport { message EmptyCardsReport {
message NoteWithEmptyCards { message NoteWithEmptyCards {
int64 note_id = 1; int64 note_id = 1;
@ -917,28 +449,6 @@ message EmptyCardsReport {
repeated NoteWithEmptyCards notes = 2; repeated NoteWithEmptyCards notes = 2;
} }
message DeckNames {
repeated DeckNameId entries = 1;
}
message DeckNameId {
int64 id = 1;
string name = 2;
}
message AddOrUpdateDeckLegacyRequest {
bytes deck = 1;
bool preserve_usn_and_mtime = 2;
}
message FieldNamesForNotesRequest {
repeated int64 nids = 1;
}
message FieldNamesForNotesResponse {
repeated string fields = 1;
}
message FindAndReplaceRequest { message FindAndReplaceRequest {
repeated int64 nids = 1; repeated int64 nids = 1;
string search = 2; string search = 2;
@ -992,12 +502,6 @@ message BrowserRow {
uint32 font_size = 4; uint32 font_size = 4;
} }
message AfterNoteUpdatesRequest {
repeated int64 nids = 1;
bool mark_notes_modified = 2;
bool generate_cards = 3;
}
message NoteIdsAndTagsRequest { message NoteIdsAndTagsRequest {
repeated int64 note_ids = 1; repeated int64 note_ids = 1;
string tags = 2; string tags = 2;
@ -1049,33 +553,6 @@ message Preferences {
Editing editing = 3; Editing editing = 3;
} }
message ClozeNumbersInNoteResponse {
repeated uint32 numbers = 1;
}
message GetDeckNamesRequest {
bool skip_empty_default = 1;
// if unset, implies skip_empty_default
bool include_filtered = 2;
}
message ReparentDecksRequest {
repeated int64 deck_ids = 1;
int64 new_parent = 2;
}
message NoteFieldsCheckResponse {
enum State {
NORMAL = 0;
EMPTY = 1;
DUPLICATE = 2;
MISSING_CLOZE = 3;
NOTETYPE_NOT_CLOZE = 4;
FIELD_NOT_CLOZE = 5;
}
State state = 1;
}
message SyncLoginRequest { message SyncLoginRequest {
string username = 1; string username = 1;
string password = 2; string password = 2;
@ -1131,11 +608,6 @@ message SyncServerMethodRequest {
bytes data = 2; bytes data = 2;
} }
message RemoveNotesRequest {
repeated int64 note_ids = 1;
repeated int64 card_ids = 2;
}
message UpdateStatsRequest { message UpdateStatsRequest {
int64 deck_id = 1; int64 deck_id = 1;
int32 new_delta = 2; int32 new_delta = 2;
@ -1403,58 +875,3 @@ message QueuedCards {
uint32 learning_count = 3; uint32 learning_count = 3;
uint32 review_count = 4; uint32 review_count = 4;
} }
message DefaultsForAddingRequest {
int64 home_deck_of_current_review_card = 1;
}
message DeckAndNotetype {
int64 deck_id = 1;
int64 notetype_id = 2;
}
message RenameDeckRequest {
int64 deck_id = 1;
string new_name = 2;
}
message FilteredDeckForUpdate {
int64 id = 1;
string name = 2;
Deck.Filtered config = 3;
}
message 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;
}

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

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

View file

@ -12,6 +12,10 @@ import anki.backend_pb2
import anki.i18n_pb2 import anki.i18n_pb2
import anki.cards_pb2 import anki.cards_pb2
import anki.collection_pb2 import anki.collection_pb2
import anki.decks_pb2
import anki.deckconfig_pb2
import anki.notes_pb2
import anki.notetypes_pb2
import stringcase import stringcase
@ -171,6 +175,10 @@ service_modules = dict(
I18N=anki.i18n_pb2, I18N=anki.i18n_pb2,
COLLECTION=anki.collection_pb2, COLLECTION=anki.collection_pb2,
CARDS=anki.cards_pb2, CARDS=anki.cards_pb2,
NOTES=anki.notes_pb2,
DECKS=anki.decks_pb2,
DECK_CONFIG=anki.deckconfig_pb2,
NOTETYPES=anki.notetypes_pb2,
) )
for service in anki.backend_pb2.ServiceIndex.DESCRIPTOR.values: for service in anki.backend_pb2.ServiceIndex.DESCRIPTOR.values:

View file

@ -22,7 +22,6 @@ OpChanges = collection_pb2.OpChanges
OpChangesWithCount = collection_pb2.OpChangesWithCount OpChangesWithCount = collection_pb2.OpChangesWithCount
OpChangesWithId = collection_pb2.OpChangesWithId OpChangesWithId = collection_pb2.OpChangesWithId
OpChangesAfterUndo = collection_pb2.OpChangesAfterUndo OpChangesAfterUndo = collection_pb2.OpChangesAfterUndo
DefaultsForAdding = _pb.DeckAndNotetype
BrowserRow = _pb.BrowserRow BrowserRow = _pb.BrowserRow
BrowserColumns = _pb.BrowserColumns BrowserColumns = _pb.BrowserColumns
@ -370,7 +369,7 @@ class Collection(DeprecatedNamesMixin):
def defaults_for_adding( def defaults_for_adding(
self, *, current_review_card: Optional[Card] self, *, current_review_card: Optional[Card]
) -> DefaultsForAdding: ) -> anki.notes.DefaultsForAdding:
"""Get starting deck and notetype for add screen. """Get starting deck and notetype for add screen.
An option in the preferences controls whether this will be based on the current deck An option in the preferences controls whether this will be based on the current deck
or current notetype. or current notetype.

View file

@ -23,7 +23,7 @@ from typing import (
if TYPE_CHECKING: if TYPE_CHECKING:
import anki import anki
import anki.backend_pb2 as _pb from anki import deckconfig_pb2, decks_pb2
from anki._legacy import DeprecatedNamesMixin, deprecated, print_deprecation_warning from anki._legacy import DeprecatedNamesMixin, deprecated, print_deprecation_warning
from anki.cards import CardId from anki.cards import CardId
from anki.collection import OpChanges, OpChangesWithCount, OpChangesWithId 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 from anki.utils import from_json_bytes, ids2str, intTime, to_json_bytes
# public exports # public exports
DeckTreeNode = _pb.DeckTreeNode DeckTreeNode = decks_pb2.DeckTreeNode
DeckNameId = _pb.DeckNameId DeckNameId = decks_pb2.DeckNameId
FilteredDeckConfig = _pb.Deck.Filtered FilteredDeckConfig = decks_pb2.Deck.Filtered
DeckCollapseScope = _pb.SetDeckCollapsedRequest.Scope DeckCollapseScope = decks_pb2.SetDeckCollapsedRequest.Scope
DeckConfigsForUpdate = _pb.DeckConfigsForUpdate DeckConfigsForUpdate = deckconfig_pb2.DeckConfigsForUpdate
UpdateDeckConfigs = _pb.UpdateDeckConfigsRequest UpdateDeckConfigs = deckconfig_pb2.UpdateDeckConfigsRequest
# type aliases until we can move away from dicts # type aliases until we can move away from dicts
DeckDict = Dict[str, Any] DeckDict = Dict[str, Any]

View file

@ -12,7 +12,7 @@ import time
from typing import Any, Dict, List, NewType, Optional, Sequence, Tuple, Union from typing import Any, Dict, List, NewType, Optional, Sequence, Tuple, Union
import anki # pylint: disable=unused-import import anki # pylint: disable=unused-import
import anki.backend_pb2 as _pb from anki import notetypes_pb2
from anki._legacy import DeprecatedNamesMixin, deprecated, print_deprecation_warning from anki._legacy import DeprecatedNamesMixin, deprecated, print_deprecation_warning
from anki.collection import OpChanges, OpChangesWithId from anki.collection import OpChanges, OpChangesWithId
from anki.consts import * from anki.consts import *
@ -22,11 +22,11 @@ from anki.stdmodels import StockNotetypeKind
from anki.utils import checksum, from_json_bytes, to_json_bytes from anki.utils import checksum, from_json_bytes, to_json_bytes
# public exports # public exports
NotetypeNameId = _pb.NotetypeNameId NotetypeNameId = notetypes_pb2.NotetypeNameId
NotetypeNameIdUseCount = _pb.NotetypeNameIdUseCount NotetypeNameIdUseCount = notetypes_pb2.NotetypeNameIdUseCount
NotetypeNames = _pb.NotetypeNames NotetypeNames = notetypes_pb2.NotetypeNames
ChangeNotetypeInfo = _pb.ChangeNotetypeInfo ChangeNotetypeInfo = notetypes_pb2.ChangeNotetypeInfo
ChangeNotetypeRequest = _pb.ChangeNotetypeRequest ChangeNotetypeRequest = notetypes_pb2.ChangeNotetypeRequest
# legacy types # legacy types
NotetypeDict = Dict[str, Any] NotetypeDict = Dict[str, Any]
@ -459,7 +459,9 @@ and notes.mid = ? and cards.ord = ?""",
def _availClozeOrds( def _availClozeOrds(
self, notetype: NotetypeDict, flds: str, allow_empty: bool = True self, notetype: NotetypeDict, flds: str, allow_empty: bool = True
) -> List[int]: ) -> 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)) return list(self.col._backend.cloze_numbers_in_note(note))
# @deprecated(replaced_by=add_template) # @deprecated(replaced_by=add_template)

View file

@ -9,15 +9,15 @@ import copy
from typing import Any, List, NewType, Optional, Sequence, Tuple, Union from typing import Any, List, NewType, Optional, Sequence, Tuple, Union
import anki # pylint: disable=unused-import import anki # pylint: disable=unused-import
import anki.backend_pb2 as _pb from anki import hooks, notes_pb2
from anki import hooks
from anki._legacy import DeprecatedNamesMixin from anki._legacy import DeprecatedNamesMixin
from anki.consts import MODEL_STD from anki.consts import MODEL_STD
from anki.models import NotetypeDict, NotetypeId, TemplateDict from anki.models import NotetypeDict, NotetypeId, TemplateDict
from anki.utils import joinFields from anki.utils import joinFields
DuplicateOrEmptyResult = _pb.NoteFieldsCheckResponse.State DuplicateOrEmptyResult = notes_pb2.NoteFieldsCheckResponse.State
NoteFieldsCheckResult = _pb.NoteFieldsCheckResponse.State NoteFieldsCheckResult = notes_pb2.NoteFieldsCheckResponse.State
DefaultsForAdding = notes_pb2.DeckAndNotetype
# types # types
NoteId = NewType("NoteId", int) NoteId = NewType("NoteId", int)
@ -53,7 +53,7 @@ class Note(DeprecatedNamesMixin):
assert note assert note
self._load_from_backend_note(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.id = NoteId(note.id)
self.guid = note.guid self.guid = note.guid
self.mid = NotetypeId(note.notetype_id) self.mid = NotetypeId(note.notetype_id)
@ -63,9 +63,9 @@ class Note(DeprecatedNamesMixin):
self.fields = list(note.fields) self.fields = list(note.fields)
self._fmap = self.col.models.field_map(self.note_type()) 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) hooks.note_will_flush(self)
return _pb.Note( return notes_pb2.Note(
id=self.id, id=self.id,
guid=self.guid, guid=self.guid,
notetype_id=self.mid, notetype_id=self.mid,

View file

@ -5,6 +5,7 @@ from __future__ import annotations
import anki import anki
import anki.backend_pb2 as _pb import anki.backend_pb2 as _pb
from anki import decks_pb2
from anki.collection import OpChanges, OpChangesWithCount, OpChangesWithId from anki.collection import OpChanges, OpChangesWithCount, OpChangesWithId
from anki.config import Config from anki.config import Config
@ -22,7 +23,7 @@ from anki.utils import ids2str, intTime
CongratsInfo = _pb.CongratsInfoResponse CongratsInfo = _pb.CongratsInfoResponse
UnburyDeck = _pb.UnburyDeckRequest UnburyDeck = _pb.UnburyDeckRequest
BuryOrSuspend = _pb.BuryOrSuspendCardsRequest BuryOrSuspend = _pb.BuryOrSuspendCardsRequest
FilteredDeckForUpdate = _pb.FilteredDeckForUpdate FilteredDeckForUpdate = decks_pb2.FilteredDeckForUpdate
class SchedulerBase: class SchedulerBase:

View file

@ -6,11 +6,11 @@ from __future__ import annotations
from typing import Any, Callable, List, Tuple from typing import Any, Callable, List, Tuple
import anki import anki
import anki.backend_pb2 as _pb from anki import notetypes_pb2
from anki.utils import from_json_bytes from anki.utils import from_json_bytes
# pylint: disable=no-member # pylint: disable=no-member
StockNotetypeKind = _pb.StockNotetype.Kind StockNotetypeKind = notetypes_pb2.StockNotetype.Kind
# add-on authors can add ("note type name", function) # add-on authors can add ("note type name", function)
# to this list to have it shown in the add/clone note type screen # to this list to have it shown in the add/clone note type screen

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> { 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( fn get_change_notetype_info(
&self, &self,
input: pb::GetChangeNotetypeInfoRequest, input: pb::GetChangeNotetypeInfoRequest,

View file

@ -1,24 +1,24 @@
// Copyright: Ankitects Pty Ltd and contributors // Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
pub mod backend { macro_rules! protobuf {
include!(concat!(env!("OUT_DIR"), "/anki.backend.rs")); ($ident:ident) => {
pub mod $ident {
include!(concat!(
env!("OUT_DIR"),
concat!("/anki.", stringify!($ident), ".rs")
));
} }
pub mod i18n { pub use $ident::*;
include!(concat!(env!("OUT_DIR"), "/anki.i18n.rs")); };
}
pub mod generic {
include!(concat!(env!("OUT_DIR"), "/anki.generic.rs"));
}
pub mod cards {
include!(concat!(env!("OUT_DIR"), "/anki.cards.rs"));
}
pub mod collection {
include!(concat!(env!("OUT_DIR"), "/anki.collection.rs"));
} }
pub use backend::*; protobuf!(backend);
pub use cards::*; protobuf!(notes);
pub use collection::*; protobuf!(notetypes);
pub use generic::*; protobuf!(decks);
pub use i18n::*; protobuf!(deckconfig);
protobuf!(i18n);
protobuf!(cards);
protobuf!(generic);
protobuf!(collection);

View file

@ -5,7 +5,7 @@
@typescript-eslint/no-explicit-any: "off", @typescript-eslint/no-explicit-any: "off",
*/ */
import { Backend } from "lib/proto"; import { Notetypes } from "lib/proto";
import { ChangeNotetypeState, negativeOneToNull, MapContext } from "./lib"; import { ChangeNotetypeState, negativeOneToNull, MapContext } from "./lib";
import { get } from "svelte/store"; import { get } from "svelte/store";
@ -64,15 +64,15 @@ const exampleInfoSame = {
function differentState(): ChangeNotetypeState { function differentState(): ChangeNotetypeState {
return new ChangeNotetypeState( return new ChangeNotetypeState(
Backend.NotetypeNames.fromObject(exampleNames), Notetypes.NotetypeNames.fromObject(exampleNames),
Backend.ChangeNotetypeInfo.fromObject(exampleInfoDifferent) Notetypes.ChangeNotetypeInfo.fromObject(exampleInfoDifferent)
); );
} }
function sameState(): ChangeNotetypeState { function sameState(): ChangeNotetypeState {
return new ChangeNotetypeState( return new ChangeNotetypeState(
Backend.NotetypeNames.fromObject(exampleNames), Notetypes.NotetypeNames.fromObject(exampleNames),
Backend.ChangeNotetypeInfo.fromObject(exampleInfoSame) Notetypes.ChangeNotetypeInfo.fromObject(exampleInfoSame)
); );
} }

View file

@ -5,20 +5,22 @@
@typescript-eslint/no-non-null-assertion: "off", @typescript-eslint/no-non-null-assertion: "off",
*/ */
import { Backend } from "lib/proto"; import { Notetypes } from "lib/proto";
import { postRequest } from "lib/postrequest"; import { postRequest } from "lib/postrequest";
import { readable, Readable } from "svelte/store"; import { readable, Readable } from "svelte/store";
import { isEqual } from "lodash-es"; import { isEqual } from "lodash-es";
export async function getNotetypeNames(): Promise<Backend.NotetypeNames> { export async function getNotetypeNames(): Promise<Notetypes.NotetypeNames> {
return Backend.NotetypeNames.decode(await postRequest("/_anki/notetypeNames", "")); return Notetypes.NotetypeNames.decode(
await postRequest("/_anki/notetypeNames", "")
);
} }
export async function getChangeNotetypeInfo( export async function getChangeNotetypeInfo(
oldNotetypeId: number, oldNotetypeId: number,
newNotetypeId: number newNotetypeId: number
): Promise<Backend.ChangeNotetypeInfo> { ): Promise<Notetypes.ChangeNotetypeInfo> {
return Backend.ChangeNotetypeInfo.decode( return Notetypes.ChangeNotetypeInfo.decode(
await postRequest( await postRequest(
"/_anki/changeNotetypeInfo", "/_anki/changeNotetypeInfo",
JSON.stringify({ oldNotetypeId, newNotetypeId }) JSON.stringify({ oldNotetypeId, newNotetypeId })
@ -27,9 +29,9 @@ export async function getChangeNotetypeInfo(
} }
export async function changeNotetype( export async function changeNotetype(
input: Backend.ChangeNotetypeRequest input: Notetypes.ChangeNotetypeRequest
): Promise<void> { ): Promise<void> {
const data: Uint8Array = Backend.ChangeNotetypeRequest.encode(input).finish(); const data: Uint8Array = Notetypes.ChangeNotetypeRequest.encode(input).finish();
await postRequest("/_anki/changeNotetype", data); await postRequest("/_anki/changeNotetype", data);
return; return;
} }
@ -47,9 +49,9 @@ export function negativeOneToNull(list: number[]): (number | null)[] {
export class ChangeNotetypeInfoWrapper { export class ChangeNotetypeInfoWrapper {
fields: (number | null)[]; fields: (number | null)[];
templates?: (number | null)[]; templates?: (number | null)[];
readonly info: Backend.ChangeNotetypeInfo; readonly info: Notetypes.ChangeNotetypeInfo;
constructor(info: Backend.ChangeNotetypeInfo) { constructor(info: Notetypes.ChangeNotetypeInfo) {
this.info = info; this.info = info;
const templates = info.input!.newTemplates!; const templates = info.input!.newTemplates!;
if (templates.length > 0) { if (templates.length > 0) {
@ -111,13 +113,13 @@ export class ChangeNotetypeInfoWrapper {
); );
} }
input(): Backend.ChangeNotetypeRequest { input(): Notetypes.ChangeNotetypeRequest {
return this.info.input as Backend.ChangeNotetypeRequest; return this.info.input as Notetypes.ChangeNotetypeRequest;
} }
/// Pack changes back into input message for saving. /// Pack changes back into input message for saving.
intoInput(): Backend.ChangeNotetypeRequest { intoInput(): Notetypes.ChangeNotetypeRequest {
const input = this.info.input as Backend.ChangeNotetypeRequest; const input = this.info.input as Notetypes.ChangeNotetypeRequest;
input.newFields = nullToNegativeOne(this.fields); input.newFields = nullToNegativeOne(this.fields);
if (this.templates) { if (this.templates) {
input.newTemplates = nullToNegativeOne(this.templates); input.newTemplates = nullToNegativeOne(this.templates);
@ -143,10 +145,13 @@ export class ChangeNotetypeState {
private info_: ChangeNotetypeInfoWrapper; private info_: ChangeNotetypeInfoWrapper;
private infoSetter!: (val: ChangeNotetypeInfoWrapper) => void; private infoSetter!: (val: ChangeNotetypeInfoWrapper) => void;
private notetypeNames: Backend.NotetypeNames; private notetypeNames: Notetypes.NotetypeNames;
private notetypesSetter!: (val: NotetypeListEntry[]) => void; private notetypesSetter!: (val: NotetypeListEntry[]) => void;
constructor(notetypes: Backend.NotetypeNames, info: Backend.ChangeNotetypeInfo) { constructor(
notetypes: Notetypes.NotetypeNames,
info: Notetypes.ChangeNotetypeInfo
) {
this.info_ = new ChangeNotetypeInfoWrapper(info); this.info_ = new ChangeNotetypeInfoWrapper(info);
this.info = readable(this.info_, (set) => { this.info = readable(this.info_, (set) => {
this.infoSetter = set; this.infoSetter = set;
@ -197,7 +202,7 @@ export class ChangeNotetypeState {
await changeNotetype(this.dataForSaving()); await changeNotetype(this.dataForSaving());
} }
dataForSaving(): Backend.ChangeNotetypeRequest { dataForSaving(): Notetypes.ChangeNotetypeRequest {
return this.info_.intoInput(); return this.info_.intoInput();
} }

View file

@ -5,7 +5,7 @@
@typescript-eslint/no-explicit-any: "off", @typescript-eslint/no-explicit-any: "off",
*/ */
import { Backend } from "lib/proto"; import { DeckConfig } from "lib/proto";
import { DeckOptionsState } from "./lib"; import { DeckOptionsState } from "./lib";
import { get } from "svelte/store"; import { get } from "svelte/store";
@ -94,7 +94,7 @@ const exampleData = {
function startingState(): DeckOptionsState { function startingState(): DeckOptionsState {
return new DeckOptionsState( return new DeckOptionsState(
123, 123,
Backend.DeckConfigsForUpdate.fromObject(exampleData) DeckConfig.DeckConfigsForUpdate.fromObject(exampleData)
); );
} }

View file

@ -5,7 +5,7 @@
@typescript-eslint/no-non-null-assertion: "off", @typescript-eslint/no-non-null-assertion: "off",
*/ */
import { Backend } from "lib/proto"; import { DeckConfig } from "lib/proto";
import { postRequest } from "lib/postrequest"; import { postRequest } from "lib/postrequest";
import { Writable, writable, get, Readable, readable } from "svelte/store"; import { Writable, writable, get, Readable, readable } from "svelte/store";
import { isEqual, cloneDeep } from "lodash-es"; import { isEqual, cloneDeep } from "lodash-es";
@ -14,16 +14,16 @@ import type { DynamicSvelteComponent } from "sveltelib/dynamicComponent";
export async function getDeckOptionsInfo( export async function getDeckOptionsInfo(
deckId: number deckId: number
): Promise<Backend.DeckConfigsForUpdate> { ): Promise<DeckConfig.DeckConfigsForUpdate> {
return Backend.DeckConfigsForUpdate.decode( return DeckConfig.DeckConfigsForUpdate.decode(
await postRequest("/_anki/deckConfigsForUpdate", JSON.stringify({ deckId })) await postRequest("/_anki/deckConfigsForUpdate", JSON.stringify({ deckId }))
); );
} }
export async function saveDeckOptions( export async function saveDeckOptions(
input: Backend.UpdateDeckConfigsRequest input: DeckConfig.UpdateDeckConfigsRequest
): Promise<void> { ): Promise<void> {
const data: Uint8Array = Backend.UpdateDeckConfigsRequest.encode(input).finish(); const data: Uint8Array = DeckConfig.UpdateDeckConfigsRequest.encode(input).finish();
await postRequest("/_anki/updateDeckConfigs", data); await postRequest("/_anki/updateDeckConfigs", data);
return; return;
} }
@ -31,7 +31,7 @@ export async function saveDeckOptions(
export type DeckOptionsId = number; export type DeckOptionsId = number;
export interface ConfigWithCount { export interface ConfigWithCount {
config: Backend.DeckConfig; config: DeckConfig.DeckConfig;
useCount: number; useCount: number;
} }
@ -48,14 +48,14 @@ export interface ConfigListEntry {
current: boolean; current: boolean;
} }
type ConfigInner = Backend.DeckConfig.Config; type ConfigInner = DeckConfig.DeckConfig.Config;
export class DeckOptionsState { export class DeckOptionsState {
readonly currentConfig: Writable<ConfigInner>; readonly currentConfig: Writable<ConfigInner>;
readonly currentAuxData: Writable<Record<string, unknown>>; readonly currentAuxData: Writable<Record<string, unknown>>;
readonly configList: Readable<ConfigListEntry[]>; readonly configList: Readable<ConfigListEntry[]>;
readonly parentLimits: Readable<ParentLimits>; readonly parentLimits: Readable<ParentLimits>;
readonly cardStateCustomizer: Writable<string>; readonly cardStateCustomizer: Writable<string>;
readonly currentDeck: Backend.DeckConfigsForUpdate.CurrentDeck; readonly currentDeck: DeckConfig.DeckConfigsForUpdate.CurrentDeck;
readonly defaults: ConfigInner; readonly defaults: ConfigInner;
readonly addonComponents: Writable<DynamicSvelteComponent[]>; readonly addonComponents: Writable<DynamicSvelteComponent[]>;
readonly v3Scheduler: boolean; readonly v3Scheduler: boolean;
@ -70,12 +70,13 @@ export class DeckOptionsState {
private removedConfigs: DeckOptionsId[] = []; private removedConfigs: DeckOptionsId[] = [];
private schemaModified: boolean; private schemaModified: boolean;
constructor(targetDeckId: number, data: Backend.DeckConfigsForUpdate) { constructor(targetDeckId: number, data: DeckConfig.DeckConfigsForUpdate) {
this.targetDeckId = targetDeckId; this.targetDeckId = targetDeckId;
this.currentDeck = data.currentDeck as Backend.DeckConfigsForUpdate.CurrentDeck; this.currentDeck =
data.currentDeck as DeckConfig.DeckConfigsForUpdate.CurrentDeck;
this.defaults = data.defaults!.config! as ConfigInner; this.defaults = data.defaults!.config! as ConfigInner;
this.configs = data.allConfig.map((config) => { this.configs = data.allConfig.map((config) => {
const configInner = config.config as Backend.DeckConfig; const configInner = config.config as DeckConfig.DeckConfig;
return { return {
config: configInner, config: configInner,
useCount: config.useCount!, useCount: config.useCount!,
@ -150,9 +151,9 @@ export class DeckOptionsState {
} }
/// Clone the current config, making it current. /// Clone the current config, making it current.
private addConfigFrom(name: string, source: Backend.DeckConfig.IConfig): void { private addConfigFrom(name: string, source: DeckConfig.DeckConfig.IConfig): void {
const uniqueName = this.ensureNewNameUnique(name); const uniqueName = this.ensureNewNameUnique(name);
const config = Backend.DeckConfig.create({ const config = DeckConfig.DeckConfig.create({
id: 0, id: 0,
name: uniqueName, name: uniqueName,
config: cloneDeep(source), config: cloneDeep(source),
@ -188,7 +189,7 @@ export class DeckOptionsState {
this.updateConfigList(); this.updateConfigList();
} }
dataForSaving(applyToChildren: boolean): Backend.UpdateDeckConfigsRequest { dataForSaving(applyToChildren: boolean): DeckConfig.UpdateDeckConfigsRequest {
const modifiedConfigsExcludingCurrent = this.configs const modifiedConfigsExcludingCurrent = this.configs
.map((c) => c.config) .map((c) => c.config)
.filter((c, idx) => { .filter((c, idx) => {
@ -202,7 +203,7 @@ export class DeckOptionsState {
// current must come last, even if unmodified // current must come last, even if unmodified
this.configs[this.selectedIdx].config, this.configs[this.selectedIdx].config,
]; ];
return Backend.UpdateDeckConfigsRequest.create({ return DeckConfig.UpdateDeckConfigsRequest.create({
targetDeckId: this.targetDeckId, targetDeckId: this.targetDeckId,
removedConfigIds: this.removedConfigs, removedConfigIds: this.removedConfigs,
configs, configs,

View file

@ -4,4 +4,6 @@
import { anki } from "./backend_proto"; import { anki } from "./backend_proto";
import Backend = anki.backend; import Backend = anki.backend;
import Cards = anki.cards; import Cards = anki.cards;
export { Backend, Cards }; import DeckConfig = anki.deckconfig;
import Notetypes = anki.notetypes;
export { Backend, Cards, DeckConfig, Notetypes };