1use std::path::PathBuf;
2use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
3use std::time::Duration;
4
5use crate::error::Error;
6use crate::file_picker::FilePicker;
7use crate::frecency::FrecencyTracker;
8use crate::git::GitStatusCache;
9use crate::query_tracker::QueryTracker;
10
11#[derive(Clone, Default)]
20pub struct SharedPicker(pub(crate) Arc<parking_lot::RwLock<Option<FilePicker>>>);
21
22impl std::fmt::Debug for SharedPicker {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 f.debug_tuple("SharedPicker").field(&"..").finish()
25 }
26}
27
28impl SharedPicker {
29 pub fn read(&self) -> Result<parking_lot::RwLockReadGuard<'_, Option<FilePicker>>, Error> {
30 Ok(self.0.read())
31 }
32
33 pub fn write(&self) -> Result<parking_lot::RwLockWriteGuard<'_, Option<FilePicker>>, Error> {
34 Ok(self.0.write())
35 }
36
37 pub fn need_complex_rebuild(&self) -> bool {
40 let guard = self.0.read();
41 guard
42 .as_ref()
43 .is_some_and(|p| p.need_enable_mmap_cache() || p.need_enable_content_indexing())
44 }
45
46 pub fn wait_for_scan(&self, timeout: Duration) -> bool {
49 let signal = {
50 let guard = self.0.read();
51 match &*guard {
52 Some(picker) => picker.scan_signal(),
53 None => return true,
54 }
55 };
56
57 let start = std::time::Instant::now();
58 while signal.load(std::sync::atomic::Ordering::Acquire) {
59 if start.elapsed() >= timeout {
60 return false;
61 }
62 std::thread::sleep(Duration::from_millis(10));
63 }
64 true
65 }
66
67 pub fn wait_for_watcher(&self, timeout: Duration) -> bool {
70 let signal = {
71 let guard = self.0.read();
72 match &*guard {
73 Some(picker) => picker.watcher_signal(),
74 None => return true,
75 }
76 };
77
78 let start = std::time::Instant::now();
79 while !signal.load(std::sync::atomic::Ordering::Acquire) {
80 if start.elapsed() >= timeout {
81 return false;
82 }
83 std::thread::sleep(Duration::from_millis(10));
84 }
85 true
86 }
87
88 pub fn refresh_git_status(&self, shared_frecency: &SharedFrecency) -> Result<usize, Error> {
90 use git2::StatusOptions;
91 use tracing::debug;
92
93 let git_status = {
94 let guard = self.read()?;
95 let Some(ref picker) = *guard else {
96 return Err(Error::FilePickerMissing);
97 };
98
99 debug!(
100 "Refreshing git statuses for picker: {:?}",
101 picker.git_root()
102 );
103
104 GitStatusCache::read_git_status(
105 picker.git_root(),
106 StatusOptions::new()
107 .include_untracked(true)
108 .recurse_untracked_dirs(true)
109 .include_unmodified(true)
110 .exclude_submodules(true),
111 )
112 };
113
114 let mut guard = self.write()?;
115 let picker = guard.as_mut().ok_or(Error::FilePickerMissing)?;
116
117 let statuses_count = if let Some(git_status) = git_status {
118 let count = git_status.statuses_len();
119 picker.update_git_statuses(git_status, shared_frecency)?;
120 count
121 } else {
122 0
123 };
124
125 Ok(statuses_count)
126 }
127}
128
129#[derive(Clone)]
131pub struct SharedFrecency {
132 inner: Arc<RwLock<Option<FrecencyTracker>>>,
133 enabled: bool,
134}
135
136impl Default for SharedFrecency {
137 fn default() -> Self {
138 Self {
139 inner: Arc::new(RwLock::new(None)),
140 enabled: true,
141 }
142 }
143}
144
145impl std::fmt::Debug for SharedFrecency {
146 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147 f.debug_tuple("SharedFrecency").field(&"..").finish()
148 }
149}
150
151impl SharedFrecency {
152 pub fn noop() -> Self {
154 Self {
155 inner: Arc::new(RwLock::new(None)),
156 enabled: false,
157 }
158 }
159
160 pub fn read(&self) -> Result<RwLockReadGuard<'_, Option<FrecencyTracker>>, Error> {
161 self.inner.read().map_err(|_| Error::AcquireFrecencyLock)
162 }
163
164 pub fn write(&self) -> Result<RwLockWriteGuard<'_, Option<FrecencyTracker>>, Error> {
165 self.inner.write().map_err(|_| Error::AcquireFrecencyLock)
166 }
167
168 pub fn init(&self, tracker: FrecencyTracker) -> Result<(), Error> {
170 if !self.enabled {
171 return Ok(());
172 }
173 let mut guard = self.write()?;
174 *guard = Some(tracker);
175 Ok(())
176 }
177
178 pub fn spawn_gc(
180 &self,
181 db_path: String,
182 use_unsafe_no_lock: bool,
183 ) -> crate::Result<std::thread::JoinHandle<()>> {
184 FrecencyTracker::spawn_gc(self.clone(), db_path, use_unsafe_no_lock)
185 }
186
187 pub fn destroy(&self) -> Result<Option<PathBuf>, Error> {
196 let mut guard = self.write()?;
197 let Some(tracker) = guard.take() else {
198 return Ok(None);
199 };
200 let db_path = tracker.db_path().to_path_buf();
201 drop(tracker);
203 drop(guard);
204 std::fs::remove_dir_all(&db_path).map_err(|source| Error::RemoveDbDir {
205 path: db_path.clone(),
206 source,
207 })?;
208 Ok(Some(db_path))
209 }
210}
211
212#[derive(Clone)]
214pub struct SharedQueryTracker {
215 inner: Arc<RwLock<Option<QueryTracker>>>,
216 enabled: bool,
217}
218
219impl Default for SharedQueryTracker {
220 fn default() -> Self {
221 Self {
222 inner: Arc::new(RwLock::new(None)),
223 enabled: true,
224 }
225 }
226}
227
228impl std::fmt::Debug for SharedQueryTracker {
229 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
230 f.debug_tuple("SharedQueryTracker").field(&"..").finish()
231 }
232}
233
234impl SharedQueryTracker {
235 pub fn noop() -> Self {
237 Self {
238 inner: Arc::new(RwLock::new(None)),
239 enabled: false,
240 }
241 }
242
243 pub fn read(&self) -> Result<RwLockReadGuard<'_, Option<QueryTracker>>, Error> {
244 self.inner.read().map_err(|_| Error::AcquireFrecencyLock)
245 }
246
247 pub fn write(&self) -> Result<RwLockWriteGuard<'_, Option<QueryTracker>>, Error> {
248 self.inner.write().map_err(|_| Error::AcquireFrecencyLock)
249 }
250
251 pub fn init(&self, tracker: QueryTracker) -> Result<(), Error> {
253 if !self.enabled {
254 return Ok(());
255 }
256 let mut guard = self.write()?;
257 *guard = Some(tracker);
258 Ok(())
259 }
260
261 pub fn destroy(&self) -> Result<Option<PathBuf>, Error> {
270 let mut guard = self.write()?;
271 let Some(tracker) = guard.take() else {
272 return Ok(None);
273 };
274 let db_path = tracker.db_path().to_path_buf();
275 drop(tracker);
276 drop(guard);
277 std::fs::remove_dir_all(&db_path).map_err(|source| Error::RemoveDbDir {
278 path: db_path.clone(),
279 source,
280 })?;
281 Ok(Some(db_path))
282 }
283}