1use std::cell::RefCell;
4use std::collections::HashMap;
5use std::path::Path;
6use std::path::PathBuf;
7
8use sys_traits::BaseFsCanonicalize;
9use sys_traits::BaseFsRead;
10use sys_traits::BaseFsReadDir;
11use sys_traits::FileType;
12use sys_traits::FsCanonicalize;
13use sys_traits::FsMetadata;
14use sys_traits::FsMetadataValue;
15use sys_traits::FsRead;
16use sys_traits::FsReadDir;
17
18pub trait NodeResolutionCache:
19 std::fmt::Debug + crate::sync::MaybeSend + crate::sync::MaybeSync
20{
21 fn get_canonicalized(
22 &self,
23 path: &Path,
24 ) -> Option<Result<PathBuf, std::io::Error>>;
25 fn set_canonicalized(&self, from: PathBuf, to: &std::io::Result<PathBuf>);
26 fn get_file_type(&self, path: &Path) -> Option<Option<FileType>>;
27 fn set_file_type(&self, path: PathBuf, value: Option<FileType>);
28}
29
30thread_local! {
31 static CANONICALIZED_CACHE: RefCell<HashMap<PathBuf, Option<PathBuf>>> = RefCell::new(HashMap::new());
32 static FILE_TYPE_CACHE: RefCell<HashMap<PathBuf, Option<FileType>>> = RefCell::new(HashMap::new());
33}
34
35#[derive(Debug)]
38pub struct NodeResolutionThreadLocalCache;
39
40impl NodeResolutionThreadLocalCache {
41 pub fn clear() {
42 CANONICALIZED_CACHE.with_borrow_mut(|cache| cache.clear());
43 FILE_TYPE_CACHE.with_borrow_mut(|cache| cache.clear());
44 }
45}
46
47impl NodeResolutionCache for NodeResolutionThreadLocalCache {
48 fn get_canonicalized(
49 &self,
50 path: &Path,
51 ) -> Option<Result<PathBuf, std::io::Error>> {
52 CANONICALIZED_CACHE.with_borrow(|cache| {
53 let item = cache.get(path)?;
54 Some(match item {
55 Some(value) => Ok(value.clone()),
56 None => Err(std::io::Error::new(
57 std::io::ErrorKind::NotFound,
58 "Not found.",
59 )),
60 })
61 })
62 }
63
64 fn set_canonicalized(&self, from: PathBuf, to: &std::io::Result<PathBuf>) {
65 CANONICALIZED_CACHE.with_borrow_mut(|cache| match to {
66 Ok(to) => {
67 cache.insert(from, Some(to.clone()));
68 }
69 Err(err) => {
70 if err.kind() == std::io::ErrorKind::NotFound {
71 cache.insert(from, None);
72 }
73 }
74 });
75 }
76
77 fn get_file_type(&self, path: &Path) -> Option<Option<FileType>> {
78 FILE_TYPE_CACHE.with_borrow(|cache| cache.get(path).cloned())
79 }
80
81 fn set_file_type(&self, path: PathBuf, value: Option<FileType>) {
82 FILE_TYPE_CACHE.with_borrow_mut(|cache| {
83 cache.insert(path, value);
84 })
85 }
86}
87
88#[allow(clippy::disallowed_types)]
89pub type NodeResolutionCacheRc = crate::sync::MaybeArc<dyn NodeResolutionCache>;
90
91#[derive(Debug, Default)]
92pub struct NodeResolutionSys<TSys> {
93 sys: TSys,
94 cache: Option<NodeResolutionCacheRc>,
95}
96
97impl<TSys: Clone> Clone for NodeResolutionSys<TSys> {
98 fn clone(&self) -> Self {
99 Self {
100 sys: self.sys.clone(),
101 cache: self.cache.clone(),
102 }
103 }
104}
105
106impl<TSys: FsMetadata> NodeResolutionSys<TSys> {
107 pub fn new(sys: TSys, store: Option<NodeResolutionCacheRc>) -> Self {
108 Self { sys, cache: store }
109 }
110
111 pub fn is_file(&self, path: &Path) -> bool {
112 match self.get_file_type(path) {
113 Ok(file_type) => file_type.is_file(),
114 Err(_) => false,
115 }
116 }
117
118 pub fn is_dir(&self, path: &Path) -> bool {
119 match self.get_file_type(path) {
120 Ok(file_type) => file_type.is_dir(),
121 Err(_) => false,
122 }
123 }
124
125 pub fn exists_(&self, path: &Path) -> bool {
126 self.get_file_type(path).is_ok()
127 }
128
129 pub fn get_file_type(&self, path: &Path) -> std::io::Result<FileType> {
130 {
131 if let Some(maybe_value) =
132 self.cache.as_ref().and_then(|c| c.get_file_type(path))
133 {
134 return match maybe_value {
135 Some(value) => Ok(value),
136 None => Err(std::io::Error::new(
137 std::io::ErrorKind::NotFound,
138 "Not found.",
139 )),
140 };
141 }
142 }
143 match self.sys.fs_metadata(path) {
144 Ok(metadata) => {
145 if let Some(cache) = &self.cache {
146 cache.set_file_type(path.to_path_buf(), Some(metadata.file_type()));
147 }
148 Ok(metadata.file_type())
149 }
150 Err(err) => {
151 if let Some(cache) = &self.cache {
152 cache.set_file_type(path.to_path_buf(), None);
153 }
154 Err(err)
155 }
156 }
157 }
158}
159
160impl<TSys: FsCanonicalize> BaseFsCanonicalize for NodeResolutionSys<TSys> {
161 fn base_fs_canonicalize(&self, from: &Path) -> std::io::Result<PathBuf> {
162 if let Some(cache) = &self.cache {
163 if let Some(result) = cache.get_canonicalized(from) {
164 return result;
165 }
166 }
167 let result = self.sys.base_fs_canonicalize(from);
168 if let Some(cache) = &self.cache {
169 cache.set_canonicalized(from.to_path_buf(), &result);
170 }
171 result
172 }
173}
174
175impl<TSys: FsReadDir> BaseFsReadDir for NodeResolutionSys<TSys> {
176 type ReadDirEntry = TSys::ReadDirEntry;
177
178 #[inline(always)]
179 fn base_fs_read_dir(
180 &self,
181 path: &Path,
182 ) -> std::io::Result<
183 Box<dyn Iterator<Item = std::io::Result<Self::ReadDirEntry>> + '_>,
184 > {
185 self.sys.base_fs_read_dir(path)
186 }
187}
188
189impl<TSys: FsRead> BaseFsRead for NodeResolutionSys<TSys> {
190 #[inline(always)]
191 fn base_fs_read(
192 &self,
193 path: &Path,
194 ) -> std::io::Result<std::borrow::Cow<'static, [u8]>> {
195 self.sys.base_fs_read(path)
196 }
197}