mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
use the backend for the deck due tree
- approx 3x faster on a large test deck - counts are no longer capped to 1000 in the tree
This commit is contained in:
parent
5fb5338d97
commit
238441f2d9
13 changed files with 274 additions and 25 deletions
|
@ -89,6 +89,7 @@ message BackendInput {
|
||||||
AddOrUpdateDeckLegacyIn add_or_update_deck_legacy = 74;
|
AddOrUpdateDeckLegacyIn add_or_update_deck_legacy = 74;
|
||||||
bool new_deck_legacy = 75;
|
bool new_deck_legacy = 75;
|
||||||
int64 remove_deck = 76;
|
int64 remove_deck = 76;
|
||||||
|
Empty deck_tree_legacy = 77;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,6 +158,7 @@ message BackendOutput {
|
||||||
int64 add_or_update_deck_legacy = 74;
|
int64 add_or_update_deck_legacy = 74;
|
||||||
bytes new_deck_legacy = 75;
|
bytes new_deck_legacy = 75;
|
||||||
Empty remove_deck = 76;
|
Empty remove_deck = 76;
|
||||||
|
bytes deck_tree_legacy = 77;
|
||||||
|
|
||||||
BackendError error = 2047;
|
BackendError error = 2047;
|
||||||
}
|
}
|
||||||
|
|
|
@ -738,6 +738,9 @@ class RustBackend:
|
||||||
def check_database(self) -> None:
|
def check_database(self) -> None:
|
||||||
self._run_command(pb.BackendInput(check_database=pb.Empty()))
|
self._run_command(pb.BackendInput(check_database=pb.Empty()))
|
||||||
|
|
||||||
|
def legacy_deck_tree(self) -> Sequence:
|
||||||
|
bytes = self._run_command(pb.BackendInput(deck_tree_legacy=pb.Empty())).deck_tree_legacy
|
||||||
|
return orjson.loads(bytes)[5]
|
||||||
|
|
||||||
def translate_string_in(
|
def translate_string_in(
|
||||||
key: TR, **kwargs: Union[str, int, float]
|
key: TR, **kwargs: Union[str, int, float]
|
||||||
|
|
|
@ -264,11 +264,7 @@ order by due"""
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def deckDueTree(self) -> Any:
|
def deckDueTree(self) -> Any:
|
||||||
self.col.decks._enable_dconf_cache()
|
return self.col.backend.legacy_deck_tree()
|
||||||
try:
|
|
||||||
return self._groupChildren(self.deckDueList())
|
|
||||||
finally:
|
|
||||||
self.col.decks._disable_dconf_cache()
|
|
||||||
|
|
||||||
def _groupChildren(self, grps: List[List[Any]]) -> Any:
|
def _groupChildren(self, grps: List[List[Any]]) -> Any:
|
||||||
# first, split the group names into components
|
# first, split the group names into components
|
||||||
|
|
|
@ -215,8 +215,6 @@ where id > ?""",
|
||||||
def nonzeroColour(cnt, klass):
|
def nonzeroColour(cnt, klass):
|
||||||
if not cnt:
|
if not cnt:
|
||||||
klass = "zero-count"
|
klass = "zero-count"
|
||||||
if cnt >= 1000:
|
|
||||||
cnt = "1000+"
|
|
||||||
return f'<span class="{klass}">{cnt}</span>'
|
return f'<span class="{klass}">{cnt}</span>'
|
||||||
|
|
||||||
buf += "<td align=right>%s</td><td align=right>%s</td>" % (
|
buf += "<td align=right>%s</td><td align=right>%s</td>" % (
|
||||||
|
|
|
@ -360,6 +360,7 @@ impl Backend {
|
||||||
self.check_database()?;
|
self.check_database()?;
|
||||||
OValue::CheckDatabase(pb::Empty {})
|
OValue::CheckDatabase(pb::Empty {})
|
||||||
}
|
}
|
||||||
|
Value::DeckTreeLegacy(_) => OValue::DeckTreeLegacy(self.deck_tree_legacy()?),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -441,13 +442,7 @@ impl Backend {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deck_tree(&self, input: pb::DeckTreeIn) -> Result<pb::DeckTreeNode> {
|
fn deck_tree(&self, input: pb::DeckTreeIn) -> Result<pb::DeckTreeNode> {
|
||||||
self.with_col(|col| {
|
self.with_col(|col| col.deck_tree(input.include_counts))
|
||||||
if input.include_counts {
|
|
||||||
todo!()
|
|
||||||
} else {
|
|
||||||
col.deck_tree()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_template(&self, input: pb::RenderCardIn) -> Result<pb::RenderCardOut> {
|
fn render_template(&self, input: pb::RenderCardIn) -> Result<pb::RenderCardOut> {
|
||||||
|
@ -1054,6 +1049,13 @@ impl Backend {
|
||||||
fn check_database(&self) -> Result<()> {
|
fn check_database(&self) -> Result<()> {
|
||||||
self.with_col(|col| col.transact(None, |col| col.check_database()))
|
self.with_col(|col| col.transact(None, |col| col.check_database()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn deck_tree_legacy(&self) -> Result<Vec<u8>> {
|
||||||
|
self.with_col(|col| {
|
||||||
|
let tree = col.legacy_deck_tree()?;
|
||||||
|
serde_json::to_vec(&tree).map_err(Into::into)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn translate_arg_to_fluent_val(arg: &pb::TranslateArgValue) -> FluentValue {
|
fn translate_arg_to_fluent_val(arg: &pb::TranslateArgValue) -> FluentValue {
|
||||||
|
|
|
@ -169,6 +169,10 @@ impl Collection {
|
||||||
Ok(timing)
|
Ok(timing)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn learn_cutoff(&self) -> u32 {
|
||||||
|
TimestampSecs::now().0 as u32 + self.learn_ahead_secs()
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn usn(&self) -> Result<Usn> {
|
pub(crate) fn usn(&self) -> Result<Usn> {
|
||||||
// if we cache this in the future, must make sure to invalidate cache when usn bumped in sync.finish()
|
// if we cache this in the future, must make sure to invalidate cache when usn bumped in sync.finish()
|
||||||
self.storage.usn(self.server)
|
self.storage.usn(self.server)
|
||||||
|
|
|
@ -42,6 +42,7 @@ pub(crate) enum ConfigKey {
|
||||||
CurrentNoteTypeID,
|
CurrentNoteTypeID,
|
||||||
NextNewCardPosition,
|
NextNewCardPosition,
|
||||||
SchedulerVersion,
|
SchedulerVersion,
|
||||||
|
LearnAheadSecs,
|
||||||
}
|
}
|
||||||
#[derive(PartialEq, Serialize_repr, Deserialize_repr, Clone, Copy)]
|
#[derive(PartialEq, Serialize_repr, Deserialize_repr, Clone, Copy)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
|
@ -62,6 +63,7 @@ impl From<ConfigKey> for &'static str {
|
||||||
ConfigKey::CurrentNoteTypeID => "curModel",
|
ConfigKey::CurrentNoteTypeID => "curModel",
|
||||||
ConfigKey::NextNewCardPosition => "nextPos",
|
ConfigKey::NextNewCardPosition => "nextPos",
|
||||||
ConfigKey::SchedulerVersion => "schedVer",
|
ConfigKey::SchedulerVersion => "schedVer",
|
||||||
|
ConfigKey::LearnAheadSecs => "collapseTime",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -156,6 +158,11 @@ impl Collection {
|
||||||
self.get_config_optional(ConfigKey::SchedulerVersion)
|
self.get_config_optional(ConfigKey::SchedulerVersion)
|
||||||
.unwrap_or(SchedulerVersion::V1)
|
.unwrap_or(SchedulerVersion::V1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn learn_ahead_secs(&self) -> u32 {
|
||||||
|
self.get_config_optional(ConfigKey::LearnAheadSecs)
|
||||||
|
.unwrap_or(1200)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, PartialEq, Debug, Clone, Copy)]
|
#[derive(Deserialize, PartialEq, Debug, Clone, Copy)]
|
||||||
|
|
21
rslib/src/decks/counts.rs
Normal file
21
rslib/src/decks/counts.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
use crate::{collection::Collection, decks::DeckID, err::Result};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct DueCounts {
|
||||||
|
pub new: u32,
|
||||||
|
pub review: u32,
|
||||||
|
pub learning: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Collection {
|
||||||
|
pub(crate) fn due_counts(&mut self) -> Result<HashMap<DeckID, DueCounts>> {
|
||||||
|
let days_elapsed = self.timing_today()?.days_elapsed;
|
||||||
|
let learn_cutoff = self.learn_cutoff();
|
||||||
|
self.storage
|
||||||
|
.due_counts(self.sched_ver(), days_elapsed, learn_cutoff)
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,8 +15,10 @@ use crate::{
|
||||||
timestamp::TimestampSecs,
|
timestamp::TimestampSecs,
|
||||||
types::Usn,
|
types::Usn,
|
||||||
};
|
};
|
||||||
|
mod counts;
|
||||||
mod schema11;
|
mod schema11;
|
||||||
mod tree;
|
mod tree;
|
||||||
|
pub(crate) use counts::DueCounts;
|
||||||
pub use schema11::DeckSchema11;
|
pub use schema11::DeckSchema11;
|
||||||
use std::{borrow::Cow, sync::Arc};
|
use std::{borrow::Cow, sync::Arc};
|
||||||
|
|
||||||
|
@ -86,6 +88,16 @@ impl Deck {
|
||||||
self.mtime_secs = TimestampSecs::now();
|
self.mtime_secs = TimestampSecs::now();
|
||||||
self.usn = usn;
|
self.usn = usn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the studied counts if studied today.
|
||||||
|
/// May be negative if user has extended limits.
|
||||||
|
pub(crate) fn new_rev_counts(&self, today: u32) -> (i32, i32) {
|
||||||
|
if self.common.last_day_studied == today {
|
||||||
|
(self.common.new_studied, self.common.review_studied)
|
||||||
|
} else {
|
||||||
|
(0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fixme: need to bump usn on upgrade if we rename
|
// fixme: need to bump usn on upgrade if we rename
|
||||||
|
|
|
@ -1,8 +1,15 @@
|
||||||
// Copyright: Ankitects Pty Ltd and contributors
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
use super::Deck;
|
use super::{Deck, DeckKind, DueCounts};
|
||||||
use crate::{backend_proto::DeckTreeNode, collection::Collection, decks::DeckID, err::Result};
|
use crate::{
|
||||||
|
backend_proto::DeckTreeNode,
|
||||||
|
collection::Collection,
|
||||||
|
deckconf::{DeckConf, DeckConfID},
|
||||||
|
decks::DeckID,
|
||||||
|
err::Result,
|
||||||
|
};
|
||||||
|
use serde_tuple::Serialize_tuple;
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
iter::Peekable,
|
iter::Peekable,
|
||||||
|
@ -68,8 +75,107 @@ fn add_collapsed(node: &mut DeckTreeNode, decks: &HashMap<DeckID, Deck>, browser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn add_counts(node: &mut DeckTreeNode, counts: &HashMap<DeckID, DueCounts>) {
|
||||||
|
if let Some(counts) = counts.get(&DeckID(node.deck_id)) {
|
||||||
|
node.new_count = counts.new;
|
||||||
|
node.review_count = counts.review;
|
||||||
|
node.learn_count = counts.learning;
|
||||||
|
}
|
||||||
|
for child in &mut node.children {
|
||||||
|
add_counts(child, counts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply parent limits to children, and add child counts to parents.
|
||||||
|
/// Counts are (new, review).
|
||||||
|
fn apply_limits(
|
||||||
|
node: &mut DeckTreeNode,
|
||||||
|
today: u32,
|
||||||
|
decks: &HashMap<DeckID, Deck>,
|
||||||
|
dconf: &HashMap<DeckConfID, DeckConf>,
|
||||||
|
parent_limits: (u32, u32),
|
||||||
|
) {
|
||||||
|
let (mut remaining_new, mut remaining_rev) =
|
||||||
|
remaining_counts_for_deck(DeckID(node.deck_id), today, decks, dconf);
|
||||||
|
|
||||||
|
// cap remaining to parent limits
|
||||||
|
remaining_new = remaining_new.min(parent_limits.0);
|
||||||
|
remaining_rev = remaining_rev.min(parent_limits.1);
|
||||||
|
|
||||||
|
// apply our limit to children and tally their counts
|
||||||
|
let mut child_new_total = 0;
|
||||||
|
let mut child_rev_total = 0;
|
||||||
|
for child in &mut node.children {
|
||||||
|
apply_limits(child, today, decks, dconf, (remaining_new, remaining_rev));
|
||||||
|
child_new_total += child.new_count;
|
||||||
|
child_rev_total += child.review_count;
|
||||||
|
// no limit on learning cards
|
||||||
|
node.learn_count += child.learn_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add child counts to our count, capped to remaining limit
|
||||||
|
node.new_count = (node.new_count + child_new_total).min(remaining_new);
|
||||||
|
node.review_count = (node.review_count + child_rev_total).min(remaining_rev);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remaining_counts_for_deck(
|
||||||
|
did: DeckID,
|
||||||
|
today: u32,
|
||||||
|
decks: &HashMap<DeckID, Deck>,
|
||||||
|
dconf: &HashMap<DeckConfID, DeckConf>,
|
||||||
|
) -> (u32, u32) {
|
||||||
|
if let Some(deck) = decks.get(&did) {
|
||||||
|
match &deck.kind {
|
||||||
|
DeckKind::Normal(norm) => {
|
||||||
|
let (new_today, rev_today) = deck.new_rev_counts(today);
|
||||||
|
if let Some(conf) = dconf
|
||||||
|
.get(&DeckConfID(norm.config_id))
|
||||||
|
.or_else(|| dconf.get(&DeckConfID(1)))
|
||||||
|
{
|
||||||
|
let new = (conf.new.per_day as i32).saturating_sub(new_today).max(0);
|
||||||
|
let rev = (conf.rev.per_day as i32).saturating_sub(rev_today).max(0);
|
||||||
|
(new as u32, rev as u32)
|
||||||
|
} else {
|
||||||
|
// missing dconf and fallback
|
||||||
|
(0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DeckKind::Filtered(_) => {
|
||||||
|
// filtered decks have no limit
|
||||||
|
(std::u32::MAX, std::u32::MAX)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// top level deck with id 0
|
||||||
|
(std::u32::MAX, std::u32::MAX)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize_tuple)]
|
||||||
|
pub(crate) struct LegacyDueCounts {
|
||||||
|
name: String,
|
||||||
|
deck_id: i64,
|
||||||
|
review: u32,
|
||||||
|
learn: u32,
|
||||||
|
new: u32,
|
||||||
|
children: Vec<LegacyDueCounts>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DeckTreeNode> for LegacyDueCounts {
|
||||||
|
fn from(n: DeckTreeNode) -> Self {
|
||||||
|
LegacyDueCounts {
|
||||||
|
name: n.name,
|
||||||
|
deck_id: n.deck_id,
|
||||||
|
review: n.review_count,
|
||||||
|
learn: n.learn_count,
|
||||||
|
new: n.new_count,
|
||||||
|
children: n.children.into_iter().map(From::from).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Collection {
|
impl Collection {
|
||||||
pub fn deck_tree(&self) -> Result<DeckTreeNode> {
|
pub fn deck_tree(&mut self, counts: bool) -> Result<DeckTreeNode> {
|
||||||
let names = self.storage.get_all_deck_names()?;
|
let names = self.storage.get_all_deck_names()?;
|
||||||
let mut tree = deck_names_to_tree(names);
|
let mut tree = deck_names_to_tree(names);
|
||||||
|
|
||||||
|
@ -80,11 +186,35 @@ impl Collection {
|
||||||
.map(|d| (d.id, d))
|
.map(|d| (d.id, d))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
add_collapsed(&mut tree, &decks_map, true);
|
add_collapsed(&mut tree, &decks_map, !counts);
|
||||||
|
|
||||||
|
if counts {
|
||||||
|
let counts = self.due_counts()?;
|
||||||
|
let today = self.timing_today()?.days_elapsed;
|
||||||
|
let dconf: HashMap<_, _> = self
|
||||||
|
.storage
|
||||||
|
.all_deck_config()?
|
||||||
|
.into_iter()
|
||||||
|
.map(|d| (d.id, d))
|
||||||
|
.collect();
|
||||||
|
add_counts(&mut tree, &counts);
|
||||||
|
apply_limits(
|
||||||
|
&mut tree,
|
||||||
|
today,
|
||||||
|
&decks_map,
|
||||||
|
&dconf,
|
||||||
|
(std::u32::MAX, std::u32::MAX),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(tree)
|
Ok(tree)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn legacy_deck_tree(&mut self) -> Result<LegacyDueCounts> {
|
||||||
|
let tree = self.deck_tree(true)?;
|
||||||
|
Ok(LegacyDueCounts::from(tree))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn add_missing_decks(&mut self, names: &[(DeckID, String)]) -> Result<()> {
|
pub(crate) fn add_missing_decks(&mut self, names: &[(DeckID, String)]) -> Result<()> {
|
||||||
let mut parents = HashSet::new();
|
let mut parents = HashSet::new();
|
||||||
for (_id, name) in names {
|
for (_id, name) in names {
|
||||||
|
@ -117,7 +247,7 @@ mod test {
|
||||||
col.get_or_create_normal_deck("2::c::A")?;
|
col.get_or_create_normal_deck("2::c::A")?;
|
||||||
col.get_or_create_normal_deck("3")?;
|
col.get_or_create_normal_deck("3")?;
|
||||||
|
|
||||||
let tree = col.deck_tree()?;
|
let tree = col.deck_tree(false)?;
|
||||||
|
|
||||||
// 4 including default
|
// 4 including default
|
||||||
assert_eq!(tree.children.len(), 4);
|
assert_eq!(tree.children.len(), 4);
|
||||||
|
@ -141,7 +271,7 @@ mod test {
|
||||||
col.storage.remove_deck(col.get_deck_id("2")?.unwrap())?;
|
col.storage.remove_deck(col.get_deck_id("2")?.unwrap())?;
|
||||||
col.storage.remove_deck(col.get_deck_id("2::3")?.unwrap())?;
|
col.storage.remove_deck(col.get_deck_id("2::3")?.unwrap())?;
|
||||||
|
|
||||||
let tree = col.deck_tree()?;
|
let tree = col.deck_tree(false)?;
|
||||||
assert_eq!(tree.children.len(), 2);
|
assert_eq!(tree.children.len(), 2);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -226,7 +226,7 @@ impl SqlWriter<'_> {
|
||||||
"filtered" => write!(self.sql, "c.odid != 0").unwrap(),
|
"filtered" => write!(self.sql, "c.odid != 0").unwrap(),
|
||||||
deck => {
|
deck => {
|
||||||
// rewrite "current" to the current deck name
|
// rewrite "current" to the current deck name
|
||||||
let deck = if deck == "current" {
|
let native_deck = if deck == "current" {
|
||||||
let current_did = self.col.get_current_deck_id();
|
let current_did = self.col.get_current_deck_id();
|
||||||
self.col
|
self.col
|
||||||
.storage
|
.storage
|
||||||
|
@ -234,12 +234,11 @@ impl SqlWriter<'_> {
|
||||||
.map(|d| d.name)
|
.map(|d| d.name)
|
||||||
.unwrap_or_else(|| "Default".into())
|
.unwrap_or_else(|| "Default".into())
|
||||||
} else {
|
} else {
|
||||||
deck.into()
|
human_deck_name_to_native(deck)
|
||||||
};
|
};
|
||||||
|
|
||||||
// convert to a regex that includes child decks
|
// convert to a regex that includes child decks
|
||||||
let human_deck = human_deck_name_to_native(&deck);
|
let re = text_to_re(&native_deck);
|
||||||
let re = text_to_re(&human_deck);
|
|
||||||
self.args.push(format!("(?i)^{}($|\x1f)", re));
|
self.args.push(format!("(?i)^{}($|\x1f)", re));
|
||||||
let arg_idx = self.args.len();
|
let arg_idx = self.args.len();
|
||||||
self.sql.push_str(&format!(concat!(
|
self.sql.push_str(&format!(concat!(
|
||||||
|
|
39
rslib/src/storage/deck/due_counts.sql
Normal file
39
rslib/src/storage/deck/due_counts.sql
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
select
|
||||||
|
did,
|
||||||
|
-- new
|
||||||
|
sum(queue = ?1),
|
||||||
|
-- reviews
|
||||||
|
sum(
|
||||||
|
queue = ?2
|
||||||
|
and due <= ?3
|
||||||
|
),
|
||||||
|
-- learning
|
||||||
|
sum(
|
||||||
|
(
|
||||||
|
case
|
||||||
|
-- v2 scheduler
|
||||||
|
?4
|
||||||
|
when 2 then (
|
||||||
|
queue = ?5
|
||||||
|
and due < ?6
|
||||||
|
)
|
||||||
|
or (
|
||||||
|
queue = ?7
|
||||||
|
and due <= ?3
|
||||||
|
)
|
||||||
|
else (
|
||||||
|
-- v1 scheduler
|
||||||
|
case
|
||||||
|
when queue = ?5
|
||||||
|
and due < ?6 then left / 1000
|
||||||
|
when queue = ?7
|
||||||
|
and due <= ?3 then 1
|
||||||
|
else 0
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
)
|
||||||
|
from cards
|
||||||
|
where
|
||||||
|
queue >= 0
|
|
@ -4,7 +4,9 @@
|
||||||
use super::SqliteStorage;
|
use super::SqliteStorage;
|
||||||
use crate::{
|
use crate::{
|
||||||
card::CardID,
|
card::CardID,
|
||||||
decks::{Deck, DeckCommon, DeckID, DeckKindProto, DeckSchema11},
|
card::CardQueue,
|
||||||
|
config::SchedulerVersion,
|
||||||
|
decks::{Deck, DeckCommon, DeckID, DeckKindProto, DeckSchema11, DueCounts},
|
||||||
err::{AnkiError, DBErrorKind, Result},
|
err::{AnkiError, DBErrorKind, Result},
|
||||||
i18n::{I18n, TR},
|
i18n::{I18n, TR},
|
||||||
timestamp::TimestampMillis,
|
timestamp::TimestampMillis,
|
||||||
|
@ -31,6 +33,17 @@ fn row_to_deck(row: &Row) -> Result<Deck> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn row_to_due_counts(row: &Row) -> Result<(DeckID, DueCounts)> {
|
||||||
|
Ok((
|
||||||
|
row.get(0)?,
|
||||||
|
DueCounts {
|
||||||
|
new: row.get(1)?,
|
||||||
|
review: row.get(2)?,
|
||||||
|
learning: row.get(3)?,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
impl SqliteStorage {
|
impl SqliteStorage {
|
||||||
pub(crate) fn get_all_decks_as_schema11(&self) -> Result<HashMap<DeckID, DeckSchema11>> {
|
pub(crate) fn get_all_decks_as_schema11(&self) -> Result<HashMap<DeckID, DeckSchema11>> {
|
||||||
self.get_all_decks()
|
self.get_all_decks()
|
||||||
|
@ -134,6 +147,29 @@ impl SqliteStorage {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn due_counts(
|
||||||
|
&self,
|
||||||
|
sched: SchedulerVersion,
|
||||||
|
day_cutoff: u32,
|
||||||
|
learn_cutoff: u32,
|
||||||
|
) -> Result<HashMap<DeckID, DueCounts>> {
|
||||||
|
self.db
|
||||||
|
.prepare_cached(concat!(include_str!("due_counts.sql"), " group by did"))?
|
||||||
|
.query_and_then(
|
||||||
|
params![
|
||||||
|
CardQueue::New as u8,
|
||||||
|
CardQueue::Review as u8,
|
||||||
|
day_cutoff,
|
||||||
|
sched as u8,
|
||||||
|
CardQueue::Learn as u8,
|
||||||
|
learn_cutoff,
|
||||||
|
CardQueue::DayLearn as u8,
|
||||||
|
],
|
||||||
|
row_to_due_counts,
|
||||||
|
)?
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
// Upgrading/downgrading/legacy
|
// Upgrading/downgrading/legacy
|
||||||
|
|
||||||
pub(super) fn add_default_deck(&self, i18n: &I18n) -> Result<()> {
|
pub(super) fn add_default_deck(&self, i18n: &I18n) -> Result<()> {
|
||||||
|
|
Loading…
Reference in a new issue