diff options
| -rw-r--r-- | README.md | 7 | ||||
| -rw-r--r-- | src/db/db.rs | 25 | ||||
| -rw-r--r-- | src/db/sql/2-primary-key-question-responses.sql | 20 | ||||
| -rw-r--r-- | src/deck.rs | 23 | ||||
| -rw-r--r-- | src/main.rs | 2 | ||||
| -rw-r--r-- | src/model/entry.rs (renamed from src/model/deck.rs) | 0 | ||||
| -rw-r--r-- | src/model/mod.rs | 2 | ||||
| -rw-r--r-- | src/util/serialization.rs | 5 | 
8 files changed, 61 insertions, 23 deletions
| @@ -17,11 +17,10 @@ Cards are created from a plain text `./deck` file:  # TODO -- When changing the back, update the card +- Indications in parenthesis +- Look at last modification of the deck, and omit db sync if last sync was after  - Fix crashes on zoom / changing size -## Nice to have +## Maybe -- Indications in parenthesis  - Phonetics indication -- Select deck diff --git a/src/db/db.rs b/src/db/db.rs index 86614ec..843d142 100644 --- a/src/db/db.rs +++ b/src/db/db.rs @@ -1,5 +1,5 @@  use crate::{ -    model::{card::Card, deck::Entry}, +    model::{card::Card, entry::Entry},      space_repetition,      util::serialization,  }; @@ -11,14 +11,21 @@ use std::time::SystemTime;  pub fn init(database: String) -> Result<Connection> {      let mut conn = Connection::open(database)?; -    let migrations = Migrations::new(vec![M::up(include_str!("sql/1-init.sql"))]); +    let migrations = Migrations::new(vec![ +        M::up(include_str!("sql/1-init.sql")), +        M::up(include_str!("sql/2-primary-key-question-responses.sql")), +    ]);      migrations.to_latest(&mut conn)?;      Ok(conn)  } -pub fn add_missing_deck_entries(conn: &Connection, entries: Vec<Entry>) -> Result<()> { +/// Synchronize the DB with the deck: +/// +/// - insert new cards, +/// - keep existing cards, +/// - hide unused cards (keep state in case the card is added back afterward). +pub fn synchronize(conn: &Connection, entries: Vec<Entry>) -> Result<()> {      let now = get_current_time()?; -    let ready = now;      let state = serde_json::to_string(&space_repetition::init())?;      let mut rng = rand::thread_rng(); @@ -28,11 +35,11 @@ pub fn add_missing_deck_entries(conn: &Connection, entries: Vec<Entry>) -> Resul          let concat_2 = serialization::words_to_line(&entry.part_2);          for w in entry.part_1.iter() { -            insert(&conn, &mut rng, now, ready, &w, &concat_2, &state)?; +            insert(&conn, &mut rng, now, &w, &concat_2, &state)?;          }          for w in entry.part_2.iter() { -            insert(&conn, &mut rng, now, ready, &w, &concat_1, &state)?; +            insert(&conn, &mut rng, now, &w, &concat_1, &state)?;          }      } @@ -45,11 +52,11 @@ fn insert(      conn: &Connection,      rng: &mut ThreadRng,      now: u64, -    ready: u64,      question: &String,      responses: &String,      state: &String,  ) -> Result<()> { +    println!("insert {} : {}", question, responses);      // Subtract a random amount of time so that cards are not initially given in the same order as      // in the deck      let ready_sub: u64 = rng.gen_range(0..100); @@ -58,9 +65,9 @@ fn insert(          "          INSERT INTO cards (question, responses, state, created, deck_read, ready)          VALUES (?, ?, ?, ?, ?, ?) -        ON CONFLICT (question) DO UPDATE SET deck_read = ?, deleted = null +        ON CONFLICT (question, responses) DO UPDATE SET deck_read = ?, deleted = null          ", -        params![question, responses, state, now, now, ready - ready_sub, now], +        params![question, responses, state, now, now, now - ready_sub, now],      )?;      Ok(()) diff --git a/src/db/sql/2-primary-key-question-responses.sql b/src/db/sql/2-primary-key-question-responses.sql new file mode 100644 index 0000000..cb7df21 --- /dev/null +++ b/src/db/sql/2-primary-key-question-responses.sql @@ -0,0 +1,20 @@ +/* Allows to use ON CONFLICT on (question, responses) when inserting a card. */ + +CREATE TABLE IF NOT EXISTS cards_copy ( +  question VARCHAR NOT NULL, +  responses VARCHAR NOT NULL, +  state VARCHAR NOT NULL, +  created TIMESTAMP NOT NULL, +  updated TIMESTAMP NULL, +  deleted TIMESTAMP NULL, +  deck_read TIMESTAMP NOT NULL, +  ready TIMESTAMP NOT NULL, +  PRIMARY KEY (question, responses) +); + +INSERT INTO cards_copy (question, responses, state, created, updated, deleted, deck_read, ready) +   SELECT question, responses, state, created, updated, deleted, deck_read, ready FROM cards; + +DROP TABLE cards; + +ALTER TABLE cards_copy RENAME TO cards; diff --git a/src/deck.rs b/src/deck.rs index 0fe8a7b..ce20ee9 100644 --- a/src/deck.rs +++ b/src/deck.rs @@ -1,8 +1,8 @@ -use crate::{model::deck::Entry, util::serialization}; -use anyhow::{Result, Error}; +use crate::{model::entry::Entry, util::serialization}; +use anyhow::{Error, Result}; +use std::fmt;  use std::fs::File;  use std::io::{prelude::*, BufReader}; -use std::fmt;  #[derive(Debug, Clone)]  struct ParseError { @@ -22,7 +22,6 @@ impl std::error::Error for ParseError {      }  } -  pub fn read(deck: String) -> Result<Vec<Entry>> {      let file = File::open(deck)?;      let reader = BufReader::new(file); @@ -34,16 +33,26 @@ pub fn read(deck: String) -> Result<Vec<Entry>> {          if !line.starts_with("#") && !line.is_empty() {              if !line.starts_with("-") { -                return Err(Error::from(ParseError { line: index + 1, message: "an entry should starts with “-”.".to_string() })) +                return Err(Error::from(ParseError { +                    line: index + 1, +                    message: "an entry should starts with “-”.".to_string(), +                }));              } else {                  let translation = line[1..].trim().split(":").collect::<Vec<&str>>();                  if translation.len() != 2 { -                    return Err(Error::from(ParseError { line: index + 1, message: "an entry should contain two parts separated by “:”.".to_string() })) +                    return Err(Error::from(ParseError { +                        line: index + 1, +                        message: "an entry should contain two parts separated by “:”.".to_string(), +                    }));                  } else {                      let t1 = translation[0].trim();                      let t2 = translation[1].trim();                      if t1.is_empty() || t2.is_empty() { -                        return Err(Error::from(ParseError { line: index + 1, message: "an entry should contain two parts separated by “:”.".to_string() })) +                        return Err(Error::from(ParseError { +                            line: index + 1, +                            message: "an entry should contain two parts separated by “:”." +                                .to_string(), +                        }));                      } else {                          entries.push(Entry {                              part_1: serialization::line_to_words(&t1.to_string()), diff --git a/src/main.rs b/src/main.rs index 6ef241b..f9d2288 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,6 +22,6 @@ fn main() -> Result<()> {      let opt = Opt::from_args();      let conn = db::db::init(opt.database)?;      let entries = deck::read(opt.deck)?; -    db::db::add_missing_deck_entries(&conn, entries)?; +    db::db::synchronize(&conn, entries)?;      gui::gui::start(&conn)  } diff --git a/src/model/deck.rs b/src/model/entry.rs index 769b38c..769b38c 100644 --- a/src/model/deck.rs +++ b/src/model/entry.rs diff --git a/src/model/mod.rs b/src/model/mod.rs index bbd7891..185f401 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -1,3 +1,3 @@  pub mod card; -pub mod deck;  pub mod difficulty; +pub mod entry; diff --git a/src/util/serialization.rs b/src/util/serialization.rs index fcb3062..cc2899f 100644 --- a/src/util/serialization.rs +++ b/src/util/serialization.rs @@ -1,5 +1,8 @@  pub fn line_to_words(line: &String) -> Vec<String> { -    line.split("|").map(|w| w.trim().to_string()).filter(|w| !w.is_empty()).collect() +    line.split("|") +        .map(|w| w.trim().to_string()) +        .filter(|w| !w.is_empty()) +        .collect()  }  pub fn words_to_line(words: &Vec<String>) -> String { | 
