update backend to support undoing of notetype changes

This commit is contained in:
Damien Elmes 2021-04-29 23:28:42 +10:00
parent 9d604f1ad0
commit 2ff8c20686
19 changed files with 338 additions and 158 deletions

View File

@ -24,3 +24,6 @@ undo-forget-card = Forget Card
undo-set-flag = Set Flag
undo-build-filtered-deck = Build Deck
undo-expand-collapse = Expand/Collapse
undo-add-notetype = Add Notetype
undo-remove-notetype = Remove Notetype
undo-update-notetype = Update Notetype

View File

@ -200,6 +200,8 @@ service ConfigService {
}
service NotetypesService {
rpc AddNotetype(Notetype) returns (OpChangesWithId);
rpc UpdateNotetype(Notetype) returns (OpChanges);
rpc AddOrUpdateNotetype(AddOrUpdateNotetypeIn) returns (NotetypeId);
rpc GetStockNotetypeLegacy(StockNotetype) returns (Json);
rpc GetNotetype(NotetypeId) returns (Notetype);
@ -207,7 +209,7 @@ service NotetypesService {
rpc GetNotetypeNames(Empty) returns (NotetypeNames);
rpc GetNotetypeNamesAndCounts(Empty) returns (NotetypeUseCounts);
rpc GetNotetypeIdByName(String) returns (NotetypeId);
rpc RemoveNotetype(NotetypeId) returns (Empty);
rpc RemoveNotetype(NotetypeId) returns (OpChanges);
}
service CardRenderingService {
@ -503,14 +505,14 @@ message Notetype {
OptionalUInt32 ord = 1;
string name = 2;
uint32 mtime_secs = 3;
int64 mtime_secs = 3;
sint32 usn = 4;
Config config = 5;
}
int64 id = 1;
string name = 2;
uint32 mtime_secs = 3;
int64 mtime_secs = 3;
sint32 usn = 4;
Config config = 7;
repeated Field fields = 8;

View File

@ -10,14 +10,35 @@ use crate::{
};
impl NotetypesService for Backend {
fn add_notetype(&self, input: pb::Notetype) -> Result<pb::OpChangesWithId> {
let mut notetype: Notetype = input.into();
self.with_col(|col| {
Ok(col
.add_notetype(&mut notetype)?
.map(|_| notetype.id.0)
.into())
})
}
fn update_notetype(&self, input: pb::Notetype) -> Result<pb::OpChanges> {
let mut notetype: Notetype = input.into();
self.with_col(|col| col.update_notetype(&mut notetype))
.map(Into::into)
}
fn add_or_update_notetype(&self, input: pb::AddOrUpdateNotetypeIn) -> Result<pb::NotetypeId> {
self.with_col(|col| {
let legacy: NotetypeSchema11 = serde_json::from_slice(&input.json)?;
let mut nt: Notetype = legacy.into();
if !input.preserve_usn_and_mtime {
nt.set_modified(col.usn()?);
}
if nt.id.0 == 0 {
col.add_notetype(&mut nt)?;
} else if !input.preserve_usn_and_mtime {
col.update_notetype(&mut nt)?;
} else {
col.update_notetype(&mut nt, input.preserve_usn_and_mtime)?;
col.add_or_update_notetype_with_existing_id(&mut nt)?;
}
Ok(pb::NotetypeId { ntid: nt.id.0 })
})
@ -91,8 +112,22 @@ impl NotetypesService for Backend {
})
}
fn remove_notetype(&self, input: pb::NotetypeId) -> Result<pb::Empty> {
fn remove_notetype(&self, input: pb::NotetypeId) -> Result<pb::OpChanges> {
self.with_col(|col| col.remove_notetype(input.into()))
.map(Into::into)
}
}
impl From<pb::Notetype> for Notetype {
fn from(n: pb::Notetype) -> Self {
Notetype {
id: n.id.into(),
name: n.name,
mtime_secs: n.mtime_secs.into(),
usn: n.usn.into(),
fields: n.fields.into_iter().map(Into::into).collect(),
templates: n.templates.into_iter().map(Into::into).collect(),
config: n.config.unwrap_or_default(),
}
}
}

View File

@ -259,7 +259,9 @@ impl Collection {
let ctx = genctx.get_or_insert_with(|| {
CardGenContext::new(&nt, self.get_last_deck_added_to_for_notetype(nt.id), usn)
});
self.update_note_inner_generating_cards(&ctx, &mut note, &original, false, norm)?;
self.update_note_inner_generating_cards(
&ctx, &mut note, &original, false, norm, true,
)?;
}
}

View File

@ -95,6 +95,7 @@ impl Collection {
changed,
generate_cards: true,
mark_modified: true,
update_tags: false,
})
})
}

