1use std::{
20 collections::HashMap,
21 fs, io,
22 path::{Path, PathBuf},
23};
24
25use chrono::{TimeZone, Utc};
26use log::debug;
27use tinyjson::JsonValue;
28
29use darkfi::util::{
30 file::{load_json_file, save_json_file},
31 time::Timestamp,
32};
33
34use crate::{
35 error::{TaudError, TaudResult},
36 task_info::TaskInfo,
37};
38
39#[derive(Clone, Debug, PartialEq, Eq)]
40pub struct MonthTasks {
41 created_at: Timestamp,
42 active_tks: Vec<String>,
43 deactive_tks: Vec<String>,
44}
45
46impl From<MonthTasks> for JsonValue {
47 fn from(mt: MonthTasks) -> JsonValue {
48 let active_tks: Vec<JsonValue> =
49 mt.active_tks.iter().map(|x| JsonValue::String(x.clone())).collect();
50
51 let deactive_tks: Vec<JsonValue> =
52 mt.deactive_tks.iter().map(|x| JsonValue::String(x.clone())).collect();
53
54 JsonValue::Object(HashMap::from([
55 ("created_at".to_string(), JsonValue::String(mt.created_at.inner().to_string())),
56 ("active_tks".to_string(), JsonValue::Array(active_tks)),
57 ("deactive_tks".to_string(), JsonValue::Array(deactive_tks)),
58 ]))
59 }
60}
61
62impl From<JsonValue> for MonthTasks {
63 fn from(value: JsonValue) -> MonthTasks {
64 let created_at = {
65 let u64_str = value["created_at"].get::<String>().unwrap();
66 Timestamp::from_u64(u64_str.parse::<u64>().unwrap())
67 };
68
69 let active_tks: Vec<String> = value["active_tks"]
70 .get::<Vec<JsonValue>>()
71 .unwrap()
72 .iter()
73 .map(|x| x.get::<String>().unwrap().clone())
74 .collect();
75
76 let deactive_tks: Vec<String> = value["deactive_tks"]
77 .get::<Vec<JsonValue>>()
78 .unwrap()
79 .iter()
80 .map(|x| x.get::<String>().unwrap().clone())
81 .collect();
82
83 MonthTasks { created_at, active_tks, deactive_tks }
84 }
85}
86
87impl MonthTasks {
88 pub fn new(active_tks: &[String], deactive_tks: &[String]) -> Self {
89 Self {
90 created_at: Timestamp::current_time(),
91 active_tks: active_tks.to_owned(),
92 deactive_tks: deactive_tks.to_owned(),
93 }
94 }
95
96 pub fn add(&mut self, ref_id: &str) {
97 debug!(target: "tau", "MonthTasks::add()");
98 if !self.active_tks.contains(&ref_id.into()) {
99 self.active_tks.push(ref_id.into());
100 }
101 }
102
103 pub fn objects(&self, dataset_path: &Path) -> TaudResult<Vec<TaskInfo>> {
104 debug!(target: "tau", "MonthTasks::objects()");
105 let mut tks: Vec<TaskInfo> = vec![];
106
107 for ref_id in self.active_tks.iter() {
108 tks.push(TaskInfo::load(ref_id, dataset_path)?);
109 }
110
111 for ref_id in self.deactive_tks.iter() {
112 tks.push(TaskInfo::load(ref_id, dataset_path)?);
113 }
114
115 Ok(tks)
116 }
117
118 pub fn remove(&mut self, ref_id: &str) {
119 debug!(target: "tau", "MonthTasks::remove()");
120 if self.active_tks.contains(&ref_id.to_string()) {
121 if let Some(index) = self.active_tks.iter().position(|t| *t == ref_id) {
122 self.deactive_tks.push(self.active_tks.remove(index));
123 }
124 } else {
125 self.deactive_tks.push(ref_id.to_owned());
126 }
127 }
128
129 pub fn set_date(&mut self, date: &Timestamp) {
130 debug!(target: "tau", "MonthTasks::set_date()");
131 self.created_at = *date;
132 }
133
134 fn get_path(date: &Timestamp, dataset_path: &Path) -> PathBuf {
135 debug!(target: "tau", "MonthTasks::get_path()");
136 dataset_path.join("month").join(
137 Utc.timestamp_opt(date.inner().try_into().unwrap(), 0)
138 .unwrap()
139 .format("%m%y")
140 .to_string(),
141 )
142 }
143
144 pub fn save(&self, dataset_path: &Path) -> TaudResult<()> {
145 debug!(target: "tau", "MonthTasks::save()");
146 let mt: JsonValue = self.clone().into();
147 save_json_file(&Self::get_path(&self.created_at, dataset_path), &mt, true)
148 .map_err(TaudError::Darkfi)
149 }
150
151 fn get_all(dataset_path: &Path) -> io::Result<Vec<PathBuf>> {
152 debug!(target: "tau", "MonthTasks::get_all()");
153
154 let mut entries = fs::read_dir(dataset_path.join("month"))?
155 .map(|res| res.map(|e| e.path()))
156 .collect::<Result<Vec<_>, io::Error>>()?;
157
158 entries.sort();
159
160 Ok(entries)
161 }
162
163 fn create(date: &Timestamp, dataset_path: &Path) -> TaudResult<Self> {
164 debug!(target: "tau", "MonthTasks::create()");
165
166 let mut mt = Self::new(&[], &[]);
167 mt.set_date(date);
168 mt.save(dataset_path)?;
169 Ok(mt)
170 }
171
172 pub fn load_or_create(date: Option<&Timestamp>, dataset_path: &Path) -> TaudResult<Self> {
173 debug!(target: "tau", "MonthTasks::load_or_create()");
174
175 match date {
178 Some(date) => match load_json_file(&Self::get_path(date, dataset_path)) {
179 Ok(mt) => Ok(mt.into()),
180 Err(_) => Self::create(date, dataset_path),
181 },
182 None => {
183 let path_all = Self::get_all(dataset_path).unwrap_or_default();
184
185 let mut loaded_mt = Self::new(&[], &[]);
186
187 for path in path_all {
188 let mt = load_json_file(&path)?;
189 let mt: MonthTasks = mt.into();
190 loaded_mt.created_at = mt.created_at;
191 for tks in mt.active_tks {
192 if !loaded_mt.active_tks.contains(&tks) {
193 loaded_mt.active_tks.push(tks)
194 }
195 }
196 for dtks in mt.deactive_tks {
197 if !loaded_mt.deactive_tks.contains(&dtks) {
198 loaded_mt.deactive_tks.push(dtks)
199 }
200 }
201 }
202 Ok(loaded_mt)
203 }
204 }
205 }
206
207 pub fn load_current_tasks(
208 dataset_path: &Path,
209 ws: String,
210 all: bool,
211 ) -> TaudResult<Vec<TaskInfo>> {
212 let mt = Self::load_or_create(None, dataset_path)?;
213
214 if all {
215 Ok(mt.objects(dataset_path)?.into_iter().filter(|t| t.workspace == ws).collect())
216 } else {
217 Ok(mt
218 .objects(dataset_path)?
219 .into_iter()
220 .filter(|t| t.get_state() != "stop" && t.workspace == ws)
221 .collect())
222 }
223 }
224
225 pub fn load_stop_tasks(
226 dataset_path: &Path,
227 ws: String,
228 date: Option<&Timestamp>,
229 ) -> TaudResult<Vec<TaskInfo>> {
230 let mt = Self::load_or_create(date, dataset_path)?;
231 Ok(mt
232 .objects(dataset_path)?
233 .into_iter()
234 .filter(|t| t.get_state() == "stop" && t.workspace == ws)
235 .collect())
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use std::fs::{create_dir_all, remove_dir_all};
242
243 use super::*;
244 use darkfi::Result;
245
246 const TEST_DATA_PATH: &str = "/tmp/test_tau_data";
247
248 fn get_path() -> Result<PathBuf> {
249 remove_dir_all(TEST_DATA_PATH).ok();
250
251 let path = PathBuf::from(TEST_DATA_PATH);
252
253 create_dir_all(path.join("month"))?;
255 create_dir_all(path.join("task"))?;
256 Ok(path)
257 }
258
259 #[test]
260 fn load_and_save_tasks() -> TaudResult<()> {
261 let dataset_path = get_path()?;
262
263 let mut task = TaskInfo::new(
267 "darkfi".to_string(),
268 "test_title",
269 "test_desc",
270 "NICKNAME",
271 None,
272 Some(0.0),
273 Timestamp::current_time(),
274 )?;
275
276 task.save(&dataset_path)?;
277
278 let t_load = TaskInfo::load(&task.ref_id, &dataset_path)?;
279
280 assert_eq!(task, t_load);
281
282 task.set_title("test_title_2");
283
284 task.save(&dataset_path)?;
285
286 let t_load = TaskInfo::load(&task.ref_id, &dataset_path)?;
287
288 assert_eq!(task, t_load);
289
290 let task_tks = vec![];
294
295 let mut mt = MonthTasks::new(&task_tks, &[]);
296
297 mt.save(&dataset_path)?;
298
299 let mt_load = MonthTasks::load_or_create(Some(&Timestamp::current_time()), &dataset_path)?;
300
301 assert_eq!(mt, mt_load);
302
303 mt.add(&task.ref_id);
304
305 mt.save(&dataset_path)?;
306
307 let mt_load = MonthTasks::load_or_create(Some(&Timestamp::current_time()), &dataset_path)?;
308
309 assert_eq!(mt, mt_load);
310
311 let task = TaskInfo::new(
315 "darkfi".to_string(),
316 "test_title_3",
317 "test_desc",
318 "NICKNAME",
319 None,
320 Some(0.0),
321 Timestamp::current_time(),
322 )?;
323
324 task.save(&dataset_path)?;
325
326 let mt_load = MonthTasks::load_or_create(Some(&Timestamp::current_time()), &dataset_path)?;
327
328 assert!(mt_load.active_tks.contains(&task.ref_id));
329
330 remove_dir_all(TEST_DATA_PATH).ok();
331
332 Ok(())
333 }
334}