1use crate::{
2 description::{DescriptionData, PkgJSON},
3 entry::EntryStat,
4 tsconfig::TsConfig,
5 RResult,
6};
7use rustc_hash::FxHasher;
8use std::{
9 fmt::Debug,
10 fs,
11 hash::BuildHasherDefault,
12 path::{Path, PathBuf},
13 sync::Arc,
14 time::SystemTime,
15};
16
17use dashmap::DashMap;
18
19use std::time::Duration;
20
21#[derive(Debug, Default)]
22pub struct CachedFS {
23 entries: CachedMap<String>,
25
26 descriptions: CachedMap<DescriptionData>,
28
29 tsconfigs: CachedMap<serde_json::Value>,
31}
32
33pub type CachedMap<T> = DashMap<PathBuf, CachedEntry<T>, BuildHasherDefault<FxHasher>>;
34
35#[derive(Debug)]
36pub struct CachedEntry<T: Sized> {
37 content: Arc<T>,
38 stat: EntryStat,
39}
40
41impl<T> Clone for CachedEntry<T> {
42 fn clone(&self) -> Self {
43 Self {
44 content: Arc::clone(&self.content),
45 stat: self.stat,
46 }
47 }
48}
49
50impl<T: Sized> CachedEntry<T> {
51 fn new(content: T, stat: EntryStat) -> Self {
52 Self {
53 content: content.into(),
54 stat,
55 }
56 }
57
58 fn content(&self) -> Arc<T> {
59 self.content.clone()
60 }
61}
62
63const DEBOUNCE_INTERVAL: Duration = Duration::from_millis(300);
64
65impl CachedFS {
66 pub fn read_file(&self, path: &Path, file_stat: EntryStat) -> RResult<Arc<String>> {
67 if let Some(cached) = self.entries.get(path) {
68 if self.is_modified(file_stat.modified(), cached.stat.modified()) {
69 return Ok(cached.value().content());
70 }
71 }
72 let string = fs::read_to_string(path)?;
73 let entry = CachedEntry::new(string, file_stat);
74 self.entries.insert(path.to_path_buf(), entry.clone());
75 Ok(entry.content())
76 }
77
78 pub fn read_description_file(
79 &self,
80 path: &Path,
81 file_stat: EntryStat,
82 ) -> RResult<Arc<DescriptionData>> {
83 if let Some(cached) = self.descriptions.get(path) {
84 if self.is_modified(file_stat.modified(), cached.stat.modified()) {
85 return Ok(cached.value().content());
86 }
87 }
88 let string = fs::read_to_string(path)?;
89 let json = PkgJSON::parse(&string, path)?;
90 let dir = path.parent().unwrap().to_path_buf();
91 let info = DescriptionData::new(json, dir);
92 let entry = CachedEntry::new(info, file_stat);
93 self.descriptions.insert(path.to_path_buf(), entry.clone());
94 Ok(entry.content())
95 }
96
97 pub fn read_tsconfig(
98 &self,
99 path: &Path,
100 file_stat: EntryStat,
101 ) -> RResult<Arc<serde_json::Value>> {
102 if let Some(cached) = self.tsconfigs.get(path) {
103 if self.is_modified(file_stat.modified(), cached.stat.modified()) {
104 return Ok(cached.value().content());
105 }
106 }
107 let string = fs::read_to_string(path)?;
108 let serde_json = TsConfig::parse(&string, path)?;
109 let entry = CachedEntry::new(serde_json, file_stat);
110 self.tsconfigs.insert(path.to_path_buf(), entry.clone());
111 Ok(entry.content())
112 }
113
114 fn is_modified(&self, before: Option<SystemTime>, after: Option<SystemTime>) -> bool {
115 if let (Some(before), Some(after)) = (before, after) {
116 if before.duration_since(after).expect("after > before") < DEBOUNCE_INTERVAL {
117 return true;
118 }
119 }
120 false
121 }
122}