View File

@ -34,6 +34,7 @@ pub(crate) struct TransformNoteOutput {
pub changed: bool,
pub generate_cards: bool,
pub mark_modified: bool,
pub update_tags: bool,
}
#[derive(Debug, PartialEq, Clone)]
@ -387,7 +388,7 @@ impl Collection {
let last_deck = self.get_last_deck_added_to_for_notetype(note.notetype_id);
let ctx = CardGenContext::new(&nt, last_deck, self.usn()?);
let norm = self.get_bool(BoolKey::NormalizeNoteText);
self.update_note_inner_generating_cards(&ctx, note, &existing_note, true, norm)?;
self.update_note_inner_generating_cards(&ctx, note, &existing_note, true, norm, true)?;
Ok(())
}
@ -398,6 +399,7 @@ impl Collection {
original: &Note,
mark_note_modified: bool,
normalize_text: bool,
update_tags: bool,
) -> Result<()> {
self.update_note_inner_without_cards(
note,
@ -406,10 +408,13 @@ impl Collection {
ctx.usn,
mark_note_modified,
normalize_text,
update_tags,
)?;
self.generate_cards_for_existing_note(ctx, note)
}
// TODO: refactor into struct
#[allow(clippy::clippy::too_many_arguments)]
pub(crate) fn update_note_inner_without_cards(
&mut self,
note: &mut Note,
@ -418,8 +423,11 @@ impl Collection {
usn: Usn,
mark_note_modified: bool,
normalize_text: bool,
update_tags: bool,
) -> Result<()> {
self.canonify_note_tags(note, usn)?;
if update_tags {
self.canonify_note_tags(note, usn)?;
}
note.prepare_for_update(nt, normalize_text)?;
if mark_note_modified {
note.set_modified(usn);
@ -453,6 +461,7 @@ impl Collection {
changed: true,
generate_cards,
mark_modified: mark_notes_modified,
update_tags: true,
})
})
}
@ -499,6 +508,7 @@ impl Collection {
&original,
out.mark_modified,
norm,
out.update_tags,
)?;
} else {
self.update_note_inner_without_cards(
@ -508,6 +518,7 @@ impl Collection {
usn,
out.mark_modified,
norm,
out.update_tags,
)?;
}
@ -570,6 +581,7 @@ impl Collection {
changed,
generate_cards: false,
mark_modified: true,
update_tags: true,
})
})
.map(|_| ())

View File

