asyncgit/
status.rs

1use crate::{
2	error::Result,
3	hash,
4	sync::{
5		self, status::StatusType, RepoPath, ShowUntrackedFilesConfig,
6	},
7	AsyncGitNotification, StatusItem,
8};
9use crossbeam_channel::Sender;
10use std::{
11	hash::Hash,
12	sync::{
13		atomic::{AtomicUsize, Ordering},
14		Arc, Mutex,
15	},
16	time::{SystemTime, UNIX_EPOCH},
17};
18
19fn current_tick() -> u128 {
20	SystemTime::now()
21		.duration_since(UNIX_EPOCH)
22		.expect("time before unix epoch!")
23		.as_millis()
24}
25
26#[derive(Default, Hash, Clone)]
27pub struct Status {
28	pub items: Vec<StatusItem>,
29}
30
31///
32#[derive(Default, Hash, Copy, Clone, PartialEq, Eq)]
33pub struct StatusParams {
34	tick: u128,
35	status_type: StatusType,
36	config: Option<ShowUntrackedFilesConfig>,
37}
38
39impl StatusParams {
40	///
41	pub fn new(
42		status_type: StatusType,
43		config: Option<ShowUntrackedFilesConfig>,
44	) -> Self {
45		Self {
46			tick: current_tick(),
47			status_type,
48			config,
49		}
50	}
51}
52
53struct Request<R, A>(R, Option<A>);
54
55///
56pub struct AsyncStatus {
57	current: Arc<Mutex<Request<u64, Status>>>,
58	last: Arc<Mutex<Status>>,
59	sender: Sender<AsyncGitNotification>,
60	pending: Arc<AtomicUsize>,
61	repo: RepoPath,
62}
63
64impl AsyncStatus {
65	///
66	pub fn new(
67		repo: RepoPath,
68		sender: Sender<AsyncGitNotification>,
69	) -> Self {
70		Self {
71			repo,
72			current: Arc::new(Mutex::new(Request(0, None))),
73			last: Arc::new(Mutex::new(Status::default())),
74			sender,
75			pending: Arc::new(AtomicUsize::new(0)),
76		}
77	}
78
79	///
80	pub fn last(&self) -> Result<Status> {
81		let last = self.last.lock()?;
82		Ok(last.clone())
83	}
84
85	///
86	pub fn is_pending(&self) -> bool {
87		self.pending.load(Ordering::Relaxed) > 0
88	}
89
90	///
91	pub fn fetch(
92		&self,
93		params: &StatusParams,
94	) -> Result<Option<Status>> {
95		if self.is_pending() {
96			log::trace!("request blocked, still pending");
97			return Ok(None);
98		}
99
100		let hash_request = hash(&params);
101
102		log::trace!(
103			"request: [hash: {}] (type: {:?})",
104			hash_request,
105			params.status_type,
106		);
107
108		{
109			let mut current = self.current.lock()?;
110
111			if current.0 == hash_request {
112				return Ok(current.1.clone());
113			}
114
115			current.0 = hash_request;
116			current.1 = None;
117		}
118
119		let arc_current = Arc::clone(&self.current);
120		let arc_last = Arc::clone(&self.last);
121		let sender = self.sender.clone();
122		let arc_pending = Arc::clone(&self.pending);
123		let status_type = params.status_type;
124		let config = params.config;
125		let repo = self.repo.clone();
126
127		self.pending.fetch_add(1, Ordering::Relaxed);
128
129		rayon_core::spawn(move || {
130			if let Err(e) = Self::fetch_helper(
131				&repo,
132				status_type,
133				config,
134				hash_request,
135				&arc_current,
136				&arc_last,
137			) {
138				log::error!("fetch_helper: {}", e);
139			}
140
141			arc_pending.fetch_sub(1, Ordering::Relaxed);
142
143			sender
144				.send(AsyncGitNotification::Status)
145				.expect("error sending status");
146		});
147
148		Ok(None)
149	}
150
151	fn fetch_helper(
152		repo: &RepoPath,
153		status_type: StatusType,
154		config: Option<ShowUntrackedFilesConfig>,
155		hash_request: u64,
156		arc_current: &Arc<Mutex<Request<u64, Status>>>,
157		arc_last: &Arc<Mutex<Status>>,
158	) -> Result<()> {
159		let res = Self::get_status(repo, status_type, config)?;
160		log::trace!(
161			"status fetched: {} (type: {:?})",
162			hash_request,
163			status_type,
164		);
165
166		{
167			let mut current = arc_current.lock()?;
168			if current.0 == hash_request {
169				current.1 = Some(res.clone());
170			}
171		}
172
173		{
174			let mut last = arc_last.lock()?;
175			*last = res;
176		}
177
178		Ok(())
179	}
180
181	fn get_status(
182		repo: &RepoPath,
183		status_type: StatusType,
184		config: Option<ShowUntrackedFilesConfig>,
185	) -> Result<Status> {
186		Ok(Status {
187			items: sync::status::get_status(
188				repo,
189				status_type,
190				config,
191			)?,
192		})
193	}
194}