From 5ff7944a26dde63703b65971c3f83b3e51a0fd67 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Fri, 29 Jan 2021 21:03:19 +1000 Subject: [PATCH] add getter/setter for boolean config values --- pylib/anki/collection.py | 13 ++++++++++++- pylib/anki/rsbackend.py | 3 +++ pylib/tests/test_find.py | 3 ++- qt/aqt/browser.py | 10 +++++----- qt/aqt/previewer.py | 7 +++++-- rslib/backend.proto | 15 +++++++++++++++ rslib/src/backend/mod.rs | 13 +++++++++++++ rslib/src/config.rs | 26 +++++++++++++++++++++++++- 8 files changed, 80 insertions(+), 10 deletions(-) diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index 808fc61b4..e8127a39e 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -12,6 +12,7 @@ import traceback import weakref from typing import TYPE_CHECKING, Any, List, Optional, Sequence, Tuple, Union +import anki.backend_pb2 as pb import anki.find import anki.latex # sets up hook import anki.template @@ -32,16 +33,19 @@ from anki.rsbackend import ( Progress, RustBackend, from_json_bytes, - pb, ) from anki.sched import Scheduler as V1Scheduler from anki.schedv2 import Scheduler as V2Scheduler from anki.tags import TagManager from anki.utils import devMode, ids2str, intTime +ConfigBoolKey = pb.ConfigBool.Key # pylint: disable=no-member + if TYPE_CHECKING: from anki.rsbackend import FormatTimeSpanContextValue, TRValue + ConfigBoolKeyValue = pb.ConfigBool.KeyValue # pylint: disable=no-member + class Collection: sched: Union[V1Scheduler, V2Scheduler] @@ -496,6 +500,13 @@ 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: ConfigBoolKeyValue) -> bool: + return self.backend.get_config_bool(key) + + def set_config_bool(self, key: ConfigBoolKeyValue, value: bool) -> None: + self.setMod() + self.backend.set_config_bool(key=key, value=value) + # Stats ########################################################################## diff --git a/pylib/anki/rsbackend.py b/pylib/anki/rsbackend.py index 0a4c20b03..493272c0b 100644 --- a/pylib/anki/rsbackend.py +++ b/pylib/anki/rsbackend.py @@ -38,6 +38,9 @@ if TYPE_CHECKING: assert anki._rsbridge.buildhash() == anki.buildinfo.buildhash +# FIXME: rather than adding new items here, items intended to be consumed +# by external libraries (eg aqt) should be exported in the module that +# refers to them, eg collection.py SchedTimingToday = pb.SchedTimingTodayOut BuiltinSortKind = pb.BuiltinSearchOrder.BuiltinSortKind BackendCard = pb.Card diff --git a/pylib/tests/test_find.py b/pylib/tests/test_find.py index e82163b48..9cbc5a46b 100644 --- a/pylib/tests/test_find.py +++ b/pylib/tests/test_find.py @@ -1,6 +1,7 @@ # coding: utf-8 import pytest +from anki.collection import ConfigBoolKey from anki.consts import * from anki.rsbackend import BuiltinSortKind from tests.shared import getEmptyCol, isNearCutoff @@ -121,7 +122,7 @@ def test_findCards(): col.flush() assert col.findCards("", order=True)[-1] in latestCardIds assert col.findCards("", order=True)[0] == firstCardId - col.conf["sortBackwards"] = True + col.set_config_bool(ConfigBoolKey.BROWSER_SORT_BACKWARDS, True) col.flush() assert col.findCards("", order=True)[0] in latestCardIds assert ( diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index 47c564ede..9faafa4b2 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -13,7 +13,7 @@ from typing import List, Optional, Sequence, Tuple, cast import aqt import aqt.forms from anki.cards import Card -from anki.collection import Collection +from anki.collection import Collection, ConfigBoolKey from anki.consts import * from anki.lang import without_unicode_isolation from anki.models import NoteType @@ -827,13 +827,13 @@ QTableView {{ gridline-color: {grid} }} # default to descending for non-text fields if type == "noteFld": ord = not ord - self.col.conf["sortBackwards"] = ord + self.col.set_config_bool(ConfigBoolKey.BROWSER_SORT_BACKWARDS, ord) self.col.setMod() self.col.save() self.search() else: - if self.col.conf["sortBackwards"] != ord: - self.col.conf["sortBackwards"] = ord + if self.col.get_config_bool(ConfigBoolKey.BROWSER_SORT_BACKWARDS) != ord: + self.col.set_config_bool(ConfigBoolKey.BROWSER_SORT_BACKWARDS, ord) self.col.setMod() self.col.save() self.model.reverse() @@ -846,7 +846,7 @@ QTableView {{ gridline-color: {grid} }} hh.setSortIndicatorShown(False) return idx = self.model.activeCols.index(type) - if self.col.conf["sortBackwards"]: + if self.col.get_config_bool(ConfigBoolKey.BROWSER_SORT_BACKWARDS): ord = Qt.DescendingOrder else: ord = Qt.AscendingOrder diff --git a/qt/aqt/previewer.py b/qt/aqt/previewer.py index 6a1164bbf..5c5b70d4d 100644 --- a/qt/aqt/previewer.py +++ b/qt/aqt/previewer.py @@ -7,6 +7,7 @@ import time from typing import Any, Callable, Optional, Union from anki.cards import Card +from anki.collection import ConfigBoolKey from aqt import AnkiQt, gui_hooks from aqt.qt import ( QAbstractItemView, @@ -87,7 +88,9 @@ class Previewer(QDialog): both_sides_button.setShortcut(QKeySequence("B")) 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.conf.get("previewBothSides", False) + self._show_both_sides = self.mw.col.get_config_bool( + ConfigBoolKey.PREVIEW_BOTH_SIDES + ) both_sides_button.setChecked(self._show_both_sides) qconnect(both_sides_button.toggled, self._on_show_both_sides) @@ -213,7 +216,7 @@ class Previewer(QDialog): def _on_show_both_sides(self, toggle): self._show_both_sides = toggle - self.mw.col.conf["previewBothSides"] = toggle + self.mw.col.set_config_bool(ConfigBoolKey.PREVIEW_BOTH_SIDES, toggle) self.mw.col.setMod() if self._state == "answer" and not toggle: self._state = "question" diff --git a/rslib/backend.proto b/rslib/backend.proto index 8d8713aa9..9b54978d4 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -220,6 +220,8 @@ service BackendService { rpc SetConfigJson(SetConfigJsonIn) returns (Empty); rpc RemoveConfig(String) returns (Empty); rpc GetAllConfig(Empty) returns (Json); + rpc GetConfigBool(ConfigBool) returns (Bool); + rpc SetConfigBool(SetConfigBoolIn) returns (Empty); // preferences @@ -1186,3 +1188,16 @@ message SetDeckIn { repeated int64 card_ids = 1; int64 deck_id = 2; } + +message ConfigBool { + enum Key { + BROWSER_SORT_BACKWARDS = 0; + PREVIEW_BOTH_SIDES = 1; + } + Key key = 1; +} + +message SetConfigBoolIn { + ConfigBool.Key key = 1; + bool value = 2; +} diff --git a/rslib/src/backend/mod.rs b/rslib/src/backend/mod.rs index 48c4034f7..2faea8785 100644 --- a/rslib/src/backend/mod.rs +++ b/rslib/src/backend/mod.rs @@ -1429,6 +1429,19 @@ impl BackendService for Backend { self.with_col(|col| col.transact(None, |col| col.set_preferences(input))) .map(Into::into) } + + fn get_config_bool(&self, input: pb::ConfigBool) -> BackendResult { + self.with_col(|col| { + Ok(pb::Bool { + val: col.get_bool(input), + }) + }) + } + + fn set_config_bool(&self, input: pb::SetConfigBoolIn) -> BackendResult { + self.with_col(|col| col.transact(None, |col| col.set_bool(input))) + .map(Into::into) + } } impl Backend { diff --git a/rslib/src/config.rs b/rslib/src/config.rs index 6003d727d..3e51b502e 100644 --- a/rslib/src/config.rs +++ b/rslib/src/config.rs @@ -2,9 +2,10 @@ // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html use crate::{ - collection::Collection, decks::DeckID, err::Result, notetype::NoteTypeID, + backend_proto as pb, collection::Collection, decks::DeckID, err::Result, notetype::NoteTypeID, timestamp::TimestampSecs, }; +use pb::config_bool::Key as BoolKey; use serde::{de::DeserializeOwned, Serialize}; use serde_aux::field_attributes::deserialize_bool_from_anything; use serde_derive::Deserialize; @@ -52,6 +53,7 @@ pub(crate) enum ConfigKey { NewReviewMix, NextNewCardPosition, NormalizeNoteText, + PreviewBothSides, Rollover, SchedulerVersion, ShowDayLearningCardsFirst, @@ -83,6 +85,7 @@ impl From for &'static str { ConfigKey::NewReviewMix => "newSpread", ConfigKey::NextNewCardPosition => "nextPos", ConfigKey::NormalizeNoteText => "normalize_note_text", + ConfigKey::PreviewBothSides => "previewBothSides", ConfigKey::Rollover => "rollover", ConfigKey::SchedulerVersion => "schedVer", ConfigKey::ShowDayLearningCardsFirst => "dayLearnFirst", @@ -92,6 +95,15 @@ impl From for &'static str { } } +impl From for ConfigKey { + fn from(key: BoolKey) -> Self { + match key { + BoolKey::BrowserSortBackwards => ConfigKey::BrowserSortReverse, + BoolKey::PreviewBothSides => ConfigKey::PreviewBothSides, + } + } +} + /// 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)] @@ -310,6 +322,18 @@ impl Collection { pub(crate) fn set_last_unburied_day(&self, day: u32) -> Result<()> { self.set_config(ConfigKey::LastUnburiedDay, &day) } + + #[allow(clippy::match_single_binding)] + pub(crate) fn get_bool(&self, config: pb::ConfigBool) -> bool { + match config.key() { + // all options default to false at the moment + other => self.get_config_default(ConfigKey::from(other)), + } + } + + pub(crate) fn set_bool(&self, input: pb::SetConfigBoolIn) -> Result<()> { + self.set_config(ConfigKey::from(input.key()), &input.value) + } } #[derive(Deserialize, PartialEq, Debug, Clone, Copy)]