use std::io::{self, Write};
use std::path::{Path, PathBuf};
use anyhow::Result;
use argh::FromArgs;
use chrono::NaiveDateTime;
use log::LevelFilter;
use sqlar::{with_each_entry, FileType};
use tabwriter::TabWriter;
#[derive(FromArgs, PartialEq, Debug)]
struct Command {
#[argh(subcommand)]
nested: Subcommand,
#[argh(switch, short = 'V')]
verbose: bool,
}
#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand)]
enum Subcommand {
Extract(Extract),
Create(Create),
List(List),
}
#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand, name = "x")]
struct Extract {
#[argh(positional)]
archive: PathBuf,
#[argh(positional)]
destination: Option<PathBuf>,
}
#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand, name = "c")]
struct Create {
#[argh(positional)]
archive: PathBuf,
#[argh(positional)]
path: PathBuf,
#[argh(positional)]
paths: Vec<PathBuf>,
}
#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand, name = "l")]
struct List {
#[argh(positional)]
archive: PathBuf,
}
fn main() {
match real_main() {
Ok(()) => {}
Err(e) => {
eprintln!("An error occured:");
eprintln!("{}", e);
}
}
}
fn real_main() -> Result<()> {
let cmd: Command = argh::from_env();
env_logger::Builder::new()
.filter_level(if cmd.verbose {
LevelFilter::Info
} else {
LevelFilter::Warn
})
.init();
match cmd.nested {
Subcommand::Extract(x) => {
let archive = &x.archive;
let destination = x
.destination
.or_else(|| archive.file_stem().map(PathBuf::from));
let destination = match destination {
Some(d) => d,
None => anyhow::bail!("missing destination"),
};
log::info!(
"Extracting {} to {}/",
archive.display(),
destination.display()
);
sqlar::extract(&archive, &destination)?
}
Subcommand::Create(c) => {
let mut paths = vec![c.path];
paths.extend_from_slice(&c.paths);
log::info!(
"Creating new archive {} with files: {:?}",
c.archive.display(),
paths
);
sqlar::create(&c.archive, &paths)?
}
Subcommand::List(l) => list(&*l.archive)?,
}
Ok(())
}
pub fn list(path: &Path) -> Result<()> {
let stdout = io::stdout();
let handle = stdout.lock();
let mut tw = TabWriter::new(handle);
writeln!(&mut tw, "Name\tType\tMode\tModified\tSize (Compressed)").unwrap();
writeln!(&mut tw, "====\t====\t====\t========\t=================").unwrap();
with_each_entry(path, false, |entry| {
let ts = NaiveDateTime::from_timestamp(entry.mtime, 0);
writeln!(
&mut tw,
"{}\t{:?}\t{:o}\t{} UTC\t{} ({:.1}%)",
entry.name,
entry.filetype,
entry.mode,
ts,
entry.size,
if entry.filetype == FileType::File {
(entry.compressed_size as f64 / entry.size as f64) * 100.0
} else {
0.0
},
)
.unwrap();
Ok(())
})?;
tw.flush().unwrap();
Ok(())
}