use anyhow::Result;
use chrono::{DateTime, Utc};
use clap::{ArgAction, Args};
use log::debug;
use nuts_archive::{Archive, Entry, Group};
use std::fmt::{self, Write};
use crate::backend::PluginBackend;
use crate::cli::open_container;
use crate::say;
use crate::time::TimeFormat;
enum Type {
File,
Directory,
Symlink,
}
impl fmt::Display for Type {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Type::File => fmt.write_char('-'),
Type::Directory => fmt.write_char('d'),
Type::Symlink => fmt.write_char('l'),
}
}
}
struct Permissions(bool, bool, bool);
impl Permissions {
fn from_entry(entry: &Entry<PluginBackend>, group: Group) -> Permissions {
Permissions(
entry.can_read(group),
entry.can_write(group),
entry.can_execute(group),
)
}
}
impl fmt::Display for Permissions {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.0 {
fmt.write_char('r')?;
} else {
fmt.write_char('-')?;
}
if self.1 {
fmt.write_char('w')?;
} else {
fmt.write_char('-')?;
}
if self.2 {
fmt.write_char('x')?;
} else {
fmt.write_char('-')?;
}
Ok(())
}
}
impl<'a> From<&'a Entry<'a, PluginBackend>> for Type {
fn from(entry: &'a Entry<'a, PluginBackend>) -> Self {
match entry {
Entry::File(_) => Type::File,
Entry::Directory(_) => Type::Directory,
Entry::Symlink(_) => Type::Symlink,
}
}
}
struct ListEntry {
name: String,
size: u64,
r#type: Type,
appended: DateTime<Utc>,
created: DateTime<Utc>,
changed: DateTime<Utc>,
modified: DateTime<Utc>,
user_perms: Permissions,
group_perms: Permissions,
other_perms: Permissions,
}
impl<'a> From<&'a Entry<'a, PluginBackend>> for ListEntry {
fn from(entry: &Entry<PluginBackend>) -> Self {
ListEntry {
name: entry.name().to_string(),
size: entry.size(),
r#type: entry.into(),
appended: *entry.appended(),
created: *entry.created(),
changed: *entry.changed(),
modified: *entry.modified(),
user_perms: Permissions::from_entry(entry, Group::User),
group_perms: Permissions::from_entry(entry, Group::Group),
other_perms: Permissions::from_entry(entry, Group::Other),
}
}
}
fn collect_entries(archive: &mut Archive<PluginBackend>) -> Result<Vec<ListEntry>> {
let mut vec = vec![];
let mut entry_opt = archive.first();
loop {
match entry_opt {
Some(Ok(entry)) => {
vec.push((&entry).into());
entry_opt = entry.next();
}
Some(Err(err)) => return Err(err.into()),
None => break,
}
}
Ok(vec)
}
#[derive(Args, Debug)]
pub struct ArchiveListArgs {
#[clap(short, long, action = ArgAction::SetTrue)]
long: bool,
#[clap(short, long, action = ArgAction::SetTrue)]
appended: bool,
#[clap(short = 'r', long, action = ArgAction::SetTrue)]
created: bool,
#[clap(short = 'n', long, action = ArgAction::SetTrue)]
changed: bool,
#[clap(
short,
long,
value_parser,
value_name = "FORMAT",
default_value = "local"
)]
time_format: TimeFormat,
#[clap(short, long, env = "NUTS_CONTAINER")]
container: String,
#[clap(from_global)]
verbose: u8,
}
impl ArchiveListArgs {
fn print_short(&self, entries: Vec<ListEntry>) {
for entry in entries {
say!("{}", entry.name);
}
}
fn print_long(&self, entries: Vec<ListEntry>) {
let max_size = entries.iter().max_by_key(|e| e.size).map_or(0, |e| e.size);
let max_n = max_size.to_string().len();
for entry in entries {
let tstamp = if self.appended {
entry.appended
} else if self.created {
entry.created
} else if self.changed {
entry.changed
} else {
entry.modified
};
say!(
"{}{}{}{} {:>max_n$} {} {}",
entry.r#type,
entry.user_perms,
entry.group_perms,
entry.other_perms,
entry.size,
self.time_format.format(&tstamp, "%d %b %H:%M"),
entry.name
);
}
}
pub fn run(&self) -> Result<()> {
debug!("args: {:?}", self);
let container = open_container(&self.container, self.verbose)?;
let mut archive = Archive::open(container)?;
let entries = collect_entries(&mut archive)?;
if self.long {
self.print_long(entries);
} else {
self.print_short(entries);
}
Ok(())
}
}