preserve mtime/usn when syncing deck config, and add snake_case names

This commit is contained in:
Damien Elmes 2020-04-03 13:54:52 +10:00
parent d5f6d8b476
commit 333d0735ff
13 changed files with 90 additions and 48 deletions

View file

@ -51,7 +51,7 @@ message BackendInput {
Card update_card = 39;
Card add_card = 40;
int64 get_deck_config = 41;
string add_or_update_deck_config = 42;
AddOrUpdateDeckConfigIn add_or_update_deck_config = 42;
Empty all_deck_config = 43;
Empty new_deck_config = 44;
int64 remove_deck_config = 45;
@ -414,3 +414,8 @@ message Card {
message CloseCollectionIn {
bool downgrade_to_schema11 = 1;
}
message AddOrUpdateDeckConfigIn {
string config = 1;
bool preserve_usn_and_mtime = 2;
}

View file

@ -74,7 +74,7 @@ class DeckManager:
if g:
# deck conf?
if "maxTaken" in g:
self.updateConf(g)
self.update_config(g)
return
else:
g["mod"] = intTime()
@ -318,7 +318,7 @@ class DeckManager:
# Deck configurations
#############################################################
def allConf(self) -> List:
def all_config(self) -> List:
"A list of all deck config."
return list(self.col.backend.all_deck_config())
@ -327,32 +327,38 @@ class DeckManager:
assert deck
if "conf" in deck:
dcid = int(deck["conf"]) # may be a string
conf = self.getConf(dcid)
conf = self.get_config(dcid)
conf["dyn"] = False
return conf
# dynamic decks have embedded conf
return deck
def getConf(self, confId: int) -> Any:
def get_config(self, conf_id: int) -> Any:
if self._dconf_cache is not None:
return self._dconf_cache.get(confId)
return self.col.backend.get_deck_config(confId)
return self._dconf_cache.get(conf_id)
return self.col.backend.get_deck_config(conf_id)
def updateConf(self, g: Dict[str, Any]) -> None:
self.col.backend.add_or_update_deck_config(g)
def update_config(self, conf: Dict[str, Any], preserve_usn=False) -> None:
self.col.backend.add_or_update_deck_config(conf, preserve_usn)
def confId(self, name: str, cloneFrom: Optional[Dict[str, Any]] = None) -> int:
"Create a new configuration and return id."
if cloneFrom is not None:
conf = copy.deepcopy(cloneFrom)
def add_config(
self, name: str, clone_from: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
if clone_from is not None:
conf = copy.deepcopy(clone_from)
conf["id"] = 0
else:
conf = self.col.backend.new_deck_config()
conf["name"] = name
self.updateConf(conf)
return conf["id"]
self.update_config(conf)
return conf
def remConf(self, id) -> None:
def add_config_returning_id(
self, name: str, clone_from: Optional[Dict[str, Any]] = None
) -> int:
return self.add_config(name, clone_from)["id"]
def remove_config(self, id) -> None:
"Remove a configuration and update all decks using it."
self.col.modSchema(check=True)
for g in self.all():
@ -380,14 +386,21 @@ class DeckManager:
new = self.col.backend.new_deck_config()
new["id"] = conf["id"]
new["name"] = conf["name"]
self.updateConf(new)
self.update_config(new)
# if it was previously randomized, re-sort
if not oldOrder:
self.col.sched.resortConf(new)
# legacy
allConf = all_config
getConf = get_config
updateConf = update_config
remConf = remove_config
confId = add_config_returning_id
# temporary caching - don't use this as it will be removed
def _enable_dconf_cache(self):
self._dconf_cache = {c["id"]: c for c in self.allConf()}
self._dconf_cache = {c["id"]: c for c in self.all_config()}
def _disable_dconf_cache(self):
self._dconf_cache = None
@ -613,8 +626,10 @@ class DeckManager:
def beforeUpload(self) -> None:
for d in self.all():
d["usn"] = 0
for c in self.allConf():
for c in self.all_config():
if c["usn"] != 0:
c["usn"] = 0
self.update_config(c, preserve_usn=True)
self.save()
# Dynamic decks

View file

@ -249,7 +249,7 @@ class AnkiExporter(Exporter):
# copy used deck confs
for dc in self.src.decks.allConf():
if dc["id"] in dconfs:
self.dst.decks.updateConf(dc)
self.dst.decks.update_config(dc)
# find used media
media = {}
self.mediaDir = self.src.media.dir()

View file

@ -277,9 +277,9 @@ class Anki2Importer(Importer):
newid = self.dst.decks.id(name)
# pull conf over
if "conf" in g and g["conf"] != 1:
conf = self.src.decks.getConf(g["conf"])
conf = self.src.decks.get_config(g["conf"])
self.dst.decks.save(conf)
self.dst.decks.updateConf(conf)
self.dst.decks.update_config(conf)
g2 = self.dst.decks.get(newid)
g2["conf"] = g["conf"]
self.dst.decks.save(g2)

View file

@ -2,6 +2,15 @@
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
# pylint: skip-file
"""
Python bindings for Anki's Rust libraries.
Please do not access methods on the backend directly - they may be changed
or removed at any time. Instead, please use the methods on the collection
instead. Eg, don't use col.backend.all_deck_config(), instead use
col.decks.all_config()
"""
import enum
import json
import os
@ -502,10 +511,14 @@ class RustBackend:
jstr = self._run_command(pb.BackendInput(get_deck_config=dcid)).get_deck_config
return json.loads(jstr)
def add_or_update_deck_config(self, conf: Dict[str, Any]) -> None:
def add_or_update_deck_config(self, conf: Dict[str, Any], preserve_usn) -> None:
conf_json = json.dumps(conf)
id = self._run_command(
pb.BackendInput(add_or_update_deck_config=conf_json)
pb.BackendInput(
add_or_update_deck_config=pb.AddOrUpdateDeckConfigIn(
config=conf_json, preserve_usn_and_mtime=preserve_usn
)
)
).add_or_update_deck_config
conf["id"] = id

View file

@ -374,9 +374,10 @@ from notes where %s"""
decks = [g for g in self.col.decks.all() if g["usn"] == -1]
for g in decks:
g["usn"] = self.maxUsn
dconf = [g for g in self.col.decks.allConf() if g["usn"] == -1]
dconf = [g for g in self.col.decks.all_config() if g["usn"] == -1]
for g in dconf:
g["usn"] = self.maxUsn
self.col.decks.update_config(g, preserve_usn=True)
self.col.decks.save()
return [decks, dconf]
@ -392,12 +393,12 @@ from notes where %s"""
self.col.decks.update(r)
for r in rchg[1]:
try:
l = self.col.decks.getConf(r["id"])
l = self.col.decks.get_config(r["id"])
except KeyError:
l = None
# if missing locally or server is newer, update
if not l or r["mod"] > l["mod"]:
self.col.decks.updateConf(r)
self.col.decks.update_config(r)
# Tags
##########################################################################

View file

@ -45,8 +45,8 @@ def test_export_anki():
# create a new deck with its own conf to test conf copying
did = deck.decks.id("test")
dobj = deck.decks.get(did)
confId = deck.decks.confId("newconf")
conf = deck.decks.getConf(confId)
confId = deck.decks.add_config_returning_id("newconf")
conf = deck.decks.get_config(confId)
conf["new"]["perDay"] = 5
deck.decks.save(conf)
deck.decks.setConf(dobj, confId)

View file

@ -90,7 +90,7 @@ def test_newLimits():
f.model()["did"] = g2
d.addNote(f)
# give the child deck a different configuration
c2 = d.decks.confId("new conf")
c2 = d.decks.add_config_returning_id("new conf")
d.decks.setConf(d.decks.get(g2), c2)
d.reset()
# both confs have defaulted to a limit of 20

View file

@ -90,7 +90,7 @@ def test_newLimits():
f.model()["did"] = g2
d.addNote(f)
# give the child deck a different configuration
c2 = d.decks.confId("new conf")
c2 = d.decks.add_config_returning_id("new conf")
d.decks.setConf(d.decks.get(g2), c2)
d.reset()
# both confs have defaulted to a limit of 20
@ -412,14 +412,14 @@ def test_review_limits():
parent = d.decks.get(d.decks.id("parent"))
child = d.decks.get(d.decks.id("parent::child"))
pconf = d.decks.getConf(d.decks.confId("parentConf"))
cconf = d.decks.getConf(d.decks.confId("childConf"))
pconf = d.decks.get_config(d.decks.add_config_returning_id("parentConf"))
cconf = d.decks.get_config(d.decks.add_config_returning_id("childConf"))
pconf["rev"]["perDay"] = 5
d.decks.updateConf(pconf)
d.decks.update_config(pconf)
d.decks.setConf(parent, pconf["id"])
cconf["rev"]["perDay"] = 10
d.decks.updateConf(cconf)
d.decks.update_config(cconf)
d.decks.setConf(child, cconf["id"])
m = d.models.current()

View file

@ -26,7 +26,7 @@ class CustomStudy(QDialog):
QDialog.__init__(self, mw)
self.mw = mw
self.deck = self.mw.col.decks.current()
self.conf = self.mw.col.decks.getConf(self.deck["conf"])
self.conf = self.mw.col.decks.get_config(self.deck["conf"])
self.form = f = aqt.forms.customstudy.Ui_Dialog()
self.created_custom_study = False
f.setupUi(self)

View file

@ -123,7 +123,7 @@ class DeckConf(QDialog):
# first, save currently entered data to current conf
self.saveConf()
# then clone the conf
id = self.mw.col.decks.confId(name, cloneFrom=self.conf)
id = self.mw.col.decks.add_config_returning_id(name, cloneFrom=self.conf)
# set the deck to the new conf
self.deck["conf"] = id
# then reload the conf list
@ -133,7 +133,7 @@ class DeckConf(QDialog):
if int(self.conf["id"]) == 1:
showInfo(_("The default configuration can't be removed."), self)
else:
self.mw.col.decks.remConf(self.conf["id"])
self.mw.col.decks.remove_config(self.conf["id"])
self.deck["conf"] = 1
self.loadConfs()

View file

@ -3,7 +3,9 @@
use crate::backend::dbproxy::db_command_bytes;
use crate::backend_proto::backend_input::Value;
use crate::backend_proto::{BuiltinSortKind, Empty, RenderedTemplateReplacement, SyncMediaIn};
use crate::backend_proto::{
AddOrUpdateDeckConfigIn, BuiltinSortKind, Empty, RenderedTemplateReplacement, SyncMediaIn,
};
use crate::card::{Card, CardID};
use crate::card::{CardQueue, CardType};
use crate::collection::{open_collection, Collection};
@ -268,8 +270,8 @@ impl Backend {
}
Value::AddCard(card) => OValue::AddCard(self.add_card(card)?),
Value::GetDeckConfig(dcid) => OValue::GetDeckConfig(self.get_deck_config(dcid)?),
Value::AddOrUpdateDeckConfig(conf_json) => {
OValue::AddOrUpdateDeckConfig(self.add_or_update_deck_config(conf_json)?)
Value::AddOrUpdateDeckConfig(input) => {
OValue::AddOrUpdateDeckConfig(self.add_or_update_deck_config(input)?)
}
Value::AllDeckConfig(_) => OValue::AllDeckConfig(self.all_deck_config()?),
Value::NewDeckConfig(_) => OValue::NewDeckConfig(self.new_deck_config()?),
@ -702,11 +704,11 @@ impl Backend {
})
}
fn add_or_update_deck_config(&self, conf_json: String) -> Result<i64> {
let mut conf: DeckConf = serde_json::from_str(&conf_json)?;
fn add_or_update_deck_config(&self, input: AddOrUpdateDeckConfigIn) -> Result<i64> {
let mut conf: DeckConf = serde_json::from_str(&input.config)?;
self.with_col(|col| {
col.transact(None, |col| {
col.add_or_update_deck_config(&mut conf)?;
col.add_or_update_deck_config(&mut conf, input.preserve_usn_and_mtime)?;
Ok(conf.id.0)
})
})

View file

@ -219,9 +219,15 @@ impl Collection {
}
}
pub(crate) fn add_or_update_deck_config(&self, conf: &mut DeckConf) -> Result<()> {
pub(crate) fn add_or_update_deck_config(
&self,
conf: &mut DeckConf,
preserve_usn_and_mtime: bool,
) -> Result<()> {
if !preserve_usn_and_mtime {
conf.mtime = TimestampSecs::now();
conf.usn = self.usn()?;
}
let orig = self.storage.get_deck_config(conf.id)?;
if let Some(_orig) = orig {
self.storage.update_deck_conf(&conf)