#![warn(bare_trait_objects)]
use chrono::Local;
use std::{collections::BTreeMap, path::Path};
use structopt::StructOpt;
use tempdir::TempDir;
use rsure::{log_init, parse_store, show_tree, Store, StoreTags, StoreVersion, Version};
pub use rsure::Result;
#[derive(StructOpt)]
#[structopt(name = "rsure", about = "File integrity")]
struct Opt {
#[structopt(short = "f", long = "file", default_value = "2sure.dat.gz")]
file: String,
#[structopt(short = "d", long = "dir", default_value = ".")]
dir: String,
#[structopt(long = "tag")]
tag: Vec<String>,
#[structopt(short = "v", long = "version")]
version: Option<String>,
#[structopt(subcommand)]
command: Command,
}
#[derive(StructOpt)]
enum Command {
#[structopt(name = "scan")]
Scan,
#[structopt(name = "update")]
Update,
#[structopt(name = "check")]
Check {
#[structopt(short = "i", long = "ignore")]
ignore: Vec<String>,
},
#[structopt(name = "signoff")]
Signoff {
#[structopt(short = "i", long = "ignore")]
ignore: Vec<String>,
},
#[structopt(name = "show")]
Show,
#[structopt(name = "list")]
List,
}
#[allow(dead_code)]
fn main() -> Result<()> {
log_init();
let opt = Opt::from_args();
let store = parse_store(&opt.file)?;
let mut tags = decode_tags(Some(opt.tag.iter().map(|x| x.as_str())));
add_name_tag(&mut tags, &opt.dir);
let latest = match opt.version {
None => Version::Latest,
Some(ref x) => Version::Tagged(x.to_string()),
};
match &opt.command {
Command::Scan => {
rsure::update(&opt.dir, &*store, false, &tags)?;
}
Command::Update => {
rsure::update(&opt.dir, &*store, true, &tags)?;
}
Command::Check { ignore } => {
let ignore: Vec<_> = ignore.iter().map(|x| x.as_str()).collect();
run_check(&*store, &opt, latest, &ignore)?;
}
Command::Signoff { ignore } => {
let ignore: Vec<_> = ignore.iter().map(|x| x.as_str()).collect();
let old_tree = store.load_iter(Version::Prior)?;
let new_tree = store.load_iter(Version::Latest)?;
println!("signoff {}", opt.file);
rsure::compare_trees(old_tree, new_tree, &Path::new(&opt.dir), &ignore)?;
}
Command::Show => {
println!("show {}", opt.file);
show_tree(&*store)?;
}
Command::List => {
let version = store.get_versions()?;
dump_versions(&version);
}
}
Ok(())
}
fn run_check(store: &dyn Store, opt: &Opt, latest: Version, ignore: &[&str]) -> Result<()> {
let tdir = TempDir::new("rsure")?;
let tpath = tdir.path().join("check.dat.gz");
let tstore = parse_store(tpath.to_str().unwrap())?;
let mut tags = BTreeMap::new();
add_name_tag(&mut tags, &opt.dir);
println!("Scanning");
rsure::update(&opt.dir, &*tstore, false, &tags)?;
let old_tree = store.load_iter(latest)?;
let new_tree = tstore.load_iter(Version::Latest)?;
println!("Check {}", opt.file);
rsure::compare_trees(old_tree, new_tree, &Path::new(&opt.dir), ignore)?;
Ok(())
}
fn decode_tags<'a, I>(tags: Option<I>) -> StoreTags
where
I: Iterator<Item = &'a str>,
{
match tags {
None => BTreeMap::new(),
Some(tags) => tags.map(|x| decode_tag(x)).collect(),
}
}
fn decode_tag(tag: &str) -> (String, String) {
let fields: Vec<_> = tag.splitn(2, '=').collect();
if fields.len() != 2 {
panic!("Tag must be key=value");
}
(fields[0].to_string(), fields[1].to_string())
}
fn add_name_tag<P: AsRef<Path>>(tags: &mut StoreTags, dir: P) {
if !tags.contains_key("name") {
tags.insert("name".to_string(), Local::now().to_rfc3339());
}
if !tags.contains_key("dir") {
tags.insert(
"dir".to_string(),
dir.as_ref()
.canonicalize()
.unwrap_or_else(|_| Path::new("invalid").to_owned())
.to_string_lossy()
.into_owned(),
);
}
}
fn dump_versions(versions: &[StoreVersion]) {
println!("vers | Time captured | name");
println!("-----+---------------------+------------------");
for v in versions {
let vers = match v.version {
Version::Latest => "tip",
Version::Prior => "prev",
Version::Tagged(ref v) => v,
};
println!(
"{:>4} | {} | {}",
vers,
v.time.with_timezone(&Local).format("%Y-%m-%d %H:%M:%S"),
v.name
);
}
}