From e2ac408c35a5a73076ae1e8a24c98125398476ae Mon Sep 17 00:00:00 2001 From: Dmitriy Pleshevskiy Date: Sat, 20 Aug 2022 16:45:45 +0300 Subject: [PATCH 1/5] db: add migration --- database/migrations/202208201623.sql | 9 ++++++++ database/schema.sql | 10 +++++++++ src/repo.rs | 2 ++ src/repo/fs.rs | 4 ++++ src/repo/sqlite.rs | 31 +++++++++++++++++++--------- 5 files changed, 46 insertions(+), 10 deletions(-) create mode 100644 database/migrations/202208201623.sql diff --git a/database/migrations/202208201623.sql b/database/migrations/202208201623.sql new file mode 100644 index 0000000..c5a6790 --- /dev/null +++ b/database/migrations/202208201623.sql @@ -0,0 +1,9 @@ +CREATE VIEW finished_tasks +AS +SELECT + t.*, + row_number() OVER (ORDER BY t.finished_at DESC) AS idx +FROM tasks AS t +WHERE t.finished_at IS NOT NULL +ORDER BY t.finished_at DESC +; diff --git a/database/schema.sql b/database/schema.sql index 981c7bb..a5e919f 100644 --- a/database/schema.sql +++ b/database/schema.sql @@ -24,3 +24,13 @@ FROM tasks AS t WHERE t.finished_at IS NULL ORDER BY t.created_at ; + +CREATE VIEW finished_tasks +AS +SELECT + t.*, + row_number() OVER (ORDER BY t.finished_at DESC) AS idx +FROM tasks AS t +WHERE t.finished_at IS NOT NULL +ORDER BY t.finished_at DESC +; diff --git a/src/repo.rs b/src/repo.rs index 50f66cc..70dd069 100755 --- a/src/repo.rs +++ b/src/repo.rs @@ -86,4 +86,6 @@ pub trait Repository { fn stop_task(&self) -> Result; fn finish_task(&self) -> Result; + + fn get_finished_tasks(&self) -> Result; } diff --git a/src/repo/fs.rs b/src/repo/fs.rs index 55f83fc..9cb4f98 100644 --- a/src/repo/fs.rs +++ b/src/repo/fs.rs @@ -203,6 +203,10 @@ impl Repository for FsRepo { Ok(task.into()) } + + fn get_finished_tasks(&self) -> Result { + todo!() + } } impl FsRepo { diff --git a/src/repo/sqlite.rs b/src/repo/sqlite.rs index 77d47bd..7239cb9 100644 --- a/src/repo/sqlite.rs +++ b/src/repo/sqlite.rs @@ -211,6 +211,10 @@ impl Repository for SqliteRepo { Ok(db_task.into()) } + + fn get_finished_tasks(&self) -> Result { + todo!() + } } impl SqliteRepo { @@ -251,12 +255,16 @@ impl SqliteRepo { } macro_rules! run_migration { - ($this:ident, $ver:ident = $version:literal) => { + ($this:ident, $ver:ident = $version:expr) => { + run_migration!($this, $ver = $version => concat!("migrations/", $version)); + }; + + ($this:ident, $ver:ident = $version:expr => $sql_name:expr) => { $this .conn .execute_batch(&format!( "BEGIN; {} COMMIT;", - include_str!(concat!("../../database/migrations/", $version, ".sql")) + include_str!(concat!("../../database/", $sql_name, ".sql")) )) .map_err(|_| MigrationError::Upgrade)?; @@ -288,18 +296,21 @@ impl std::fmt::Display for MigrationError { impl std::error::Error for MigrationError {} -const LATEST_VERSION: i64 = 202208162308; +const LATEST_VERSION: i64 = 202208201623; impl SqliteRepo { pub fn upgrade(&self) -> Result<(), MigrationError> { let mut version = self.version(); - if version == Some(LATEST_VERSION) { - return Ok(()); - } - - // TODO: execute full schema if version is none - if version.is_none() { - run_migration!(self, version = 202208162308); + match version { + Some(LATEST_VERSION) => return Ok(()), + None => { + run_migration!(self, version = LATEST_VERSION => "schema"); + } + Some(v) => { + if v == 202208162308 { + run_migration!(self, version = 202208201623); + } + } } self.conn From 1224eb2d39c295e5dbc178a263bc298954c57abb Mon Sep 17 00:00:00 2001 From: Dmitriy Pleshevskiy Date: Sat, 20 Aug 2022 22:56:09 +0300 Subject: [PATCH 2/5] add a makefile with frequently used commands --- makefile | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 makefile diff --git a/makefile b/makefile new file mode 100644 index 0000000..db170dd --- /dev/null +++ b/makefile @@ -0,0 +1,8 @@ + + +install: + cargo install --path . + +new-migration: + touch ./database/migrations/$$(date +%Y%m%d%H%M).sql + From a8f8e23cf453e71010420087d60388cb2c099460 Mon Sep 17 00:00:00 2001 From: Dmitriy Pleshevskiy Date: Sat, 20 Aug 2022 23:38:38 +0300 Subject: [PATCH 3/5] repo/sqlite: returns finished tasks - repo/fs: returns finished tasks - cli/list: add --finished flag to get finished tasks Closes #4 --- src/bin/fs_to_sqlite.rs | 4 ++-- src/cli/list.rs | 5 ++++- src/repo.rs | 4 +--- src/repo/fs.rs | 15 ++++++++------- src/repo/sqlite.rs | 21 +++++++++++---------- 5 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/bin/fs_to_sqlite.rs b/src/bin/fs_to_sqlite.rs index 3501306..9f509dc 100644 --- a/src/bin/fs_to_sqlite.rs +++ b/src/bin/fs_to_sqlite.rs @@ -4,10 +4,10 @@ use xdg::BaseDirectories; fn main() { let xdg_dirs = BaseDirectories::with_prefix(env!("CARGO_PKG_NAME")).unwrap(); let fs_repo = repo::fs::FsRepo::new(xdg_dirs.clone()); - let tasks = fs_repo.get_tasks().unwrap(); + let fs_tasks = fs_repo.get_tasks(false).unwrap(); let sqlite_repo = repo::sqlite::SqliteRepo::new(xdg_dirs).unwrap(); - for task in tasks { + for task in fs_tasks { log::info!("task: {}", task.name); log::info!(" inserting..."); diff --git a/src/cli/list.rs b/src/cli/list.rs index 9703bfc..3ed7fdd 100644 --- a/src/cli/list.rs +++ b/src/cli/list.rs @@ -18,11 +18,14 @@ use crate::repo::Repository; #[derive(clap::Args)] pub struct Args { + #[clap(short, long)] + finished: bool, + projects: Vec, } pub fn execute(repo: impl Repository, args: Args) { - let tasks = match repo.get_tasks() { + let tasks = match repo.get_tasks(args.finished) { Ok(tasks) => tasks, Err(err) => return eprintln!("Cannot read tasks: {}", err), }; diff --git a/src/repo.rs b/src/repo.rs index 70dd069..119da4d 100755 --- a/src/repo.rs +++ b/src/repo.rs @@ -69,7 +69,7 @@ pub trait Repository { fn get_task_opt(&self, id: domain::TaskIdx) -> Result, Error>; - fn get_tasks(&self) -> Result, Error>; + fn get_tasks(&self, finished: bool) -> Result, Error>; fn remove_task(&self, id: domain::TaskIdx) -> Result; @@ -86,6 +86,4 @@ pub trait Repository { fn stop_task(&self) -> Result; fn finish_task(&self) -> Result; - - fn get_finished_tasks(&self) -> Result; } diff --git a/src/repo/fs.rs b/src/repo/fs.rs index 9cb4f98..9ec94b8 100644 --- a/src/repo/fs.rs +++ b/src/repo/fs.rs @@ -89,9 +89,14 @@ impl Repository for FsRepo { Ok(Some(tasks[id - 1].clone().into())) } - fn get_tasks(&self) -> Result, Error> { - self.get_tasks_impl() - .map(|tasks| tasks.into_iter().map(Task::into).collect()) + fn get_tasks(&self, finished: bool) -> Result, Error> { + let fs_tasks = if finished { + self.get_finished_tasks_impl()?.into_iter().rev().collect() + } else { + self.get_tasks_impl()? + }; + + Ok(fs_tasks.into_iter().map(Task::into).collect()) } fn remove_task(&self, id: domain::TaskIdx) -> Result { @@ -203,10 +208,6 @@ impl Repository for FsRepo { Ok(task.into()) } - - fn get_finished_tasks(&self) -> Result { - todo!() - } } impl FsRepo { diff --git a/src/repo/sqlite.rs b/src/repo/sqlite.rs index 7239cb9..5e09e4a 100644 --- a/src/repo/sqlite.rs +++ b/src/repo/sqlite.rs @@ -53,7 +53,7 @@ impl<'r> TryFrom<&'r rusqlite::Row<'_>> for Task { } } -const SCHEMA_FILE: &str = "schema.sql"; +const SCHEMA_FILE: &str = "tas.db"; pub struct SqliteRepo { conn: Connection, @@ -82,11 +82,16 @@ impl Repository for SqliteRepo { self.get_task_opt_impl(id).map(|t| t.map(From::from)) } - fn get_tasks(&self) -> Result, Error> { - let mut stmt = self - .conn - .prepare("SELECT * FROM active_tasks") - .map_err(|_| Error::PrepareQuery)?; + fn get_tasks(&self, finished: bool) -> Result, Error> { + let mut stmt = if finished { + self.conn + .prepare("SELECT * FROM finished_tasks") + .map_err(|_| Error::PrepareQuery)? + } else { + self.conn + .prepare("SELECT * FROM active_tasks") + .map_err(|_| Error::PrepareQuery)? + }; let rows = stmt .query_map([], |row| Task::try_from(row)) @@ -211,10 +216,6 @@ impl Repository for SqliteRepo { Ok(db_task.into()) } - - fn get_finished_tasks(&self) -> Result { - todo!() - } } impl SqliteRepo { From 73ab922c77a1ba412e8462f3bc151487a1287db9 Mon Sep 17 00:00:00 2001 From: Dmitriy Pleshevskiy Date: Sun, 21 Aug 2022 00:01:00 +0300 Subject: [PATCH 4/5] cli/list: don't show current task on finished --- src/cli/list.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/cli/list.rs b/src/cli/list.rs index 3ed7fdd..9aa067b 100644 --- a/src/cli/list.rs +++ b/src/cli/list.rs @@ -30,10 +30,14 @@ pub fn execute(repo: impl Repository, args: Args) { Err(err) => return eprintln!("Cannot read tasks: {}", err), }; - let cur_task = match repo.get_current_task_opt() { - Ok(cur_task) => cur_task, - Err(err) => { - return eprintln!("Cannot read current task: {}", err); + let cur_task = if args.finished { + None + } else { + match repo.get_current_task_opt() { + Ok(cur_task) => cur_task, + Err(err) => { + return eprintln!("Cannot read current task: {}", err); + } } }; From 8bd588070e3acfa2d895e40db90a32f4e8f62869 Mon Sep 17 00:00:00 2001 From: Dmitriy Pleshevskiy Date: Sun, 21 Aug 2022 00:01:28 +0300 Subject: [PATCH 5/5] bin: add migration of finished fs tasks --- src/bin/fs_to_sqlite.rs | 63 ++++++++++++++++++++++++++++++++--------- src/cli/add.rs | 1 + src/cli/priority.rs | 1 + src/repo.rs | 1 + src/repo/fs.rs | 6 ++-- src/repo/sqlite.rs | 5 ++-- 6 files changed, 58 insertions(+), 19 deletions(-) diff --git a/src/bin/fs_to_sqlite.rs b/src/bin/fs_to_sqlite.rs index 9f509dc..2c478f0 100644 --- a/src/bin/fs_to_sqlite.rs +++ b/src/bin/fs_to_sqlite.rs @@ -4,23 +4,58 @@ use xdg::BaseDirectories; fn main() { let xdg_dirs = BaseDirectories::with_prefix(env!("CARGO_PKG_NAME")).unwrap(); let fs_repo = repo::fs::FsRepo::new(xdg_dirs.clone()); + let sqlite_repo = repo::sqlite::SqliteRepo::new(xdg_dirs.clone()).unwrap(); + + log::info!("active tasks"); + let fs_tasks = fs_repo.get_tasks(false).unwrap(); + if !fs_tasks.is_empty() { + for task in fs_tasks { + log::info!("task: {}", task.name); + log::info!(" inserting..."); - let sqlite_repo = repo::sqlite::SqliteRepo::new(xdg_dirs).unwrap(); - for task in fs_tasks { - log::info!("task: {}", task.name); - log::info!(" inserting..."); + sqlite_repo + .insert_task(repo::InsertTaskData { + name: task.name, + project: task.project, + link: task.link, + dir_path: task.dir_path, + index: None, + finished_at: None, + }) + .unwrap(); - sqlite_repo - .insert_task(repo::InsertTaskData { - name: task.name, - project: task.project, - link: task.link, - dir_path: task.dir_path, - index: None, - }) - .unwrap(); + log::info!(" inserted"); + } + } - log::info!(" inserted"); + log::info!("finished tasks"); + + let fs_tasks = fs_repo.get_tasks(true).unwrap(); + if !fs_tasks.is_empty() { + let meta = std::fs::metadata(xdg_dirs.get_data_file(repo::fs::FINISHED_DATA_FILE)).unwrap(); + let finished_at = meta + .modified() + .map(time::OffsetDateTime::from) + .unwrap_or_else(|_| time::OffsetDateTime::now_utc()); + + for task in fs_tasks { + log::info!("task: {}", task.name); + log::info!(" inserting..."); + + sqlite_repo + .insert_task(repo::InsertTaskData { + name: task.name, + project: task.project, + link: task.link, + dir_path: task.dir_path, + index: None, + finished_at: Some(finished_at), + }) + // TODO: think of a better solution than idx + .ok(); + + log::info!(" inserted"); + } } } diff --git a/src/cli/add.rs b/src/cli/add.rs index bd3373b..94434ca 100644 --- a/src/cli/add.rs +++ b/src/cli/add.rs @@ -43,6 +43,7 @@ pub fn execute(repo: impl Repository, args: Args) { .transpose() .unwrap(), index: None, + finished_at: None, }); match res { diff --git a/src/cli/priority.rs b/src/cli/priority.rs index 5b360a8..2dc795b 100644 --- a/src/cli/priority.rs +++ b/src/cli/priority.rs @@ -77,6 +77,7 @@ pub fn execute(repo: impl Repository, args: Args) { project: target.project, link: target.link, dir_path: target.dir_path, + finished_at: None, }); match res { Ok(task) => { diff --git a/src/repo.rs b/src/repo.rs index 119da4d..d1bbd8e 100755 --- a/src/repo.rs +++ b/src/repo.rs @@ -55,6 +55,7 @@ pub struct InsertTaskData { pub link: Option, pub dir_path: Option, pub index: Option, + pub finished_at: Option, } pub struct UpdateTaskData { diff --git a/src/repo/fs.rs b/src/repo/fs.rs index 9ec94b8..1ff14cd 100644 --- a/src/repo/fs.rs +++ b/src/repo/fs.rs @@ -60,9 +60,9 @@ impl From for domain::CurrentTaskInfo { } } -const CURRENT_TASK_FILE: &str = "current.json"; -const DATA_FILE: &str = "data.json"; -const FINISHED_DATA_FILE: &str = "finished_data.json"; +pub const CURRENT_TASK_FILE: &str = "current.json"; +pub const DATA_FILE: &str = "data.json"; +pub const FINISHED_DATA_FILE: &str = "finished_data.json"; pub struct FsRepo { xdg_dirs: BaseDirectories, diff --git a/src/repo/sqlite.rs b/src/repo/sqlite.rs index 5e09e4a..3f3bc30 100644 --- a/src/repo/sqlite.rs +++ b/src/repo/sqlite.rs @@ -121,8 +121,8 @@ impl Repository for SqliteRepo { 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, finished_at) + VALUES (?1, ?2, ?3, ?4, ?5, ?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()), + &insert_data.finished_at, )) .map_err(|_| Error::InsertData)?;