Quellcode durchsuchen

feat: added recursive directory parser

xempt vor 2 Jahren
Ursprung
Commit
0410b1895d
11 geänderte Dateien mit 107 neuen und 11 gelöschten Zeilen
  1. 53 0
      src/fs/file.rs
  2. 14 1
      src/fs/filter.rs
  3. 7 0
      src/fs/mod.rs
  4. 8 1
      src/main.rs
  5. 2 1
      src/options/flags.rs
  6. 1 0
      src/options/help.rs
  7. 2 0
      src/options/view.rs
  8. 3 1
      src/output/details.rs
  9. 5 1
      src/output/grid_details.rs
  10. 1 0
      src/output/mod.rs
  11. 11 6
      src/output/table.rs

+ 53 - 0
src/fs/file.rs

@@ -15,6 +15,7 @@ use log::*;
 use crate::fs::dir::Dir;
 use crate::fs::feature::xattr;
 use crate::fs::feature::xattr::{Attribute, FileAttributes};
+use crate::fs::RECURSIVE_SIZE_HASHMAP;
 use crate::fs::fields as f;
 
 use super::mounts::all_mounts;
@@ -514,6 +515,58 @@ impl<'dir> File<'dir> {
         }
     }
 
+    /// Recursive folder size
+    #[cfg(unix)]
+    pub fn recursive_size(&self) -> f::Size {
+        if self.is_directory() {
+            let dir = match Dir::read_dir(self.path.clone()) {
+                Ok(v) => v,
+                Err(_) => return f::Size::None
+            };
+            let mut recursive_size: u64 = 0;
+            let files = dir.files(super::DotFilter::Dotfiles, None, false, false);
+            for fileresult in files {
+                let file = match fileresult {
+                    Ok(f) => f,
+                    _ => continue
+                };
+                if file.is_file() {
+                    recursive_size += file.metadata.size();
+                } else {
+                    recursive_size += match file.recursive_size() {
+                        f::Size::Some(s) => s,
+                        _ => file.metadata.size()
+                    };
+                }
+            }
+            RECURSIVE_SIZE_HASHMAP.lock().unwrap().insert(self.metadata.ino(), recursive_size);
+            f::Size::Some(recursive_size)
+        } else if self.is_char_device() || self.is_block_device() {
+            let device_id = self.metadata.rdev();
+
+            // MacOS and Linux have different arguments and return types for the
+            // functions major and minor.  On Linux the try_into().unwrap() and
+            // the "as u32" cast are not needed.  We turn off the warning to
+            // allow it to compile cleanly on Linux.
+            #[allow(trivial_numeric_casts)]
+            #[allow(clippy::unnecessary_cast)]
+            #[allow(clippy::useless_conversion)]
+            f::Size::DeviceIDs(f::DeviceIDs {
+                // SAFETY: Calling libc function to decompose the device_id
+                major: unsafe { libc::major(device_id.try_into().unwrap()) } as u32,
+                minor: unsafe { libc::minor(device_id.try_into().unwrap()) } as u32,
+            })
+        } else if self.is_link() && self.deref_links {
+            match self.link_target() {
+                FileTarget::Ok(f) => f::Size::Some(f.metadata.size()),
+                _ => f::Size::None,
+            }
+        } else {
+            f::Size::Some(self.metadata.len())
+        }
+    }
+
+
     /// 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

+ 14 - 1
src/fs/filter.rs

