2020-02-10 05:19:39 +01:00
|
|
|
// Copyright: Ankitects Pty Ltd and contributors
|
|
|
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
|
2020-03-05 07:29:04 +01:00
|
|
|
/// At the moment, this is just basic note reading/updating functionality for
|
|
|
|
/// the media DB check.
|
2020-03-03 06:04:03 +01:00
|
|
|
use crate::err::{AnkiError, DBErrorKind, Result};
|
2020-02-10 05:19:39 +01:00
|
|
|
use crate::text::strip_html_preserving_image_filenames;
|
2020-03-05 07:29:04 +01:00
|
|
|
use crate::time::i64_unix_secs;
|
2020-02-10 05:19:39 +01:00
|
|
|
use crate::types::{ObjID, Timestamp, Usn};
|
|
|
|
use rusqlite::{params, Connection, Row, NO_PARAMS};
|
|
|
|
use serde_aux::field_attributes::deserialize_number_from_string;
|
|
|
|
use serde_derive::Deserialize;
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::convert::TryInto;
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub(super) struct Note {
|
|
|
|
pub id: ObjID,
|
|
|
|
pub mid: ObjID,
|
|
|
|
pub mtime_secs: Timestamp,
|
|
|
|
pub usn: Usn,
|
|
|
|
fields: Vec<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Note {
|
|
|
|
pub fn fields(&self) -> &Vec<String> {
|
|
|
|
&self.fields
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_field(&mut self, idx: usize, text: impl Into<String>) -> Result<()> {
|
|
|
|
if idx >= self.fields.len() {
|
|
|
|
return Err(AnkiError::invalid_input(
|
|
|
|
"field idx out of range".to_string(),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
self.fields[idx] = text.into();
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-17 05:52:55 +01:00
|
|
|
/// Text must be passed to strip_html_preserving_image_filenames() by
|
|
|
|
/// caller prior to passing in here.
|
2020-03-17 03:31:54 +01:00
|
|
|
pub(crate) fn field_checksum(text: &str) -> u32 {
|
2020-03-17 05:52:55 +01:00
|
|
|
let digest = sha1::Sha1::from(text).digest().bytes();
|
2020-02-10 05:19:39 +01:00
|
|
|
u32::from_be_bytes(digest[..4].try_into().unwrap())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
|
|
pub(super) struct NoteType {
|
|
|
|
#[serde(deserialize_with = "deserialize_number_from_string")]
|
|
|
|
id: ObjID,
|
|
|
|
#[serde(rename = "sortf")]
|
|
|
|
sort_field_idx: u16,
|
2020-02-11 04:11:20 +01:00
|
|
|
|
|
|
|
#[serde(rename = "latexsvg", default)]
|
|
|
|
latex_svg: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl NoteType {
|
|
|
|
pub fn latex_uses_svg(&self) -> bool {
|
|
|
|
self.latex_svg
|
|
|
|
}
|
2020-02-10 05:19:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
pub(super) fn get_note_types(db: &Connection) -> Result<HashMap<ObjID, NoteType>> {
|
|
|
|
let mut stmt = db.prepare("select models from col")?;
|
|
|
|
let note_types = stmt
|
|
|
|
.query_and_then(NO_PARAMS, |row| -> Result<HashMap<ObjID, NoteType>> {
|
|
|
|
let v: HashMap<ObjID, 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(),
|
2020-03-03 06:04:03 +01:00
|
|
|
kind: DBErrorKind::MissingEntity,
|
2020-02-10 05:19:39 +01:00
|
|
|
})??;
|
|
|
|
Ok(note_types)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(dead_code)]
|
|
|
|
fn get_note(db: &Connection, nid: ObjID) -> Result<Option<Note>> {
|
|
|
|
let mut stmt = db.prepare_cached("select id, mid, mod, usn, flds from notes where id=?")?;
|
|
|
|
let note = stmt.query_and_then(params![nid], row_to_note)?.next();
|
|
|
|
|
|
|
|
note.transpose()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(super) fn for_every_note<F: FnMut(&mut Note) -> Result<()>>(
|
|
|
|
db: &Connection,
|
|
|
|
mut func: F,
|
|
|
|
) -> Result<()> {
|
|
|
|
let mut stmt = db.prepare("select id, mid, mod, usn, flds from notes")?;
|
|
|
|
for result in stmt.query_and_then(NO_PARAMS, |row| {
|
|
|
|
let mut note = row_to_note(row)?;
|
|
|
|
func(&mut note)
|
|
|
|
})? {
|
|
|
|
result?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn row_to_note(row: &Row) -> Result<Note> {
|
|
|
|
Ok(Note {
|
|
|
|
id: row.get(0)?,
|
|
|
|
mid: row.get(1)?,
|
|
|
|
mtime_secs: row.get(2)?,
|
|
|
|
usn: row.get(3)?,
|
|
|
|
fields: row
|
|
|
|
.get_raw(4)
|
|
|
|
.as_str()?
|
|
|
|
.split('\x1f')
|
|
|
|
.map(|s| s.to_string())
|
|
|
|
.collect(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(super) fn set_note(db: &Connection, note: &mut Note, note_type: &NoteType) -> Result<()> {
|
2020-03-05 05:55:03 +01:00
|
|
|
note.mtime_secs = i64_unix_secs();
|
2020-02-10 05:19:39 +01:00
|
|
|
// hard-coded for now
|
|
|
|
note.usn = -1;
|
2020-03-17 05:52:55 +01:00
|
|
|
let field1_nohtml = strip_html_preserving_image_filenames(¬e.fields()[0]);
|
|
|
|
let csum = field_checksum(field1_nohtml.as_ref());
|
|
|
|
let sort_field = if note_type.sort_field_idx == 0 {
|
|
|
|
field1_nohtml
|
|
|
|
} else {
|
|
|
|
strip_html_preserving_image_filenames(
|
|
|
|
note.fields()
|
|
|
|
.get(note_type.sort_field_idx as usize)
|
|
|
|
.ok_or_else(|| AnkiError::DBError {
|
|
|
|
info: "sort field out of range".to_string(),
|
|
|
|
kind: DBErrorKind::MissingEntity,
|
|
|
|
})?,
|
|
|
|
)
|
|
|
|
};
|
2020-02-10 05:19:39 +01:00
|
|
|
|
|
|
|
let mut stmt =
|
|
|
|
db.prepare_cached("update notes set mod=?,usn=?,flds=?,sfld=?,csum=? where id=?")?;
|
|
|
|
stmt.execute(params![
|
|
|
|
note.mtime_secs,
|
|
|
|
note.usn,
|
|
|
|
note.fields().join("\x1f"),
|
|
|
|
sort_field,
|
|
|
|
csum,
|
|
|
|
note.id,
|
|
|
|
])?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|