From 99141d9dfbe3db435c645f0d44e51a6bf7381234 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 6 Jan 2020 12:24:47 +1000 Subject: [PATCH] add a partial Python implementation of the backend --- pylib/anki/collection.py | 6 +- pylib/anki/pybackend.py | 82 +++++++++++++++++++++++++ pylib/anki/{backend.py => rsbackend.py} | 4 +- pylib/anki/schedv2.py | 2 +- pylib/anki/storage.py | 4 +- 5 files changed, 91 insertions(+), 7 deletions(-) create mode 100644 pylib/anki/pybackend.py rename pylib/anki/{backend.py => rsbackend.py} (95%) diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index 85884f3e9..1ac8fd917 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -16,7 +16,6 @@ from typing import Any, Dict, Iterable, List, Optional, Tuple, Union import anki.find import anki.latex # sets up hook import anki.template -from anki.backend import Backend from anki.cards import Card from anki.consts import * from anki.db import DB @@ -27,6 +26,7 @@ from anki.lang import _, ngettext from anki.media import MediaManager from anki.models import ModelManager from anki.notes import Note +from anki.rsbackend import RustBackend from anki.sched import Scheduler as V1Scheduler from anki.schedv2 import Scheduler as V2Scheduler from anki.sound import stripSounds @@ -75,12 +75,12 @@ class _Collection: ls: int conf: Dict[str, Any] _undo: List[Any] - backend: Backend + backend: RustBackend def __init__( self, db: DB, - backend: Backend, + backend: RustBackend, server: Optional["anki.storage.ServerData"] = None, log: bool = False, ) -> None: diff --git a/pylib/anki/pybackend.py b/pylib/anki/pybackend.py new file mode 100644 index 000000000..46f61a59f --- /dev/null +++ b/pylib/anki/pybackend.py @@ -0,0 +1,82 @@ +# Copyright: Ankitects Pty Ltd and contributors +# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +""" +A Python implementation of some backend commands. + +Unimplemented commands will be forwarded on to the Rust backend. +""" + +from typing import Tuple, Any, Dict + +import anki # pylint: disable=unused-import +import anki.backend_pb2 as pb + + +class PythonBackend: + def __init__(self, col: "anki.storage._Collection"): + self.col = col + + def run_command_bytes(self, input: bytes) -> bytes: + pb_input = pb.BackendInput() + pb_input.ParseFromString(input) + + pb_output = self.run_command(pb_input) + + output = pb_output.SerializeToString() + return output + + def run_command(self, input: pb.BackendInput) -> pb.BackendOutput: + kind = input.WhichOneof("value") + handler = getattr(self, kind, None) + # run the equivalent of the following, based on available method names + # if kind == "deck_tree": + # return pb.BackendOutput(deck_tree=self.deck_tree(input.deck_tree)) + if handler is not None: + input_variant = getattr(input, kind) + output_variant = handler(input_variant) + output_args: Dict[str, Any] = {kind: output_variant} + output = pb.BackendOutput(**output_args) + return output + else: + # forward any unknown commands onto the Rust backend + return self.col.backend._run_command(input) + + def deck_tree(self, _input: pb.Empty) -> pb.DeckTreeOut: + native = self.col.sched.deckDueTree() + return native_deck_tree_to_proto(native) + + def find_cards(self, input: pb.FindCardsIn) -> pb.FindCardsOut: + cids = self.col.findCards(input.search) + return pb.FindCardsOut(card_ids=cids) + + def browser_rows(self, input: pb.BrowserRowsIn) -> pb.BrowserRowsOut: + sort_fields = [] + for cid in input.card_ids: + sort_fields.append( + self.col.db.scalar( + "select sfld from notes n,cards c where n.id=c.nid and c.id=?", cid + ) + ) + return pb.BrowserRowsOut(sort_fields=sort_fields) + + +def native_deck_tree_to_proto(native): + top = pb.DeckTreeNode(children=[native_deck_node_to_proto(c) for c in native]) + out = pb.DeckTreeOut(top=top) + return out + + +def native_deck_node_to_proto(native: Tuple) -> pb.DeckTreeNode: + return pb.DeckTreeNode( + # fixme: need to decide whether full list + # should be included or just tail element + names=[native[0]], + deck_id=native[1], + review_count=native[2], + learn_count=native[3], + new_count=native[4], + children=[native_deck_node_to_proto(c) for c in native[5]], + # fixme: currently hard-coded + collapsed=False, + ) diff --git a/pylib/anki/backend.py b/pylib/anki/rsbackend.py similarity index 95% rename from pylib/anki/backend.py rename to pylib/anki/rsbackend.py index d555a0a20..50ff15561 100644 --- a/pylib/anki/backend.py +++ b/pylib/anki/rsbackend.py @@ -1,3 +1,5 @@ +# Copyright: Ankitects Pty Ltd and contributors +# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html # pylint: skip-file from typing import Dict, List @@ -44,7 +46,7 @@ def proto_template_reqs_to_legacy( return legacy_reqs -class Backend: +class RustBackend: def __init__(self, path: str): self._backend = ankirspy.Backend(path) diff --git a/pylib/anki/schedv2.py b/pylib/anki/schedv2.py index ab2ba5108..a201e83be 100644 --- a/pylib/anki/schedv2.py +++ b/pylib/anki/schedv2.py @@ -12,11 +12,11 @@ from operator import itemgetter from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union import anki # pylint: disable=unused-import -from anki.backend import SchedTimingToday from anki.cards import Card from anki.consts import * from anki.hooks import runHook from anki.lang import _ +from anki.rsbackend import SchedTimingToday from anki.utils import fmtTimeSpan, ids2str, intTime # card types: 0=new, 1=lrn, 2=rev, 3=relrn diff --git a/pylib/anki/storage.py b/pylib/anki/storage.py index f95722cfd..619853a4b 100644 --- a/pylib/anki/storage.py +++ b/pylib/anki/storage.py @@ -7,11 +7,11 @@ import os import re from typing import Any, Dict, Optional, Tuple -from anki.backend import Backend from anki.collection import _Collection from anki.consts import * from anki.db import DB from anki.lang import _ +from anki.rsbackend import RustBackend from anki.stdmodels import ( addBasicModel, addBasicTypingModel, @@ -30,7 +30,7 @@ def Collection( path: str, lock: bool = True, server: Optional[ServerData] = None, log: bool = False ) -> _Collection: "Open a new or existing collection. Path must be unicode." - backend = Backend(path) + backend = RustBackend(path) # fixme: this call is temporarily here to ensure the brige is working # on all platforms, and should be removed in a future beta assert backend.plus_one(5) == 6