@@ -232,6 +232,7 @@ impl SortField {
     /// because of the `1`.
     pub fn compare_files(self, a: &File<'_>, b: &File<'_>) -> Ordering {
         use self::SortCase::{ABCabc, AaBbCc};
+        use crate::fs::RECURSIVE_SIZE_HASHMAP;
 
         #[rustfmt::skip]
         return match self {
@@ -240,7 +241,19 @@ impl SortField {
             Self::Name(ABCabc)  => natord::compare(&a.name, &b.name),
             Self::Name(AaBbCc)  => natord::compare_ignore_case(&a.name, &b.name),
 
-            Self::Size          => a.metadata.len().cmp(&b.metadata.len()),
+            Self::Size => {
+                let mut _map = RECURSIVE_SIZE_HASHMAP.lock().unwrap();
+                let asize: u64 = match _map.get(&a.metadata.ino()) {
+                    Some(s) => *s,
+                    _ => a.metadata.len()
+                };
+                let bsize = match _map.get(&b.metadata.ino()) {
+                    Some(s) => *s,
+                    _ => b.metadata.len()
+                };
+                asize.cmp(&bsize)
+            }
+
             #[cfg(unix)]
             Self::FileInode     => a.metadata.ino().cmp(&b.metadata.ino()),
             Self::ModifiedDate  => a.modified_time().cmp(&b.modified_time()),

+ 7 - 0
src/fs/mod.rs

@@ -9,3 +9,10 @@ pub mod feature;
 pub mod fields;
 pub mod filter;
 pub mod mounts;
+
+use std::sync::Mutex;
+use std::collections::HashMap;
+use lazy_static::lazy_static;
+lazy_static! {
+    static ref RECURSIVE_SIZE_HASHMAP: Mutex<HashMap<u64, u64>> = Mutex::new(HashMap::new());
+}

+ 8 - 1
src/main.rs

@@ -353,6 +353,8 @@ impl<'args> Exa<'args> {
 
                 let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
                 let git = self.git.as_ref();
+                let total_size = self.options.view.total_size;
+
                 let r = details::Render {
                     dir,
                     files,
@@ -363,6 +365,7 @@ impl<'args> Exa<'args> {
                     filter,
                     git_ignoring,
                     git,
+                    total_size
                 };
                 r.render(&mut self.writer)
             }
@@ -375,6 +378,7 @@ impl<'args> Exa<'args> {
                 let filter = &self.options.filter;
                 let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
                 let git = self.git.as_ref();
+                let total_size = self.options.view.total_size;
 
                 let r = grid_details::Render {
                     dir,
@@ -388,6 +392,7 @@ impl<'args> Exa<'args> {
                     git_ignoring,
                     git,
                     console_width,
+                    total_size
                 };
                 r.render(&mut self.writer)
             }
@@ -397,8 +402,9 @@ impl<'args> Exa<'args> {
                 let filter = &self.options.filter;
                 let recurse = self.options.dir_action.recurse_options();
                 let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
-
                 let git = self.git.as_ref();
+                let total_size = self.options.view.total_size;
+
                 let r = details::Render {
                     dir,
                     files,
@@ -409,6 +415,7 @@ impl<'args> Exa<'args> {
                     filter,
                     git_ignoring,
                     git,
+                    total_size
                 };
                 r.render(&mut self.writer)
             }

+ 2 - 1
src/options/flags.rs

@@ -51,6 +51,7 @@ pub static LINKS:       Arg = Arg { short: Some(b'H'), long: "links",       take
 pub static MODIFIED:    Arg = Arg { short: Some(b'm'), long: "modified",    takes_value: TakesValue::Forbidden };
 pub static CHANGED:     Arg = Arg { short: None,       long: "changed",     takes_value: TakesValue::Forbidden };
 pub static BLOCKSIZE:   Arg = Arg { short: Some(b'S'), long: "blocksize",   takes_value: TakesValue::Forbidden };
+pub static TOTALSIZE:   Arg = Arg { short: None,       long: "totalsize",   takes_value: TakesValue::Forbidden };
 pub static TIME:        Arg = Arg { short: Some(b't'), long: "time",        takes_value: TakesValue::Necessary(Some(TIMES)) };
 pub static ACCESSED:    Arg = Arg { short: Some(b'u'), long: "accessed",    takes_value: TakesValue::Forbidden };
 pub static CREATED:     Arg = Arg { short: Some(b'U'), long: "created",     takes_value: TakesValue::Forbidden };
@@ -87,7 +88,7 @@ pub static ALL_ARGS: Args = Args(&[
     &IGNORE_GLOB, &GIT_IGNORE, &ONLY_DIRS, &ONLY_FILES,
 
     &BINARY, &BYTES, &GROUP, &NUMERIC, &HEADER, &ICONS, &INODE, &LINKS, &MODIFIED, &CHANGED,
-    &BLOCKSIZE, &TIME, &ACCESSED, &CREATED, &TIME_STYLE, &HYPERLINK, &MOUNTS,
+    &BLOCKSIZE, &TOTALSIZE, &TIME, &ACCESSED, &CREATED, &TIME_STYLE, &HYPERLINK, &MOUNTS,
     &NO_PERMISSIONS, &NO_FILESIZE, &NO_USER, &NO_TIME, &NO_ICONS, &SMART_GROUP,
 
     &GIT, &NO_GIT, &GIT_REPOS, &GIT_REPOS_NO_STAT,

+ 1 - 0
src/options/help.rs

@@ -66,6 +66,7 @@ LONG VIEW OPTIONS
   -U, --created            use the created timestamp field
   --changed                use the changed timestamp field
   --time-style             how to format timestamps (default, iso, long-iso, full-iso, relative, or a custom style with '+' as prefix. Ex: '+%Y/%m/%d')
+  --totalsize              show recursive directory size
   --no-permissions         suppress the permissions field
   -o, --octal-permissions  list each file's permission in octal format
   --no-filesize            suppress the filesize field

+ 2 - 0
src/options/view.rs

@@ -15,11 +15,13 @@ impl View {
         let width = TerminalWidth::deduce(matches, vars)?;
         let file_style = FileStyle::deduce(matches, vars)?;
         let deref_links = matches.has(&flags::DEREF_LINKS)?;
+        let total_size = matches.has(&flags::TOTALSIZE)?;
         Ok(Self {
             mode,
             width,
             file_style,
             deref_links,
+            total_size
         })
     }
 }

+ 3 - 1
src/output/details.rs

@@ -134,6 +134,8 @@ pub struct Render<'a> {
     pub git_ignoring: bool,
 
     pub git: Option<&'a GitCache>,
+
+    pub total_size: bool
 }
 
 #[rustfmt::skip]
@@ -284,7 +286,7 @@ impl<'a> Render<'a> {
 
                     let table_row = table
                         .as_ref()
-                        .map(|t| t.row_for_file(file, self.show_xattr_hint(file)));
+                        .map(|t| t.row_for_file(file, self.show_xattr_hint(file), self.total_size));
 
                     let mut dir = None;
                     if let Some(r) = self.recurse {

+ 5 - 1
src/output/grid_details.rs

@@ -85,6 +85,8 @@ pub struct Render<'a> {
     pub git: Option<&'a GitCache>,
 
     pub console_width: usize,
+
+    pub total_size: bool
 }
 
 impl<'a> Render<'a> {
@@ -106,6 +108,7 @@ impl<'a> Render<'a> {
             filter:        self.filter,
             git_ignoring:  self.git_ignoring,
             git:           self.git,
+            total_size:    self.total_size
         };
     }
 
@@ -125,6 +128,7 @@ impl<'a> Render<'a> {
             filter:        self.filter,
             git_ignoring:  self.git_ignoring,
             git:           self.git,
+            total_size:    self.total_size
         };
     }
 
@@ -153,7 +157,7 @@ impl<'a> Render<'a> {
         let rows = self
             .files
             .iter()
-            .map(|file| first_table.row_for_file(file, drender.show_xattr_hint(file)))
+            .map(|file| first_table.row_for_file(file, drender.show_xattr_hint(file), self.total_size))
             .collect::<Vec<_>>();
 
         let file_names = self

+ 1 - 0
src/output/mod.rs

@@ -22,6 +22,7 @@ pub struct View {
     pub width: TerminalWidth,
     pub file_style: file_name::Options,
     pub deref_links: bool,
+    pub total_size: bool
 }
 
 /// The **mode** is the “type” of output.

+ 11 - 6
src/output/table.rs

@@ -410,11 +410,11 @@ impl<'a> Table<'a> {
         Row { cells }
     }
 
-    pub fn row_for_file(&self, file: &File<'_>, xattrs: bool) -> Row {
+    pub fn row_for_file(&self, file: &File<'_>, xattrs: bool, total_size: bool) -> Row {
         let cells = self
             .columns
             .iter()
-            .map(|c| self.display(file, *c, xattrs))
+            .map(|c| self.display(file, *c, xattrs, total_size))
             .collect();
 
         Row { cells }
@@ -450,12 +450,17 @@ impl<'a> Table<'a> {
             .map(|p| f::OctalPermissions { permissions: p })
     }
 
-    fn display(&self, file: &File<'_>, column: Column, xattrs: bool) -> TextCell {
+    fn display(&self, file: &File<'_>, column: Column, xattrs: bool, total_size: bool) -> TextCell {
         match column {
             Column::Permissions => self.permissions_plus(file, xattrs).render(self.theme),
-            Column::FileSize => file
-                .size()
-                .render(self.theme, self.size_format, &self.env.numeric),
+            Column::FileSize => match total_size {
+                true => file
+                    .recursive_size()
+                    .render(self.theme, self.size_format, &self.env.numeric),
+                false => file
+                    .size()
+                    .render(self.theme, self.size_format, &self.env.numeric)
+            }
             #[cfg(unix)]
             Column::HardLinks => file.links().render(self.theme, &self.env.numeric),
             #[cfg(unix)]