split out remaining tags, stats, media and rendering

This commit is contained in:
Damien Elmes 2021-07-10 21:58:34 +10:00
parent 35b059ecdb
commit 185e9acd22
44 changed files with 413 additions and 382 deletions

View file

@ -3,7 +3,7 @@ load("@bazel_skylib//lib:versions.bzl", "versions")
load("@rules_rust//rust:repositories.bzl", "rust_repositories")
load("@anki//cargo:crates.bzl", "raze_fetch_remote_crates")
load(":python.bzl", "setup_local_python")
load(":protobuf.bzl", "setup_protobuf_binary")
load("//proto:protobuf.bzl", "setup_protobuf_binary")
load("//proto:format.bzl", "setup_clang_format")
load("@build_bazel_rules_nodejs//:index.bzl", "node_repositories", "yarn_install")
load("@io_bazel_rules_sass//:defs.bzl", "sass_repositories")

View file

@ -5,15 +5,6 @@ syntax = "proto3";
package anki.backend;
import "anki/generic.proto";
import "anki/cards.proto";
import "anki/collection.proto";
import "anki/notes.proto";
import "anki/notetypes.proto";
// Backend methods
///////////////////////////////////////////////////////////
/// while the protobuf descriptors expose the order services are defined in,
/// that information is not available in prost, so we define an enum to make
/// sure all clients agree on the service index
@ -35,54 +26,6 @@ enum ServiceIndex {
SERVICE_INDEX_CARDS = 14;
}
service CardRenderingService {
rpc ExtractAVTags(ExtractAVTagsRequest) returns (ExtractAVTagsResponse);
rpc ExtractLatex(ExtractLatexRequest) returns (ExtractLatexResponse);
rpc GetEmptyCards(generic.Empty) returns (EmptyCardsReport);
rpc RenderExistingCard(RenderExistingCardRequest)
returns (RenderCardResponse);
rpc RenderUncommittedCard(RenderUncommittedCardRequest)
returns (RenderCardResponse);
rpc RenderUncommittedCardLegacy(RenderUncommittedCardLegacyRequest)
returns (RenderCardResponse);
rpc StripAVTags(generic.String) returns (generic.String);
rpc RenderMarkdown(RenderMarkdownRequest) returns (generic.String);
}
service TagsService {
rpc ClearUnusedTags(generic.Empty) returns (collection.OpChangesWithCount);
rpc AllTags(generic.Empty) returns (generic.StringList);
rpc RemoveTags(generic.String) returns (collection.OpChangesWithCount);
rpc SetTagCollapsed(SetTagCollapsedRequest) returns (collection.OpChanges);
rpc TagTree(generic.Empty) returns (TagTreeNode);
rpc ReparentTags(ReparentTagsRequest) returns (collection.OpChangesWithCount);
rpc RenameTags(RenameTagsRequest) returns (collection.OpChangesWithCount);
rpc AddNoteTags(NoteIdsAndTagsRequest)
returns (collection.OpChangesWithCount);
rpc RemoveNoteTags(NoteIdsAndTagsRequest)
returns (collection.OpChangesWithCount);
rpc FindAndReplaceTag(FindAndReplaceTagRequest)
returns (collection.OpChangesWithCount);
}
service StatsService {
rpc CardStats(cards.CardId) returns (generic.String);
rpc Graphs(GraphsRequest) returns (GraphsResponse);
rpc GetGraphPreferences(generic.Empty) returns (GraphPreferences);
rpc SetGraphPreferences(GraphPreferences) returns (generic.Empty);
}
service MediaService {
rpc CheckMedia(generic.Empty) returns (CheckMediaResponse);
rpc TrashMediaFiles(TrashMediaFilesRequest) returns (generic.Empty);
rpc AddMediaFile(AddMediaFileRequest) returns (generic.String);
rpc EmptyTrash(generic.Empty) returns (generic.Empty);
rpc RestoreTrash(generic.Empty) returns (generic.Empty);
}
// Backend
///////////////////////////////////////////////////////////
message BackendInit {
repeated string preferred_langs = 1;
string locale_folder_path = 2;
@ -94,9 +37,6 @@ message I18nBackendInit {
string locale_folder_path = 5;
}
// Errors
///////////////////////////////////////////////////////////
message BackendError {
enum Kind {
INVALID_INPUT = 0;
@ -121,208 +61,3 @@ message BackendError {
// the error subtype
Kind kind = 2;
}
// Messages
///////////////////////////////////////////////////////////
message RenderExistingCardRequest {
int64 card_id = 1;
bool browser = 2;
}
message RenderUncommittedCardRequest {
notes.Note note = 1;
uint32 card_ord = 2;
notetypes.Notetype.Template template = 3;
bool fill_empty = 4;
}
message RenderUncommittedCardLegacyRequest {
notes.Note note = 1;
uint32 card_ord = 2;
bytes template = 3;
bool fill_empty = 4;
}
message RenderCardResponse {
repeated RenderedTemplateNode question_nodes = 1;
repeated RenderedTemplateNode answer_nodes = 2;
string css = 3;
bool latex_svg = 4;
}
message RenderedTemplateNode {
oneof value {
string text = 1;
RenderedTemplateReplacement replacement = 2;
}
}
message RenderedTemplateReplacement {
string field_name = 1;
string current_text = 2;
repeated string filters = 3;
}
message ExtractAVTagsRequest {
string text = 1;
bool question_side = 2;
}
message ExtractAVTagsResponse {
string text = 1;
repeated AVTag av_tags = 2;
}
message AVTag {
oneof value {
string sound_or_video = 1;
TTSTag tts = 2;
}
}
message TTSTag {
string field_text = 1;
string lang = 2;
repeated string voices = 3;
float speed = 4;
repeated string other_args = 5;
}
message ExtractLatexRequest {
string text = 1;
bool svg = 2;
bool expand_clozes = 3;
}
message ExtractLatexResponse {
string text = 1;
repeated ExtractedLatex latex = 2;
}
message ExtractedLatex {
string filename = 1;
string latex_body = 2;
}
message AddMediaFileRequest {
string desired_name = 1;
bytes data = 2;
}
message CheckMediaResponse {
repeated string unused = 1;
repeated string missing = 2;
string report = 3;
bool have_trash = 4;
}
message TrashMediaFilesRequest {
repeated string fnames = 1;
}
message CongratsLearnMessageRequest {
float next_due = 1;
uint32 remaining = 2;
}
message SetTagCollapsedRequest {
string name = 1;
bool collapsed = 2;
}
message GetChangedTagsResponse {
repeated string tags = 1;
}
message TagTreeNode {
string name = 1;
repeated TagTreeNode children = 2;
uint32 level = 3;
bool collapsed = 4;
}
message ReparentTagsRequest {
repeated string tags = 1;
string new_parent = 2;
}
message RenameTagsRequest {
string current_prefix = 1;
string new_prefix = 2;
}
message EmptyCardsReport {
message NoteWithEmptyCards {
int64 note_id = 1;
repeated int64 card_ids = 2;
bool will_delete_note = 3;
}
string report = 1;
repeated NoteWithEmptyCards notes = 2;
}
message NoteIdsAndTagsRequest {
repeated int64 note_ids = 1;
string tags = 2;
}
message FindAndReplaceTagRequest {
repeated int64 note_ids = 1;
string search = 2;
string replacement = 3;
bool regex = 4;
bool match_case = 5;
}
message GraphsRequest {
string search = 1;
uint32 days = 2;
}
message GraphsResponse {
repeated cards.Card cards = 1;
repeated RevlogEntry revlog = 2;
uint32 days_elapsed = 3;
// Based on rollover hour
uint32 next_day_at_secs = 4;
uint32 scheduler_version = 5;
/// Seconds to add to UTC timestamps to get local time.
int32 local_offset_secs = 7;
}
message GraphPreferences {
enum Weekday {
SUNDAY = 0;
MONDAY = 1;
FRIDAY = 5;
SATURDAY = 6;
}
Weekday calendar_first_day_of_week = 1;
bool card_counts_separate_inactive = 2;
bool browser_links_supported = 3;
bool future_due_show_backlog = 4;
}
message RevlogEntry {
enum ReviewKind {
LEARNING = 0;
REVIEW = 1;
RELEARNING = 2;
EARLY_REVIEW = 3;
MANUAL = 4;
}
int64 id = 1;
int64 cid = 2;
int32 usn = 3;
uint32 button_chosen = 4;
int32 interval = 5;
int32 last_interval = 6;
uint32 ease_factor = 7;
uint32 taken_millis = 8;
ReviewKind review_kind = 9;
}
message RenderMarkdownRequest {
string markdown = 1;
bool sanitize = 2;
}

View file

@ -0,0 +1,119 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
syntax = "proto3";
package anki.card_rendering;
import "anki/generic.proto";
import "anki/notes.proto";
import "anki/notetypes.proto";
service CardRenderingService {
rpc ExtractAVTags(ExtractAVTagsRequest) returns (ExtractAVTagsResponse);
rpc ExtractLatex(ExtractLatexRequest) returns (ExtractLatexResponse);
rpc GetEmptyCards(generic.Empty) returns (EmptyCardsReport);
rpc RenderExistingCard(RenderExistingCardRequest)
returns (RenderCardResponse);
rpc RenderUncommittedCard(RenderUncommittedCardRequest)
returns (RenderCardResponse);
rpc RenderUncommittedCardLegacy(RenderUncommittedCardLegacyRequest)
returns (RenderCardResponse);
rpc StripAVTags(generic.String) returns (generic.String);
rpc RenderMarkdown(RenderMarkdownRequest) returns (generic.String);
}
message ExtractAVTagsRequest {
string text = 1;
bool question_side = 2;
}
message ExtractAVTagsResponse {
string text = 1;
repeated AVTag av_tags = 2;
}
message AVTag {
oneof value {
string sound_or_video = 1;
TTSTag tts = 2;
}
}
message TTSTag {
string field_text = 1;
string lang = 2;
repeated string voices = 3;
float speed = 4;
repeated string other_args = 5;
}
message ExtractLatexRequest {
string text = 1;
bool svg = 2;
bool expand_clozes = 3;
}
message ExtractLatexResponse {
string text = 1;
repeated ExtractedLatex latex = 2;
}
message ExtractedLatex {
string filename = 1;
string latex_body = 2;
}
message EmptyCardsReport {
message NoteWithEmptyCards {
int64 note_id = 1;
repeated int64 card_ids = 2;
bool will_delete_note = 3;
}
string report = 1;
repeated NoteWithEmptyCards notes = 2;
}
message RenderExistingCardRequest {
int64 card_id = 1;
bool browser = 2;
}
message RenderUncommittedCardRequest {
notes.Note note = 1;
uint32 card_ord = 2;
notetypes.Notetype.Template template = 3;
bool fill_empty = 4;
}
message RenderUncommittedCardLegacyRequest {
notes.Note note = 1;
uint32 card_ord = 2;
bytes template = 3;
bool fill_empty = 4;
}
message RenderCardResponse {
repeated RenderedTemplateNode question_nodes = 1;
repeated RenderedTemplateNode answer_nodes = 2;
string css = 3;
bool latex_svg = 4;
}
message RenderedTemplateNode {
oneof value {
string text = 1;
RenderedTemplateReplacement replacement = 2;
}
}
message RenderedTemplateReplacement {
string field_name = 1;
string current_text = 2;
repeated string filters = 3;
}
message RenderMarkdownRequest {
string markdown = 1;
bool sanitize = 2;
}

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

@ -0,0 +1,32 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
syntax = "proto3";
package anki.media;
import "anki/generic.proto";
service MediaService {
rpc CheckMedia(generic.Empty) returns (CheckMediaResponse);
rpc TrashMediaFiles(TrashMediaFilesRequest) returns (generic.Empty);
rpc AddMediaFile(AddMediaFileRequest) returns (generic.String);
rpc EmptyTrash(generic.Empty) returns (generic.Empty);
rpc RestoreTrash(generic.Empty) returns (generic.Empty);
}
message CheckMediaResponse {
repeated string unused = 1;
repeated string missing = 2;
string report = 3;
bool have_trash = 4;
}
message TrashMediaFilesRequest {
repeated string fnames = 1;
}
message AddMediaFileRequest {
string desired_name = 1;
bytes data = 2;
}

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

@ -0,0 +1,64 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
syntax = "proto3";
package anki.stats;
import "anki/generic.proto";
import "anki/cards.proto";
service StatsService {
rpc CardStats(cards.CardId) returns (generic.String);
rpc Graphs(GraphsRequest) returns (GraphsResponse);
rpc GetGraphPreferences(generic.Empty) returns (GraphPreferences);
rpc SetGraphPreferences(GraphPreferences) returns (generic.Empty);
}
message GraphsRequest {
string search = 1;
uint32 days = 2;
}
message GraphsResponse {
repeated cards.Card cards = 1;
repeated RevlogEntry revlog = 2;
uint32 days_elapsed = 3;
// Based on rollover hour
uint32 next_day_at_secs = 4;
uint32 scheduler_version = 5;
/// Seconds to add to UTC timestamps to get local time.
int32 local_offset_secs = 7;
}
message GraphPreferences {
enum Weekday {
SUNDAY = 0;
MONDAY = 1;
FRIDAY = 5;
SATURDAY = 6;
}
Weekday calendar_first_day_of_week = 1;
bool card_counts_separate_inactive = 2;
bool browser_links_supported = 3;
bool future_due_show_backlog = 4;
}
message RevlogEntry {
enum ReviewKind {
LEARNING = 0;
REVIEW = 1;
RELEARNING = 2;
EARLY_REVIEW = 3;
MANUAL = 4;
}
int64 id = 1;
int64 cid = 2;
int32 usn = 3;
uint32 button_chosen = 4;
int32 interval = 5;
int32 last_interval = 6;
uint32 ease_factor = 7;
uint32 taken_millis = 8;
ReviewKind review_kind = 9;
}

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

@ -0,0 +1,60 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
syntax = "proto3";
package anki.tags;
import "anki/generic.proto";
import "anki/collection.proto";
service TagsService {
rpc ClearUnusedTags(generic.Empty) returns (collection.OpChangesWithCount);
rpc AllTags(generic.Empty) returns (generic.StringList);
rpc RemoveTags(generic.String) returns (collection.OpChangesWithCount);
rpc SetTagCollapsed(SetTagCollapsedRequest) returns (collection.OpChanges);
rpc TagTree(generic.Empty) returns (TagTreeNode);
rpc ReparentTags(ReparentTagsRequest) returns (collection.OpChangesWithCount);
rpc RenameTags(RenameTagsRequest) returns (collection.OpChangesWithCount);
rpc AddNoteTags(NoteIdsAndTagsRequest)
returns (collection.OpChangesWithCount);
rpc RemoveNoteTags(NoteIdsAndTagsRequest)
returns (collection.OpChangesWithCount);
rpc FindAndReplaceTag(FindAndReplaceTagRequest)
returns (collection.OpChangesWithCount);
}
message SetTagCollapsedRequest {
string name = 1;
bool collapsed = 2;
}
message TagTreeNode {
string name = 1;
repeated TagTreeNode children = 2;
uint32 level = 3;
bool collapsed = 4;
}
message ReparentTagsRequest {
repeated string tags = 1;
string new_parent = 2;
}
message RenameTagsRequest {
string current_prefix = 1;
string new_prefix = 2;
}
message NoteIdsAndTagsRequest {
repeated int64 note_ids = 1;
string tags = 2;
}
message FindAndReplaceTagRequest {
repeated int64 note_ids = 1;
string search = 2;
string replacement = 3;
bool regex = 4;
bool match_case = 5;
}

View file

@ -32,7 +32,7 @@ py_library(
"py.typed",
":buildinfo",
":hooks_gen",
":proto",
":proto_py",
"//pylib/anki/_backend",
],
imports = [
@ -110,26 +110,17 @@ filegroup(
load("//pylib:protobuf.bzl", "py_proto")
py_proto(
name = "proto_files",
name = "proto_py",
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"],
srcs = [":proto_py", "__init__.py"],
imports = [".."],
visibility = ["//pylib:__subpackages__"],
visibility = ["//visibility:public"],
)

View file

@ -1,18 +1,20 @@
# Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import os
import sys
from anki.buildinfo import version
from anki.collection import Collection
if not os.getenv("PROTOS_ONLY"):
from anki.buildinfo import version
from anki.collection import Collection
if sys.version_info[0] < 3 or sys.version_info[1] < 7:
raise Exception("Anki requires Python 3.7+")
if sys.version_info[0] < 3 or sys.version_info[1] < 7:
raise Exception("Anki requires Python 3.7+")
# ensure unicode filenames are supported
try:
"テスト".encode(sys.getfilesystemencoding())
except UnicodeEncodeError as exc:
raise Exception("Anki requires a UTF-8 locale.") from exc
# ensure unicode filenames are supported
try:
"テスト".encode(sys.getfilesystemencoding())
except UnicodeEncodeError as exc:
raise Exception("Anki requires a UTF-8 locale.") from exc
__all__ = ["Collection"]
__all__ = ["Collection"]

View file

@ -8,6 +8,8 @@ import sys
import google.protobuf.descriptor
os.environ["PROTOS_ONLY"] = "1"
import anki.backend_pb2
import anki.i18n_pb2
import anki.cards_pb2
@ -20,6 +22,10 @@ import anki.scheduler_pb2
import anki.sync_pb2
import anki.configs_pb2
import anki.search_pb2
import anki.stats_pb2
import anki.card_rendering_pb2
import anki.tags_pb2
import anki.media_pb2
import stringcase
@ -187,12 +193,16 @@ service_modules = dict(
SYNC=anki.sync_pb2,
CONFIGS=anki.configs_pb2,
SEARCH=anki.search_pb2,
STATS=anki.stats_pb2,
CARD_RENDERING=anki.card_rendering_pb2,
TAGS=anki.tags_pb2,
MEDIA=anki.media_pb2,
)
for service in anki.backend_pb2.ServiceIndex.DESCRIPTOR.values:
# SERVICE_INDEX_TEST -> _TESTSERVICE
base = service.name.replace("SERVICE_INDEX_", "")
service_pkg = service_modules.get(base) or anki.backend_pb2
service_pkg = service_modules.get(base)
service_var = "_" + base.replace("_", "") + "SERVICE"
service_obj = getattr(service_pkg, service_var)
service_index = service.number

View file

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

View file

@ -7,15 +7,21 @@ from __future__ import annotations
from typing import Any, Generator, List, Literal, Optional, Sequence, Tuple, Union, cast
import anki.backend_pb2 as _pb
from anki import collection_pb2, configs_pb2, generic_pb2, search_pb2
from anki import (
card_rendering_pb2,
collection_pb2,
configs_pb2,
generic_pb2,
search_pb2,
stats_pb2,
)
from anki._legacy import DeprecatedNamesMixin, deprecated
# protobuf we publicly export - listed first to avoid circular imports
SearchNode = search_pb2.SearchNode
Progress = collection_pb2.Progress
EmptyCardsReport = _pb.EmptyCardsReport
GraphPreferences = _pb.GraphPreferences
EmptyCardsReport = card_rendering_pb2.EmptyCardsReport
GraphPreferences = stats_pb2.GraphPreferences
Preferences = configs_pb2.Preferences
UndoStatus = collection_pb2.UndoStatus
OpChanges = collection_pb2.OpChanges

View file

@ -10,8 +10,7 @@ from dataclasses import dataclass
from typing import Any, List, Optional, Tuple
import anki
import anki.backend_pb2 as _pb
from anki import hooks
from anki import card_rendering_pb2, hooks
from anki.models import NotetypeDict
from anki.template import TemplateRenderContext, TemplateRenderOutput
from anki.utils import call, isMac, namedtmp, tmpdir
@ -45,7 +44,9 @@ class ExtractedLatexOutput:
latex: List[ExtractedLatex]
@staticmethod
def from_proto(proto: _pb.ExtractLatexResponse) -> ExtractedLatexOutput:
def from_proto(
proto: card_rendering_pb2.ExtractLatexResponse,
) -> ExtractedLatexOutput:
return ExtractedLatexOutput(
html=proto.text,
latex=[

View file

@ -10,8 +10,7 @@ import sys
import time
from typing import Any, Callable, List, Optional, Tuple
import anki
import anki.backend_pb2 as _pb
from anki import media_pb2
from anki._legacy import deprecated
from anki.consts import *
from anki.latex import render_latex, render_latex_returning_errors
@ -27,7 +26,7 @@ def media_paths_from_col_path(col_path: str) -> Tuple[str, str]:
return (media_folder, media_db)
CheckMediaResponse = _pb.CheckMediaResponse
CheckMediaResponse = media_pb2.CheckMediaResponse
# fixme: look into whether we can drop chdir() below

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

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

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

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

View file

@ -16,15 +16,15 @@ import re
from typing import Collection, List, Match, Optional, Sequence
import anki # pylint: disable=unused-import
import anki.backend_pb2 as _pb
import anki.collection
from anki import tags_pb2
from anki.collection import OpChanges, OpChangesWithCount
from anki.decks import DeckId
from anki.notes import NoteId
from anki.utils import ids2str
# public exports
TagTreeNode = _pb.TagTreeNode
TagTreeNode = tags_pb2.TagTreeNode
MARKED_TAG = "marked"

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

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

View file

@ -32,8 +32,7 @@ from dataclasses import dataclass
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union
import anki
import anki.backend_pb2 as _pb
from anki import hooks
from anki import card_rendering_pb2, hooks
from anki.cards import Card
from anki.decks import DeckManager
from anki.errors import TemplateError
@ -65,7 +64,9 @@ class PartiallyRenderedCard:
latex_svg: bool
@classmethod
def from_proto(cls, out: _pb.RenderCardResponse) -> PartiallyRenderedCard:
def from_proto(
cls, out: card_rendering_pb2.RenderCardResponse
) -> PartiallyRenderedCard:
qnodes = cls.nodes_from_proto(out.question_nodes)
anodes = cls.nodes_from_proto(out.answer_nodes)
@ -73,7 +74,7 @@ class PartiallyRenderedCard:
@staticmethod
def nodes_from_proto(
nodes: Sequence[_pb.RenderedTemplateNode],
nodes: Sequence[card_rendering_pb2.RenderedTemplateNode],
) -> TemplateReplacementList:
results: TemplateReplacementList = []
for node in nodes:
@ -90,7 +91,7 @@ class PartiallyRenderedCard:
return results
def av_tag_to_native(tag: _pb.AVTag) -> AVTag:
def av_tag_to_native(tag: card_rendering_pb2.AVTag) -> AVTag:
val = tag.WhichOneof("value")
if val == "sound_or_video":
return SoundOrVideoTag(filename=tag.sound_or_video)
@ -104,7 +105,7 @@ def av_tag_to_native(tag: _pb.AVTag) -> AVTag:
)
def av_tags_to_native(tags: Sequence[_pb.AVTag]) -> List[AVTag]:
def av_tags_to_native(tags: Sequence[card_rendering_pb2.AVTag]) -> List[AVTag]:
return list(map(av_tag_to_native, tags))

View file

@ -12,7 +12,10 @@ import sys
(protoc, mypy_protobuf, outdir, *protos) = sys.argv[1:]
prefix = "proto/"
if protos[0].startswith("external"):
prefix = "external/anki/proto/"
else:
prefix = "proto/"
# invoke protoc
subprocess.run(

View file

@ -14,6 +14,7 @@ macro_rules! protobuf {
}
protobuf!(backend);
protobuf!(card_rendering);
protobuf!(cards);
protobuf!(collection);
protobuf!(configs);
@ -21,8 +22,11 @@ protobuf!(deckconfig);
protobuf!(decks);
protobuf!(generic);
protobuf!(i18n);
protobuf!(media);
protobuf!(notes);
protobuf!(notetypes);
protobuf!(scheduler);
protobuf!(search);
protobuf!(stats);
protobuf!(sync);
protobuf!(tags);

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
-->
<script lang="typescript">
import type { Backend } from "lib/proto";
import type { Stats } from "lib/proto";
import type { PreferenceStore } from "sveltelib/preferences";
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 type { GraphData } from "./added";
export let sourceData: Backend.GraphsResponse | null = null;
export let sourceData: Stats.GraphsResponse | null = null;
import * as tr from "lib/i18n";
export let preferences: PreferenceStore<Backend.GraphPreferences>;
export let preferences: PreferenceStore<Stats.GraphPreferences>;
let histogramData = null as HistogramData | null;
let tableData: TableDatum[] = [];

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
-->
<script lang="typescript">
import type { Backend } from "lib/proto";
import type { Stats } from "lib/proto";
import Graph from "./Graph.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 { defaultGraphBounds, GraphRange, RevlogRange } from "./graph-helpers";
export let sourceData: Backend.GraphsResponse | null = null;
export let sourceData: Stats.GraphsResponse | null = null;
import * as tr from "lib/i18n";
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
-->
<script lang="typescript">
import type { Backend } from "lib/proto";
import type { Stats } from "lib/proto";
import type { PreferenceStore } from "sveltelib/preferences";
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 type { GraphData } from "./calendar";
export let sourceData: Backend.GraphsResponse;
export let preferences: PreferenceStore<Backend.GraphPreferences>;
export let sourceData: Stats.GraphsResponse;
export let preferences: PreferenceStore<Stats.GraphPreferences>;
export let revlogRange: RevlogRange;
import * as tr from "lib/i18n";
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">
import { createEventDispatcher } from "svelte";
import type { Backend } from "lib/proto";
import type { Stats } from "lib/proto";
import type { PreferenceStore } from "sveltelib/preferences";
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 type { GraphData, TableDatum } from "./card-counts";
export let sourceData: Backend.GraphsResponse;
export let sourceData: Stats.GraphsResponse;
import * as tr2 from "lib/i18n";
export let preferences: PreferenceStore<Backend.GraphPreferences>;
export let preferences: PreferenceStore<Stats.GraphPreferences>;
let { cardCountsSeparateInactive, browserLinksSupported } = preferences;
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
-->
<script lang="typescript">
import type { Backend } from "lib/proto";
import type { Stats } from "lib/proto";
import * as tr from "lib/i18n";
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 type { TableDatum, SearchEventMap } from "./graph-helpers";
export let sourceData: Backend.GraphsResponse | null = null;
export let preferences: PreferenceStore<Backend.GraphPreferences>;
export let sourceData: Stats.GraphsResponse | null = null;
export let preferences: PreferenceStore<Stats.GraphPreferences>;
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">
import { createEventDispatcher } from "svelte";
import type { Backend } from "lib/proto";
import type { Stats } from "lib/proto";
import Graph from "./Graph.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 type { GraphData } from "./future-due";
export let sourceData: Backend.GraphsResponse | null = null;
export let sourceData: Stats.GraphsResponse | null = null;
import * as tr from "lib/i18n";
export let preferences: PreferenceStore<Backend.GraphPreferences>;
export let preferences: PreferenceStore<Stats.GraphPreferences>;
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
-->
<script lang="typescript">
import type { Backend } from "lib/proto";
import type { Stats } from "lib/proto";
import Graph from "./Graph.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 { renderHours } from "./hours";
export let sourceData: Backend.GraphsResponse | null = null;
export let sourceData: Stats.GraphsResponse | null = null;
import * as tr from "lib/i18n";
export let revlogRange: RevlogRange;
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">
import { timeSpan, MONTH } from "lib/time";
import type { Backend } from "lib/proto";
import type { Stats } from "lib/proto";
import type { PreferenceStore } from "sveltelib/preferences";
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 { TableDatum, SearchEventMap } from "./graph-helpers";
export let sourceData: Backend.GraphsResponse | null = null;
export let sourceData: Stats.GraphsResponse | null = null;
import * as tr from "lib/i18n";
export let preferences: PreferenceStore<Backend.GraphPreferences>;
export let preferences: PreferenceStore<Stats.GraphPreferences>;
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
-->
<script lang="typescript">
import type { Backend } from "lib/proto";
import type { Stats } from "lib/proto";
import Graph from "./Graph.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 type { GraphData } from "./reviews";
export let sourceData: Backend.GraphsResponse | null = null;
export let sourceData: Stats.GraphsResponse | null = null;
export let revlogRange: RevlogRange;
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
-->
<script lang="typescript">
import type { Backend } from "lib/proto";
import type { Stats } from "lib/proto";
import Graph from "./Graph.svelte";
import type { TodayData } from "./today";
import { gatherData } from "./today";
export let sourceData: Backend.GraphsResponse | null = null;
export let sourceData: Stats.GraphsResponse | null = null;
let todayData: TodayData | null = null;
$: 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 { PreferenceRaw, PreferencePayload } from "sveltelib/preferences";
import { Backend } from "lib/proto";
import { Stats } from "lib/proto";
import { postRequest } from "lib/postrequest";
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(
search: string,
days: number
): Promise<Backend.GraphsResponse> {
return Backend.GraphsResponse.decode(
): Promise<Stats.GraphsResponse> {
return Stats.GraphsResponse.decode(
await postRequest("/_anki/graphData", JSON.stringify({ search, days }))
);
}
async function getGraphPreferences(): Promise<Backend.GraphPreferences> {
return Backend.GraphPreferences.decode(
async function getGraphPreferences(): Promise<Stats.GraphPreferences> {
return Stats.GraphPreferences.decode(
await postRequest("/_anki/graphPreferences", JSON.stringify({}))
);
}
async function setGraphPreferences(
prefs: PreferencePayload<Backend.GraphPreferences>
prefs: PreferencePayload<Stats.GraphPreferences>
): Promise<void> {
await postRequest(
"/_anki/setGraphPreferences",
Backend.GraphPreferences.encode(prefs).finish()
Stats.GraphPreferences.encode(prefs).finish()
);
}
@ -56,10 +56,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
getPreferences(
getGraphPreferences,
setGraphPreferences,
Backend.GraphPreferences.toObject.bind(Backend.GraphPreferences) as (
preferences: Backend.GraphPreferences,
Stats.GraphPreferences.toObject.bind(Stats.GraphPreferences) as (
preferences: Stats.GraphPreferences,
options: { defaults: boolean }
) => PreferenceRaw<Backend.GraphPreferences>
) => PreferenceRaw<Stats.GraphPreferences>
)
);

View file

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

View file

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

View file

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

View file

@ -7,7 +7,7 @@
*/
import { CardQueue, CardType } from "lib/cards";
import type { Backend, Cards } from "lib/proto";
import type { Stats, Cards } from "lib/proto";
import {
schemeGreens,
schemeBlues,
@ -124,7 +124,7 @@ function countCards(cards: Cards.ICard[], separateInactive: boolean): Count[] {
}
export function gatherData(
data: Backend.GraphsResponse,
data: Stats.GraphsResponse,
separateInactive: boolean
): GraphData {
const totalCards = data.cards.length;

View file

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

View file

@ -6,7 +6,7 @@
@typescript-eslint/no-explicit-any: "off",
*/
import type { Backend, Cards } from "lib/proto";
import type { Stats, Cards } from "lib/proto";
import {
extent,
histogram,
@ -30,7 +30,7 @@ export interface GraphData {
haveBacklog: boolean;
}
export function gatherData(data: Backend.GraphsResponse): GraphData {
export function gatherData(data: Stats.GraphsResponse): GraphData {
const isLearning = (card: Cards.Card): boolean =>
[CardQueue.Learn, CardQueue.PreviewRepeat].includes(card.queue);

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { Backend } from "lib/proto";
import { Stats } from "lib/proto";
import { studiedToday } from "lib/time";
import * as tr from "lib/i18n";
@ -11,9 +11,9 @@ export interface TodayData {
lines: string[];
}
const ReviewKind = Backend.RevlogEntry.ReviewKind;
const ReviewKind = Stats.RevlogEntry.ReviewKind;
export function gatherData(data: Backend.GraphsResponse): TodayData {
export function gatherData(data: Stats.GraphsResponse): TodayData {
let answerCount = 0;
let answerMillis = 0;
let correctCount = 0;
@ -26,7 +26,7 @@ export function gatherData(data: Backend.GraphsResponse): TodayData {
const startOfTodayMillis = (data.nextDayAtSecs - 86400) * 1000;
for (const review of data.revlog as Backend.RevlogEntry[]) {
for (const review of data.revlog as Stats.RevlogEntry[]) {
if (review.id < startOfTodayMillis) {
continue;
}

View file

@ -2,9 +2,9 @@
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { anki } from "./backend_proto";
import Backend = anki.backend;
import Cards = anki.cards;
import DeckConfig = anki.deckconfig;
import Notetypes = anki.notetypes;
import Scheduler = anki.scheduler;
export { Backend, Cards, DeckConfig, Notetypes, Scheduler };
import Stats = anki.stats;
export { Stats, Cards, DeckConfig, Notetypes, Scheduler };