aboutsummaryrefslogtreecommitdiff
path: root/src/sync.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/sync.rs')
-rw-r--r--src/sync.rs173
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())
+ })
+ );
+ }
+}