gnostr_asyncgit/
status.rs

1use 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///
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(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
52///
53pub 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    ///
63    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    ///
74    pub fn last(&mut self) -> Result<Status> {
75        let last = self.last.lock()?;
76        Ok(last.clone())
77    }
78
79    ///
80    pub fn is_pending(&self) -> bool {
81        self.pending.load(Ordering::Relaxed) > 0
82    }
83
84    ///
85    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(&params);
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}