From 57ec4cc7b5f61374f1d6f3333583084d4d300ed6 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Wed, 26 May 2021 12:59:45 +1000 Subject: [PATCH] change get_queued_cards() to no longer return congrats info --- pylib/anki/scheduler/v3.py | 48 +++------ qt/aqt/reviewer.py | 16 +-- rslib/backend.proto | 9 +- rslib/src/backend/scheduler/mod.rs | 3 +- rslib/src/notes/mod.rs | 6 +- rslib/src/scheduler/answering/mod.rs | 2 +- rslib/src/scheduler/queue/entry.rs | 2 +- rslib/src/scheduler/queue/mod.rs | 148 +++++++++++---------------- rslib/src/scheduler/queue/undo.rs | 6 +- rslib/src/scheduler/states/mod.rs | 2 +- 10 files changed, 92 insertions(+), 150 deletions(-) diff --git a/pylib/anki/scheduler/v3.py b/pylib/anki/scheduler/v3.py index 1f5820619..8204bcdb3 100644 --- a/pylib/anki/scheduler/v3.py +++ b/pylib/anki/scheduler/v3.py @@ -2,8 +2,9 @@ # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html """ -This file contains experimental scheduler changes, and is not currently -used by Anki. +This file contains experimental scheduler changes: + +https://betas.ankiweb.net/2021-scheduler.html It uses the same DB schema as the V2 scheduler, and 'schedVer' remains as '2' internally. @@ -11,7 +12,7 @@ as '2' internally. from __future__ import annotations -from typing import List, Literal, Sequence, Tuple, Union +from typing import List, Literal, Sequence, Tuple import anki._backend.backend_pb2 as _pb from anki.cards import Card, CardId @@ -19,7 +20,6 @@ from anki.collection import OpChanges from anki.consts import * from anki.decks import DeckId from anki.errors import DBError -from anki.scheduler.base import CongratsInfo from anki.scheduler.legacy import SchedulerBaseWithLegacy from anki.types import assert_exhaustive from anki.utils import intTime @@ -44,19 +44,11 @@ class Scheduler(SchedulerBaseWithLegacy): *, fetch_limit: int = 1, intraday_learning_only: bool = False, - ) -> Union[QueuedCards, CongratsInfo]: - "Returns one or more card ids, or the congratulations screen info." - info = self.col._backend.get_queued_cards( + ) -> QueuedCards: + "Returns zero or more pending cards, and the remaining counts. Idempotent." + return self.col._backend.get_queued_cards( fetch_limit=fetch_limit, intraday_learning_only=intraday_learning_only ) - kind = info.WhichOneof("value") - if kind == "queued_cards": - return info.queued_cards - elif kind == "congrats_info": - return info.congrats_info - else: - assert_exhaustive(kind) - assert False def next_states(self, card_id: CardId) -> NextStates: "New states corresponding to each answer button press." @@ -111,29 +103,23 @@ class Scheduler(SchedulerBaseWithLegacy): def getCard(self) -> Optional[Card]: """Fetch the next card from the queue. None if finished.""" - response = self.get_queued_cards() - if isinstance(response, QueuedCards): - backend_card = response.cards[0].card - card = Card(self.col) - card._load_from_backend_card(backend_card) - card.startTimer() - return card - else: + try: + queued_card = self.get_queued_cards().cards[0] + except IndexError: return None + card = Card(self.col) + card._load_from_backend_card(queued_card.card) + card.startTimer() + return card + def _is_finished(self) -> bool: "Don't use this, it is a stop-gap until this code is refactored." - info = self.get_queued_cards() - return isinstance(info, CongratsInfo) + return not self.get_queued_cards().cards def counts(self, card: Optional[Card] = None) -> Tuple[int, int, int]: info = self.get_queued_cards() - if isinstance(info, CongratsInfo): - counts = [0, 0, 0] - else: - counts = [info.new_count, info.learning_count, info.review_count] - - return tuple(counts) # type: ignore + return (info.new_count, info.learning_count, info.review_count) @property def newCount(self) -> int: diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index 3fc2cd75c..ffca8ebf9 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -11,25 +11,13 @@ import re import unicodedata as ucd from dataclasses import dataclass from enum import Enum, auto -from typing import ( - Any, - Callable, - List, - Literal, - Match, - Optional, - Sequence, - Tuple, - Union, - cast, -) +from typing import Any, Callable, List, Literal, Match, Optional, Sequence, Tuple, cast from PyQt5.QtCore import Qt from anki import hooks from anki.cards import Card, CardId from anki.collection import Config, OpChanges, OpChangesWithCount -from anki.scheduler import CongratsInfo from anki.scheduler.v3 import CardAnswer, NextStates, QueuedCards from anki.scheduler.v3 import Scheduler as V3Scheduler from anki.tags import MARKED_TAG @@ -240,7 +228,7 @@ class Reviewer: def _get_next_v3_card(self) -> None: assert isinstance(self.mw.col.sched, V3Scheduler) output = self.mw.col.sched.get_queued_cards() - if isinstance(output, CongratsInfo): + if not output.cards: return self._v3 = V3CardInfo.from_queue(output) self.card = Card(self.mw.col, backend_card=self._v3.top_card().card) diff --git a/rslib/backend.proto b/rslib/backend.proto index 7247c4397..6747073f4 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -132,7 +132,7 @@ service SchedulingService { rpc StateIsLeech(SchedulingState) returns (Bool); rpc AnswerCard(CardAnswer) returns (OpChanges); rpc UpgradeScheduler(Empty) returns (Empty); - rpc GetQueuedCards(GetQueuedCardsIn) returns (GetQueuedCardsOut); + rpc GetQueuedCards(GetQueuedCardsIn) returns (QueuedCards); } service DecksService { @@ -1563,13 +1563,6 @@ message QueuedCards { uint32 review_count = 4; } -message GetQueuedCardsOut { - oneof value { - QueuedCards queued_cards = 1; - CongratsInfoOut congrats_info = 2; - } -} - message OpChanges { bool card = 1; bool note = 2; diff --git a/rslib/src/backend/scheduler/mod.rs b/rslib/src/backend/scheduler/mod.rs index 6baac8b3b..684c271cc 100644 --- a/rslib/src/backend/scheduler/mod.rs +++ b/rslib/src/backend/scheduler/mod.rs @@ -176,9 +176,10 @@ impl SchedulingService for Backend { .map(Into::into) } - fn get_queued_cards(&self, input: pb::GetQueuedCardsIn) -> Result { + fn get_queued_cards(&self, input: pb::GetQueuedCardsIn) -> Result { self.with_col(|col| { col.get_queued_cards(input.fetch_limit as usize, input.intraday_learning_only) + .map(Into::into) }) } } diff --git a/rslib/src/notes/mod.rs b/rslib/src/notes/mod.rs index 265d0d06e..2a88a1171 100644 --- a/rslib/src/notes/mod.rs +++ b/rslib/src/notes/mod.rs @@ -693,7 +693,7 @@ mod test { col.storage.db_scalar::("select count() from graves")?, 0 ); - assert_eq!(col.next_card()?.is_some(), false); + assert_eq!(col.get_next_card()?.is_some(), false); Ok(()) }; @@ -704,7 +704,7 @@ mod test { col.storage.db_scalar::("select count() from graves")?, 0 ); - assert_eq!(col.next_card()?.is_some(), true); + assert_eq!(col.get_next_card()?.is_some(), true); Ok(()) }; @@ -732,7 +732,7 @@ mod test { col.storage.db_scalar::("select count() from graves")?, 3 ); - assert_eq!(col.next_card()?.is_some(), false); + assert_eq!(col.get_next_card()?.is_some(), false); Ok(()) }; diff --git a/rslib/src/scheduler/answering/mod.rs b/rslib/src/scheduler/answering/mod.rs index dee15a755..0b728aeb2 100644 --- a/rslib/src/scheduler/answering/mod.rs +++ b/rslib/src/scheduler/answering/mod.rs @@ -401,7 +401,7 @@ pub mod test_helpers { where F: FnOnce(&NextCardStates) -> CardState, { - let queued = self.next_card()?.unwrap(); + let queued = self.get_next_card()?.unwrap(); let new_state = get_state(&queued.next_states); self.answer_card(&CardAnswer { card_id: queued.card.id, diff --git a/rslib/src/scheduler/queue/entry.rs b/rslib/src/scheduler/queue/entry.rs index 796590ab4..ad1ba2134 100644 --- a/rslib/src/scheduler/queue/entry.rs +++ b/rslib/src/scheduler/queue/entry.rs @@ -37,7 +37,7 @@ impl QueueEntry { } #[derive(Clone, Copy, Debug, PartialEq)] -pub(crate) enum QueueEntryKind { +pub enum QueueEntryKind { New, Learning, Review, diff --git a/rslib/src/scheduler/queue/mod.rs b/rslib/src/scheduler/queue/mod.rs index 8bba17ec7..50017273d 100644 --- a/rslib/src/scheduler/queue/mod.rs +++ b/rslib/src/scheduler/queue/mod.rs @@ -16,7 +16,7 @@ pub(crate) use main::{MainQueueEntry, MainQueueEntryKind}; use self::undo::QueueUpdate; use super::{states::NextCardStates, timing::SchedTimingToday}; -use crate::{backend_proto as pb, prelude::*, timestamp::TimestampSecs}; +use crate::{prelude::*, timestamp::TimestampSecs}; #[derive(Debug)] pub(crate) struct CardQueues { @@ -32,26 +32,81 @@ pub(crate) struct CardQueues { } #[derive(Debug, Copy, Clone)] -pub(crate) struct Counts { +pub struct Counts { pub new: usize, pub learning: usize, pub review: usize, } -#[derive(Debug)] -pub(crate) struct QueuedCard { +#[derive(Debug, Clone)] +pub struct QueuedCard { pub card: Card, pub kind: QueueEntryKind, pub next_states: NextCardStates, } -pub(crate) struct QueuedCards { +#[derive(Debug)] +pub struct QueuedCards { pub cards: Vec, pub new_count: usize, pub learning_count: usize, pub review_count: usize, } +impl Collection { + pub fn get_next_card(&mut self) -> Result> { + self.get_queued_cards(1, false) + .map(|queued| queued.cards.get(0).cloned()) + } + + pub fn get_queued_cards( + &mut self, + fetch_limit: usize, + intraday_learning_only: bool, + ) -> Result { + let queues = self.get_queues()?; + let counts = queues.counts(); + let entries: Vec<_> = if intraday_learning_only { + queues + .intraday_now_iter() + .chain(queues.intraday_ahead_iter()) + .map(Into::into) + .collect() + } else { + queues.iter().take(fetch_limit).collect() + }; + let cards: Vec<_> = entries + .into_iter() + .map(|entry| { + let card = self + .storage + .get_card(entry.card_id())? + .ok_or(AnkiError::NotFound)?; + if card.mtime != entry.mtime() { + return Err(AnkiError::invalid_input( + "bug: card modified without updating queue", + )); + } + + // fixme: pass in card instead of id + let next_states = self.get_next_card_states(card.id)?; + + Ok(QueuedCard { + card, + next_states, + kind: entry.kind(), + }) + }) + .collect::>()?; + Ok(QueuedCards { + cards, + new_count: counts.new, + learning_count: counts.learning, + review_count: counts.review, + }) + } +} + impl CardQueues { /// An iterator over the card queues, in the order the cards will /// be presented. @@ -103,26 +158,6 @@ impl CardQueues { } impl Collection { - pub(crate) fn get_queued_cards( - &mut self, - fetch_limit: usize, - intraday_learning_only: bool, - ) -> Result { - if let Some(next_cards) = self.next_cards(fetch_limit, intraday_learning_only)? { - Ok(pb::GetQueuedCardsOut { - value: Some(pb::get_queued_cards_out::Value::QueuedCards( - next_cards.into(), - )), - }) - } else { - Ok(pb::GetQueuedCardsOut { - value: Some(pb::get_queued_cards_out::Value::CongratsInfo( - self.congrats_info()?, - )), - }) - } - } - /// This is automatically done when transact() is called for everything /// except card answers, so unless you are modifying state outside of a /// transaction, you probably don't need this. @@ -177,74 +212,13 @@ impl Collection { Ok(self.state.card_queues.as_mut().unwrap()) } - - fn next_cards( - &mut self, - fetch_limit: usize, - intraday_learning_only: bool, - ) -> Result> { - let queues = self.get_queues()?; - let counts = queues.counts(); - let entries: Vec<_> = if intraday_learning_only { - queues - .intraday_now_iter() - .chain(queues.intraday_ahead_iter()) - .map(Into::into) - .collect() - } else { - queues.iter().take(fetch_limit).collect() - }; - if entries.is_empty() { - Ok(None) - } else { - let cards: Vec<_> = entries - .into_iter() - .map(|entry| { - let card = self - .storage - .get_card(entry.card_id())? - .ok_or(AnkiError::NotFound)?; - if card.mtime != entry.mtime() { - return Err(AnkiError::invalid_input( - "bug: card modified without updating queue", - )); - } - - // fixme: pass in card instead of id - let next_states = self.get_next_card_states(card.id)?; - - Ok(QueuedCard { - card, - next_states, - kind: entry.kind(), - }) - }) - .collect::>()?; - Ok(Some(QueuedCards { - cards, - new_count: counts.new, - learning_count: counts.learning, - review_count: counts.review, - })) - } - } } // test helpers #[cfg(test)] impl Collection { - pub(crate) fn next_card(&mut self) -> Result> { - Ok(self - .next_cards(1, false)? - .map(|mut resp| resp.cards.pop().unwrap())) - } - - fn get_queue_single(&mut self) -> Result { - self.next_cards(1, false)?.ok_or(AnkiError::NotFound) - } - pub(crate) fn counts(&mut self) -> [usize; 3] { - self.get_queue_single() + self.get_queued_cards(1, false) .map(|q| [q.new_count, q.learning_count, q.review_count]) .unwrap_or([0; 3]) } diff --git a/rslib/src/scheduler/queue/undo.rs b/rslib/src/scheduler/queue/undo.rs index 3e50dd046..617f73188 100644 --- a/rslib/src/scheduler/queue/undo.rs +++ b/rslib/src/scheduler/queue/undo.rs @@ -102,7 +102,7 @@ mod test { col.storage.update_deck_conf(&conf)?; // get the first card - let queued = col.next_card()?.unwrap(); + let queued = col.get_next_card()?.unwrap(); let cid = queued.card.id; let sibling_cid = col.storage.all_card_ids_of_note_in_order(nid)?[1]; @@ -152,7 +152,7 @@ mod test { let deck = col.get_deck(DeckId(1))?.unwrap(); assert_eq!(deck.common.review_studied, 1); - assert_eq!(col.next_card()?.is_some(), false); + assert_eq!(col.get_next_card()?.is_some(), false); Ok(()) }; @@ -177,7 +177,7 @@ mod test { let deck = col.get_deck(DeckId(1))?.unwrap(); assert_eq!(deck.common.review_studied, 0); - assert_eq!(col.next_card()?.is_some(), true); + assert_eq!(col.get_next_card()?.is_some(), true); assert_eq!(col.counts(), [0, 0, 1]); Ok(()) diff --git a/rslib/src/scheduler/states/mod.rs b/rslib/src/scheduler/states/mod.rs index 4adc6e165..d9ffde031 100644 --- a/rslib/src/scheduler/states/mod.rs +++ b/rslib/src/scheduler/states/mod.rs @@ -135,7 +135,7 @@ fn fuzz_range(interval: f32, factor: f32, minimum: f32) -> (f32, f32) { (interval - delta, interval + delta + 1.0) } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct NextCardStates { pub current: CardState, pub again: CardState,