From 63f8e510d891a917ae0ee2f02369f10c0ebe4a13 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 17 Aug 2020 12:30:14 +1000 Subject: [PATCH 1/7] journal mode was not being set on media db --- rslib/src/media/database.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rslib/src/media/database.rs b/rslib/src/media/database.rs index e4fef9027..1ce500567 100644 --- a/rslib/src/media/database.rs +++ b/rslib/src/media/database.rs @@ -19,7 +19,7 @@ pub(super) fn open_or_create>(path: P) -> Result { db.pragma_update(None, "page_size", &4096)?; db.pragma_update(None, "legacy_file_format", &false)?; - db.pragma_update(None, "journal", &"wal")?; + db.pragma_update_and_check(None, "journal_mode", &"wal", |_| Ok(()))?; initial_db_setup(&mut db)?; From 3d0d21e4bcd05352ba4874b6b10c81088bb39aca Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 17 Aug 2020 17:50:27 +1000 Subject: [PATCH 2/7] fix duplicates not escaping html https://forums.ankiweb.net/t/bug-report-showdupes-not-html-escaping-field-content/2167 --- qt/aqt/editor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index 2f0e95c81..5ffc57981 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -490,7 +490,7 @@ class Editor: self.web.eval("setBackgrounds(%s);" % json.dumps(cols)) def showDupes(self): - contents = stripHTMLMedia(self.note.fields[0]) + contents = html.escape(stripHTMLMedia(self.note.fields[0])) browser = aqt.dialogs.open("Browser", self.mw) browser.form.searchEdit.lineEdit().setText( '"dupe:%s,%s"' % (self.note.model()["id"], contents) From 9e53c84a35d5058dc55c31c784f8bba0d45b566a Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 17 Aug 2020 18:14:00 +1000 Subject: [PATCH 3/7] fix globs not working in bulk tag add/remove --- rslib/src/search/sqlwriter.rs | 36 +---------------------------------- rslib/src/tags.rs | 11 ++++++----- rslib/src/text.rs | 32 +++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 40 deletions(-) diff --git a/rslib/src/search/sqlwriter.rs b/rslib/src/search/sqlwriter.rs index b145c4635..5b24f6453 100644 --- a/rslib/src/search/sqlwriter.rs +++ b/rslib/src/search/sqlwriter.rs @@ -9,11 +9,9 @@ use crate::{ err::Result, notes::field_checksum, notetype::NoteTypeID, - text::matches_wildcard, + text::{matches_wildcard, text_to_re}, text::{normalize_to_nfc, strip_html_preserving_image_filenames, without_combining}, }; -use lazy_static::lazy_static; -use regex::{Captures, Regex}; use std::{borrow::Cow, fmt::Write}; pub(crate) struct SqlWriter<'a> { @@ -458,38 +456,6 @@ fn glob_to_re(glob: &str) -> Option { Some(text_to_re(glob)) } -/// Escape text, converting glob characters to regex syntax, then return. -fn text_to_re(glob: &str) -> String { - lazy_static! { - static ref ESCAPED: Regex = Regex::new(r"(\\\\)?\\\*").unwrap(); - static ref GLOB: Regex = Regex::new(r"(\\\\)?[_%]").unwrap(); - } - - let escaped = regex::escape(glob); - - let text = ESCAPED.replace_all(&escaped, |caps: &Captures| { - if caps.get(0).unwrap().as_str().len() == 2 { - ".*" - } else { - r"\*" - } - }); - - let text2 = GLOB.replace_all(&text, |caps: &Captures| { - match caps.get(0).unwrap().as_str() { - "_" => ".", - "%" => ".*", - other => { - // strip off the escaping char - &other[2..] - } - } - .to_string() - }); - - text2.into() -} - #[derive(Debug, PartialEq, Clone, Copy)] pub enum RequiredTable { Notes, diff --git a/rslib/src/tags.rs b/rslib/src/tags.rs index 93b22e41a..a3cd45106 100644 --- a/rslib/src/tags.rs +++ b/rslib/src/tags.rs @@ -5,6 +5,7 @@ use crate::{ collection::Collection, err::{AnkiError, Result}, notes::{NoteID, TransformNoteOutput}, + text::text_to_re, {text::normalize_to_nfc, types::Usn}, }; use regex::{NoExpand, Regex, Replacer}; @@ -123,11 +124,7 @@ impl Collection { // generate regexps let tags = split_tags(tags) .map(|tag| { - let tag = if regex { - tag.into() - } else { - regex::escape(tag) - }; + let tag = if regex { tag.into() } else { text_to_re(tag) }; Regex::new(&format!("(?i){}", tag)) .map_err(|_| AnkiError::invalid_input("invalid regex")) }) @@ -234,6 +231,10 @@ mod test { let note = col.storage.get_note(note.id)?.unwrap(); assert_eq!(note.tags[0], "bar"); + col.replace_tags_for_notes(&[note.id], "b*r", "baz", false)?; + let note = col.storage.get_note(note.id)?.unwrap(); + assert_eq!(note.tags[0], "baz"); + col.replace_tags_for_notes(&[note.id], "b.r", "baz", true)?; let note = col.storage.get_note(note.id)?.unwrap(); assert_eq!(note.tags[0], "baz"); diff --git a/rslib/src/text.rs b/rslib/src/text.rs index 7272b3e6d..57845615e 100644 --- a/rslib/src/text.rs +++ b/rslib/src/text.rs @@ -261,6 +261,38 @@ pub(crate) fn without_combining(s: &str) -> Cow { .into() } +/// Escape text, converting glob characters to regex syntax, then return. +pub(crate) fn text_to_re(glob: &str) -> String { + lazy_static! { + static ref ESCAPED: Regex = Regex::new(r"(\\\\)?\\\*").unwrap(); + static ref GLOB: Regex = Regex::new(r"(\\\\)?[_%]").unwrap(); + } + + let escaped = regex::escape(glob); + + let text = ESCAPED.replace_all(&escaped, |caps: &Captures| { + if caps.get(0).unwrap().as_str().len() == 2 { + ".*" + } else { + r"\*" + } + }); + + let text2 = GLOB.replace_all(&text, |caps: &Captures| { + match caps.get(0).unwrap().as_str() { + "_" => ".", + "%" => ".*", + other => { + // strip off the escaping char + &other[2..] + } + } + .to_string() + }); + + text2.into() +} + #[cfg(test)] mod test { use super::matches_wildcard; From d24bd7609c731fadb13717aac5d62adb39156fc7 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 17 Aug 2020 19:44:09 +1000 Subject: [PATCH 4/7] allow setting sort field before add --- rslib/src/notetype/mod.rs | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/rslib/src/notetype/mod.rs b/rslib/src/notetype/mod.rs index 57ce56d2d..63fd95a39 100644 --- a/rslib/src/notetype/mod.rs +++ b/rslib/src/notetype/mod.rs @@ -163,15 +163,26 @@ impl NoteType { .collect() } + /// Adjust sort index to match repositioned fields. fn reposition_sort_idx(&mut self) { - let adjusted_idx = self.fields.iter().enumerate().find_map(|(idx, f)| { - if f.ord == Some(self.config.sort_field_idx) { - Some(idx) - } else { - None - } - }); - self.config.sort_field_idx = adjusted_idx.unwrap_or(0) as u32; + self.config.sort_field_idx = self + .fields + .iter() + .enumerate() + .find_map(|(idx, f)| { + if f.ord == Some(self.config.sort_field_idx) { + Some(idx as u32) + } else { + None + } + }) + .unwrap_or_else(|| { + // provided ordinal not on any existing field; cap to bounds + self.config + .sort_field_idx + .max(0) + .min((self.fields.len() - 1) as u32) + }); } pub(crate) fn normalize_names(&mut self) { From dab0c9ef7cee4df80eecfddf032955e023580b0c Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 17 Aug 2020 19:44:27 +1000 Subject: [PATCH 5/7] catch attempt to update missing note https://forums.ankiweb.net/t/bug-report-editor-updating-non-existing-card/2117/4?u=dae --- rslib/src/notes.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rslib/src/notes.rs b/rslib/src/notes.rs index 23e9f7019..9a1256c5a 100644 --- a/rslib/src/notes.rs +++ b/rslib/src/notes.rs @@ -274,6 +274,8 @@ impl Collection { // nothing to do return Ok(()); } + } else { + return Err(AnkiError::NotFound); } self.transact(None, |col| { From 06275478e01185814d9ec60ec22d42fcd0b71ca8 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 17 Aug 2020 20:07:48 +1000 Subject: [PATCH 6/7] close edit current window when card deleted https://forums.ankiweb.net/t/bug-report-editor-updating-non-existing-card/2117 --- qt/aqt/browser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index f68ad3670..9f1865366 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -1564,7 +1564,7 @@ where id in %s""" newRow = max(newRow, 0) self.model.focusedCard = self.model.cards[newRow] self.model.endReset() - self.mw.requireReset() + self.mw.reset() tooltip( ngettext("%d note deleted.", "%d notes deleted.", len(nids)) % len(nids) ) From 177ced7a31be8692abcb677a93639f5d708f033c Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 17 Aug 2020 20:08:11 +1000 Subject: [PATCH 7/7] clear undo queue on sync https://forums.ankiweb.net/t/ios-ipad-sync-failure-with-filtered-decks/1617/34 --- qt/aqt/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qt/aqt/main.py b/qt/aqt/main.py index e1d680a4f..60b09a6ad 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -890,6 +890,7 @@ title="%s" %s>%s""" % ( self.media_syncer.start() def on_collection_sync_finished(): + self.col.clearUndo() self.reset() after_sync()