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