move unbury/unsuspend routines into backend

This commit is contained in:
Damien Elmes 2020-08-29 22:02:22 +10:00
parent e80b3eeeef
commit ccfa989c62
14 changed files with 272 additions and 200 deletions

View file

@ -56,6 +56,10 @@ message CardID {
int64 cid = 1; int64 cid = 1;
} }
message CardIDs {
repeated int64 cids = 1;
}
message DeckID { message DeckID {
int64 did = 1; int64 did = 1;
} }
@ -96,6 +100,8 @@ service BackendService {
rpc ExtendLimits (ExtendLimitsIn) returns (Empty); rpc ExtendLimits (ExtendLimitsIn) returns (Empty);
rpc CountsForDeckToday (DeckID) returns (CountsForDeckTodayOut); rpc CountsForDeckToday (DeckID) returns (CountsForDeckTodayOut);
rpc CongratsInfo (Empty) returns (CongratsInfoOut); rpc CongratsInfo (Empty) returns (CongratsInfoOut);
rpc RestoreBuriedAndSuspendedCards (CardIDs) returns (Empty);
rpc UnburyCardsInCurrentDeck (UnburyCardsInCurrentDeckIn) returns (Empty);
// stats // stats
@ -1027,3 +1033,12 @@ message CongratsInfoOut {
bool is_filtered_deck = 7; bool is_filtered_deck = 7;
bool bridge_commands_supported = 8; bool bridge_commands_supported = 8;
} }
message UnburyCardsInCurrentDeckIn {
enum Mode {
ALL = 0;
SCHED_ONLY = 1;
USER_ONLY = 2;
}
Mode mode = 1;
}

View file

@ -121,32 +121,6 @@ class Scheduler(V2):
else: else:
return 3 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 # Getting the next card
########################################################################## ##########################################################################
@ -848,16 +822,6 @@ did = ?, queue = %s, due = ?, usn = ? where id = ?"""
self.col.usn(), 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: def buryCards(self, cids: List[int], manual: bool = False) -> None:
# v1 only supported automatic burying # v1 only supported automatic burying
assert not manual assert not manual

View file

@ -7,7 +7,18 @@ import pprint
import random import random
import time import time
from heapq import * 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 # pylint: disable=unused-import
import anki.backend_pb2 as pb import anki.backend_pb2 as pb
@ -25,6 +36,12 @@ from anki.rsbackend import (
) )
from anki.utils import ids2str, intTime 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 # card types: 0=new, 1=lrn, 2=rev, 3=relrn
# queue types: 0=new, 1=(re)lrn, 2=rev, 3=day (re)lrn, # queue types: 0=new, 1=(re)lrn, 2=rev, 3=day (re)lrn,
# 4=preview, -1=suspended, -2=sibling buried, -3=manually buried # 4=preview, -1=suspended, -2=sibling buried, -3=manually buried
@ -1265,12 +1282,6 @@ where id = ?
self.today = timing.days_elapsed self.today = timing.days_elapsed
self.dayCutoff = timing.next_day_at 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: def _checkDay(self) -> None:
# check if the day has rolled over # check if the day has rolled over
if time.time() > self.dayCutoff: if time.time() > self.dayCutoff:
@ -1364,16 +1375,16 @@ where id = ?
# Suspending & burying # Suspending & burying
########################################################################## ##########################################################################
# learning and relearning cards may be seconds-based or day-based; def unsuspend_cards(self, ids: List[int]) -> None:
# other types map directly to queues self.col.backend.restore_buried_and_suspended_cards(ids)
_restoreQueueSnippet = f"""
queue = (case when type in ({CARD_TYPE_LRN},{CARD_TYPE_RELEARNING}) then def unbury_cards(self, ids: List[int]) -> None:
(case when (case when odue then odue else due end) > 1000000000 then self.col.backend.restore_buried_and_suspended_cards(ids)
{QUEUE_TYPE_LRN} else {QUEUE_TYPE_DAY_LEARN_RELEARN} end)
else def unbury_cards_in_current_deck(
type self, mode: UnburyCurrentDeckModeValue = UnburyCurrentDeckMode.ALL,
end) ) -> None:
""" self.col.backend.unbury_cards_in_current_deck(mode)
def suspendCards(self, ids: List[int]) -> None: def suspendCards(self, ids: List[int]) -> None:
"Suspend cards." "Suspend cards."
@ -1385,18 +1396,6 @@ end)
self.col.usn(), 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: def buryCards(self, cids: List[int], manual: bool = True) -> None:
queue = manual and QUEUE_TYPE_MANUALLY_BURIED or QUEUE_TYPE_SIBLING_BURIED queue = manual and QUEUE_TYPE_MANUALLY_BURIED or QUEUE_TYPE_SIBLING_BURIED
self.col.log(cids) self.col.log(cids)
@ -1416,42 +1415,24 @@ update cards set queue=?,mod=?,usn=? where id in """
) )
self.buryCards(cids) self.buryCards(cids)
# legacy
def unburyCards(self) -> None: def unburyCards(self) -> None:
"Unbury all buried cards in all decks." print(
self.col.log( "please use unbury_cards() or unbury_cards_in_current_deck instead of unburyCards()"
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
) )
self.unbury_cards_in_current_deck()
def unburyCardsForDeck(self, type: str = "all") -> None: def unburyCardsForDeck(self, type: str = "all") -> None:
if type == "all": if type == "all":
queue = ( mode = UnburyCurrentDeckMode.ALL
f"queue in ({QUEUE_TYPE_SIBLING_BURIED}, {QUEUE_TYPE_MANUALLY_BURIED})"
)
elif type == "manual": elif type == "manual":
queue = f"queue = {QUEUE_TYPE_MANUALLY_BURIED}" mode = UnburyCurrentDeckMode.USER_ONLY
elif type == "siblings": else: # elif type == "siblings":
queue = f"queue = {QUEUE_TYPE_SIBLING_BURIED}" mode = UnburyCurrentDeckMode.SCHED_ONLY
else: self.unbury_cards_in_current_deck(mode)
raise Exception("unknown type")
self.col.log( unsuspendCards = unsuspend_cards
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(),
)
# Sibling spacing # Sibling spacing
########################################################################## ##########################################################################

