1use anyhow::{anyhow, Result};
2use fsevent::EventStream;
3use futures::{Stream, StreamExt};
4use smol::io::{AsyncReadExt, AsyncWriteExt};
5use std::{
6 io,
7 os::unix::fs::MetadataExt,
8 path::{Component, Path, PathBuf},
9 pin::Pin,
10 time::{Duration, SystemTime},
11};
12use text::Rope;
13
14#[async_trait::async_trait]
15pub trait Fs: Send + Sync {
16 async fn create_dir(&self, path: &Path) -> Result<()>;
17 async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()>;
18 async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()>;
19 async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()>;
20 async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()>;
21 async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>>;
22 async fn load(&self, path: &Path) -> Result<String>;
23 async fn save(&self, path: &Path, text: &Rope) -> Result<()>;
24 async fn canonicalize(&self, path: &Path) -> Result<PathBuf>;
25 async fn is_file(&self, path: &Path) -> bool;
26 async fn metadata(&self, path: &Path) -> Result<Option<Metadata>>;
27 async fn read_dir(
28 &self,
29 path: &Path,
30 ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>>;
31 async fn watch(
32 &self,
33 path: &Path,
34 latency: Duration,
35 ) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>>;
36 fn is_fake(&self) -> bool;
37 #[cfg(any(test, feature = "test-support"))]
38 fn as_fake(&self) -> &FakeFs;
39}
40
41#[derive(Copy, Clone, Default)]
42pub struct CreateOptions {
43 pub overwrite: bool,
44 pub ignore_if_exists: bool,
45}
46
47#[derive(Copy, Clone, Default)]
48pub struct RenameOptions {
49 pub overwrite: bool,
50 pub ignore_if_exists: bool,
51}
52
53#[derive(Copy, Clone, Default)]
54pub struct RemoveOptions {
55 pub recursive: bool,
56 pub ignore_if_not_exists: bool,
57}
58
59#[derive(Clone, Debug)]
60pub struct Metadata {
61 pub inode: u64,
62 pub mtime: SystemTime,
63 pub is_symlink: bool,
64 pub is_dir: bool,
65}
66
67pub struct RealFs;
68
69#[async_trait::async_trait]
70impl Fs for RealFs {
71 async fn create_dir(&self, path: &Path) -> Result<()> {
72 Ok(smol::fs::create_dir_all(path).await?)
73 }
74
75 async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()> {
76 let mut open_options = smol::fs::OpenOptions::new();
77 open_options.write(true).create(true);
78 if options.overwrite {
79 open_options.truncate(true);
80 } else if !options.ignore_if_exists {
81 open_options.create_new(true);
82 }
83 open_options.open(path).await?;
84 Ok(())
85 }
86
87 async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()> {
88 if !options.overwrite && smol::fs::metadata(target).await.is_ok() {
89 if options.ignore_if_exists {
90 return Ok(());
91 } else {
92 return Err(anyhow!("{target:?} already exists"));
93 }
94 }
95
96 smol::fs::rename(source, target).await?;
97 Ok(())
98 }
99
100 async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> {
101 let result = if options.recursive {
102 smol::fs::remove_dir_all(path).await
103 } else {
104 smol::fs::remove_dir(path).await
105 };
106 match result {
107 Ok(()) => Ok(()),
108 Err(err) if err.kind() == io::ErrorKind::NotFound && options.ignore_if_not_exists => {
109 Ok(())
110 }
111 Err(err) => Err(err)?,
112 }
113 }
114
115 async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()> {
116 match smol::fs::remove_file(path).await {
117 Ok(()) => Ok(()),
118 Err(err) if err.kind() == io::ErrorKind::NotFound && options.ignore_if_not_exists => {
119 Ok(())
120 }
121 Err(err) => Err(err)?,
122 }
123 }
124
125 async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>> {
126 Ok(Box::new(std::fs::File::open(path)?))
127 }
128
129 async fn load(&self, path: &Path) -> Result<String> {
130 let mut file = smol::fs::File::open(path).await?;
131 let mut text = String::new();
132 file.read_to_string(&mut text).await?;
133 Ok(text)
134 }
135
136 async fn save(&self, path: &Path, text: &Rope) -> Result<()> {
137 let buffer_size = text.summary().bytes.min(10 * 1024);
138 let file = smol::fs::File::create(path).await?;
139 let mut writer = smol::io::BufWriter::with_capacity(buffer_size, file);
140 for chunk in text.chunks() {
141 writer.write_all(chunk.as_bytes()).await?;
142 }
143 writer.flush().await?;
144 Ok(())
145 }
146
147 async fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
148 Ok(smol::fs::canonicalize(path).await?)
149 }
150
151 async fn is_file(&self, path: &Path) -> bool {
152 smol::fs::metadata(path)
153 .await
154 .map_or(false, |metadata| metadata.is_file())
155 }
156
157 async fn metadata(&self, path: &Path) -> Result<Option<Metadata>> {
158 let symlink_metadata = match smol::fs::symlink_metadata(path).await {
159 Ok(metadata) => metadata,
160 Err(err) => {
161 return match (err.kind(), err.raw_os_error()) {
162 (io::ErrorKind::NotFound, _) => Ok(None),
163 (io::ErrorKind::Other, Some(libc::ENOTDIR)) => Ok(None),
164 _ => Err(anyhow::Error::new(err)),
165 }
166 }
167 };
168
169 let is_symlink = symlink_metadata.file_type().is_symlink();
170 let metadata = if is_symlink {
171 smol::fs::metadata(path).await?
172 } else {
173 symlink_metadata
174 };
175 Ok(Some(Metadata {
176 inode: metadata.ino(),
177 mtime: metadata.modified().unwrap(),
178 is_symlink,
179 is_dir: metadata.file_type().is_dir(),
180 }))
181 }
182
183 async fn read_dir(
184 &self,
185 path: &Path,
186 ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>> {
187 let result = smol::fs::read_dir(path).await?.map(|entry| match entry {
188 Ok(entry) => Ok(entry.path()),
189 Err(error) => Err(anyhow!("failed to read dir entry {:?}", error)),
190 });
191 Ok(Box::pin(result))
192 }
193
194 async fn watch(
195 &self,
196 path: &Path,
197 latency: Duration,
198 ) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>> {
199 let (tx, rx) = smol::channel::unbounded();
200 let (stream, handle) = EventStream::new(&[path], latency);
201 std::mem::forget(handle);
202 std::thread::spawn(move || {
203 stream.run(move |events| smol::block_on(tx.send(events)).is_ok());
204 });
205 Box::pin(rx)
206 }
207
208 fn is_fake(&self) -> bool {
209 false
210 }
211 #[cfg(any(test, feature = "test-support"))]
212 fn as_fake(&self) -> &FakeFs {
213 panic!("called `RealFs::as_fake`")
214 }
215}
216
217#[cfg(any(test, feature = "test-support"))]
218#[derive(Clone, Debug)]
219struct FakeFsEntry {
220 metadata: Metadata,
221 content: Option<String>,
222}
223
224#[cfg(any(test, feature = "test-support"))]
225struct FakeFsState {
226 entries: std::collections::BTreeMap<PathBuf, FakeFsEntry>,
227 next_inode: u64,
228}
229
230#[cfg(any(test, feature = "test-support"))]
231impl FakeFsState {
232 fn validate_path(&self, path: &Path) -> Result<()> {
233 if path.is_absolute()
234 && path
235 .parent()
236 .and_then(|path| self.entries.get(path))
237 .map_or(false, |e| e.metadata.is_dir)
238 {
239 Ok(())
240 } else {
241 Err(anyhow!("invalid path {:?}", path))
242 }
243 }
244}
245
246#[cfg(any(test, feature = "test-support"))]
247pub struct FakeFs {
248 // Use an unfair lock to ensure tests are deterministic.
249 state: futures::lock::Mutex<FakeFsState>,
250 executor: std::sync::Weak<gpui::executor::Background>,
251 events: (
252 async_broadcast::Sender<Vec<fsevent::Event>>,
253 async_broadcast::Receiver<Vec<fsevent::Event>>,
254 ),
255}
256
257#[cfg(any(test, feature = "test-support"))]
258impl FakeFs {
259 pub fn new(executor: std::sync::Arc<gpui::executor::Background>) -> std::sync::Arc<Self> {
260 let mut entries = std::collections::BTreeMap::new();
261 entries.insert(
262 Path::new("/").to_path_buf(),
263 FakeFsEntry {
264 metadata: Metadata {
265 inode: 0,
266 mtime: SystemTime::now(),
267 is_dir: true,
268 is_symlink: false,
269 },
270 content: None,
271 },
272 );
273 std::sync::Arc::new(Self {
274 executor: std::sync::Arc::downgrade(&executor),
275 state: futures::lock::Mutex::new(FakeFsState {
276 entries,
277 next_inode: 1,
278 }),
279 events: async_broadcast::broadcast(16),
280 })
281 }
282
283 pub async fn insert_dir(&self, path: impl AsRef<Path>) {
284 let mut state = self.state.lock().await;
285 let path = path.as_ref();
286 state.validate_path(path).unwrap();
287
288 let inode = state.next_inode;
289 state.next_inode += 1;
290 state.entries.insert(
291 path.to_path_buf(),
292 FakeFsEntry {
293 metadata: Metadata {
294 inode,
295 mtime: SystemTime::now(),
296 is_dir: true,
297 is_symlink: false,
298 },
299 content: None,
300 },
301 );
302
303 drop(state);
304 self.emit_event(&[path]).await;
305 }
306
307 pub async fn insert_file(&self, path: impl AsRef<Path>, content: String) {
308 let mut state = self.state.lock().await;
309 let path = path.as_ref();
310 state.validate_path(path).unwrap();
311
312 let inode = state.next_inode;
313 state.next_inode += 1;
314 state.entries.insert(
315 path.to_path_buf(),
316 FakeFsEntry {
317 metadata: Metadata {
318 inode,
319 mtime: SystemTime::now(),
320 is_dir: false,
321 is_symlink: false,
322 },
323 content: Some(content),
324 },
325 );
326
327 drop(state);
328 self.emit_event(&[path]).await;
329 }
330
331 #[must_use]
332 pub fn insert_tree<'a>(
333 &'a self,
334 path: impl 'a + AsRef<Path> + Send,
335 tree: serde_json::Value,
336 ) -> futures::future::BoxFuture<'a, ()> {
337 use futures::FutureExt as _;
338 use serde_json::Value::*;
339
340 async move {
341 let path = path.as_ref();
342
343 match tree {
344 Object(map) => {
345 self.insert_dir(path).await;
346 for (name, contents) in map {
347 let mut path = PathBuf::from(path);
348 path.push(name);
349 self.insert_tree(&path, contents).await;
350 }
351 }
352 Null => {
353 self.insert_dir(&path).await;
354 }
355 String(contents) => {
356 self.insert_file(&path, contents).await;
357 }
358 _ => {
359 panic!("JSON object must contain only objects, strings, or null");
360 }
361 }
362 }
363 .boxed()
364 }
365
366 async fn simulate_random_delay(&self) {
367 self.executor
368 .upgrade()
369 .expect("excecutor has been dropped")
370 .simulate_random_delay()
371 .await;
372 }
373
374 async fn emit_event<I, T>(&self, paths: I)
375 where
376 I: IntoIterator<Item = T>,
377 T: Into<PathBuf>,
378 {
379 let events = paths
380 .into_iter()
381 .map(|path| fsevent::Event {
382 event_id: 0,
383 flags: fsevent::StreamFlags::empty(),
384 path: path.into(),
385 })
386 .collect::<Vec<_>>();
387
388 let _ = self.events.0.broadcast(events).await;
389 }
390}
391
392#[cfg(any(test, feature = "test-support"))]
393#[async_trait::async_trait]
394impl Fs for FakeFs {
395 async fn create_dir(&self, path: &Path) -> Result<()> {
396 self.simulate_random_delay().await;
397 let state = &mut *self.state.lock().await;
398 let path = normalize_path(path);
399 let mut ancestor_path = PathBuf::new();
400 let mut created_dir_paths = Vec::new();
401 for component in path.components() {
402 ancestor_path.push(component);
403 let entry = state
404 .entries
405 .entry(ancestor_path.clone())
406 .or_insert_with(|| {
407 let inode = state.next_inode;
408 state.next_inode += 1;
409 created_dir_paths.push(ancestor_path.clone());
410 FakeFsEntry {
411 metadata: Metadata {
412 inode,
413 mtime: SystemTime::now(),
414 is_dir: true,
415 is_symlink: false,
416 },
417 content: None,
418 }
419 });
420 if !entry.metadata.is_dir {
421 return Err(anyhow!(
422 "cannot create directory because {:?} is a file",
423 ancestor_path
424 ));
425 }
426 }
427 drop(state);
428 self.emit_event(&created_dir_paths).await;
429
430 Ok(())
431 }
432
433 async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()> {
434 self.simulate_random_delay().await;
435 let mut state = self.state.lock().await;
436 let path = normalize_path(path);
437 state.validate_path(&path)?;
438 if let Some(entry) = state.entries.get_mut(&path) {
439 if entry.metadata.is_dir || entry.metadata.is_symlink {
440 return Err(anyhow!(
441 "cannot create file because {:?} is a dir or a symlink",
442 path
443 ));
444 }
445
446 if options.overwrite {
447 entry.metadata.mtime = SystemTime::now();
448 entry.content = Some(Default::default());
449 } else if !options.ignore_if_exists {
450 return Err(anyhow!(
451 "cannot create file because {:?} already exists",
452 &path
453 ));
454 }
455 } else {
456 let inode = state.next_inode;
457 state.next_inode += 1;
458 let entry = FakeFsEntry {
459 metadata: Metadata {
460 inode,
461 mtime: SystemTime::now(),
462 is_dir: false,
463 is_symlink: false,
464 },
465 content: Some(Default::default()),
466 };
467 state.entries.insert(path.to_path_buf(), entry);
468 }
469 drop(state);
470 self.emit_event(&[path]).await;
471
472 Ok(())
473 }
474
475 async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()> {
476 let source = normalize_path(source);
477 let target = normalize_path(target);
478
479 let mut state = self.state.lock().await;
480 state.validate_path(&source)?;
481 state.validate_path(&target)?;
482
483 if !options.overwrite && state.entries.contains_key(&target) {
484 if options.ignore_if_exists {
485 return Ok(());
486 } else {
487 return Err(anyhow!("{target:?} already exists"));
488 }
489 }
490
491 let mut removed = Vec::new();
492 state.entries.retain(|path, entry| {
493 if let Ok(relative_path) = path.strip_prefix(&source) {
494 removed.push((relative_path.to_path_buf(), entry.clone()));
495 false
496 } else {
497 true
498 }
499 });
500
501 for (relative_path, entry) in removed {
502 let new_path = target.join(relative_path);
503 state.entries.insert(new_path, entry);
504 }
505
506 drop(state);
507 self.emit_event(&[source, target]).await;
508 Ok(())
509 }
510
511 async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> {
512 let path = normalize_path(path);
513 let mut state = self.state.lock().await;
514 state.validate_path(&path)?;
515 if let Some(entry) = state.entries.get(&path) {
516 if !entry.metadata.is_dir {
517 return Err(anyhow!("cannot remove {path:?} because it is not a dir"));
518 }
519
520 if !options.recursive {
521 let descendants = state
522 .entries
523 .keys()
524 .filter(|path| path.starts_with(path))
525 .count();
526 if descendants > 1 {
527 return Err(anyhow!("{path:?} is not empty"));
528 }
529 }
530
531 state.entries.retain(|path, _| !path.starts_with(path));
532 drop(state);
533 self.emit_event(&[path]).await;
534 } else if !options.ignore_if_not_exists {
535 return Err(anyhow!("{path:?} does not exist"));
536 }
537
538 Ok(())
539 }
540
541 async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()> {
542 let path = normalize_path(path);
543 let mut state = self.state.lock().await;
544 state.validate_path(&path)?;
545 if let Some(entry) = state.entries.get(&path) {
546 if entry.metadata.is_dir {
547 return Err(anyhow!("cannot remove {path:?} because it is not a file"));
548 }
549
550 state.entries.remove(&path);
551 drop(state);
552 self.emit_event(&[path]).await;
553 } else if !options.ignore_if_not_exists {
554 return Err(anyhow!("{path:?} does not exist"));
555 }
556 Ok(())
557 }
558
559 async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>> {
560 let text = self.load(path).await?;
561 Ok(Box::new(io::Cursor::new(text)))
562 }
563
564 async fn load(&self, path: &Path) -> Result<String> {
565 let path = normalize_path(path);
566 self.simulate_random_delay().await;
567 let state = self.state.lock().await;
568 let text = state
569 .entries
570 .get(&path)
571 .and_then(|e| e.content.as_ref())
572 .ok_or_else(|| anyhow!("file {:?} does not exist", path))?;
573 Ok(text.clone())
574 }
575
576 async fn save(&self, path: &Path, text: &Rope) -> Result<()> {
577 self.simulate_random_delay().await;
578 let mut state = self.state.lock().await;
579 let path = normalize_path(path);
580 state.validate_path(&path)?;
581 if let Some(entry) = state.entries.get_mut(&path) {
582 if entry.metadata.is_dir {
583 Err(anyhow!("cannot overwrite a directory with a file"))
584 } else {
585 entry.content = Some(text.chunks().collect());
586 entry.metadata.mtime = SystemTime::now();
587 drop(state);
588 self.emit_event(&[path]).await;
589 Ok(())
590 }
591 } else {
592 let inode = state.next_inode;
593 state.next_inode += 1;
594 let entry = FakeFsEntry {
595 metadata: Metadata {
596 inode,
597 mtime: SystemTime::now(),
598 is_dir: false,
599 is_symlink: false,
600 },
601 content: Some(text.chunks().collect()),
602 };
603 state.entries.insert(path.to_path_buf(), entry);
604 drop(state);
605 self.emit_event(&[path]).await;
606 Ok(())
607 }
608 }
609
610 async fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
611 self.simulate_random_delay().await;
612 Ok(normalize_path(path))
613 }
614
615 async fn is_file(&self, path: &Path) -> bool {
616 let path = normalize_path(path);
617 self.simulate_random_delay().await;
618 let state = self.state.lock().await;
619 state
620 .entries
621 .get(&path)
622 .map_or(false, |entry| !entry.metadata.is_dir)
623 }
624
625 async fn metadata(&self, path: &Path) -> Result<Option<Metadata>> {
626 self.simulate_random_delay().await;
627 let state = self.state.lock().await;
628 let path = normalize_path(path);
629 Ok(state.entries.get(&path).map(|entry| entry.metadata.clone()))
630 }
631
632 async fn read_dir(
633 &self,
634 abs_path: &Path,
635 ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>> {
636 use futures::{future, stream};
637 self.simulate_random_delay().await;
638 let state = self.state.lock().await;
639 let abs_path = normalize_path(abs_path);
640 Ok(Box::pin(stream::iter(state.entries.clone()).filter_map(
641 move |(child_path, _)| {
642 future::ready(if child_path.parent() == Some(&abs_path) {
643 Some(Ok(child_path))
644 } else {
645 None
646 })
647 },
648 )))
649 }
650
651 async fn watch(
652 &self,
653 path: &Path,
654 _: Duration,
655 ) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>> {
656 self.simulate_random_delay().await;
657 let rx = self.events.1.clone();
658 let path = path.to_path_buf();
659 Box::pin(futures::StreamExt::filter(rx, move |events| {
660 let result = events.iter().any(|event| event.path.starts_with(&path));
661 async move { result }
662 }))
663 }
664
665 fn is_fake(&self) -> bool {
666 true
667 }
668
669 #[cfg(any(test, feature = "test-support"))]
670 fn as_fake(&self) -> &FakeFs {
671 self
672 }
673}
674
675pub fn normalize_path(path: &Path) -> PathBuf {
676 let mut components = path.components().peekable();
677 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
678 components.next();
679 PathBuf::from(c.as_os_str())
680 } else {
681 PathBuf::new()
682 };
683
684 for component in components {
685 match component {
686 Component::Prefix(..) => unreachable!(),
687 Component::RootDir => {
688 ret.push(component.as_os_str());
689 }
690 Component::CurDir => {}
691 Component::ParentDir => {
692 ret.pop();
693 }
694 Component::Normal(c) => {
695 ret.push(c);
696 }
697 }
698 }
699 ret
700}