1use std::{
2 hash::Hash,
3 sync::{
4 atomic::{AtomicUsize, Ordering},
5 Arc, Mutex,
6 },
7};
8
9use crossbeam_channel::Sender;
10
11use crate::{
12 error::Result,
13 hash,
14 sync::{self, commit_files::OldNew, diff::DiffOptions, CommitId, RepoPath},
15 AsyncGitNotification, FileDiff,
16};
17
18#[derive(Debug, Hash, Clone, PartialEq, Eq)]
20pub enum DiffType {
21 Commits(OldNew<CommitId>),
23 Commit(CommitId),
25 Stage,
27 WorkDir,
29}
30
31#[derive(Debug, Hash, Clone, PartialEq, Eq)]
33pub struct DiffParams {
34 pub path: String,
36 pub diff_type: DiffType,
38 pub options: DiffOptions,
40}
41
42struct Request<R, A>(R, Option<A>);
43
44#[derive(Default, Clone)]
45struct LastResult<P, R> {
46 params: P,
47 result: R,
48}
49
50pub struct AsyncDiff {
52 current: Arc<Mutex<Request<u64, FileDiff>>>,
53 last: Arc<Mutex<Option<LastResult<DiffParams, FileDiff>>>>,
54 sender: Sender<AsyncGitNotification>,
55 pending: Arc<AtomicUsize>,
56 repo: RepoPath,
57}
58
59impl AsyncDiff {
60 pub fn new(repo: RepoPath, sender: &Sender<AsyncGitNotification>) -> Self {
62 Self {
63 repo,
64 current: Arc::new(Mutex::new(Request(0, None))),
65 last: Arc::new(Mutex::new(None)),
66 sender: sender.clone(),
67 pending: Arc::new(AtomicUsize::new(0)),
68 }
69 }
70
71 pub fn last(&mut self) -> Result<Option<(DiffParams, FileDiff)>> {
73 let last = self.last.lock()?;
74
75 Ok(last.clone().map(|res| (res.params, res.result)))
76 }
77
78 pub fn refresh(&mut self) -> Result<()> {
80 if let Ok(Some(param)) = self.get_last_param() {
81 self.clear_current()?;
82 self.request(param)?;
83 }
84 Ok(())
85 }
86
87 pub fn is_pending(&self) -> bool {
89 self.pending.load(Ordering::Relaxed) > 0
90 }
91
92 pub fn request(&mut self, params: DiffParams) -> Result<Option<FileDiff>> {
94 log::trace!("request {:?}", params);
95
96 let hash = hash(¶ms);
98
99 {
100 let mut current = self.current.lock()?;
101
102 if current.0 == hash {
103 return Ok(current.1.clone());
104 }
105
106 current.0 = hash;
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 repo = self.repo.clone();
115
116 self.pending.fetch_add(1, Ordering::Relaxed);
117
118 rayon_core::spawn(move || {
119 let notify = Self::get_diff_helper(&repo, params, &arc_last, &arc_current, hash);
120
121 let notify = match notify {
122 Err(err) => {
123 log::error!("get_diff_helper error: {}", err);
124 true
125 }
126 Ok(notify) => notify,
127 };
128
129 arc_pending.fetch_sub(1, Ordering::Relaxed);
130
131 sender
132 .send(if notify {
133 AsyncGitNotification::Diff
134 } else {
135 AsyncGitNotification::FinishUnchanged
136 })
137 .expect("error sending diff");
138 });
139
140 Ok(None)
141 }
142
143 fn get_diff_helper(
144 repo_path: &RepoPath,
145 params: DiffParams,
146 arc_last: &Arc<Mutex<Option<LastResult<DiffParams, FileDiff>>>>,
147 arc_current: &Arc<Mutex<Request<u64, FileDiff>>>,
148 hash: u64,
149 ) -> Result<bool> {
150 let res = match params.diff_type {
151 DiffType::Stage => {
152 sync::diff::get_diff(repo_path, ¶ms.path, true, Some(params.options))?
153 }
154 DiffType::WorkDir => {
155 sync::diff::get_diff(repo_path, ¶ms.path, false, Some(params.options))?
156 }
157 DiffType::Commit(id) => sync::diff::get_diff_commit(
158 repo_path,
159 id,
160 params.path.clone(),
161 Some(params.options),
162 )?,
163 DiffType::Commits(ids) => sync::diff::get_diff_commits(
164 repo_path,
165 ids,
166 params.path.clone(),
167 Some(params.options),
168 )?,
169 };
170
171 let mut notify = false;
172 {
173 let mut current = arc_current.lock()?;
174 if current.0 == hash {
175 current.1 = Some(res.clone());
176 notify = true;
177 }
178 }
179
180 {
181 let mut last = arc_last.lock()?;
182 *last = Some(LastResult {
183 result: res,
184 params,
185 });
186 }
187
188 Ok(notify)
189 }
190
191 fn get_last_param(&self) -> Result<Option<DiffParams>> {
192 Ok(self.last.lock()?.clone().map(|e| e.params))
193 }
194
195 fn clear_current(&mut self) -> Result<()> {
196 let mut current = self.current.lock()?;
197 current.0 = 0;
198 current.1 = None;
199 Ok(())
200 }
201}