fetch timing_today() params in backend

This commit is contained in:
Damien Elmes 2020-05-11 13:58:40 +10:00
parent eee0d7e92f
commit ea4f150455
9 changed files with 81 additions and 149 deletions

View file

@ -29,7 +29,7 @@ message I18nBackendInit {
message BackendInput { message BackendInput {
oneof value { oneof value {
SchedTimingTodayIn sched_timing_today = 17; Empty sched_timing_today = 17;
DeckTreeIn deck_tree = 18; DeckTreeIn deck_tree = 18;
SearchCardsIn search_cards = 19; SearchCardsIn search_cards = 19;
SearchNotesIn search_notes = 20; SearchNotesIn search_notes = 20;
@ -101,7 +101,6 @@ message BackendInput {
message BackendOutput { message BackendOutput {
oneof value { oneof value {
// infallible commands // infallible commands
SchedTimingTodayOut sched_timing_today = 17;
sint32 local_minutes_west = 22; sint32 local_minutes_west = 22;
string strip_av_tags = 23; string strip_av_tags = 23;
ExtractAVTagsOut extract_av_tags = 24; ExtractAVTagsOut extract_av_tags = 24;
@ -114,6 +113,7 @@ message BackendOutput {
AllStockNotetypesOut all_stock_notetypes = 60; AllStockNotetypesOut all_stock_notetypes = 60;
// fallible commands // fallible commands
SchedTimingTodayOut sched_timing_today = 17;
DeckTreeNode deck_tree = 18; DeckTreeNode deck_tree = 18;
SearchCardsOut search_cards = 19; SearchCardsOut search_cards = 19;
SearchNotesOut search_notes = 20; SearchNotesOut search_notes = 20;
@ -237,14 +237,6 @@ message MediaSyncUploadProgress {
uint32 deletions = 2; uint32 deletions = 2;
} }
message SchedTimingTodayIn {
int64 created_secs = 1;
int64 now_secs = 2;
OptionalInt32 created_mins_west = 3;
OptionalInt32 now_mins_west = 4;
OptionalInt32 rollover_hour = 5;
}
message SchedTimingTodayOut { message SchedTimingTodayOut {
uint32 days_elapsed = 1; uint32 days_elapsed = 1;
int64 next_day_at = 2; int64 next_day_at = 2;

View file

@ -287,38 +287,9 @@ class RustBackend:
release_gil=True, release_gil=True,
) )
def sched_timing_today( def sched_timing_today(self,) -> SchedTimingToday:
self,
created_secs: int,
created_mins_west: Optional[int],
now_mins_west: Optional[int],
rollover: Optional[int],
) -> SchedTimingToday:
if created_mins_west is not None:
crt_west = pb.OptionalInt32(val=created_mins_west)
else:
crt_west = None
if now_mins_west is not None:
now_west = pb.OptionalInt32(val=now_mins_west)
else:
now_west = None
if rollover is not None:
roll = pb.OptionalInt32(val=rollover)
else:
roll = None
return self._run_command( return self._run_command(
pb.BackendInput( pb.BackendInput(sched_timing_today=pb.Empty())
sched_timing_today=pb.SchedTimingTodayIn(
created_secs=created_secs,
now_secs=intTime(),
created_mins_west=crt_west,
now_mins_west=now_west,
rollover_hour=roll,
)
)
).sched_timing_today ).sched_timing_today
def render_card( def render_card(

View file

@ -1374,28 +1374,8 @@ where id = ?
if time.time() > self.dayCutoff: if time.time() > self.dayCutoff:
self.reset() self.reset()
def _rolloverHour(self) -> int:
return self.col.conf.get("rollover", 4)
def _timing_today(self) -> SchedTimingToday: def _timing_today(self) -> SchedTimingToday:
roll: Optional[int] = None return self.col.backend.sched_timing_today()
if self.col.schedVer() > 1:
roll = self._rolloverHour()
return self.col.backend.sched_timing_today(
self.col.crt,
self._creation_timezone_offset(),
self._current_timezone_offset(),
roll,
)
def _current_timezone_offset(self) -> Optional[int]:
if self.col.server:
return self.col.conf.get("localOffset", 0)
else:
return None
def _creation_timezone_offset(self) -> Optional[int]:
return self.col.conf.get("creationOffset", None)
# New timezone handling - GUI helpers # New timezone handling - GUI helpers
########################################################################## ##########################################################################

View file

@ -106,6 +106,7 @@ def test_export_anki_due():
f["Front"] = "foo" f["Front"] = "foo"
deck.addNote(f) deck.addNote(f)
deck.crt -= 86400 * 10 deck.crt -= 86400 * 10
deck.flush()
deck.sched.reset() deck.sched.reset()
c = deck.sched.getCard() c = deck.sched.getCard()
deck.sched.answerCard(c, 3) deck.sched.answerCard(c, 3)

View file

@ -22,7 +22,7 @@ use crate::{
media::MediaManager, media::MediaManager,
notes::{Note, NoteID}, notes::{Note, NoteID},
notetype::{all_stock_notetypes, NoteType, NoteTypeID, NoteTypeSchema11}, notetype::{all_stock_notetypes, NoteType, NoteTypeID, NoteTypeSchema11},
sched::cutoff::{local_minutes_west_for_stamp, sched_timing_today}, sched::cutoff::local_minutes_west_for_stamp,
sched::timespan::{answer_button_time, learning_congrats, studied_today, time_span}, sched::timespan::{answer_button_time, learning_congrats, studied_today, time_span},
search::SortMode, search::SortMode,
template::{render_card, RenderedNode}, template::{render_card, RenderedNode},
@ -214,9 +214,7 @@ impl Backend {
) -> Result<pb::backend_output::Value> { ) -> Result<pb::backend_output::Value> {
use pb::backend_output::Value as OValue; use pb::backend_output::Value as OValue;
Ok(match ival { Ok(match ival {
Value::SchedTimingToday(input) => { Value::SchedTimingToday(_) => OValue::SchedTimingToday(self.sched_timing_today()?),
OValue::SchedTimingToday(self.sched_timing_today(input))
}
Value::DeckTree(input) => OValue::DeckTree(self.deck_tree(input)?), Value::DeckTree(input) => OValue::DeckTree(self.deck_tree(input)?),
Value::RenderCard(input) => OValue::RenderCard(self.render_template(input)?), Value::RenderCard(input) => OValue::RenderCard(self.render_template(input)?),
Value::LocalMinutesWest(stamp) => { Value::LocalMinutesWest(stamp) => {
@ -436,18 +434,8 @@ impl Backend {
self.progress_callback = progress_cb; self.progress_callback = progress_cb;
} }
fn sched_timing_today(&self, input: pb::SchedTimingTodayIn) -> pb::SchedTimingTodayOut { fn sched_timing_today(&self) -> Result<pb::SchedTimingTodayOut> {
let today = sched_timing_today( self.with_col(|col| col.timing_today().map(Into::into))
TimestampSecs(input.created_secs),
TimestampSecs(input.now_secs),
input.created_mins_west.map(|v| v.val),
input.now_mins_west.map(|v| v.val),
input.rollover_hour.map(|v| v.val as i8),
);
pb::SchedTimingTodayOut {
days_elapsed: today.days_elapsed,
next_day_at: today.next_day_at,
}
} }
fn deck_tree(&self, input: pb::DeckTreeIn) -> Result<pb::DeckTreeNode> { fn deck_tree(&self, input: pb::DeckTreeIn) -> Result<pb::DeckTreeNode> {
@ -1269,3 +1257,12 @@ fn pbcard_to_native(c: pb::Card) -> Result<Card> {
data: c.data, data: c.data,
}) })
} }
impl From<crate::sched::cutoff::SchedTimingToday> for pb::SchedTimingTodayOut {
fn from(t: crate::sched::cutoff::SchedTimingToday) -> pb::SchedTimingTodayOut {
pb::SchedTimingTodayOut {
days_elapsed: t.days_elapsed,
next_day_at: t.next_day_at,
}
}
}

View file

@ -4,12 +4,10 @@
use crate::err::{AnkiError, Result}; use crate::err::{AnkiError, Result};
use crate::i18n::I18n; use crate::i18n::I18n;
use crate::log::Logger; use crate::log::Logger;
use crate::timestamp::TimestampSecs;
use crate::types::Usn; use crate::types::Usn;
use crate::{ use crate::{
decks::{Deck, DeckID}, decks::{Deck, DeckID},
notetype::{NoteType, NoteTypeID}, notetype::{NoteType, NoteTypeID},
sched::cutoff::{sched_timing_today, SchedTimingToday},
storage::SqliteStorage, storage::SqliteStorage,
undo::UndoManager, undo::UndoManager,
}; };
@ -51,7 +49,6 @@ pub fn open_test_collection() -> Collection {
pub struct CollectionState { pub struct CollectionState {
task_state: CollectionTaskState, task_state: CollectionTaskState,
pub(crate) undo: UndoManager, pub(crate) undo: UndoManager,
timing_today: Option<SchedTimingToday>,
pub(crate) notetype_cache: HashMap<NoteTypeID, Arc<NoteType>>, pub(crate) notetype_cache: HashMap<NoteTypeID, Arc<NoteType>>,
pub(crate) deck_cache: HashMap<DeckID, Arc<Deck>>, pub(crate) deck_cache: HashMap<DeckID, Arc<Deck>>,
} }
@ -142,37 +139,6 @@ impl Collection {
self.storage.close(downgrade) self.storage.close(downgrade)
} }
// fixme: invalidate when config changes
pub fn timing_today(&mut self) -> Result<SchedTimingToday> {
if let Some(timing) = &self.state.timing_today {
if timing.next_day_at > TimestampSecs::now().0 {
return Ok(*timing);
}
}
let local_offset = if self.server {
self.get_local_mins_west()
} else {
None
};
let timing = sched_timing_today(
self.storage.creation_stamp()?,
TimestampSecs::now(),
self.get_creation_mins_west(),
local_offset,
self.get_rollover(),
);
self.state.timing_today = Some(timing);
Ok(timing)
}
pub(crate) fn learn_cutoff(&self) -> u32 {
TimestampSecs::now().0 as u32 + self.learn_ahead_secs()
}
pub(crate) fn usn(&self) -> Result<Usn> { pub(crate) fn usn(&self) -> Result<Usn> {
// if we cache this in the future, must make sure to invalidate cache when usn bumped in sync.finish() // if we cache this in the future, must make sure to invalidate cache when usn bumped in sync.finish()
self.storage.usn(self.server) self.storage.usn(self.server)

View file

@ -138,8 +138,9 @@ impl Collection {
self.set_config(ConfigKey::LocalOffset, &mins) self.set_config(ConfigKey::LocalOffset, &mins)
} }
pub(crate) fn get_rollover(&self) -> Option<i8> { pub(crate) fn get_rollover(&self) -> Option<u8> {
self.get_config_optional(ConfigKey::Rollover) self.get_config_optional::<u8, _>(ConfigKey::Rollover)
.map(|r| r.min(23))
} }
#[allow(dead_code)] #[allow(dead_code)]

View file

@ -2,7 +2,7 @@
// 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
use crate::timestamp::TimestampSecs; use crate::timestamp::TimestampSecs;
use chrono::{Date, Duration, FixedOffset, Local, TimeZone}; use chrono::{Date, Duration, FixedOffset, Local, TimeZone, Timelike};
#[derive(Debug, PartialEq, Clone, Copy)] #[derive(Debug, PartialEq, Clone, Copy)]
pub struct SchedTimingToday { pub struct SchedTimingToday {
@ -24,7 +24,7 @@ pub fn sched_timing_today_v2_new(
created_mins_west: i32, created_mins_west: i32,
now_secs: i64, now_secs: i64,
now_mins_west: i32, now_mins_west: i32,
rollover_hour: i8, rollover_hour: u8,
) -> SchedTimingToday { ) -> SchedTimingToday {
// get date(times) based on timezone offsets // get date(times) based on timezone offsets
let created_date = fixed_offset_from_minutes(created_mins_west) let created_date = fixed_offset_from_minutes(created_mins_west)
@ -34,7 +34,6 @@ pub fn sched_timing_today_v2_new(
let today = now_datetime.date(); let today = now_datetime.date();
// rollover // rollover
let rollover_hour = normalized_rollover_hour(rollover_hour);
let rollover_today_datetime = today.and_hms(rollover_hour as u32, 0, 0); let rollover_today_datetime = today.and_hms(rollover_hour as u32, 0, 0);
let rollover_passed = rollover_today_datetime <= now_datetime; let rollover_passed = rollover_today_datetime <= now_datetime;
let next_day_at = if rollover_passed { let next_day_at = if rollover_passed {
@ -67,17 +66,6 @@ fn days_elapsed(
days.max(0) as u32 days.max(0) as u32
} }
/// Negative rollover hours are relative to the next day, eg -1 = 23.
/// Cap hour to 23.
fn normalized_rollover_hour(hour: i8) -> u8 {
let capped_hour = hour.max(-23).min(23);
if capped_hour < 0 {
(24 + capped_hour) as u8
} else {
capped_hour as u8
}
}
/// Build a FixedOffset struct, capping minutes_west if out of bounds. /// Build a FixedOffset struct, capping minutes_west if out of bounds.
fn fixed_offset_from_minutes(minutes_west: i32) -> FixedOffset { fn fixed_offset_from_minutes(minutes_west: i32) -> FixedOffset {
let bounded_minutes = minutes_west.max(-23 * 60).min(23 * 60); let bounded_minutes = minutes_west.max(-23 * 60).min(23 * 60);
@ -95,6 +83,10 @@ pub fn local_minutes_west_for_stamp(stamp: i64) -> i32 {
// Legacy code // Legacy code
// ---------------------------------- // ----------------------------------
pub(crate) fn v1_rollover_from_creation_stamp(crt: i64) -> u8 {
Local.timestamp(crt, 0).hour() as u8
}
pub(crate) fn v1_creation_date() -> i64 { pub(crate) fn v1_creation_date() -> i64 {
let now = TimestampSecs::now(); let now = TimestampSecs::now();
v1_creation_date_inner(now, local_minutes_west_for_stamp(now.0)) v1_creation_date_inner(now, local_minutes_west_for_stamp(now.0))
@ -124,24 +116,23 @@ fn sched_timing_today_v1(crt: i64, now: i64) -> SchedTimingToday {
fn sched_timing_today_v2_legacy( fn sched_timing_today_v2_legacy(
crt: i64, crt: i64,
rollover: i8, rollover: u8,
now: i64, now: i64,
mins_west: i32, mins_west: i32,
) -> SchedTimingToday { ) -> SchedTimingToday {
let normalized_rollover = normalized_rollover_hour(rollover);
let offset = fixed_offset_from_minutes(mins_west); let offset = fixed_offset_from_minutes(mins_west);
let crt_at_rollover = offset let crt_at_rollover = offset
.timestamp(crt, 0) .timestamp(crt, 0)
.date() .date()
.and_hms(normalized_rollover as u32, 0, 0) .and_hms(rollover as u32, 0, 0)
.timestamp(); .timestamp();
let days_elapsed = (now - crt_at_rollover) / 86_400; let days_elapsed = (now - crt_at_rollover) / 86_400;
let mut next_day_at = offset let mut next_day_at = offset
.timestamp(now, 0) .timestamp(now, 0)
.date() .date()
.and_hms(normalized_rollover as u32, 0, 0) .and_hms(rollover as u32, 0, 0)
.timestamp(); .timestamp();
if next_day_at < now { if next_day_at < now {
next_day_at += 86_400; next_day_at += 86_400;
@ -161,7 +152,7 @@ pub(crate) fn sched_timing_today(
now_secs: TimestampSecs, now_secs: TimestampSecs,
created_mins_west: Option<i32>, created_mins_west: Option<i32>,
now_mins_west: Option<i32>, now_mins_west: Option<i32>,
rollover_hour: Option<i8>, rollover_hour: Option<u8>,
) -> SchedTimingToday { ) -> SchedTimingToday {
let now_west = now_mins_west.unwrap_or_else(|| local_minutes_west_for_stamp(now_secs.0)); let now_west = now_mins_west.unwrap_or_else(|| local_minutes_west_for_stamp(now_secs.0));
match (rollover_hour, created_mins_west) { match (rollover_hour, created_mins_west) {
@ -188,17 +179,6 @@ mod test {
// static timezone for tests // static timezone for tests
const AEST_MINS_WEST: i32 = -600; const AEST_MINS_WEST: i32 = -600;
#[test]
fn rollover() {
assert_eq!(normalized_rollover_hour(4), 4);
assert_eq!(normalized_rollover_hour(23), 23);
assert_eq!(normalized_rollover_hour(24), 23);
assert_eq!(normalized_rollover_hour(-1), 23);
assert_eq!(normalized_rollover_hour(-2), 22);
assert_eq!(normalized_rollover_hour(-23), 1);
assert_eq!(normalized_rollover_hour(-24), 1);
}
#[test] #[test]
fn fixed_offset() { fn fixed_offset() {
let offset = fixed_offset_from_minutes(AEST_MINS_WEST); let offset = fixed_offset_from_minutes(AEST_MINS_WEST);
@ -206,7 +186,7 @@ mod test {
} }
// helper // helper
fn elap(start: i64, end: i64, start_west: i32, end_west: i32, rollhour: i8) -> u32 { fn elap(start: i64, end: i64, start_west: i32, end_west: i32, rollhour: u8) -> u32 {
let today = sched_timing_today_v2_new(start, start_west, end, end_west, rollhour); let today = sched_timing_today_v2_new(start, start_west, end, end_west, rollhour);
today.days_elapsed today.days_elapsed
} }
@ -300,7 +280,7 @@ mod test {
end_stamp, end_stamp,
crt_offset, crt_offset,
end_offset, end_offset,
*rollover_hour as i8 *rollover_hour as u8
), ),
elap_day elap_day
); );
@ -323,7 +303,7 @@ mod test {
crt.offset().utc_minus_local() / 60, crt.offset().utc_minus_local() / 60,
now.timestamp(), now.timestamp(),
now.offset().utc_minus_local() / 60, now.offset().utc_minus_local() / 60,
rollhour as i8, rollhour as u8,
); );
assert_eq!(today.next_day_at, next_day_at.timestamp()); assert_eq!(today.next_day_at, next_day_at.timestamp());
@ -335,7 +315,7 @@ mod test {
crt.offset().utc_minus_local() / 60, crt.offset().utc_minus_local() / 60,
now.timestamp(), now.timestamp(),
now.offset().utc_minus_local() / 60, now.offset().utc_minus_local() / 60,
rollhour as i8, rollhour as u8,
); );
assert_eq!(today.next_day_at, next_day_at.timestamp()); assert_eq!(today.next_day_at, next_day_at.timestamp());
@ -347,7 +327,7 @@ mod test {
crt.offset().utc_minus_local() / 60, crt.offset().utc_minus_local() / 60,
now.timestamp(), now.timestamp(),
now.offset().utc_minus_local() / 60, now.offset().utc_minus_local() / 60,
rollhour as i8, rollhour as u8,
); );
assert_eq!(today.next_day_at, next_day_at.timestamp()); assert_eq!(today.next_day_at, next_day_at.timestamp());
} }

View file

@ -1,2 +1,46 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use crate::{
collection::Collection, config::SchedulerVersion, err::Result, timestamp::TimestampSecs,
};
pub mod cutoff; pub mod cutoff;
pub mod timespan; pub mod timespan;
use cutoff::{sched_timing_today, v1_rollover_from_creation_stamp, SchedTimingToday};
impl Collection {
pub fn timing_today(&mut self) -> Result<SchedTimingToday> {
self.timing_for_timestamp(TimestampSecs::now())
}
pub(crate) fn timing_for_timestamp(&mut self, now: TimestampSecs) -> Result<SchedTimingToday> {
let local_offset = if self.server {
self.get_local_mins_west()
} else {
None
};
Ok(sched_timing_today(
self.storage.creation_stamp()?,
now,
self.get_creation_mins_west(),
local_offset,
self.get_rollover(),
))
}
pub fn rollover_for_current_scheduler(&self) -> Result<u8> {
match self.sched_ver() {
SchedulerVersion::V1 => Ok(v1_rollover_from_creation_stamp(
self.storage.creation_stamp()?.0,
)),
SchedulerVersion::V2 => Ok(self.get_rollover().unwrap_or(4)),
}
}
pub(crate) fn learn_cutoff(&self) -> u32 {
TimestampSecs::now().0 as u32 + self.learn_ahead_secs()
}
}