@ -7,7 +7,7 @@ use crate::{
error::{AnkiError, Result},
};
#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Clone)]
pub struct NoteField {
pub ord: Option<u32>,
pub name: String,
@ -24,6 +24,16 @@ impl From<NoteField> for NoteFieldProto {
}
}
impl From<NoteFieldProto> for NoteField {
fn from(f: NoteFieldProto) -> Self {
NoteField {
ord: f.ord.map(|n| n.val),
name: f.name,
config: f.config.unwrap_or_default(),
}
}
}
impl NoteField {
pub fn new(name: impl Into<String>) -> Self {
NoteField {

View File

@ -9,6 +9,7 @@ mod schema11;
mod schemachange;
mod stock;
mod templates;
pub(crate) mod undo;
use std::{
collections::{HashMap, HashSet},
@ -48,7 +49,7 @@ pub(crate) const DEFAULT_CSS: &str = include_str!("styling.css");
pub(crate) const DEFAULT_LATEX_HEADER: &str = include_str!("header.tex");
pub(crate) const DEFAULT_LATEX_FOOTER: &str = r"\end{document}";
#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Clone)]
pub struct Notetype {
pub id: NotetypeId,
pub name: String,
@ -97,41 +98,39 @@ impl Notetype {
}
impl Collection {
/// Add a new notetype, and allocate it an ID.
pub fn add_notetype(&mut self, notetype: &mut Notetype) -> Result<OpOutput<()>> {
self.transact(Op::AddNotetype, |col| {
let usn = col.usn()?;
notetype.set_modified(usn);
col.add_notetype_inner(notetype, usn)
})
}
/// Saves changes to a note type. This will force a full sync if templates
/// or fields have been added/removed/reordered.
pub fn update_notetype(&mut self, nt: &mut Notetype, preserve_usn: bool) -> Result<()> {
let existing = self.get_notetype(nt.id)?;
let norm = self.get_bool(BoolKey::NormalizeNoteText);
nt.prepare_for_update(existing.as_ref().map(AsRef::as_ref))?;
self.transact_no_undo(|col| {
if let Some(existing_notetype) = existing {
if existing_notetype.mtime_secs > nt.mtime_secs {
return Err(AnkiError::invalid_input("attempt to save stale notetype"));
}
col.update_notes_for_changed_fields(
nt,
existing_notetype.fields.len(),
existing_notetype.config.sort_field_idx,
norm,
)?;
col.update_cards_for_changed_templates(nt, existing_notetype.templates.len())?;
}
pub fn update_notetype(&mut self, notetype: &mut Notetype) -> Result<OpOutput<()>> {
self.transact(Op::UpdateNotetype, |col| {
let original = col
.storage
.get_notetype(notetype.id)?
.ok_or(AnkiError::NotFound)?;
let usn = col.usn()?;
if !preserve_usn {
nt.set_modified(usn);
}
col.ensure_notetype_name_unique(nt, usn)?;
notetype.set_modified(usn);
col.add_or_update_notetype_with_existing_id_inner(notetype, Some(original), usn)
})
}
col.storage.update_notetype_config(&nt)?;
col.storage.update_notetype_fields(nt.id, &nt.fields)?;
col.storage
.update_notetype_templates(nt.id, &nt.templates)?;
// fixme: update cache instead of clearing
col.state.notetype_cache.remove(&nt.id);
Ok(())
/// Used to support the current importing code; does not mark notetype as modified,
/// and does not support undo.
pub fn add_or_update_notetype_with_existing_id(
&mut self,
notetype: &mut Notetype,
) -> Result<()> {
self.transact_no_undo(|col| {
let usn = col.usn()?;
let existing = col.storage.get_notetype(notetype.id)?;
col.add_or_update_notetype_with_existing_id_inner(notetype, existing, usn)
})
}
@ -169,8 +168,8 @@ impl Collection {
.collect()
}
pub fn remove_notetype(&mut self, ntid: NotetypeId) -> Result<()> {
self.transact_no_undo(|col| col.remove_notetype_inner(ntid))
pub fn remove_notetype(&mut self, ntid: NotetypeId) -> Result<OpOutput<()>> {
self.transact(Op::RemoveNotetype, |col| col.remove_notetype_inner(ntid))
}
}
@ -437,7 +436,7 @@ impl From<Notetype> for NotetypeProto {
NotetypeProto {
id: nt.id.0,
name: nt.name,
mtime_secs: nt.mtime_secs.0 as u32,
mtime_secs: nt.mtime_secs.0,
usn: nt.usn.0,
config: Some(nt.config),
fields: nt.fields.into_iter().map(Into::into).collect(),
@ -447,21 +446,6 @@ impl From<Notetype> for NotetypeProto {
}
impl Collection {
/// Add a new notetype, and allocate it an ID.
pub fn add_notetype(&mut self, nt: &mut Notetype) -> Result<()> {
self.transact_no_undo(|col| {
let usn = col.usn()?;
nt.set_modified(usn);
col.add_notetype_inner(nt, usn)
})
}
pub(crate) fn add_notetype_inner(&mut self, nt: &mut Notetype, usn: Usn) -> Result<()> {
nt.prepare_for_update(None)?;
self.ensure_notetype_name_unique(nt, usn)?;
self.storage.add_new_notetype(nt)
}
pub(crate) fn ensure_notetype_name_unique(
&self,
notetype: &mut Notetype,
@ -482,7 +466,52 @@ impl Collection {
Ok(())
}
/// Caller must set notetype as modified if appropriate.
pub(crate) fn add_notetype_inner(&mut self, notetype: &mut Notetype, usn: Usn) -> Result<()> {
notetype.prepare_for_update(None)?;
self.ensure_notetype_name_unique(notetype, usn)?;
self.add_notetype_undoable(notetype)
}
/// - Caller must set notetype as modified if appropriate.
/// - This only supports undo when an existing notetype is passed in.
fn add_or_update_notetype_with_existing_id_inner(
&mut self,
notetype: &mut Notetype,
original: Option<Notetype>,
usn: Usn,
) -> Result<()> {
let normalize = self.get_bool(BoolKey::NormalizeNoteText);
notetype.prepare_for_update(original.as_ref())?;
self.ensure_notetype_name_unique(notetype, usn)?;
self.state.notetype_cache.remove(&notetype.id);
if let Some(original) = original {
self.update_notes_for_changed_fields(
notetype,
original.fields.len(),
original.config.sort_field_idx,
normalize,
)?;
self.update_cards_for_changed_templates(notetype, original.templates.len())?;
self.update_notetype_undoable(notetype, original)?;
} else {
// adding with existing id for old undo code, bypass undo
self.storage
.add_or_update_notetype_with_existing_id(&notetype)?;
}
Ok(())
}
fn remove_notetype_inner(&mut self, ntid: NotetypeId) -> Result<()> {
let notetype = if let Some(notetype) = self.storage.get_notetype(ntid)? {
notetype
} else {
// already removed
return Ok(());
};
// remove associated cards/notes
let usn = self.usn()?;
let note_ids = self.search_notes_unordered(ntid)?;
@ -492,7 +521,7 @@ impl Collection {
self.set_schema_modified()?;
self.state.notetype_cache.remove(&ntid);
self.clear_aux_config_for_notetype(ntid)?;
self.storage.remove_notetype(ntid)?;
self.remove_notetype_only_undoable(notetype)?;
// update last-used notetype
let all = self.storage.get_all_notetype_names()?;

View File

@ -1,8 +1,15 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use std::collections::HashMap;
use super::{CardGenContext, Notetype};
use crate::{collection::Collection, error::Result};
use crate::{
collection::Collection,
error::Result,
match_all,
search::{Node, SortMode, TemplateKind},
};
/// True if any ordinals added, removed or reordered.
fn ords_changed(ords: &[Option<u32>], previous_len: usize) -> bool {
@ -16,15 +23,15 @@ fn ords_changed(ords: &[Option<u32>], previous_len: usize) -> bool {
#[derive(Default, PartialEq, Debug)]
struct TemplateOrdChanges {
added: Vec<u32>,
removed: Vec<u32>,
removed: Vec<u16>,
// map of old->new
moved: Vec<(u32, u32)>,
moved: HashMap<u16, u16>,
}
impl TemplateOrdChanges {
fn new(ords: Vec<Option<u32>>, previous_len: u32) -> Self {
let mut changes = TemplateOrdChanges::default();
let mut removed: Vec<_> = (0..previous_len).map(|v| Some(v as u32)).collect();
let mut removed: Vec<_> = (0..previous_len).map(|v| Some(v as u16)).collect();
for (idx, old_ord) in ords.into_iter().enumerate() {
if let Some(old_ord) = old_ord {
if let Some(entry) = removed.get_mut(old_ord as usize) {
@ -34,7 +41,7 @@ impl TemplateOrdChanges {
if old_ord == idx as u32 {
// no action
} else {
changes.moved.push((old_ord as u32, idx as u32));
changes.moved.insert(old_ord as u16, idx as u16);
}
} else {
changes.added.push(idx as u32);
@ -57,6 +64,7 @@ impl Collection {
previous_sort_idx: u32,
normalize_text: bool,
) -> Result<()> {
let usn = self.usn()?;
let ords: Vec<_> = nt.fields.iter().map(|f| f.ord).collect();
if !ords_changed(&ords, previous_field_count) {
if nt.config.sort_field_idx != previous_sort_idx {
@ -64,8 +72,16 @@ impl Collection {
let nids = self.search_notes_unordered(nt.id)?;
for nid in nids {
let mut note = self.storage.get_note(nid)?.unwrap();
note.prepare_for_update(nt, normalize_text)?;
self.storage.update_note(&note)?;
let original = note.clone();
self.update_note_inner_without_cards(
&mut note,
&original,
nt,
usn,
true,
normalize_text,
false,
)?;
}
return Ok(());
} else {
@ -74,11 +90,13 @@ impl Collection {
}
}
// fields have changed
self.set_schema_modified()?;
let nids = self.search_notes_unordered(nt.id)?;
let usn = self.usn()?;
for nid in nids {
let mut note = self.storage.get_note(nid)?.unwrap();
let original = note.clone();
*note.fields_mut() = ords
.iter()
.map(|f| {
@ -93,9 +111,15 @@ impl Collection {
})
.map(Into::into)
.collect();
note.prepare_for_update(nt, normalize_text)?;
note.set_modified(usn);
self.storage.update_note(&note)?;
self.update_note_inner_without_cards(
&mut note,
&original,
nt,
usn,
true,
normalize_text,
false,
)?;
}
Ok(())
}
@ -115,15 +139,42 @@ impl Collection {
}
self.set_schema_modified()?;
let usn = self.usn()?;
let changes = TemplateOrdChanges::new(ords, previous_template_count as u32);
// remove any cards where the template was deleted
if !changes.removed.is_empty() {
self.storage
.remove_cards_for_deleted_templates(nt.id, &changes.removed)?;
let ords = Node::any(
changes
.removed
.into_iter()
.map(TemplateKind::Ordinal)
.map(Into::into),
);
self.search_cards_into_table(match_all![nt.id, ords], SortMode::NoOrder)?;
for card in self.storage.all_searched_cards()? {
self.remove_card_and_add_grave_undoable(card, usn)?;
}
self.storage.clear_searched_cards_table()?;
}
// update ordinals for cards with a repositioned template
if !changes.moved.is_empty() {
self.storage
.move_cards_for_repositioned_templates(nt.id, &changes.moved)?;
let ords = Node::any(
changes
.moved
.keys()
.cloned()
.map(TemplateKind::Ordinal)
.map(Into::into),
);
self.search_cards_into_table(match_all![nt.id, ords], SortMode::NoOrder)?;
for mut card in self.storage.all_searched_cards()? {
let original = card.clone();
card.template_idx = *changes.moved.get(&card.template_idx).unwrap();
self.update_card_inner(&mut card, original, usn)?;
}
self.storage.clear_searched_cards_table()?;
}
let last_deck = self.get_last_deck_added_to_for_notetype(nt.id);
@ -165,7 +216,7 @@ mod test {
TemplateOrdChanges::new(vec![Some(1)], 2),
TemplateOrdChanges {
removed: vec![0],
moved: vec![(1, 0)],
moved: vec![(1, 0)].into_iter().collect(),
..Default::default()
}
);
@ -180,7 +231,7 @@ mod test {
TemplateOrdChanges::new(vec![Some(2), None, Some(0)], 2),
TemplateOrdChanges {
added: vec![1],
moved: vec![(2, 0), (0, 2)],
moved: vec![(2, 0), (0, 2)].into_iter().collect(),
removed: vec![1],
}
);
@ -188,7 +239,7 @@ mod test {
TemplateOrdChanges::new(vec![None, Some(2), None, Some(4)], 5),
TemplateOrdChanges {
added: vec![0, 2],
moved: vec![(2, 1), (4, 3)],
moved: vec![(2, 1), (4, 3)].into_iter().collect(),
removed: vec![0, 1, 3],
}
);
@ -208,13 +259,13 @@ mod test {
col.add_note(&mut note, DeckId(1))?;
nt.add_field("three");
col.update_notetype(&mut nt, false)?;
col.update_notetype(&mut nt)?;
let note = col.storage.get_note(note.id)?.unwrap();
assert_eq!(note.fields(), &["one".to_string(), "two".into(), "".into()]);
nt.fields.remove(1);
col.update_notetype(&mut nt, false)?;
col.update_notetype(&mut nt)?;
let note = col.storage.get_note(note.id)?.unwrap();
assert_eq!(note.fields(), &["one".to_string(), "".into()]);
@ -231,13 +282,13 @@ mod test {
.unwrap();
nt.templates[0].config.q_format += "\n{{#Front}}{{some:Front}}{{Back}}{{/Front}}";
nt.fields[0].name = "Test".into();
col.update_notetype(&mut nt, false)?;
col.update_notetype(&mut nt)?;
assert_eq!(
&nt.templates[0].config.q_format,
"{{Test}}\n{{#Test}}{{some:Test}}{{Back}}{{/Test}}"
);
nt.fields.remove(0);
col.update_notetype(&mut nt, false)?;
col.update_notetype(&mut nt)?;
assert_eq!(&nt.templates[0].config.q_format, "\n{{Back}}");
Ok(())
@ -263,7 +314,7 @@ mod test {
// add an extra card template
nt.add_template("card 2", "{{Front}}", "");
col.update_notetype(&mut nt, false)?;
col.update_notetype(&mut nt)?;
assert_eq!(
col.search_cards(note.id, SortMode::NoOrder).unwrap().len(),

View File

@ -16,7 +16,7 @@ impl SqliteStorage {
pub(crate) fn add_stock_notetypes(&self, tr: &I18n) -> Result<()> {
for (idx, mut nt) in all_stock_notetypes(tr).into_iter().enumerate() {
nt.prepare_for_update(None)?;
self.add_new_notetype(&mut nt)?;
self.add_notetype(&mut nt)?;
if idx == Kind::Basic as usize {
self.set_config_entry(&ConfigEntry::boxed(
ConfigKey::CurrentNotetypeId.into(),

View File

@ -11,7 +11,7 @@ use crate::{
types::Usn,
};
#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Clone)]
pub struct CardTemplate {
pub ord: Option<u32>,
pub mtime_secs: TimestampSecs,
@ -58,7 +58,7 @@ impl From<CardTemplate> for CardTemplateProto {
fn from(t: CardTemplate) -> Self {
CardTemplateProto {
ord: t.ord.map(|n| OptionalUInt32 { val: n }),
mtime_secs: t.mtime_secs.0 as u32,
mtime_secs: t.mtime_secs.0,
usn: t.usn.0,
name: t.name,
config: Some(t.config),
@ -66,6 +66,18 @@ impl From<CardTemplate> for CardTemplateProto {
}
}
impl From<CardTemplateProto> for CardTemplate {
fn from(t: CardTemplateProto) -> Self {
CardTemplate {
ord: t.ord.map(|n| n.val),
mtime_secs: t.mtime_secs.into(),
usn: t.usn.into(),
name: t.name,
config: t.config.unwrap_or_default(),
}
}
}
impl CardTemplate {
pub fn new<S1, S2, S3>(name: S1, qfmt: S2, afmt: S3) -> Self
where

View File

@ -0,0 +1,60 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use crate::prelude::*;
#[derive(Debug)]
pub(crate) enum UndoableNotetypeChange {
Added(Box<Notetype>),
Updated(Box<Notetype>),
Removed(Box<Notetype>),
}
impl Collection {
pub(crate) fn undo_notetype_change(&mut self, change: UndoableNotetypeChange) -> Result<()> {
match change {
UndoableNotetypeChange::Added(nt) => self.remove_notetype_only_undoable(*nt),
UndoableNotetypeChange::Updated(nt) => {
let current = self
.storage
.get_notetype(nt.id)?
.ok_or_else(|| AnkiError::invalid_input("notetype disappeared"))?;
self.update_notetype_undoable(&nt, current)
}
UndoableNotetypeChange::Removed(nt) => self.restore_deleted_notetype(*nt),
}
}
pub(crate) fn remove_notetype_only_undoable(&mut self, notetype: Notetype) -> Result<()> {
self.storage.remove_notetype(notetype.id)?;
self.save_undo(UndoableNotetypeChange::Removed(Box::new(notetype)));
Ok(())
}
pub(super) fn add_notetype_undoable(
&mut self,
notetype: &mut Notetype,
) -> Result<(), AnkiError> {
self.storage.add_notetype(notetype)?;
self.save_undo(UndoableNotetypeChange::Added(Box::new(notetype.clone())));
Ok(())
}
pub(super) fn update_notetype_undoable(
&mut self,
notetype: &Notetype,
original: Notetype,
) -> Result<()> {
self.save_undo(UndoableNotetypeChange::Updated(Box::new(original)));
self.storage
.add_or_update_notetype_with_existing_id(notetype)
}
fn restore_deleted_notetype(&mut self, notetype: Notetype) -> Result<()> {
self.storage
.add_or_update_notetype_with_existing_id(&notetype)?;
self.save_undo(UndoableNotetypeChange::Added(Box::new(notetype)));
Ok(())
}
}

View File

@ -7,6 +7,7 @@ use crate::prelude::*;
pub enum Op {
AddDeck,
AddNote,
AddNotetype,
AnswerCard,
BuildFilteredDeck,
Bury,
@ -17,6 +18,7 @@ pub enum Op {
RebuildFilteredDeck,
RemoveDeck,
RemoveNote,
RemoveNotetype,
RemoveTag,
RenameDeck,
ReparentDeck,
@ -35,6 +37,7 @@ pub enum Op {
UpdateNote,
UpdatePreferences,
UpdateTag,
UpdateNotetype,
SetCurrentDeck,
}
@ -72,6 +75,9 @@ impl Op {
Op::ExpandCollapse => tr.undo_expand_collapse(),
Op::SetCurrentDeck => tr.browsing_change_deck(),
Op::UpdateDeckConfig => tr.deck_config_title(),
Op::AddNotetype => tr.undo_add_notetype(),
Op::RemoveNotetype => tr.undo_remove_notetype(),
Op::UpdateNotetype => tr.undo_update_notetype(),
}
.into()
}

View File

@ -1,7 +0,0 @@
DELETE FROM cards
WHERE nid IN (
SELECT id
FROM notes
WHERE mid = ?
)
AND ord = ?;

View File

@ -114,11 +114,7 @@ impl SqliteStorage {
.collect()
}
pub(crate) fn update_notetype_fields(
&self,
ntid: NotetypeId,
fields: &[NoteField],
) -> Result<()> {
fn update_notetype_fields(&self, ntid: NotetypeId, fields: &[NoteField]) -> Result<()> {
self.db
.prepare_cached("delete from fields where ntid=?")?
.execute(&[ntid])?;
@ -166,7 +162,7 @@ impl SqliteStorage {
.collect()
}
pub(crate) fn update_notetype_templates(
fn update_notetype_templates(
&self,
ntid: NotetypeId,
templates: &[CardTemplate],
@ -193,11 +189,14 @@ impl SqliteStorage {
Ok(())
}
pub(crate) fn update_notetype_config(&self, nt: &Notetype) -> Result<()> {
assert!(nt.id.0 != 0);
let mut stmt = self
.db
.prepare_cached(include_str!("update_notetype_core.sql"))?;
/// Notetype should have an existing id, and will be added if missing.
fn update_notetype_core(&self, nt: &Notetype) -> Result<()> {
if nt.id.0 == 0 {
return Err(AnkiError::invalid_input(
"notetype with id 0 passed in as existing",
));
}
let mut stmt = self.db.prepare_cached(include_str!("add_or_update.sql"))?;
let mut config_bytes = vec![];
nt.config.encode(&mut config_bytes)?;
stmt.execute(params![nt.id, nt.name, nt.mtime_secs, nt.usn, config_bytes])?;
@ -205,7 +204,7 @@ impl SqliteStorage {
Ok(())
}
pub(crate) fn add_new_notetype(&self, nt: &mut Notetype) -> Result<()> {
pub(crate) fn add_notetype(&self, nt: &mut Notetype) -> Result<()> {
assert!(nt.id.0 == 0);
let mut stmt = self.db.prepare_cached(include_str!("add_notetype.sql"))?;
@ -226,33 +225,15 @@ impl SqliteStorage {
Ok(())
}
/// Used for syncing.
pub(crate) fn add_or_update_notetype(&self, nt: &Notetype) -> Result<()> {
let mut stmt = self.db.prepare_cached(include_str!("add_or_update.sql"))?;
let mut config_bytes = vec![];
nt.config.encode(&mut config_bytes)?;
stmt.execute(params![nt.id, nt.name, nt.mtime_secs, nt.usn, config_bytes])?;
/// Used for both regular updates, and for syncing/import.
pub(crate) fn add_or_update_notetype_with_existing_id(&self, nt: &Notetype) -> Result<()> {
self.update_notetype_core(nt)?;
self.update_notetype_fields(nt.id, &nt.fields)?;
self.update_notetype_templates(nt.id, &nt.templates)?;
Ok(())
}
pub(crate) fn remove_cards_for_deleted_templates(
&self,
ntid: NotetypeId,
ords: &[u32],
) -> Result<()> {
let mut stmt = self
.db
.prepare(include_str!("delete_cards_for_template.sql"))?;
for ord in ords {
stmt.execute(params![ntid, ord])?;
}
Ok(())
}
pub(crate) fn remove_notetype(&self, ntid: NotetypeId) -> Result<()> {
self.db
.prepare_cached("delete from templates where ntid=?")?
@ -267,29 +248,6 @@ impl SqliteStorage {
Ok(())
}
pub(crate) fn move_cards_for_repositioned_templates(
&self,
ntid: NotetypeId,
changes: &[(u32, u32)],
) -> Result<()> {
let case_clauses: Vec<_> = changes
.iter()
.map(|(old, new)| format!("when {} then {}", old, new))
.collect();
let mut sql = format!(
"update cards set ord = (case ord {} end)
where nid in (select id from notes where mid = ?)
and ord in ",
case_clauses.join(" ")
);
ids_to_string(
&mut sql,
&changes.iter().map(|(old, _)| old).collect::<Vec<_>>(),
);
self.db.prepare(&sql)?.execute(&[ntid])?;
Ok(())
}
pub(crate) fn existing_cards_for_notetype(
&self,
ntid: NotetypeId,
@ -363,7 +321,7 @@ and ord in ",
}
nt.name.push('_');
}
self.update_notetype_config(&nt)?;
self.update_notetype_core(&nt)?;
self.update_notetype_fields(ntid, &nt.fields)?;
self.update_notetype_templates(ntid, &nt.templates)?;
}

View File

@ -1,3 +0,0 @@
INSERT
OR REPLACE INTO notetypes (id, name, mtime_secs, usn, config)
VALUES (?, ?, ?, ?, ?)

View File

@ -860,7 +860,7 @@ impl Collection {
};
if proceed {
self.ensure_notetype_name_unique(&mut nt, latest_usn)?;
self.storage.add_or_update_notetype(&nt)?;
self.storage.add_or_update_notetype_with_existing_id(&nt)?;
self.state.notetype_cache.remove(&nt.id);
}
}
@ -1503,7 +1503,7 @@ mod test {
let mut nt = col2.storage.get_notetype(nt.id)?.unwrap();
nt.name = "newer".into();
col2.update_notetype(&mut nt, false)?;
col2.update_notetype(&mut nt)?;
// sync the changes back
let out = ctx.normal_sync(&mut col2).await;

View File

@ -4,9 +4,9 @@
use crate::{
card::undo::UndoableCardChange, collection::undo::UndoableCollectionChange,
config::undo::UndoableConfigChange, deckconfig::undo::UndoableDeckConfigChange,
decks::undo::UndoableDeckChange, notes::undo::UndoableNoteChange, prelude::*,
revlog::undo::UndoableRevlogChange, scheduler::queue::undo::UndoableQueueChange,
tags::undo::UndoableTagChange,
decks::undo::UndoableDeckChange, notes::undo::UndoableNoteChange,
notetype::undo::UndoableNotetypeChange, prelude::*, revlog::undo::UndoableRevlogChange,
scheduler::queue::undo::UndoableQueueChange, tags::undo::UndoableTagChange,
};
#[derive(Debug)]
@ -20,6 +20,7 @@ pub(crate) enum UndoableChange {
Queue(UndoableQueueChange),
Config(UndoableConfigChange),
Collection(UndoableCollectionChange),
Notetype(UndoableNotetypeChange),
}
impl UndoableChange {
@ -34,6 +35,7 @@ impl UndoableChange {
UndoableChange::Config(c) => col.undo_config_change(c),
UndoableChange::DeckConfig(c) => col.undo_deck_config_change(c),
UndoableChange::Collection(c) => col.undo_collection_change(c),
UndoableChange::Notetype(c) => col.undo_notetype_change(c),
}
}
}
@ -91,3 +93,9 @@ impl From<UndoableCollectionChange> for UndoableChange {
UndoableChange::Collection(c)
}
}
impl From<UndoableNotetypeChange> for UndoableChange {
fn from(c: UndoableNotetypeChange) -> Self {
UndoableChange::Notetype(c)
}
}

View File

@ -143,6 +143,7 @@ impl UndoManager {
UndoableChange::Config(_) => changes.config = true,
UndoableChange::DeckConfig(_) => changes.deck_config = true,
UndoableChange::Collection(_) => {}
UndoableChange::Notetype(_) => changes.notetype = true,
}
}