//! Copyright (C) 2022, Dmitriy Pleshevskiy //! //! tas is free software: you can redistribute it and/or modify //! it under the terms of the GNU General Public License as published by //! the Free Software Foundation, either version 3 of the License, or //! (at your option) any later version. //! //! tas is distributed in the hope that it will be useful, //! but WITHOUT ANY WARRANTY; without even the implied warranty of //! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //! GNU General Public License for more details. //! //! You should have received a copy of the GNU General Public License //! along with tas. If not, see . //! use std::fs::File; use std::io::Write; use std::path::PathBuf; use crate::domain; use crate::repo::{Error, InsertTaskData, Repository, UpdateTaskData}; use serde::{Deserialize, Serialize}; use xdg::BaseDirectories; #[derive(Deserialize, Serialize, Clone)] pub struct Task { name: String, group: Option, link: Option, path: Option, // created_at } impl From for domain::Task { fn from(repo: Task) -> Self { domain::Task { name: repo.name, project: repo.group, link: repo.link, dir_path: repo.path.map(PathBuf::from), created_at: time::OffsetDateTime::now_utc(), } } } #[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, } impl FsRepo { pub fn new(xdg_dirs: BaseDirectories) -> Self { Self { xdg_dirs } } } 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::TaskIdx) -> 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::TaskIdx) -> 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::TaskIdx, 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 - 1]; if let Some(name) = update_data.name { task.name = name; } if let Some(group) = update_data.project { task.group = group; } if let Some(link) = update_data.link { task.link = link; } if let Some(path) = update_data.dir_path { task.path = path.and_then(|p| p.into_os_string().into_string().ok()); } 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, group: insert_data.project, link: insert_data.link, path: insert_data .dir_path .and_then(|p| p.into_os_string().into_string().ok()), }; let mut tasks = self.get_tasks_impl()?; 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::TaskIdx) -> 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); Ok(File::open(&file_path) .ok() .and_then(|file| serde_json::from_reader(file).ok())) } 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) .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> { 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) } 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) } }