move answer_button_time to the backend, split sched into separate module

This commit is contained in:
Damien Elmes 2020-02-20 14:16:33 +10:00
parent 7b26814922
commit 99c07cfdcb
9 changed files with 171 additions and 27 deletions

View file

@ -44,6 +44,7 @@ message BackendInput {
Empty check_media = 28;
TrashMediaFilesIn trash_media_files = 29;
TranslateStringIn translate_string = 30;
FormatTimeSpanIn format_time_span = 31;
}
}
@ -64,6 +65,7 @@ message BackendOutput {
MediaCheckOut check_media = 28;
Empty trash_media_files = 29;
string translate_string = 30;
string format_time_span = 31;
BackendError error = 2047;
}
@ -303,3 +305,12 @@ message TranslateArgValue {
double number = 2;
}
}
message FormatTimeSpanIn {
enum Context {
ANSWER_BUTTONS = 0;
}
float seconds = 1;
Context context = 2;
}

View file

@ -134,6 +134,8 @@ MediaCheckOutput = pb.MediaCheckOut
StringsGroup = pb.StringsGroup
FormatTimeSpanContext = pb.FormatTimeSpanIn.Context
@dataclass
class ExtractedLatex:
@ -342,3 +344,10 @@ class RustBackend:
translate_string=pb.TranslateStringIn(group=group, key=key, args=args)
)
).translate_string
def format_time_span(self, seconds: float, context: FormatTimeSpanContext) -> str:
return self._run_command(
pb.BackendInput(
format_time_span=pb.FormatTimeSpanIn(seconds=seconds, context=context)
)
).format_time_span

View file

@ -1,6 +1,8 @@
# Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from __future__ import annotations
import itertools
import random
import time
@ -8,11 +10,13 @@ from heapq import *
from operator import itemgetter
from typing import List, Optional, Set
import anki
from anki import hooks
from anki.cards import Card
from anki.consts import *
from anki.lang import _
from anki.utils import answer_button_time, ids2str, intTime
from anki.rsbackend import FormatTimeSpanContext
from anki.utils import ids2str, intTime
# queue types: 0=new/cram, 1=lrn, 2=rev, 3=day lrn, -1=suspended, -2=buried
# revlog types: 0=lrn, 1=rev, 2=relrn, 3=cram
@ -25,7 +29,7 @@ class Scheduler:
_spreadRev = True
_burySiblingsOnAnswer = True
def __init__(self, col):
def __init__(self, col: anki.storage._Collection) -> None:
self.col = col
self.queueLimit = 50
self.reportLimit = 1000
@ -33,7 +37,7 @@ class Scheduler:
self.lrnCount = 0
self.revCount = 0
self.newCount = 0
self.today = None
self.today: Optional[int] = None
self._haveQueues = False
self._updateCutoff()
@ -1354,7 +1358,9 @@ To study outside of the normal schedule, click the Custom Study button below."""
ivl_secs = self.nextIvl(card, ease)
if not ivl_secs:
return _("(end)")
s = answer_button_time(self.col, ivl_secs)
s = self.col.backend.format_time_span(
ivl_secs, FormatTimeSpanContext.ANSWER_BUTTONS
)
if ivl_secs < self.col.conf["collapseTime"]:
s = "<" + s
return s

View file

@ -18,8 +18,8 @@ from anki import hooks
from anki.cards import Card
from anki.consts import *
from anki.lang import _
from anki.rsbackend import SchedTimingToday
from anki.utils import answer_button_time, ids2str, intTime
from anki.rsbackend import FormatTimeSpanContext, SchedTimingToday
from anki.utils import ids2str, intTime
# card types: 0=new, 1=lrn, 2=rev, 3=relrn
# queue types: 0=new, 1=(re)lrn, 2=rev, 3=day (re)lrn,
@ -1548,7 +1548,9 @@ To study outside of the normal schedule, click the Custom Study button below."""
ivl_secs = self.nextIvl(card, ease)
if not ivl_secs:
return _("(end)")
s = answer_button_time(self.col, ivl_secs)
s = self.col.backend.format_time_span(
ivl_secs, FormatTimeSpanContext.ANSWER_BUTTONS
)
if ivl_secs < self.col.conf["collapseTime"]:
s = "<" + s
return s

View file

