gnostr_asyncgit/
tags.rs

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///
18#[derive(Default, Clone)]
19pub struct TagsResult {
20	hash: u64,
21	tags: Tags,
22}
23
24///
25pub struct AsyncTags {
26	last: Option<(Instant, TagsResult)>,
27	sender: Sender<AsyncGitNotification>,
28	job: AsyncSingleJob<AsyncTagsJob>,
29	repo: RepoPath,
30}
31
32impl AsyncTags {
33	///
34	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	/// last fetched result
47	pub fn last(&self) -> Result<Option<Tags>> {
48		Ok(self.last.as_ref().map(|result| result.1.tags.clone()))
49	}
50
51	///
52	pub fn is_pending(&self) -> bool {
53		self.job.is_pending()
54	}
55
56	///
57	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	///
64	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///
111#[derive(Clone, Default)]
112pub struct AsyncTagsJob {
113	state: Arc<Mutex<Option<JobState>>>,
114}
115
116///
117impl AsyncTagsJob {
118	///
119	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	///
128	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}