use std::{
collections::HashMap,
fs, io,
path::{Path, PathBuf},
};
use chrono::{TimeZone, Utc};
use log::debug;
use tinyjson::JsonValue;
use darkfi::util::{
file::{load_json_file, save_json_file},
time::Timestamp,
};
use crate::{
error::{TaudError, TaudResult},
task_info::TaskInfo,
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MonthTasks {
created_at: Timestamp,
active_tks: Vec<String>,
deactive_tks: Vec<String>,
}
impl From<MonthTasks> for JsonValue {
fn from(mt: MonthTasks) -> JsonValue {
let active_tks: Vec<JsonValue> =
mt.active_tks.iter().map(|x| JsonValue::String(x.clone())).collect();
let deactive_tks: Vec<JsonValue> =
mt.deactive_tks.iter().map(|x| JsonValue::String(x.clone())).collect();
JsonValue::Object(HashMap::from([
("created_at".to_string(), JsonValue::String(mt.created_at.inner().to_string())),
("active_tks".to_string(), JsonValue::Array(active_tks)),
("deactive_tks".to_string(), JsonValue::Array(deactive_tks)),
]))
}
}
impl From<JsonValue> for MonthTasks {
fn from(value: JsonValue) -> MonthTasks {
let created_at = {
let u64_str = value["created_at"].get::<String>().unwrap();
Timestamp::from_u64(u64_str.parse::<u64>().unwrap())
};
let active_tks: Vec<String> = value["active_tks"]
.get::<Vec<JsonValue>>()
.unwrap()
.iter()
.map(|x| x.get::<String>().unwrap().clone())
.collect();
let deactive_tks: Vec<String> = value["deactive_tks"]
.get::<Vec<JsonValue>>()
.unwrap()
.iter()
.map(|x| x.get::<String>().unwrap().clone())
.collect();
MonthTasks { created_at, active_tks, deactive_tks }
}
}
impl MonthTasks {
pub fn new(active_tks: &[String], deactive_tks: &[String]) -> Self {
Self {
created_at: Timestamp::current_time(),
active_tks: active_tks.to_owned(),
deactive_tks: deactive_tks.to_owned(),
}
}
pub fn add(&mut self, ref_id: &str) {
debug!(target: "tau", "MonthTasks::add()");
if !self.active_tks.contains(&ref_id.into()) {
self.active_tks.push(ref_id.into());
}
}
pub fn objects(&self, dataset_path: &Path) -> TaudResult<Vec<TaskInfo>> {
debug!(target: "tau", "MonthTasks::objects()");
let mut tks: Vec<TaskInfo> = vec![];
for ref_id in self.active_tks.iter() {
tks.push(TaskInfo::load(ref_id, dataset_path)?);
}
for ref_id in self.deactive_tks.iter() {
tks.push(TaskInfo::load(ref_id, dataset_path)?);
}
Ok(tks)
}
pub fn remove(&mut self, ref_id: &str) {
debug!(target: "tau", "MonthTasks::remove()");
if self.active_tks.contains(&ref_id.to_string()) {
if let Some(index) = self.active_tks.iter().position(|t| *t == ref_id) {
self.deactive_tks.push(self.active_tks.remove(index));
}
} else {
self.deactive_tks.push(ref_id.to_owned());
}
}
pub fn set_date(&mut self, date: &Timestamp) {
debug!(target: "tau", "MonthTasks::set_date()");
self.created_at = *date;
}
fn get_path(date: &Timestamp, dataset_path: &Path) -> PathBuf {
debug!(target: "tau", "MonthTasks::get_path()");
dataset_path.join("month").join(
Utc.timestamp_opt(date.inner().try_into().unwrap(), 0)
.unwrap()
.format("%m%y")
.to_string(),
)
}
pub fn save(&self, dataset_path: &Path) -> TaudResult<()> {
debug!(target: "tau", "MonthTasks::save()");
let mt: JsonValue = self.clone().into();
save_json_file(&Self::get_path(&self.created_at, dataset_path), &mt, true)
.map_err(TaudError::Darkfi)
}
fn get_all(dataset_path: &Path) -> io::Result<Vec<PathBuf>> {
debug!(target: "tau", "MonthTasks::get_all()");
let mut entries = fs::read_dir(dataset_path.join("month"))?
.map(|res| res.map(|e| e.path()))
.collect::<Result<Vec<_>, io::Error>>()?;
entries.sort();
Ok(entries)
}
fn create(date: &Timestamp, dataset_path: &Path) -> TaudResult<Self> {
debug!(target: "tau", "MonthTasks::create()");
let mut mt = Self::new(&[], &[]);
mt.set_date(date);
mt.save(dataset_path)?;
Ok(mt)
}
pub fn load_or_create(date: Option<&Timestamp>, dataset_path: &Path) -> TaudResult<Self> {
debug!(target: "tau", "MonthTasks::load_or_create()");
match date {
Some(date) => match load_json_file(&Self::get_path(date, dataset_path)) {
Ok(mt) => Ok(mt.into()),
Err(_) => Self::create(date, dataset_path),
},
None => {
let path_all = Self::get_all(dataset_path).unwrap_or_default();
let mut loaded_mt = Self::new(&[], &[]);
for path in path_all {
let mt = load_json_file(&path)?;
let mt: MonthTasks = mt.into();
loaded_mt.created_at = mt.created_at;
for tks in mt.active_tks {
if !loaded_mt.active_tks.contains(&tks) {
loaded_mt.active_tks.push(tks)
}
}
for dtks in mt.deactive_tks {
if !loaded_mt.deactive_tks.contains(&dtks) {
loaded_mt.deactive_tks.push(dtks)
}
}
}
Ok(loaded_mt)
}
}
}
pub fn load_current_tasks(
dataset_path: &Path,
ws: String,
all: bool,
) -> TaudResult<Vec<TaskInfo>> {
let mt = Self::load_or_create(None, dataset_path)?;
if all {
Ok(mt.objects(dataset_path)?.into_iter().filter(|t| t.workspace == ws).collect())
} else {
Ok(mt
.objects(dataset_path)?
.into_iter()
.filter(|t| t.get_state() != "stop" && t.workspace == ws)
.collect())
}
}
pub fn load_stop_tasks(
dataset_path: &Path,
ws: String,
date: Option<&Timestamp>,
) -> TaudResult<Vec<TaskInfo>> {
let mt = Self::load_or_create(date, dataset_path)?;
Ok(mt
.objects(dataset_path)?
.into_iter()
.filter(|t| t.get_state() == "stop" && t.workspace == ws)
.collect())
}
}
#[cfg(test)]
mod tests {
use std::fs::{create_dir_all, remove_dir_all};
use super::*;
use darkfi::Result;
const TEST_DATA_PATH: &str = "/tmp/test_tau_data";
fn get_path() -> Result<PathBuf> {
remove_dir_all(TEST_DATA_PATH).ok();
let path = PathBuf::from(TEST_DATA_PATH);
create_dir_all(path.join("month"))?;
create_dir_all(path.join("task"))?;
Ok(path)
}
#[test]
fn load_and_save_tasks() -> TaudResult<()> {
let dataset_path = get_path()?;
let mut task = TaskInfo::new(
"darkfi".to_string(),
"test_title",
"test_desc",
"NICKNAME",
None,
Some(0.0),
Timestamp::current_time(),
)?;
task.save(&dataset_path)?;
let t_load = TaskInfo::load(&task.ref_id, &dataset_path)?;
assert_eq!(task, t_load);
task.set_title("test_title_2");
task.save(&dataset_path)?;
let t_load = TaskInfo::load(&task.ref_id, &dataset_path)?;
assert_eq!(task, t_load);
let task_tks = vec![];
let mut mt = MonthTasks::new(&task_tks, &[]);
mt.save(&dataset_path)?;
let mt_load = MonthTasks::load_or_create(Some(&Timestamp::current_time()), &dataset_path)?;
assert_eq!(mt, mt_load);
mt.add(&task.ref_id);
mt.save(&dataset_path)?;
let mt_load = MonthTasks::load_or_create(Some(&Timestamp::current_time()), &dataset_path)?;
assert_eq!(mt, mt_load);
let task = TaskInfo::new(
"darkfi".to_string(),
"test_title_3",
"test_desc",
"NICKNAME",
None,
Some(0.0),
Timestamp::current_time(),
)?;
task.save(&dataset_path)?;
let mt_load = MonthTasks::load_or_create(Some(&Timestamp::current_time()), &dataset_path)?;
assert!(mt_load.active_tks.contains(&task.ref_id));
remove_dir_all(TEST_DATA_PATH).ok();
Ok(())
}
}