1use std::{
2 cmp::Ordering,
3 sync::{Arc, Mutex},
4};
5
6use anyhow::Result;
7use chrono::{DateTime, Utc};
8use eframe::epaint::ahash::HashMap;
9use keyring::Entry;
10#[cfg(test)]
11use mockall::mock;
12#[cfg(test)]
13use serde::Deserializer;
14use serde::{Deserialize, Serialize};
15
16use crate::sources::TaskSource;
17
18#[derive(Serialize, Deserialize, Clone, Debug)]
19pub struct Task {
20 pub project: String,
21 pub title: String,
22 pub description: String,
23 pub due: Option<DateTime<Utc>>,
24 pub created: Option<DateTime<Utc>>,
25 pub id: Option<String>,
26}
27
28impl Task {
29 pub fn get_id(&self) -> String {
30 self.id
32 .as_ref()
33 .cloned()
34 .unwrap_or_else(|| format!("{}/{}", self.project, self.title))
35 }
36}
37
38#[derive(Serialize, Deserialize, Default)]
39#[serde(default)]
40pub struct TaskManager {
41 tasks: Arc<Mutex<Vec<Task>>>,
42 sources: Vec<(TaskSource, bool)>,
43 #[serde(skip)]
44 error_by_source: Arc<Mutex<HashMap<String, anyhow::Error>>>,
45}
46
47fn compare_optional<T: Ord>(a: &Option<T>, b: &Option<T>) -> Ordering {
48 if let (Some(a), Some(b)) = (a, b) {
49 a.cmp(b)
50 } else if a.is_none() {
51 if b.is_none() {
52 Ordering::Equal
53 } else {
54 Ordering::Greater
55 }
56 } else {
57 Ordering::Less
58 }
59}
60
61#[cfg(test)]
62mock! {
63 pub TaskManager {
64 pub fn tasks(&self) -> Vec<Task>;
65
66 pub fn add_or_replace_source(&mut self, source: TaskSource, secret: &str);
67 pub fn remove_source(&mut self, idx: usize) -> (TaskSource, bool);
68 pub fn refresh<F>(&mut self, finish_callback: F)
69 where
70 F: FnOnce() + Send + 'static;
71 pub fn sources(&self) -> &Vec<(TaskSource, bool)>;
72 pub fn source_ref_mut(&mut self, idx: usize) -> &mut (TaskSource, bool);
73 pub fn get_and_clear_last_err(&self, source: &str) -> Option<anyhow::Error>;
74
75 fn private_deserialize(deserializable: Result<TaskManager, ()>) -> Self;
76 fn private_serialize(&self) -> TaskManager;
77
78 }
79}
80
81#[cfg(test)]
82impl serde::Serialize for MockTaskManager {
83 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
84 self.private_serialize().serialize(s)
85 }
86}
87
88#[cfg(test)]
89impl<'de> Deserialize<'de> for MockTaskManager {
90 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
91 where
92 D: Deserializer<'de>,
93 {
94 let serializable = TaskManager::deserialize(deserializer).map_err(|_| ());
95 Ok(MockTaskManager::private_deserialize(serializable))
96 }
97}
98
99fn save_password(source_name: &str, secret: &str) -> Result<()> {
101 let keyring_entry = Entry::new("task-picker", source_name)?;
102 keyring_entry.set_password(secret)?;
103 Ok(())
104}
105
106impl TaskManager {
107 pub fn refresh<F>(&mut self, finish_callback: F)
109 where
110 F: FnOnce() + Send + 'static,
111 {
112 let sources = self.sources.clone();
113 let error_by_source = self.error_by_source.clone();
114 let tasks = self.tasks.clone();
115
116 rayon::spawn(move || {
117 let mut new_tasks = Vec::default();
120 let mut new_errors = HashMap::default();
121 for (source, active) in &sources {
122 if *active {
123 let secret = source.secret();
124 let tasks_for_source = match source {
125 TaskSource::CalDav(s) => s.query_tasks(secret),
126 TaskSource::GitHub(s) => s.query_tasks(secret),
127 TaskSource::GitLab(s) => s.query_tasks(secret),
128 TaskSource::OpenProject(s) => s.query_tasks(secret),
129 };
130 match tasks_for_source {
131 Ok(tasks_for_source) => new_tasks.extend(tasks_for_source),
132 Err(e) => {
133 new_errors.insert(source.name().to_string(), e);
134 }
135 }
136 }
137 }
138
139 new_tasks.sort_by(|a, b| {
142 let by_due_date = compare_optional(&a.due, &b.due);
143
144 if by_due_date == Ordering::Equal {
145 compare_optional(&a.created, &b.created)
146 } else {
147 by_due_date
148 }
149 });
150
151 {
152 let mut tasks = tasks.lock().expect("Lock poisoning");
153 *tasks = new_tasks;
154 }
155 {
156 let mut error_by_source = error_by_source.lock().expect("Lock poisoning");
157 *error_by_source = new_errors;
158 }
159
160 finish_callback();
161 });
162 }
163
164 pub fn tasks(&self) -> Vec<Task> {
165 let tasks = self.tasks.lock().expect("Lock poisoning");
166 tasks.clone()
167 }
168
169 pub fn sources(&self) -> &Vec<(TaskSource, bool)> {
170 &self.sources
171 }
172
173 pub fn source_ref_mut(&mut self, idx: usize) -> &mut (TaskSource, bool) {
174 &mut self.sources[idx]
175 }
176
177 pub fn add_or_replace_source(&mut self, source: TaskSource, secret: &str) {
180 let source_name = source.name().to_string();
181 let existing = self
182 .sources
183 .binary_search_by(|(probe, _)| probe.name().cmp(source.name()));
184 match existing {
185 Ok(i) => self.sources[i].0 = source,
186 Err(i) => self.sources.insert(i, (source, true)),
187 };
188
189 if let Err(e) = save_password(&source_name, secret) {
190 let mut error_by_source = self.error_by_source.lock().expect("Lock poisoning");
191 error_by_source.insert(source_name, e);
192 }
193 }
194
195 pub fn remove_source(&mut self, idx: usize) -> (TaskSource, bool) {
196 self.sources.remove(idx)
197 }
198
199 pub fn get_and_clear_last_err(&self, source: &str) -> Option<anyhow::Error> {
200 let mut error_by_source = self.error_by_source.lock().expect("Lock poisoning");
201 error_by_source.remove(source)
202 }
203}