refactor protobuf handling for split/import

In order to split backend.proto into a more manageable size, the protobuf
handling needed to be updated. This took more time than I would have
liked, as each language handles protobuf differently:

- The Python Protobuf code ignores "package" directives, and relies
solely on how the files are laid out on disk. While it would have been
nice to keep the generated files in a private subpackage, Protobuf gets
confused if the files are located in a location that does not match
their original .proto layout, so the old approach of storing them in
_backend/ will not work. They now clutter up pylib/anki instead. I'm
rather annoyed by that, but alternatives seem to be having to add an extra
level to the Protobuf path, making the other languages suffer, or trying
to hack around the issue by munging sys.modules.
- Protobufjs fails to expose packages if they don't start with a capital
letter, despite the fact that lowercase packages are the norm in most
languages :-( This required a patch to fix.
- Rust was the easiest, as Prost is relatively straightforward compared
to Google's tools.

The Protobuf files are now stored in /proto/anki, with a separate package
for each file. I've split backend.proto into a few files as a test, but
the majority of that work is still to come.

The Python Protobuf building is a bit of a hack at the moment, hard-coding
"proto" as the top level folder, but it seems to get the job done for now.

Also changed the workspace name, as there seems to be a number of Bazel
repos moving away from the more awkward reverse DNS naming style.
This commit is contained in:
Damien Elmes 2021-07-10 17:50:18 +10:00
parent 1d4b58419e
commit 616db33c0e
81 changed files with 516 additions and 467 deletions

View file

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

View file

@ -1,7 +1,7 @@
load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
load("@bazel_skylib//lib:versions.bzl", "versions") load("@bazel_skylib//lib:versions.bzl", "versions")
load("@rules_rust//rust:repositories.bzl", "rust_repositories") load("@rules_rust//rust:repositories.bzl", "rust_repositories")
load("@net_ankiweb_anki//cargo:crates.bzl", "raze_fetch_remote_crates") load("@anki//cargo:crates.bzl", "raze_fetch_remote_crates")
load(":python.bzl", "setup_local_python") load(":python.bzl", "setup_local_python")
load(":protobuf.bzl", "setup_protobuf_binary") load(":protobuf.bzl", "setup_protobuf_binary")
load("//proto:format.bzl", "setup_clang_format") load("//proto:format.bzl", "setup_clang_format")
@ -35,7 +35,7 @@ def setup_deps():
pip_import( pip_import(
name = "py_deps", name = "py_deps",
requirements = "@net_ankiweb_anki//pip:requirements.txt", requirements = "@anki//pip:requirements.txt",
python_runtime = "@python//:python", python_runtime = "@python//:python",
) )
@ -44,12 +44,12 @@ def setup_deps():
python_runtime = "@python//:python", python_runtime = "@python//:python",
) )
node_repositories(package_json = ["@net_ankiweb_anki//ts:package.json"]) node_repositories(package_json = ["@anki//ts:package.json"])
yarn_install( yarn_install(
name = "npm", name = "npm",
package_json = "@net_ankiweb_anki//ts:package.json", package_json = "@anki//ts:package.json",
yarn_lock = "@net_ankiweb_anki//ts:yarn.lock", yarn_lock = "@anki//ts:yarn.lock",
) )
sass_repositories() sass_repositories()

View file

@ -74,14 +74,14 @@ index eff3d9df2..fb2e9f7fe 100644
python_runtime = "@python//:python", python_runtime = "@python//:python",
) )
- node_repositories(package_json = ["@net_ankiweb_anki//ts:package.json"]) - node_repositories(package_json = ["@anki//ts:package.json"])
+ native.local_repository( + native.local_repository(
+ name = "local_node", + name = "local_node",
+ path = "local_node", + path = "local_node",
+ ) + )
+ +
+ node_repositories( + node_repositories(
+ package_json = ["@net_ankiweb_anki//ts:package.json"], + package_json = ["@anki//ts:package.json"],
+ vendored_node = "@local_node//:node", + vendored_node = "@local_node//:node",
+ ) + )

0
proto/.top_level Normal file
View file

View file

@ -6,13 +6,27 @@ load("//proto:clang_format.bzl", "proto_format")
proto_format( proto_format(
name = "format", name = "format",
srcs = ["backend.proto"], srcs = glob(["**/*.proto"]),
visibility = ["//visibility:public"],
) )
proto_library( proto_library(
name = "backend_proto_lib", name = "backend_proto_lib",
srcs = ["backend.proto"], srcs = glob(["**/*.proto"]),
# "" removes the "proto/" prefix
strip_import_prefix = "",
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
) )
exports_files(["backend.proto"]) 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",
])

View file

