aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoris2022-02-06 19:30:53 +0100
committerJoris2022-02-06 19:30:53 +0100
commit20cfe717065fa53953e9799036a9972880fec801 (patch)
tree7fc2a7b4a43eb6d280d52c468927592c12798cb0
parentdc0f32017cceabb6c683b6e1b4a2ae0248c37dbf (diff)
Replace a card whenever the front or the back changed
-rw-r--r--README.md7
-rw-r--r--src/db/db.rs25
-rw-r--r--src/db/sql/2-primary-key-question-responses.sql20
-rw-r--r--src/deck.rs23
-rw-r--r--src/main.rs2
-rw-r--r--src/model/entry.rs (renamed from src/model/deck.rs)0
-rw-r--r--src/model/mod.rs2
-rw-r--r--src/util/serialization.rs5
8 files changed, 61 insertions, 23 deletions
diff --git a/README.md b/README.md
index a6b0d05..116c6c5 100644
--- a/README.md
+++ b/README.md
@@ -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 {