diff --git a/Cargo.lock b/Cargo.lock index 5923724..2bccd02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -284,8 +284,15 @@ dependencies = [ "libsqlite3-sys", "smallvec", "time", + "uuid", ] +[[package]] +name = "sha1_smol" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" + [[package]] name = "smallvec" version = "1.9.0" @@ -311,6 +318,7 @@ dependencies = [ "log", "rusqlite", "time", + "uuid", "xdg", ] @@ -364,6 +372,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" +[[package]] +name = "uuid" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" +dependencies = [ + "sha1_smol", +] + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/Cargo.toml b/Cargo.toml index fb8a5d8..dcb79ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,8 +13,9 @@ maintenance = { status = "experimental" } [dependencies] clap = { version = "3.2.16", default-features = false, features = ["derive", "std"] } log = "0.4.17" -rusqlite = { version = "0.28.0", features = ["bundled", "time"] } +rusqlite = { version = "0.28.0", features = ["bundled", "time", "uuid"] } time = "0.3" +uuid = { version = "1.1.2", features = ["v5"] } xdg = "2.4.1" diff --git a/database/migrations/202208211624.sql b/database/migrations/202208211624.sql new file mode 100644 index 0000000..61b380d --- /dev/null +++ b/database/migrations/202208211624.sql @@ -0,0 +1,2 @@ +ALTER TABLE tasks + ADD COLUMN uuid BLOB; diff --git a/database/schema.sql b/database/schema.sql index a5e919f..290efdb 100644 --- a/database/schema.sql +++ b/database/schema.sql @@ -8,6 +8,7 @@ CREATE TABLE tasks ( project TEXT , link TEXT , dir_path TEXT , + uuid BLOB , current BOOLEAN NOT NULL DEFAULT false, diff --git a/src/repo.rs b/src/repo.rs index 4d72c04..7200ff7 100755 --- a/src/repo.rs +++ b/src/repo.rs @@ -64,7 +64,7 @@ pub struct UpdateTaskData { } pub trait Repository { - fn get_current_task_opt(&self) -> Result, Error>; + fn get_current_task_opt(&self) -> Result, Error>; fn get_task_opt(&self, id: domain::TaskIdx) -> Result, Error>; diff --git a/src/repo/sqlite.rs b/src/repo/sqlite.rs index 5e09e4a..8d6c9a8 100644 --- a/src/repo/sqlite.rs +++ b/src/repo/sqlite.rs @@ -1,6 +1,7 @@ use std::path::PathBuf; use rusqlite::{Connection, OptionalExtension}; +use uuid::Uuid; use xdg::BaseDirectories; use crate::domain; @@ -18,7 +19,7 @@ struct Task { */ created_at: time::OffsetDateTime, - idx: i64, + uuid: uuid::Uuid, } impl From for domain::Task { @@ -48,7 +49,7 @@ impl<'r> TryFrom<&'r rusqlite::Row<'_>> for Task { finished_at: row.get("finished_at")?, */ created_at: row.get("created_at")?, - idx: row.get("idx")?, + uuid: row.get("uuid")?, }) } } @@ -69,13 +70,10 @@ impl SqliteRepo { } impl Repository for SqliteRepo { - fn get_current_task_opt(&self) -> Result, Error> { + fn get_current_task_opt(&self) -> Result, Error> { let db_task = self.get_current_task_opt_impl()?; - Ok(db_task.map(|db_task| domain::CurrentTaskInfo { - task_idx: db_task.idx as usize, - task: db_task.into(), - })) + Ok(db_task.map(|db_task| db_task.into())) } fn get_task_opt(&self, id: domain::TaskIdx) -> Result, Error> { @@ -118,11 +116,13 @@ impl Repository for SqliteRepo { } fn insert_task(&self, insert_data: super::InsertTaskData) -> Result { + let created_at = time::OffsetDateTime::now_utc(); + let mut stmt = self .conn .prepare( - "INSERT INTO tasks (name, project, link, dir_path) - VALUES (?1, ?2, ?3, ?4)", + "INSERT INTO tasks (name, project, link, dir_path, created_at) + VALUES (?1, ?2, ?3, ?4, ?5)", ) .map_err(|_| Error::PrepareQuery)?; @@ -134,6 +134,7 @@ impl Repository for SqliteRepo { &insert_data .dir_path .and_then(|p| p.into_os_string().into_string().ok()), + &created_at, )) .map_err(|_| Error::InsertData)?; @@ -243,7 +244,7 @@ impl SqliteRepo { fn get_current_task_opt_impl(&self) -> Result, Error> { let mut stmt = self .conn - .prepare("SELECT * FROM active_tasks WHERE current IS true") + .prepare("SELECT * FROM active_tasks WHERE current") .map_err(|_| Error::PrepareQuery)?; let row = stmt @@ -257,10 +258,14 @@ impl SqliteRepo { macro_rules! run_migration { ($this:ident, $ver:ident = $version:expr) => { - run_migration!($this, $ver = $version => concat!("migrations/", $version)); + run_migration!( + $this, + $ver = $version, + file = concat!("migrations/", $version) + ); }; - ($this:ident, $ver:ident = $version:expr => $sql_name:expr) => { + ($this:ident, $ver:ident = $version:expr, file = $sql_name:expr) => { $this .conn .execute_batch(&format!( @@ -279,6 +284,8 @@ pub enum MigrationError { Upgrade, DeleteInfo, InsertInfo, + StartTransaction, + Db(Error), } impl std::fmt::Display for MigrationError { @@ -291,13 +298,14 @@ impl std::fmt::Display for MigrationError { MigrationError::InsertInfo => { f.write_str("Cannot insert a new tas information to the database") } + MigrationError::Db(inner) => write!(f, "Database error: {}", inner), } } } impl std::error::Error for MigrationError {} -const LATEST_VERSION: i64 = 202208201623; +const LATEST_VERSION: i64 = 202208211624; impl SqliteRepo { pub fn upgrade(&self) -> Result<(), MigrationError> { @@ -305,12 +313,50 @@ impl SqliteRepo { match version { Some(LATEST_VERSION) => return Ok(()), None => { - run_migration!(self, version = LATEST_VERSION => "schema"); + run_migration!(self, version = LATEST_VERSION, file = "schema"); } - Some(v) => { - if v == 202208162308 { + _ => { + if version == Some(202208162308) { run_migration!(self, version = 202208201623); } + if version == Some(202208201623) { + run_migration!(self, version = 202208211624); + + let mut stmt = self + .conn + .prepare("SELECT id, created_at, project, name, link FROM tasks") + .map_err(|_| MigrationError::Db(Error::PrepareQuery))?; + + let rows = stmt + .query_map([], |row| { + let id: i64 = row.get("id")?; + let created_at: time::OffsetDateTime = row.get("created_at")?; + let project: Option = row.get("project")?; + let name: String = row.get("name")?; + let link: Option = row.get("link")?; + let uuid = Uuid::new_v5( + &Uuid::NAMESPACE_OID, + format!( + "p:{},n:{},l:{},d:{}", + project.unwrap_or_default(), + name, + link.unwrap_or_default(), + created_at.unix_timestamp() + ) + .as_bytes(), + ); + Ok((id, uuid)) + }) + .map_err(|_| MigrationError::Db(Error::QueryData))?; + + for row in rows { + let (id, uuid) = row.map_err(|_| MigrationError::Db(Error::InvalidData))?; + + this.conn + .execute("UPDATE tasks SET uuid = ?2 WHERE id = ?1", (uuid, id)) + .map_err(|_| MigrationError::Db(Error::UpdateData))?; + } + } } }