From f434cff36fd0b58d6a6b288efaa960c9f397457d Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 8 Feb 2021 14:10:05 +1000 Subject: [PATCH] remember last input for 'set due'; add string config; nest config types --- Cargo.toml | 3 ++- pylib/anki/collection.py | 13 +++++++++--- pylib/tests/test_find.py | 4 ++-- qt/.pylintrc | 4 +++- qt/aqt/browser.py | 12 +++++------ qt/aqt/previewer.py | 6 +++--- qt/aqt/reviewer.py | 3 ++- qt/aqt/scheduling.py | 16 +++++++++----- qt/aqt/sidebar.py | 18 ++++++++-------- rslib/backend.proto | 45 +++++++++++++++++++++++++++------------- rslib/src/backend/mod.rs | 15 +++++++++++++- rslib/src/config.rs | 33 +++++++++++++++++++++++++++-- 12 files changed, 124 insertions(+), 48 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4f0dd4f81..f4a2c2a74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,8 @@ license = "AGPL-3.0-or-later" members = ["rslib", "pylib/rsbridge"] [lib] -name = "dummy" +# dummy top level for tooling +name = "anki" path = "src/lib.rs" [package.metadata.raze] diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index c583c0ab2..b8c0cd51f 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -50,7 +50,7 @@ MediaSyncProgress = _pb.MediaSyncProgress FullSyncProgress = _pb.FullSyncProgress NormalSyncProgress = _pb.NormalSyncProgress DatabaseCheckProgress = _pb.DatabaseCheckProgress -ConfigBool = _pb.ConfigBool +Config = _pb.Config EmptyCardsReport = _pb.EmptyCardsReport NoteWithEmptyCards = _pb.NoteWithEmptyCards GraphPreferences = _pb.GraphPreferences @@ -586,13 +586,20 @@ class Collection: "This is a debugging aid. Prefer .get_config() when you know the key you need." return from_json_bytes(self._backend.get_all_config()) - def get_config_bool(self, key: ConfigBool.Key.V) -> bool: + def get_config_bool(self, key: Config.Bool.Key.V) -> bool: return self._backend.get_config_bool(key) - def set_config_bool(self, key: ConfigBool.Key.V, value: bool) -> None: + def set_config_bool(self, key: Config.Bool.Key.V, value: bool) -> None: self.setMod() self._backend.set_config_bool(key=key, value=value) + def get_config_string(self, key: Config.String.Key.V) -> str: + return self._backend.get_config_string(key) + + def set_config_string(self, key: Config.String.Key.V, value: str) -> None: + self.setMod() + self._backend.set_config_string(key=key, value=value) + # Stats ########################################################################## diff --git a/pylib/tests/test_find.py b/pylib/tests/test_find.py index 264f2b3db..0c23b0117 100644 --- a/pylib/tests/test_find.py +++ b/pylib/tests/test_find.py @@ -1,7 +1,7 @@ # coding: utf-8 import pytest -from anki.collection import BuiltinSort, ConfigBool +from anki.collection import BuiltinSort, Config from anki.consts import * from tests.shared import getEmptyCol, isNearCutoff @@ -121,7 +121,7 @@ def test_findCards(): col.flush() assert col.findCards("", order=True)[-1] in latestCardIds assert col.findCards("", order=True)[0] == firstCardId - col.set_config_bool(ConfigBool.BROWSER_SORT_BACKWARDS, True) + col.set_config_bool(Config.Bool.BROWSER_SORT_BACKWARDS, True) col.flush() assert col.findCards("", order=True)[0] in latestCardIds assert ( diff --git a/qt/.pylintrc b/qt/.pylintrc index 206cb6966..9084c2f85 100644 --- a/qt/.pylintrc +++ b/qt/.pylintrc @@ -5,7 +5,9 @@ ignore = forms,hooks_gen.py [TYPECHECK] ignored-modules=win32file,pywintypes,socket,win32pipe,winrt,pyaudio -ignored-classes=SearchTerm,ConfigBool +ignored-classes= + SearchTerm, + Config, [REPORTS] output-format=colorized diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index 391c65eb2..e01bb8686 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -13,7 +13,7 @@ from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union, import aqt import aqt.forms from anki.cards import Card -from anki.collection import Collection, ConfigBool, SearchTerm +from anki.collection import Collection, Config, SearchTerm from anki.consts import * from anki.errors import InvalidInput from anki.lang import without_unicode_isolation @@ -852,13 +852,13 @@ QTableView {{ gridline-color: {grid} }} # default to descending for non-text fields if type == "noteFld": ord = not ord - self.col.set_config_bool(ConfigBool.BROWSER_SORT_BACKWARDS, ord) + self.col.set_config_bool(Config.Bool.BROWSER_SORT_BACKWARDS, ord) self.col.setMod() self.col.save() self.search() else: - if self.col.get_config_bool(ConfigBool.BROWSER_SORT_BACKWARDS) != ord: - self.col.set_config_bool(ConfigBool.BROWSER_SORT_BACKWARDS, ord) + if self.col.get_config_bool(Config.Bool.BROWSER_SORT_BACKWARDS) != ord: + self.col.set_config_bool(Config.Bool.BROWSER_SORT_BACKWARDS, ord) self.col.setMod() self.col.save() self.model.reverse() @@ -871,7 +871,7 @@ QTableView {{ gridline-color: {grid} }} hh.setSortIndicatorShown(False) return idx = self.model.activeCols.index(type) - if self.col.get_config_bool(ConfigBool.BROWSER_SORT_BACKWARDS): + if self.col.get_config_bool(Config.Bool.BROWSER_SORT_BACKWARDS): ord = Qt.DescendingOrder else: ord = Qt.AscendingOrder @@ -1399,7 +1399,7 @@ where id in %s""" mw=self.mw, parent=self, card_ids=self.selectedCards(), - default="0", + default_key=Config.String.SET_DUE_BROWSER, on_done=self._after_schedule, ) ) diff --git a/qt/aqt/previewer.py b/qt/aqt/previewer.py index 983e07aff..fc5718c6d 100644 --- a/qt/aqt/previewer.py +++ b/qt/aqt/previewer.py @@ -7,7 +7,7 @@ import time from typing import Any, Callable, Optional, Tuple, Union from anki.cards import Card -from anki.collection import ConfigBool +from anki.collection import Config from aqt import AnkiQt, gui_hooks from aqt.qt import ( QAbstractItemView, @@ -94,7 +94,7 @@ class Previewer(QDialog): both_sides_button.setToolTip(tr(TR.ACTIONS_SHORTCUT_KEY, val="B")) self.bbox.addButton(both_sides_button, QDialogButtonBox.ActionRole) self._show_both_sides = self.mw.col.get_config_bool( - ConfigBool.PREVIEW_BOTH_SIDES + Config.Bool.PREVIEW_BOTH_SIDES ) both_sides_button.setChecked(self._show_both_sides) qconnect(both_sides_button.toggled, self._on_show_both_sides) @@ -221,7 +221,7 @@ class Previewer(QDialog): def _on_show_both_sides(self, toggle: bool) -> None: self._show_both_sides = toggle - self.mw.col.set_config_bool(ConfigBool.PREVIEW_BOTH_SIDES, toggle) + self.mw.col.set_config_bool(Config.Bool.PREVIEW_BOTH_SIDES, toggle) self.mw.col.setMod() if self._state == "answer" and not toggle: self._state = "question" diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index dc2b71a15..253503e36 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -14,6 +14,7 @@ from PyQt5.QtCore import Qt from anki import hooks from anki.cards import Card +from anki.collection import Config from anki.utils import stripHTML from aqt import AnkiQt, gui_hooks from aqt.profiles import VideoDriver @@ -809,7 +810,7 @@ time = %(time)d; mw=self.mw, parent=self.mw, card_ids=[self.card.id], - default="1", + default_key=Config.String.SET_DUE_REVIEWER, on_done=self.mw.reset, ) diff --git a/qt/aqt/scheduling.py b/qt/aqt/scheduling.py index 7c57bcd05..bc753d21c 100644 --- a/qt/aqt/scheduling.py +++ b/qt/aqt/scheduling.py @@ -7,6 +7,7 @@ from concurrent.futures import Future from typing import List import aqt +from anki.collection import Config from anki.errors import InvalidInput from anki.lang import TR from aqt.qt import * @@ -18,12 +19,14 @@ def set_due_date_dialog( mw: aqt.AnkiQt, parent: QDialog, card_ids: List[int], - default: str, + default_key: Config.String.Key.V, on_done: Callable[[], None], ) -> None: if not card_ids: return + default = mw.col.get_config_string(default_key) + (days, success) = getText( prompt=tr(TR.SCHEDULING_SET_DUE_DATE_PROMPT, cards=len(card_ids)), parent=parent, @@ -33,7 +36,12 @@ def set_due_date_dialog( if not success or not days.strip(): return - def on_done_wrapper(fut: Future) -> None: + def set_due() -> None: + mw.col.sched.set_due_date(card_ids, days) + if days != default: + mw.col.set_config_string(default_key, days) + + def after_set(fut: Future) -> None: try: fut.result() except Exception as e: @@ -53,9 +61,7 @@ def set_due_date_dialog( on_done() mw.checkpoint(tr(TR.ACTIONS_SET_DUE_DATE)) - mw.taskman.with_progress( - lambda: mw.col.sched.set_due_date(card_ids, days), on_done_wrapper - ) + mw.taskman.with_progress(set_due, after_set) def forget_cards( diff --git a/qt/aqt/sidebar.py b/qt/aqt/sidebar.py index e67c60f45..ee0b9c11c 100644 --- a/qt/aqt/sidebar.py +++ b/qt/aqt/sidebar.py @@ -9,7 +9,7 @@ from enum import Enum, auto from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple, cast import aqt -from anki.collection import ConfigBool, SearchTerm +from anki.collection import Config, SearchTerm from anki.decks import DeckTreeNode from anki.errors import DeckRenameError, InvalidInput from anki.tags import TagTreeNode @@ -526,7 +526,7 @@ class SidebarTreeView(QTreeView): root: SidebarItem, name: TR.V, icon: Union[str, ColoredIcon], - collapse_key: ConfigBool.Key.V, + collapse_key: Config.Bool.Key.V, type: Optional[SidebarItemType] = None, ) -> SidebarItem: def update(expanded: bool) -> None: @@ -557,7 +557,7 @@ class SidebarTreeView(QTreeView): root=root, name=TR.BROWSING_SIDEBAR_SAVED_SEARCHES, icon=icon, - collapse_key=ConfigBool.COLLAPSE_SAVED_SEARCHES, + collapse_key=Config.Bool.COLLAPSE_SAVED_SEARCHES, type=SidebarItemType.SAVED_SEARCH_ROOT, ) @@ -584,7 +584,7 @@ class SidebarTreeView(QTreeView): root=root, name=TR.BROWSING_SIDEBAR_RECENT, icon=icon, - collapse_key=ConfigBool.COLLAPSE_RECENT, + collapse_key=Config.Bool.COLLAPSE_RECENT, type=SidebarItemType.FLAG_ROOT, ) type = SidebarItemType.FLAG @@ -646,7 +646,7 @@ class SidebarTreeView(QTreeView): root=root, name=TR.BROWSING_SIDEBAR_CARD_STATE, icon=icon, - collapse_key=ConfigBool.COLLAPSE_CARD_STATE, + collapse_key=Config.Bool.COLLAPSE_CARD_STATE, type=SidebarItemType.CARD_STATE_ROOT, ) type = SidebarItemType.CARD_STATE @@ -693,7 +693,7 @@ class SidebarTreeView(QTreeView): root=root, name=TR.BROWSING_SIDEBAR_FLAGS, icon=icon, - collapse_key=ConfigBool.COLLAPSE_FLAGS, + collapse_key=Config.Bool.COLLAPSE_FLAGS, type=SidebarItemType.FLAG_ROOT, ) type = SidebarItemType.FLAG @@ -771,7 +771,7 @@ class SidebarTreeView(QTreeView): root=root, name=TR.BROWSING_SIDEBAR_TAGS, icon=icon, - collapse_key=ConfigBool.COLLAPSE_TAGS, + collapse_key=Config.Bool.COLLAPSE_TAGS, type=SidebarItemType.TAG_ROOT, ) render(root, tree.children) @@ -810,7 +810,7 @@ class SidebarTreeView(QTreeView): root=root, name=TR.BROWSING_SIDEBAR_DECKS, icon=icon, - collapse_key=ConfigBool.COLLAPSE_DECKS, + collapse_key=Config.Bool.COLLAPSE_DECKS, type=SidebarItemType.DECK_ROOT, ) render(root, tree.children) @@ -824,7 +824,7 @@ class SidebarTreeView(QTreeView): root=root, name=TR.BROWSING_SIDEBAR_NOTETYPES, icon=icon, - collapse_key=ConfigBool.COLLAPSE_NOTETYPES, + collapse_key=Config.Bool.COLLAPSE_NOTETYPES, type=SidebarItemType.NOTETYPE_ROOT, ) diff --git a/rslib/backend.proto b/rslib/backend.proto index 513e2b2d1..9c72f8997 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -228,8 +228,10 @@ service BackendService { rpc SetConfigJson(SetConfigJsonIn) returns (Empty); rpc RemoveConfig(String) returns (Empty); rpc GetAllConfig(Empty) returns (Json); - rpc GetConfigBool(ConfigBool) returns (Bool); + rpc GetConfigBool(Config.Bool) returns (Bool); rpc SetConfigBool(SetConfigBoolIn) returns (Empty); + rpc GetConfigString(Config.String) returns (String); + rpc SetConfigString(SetConfigStringIn) returns (Empty); // preferences @@ -1216,22 +1218,37 @@ message SetDeckIn { int64 deck_id = 2; } -message ConfigBool { - enum Key { - BROWSER_SORT_BACKWARDS = 0; - PREVIEW_BOTH_SIDES = 1; - COLLAPSE_TAGS = 2; - COLLAPSE_NOTETYPES = 3; - COLLAPSE_DECKS = 4; - COLLAPSE_SAVED_SEARCHES = 5; - COLLAPSE_RECENT = 6; - COLLAPSE_CARD_STATE = 7; - COLLAPSE_FLAGS = 8; +message Config { + message Bool { + enum Key { + BROWSER_SORT_BACKWARDS = 0; + PREVIEW_BOTH_SIDES = 1; + COLLAPSE_TAGS = 2; + COLLAPSE_NOTETYPES = 3; + COLLAPSE_DECKS = 4; + COLLAPSE_SAVED_SEARCHES = 5; + COLLAPSE_RECENT = 6; + COLLAPSE_CARD_STATE = 7; + COLLAPSE_FLAGS = 8; + } + Key key = 1; + } + + message String { + enum Key { + SET_DUE_BROWSER = 0; + SET_DUE_REVIEWER = 1; + } + Key key = 1; } - Key key = 1; } message SetConfigBoolIn { - ConfigBool.Key key = 1; + Config.Bool.Key key = 1; bool value = 2; } + +message SetConfigStringIn { + Config.String.Key key = 1; + string value = 2; +} diff --git a/rslib/src/backend/mod.rs b/rslib/src/backend/mod.rs index be87953fd..4ceee747a 100644 --- a/rslib/src/backend/mod.rs +++ b/rslib/src/backend/mod.rs @@ -1493,7 +1493,7 @@ impl BackendService for Backend { .map(Into::into) } - fn get_config_bool(&self, input: pb::ConfigBool) -> BackendResult { + fn get_config_bool(&self, input: pb::config::Bool) -> BackendResult { self.with_col(|col| { Ok(pb::Bool { val: col.get_bool(input), @@ -1505,6 +1505,19 @@ impl BackendService for Backend { self.with_col(|col| col.transact(None, |col| col.set_bool(input))) .map(Into::into) } + + fn get_config_string(&self, input: pb::config::String) -> BackendResult { + self.with_col(|col| { + Ok(pb::String { + val: col.get_string(input), + }) + }) + } + + fn set_config_string(&self, input: pb::SetConfigStringIn) -> BackendResult { + self.with_col(|col| col.transact(None, |col| col.set_string(input))) + .map(Into::into) + } } impl Backend { diff --git a/rslib/src/config.rs b/rslib/src/config.rs index dac2d56d4..beea09585 100644 --- a/rslib/src/config.rs +++ b/rslib/src/config.rs @@ -5,7 +5,8 @@ use crate::{ backend_proto as pb, collection::Collection, decks::DeckID, err::Result, notetype::NoteTypeID, timestamp::TimestampSecs, }; -use pb::config_bool::Key as BoolKey; +use pb::config::bool::Key as BoolKey; +use pb::config::string::Key as StringKey; use serde::{de::DeserializeOwned, Serialize}; use serde_aux::field_attributes::deserialize_bool_from_anything; use serde_derive::Deserialize; @@ -63,6 +64,8 @@ pub(crate) enum ConfigKey { PreviewBothSides, Rollover, SchedulerVersion, + SetDueBrowser, + SetDueReviewer, ShowDayLearningCardsFirst, ShowIntervalsAboveAnswerButtons, ShowRemainingDueCountsInStudy, @@ -102,6 +105,8 @@ impl From for &'static str { ConfigKey::PreviewBothSides => "previewBothSides", ConfigKey::Rollover => "rollover", ConfigKey::SchedulerVersion => "schedVer", + ConfigKey::SetDueBrowser => "setDueBrowser", + ConfigKey::SetDueReviewer => "setDueReviewer", ConfigKey::ShowDayLearningCardsFirst => "dayLearnFirst", ConfigKey::ShowIntervalsAboveAnswerButtons => "estTimes", ConfigKey::ShowRemainingDueCountsInStudy => "dueCounts", @@ -125,6 +130,15 @@ impl From for ConfigKey { } } +impl From for ConfigKey { + fn from(key: StringKey) -> Self { + match key { + StringKey::SetDueBrowser => ConfigKey::SetDueBrowser, + StringKey::SetDueReviewer => ConfigKey::SetDueReviewer, + } + } +} + /// This is a workaround for old clients that used ints to represent boolean /// values. For new config items, prefer using a bool directly. #[derive(Deserialize, Default)] @@ -345,7 +359,7 @@ impl Collection { } #[allow(clippy::match_single_binding)] - pub(crate) fn get_bool(&self, config: pb::ConfigBool) -> bool { + pub(crate) fn get_bool(&self, config: pb::config::Bool) -> bool { match config.key() { // all options default to false at the moment other => self.get_config_default(ConfigKey::from(other)), @@ -355,6 +369,21 @@ impl Collection { pub(crate) fn set_bool(&self, input: pb::SetConfigBoolIn) -> Result<()> { self.set_config(ConfigKey::from(input.key()), &input.value) } + + pub(crate) fn get_string(&self, config: pb::config::String) -> String { + let key = config.key(); + let default = match key { + StringKey::SetDueBrowser => "0", + StringKey::SetDueReviewer => "1", + // other => "", + }; + self.get_config_optional(ConfigKey::from(key)) + .unwrap_or_else(|| default.to_string()) + } + + pub(crate) fn set_string(&self, input: pb::SetConfigStringIn) -> Result<()> { + self.set_config(ConfigKey::from(input.key()), &input.value) + } } #[derive(Deserialize, PartialEq, Debug, Clone, Copy)]