diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index f6ba018ab..703d06461 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -21,7 +21,7 @@ import anki.template from anki import hooks from anki._backend import RustBackend from anki.cards import Card -from anki.config import ConfigManager +from anki.config import Config, ConfigManager from anki.consts import * from anki.dbproxy import DBProxy from anki.decks import DeckManager @@ -49,7 +49,6 @@ from anki.utils import ( SearchNode = _pb.SearchNode SearchJoiner = Literal["AND", "OR"] Progress = _pb.Progress -Config = _pb.Config EmptyCardsReport = _pb.EmptyCardsReport GraphPreferences = _pb.GraphPreferences BuiltinSort = _pb.SortOrder.Builtin diff --git a/pylib/anki/config.py b/pylib/anki/config.py index e77e3e7a8..9a194645e 100644 --- a/pylib/anki/config.py +++ b/pylib/anki/config.py @@ -24,9 +24,12 @@ from typing import Any from weakref import ref import anki +from anki._backend import backend_pb2 as _pb from anki.errors import NotFoundError from anki.utils import from_json_bytes, to_json_bytes +Config = _pb.Config + class ConfigManager: def __init__(self, col: anki.collection.Collection): diff --git a/pylib/anki/scheduler/base.py b/pylib/anki/scheduler/base.py index 401d79834..f8c05392b 100644 --- a/pylib/anki/scheduler/base.py +++ b/pylib/anki/scheduler/base.py @@ -5,6 +5,7 @@ from __future__ import annotations import anki import anki._backend.backend_pb2 as _pb +from anki.config import Config SchedTimingToday = _pb.SchedTimingTodayOut @@ -129,10 +130,20 @@ select id from cards where did in %s and queue = {QUEUE_TYPE_REV} and due <= ? l "Put cards at the end of the new queue." self.col._backend.schedule_cards_as_new(card_ids=card_ids, log=True) - def set_due_date(self, card_ids: List[int], days: str) -> None: + def set_due_date( + self, + card_ids: List[int], + days: str, + config_key: Optional[Config.String.Key.V] = None, + ) -> None: """Set cards to be due in `days`, turning them into review cards if necessary. - `days` can be of the form '5' or '5..7'""" - self.col._backend.set_due_date(card_ids=card_ids, days=days) + `days` can be of the form '5' or '5..7' + If `config_key` is provided, provided days will be remembered in config.""" + if config_key: + key = Config.String(key=config_key) + else: + key = None + self.col._backend.set_due_date(card_ids=card_ids, days=days, config_key=key) def resetCards(self, ids: List[int]) -> None: "Completely reset cards for export." diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index 3d53e290c..c65636f51 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -1349,6 +1349,7 @@ where id in %s""" def _after_schedule(self) -> None: self.model.reset() + # updates undo status self.mw.requireReset(reason=ResetReason.BrowserReschedule, context=self) @save_editor @@ -1357,7 +1358,7 @@ where id in %s""" mw=self.mw, parent=self, card_ids=self.selectedCards(), - default_key=Config.String.SET_DUE_BROWSER, + config_key=Config.String.SET_DUE_BROWSER, on_done=self._after_schedule, ) diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index 4d3ba62dc..420293ea3 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -809,7 +809,7 @@ time = %(time)d; mw=self.mw, parent=self.mw, card_ids=[self.card.id], - default_key=Config.String.SET_DUE_REVIEWER, + config_key=Config.String.SET_DUE_REVIEWER, on_done=self.mw.reset, ) diff --git a/qt/aqt/scheduling.py b/qt/aqt/scheduling.py index 7366fa02e..cd9f511aa 100644 --- a/qt/aqt/scheduling.py +++ b/qt/aqt/scheduling.py @@ -4,7 +4,7 @@ from __future__ import annotations from concurrent.futures import Future -from typing import List +from typing import List, Optional import aqt from anki.collection import Config @@ -18,20 +18,19 @@ def set_due_date_dialog( mw: aqt.AnkiQt, parent: QDialog, card_ids: List[int], - default_key: Config.String.Key.V, + config_key: Optional[Config.String.Key.V], on_done: Callable[[], None], ) -> None: if not card_ids: return - default = mw.col.get_config_string(default_key) + default = mw.col.get_config_string(config_key) if config_key else "" prompt = "\n".join( [ tr(TR.SCHEDULING_SET_DUE_DATE_PROMPT, cards=len(card_ids)), tr(TR.SCHEDULING_SET_DUE_DATE_PROMPT_HINT), ] ) - (days, success) = getText( prompt=prompt, parent=parent, @@ -42,9 +41,7 @@ def set_due_date_dialog( return def set_due() -> None: - mw.col.sched.set_due_date(card_ids, days) - if days != default: - mw.col.set_config_string(default_key, days) + mw.col.sched.set_due_date(card_ids, days, config_key) def after_set(fut: Future) -> None: try: diff --git a/rslib/backend.proto b/rslib/backend.proto index a18f34dc6..7ffc67010 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -1273,6 +1273,7 @@ message ScheduleCardsAsNewIn { message SetDueDateIn { repeated int64 card_ids = 1; string days = 2; + Config.String config_key = 3; } message SortCardsIn { diff --git a/rslib/src/backend/config.rs b/rslib/src/backend/config.rs index 965be7e6d..58ae16984 100644 --- a/rslib/src/backend/config.rs +++ b/rslib/src/backend/config.rs @@ -43,6 +43,12 @@ impl From for StringKey { } } +impl From for StringKey { + fn from(key: pb::config::String) -> Self { + key.key().into() + } +} + impl ConfigService for Backend { fn get_config_json(&self, input: pb::String) -> Result { self.with_col(|col| { diff --git a/rslib/src/backend/scheduler/mod.rs b/rslib/src/backend/scheduler/mod.rs index 63ffce7e5..be9405edd 100644 --- a/rslib/src/backend/scheduler/mod.rs +++ b/rslib/src/backend/scheduler/mod.rs @@ -10,7 +10,6 @@ use crate::{ prelude::*, scheduler::{ new::NewCardSortOrder, - parse_due_date_str, states::{CardState, NextCardStates}, }, stats::studied_today, @@ -113,9 +112,10 @@ impl SchedulingService for Backend { } fn set_due_date(&self, input: pb::SetDueDateIn) -> Result { + let config = input.config_key.map(Into::into); + let days = input.days; let cids: Vec<_> = input.card_ids.into_iter().map(CardID).collect(); - let spec = parse_due_date_str(&input.days)?; - self.with_col(|col| col.set_due_date(&cids, spec).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 { diff --git a/rslib/src/scheduler/reviews.rs b/rslib/src/scheduler/reviews.rs index 1f540740b..04a2e469c 100644 --- a/rslib/src/scheduler/reviews.rs +++ b/rslib/src/scheduler/reviews.rs @@ -4,9 +4,11 @@ use crate::{ card::{Card, CardID, CardQueue, CardType}, collection::Collection, + config::StringKey, deckconf::INITIAL_EASE_FACTOR_THOUSANDS, err::Result, prelude::AnkiError, + undo::UndoableOpKind, }; use lazy_static::lazy_static; use rand::distributions::{Distribution, Uniform}; @@ -84,12 +86,21 @@ pub fn parse_due_date_str(s: &str) -> Result { } impl Collection { - pub fn set_due_date(&mut self, cids: &[CardID], spec: DueDateSpecifier) -> Result<()> { + /// `days` should be in a format parseable by `parse_due_date_str`. + /// If `context` is provided, provided key will be updated with the new + /// value of `days`. + pub fn set_due_date( + &mut self, + cids: &[CardID], + days: &str, + context: Option, + ) -> Result<()> { + let spec = parse_due_date_str(days)?; let usn = self.usn()?; let today = self.timing_today()?.days_elapsed; let mut rng = rand::thread_rng(); let distribution = Uniform::from(spec.min..=spec.max); - self.transact(None, |col| { + self.transact(Some(UndoableOpKind::SetDueDate), |col| { col.storage.set_search_table_to_card_ids(cids, false)?; for mut card in col.storage.all_searched_cards()? { let original = card.clone(); @@ -99,6 +110,9 @@ impl Collection { col.update_card_inner(&mut card, &original, usn)?; } col.storage.clear_searched_cards_table()?; + if let Some(key) = context { + col.set_string(key, days)?; + } Ok(()) }) } diff --git a/rslib/src/undo/ops.rs b/rslib/src/undo/ops.rs index 23d9019d3..d006101ed 100644 --- a/rslib/src/undo/ops.rs +++ b/rslib/src/undo/ops.rs @@ -12,6 +12,7 @@ pub enum UndoableOpKind { RemoveDeck, RemoveNote, RenameDeck, + SetDueDate, Suspend, UnburyUnsuspend, UpdateCard, @@ -37,6 +38,7 @@ impl Collection { UndoableOpKind::RemoveDeck => TR::DecksDeleteDeck, UndoableOpKind::RemoveNote => TR::StudyingDeleteNote, UndoableOpKind::RenameDeck => TR::ActionsRenameDeck, + UndoableOpKind::SetDueDate => TR::ActionsSetDueDate, UndoableOpKind::Suspend => TR::StudyingSuspend, UndoableOpKind::UnburyUnsuspend => TR::UndoUnburyUnsuspend, UndoableOpKind::UpdateCard => TR::UndoUpdateCard,