update scheduling ops

- migrate to CollectionOp()
- return actual change count when suspending/burying
- add helper to convert vec to vec of newtype
This commit is contained in:
Damien Elmes 2021-04-06 16:38:42 +10:00
parent 2de8cc1a94
commit 84fe309583
15 changed files with 172 additions and 127 deletions

View file

@ -16,7 +16,7 @@ from typing import List, Optional, Sequence
from anki.cards import CardId from anki.cards import CardId
from anki.consts import CARD_TYPE_NEW, NEW_CARDS_RANDOM, QUEUE_TYPE_NEW, QUEUE_TYPE_REV from anki.consts import CARD_TYPE_NEW, NEW_CARDS_RANDOM, QUEUE_TYPE_NEW, QUEUE_TYPE_REV
from anki.decks import DeckConfigDict, DeckId, DeckTreeNode from anki.decks import DeckConfigDict, DeckId, DeckTreeNode
from anki.notes import Note from anki.notes import NoteId
from anki.utils import ids2str, intTime from anki.utils import ids2str, intTime
CongratsInfo = _pb.CongratsInfoOut CongratsInfo = _pb.CongratsInfoOut
@ -123,20 +123,31 @@ select id from cards where did in %s and queue = {QUEUE_TYPE_REV} and due <= ? l
) -> None: ) -> None:
self.col._backend.unbury_cards_in_current_deck(mode) self.col._backend.unbury_cards_in_current_deck(mode)
def suspend_cards(self, ids: Sequence[CardId]) -> OpChanges: def suspend_cards(self, ids: Sequence[CardId]) -> OpChangesWithCount:
return self.col._backend.bury_or_suspend_cards( return self.col._backend.bury_or_suspend_cards(
card_ids=ids, mode=BuryOrSuspend.SUSPEND card_ids=ids, note_ids=[], mode=BuryOrSuspend.SUSPEND
) )
def bury_cards(self, ids: Sequence[CardId], manual: bool = True) -> OpChanges: def suspend_notes(self, ids: Sequence[NoteId]) -> OpChangesWithCount:
return self.col._backend.bury_or_suspend_cards(
card_ids=[], note_ids=ids, mode=BuryOrSuspend.SUSPEND
)
def bury_cards(
self, ids: Sequence[CardId], manual: bool = True
) -> OpChangesWithCount:
if manual: if manual:
mode = BuryOrSuspend.BURY_USER mode = BuryOrSuspend.BURY_USER
else: else:
mode = BuryOrSuspend.BURY_SCHED mode = BuryOrSuspend.BURY_SCHED
return self.col._backend.bury_or_suspend_cards(card_ids=ids, mode=mode) return self.col._backend.bury_or_suspend_cards(
card_ids=ids, note_ids=[], mode=mode
)
def bury_note(self, note: Note) -> None: def bury_notes(self, note_ids: Sequence[NoteId]) -> OpChangesWithCount:
self.bury_cards(note.card_ids()) return self.col._backend.bury_or_suspend_cards(
card_ids=[], note_ids=note_ids, mode=BuryOrSuspend.BURY_USER
)
# Resetting/rescheduling # Resetting/rescheduling
########################################################################## ##########################################################################

View file

@ -501,7 +501,7 @@ def test_misc():
col.addNote(note) col.addNote(note)
c = note.cards()[0] c = note.cards()[0]
# burying # burying
col.sched.bury_note(note) col.sched.bury_notes([note.id])
col.reset() col.reset()
assert not col.sched.getCard() assert not col.sched.getCard()
col.sched.unbury_cards_in_current_deck() col.sched.unbury_cards_in_current_deck()

View file

