set_deck()

This commit is contained in:
Damien Elmes 2020-09-03 17:42:46 +10:00
parent 9214c4a700
commit 56ceb6ba76
13 changed files with 167 additions and 139 deletions

View file

@ -150,6 +150,7 @@ service BackendService {
rpc UpdateCard (Card) returns (Empty);
rpc AddCard (Card) returns (CardID);
rpc RemoveCards (RemoveCardsIn) returns (Empty);
rpc SetDeck (SetDeckIn) returns (Empty);
// notes
@ -1081,3 +1082,8 @@ message SortDeckIn {
int64 deck_id = 1;
bool randomize = 2;
}
message SetDeckIn {
repeated int64 card_ids = 1;
int64 deck_id = 2;
}

View file

@ -384,6 +384,9 @@ class Collection:
"You probably want .remove_notes_by_card() instead."
self.backend.remove_cards(card_ids=card_ids)
def set_deck(self, card_ids: List[int], deck_id: int) -> None:
self.backend.set_deck(card_ids=card_ids, deck_id=deck_id)
# legacy
def remCards(self, ids: List[int], notes: bool = True) -> None:

View file

@ -22,7 +22,7 @@ from anki.models import NoteType
from anki.notes import Note
from anki.rsbackend import TR, DeckTreeNode, InvalidInput
from anki.stats import CardStats
from anki.utils import htmlToTextLine, ids2str, intTime, isMac, isWin
from anki.utils import htmlToTextLine, ids2str, isMac, isWin
from aqt import AnkiQt, gui_hooks
from aqt.editor import Editor
from aqt.exporting import ExportDialog
@ -1601,21 +1601,7 @@ where id in %s"""
return
self.model.beginReset()
self.mw.checkpoint(_("Change Deck"))
mod = intTime()
usn = self.col.usn()
# normal cards
scids = ids2str(cids)
# remove any cards from filtered deck first
self.col.sched.remFromDyn(cids)
# then move into new deck
self.col.db.execute(
"""
update cards set usn=?, mod=?, did=? where id in """
+ scids,
usn,
mod,
did,
)
self.col.set_deck(cids, did)
self.model.endReset()
self.mw.requireReset(reason=ResetReason.BrowserSetDeck, context=self)

View file

@ -817,6 +817,12 @@ impl BackendService for Backend {
})
}
fn set_deck(&mut self, input: pb::SetDeckIn) -> BackendResult<Empty> {
let cids: Vec<_> = input.card_ids.into_iter().map(CardID).collect();
let deck_id = input.deck_id.into();
self.with_col(|col| col.set_deck(&cids, deck_id).map(Into::into))
}
// notes
//-------------------------------------------------------------------

View file

@ -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::{DeckFilterContext, DeckID};
use crate::decks::DeckID;
use crate::define_newtype;
use crate::err::{AnkiError, Result};
use crate::notes::NoteID;
@ -102,103 +102,10 @@ impl Card {
self.usn = usn;
}
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;
}
}
}
/// Restores to the original deck and clears original_due.
/// This does not update the queue or type, so should only be used as
/// part of an operation that adjusts those separately.
pub(crate) fn remove_from_filtered_deck_before_reschedule(&mut self) {
if self.original_deck_id.0 != 0 {
self.deck_id = self.original_deck_id;
self.original_deck_id.0 = 0;
self.original_due = 0;
}
}
pub(crate) fn remove_from_filtered_deck_restoring_queue(&mut self, sched: SchedulerVersion) {
if self.original_deck_id.0 == 0 {
// not in a filtered deck
return;
}
self.deck_id = self.original_deck_id;
self.original_deck_id.0 = 0;
match sched {
SchedulerVersion::V1 => {
self.due = self.original_due;
self.queue = match self.ctype {
CardType::New => CardQueue::New,
CardType::Learn => CardQueue::New,
CardType::Review => CardQueue::Review,
// not applicable in v1, should not happen
CardType::Relearn => {
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 {
self.queue = match self.ctype {
CardType::Learn | CardType::Relearn => {
if self.due > 1_000_000_000 {
// unix timestamp
CardQueue::Learn
} else {
// day number
CardQueue::DayLearn
}
}
CardType::New => CardQueue::New,
CardType::Review => CardQueue::Review,
}
}
}
}
self.original_due = 0;
/// Caller must ensure provided deck exists and is not filtered.
fn set_deck(&mut self, deck: DeckID, sched: SchedulerVersion) {
self.remove_from_filtered_deck_restoring_queue(sched);
self.deck_id = deck;
}
}
#[derive(Debug)]
@ -289,6 +196,27 @@ impl Collection {
Ok(())
}
pub fn set_deck(&mut self, cards: &[CardID], deck_id: DeckID) -> Result<()> {
let deck = self.get_deck(deck_id)?.ok_or(AnkiError::NotFound)?;
if deck.is_filtered() {
return Err(AnkiError::DeckIsFiltered);
}
self.storage.set_search_table_to_card_ids(cards)?;
let sched = self.sched_ver();
let usn = self.usn()?;
self.transact(None, |col| {
for mut card in col.storage.all_searched_cards()? {
if card.deck_id == deck_id {
continue;
}
let original = card.clone();
card.set_deck(deck_id, sched);
col.update_card(&mut card, &original, usn)?;
}
Ok(())
})
}
}
#[cfg(test)]

View file

@ -17,11 +17,9 @@ 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};

View file

@ -1,13 +1,13 @@
// 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::decks::{Deck, DeckID};
use crate::{
card::{CardID, CardQueue},
card::{Card, CardID, CardQueue, CardType},
collection::Collection,
config::SchedulerVersion,
err::Result,
@ -17,6 +17,107 @@ use crate::{
types::Usn,
};
impl Card {
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;
}
}
}
/// Restores to the original deck and clears original_due.
/// This does not update the queue or type, so should only be used as
/// part of an operation that adjusts those separately.
pub(crate) fn remove_from_filtered_deck_before_reschedule(&mut self) {
if self.original_deck_id.0 != 0 {
self.deck_id = self.original_deck_id;
self.original_deck_id.0 = 0;
self.original_due = 0;
}
}
pub(crate) fn remove_from_filtered_deck_restoring_queue(&mut self, sched: SchedulerVersion) {
if self.original_deck_id.0 == 0 {
// not in a filtered deck
return;
}
self.deck_id = self.original_deck_id;
self.original_deck_id.0 = 0;
match sched {
SchedulerVersion::V1 => {
self.due = self.original_due;
self.queue = match self.ctype {
CardType::New => CardQueue::New,
CardType::Learn => CardQueue::New,
CardType::Review => CardQueue::Review,
// not applicable in v1, should not happen
CardType::Relearn => {
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 {
self.queue = match self.ctype {
CardType::Learn | CardType::Relearn => {
if self.due > 1_000_000_000 {
// unix timestamp
CardQueue::Learn
} else {
// day number
CardQueue::DayLearn
}
}
CardType::New => CardQueue::New,
CardType::Review => CardQueue::Review,
}
}
}
}
self.original_due = 0;
}
}
impl Deck {
pub fn new_filtered() -> Deck {
let mut filt = FilteredDeck::default();

View file

@ -13,6 +13,7 @@ pub mod dbcheck;
pub mod deckconf;
pub mod decks;
pub mod err;
pub mod filtered;
pub mod findreplace;
pub mod i18n;
pub mod latex;

View file

@ -83,7 +83,7 @@ impl Collection {
pub fn unbury_or_unsuspend_cards(&mut self, cids: &[CardID]) -> Result<()> {
self.transact(None, |col| {
col.set_search_table_to_card_ids(cids)?;
col.storage.set_search_table_to_card_ids(cids)?;
col.unsuspend_or_unbury_searched_cards()
})
}
@ -143,7 +143,7 @@ impl Collection {
mode: pb::bury_or_suspend_cards_in::Mode,
) -> Result<()> {
self.transact(None, |col| {
col.set_search_table_to_card_ids(cids)?;
col.storage.set_search_table_to_card_ids(cids)?;
col.bury_or_suspend_searched_cards(mode)
})
}

View file

@ -72,7 +72,7 @@ impl Collection {
let usn = self.usn()?;
let mut position = self.get_next_card_position();
self.transact(None, |col| {
col.set_search_table_to_card_ids(cids)?;
col.storage.set_search_table_to_card_ids(cids)?;
let cards = col.storage.all_searched_cards()?;
for mut card in cards {
let original = card.clone();
@ -113,7 +113,7 @@ impl Collection {
if shift {
self.shift_existing_cards(starting_from, step * cids.len() as u32, usn)?;
}
self.set_search_table_to_card_ids(cids)?;
self.storage.set_search_table_to_card_ids(cids)?;
let cards = self.storage.all_searched_cards()?;
let sorter = NewCardSorter::new(&cards, starting_from, step, random);
for mut card in cards {

View file

@ -36,7 +36,7 @@ impl Collection {
let mut rng = rand::thread_rng();
let distribution = Uniform::from(min_days..=max_days);
self.transact(None, |col| {
col.set_search_table_to_card_ids(cids)?;
col.storage.set_search_table_to_card_ids(cids)?;
for mut card in col.storage.all_searched_cards()? {
let original = card.clone();
let interval = distribution.sample(&mut rng);

View file

@ -106,22 +106,6 @@ 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.setup_searched_cards_table()?;
let mut stmt = self
.storage
.db
.prepare_cached("insert into search_cids values (?)")?;
for cid in cards {
stmt.execute(&[cid])?;
}
Ok(())
}
/// If the sort mode is based on a config setting, look it up.
fn resolve_config_sort(&self, mode: &mut SortMode) {
if mode == &SortMode::FromConfig {

View file

@ -335,6 +335,21 @@ impl super::SqliteStorage {
.execute("drop table if exists search_cids", NO_PARAMS)?;
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.setup_searched_cards_table()?;
let mut stmt = self
.db
.prepare_cached("insert into search_cids values (?)")?;
for cid in cards {
stmt.execute(&[cid])?;
}
Ok(())
}
}
#[cfg(test)]