use std::{
env,
fs::{create_dir_all, write},
path::PathBuf,
process::{Command, ExitCode},
};
use log::{debug, info};
use serde::Serialize;
mod help;
mod sh;
pub use sh::sh_run;
use crate::{
clean::{Snapshot, decide, ls_snapshot, safe_rm_subvol_list},
config::{ConfigEnv, PmbsConfigFile, get_env_config, list_config, read_config},
snapshot::{format_t_local, get_t, make_snapshot},
};
use help::bad_cli_arg;
fn c_snapshot(a: Vec<String>) -> Result<(), ExitCode> {
if a.len() != 1 {
bad_cli_arg();
return Err(ExitCode::from(1));
}
let subvol = &a[0];
let c = get_env_config();
make_snapshot(&c, subvol)
}
fn c_ls(a: Vec<String>) -> Result<(), ExitCode> {
if a.len() != 1 {
bad_cli_arg();
return Err(ExitCode::from(1));
}
let subvol = &a[0];
let mut list = ls_snapshot(subvol);
list.sort_by(|a, b| b.t.cmp(&a.t));
for i in list {
let latest = if i.latest { "\t*latest" } else { "" };
println!(
"{}\t{}{}",
i.p.to_string_lossy(),
format_t_local(i.t),
latest
);
}
Ok(())
}
fn get_exe() -> PathBuf {
env::current_exe().unwrap()
}
fn run_config(c: &ConfigEnv, a: &str) -> Result<(), ExitCode> {
let mut r = Ok(());
for i in list_config(c) {
let mut c = Command::new(get_exe());
c.arg("config").arg(a).arg(i);
let code = sh_run(c);
if 0 != code {
r = Err(ExitCode::from(1));
}
}
r
}
fn c_config_snapshot(a: Vec<String>) -> Result<(), ExitCode> {
let path = match a.len() {
0 => None,
1 => Some(&a[0]),
_ => {
bad_cli_arg();
return Err(ExitCode::from(1));
}
};
let c = get_env_config();
match path {
Some(path) => match read_config(&PathBuf::from(path)) {
Some(config) => {
debug!("config {}", serde_json::to_string(&config).unwrap());
make_snapshot(&c, &config.config.subvol)
}
None => {
return Err(ExitCode::from(1));
}
},
None => run_config(&c, "snapshot"),
}
}
fn c_config_clean(a: Vec<String>) -> Result<(), ExitCode> {
let path = match a.len() {
0 => None,
1 => Some(&a[0]),
_ => {
bad_cli_arg();
return Err(ExitCode::from(1));
}
};
let c = get_env_config();
match path {
Some(path) => match get_clean(path) {
Some((config, keep, clean)) => {
write_clean_log(&c, &config, &keep, &clean);
safe_rm_subvol_list(
&c,
clean
.iter()
.map(|x| x.p.to_string_lossy().to_string())
.collect(),
);
Ok(())
}
None => {
return Err(ExitCode::from(1));
}
},
None => run_config(&c, "clean"),
}
}
#[derive(Debug, Clone, Serialize)]
pub struct CleanLog {
pub t: u64,
pub config: PmbsConfigFile,
pub keep: Vec<String>,
pub clean: Vec<String>,
}
fn write_clean_log(
c: &ConfigEnv,
config: &PmbsConfigFile,
keep: &Vec<Snapshot>,
clean: &Vec<Snapshot>,
) {
let t = get_t();
let filename = format!("clean-{}-{}.log.json", t, config.path);
let log = CleanLog {
t,
config: config.clone(),
keep: keep.iter().map(|x| x.path.clone()).collect(),
clean: clean.iter().map(|x| x.path.clone()).collect(),
};
let text = serde_json::to_string_pretty(&log).unwrap();
let mut p = PathBuf::from(&c.dir_log);
p.push(filename);
debug!("write clean log {}", p.to_string_lossy());
create_dir_all(&c.dir_log).unwrap();
write(p, text.as_bytes()).unwrap();
}
fn c_config_test(a: Vec<String>) -> Result<(), ExitCode> {
if a.len() > 0 {
bad_cli_arg();
return Err(ExitCode::from(1));
}
let c = get_env_config();
for i in list_config(&c) {
info!("check {}", i.to_string_lossy());
match read_config(&i) {
Some(c) => {
debug!("config {}", serde_json::to_string(&c).unwrap());
}
None => {
return Err(ExitCode::from(1));
}
}
}
Ok(())
}
fn c_config_test_clean(a: Vec<String>) -> Result<(), ExitCode> {
if a.len() != 1 {
bad_cli_arg();
return Err(ExitCode::from(1));
}
let path = &a[0];
match get_clean(path) {
Some((_, keep, clean)) => {
for i in keep {
println!("keep {} {}", i.path, format_t_local(i.t));
}
for i in clean {
println!("clean {} {}", i.path, format_t_local(i.t));
}
Ok(())
}
None => {
return Err(ExitCode::from(1));
}
}
}
fn get_clean(path: &str) -> Option<(PmbsConfigFile, Vec<Snapshot>, Vec<Snapshot>)> {
match read_config(&PathBuf::from(path)) {
Some(config) => {
debug!("config {}", serde_json::to_string(&config).unwrap());
let snapshot = ls_snapshot(&config.config.subvol);
let total = snapshot.len();
let (keep, clean) = decide(config.config.keep.clone(), snapshot);
debug!(
"total = {}, keep = {}, clean = {}",
total,
keep.len(),
clean.len()
);
if total != (keep.len() + clean.len()) {
panic!(
"bad clean, total = {}, keep = {}, clean = {}",
total,
keep.len(),
clean.len()
);
}
Some((config, keep, clean))
}
None => None,
}
}
fn c_config(a: Vec<String>) -> Result<(), ExitCode> {
if a.len() < 1 {
bad_cli_arg();
return Err(ExitCode::from(1));
}
let r: Vec<String> = (&a[1..]).into();
match a[0].as_str() {
"snapshot" => c_config_snapshot(r),
"clean" => c_config_clean(r),
"test" => c_config_test(r),
"test-clean" => c_config_test_clean(r),
_ => {
bad_cli_arg();
Err(ExitCode::from(1))
}
}
}
pub fn main(a: Vec<String>) -> Result<(), ExitCode> {
if a.len() > 0 {
let r: Vec<String> = (&a[1..]).into();
match a[0].as_str() {
"--help" => {
help::help_en();
Ok(())
}
"--帮助" => {
help::help_zh();
Ok(())
}
"snapshot" => c_snapshot(r),
"ls" => c_ls(r),
"config" => c_config(r),
_ => {
eprintln!("ERROR: Bad command `{}`, try --help", a[0]);
Err(ExitCode::from(1))
}
}
} else {
eprintln!("ERROR: Bad command, try --help");
Err(ExitCode::from(1))
}
}