@ -732,9 +732,9 @@ where id in %s"""
def suspend_selected_cards(self, checked: bool) -> None: def suspend_selected_cards(self, checked: bool) -> None:
cids = self.selected_cards() cids = self.selected_cards()
if checked: if checked:
suspend_cards(mw=self.mw, card_ids=cids) suspend_cards(parent=self, card_ids=cids).run_in_background()
else: else:
unsuspend_cards(mw=self.mw, card_ids=cids) unsuspend_cards(parent=self.mw, card_ids=cids).run_in_background()
# Exporting # Exporting
###################################################################### ######################################################################
@ -796,25 +796,23 @@ where id in %s"""
return return
reposition_new_cards_dialog( reposition_new_cards_dialog(
mw=self.mw, parent=self, card_ids=self.selected_cards() parent=self, card_ids=self.selected_cards()
) ).run_in_background()
@ensure_editor_saved_on_trigger @ensure_editor_saved_on_trigger
def set_due_date(self) -> None: def set_due_date(self) -> None:
set_due_date_dialog( set_due_date_dialog(
mw=self.mw,
parent=self, parent=self,
card_ids=self.selected_cards(), card_ids=self.selected_cards(),
config_key=Config.String.SET_DUE_BROWSER, config_key=Config.String.SET_DUE_BROWSER,
) ).run_in_background()
@ensure_editor_saved_on_trigger @ensure_editor_saved_on_trigger
def forget_cards(self) -> None: def forget_cards(self) -> None:
forget_cards( forget_cards(
mw=self.mw,
parent=self, parent=self,
card_ids=self.selected_cards(), card_ids=self.selected_cards(),
) ).run_in_background()
# Edit: selection # Edit: selection
###################################################################### ######################################################################

View file

@ -310,7 +310,9 @@ class FilteredDeckConfigDialog(QDialog):
gui_hooks.filtered_deck_dialog_will_add_or_update_deck(self, self.deck) gui_hooks.filtered_deck_dialog_will_add_or_update_deck(self, self.deck)
add_or_update_filtered_deck(mw=self.mw, deck=self.deck, success=success) add_or_update_filtered_deck(parent=self, deck=self.deck).success(
success
).run_in_background()
# Step load/save # Step load/save
######################################################## ########################################################

View file

