phabricator/
task.rs

1use crate::Project;
2use crate::{Client, WeakClient};
3use futures::prelude::*;
4use phabricator_api::edge::search::Search as EdgeSearch;
5use phabricator_api::edge::search::Type as EdgeType;
6use phabricator_api::maniphest::search::Projects;
7use phabricator_api::maniphest::search::SearchData;
8use phabricator_api::types::Phid;
9use phabricator_api::RequestError;
10use rust_decimal::prelude::*;
11use std::collections::hash_map::Entry;
12use std::collections::HashMap;
13use std::sync::Arc;
14use std::sync::Mutex;
15
16#[derive(Clone, Debug)]
17pub struct Task {
18    id: u32,
19    phid: Phid,
20    client: WeakClient,
21    inner: Arc<Mutex<Inner>>,
22}
23
24#[derive(Clone, Debug, Default)]
25struct Inner {
26    title: String,
27    description: String,
28    status: String,
29    projects: Option<Vec<Project>>,
30    parents: Option<Vec<Task>>,
31    subtasks: Option<Vec<Task>>,
32    points: Option<Decimal>,
33}
34
35impl Task {
36    fn map_projects(projects: Projects, cache: &HashMap<Phid, Project>) -> Vec<Project> {
37        projects
38            .projects
39            .iter()
40            .map(|phid| cache[phid].clone())
41            .collect()
42    }
43
44    fn from_searchdata(data: SearchData, client: &Client, cache: &HashMap<Phid, Project>) -> Task {
45        let projects = data
46            .attachments
47            .projects
48            .map(|p| Self::map_projects(p, cache));
49
50        let inner = Arc::new(Mutex::new(Inner {
51            title: data.fields.name,
52            description: data.fields.description,
53            points: data.fields.points,
54            status: data.fields.status.value,
55            projects,
56            subtasks: None,
57            parents: None,
58        }));
59
60        Task {
61            id: data.id,
62            client: client.downgrade(),
63            phid: data.phid,
64            inner,
65        }
66    }
67
68    fn update_searchdata(&mut self, data: SearchData, cache: &HashMap<Phid, Project>) {
69        let projects = data
70            .attachments
71            .projects
72            .map(|p| Self::map_projects(p, cache));
73        let mut inner = self.inner.lock().unwrap();
74        inner.title = data.fields.name;
75        inner.description = data.fields.description;
76        inner.points = data.fields.points;
77        inner.status = data.fields.status.value;
78        inner.projects = projects;
79        // TODO update other fields
80    }
81
82    pub(crate) fn update_from_searchdata(data: SearchData, client: &Client) -> Task {
83        client.update_cache(|cache| match cache.tasks.entry(data.id) {
84            Entry::Vacant(v) => v
85                .insert(Self::from_searchdata(data, client, &cache.projects))
86                .clone(),
87            Entry::Occupied(mut o) => {
88                let t = o.get_mut();
89                t.update_searchdata(data, &cache.projects);
90                t.clone()
91            }
92        })
93    }
94
95    pub fn id(&self) -> u32 {
96        self.id
97    }
98
99    pub fn phid(&self) -> &Phid {
100        &self.phid
101    }
102
103    pub fn title(&self) -> String {
104        let l = self.inner.lock().unwrap();
105        l.title.clone()
106    }
107
108    pub fn description(&self) -> String {
109        let l = self.inner.lock().unwrap();
110        l.description.clone()
111    }
112
113    pub fn status(&self) -> String {
114        let l = self.inner.lock().unwrap();
115        l.status.clone()
116    }
117
118    pub fn points(&self) -> Option<Decimal> {
119        let l = self.inner.lock().unwrap();
120        l.points
121    }
122
123    pub(crate) fn projects_resolved(&self) -> bool {
124        let l = self.inner.lock().unwrap();
125        l.projects.is_some()
126    }
127
128    pub async fn projects(&self) -> Result<Vec<Project>, RequestError> {
129        {
130            let l = self.inner.lock().unwrap();
131            if let Some(ref projects) = l.projects {
132                return Ok(projects.clone());
133            }
134        }
135        let client = self.client.upgrade().unwrap();
136        client
137            .tasks(&[self.id])
138            .projects()
139            .query()
140            .try_for_each(|_| future::ready(Ok(())))
141            .await?;
142        let l = self.inner.lock().unwrap();
143        Ok(l.projects.as_ref().unwrap().clone())
144    }
145
146    async fn edges(&self, edge: EdgeType) -> Result<Vec<Task>, RequestError> {
147        // TODO error handle
148        let client = self.client.upgrade().unwrap();
149        // TODO handle pagination
150        let s = EdgeSearch {
151            sources: vec![self.phid.clone()],
152            types: vec![edge],
153            ..Default::default()
154        };
155
156        // TODO error handle
157        let r = client.client().request(&s).await?;
158
159        let mut phids = r.data.iter().map(|data| &data.dest);
160
161        let tasks = client
162            .tasks_by_phid(&mut phids)
163            .query()
164            .try_collect()
165            .await?;
166        Ok(tasks)
167    }
168
169    pub async fn parents(&self) -> Result<Vec<Task>, RequestError> {
170        {
171            let l = self.inner.lock().unwrap();
172            if let Some(ref parents) = l.parents {
173                return Ok(parents.clone());
174            }
175        }
176        let tasks = self.edges(EdgeType::TaskParent).await?;
177        let mut l = self.inner.lock().unwrap();
178        l.parents = Some(tasks);
179        Ok(l.parents.as_ref().unwrap().clone())
180    }
181
182    pub async fn subtasks(&self) -> Result<Vec<Task>, RequestError> {
183        {
184            let l = self.inner.lock().unwrap();
185            if let Some(ref subtasks) = l.subtasks {
186                return Ok(subtasks.clone());
187            }
188        }
189        let tasks = self.edges(EdgeType::TaskSubtask).await?;
190        let mut l = self.inner.lock().unwrap();
191        l.subtasks = Some(tasks);
192        Ok(l.subtasks.as_ref().unwrap().clone())
193    }
194}