1use std::borrow::Cow;
64use std::path::Path;
65use std::path::PathBuf;
66use std::sync::Arc;
67
68use foldhash::HashMap;
69use foldhash::HashMapExt;
70use rayon::iter::IntoParallelIterator;
71use rayon::iter::ParallelIterator;
72use serde::Deserialize;
73use serde::Serialize;
74
75use crate::change::Change;
76use crate::change::ChangeLog;
77use crate::error::DatabaseError;
78use crate::exclusion::Exclusion;
79use crate::file::File;
80use crate::file::FileId;
81use crate::file::FileType;
82use crate::file::line_starts;
83use crate::operation::FilesystemOperation;
84
85mod utils;
86
87pub mod change;
88pub mod error;
89pub mod exclusion;
90pub mod file;
91pub mod loader;
92pub mod watcher;
93
94mod operation;
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct DatabaseConfiguration<'a> {
99 pub workspace: Cow<'a, Path>,
100 pub paths: Vec<Cow<'a, str>>,
103 pub includes: Vec<Cow<'a, str>>,
106 pub excludes: Vec<Exclusion<'a>>,
107 pub extensions: Vec<Cow<'a, str>>,
108}
109
110impl<'a> DatabaseConfiguration<'a> {
111 pub fn new(
112 workspace: &'a Path,
113 paths: Vec<&'a str>,
114 includes: Vec<&'a str>,
115 excludes: Vec<Exclusion<'a>>,
116 extensions: Vec<&'a str>,
117 ) -> Self {
118 let paths = paths.into_iter().map(Cow::Borrowed).collect();
119 let includes = includes.into_iter().map(Cow::Borrowed).collect();
120
121 let excludes = excludes
122 .into_iter()
123 .filter_map(|exclusion| match exclusion {
124 Exclusion::Path(p) => Some(if p.is_absolute() {
125 Exclusion::Path(p)
126 } else {
127 workspace.join(p).canonicalize().ok().map(Cow::Owned).map(Exclusion::Path)?
128 }),
129 Exclusion::Pattern(pat) => Some(Exclusion::Pattern(pat)),
130 })
131 .collect();
132
133 let extensions = extensions.into_iter().map(Cow::Borrowed).collect();
134
135 Self { workspace: Cow::Borrowed(workspace), paths, includes, excludes, extensions }
136 }
137
138 #[must_use]
139 pub fn into_static(self) -> DatabaseConfiguration<'static> {
140 DatabaseConfiguration {
141 workspace: Cow::Owned(self.workspace.into_owned()),
142 paths: self.paths.into_iter().map(|s| Cow::Owned(s.into_owned())).collect(),
143 includes: self.includes.into_iter().map(|s| Cow::Owned(s.into_owned())).collect(),
144 excludes: self
145 .excludes
146 .into_iter()
147 .map(|e| match e {
148 Exclusion::Path(p) => Exclusion::Path(Cow::Owned(p.into_owned())),
149 Exclusion::Pattern(pat) => Exclusion::Pattern(Cow::Owned(pat.into_owned())),
150 })
151 .collect(),
152 extensions: self.extensions.into_iter().map(|s| Cow::Owned(s.into_owned())).collect(),
153 }
154 }
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct Database<'a> {
160 files: HashMap<Cow<'static, str>, Arc<File>>,
161 id_to_name: HashMap<FileId, Cow<'static, str>>,
162 pub(crate) configuration: DatabaseConfiguration<'a>,
163}
164
165#[derive(Debug)]
167pub struct ReadDatabase {
168 files: Vec<Arc<File>>,
169 id_to_index: HashMap<FileId, usize>,
170 name_to_index: HashMap<Cow<'static, str>, usize>,
171 path_to_index: HashMap<PathBuf, usize>,
172}
173
174impl<'a> Database<'a> {
175 #[must_use]
176 pub fn new(configuration: DatabaseConfiguration<'a>) -> Self {
177 Self { files: HashMap::default(), id_to_name: HashMap::default(), configuration }
178 }
179
180 #[must_use]
181 pub fn single(file: File, configuration: DatabaseConfiguration<'a>) -> Self {
182 let mut db = Self::new(configuration);
183 db.add(file);
184 db
185 }
186
187 pub fn add(&mut self, file: File) -> FileId {
188 let name = file.name.clone();
189 let id = file.id;
190
191 if let Some(old_file) = self.files.insert(name.clone(), Arc::new(file)) {
192 self.id_to_name.remove(&old_file.id);
193 }
194
195 self.id_to_name.insert(id, name);
196
197 id
198 }
199
200 pub fn update(&mut self, id: FileId, new_contents: Cow<'static, str>) -> bool {
208 let Some(name) = self.id_to_name.get(&id) else {
209 return false;
210 };
211
212 let Some(arc) = self.files.get_mut(name) else {
213 return false;
214 };
215
216 if let Some(file) = Arc::get_mut(arc) {
217 file.contents = new_contents;
218 file.size = file.contents.len() as u32;
219 file.lines = line_starts(file.contents.as_ref());
220 } else {
221 let old = &**arc;
224 *arc = Arc::new(File::new(old.name.clone(), old.file_type, old.path.clone(), new_contents));
225 }
226
227 true
228 }
229
230 pub fn delete(&mut self, id: FileId) -> bool {
234 if let Some(name) = self.id_to_name.remove(&id) { self.files.remove(&name).is_some() } else { false }
235 }
236
237 pub fn commit(&mut self, change_log: ChangeLog, write_to_disk: bool) -> Result<(), DatabaseError> {
251 let changes = change_log.into_inner()?;
252 let mut fs_operations = if write_to_disk { Vec::new() } else { Vec::with_capacity(0) };
253
254 for change in changes {
255 match change {
256 Change::Add(file) => {
257 if write_to_disk && let Some(path) = &file.path {
258 fs_operations.push(FilesystemOperation::Write(path.clone(), file.contents.clone()));
259 }
260
261 self.add(file);
262 }
263 Change::Update(id, contents) => {
264 if write_to_disk
265 && let Ok(file) = self.get(&id)
266 && let Some(path) = &file.path
267 {
268 fs_operations.push(FilesystemOperation::Write(path.clone(), contents.clone()));
269 }
270
271 self.update(id, contents);
272 }
273 Change::Delete(id) => {
274 if write_to_disk
275 && let Ok(file) = self.get(&id)
276 && let Some(path) = &file.path
277 {
278 fs_operations.push(FilesystemOperation::Delete(path.clone()));
279 }
280
281 self.delete(id);
282 }
283 }
284 }
285
286 if write_to_disk {
287 fs_operations.into_par_iter().try_for_each(|op| -> Result<(), DatabaseError> { op.execute() })?;
288 }
289
290 Ok(())
291 }
292
293 #[must_use]
300 pub fn read_only(&self) -> ReadDatabase {
301 let mut files_vec: Vec<Arc<File>> = self.files.values().cloned().collect();
302 files_vec.sort_unstable_by_key(|f| f.id);
303
304 let mut id_to_index = HashMap::with_capacity(files_vec.len());
305 let mut name_to_index = HashMap::with_capacity(files_vec.len());
306 let mut path_to_index = HashMap::with_capacity(files_vec.len());
307
308 for (index, file) in files_vec.iter().enumerate() {
309 id_to_index.insert(file.id, index);
310 name_to_index.insert(file.name.clone(), index);
311 if let Some(path) = &file.path {
312 path_to_index.insert(path.clone(), index);
313 }
314 }
315
316 ReadDatabase { files: files_vec, id_to_index, name_to_index, path_to_index }
317 }
318}
319
320impl ReadDatabase {
321 #[must_use]
322 pub fn empty() -> Self {
323 Self {
324 files: Vec::with_capacity(0),
325 id_to_index: HashMap::with_capacity(0),
326 name_to_index: HashMap::with_capacity(0),
327 path_to_index: HashMap::with_capacity(0),
328 }
329 }
330
331 #[must_use]
341 pub fn single(file: File) -> Self {
342 let mut id_to_index = HashMap::with_capacity(1);
343 let mut name_to_index = HashMap::with_capacity(1);
344 let mut path_to_index = HashMap::with_capacity(1);
345
346 id_to_index.insert(file.id, 0);
347 name_to_index.insert(file.name.clone(), 0);
348 if let Some(path) = &file.path {
349 path_to_index.insert(path.clone(), 0);
350 }
351
352 Self { files: vec![Arc::new(file)], id_to_index, name_to_index, path_to_index }
353 }
354}
355
356pub trait DatabaseReader {
362 fn get_id(&self, name: &str) -> Option<FileId>;
364
365 fn get(&self, id: &FileId) -> Result<Arc<File>, DatabaseError>;
371
372 fn get_ref(&self, id: &FileId) -> Result<&File, DatabaseError>;
378
379 fn get_by_name(&self, name: &str) -> Result<Arc<File>, DatabaseError>;
385
386 fn get_by_path(&self, path: &Path) -> Result<Arc<File>, DatabaseError>;
392
393 fn files(&self) -> impl Iterator<Item = Arc<File>>;
398
399 fn files_with_type(&self, file_type: FileType) -> impl Iterator<Item = Arc<File>> {
401 self.files().filter(move |file| file.file_type == file_type)
402 }
403
404 fn files_without_type(&self, file_type: FileType) -> impl Iterator<Item = Arc<File>> {
406 self.files().filter(move |file| file.file_type != file_type)
407 }
408
409 fn file_ids(&self) -> impl Iterator<Item = FileId> {
411 self.files().map(|file| file.id)
412 }
413
414 fn file_ids_with_type(&self, file_type: FileType) -> impl Iterator<Item = FileId> {
416 self.files_with_type(file_type).map(|file| file.id)
417 }
418
419 fn file_ids_without_type(&self, file_type: FileType) -> impl Iterator<Item = FileId> {
421 self.files_without_type(file_type).map(|file| file.id)
422 }
423
424 fn len(&self) -> usize;
426
427 fn is_empty(&self) -> bool {
429 self.len() == 0
430 }
431}
432
433impl DatabaseReader for Database<'_> {
434 fn get_id(&self, name: &str) -> Option<FileId> {
435 self.files.get(name).map(|f| f.id)
436 }
437
438 fn get(&self, id: &FileId) -> Result<Arc<File>, DatabaseError> {
439 let name = self.id_to_name.get(id).ok_or(DatabaseError::FileNotFound)?;
440 let file = self.files.get(name).ok_or(DatabaseError::FileNotFound)?;
441
442 Ok(file.clone())
443 }
444
445 fn get_ref(&self, id: &FileId) -> Result<&File, DatabaseError> {
446 let name = self.id_to_name.get(id).ok_or(DatabaseError::FileNotFound)?;
447 self.files.get(name).map(std::convert::AsRef::as_ref).ok_or(DatabaseError::FileNotFound)
448 }
449
450 fn get_by_name(&self, name: &str) -> Result<Arc<File>, DatabaseError> {
451 self.files.get(name).cloned().ok_or(DatabaseError::FileNotFound)
452 }
453
454 fn get_by_path(&self, path: &Path) -> Result<Arc<File>, DatabaseError> {
455 self.files.values().find(|file| file.path.as_deref() == Some(path)).cloned().ok_or(DatabaseError::FileNotFound)
456 }
457
458 fn files(&self) -> impl Iterator<Item = Arc<File>> {
459 self.files.values().cloned()
460 }
461
462 fn len(&self) -> usize {
463 self.files.len()
464 }
465}
466
467impl DatabaseReader for ReadDatabase {
468 fn get_id(&self, name: &str) -> Option<FileId> {
469 self.name_to_index.get(name).and_then(|&i| self.files.get(i)).map(|f| f.id)
470 }
471
472 fn get(&self, id: &FileId) -> Result<Arc<File>, DatabaseError> {
473 let index = self.id_to_index.get(id).ok_or(DatabaseError::FileNotFound)?;
474
475 self.files.get(*index).cloned().ok_or(DatabaseError::FileNotFound)
476 }
477
478 fn get_ref(&self, id: &FileId) -> Result<&File, DatabaseError> {
479 let index = self.id_to_index.get(id).ok_or(DatabaseError::FileNotFound)?;
480
481 self.files.get(*index).map(std::convert::AsRef::as_ref).ok_or(DatabaseError::FileNotFound)
482 }
483
484 fn get_by_name(&self, name: &str) -> Result<Arc<File>, DatabaseError> {
485 self.name_to_index.get(name).and_then(|&i| self.files.get(i)).cloned().ok_or(DatabaseError::FileNotFound)
486 }
487
488 fn get_by_path(&self, path: &Path) -> Result<Arc<File>, DatabaseError> {
489 self.path_to_index.get(path).and_then(|&i| self.files.get(i)).cloned().ok_or(DatabaseError::FileNotFound)
490 }
491
492 fn files(&self) -> impl Iterator<Item = Arc<File>> {
493 self.files.iter().cloned()
494 }
495
496 fn len(&self) -> usize {
497 self.files.len()
498 }
499}