From 4662a9fe1af47ec0632af21105c88fcb2e5858a2 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Fri, 28 Aug 2020 21:09:07 +1000 Subject: [PATCH] check if we need to unbury at the start of the sync process https://forums.ankiweb.net/t/bug-buried-cards-in-filtered-decks-not-being-unburied-next-day/2541/24 --- pylib/anki/schedv2.py | 4 +-- rslib/src/card.rs | 17 +++++++++++ rslib/src/config.rs | 11 +++++++ rslib/src/sched/mod.rs | 57 +++++++++++++++++++++++++++++++++++ rslib/src/storage/card/mod.rs | 17 +++++++++++ rslib/src/sync/mod.rs | 1 + 6 files changed, 105 insertions(+), 2 deletions(-) diff --git a/pylib/anki/schedv2.py b/pylib/anki/schedv2.py index 49cdcd0d5..053728151 100644 --- a/pylib/anki/schedv2.py +++ b/pylib/anki/schedv2.py @@ -1456,8 +1456,8 @@ To study outside of the normal schedule, click the Custom Study button below.""" # other types map directly to queues _restoreQueueSnippet = f""" queue = (case when type in ({CARD_TYPE_LRN},{CARD_TYPE_RELEARNING}) then - (case when (case when odue then odue else due end) > 1000000000 then 1 else - {QUEUE_TYPE_DAY_LEARN_RELEARN} end) + (case when (case when odue then odue else due end) > 1000000000 then + {QUEUE_TYPE_LRN} else {QUEUE_TYPE_DAY_LEARN_RELEARN} end) else type end) diff --git a/rslib/src/card.rs b/rslib/src/card.rs index 98180066e..c38c0c046 100644 --- a/rslib/src/card.rs +++ b/rslib/src/card.rs @@ -149,6 +149,23 @@ impl Card { self.ctype = CardType::New; } } + + pub(crate) fn restore_queue_after_bury_or_suspend(&mut self) { + self.queue = match self.ctype { + CardType::Learn | CardType::Relearn => { + let original_due = if self.odue > 0 { self.odue } else { self.due }; + if original_due > 1_000_000_000 { + // previous interval was in seconds + CardQueue::Learn + } else { + // previous interval was in days + CardQueue::DayLearn + } + } + CardType::New => CardQueue::New, + CardType::Review => CardQueue::Review, + } + } } #[derive(Debug)] pub(crate) struct UpdateCardUndo(Card); diff --git a/rslib/src/config.rs b/rslib/src/config.rs index e2f1e3aed..b3c2e845e 100644 --- a/rslib/src/config.rs +++ b/rslib/src/config.rs @@ -49,6 +49,7 @@ pub(crate) enum ConfigKey { NewReviewMix, AnswerTimeLimitSecs, ShowDayLearningCardsFirst, + LastUnburiedDay, } #[derive(PartialEq, Serialize_repr, Deserialize_repr, Clone, Copy)] #[repr(u8)] @@ -76,6 +77,7 @@ impl From for &'static str { ConfigKey::NewReviewMix => "newSpread", ConfigKey::AnswerTimeLimitSecs => "timeLim", ConfigKey::ShowDayLearningCardsFirst => "dayLearnFirst", + ConfigKey::LastUnburiedDay => "lastUnburied", } } } @@ -256,6 +258,15 @@ impl Collection { pub(crate) fn set_day_learn_first(&self, on: bool) -> Result<()> { self.set_config(ConfigKey::ShowDayLearningCardsFirst, &on) } + + pub(crate) fn get_last_unburied_day(&self) -> u32 { + self.get_config_optional(ConfigKey::LastUnburiedDay) + .unwrap_or_default() + } + + pub(crate) fn set_last_unburied_day(&self, day: u32) -> Result<()> { + self.set_config(ConfigKey::LastUnburiedDay, &day) + } } #[derive(Deserialize, PartialEq, Debug, Clone, Copy)] diff --git a/rslib/src/sched/mod.rs b/rslib/src/sched/mod.rs index c176ff2d7..70e745632 100644 --- a/rslib/src/sched/mod.rs +++ b/rslib/src/sched/mod.rs @@ -78,4 +78,61 @@ impl Collection { SchedulerVersion::V2 => self.set_v2_rollover(hour as u32), } } + + pub(crate) fn unbury_if_day_rolled_over(&mut self) -> Result<()> { + let last_unburied = self.get_last_unburied_day(); + let today = self.timing_today()?.days_elapsed; + if last_unburied < today || (today + 7) < last_unburied { + self.unbury_on_day_rollover()?; + self.set_last_unburied_day(today)?; + } + + Ok(()) + } + + fn unbury_on_day_rollover(&mut self) -> Result<()> { + self.search_cards_into_table("is:buried")?; + self.storage.for_each_card_in_search(|mut card| { + card.restore_queue_after_bury_or_suspend(); + self.storage.update_card(&card) + })?; + self.clear_searched_cards()?; + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use crate::{ + card::{Card, CardQueue}, + collection::{open_test_collection, Collection}, + search::SortMode, + }; + + #[test] + fn unbury() { + let mut col = open_test_collection(); + let mut card = Card::default(); + card.queue = CardQueue::UserBuried; + col.add_card(&mut card).unwrap(); + let assert_count = |col: &mut Collection, cnt| { + assert_eq!( + col.search_cards("is:buried", SortMode::NoOrder) + .unwrap() + .len(), + cnt + ); + }; + assert_count(&mut col, 1); + // day 0, last unburied 0, so no change + col.unbury_if_day_rolled_over().unwrap(); + assert_count(&mut col, 1); + // move creation time back and it should succeed + let mut stamp = col.storage.creation_stamp().unwrap(); + stamp.0 -= 86_400; + col.storage.set_creation_stamp(stamp).unwrap(); + col.unbury_if_day_rolled_over().unwrap(); + assert_count(&mut col, 0); + } } diff --git a/rslib/src/storage/card/mod.rs b/rslib/src/storage/card/mod.rs index 8ca5ad79d..232ed1792 100644 --- a/rslib/src/storage/card/mod.rs +++ b/rslib/src/storage/card/mod.rs @@ -260,6 +260,23 @@ impl super::SqliteStorage { .query_and_then(NO_PARAMS, |r| row_to_card(r).map_err(Into::into))? .collect() } + + pub(crate) fn for_each_card_in_search(&self, mut func: F) -> Result<()> + where + F: FnMut(Card) -> Result<()>, + { + let mut stmt = self.db.prepare_cached(concat!( + include_str!("get_card.sql"), + " where id in (select id from search_cids)" + ))?; + let mut rows = stmt.query(NO_PARAMS)?; + while let Some(row) = rows.next()? { + let card = row_to_card(row)?; + func(card)? + } + + Ok(()) + } } #[cfg(test)] diff --git a/rslib/src/sync/mod.rs b/rslib/src/sync/mod.rs index b2933e22d..d3fe5f2a9 100644 --- a/rslib/src/sync/mod.rs +++ b/rslib/src/sync/mod.rs @@ -319,6 +319,7 @@ where SyncActionRequired::FullSyncRequired { .. } => Ok(state.into()), SyncActionRequired::NormalSyncRequired => { self.col.storage.begin_trx()?; + self.col.unbury_if_day_rolled_over()?; match self.normal_sync_inner(state).await { Ok(success) => { self.col.storage.commit_trx()?;