View file

@ -503,7 +503,7 @@ def test_misc():
col.sched.buryNote(c.nid) col.sched.buryNote(c.nid)
col.reset() col.reset()
assert not col.sched.getCard() assert not col.sched.getCard()
col.sched.unburyCards() col.sched.unbury_cards_in_current_deck()
col.reset() col.reset()
assert col.sched.getCard() assert col.sched.getCard()

View file

@ -6,6 +6,7 @@ import time
from anki import hooks from anki import hooks
from anki.consts import * from anki.consts import *
from anki.lang import without_unicode_isolation from anki.lang import without_unicode_isolation
from anki.schedv2 import UnburyCurrentDeckMode
from anki.utils import intTime from anki.utils import intTime
from tests.shared import getEmptyCol as getEmptyColOrig from tests.shared import getEmptyCol as getEmptyColOrig
@ -609,22 +610,18 @@ def test_bury():
col.reset() col.reset()
assert not col.sched.getCard() assert not col.sched.getCard()
col.sched.unburyCardsForDeck( # pylint: disable=unexpected-keyword-arg col.sched.unbury_cards_in_current_deck(UnburyCurrentDeckMode.USER_ONLY)
type="manual"
)
c.load() c.load()
assert c.queue == QUEUE_TYPE_NEW assert c.queue == QUEUE_TYPE_NEW
c2.load() c2.load()
assert c2.queue == QUEUE_TYPE_SIBLING_BURIED assert c2.queue == QUEUE_TYPE_SIBLING_BURIED
col.sched.unburyCardsForDeck( # pylint: disable=unexpected-keyword-arg col.sched.unbury_cards_in_current_deck(UnburyCurrentDeckMode.SCHED_ONLY)
type="siblings"
)
c2.load() c2.load()
assert c2.queue == QUEUE_TYPE_NEW assert c2.queue == QUEUE_TYPE_NEW
col.sched.buryCards([c.id, c2.id]) 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() col.reset()
@ -1214,7 +1211,7 @@ def test_moveVersions():
assert c.queue == QUEUE_TYPE_SIBLING_BURIED assert c.queue == QUEUE_TYPE_SIBLING_BURIED
# and it should be new again when unburied # and it should be new again when unburied
col.sched.unburyCards() col.sched.unbury_cards_in_current_deck()
c.load() c.load()
assert c.type == CARD_TYPE_NEW and c.queue == QUEUE_TYPE_NEW assert c.type == CARD_TYPE_NEW and c.queue == QUEUE_TYPE_NEW

View file

@ -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 { impl From<pb::NoteId> for NoteID {
fn from(nid: pb::NoteId) -> Self { fn from(nid: pb::NoteId) -> Self {
NoteID(nid.nid) NoteID(nid.nid)
@ -442,8 +448,14 @@ impl BackendService for Backend {
// scheduling // 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> { 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> { 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()) 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 // statistics
//----------------------------------------------- //-----------------------------------------------
@ -695,7 +724,7 @@ impl BackendService for Backend {
.storage .storage
.get_card(card.id)? .get_card(card.id)?
.ok_or_else(|| AnkiError::invalid_input("missing card"))?; .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) .map(Into::into)

View file

@ -97,6 +97,11 @@ impl Default for Card {
} }
impl 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) { pub(crate) fn return_home(&mut self, sched: SchedulerVersion) {
if self.odid.0 == 0 { if self.odid.0 == 0 {
// this should not happen // this should not happen
@ -149,34 +154,17 @@ 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);
impl Undoable for UpdateCardUndo { 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 let current = col
.storage .storage
.get_card(self.0.id)? .get_card(self.0.id)?
.ok_or_else(|| AnkiError::invalid_input("card disappeared"))?; .ok_or_else(|| AnkiError::invalid_input("card disappeared"))?;
col.update_card(&mut self.0.clone(), &current) col.update_card(&mut self.0.clone(), &current, usn)
} }
} }
@ -202,19 +190,18 @@ impl Collection {
.ok_or_else(|| AnkiError::invalid_input("no such card"))?; .ok_or_else(|| AnkiError::invalid_input("no such card"))?;
let mut card = orig.clone(); let mut card = orig.clone();
func(&mut card)?; func(&mut card)?;
self.update_card(&mut card, &orig)?; self.update_card(&mut card, &orig, self.usn()?)?;
Ok(card) 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 { if card.id.0 == 0 {
return Err(AnkiError::invalid_input("card id not set")); return Err(AnkiError::invalid_input("card id not set"));
} }
self.state self.state
.undo .undo
.save_undoable(Box::new(UpdateCardUndo(original.clone()))); .save_undoable(Box::new(UpdateCardUndo(original.clone())));
card.mtime = TimestampSecs::now(); card.set_modified(usn);
card.usn = self.usn()?;
self.storage.update_card(card) self.storage.update_card(card)
} }

View 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);
}
}

