1use std::{
2 sync::{Arc, Mutex},
3 time::{Duration, Instant},
4};
5
6use crossbeam_channel::Sender;
7use sync::Tags;
8
9use crate::{
10 AsyncGitNotification,
11 asyncjob::{AsyncJob, AsyncSingleJob, RunParams},
12 error::Result,
13 hash,
14 sync::{self, RepoPath},
15};
16
17#[derive(Default, Clone)]
19pub struct TagsResult {
20 hash: u64,
21 tags: Tags,
22}
23
24pub struct AsyncTags {
26 last: Option<(Instant, TagsResult)>,
27 sender: Sender<AsyncGitNotification>,
28 job: AsyncSingleJob<AsyncTagsJob>,
29 repo: RepoPath,
30}
31
32impl AsyncTags {
33 pub fn new(
35 repo: RepoPath,
36 sender: &Sender<AsyncGitNotification>,
37 ) -> Self {
38 Self {
39 repo,
40 last: None,
41 sender: sender.clone(),
42 job: AsyncSingleJob::new(sender.clone()),
43 }
44 }
45
46 pub fn last(&self) -> Result<Option<Tags>> {
48 Ok(self.last.as_ref().map(|result| result.1.tags.clone()))
49 }
50
51 pub fn is_pending(&self) -> bool {
53 self.job.is_pending()
54 }
55
56 fn is_outdated(&self, dur: Duration) -> bool {
58 self.last
59 .as_ref()
60 .map_or(true, |(last_time, _)| last_time.elapsed() > dur)
61 }
62
63 pub fn request(
65 &mut self,
66 dur: Duration,
67 force: bool,
68 ) -> Result<()> {
69 log::trace!("request");
70
71 if !force && self.job.is_pending() {
72 return Ok(());
73 }
74
75 let outdated = self.is_outdated(dur);
76
77 if !force && !outdated {
78 return Ok(());
79 }
80
81 let repo = self.repo.clone();
82
83 if outdated {
84 self.job.spawn(AsyncTagsJob::new(
85 self.last
86 .as_ref()
87 .map_or(0, |(_, result)| result.hash),
88 repo,
89 ));
90
91 if let Some(job) = self.job.take_last() {
92 if let Some(Ok(result)) = job.result() {
93 self.last = Some(result);
94 }
95 }
96 } else {
97 self.sender
98 .send(AsyncGitNotification::FinishUnchanged)?;
99 }
100
101 Ok(())
102 }
103}
104
105enum JobState {
106 Request(u64, RepoPath),
107 Response(Result<(Instant, TagsResult)>),
108}
109
110#[derive(Clone, Default)]
112pub struct AsyncTagsJob {
113 state: Arc<Mutex<Option<JobState>>>,
114}
115
116impl AsyncTagsJob {
118 pub fn new(last_hash: u64, repo: RepoPath) -> Self {
120 Self {
121 state: Arc::new(Mutex::new(Some(JobState::Request(
122 last_hash, repo,
123 )))),
124 }
125 }
126
127 pub fn result(&self) -> Option<Result<(Instant, TagsResult)>> {
129 if let Ok(mut state) = self.state.lock() {
130 if let Some(state) = state.take() {
131 return match state {
132 JobState::Request(_, _) => None,
133 JobState::Response(result) => Some(result),
134 };
135 }
136 }
137
138 None
139 }
140}
141
142impl AsyncJob for AsyncTagsJob {
143 type Notification = AsyncGitNotification;
144 type Progress = ();
145
146 fn run(
147 &mut self,
148 _params: RunParams<Self::Notification, Self::Progress>,
149 ) -> Result<Self::Notification> {
150 let mut notification = AsyncGitNotification::FinishUnchanged;
151 if let Ok(mut state) = self.state.lock() {
152 *state = state.take().map(|state| match state {
153 JobState::Request(last_hash, repo) => {
154 let tags = sync::get_tags(&repo);
155
156 JobState::Response(tags.map(|tags| {
157 let hash = hash(&tags);
158 if last_hash != hash {
159 notification = AsyncGitNotification::Tags;
160 }
161
162 (Instant::now(), TagsResult { hash, tags })
163 }))
164 }
165 JobState::Response(result) => {
166 JobState::Response(result)
167 }
168 });
169 }
170
171 Ok(notification)
172 }
173}