gnostr_asyncgit/
status.rs1use std::{
2 hash::Hash,
3 sync::{
4 atomic::{AtomicUsize, Ordering},
5 Arc, Mutex,
6 },
7 time::{SystemTime, UNIX_EPOCH},
8};
9
10use crossbeam_channel::Sender;
11
12use crate::{
13 error::Result,
14 hash,
15 sync::{self, status::StatusType, RepoPath, ShowUntrackedFilesConfig},
16 AsyncGitNotification, StatusItem,
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(status_type: StatusType, config: Option<ShowUntrackedFilesConfig>) -> Self {
42 Self {
43 tick: current_tick(),
44 status_type,
45 config,
46 }
47 }
48}
49
50struct Request<R, A>(R, Option<A>);
51
52pub struct AsyncStatus {
54 current: Arc<Mutex<Request<u64, Status>>>,
55 last: Arc<Mutex<Status>>,
56 sender: Sender<AsyncGitNotification>,
57 pending: Arc<AtomicUsize>,
58 repo: RepoPath,
59}
60
61impl AsyncStatus {
62 pub fn new(repo: RepoPath, sender: Sender<AsyncGitNotification>) -> Self {
64 Self {
65 repo,
66 current: Arc::new(Mutex::new(Request(0, None))),
67 last: Arc::new(Mutex::new(Status::default())),
68 sender,
69 pending: Arc::new(AtomicUsize::new(0)),
70 }
71 }
72
73 pub fn last(&mut self) -> Result<Status> {
75 let last = self.last.lock()?;
76 Ok(last.clone())
77 }
78
79 pub fn is_pending(&self) -> bool {
81 self.pending.load(Ordering::Relaxed) > 0
82 }
83
84 pub fn fetch(&mut self, params: &StatusParams) -> Result<Option<Status>> {
86 if self.is_pending() {
87 log::trace!("request blocked, still pending");
88 return Ok(None);
89 }
90
91 let hash_request = hash(¶ms);
92
93 log::trace!(
94 "request: [hash: {}] (type: {:?})",
95 hash_request,
96 params.status_type,
97 );
98
99 {
100 let mut current = self.current.lock()?;
101
102 if current.0 == hash_request {
103 return Ok(current.1.clone());
104 }
105
106 current.0 = hash_request;
107 current.1 = None;
108 }
109
110 let arc_current = Arc::clone(&self.current);
111 let arc_last = Arc::clone(&self.last);
112 let sender = self.sender.clone();
113 let arc_pending = Arc::clone(&self.pending);
114 let status_type = params.status_type;
115 let config = params.config;
116 let repo = self.repo.clone();
117
118 self.pending.fetch_add(1, Ordering::Relaxed);
119
120 rayon_core::spawn(move || {
121 if let Err(e) = Self::fetch_helper(
122 &repo,
123 status_type,
124 config,
125 hash_request,
126 &arc_current,
127 &arc_last,
128 ) {
129 log::error!("fetch_helper: {}", e);
130 }
131
132 arc_pending.fetch_sub(1, Ordering::Relaxed);
133
134 sender
135 .send(AsyncGitNotification::Status)
136 .expect("error sending status");
137 });
138
139 Ok(None)
140 }
141
142 fn fetch_helper(
143 repo: &RepoPath,
144 status_type: StatusType,
145 config: Option<ShowUntrackedFilesConfig>,
146 hash_request: u64,
147 arc_current: &Arc<Mutex<Request<u64, Status>>>,
148 arc_last: &Arc<Mutex<Status>>,
149 ) -> Result<()> {
150 let res = Self::get_status(repo, status_type, config)?;
151 log::trace!("status fetched: {} (type: {:?})", hash_request, status_type,);
152
153 {
154 let mut current = arc_current.lock()?;
155 if current.0 == hash_request {
156 current.1 = Some(res.clone());
157 }
158 }
159
160 {
161 let mut last = arc_last.lock()?;
162 *last = res;
163 }
164
165 Ok(())
166 }
167
168 fn get_status(
169 repo: &RepoPath,
170 status_type: StatusType,
171 config: Option<ShowUntrackedFilesConfig>,
172 ) -> Result<Status> {
173 Ok(Status {
174 items: sync::status::get_status(repo, status_type, config)?,
175 })
176 }
177}