View file

@ -5,6 +5,7 @@ use crate::{
collection::Collection, config::SchedulerVersion, err::Result, timestamp::TimestampSecs, collection::Collection, config::SchedulerVersion, err::Result, timestamp::TimestampSecs,
}; };
pub mod bury_and_suspend;
pub(crate) mod congrats; pub(crate) mod congrats;
pub mod cutoff; pub mod cutoff;
pub mod timespan; pub mod timespan;
@ -79,61 +80,4 @@ 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

@ -5,12 +5,10 @@ use super::{
parser::Node, parser::Node,
sqlwriter::{RequiredTable, SqlWriter}, sqlwriter::{RequiredTable, SqlWriter},
}; };
use crate::card::CardID; use crate::{
use crate::card::CardType; card::CardID, card::CardType, collection::Collection, config::SortKind, err::Result,
use crate::collection::Collection; search::parser::parse,
use crate::config::SortKind; };
use crate::err::Result;
use crate::search::parser::parse;
use rusqlite::NO_PARAMS; use rusqlite::NO_PARAMS;
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
@ -95,10 +93,9 @@ impl Collection {
let writer = SqlWriter::new(self); let writer = SqlWriter::new(self);
let (sql, args) = writer.build_cards_query(&top_node, RequiredTable::Cards)?; let (sql, args) = writer.build_cards_query(&top_node, RequiredTable::Cards)?;
self.storage.db.execute_batch(concat!( self.storage
"drop table if exists search_cids;", .db
"create temporary table search_cids (id integer primary key not null);" .execute_batch(include_str!("search_cids_setup.sql"))?;
))?;
let sql = format!("insert into search_cids {}", sql); let sql = format!("insert into search_cids {}", sql);
self.storage.db.prepare(&sql)?.execute(&args)?; self.storage.db.prepare(&sql)?.execute(&args)?;
@ -106,6 +103,24 @@ impl Collection {
Ok(()) 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<()> { pub(crate) fn clear_searched_cards(&self) -> Result<()> {
self.storage self.storage
.db .db

View file

@ -0,0 +1,2 @@
drop table if exists search_cids;
create temporary table search_cids (id integer primary key not null);

View file

@ -279,7 +279,7 @@ impl super::SqliteStorage {
Ok(()) Ok(())
} }
pub(crate) fn congrats_info(&self, current: &Deck, today: u32) -> Result<CongratsInfo> { pub(crate) fn congrats_info(&self, current: &Deck, today: u32) -> Result<CongratsInfo> {
self.update_active_decks(current)?; self.update_active_decks(current)?;
self.db self.db

View file

@ -319,7 +319,8 @@ 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()?; self.col
.unbury_if_day_rolled_over(self.col.timing_today()?)?;
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()?;

View file

@ -4,11 +4,13 @@
use crate::{ use crate::{
collection::{Collection, CollectionOp}, collection::{Collection, CollectionOp},
err::Result, err::Result,
types::Usn,
}; };
use std::fmt; use std::fmt;
pub(crate) trait Undoable: fmt::Debug + Send { 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)] #[derive(Debug)]
@ -97,8 +99,9 @@ impl Collection {
let changes = step.changes; let changes = step.changes;
self.state.undo.mode = UndoMode::Undoing; self.state.undo.mode = UndoMode::Undoing;
let res = self.transact(Some(step.kind), |col| { let res = self.transact(Some(step.kind), |col| {
let usn = col.usn()?;
for change in changes.iter().rev() { for change in changes.iter().rev() {
change.apply(col)?; change.apply(col, usn)?;
} }
Ok(()) Ok(())
}); });
@ -113,8 +116,9 @@ impl Collection {
let changes = step.changes; let changes = step.changes;
self.state.undo.mode = UndoMode::Redoing; self.state.undo.mode = UndoMode::Redoing;
let res = self.transact(Some(step.kind), |col| { let res = self.transact(Some(step.kind), |col| {
let usn = col.usn()?;
for change in changes.iter().rev() { for change in changes.iter().rev() {
change.apply(col)?; change.apply(col, usn)?;
} }
Ok(()) Ok(())
}); });