@ -23,8 +23,6 @@ from hashlib import sha1
from html.entities import name2codepoint
from typing import Iterable, Iterator, List, Optional, Tuple, Union
import anki
from anki.backend_pb2 import StringsGroup
from anki.db import DB
from anki.lang import _, ngettext
@ -39,22 +37,6 @@ def intTime(scale: int = 1) -> int:
return int(time.time() * scale)
# eg 70 seconds -> (1.16, "minutes")
def seconds_to_appropriate_unit(seconds: int) -> Tuple[float, str]:
unit, _ = optimalPeriod(seconds, 0, 99)
amount = convertSecondsTo(seconds, unit)
return (amount, unit)
def answer_button_time(col: anki.storage._Collection, seconds: int) -> str:
(amount, unit) = seconds_to_appropriate_unit(seconds)
if unit not in ("months", "years"):
amount = int(amount)
return col.backend.translate(
StringsGroup.SCHEDULING, f"answer-button-time-{unit}", amount=amount
)
timeTable = {
"years": lambda n: ngettext("%s year", "%s years", n),
"months": lambda n: ngettext("%s month", "%s months", n),

View file

@ -10,7 +10,8 @@ use crate::latex::{extract_latex, ExtractedLatex};
use crate::media::check::MediaChecker;
use crate::media::sync::MediaSyncProgress;
use crate::media::MediaManager;
use crate::sched::{local_minutes_west_for_stamp, sched_timing_today};
use crate::sched::cutoff::{local_minutes_west_for_stamp, sched_timing_today};
use crate::sched::timespan::answer_button_time;
use crate::template::{
render_card, without_legacy_template_directives, FieldMap, FieldRequirements, ParsedTemplate,
RenderedNode,
@ -200,6 +201,7 @@ impl Backend {
OValue::TrashMediaFiles(Empty {})
}
Value::TranslateString(input) => OValue::TranslateString(self.translate_string(input)),
Value::FormatTimeSpan(input) => OValue::FormatTimeSpan(self.format_time_span(input)),
})
}
@ -397,6 +399,18 @@ impl Backend {
self.i18n.get(group).trn(&input.key, map)
}
fn format_time_span(&self, input: pb::FormatTimeSpanIn) -> String {
let context = match pb::format_time_span_in::Context::from_i32(input.context) {
Some(context) => context,
None => return "".to_string(),
};
match context {
pb::format_time_span_in::Context::AnswerButtons => {
answer_button_time(input.seconds, &self.i18n)
}
}
}
}
fn translate_arg_to_fluent_val(arg: &pb::TranslateArgValue) -> FluentValue {

View file

@ -92,7 +92,7 @@ pub fn local_minutes_west_for_stamp(stamp: i64) -> i32 {
#[cfg(test)]
mod test {
use crate::sched::{
use crate::sched::cutoff::{
fixed_offset_from_minutes, local_minutes_west_for_stamp, normalized_rollover_hour,
sched_timing_today,
};

2
rslib/src/sched/mod.rs Normal file
View file

@ -0,0 +1,2 @@
pub mod cutoff;
pub mod timespan;

118
rslib/src/sched/timespan.rs Normal file
View file

@ -0,0 +1,118 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use crate::i18n::{tr_args, I18n, StringsGroup};
pub fn answer_button_time(seconds: f32, i18n: &I18n) -> String {
let span = Timespan::from_secs(seconds).natural_span();
let amount = match span.unit() {
TimespanUnit::Months | TimespanUnit::Years => span.as_unit(),
// we don't show fractional value except for months/years
_ => span.as_unit().round(),
};
let unit = span.unit().as_str();
let args = tr_args!["amount" => amount];
i18n.get(StringsGroup::Scheduling)
.trn(&format!("answer-button-time-{}", unit), args)
}
const SECOND: f32 = 1.0;
const MINUTE: f32 = 60.0 * SECOND;
const HOUR: f32 = 60.0 * MINUTE;
const DAY: f32 = 24.0 * HOUR;
const MONTH: f32 = 30.0 * DAY;
const YEAR: f32 = 365.0 * MONTH;
#[derive(Clone, Copy)]
enum TimespanUnit {
Seconds,
Minutes,
Hours,
Days,
Months,
Years,
}
impl TimespanUnit {
fn as_str(self) -> &'static str {
match self {
TimespanUnit::Seconds => "seconds",
TimespanUnit::Minutes => "minutes",
TimespanUnit::Hours => "hours",
TimespanUnit::Days => "days",
TimespanUnit::Months => "months",
TimespanUnit::Years => "years",
}
}
}
#[derive(Clone, Copy)]
struct Timespan {
seconds: f32,
unit: TimespanUnit,
}
impl Timespan {
fn from_secs(seconds: f32) -> Self {
Timespan {
seconds,
unit: TimespanUnit::Seconds,
}
}
/// Return the value as the configured unit, eg seconds=70/unit=Minutes
/// returns 1.17
fn as_unit(self) -> f32 {
let s = self.seconds;
match self.unit {
TimespanUnit::Seconds => s,
TimespanUnit::Minutes => s / MINUTE,
TimespanUnit::Hours => s / HOUR,
TimespanUnit::Days => s / DAY,
TimespanUnit::Months => s / MONTH,
TimespanUnit::Years => s / YEAR,
}
}
fn unit(self) -> TimespanUnit {
self.unit
}
/// Return a new timespan in the most appropriate unit, eg
/// 70 secs -> timespan in minutes
fn natural_span(self) -> Timespan {
let secs = self.seconds.abs();
let unit = if secs < MINUTE {
TimespanUnit::Seconds
} else if secs < HOUR {
TimespanUnit::Minutes
} else if secs < DAY {
TimespanUnit::Hours
} else if secs < MONTH {
TimespanUnit::Days
} else if secs < YEAR {
TimespanUnit::Months
} else {
TimespanUnit::Years
};
Timespan {
seconds: self.seconds,
unit,
}
}
}
#[cfg(test)]
mod test {
use crate::i18n::I18n;
use crate::sched::timespan::{answer_button_time, MONTH};
#[test]
fn answer_buttons() {
let i18n = I18n::new(&["zz"], "");
assert_eq!(answer_button_time(30.0, &i18n), "30s");
assert_eq!(answer_button_time(70.0, &i18n), "1m");
assert_eq!(answer_button_time(1.1 * MONTH, &i18n), "1.10mo");
}
}