hypertask/
task.rs

1use crate::engine::{Mutation, Mutations, Queries, Query};
2use crate::id::Id;
3use crate::prop::Prop;
4use crate::recur::Recur;
5use crate::tag::{Sign, Tag};
6use chrono::prelude::*;
7use serde::{Deserialize, Serialize, Serializer};
8use std::cmp::Ordering;
9use std::collections::{HashMap, HashSet};
10use time::Duration;
11
12//extern crate serde;
13//#[macro_use]
14//extern crate serde_derive;
15//extern crate serde_json;
16
17//use serde::{Serialize, Serializer};
18//use std::collections::{BTreeMap, HashMap};
19
20//#[derive(Serialize, Deserialize, Default)]
21//struct MyStruct {
22//#[serde(serialize_with = "ordered_map")]
23//map: HashMap<String, String>,
24//}
25
26//fn ordered_map<S>(value: &HashMap<String, String>, serializer: S) -> Result<S::Ok, S::Error>
27//where
28//S: Serializer,
29//{
30//let ordered: BTreeMap<_, _> = value.iter().collect();
31//ordered.serialize(serializer)
32//}
33
34#[derive(Serialize, Deserialize, Debug)]
35pub struct Task {
36    created_at: DateTime<Utc>,
37    description: Option<String>,
38    done: Option<DateTime<Utc>>,
39    due: Option<DateTime<Utc>>,
40    id: Id,
41    recur: Option<Recur>,
42    snooze: Option<DateTime<Utc>>,
43    #[serde(serialize_with = "ordered_set")]
44    tags: HashSet<String>,
45    updated_at: DateTime<Utc>,
46    wait: Option<DateTime<Utc>>,
47}
48
49fn ordered_set<S>(value: &HashSet<String>, serializer: S) -> Result<S::Ok, S::Error>
50where
51    S: Serializer,
52{
53    let mut vec = value.iter().collect::<Vec<&String>>();
54
55    vec.sort();
56
57    vec.serialize(serializer)
58}
59
60impl Task {
61    pub fn generate() -> Self {
62        Self {
63            created_at: Utc::now(),
64            description: None,
65            done: None,
66            due: None,
67            id: Id::generate(),
68            recur: None,
69            snooze: None,
70            tags: HashSet::new(),
71            updated_at: Utc::now(),
72            wait: None,
73        }
74    }
75
76    pub fn to_renderable_hash_map(&self) -> HashMap<&str, String> {
77        let mut hm = HashMap::<&str, String>::new();
78
79        let Id(id) = &self.id;
80        hm.insert("id", id.to_string());
81
82        if let Some(description) = &self.description {
83            hm.insert("description", description.to_string());
84        }
85
86        hm.insert(
87            "tags",
88            self.tags
89                .iter()
90                .map(|t| format!("+{}", t))
91                .collect::<Vec<String>>()
92                .join(" "),
93        );
94
95        if let Some(due) = &self.due {
96            hm.insert("due", due.format("%Y-%m-%d %H:%M").to_string());
97        }
98
99        if let Some(recur) = &self.recur {
100            hm.insert("recur", format!("{}", recur));
101        }
102
103        hm
104    }
105
106    pub fn get_id(&self) -> &Id {
107        &(self.id)
108    }
109
110    pub fn satisfies_queries(&self, queries: &Queries) -> bool {
111        if queries.len() == 0 {
112            return false;
113        }
114
115        let mut default = false;
116
117        for query in queries {
118            match query {
119                Query::Id(id) => {
120                    if id == &self.id {
121                        return true;
122                    } else {
123                        continue;
124                    }
125                }
126
127                Query::Tag(Tag {
128                    sign: Sign::Plus,
129                    name,
130                }) => {
131                    if self.tags.contains(name) {
132                        return true;
133                    } else {
134                        continue;
135                    }
136                }
137
138                Query::Tag(Tag {
139                    sign: Sign::Minus,
140                    name,
141                }) => {
142                    if self.tags.contains(name) {
143                        return false;
144                    } else {
145                        default = true;
146                        continue;
147                    }
148                }
149            }
150        }
151
152        return default;
153    }
154
155    pub fn apply_mutations(&mut self, mutations: &Mutations) -> &Self {
156        for m in mutations {
157            self.apply_mutation(m);
158        }
159
160        self
161    }
162
163    pub fn apply_mutation(&mut self, mutation: &Mutation) -> &Self {
164        match mutation {
165            Mutation::SetTag(Tag {
166                sign: Sign::Plus,
167                name,
168            }) => {
169                self.tags.insert(name.to_string());
170            }
171            Mutation::SetTag(Tag {
172                sign: Sign::Minus,
173                name,
174            }) => {
175                self.tags.remove(name);
176            }
177            Mutation::SetProp(Prop::Description(description)) => {
178                self.description = Some(description.to_string());
179            }
180            Mutation::SetProp(Prop::Done(done)) => {
181                if let Some(recur) = &self.recur {
182                    let dt: Duration = Duration::from(recur);
183
184                    if let Some(due) = self.due {
185                        self.due = Some(due + dt);
186                    }
187                    if let Some(wait) = self.wait {
188                        self.wait = Some(wait + dt);
189                    }
190                } else {
191                    self.done = Some(done.clone());
192                }
193            }
194            Mutation::SetProp(Prop::Due(due)) => {
195                self.due = due.clone();
196            }
197            Mutation::SetProp(Prop::Snooze(snooze)) => {
198                self.snooze = snooze.clone();
199            }
200            Mutation::SetProp(Prop::Wait(wait)) => {
201                self.wait = wait.clone();
202            }
203            Mutation::SetProp(Prop::Recur(recur)) => self.recur = recur.clone(),
204        }
205
206        self.updated_at = Utc::now();
207
208        self
209    }
210
211    pub fn get_score(&self) -> u64 {
212        //this is perfectly fine for now, but I'd like to aim to replace this with
213        //something user-configureable, possibly https://github.com/jonathandturner/rhai
214
215        let mut score: u64 = 0;
216
217        if let Some(_) = self.done {
218            return 0;
219        }
220
221        if let Some(wait) = self.wait {
222            if wait > Utc::now() {
223                return 0;
224            }
225        }
226
227        if let Some(snooze) = self.snooze {
228            if snooze > Utc::now() {
229                return 0;
230            }
231        }
232
233        score = score + (Utc::now() - self.updated_at).num_seconds() as u64;
234
235        if let Some(due) = self.due {
236            score = score
237                + if self.tags.contains("timely") && due < Utc::now() {
238                    2 * (2147483647 - (due.timestamp() as u64))
239                } else {
240                    (2147483647 - (due.timestamp() as u64))
241                };
242        }
243
244        score = score
245            + if self.tags.contains("urgent") {
246                score
247            } else {
248                0
249            };
250
251        score
252    }
253
254    pub fn is_overdue(&self) -> bool {
255        if let Some(due) = self.due {
256            return due < Utc::now();
257        } else {
258            return false;
259        }
260    }
261
262    pub fn is_soon_due(&self) -> bool {
263        if let Some(due) = self.due {
264            return due < (Utc::now() + Duration::days(3));
265        } else {
266            return false;
267        }
268    }
269}
270
271impl PartialOrd for Task {
272    fn partial_cmp(&self, other: &Task) -> Option<Ordering> {
273        Some(self.get_score().cmp(&other.get_score()).reverse())
274    }
275}
276
277impl Ord for Task {
278    fn cmp(&self, other: &Task) -> Ordering {
279        self.get_score().cmp(&other.get_score()).reverse()
280    }
281}
282
283impl Eq for Task {}
284impl PartialEq for Task {
285    fn eq(&self, other: &Task) -> bool {
286        self.get_score() == other.get_score()
287    }
288}