use std::path;
use std::process;
use utils;
use errors::*;
pub fn borg_create(
repo: &str,
password: &str,
source: &path::Path,
exclude: &[String],
) -> Result<()> {
let archive_name = borg_archive_name(repo);
let source = source.to_str();
let source = match source {
Some(x) => x,
None => bail!("expected valid source path"),
};
let excludes: Vec<String> = utils::expanded_vec(&exclude.to_vec())?
.into_iter()
.flat_map(|x| vec!["--exclude".to_string(), x.into_owned()])
.collect();
let args = vec![
"create",
"--compression",
"lz4",
"--exclude-caches",
"--info",
"--one-file-system",
"--progress",
"--stats",
].into_iter()
.chain(excludes.iter().map(|x| x.as_ref()))
.chain(vec![archive_name.as_ref(), source])
.collect();
borg_verbose(repo, password, args)
}
pub fn borg_archive_name(repo: &str) -> String {
format!("{}::home-{{now:%Y-%m-%dT%H:%M:%S}}", repo)
}
pub fn borg_init(repo: &str, password: &str) -> Result<()> {
borg_silent(repo, password, vec!["init", "--encryption=repokey"])
}
pub fn borg_prune(repo: &str, password: &str) -> Result<()> {
borg_silent(
repo,
password,
vec![
"prune",
"--info",
"--keep-hourly",
"24",
"--keep-daily",
"7",
"--keep-weekly",
"-1",
],
)
}
pub fn borg_silent(repo: &str, password: &str, args: Vec<&str>) -> Result<()> {
borg(repo, password, args, true)
}
pub fn borg_verbose(repo: &str, password: &str, args: Vec<&str>) -> Result<()> {
borg(repo, password, args, cfg!(test))
}
fn borg(repo: &str, password: &str, args: Vec<&str>, silent: bool) -> Result<()> {
let (stderr, stdout) = if silent {
(process::Stdio::null(), process::Stdio::null())
} else {
(process::Stdio::inherit(), process::Stdio::inherit())
};
debug!("running borg {:?} (with repo: {:?})", args, repo);
let status = process::Command::new("nice")
.arg("-n")
.arg("19")
.arg("borg")
.args(args)
.env("BORG_PASSPHRASE", password)
.env("BORG_REPO", repo)
.stderr(stderr)
.stdin(process::Stdio::null())
.stdout(stdout)
.status()
.chain_err(|| "unable to run borg")?;
if !status.success() {
match status.code() {
Some(code) => bail!("borg exited with status code {}", code),
None => bail!("borg exited with an unknown status code"),
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use std::fs;
use std::path;
use tempdir;
use super::*;
#[test]
fn test_no_args() {
with_repo(|repo| {
assert!(borg_silent(repo, "test", vec![]).is_ok());
});
}
#[test]
fn test_bogus_arg() {
with_repo(|repo| {
assert!(borg_silent(repo, "test", vec!["bogus"]).is_err());
});
}
#[test]
fn test_repo_not_a_directory() {
with_repo(|repo| {
let test_file = path::Path::new(repo).join("test_file");
fs::File::create(&test_file).unwrap();
assert!(borg_silent(test_file.to_str().unwrap(), "test", vec!["init"]).is_err());
});
}
#[test]
fn test_init() {
with_repo(|repo| {
assert!(borg_silent(repo, "test", vec!["init"]).is_ok());
assert!(borg_silent(repo, "test", vec!["list"]).is_ok());
});
}
#[test]
fn test_init_wrong_password() {
with_repo(|repo| {
assert!(borg_silent(repo, "test", vec!["init"]).is_ok());
assert!(borg_silent(repo, "wrong", vec!["list"]).is_err());
});
}
#[test]
fn test_no_reinit() {
with_repo(|repo| {
let repo_path = path::Path::new(repo);
assert!(!repo_path.join("config").is_file());
assert!(borg_init(repo, "test").is_ok());
assert!(repo_path.join("config").is_file());
assert!(borg_silent(repo, "test", vec!["list"]).is_ok());
assert!(borg_init(repo, "test_no_re_init").is_err());
assert!(borg_silent(repo, "test_no_re_init", vec!["list"]).is_err());
assert!(borg_silent(repo, "test", vec!["list"]).is_ok());
});
}
#[test]
fn test_create() {
with_repo(|repo| {
let repo_path = path::Path::new(repo);
with_test_source(|source| {
assert!(borg_create(repo, "test", source, &vec![]).is_err());
let index_0 = repo_path.join("index.0");
assert!(borg_init(repo, "test").is_ok());
assert!(index_0.is_file());
assert!(borg_create(repo, "test", source, &vec![]).is_ok());
assert_eq!(index_0.is_file(), false);
});
});
}
#[test]
fn test_archive_name() {
assert_eq!(
borg_archive_name("/tmp/borg-hive"),
"/tmp/borg-hive::home-{now:%Y-%m-%dT%H:%M:%S}"
);
}
fn with_repo<F: FnOnce(&str) -> ()>(block: F) {
let repo = tempdir::TempDir::new("borg-hive").unwrap();
block(repo.path().to_str().unwrap());
}
fn with_test_source<F: FnOnce(&path::Path) -> ()>(block: F) {
let source = tempdir::TempDir::new("borg-hive-backup").unwrap();
fs::File::create(source.path().join("test_file_1.txt")).unwrap();
fs::File::create(source.path().join("test_file_2.txt")).unwrap();
block(source.path());
}
}