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
This commit is contained in:
Damien Elmes 2020-08-28 21:09:07 +10:00
parent 3bd85bf180
commit 4662a9fe1a
6 changed files with 105 additions and 2 deletions

View File

@ -1456,8 +1456,8 @@ To study outside of the normal schedule, click the Custom Study button below."""
# other types map directly to queues # other types map directly to queues
_restoreQueueSnippet = f""" _restoreQueueSnippet = f"""
queue = (case when type in ({CARD_TYPE_LRN},{CARD_TYPE_RELEARNING}) then 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 (case when (case when odue then odue else due end) > 1000000000 then
{QUEUE_TYPE_DAY_LEARN_RELEARN} end) {QUEUE_TYPE_LRN} else {QUEUE_TYPE_DAY_LEARN_RELEARN} end)
else else
type type
end) end)

View File

@ -149,6 +149,23 @@ impl Card {
self.ctype = CardType::New; 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)] #[derive(Debug)]
pub(crate) struct UpdateCardUndo(Card); pub(crate) struct UpdateCardUndo(Card);

View File

@ -49,6 +49,7 @@ pub(crate) enum ConfigKey {
NewReviewMix, NewReviewMix,
AnswerTimeLimitSecs, AnswerTimeLimitSecs,
ShowDayLearningCardsFirst, ShowDayLearningCardsFirst,
LastUnburiedDay,
} }
#[derive(PartialEq, Serialize_repr, Deserialize_repr, Clone, Copy)] #[derive(PartialEq, Serialize_repr, Deserialize_repr, Clone, Copy)]
#[repr(u8)] #[repr(u8)]
@ -76,6 +77,7 @@ impl From<ConfigKey> for &'static str {
ConfigKey::NewReviewMix => "newSpread", ConfigKey::NewReviewMix => "newSpread",
ConfigKey::AnswerTimeLimitSecs => "timeLim", ConfigKey::AnswerTimeLimitSecs => "timeLim",
ConfigKey::ShowDayLearningCardsFirst => "dayLearnFirst", ConfigKey::ShowDayLearningCardsFirst => "dayLearnFirst",
ConfigKey::LastUnburiedDay => "lastUnburied",
} }
} }
} }
@ -256,6 +258,15 @@ impl Collection {
pub(crate) fn set_day_learn_first(&self, on: bool) -> Result<()> { pub(crate) fn set_day_learn_first(&self, on: bool) -> Result<()> {
self.set_config(ConfigKey::ShowDayLearningCardsFirst, &on) 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)] #[derive(Deserialize, PartialEq, Debug, Clone, Copy)]

View File

@ -78,4 +78,61 @@ impl Collection {
SchedulerVersion::V2 => self.set_v2_rollover(hour as u32), 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);
}
} }

View File

@ -260,6 +260,23 @@ impl super::SqliteStorage {
.query_and_then(NO_PARAMS, |r| row_to_card(r).map_err(Into::into))? .query_and_then(NO_PARAMS, |r| row_to_card(r).map_err(Into::into))?
.collect() .collect()
} }
pub(crate) fn for_each_card_in_search<F>(&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)] #[cfg(test)]

View File

@ -319,6 +319,7 @@ where
SyncActionRequired::FullSyncRequired { .. } => Ok(state.into()), SyncActionRequired::FullSyncRequired { .. } => Ok(state.into()),
SyncActionRequired::NormalSyncRequired => { SyncActionRequired::NormalSyncRequired => {
self.col.storage.begin_trx()?; self.col.storage.begin_trx()?;
self.col.unbury_if_day_rolled_over()?;
match self.normal_sync_inner(state).await { match self.normal_sync_inner(state).await {
Ok(success) => { Ok(success) => {
self.col.storage.commit_trx()?; self.col.storage.commit_trx()?;