@ -7,28 +7,32 @@ from typing import Optional, Sequence
import aqt import aqt
from anki.cards import CardId from anki.cards import CardId
from anki.collection import CARD_TYPE_NEW, Config from anki.collection import (
CARD_TYPE_NEW,
Config,
OpChanges,
OpChangesWithCount,
OpChangesWithId,
)
from anki.decks import DeckId from anki.decks import DeckId
from anki.notes import NoteId from anki.notes import NoteId
from anki.scheduler import FilteredDeckForUpdate from anki.scheduler import FilteredDeckForUpdate
from aqt import AnkiQt from aqt.operations import CollectionOp
from aqt.main import PerformOpOptionalSuccessCallback
from aqt.qt import * from aqt.qt import *
from aqt.utils import disable_help_button, getText, tooltip, tr from aqt.utils import disable_help_button, getText, tooltip, tr
def set_due_date_dialog( def set_due_date_dialog(
*, *,
mw: aqt.AnkiQt,
parent: QWidget, parent: QWidget,
card_ids: Sequence[CardId], card_ids: Sequence[CardId],
config_key: Optional[Config.String.Key.V], config_key: Optional[Config.String.Key.V],
) -> None: ) -> Optional[CollectionOp[OpChanges]]:
if not card_ids: if not card_ids:
return return None
default_text = ( default_text = (
mw.col.get_config_string(config_key) if config_key is not None else "" aqt.mw.col.get_config_string(config_key) if config_key is not None else ""
) )
prompt = "\n".join( prompt = "\n".join(
[ [
@ -43,34 +47,35 @@ def set_due_date_dialog(
title=tr.actions_set_due_date(), title=tr.actions_set_due_date(),
) )
if not success or not days.strip(): if not success or not days.strip():
return return None
else:
mw.perform_op( return CollectionOp(
lambda: mw.col.sched.set_due_date(card_ids, days, config_key), parent, lambda col: col.sched.set_due_date(card_ids, days, config_key)
success=lambda _: tooltip( ).success(
lambda _: tooltip(
tr.scheduling_set_due_date_done(cards=len(card_ids)), tr.scheduling_set_due_date_done(cards=len(card_ids)),
parent=parent, parent=parent,
), )
) )
def forget_cards( def forget_cards(
*, mw: aqt.AnkiQt, parent: QWidget, card_ids: Sequence[CardId] *, parent: QWidget, card_ids: Sequence[CardId]
) -> None: ) -> CollectionOp[OpChanges]:
if not card_ids: return CollectionOp(
return parent, lambda col: col.sched.schedule_cards_as_new(card_ids)
).success(
mw.perform_op( lambda _: tooltip(
lambda: mw.col.sched.schedule_cards_as_new(card_ids),
success=lambda _: tooltip(
tr.scheduling_forgot_cards(cards=len(card_ids)), parent=parent tr.scheduling_forgot_cards(cards=len(card_ids)), parent=parent
), )
) )
def reposition_new_cards_dialog( def reposition_new_cards_dialog(
*, mw: AnkiQt, parent: QWidget, card_ids: Sequence[CardId] *, parent: QWidget, card_ids: Sequence[CardId]
) -> None: ) -> Optional[CollectionOp[OpChangesWithCount]]:
from aqt import mw
assert mw.col.db assert mw.col.db
row = mw.col.db.first( row = mw.col.db.first(
f"select min(due), max(due) from cards where type={CARD_TYPE_NEW} and odid=0" f"select min(due), max(due) from cards where type={CARD_TYPE_NEW} and odid=0"
@ -92,15 +97,14 @@ def reposition_new_cards_dialog(
frm.start.selectAll() frm.start.selectAll()
if not d.exec_(): if not d.exec_():
return return None
start = frm.start.value() start = frm.start.value()
step = frm.step.value() step = frm.step.value()
randomize = frm.randomize.isChecked() randomize = frm.randomize.isChecked()
shift = frm.shift.isChecked() shift = frm.shift.isChecked()
reposition_new_cards( return reposition_new_cards(
mw=mw,
parent=parent, parent=parent,
card_ids=card_ids, card_ids=card_ids,
starting_from=start, starting_from=start,
@ -112,89 +116,80 @@ def reposition_new_cards_dialog(
def reposition_new_cards( def reposition_new_cards(
*, *,
mw: AnkiQt,
parent: QWidget, parent: QWidget,
card_ids: Sequence[CardId], card_ids: Sequence[CardId],
starting_from: int, starting_from: int,
step_size: int, step_size: int,
randomize: bool, randomize: bool,
shift_existing: bool, shift_existing: bool,
) -> None: ) -> CollectionOp[OpChangesWithCount]:
mw.perform_op( return CollectionOp(
lambda: mw.col.sched.reposition_new_cards( parent,
lambda col: col.sched.reposition_new_cards(
card_ids=card_ids, card_ids=card_ids,
starting_from=starting_from, starting_from=starting_from,
step_size=step_size, step_size=step_size,
randomize=randomize, randomize=randomize,
shift_existing=shift_existing, shift_existing=shift_existing,
), ),
success=lambda out: tooltip( ).success(
lambda out: tooltip(
tr.browsing_changed_new_position(count=out.count), parent=parent tr.browsing_changed_new_position(count=out.count), parent=parent
), )
) )
def suspend_cards( def suspend_cards(
*, *,
mw: AnkiQt, parent: QWidget,
card_ids: Sequence[CardId], card_ids: Sequence[CardId],
success: PerformOpOptionalSuccessCallback = None, ) -> CollectionOp[OpChangesWithCount]:
) -> None: return CollectionOp(parent, lambda col: col.sched.suspend_cards(card_ids))
mw.perform_op(lambda: mw.col.sched.suspend_cards(card_ids), success=success)
def suspend_note( def suspend_note(
*, *,
mw: AnkiQt, parent: QWidget,
note_id: NoteId, note_ids: Sequence[NoteId],
success: PerformOpOptionalSuccessCallback = None, ) -> CollectionOp[OpChangesWithCount]:
) -> None: return CollectionOp(parent, lambda col: col.sched.suspend_notes(note_ids))
mw.taskman.run_in_background(
lambda: mw.col.card_ids_of_note(note_id),
lambda future: suspend_cards(mw=mw, card_ids=future.result(), success=success),
)
def unsuspend_cards(*, mw: AnkiQt, card_ids: Sequence[CardId]) -> None: def unsuspend_cards(
mw.perform_op(lambda: mw.col.sched.unsuspend_cards(card_ids)) *, parent: QWidget, card_ids: Sequence[CardId]
) -> CollectionOp[OpChanges]:
return CollectionOp(parent, lambda col: col.sched.unsuspend_cards(card_ids))
def bury_cards( def bury_cards(
*, *,
mw: AnkiQt, parent: QWidget,
card_ids: Sequence[CardId], card_ids: Sequence[CardId],
success: PerformOpOptionalSuccessCallback = None, ) -> CollectionOp[OpChangesWithCount]:
) -> None: return CollectionOp(parent, lambda col: col.sched.bury_cards(card_ids))
mw.perform_op(lambda: mw.col.sched.bury_cards(card_ids), success=success)
def bury_note( def bury_notes(
*, *,
mw: AnkiQt, parent: QWidget,
note_id: NoteId, note_ids: Sequence[NoteId],
success: PerformOpOptionalSuccessCallback = None, ) -> CollectionOp[OpChangesWithCount]:
) -> None: return CollectionOp(parent, lambda col: col.sched.bury_notes(note_ids))
mw.taskman.run_in_background(
lambda: mw.col.card_ids_of_note(note_id),
lambda future: bury_cards(mw=mw, card_ids=future.result(), success=success),
)
def rebuild_filtered_deck(*, mw: AnkiQt, deck_id: DeckId) -> None: def rebuild_filtered_deck(
mw.perform_op(lambda: mw.col.sched.rebuild_filtered_deck(deck_id)) *, parent: QWidget, deck_id: DeckId
) -> CollectionOp[OpChangesWithCount]:
return CollectionOp(parent, lambda col: col.sched.rebuild_filtered_deck(deck_id))
def empty_filtered_deck(*, mw: AnkiQt, deck_id: DeckId) -> None: def empty_filtered_deck(*, parent: QWidget, deck_id: DeckId) -> CollectionOp[OpChanges]:
mw.perform_op(lambda: mw.col.sched.empty_filtered_deck(deck_id)) return CollectionOp(parent, lambda col: col.sched.empty_filtered_deck(deck_id))
def add_or_update_filtered_deck( def add_or_update_filtered_deck(
*, *,
mw: AnkiQt, parent: QWidget,
deck: FilteredDeckForUpdate, deck: FilteredDeckForUpdate,
success: PerformOpOptionalSuccessCallback, ) -> CollectionOp[OpChangesWithId]:
) -> None: return CollectionOp(parent, lambda col: col.sched.add_or_update_filtered_deck(deck))
mw.perform_op(
lambda: mw.col.sched.add_or_update_filtered_deck(deck),
success=success,
)

View file

@ -119,12 +119,14 @@ class Overview:
return self.mw.col.decks.current()["dyn"] return self.mw.col.decks.current()["dyn"]
def rebuild_current_filtered_deck(self) -> None: def rebuild_current_filtered_deck(self) -> None:
if self._current_deck_is_filtered(): rebuild_filtered_deck(
rebuild_filtered_deck(mw=self.mw, deck_id=self.mw.col.decks.selected()) parent=self.mw, deck_id=self.mw.col.decks.selected()
).run_in_background()
def empty_current_filtered_deck(self) -> None: def empty_current_filtered_deck(self) -> None:
if self._current_deck_is_filtered(): empty_filtered_deck(
empty_filtered_deck(mw=self.mw, deck_id=self.mw.col.decks.selected()) parent=self.mw, deck_id=self.mw.col.decks.selected()
).run_in_background()
def onCustomStudyKey(self) -> None: def onCustomStudyKey(self) -> None:
if not self._current_deck_is_filtered(): if not self._current_deck_is_filtered():

View file

@ -22,7 +22,7 @@ from aqt.operations.card import set_card_flag
from aqt.operations.note import remove_notes from aqt.operations.note import remove_notes
from aqt.operations.scheduling import ( from aqt.operations.scheduling import (
bury_cards, bury_cards,
bury_note, bury_notes,
set_due_date_dialog, set_due_date_dialog,
suspend_cards, suspend_cards,
suspend_note, suspend_note,
@ -861,39 +861,34 @@ time = %(time)d;
return return
set_due_date_dialog( set_due_date_dialog(
mw=self.mw,
parent=self.mw, parent=self.mw,
card_ids=[self.card.id], card_ids=[self.card.id],
config_key=Config.String.SET_DUE_REVIEWER, config_key=Config.String.SET_DUE_REVIEWER,
) ).run_in_background()
def suspend_current_note(self) -> None: def suspend_current_note(self) -> None:
suspend_note( suspend_note(
mw=self.mw, parent=self.mw,
note_id=self.card.nid, note_ids=[self.card.nid],
success=lambda _: tooltip(tr.studying_note_suspended()), ).success(lambda _: tooltip(tr.studying_note_suspended())).run_in_background()
)
def suspend_current_card(self) -> None: def suspend_current_card(self) -> None:
suspend_cards( suspend_cards(
mw=self.mw, parent=self.mw,
card_ids=[self.card.id], card_ids=[self.card.id],
success=lambda _: tooltip(tr.studying_card_suspended()), ).success(lambda _: tooltip(tr.studying_card_suspended())).run_in_background()
)
def bury_current_note(self) -> None: def bury_current_note(self) -> None:
bury_note( bury_notes(
mw=self.mw, parent=self.mw,
note_id=self.card.nid, note_ids=[self.card.nid],
success=lambda _: tooltip(tr.studying_note_buried()), ).success(lambda _: tooltip(tr.studying_note_buried())).run_in_background()
)
def bury_current_card(self) -> None: def bury_current_card(self) -> None:
bury_cards( bury_cards(
mw=self.mw, parent=self.mw,
card_ids=[self.card.id], card_ids=[self.card.id],
success=lambda _: tooltip(tr.studying_card_buried()), ).success(lambda _: tooltip(tr.studying_card_buried())).run_in_background()
)
def delete_current_note(self) -> None: def delete_current_note(self) -> None:
# need to check state because the shortcut is global to the main # need to check state because the shortcut is global to the main

View file

@ -120,7 +120,7 @@ service SchedulingService {
rpc CongratsInfo(Empty) returns (CongratsInfoOut); rpc CongratsInfo(Empty) returns (CongratsInfoOut);
rpc RestoreBuriedAndSuspendedCards(CardIds) returns (OpChanges); rpc RestoreBuriedAndSuspendedCards(CardIds) returns (OpChanges);
rpc UnburyCardsInCurrentDeck(UnburyCardsInCurrentDeckIn) returns (Empty); rpc UnburyCardsInCurrentDeck(UnburyCardsInCurrentDeckIn) returns (Empty);
rpc BuryOrSuspendCards(BuryOrSuspendCardsIn) returns (OpChanges); rpc BuryOrSuspendCards(BuryOrSuspendCardsIn) returns (OpChangesWithCount);
rpc EmptyFilteredDeck(DeckId) returns (OpChanges); rpc EmptyFilteredDeck(DeckId) returns (OpChanges);
rpc RebuildFilteredDeck(DeckId) returns (OpChangesWithCount); rpc RebuildFilteredDeck(DeckId) returns (OpChangesWithCount);
rpc ScheduleCardsAsNew(ScheduleCardsAsNewIn) returns (OpChanges); rpc ScheduleCardsAsNew(ScheduleCardsAsNewIn) returns (OpChanges);
@ -1314,7 +1314,8 @@ message BuryOrSuspendCardsIn {
BURY_USER = 2; BURY_USER = 2;
} }
repeated int64 card_ids = 1; repeated int64 card_ids = 1;
Mode mode = 2; repeated int64 note_ids = 2;
Mode mode = 3;
} }
message ScheduleCardsAsNewIn { message ScheduleCardsAsNewIn {

View file

@ -131,7 +131,7 @@ impl NotesService for Backend {
fn cards_of_note(&self, input: pb::NoteId) -> Result<pb::CardIds> { fn cards_of_note(&self, input: pb::NoteId) -> Result<pb::CardIds> {
self.with_col(|col| { self.with_col(|col| {
col.storage col.storage
.all_card_ids_of_note(NoteId(input.nid)) .all_card_ids_of_note_in_order(NoteId(input.nid))
.map(|v| pb::CardIds { .map(|v| pb::CardIds {
cids: v.into_iter().map(Into::into).collect(), cids: v.into_iter().map(Into::into).collect(),
}) })

View file

@ -87,10 +87,18 @@ impl SchedulingService for Backend {
}) })
} }
fn bury_or_suspend_cards(&self, input: pb::BuryOrSuspendCardsIn) -> Result<pb::OpChanges> { fn bury_or_suspend_cards(
&self,
input: pb::BuryOrSuspendCardsIn,
) -> Result<pb::OpChangesWithCount> {
self.with_col(|col| { self.with_col(|col| {
let mode = input.mode(); let mode = input.mode();
let cids: Vec<_> = input.card_ids.into_iter().map(CardId).collect(); let cids = if input.card_ids.is_empty() {
col.storage
.card_ids_of_notes(&input.note_ids.into_newtype(NoteId))?
} else {
input.card_ids.into_newtype(CardId)
};
col.bury_or_suspend_cards(&cids, mode).map(Into::into) col.bury_or_suspend_cards(&cids, mode).map(Into::into)
}) })
} }
@ -105,7 +113,7 @@ impl SchedulingService for Backend {
fn schedule_cards_as_new(&self, input: pb::ScheduleCardsAsNewIn) -> Result<pb::OpChanges> { fn schedule_cards_as_new(&self, input: pb::ScheduleCardsAsNewIn) -> Result<pb::OpChanges> {
self.with_col(|col| { self.with_col(|col| {
let cids: Vec<_> = input.card_ids.into_iter().map(CardId).collect(); let cids = input.card_ids.into_newtype(CardId);
let log = input.log; let log = input.log;
col.reschedule_cards_as_new(&cids, log).map(Into::into) col.reschedule_cards_as_new(&cids, log).map(Into::into)
}) })
@ -114,12 +122,12 @@ impl SchedulingService for Backend {
fn set_due_date(&self, input: pb::SetDueDateIn) -> Result<pb::OpChanges> { fn set_due_date(&self, input: pb::SetDueDateIn) -> Result<pb::OpChanges> {
let config = input.config_key.map(Into::into); let config = input.config_key.map(Into::into);
let days = input.days; let days = input.days;
let cids: Vec<_> = input.card_ids.into_iter().map(CardId).collect(); let cids = input.card_ids.into_newtype(CardId);
self.with_col(|col| col.set_due_date(&cids, &days, config).map(Into::into)) self.with_col(|col| col.set_due_date(&cids, &days, config).map(Into::into))
} }
fn sort_cards(&self, input: pb::SortCardsIn) -> Result<pb::OpChangesWithCount> { fn sort_cards(&self, input: pb::SortCardsIn) -> Result<pb::OpChangesWithCount> {
let cids: Vec<_> = input.card_ids.into_iter().map(CardId).collect(); let cids = input.card_ids.into_newtype(CardId);
let (start, step, random, shift) = ( let (start, step, random, shift) = (
input.starting_from, input.starting_from,
input.step_size, input.step_size,

View file

@ -1,6 +1,7 @@
// 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
pub(crate) use crate::types::IntoNewtypeVec;
pub use crate::{ pub use crate::{
card::{Card, CardId}, card::{Card, CardId},
collection::Collection, collection::Collection,

View file

@ -33,7 +33,7 @@ mod test {
let queued = col.next_card()?.unwrap(); let queued = col.next_card()?.unwrap();
let nid = note.id; let nid = note.id;
let cid = queued.card.id; let cid = queued.card.id;
let sibling_cid = col.storage.all_card_ids_of_note(nid)?[1]; let sibling_cid = col.storage.all_card_ids_of_note_in_order(nid)?[1];
let assert_initial_state = |col: &mut Collection| -> Result<()> { let assert_initial_state = |col: &mut Collection| -> Result<()> {
let first = col.storage.get_card(cid)?.unwrap(); let first = col.storage.get_card(cid)?.unwrap();

View file

@ -89,7 +89,8 @@ impl Collection {
/// Bury/suspend cards in search table, and clear it. /// Bury/suspend cards in search table, and clear it.
/// Marks the cards as modified. /// Marks the cards as modified.
fn bury_or_suspend_searched_cards(&mut self, mode: BuryOrSuspendMode) -> Result<()> { fn bury_or_suspend_searched_cards(&mut self, mode: BuryOrSuspendMode) -> Result<usize> {
let mut count = 0;
let usn = self.usn()?; let usn = self.usn()?;
let sched = self.scheduler_version(); let sched = self.scheduler_version();
@ -113,18 +114,21 @@ impl Collection {
card.remove_from_learning(); card.remove_from_learning();
} }
card.queue = desired_queue; card.queue = desired_queue;
count += 1;
self.update_card_inner(&mut card, original, usn)?; self.update_card_inner(&mut card, original, usn)?;
} }
} }
self.storage.clear_searched_cards_table() self.storage.clear_searched_cards_table()?;
Ok(count)
} }
pub fn bury_or_suspend_cards( pub fn bury_or_suspend_cards(
&mut self, &mut self,
cids: &[CardId], cids: &[CardId],
mode: BuryOrSuspendMode, mode: BuryOrSuspendMode,
) -> Result<OpOutput<()>> { ) -> Result<OpOutput<usize>> {
let op = match mode { let op = match mode {
BuryOrSuspendMode::Suspend => Op::Suspend, BuryOrSuspendMode::Suspend => Op::Suspend,
BuryOrSuspendMode::BurySched | BuryOrSuspendMode::BuryUser => Op::Bury, BuryOrSuspendMode::BurySched | BuryOrSuspendMode::BuryUser => Op::Bury,
@ -141,7 +145,7 @@ impl Collection {
nid: NoteId, nid: NoteId,
include_new: bool, include_new: bool,
include_reviews: bool, include_reviews: bool,
) -> Result<()> { ) -> Result<usize> {
self.storage self.storage
.search_siblings_for_bury(cid, nid, include_new, include_reviews)?; .search_siblings_for_bury(cid, nid, include_new, include_reviews)?;
self.bury_or_suspend_searched_cards(BuryOrSuspendMode::BurySched) self.bury_or_suspend_searched_cards(BuryOrSuspendMode::BurySched)

View file

@ -308,13 +308,26 @@ impl super::SqliteStorage {
.collect() .collect()
} }
pub(crate) fn all_card_ids_of_note(&self, nid: NoteId) -> Result<Vec<CardId>> { pub(crate) fn all_card_ids_of_note_in_order(&self, nid: NoteId) -> Result<Vec<CardId>> {
self.db self.db
.prepare_cached("select id from cards where nid = ? order by ord")? .prepare_cached("select id from cards where nid = ? order by ord")?
.query_and_then(&[nid], |r| Ok(CardId(r.get(0)?)))? .query_and_then(&[nid], |r| Ok(CardId(r.get(0)?)))?
.collect() .collect()
} }
pub(crate) fn card_ids_of_notes(&self, nids: &[NoteId]) -> Result<Vec<CardId>> {
let mut stmt = self
.db
.prepare_cached("select id from cards where nid = ?")?;
let mut cids = vec![];
for nid in nids {
for cid in stmt.query_map(&[nid], |row| row.get(0))? {
cids.push(cid?);
}
}
Ok(cids)
}
/// Place matching card ids into the search table. /// Place matching card ids into the search table.
pub(crate) fn search_siblings_for_bury( pub(crate) fn search_siblings_for_bury(
&self, &self,

View file

@ -68,3 +68,18 @@ macro_rules! define_newtype {
} }
define_newtype!(Usn, i32); define_newtype!(Usn, i32);
pub(crate) trait IntoNewtypeVec {
fn into_newtype<F, T>(self, func: F) -> Vec<T>
where
F: FnMut(i64) -> T;
}
impl IntoNewtypeVec for Vec<i64> {
fn into_newtype<F, T>(self, func: F) -> Vec<T>
where
F: FnMut(i64) -> T,
{
self.into_iter().map(func).collect()
}
}