From 4dbfe41e84b05fdc2c9b9d57da85176b2505ca7c Mon Sep 17 00:00:00 2001 From: Dmitriy Pleshevskiy Date: Fri, 5 Aug 2022 23:40:26 +0300 Subject: [PATCH 1/2] repo: add update data method --- src/cli/add.rs | 4 ++-- src/cli/edit.rs | 45 ++++++++++++++++++++++----------------------- src/domain.rs | 2 ++ src/main.rs | 13 +++++++------ src/repo.rs | 11 +++++++++++ src/repo/fs.rs | 46 ++++++++++++++++++++++++++++++++++++++-------- 6 files changed, 82 insertions(+), 39 deletions(-) diff --git a/src/cli/add.rs b/src/cli/add.rs index 475b277..b304b3d 100644 --- a/src/cli/add.rs +++ b/src/cli/add.rs @@ -16,14 +16,14 @@ pub fn execute(repo: impl Repository, args: Args) { match res { Ok(task) => { - println!("Task was added successfully"); + println!("The task was added successfully"); println!(" {}", task.name); if let Some(link) = task.link { println!(" link: {}", link); } } Err(err) => { - eprintln!("Cannot insert data: {}", err); + eprintln!("Cannot insert a new task: {}", err); } } diff --git a/src/cli/edit.rs b/src/cli/edit.rs index 43566b2..02bee6f 100644 --- a/src/cli/edit.rs +++ b/src/cli/edit.rs @@ -1,7 +1,7 @@ use std::io::Write; -use std::path::PathBuf; -use crate::{CurrentTaskInfo, Task}; +use crate::repo::{self, Repository}; +use crate::CurrentTaskInfo; #[derive(clap::Args)] pub struct Args { @@ -19,33 +19,32 @@ pub struct Args { pub struct Request { pub args: Args, pub current_task_info: Option, - pub tasks: Vec, - pub tasks_file_path: PathBuf, } -pub fn execute(mut req: Request) { +pub fn execute(repo: impl Repository, mut req: Request) { if req.current_task_info.is_some() { panic!("You can edit task only when you don't have an active task, yet"); } - let idx = req.args.idx; - if idx == 0 || idx > req.tasks.len() { - panic!("invalid index"); - } + let args = req.args; - let mut task = &mut req.tasks[idx - 1]; - if let Some(name) = req.args.name { - task.name = name; + let res = repo.update_task( + args.idx, + repo::UpdateTaskData { + name: args.name, + link: args.no_link.then(|| None).or(args.link.map(Some)), + }, + ); + match res { + Ok(task) => { + println!("The task was changed successfully"); + println!(" {}", task.name); + if let Some(link) = task.link { + println!(" link: {}", link); + } + } + Err(err) => { + eprintln!("Cannot update the task: {}", err); + } } - if let Some(link) = req.args.link { - task.link = Some(link); - } else if req.args.no_link { - task.link = None; - } - - let mut file = std::fs::File::create(&req.tasks_file_path).unwrap(); - file.write_all(&serde_json::to_vec(&req.tasks).unwrap()) - .unwrap(); - - println!("changed"); } diff --git a/src/domain.rs b/src/domain.rs index 05f8920..f9dbeb2 100644 --- a/src/domain.rs +++ b/src/domain.rs @@ -1,3 +1,5 @@ +pub type TaskId = usize; + pub struct Task { pub name: String, pub link: Option, diff --git a/src/main.rs b/src/main.rs index 6c8734f..0c6f98d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,12 +55,13 @@ fn main() { cli::add::execute(repo, args); } cli::SubCommand::Edit(args) => { - cli::edit::execute(cli::edit::Request { - args, - current_task_info, - tasks, - tasks_file_path, - }); + cli::edit::execute( + repo, + cli::edit::Request { + args, + current_task_info, + }, + ); } cli::SubCommand::Remove(args) => { cli::remove::execute(cli::remove::Request { diff --git a/src/repo.rs b/src/repo.rs index f2e54c5..2eb691f 100755 --- a/src/repo.rs +++ b/src/repo.rs @@ -26,8 +26,19 @@ pub struct InsertTaskData { link: Option, } +pub struct UpdateTaskData { + name: Option, + link: Option>, +} + pub trait Repository { fn get_tasks(&self) -> Result, Error>; + fn update_task( + &self, + id: domain::TaskId, + update_data: UpdateTaskData, + ) -> Result; + fn insert_task(&self, insert_data: InsertTaskData) -> Result; } diff --git a/src/repo/fs.rs b/src/repo/fs.rs index 750f85f..2cb5eaa 100644 --- a/src/repo/fs.rs +++ b/src/repo/fs.rs @@ -2,7 +2,7 @@ use std::fs::File; use std::io::Write; use crate::domain; -use crate::repo::{Error, InsertTaskData, Repository}; +use crate::repo::{Error, InsertTaskData, Repository, UpdateTaskData}; use serde::{Deserialize, Serialize}; use xdg::BaseDirectories; @@ -41,6 +41,32 @@ impl Repository for FsRepo { .map(|tasks| tasks.into_iter().map(Task::into).collect()) } + fn update_task( + &self, + id: domain::TaskId, + update_data: UpdateTaskData, + ) -> Result { + let mut tasks = self.get_tasks_impl()?; + if id == 0 || id > tasks.len() { + return Err(Error::NotFound); + } + + let mut task = &mut tasks[id]; + if let Some(name) = update_data.name { + task.name = name; + } + + if let Some(link) = update_data.link { + task.link = link; + } + + let new_task = task.clone(); + + self.save_tasks_impl(tasks)?; + + Ok(new_task.into()) + } + fn insert_task(&self, insert_data: InsertTaskData) -> Result { let new_task = Task { name: insert_data.name, @@ -50,13 +76,7 @@ impl Repository for FsRepo { let mut tasks = self.get_tasks_impl()?; tasks.push(new_task.clone()); - let file_path = self - .xdg_dirs - .place_data_file(DATA_FILE) - .map_err(|_| Error::InsertData)?; - let mut file = File::create(&file_path).map_err(|_| Error::InsertData)?; - let new_data = serde_json::to_vec(&tasks).map_err(|_| Error::InvalidData)?; - file.write_all(&new_data).map_err(|_| Error::InsertData); + self.save_tasks_impl(tasks)?; Ok(new_task.into()) } @@ -69,4 +89,14 @@ impl FsRepo { .map_err(|_| Error::NotFound) .and_then(|file| serde_json::from_reader(file).map_err(|_| Error::InvalidData)) } + + fn save_tasks_impl(&self, tasks: Vec) -> Result<(), Error> { + let file_path = self + .xdg_dirs + .place_data_file(DATA_FILE) + .map_err(|_| Error::InsertData)?; + let mut file = File::create(&file_path).map_err(|_| Error::InsertData)?; + let new_data = serde_json::to_vec(&tasks).map_err(|_| Error::InvalidData)?; + file.write_all(&new_data).map_err(|_| Error::InsertData) + } } From 72a37b4a1db3d1a6096252670afbb9755ddac974 Mon Sep 17 00:00:00 2001 From: Dmitriy Pleshevskiy Date: Tue, 16 Aug 2022 15:43:44 +0300 Subject: [PATCH 2/2] add move other methods to repo --- src/cli/add.rs | 1 + src/cli/edit.rs | 24 ++++---- src/cli/finish.rs | 48 ++++------------ src/cli/list.rs | 24 +++++--- src/cli/pause.rs | 38 +++++++------ src/cli/priority.rs | 71 +++++++++++++---------- src/cli/remove.rs | 52 +++++++++-------- src/cli/start.rs | 36 ++++-------- src/cli/status.rs | 13 +++-- src/domain.rs | 6 ++ src/main.rs | 66 +++------------------- src/repo.rs | 21 +++++-- src/repo/fs.rs | 135 +++++++++++++++++++++++++++++++++++++++++++- 13 files changed, 313 insertions(+), 222 deletions(-) diff --git a/src/cli/add.rs b/src/cli/add.rs index b304b3d..b928042 100644 --- a/src/cli/add.rs +++ b/src/cli/add.rs @@ -12,6 +12,7 @@ pub fn execute(repo: impl Repository, args: Args) { let res = repo.insert_task(repo::InsertTaskData { name: args.name, link: args.link, + index: None, }); match res { diff --git a/src/cli/edit.rs b/src/cli/edit.rs index 02bee6f..aa101bf 100644 --- a/src/cli/edit.rs +++ b/src/cli/edit.rs @@ -1,7 +1,4 @@ -use std::io::Write; - use crate::repo::{self, Repository}; -use crate::CurrentTaskInfo; #[derive(clap::Args)] pub struct Args { @@ -16,23 +13,22 @@ pub struct Args { idx: usize, } -pub struct Request { - pub args: Args, - pub current_task_info: Option, -} - -pub fn execute(repo: impl Repository, mut req: Request) { - if req.current_task_info.is_some() { - panic!("You can edit task only when you don't have an active task, yet"); +pub fn execute(repo: impl Repository, args: Args) { + match repo.get_current_task_opt() { + Ok(Some(_)) => { + return eprintln!("You can edit task only when you don't have an active task, yet") + } + Err(err) => { + return eprintln!("Cannot read current task: {}", err); + } + _ => {} } - let args = req.args; - let res = repo.update_task( args.idx, repo::UpdateTaskData { name: args.name, - link: args.no_link.then(|| None).or(args.link.map(Some)), + link: args.no_link.then(|| None).or_else(|| args.link.map(Some)), }, ); match res { diff --git a/src/cli/finish.rs b/src/cli/finish.rs index 30795a2..451b782 100644 --- a/src/cli/finish.rs +++ b/src/cli/finish.rs @@ -1,41 +1,17 @@ -use crate::{CurrentTaskInfo, Task}; -use std::io::Write; -use std::path::PathBuf; +use crate::repo::{self, Repository}; -pub struct Request { - pub current_task_info: Option, - pub tasks: Vec, - pub finished_tasks_file_path: PathBuf, - pub tasks_file_path: PathBuf, - pub current_task_info_file_path: PathBuf, -} - -pub fn execute(mut req: Request) { - match req.current_task_info.take() { - None => { - panic!("You can use the finish subcommand only when you have an active task") +pub fn execute(repo: impl Repository) { + match repo.finish_task() { + Err(repo::Error::NotFound) => { + eprintln!("You can use the finish subcommand only when you have an active task") } - Some(info) => { - let mut finished_tasks: Vec = std::fs::File::open(&req.finished_tasks_file_path) - .map(|file| serde_json::from_reader(file).unwrap()) - .unwrap_or_default(); - - let task = req.tasks.remove(info.task_idx - 1); - finished_tasks.push(task); - - let mut file = std::fs::File::create(req.tasks_file_path).unwrap(); - file.write_all(&serde_json::to_vec(&req.tasks).unwrap()) - .unwrap(); - - let mut file = std::fs::File::create(req.finished_tasks_file_path).unwrap(); - file.write_all(&serde_json::to_vec(&finished_tasks).unwrap()) - .unwrap(); - - let mut file = std::fs::File::create(req.current_task_info_file_path).unwrap(); - file.write_all(&serde_json::to_vec(&req.current_task_info).unwrap()) - .unwrap(); - - println!("finished"); + Err(err) => eprintln!("Cannot finish the task: {}", err), + Ok(task) => { + println!("The task was finished successfully"); + println!(" {}", task.name); + if let Some(link) = task.link { + println!(" link: {}", link); + } } } } diff --git a/src/cli/list.rs b/src/cli/list.rs index 818bdae..d309534 100644 --- a/src/cli/list.rs +++ b/src/cli/list.rs @@ -1,15 +1,23 @@ -use crate::{CurrentTaskInfo, Task}; +use crate::domain::CurrentTaskInfo; +use crate::repo::Repository; -pub struct Request { - pub tasks: Vec, - pub current_task_info: Option, -} +pub fn execute(repo: impl Repository) { + let tasks = match repo.get_tasks() { + Ok(tasks) => tasks, + Err(err) => return eprintln!("Cannot read tasks: {}", err), + }; -pub fn execute(req: Request) { - for (i, task) in req.tasks.iter().enumerate() { + let cur_task = match repo.get_current_task_opt() { + Ok(cur_task) => cur_task, + Err(err) => { + return eprintln!("Cannot read current task: {}", err); + } + }; + + for (i, task) in tasks.iter().enumerate() { let idx = i + 1; - match req.current_task_info { + match cur_task { Some(CurrentTaskInfo { task_idx, .. }) if task_idx == idx => print!("> "), _ => print!(" "), } diff --git a/src/cli/pause.rs b/src/cli/pause.rs index d1b34b2..1fc574e 100644 --- a/src/cli/pause.rs +++ b/src/cli/pause.rs @@ -1,20 +1,26 @@ -use std::io::Write; -use std::path::PathBuf; +use crate::repo::{self, Repository}; -use crate::CurrentTaskInfo; +pub fn execute(repo: impl Repository) { + let task = match repo.stop_task() { + Err(repo::Error::NotFound) => { + return eprintln!("You can use the pause subcommand only when you have an active task") + } + Err(err) => { + return eprintln!("Cannot read current task: {}", err); + } + Ok(task) => task, + }; -pub struct Request { - pub current_task_info: Option, - pub current_task_info_file_path: PathBuf, -} - -pub fn execute(mut req: Request) { - if req.current_task_info.take().is_none() { - panic!("You can use the pause subcommand only when you have an active task"); + match repo.stop_task() { + Ok(_) => { + println!("The task was paused successfully"); + println!(" {}", task.name); + if let Some(link) = task.link { + println!(" link: {}", link); + } + } + Err(err) => { + eprintln!("Cannot pause the task: {}", err); + } } - - let mut file = std::fs::File::create(&req.current_task_info_file_path).unwrap(); - file.write_all(&serde_json::to_vec(&req.current_task_info).unwrap()) - .unwrap(); - println!("paused"); } diff --git a/src/cli/priority.rs b/src/cli/priority.rs index 23d08d0..bc8a5dc 100644 --- a/src/cli/priority.rs +++ b/src/cli/priority.rs @@ -1,8 +1,6 @@ -use crate::{CurrentTaskInfo, Task}; +use crate::repo::{self, Repository}; use std::cmp::Ordering; -use std::io::Write; -use std::path::PathBuf; #[derive(clap::Args)] pub struct Args { @@ -17,46 +15,61 @@ pub enum Priority { After { idx: usize }, } -pub struct Request { - pub args: Args, - pub tasks: Vec, - pub current_task_info: Option, - pub tasks_file_path: PathBuf, -} - -pub fn execute(mut req: Request) { - if req.current_task_info.is_some() { - panic!("You can change priority only when you don't have an active task, yet"); +pub fn execute(repo: impl Repository, args: Args) { + match repo.get_current_task_opt() { + Ok(Some(_)) => { + return eprintln!( + "You can change priority only when you don't have an active task, yet" + ) + } + Err(err) => { + return eprintln!("Cannot read current task: {}", err); + } + _ => {} } - let target_idx = req.args.idx; - if target_idx == 0 || target_idx > req.tasks.len() { - panic!("invalid index"); + let target_idx = args.idx; + + if let Err(err) = repo.get_task_opt(target_idx) { + return eprintln!("Task not found: {}", err); } - let idx = match req.args.priority { + let idx = match args.priority { Priority::Before { idx } | Priority::After { idx } => match target_idx.cmp(&idx) { Ordering::Equal => return, Ordering::Less => idx - 1, Ordering::Greater => idx, }, }; - if idx == 0 || idx > req.tasks.len() { - panic!("invalid index"); + if let Err(err) = repo.get_task_opt(idx) { + return eprintln!("Task not found: {}", err); } - let target = req.tasks.remove(target_idx - 1); + let target = match repo.remove_task(target_idx) { + Ok(removed) => removed, + Err(err) => return eprintln!("Cannot remove the task: {}", err), + }; - match req.args.priority { - Priority::Before { .. } => { - req.tasks.insert(idx - 1, target); + let new_idx = match args.priority { + Priority::Before { .. } => idx - 1, + Priority::After { .. } => idx, + }; + + let res = repo.insert_task(repo::InsertTaskData { + index: Some(new_idx), + name: target.name, + link: target.link, + }); + match res { + Ok(task) => { + println!("The task was reordered successfully"); + println!(" {}", task.name); + if let Some(link) = task.link { + println!(" link: {}", link); + } } - Priority::After { .. } => { - req.tasks.insert(idx, target); + Err(err) => { + eprintln!("Cannot reorder the task: {}", err); } } - - let mut file = std::fs::File::create(&req.tasks_file_path).unwrap(); - file.write_all(&serde_json::to_vec(&req.tasks).unwrap()) - .unwrap(); } diff --git a/src/cli/remove.rs b/src/cli/remove.rs index 86f8c6b..34835fe 100644 --- a/src/cli/remove.rs +++ b/src/cli/remove.rs @@ -1,31 +1,28 @@ +use crate::repo::{self, Repository}; use std::io::{BufRead, Write}; -use std::path::PathBuf; - -use crate::{CurrentTaskInfo, Task}; #[derive(clap::Args)] pub struct Args { idx: usize, } -pub struct Request { - pub args: Args, - pub current_task_info: Option, - pub tasks: Vec, - pub tasks_file_path: PathBuf, -} - -pub fn execute(mut req: Request) { - if req.current_task_info.is_some() { - panic!("You can remove task only when you don't have an active task, yet"); +pub fn execute(repo: impl Repository, args: Args) { + match repo.get_current_task_opt() { + Ok(Some(_)) => { + return eprintln!("You can remove task only when you don't have an active task, yet"); + } + Err(err) => { + return eprintln!("Cannot read current task: {}", err); + } + _ => {} } - let idx = req.args.idx; - if idx == 0 || idx > req.tasks.len() { - panic!("invalid index"); - } + let task = match repo.get_task_opt(args.idx) { + Ok(Some(task)) => task, + Ok(None) | Err(repo::Error::NotFound) => return eprintln!("Task not found"), + Err(err) => return eprintln!("Cannot get task: {}", err), + }; - let task = &req.tasks[idx - 1]; println!("You are deleting task:"); println!(" {}", task.name); if let Some(ref link) = task.link { @@ -46,11 +43,16 @@ pub fn execute(mut req: Request) { } } - req.tasks.remove(idx - 1); - - let mut file = std::fs::File::create(&req.tasks_file_path).unwrap(); - file.write_all(&serde_json::to_vec(&req.tasks).unwrap()) - .unwrap(); - - println!("removed"); + match repo.remove_task(args.idx) { + Ok(_) => { + println!("The task was removed successfully"); + println!(" {}", task.name); + if let Some(link) = task.link { + println!(" link: {}", link); + } + } + Err(err) => { + eprintln!("Cannot remove the task: {}", err); + } + } } diff --git a/src/cli/start.rs b/src/cli/start.rs index 4e05a42..76aa748 100644 --- a/src/cli/start.rs +++ b/src/cli/start.rs @@ -1,5 +1,4 @@ -use crate::{CurrentTaskInfo, Task}; -use std::{io::Write, path::PathBuf}; +use crate::repo::{self, Repository}; #[derive(clap::Args)] pub struct Args { @@ -9,31 +8,20 @@ pub struct Args { idx: Option, } -pub struct Request { - pub args: Args, - pub tasks: Vec, - pub current_task_info: Option, - pub current_task_info_file_path: PathBuf, -} +pub fn execute(repo: impl Repository, args: Args) { + let task = match repo.start_task(args.idx.unwrap_or(1)) { + Ok(task) => task, + Err(repo::Error::NotFound) => return eprintln!("Task not found"), + Err(err) => return eprintln!("Cannot start task: {}", err), + }; -pub fn execute(mut req: Request) { - let idx = req.args.idx.unwrap_or(1); - if idx == 0 || idx > req.tasks.len() { - panic!("invalid index"); + println!("The task was started successfully"); + println!(" {}", task.name); + if let Some(link) = task.link.as_ref() { + println!(" link: {}", link); } - let task = &req.tasks[idx - 1]; - req.current_task_info.replace(CurrentTaskInfo { - task_idx: idx, - task: task.clone(), - }); - - let mut file = std::fs::File::create(&req.current_task_info_file_path).unwrap(); - file.write_all(&serde_json::to_vec(&req.current_task_info).unwrap()) - .unwrap(); - println!("started"); - - if let (Some(link), true) = (task.link.as_ref(), req.args.open) { + if let (Some(link), true) = (task.link.as_ref(), args.open) { log::debug!("opening link..."); std::process::Command::new("xdg-open") .arg(link) diff --git a/src/cli/status.rs b/src/cli/status.rs index 3001b28..194d639 100644 --- a/src/cli/status.rs +++ b/src/cli/status.rs @@ -1,16 +1,19 @@ -use crate::CurrentTaskInfo; +use crate::repo::Repository; -pub fn execute(current_task_info: Option) { - match current_task_info { - None => { +pub fn execute(repo: impl Repository) { + match repo.get_current_task_opt() { + Ok(None) => { eprintln!("You don't have an active task."); } - Some(info) => { + Ok(Some(info)) => { println!("Information about your current task:"); println!(" {}", info.task.name); if let Some(link) = info.task.link { println!(" link: {}", link); } } + Err(err) => { + eprintln!("Cannot read current task: {}", err); + } } } diff --git a/src/domain.rs b/src/domain.rs index f9dbeb2..f52d9f0 100644 --- a/src/domain.rs +++ b/src/domain.rs @@ -5,3 +5,9 @@ pub struct Task { pub link: Option, // created_at } + +pub struct CurrentTaskInfo { + pub task_idx: usize, + pub task: Task, + // started_at +} diff --git a/src/main.rs b/src/main.rs index 0c6f98d..5d1cd75 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,7 +28,6 @@ use clap::Parser; use repo::fs::FsRepo; -use serde::{Deserialize, Serialize}; use xdg::BaseDirectories; mod cli; @@ -39,84 +38,35 @@ fn main() { let args = cli::Args::parse(); let xdg_dirs = BaseDirectories::with_prefix(env!("CARGO_PKG_NAME")).unwrap(); - let repo = FsRepo::new(xdg_dirs); - let finished_tasks_file_path = xdg_dirs.place_data_file("finished_data.json").unwrap(); - - let current_task_info_file_path = xdg_dirs.place_data_file("current.json").unwrap(); - let current_task_info: Option = - std::fs::File::open(¤t_task_info_file_path) - .map(|file| serde_json::from_reader(file).unwrap()) - .unwrap_or_default(); - match args.command { cli::SubCommand::Add(args) => { cli::add::execute(repo, args); } cli::SubCommand::Edit(args) => { - cli::edit::execute( - repo, - cli::edit::Request { - args, - current_task_info, - }, - ); + cli::edit::execute(repo, args); } cli::SubCommand::Remove(args) => { - cli::remove::execute(cli::remove::Request { - args, - current_task_info, - tasks, - tasks_file_path, - }); + cli::remove::execute(repo, args); } cli::SubCommand::List => { - cli::list::execute(cli::list::Request { - tasks, - current_task_info, - }); + cli::list::execute(repo); } cli::SubCommand::Priority(args) => { - cli::priority::execute(cli::priority::Request { - args, - tasks, - tasks_file_path, - current_task_info, - }); + cli::priority::execute(repo, args); } cli::SubCommand::Start(args) => { - cli::start::execute(cli::start::Request { - args, - tasks, - current_task_info, - current_task_info_file_path, - }); + cli::start::execute(repo, args); } cli::SubCommand::Pause => { - cli::pause::execute(cli::pause::Request { - current_task_info, - current_task_info_file_path, - }); + cli::pause::execute(repo); } cli::SubCommand::Finish => { - cli::finish::execute(cli::finish::Request { - tasks, - current_task_info, - current_task_info_file_path, - tasks_file_path, - finished_tasks_file_path, - }); + cli::finish::execute(repo); } cli::SubCommand::Status => { - cli::status::execute(current_task_info); + cli::status::execute(repo); } } } - -#[derive(Deserialize, Serialize)] -pub struct CurrentTaskInfo { - task_idx: usize, - task: Task, - // started_at -} diff --git a/src/repo.rs b/src/repo.rs index 2eb691f..00097c3 100755 --- a/src/repo.rs +++ b/src/repo.rs @@ -22,18 +22,25 @@ impl std::fmt::Display for Error { impl std::error::Error for Error {} pub struct InsertTaskData { - name: String, - link: Option, + pub name: String, + pub link: Option, + pub index: Option, } pub struct UpdateTaskData { - name: Option, - link: Option>, + pub name: Option, + pub link: Option>, } pub trait Repository { + fn get_current_task_opt(&self) -> Result, Error>; + + fn get_task_opt(&self, id: domain::TaskId) -> Result, Error>; + fn get_tasks(&self) -> Result, Error>; + fn remove_task(&self, id: domain::TaskId) -> Result; + fn update_task( &self, id: domain::TaskId, @@ -41,4 +48,10 @@ pub trait Repository { ) -> Result; fn insert_task(&self, insert_data: InsertTaskData) -> Result; + + fn start_task(&self, id: domain::TaskId) -> Result; + + fn stop_task(&self) -> Result; + + fn finish_task(&self) -> Result; } diff --git a/src/repo/fs.rs b/src/repo/fs.rs index 2cb5eaa..aeaa29c 100644 --- a/src/repo/fs.rs +++ b/src/repo/fs.rs @@ -23,7 +23,25 @@ impl From for domain::Task { } } +#[derive(Deserialize, Serialize, Clone)] +pub struct CurrentTaskInfo { + task_idx: usize, + task: Task, + // started_at +} + +impl From for domain::CurrentTaskInfo { + fn from(repo: CurrentTaskInfo) -> Self { + domain::CurrentTaskInfo { + task_idx: repo.task_idx, + task: repo.task.into(), + } + } +} + +const CURRENT_TASK_FILE: &str = "current.json"; const DATA_FILE: &str = "data.json"; +const FINISHED_DATA_FILE: &str = "finished_data.json"; pub struct FsRepo { xdg_dirs: BaseDirectories, @@ -36,11 +54,37 @@ impl FsRepo { } impl Repository for FsRepo { + fn get_current_task_opt(&self) -> Result, Error> { + self.get_current_task_impl() + .map(|cur_task| cur_task.map(From::from)) + } + + fn get_task_opt(&self, id: domain::TaskId) -> Result, Error> { + let tasks = self.get_tasks_impl()?; + if id == 0 || id > tasks.len() { + return Err(Error::NotFound); + } + + 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 remove_task(&self, id: domain::TaskId) -> Result { + let mut tasks = self.get_tasks_impl()?; + if id == 0 || id > tasks.len() { + return Err(Error::NotFound); + } + + let removed = tasks.remove(id - 1); + self.save_tasks_impl(tasks)?; + + Ok(removed.into()) + } + fn update_task( &self, id: domain::TaskId, @@ -74,20 +118,86 @@ impl Repository for FsRepo { }; let mut tasks = self.get_tasks_impl()?; - tasks.push(new_task.clone()); + + match insert_data.index { + None => tasks.push(new_task.clone()), + Some(idx) => tasks.insert(idx, new_task.clone()), + } self.save_tasks_impl(tasks)?; Ok(new_task.into()) } + + fn start_task(&self, id: domain::TaskId) -> Result { + let tasks = self.get_tasks_impl()?; + if id == 0 || id > tasks.len() { + return Err(Error::NotFound); + } + + let task = &tasks[id - 1]; + let mut cur_task = self.get_current_task_impl()?; + + cur_task.replace(CurrentTaskInfo { + task_idx: id, + task: task.clone(), + }); + + self.save_current_task_impl(cur_task)?; + Ok(task.clone().into()) + } + + fn stop_task(&self) -> Result { + let mut cur_task = self.get_current_task_impl()?; + let old = cur_task.take().ok_or(Error::NotFound)?; + self.save_current_task_impl(cur_task)?; + Ok(old.task.into()) + } + + fn finish_task(&self) -> Result { + let mut cur_task = self.get_current_task_impl()?; + let old = cur_task.take().ok_or(Error::NotFound)?; + + let mut finished_tasks = self.get_finished_tasks_impl()?; + let mut tasks = self.get_tasks_impl()?; + + let task = tasks.remove(old.task_idx - 1); + finished_tasks.push(task.clone()); + + self.save_current_task_impl(cur_task)?; + self.save_tasks_impl(tasks)?; + self.save_finished_tasks_impl(finished_tasks)?; + + Ok(task.into()) + } } impl FsRepo { + fn get_current_task_impl(&self) -> Result, Error> { + let file_path = self.xdg_dirs.get_data_file(CURRENT_TASK_FILE); + File::open(&file_path) + .ok() + .map(|file| serde_json::from_reader(file).map_err(|_| Error::InvalidData)) + .transpose() + } + + fn save_current_task_impl(&self, cur_task: Option) -> Result<(), Error> { + let file_path = self + .xdg_dirs + .place_data_file(CURRENT_TASK_FILE) + .map_err(|_| Error::InsertData)?; + let mut file = File::create(&file_path).map_err(|_| Error::InsertData)?; + let new_data = serde_json::to_vec(&cur_task).map_err(|_| Error::InvalidData)?; + file.write_all(&new_data).map_err(|_| Error::InsertData) + } + fn get_tasks_impl(&self) -> Result, Error> { let file_path = self.xdg_dirs.get_data_file(DATA_FILE); File::open(&file_path) - .map_err(|_| Error::NotFound) - .and_then(|file| serde_json::from_reader(file).map_err(|_| Error::InvalidData)) + .ok() + .map(|file| serde_json::from_reader(file).map_err(|_| Error::InvalidData)) + .transpose() + .map(|tasks| tasks.unwrap_or_default()) } fn save_tasks_impl(&self, tasks: Vec) -> Result<(), Error> { @@ -99,4 +209,23 @@ impl FsRepo { let new_data = serde_json::to_vec(&tasks).map_err(|_| Error::InvalidData)?; file.write_all(&new_data).map_err(|_| Error::InsertData) } + + fn get_finished_tasks_impl(&self) -> Result, Error> { + let file_path = self.xdg_dirs.get_data_file(FINISHED_DATA_FILE); + File::open(&file_path) + .ok() + .map(|file| serde_json::from_reader(file).map_err(|_| Error::InvalidData)) + .transpose() + .map(|tasks| tasks.unwrap_or_default()) + } + + fn save_finished_tasks_impl(&self, tasks: Vec) -> Result<(), Error> { + let file_path = self + .xdg_dirs + .place_data_file(FINISHED_DATA_FILE) + .map_err(|_| Error::InsertData)?; + let mut file = File::create(&file_path).map_err(|_| Error::InsertData)?; + let new_data = serde_json::to_vec(&tasks).map_err(|_| Error::InvalidData)?; + file.write_all(&new_data).map_err(|_| Error::InsertData) + } }