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#[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 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
55pub 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 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 pub fn last(&self) -> Result<Status> {
81 let last = self.last.lock()?;
82 Ok(last.clone())
83 }
84
85 pub fn is_pending(&self) -> bool {
87 self.pending.load(Ordering::Relaxed) > 0
88 }
89
90 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(¶ms);
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}