/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 Dyne.org foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
use std::path::{Path, PathBuf};
use darkfi_serial::{SerialDecodable, SerialEncodable};
use log::debug;
use serde::{Deserialize, Serialize};
use darkfi::{
event_graph::gen_id,
util::{
file::{load_json_file, save_json_file},
time::Timestamp,
},
};
use crate::{
error::{TaudError, TaudResult},
month_tasks::MonthTasks,
util::find_free_id,
};
#[derive(Clone, Debug, Serialize, Deserialize, SerialEncodable, SerialDecodable, PartialEq, Eq)]
pub struct TaskEvent {
pub action: String,
pub author: String,
pub content: String,
pub timestamp: Timestamp,
}
impl TaskEvent {
pub fn new(action: String, author: String, content: String) -> Self {
Self { action, author, content, timestamp: Timestamp::current_time() }
}
}
#[derive(Clone, Debug, Serialize, Deserialize, SerialDecodable, SerialEncodable, PartialEq, Eq)]
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, Eq, SerialEncodable, SerialDecodable)]
pub struct TaskEvents(pub Vec);
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, SerialEncodable, SerialDecodable)]
pub struct TaskComments(Vec);
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, SerialEncodable, SerialDecodable)]
pub struct TaskProjects(Vec);
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, SerialEncodable, SerialDecodable)]
pub struct TaskAssigns(Vec);
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, SerialEncodable, SerialDecodable)]
pub struct TaskTags(Vec);
#[derive(Clone, Debug, Serialize, Deserialize, SerialEncodable, SerialDecodable, PartialEq)]
pub struct TaskInfo {
pub(crate) ref_id: String,
pub(crate) workspace: String,
pub(crate) id: u32,
pub(crate) title: String,
tags: TaskTags,
desc: String,
pub(crate) owner: String,
assign: TaskAssigns,
project: TaskProjects,
due: Option,
rank: Option,
created_at: Timestamp,
state: String,
pub(crate) events: TaskEvents,
comments: TaskComments,
}
impl TaskInfo {
pub fn new(
workspace: String,
title: &str,
desc: &str,
owner: &str,
due: Option,
rank: Option,
dataset_path: &Path,
) -> TaudResult {
// generate ref_id
let ref_id = gen_id(30);
let created_at = Timestamp::current_time();
let task_ids: Vec =
MonthTasks::load_current_tasks(dataset_path, workspace.clone(), false)?
.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,
workspace,
id,
title: title.into(),
desc: desc.into(),
owner: owner.into(),
tags: TaskTags(vec![]),
assign: TaskAssigns(vec![]),
project: TaskProjects(vec![]),
due,
rank,
created_at,
state: "open".into(),
comments: TaskComments(vec![]),
events: TaskEvents(vec![]),
})
}
pub fn load(ref_id: &str, dataset_path: &Path) -> TaudResult {
debug!(target: "tau", "TaskInfo::load()");
let task = load_json_file::(&Self::get_path(ref_id, dataset_path))?;
Ok(task)
}
pub fn save(&self, dataset_path: &Path) -> TaudResult<()> {
debug!(target: "tau", "TaskInfo::save()");
save_json_file::(&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()");
self.state.clone()
}
pub 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_tags(&mut self, tags: &[String]) {
debug!(target: "tau", "TaskInfo::set_tags()");
for tag in tags.iter() {
if tag.starts_with('+') && !self.tags.0.contains(tag) {
self.tags.0.push(tag.to_string());
}
if tag.starts_with('-') {
let t = tag.replace('-', "+");
self.tags.0.retain(|tag| tag != &t);
}
}
}
pub fn set_assign(&mut self, assigns: &[String]) {
debug!(target: "tau", "TaskInfo::set_assign()");
self.assign = TaskAssigns(assigns.to_owned());
}
pub fn set_project(&mut self, projects: &[String]) {
debug!(target: "tau", "TaskInfo::set_project()");
self.project = TaskProjects(projects.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: Option) {
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, state: &str) {
debug!(target: "tau", "TaskInfo::set_state()");
if self.get_state() == state {
return
}
self.state = state.to_string();
}
}