Explorar el Código

Merge pull request #183 from cfxegbert/empty-directory

feat: Optimize checking for empty directories when a directory has subdirectories
Christina Sørensen hace 2 años
padre
commit
36923dfa6b
Se han modificado 1 ficheros con 56 adiciones y 12 borrados
  1. 56 12
      src/fs/file.rs

+ 56 - 12
src/fs/file.rs

@@ -427,30 +427,74 @@ impl<'dir> File<'dir> {
         }
     }
 
-    // To display icons for empty folders.
-    // The naive approach, as one would think that this info may have been cached.
-    // but as mentioned in the size function comment above, different filesystems
-    // make it difficult to get any info about a dir by it's size, so this may be it.
+    /// Returns the size of the file or indicates no size if it's a directory.
+    ///
+    /// For Windows platforms, the size of directories is not computed and will 
+    /// return `Size::None`.
+    #[cfg(windows)]
+    pub fn size(&self) -> f::Size {
+        if self.is_directory() {
+            f::Size::None
+        }
+        else {
+            f::Size::Some(self.metadata.len())
+        }
+    }
+
+    /// Determines if the directory is empty or not.
+    ///
+    /// For Unix platforms, this function first checks the link count to quickly 
+    /// determine non-empty directories. On most UNIX filesystems the link count
+    /// is two plus the number of subdirectories. If the link count is less than
+    /// or equal to 2, it then checks the directory contents to determine if
+    /// it's truly empty. The naive approach used here checks the contents
+    /// directly, as certain filesystems make it difficult to infer emptiness
+    /// based on directory size alone.
     #[cfg(unix)]
     pub fn is_empty_dir(&self) -> bool {
         if self.is_directory() {
-            match Dir::read_dir(self.path.clone()) {
-                // . & .. are skipped, if the returned iterator has .next(), it's not empty
-                Ok(has_files) => has_files.files(super::DotFilter::Dotfiles, None, false, false).next().is_none(),
-                Err(_) => false,
+            if self.metadata.nlink() > 2 {
+                // Directories will have a link count of two if they do not have any subdirectories.
+                // The '.' entry is a link to itself and the '..' is a link to the parent directory.
+                // A subdirectory will have a link to its parent directory increasing the link count
+                // above two.  This will avoid the expensive read_dir call below when a directory
+                // has subdirectories.
+                false
+            } else {
+                self.is_empty_directory()
             }
         } else {
             false
         }
     }
 
+    /// Determines if the directory is empty or not.
+    ///
+    /// For Windows platforms, this function checks the directory contents directly 
+    /// to determine if it's empty. Since certain filesystems on Windows make it 
+    /// challenging to infer emptiness based on directory size, this approach is used.
     #[cfg(windows)]
-    pub fn size(&self) -> f::Size {
+    pub fn is_empty_dir(&self) -> bool {
         if self.is_directory() {
-            f::Size::None
+            self.is_empty_directory()
+        } else {
+            false
         }
-        else {
-            f::Size::Some(self.metadata.len())
+    }
+
+    /// Checks the contents of the directory to determine if it's empty.
+    ///
+    /// This function avoids counting '.' and '..' when determining if the directory is 
+    /// empty. If any other entries are found, it returns `false`.
+    ///
+    /// The naive approach, as one would think that this info may have been cached.
+    /// but as mentioned in the size function comment above, different filesystems
+    /// make it difficult to get any info about a dir by it's size, so this may be it.
+    fn is_empty_directory(&self) -> bool {
+        match Dir::read_dir(self.path.clone()) {
+            // . & .. are skipped, if the returned iterator has .next(), it's not empty
+            Ok(has_files) => has_files.files(super::DotFilter::Dotfiles, None, false, false).next().is_none(),
+            Err(_) => false,
         }
     }