mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
load/save note types in backend
This allows us to normalize bad data, and is the first step towards splitting note types into separate tables.
This commit is contained in:
parent
6ecf2ffa2c
commit
36ec7830a9
19 changed files with 332 additions and 108 deletions
|
@ -65,6 +65,8 @@ message BackendInput {
|
||||||
SetConfigJson set_config_json = 53;
|
SetConfigJson set_config_json = 53;
|
||||||
bytes set_all_config = 54;
|
bytes set_all_config = 54;
|
||||||
Empty get_all_config = 55;
|
Empty get_all_config = 55;
|
||||||
|
Empty get_all_notetypes = 56;
|
||||||
|
bytes set_all_notetypes = 57;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,6 +115,8 @@ message BackendOutput {
|
||||||
Empty set_config_json = 53;
|
Empty set_config_json = 53;
|
||||||
Empty set_all_config = 54;
|
Empty set_all_config = 54;
|
||||||
bytes get_all_config = 55;
|
bytes get_all_config = 55;
|
||||||
|
bytes get_all_notetypes = 56;
|
||||||
|
Empty set_all_notetypes = 57;
|
||||||
|
|
||||||
BackendError error = 2047;
|
BackendError error = 2047;
|
||||||
}
|
}
|
||||||
|
|
|
@ -160,15 +160,15 @@ class _Collection:
|
||||||
self.dty, # no longer used
|
self.dty, # no longer used
|
||||||
self._usn,
|
self._usn,
|
||||||
self.ls,
|
self.ls,
|
||||||
models,
|
|
||||||
decks,
|
decks,
|
||||||
) = self.db.first(
|
) = self.db.first(
|
||||||
"""
|
"""
|
||||||
select crt, mod, scm, dty, usn, ls,
|
select crt, mod, scm, dty, usn, ls,
|
||||||
models, decks from col"""
|
decks from col"""
|
||||||
)
|
)
|
||||||
self.models.load(models)
|
|
||||||
self.decks.load(decks)
|
self.decks.load(decks)
|
||||||
|
self.models.models = self.backend.get_all_notetypes()
|
||||||
|
self.models.changed = False
|
||||||
|
|
||||||
def setMod(self) -> None:
|
def setMod(self) -> None:
|
||||||
"""Mark DB modified.
|
"""Mark DB modified.
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import json
|
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
||||||
|
@ -95,11 +94,6 @@ class ModelManager:
|
||||||
self.models = {}
|
self.models = {}
|
||||||
self.changed = False
|
self.changed = False
|
||||||
|
|
||||||
def load(self, json_: str) -> None:
|
|
||||||
"Load registry from JSON."
|
|
||||||
self.changed = False
|
|
||||||
self.models = json.loads(json_)
|
|
||||||
|
|
||||||
def save(
|
def save(
|
||||||
self,
|
self,
|
||||||
m: Optional[NoteType] = None,
|
m: Optional[NoteType] = None,
|
||||||
|
@ -121,7 +115,7 @@ class ModelManager:
|
||||||
"Flush the registry if any models were changed."
|
"Flush the registry if any models were changed."
|
||||||
if self.changed:
|
if self.changed:
|
||||||
self.ensureNotEmpty()
|
self.ensureNotEmpty()
|
||||||
self.col.db.execute("update col set models = ?", json.dumps(self.models))
|
self.col.backend.set_all_notetypes(self.models)
|
||||||
self.changed = False
|
self.changed = False
|
||||||
|
|
||||||
def ensureNotEmpty(self) -> Optional[bool]:
|
def ensureNotEmpty(self) -> Optional[bool]:
|
||||||
|
|
|
@ -113,6 +113,8 @@ def proto_exception_to_native(err: pb.BackendError) -> Exception:
|
||||||
return TemplateError(err.localized)
|
return TemplateError(err.localized)
|
||||||
elif val == "invalid_input":
|
elif val == "invalid_input":
|
||||||
return StringError(err.localized)
|
return StringError(err.localized)
|
||||||
|
elif val == "json_error":
|
||||||
|
return StringError(err.localized)
|
||||||
else:
|
else:
|
||||||
assert_impossible_literal(val)
|
assert_impossible_literal(val)
|
||||||
|
|
||||||
|
@ -606,6 +608,15 @@ class RustBackend:
|
||||||
def set_all_config(self, conf: Dict[str, Any]):
|
def set_all_config(self, conf: Dict[str, Any]):
|
||||||
self._run_command(pb.BackendInput(set_all_config=orjson.dumps(conf)))
|
self._run_command(pb.BackendInput(set_all_config=orjson.dumps(conf)))
|
||||||
|
|
||||||
|
def get_all_notetypes(self) -> Dict[str, Dict[str, Any]]:
|
||||||
|
jstr = self._run_command(
|
||||||
|
pb.BackendInput(get_all_notetypes=pb.Empty())
|
||||||
|
).get_all_notetypes
|
||||||
|
return orjson.loads(jstr)
|
||||||
|
|
||||||
|
def set_all_notetypes(self, nts: Dict[str, Dict[str, Any]]):
|
||||||
|
self._run_command(pb.BackendInput(set_all_notetypes=orjson.dumps(nts)))
|
||||||
|
|
||||||
|
|
||||||
def translate_string_in(
|
def translate_string_in(
|
||||||
key: TR, **kwargs: Union[str, int, float]
|
key: TR, **kwargs: Union[str, int, float]
|
||||||
|
|
|
@ -1,35 +1,39 @@
|
||||||
// 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
|
||||||
|
|
||||||
use crate::backend::dbproxy::db_command_bytes;
|
use crate::{
|
||||||
use crate::backend_proto::{
|
backend::dbproxy::db_command_bytes,
|
||||||
|
backend_proto as pb,
|
||||||
|
backend_proto::{
|
||||||
AddOrUpdateDeckConfigIn, BuiltinSortKind, Empty, RenderedTemplateReplacement, SyncMediaIn,
|
AddOrUpdateDeckConfigIn, BuiltinSortKind, Empty, RenderedTemplateReplacement, SyncMediaIn,
|
||||||
|
},
|
||||||
|
card::{Card, CardID},
|
||||||
|
card::{CardQueue, CardType},
|
||||||
|
collection::{open_collection, Collection},
|
||||||
|
config::SortKind,
|
||||||
|
deckconf::{DeckConf, DeckConfID},
|
||||||
|
decks::DeckID,
|
||||||
|
err::{AnkiError, NetworkErrorKind, Result, SyncErrorKind},
|
||||||
|
i18n::{tr_args, I18n, TR},
|
||||||
|
latex::{extract_latex, extract_latex_expanding_clozes, ExtractedLatex},
|
||||||
|
log,
|
||||||
|
log::{default_logger, Logger},
|
||||||
|
media::check::MediaChecker,
|
||||||
|
media::sync::MediaSyncProgress,
|
||||||
|
media::MediaManager,
|
||||||
|
notes::NoteID,
|
||||||
|
notetype::{NoteType, NoteTypeID},
|
||||||
|
sched::cutoff::{local_minutes_west_for_stamp, sched_timing_today},
|
||||||
|
sched::timespan::{answer_button_time, learning_congrats, studied_today, time_span},
|
||||||
|
search::{search_cards, search_notes, SortMode},
|
||||||
|
template::{
|
||||||
|
render_card, without_legacy_template_directives, FieldMap, FieldRequirements,
|
||||||
|
ParsedTemplate, RenderedNode,
|
||||||
|
},
|
||||||
|
text::{extract_av_tags, strip_av_tags, AVTag},
|
||||||
|
timestamp::TimestampSecs,
|
||||||
|
types::Usn,
|
||||||
};
|
};
|
||||||
use crate::card::{Card, CardID};
|
|
||||||
use crate::card::{CardQueue, CardType};
|
|
||||||
use crate::collection::{open_collection, Collection};
|
|
||||||
use crate::config::SortKind;
|
|
||||||
use crate::deckconf::{DeckConf, DeckConfID};
|
|
||||||
use crate::decks::DeckID;
|
|
||||||
use crate::err::{AnkiError, NetworkErrorKind, Result, SyncErrorKind};
|
|
||||||
use crate::i18n::{tr_args, I18n, TR};
|
|
||||||
use crate::latex::{extract_latex, extract_latex_expanding_clozes, ExtractedLatex};
|
|
||||||
use crate::log::{default_logger, Logger};
|
|
||||||
use crate::media::check::MediaChecker;
|
|
||||||
use crate::media::sync::MediaSyncProgress;
|
|
||||||
use crate::media::MediaManager;
|
|
||||||
use crate::notes::NoteID;
|
|
||||||
use crate::sched::cutoff::{local_minutes_west_for_stamp, sched_timing_today};
|
|
||||||
use crate::sched::timespan::{answer_button_time, learning_congrats, studied_today, time_span};
|
|
||||||
use crate::search::{search_cards, search_notes, SortMode};
|
|
||||||
use crate::template::{
|
|
||||||
render_card, without_legacy_template_directives, FieldMap, FieldRequirements, ParsedTemplate,
|
|
||||||
RenderedNode,
|
|
||||||
};
|
|
||||||
use crate::text::{extract_av_tags, strip_av_tags, AVTag};
|
|
||||||
use crate::timestamp::TimestampSecs;
|
|
||||||
use crate::types::Usn;
|
|
||||||
use crate::{backend_proto as pb, log};
|
|
||||||
use fluent::FluentValue;
|
use fluent::FluentValue;
|
||||||
use futures::future::{AbortHandle, Abortable};
|
use futures::future::{AbortHandle, Abortable};
|
||||||
use log::error;
|
use log::error;
|
||||||
|
@ -304,6 +308,11 @@ impl Backend {
|
||||||
pb::Empty {}
|
pb::Empty {}
|
||||||
}),
|
}),
|
||||||
Value::GetAllConfig(_) => OValue::GetAllConfig(self.get_all_config()?),
|
Value::GetAllConfig(_) => OValue::GetAllConfig(self.get_all_config()?),
|
||||||
|
Value::GetAllNotetypes(_) => OValue::GetAllNotetypes(self.get_all_notetypes()?),
|
||||||
|
Value::SetAllNotetypes(bytes) => {
|
||||||
|
self.set_all_notetypes(&bytes)?;
|
||||||
|
OValue::SetAllNotetypes(pb::Empty {})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -840,6 +849,23 @@ impl Backend {
|
||||||
serde_json::to_vec(&conf).map_err(Into::into)
|
serde_json::to_vec(&conf).map_err(Into::into)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_all_notetypes(&self, json: &[u8]) -> Result<()> {
|
||||||
|
let val: HashMap<NoteTypeID, NoteType> = serde_json::from_slice(json)?;
|
||||||
|
self.with_col(|col| {
|
||||||
|
col.transact(None, |col| {
|
||||||
|
col.storage
|
||||||
|
.set_all_notetypes(val, col.usn()?, TimestampSecs::now())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_all_notetypes(&self) -> Result<Vec<u8>> {
|
||||||
|
self.with_col(|col| {
|
||||||
|
let nts = col.storage.get_all_notetypes()?;
|
||||||
|
serde_json::to_vec(&nts).map_err(Into::into)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn translate_arg_to_fluent_val(arg: &pb::TranslateArgValue) -> FluentValue {
|
fn translate_arg_to_fluent_val(arg: &pb::TranslateArgValue) -> FluentValue {
|
||||||
|
|
|
@ -22,7 +22,7 @@ pub mod latex;
|
||||||
pub mod log;
|
pub mod log;
|
||||||
pub mod media;
|
pub mod media;
|
||||||
pub mod notes;
|
pub mod notes;
|
||||||
pub mod notetypes;
|
pub mod notetype;
|
||||||
pub mod sched;
|
pub mod sched;
|
||||||
pub mod search;
|
pub mod search;
|
||||||
pub mod serde;
|
pub mod serde;
|
||||||
|
|
|
@ -380,7 +380,7 @@ where
|
||||||
renamed: &HashMap<String, String>,
|
renamed: &HashMap<String, String>,
|
||||||
) -> Result<HashSet<String>> {
|
) -> Result<HashSet<String>> {
|
||||||
let mut referenced_files = HashSet::new();
|
let mut referenced_files = HashSet::new();
|
||||||
let note_types = self.ctx.storage.all_note_types()?;
|
let note_types = self.ctx.storage.get_all_notetypes()?;
|
||||||
let mut collection_modified = false;
|
let mut collection_modified = false;
|
||||||
|
|
||||||
for_every_note(&self.ctx.storage.db, |note| {
|
for_every_note(&self.ctx.storage.db, |note| {
|
||||||
|
|
|
@ -4,10 +4,10 @@
|
||||||
/// At the moment, this is just basic note reading/updating functionality for
|
/// At the moment, this is just basic note reading/updating functionality for
|
||||||
/// the media DB check.
|
/// the media DB check.
|
||||||
use crate::err::{AnkiError, DBErrorKind, Result};
|
use crate::err::{AnkiError, DBErrorKind, Result};
|
||||||
use crate::notetypes::NoteTypeID;
|
use crate::notetype::NoteTypeID;
|
||||||
use crate::text::strip_html_preserving_image_filenames;
|
use crate::text::strip_html_preserving_image_filenames;
|
||||||
use crate::timestamp::TimestampSecs;
|
use crate::timestamp::TimestampSecs;
|
||||||
use crate::{define_newtype, notetypes::NoteType, types::Usn};
|
use crate::{define_newtype, notetype::NoteType, types::Usn};
|
||||||
use rusqlite::{params, Connection, Row, NO_PARAMS};
|
use rusqlite::{params, Connection, Row, NO_PARAMS};
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
|
||||||
|
|
35
rslib/src/notetype/field.rs
Normal file
35
rslib/src/notetype/field.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
use crate::serde::deserialize_bool_from_anything;
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct NoteField {
|
||||||
|
pub(crate) name: String,
|
||||||
|
pub(crate) ord: u16,
|
||||||
|
#[serde(deserialize_with = "deserialize_bool_from_anything")]
|
||||||
|
pub(crate) sticky: bool,
|
||||||
|
#[serde(deserialize_with = "deserialize_bool_from_anything")]
|
||||||
|
pub(crate) rtl: bool,
|
||||||
|
pub(crate) font: String,
|
||||||
|
pub(crate) size: u16,
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub(crate) other: HashMap<String, Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for NoteField {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
name: String::new(),
|
||||||
|
ord: 0,
|
||||||
|
sticky: false,
|
||||||
|
rtl: false,
|
||||||
|
font: "Arial".to_string(),
|
||||||
|
size: 20,
|
||||||
|
other: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
139
rslib/src/notetype/mod.rs
Normal file
139
rslib/src/notetype/mod.rs
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
mod field;
|
||||||
|
mod template;
|
||||||
|
|
||||||
|
pub use field::NoteField;
|
||||||
|
pub use template::CardTemplate;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
decks::DeckID,
|
||||||
|
define_newtype,
|
||||||
|
serde::{default_on_invalid, deserialize_number_from_string},
|
||||||
|
timestamp::TimestampSecs,
|
||||||
|
types::Usn,
|
||||||
|
};
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
use serde_json::Value;
|
||||||
|
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||||
|
use serde_tuple::Serialize_tuple;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
define_newtype!(NoteTypeID, i64);
|
||||||
|
|
||||||
|
pub(crate) const DEFAULT_CSS: &str = "\
|
||||||
|
.card {
|
||||||
|
font-family: arial;
|
||||||
|
font-size: 20px;
|
||||||
|
text-align: center;
|
||||||
|
color: black;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
";
|
||||||
|
|
||||||
|
pub(crate) const DEFAULT_LATEX_HEADER: &str = r#"\documentclass[12pt]{article}
|
||||||
|
\special{papersize=3in,5in}
|
||||||
|
\usepackage[utf8]{inputenc}
|
||||||
|
\usepackage{amssymb,amsmath}
|
||||||
|
\pagestyle{empty}
|
||||||
|
\setlength{\parindent}{0in}
|
||||||
|
\begin{document}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
pub(crate) const DEFAULT_LATEX_FOOTER: &str = r#"\end{document}"#;
|
||||||
|
|
||||||
|
#[derive(Serialize_repr, Deserialize_repr, PartialEq, Debug, Clone)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum NoteTypeKind {
|
||||||
|
Standard = 0,
|
||||||
|
Cloze = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct NoteType {
|
||||||
|
#[serde(deserialize_with = "deserialize_number_from_string")]
|
||||||
|
pub(crate) id: NoteTypeID,
|
||||||
|
pub(crate) name: String,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub(crate) kind: NoteTypeKind,
|
||||||
|
#[serde(rename = "mod")]
|
||||||
|
pub(crate) mtime: TimestampSecs,
|
||||||
|
pub(crate) usn: Usn,
|
||||||
|
#[serde(rename = "sortf")]
|
||||||
|
pub(crate) sort_field_idx: u16,
|
||||||
|
#[serde(rename = "did", deserialize_with = "default_on_invalid")]
|
||||||
|
pub(crate) deck_id_for_adding: Option<DeckID>,
|
||||||
|
#[serde(rename = "tmpls")]
|
||||||
|
pub(crate) templates: Vec<CardTemplate>,
|
||||||
|
#[serde(rename = "flds")]
|
||||||
|
pub(crate) fields: Vec<NoteField>,
|
||||||
|
#[serde(deserialize_with = "default_on_invalid")]
|
||||||
|
pub(crate) css: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub(crate) latex_pre: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub(crate) latex_post: String,
|
||||||
|
#[serde(rename = "latexsvg", default)]
|
||||||
|
pub latex_svg: bool,
|
||||||
|
#[serde(default, deserialize_with = "default_on_invalid")]
|
||||||
|
pub(crate) req: CardRequirements,
|
||||||
|
#[serde(default, deserialize_with = "default_on_invalid")]
|
||||||
|
pub(crate) tags: Vec<String>,
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub(crate) other: HashMap<String, Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub(crate) struct CardRequirements(pub(crate) Vec<CardRequirement>);
|
||||||
|
|
||||||
|
impl Default for CardRequirements {
|
||||||
|
fn default() -> Self {
|
||||||
|
CardRequirements(vec![])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize_tuple, Deserialize, Debug, Clone)]
|
||||||
|
pub(crate) struct CardRequirement {
|
||||||
|
pub(crate) card_ord: u16,
|
||||||
|
pub(crate) kind: FieldRequirementKind,
|
||||||
|
pub(crate) field_ords: Vec<u16>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum FieldRequirementKind {
|
||||||
|
Any,
|
||||||
|
All,
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for NoteType {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
id: NoteTypeID(0),
|
||||||
|
name: String::new(),
|
||||||
|
kind: NoteTypeKind::Standard,
|
||||||
|
mtime: TimestampSecs(0),
|
||||||
|
usn: Usn(0),
|
||||||
|
sort_field_idx: 0,
|
||||||
|
deck_id_for_adding: None,
|
||||||
|
fields: vec![],
|
||||||
|
templates: vec![],
|
||||||
|
css: DEFAULT_CSS.to_owned(),
|
||||||
|
latex_pre: DEFAULT_LATEX_HEADER.to_owned(),
|
||||||
|
latex_post: DEFAULT_LATEX_FOOTER.to_owned(),
|
||||||
|
req: Default::default(),
|
||||||
|
tags: vec![],
|
||||||
|
latex_svg: false,
|
||||||
|
other: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NoteType {
|
||||||
|
pub fn latex_uses_svg(&self) -> bool {
|
||||||
|
self.latex_svg
|
||||||
|
}
|
||||||
|
}
|
28
rslib/src/notetype/template.rs
Normal file
28
rslib/src/notetype/template.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
use crate::{decks::DeckID, serde::default_on_invalid};
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
||||||
|
pub struct CardTemplate {
|
||||||
|
pub(crate) name: String,
|
||||||
|
pub(crate) ord: u16,
|
||||||
|
pub(crate) qfmt: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub(crate) afmt: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub(crate) bqfmt: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub(crate) bafmt: String,
|
||||||
|
#[serde(rename = "did", deserialize_with = "default_on_invalid")]
|
||||||
|
pub(crate) override_did: Option<DeckID>,
|
||||||
|
#[serde(default, deserialize_with = "default_on_invalid")]
|
||||||
|
pub(crate) bfont: String,
|
||||||
|
#[serde(default, deserialize_with = "default_on_invalid")]
|
||||||
|
pub(crate) bsize: u8,
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub(crate) other: HashMap<String, Value>,
|
||||||
|
}
|
|
@ -1,41 +0,0 @@
|
||||||
// Copyright: Ankitects Pty Ltd and contributors
|
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
||||||
|
|
||||||
use crate::define_newtype;
|
|
||||||
use serde_aux::field_attributes::deserialize_number_from_string;
|
|
||||||
use serde_derive::Deserialize;
|
|
||||||
|
|
||||||
define_newtype!(NoteTypeID, i64);
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub(crate) struct NoteType {
|
|
||||||
#[serde(deserialize_with = "deserialize_number_from_string")]
|
|
||||||
pub id: NoteTypeID,
|
|
||||||
pub name: String,
|
|
||||||
#[serde(rename = "sortf")]
|
|
||||||
pub sort_field_idx: u16,
|
|
||||||
#[serde(rename = "latexsvg", default)]
|
|
||||||
pub latex_svg: bool,
|
|
||||||
#[serde(rename = "tmpls")]
|
|
||||||
pub templates: Vec<CardTemplate>,
|
|
||||||
#[serde(rename = "flds")]
|
|
||||||
pub fields: Vec<NoteField>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub(crate) struct CardTemplate {
|
|
||||||
pub name: String,
|
|
||||||
pub ord: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub(crate) struct NoteField {
|
|
||||||
pub name: String,
|
|
||||||
pub ord: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NoteType {
|
|
||||||
pub fn latex_uses_svg(&self) -> bool {
|
|
||||||
self.latex_svg
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -113,7 +113,7 @@ fn prepare_sort(req: &mut Collection, kind: &SortKind) -> Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NoteType => {
|
NoteType => {
|
||||||
for (k, v) in req.storage.all_note_types()? {
|
for (k, v) in req.storage.get_all_notetypes()? {
|
||||||
stmt.execute(params![k, v.name])?;
|
stmt.execute(params![k, v.name])?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,7 +127,7 @@ fn prepare_sort(req: &mut Collection, kind: &SortKind) -> Result<()> {
|
||||||
.db
|
.db
|
||||||
.prepare("insert into sort_order (k1,k2,v) values (?,?,?)")?;
|
.prepare("insert into sort_order (k1,k2,v) values (?,?,?)")?;
|
||||||
|
|
||||||
for (ntid, nt) in req.storage.all_note_types()? {
|
for (ntid, nt) in req.storage.get_all_notetypes()? {
|
||||||
for tmpl in nt.templates {
|
for tmpl in nt.templates {
|
||||||
stmt.execute(params![ntid, tmpl.ord, tmpl.name])?;
|
stmt.execute(params![ntid, tmpl.ord, tmpl.name])?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// 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
|
||||||
|
|
||||||
use crate::err::{AnkiError, Result};
|
use crate::err::{AnkiError, Result};
|
||||||
use crate::notetypes::NoteTypeID;
|
use crate::notetype::NoteTypeID;
|
||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
use nom::bytes::complete::{escaped, is_not, tag, take_while1};
|
use nom::bytes::complete::{escaped, is_not, tag, take_while1};
|
||||||
use nom::character::complete::{anychar, char, one_of};
|
use nom::character::complete::{anychar, char, one_of};
|
||||||
|
|
|
@ -7,7 +7,7 @@ use crate::decks::child_ids;
|
||||||
use crate::decks::get_deck;
|
use crate::decks::get_deck;
|
||||||
use crate::err::{AnkiError, Result};
|
use crate::err::{AnkiError, Result};
|
||||||
use crate::notes::field_checksum;
|
use crate::notes::field_checksum;
|
||||||
use crate::notetypes::NoteTypeID;
|
use crate::notetype::NoteTypeID;
|
||||||
use crate::text::matches_wildcard;
|
use crate::text::matches_wildcard;
|
||||||
use crate::text::without_combining;
|
use crate::text::without_combining;
|
||||||
use crate::{collection::Collection, text::strip_html_preserving_image_filenames};
|
use crate::{collection::Collection, text::strip_html_preserving_image_filenames};
|
||||||
|
@ -263,7 +263,7 @@ impl SqlWriter<'_> {
|
||||||
write!(self.sql, "c.ord = {}", n).unwrap();
|
write!(self.sql, "c.ord = {}", n).unwrap();
|
||||||
}
|
}
|
||||||
TemplateKind::Name(name) => {
|
TemplateKind::Name(name) => {
|
||||||
let note_types = self.col.storage.all_note_types()?;
|
let note_types = self.col.storage.get_all_notetypes()?;
|
||||||
let mut id_ords = vec![];
|
let mut id_ords = vec![];
|
||||||
for nt in note_types.values() {
|
for nt in note_types.values() {
|
||||||
for tmpl in &nt.templates {
|
for tmpl in &nt.templates {
|
||||||
|
@ -294,7 +294,7 @@ impl SqlWriter<'_> {
|
||||||
let mut ntids: Vec<_> = self
|
let mut ntids: Vec<_> = self
|
||||||
.col
|
.col
|
||||||
.storage
|
.storage
|
||||||
.all_note_types()?
|
.get_all_notetypes()?
|
||||||
.values()
|
.values()
|
||||||
.filter(|nt| matches_wildcard(&nt.name, nt_name))
|
.filter(|nt| matches_wildcard(&nt.name, nt_name))
|
||||||
.map(|nt| nt.id)
|
.map(|nt| nt.id)
|
||||||
|
@ -307,7 +307,7 @@ impl SqlWriter<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_single_field(&mut self, field_name: &str, val: &str, is_re: bool) -> Result<()> {
|
fn write_single_field(&mut self, field_name: &str, val: &str, is_re: bool) -> Result<()> {
|
||||||
let note_types = self.col.storage.all_note_types()?;
|
let note_types = self.col.storage.get_all_notetypes()?;
|
||||||
|
|
||||||
let mut field_map = vec![];
|
let mut field_map = vec![];
|
||||||
for nt in note_types.values() {
|
for nt in note_types.values() {
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
// 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
|
||||||
|
|
||||||
use serde::Deserialize as DeTrait;
|
use serde::Deserialize as DeTrait;
|
||||||
|
pub(crate) use serde_aux::field_attributes::{
|
||||||
|
deserialize_bool_from_anything, deserialize_number_from_string,
|
||||||
|
};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
pub(crate) fn default_on_invalid<'de, T, D>(deserializer: D) -> Result<T, D::Error>
|
pub(crate) fn default_on_invalid<'de, T, D>(deserializer: D) -> Result<T, D::Error>
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
mod card;
|
mod card;
|
||||||
mod config;
|
mod config;
|
||||||
mod deckconf;
|
mod deckconf;
|
||||||
|
mod notetype;
|
||||||
mod sqlite;
|
mod sqlite;
|
||||||
mod tag;
|
mod tag;
|
||||||
mod upgrades;
|
mod upgrades;
|
||||||
|
|
41
rslib/src/storage/notetype/mod.rs
Normal file
41
rslib/src/storage/notetype/mod.rs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
use super::SqliteStorage;
|
||||||
|
use crate::{
|
||||||
|
err::{AnkiError, DBErrorKind, Result},
|
||||||
|
notetype::{NoteType, NoteTypeID},
|
||||||
|
timestamp::TimestampSecs,
|
||||||
|
types::Usn,
|
||||||
|
};
|
||||||
|
use rusqlite::NO_PARAMS;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
impl SqliteStorage {
|
||||||
|
pub(crate) fn get_all_notetypes(&self) -> Result<HashMap<NoteTypeID, NoteType>> {
|
||||||
|
let mut stmt = self.db.prepare("select models from col")?;
|
||||||
|
let note_types = stmt
|
||||||
|
.query_and_then(NO_PARAMS, |row| -> Result<HashMap<NoteTypeID, NoteType>> {
|
||||||
|
let v: HashMap<NoteTypeID, NoteType> =
|
||||||
|
serde_json::from_str(row.get_raw(0).as_str()?)?;
|
||||||
|
Ok(v)
|
||||||
|
})?
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| AnkiError::DBError {
|
||||||
|
info: "col table empty".to_string(),
|
||||||
|
kind: DBErrorKind::MissingEntity,
|
||||||
|
})??;
|
||||||
|
Ok(note_types)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_all_notetypes(
|
||||||
|
&self,
|
||||||
|
notetypes: HashMap<NoteTypeID, NoteType>,
|
||||||
|
_usn: Usn,
|
||||||
|
_mtime: TimestampSecs,
|
||||||
|
) -> Result<()> {
|
||||||
|
let json = serde_json::to_string(¬etypes)?;
|
||||||
|
self.db.execute("update col set models = ?", &[json])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,9 +5,8 @@ use crate::config::schema11_config_as_string;
|
||||||
use crate::decks::DeckID;
|
use crate::decks::DeckID;
|
||||||
use crate::err::Result;
|
use crate::err::Result;
|
||||||
use crate::err::{AnkiError, DBErrorKind};
|
use crate::err::{AnkiError, DBErrorKind};
|
||||||
use crate::notetypes::NoteTypeID;
|
|
||||||
use crate::timestamp::{TimestampMillis, TimestampSecs};
|
use crate::timestamp::{TimestampMillis, TimestampSecs};
|
||||||
use crate::{decks::Deck, i18n::I18n, notetypes::NoteType, text::without_combining, types::Usn};
|
use crate::{decks::Deck, i18n::I18n, text::without_combining, types::Usn};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use rusqlite::{functions::FunctionFlags, params, Connection, NO_PARAMS};
|
use rusqlite::{functions::FunctionFlags, params, Connection, NO_PARAMS};
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
@ -292,22 +291,6 @@ impl SqliteStorage {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn all_note_types(&self) -> Result<HashMap<NoteTypeID, NoteType>> {
|
|
||||||
let mut stmt = self.db.prepare("select models from col")?;
|
|
||||||
let note_types = stmt
|
|
||||||
.query_and_then(NO_PARAMS, |row| -> Result<HashMap<NoteTypeID, NoteType>> {
|
|
||||||
let v: HashMap<NoteTypeID, NoteType> =
|
|
||||||
serde_json::from_str(row.get_raw(0).as_str()?)?;
|
|
||||||
Ok(v)
|
|
||||||
})?
|
|
||||||
.next()
|
|
||||||
.ok_or_else(|| AnkiError::DBError {
|
|
||||||
info: "col table empty".to_string(),
|
|
||||||
kind: DBErrorKind::MissingEntity,
|
|
||||||
})??;
|
|
||||||
Ok(note_types)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn creation_stamp(&self) -> Result<TimestampSecs> {
|
pub(crate) fn creation_stamp(&self) -> Result<TimestampSecs> {
|
||||||
self.db
|
self.db
|
||||||
.prepare_cached("select crt from col")?
|
.prepare_cached("select crt from col")?
|
||||||
|
|
Loading…
Reference in a new issue