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:
Damien Elmes 2020-04-08 10:05:07 +10:00
parent 6ecf2ffa2c
commit 36ec7830a9
19 changed files with 332 additions and 108 deletions

View file

@ -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;
} }

View file

@ -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.

View file

@ -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]:

View file

@ -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]

View file

@ -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 {

View file

@ -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;

View file

@ -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| {

View file

@ -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;

View 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
View 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
}
}

View 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>,
}

View file

@ -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
}
}

View file

@ -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])?;
} }

View file

@ -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};

View file

@ -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() {

View file

@ -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>

View file

@ -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;

View 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(&notetypes)?;
self.db.execute("update col set models = ?", &[json])?;
Ok(())
}
}

View file

@ -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")?