Anki/anki/backend.py
Damien Elmes 92673c99d8 rework the new scheduler's rollover/day counting code
The previous implementation interpreted the creation date as a local
time, and applied the rollover to that. If the initial creation date
was around midnight local time, even a one hour change due to daylight
savings could result in Anki skipping or doubling up on a day. To
address this, the rollover is now applied to the current time instead
of the creation date.

The new code needs the current time passed into it. This makes it
easier to unit test, and for AnkiWeb to be able to use the user's local
timezone.

The new timezone code is currently disabled, as this code needs to be
ported to all clients before it can be activated.
2019-12-27 21:53:20 +10:00

85 lines
2.8 KiB
Python

# pylint: skip-file
from typing import Dict, List
import _ankirs # pytype: disable=import-error
import anki.backend_pb2 as pb
from .types import AllTemplateReqs
SchedTimingToday = pb.SchedTimingTodayOut
class BackendException(Exception):
def __str__(self) -> str:
err: pb.BackendError = self.args[0] # pylint: disable=unsubscriptable-object
kind = err.WhichOneof("value")
if kind == "invalid_input":
return f"invalid input: {err.invalid_input.info}"
elif kind == "template_parse":
return f"template parse: {err.template_parse.info}"
else:
return f"unhandled error: {err}"
def proto_template_reqs_to_legacy(
reqs: List[pb.TemplateRequirement],
) -> AllTemplateReqs:
legacy_reqs = []
for (idx, req) in enumerate(reqs):
kind = req.WhichOneof("value")
# fixme: sorting is for the unit tests - should check if any
# code depends on the order
if kind == "any":
legacy_reqs.append((idx, "any", sorted(req.any.ords)))
elif kind == "all":
legacy_reqs.append((idx, "all", sorted(req.all.ords)))
else:
l: List[int] = []
legacy_reqs.append((idx, "none", l))
return legacy_reqs
class Backend:
def __init__(self):
self._backend = _ankirs.Backend()
def _run_command(self, input: pb.BackendInput) -> pb.BackendOutput:
input_bytes = input.SerializeToString()
output_bytes = self._backend.command(input_bytes)
output = pb.BackendOutput()
output.ParseFromString(output_bytes)
kind = output.WhichOneof("value")
if kind == "error":
raise BackendException(output.error)
else:
return output
def plus_one(self, num: int) -> int:
input = pb.BackendInput(plus_one=pb.PlusOneIn(num=num))
output = self._run_command(input)
return output.plus_one.num
def template_requirements(
self, template_fronts: List[str], field_map: Dict[str, int]
) -> AllTemplateReqs:
input = pb.BackendInput(
template_requirements=pb.TemplateRequirementsIn(
template_front=template_fronts, field_names_to_ordinals=field_map
)
)
output = self._run_command(input).template_requirements
reqs: List[pb.TemplateRequirement] = output.requirements # type: ignore
return proto_template_reqs_to_legacy(reqs)
def sched_timing_today(
self, start: int, end: int, offset: int, rollover: int
) -> SchedTimingToday:
return self._run_command(
pb.BackendInput(
sched_timing_today=pb.SchedTimingTodayIn(
created=start, now=end, minutes_west=offset, rollover_hour=rollover,
)
)
).sched_timing_today