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