From 5304076ed66d67f77a47f02c7ee6873cce869be1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E7=9A=93?= Date: Sun, 14 Jul 2024 01:02:17 -0400 Subject: [PATCH 1/7] Fix path cache `open_file` file creation code --- crates/filesystem/src/path_cache.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/crates/filesystem/src/path_cache.rs b/crates/filesystem/src/path_cache.rs index 2c6df6a2..5e662ed8 100644 --- a/crates/filesystem/src/path_cache.rs +++ b/crates/filesystem/src/path_cache.rs @@ -303,7 +303,16 @@ where cache.regen(&self.fs, path).wrap_err_with(|| c.clone())?; if flags.contains(OpenFlags::Create) && cache.desensitize(path).is_none() { - let path = cache + // If `OpenFlags::Create` was passed via `flags` and the given path doesn't exist in + // the cache, then it must be the case that the path doesn't exist because we just + // called `.regen` to attempt to insert the path into the cache a few lines ago. So we + // need to create the file. + + // Use the path cache to get the desensitized version of the path to the parent + // directory of the new file we need to create. If the parent directory doesn't exist + // in the cache either then the parent directory doesn't exist yet, so error out with a + // "does not exist" error because we don't recursively create parent directories. + let parent_path = cache .desensitize( path.parent() .ok_or(Error::NotExist) @@ -311,11 +320,18 @@ where ) .ok_or(Error::NotExist) .wrap_err_with(|| c.clone())?; + + // Create the file in the parent directory with the filename at the end of the original + // path. + let path = parent_path.join(path.file_name().unwrap()); let file = self .fs .open_file(&path, flags) .wrap_err_with(|| c.clone())?; + + // Add the new file to the path cache. cache.regen(&self.fs, &path).wrap_err_with(|| c.clone())?; + Ok(file) } else { self.fs From f011b25d65ff21711a23cb45d6745e9f5a706d82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E7=9A=93?= Date: Sun, 14 Jul 2024 12:32:01 -0400 Subject: [PATCH 2/7] Add `.desensitize` method to path cache --- crates/filesystem/src/path_cache.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/crates/filesystem/src/path_cache.rs b/crates/filesystem/src/path_cache.rs index 5e662ed8..a5708874 100644 --- a/crates/filesystem/src/path_cache.rs +++ b/crates/filesystem/src/path_cache.rs @@ -267,6 +267,22 @@ where }, ); } + + /// Finds the correct letter casing and file extension for the given RPG Maker-style + /// case-insensitive path. Returns `Err(NotExist)` if no matching path is found. + /// + /// If the path you pass to this function has a file extension, this function will prioritize + /// files with that file extension (case-insensitive) and then fall back to ignoring the file + /// extension. + /// + /// If the path you pass to this function has no file extension, this function will prioritize + /// files with no file extension and then fall back to a wildcard file extension search. + pub fn desensitize(&self, path: impl AsRef) -> Result { + let path = path.as_ref(); + let mut cache = self.cache.write(); + cache.regen(&self.fs, path)?; + cache.desensitize(path).ok_or(Error::NotExist.into()) + } } pub fn to_lowercase(p: impl AsRef) -> camino::Utf8PathBuf { From 553f072f9eb85685023c67e3a5479429e24f5022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E7=9A=93?= Date: Sun, 14 Jul 2024 22:20:44 -0400 Subject: [PATCH 3/7] Fix path cache `.remove_file` and `.remove_dir` implementations --- crates/filesystem/src/path_cache.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/crates/filesystem/src/path_cache.rs b/crates/filesystem/src/path_cache.rs index a5708874..5fb661a4 100644 --- a/crates/filesystem/src/path_cache.rs +++ b/crates/filesystem/src/path_cache.rs @@ -465,8 +465,13 @@ where self.fs.remove_dir(&path).wrap_err_with(|| c.clone())?; + // Remove the directory and its contents from `cache.trie` and `cache.cactus` { let cache = &mut *cache; + + let mut path = to_lowercase(path); + path.set_extension(""); + for extension_trie in cache.trie.iter_prefix(&path).unwrap().map(|(_, t)| t) { for index in extension_trie.values().copied() { cache.cactus.remove(index); @@ -490,14 +495,19 @@ where self.fs.remove_file(&path).wrap_err_with(|| c.clone())?; + // Remove the file from `cache.trie` and `cache.cactus` { let cache = &mut *cache; - for extension_trie in cache.trie.iter_prefix(&path).unwrap().map(|(_, t)| t) { - for index in extension_trie.values().copied() { - cache.cactus.remove(index); - } + + let mut path = to_lowercase(path); + path.set_extension(""); + let path = with_trie_suffix(&path); + + let extension_trie = cache.trie.get_file(&path).unwrap(); + for index in extension_trie.values().copied() { + cache.cactus.remove(index); } - cache.trie.remove_dir(&path); + cache.trie.remove_file(&path); } Ok(()) From 826a5c77f4c7cb62c79915072bcf8c47b17e04ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E7=9A=93?= Date: Sun, 14 Jul 2024 22:31:57 -0400 Subject: [PATCH 4/7] Fix another problem with `.remove_dir` --- crates/filesystem/src/path_cache.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/filesystem/src/path_cache.rs b/crates/filesystem/src/path_cache.rs index 5fb661a4..7a12e49d 100644 --- a/crates/filesystem/src/path_cache.rs +++ b/crates/filesystem/src/path_cache.rs @@ -478,6 +478,13 @@ where } } cache.trie.remove_dir(&path); + + let path = with_trie_suffix(&path); + let extension_trie = cache.trie.get_file(&path).unwrap(); + for index in extension_trie.values().copied() { + cache.cactus.remove(index); + } + cache.trie.remove_file(&path); } Ok(()) From f01075f37c5fbf451f049a0acbf346d3ac99ca77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E7=9A=93?= Date: Sun, 14 Jul 2024 22:40:44 -0400 Subject: [PATCH 5/7] Remove the unwraps from `.remove_file` and `.remove_dir` --- crates/filesystem/src/path_cache.rs | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/crates/filesystem/src/path_cache.rs b/crates/filesystem/src/path_cache.rs index 7a12e49d..e6cc204b 100644 --- a/crates/filesystem/src/path_cache.rs +++ b/crates/filesystem/src/path_cache.rs @@ -472,19 +472,22 @@ where let mut path = to_lowercase(path); path.set_extension(""); - for extension_trie in cache.trie.iter_prefix(&path).unwrap().map(|(_, t)| t) { - for index in extension_trie.values().copied() { - cache.cactus.remove(index); + if let Some(iter) = cache.trie.iter_prefix(&path) { + for extension_trie in iter.map(|(_, t)| t) { + for index in extension_trie.values().copied() { + cache.cactus.remove(index); + } } + cache.trie.remove_dir(&path); } - cache.trie.remove_dir(&path); let path = with_trie_suffix(&path); - let extension_trie = cache.trie.get_file(&path).unwrap(); - for index in extension_trie.values().copied() { - cache.cactus.remove(index); + if let Some(extension_trie) = cache.trie.get_file(&path) { + for index in extension_trie.values().copied() { + cache.cactus.remove(index); + } + cache.trie.remove_file(&path); } - cache.trie.remove_file(&path); } Ok(()) @@ -510,11 +513,12 @@ where path.set_extension(""); let path = with_trie_suffix(&path); - let extension_trie = cache.trie.get_file(&path).unwrap(); - for index in extension_trie.values().copied() { - cache.cactus.remove(index); + if let Some(extension_trie) = cache.trie.get_file(&path) { + for index in extension_trie.values().copied() { + cache.cactus.remove(index); + } + cache.trie.remove_file(&path); } - cache.trie.remove_file(&path); } Ok(()) From 1574fcda54c4ed4b7e671a7884433ce20b31f9f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E7=9A=93?= Date: Sun, 14 Jul 2024 23:04:15 -0400 Subject: [PATCH 6/7] Change path cache desensitize algorithm --- crates/filesystem/src/path_cache.rs | 45 ++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/crates/filesystem/src/path_cache.rs b/crates/filesystem/src/path_cache.rs index e6cc204b..0e9fafeb 100644 --- a/crates/filesystem/src/path_cache.rs +++ b/crates/filesystem/src/path_cache.rs @@ -188,16 +188,32 @@ impl Cache { } let mut path = to_lowercase(path); let extension = path.extension().unwrap_or_default().to_string(); + let path_clone = path.clone(); path.set_extension(""); - self.trie - .get_file(with_trie_suffix(&path)) - .map(|extension_trie| { - self.get_path_from_cactus_index( - *extension_trie + + // Try to search for the given path exactly (case-insensitive) + let maybe_exact_match = + self.trie + .get_file(with_trie_suffix(&path)) + .and_then(|extension_trie| { + extension_trie .get_str(&extension) - .unwrap_or(extension_trie.values().next().unwrap()), - ) - }) + .map(|&index| self.get_path_from_cactus_index(index)) + }); + + maybe_exact_match.or_else(|| { + // If we didn't find anything the first time, try again using a '.*' glob pattern + // appended to the end (still case-insensitive) + let path = path_clone; + self.trie + .get_file(with_trie_suffix(path)) + .and_then(|extension_trie| { + extension_trie + .values() + .next() + .map(|&index| self.get_path_from_cactus_index(index)) + }) + }) } } @@ -269,14 +285,15 @@ where } /// Finds the correct letter casing and file extension for the given RPG Maker-style - /// case-insensitive path. Returns `Err(NotExist)` if no matching path is found. + /// case-insensitive path. + /// + /// First this function will perform a case-insensitive search for the given path. /// - /// If the path you pass to this function has a file extension, this function will prioritize - /// files with that file extension (case-insensitive) and then fall back to ignoring the file - /// extension. + /// If no file or folder at that path is found, this function searches a second time with a + /// '.*' glob pattern appended to the end of the path (e.g. if you're looking for "my/path", + /// this will also find stuff like "my/path.txt" or "my/path.json"). /// - /// If the path you pass to this function has no file extension, this function will prioritize - /// files with no file extension and then fall back to a wildcard file extension search. + /// If no match was found either time, returns `Err(NotExist)`. pub fn desensitize(&self, path: impl AsRef) -> Result { let path = path.as_ref(); let mut cache = self.cache.write(); From 4ce7197b21c54605053dfe478714630d40bfc714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E7=9A=93?= Date: Mon, 15 Jul 2024 14:34:05 -0400 Subject: [PATCH 7/7] Update `.regen` and `.remove_file` for new path cache algorithm --- crates/filesystem/src/path_cache.rs | 76 +++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 15 deletions(-) diff --git a/crates/filesystem/src/path_cache.rs b/crates/filesystem/src/path_cache.rs index 0e9fafeb..6060847a 100644 --- a/crates/filesystem/src/path_cache.rs +++ b/crates/filesystem/src/path_cache.rs @@ -79,13 +79,48 @@ impl Cache { fs: &impl FileSystemTrait, path: impl AsRef, ) -> crate::Result<()> { - let mut path = to_lowercase(path); + let path = path.as_ref(); + + // We don't need to do anything if there already is a path in the trie matching the given + // path + if self.desensitize(path).is_some() { + return Ok(()); + } + let extension = path.extension().unwrap_or_default().to_string(); + let mut path = to_lowercase(path); path.set_extension(""); - if self.trie.contains_dir(&path) { - return Ok(()); + + // If there is a matching path with a different file extension than this one, we may need + // to add this new path to the extension trie + if self.trie.contains_file(with_trie_suffix(&path)) { + if let Some(mut desensitized_path) = self + .trie + .get_file(with_trie_suffix(&path)) + .unwrap() + .values() + .next() + .map(|&cactus_index| self.get_path_from_cactus_index(cactus_index)) + { + desensitized_path.set_extension(&extension); + if fs.exists(&desensitized_path)? { + let extension_trie = self.trie.get_file_mut(with_trie_suffix(&path)).unwrap(); + let sibling = self + .cactus + .get(*extension_trie.values().next().unwrap()) + .unwrap(); + let cactus_index = self.cactus.insert(CactusNode { + value: desensitized_path.file_name().unwrap_or_default().into(), + ..*sibling + }); + extension_trie.insert_str(&extension, cactus_index); + return Ok(()); + } + } } + let extension = extension.to_lowercase(); + let prefix = self.trie.get_dir_prefix(&path); let mut cactus_index = (!prefix.as_str().is_empty()).then(|| { let extension_trie = self.trie.get_file(with_trie_suffix(prefix)).unwrap(); @@ -497,14 +532,6 @@ where } cache.trie.remove_dir(&path); } - - let path = with_trie_suffix(&path); - if let Some(extension_trie) = cache.trie.get_file(&path) { - for index in extension_trie.values().copied() { - cache.cactus.remove(index); - } - cache.trie.remove_file(&path); - } } Ok(()) @@ -527,14 +554,33 @@ where let cache = &mut *cache; let mut path = to_lowercase(path); + let extension = path.extension().unwrap_or_default().to_string(); + let path_clone = path.clone(); path.set_extension(""); - let path = with_trie_suffix(&path); - if let Some(extension_trie) = cache.trie.get_file(&path) { - for index in extension_trie.values().copied() { + // Remove by exact match + if let Some(extension_trie) = cache.trie.get_file_mut(with_trie_suffix(&path)) { + if let Some(&index) = extension_trie.get_str(&extension) { cache.cactus.remove(index); + extension_trie.remove_str(&extension); + if extension_trie.is_empty() { + cache.trie.remove_dir(&path); + } + return Ok(()); + } + } + + // Remove with added '.*' glob pattern + let path = path_clone; + if let Some(extension_trie) = cache.trie.get_file_mut(with_trie_suffix(&path)) { + if let Some((key, &index)) = extension_trie.iter().next() { + let key = key.to_owned(); + cache.cactus.remove(index); + extension_trie.remove(&key); + } + if extension_trie.is_empty() { + cache.trie.remove_dir(&path); } - cache.trie.remove_file(&path); } }