speed up "add tags" and avoid usage of regex

This commit is contained in:
Damien Elmes 2021-03-19 14:35:44 +10:00
parent 08895c58d9
commit b287cd5238
4 changed files with 106 additions and 36 deletions

88
rslib/src/tags/bulkadd.rs Normal file
View file

@ -0,0 +1,88 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
//! Adding tags to selected notes in the browse screen.
use std::collections::HashSet;
use unicase::UniCase;
use super::{join_tags, split_tags};
use crate::{notes::NoteTags, prelude::*};
impl Collection {
pub fn add_tags_to_notes(&mut self, nids: &[NoteID], tags: &str) -> Result<OpOutput<usize>> {
self.transact(Op::UpdateTag, |col| col.add_tags_to_notes_inner(nids, tags))
}
}
impl Collection {
fn add_tags_to_notes_inner(&mut self, nids: &[NoteID], tags: &str) -> Result<usize> {
let usn = self.usn()?;
// will update tag list for any new tags, and match case
let tags_to_add = self.canonified_tags_as_vec(tags, usn)?;
// modify notes
let mut match_count = 0;
let notes = self.storage.get_note_tags_by_id_list(nids)?;
for original in notes {
if let Some(updated_tags) = add_missing_tags(&original.tags, &tags_to_add) {
match_count += 1;
let mut note = NoteTags {
tags: updated_tags,
..original
};
note.set_modified(usn);
self.update_note_tags_undoable(&note, original)?;
}
}
Ok(match_count)
}
}
/// Returns the sorted new tag string if any tags were added.
fn add_missing_tags(note_tags: &str, desired: &[UniCase<String>]) -> Option<String> {
let mut note_tags: HashSet<_> = split_tags(note_tags)
.map(ToOwned::to_owned)
.map(UniCase::new)
.collect();
let mut modified = false;
for tag in desired {
if !note_tags.contains(tag) {
note_tags.insert(tag.clone());
modified = true;
}
}
if !modified {
return None;
}
// sort
let mut tags: Vec<_> = note_tags.into_iter().collect::<Vec<_>>();
tags.sort_unstable();
// turn back into a string
let tags: Vec<_> = tags.into_iter().map(|s| s.into_inner()).collect();
Some(join_tags(&tags))
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn add_missing() {
let desired: Vec<_> = ["xyz", "abc", "DEF"]
.iter()
.map(|s| UniCase::new(s.to_string()))
.collect();
let add_to = |text| add_missing_tags(text, &desired).unwrap();
assert_eq!(&add_to(""), " abc DEF xyz ");
assert_eq!(&add_to("XYZ deF aaa"), " aaa abc deF XYZ ");
assert_eq!(add_missing_tags("def xyz abc", &desired).is_none(), true);
}
}

View file

@ -1,6 +1,7 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
mod bulkadd;
mod dragdrop;
mod prefix_replacer;
mod register;

View file

@ -38,6 +38,23 @@ impl Collection {
Ok((tags, added))
}
/// Returns true if any cards were added to the tag list.
pub(crate) fn canonified_tags_as_vec(
&mut self,
tags: &str,
usn: Usn,
) -> Result<Vec<UniCase<String>>> {
let mut out_tags = vec![];
for tag in split_tags(tags) {
let mut tag = Tag::new(tag.to_string(), usn);
self.register_tag(&mut tag)?;
out_tags.push(UniCase::new(tag.name));
}
Ok(out_tags)
}
/// Adjust tag casing to match any existing parents, and register it if it's not already
/// in the tags list. True if the tag was added and not already in tag list.
/// In the case the tag is already registered, tag will be mutated to match the existing

View file

@ -57,42 +57,6 @@ impl Collection {
self.replace_tags_for_notes_inner(nids, &tags, repl)
}
}
pub fn add_tags_to_notes(&mut self, nids: &[NoteID], tags: &str) -> Result<OpOutput<usize>> {
let tags: Vec<_> = split_tags(tags).collect();
let matcher = regex::RegexSet::new(
tags.iter()
.map(|s| regex::escape(s))
.map(|s| format!("(?i)^{}$", s)),
)
.map_err(|_| AnkiError::invalid_input("invalid regex"))?;
self.transact(Op::UpdateTag, |col| {
col.transform_notes(nids, |note, _nt| {
let mut need_to_add = true;
let mut match_count = 0;
for tag in &note.tags {
if matcher.is_match(tag) {
match_count += 1;
}
if match_count == tags.len() {
need_to_add = false;
break;
}
}
if need_to_add {
note.tags.extend(tags.iter().map(|&s| s.to_string()))
}
Ok(TransformNoteOutput {
changed: need_to_add,
generate_cards: false,
mark_modified: true,
})
})
})
}
}
#[cfg(test)]