diff options
author | Joris | 2022-11-27 15:38:39 +0100 |
---|---|---|
committer | Joris | 2022-11-27 15:38:39 +0100 |
commit | d3fb69cf129fe70c932a5d82fdd1bcc613544b5b (patch) | |
tree | 6a098c7900f96791489b8548979fb71ccea03e59 /src/sync.rs | |
parent | 765ba94cdd65184bd4b443c3a946d0d96dc805d6 (diff) |
Speed up deck synchronization
Apply changes for what has been inserted, updated, removed.
Also use transactions to speed up multi-writing.
Diffstat (limited to 'src/sync.rs')
-rw-r--r-- | src/sync.rs | 173 |
1 files changed, 173 insertions, 0 deletions
diff --git a/src/sync.rs b/src/sync.rs new file mode 100644 index 0000000..3911d55 --- /dev/null +++ b/src/sync.rs @@ -0,0 +1,173 @@ +use crate::{ + db, deck, + model::{DbEntry, Line, Question}, +}; +use anyhow::Result; +use rusqlite::Connection; +use std::collections::HashSet; + +pub fn run(conn: &mut Connection, deck_path: &str) -> Result<()> { + let db_entries = db::all(conn)?; + let lines = deck::read(deck_path)?; + let Diff { + new, + deleted, + undeleted, + } = diff(db_entries, lines); + + db::insert(conn, &new)?; + db::delete(conn, &deleted)?; + db::undelete(conn, &undeleted)?; + + Ok(()) +} + +struct Diff { + pub new: Vec<Question>, + pub deleted: Vec<Question>, + pub undeleted: Vec<Question>, +} + +fn diff(db_entries: Vec<DbEntry>, lines: Vec<Line>) -> Diff { + let mut file_questions: HashSet<Question> = HashSet::new(); + let mut db_questions_not_deleted: HashSet<Question> = HashSet::new(); + let mut db_questions_deleted: HashSet<Question> = HashSet::new(); + + for Line { part_1, part_2 } in lines { + for question in part_1.clone() { + let mut responses = part_2.clone(); + responses.sort(); + file_questions.insert(Question { + question, + responses, + }); + } + for question in part_2 { + let mut responses = part_1.clone(); + responses.sort(); + file_questions.insert(Question { + question, + responses, + }); + } + } + + for DbEntry { + question, + mut responses, + deleted, + } in db_entries + { + responses.sort(); + if deleted.is_some() { + db_questions_deleted.insert(Question { + question, + responses, + }); + } else { + db_questions_not_deleted.insert(Question { + question, + responses, + }); + } + } + + let new = file_questions + .difference(&db_questions_not_deleted) + .cloned() + .collect::<HashSet<Question>>() + .difference(&db_questions_deleted) + .cloned() + .collect(); + + let deleted = db_questions_not_deleted + .difference(&file_questions) + .cloned() + .collect(); + + let undeleted = file_questions + .intersection(&db_questions_deleted) + .cloned() + .collect(); + + Diff { + new, + deleted, + undeleted, + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn sync() { + let db_entries = vec![ + DbEntry { + question: "A".to_string(), + responses: vec!["A".to_string()], + deleted: None, + }, + DbEntry { + question: "B".to_string(), + responses: vec!["B".to_string()], + deleted: None, + }, + DbEntry { + question: "C".to_string(), + responses: vec!["C".to_string()], + deleted: Some(0), + }, + DbEntry { + question: "D".to_string(), + responses: vec!["D".to_string()], + deleted: Some(0), + }, + ]; + + let lines = vec![ + Line { + part_1: vec!["A".to_string()], + part_2: vec!["A".to_string()], + }, + Line { + part_1: vec!["C".to_string()], + part_2: vec!["C".to_string()], + }, + Line { + part_1: vec!["E".to_string()], + part_2: vec!["E".to_string()], + }, + ]; + + let Res { + new, + deleted, + undeleted, + } = sync(db_entries, lines); + + assert_eq!( + new, + vec!(Question { + question: "E".to_string(), + responses: vec!("E".to_string()) + }) + ); + assert_eq!( + deleted, + vec!(Question { + question: "B".to_string(), + responses: vec!("B".to_string()) + }) + ); + assert_eq!( + undeleted, + vec!(Question { + question: "C".to_string(), + responses: vec!("C".to_string()) + }) + ); + } +} |