@ -1,59 +1,11 @@
// 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
syntax = "proto3"; syntax = "proto3";
package BackendProto; package anki.backend;
// Generic containers import "anki/generic.proto";
///////////////////////////////////////////////////////////
message Empty {}
message OptionalInt32 {
sint32 val = 1;
}
message OptionalUInt32 {
uint32 val = 1;
}
message Int32 {
sint32 val = 1;
}
message UInt32 {
uint32 val = 1;
}
message Int64 {
int64 val = 1;
}
message String {
string val = 1;
}
message Json {
bytes json = 1;
}
message Bool {
bool val = 1;
}
message StringList {
repeated string vals = 1;
}
message OpChangesWithCount {
uint32 count = 1;
OpChanges changes = 2;
}
message OpChangesWithId {
int64 id = 1;
OpChanges changes = 2;
}
// IDs used in RPC calls // IDs used in RPC calls
/////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////
@ -115,13 +67,13 @@ enum ServiceIndex {
} }
service SchedulingService { service SchedulingService {
rpc SchedTimingToday(Empty) returns (SchedTimingTodayResponse); rpc SchedTimingToday(generic.Empty) returns (SchedTimingTodayResponse);
rpc StudiedToday(Empty) returns (String); rpc StudiedToday(generic.Empty) returns (generic.String);
rpc StudiedTodayMessage(StudiedTodayMessageRequest) returns (String); rpc StudiedTodayMessage(StudiedTodayMessageRequest) returns (generic.String);
rpc UpdateStats(UpdateStatsRequest) returns (Empty); rpc UpdateStats(UpdateStatsRequest) returns (generic.Empty);
rpc ExtendLimits(ExtendLimitsRequest) returns (Empty); rpc ExtendLimits(ExtendLimitsRequest) returns (generic.Empty);
rpc CountsForDeckToday(DeckId) returns (CountsForDeckTodayResponse); rpc CountsForDeckToday(DeckId) returns (CountsForDeckTodayResponse);
rpc CongratsInfo(Empty) returns (CongratsInfoResponse); rpc CongratsInfo(generic.Empty) returns (CongratsInfoResponse);
rpc RestoreBuriedAndSuspendedCards(CardIds) returns (OpChanges); rpc RestoreBuriedAndSuspendedCards(CardIds) returns (OpChanges);
rpc UnburyDeck(UnburyDeckRequest) returns (OpChanges); rpc UnburyDeck(UnburyDeckRequest) returns (OpChanges);
rpc BuryOrSuspendCards(BuryOrSuspendCardsRequest) rpc BuryOrSuspendCards(BuryOrSuspendCardsRequest)
@ -133,35 +85,35 @@ service SchedulingService {
rpc SortCards(SortCardsRequest) returns (OpChangesWithCount); rpc SortCards(SortCardsRequest) returns (OpChangesWithCount);
rpc SortDeck(SortDeckRequest) returns (OpChangesWithCount); rpc SortDeck(SortDeckRequest) returns (OpChangesWithCount);
rpc GetNextCardStates(CardId) returns (NextCardStates); rpc GetNextCardStates(CardId) returns (NextCardStates);
rpc DescribeNextStates(NextCardStates) returns (StringList); rpc DescribeNextStates(NextCardStates) returns (generic.StringList);
rpc StateIsLeech(SchedulingState) returns (Bool); rpc StateIsLeech(SchedulingState) returns (generic.Bool);
rpc AnswerCard(CardAnswer) returns (OpChanges); rpc AnswerCard(CardAnswer) returns (OpChanges);
rpc UpgradeScheduler(Empty) returns (Empty); rpc UpgradeScheduler(generic.Empty) returns (generic.Empty);
rpc GetQueuedCards(GetQueuedCardsRequest) returns (QueuedCards); rpc GetQueuedCards(GetQueuedCardsRequest) returns (QueuedCards);
} }
service DecksService { service DecksService {
rpc AddDeckLegacy(Json) returns (OpChangesWithId); rpc AddDeckLegacy(generic.Json) returns (OpChangesWithId);
rpc AddOrUpdateDeckLegacy(AddOrUpdateDeckLegacyRequest) returns (DeckId); rpc AddOrUpdateDeckLegacy(AddOrUpdateDeckLegacyRequest) returns (DeckId);
rpc DeckTree(DeckTreeRequest) returns (DeckTreeNode); rpc DeckTree(DeckTreeRequest) returns (DeckTreeNode);
rpc DeckTreeLegacy(Empty) returns (Json); rpc DeckTreeLegacy(generic.Empty) returns (generic.Json);
rpc GetAllDecksLegacy(Empty) returns (Json); rpc GetAllDecksLegacy(generic.Empty) returns (generic.Json);
rpc GetDeckIdByName(String) returns (DeckId); rpc GetDeckIdByName(generic.String) returns (DeckId);
rpc GetDeck(DeckId) returns (Deck); rpc GetDeck(DeckId) returns (Deck);
rpc UpdateDeck(Deck) returns (OpChanges); rpc UpdateDeck(Deck) returns (OpChanges);
rpc UpdateDeckLegacy(Json) returns (OpChanges); rpc UpdateDeckLegacy(generic.Json) returns (OpChanges);
rpc SetDeckCollapsed(SetDeckCollapsedRequest) returns (OpChanges); rpc SetDeckCollapsed(SetDeckCollapsedRequest) returns (OpChanges);
rpc GetDeckLegacy(DeckId) returns (Json); rpc GetDeckLegacy(DeckId) returns (generic.Json);
rpc GetDeckNames(GetDeckNamesRequest) returns (DeckNames); rpc GetDeckNames(GetDeckNamesRequest) returns (DeckNames);
rpc NewDeckLegacy(Bool) returns (Json); rpc NewDeckLegacy(generic.Bool) returns (generic.Json);
rpc RemoveDecks(DeckIds) returns (OpChangesWithCount); rpc RemoveDecks(DeckIds) returns (OpChangesWithCount);
rpc ReparentDecks(ReparentDecksRequest) returns (OpChangesWithCount); rpc ReparentDecks(ReparentDecksRequest) returns (OpChangesWithCount);
rpc RenameDeck(RenameDeckRequest) returns (OpChanges); rpc RenameDeck(RenameDeckRequest) returns (OpChanges);
rpc GetOrCreateFilteredDeck(DeckId) returns (FilteredDeckForUpdate); rpc GetOrCreateFilteredDeck(DeckId) returns (FilteredDeckForUpdate);
rpc AddOrUpdateFilteredDeck(FilteredDeckForUpdate) returns (OpChangesWithId); rpc AddOrUpdateFilteredDeck(FilteredDeckForUpdate) returns (OpChangesWithId);
rpc FilteredDeckOrderLabels(Empty) returns (StringList); rpc FilteredDeckOrderLabels(generic.Empty) returns (generic.StringList);
rpc SetCurrentDeck(DeckId) returns (OpChanges); rpc SetCurrentDeck(DeckId) returns (OpChanges);
rpc GetCurrentDeck(Empty) returns (Deck); rpc GetCurrentDeck(generic.Empty) returns (Deck);
} }
service NotesService { service NotesService {
@ -181,47 +133,48 @@ service NotesService {
} }
service SyncService { service SyncService {
rpc SyncMedia(SyncAuth) returns (Empty); rpc SyncMedia(SyncAuth) returns (generic.Empty);
rpc AbortSync(Empty) returns (Empty); rpc AbortSync(generic.Empty) returns (generic.Empty);
rpc AbortMediaSync(Empty) returns (Empty); rpc AbortMediaSync(generic.Empty) returns (generic.Empty);
rpc BeforeUpload(Empty) returns (Empty); rpc BeforeUpload(generic.Empty) returns (generic.Empty);
rpc SyncLogin(SyncLoginRequest) returns (SyncAuth); rpc SyncLogin(SyncLoginRequest) returns (SyncAuth);
rpc SyncStatus(SyncAuth) returns (SyncStatusResponse); rpc SyncStatus(SyncAuth) returns (SyncStatusResponse);
rpc SyncCollection(SyncAuth) returns (SyncCollectionResponse); rpc SyncCollection(SyncAuth) returns (SyncCollectionResponse);
rpc FullUpload(SyncAuth) returns (Empty); rpc FullUpload(SyncAuth) returns (generic.Empty);
rpc FullDownload(SyncAuth) returns (Empty); rpc FullDownload(SyncAuth) returns (generic.Empty);
rpc SyncServerMethod(SyncServerMethodRequest) returns (Json); rpc SyncServerMethod(SyncServerMethodRequest) returns (generic.Json);
} }
service ConfigService { service ConfigService {
rpc GetConfigJson(String) returns (Json); rpc GetConfigJson(generic.String) returns (generic.Json);
rpc SetConfigJson(SetConfigJsonRequest) returns (OpChanges); rpc SetConfigJson(SetConfigJsonRequest) returns (OpChanges);
rpc SetConfigJsonNoUndo(SetConfigJsonRequest) returns (Empty); rpc SetConfigJsonNoUndo(SetConfigJsonRequest) returns (generic.Empty);
rpc RemoveConfig(String) returns (OpChanges); rpc RemoveConfig(generic.String) returns (OpChanges);
rpc GetAllConfig(Empty) returns (Json); rpc GetAllConfig(generic.Empty) returns (generic.Json);
rpc GetConfigBool(Config.Bool) returns (Bool); rpc GetConfigBool(Config.Bool) returns (generic.Bool);
rpc SetConfigBool(SetConfigBoolRequest) returns (OpChanges); rpc SetConfigBool(SetConfigBoolRequest) returns (OpChanges);
rpc GetConfigString(Config.String) returns (String); rpc GetConfigString(Config.String) returns (generic.String);
rpc SetConfigString(SetConfigStringRequest) returns (OpChanges); rpc SetConfigString(SetConfigStringRequest) returns (OpChanges);
rpc GetPreferences(Empty) returns (Preferences); rpc GetPreferences(generic.Empty) returns (Preferences);
rpc SetPreferences(Preferences) returns (OpChanges); rpc SetPreferences(Preferences) returns (OpChanges);
} }
service NotetypesService { service NotetypesService {
rpc AddNotetype(Notetype) returns (OpChangesWithId); rpc AddNotetype(Notetype) returns (OpChangesWithId);
rpc UpdateNotetype(Notetype) returns (OpChanges); rpc UpdateNotetype(Notetype) returns (OpChanges);
rpc AddNotetypeLegacy(Json) returns (OpChangesWithId); rpc AddNotetypeLegacy(generic.Json) returns (OpChangesWithId);
rpc UpdateNotetypeLegacy(Json) returns (OpChanges); rpc UpdateNotetypeLegacy(generic.Json) returns (OpChanges);
rpc AddOrUpdateNotetype(AddOrUpdateNotetypeRequest) returns (NotetypeId); rpc AddOrUpdateNotetype(AddOrUpdateNotetypeRequest) returns (NotetypeId);
rpc GetStockNotetypeLegacy(StockNotetype) returns (Json); rpc GetStockNotetypeLegacy(StockNotetype) returns (generic.Json);
rpc GetNotetype(NotetypeId) returns (Notetype); rpc GetNotetype(NotetypeId) returns (Notetype);
rpc GetNotetypeLegacy(NotetypeId) returns (Json); rpc GetNotetypeLegacy(NotetypeId) returns (generic.Json);
rpc GetNotetypeNames(Empty) returns (NotetypeNames); rpc GetNotetypeNames(generic.Empty) returns (NotetypeNames);
rpc GetNotetypeNamesAndCounts(Empty) returns (NotetypeUseCounts); rpc GetNotetypeNamesAndCounts(generic.Empty) returns (NotetypeUseCounts);
rpc GetNotetypeIdByName(String) returns (NotetypeId); rpc GetNotetypeIdByName(generic.String) returns (NotetypeId);
rpc RemoveNotetype(NotetypeId) returns (OpChanges); rpc RemoveNotetype(NotetypeId) returns (OpChanges);
rpc GetAuxNotetypeConfigKey(GetAuxConfigKeyRequest) returns (String); rpc GetAuxNotetypeConfigKey(GetAuxConfigKeyRequest) returns (generic.String);
rpc GetAuxTemplateConfigKey(GetAuxTemplateConfigKeyRequest) returns (String); rpc GetAuxTemplateConfigKey(GetAuxTemplateConfigKeyRequest)
returns (generic.String);
rpc GetSingleNotetypeOfNotes(NoteIds) returns (NotetypeId); rpc GetSingleNotetypeOfNotes(NoteIds) returns (NotetypeId);
rpc GetChangeNotetypeInfo(GetChangeNotetypeInfoRequest) rpc GetChangeNotetypeInfo(GetChangeNotetypeInfoRequest)
returns (ChangeNotetypeInfo); returns (ChangeNotetypeInfo);
@ -231,34 +184,34 @@ service NotetypesService {
service CardRenderingService { service CardRenderingService {
rpc ExtractAVTags(ExtractAVTagsRequest) returns (ExtractAVTagsResponse); rpc ExtractAVTags(ExtractAVTagsRequest) returns (ExtractAVTagsResponse);
rpc ExtractLatex(ExtractLatexRequest) returns (ExtractLatexResponse); rpc ExtractLatex(ExtractLatexRequest) returns (ExtractLatexResponse);
rpc GetEmptyCards(Empty) returns (EmptyCardsReport); rpc GetEmptyCards(generic.Empty) returns (EmptyCardsReport);
rpc RenderExistingCard(RenderExistingCardRequest) rpc RenderExistingCard(RenderExistingCardRequest)
returns (RenderCardResponse); returns (RenderCardResponse);
rpc RenderUncommittedCard(RenderUncommittedCardRequest) rpc RenderUncommittedCard(RenderUncommittedCardRequest)
returns (RenderCardResponse); returns (RenderCardResponse);
rpc RenderUncommittedCardLegacy(RenderUncommittedCardLegacyRequest) rpc RenderUncommittedCardLegacy(RenderUncommittedCardLegacyRequest)
returns (RenderCardResponse); returns (RenderCardResponse);
rpc StripAVTags(String) returns (String); rpc StripAVTags(generic.String) returns (generic.String);
rpc RenderMarkdown(RenderMarkdownRequest) returns (String); rpc RenderMarkdown(RenderMarkdownRequest) returns (generic.String);
} }
service DeckConfigService { service DeckConfigService {
rpc AddOrUpdateDeckConfigLegacy(Json) returns (DeckConfigId); rpc AddOrUpdateDeckConfigLegacy(generic.Json) returns (DeckConfigId);
rpc GetDeckConfig(DeckConfigId) returns (DeckConfig); rpc GetDeckConfig(DeckConfigId) returns (DeckConfig);
rpc AllDeckConfigLegacy(Empty) returns (Json); rpc AllDeckConfigLegacy(generic.Empty) returns (generic.Json);
rpc GetDeckConfigLegacy(DeckConfigId) returns (Json); rpc GetDeckConfigLegacy(DeckConfigId) returns (generic.Json);
rpc NewDeckConfigLegacy(Empty) returns (Json); rpc NewDeckConfigLegacy(generic.Empty) returns (generic.Json);
rpc RemoveDeckConfig(DeckConfigId) returns (Empty); rpc RemoveDeckConfig(DeckConfigId) returns (generic.Empty);
rpc GetDeckConfigsForUpdate(DeckId) returns (DeckConfigsForUpdate); rpc GetDeckConfigsForUpdate(DeckId) returns (DeckConfigsForUpdate);
rpc UpdateDeckConfigs(UpdateDeckConfigsRequest) returns (OpChanges); rpc UpdateDeckConfigs(UpdateDeckConfigsRequest) returns (OpChanges);
} }
service TagsService { service TagsService {
rpc ClearUnusedTags(Empty) returns (OpChangesWithCount); rpc ClearUnusedTags(generic.Empty) returns (OpChangesWithCount);
rpc AllTags(Empty) returns (StringList); rpc AllTags(generic.Empty) returns (generic.StringList);
rpc RemoveTags(String) returns (OpChangesWithCount); rpc RemoveTags(generic.String) returns (OpChangesWithCount);
rpc SetTagCollapsed(SetTagCollapsedRequest) returns (OpChanges); rpc SetTagCollapsed(SetTagCollapsedRequest) returns (OpChanges);
rpc TagTree(Empty) returns (TagTreeNode); rpc TagTree(generic.Empty) returns (TagTreeNode);
rpc ReparentTags(ReparentTagsRequest) returns (OpChangesWithCount); rpc ReparentTags(ReparentTagsRequest) returns (OpChangesWithCount);
rpc RenameTags(RenameTagsRequest) returns (OpChangesWithCount); rpc RenameTags(RenameTagsRequest) returns (OpChangesWithCount);
rpc AddNoteTags(NoteIdsAndTagsRequest) returns (OpChangesWithCount); rpc AddNoteTags(NoteIdsAndTagsRequest) returns (OpChangesWithCount);
@ -267,55 +220,49 @@ service TagsService {
} }
service SearchService { service SearchService {
rpc BuildSearchString(SearchNode) returns (String); rpc BuildSearchString(SearchNode) returns (generic.String);
rpc SearchCards(SearchRequest) returns (SearchResponse); rpc SearchCards(SearchRequest) returns (SearchResponse);
rpc SearchNotes(SearchRequest) returns (SearchResponse); rpc SearchNotes(SearchRequest) returns (SearchResponse);
rpc JoinSearchNodes(JoinSearchNodesRequest) returns (String); rpc JoinSearchNodes(JoinSearchNodesRequest) returns (generic.String);
rpc ReplaceSearchNode(ReplaceSearchNodeRequest) returns (String); rpc ReplaceSearchNode(ReplaceSearchNodeRequest) returns (generic.String);
rpc FindAndReplace(FindAndReplaceRequest) returns (OpChangesWithCount); rpc FindAndReplace(FindAndReplaceRequest) returns (OpChangesWithCount);
rpc AllBrowserColumns(Empty) returns (BrowserColumns); rpc AllBrowserColumns(generic.Empty) returns (BrowserColumns);
rpc BrowserRowForId(Int64) returns (BrowserRow); rpc BrowserRowForId(generic.Int64) returns (BrowserRow);
rpc SetActiveBrowserColumns(StringList) returns (Empty); rpc SetActiveBrowserColumns(generic.StringList) returns (generic.Empty);
} }
service StatsService { service StatsService {
rpc CardStats(CardId) returns (String); rpc CardStats(CardId) returns (generic.String);
rpc Graphs(GraphsRequest) returns (GraphsResponse); rpc Graphs(GraphsRequest) returns (GraphsResponse);
rpc GetGraphPreferences(Empty) returns (GraphPreferences); rpc GetGraphPreferences(generic.Empty) returns (GraphPreferences);
rpc SetGraphPreferences(GraphPreferences) returns (Empty); rpc SetGraphPreferences(GraphPreferences) returns (generic.Empty);
} }
service MediaService { service MediaService {
rpc CheckMedia(Empty) returns (CheckMediaResponse); rpc CheckMedia(generic.Empty) returns (CheckMediaResponse);
rpc TrashMediaFiles(TrashMediaFilesRequest) returns (Empty); rpc TrashMediaFiles(TrashMediaFilesRequest) returns (generic.Empty);
rpc AddMediaFile(AddMediaFileRequest) returns (String); rpc AddMediaFile(AddMediaFileRequest) returns (generic.String);
rpc EmptyTrash(Empty) returns (Empty); rpc EmptyTrash(generic.Empty) returns (generic.Empty);
rpc RestoreTrash(Empty) returns (Empty); rpc RestoreTrash(generic.Empty) returns (generic.Empty);
}
service I18nService {
rpc TranslateString(TranslateStringRequest) returns (String);
rpc FormatTimespan(FormatTimespanRequest) returns (String);
rpc I18nResources(I18nResourcesRequest) returns (Json);
} }
service CollectionService { service CollectionService {
rpc OpenCollection(OpenCollectionRequest) returns (Empty); rpc OpenCollection(OpenCollectionRequest) returns (generic.Empty);
rpc CloseCollection(CloseCollectionRequest) returns (Empty); rpc CloseCollection(CloseCollectionRequest) returns (generic.Empty);
rpc CheckDatabase(Empty) returns (CheckDatabaseResponse); rpc CheckDatabase(generic.Empty) returns (CheckDatabaseResponse);
rpc GetUndoStatus(Empty) returns (UndoStatus); rpc GetUndoStatus(generic.Empty) returns (UndoStatus);
rpc Undo(Empty) returns (OpChangesAfterUndo); rpc Undo(generic.Empty) returns (OpChangesAfterUndo);
rpc Redo(Empty) returns (OpChangesAfterUndo); rpc Redo(generic.Empty) returns (OpChangesAfterUndo);
rpc AddCustomUndoEntry(String) returns (UInt32); rpc AddCustomUndoEntry(generic.String) returns (generic.UInt32);
rpc MergeUndoEntries(UInt32) returns (OpChanges); rpc MergeUndoEntries(generic.UInt32) returns (OpChanges);
rpc LatestProgress(Empty) returns (Progress); rpc LatestProgress(generic.Empty) returns (Progress);
rpc SetWantsAbort(Empty) returns (Empty); rpc SetWantsAbort(generic.Empty) returns (generic.Empty);
} }
service CardsService { service CardsService {
rpc GetCard(CardId) returns (Card); rpc GetCard(CardId) returns (Card);
rpc UpdateCard(UpdateCardRequest) returns (OpChanges); rpc UpdateCard(UpdateCardRequest) returns (OpChanges);
rpc RemoveCards(RemoveCardsRequest) returns (Empty); rpc RemoveCards(RemoveCardsRequest) returns (generic.Empty);
rpc SetDeck(SetDeckRequest) returns (OpChangesWithCount); rpc SetDeck(SetDeckRequest) returns (OpChangesWithCount);
rpc SetFlag(SetFlagRequest) returns (OpChangesWithCount); rpc SetFlag(SetFlagRequest) returns (OpChangesWithCount);
} }
@ -525,7 +472,7 @@ message Notetype {
bytes other = 255; bytes other = 255;
} }
OptionalUInt32 ord = 1; generic.OptionalUInt32 ord = 1;
string name = 2; string name = 2;
Config config = 5; Config config = 5;
} }
@ -542,7 +489,7 @@ message Notetype {
bytes other = 255; bytes other = 255;
} }
OptionalUInt32 ord = 1; generic.OptionalUInt32 ord = 1;
string name = 2; string name = 2;
int64 mtime_secs = 3; int64 mtime_secs = 3;
sint32 usn = 4; sint32 usn = 4;
@ -661,7 +608,7 @@ message Progress {
uint32 stage_current = 3; uint32 stage_current = 3;
} }
oneof value { oneof value {
Empty none = 1; generic.Empty none = 1;
MediaSync media_sync = 2; MediaSync media_sync = 2;
string media_check = 3; string media_check = 3;
FullSync full_sync = 4; FullSync full_sync = 4;
@ -797,34 +744,6 @@ message TrashMediaFilesRequest {
repeated string fnames = 1; repeated string fnames = 1;
} }
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;
}
message StudiedTodayMessageRequest { message StudiedTodayMessageRequest {
uint32 cards = 1; uint32 cards = 1;
double seconds = 2; double seconds = 2;
@ -857,7 +776,7 @@ message SortOrder {
bool reverse = 2; bool reverse = 2;
} }
oneof value { oneof value {
Empty none = 1; generic.Empty none = 1;
string custom = 2; string custom = 2;
Builtin builtin = 3; Builtin builtin = 3;
} }
@ -1606,6 +1525,16 @@ message OpChanges {
bool study_queues = 10; bool study_queues = 10;
} }
message OpChangesWithCount {
uint32 count = 1;
OpChanges changes = 2;
}
message OpChangesWithId {
int64 id = 1;
OpChanges changes = 2;
}
message UndoStatus { message UndoStatus {
string undo = 1; string undo = 1;
string redo = 2; string redo = 2;

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

View file

@ -14,9 +14,9 @@ def _impl(rctx):
alias( alias(
name = "clang_format", name = "clang_format",
actual = select({ actual = select({
"@net_ankiweb_anki//platforms:windows_x86_64": "@clang_format_windows_x86_64//:clang-format.exe", "@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", "@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", "@anki//platforms:linux_x86_64": "@clang_format_linux_x86_64//:clang-format",
}), }),
visibility = ["//visibility:public"] visibility = ["//visibility:public"]
) )
@ -68,7 +68,7 @@ def proto_format(name, srcs, **kwargs):
py_test( py_test(
name = name, name = name,
srcs = [ srcs = [
"format.py", "@anki//proto:format.py",
], ],
data = ["@clang_format//:clang_format"] + srcs, data = ["@clang_format//:clang_format"] + srcs,
args = ["$(location @clang_format//:clang_format)"] + [native.package_name() + "/" + f for f in srcs], args = ["$(location @clang_format//:clang_format)"] + [native.package_name() + "/" + f for f in srcs],

View file

@ -14,9 +14,9 @@ def _impl(rctx):
alias( alias(
name = "clang_format", name = "clang_format",
actual = select({ actual = select({
"@net_ankiweb_anki//platforms:windows_x86_64": "@clang_format_windows_x86_64//:clang-format.exe", "@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", "@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", "@anki//platforms:linux_x86_64": "@clang_format_linux_x86_64//:clang-format",
}), }),
visibility = ["//visibility:public"] visibility = ["//visibility:public"]
) )
@ -68,7 +68,7 @@ def proto_format(name, srcs, **kwargs):
py_test( py_test(
name = name, name = name,
srcs = [ srcs = [
"format.py", "@anki//format.py",
], ],
data = ["@clang_format//:clang_format"] + srcs, data = ["@clang_format//:clang_format"] + srcs,
args = ["$(location @clang_format//:clang_format)"] + [native.package_name() + "/" + f for f in srcs], args = ["$(location @clang_format//:clang_format)"] + [native.package_name() + "/" + f for f in srcs],

View file

@ -12,10 +12,10 @@ def _impl(rctx):
alias( alias(
name = "protoc", name = "protoc",
actual = select({ actual = select({
"@net_ankiweb_anki//platforms:windows_x86_64": "@protoc_bin_windows//:bin/protoc.exe", "@anki//platforms:windows_x86_64": "@protoc_bin_windows//:bin/protoc.exe",
"@net_ankiweb_anki//platforms:macos_x86_64": "@protoc_bin_macos//:bin/protoc", "@anki//platforms:macos_x86_64": "@protoc_bin_macos//:bin/protoc",
"@net_ankiweb_anki//platforms:linux_x86_64": "@protoc_bin_linux_x86_64//:bin/protoc", "@anki//platforms:linux_x86_64": "@protoc_bin_linux_x86_64//:bin/protoc",
"@net_ankiweb_anki//platforms:linux_arm64": "@protoc_bin_linux_arm64//:bin/protoc" "@anki//platforms:linux_arm64": "@protoc_bin_linux_arm64//:bin/protoc"
}), }),
visibility = ["//visibility:public"] visibility = ["//visibility:public"]
) )

View file

@ -32,6 +32,7 @@ py_library(
"py.typed", "py.typed",
":buildinfo", ":buildinfo",
":hooks_gen", ":hooks_gen",
":proto",
"//pylib/anki/_backend", "//pylib/anki/_backend",
], ],
imports = [ imports = [
@ -105,3 +106,30 @@ filegroup(
"//pylib:__subpackages__", "//pylib:__subpackages__",
], ],
) )
load("//pylib:protobuf.bzl", "py_proto")
py_proto(
name = "proto_files",
srcs = ["//proto"],
visibility = [
"//visibility:public",
],
)
filegroup(
name = "proto",
srcs = [
# "__init__.py",
":proto_files",
],
visibility = ["//pylib:__subpackages__"],
)
# only used for genbackend.py
py_library(
name = "proto_lib",
srcs = [":proto"],
imports = [".."],
visibility = ["//pylib:__subpackages__"],
)

View file

@ -1,27 +1,18 @@
load("@rules_python//python:defs.bzl", "py_binary") load("@rules_python//python:defs.bzl", "py_binary")
load("@py_deps//:requirements.bzl", "requirement") 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//rules:copy_file.bzl", "copy_file")
load("@bazel_skylib//lib:selects.bzl", "selects") load("@bazel_skylib//lib:selects.bzl", "selects")
py_proto_library_typed(
name = "backend_pb2",
src = "//proto:backend.proto",
visibility = [
"//visibility:public",
],
)
py_binary( py_binary(
name = "genbackend", name = "genbackend",
srcs = [ srcs = [
"backend_pb2",
"genbackend.py", "genbackend.py",
], ],
deps = [ deps = [
requirement("black"), requirement("black"),
requirement("stringcase"), requirement("stringcase"),
requirement("protobuf"), requirement("protobuf"),
"//pylib/anki:proto_lib",
], ],
) )
@ -94,7 +85,6 @@ filegroup(
srcs = [ srcs = [
"__init__.py", "__init__.py",
"rsbridge.pyi", "rsbridge.pyi",
":backend_pb2",
":fluent_gen", ":fluent_gen",
":rsbackend_gen", ":rsbackend_gen",
":rsbridge", ":rsbridge",

View file

@ -11,6 +11,7 @@ from weakref import ref
from markdown import markdown from markdown import markdown
import anki.buildinfo import anki.buildinfo
from anki import backend_pb2, i18n_pb2
from anki._backend.generated import RustBackendGenerated from anki._backend.generated import RustBackendGenerated
from anki.dbproxy import Row as DBRow from anki.dbproxy import Row as DBRow
from anki.dbproxy import ValueForDB from anki.dbproxy import ValueForDB
@ -32,7 +33,6 @@ from ..errors import (
TemplateError, TemplateError,
UndoEmpty, UndoEmpty,
) )
from . import backend_pb2 as pb
from . import rsbridge from . import rsbridge
from .fluent import GeneratedTranslations, LegacyTranslationEnum from .fluent import GeneratedTranslations, LegacyTranslationEnum
@ -65,7 +65,7 @@ class RustBackend(RustBackendGenerated):
if langs is None: if langs is None:
langs = [anki.lang.currentLang] langs = [anki.lang.currentLang]
init_msg = pb.BackendInit( init_msg = backend_pb2.BackendInit(
preferred_langs=langs, preferred_langs=langs,
server=server, server=server,
) )
@ -95,7 +95,7 @@ class RustBackend(RustBackendGenerated):
return from_json_bytes(self._backend.db_command(to_json_bytes(input))) return from_json_bytes(self._backend.db_command(to_json_bytes(input)))
except Exception as e: except Exception as e:
err_bytes = bytes(e.args[0]) err_bytes = bytes(e.args[0])
err = pb.BackendError() err = backend_pb2.BackendError()
err.ParseFromString(err_bytes) err.ParseFromString(err_bytes)
raise backend_exception_to_pylib(err) raise backend_exception_to_pylib(err)
@ -125,21 +125,21 @@ class RustBackend(RustBackendGenerated):
return self._backend.command(service, method, input_bytes) return self._backend.command(service, method, input_bytes)
except Exception as e: except Exception as e:
err_bytes = bytes(e.args[0]) err_bytes = bytes(e.args[0])
err = pb.BackendError() err = backend_pb2.BackendError()
err.ParseFromString(err_bytes) err.ParseFromString(err_bytes)
raise backend_exception_to_pylib(err) raise backend_exception_to_pylib(err)
def translate_string_in( def translate_string_in(
module_index: int, message_index: int, **kwargs: Union[str, int, float] module_index: int, message_index: int, **kwargs: Union[str, int, float]
) -> pb.TranslateStringRequest: ) -> i18n_pb2.TranslateStringRequest:
args = {} args = {}
for (k, v) in kwargs.items(): for (k, v) in kwargs.items():
if isinstance(v, str): if isinstance(v, str):
args[k] = pb.TranslateArgValue(str=v) args[k] = i18n_pb2.TranslateArgValue(str=v)
else: else:
args[k] = pb.TranslateArgValue(number=v) args[k] = i18n_pb2.TranslateArgValue(number=v)
return pb.TranslateStringRequest( return i18n_pb2.TranslateStringRequest(
module_index=module_index, message_index=message_index, args=args module_index=module_index, message_index=message_index, args=args
) )
@ -167,8 +167,8 @@ class Translations(GeneratedTranslations):
) )
def backend_exception_to_pylib(err: pb.BackendError) -> Exception: def backend_exception_to_pylib(err: backend_pb2.BackendError) -> Exception:
kind = pb.BackendError kind = backend_pb2.BackendError
val = err.kind val = err.kind
if val == kind.INTERRUPTED: if val == kind.INTERRUPTED:
return Interrupted() return Interrupted()

View file

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

View file

@ -7,7 +7,10 @@ import re
import sys import sys
import google.protobuf.descriptor import google.protobuf.descriptor
import pylib.anki._backend.backend_pb2 as pb
import anki.backend_pb2
import anki.i18n_pb2
import stringcase import stringcase
TYPE_DOUBLE = 1 TYPE_DOUBLE = 1
@ -73,11 +76,11 @@ def python_type_inner(field):
raise Exception(f"unknown type: {type}") raise Exception(f"unknown type: {type}")
def fullname(fullname): def fullname(fullname: str) -> str:
if "FluentString" in fullname: # eg anki.generic.Empty -> anki.generic_pb2.Empty
return fullname.replace("BackendProto", "anki.fluent_pb2") components = fullname.split(".")
else: components[1] += "_pb2"
return fullname.replace("BackendProto", "pb") return ".".join(components)
# get_deck_i_d -> get_deck_id etc # get_deck_i_d -> get_deck_id etc
@ -131,7 +134,7 @@ def render_method(service_idx, method_idx, method):
return_type = python_type(f) return_type = python_type(f)
else: else:
single_field = "" single_field = ""
return_type = f"pb.{method.output_type.name}" return_type = fullname(method.output_type.full_name)
if method.name in SKIP_DECODE: if method.name in SKIP_DECODE:
return_type = "bytes" return_type = "bytes"
@ -144,7 +147,7 @@ def render_method(service_idx, method_idx, method):
buf += f"""return self._run_command({service_idx}, {method_idx}, input) buf += f"""return self._run_command({service_idx}, {method_idx}, input)
""" """
else: 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)) output.ParseFromString(self._run_command({service_idx}, {method_idx}, input))
return output{single_field} return output{single_field}
""" """
@ -162,12 +165,14 @@ def render_service(
out.append(render_method(service_index, method_index, method)) out.append(render_method(service_index, method_index, method))
for service in pb.ServiceIndex.DESCRIPTOR.values: service_modules = dict(I18N="i18n")
for service in anki.backend_pb2.ServiceIndex.DESCRIPTOR.values:
# SERVICE_INDEX_TEST -> _TESTSERVICE # SERVICE_INDEX_TEST -> _TESTSERVICE
service_var = ( base = service.name.replace("SERVICE_INDEX_", "")
"_" + service.name.replace("SERVICE_INDEX", "").replace("_", "") + "SERVICE" service_pkg = (service_modules.get(base) or "backend") + ""
) service_var = "_" + base.replace("_", "") + "SERVICE"
service_obj = getattr(pb, service_var) service_obj = getattr(getattr(anki, service_pkg + "_pb2"), service_var)
service_index = service.number service_index = service.number
render_service(service_obj, service_index) render_service(service_obj, service_index)
@ -194,7 +199,7 @@ col.decks.all_config()
from typing import * from typing import *
import anki._backend.backend_pb2 as pb import anki
class RustBackendGenerated: class RustBackendGenerated:
def _run_command(self, service: int, method: int, input: Any) -> bytes: 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

@ -10,7 +10,7 @@ import time
from typing import List, NewType, Optional from typing import List, NewType, Optional
import anki # pylint: disable=unused-import import anki # pylint: disable=unused-import
import anki._backend.backend_pb2 as _pb import anki.backend_pb2 as _pb
from anki import hooks from anki import hooks
from anki._legacy import DeprecatedNamesMixin, deprecated from anki._legacy import DeprecatedNamesMixin, deprecated
from anki.consts import * from anki.consts import *

View file

@ -7,7 +7,7 @@ from __future__ import annotations
from typing import Any, Generator, List, Literal, Optional, Sequence, Tuple, Union, cast from typing import Any, Generator, List, Literal, Optional, Sequence, Tuple, Union, cast
import anki._backend.backend_pb2 as _pb import anki.backend_pb2 as _pb
# protobuf we publicly export - listed first to avoid circular imports # protobuf we publicly export - listed first to avoid circular imports
from anki._legacy import DeprecatedNamesMixin, deprecated from anki._legacy import DeprecatedNamesMixin, deprecated
@ -35,7 +35,7 @@ import weakref
from dataclasses import dataclass, field from dataclasses import dataclass, field
import anki.latex import anki.latex
from anki import hooks from anki import generic_pb2, hooks
from anki._backend import RustBackend, Translations from anki._backend import RustBackend, Translations
from anki.browser import BrowserConfig, BrowserDefaults from anki.browser import BrowserConfig, BrowserDefaults
from anki.cards import Card, CardId from anki.cards import Card, CardId
@ -492,7 +492,7 @@ class Collection(DeprecatedNamesMixin):
return _pb.SortOrder(custom=order) return _pb.SortOrder(custom=order)
if isinstance(order, bool): if isinstance(order, bool):
if order is False: if order is False:
return _pb.SortOrder(none=_pb.Empty()) return _pb.SortOrder(none=generic_pb2.Empty())
# order=True: set args to sort column and reverse from config # order=True: set args to sort column and reverse from config
sort_key = BrowserConfig.sort_column_key(finding_notes) sort_key = BrowserConfig.sort_column_key(finding_notes)
order = self.get_browser_column(self.get_config(sort_key)) order = self.get_browser_column(self.get_config(sort_key))
@ -506,7 +506,7 @@ class Collection(DeprecatedNamesMixin):
# eg, user is ordering on an add-on field with the add-on not installed # eg, user is ordering on an add-on field with the add-on not installed
print(f"{order} is not a valid sort order.") print(f"{order} is not a valid sort order.")
return _pb.SortOrder(none=_pb.Empty()) return _pb.SortOrder(none=generic_pb2.Empty())
def find_and_replace( def find_and_replace(
self, self,

View file

@ -25,7 +25,7 @@ from typing import Any
from weakref import ref from weakref import ref
import anki import anki
from anki._backend import backend_pb2 as _pb from anki import backend_pb2 as _pb
from anki.collection import OpChanges from anki.collection import OpChanges
from anki.errors import NotFoundError from anki.errors import NotFoundError
from anki.utils import from_json_bytes, to_json_bytes from anki.utils import from_json_bytes, to_json_bytes

View file

@ -23,7 +23,7 @@ from typing import (
if TYPE_CHECKING: if TYPE_CHECKING:
import anki import anki
import anki._backend.backend_pb2 as _pb import anki.backend_pb2 as _pb
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

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

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

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

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

View file

@ -9,7 +9,8 @@ import weakref
from typing import Optional, Tuple from typing import Optional, Tuple
import anki import anki
import anki._backend.backend_pb2 as _pb import anki._backend
import anki.i18n_pb2 as _pb
# public exports # public exports
TR = anki._backend.LegacyTranslationEnum TR = anki._backend.LegacyTranslationEnum

View file

@ -10,7 +10,7 @@ from dataclasses import dataclass
from typing import Any, List, Optional, Tuple from typing import Any, List, Optional, Tuple
import anki import anki
import anki._backend.backend_pb2 as _pb import anki.backend_pb2 as _pb
from anki import hooks from anki import hooks
from anki.models import NotetypeDict from anki.models import NotetypeDict
from anki.template import TemplateRenderContext, TemplateRenderOutput from anki.template import TemplateRenderContext, TemplateRenderOutput

View file

@ -11,7 +11,7 @@ import time
from typing import Any, Callable, List, Optional, Tuple from typing import Any, Callable, List, Optional, Tuple
import anki import anki
import anki._backend.backend_pb2 as _pb import anki.backend_pb2 as _pb
from anki._legacy import deprecated from anki._legacy import deprecated
from anki.consts import * from anki.consts import *
from anki.latex import render_latex, render_latex_returning_errors from anki.latex import render_latex, render_latex_returning_errors

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.backend_pb2 as _pb import anki.backend_pb2 as _pb
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 *

View file

@ -9,7 +9,7 @@ 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.backend_pb2 as _pb import anki.backend_pb2 as _pb
from anki import hooks 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

View file

@ -4,7 +4,7 @@
from __future__ import annotations from __future__ import annotations
import anki import anki
import anki._backend.backend_pb2 as _pb import anki.backend_pb2 as _pb
from anki.collection import OpChanges, OpChangesWithCount, OpChangesWithId from anki.collection import OpChanges, OpChangesWithCount, OpChangesWithId
from anki.config import Config from anki.config import Config

View file

@ -11,7 +11,7 @@ from heapq import *
from typing import Any, Callable, Dict, List, Optional, Tuple, Union from typing import Any, Callable, Dict, List, Optional, Tuple, Union
import anki # pylint: disable=unused-import import anki # pylint: disable=unused-import
import anki._backend.backend_pb2 as _pb import anki.backend_pb2 as _pb
from anki import hooks from anki import hooks
from anki.cards import Card, CardId from anki.cards import Card, CardId
from anki.consts import * from anki.consts import *

View file

@ -14,7 +14,7 @@ from __future__ import annotations
from typing import List, Literal, Sequence, Tuple from typing import List, Literal, Sequence, Tuple
import anki._backend.backend_pb2 as _pb import anki.backend_pb2 as _pb
from anki.cards import Card from anki.cards import Card
from anki.collection import OpChanges from anki.collection import OpChanges
from anki.consts import * from anki.consts import *

View file

@ -6,7 +6,7 @@ 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.backend_pb2 as _pb import anki.backend_pb2 as _pb
from anki.utils import from_json_bytes from anki.utils import from_json_bytes
# pylint: disable=no-member # pylint: disable=no-member

View file

@ -1,7 +1,7 @@
# 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
import anki._backend.backend_pb2 as _pb import anki.backend_pb2 as _pb
# public exports # public exports
SyncAuth = _pb.SyncAuth SyncAuth = _pb.SyncAuth

View file

@ -27,7 +27,7 @@ except ImportError as e:
from flask import Response from flask import Response
from anki import Collection from anki import Collection
from anki._backend.backend_pb2 import SyncServerMethodRequest from anki.backend_pb2 import SyncServerMethodRequest
Method = SyncServerMethodRequest.Method # pylint: disable=no-member Method = SyncServerMethodRequest.Method # pylint: disable=no-member

View file

@ -16,7 +16,7 @@ import re
from typing import Collection, List, Match, Optional, Sequence from typing import Collection, List, Match, Optional, Sequence
import anki # pylint: disable=unused-import import anki # pylint: disable=unused-import
import anki._backend.backend_pb2 as _pb import anki.backend_pb2 as _pb
import anki.collection import anki.collection
from anki.collection import OpChanges, OpChangesWithCount from anki.collection import OpChanges, OpChangesWithCount
from anki.decks import DeckId from anki.decks import DeckId

View file

@ -32,7 +32,7 @@ from dataclasses import dataclass
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union from typing import Any, Dict, List, Optional, Sequence, Tuple, Union
import anki import anki
import anki._backend.backend_pb2 as _pb import anki.backend_pb2 as _pb
from anki import hooks from anki import hooks
from anki.cards import Card from anki.cards import Card
from anki.decks import DeckManager from anki.decks import DeckManager

View file

@ -1,21 +1,21 @@
load("@bazel_skylib//lib:paths.bzl", "paths") load("@bazel_skylib//lib:paths.bzl", "paths")
def _py_proto_library_impl(ctx): def _py_proto_impl(ctx):
basename = ctx.file.src.basename outs = []
outs = [ for src in ctx.files.srcs:
ctx.actions.declare_file(paths.replace_extension(basename, "_pb2.py")), base = paths.basename(src.path)
ctx.actions.declare_file(paths.replace_extension(basename, "_pb2.pyi")), 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( ctx.actions.run(
outputs = outs, outputs = outs,
inputs = [ctx.file.src], inputs = ctx.files.srcs,
executable = ctx.executable.protoc_wrapper, executable = ctx.executable.protoc_wrapper,
arguments = [ arguments = [
ctx.executable.protoc.path, ctx.executable.protoc.path,
ctx.executable.mypy_protobuf.path, ctx.executable.mypy_protobuf.path,
ctx.file.src.path,
paths.dirname(outs[0].path), paths.dirname(outs[0].path),
], ] + [file.path for file in ctx.files.srcs],
tools = [ tools = [
ctx.executable.protoc, ctx.executable.protoc,
ctx.executable.mypy_protobuf, 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)), DefaultInfo(files = depset(direct = outs), data_runfiles = ctx.runfiles(files = outs)),
] ]
py_proto_library_typed = rule( py_proto = rule(
implementation = _py_proto_library_impl, implementation = _py_proto_impl,
attrs = { attrs = {
"src": attr.label(allow_single_file = [".proto"]), "srcs": attr.label_list(allow_files = [".proto"]),
"protoc_wrapper": attr.label( "protoc_wrapper": attr.label(
executable = True, executable = True,
cfg = "exec", cfg = "exec",

View file

@ -10,16 +10,9 @@ import shutil
import subprocess import subprocess
import sys import sys
(protoc, mypy_protobuf, proto, outdir) = sys.argv[1:] (protoc, mypy_protobuf, outdir, *protos) = sys.argv[1:]
# copy to current dir prefix = "proto/"
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"
# invoke protoc # invoke protoc
subprocess.run( subprocess.run(
@ -28,13 +21,17 @@ subprocess.run(
"--plugin=protoc-gen-mypy=" + mypy_protobuf, "--plugin=protoc-gen-mypy=" + mypy_protobuf,
"--python_out=.", "--python_out=.",
"--mypy_out=.", "--mypy_out=.",
basename, "-I" + prefix,
"-Iexternal/anki/" + prefix,
*protos,
], ],
# mypy prints to stderr on success :-( # mypy prints to stderr on success :-(
stderr=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
check=True, check=True,
) )
# move files into output for proto in protos:
shutil.move(pb2_py, outdir + "/" + pb2_py) without_prefix_and_ext, _ = os.path.splitext(proto[len(prefix) :])
shutil.move(pb2_pyi, outdir + "/" + pb2_pyi) 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

@ -30,8 +30,8 @@ if subprocess.run(
"--", "--",
"--config-file", "--config-file",
"qt/mypy.ini", "qt/mypy.ini",
"bazel-bin/qt/dmypy.runfiles/net_ankiweb_anki/pylib/anki", "bazel-bin/qt/dmypy.runfiles/anki/pylib/anki",
"bazel-bin/qt/dmypy.runfiles/net_ankiweb_anki/qt/aqt", "bazel-bin/qt/dmypy.runfiles/anki/qt/aqt",
"--python-executable", "--python-executable",
os.path.abspath("pip/stubs/extendsitepkgs"), os.path.abspath("pip/stubs/extendsitepkgs"),
], ],

View file

@ -13,7 +13,7 @@ cargo_build_script(
name = "build_script", name = "build_script",
srcs = glob(["build/*.rs"]), srcs = glob(["build/*.rs"]),
build_script_env = { build_script_env = {
"BACKEND_PROTO": "$(location //proto:backend.proto)", "PROTO_TOP": "$(location //proto:.top_level)",
"PROTOC": "$(location @com_google_protobuf//:protoc)", "PROTOC": "$(location @com_google_protobuf//:protoc)",
"RSLIB_FTL_ROOT": "$(location @rslib_ftl//:l10n.toml)", "RSLIB_FTL_ROOT": "$(location @rslib_ftl//:l10n.toml)",
"EXTRA_FTL_ROOT": "$(location @extra_ftl//:l10n.toml)", "EXTRA_FTL_ROOT": "$(location @extra_ftl//:l10n.toml)",
@ -22,9 +22,10 @@ cargo_build_script(
crate_root = "build/main.rs", crate_root = "build/main.rs",
data = [ data = [
"//ftl", "//ftl",
"//proto:backend.proto", "//proto",
"@com_google_protobuf//:protoc", "@com_google_protobuf//:protoc",
# bazel requires us to list these out separately # bazel requires us to list these out separately
"//proto:.top_level",
"@rslib_ftl//:l10n.toml", "@rslib_ftl//:l10n.toml",
"@extra_ftl//:l10n.toml", "@extra_ftl//:l10n.toml",
], ],

View file

@ -17,7 +17,7 @@ pub trait Service {
write!( write!(
buf, buf,
concat!(" ", 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 output = self.{rust_method}(input)?;\n",
"let mut out_bytes = Vec::new(); output.encode(&mut out_bytes)?; Ok(out_bytes) }}, "), "let mut out_bytes = Vec::new(); output.encode(&mut out_bytes)?; Ok(out_bytes) }}, "),
idx = idx, idx = idx,
@ -38,8 +38,8 @@ pub trait Service {
write!( write!(
buf, buf,
concat!( concat!(
" fn {method_name}(&self, input: {input_type}) -> ", " fn {method_name}(&self, input: super::{input_type}) -> ",
"Result<{output_type}>;\n" "Result<super::{output_type}>;\n"
), ),
method_name = method.name, method_name = method.name,
input_type = method.input_type, input_type = method.input_type,
@ -55,7 +55,6 @@ impl prost_build::ServiceGenerator for CustomGenerator {
write!( write!(
buf, buf,
"pub mod {name}_service {{ "pub mod {name}_service {{
use super::*;
use prost::Message; use prost::Message;
use crate::error::Result; use crate::error::Result;
", ",
@ -73,16 +72,32 @@ fn service_generator() -> Box<dyn prost_build::ServiceGenerator> {
pub fn write_backend_proto_rs() { pub fn write_backend_proto_rs() {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let backend_proto; let proto_dir = if let Ok(proto) = env::var("PROTO_TOP") {
let proto_dir; let backend_proto = PathBuf::from(proto);
if let Ok(proto) = env::var("BACKEND_PROTO") { backend_proto.parent().unwrap().to_owned()
backend_proto = PathBuf::from(proto);
proto_dir = backend_proto.parent().unwrap().to_owned();
} else { } else {
backend_proto = PathBuf::from("backend.proto"); PathBuf::from("../proto")
proto_dir = 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(); let mut config = prost_build::Config::new();
config config
@ -92,6 +107,6 @@ pub fn write_backend_proto_rs() {
"Deck.Filtered.SearchTerm.Order", "Deck.Filtered.SearchTerm.Order",
"#[derive(strum::EnumIter)]", "#[derive(strum::EnumIter)]",
) )
.compile_protos(&[&backend_proto], &[&proto_dir, &out_dir]) .compile_protos(paths.as_slice(), &[proto_dir, out_dir])
.unwrap(); .unwrap();
} }

View file

@ -1,4 +1,16 @@
// 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
include!(concat!(env!("OUT_DIR"), "/backend_proto.rs")); pub mod backend {
include!(concat!(env!("OUT_DIR"), "/anki.backend.rs"));
}
pub mod i18n {
include!(concat!(env!("OUT_DIR"), "/anki.i18n.rs"));
}
pub mod generic {
include!(concat!(env!("OUT_DIR"), "/anki.generic.rs"));
}
pub use backend::*;
pub use generic::*;
pub use i18n::*;

View file

@ -52,7 +52,6 @@ ts_library(
deps = [ deps = [
"//ts/components", "//ts/components",
"//ts/lib", "//ts/lib",
"//ts/lib:backend_proto",
"//ts/sveltelib", "//ts/sveltelib",
"@npm//lodash-es", "@npm//lodash-es",
"@npm//svelte", "@npm//svelte",
@ -76,7 +75,6 @@ esbuild(
"@npm//bootstrap", "@npm//bootstrap",
"@npm//marked", "@npm//marked",
"//ts/lib", "//ts/lib",
"//ts/lib:backend_proto",
"//ts/sveltelib", "//ts/sveltelib",
"//ts/components", "//ts/components",
"//ts/components:svelte_components", "//ts/components:svelte_components",
@ -123,7 +121,7 @@ jest_test(
protobuf = True, protobuf = True,
deps = [ deps = [
":lib", ":lib",
"//ts/lib:backend_proto", "//ts/lib",
"@npm//protobufjs", "@npm//protobufjs",
"@npm//svelte", "@npm//svelte",
], ],

View file

@ -5,7 +5,7 @@
@typescript-eslint/no-explicit-any: "off", @typescript-eslint/no-explicit-any: "off",
*/ */
import * as pb from "lib/backend_proto"; import { Backend } 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(
pb.BackendProto.NotetypeNames.fromObject(exampleNames), Backend.NotetypeNames.fromObject(exampleNames),
pb.BackendProto.ChangeNotetypeInfo.fromObject(exampleInfoDifferent) Backend.ChangeNotetypeInfo.fromObject(exampleInfoDifferent)
); );
} }
function sameState(): ChangeNotetypeState { function sameState(): ChangeNotetypeState {
return new ChangeNotetypeState( return new ChangeNotetypeState(
pb.BackendProto.NotetypeNames.fromObject(exampleNames), Backend.NotetypeNames.fromObject(exampleNames),
pb.BackendProto.ChangeNotetypeInfo.fromObject(exampleInfoSame) Backend.ChangeNotetypeInfo.fromObject(exampleInfoSame)
); );
} }

View file

@ -5,22 +5,20 @@
@typescript-eslint/no-non-null-assertion: "off", @typescript-eslint/no-non-null-assertion: "off",
*/ */
import pb from "lib/backend_proto"; import { Backend } 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<pb.BackendProto.NotetypeNames> { export async function getNotetypeNames(): Promise<Backend.NotetypeNames> {
return pb.BackendProto.NotetypeNames.decode( return Backend.NotetypeNames.decode(await postRequest("/_anki/notetypeNames", ""));
await postRequest("/_anki/notetypeNames", "")
);
} }
export async function getChangeNotetypeInfo( export async function getChangeNotetypeInfo(
oldNotetypeId: number, oldNotetypeId: number,
newNotetypeId: number newNotetypeId: number
): Promise<pb.BackendProto.ChangeNotetypeInfo> { ): Promise<Backend.ChangeNotetypeInfo> {
return pb.BackendProto.ChangeNotetypeInfo.decode( return Backend.ChangeNotetypeInfo.decode(
await postRequest( await postRequest(
"/_anki/changeNotetypeInfo", "/_anki/changeNotetypeInfo",
JSON.stringify({ oldNotetypeId, newNotetypeId }) JSON.stringify({ oldNotetypeId, newNotetypeId })
@ -29,10 +27,9 @@ export async function getChangeNotetypeInfo(
} }
export async function changeNotetype( export async function changeNotetype(
input: pb.BackendProto.ChangeNotetypeRequest input: Backend.ChangeNotetypeRequest
): Promise<void> { ): Promise<void> {
const data: Uint8Array = const data: Uint8Array = Backend.ChangeNotetypeRequest.encode(input).finish();
pb.BackendProto.ChangeNotetypeRequest.encode(input).finish();
await postRequest("/_anki/changeNotetype", data); await postRequest("/_anki/changeNotetype", data);
return; return;
} }
@ -50,9 +47,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: pb.BackendProto.ChangeNotetypeInfo; readonly info: Backend.ChangeNotetypeInfo;
constructor(info: pb.BackendProto.ChangeNotetypeInfo) { constructor(info: Backend.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) {
@ -114,13 +111,13 @@ export class ChangeNotetypeInfoWrapper {
); );
} }
input(): pb.BackendProto.ChangeNotetypeRequest { input(): Backend.ChangeNotetypeRequest {
return this.info.input as pb.BackendProto.ChangeNotetypeRequest; return this.info.input as Backend.ChangeNotetypeRequest;
} }
/// Pack changes back into input message for saving. /// Pack changes back into input message for saving.
intoInput(): pb.BackendProto.ChangeNotetypeRequest { intoInput(): Backend.ChangeNotetypeRequest {
const input = this.info.input as pb.BackendProto.ChangeNotetypeRequest; const input = this.info.input as Backend.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);
@ -146,13 +143,10 @@ export class ChangeNotetypeState {
private info_: ChangeNotetypeInfoWrapper; private info_: ChangeNotetypeInfoWrapper;
private infoSetter!: (val: ChangeNotetypeInfoWrapper) => void; private infoSetter!: (val: ChangeNotetypeInfoWrapper) => void;
private notetypeNames: pb.BackendProto.NotetypeNames; private notetypeNames: Backend.NotetypeNames;
private notetypesSetter!: (val: NotetypeListEntry[]) => void; private notetypesSetter!: (val: NotetypeListEntry[]) => void;
constructor( constructor(notetypes: Backend.NotetypeNames, info: Backend.ChangeNotetypeInfo) {
notetypes: pb.BackendProto.NotetypeNames,
info: pb.BackendProto.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;
@ -203,7 +197,7 @@ export class ChangeNotetypeState {
await changeNotetype(this.dataForSaving()); await changeNotetype(this.dataForSaving());
} }
dataForSaving(): pb.BackendProto.ChangeNotetypeRequest { dataForSaving(): Backend.ChangeNotetypeRequest {
return this.info_.intoInput(); return this.info_.intoInput();
} }

View file

@ -14,7 +14,7 @@ def compile_sass(group, srcs, deps = [], visibility = ["//visibility:private"]):
sourcemap = False, sourcemap = False,
deps = deps, deps = deps,
visibility = visibility, visibility = visibility,
include_paths = ["external/net_ankiweb_anki"], include_paths = ["external/anki"],
) )
native.filegroup( native.filegroup(

View file

@ -17,25 +17,24 @@ filegroup(
compile_svelte( compile_svelte(
name = "svelte", name = "svelte",
srcs = svelte_files, srcs = svelte_files,
visibility = ["//visibility:public"],
deps = [ deps = [
"//ts/sass:button_mixins_lib", "//ts/sass:button_mixins_lib",
"//ts/sass/bootstrap", "//ts/sass/bootstrap",
], ],
visibility = ["//visibility:public"],
) )
ts_library( ts_library(
name = "components", name = "components",
module_name = "components",
srcs = glob( srcs = glob(
["*.ts"], ["*.ts"],
exclude = ["*.test.ts"], exclude = ["*.test.ts"],
), ),
module_name = "components",
tsconfig = "//ts:tsconfig.json", tsconfig = "//ts:tsconfig.json",
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
"//ts/lib", "//ts/lib",
"//ts/lib:backend_proto",
"//ts/sveltelib", "//ts/sveltelib",
"@npm//@popperjs/core", "@npm//@popperjs/core",
"@npm//@types/bootstrap", "@npm//@types/bootstrap",

View file

@ -35,10 +35,7 @@ ts_library(
ts_library( ts_library(
name = "lib", name = "lib",
srcs = ["lib.ts"], srcs = ["lib.ts"],
deps = [ deps = ["//ts/lib"],
"//ts/lib",
"//ts/lib:backend_proto",
],
) )
esbuild( esbuild(
@ -56,7 +53,6 @@ esbuild(
":base_css", ":base_css",
":index", ":index",
"//ts/lib", "//ts/lib",
"//ts/lib:backend_proto",
"@npm//protobufjs", "@npm//protobufjs",
], ],
) )

View file

@ -3,11 +3,11 @@ 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
--> -->
<script lang="ts"> <script lang="ts">
import type pb from "lib/backend_proto"; import type { Backend } from "lib/proto";
import { buildNextLearnMsg } from "./lib"; import { buildNextLearnMsg } from "./lib";
import { bridgeLink } from "lib/bridgecommand"; import { bridgeLink } from "lib/bridgecommand";
export let info: pb.BackendProto.CongratsInfoResponse; export let info: Backend.CongratsInfoResponse;
import * as tr from "lib/i18n"; import * as tr from "lib/i18n";
const congrats = tr.schedulingCongratulationsFinished(); const congrats = tr.schedulingCongratulationsFinished();

View file

@ -1,19 +1,19 @@
// 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
import pb from "lib/backend_proto"; import { Backend } from "lib/proto";
import { postRequest } from "lib/postrequest"; import { postRequest } from "lib/postrequest";
import { naturalUnit, unitAmount, unitName } from "lib/time"; import { naturalUnit, unitAmount, unitName } from "lib/time";
import * as tr from "lib/i18n"; import * as tr from "lib/i18n";
export async function getCongratsInfo(): Promise<pb.BackendProto.CongratsInfoResponse> { export async function getCongratsInfo(): Promise<Backend.CongratsInfoResponse> {
return pb.BackendProto.CongratsInfoResponse.decode( return Backend.CongratsInfoResponse.decode(
await postRequest("/_anki/congratsInfo", "") await postRequest("/_anki/congratsInfo", "")
); );
} }
export function buildNextLearnMsg(info: pb.BackendProto.CongratsInfoResponse): string { export function buildNextLearnMsg(info: Backend.CongratsInfoResponse): string {
const secsUntil = info.secsUntilNextLearn; const secsUntil = info.secsUntilNextLearn;
// next learning card not due (/ until tomorrow)? // next learning card not due (/ until tomorrow)?
if (secsUntil == 0 || secsUntil > 86_400) { if (secsUntil == 0 || secsUntil > 86_400) {

View file

@ -68,7 +68,6 @@ ts_library(
"//ts:image_module_support", "//ts:image_module_support",
"//ts/components", "//ts/components",
"//ts/lib", "//ts/lib",
"//ts/lib:backend_proto",
"//ts/sveltelib", "//ts/sveltelib",
"@npm//lodash-es", "@npm//lodash-es",
"@npm//svelte", "@npm//svelte",
@ -94,7 +93,6 @@ esbuild(
"@npm//marked", "@npm//marked",
"@npm//protobufjs", "@npm//protobufjs",
"//ts/lib", "//ts/lib",
"//ts/lib:backend_proto",
"//ts/sveltelib", "//ts/sveltelib",
"//ts/components", "//ts/components",
"//ts/components:svelte_components", "//ts/components:svelte_components",
@ -141,7 +139,7 @@ jest_test(
protobuf = True, protobuf = True,
deps = [ deps = [
":lib", ":lib",
"//ts/lib:backend_proto", "//ts/lib",
"@npm//protobufjs", "@npm//protobufjs",
"@npm//svelte", "@npm//svelte",
], ],

View file

@ -5,7 +5,7 @@
@typescript-eslint/no-explicit-any: "off", @typescript-eslint/no-explicit-any: "off",
*/ */
import * as pb from "lib/backend_proto"; import { Backend } 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,
pb.BackendProto.DeckConfigsForUpdate.fromObject(exampleData) Backend.DeckConfigsForUpdate.fromObject(exampleData)
); );
} }

View file

@ -5,7 +5,7 @@
@typescript-eslint/no-non-null-assertion: "off", @typescript-eslint/no-non-null-assertion: "off",
*/ */
import pb from "lib/backend_proto"; import { Backend } 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,17 +14,16 @@ import type { DynamicSvelteComponent } from "sveltelib/dynamicComponent";
export async function getDeckOptionsInfo( export async function getDeckOptionsInfo(
deckId: number deckId: number
): Promise<pb.BackendProto.DeckConfigsForUpdate> { ): Promise<Backend.DeckConfigsForUpdate> {
return pb.BackendProto.DeckConfigsForUpdate.decode( return Backend.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: pb.BackendProto.UpdateDeckConfigsRequest input: Backend.UpdateDeckConfigsRequest
): Promise<void> { ): Promise<void> {
const data: Uint8Array = const data: Uint8Array = Backend.UpdateDeckConfigsRequest.encode(input).finish();
pb.BackendProto.UpdateDeckConfigsRequest.encode(input).finish();
await postRequest("/_anki/updateDeckConfigs", data); await postRequest("/_anki/updateDeckConfigs", data);
return; return;
} }
@ -32,7 +31,7 @@ export async function saveDeckOptions(
export type DeckOptionsId = number; export type DeckOptionsId = number;
export interface ConfigWithCount { export interface ConfigWithCount {
config: pb.BackendProto.DeckConfig; config: Backend.DeckConfig;
useCount: number; useCount: number;
} }
@ -49,14 +48,14 @@ export interface ConfigListEntry {
current: boolean; current: boolean;
} }
type ConfigInner = pb.BackendProto.DeckConfig.Config; type ConfigInner = Backend.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: pb.BackendProto.DeckConfigsForUpdate.CurrentDeck; readonly currentDeck: Backend.DeckConfigsForUpdate.CurrentDeck;
readonly defaults: ConfigInner; readonly defaults: ConfigInner;
readonly addonComponents: Writable<DynamicSvelteComponent[]>; readonly addonComponents: Writable<DynamicSvelteComponent[]>;
readonly v3Scheduler: boolean; readonly v3Scheduler: boolean;
@ -71,13 +70,12 @@ export class DeckOptionsState {
private removedConfigs: DeckOptionsId[] = []; private removedConfigs: DeckOptionsId[] = [];
private schemaModified: boolean; private schemaModified: boolean;
constructor(targetDeckId: number, data: pb.BackendProto.DeckConfigsForUpdate) { constructor(targetDeckId: number, data: Backend.DeckConfigsForUpdate) {
this.targetDeckId = targetDeckId; this.targetDeckId = targetDeckId;
this.currentDeck = this.currentDeck = data.currentDeck as Backend.DeckConfigsForUpdate.CurrentDeck;
data.currentDeck as pb.BackendProto.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 pb.BackendProto.DeckConfig; const configInner = config.config as Backend.DeckConfig;
return { return {
config: configInner, config: configInner,
useCount: config.useCount!, useCount: config.useCount!,
@ -152,12 +150,9 @@ export class DeckOptionsState {
} }
/// Clone the current config, making it current. /// Clone the current config, making it current.
private addConfigFrom( private addConfigFrom(name: string, source: Backend.DeckConfig.IConfig): void {
name: string,
source: pb.BackendProto.DeckConfig.IConfig
): void {
const uniqueName = this.ensureNewNameUnique(name); const uniqueName = this.ensureNewNameUnique(name);
const config = pb.BackendProto.DeckConfig.create({ const config = Backend.DeckConfig.create({
id: 0, id: 0,
name: uniqueName, name: uniqueName,
config: cloneDeep(source), config: cloneDeep(source),
@ -193,7 +188,7 @@ export class DeckOptionsState {
this.updateConfigList(); this.updateConfigList();
} }
dataForSaving(applyToChildren: boolean): pb.BackendProto.UpdateDeckConfigsRequest { dataForSaving(applyToChildren: boolean): Backend.UpdateDeckConfigsRequest {
const modifiedConfigsExcludingCurrent = this.configs const modifiedConfigsExcludingCurrent = this.configs
.map((c) => c.config) .map((c) => c.config)
.filter((c, idx) => { .filter((c, idx) => {
@ -207,7 +202,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 pb.BackendProto.UpdateDeckConfigsRequest.create({ return Backend.UpdateDeckConfigsRequest.create({
targetDeckId: this.targetDeckId, targetDeckId: this.targetDeckId,
removedConfigIds: this.removedConfigs, removedConfigIds: this.removedConfigs,
configs, configs,

View file

@ -3,7 +3,7 @@ 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
--> -->
<script lang="typescript"> <script lang="typescript">
import type pb from "lib/backend_proto"; import type { Backend } from "lib/proto";
import type { PreferenceStore } from "sveltelib/preferences"; import type { PreferenceStore } from "sveltelib/preferences";
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
@ -19,9 +19,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { gatherData, buildHistogram } from "./added"; import { gatherData, buildHistogram } from "./added";
import type { GraphData } from "./added"; import type { GraphData } from "./added";
export let sourceData: pb.BackendProto.GraphsResponse | null = null; export let sourceData: Backend.GraphsResponse | null = null;
import * as tr from "lib/i18n"; import * as tr from "lib/i18n";
export let preferences: PreferenceStore<pb.BackendProto.GraphPreferences>; export let preferences: PreferenceStore<Backend.GraphPreferences>;
let histogramData = null as HistogramData | null; let histogramData = null as HistogramData | null;
let tableData: TableDatum[] = []; let tableData: TableDatum[] = [];

View file

@ -45,7 +45,6 @@ ts_library(
), ),
deps = [ deps = [
"//ts/lib", "//ts/lib",
"//ts/lib:backend_proto",
"//ts/sveltelib", "//ts/sveltelib",
"@npm//@types/d3", "@npm//@types/d3",
"@npm//@types/lodash", "@npm//@types/lodash",
@ -68,7 +67,6 @@ esbuild(
deps = [ deps = [
"//ts/sveltelib", "//ts/sveltelib",
"//ts/lib", "//ts/lib",
"//ts/lib:backend_proto",
":index", ":index",
":base_css", ":base_css",
"@npm//protobufjs", "@npm//protobufjs",

View file

@ -3,7 +3,7 @@ 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
--> -->
<script lang="typescript"> <script lang="typescript">
import type pb from "lib/backend_proto"; import type { Backend } from "lib/proto";
import Graph from "./Graph.svelte"; import Graph from "./Graph.svelte";
import InputBox from "./InputBox.svelte"; import InputBox from "./InputBox.svelte";
@ -14,7 +14,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { renderButtons } from "./buttons"; import { renderButtons } from "./buttons";
import { defaultGraphBounds, GraphRange, RevlogRange } from "./graph-helpers"; import { defaultGraphBounds, GraphRange, RevlogRange } from "./graph-helpers";
export let sourceData: pb.BackendProto.GraphsResponse | null = null; export let sourceData: Backend.GraphsResponse | null = null;
import * as tr from "lib/i18n"; import * as tr from "lib/i18n";
export let revlogRange: RevlogRange; export let revlogRange: RevlogRange;

View file

@ -3,7 +3,7 @@ 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
--> -->
<script lang="typescript"> <script lang="typescript">
import type pb from "lib/backend_proto"; import type { Backend } from "lib/proto";
import type { PreferenceStore } from "sveltelib/preferences"; import type { PreferenceStore } from "sveltelib/preferences";
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
@ -18,8 +18,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { gatherData, renderCalendar } from "./calendar"; import { gatherData, renderCalendar } from "./calendar";
import type { GraphData } from "./calendar"; import type { GraphData } from "./calendar";
export let sourceData: pb.BackendProto.GraphsResponse; export let sourceData: Backend.GraphsResponse;
export let preferences: PreferenceStore<pb.BackendProto.GraphPreferences>; export let preferences: PreferenceStore<Backend.GraphPreferences>;
export let revlogRange: RevlogRange; export let revlogRange: RevlogRange;
import * as tr from "lib/i18n"; import * as tr from "lib/i18n";
export let nightMode: boolean; export let nightMode: boolean;

View file

@ -4,7 +4,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
--> -->
<script lang="typescript"> <script lang="typescript">
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
import type pb from "lib/backend_proto"; import type { Backend } from "lib/proto";
import type { PreferenceStore } from "sveltelib/preferences"; import type { PreferenceStore } from "sveltelib/preferences";
import Graph from "./Graph.svelte"; import Graph from "./Graph.svelte";
@ -15,9 +15,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { gatherData, renderCards } from "./card-counts"; import { gatherData, renderCards } from "./card-counts";
import type { GraphData, TableDatum } from "./card-counts"; import type { GraphData, TableDatum } from "./card-counts";
export let sourceData: pb.BackendProto.GraphsResponse; export let sourceData: Backend.GraphsResponse;
import * as tr2 from "lib/i18n"; import * as tr2 from "lib/i18n";
export let preferences: PreferenceStore<pb.BackendProto.GraphPreferences>; export let preferences: PreferenceStore<Backend.GraphPreferences>;
let { cardCountsSeparateInactive, browserLinksSupported } = preferences; let { cardCountsSeparateInactive, browserLinksSupported } = preferences;
const dispatch = createEventDispatcher<SearchEventMap>(); const dispatch = createEventDispatcher<SearchEventMap>();

View file

@ -3,7 +3,7 @@ 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
--> -->
<script lang="typescript"> <script lang="typescript">
import type pb from "lib/backend_proto"; import type { Backend } from "lib/proto";
import * as tr from "lib/i18n"; import * as tr from "lib/i18n";
import type { PreferenceStore } from "sveltelib/preferences"; import type { PreferenceStore } from "sveltelib/preferences";
@ -17,8 +17,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { gatherData, prepareData } from "./ease"; import { gatherData, prepareData } from "./ease";
import type { TableDatum, SearchEventMap } from "./graph-helpers"; import type { TableDatum, SearchEventMap } from "./graph-helpers";
export let sourceData: pb.BackendProto.GraphsResponse | null = null; export let sourceData: Backend.GraphsResponse | null = null;
export let preferences: PreferenceStore<pb.BackendProto.GraphPreferences>; export let preferences: PreferenceStore<Backend.GraphPreferences>;
const dispatch = createEventDispatcher<SearchEventMap>(); const dispatch = createEventDispatcher<SearchEventMap>();

View file

@ -5,7 +5,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<script lang="typescript"> <script lang="typescript">
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
import type pb from "lib/backend_proto"; import type { Backend } from "lib/proto";
import Graph from "./Graph.svelte"; import Graph from "./Graph.svelte";
import InputBox from "./InputBox.svelte"; import InputBox from "./InputBox.svelte";
@ -20,9 +20,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { gatherData, buildHistogram } from "./future-due"; import { gatherData, buildHistogram } from "./future-due";
import type { GraphData } from "./future-due"; import type { GraphData } from "./future-due";
export let sourceData: pb.BackendProto.GraphsResponse | null = null; export let sourceData: Backend.GraphsResponse | null = null;
import * as tr from "lib/i18n"; import * as tr from "lib/i18n";
export let preferences: PreferenceStore<pb.BackendProto.GraphPreferences>; export let preferences: PreferenceStore<Backend.GraphPreferences>;
const dispatch = createEventDispatcher<SearchEventMap>(); const dispatch = createEventDispatcher<SearchEventMap>();

View file

@ -3,7 +3,7 @@ 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
--> -->
<script lang="typescript"> <script lang="typescript">
import type pb from "lib/backend_proto"; import type { Backend } from "lib/proto";
import Graph from "./Graph.svelte"; import Graph from "./Graph.svelte";
import InputBox from "./InputBox.svelte"; import InputBox from "./InputBox.svelte";
@ -15,7 +15,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { defaultGraphBounds, RevlogRange, GraphRange } from "./graph-helpers"; import { defaultGraphBounds, RevlogRange, GraphRange } from "./graph-helpers";
import { renderHours } from "./hours"; import { renderHours } from "./hours";
export let sourceData: pb.BackendProto.GraphsResponse | null = null; export let sourceData: Backend.GraphsResponse | null = null;
import * as tr from "lib/i18n"; import * as tr from "lib/i18n";
export let revlogRange: RevlogRange; export let revlogRange: RevlogRange;
let graphRange: GraphRange = GraphRange.Year; let graphRange: GraphRange = GraphRange.Year;

View file

@ -5,7 +5,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<script lang="typescript"> <script lang="typescript">
import { timeSpan, MONTH } from "lib/time"; import { timeSpan, MONTH } from "lib/time";
import type pb from "lib/backend_proto"; import type { Backend } from "lib/proto";
import type { PreferenceStore } from "sveltelib/preferences"; import type { PreferenceStore } from "sveltelib/preferences";
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
@ -23,9 +23,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import type { IntervalGraphData } from "./intervals"; import type { IntervalGraphData } from "./intervals";
import type { TableDatum, SearchEventMap } from "./graph-helpers"; import type { TableDatum, SearchEventMap } from "./graph-helpers";
export let sourceData: pb.BackendProto.GraphsResponse | null = null; export let sourceData: Backend.GraphsResponse | null = null;
import * as tr from "lib/i18n"; import * as tr from "lib/i18n";
export let preferences: PreferenceStore<pb.BackendProto.GraphPreferences>; export let preferences: PreferenceStore<Backend.GraphPreferences>;
const dispatch = createEventDispatcher<SearchEventMap>(); const dispatch = createEventDispatcher<SearchEventMap>();

View file

@ -3,7 +3,7 @@ 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
--> -->
<script lang="typescript"> <script lang="typescript">
import type pb from "lib/backend_proto"; import type { Backend } from "lib/proto";
import Graph from "./Graph.svelte"; import Graph from "./Graph.svelte";
import InputBox from "./InputBox.svelte"; import InputBox from "./InputBox.svelte";
@ -19,7 +19,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { gatherData, renderReviews } from "./reviews"; import { gatherData, renderReviews } from "./reviews";
import type { GraphData } from "./reviews"; import type { GraphData } from "./reviews";
export let sourceData: pb.BackendProto.GraphsResponse | null = null; export let sourceData: Backend.GraphsResponse | null = null;
export let revlogRange: RevlogRange; export let revlogRange: RevlogRange;
import * as tr from "lib/i18n"; import * as tr from "lib/i18n";

View file

@ -3,14 +3,14 @@ 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
--> -->
<script lang="typescript"> <script lang="typescript">
import type pb from "lib/backend_proto"; import type { Backend } from "lib/proto";
import Graph from "./Graph.svelte"; import Graph from "./Graph.svelte";
import type { TodayData } from "./today"; import type { TodayData } from "./today";
import { gatherData } from "./today"; import { gatherData } from "./today";
export let sourceData: pb.BackendProto.GraphsResponse | null = null; export let sourceData: Backend.GraphsResponse | null = null;
let todayData: TodayData | null = null; let todayData: TodayData | null = null;
$: if (sourceData) { $: if (sourceData) {

View file

@ -6,7 +6,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import type { Writable } from "svelte/store"; import type { Writable } from "svelte/store";
import type { PreferenceRaw, PreferencePayload } from "sveltelib/preferences"; import type { PreferenceRaw, PreferencePayload } from "sveltelib/preferences";
import pb from "lib/backend_proto"; import { Backend } from "lib/proto";
import { postRequest } from "lib/postrequest"; import { postRequest } from "lib/postrequest";
import useAsync from "sveltelib/async"; import useAsync from "sveltelib/async";
@ -21,24 +21,24 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
async function getGraphData( async function getGraphData(
search: string, search: string,
days: number days: number
): Promise<pb.BackendProto.GraphsResponse> { ): Promise<Backend.GraphsResponse> {
return pb.BackendProto.GraphsResponse.decode( return Backend.GraphsResponse.decode(
await postRequest("/_anki/graphData", JSON.stringify({ search, days })) await postRequest("/_anki/graphData", JSON.stringify({ search, days }))
); );
} }
async function getGraphPreferences(): Promise<pb.BackendProto.GraphPreferences> { async function getGraphPreferences(): Promise<Backend.GraphPreferences> {
return pb.BackendProto.GraphPreferences.decode( return Backend.GraphPreferences.decode(
await postRequest("/_anki/graphPreferences", JSON.stringify({})) await postRequest("/_anki/graphPreferences", JSON.stringify({}))
); );
} }
async function setGraphPreferences( async function setGraphPreferences(
prefs: PreferencePayload<pb.BackendProto.GraphPreferences> prefs: PreferencePayload<Backend.GraphPreferences>
): Promise<void> { ): Promise<void> {
await postRequest( await postRequest(
"/_anki/setGraphPreferences", "/_anki/setGraphPreferences",
pb.BackendProto.GraphPreferences.encode(prefs).finish() Backend.GraphPreferences.encode(prefs).finish()
); );
} }
@ -56,12 +56,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
getPreferences( getPreferences(
getGraphPreferences, getGraphPreferences,
setGraphPreferences, setGraphPreferences,
pb.BackendProto.GraphPreferences.toObject.bind( Backend.GraphPreferences.toObject.bind(Backend.GraphPreferences) as (
pb.BackendProto.GraphPreferences preferences: Backend.GraphPreferences,
) as (
preferences: pb.BackendProto.GraphPreferences,
options: { defaults: boolean } options: { defaults: boolean }
) => PreferenceRaw<pb.BackendProto.GraphPreferences> ) => PreferenceRaw<Backend.GraphPreferences>
) )
); );

View file

@ -6,7 +6,7 @@
@typescript-eslint/no-explicit-any: "off", @typescript-eslint/no-explicit-any: "off",
*/ */
import type pb from "lib/backend_proto"; import type { Backend } from "lib/proto";
import { import {
extent, extent,
@ -28,8 +28,8 @@ export interface GraphData {
daysAdded: number[]; daysAdded: number[];
} }
export function gatherData(data: pb.BackendProto.GraphsResponse): GraphData { export function gatherData(data: Backend.GraphsResponse): GraphData {
const daysAdded = (data.cards as pb.BackendProto.Card[]).map((card) => { const daysAdded = (data.cards as Backend.Card[]).map((card) => {
const elapsedSecs = (card.id as number) / 1000 - data.nextDayAtSecs; const elapsedSecs = (card.id as number) / 1000 - data.nextDayAtSecs;
return Math.ceil(elapsedSecs / 86400); return Math.ceil(elapsedSecs / 86400);
}); });

View file

@ -6,7 +6,7 @@
@typescript-eslint/no-explicit-any: "off", @typescript-eslint/no-explicit-any: "off",
*/ */
import pb from "lib/backend_proto"; import { Backend } from "lib/proto";
import { import {
interpolateRdYlGn, interpolateRdYlGn,
@ -36,18 +36,15 @@ export interface GraphData {
mature: ButtonCounts; mature: ButtonCounts;
} }
const ReviewKind = pb.BackendProto.RevlogEntry.ReviewKind; const ReviewKind = Backend.RevlogEntry.ReviewKind;
export function gatherData( export function gatherData(data: Backend.GraphsResponse, range: GraphRange): GraphData {
data: pb.BackendProto.GraphsResponse,
range: GraphRange
): GraphData {
const cutoff = millisecondCutoffForRange(range, data.nextDayAtSecs); const cutoff = millisecondCutoffForRange(range, data.nextDayAtSecs);
const learning: ButtonCounts = [0, 0, 0, 0]; const learning: ButtonCounts = [0, 0, 0, 0];
const young: ButtonCounts = [0, 0, 0, 0]; const young: ButtonCounts = [0, 0, 0, 0];
const mature: ButtonCounts = [0, 0, 0, 0]; const mature: ButtonCounts = [0, 0, 0, 0];
for (const review of data.revlog as pb.BackendProto.RevlogEntry[]) { for (const review of data.revlog as Backend.RevlogEntry[]) {
if (cutoff && (review.id as number) < cutoff) { if (cutoff && (review.id as number) < cutoff) {
continue; continue;
} }
@ -99,7 +96,7 @@ interface TotalCorrect {
export function renderButtons( export function renderButtons(
svgElem: SVGElement, svgElem: SVGElement,
bounds: GraphBounds, bounds: GraphBounds,
origData: pb.BackendProto.GraphsResponse, origData: Backend.GraphsResponse,
range: GraphRange range: GraphRange
): void { ): void {
const sourceData = gatherData(origData, range); const sourceData = gatherData(origData, range);

View file

@ -5,7 +5,7 @@
@typescript-eslint/no-non-null-assertion: "off", @typescript-eslint/no-non-null-assertion: "off",
*/ */
import pb from "lib/backend_proto"; import { Backend } from "lib/proto";
import { import {
interpolateBlues, interpolateBlues,
select, select,
@ -49,16 +49,16 @@ interface DayDatum {
date: Date; date: Date;
} }
type WeekdayType = pb.BackendProto.GraphPreferences.Weekday; type WeekdayType = Backend.GraphPreferences.Weekday;
const Weekday = pb.BackendProto.GraphPreferences.Weekday; /* enum */ const Weekday = Backend.GraphPreferences.Weekday; /* enum */
export function gatherData( export function gatherData(
data: pb.BackendProto.GraphsResponse, data: Backend.GraphsResponse,
firstDayOfWeek: WeekdayType firstDayOfWeek: WeekdayType
): GraphData { ): GraphData {
const reviewCount = new Map<number, number>(); const reviewCount = new Map<number, number>();
for (const review of data.revlog as pb.BackendProto.RevlogEntry[]) { for (const review of data.revlog as Backend.RevlogEntry[]) {
if (review.buttonChosen == 0) { if (review.buttonChosen == 0) {
continue; continue;
} }

View file

@ -7,7 +7,7 @@
*/ */
import { CardQueue, CardType } from "lib/cards"; import { CardQueue, CardType } from "lib/cards";
import type pb from "lib/backend_proto"; import type { Backend } from "lib/proto";
import { import {
schemeGreens, schemeGreens,
schemeBlues, schemeBlues,
@ -41,10 +41,7 @@ const barColours = [
"grey" /* buried */, "grey" /* buried */,
]; ];
function countCards( function countCards(cards: Backend.ICard[], separateInactive: boolean): Count[] {
cards: pb.BackendProto.ICard[],
separateInactive: boolean
): Count[] {
let newCards = 0; let newCards = 0;
let learn = 0; let learn = 0;
let relearn = 0; let relearn = 0;
@ -53,7 +50,7 @@ function countCards(
let suspended = 0; let suspended = 0;
let buried = 0; let buried = 0;
for (const card of cards as pb.BackendProto.Card[]) { for (const card of cards as Backend.Card[]) {
if (separateInactive) { if (separateInactive) {
switch (card.queue) { switch (card.queue) {
case CardQueue.Suspended: case CardQueue.Suspended:
@ -127,7 +124,7 @@ function countCards(
} }
export function gatherData( export function gatherData(
data: pb.BackendProto.GraphsResponse, data: Backend.GraphsResponse,
separateInactive: boolean separateInactive: boolean
): GraphData { ): GraphData {
const totalCards = data.cards.length; const totalCards = data.cards.length;

View file

@ -6,7 +6,7 @@
@typescript-eslint/no-explicit-any: "off", @typescript-eslint/no-explicit-any: "off",
*/ */
import type pb from "lib/backend_proto"; import type { Backend } from "lib/proto";
import { import {
extent, extent,
histogram, histogram,
@ -26,8 +26,8 @@ export interface GraphData {
eases: number[]; eases: number[];
} }
export function gatherData(data: pb.BackendProto.GraphsResponse): GraphData { export function gatherData(data: Backend.GraphsResponse): GraphData {
const eases = (data.cards as pb.BackendProto.Card[]) const eases = (data.cards as Backend.Card[])
.filter((c) => [CardType.Review, CardType.Relearn].includes(c.ctype)) .filter((c) => [CardType.Review, CardType.Relearn].includes(c.ctype))
.map((c) => c.easeFactor / 10); .map((c) => c.easeFactor / 10);
return { eases }; return { eases };

View file

@ -6,7 +6,7 @@
@typescript-eslint/no-explicit-any: "off", @typescript-eslint/no-explicit-any: "off",
*/ */
import type pb from "lib/backend_proto"; import type { Backend } from "lib/proto";
import { import {
extent, extent,
histogram, histogram,
@ -30,13 +30,13 @@ export interface GraphData {
haveBacklog: boolean; haveBacklog: boolean;
} }
export function gatherData(data: pb.BackendProto.GraphsResponse): GraphData { export function gatherData(data: Backend.GraphsResponse): GraphData {
const isLearning = (card: pb.BackendProto.Card): boolean => const isLearning = (card: Backend.Card): boolean =>
[CardQueue.Learn, CardQueue.PreviewRepeat].includes(card.queue); [CardQueue.Learn, CardQueue.PreviewRepeat].includes(card.queue);
let haveBacklog = false; let haveBacklog = false;
const due = (data.cards as pb.BackendProto.Card[]) const due = (data.cards as Backend.Card[])
.filter((c: pb.BackendProto.Card) => { .filter((c: Backend.Card) => {
// reviews // reviews
return ( return (
[CardQueue.Review, CardQueue.DayLearn].includes(c.queue) || [CardQueue.Review, CardQueue.DayLearn].includes(c.queue) ||
@ -44,7 +44,7 @@ export function gatherData(data: pb.BackendProto.GraphsResponse): GraphData {
isLearning(c) isLearning(c)
); );
}) })
.map((c: pb.BackendProto.Card) => { .map((c: Backend.Card) => {
let dueDay: number; let dueDay: number;
if (isLearning(c)) { if (isLearning(c)) {

View file

@ -6,7 +6,7 @@
@typescript-eslint/no-explicit-any: "off", @typescript-eslint/no-explicit-any: "off",
@typescript-eslint/ban-ts-comment: "off" */ @typescript-eslint/ban-ts-comment: "off" */
import type pb from "lib/backend_proto"; import type { Backend } from "lib/proto";
import type { Selection } from "d3"; import type { Selection } from "d3";
// amount of data to fetch from backend // amount of data to fetch from backend
@ -28,8 +28,8 @@ export enum GraphRange {
} }
export interface GraphsContext { export interface GraphsContext {
cards: pb.BackendProto.Card[]; cards: Backend.Card[];
revlog: pb.BackendProto.RevlogEntry[]; revlog: Backend.RevlogEntry[];
revlogRange: RevlogRange; revlogRange: RevlogRange;
nightMode: boolean; nightMode: boolean;
} }

View file

@ -6,7 +6,7 @@
@typescript-eslint/no-explicit-any: "off", @typescript-eslint/no-explicit-any: "off",
*/ */
import pb from "lib/backend_proto"; import { Backend } from "lib/proto";
import { import {
interpolateBlues, interpolateBlues,
select, select,
@ -37,15 +37,15 @@ interface Hour {
correctCount: number; correctCount: number;
} }
const ReviewKind = pb.BackendProto.RevlogEntry.ReviewKind; const ReviewKind = Backend.RevlogEntry.ReviewKind;
function gatherData(data: pb.BackendProto.GraphsResponse, range: GraphRange): Hour[] { function gatherData(data: Backend.GraphsResponse, range: GraphRange): Hour[] {
const hours = [...Array(24)].map((_n, idx: number) => { const hours = [...Array(24)].map((_n, idx: number) => {
return { hour: idx, totalCount: 0, correctCount: 0 } as Hour; return { hour: idx, totalCount: 0, correctCount: 0 } as Hour;
}); });
const cutoff = millisecondCutoffForRange(range, data.nextDayAtSecs); const cutoff = millisecondCutoffForRange(range, data.nextDayAtSecs);
for (const review of data.revlog as pb.BackendProto.RevlogEntry[]) { for (const review of data.revlog as Backend.RevlogEntry[]) {
switch (review.reviewKind) { switch (review.reviewKind) {
case ReviewKind.LEARNING: case ReviewKind.LEARNING:
case ReviewKind.REVIEW: case ReviewKind.REVIEW:
@ -74,7 +74,7 @@ function gatherData(data: pb.BackendProto.GraphsResponse, range: GraphRange): Ho
export function renderHours( export function renderHours(
svgElem: SVGElement, svgElem: SVGElement,
bounds: GraphBounds, bounds: GraphBounds,
origData: pb.BackendProto.GraphsResponse, origData: Backend.GraphsResponse,
range: GraphRange range: GraphRange
): void { ): void {
const data = gatherData(origData, range); const data = gatherData(origData, range);

View file

@ -6,7 +6,7 @@
@typescript-eslint/no-explicit-any: "off", @typescript-eslint/no-explicit-any: "off",
*/ */
import type pb from "lib/backend_proto"; import type { Backend } from "lib/proto";
import { import {
extent, extent,
histogram, histogram,
@ -36,10 +36,8 @@ export enum IntervalRange {
All = 3, All = 3,
} }
export function gatherIntervalData( export function gatherIntervalData(data: Backend.GraphsResponse): IntervalGraphData {
data: pb.BackendProto.GraphsResponse const intervals = (data.cards as Backend.Card[])
): IntervalGraphData {
const intervals = (data.cards as pb.BackendProto.Card[])
.filter((c) => [CardType.Review, CardType.Relearn].includes(c.ctype)) .filter((c) => [CardType.Review, CardType.Relearn].includes(c.ctype))
.map((c) => c.interval); .map((c) => c.interval);
return { intervals }; return { intervals };

View file

@ -6,7 +6,7 @@
@typescript-eslint/no-explicit-any: "off", @typescript-eslint/no-explicit-any: "off",
*/ */
import pb from "lib/backend_proto"; import { Backend } from "lib/proto";
import { timeSpan, dayLabel } from "lib/time"; import { timeSpan, dayLabel } from "lib/time";
import { import {
@ -50,15 +50,15 @@ export interface GraphData {
reviewTime: Map<number, Reviews>; reviewTime: Map<number, Reviews>;
} }
const ReviewKind = pb.BackendProto.RevlogEntry.ReviewKind; const ReviewKind = Backend.RevlogEntry.ReviewKind;
type BinType = Bin<Map<number, Reviews[]>, number>; type BinType = Bin<Map<number, Reviews[]>, number>;
export function gatherData(data: pb.BackendProto.GraphsResponse): GraphData { export function gatherData(data: Backend.GraphsResponse): GraphData {
const reviewCount = new Map<number, Reviews>(); const reviewCount = new Map<number, Reviews>();
const reviewTime = new Map<number, Reviews>(); const reviewTime = new Map<number, Reviews>();
const empty = { mature: 0, young: 0, learn: 0, relearn: 0, early: 0 }; const empty = { mature: 0, young: 0, learn: 0, relearn: 0, early: 0 };
for (const review of data.revlog as pb.BackendProto.RevlogEntry[]) { for (const review of data.revlog as Backend.RevlogEntry[]) {
if (review.reviewKind == ReviewKind.MANUAL) { if (review.reviewKind == ReviewKind.MANUAL) {
// don't count days with only manual scheduling // don't count days with only manual scheduling
continue; continue;

View file

@ -1,7 +1,7 @@
// 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
import pb from "lib/backend_proto"; import { Backend } from "lib/proto";
import { studiedToday } from "lib/time"; import { studiedToday } from "lib/time";
import * as tr from "lib/i18n"; import * as tr from "lib/i18n";
@ -11,9 +11,9 @@ export interface TodayData {
lines: string[]; lines: string[];
} }
const ReviewKind = pb.BackendProto.RevlogEntry.ReviewKind; const ReviewKind = Backend.RevlogEntry.ReviewKind;
export function gatherData(data: pb.BackendProto.GraphsResponse): TodayData { export function gatherData(data: Backend.GraphsResponse): TodayData {
let answerCount = 0; let answerCount = 0;
let answerMillis = 0; let answerMillis = 0;
let correctCount = 0; let correctCount = 0;
@ -26,7 +26,7 @@ export function gatherData(data: pb.BackendProto.GraphsResponse): TodayData {
const startOfTodayMillis = (data.nextDayAtSecs - 86400) * 1000; const startOfTodayMillis = (data.nextDayAtSecs - 86400) * 1000;
for (const review of data.revlog as pb.BackendProto.RevlogEntry[]) { for (const review of data.revlog as Backend.RevlogEntry[]) {
if (review.id < startOfTodayMillis) { if (review.id < startOfTodayMillis) {
continue; continue;
} }

6
ts/lib/proto.ts Normal file
View file

@ -0,0 +1,6 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { anki } from "./backend_proto";
import Backend = anki.backend;
export { Backend };

View file

@ -8,10 +8,7 @@ load("//ts:compile_sass.bzl", "compile_sass")
ts_library( ts_library(
name = "lib", name = "lib",
srcs = glob(["*.ts"]), srcs = glob(["*.ts"]),
deps = [ deps = ["//ts/lib"],
"//ts/lib",
"//ts/lib:backend_proto",
],
) )
esbuild( esbuild(
@ -25,7 +22,6 @@ esbuild(
deps = [ deps = [
":lib", ":lib",
"//ts/lib", "//ts/lib",
"//ts/lib:backend_proto",
"@npm//protobufjs", "@npm//protobufjs",
], ],
) )

View file

@ -1,26 +1,26 @@
// 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
import * as pb from "lib/backend_proto"; import { Backend } from "lib/proto";
import { postRequest } from "lib/postrequest"; import { postRequest } from "lib/postrequest";
async function getNextStates(): Promise<pb.BackendProto.NextCardStates> { async function getNextStates(): Promise<Backend.NextCardStates> {
return pb.BackendProto.NextCardStates.decode( return Backend.NextCardStates.decode(
await postRequest("/_anki/nextCardStates", "") await postRequest("/_anki/nextCardStates", "")
); );
} }
async function setNextStates( async function setNextStates(
key: string, key: string,
states: pb.BackendProto.NextCardStates states: Backend.NextCardStates
): Promise<void> { ): Promise<void> {
const data: Uint8Array = pb.BackendProto.NextCardStates.encode(states).finish(); const data: Uint8Array = Backend.NextCardStates.encode(states).finish();
await postRequest("/_anki/setNextCardStates", data, { key }); await postRequest("/_anki/setNextCardStates", data, { key });
} }
export async function mutateNextCardStates( export async function mutateNextCardStates(
key: string, key: string,
mutator: (states: pb.BackendProto.NextCardStates) => void mutator: (states: Backend.NextCardStates) => void
): Promise<void> { ): Promise<void> {
const states = await getNextStates(); const states = await getNextStates();
mutator(states); mutator(states);

View file

@ -91,7 +91,6 @@ def svelte_check(name = "svelte_check", srcs = []):
"//ts:tsconfig.json", "//ts:tsconfig.json",
"//ts/sveltelib", "//ts/sveltelib",
"//ts/lib", "//ts/lib",
"//ts/lib:backend_proto",
"@npm//sass", "@npm//sass",
] + srcs, ] + srcs,
env = {"SASS_PATH": "$(rootpath //ts:tsconfig.json)/../.."}, env = {"SASS_PATH": "$(rootpath //ts:tsconfig.json)/../.."},

View file

@ -157,9 +157,9 @@ async function writeJs(
genDir, genDir,
// a nasty hack to ensure ts/sass/... resolves correctly // a nasty hack to ensure ts/sass/... resolves correctly
// when invoked from an external workspace // when invoked from an external workspace
binDir + "/external/net_ankiweb_anki", binDir + "/external/anki",
genDir + "/external/net_ankiweb_anki", genDir + "/external/anki",
binDir + "/../../../external/net_ankiweb_anki", binDir + "/../../../external/anki",
], ],
}, },
}); });