diff --git a/changelog/new.txt b/changelog/new.txt index 7211335c3..08e163115 100644 --- a/changelog/new.txt +++ b/changelog/new.txt @@ -14,4 +14,5 @@ New features: - fix: wait for password-command to successfully exit, allowing to input something into the command, and read password from stdout. - repoinfo: Added new options --json, --only-files, --only-index - Creation of new keys now enforces confirmation of entered key. This helps to prevent mistype of passwords during the initial entry -- Check: Add check if time is set for packs-to-delete \ No newline at end of file +- Check: Add check if time is set for packs-to-delete +- ls: Options --long (-l) and --summary (-s) have been added. \ No newline at end of file diff --git a/src/commands/ls.rs b/src/commands/ls.rs index beb8c33c1..d8614d29e 100644 --- a/src/commands/ls.rs +++ b/src/commands/ls.rs @@ -1,5 +1,7 @@ //! `ls` subcommand +use std::path::Path; + /// App-local prelude includes `app_reader()`/`app_writer()`/`app_config()` /// accessors along with logging macros. Customize as you see fit. use crate::{commands::open_repository, status_err, Application, RUSTIC_APP}; @@ -7,7 +9,8 @@ use crate::{commands::open_repository, status_err, Application, RUSTIC_APP}; use abscissa_core::{Command, Runnable, Shutdown}; use anyhow::Result; -use rustic_core::TreeStreamerOptions; +use libc::{S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR}; +use rustic_core::{Node, NodeType, TreeStreamerOptions}; /// `ls` subcommand #[derive(clap::Parser, Command, Debug)] @@ -20,6 +23,14 @@ pub(crate) struct LsCmd { #[clap(long)] recursive: bool, + /// show summary + #[clap(long, short = 's')] + summary: bool, + + /// show long listing + #[clap(long, short = 'l')] + long: bool, + #[clap(flatten)] streamer_opts: TreeStreamerOptions, } @@ -33,6 +44,25 @@ impl Runnable for LsCmd { } } +#[derive(Default)] +struct Summary { + files: usize, + size: u64, + dirs: usize, +} + +impl Summary { + fn update(&mut self, node: &Node) { + if node.is_dir() { + self.dirs += 1; + } + if node.is_file() { + self.files += 1; + self.size += node.meta.size; + } + } +} + impl LsCmd { fn inner_run(&self) -> Result<()> { let config = RUSTIC_APP.config(); @@ -42,13 +72,83 @@ impl LsCmd { let node = repo.node_from_snapshot_path(&self.snap, |sn| config.snapshot_filter.matches(sn))?; + // recursive if standard if we specify a snapshot without dirs. In other cases, use the parameter `recursive` let recursive = !self.snap.contains(':') || self.recursive; + let mut summary = Summary::default(); + for item in repo.ls(&node, &self.streamer_opts, recursive)? { - let (path, _) = item?; - println!("{path:?} "); + let (path, node) = item?; + summary.update(&node); + if self.long { + print_node(&node, &path); + } else { + println!("{path:?} "); + } + } + + if self.summary { + println!( + "total: {} dirs, {} files, {} bytes", + summary.dirs, summary.files, summary.size + ) } Ok(()) } } + +// print node in format similar to unix `ls` +fn print_node(node: &Node, path: &Path) { + println!( + "{:>1}{:>9} {:>8} {:>8} {:>9} {:>12} {path:?} {}", + match node.node_type { + NodeType::Dir => 'd', + NodeType::Symlink { .. } => 'l', + NodeType::Chardev { .. } => 'c', + NodeType::Dev { .. } => 'b', + NodeType::Fifo { .. } => 'p', + NodeType::Socket => 's', + _ => '-', + }, + node.meta + .mode + .map(|m| parse_permissions(m)) + .unwrap_or("?????????".to_string()), + node.meta.user.clone().unwrap_or("?".to_string()), + node.meta.group.clone().unwrap_or("?".to_string()), + node.meta.size, + node.meta + .mtime + .map(|t| t.format("%_d %b %H:%M").to_string()) + .unwrap_or("?".to_string()), + if let NodeType::Symlink { linktarget } = &node.node_type { + ["->", linktarget].join(" ") + } else { + "".to_string() + } + ); +} + +// helper fn to put permissions in readable format +fn parse_permissions(mode: u32) -> String { + let user = triplet(mode, S_IRUSR, S_IWUSR, S_IXUSR); + let group = triplet(mode, S_IRGRP, S_IWGRP, S_IXGRP); + let other = triplet(mode, S_IROTH, S_IWOTH, S_IXOTH); + [user, group, other].join("") +} + +// helper fn to put permissions in readable format +fn triplet(mode: u32, read: u32, write: u32, execute: u32) -> String { + match (mode & read, mode & write, mode & execute) { + (0, 0, 0) => "---", + (_, 0, 0) => "r--", + (0, _, 0) => "-w-", + (0, 0, _) => "--x", + (_, 0, _) => "r-x", + (_, _, 0) => "rw-", + (0, _, _) => "-wx", + (_, _, _) => "rwx", + } + .to_string() +}