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, pub deleted: Vec, pub undeleted: Vec, } fn diff(db_entries: Vec, lines: Vec) -> Diff { let mut file_questions: HashSet = HashSet::new(); let mut db_questions_not_deleted: HashSet = HashSet::new(); let mut db_questions_deleted: HashSet = 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::>() .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()) }) ); } }