use std::{ io, path::{Path, PathBuf}, }; use log::debug; use serde::{Deserialize, Serialize}; use darkfi::util::{ serial::{Decodable, Encodable, SerialDecodable, SerialEncodable, VarInt}, Timestamp, }; use crate::{ error::{TaudError, TaudResult}, month_tasks::MonthTasks, util::{find_free_id, load, random_ref_id, save}, }; #[derive(Clone, Debug, Serialize, Deserialize, SerialEncodable, SerialDecodable, PartialEq)] struct TaskEvent { action: String, timestamp: Timestamp, } impl TaskEvent { fn new(action: String) -> Self { Self { action, timestamp: Timestamp::current_time() } } } #[derive(Clone, Debug, Serialize, Deserialize, SerialDecodable, SerialEncodable, PartialEq)] pub struct Comment { content: String, author: String, timestamp: Timestamp, } impl Comment { pub fn new(content: &str, author: &str) -> Self { Self { content: content.into(), author: author.into(), timestamp: Timestamp::current_time(), } } } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct TaskEvents(Vec); #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct TaskComments(Vec); #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct TaskProjects(Vec); #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct TaskAssigns(Vec); #[derive(Clone, Debug, Serialize, Deserialize, SerialEncodable, SerialDecodable, PartialEq)] pub struct TaskInfo { pub(crate) ref_id: String, id: u32, title: String, desc: String, owner: String, assign: TaskAssigns, project: TaskProjects, due: Option, rank: f32, created_at: Timestamp, events: TaskEvents, comments: TaskComments, } impl TaskInfo { pub fn new( title: &str, desc: &str, owner: &str, due: Option, rank: f32, dataset_path: &Path, ) -> TaudResult { // generate ref_id let ref_id = random_ref_id(); let created_at = Timestamp::current_time(); let task_ids: Vec = MonthTasks::load_current_open_tasks(dataset_path)?.into_iter().map(|t| t.id).collect(); let id: u32 = find_free_id(&task_ids); if let Some(d) = &due { if *d < Timestamp::current_time() { return Err(TaudError::InvalidDueTime) } } Ok(Self { ref_id, id, title: title.into(), desc: desc.into(), owner: owner.into(), assign: TaskAssigns(vec![]), project: TaskProjects(vec![]), due, rank, created_at, comments: TaskComments(vec![]), events: TaskEvents(vec![]), }) } pub fn load(ref_id: &str, dataset_path: &Path) -> TaudResult { debug!(target: "tau", "TaskInfo::load()"); let task = load::(&Self::get_path(ref_id, dataset_path))?; Ok(task) } pub fn save(&self, dataset_path: &Path) -> TaudResult<()> { debug!(target: "tau", "TaskInfo::save()"); save::(&Self::get_path(&self.ref_id, dataset_path), self) .map_err(TaudError::Darkfi)?; if self.get_state() == "stop" { self.deactivate(dataset_path)?; } else { self.activate(dataset_path)?; } Ok(()) } pub fn activate(&self, path: &Path) -> TaudResult<()> { debug!(target: "tau", "TaskInfo::activate()"); let mut mt = MonthTasks::load_or_create(Some(&self.created_at), path)?; mt.add(&self.ref_id); mt.save(path) } pub fn deactivate(&self, path: &Path) -> TaudResult<()> { debug!(target: "tau", "TaskInfo::deactivate()"); let mut mt = MonthTasks::load_or_create(Some(&self.created_at), path)?; mt.remove(&self.ref_id); mt.save(path) } pub fn get_state(&self) -> String { debug!(target: "tau", "TaskInfo::get_state()"); if let Some(ev) = self.events.0.last() { ev.action.clone() } else { "open".into() } } fn get_path(ref_id: &str, dataset_path: &Path) -> PathBuf { debug!(target: "tau", "TaskInfo::get_path()"); dataset_path.join("task").join(ref_id) } pub fn get_id(&self) -> u32 { debug!(target: "tau", "TaskInfo::get_id()"); self.id } pub fn set_title(&mut self, title: &str) { debug!(target: "tau", "TaskInfo::set_title()"); self.title = title.into(); } pub fn set_desc(&mut self, desc: &str) { debug!(target: "tau", "TaskInfo::set_desc()"); self.desc = desc.into(); } pub fn set_assign(&mut self, assign: &[String]) { debug!(target: "tau", "TaskInfo::set_assign()"); self.assign = TaskAssigns(assign.to_owned()); } pub fn set_project(&mut self, project: &[String]) { debug!(target: "tau", "TaskInfo::set_project()"); self.project = TaskProjects(project.to_owned()); } pub fn set_comment(&mut self, c: Comment) { debug!(target: "tau", "TaskInfo::set_comment()"); self.comments.0.push(c); } pub fn set_rank(&mut self, r: f32) { debug!(target: "tau", "TaskInfo::set_rank()"); self.rank = r; } pub fn set_due(&mut self, d: Option) { debug!(target: "tau", "TaskInfo::set_due()"); self.due = d; } pub fn set_state(&mut self, action: &str) { debug!(target: "tau", "TaskInfo::set_state()"); if self.get_state() == action { return } self.events.0.push(TaskEvent::new(action.into())); } } impl Encodable for TaskEvents { fn encode(&self, s: S) -> darkfi::Result { encode_vec(&self.0, s) } } impl Decodable for TaskEvents { fn decode(d: D) -> darkfi::Result { Ok(Self(decode_vec(d)?)) } } impl Encodable for TaskComments { fn encode(&self, s: S) -> darkfi::Result { encode_vec(&self.0, s) } } impl Decodable for TaskComments { fn decode(d: D) -> darkfi::Result { Ok(Self(decode_vec(d)?)) } } impl Encodable for TaskProjects { fn encode(&self, s: S) -> darkfi::Result { encode_vec(&self.0, s) } } impl Decodable for TaskProjects { fn decode(d: D) -> darkfi::Result { Ok(Self(decode_vec(d)?)) } } impl Encodable for TaskAssigns { fn encode(&self, s: S) -> darkfi::Result { encode_vec(&self.0, s) } } impl Decodable for TaskAssigns { fn decode(d: D) -> darkfi::Result { Ok(Self(decode_vec(d)?)) } } fn encode_vec(vec: &[T], mut s: S) -> darkfi::Result { let mut len = 0; len += VarInt(vec.len() as u64).encode(&mut s)?; for c in vec.iter() { len += c.encode(&mut s)?; } Ok(len) } fn decode_vec(mut d: D) -> darkfi::Result> { let len = VarInt::decode(&mut d)?.0; let mut ret = Vec::with_capacity(len as usize); for _ in 0..len { ret.push(Decodable::decode(&mut d)?); } Ok(ret) }