mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
move unbury/unsuspend routines into backend
This commit is contained in:
parent
e80b3eeeef
commit
ccfa989c62
14 changed files with 272 additions and 200 deletions
|
@ -56,6 +56,10 @@ message CardID {
|
|||
int64 cid = 1;
|
||||
}
|
||||
|
||||
message CardIDs {
|
||||
repeated int64 cids = 1;
|
||||
}
|
||||
|
||||
message DeckID {
|
||||
int64 did = 1;
|
||||
}
|
||||
|
@ -96,6 +100,8 @@ service BackendService {
|
|||
rpc ExtendLimits (ExtendLimitsIn) returns (Empty);
|
||||
rpc CountsForDeckToday (DeckID) returns (CountsForDeckTodayOut);
|
||||
rpc CongratsInfo (Empty) returns (CongratsInfoOut);
|
||||
rpc RestoreBuriedAndSuspendedCards (CardIDs) returns (Empty);
|
||||
rpc UnburyCardsInCurrentDeck (UnburyCardsInCurrentDeckIn) returns (Empty);
|
||||
|
||||
// stats
|
||||
|
||||
|
@ -1027,3 +1033,12 @@ message CongratsInfoOut {
|
|||
bool is_filtered_deck = 7;
|
||||
bool bridge_commands_supported = 8;
|
||||
}
|
||||
|
||||
message UnburyCardsInCurrentDeckIn {
|
||||
enum Mode {
|
||||
ALL = 0;
|
||||
SCHED_ONLY = 1;
|
||||
USER_ONLY = 2;
|
||||
}
|
||||
Mode mode = 1;
|
||||
}
|
||||
|
|
|
@ -121,32 +121,6 @@ class Scheduler(V2):
|
|||
else:
|
||||
return 3
|
||||
|
||||
def unburyCards(self) -> None:
|
||||
"Unbury cards."
|
||||
self.col.log(
|
||||
self.col.db.list(
|
||||
f"select id from cards where queue = {QUEUE_TYPE_SIBLING_BURIED}"
|
||||
)
|
||||
)
|
||||
self.col.db.execute(
|
||||
f"update cards set queue=type where queue = {QUEUE_TYPE_SIBLING_BURIED}"
|
||||
)
|
||||
|
||||
def unburyCardsForDeck(self) -> None: # type: ignore[override]
|
||||
sids = self._deckLimit()
|
||||
self.col.log(
|
||||
self.col.db.list(
|
||||
f"select id from cards where queue = {QUEUE_TYPE_SIBLING_BURIED} and did in %s"
|
||||
% sids
|
||||
)
|
||||
)
|
||||
self.col.db.execute(
|
||||
f"update cards set mod=?,usn=?,queue=type where queue = {QUEUE_TYPE_SIBLING_BURIED} and did in %s"
|
||||
% sids,
|
||||
intTime(),
|
||||
self.col.usn(),
|
||||
)
|
||||
|
||||
# Getting the next card
|
||||
##########################################################################
|
||||
|
||||
|
@ -848,16 +822,6 @@ did = ?, queue = %s, due = ?, usn = ? where id = ?"""
|
|||
self.col.usn(),
|
||||
)
|
||||
|
||||
def unsuspendCards(self, ids: List[int]) -> None:
|
||||
"Unsuspend cards."
|
||||
self.col.log(ids)
|
||||
self.col.db.execute(
|
||||
"update cards set queue=type,mod=?,usn=? "
|
||||
f"where queue = {QUEUE_TYPE_SUSPENDED} and id in " + ids2str(ids),
|
||||
intTime(),
|
||||
self.col.usn(),
|
||||
)
|
||||
|
||||
def buryCards(self, cids: List[int], manual: bool = False) -> None:
|
||||
# v1 only supported automatic burying
|
||||
assert not manual
|
||||
|
|
|
@ -7,7 +7,18 @@ import pprint
|
|||
import random
|
||||
import time
|
||||
from heapq import *
|
||||
from typing import Any, Callable, Dict, List, Optional, Sequence, Set, Tuple, Union
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
List,
|
||||
Optional,
|
||||
Sequence,
|
||||
Set,
|
||||
Tuple,
|
||||
Union,
|
||||
)
|
||||
|
||||
import anki # pylint: disable=unused-import
|
||||
import anki.backend_pb2 as pb
|
||||
|
@ -25,6 +36,12 @@ from anki.rsbackend import (
|
|||
)
|
||||
from anki.utils import ids2str, intTime
|
||||
|
||||
UnburyCurrentDeckMode = pb.UnburyCardsInCurrentDeckIn.Mode # pylint: disable=no-member
|
||||
if TYPE_CHECKING:
|
||||
UnburyCurrentDeckModeValue = (
|
||||
pb.UnburyCardsInCurrentDeckIn.ModeValue # pylint: disable=no-member
|
||||
)
|
||||
|
||||
# card types: 0=new, 1=lrn, 2=rev, 3=relrn
|
||||
# queue types: 0=new, 1=(re)lrn, 2=rev, 3=day (re)lrn,
|
||||
# 4=preview, -1=suspended, -2=sibling buried, -3=manually buried
|
||||
|
@ -1265,12 +1282,6 @@ where id = ?
|
|||
self.today = timing.days_elapsed
|
||||
self.dayCutoff = timing.next_day_at
|
||||
|
||||
# unbury if the day has rolled over
|
||||
unburied = self.col.conf.get("lastUnburied", 0)
|
||||
if unburied < self.today or self.today + 7 < unburied:
|
||||
self.unburyCards()
|
||||
self.col.conf["lastUnburied"] = self.today
|
||||
|
||||
def _checkDay(self) -> None:
|
||||
# check if the day has rolled over
|
||||
if time.time() > self.dayCutoff:
|
||||
|
@ -1364,16 +1375,16 @@ where id = ?
|
|||
# Suspending & burying
|
||||
##########################################################################
|
||||
|
||||
# learning and relearning cards may be seconds-based or day-based;
|
||||
# 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
|
||||
{QUEUE_TYPE_LRN} else {QUEUE_TYPE_DAY_LEARN_RELEARN} end)
|
||||
else
|
||||
type
|
||||
end)
|
||||
"""
|
||||
def unsuspend_cards(self, ids: List[int]) -> None:
|
||||
self.col.backend.restore_buried_and_suspended_cards(ids)
|
||||
|
||||
def unbury_cards(self, ids: List[int]) -> None:
|
||||
self.col.backend.restore_buried_and_suspended_cards(ids)
|
||||
|
||||
def unbury_cards_in_current_deck(
|
||||
self, mode: UnburyCurrentDeckModeValue = UnburyCurrentDeckMode.ALL,
|
||||
) -> None:
|
||||
self.col.backend.unbury_cards_in_current_deck(mode)
|
||||
|
||||
def suspendCards(self, ids: List[int]) -> None:
|
||||
"Suspend cards."
|
||||
|
@ -1385,18 +1396,6 @@ end)
|
|||
self.col.usn(),
|
||||
)
|
||||
|
||||
def unsuspendCards(self, ids: List[int]) -> None:
|
||||
"Unsuspend cards."
|
||||
self.col.log(ids)
|
||||
self.col.db.execute(
|
||||
(
|
||||
f"update cards set %s,mod=?,usn=? where queue = {QUEUE_TYPE_SUSPENDED} and id in %s"
|
||||
)
|
||||
% (self._restoreQueueSnippet, ids2str(ids)),
|
||||
intTime(),
|
||||
self.col.usn(),
|
||||
)
|
||||
|
||||
def buryCards(self, cids: List[int], manual: bool = True) -> None:
|
||||
queue = manual and QUEUE_TYPE_MANUALLY_BURIED or QUEUE_TYPE_SIBLING_BURIED
|
||||
self.col.log(cids)
|
||||
|
@ -1416,42 +1415,24 @@ update cards set queue=?,mod=?,usn=? where id in """
|
|||
)
|
||||
self.buryCards(cids)
|
||||
|
||||
# legacy
|
||||
|
||||
def unburyCards(self) -> None:
|
||||
"Unbury all buried cards in all decks."
|
||||
self.col.log(
|
||||
self.col.db.list(
|
||||
f"select id from cards where queue in ({QUEUE_TYPE_SIBLING_BURIED}, {QUEUE_TYPE_MANUALLY_BURIED})"
|
||||
)
|
||||
)
|
||||
self.col.db.execute(
|
||||
f"update cards set %s where queue in ({QUEUE_TYPE_SIBLING_BURIED}, {QUEUE_TYPE_MANUALLY_BURIED})"
|
||||
% self._restoreQueueSnippet
|
||||
print(
|
||||
"please use unbury_cards() or unbury_cards_in_current_deck instead of unburyCards()"
|
||||
)
|
||||
self.unbury_cards_in_current_deck()
|
||||
|
||||
def unburyCardsForDeck(self, type: str = "all") -> None:
|
||||
if type == "all":
|
||||
queue = (
|
||||
f"queue in ({QUEUE_TYPE_SIBLING_BURIED}, {QUEUE_TYPE_MANUALLY_BURIED})"
|
||||
)
|
||||
mode = UnburyCurrentDeckMode.ALL
|
||||
elif type == "manual":
|
||||
queue = f"queue = {QUEUE_TYPE_MANUALLY_BURIED}"
|
||||
elif type == "siblings":
|
||||
queue = f"queue = {QUEUE_TYPE_SIBLING_BURIED}"
|
||||
else:
|
||||
raise Exception("unknown type")
|
||||
mode = UnburyCurrentDeckMode.USER_ONLY
|
||||
else: # elif type == "siblings":
|
||||
mode = UnburyCurrentDeckMode.SCHED_ONLY
|
||||
self.unbury_cards_in_current_deck(mode)
|
||||
|
||||
self.col.log(
|
||||
self.col.db.list(
|
||||
"select id from cards where %s and did in %s"
|
||||
% (queue, self._deckLimit())
|
||||
)
|
||||
)
|
||||
self.col.db.execute(
|
||||
"update cards set mod=?,usn=?,%s where %s and did in %s"
|
||||
% (self._restoreQueueSnippet, queue, self._deckLimit()),
|
||||
intTime(),
|
||||
self.col.usn(),
|
||||
)
|
||||
unsuspendCards = unsuspend_cards
|
||||
|
||||
# Sibling spacing
|
||||
##########################################################################
|
||||
|
|
|
@ -503,7 +503,7 @@ def test_misc():
|
|||
col.sched.buryNote(c.nid)
|
||||
col.reset()
|
||||
assert not col.sched.getCard()
|
||||
col.sched.unburyCards()
|
||||
col.sched.unbury_cards_in_current_deck()
|
||||
col.reset()
|
||||
assert col.sched.getCard()
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import time
|
|||
from anki import hooks
|
||||
from anki.consts import *
|
||||
from anki.lang import without_unicode_isolation
|
||||
from anki.schedv2 import UnburyCurrentDeckMode
|
||||
from anki.utils import intTime
|
||||
from tests.shared import getEmptyCol as getEmptyColOrig
|
||||
|
||||
|
@ -609,22 +610,18 @@ def test_bury():
|
|||
col.reset()
|
||||
assert not col.sched.getCard()
|
||||
|
||||
col.sched.unburyCardsForDeck( # pylint: disable=unexpected-keyword-arg
|
||||
type="manual"
|
||||
)
|
||||
col.sched.unbury_cards_in_current_deck(UnburyCurrentDeckMode.USER_ONLY)
|
||||
c.load()
|
||||
assert c.queue == QUEUE_TYPE_NEW
|
||||
c2.load()
|
||||
assert c2.queue == QUEUE_TYPE_SIBLING_BURIED
|
||||
|
||||
col.sched.unburyCardsForDeck( # pylint: disable=unexpected-keyword-arg
|
||||
type="siblings"
|
||||
)
|
||||
col.sched.unbury_cards_in_current_deck(UnburyCurrentDeckMode.SCHED_ONLY)
|
||||
c2.load()
|
||||
assert c2.queue == QUEUE_TYPE_NEW
|
||||
|
||||
col.sched.buryCards([c.id, c2.id])
|
||||
col.sched.unburyCardsForDeck(type="all") # pylint: disable=unexpected-keyword-arg
|
||||
col.sched.unbury_cards_in_current_deck()
|
||||
|
||||
col.reset()
|
||||
|
||||
|
@ -1214,7 +1211,7 @@ def test_moveVersions():
|
|||
assert c.queue == QUEUE_TYPE_SIBLING_BURIED
|
||||
|
||||
# and it should be new again when unburied
|
||||
col.sched.unburyCards()
|
||||
col.sched.unbury_cards_in_current_deck()
|
||||
c.load()
|
||||
assert c.type == CARD_TYPE_NEW and c.queue == QUEUE_TYPE_NEW
|
||||
|
||||
|
|
|
@ -235,6 +235,12 @@ impl From<pb::CardId> for CardID {
|
|||
}
|
||||
}
|
||||
|
||||
impl pb::CardIDs {
|
||||
fn into_native(self) -> Vec<CardID> {
|
||||
self.cids.into_iter().map(CardID).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<pb::NoteId> for NoteID {
|
||||
fn from(nid: pb::NoteId) -> Self {
|
||||
NoteID(nid.nid)
|
||||
|
@ -442,8 +448,14 @@ impl BackendService for Backend {
|
|||
// scheduling
|
||||
//-----------------------------------------------
|
||||
|
||||
/// This behaves like _updateCutoff() in older code - it also unburies at the start of
|
||||
/// a new day.
|
||||
fn sched_timing_today(&mut self, _input: pb::Empty) -> Result<pb::SchedTimingTodayOut> {
|
||||
self.with_col(|col| col.timing_today().map(Into::into))
|
||||
self.with_col(|col| {
|
||||
let timing = col.timing_today()?;
|
||||
col.unbury_if_day_rolled_over(timing)?;
|
||||
Ok(timing.into())
|
||||
})
|
||||
}
|
||||
|
||||
fn local_minutes_west(&mut self, input: pb::Int64) -> BackendResult<pb::Int32> {
|
||||
|
@ -502,6 +514,23 @@ impl BackendService for Backend {
|
|||
self.with_col(|col| col.congrats_info())
|
||||
}
|
||||
|
||||
fn restore_buried_and_suspended_cards(&mut self, input: pb::CardIDs) -> BackendResult<Empty> {
|
||||
self.with_col(|col| {
|
||||
col.unbury_or_unsuspend_cards(&input.into_native())
|
||||
.map(Into::into)
|
||||
})
|
||||
}
|
||||
|
||||
fn unbury_cards_in_current_deck(
|
||||
&mut self,
|
||||
input: pb::UnburyCardsInCurrentDeckIn,
|
||||
) -> BackendResult<Empty> {
|
||||
self.with_col(|col| {
|
||||
col.unbury_cards_in_current_deck(input.mode())
|
||||
.map(Into::into)
|
||||
})
|
||||
}
|
||||
|
||||
// statistics
|
||||
//-----------------------------------------------
|
||||
|
||||
|
@ -695,7 +724,7 @@ impl BackendService for Backend {
|
|||
.storage
|
||||
.get_card(card.id)?
|
||||
.ok_or_else(|| AnkiError::invalid_input("missing card"))?;
|
||||
ctx.update_card(&mut card, &orig)
|
||||
ctx.update_card(&mut card, &orig, ctx.usn()?)
|
||||
})
|
||||
})
|
||||
.map(Into::into)
|
||||
|
|
|
@ -97,6 +97,11 @@ impl Default for Card {
|
|||
}
|
||||
|
||||
impl Card {
|
||||
pub fn set_modified(&mut self, usn: Usn) {
|
||||
self.mtime = TimestampSecs::now();
|
||||
self.usn = usn;
|
||||
}
|
||||
|
||||
pub(crate) fn return_home(&mut self, sched: SchedulerVersion) {
|
||||
if self.odid.0 == 0 {
|
||||
// this should not happen
|
||||
|
@ -149,34 +154,17 @@ 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);
|
||||
|
||||
impl Undoable for UpdateCardUndo {
|
||||
fn apply(&self, col: &mut crate::collection::Collection) -> Result<()> {
|
||||
fn apply(&self, col: &mut crate::collection::Collection, usn: Usn) -> Result<()> {
|
||||
let current = col
|
||||
.storage
|
||||
.get_card(self.0.id)?
|
||||
.ok_or_else(|| AnkiError::invalid_input("card disappeared"))?;
|
||||
col.update_card(&mut self.0.clone(), ¤t)
|
||||
col.update_card(&mut self.0.clone(), ¤t, usn)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -202,19 +190,18 @@ impl Collection {
|
|||
.ok_or_else(|| AnkiError::invalid_input("no such card"))?;
|
||||
let mut card = orig.clone();
|
||||
func(&mut card)?;
|
||||
self.update_card(&mut card, &orig)?;
|
||||
self.update_card(&mut card, &orig, self.usn()?)?;
|
||||
Ok(card)
|
||||
}
|
||||
|
||||
pub(crate) fn update_card(&mut self, card: &mut Card, original: &Card) -> Result<()> {
|
||||
pub(crate) fn update_card(&mut self, card: &mut Card, original: &Card, usn: Usn) -> Result<()> {
|
||||
if card.id.0 == 0 {
|
||||
return Err(AnkiError::invalid_input("card id not set"));
|
||||
}
|
||||
self.state
|
||||
.undo
|
||||
.save_undoable(Box::new(UpdateCardUndo(original.clone())));
|
||||
card.mtime = TimestampSecs::now();
|
||||
card.usn = self.usn()?;
|
||||
card.set_modified(usn);
|
||||
self.storage.update_card(card)
|
||||
}
|
||||
|
||||
|
|
133
rslib/src/sched/bury_and_suspend.rs
Normal file
133
rslib/src/sched/bury_and_suspend.rs
Normal file
|
@ -0,0 +1,133 @@
|
|||
// Copyright: Ankitects Pty Ltd and contributors
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
use crate::{
|
||||
backend_proto as pb,
|
||||
card::{Card, CardID, CardQueue, CardType},
|
||||
collection::Collection,
|
||||
err::Result,
|
||||
};
|
||||
|
||||
use super::cutoff::SchedTimingToday;
|
||||
use pb::unbury_cards_in_current_deck_in::Mode as UnburyDeckMode;
|
||||
|
||||
impl Card {
|
||||
/// True if card was buried/suspended prior to the call.
|
||||
pub(crate) fn restore_queue_after_bury_or_suspend(&mut self) -> bool {
|
||||
if !matches!(
|
||||
self.queue,
|
||||
CardQueue::Suspended | CardQueue::SchedBuried | CardQueue::UserBuried
|
||||
) {
|
||||
false
|
||||
} else {
|
||||
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,
|
||||
};
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Collection {
|
||||
pub(crate) fn unbury_if_day_rolled_over(&mut self, timing: SchedTimingToday) -> Result<()> {
|
||||
let last_unburied = self.get_last_unburied_day();
|
||||
let today = timing.days_elapsed;
|
||||
if last_unburied < today || (today + 7) < last_unburied {
|
||||
self.unbury_on_day_rollover()?;
|
||||
self.set_last_unburied_day(today)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Unbury cards from the previous day.
|
||||
/// Done automatically, and does not mark the cards as modified.
|
||||
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()
|
||||
}
|
||||
|
||||
/// Unsuspend/unbury cards in search table, and clear it.
|
||||
/// Marks the cards as modified.
|
||||
fn unsuspend_or_unbury_searched_cards(&mut self) -> Result<()> {
|
||||
let usn = self.usn()?;
|
||||
for original in self.storage.all_searched_cards()? {
|
||||
let mut card = original.clone();
|
||||
if card.restore_queue_after_bury_or_suspend() {
|
||||
self.update_card(&mut card, &original, usn)?;
|
||||
}
|
||||
}
|
||||
self.clear_searched_cards()
|
||||
}
|
||||
|
||||
pub fn unbury_or_unsuspend_cards(&mut self, cids: &[CardID]) -> Result<()> {
|
||||
self.transact(None, |col| {
|
||||
col.set_search_table_to_card_ids(cids)?;
|
||||
col.unsuspend_or_unbury_searched_cards()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn unbury_cards_in_current_deck(&mut self, mode: UnburyDeckMode) -> Result<()> {
|
||||
let search = match mode {
|
||||
UnburyDeckMode::All => "is:buried",
|
||||
UnburyDeckMode::UserOnly => "is:buried-manually",
|
||||
UnburyDeckMode::SchedOnly => "is:buried-sibling",
|
||||
};
|
||||
self.transact(None, |col| {
|
||||
col.search_cards_into_table(&format!("deck:current {}", search))?;
|
||||
col.unsuspend_or_unbury_searched_cards()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[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
|
||||
let timing = col.timing_today().unwrap();
|
||||
col.unbury_if_day_rolled_over(timing).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();
|
||||
let timing = col.timing_today().unwrap();
|
||||
col.unbury_if_day_rolled_over(timing).unwrap();
|
||||
assert_count(&mut col, 0);
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ use crate::{
|
|||
collection::Collection, config::SchedulerVersion, err::Result, timestamp::TimestampSecs,
|
||||
};
|
||||
|
||||
pub mod bury_and_suspend;
|
||||
pub(crate) mod congrats;
|
||||
pub mod cutoff;
|
||||
pub mod timespan;
|
||||
|
@ -79,61 +80,4 @@ 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,12 +5,10 @@ use super::{
|
|||
parser::Node,
|
||||
sqlwriter::{RequiredTable, SqlWriter},
|
||||
};
|
||||
use crate::card::CardID;
|
||||
use crate::card::CardType;
|
||||
use crate::collection::Collection;
|
||||
use crate::config::SortKind;
|
||||
use crate::err::Result;
|
||||
use crate::search::parser::parse;
|
||||
use crate::{
|
||||
card::CardID, card::CardType, collection::Collection, config::SortKind, err::Result,
|
||||
search::parser::parse,
|
||||
};
|
||||
use rusqlite::NO_PARAMS;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
|
@ -95,10 +93,9 @@ impl Collection {
|
|||
let writer = SqlWriter::new(self);
|
||||
|
||||
let (sql, args) = writer.build_cards_query(&top_node, RequiredTable::Cards)?;
|
||||
self.storage.db.execute_batch(concat!(
|
||||
"drop table if exists search_cids;",
|
||||
"create temporary table search_cids (id integer primary key not null);"
|
||||
))?;
|
||||
self.storage
|
||||
.db
|
||||
.execute_batch(include_str!("search_cids_setup.sql"))?;
|
||||
let sql = format!("insert into search_cids {}", sql);
|
||||
|
||||
self.storage.db.prepare(&sql)?.execute(&args)?;
|
||||
|
@ -106,6 +103,24 @@ impl Collection {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Injects the provided card IDs into the search_cids table, for
|
||||
/// when ids have arrived outside of a search.
|
||||
/// Clear with clear_searched_cards().
|
||||
pub(crate) fn set_search_table_to_card_ids(&mut self, cards: &[CardID]) -> Result<()> {
|
||||
self.storage
|
||||
.db
|
||||
.execute_batch(include_str!("search_cids_setup.sql"))?;
|
||||
let mut stmt = self
|
||||
.storage
|
||||
.db
|
||||
.prepare_cached("insert into search_cids values (?)")?;
|
||||
for cid in cards {
|
||||
stmt.execute(&[cid])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn clear_searched_cards(&self) -> Result<()> {
|
||||
self.storage
|
||||
.db
|
||||
|
|
2
rslib/src/search/search_cids_setup.sql
Normal file
2
rslib/src/search/search_cids_setup.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
drop table if exists search_cids;
|
||||
create temporary table search_cids (id integer primary key not null);
|
|
@ -279,7 +279,7 @@ impl super::SqliteStorage {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
pub(crate) fn congrats_info(&self, current: &Deck, today: u32) -> Result<CongratsInfo> {
|
||||
self.update_active_decks(current)?;
|
||||
self.db
|
||||
|
|
|
@ -319,7 +319,8 @@ where
|
|||
SyncActionRequired::FullSyncRequired { .. } => Ok(state.into()),
|
||||
SyncActionRequired::NormalSyncRequired => {
|
||||
self.col.storage.begin_trx()?;
|
||||
self.col.unbury_if_day_rolled_over()?;
|
||||
self.col
|
||||
.unbury_if_day_rolled_over(self.col.timing_today()?)?;
|
||||
match self.normal_sync_inner(state).await {
|
||||
Ok(success) => {
|
||||
self.col.storage.commit_trx()?;
|
||||
|
|
|
@ -4,11 +4,13 @@
|
|||
use crate::{
|
||||
collection::{Collection, CollectionOp},
|
||||
err::Result,
|
||||
types::Usn,
|
||||
};
|
||||
use std::fmt;
|
||||
|
||||
pub(crate) trait Undoable: fmt::Debug + Send {
|
||||
fn apply(&self, ctx: &mut Collection) -> Result<()>;
|
||||
/// Undo the recorded action.
|
||||
fn apply(&self, ctx: &mut Collection, usn: Usn) -> Result<()>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -97,8 +99,9 @@ impl Collection {
|
|||
let changes = step.changes;
|
||||
self.state.undo.mode = UndoMode::Undoing;
|
||||
let res = self.transact(Some(step.kind), |col| {
|
||||
let usn = col.usn()?;
|
||||
for change in changes.iter().rev() {
|
||||
change.apply(col)?;
|
||||
change.apply(col, usn)?;
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
@ -113,8 +116,9 @@ impl Collection {
|
|||
let changes = step.changes;
|
||||
self.state.undo.mode = UndoMode::Redoing;
|
||||
let res = self.transact(Some(step.kind), |col| {
|
||||
let usn = col.usn()?;
|
||||
for change in changes.iter().rev() {
|
||||
change.apply(col)?;
|
||||
change.apply(col, usn)?;
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue