use std::{
io::{Read, Write},
path::{Path, PathBuf},
};
#[derive(Debug, clap::Args)]
pub(crate) struct BackupCmd {
#[arg(short, long, default_value_t = false)]
no_archive: bool,
}
impl super::Command for BackupCmd {
fn run(
&self,
_: &super::nedots::RootCmd,
config: &crate::config::Config,
) -> Result<(), super::CommandError> {
backup(
&config.sources,
&config.root.join(get_dst()),
!self.no_archive,
)
}
}
pub(crate) fn get_dst() -> String {
format!("backups/{}", chrono::offset::Local::now().timestamp())
}
pub(crate) fn backup(
sources: &[PathBuf],
dst: &Path,
archive: bool,
) -> Result<(), super::CommandError> {
std::fs::create_dir_all(dst)?;
log::debug!("Backing up to {}", dst.to_string_lossy());
let pb = indicatif::ProgressBar::new_spinner();
for source in sources {
let src = Path::new(source);
pb.tick();
pb.set_message(format!("Backing up {}...", source.to_string_lossy()));
crate::cmd::gather::gather(src, &dst.join(src.strip_prefix("/")?))?;
}
pb.finish_and_clear();
if archive {
archive_dir(dst)?;
}
Ok(())
}
fn archive_dir(dst: &Path) -> Result<(), super::CommandError> {
let archive_name = format!("{}.zip", dst.to_string_lossy());
log::debug!("Archiving to {}", archive_name);
let file = std::fs::File::create(archive_name).unwrap();
let mut zip = zip::ZipWriter::new(file);
let options = zip::write::FileOptions::default()
.compression_method(zip::CompressionMethod::Zstd)
.unix_permissions(0o755);
let pb = indicatif::ProgressBar::new_spinner();
for entry in dst.read_dir()? {
let path = entry?.path();
let name = path.strip_prefix(Path::new(dst)).unwrap();
pb.tick();
pb.set_message(format!("Archiving {}...", path.to_string_lossy()));
compress(&mut zip, options, dst, name, &path)?;
}
std::fs::remove_dir_all(dst)?;
pb.finish_and_clear();
Ok(())
}
fn compress(
zip: &mut zip::ZipWriter<std::fs::File>,
options: zip::write::FileOptions,
dst: &Path,
name: &Path,
path: &Path,
) -> Result<(), super::CommandError> {
let mut buffer = Vec::new();
if path.is_file() {
log::debug!("Compressing {}", name.to_string_lossy());
zip.start_file(name.to_string_lossy(), options)?;
let mut f = std::fs::File::open(path)?;
f.read_to_end(&mut buffer)?;
zip.write_all(&*buffer)?;
buffer.clear();
} else {
for entry in path.read_dir()? {
let path = entry?.path();
let name = path.strip_prefix(Path::new(&dst)).unwrap();
compress(zip, options, dst, name, &path)?;
}
}
Ok(())
}