mirror of
https://github.com/ankitects/anki.git
synced 2025-09-21 15:32:23 -04:00
move filtered deck empty/fill to backend
emptying of individual card ids still to be done
This commit is contained in:
parent
8d2867aa2d
commit
8f9037cf0f
10 changed files with 269 additions and 231 deletions
|
@ -103,6 +103,8 @@ service BackendService {
|
|||
rpc RestoreBuriedAndSuspendedCards (CardIDs) returns (Empty);
|
||||
rpc UnburyCardsInCurrentDeck (UnburyCardsInCurrentDeckIn) returns (Empty);
|
||||
rpc BuryOrSuspendCards (BuryOrSuspendCardsIn) returns (Empty);
|
||||
rpc EmptyFilteredDeck (DeckID) returns (Empty);
|
||||
rpc RebuildFilteredDeck (DeckID) returns (UInt32);
|
||||
|
||||
// stats
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ from __future__ import annotations
|
|||
import random
|
||||
import time
|
||||
from heapq import *
|
||||
from typing import Any, List, Optional, Sequence, Tuple, Union
|
||||
from typing import Any, List, Optional, Tuple, Union
|
||||
|
||||
import anki
|
||||
from anki import hooks
|
||||
|
@ -599,77 +599,9 @@ did = ? and queue = {QUEUE_TYPE_REV} and due <= ? limit ?""",
|
|||
idealIvl = self._fuzzedIvl(idealIvl)
|
||||
return idealIvl
|
||||
|
||||
# Dynamic deck handling
|
||||
# Filtered deck handling
|
||||
##########################################################################
|
||||
|
||||
def rebuildDyn(self, did: Optional[int] = None) -> Optional[Sequence[int]]: # type: ignore[override]
|
||||
"Rebuild a dynamic deck."
|
||||
did = did or self.col.decks.selected()
|
||||
deck = self.col.decks.get(did)
|
||||
assert deck["dyn"]
|
||||
# move any existing cards back first, then fill
|
||||
self.emptyDyn(did)
|
||||
ids = self._fillDyn(deck)
|
||||
if not ids:
|
||||
return None
|
||||
# and change to our new deck
|
||||
self.col.decks.select(did)
|
||||
return ids
|
||||
|
||||
def _fillDyn(self, deck: Deck) -> Sequence[int]: # type: ignore[override]
|
||||
search, limit, order = deck["terms"][0]
|
||||
orderlimit = self._dynOrder(order, limit)
|
||||
if search.strip():
|
||||
search = "(%s)" % search
|
||||
search = "%s -is:suspended -is:buried -deck:filtered -is:learn" % search
|
||||
try:
|
||||
ids = self.col.findCards(search, order=orderlimit)
|
||||
except:
|
||||
ids = []
|
||||
return ids
|
||||
# move the cards over
|
||||
self.col.log(deck["id"], ids)
|
||||
self._moveToDyn(deck["id"], ids)
|
||||
return ids
|
||||
|
||||
def emptyDyn(self, did: Optional[int], lim: Optional[str] = None) -> None:
|
||||
if not lim:
|
||||
lim = "did = %s" % did
|
||||
self.col.log(self.col.db.list("select id from cards where %s" % lim))
|
||||
# move out of cram queue
|
||||
self.col.db.execute(
|
||||
f"""
|
||||
update cards set did = odid, queue = (case when type = {CARD_TYPE_LRN} then {QUEUE_TYPE_NEW}
|
||||
else type end), type = (case when type = {CARD_TYPE_LRN} then {CARD_TYPE_NEW} else type end),
|
||||
due = odue, odue = 0, odid = 0, usn = ? where %s"""
|
||||
% lim,
|
||||
self.col.usn(),
|
||||
)
|
||||
|
||||
def _moveToDyn(self, did: int, ids: Sequence[int]) -> None: # type: ignore[override]
|
||||
deck = self.col.decks.get(did)
|
||||
data = []
|
||||
t = intTime()
|
||||
u = self.col.usn()
|
||||
for c, id in enumerate(ids):
|
||||
# start at -100000 so that reviews are all due
|
||||
data.append((did, -100000 + c, u, id))
|
||||
# due reviews stay in the review queue. careful: can't use
|
||||
# "odid or did", as sqlite converts to boolean
|
||||
queue = f"""
|
||||
(case when type={CARD_TYPE_REV} and (case when odue then odue <= %d else due <= %d end)
|
||||
then {QUEUE_TYPE_REV} else {QUEUE_TYPE_NEW} end)"""
|
||||
queue %= (self.today, self.today)
|
||||
self.col.db.executemany(
|
||||
"""
|
||||
update cards set
|
||||
odid = (case when odid then odid else did end),
|
||||
odue = (case when odue then odue else due end),
|
||||
did = ?, queue = %s, due = ?, usn = ? where id = ?"""
|
||||
% queue,
|
||||
data,
|
||||
)
|
||||
|
||||
def _dynIvlBoost(self, card: Card) -> int:
|
||||
assert card.odid and card.type == CARD_TYPE_REV
|
||||
assert card.factor
|
||||
|
|
|
@ -25,7 +25,7 @@ import anki.backend_pb2 as pb
|
|||
from anki import hooks
|
||||
from anki.cards import Card
|
||||
from anki.consts import *
|
||||
from anki.decks import Deck, DeckConfig, DeckManager, FilteredDeck, QueueConfig
|
||||
from anki.decks import Deck, DeckConfig, DeckManager, QueueConfig
|
||||
from anki.lang import _
|
||||
from anki.notes import Note
|
||||
from anki.rsbackend import (
|
||||
|
@ -1062,7 +1062,7 @@ select id from cards where did in %s and queue = {QUEUE_TYPE_REV} and due <= ? l
|
|||
|
||||
return ivl
|
||||
|
||||
# Dynamic deck handling
|
||||
# Filtered deck handling
|
||||
##########################################################################
|
||||
|
||||
_restoreQueueWhenEmptyingSnippet = f"""
|
||||
|
@ -1076,41 +1076,19 @@ end)
|
|||
"""
|
||||
|
||||
def rebuildDyn(self, did: Optional[int] = None) -> Optional[int]:
|
||||
"Rebuild a dynamic deck."
|
||||
"Rebuild a filtered deck."
|
||||
did = did or self.col.decks.selected()
|
||||
deck = self.col.decks.get(did)
|
||||
assert deck["dyn"]
|
||||
# move any existing cards back first, then fill
|
||||
self.emptyDyn(did)
|
||||
cnt = self._fillDyn(deck)
|
||||
if not cnt:
|
||||
count = self.col.backend.rebuild_filtered_deck(did) or None
|
||||
if not count:
|
||||
return None
|
||||
# and change to our new deck
|
||||
self.col.decks.select(did)
|
||||
return cnt
|
||||
|
||||
def _fillDyn(self, deck: FilteredDeck) -> int:
|
||||
start = -100000
|
||||
total = 0
|
||||
for search, limit, order in deck["terms"]:
|
||||
orderlimit = self._dynOrder(order, limit)
|
||||
if search.strip():
|
||||
search = "(%s)" % search
|
||||
search = "%s -is:suspended -is:buried -deck:filtered" % search
|
||||
try:
|
||||
ids = self.col.findCards(search, order=orderlimit)
|
||||
except:
|
||||
return total
|
||||
# move the cards over
|
||||
self.col.log(deck["id"], ids)
|
||||
self._moveToDyn(deck["id"], ids, start=start + total)
|
||||
total += len(ids)
|
||||
return total
|
||||
return count
|
||||
|
||||
def emptyDyn(self, did: Optional[int], lim: Optional[str] = None) -> None:
|
||||
if not lim:
|
||||
lim = "did = %s" % did
|
||||
self.col.log(self.col.db.list("select id from cards where %s" % lim))
|
||||
if lim is None:
|
||||
self.col.backend.empty_filtered_deck(did)
|
||||
return
|
||||
|
||||
self.col.db.execute(
|
||||
"""
|
||||
|
@ -1123,57 +1101,6 @@ due = (case when odue>0 then odue else due end), odue = 0, odid = 0, usn = ? whe
|
|||
def remFromDyn(self, cids: List[int]) -> None:
|
||||
self.emptyDyn(None, "id in %s and odid" % ids2str(cids))
|
||||
|
||||
def _dynOrder(self, o: int, l: int) -> str:
|
||||
if o == DYN_OLDEST:
|
||||
t = "(select max(id) from revlog where cid=c.id)"
|
||||
elif o == DYN_RANDOM:
|
||||
t = "random()"
|
||||
elif o == DYN_SMALLINT:
|
||||
t = "ivl"
|
||||
elif o == DYN_BIGINT:
|
||||
t = "ivl desc"
|
||||
elif o == DYN_LAPSES:
|
||||
t = "lapses desc"
|
||||
elif o == DYN_ADDED:
|
||||
t = "n.id"
|
||||
elif o == DYN_REVADDED:
|
||||
t = "n.id desc"
|
||||
elif o == DYN_DUEPRIORITY:
|
||||
t = (
|
||||
f"(case when queue={QUEUE_TYPE_REV} and due <= %d then (ivl / cast(%d-due+0.001 as real)) else 100000+due end)"
|
||||
% (self.today, self.today)
|
||||
)
|
||||
else: # DYN_DUE or unknown
|
||||
t = "c.due, c.ord"
|
||||
return t + " limit %d" % l
|
||||
|
||||
def _moveToDyn(self, did: int, ids: Sequence[int], start: int = -100000) -> None:
|
||||
deck = self.col.decks.get(did)
|
||||
data = []
|
||||
u = self.col.usn()
|
||||
due = start
|
||||
for id in ids:
|
||||
data.append((did, due, u, id))
|
||||
due += 1
|
||||
|
||||
queue = ""
|
||||
if not deck["resched"]:
|
||||
queue = f",queue={QUEUE_TYPE_REV}"
|
||||
|
||||
query = (
|
||||
"""
|
||||
update cards set
|
||||
odid = did, odue = due,
|
||||
did = ?,
|
||||
due = (case when due <= 0 then due else ? end),
|
||||
usn = ?
|
||||
%s
|
||||
where id = ?
|
||||
"""
|
||||
% queue
|
||||
)
|
||||
self.col.db.executemany(query, data)
|
||||
|
||||
def _removeFromFiltered(self, card: Card) -> None:
|
||||
if card.odid:
|
||||
card.did = card.odid
|
||||
|
|
|
@ -539,6 +539,14 @@ impl BackendService for Backend {
|
|||
})
|
||||
}
|
||||
|
||||
fn empty_filtered_deck(&mut self, input: pb::DeckId) -> BackendResult<Empty> {
|
||||
self.with_col(|col| col.empty_filtered_deck(input.did.into()).map(Into::into))
|
||||
}
|
||||
|
||||
fn rebuild_filtered_deck(&mut self, input: pb::DeckId) -> BackendResult<pb::UInt32> {
|
||||
self.with_col(|col| col.rebuild_filtered_deck(input.did.into()).map(Into::into))
|
||||
}
|
||||
|
||||
// statistics
|
||||
//-----------------------------------------------
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright: Ankitects Pty Ltd and contributors
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
use crate::decks::DeckID;
|
||||
use crate::decks::{DeckFilterContext, DeckID};
|
||||
use crate::define_newtype;
|
||||
use crate::err::{AnkiError, Result};
|
||||
use crate::notes::NoteID;
|
||||
|
@ -102,7 +102,42 @@ impl Card {
|
|||
self.usn = usn;
|
||||
}
|
||||
|
||||
pub(crate) fn return_home(&mut self, sched: SchedulerVersion) {
|
||||
pub(crate) fn move_into_filtered_deck(&mut self, ctx: &DeckFilterContext, position: i32) {
|
||||
// filtered and v1 learning cards are excluded, so odue should be guaranteed to be zero
|
||||
if self.original_due != 0 {
|
||||
println!("bug: odue was set");
|
||||
return;
|
||||
}
|
||||
|
||||
self.original_deck_id = self.deck_id;
|
||||
self.deck_id = ctx.target_deck;
|
||||
|
||||
self.original_due = self.due;
|
||||
|
||||
if ctx.scheduler == SchedulerVersion::V1 {
|
||||
if self.ctype == CardType::Review && self.due <= ctx.today as i32 {
|
||||
// review cards that are due are left in the review queue
|
||||
} else {
|
||||
// new + non-due go into new queue
|
||||
self.queue = CardQueue::New;
|
||||
}
|
||||
if self.due != 0 {
|
||||
self.due = position;
|
||||
}
|
||||
} else {
|
||||
// if rescheduling is disabled, all cards go in the review queue
|
||||
if !ctx.config.reschedule {
|
||||
self.queue = CardQueue::Review;
|
||||
}
|
||||
// fixme: can we unify this with v1 scheduler in the future?
|
||||
// https://anki.tenderapp.com/discussions/ankidesktop/35978-rebuilding-filtered-deck-on-experimental-v2-empties-deck-and-reschedules-to-the-year-1745
|
||||
if self.due > 0 {
|
||||
self.due = position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn remove_from_filtered_deck(&mut self, sched: SchedulerVersion) {
|
||||
if self.original_deck_id.0 == 0 {
|
||||
// not in a filtered deck
|
||||
return;
|
||||
|
@ -110,14 +145,11 @@ impl Card {
|
|||
|
||||
self.deck_id = self.original_deck_id;
|
||||
self.original_deck_id.0 = 0;
|
||||
if self.original_due > 0 {
|
||||
self.due = self.original_due;
|
||||
}
|
||||
self.original_due = 0;
|
||||
|
||||
self.queue = match sched {
|
||||
match sched {
|
||||
SchedulerVersion::V1 => {
|
||||
match self.ctype {
|
||||
self.due = self.original_due;
|
||||
self.queue = match self.ctype {
|
||||
CardType::New => CardQueue::New,
|
||||
CardType::Learn => CardQueue::New,
|
||||
CardType::Review => CardQueue::Review,
|
||||
|
@ -126,11 +158,19 @@ impl Card {
|
|||
println!("did not expect relearn type in v1 for card {}", self.id);
|
||||
CardQueue::New
|
||||
}
|
||||
};
|
||||
if self.ctype == CardType::Learn {
|
||||
self.ctype = CardType::New;
|
||||
}
|
||||
}
|
||||
SchedulerVersion::V2 => {
|
||||
// original_due is cleared if card answered in filtered deck
|
||||
if self.original_due > 0 {
|
||||
self.due = self.original_due;
|
||||
}
|
||||
|
||||
if (self.queue as i8) >= 0 {
|
||||
match self.ctype {
|
||||
self.queue = match self.ctype {
|
||||
CardType::Learn | CardType::Relearn => {
|
||||
if self.due > 1_000_000_000 {
|
||||
// unix timestamp
|
||||
|
@ -143,15 +183,11 @@ impl Card {
|
|||
CardType::New => CardQueue::New,
|
||||
CardType::Review => CardQueue::Review,
|
||||
}
|
||||
} else {
|
||||
self.queue
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if sched == SchedulerVersion::V1 && self.ctype == CardType::Learn {
|
||||
self.ctype = CardType::New;
|
||||
}
|
||||
|
||||
self.original_due = 0;
|
||||
}
|
||||
|
||||
/// Remove the card from the (re)learning queue.
|
||||
|
|
167
rslib/src/decks/filtered.rs
Normal file
167
rslib/src/decks/filtered.rs
Normal file
|
@ -0,0 +1,167 @@
|
|||
// Copyright: Ankitects Pty Ltd and contributors
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
use super::{Deck, DeckID};
|
||||
pub use crate::backend_proto::{
|
||||
deck_kind::Kind as DeckKind, filtered_search_term::FilteredSearchOrder, Deck as DeckProto,
|
||||
DeckCommon, DeckKind as DeckKindProto, FilteredDeck, FilteredSearchTerm, NormalDeck,
|
||||
};
|
||||
use crate::{
|
||||
card::{CardID, CardQueue},
|
||||
collection::Collection,
|
||||
config::SchedulerVersion,
|
||||
err::Result,
|
||||
prelude::AnkiError,
|
||||
search::SortMode,
|
||||
timestamp::TimestampSecs,
|
||||
types::Usn,
|
||||
};
|
||||
|
||||
impl Deck {
|
||||
pub fn new_filtered() -> Deck {
|
||||
let mut filt = FilteredDeck::default();
|
||||
filt.search_terms.push(FilteredSearchTerm {
|
||||
search: "".into(),
|
||||
limit: 100,
|
||||
order: 0,
|
||||
});
|
||||
filt.preview_delay = 10;
|
||||
filt.reschedule = true;
|
||||
Deck {
|
||||
id: DeckID(0),
|
||||
name: "".into(),
|
||||
mtime_secs: TimestampSecs(0),
|
||||
usn: Usn(0),
|
||||
common: DeckCommon::default(),
|
||||
kind: DeckKind::Filtered(filt),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_filtered(&self) -> bool {
|
||||
matches!(self.kind, DeckKind::Filtered(_))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct DeckFilterContext<'a> {
|
||||
pub target_deck: DeckID,
|
||||
pub config: &'a FilteredDeck,
|
||||
pub scheduler: SchedulerVersion,
|
||||
pub usn: Usn,
|
||||
pub today: u32,
|
||||
}
|
||||
|
||||
impl Collection {
|
||||
pub fn empty_filtered_deck(&mut self, did: DeckID) -> Result<()> {
|
||||
self.transact(None, |col| col.return_all_cards_in_filtered_deck(did))
|
||||
}
|
||||
pub(super) fn return_all_cards_in_filtered_deck(&mut self, did: DeckID) -> Result<()> {
|
||||
let cids = self.storage.all_cards_in_single_deck(did)?;
|
||||
self.return_cards_to_home_deck(&cids)
|
||||
}
|
||||
|
||||
// Unlike the old Python code, this also marks the cards as modified.
|
||||
fn return_cards_to_home_deck(&mut self, cids: &[CardID]) -> Result<()> {
|
||||
let sched = self.sched_ver();
|
||||
let usn = self.usn()?;
|
||||
for cid in cids {
|
||||
if let Some(mut card) = self.storage.get_card(*cid)? {
|
||||
let original = card.clone();
|
||||
card.remove_from_filtered_deck(sched);
|
||||
self.update_card(&mut card, &original, usn)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Unlike the old Python code, this also marks the cards as modified.
|
||||
pub fn rebuild_filtered_deck(&mut self, did: DeckID) -> Result<u32> {
|
||||
let deck = self.get_deck(did)?.ok_or(AnkiError::NotFound)?;
|
||||
let config = if let DeckKind::Filtered(kind) = &deck.kind {
|
||||
kind
|
||||
} else {
|
||||
return Err(AnkiError::invalid_input("not filtered"));
|
||||
};
|
||||
let ctx = DeckFilterContext {
|
||||
target_deck: did,
|
||||
config,
|
||||
scheduler: self.sched_ver(),
|
||||
usn: self.usn()?,
|
||||
today: self.timing_today()?.days_elapsed,
|
||||
};
|
||||
|
||||
self.transact(None, |col| {
|
||||
col.return_all_cards_in_filtered_deck(did)?;
|
||||
col.build_filtered_deck(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
fn build_filtered_deck(&mut self, ctx: DeckFilterContext) -> Result<u32> {
|
||||
let start = -100_000;
|
||||
let mut position = start;
|
||||
for term in &ctx.config.search_terms {
|
||||
position = self.move_cards_matching_term(&ctx, term, position)?;
|
||||
}
|
||||
|
||||
Ok((position - start) as u32)
|
||||
}
|
||||
|
||||
/// Move matching cards into filtered deck.
|
||||
/// Returns the new starting position.
|
||||
fn move_cards_matching_term(
|
||||
&mut self,
|
||||
ctx: &DeckFilterContext,
|
||||
term: &FilteredSearchTerm,
|
||||
mut position: i32,
|
||||
) -> Result<i32> {
|
||||
let search = format!(
|
||||
"{} -is:suspended -is:buried -deck:filtered {}",
|
||||
if term.search.trim().is_empty() {
|
||||
"".to_string()
|
||||
} else {
|
||||
format!("({})", term.search)
|
||||
},
|
||||
if ctx.scheduler == SchedulerVersion::V1 {
|
||||
"-is:learn"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
);
|
||||
let order = order_and_limit_for_search(term, ctx.today);
|
||||
|
||||
self.search_cards_into_table(&search, SortMode::Custom(order))?;
|
||||
for mut card in self.storage.all_searched_cards()? {
|
||||
let original = card.clone();
|
||||
card.move_into_filtered_deck(ctx, position);
|
||||
self.update_card(&mut card, &original, ctx.usn)?;
|
||||
position += 1;
|
||||
}
|
||||
|
||||
Ok(position)
|
||||
}
|
||||
}
|
||||
|
||||
fn order_and_limit_for_search(term: &FilteredSearchTerm, today: u32) -> String {
|
||||
let temp_string;
|
||||
let order = match term.order() {
|
||||
FilteredSearchOrder::OldestFirst => "(select max(id) from revlog where cid=c.id)",
|
||||
FilteredSearchOrder::Random => "random()",
|
||||
FilteredSearchOrder::IntervalsAscending => "ivl",
|
||||
FilteredSearchOrder::IntervalsDescending => "ivl desc",
|
||||
FilteredSearchOrder::Lapses => "lapses desc",
|
||||
FilteredSearchOrder::Added => "n.id",
|
||||
FilteredSearchOrder::ReverseAdded => "n.id desc",
|
||||
FilteredSearchOrder::Due => "c.due, c.ord",
|
||||
FilteredSearchOrder::DuePriority => {
|
||||
temp_string = format!(
|
||||
"
|
||||
(case when queue={rev_queue} and due <= {today}
|
||||
then (ivl / cast({today}-due+0.001 as real)) else 100000+due end)",
|
||||
rev_queue = CardQueue::Review as i8,
|
||||
today = today
|
||||
);
|
||||
&temp_string
|
||||
}
|
||||
};
|
||||
|
||||
format!("{} limit {}", order, term.limit)
|
||||
}
|
|
@ -7,7 +7,6 @@ pub use crate::backend_proto::{
|
|||
DeckCommon, DeckKind as DeckKindProto, FilteredDeck, FilteredSearchTerm, NormalDeck,
|
||||
};
|
||||
use crate::{
|
||||
card::CardID,
|
||||
collection::Collection,
|
||||
deckconf::DeckConfID,
|
||||
define_newtype,
|
||||
|
@ -18,9 +17,11 @@ use crate::{
|
|||
types::Usn,
|
||||
};
|
||||
mod counts;
|
||||
mod filtered;
|
||||
mod schema11;
|
||||
mod tree;
|
||||
pub(crate) use counts::DueCounts;
|
||||
pub(crate) use filtered::DeckFilterContext;
|
||||
pub use schema11::DeckSchema11;
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
|
||||
|
@ -51,25 +52,6 @@ impl Deck {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn new_filtered() -> Deck {
|
||||
let mut filt = FilteredDeck::default();
|
||||
filt.search_terms.push(FilteredSearchTerm {
|
||||
search: "".into(),
|
||||
limit: 100,
|
||||
order: 0,
|
||||
});
|
||||
filt.preview_delay = 10;
|
||||
filt.reschedule = true;
|
||||
Deck {
|
||||
id: DeckID(0),
|
||||
name: "".into(),
|
||||
mtime_secs: TimestampSecs(0),
|
||||
usn: Usn(0),
|
||||
common: DeckCommon::default(),
|
||||
kind: DeckKind::Filtered(filt),
|
||||
}
|
||||
}
|
||||
|
||||
fn reset_stats_if_day_changed(&mut self, today: u32) {
|
||||
let c = &mut self.common;
|
||||
if c.last_day_studied != today {
|
||||
|
@ -80,12 +62,6 @@ impl Deck {
|
|||
c.last_day_studied = today;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deck {
|
||||
pub(crate) fn is_filtered(&self) -> bool {
|
||||
matches!(self.kind, DeckKind::Filtered(_))
|
||||
}
|
||||
|
||||
/// Returns deck config ID if deck is a normal deck.
|
||||
pub(crate) fn config_id(&self) -> Option<DeckConfID> {
|
||||
|
@ -434,23 +410,6 @@ impl Collection {
|
|||
self.remove_cards_and_orphaned_notes(&cids)
|
||||
}
|
||||
|
||||
fn return_all_cards_in_filtered_deck(&mut self, did: DeckID) -> Result<()> {
|
||||
let cids = self.storage.all_cards_in_single_deck(did)?;
|
||||
self.return_cards_to_home_deck(&cids)
|
||||
}
|
||||
|
||||
fn return_cards_to_home_deck(&mut self, cids: &[CardID]) -> Result<()> {
|
||||
let sched = self.sched_ver();
|
||||
for cid in cids {
|
||||
if let Some(mut card) = self.storage.get_card(*cid)? {
|
||||
// fixme: undo
|
||||
card.return_home(sched);
|
||||
self.storage.update_card(&card)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_all_deck_names(&self, skip_empty_default: bool) -> Result<Vec<(DeckID, String)>> {
|
||||
if skip_empty_default && self.default_deck_is_empty()? {
|
||||
Ok(self
|
||||
|
|
|
@ -7,6 +7,7 @@ use crate::{
|
|||
collection::Collection,
|
||||
config::SchedulerVersion,
|
||||
err::Result,
|
||||
search::SortMode,
|
||||
};
|
||||
|
||||
use super::cutoff::SchedTimingToday;
|
||||
|
@ -59,7 +60,7 @@ impl Collection {
|
|||
/// 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.search_cards_into_table("is:buried", SortMode::NoOrder)?;
|
||||
self.storage.for_each_card_in_search(|mut card| {
|
||||
card.restore_queue_after_bury_or_suspend();
|
||||
self.storage.update_card(&card)
|
||||
|
@ -94,7 +95,7 @@ impl Collection {
|
|||
UnburyDeckMode::SchedOnly => "is:buried-sibling",
|
||||
};
|
||||
self.transact(None, |col| {
|
||||
col.search_cards_into_table(&format!("deck:current {}", search))?;
|
||||
col.search_cards_into_table(&format!("deck:current {}", search), SortMode::NoOrder)?;
|
||||
col.unsuspend_or_unbury_searched_cards()
|
||||
})
|
||||
}
|
||||
|
@ -125,7 +126,7 @@ impl Collection {
|
|||
};
|
||||
if card.queue != desired_queue {
|
||||
if sched == SchedulerVersion::V1 {
|
||||
card.return_home(sched);
|
||||
card.remove_from_filtered_deck(sched);
|
||||
card.remove_from_learning();
|
||||
}
|
||||
card.queue = desired_queue;
|
||||
|
|
|
@ -63,20 +63,7 @@ impl Collection {
|
|||
let writer = SqlWriter::new(self);
|
||||
|
||||
let (mut sql, args) = writer.build_cards_query(&top_node, mode.required_table())?;
|
||||
|
||||
match mode {
|
||||
SortMode::NoOrder => (),
|
||||
SortMode::FromConfig => unreachable!(),
|
||||
SortMode::Builtin { kind, reverse } => {
|
||||
prepare_sort(self, kind)?;
|
||||
sql.push_str(" order by ");
|
||||
write_order(&mut sql, kind, reverse)?;
|
||||
}
|
||||
SortMode::Custom(order_clause) => {
|
||||
sql.push_str(" order by ");
|
||||
sql.push_str(&order_clause);
|
||||
}
|
||||
}
|
||||
self.add_order(&mut sql, mode)?;
|
||||
|
||||
let mut stmt = self.storage.db.prepare(&sql)?;
|
||||
let ids: Vec<_> = stmt
|
||||
|
@ -86,13 +73,32 @@ impl Collection {
|
|||
Ok(ids)
|
||||
}
|
||||
|
||||
fn add_order(&mut self, sql: &mut String, mode: SortMode) -> Result<()> {
|
||||
match mode {
|
||||
SortMode::NoOrder => (),
|
||||
SortMode::FromConfig => unreachable!(),
|
||||
SortMode::Builtin { kind, reverse } => {
|
||||
prepare_sort(self, kind)?;
|
||||
sql.push_str(" order by ");
|
||||
write_order(sql, kind, reverse)?;
|
||||
}
|
||||
SortMode::Custom(order_clause) => {
|
||||
sql.push_str(" order by ");
|
||||
sql.push_str(&order_clause);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Place the matched card ids into a temporary 'search_cids' table
|
||||
/// instead of returning them. Use clear_searched_cards() to remove it.
|
||||
pub(crate) fn search_cards_into_table(&mut self, search: &str) -> Result<()> {
|
||||
pub(crate) fn search_cards_into_table(&mut self, search: &str, mode: SortMode) -> Result<()> {
|
||||
let top_node = Node::Group(parse(search)?);
|
||||
let writer = SqlWriter::new(self);
|
||||
|
||||
let (sql, args) = writer.build_cards_query(&top_node, RequiredTable::Cards)?;
|
||||
let (mut sql, args) = writer.build_cards_query(&top_node, mode.required_table())?;
|
||||
self.add_order(&mut sql, mode)?;
|
||||
|
||||
self.storage
|
||||
.db
|
||||
.execute_batch(include_str!("search_cids_setup.sql"))?;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// 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, prelude::*, revlog::RevlogEntry};
|
||||
use crate::{backend_proto as pb, prelude::*, revlog::RevlogEntry, search::SortMode};
|
||||
|
||||
impl Collection {
|
||||
pub(crate) fn graph_data_for_search(
|
||||
|
@ -9,7 +9,7 @@ impl Collection {
|
|||
search: &str,
|
||||
days: u32,
|
||||
) -> Result<pb::GraphsOut> {
|
||||
self.search_cards_into_table(search)?;
|
||||
self.search_cards_into_table(search, SortMode::NoOrder)?;
|
||||
let all = search.trim().is_empty();
|
||||
self.graph_data(all, days)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue