Compare commits

..

1 commit

Author SHA1 Message Date
qwjyh
1967cf0cb8
Merge a409a43906 into 0abf9c0693 2024-08-09 19:47:31 +00:00
9 changed files with 77 additions and 469 deletions

View file

@ -10,17 +10,13 @@ env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
jobs: jobs:
build-and-lint: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Setup
run: rustup component add clippy
- name: Build - name: Build
run: cargo build --verbose run: cargo build --verbose
- name: Run tests - name: Run tests
run: cargo test --verbose run: cargo test --verbose
- name: Lint
run: cargo clippy --all-targets --all-features

View file

@ -4,8 +4,6 @@
### Changed ### Changed
- Colored output for `storage list` and `backup list` ([#15](https://github.com/qwjyh/xdbm/pull/15)) - Colored output for `storage list` and `backup list` ([#15](https://github.com/qwjyh/xdbm/pull/15))
- **BREAKING** Relative path is changed from `PathBuf` to `Vector<String>` for portability. This means that existing config files need to be changed.
- Add `status` subcommand to see storage and backup on given path or current working directory ([#17](https://github.com/qwjyh/xdbm/pull/17)).
## [0.2.1] - 2024-06-19 ## [0.2.1] - 2024-06-19

View file

@ -27,38 +27,34 @@ pub fn backups_file(device: &Device) -> PathBuf {
} }
/// Targets for backup source or destination. /// Targets for backup source or destination.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct BackupTarget { pub struct BackupTarget {
/// `name()` of [`crate::storages::Storage`]. /// `name()` of [`crate::storages::Storage`].
/// Use `String` for serialization/deserialization. /// Use `String` for serialization/deserialization.
pub storage: String, pub storage: String,
/// Relative path to the `storage`. /// Relative path to the `storage`.
pub path: Vec<String>, pub path: PathBuf,
} }
impl BackupTarget { impl BackupTarget {
pub fn new(storage_name: String, relative_path: PathBuf) -> Result<Self> { pub fn new(storage_name: String, relative_path: PathBuf) -> Self {
let relative_path = relative_path BackupTarget {
.components()
.map(|c| c.as_os_str().to_str().map(|s| s.to_owned()))
.collect::<Option<_>>()
.context("Path contains non-utf8 character")?;
Ok(BackupTarget {
storage: storage_name, storage: storage_name,
path: relative_path, path: relative_path,
}) }
} }
/// Get full path of the [`BackupTarget`]. /// Get full path of the [`BackupTarget`].
pub fn path(&self, storages: &Storages, device: &Device) -> Option<PathBuf> { pub fn path(&self, storages: &Storages, device: &Device) -> Option<PathBuf> {
let parent = storages.get(&self.storage).unwrap(); let parent = storages.get(&self.storage).unwrap();
let parent_path = parent.mount_path(device)?; let parent_path = parent
Some(parent_path.join(self.path.clone().iter().collect::<PathBuf>())) .mount_path(device)?;
Some(parent_path.join(self.path.clone()))
} }
} }
/// Type of backup commands. /// Type of backup commands.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub enum BackupCommand { pub enum BackupCommand {
ExternallyInvoked(ExternallyInvoked), ExternallyInvoked(ExternallyInvoked),
} }
@ -85,7 +81,7 @@ impl BackupCommandExt for BackupCommand {
/// Backup commands which is not invoked from xdbm itself. /// Backup commands which is not invoked from xdbm itself.
/// Call xdbm externally to record backup datetime and status. /// Call xdbm externally to record backup datetime and status.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct ExternallyInvoked { pub struct ExternallyInvoked {
name: String, name: String,
pub note: String, pub note: String,
@ -108,7 +104,7 @@ impl BackupCommandExt for ExternallyInvoked {
} }
/// Backup execution log. /// Backup execution log.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct BackupLog { pub struct BackupLog {
pub datetime: DateTime<Local>, pub datetime: DateTime<Local>,
status: BackupResult, status: BackupResult,
@ -128,7 +124,7 @@ impl BackupLog {
} }
/// Result of backup. /// Result of backup.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub enum BackupResult { pub enum BackupResult {
Success, Success,
Failure, Failure,
@ -145,7 +141,7 @@ impl BackupResult {
} }
/// Backup source, destination, command and logs. /// Backup source, destination, command and logs.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct Backup { pub struct Backup {
/// must be unique /// must be unique
name: String, name: String,
@ -180,7 +176,7 @@ impl Backup {
&self.name &self.name
} }
pub fn device<'a>(&'a self, devices: &'a [Device]) -> Option<&'a Device> { pub fn device<'a>(&'a self, devices: &'a [Device]) -> Option<&Device> {
devices.iter().find(|dev| dev.name() == self.device) devices.iter().find(|dev| dev.name() == self.device)
} }
@ -206,7 +202,7 @@ impl Backup {
} }
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct Backups { pub struct Backups {
pub list: BTreeMap<String, Backup>, pub list: BTreeMap<String, Backup>,
} }

View file

@ -49,10 +49,10 @@ pub(crate) enum Commands {
/// Target path. Default is the current directory. /// Target path. Default is the current directory.
path: Option<PathBuf>, path: Option<PathBuf>,
/// Show storage which the path belongs to. /// Show storage which the path belongs to.
#[arg(short, long)] #[arg(short)]
storage: bool, storage: bool,
/// Show backup config covering the path. /// Show backup config covering the path.
#[arg(short, long)] #[arg(short)]
backup: bool, backup: bool,
}, },

View file

@ -5,7 +5,7 @@ use std::{
}; };
use anyhow::{anyhow, Context, Ok, Result}; use anyhow::{anyhow, Context, Ok, Result};
use chrono::Local; use chrono::{Local, TimeDelta};
use console::Style; use console::Style;
use dunce::canonicalize; use dunce::canonicalize;
use git2::Repository; use git2::Repository;
@ -89,8 +89,8 @@ fn new_backup(
Ok(Backup::new( Ok(Backup::new(
name, name,
device.name(), device.name(),
src_target?, src_target,
dest_target?, dest_target,
command, command,
)) ))
} }
@ -154,6 +154,17 @@ pub fn cmd_backup_list(
Ok(()) Ok(())
} }
fn duration_style(time: TimeDelta) -> Style {
match time {
x if x < TimeDelta::days(7) => Style::new().green(),
x if x < TimeDelta::days(14) => Style::new().yellow(),
x if x < TimeDelta::days(28) => Style::new().magenta(),
x if x < TimeDelta::days(28 * 3) => Style::new().red(),
x if x < TimeDelta::days(180) => Style::new().red().bold(),
_ => Style::new().on_red().black(),
}
}
/// TODO: status printing /// TODO: status printing
fn write_backups_list( fn write_backups_list(
mut writer: impl io::Write, mut writer: impl io::Write,
@ -211,7 +222,7 @@ fn write_backups_list(
Some(log) => { Some(log) => {
let time = Local::now() - log.datetime; let time = Local::now() - log.datetime;
let s = util::format_summarized_duration(time); let s = util::format_summarized_duration(time);
let style = util::duration_style(time); let style = duration_style(time);
(style.apply_to(s), style) (style.apply_to(s), style)
} }
None => { None => {
@ -361,9 +372,9 @@ mod test {
&storages, &storages,
)?; )?;
assert!(backup.source().storage == "online"); assert!(backup.source().storage == "online");
assert_eq!(backup.source().path, vec!["docs"]); assert_eq!(backup.source().path, PathBuf::from("docs"));
assert!(backup.destination().storage == "online"); assert!(backup.destination().storage == "online");
assert!(backup.destination().path == vec!["tmp"]); assert!(backup.destination().path == PathBuf::from("tmp"));
Ok(()) Ok(())
} }
} }

View file

@ -1,5 +1,4 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use chrono::Local;
use console::Style; use console::Style;
use std::{ use std::{
env, env,
@ -21,11 +20,11 @@ pub(crate) fn cmd_status(
config_dir: &Path, config_dir: &Path,
) -> Result<()> { ) -> Result<()> {
let path = path.unwrap_or(env::current_dir().context("Failed to get current directory.")?); let path = path.unwrap_or(env::current_dir().context("Failed to get current directory.")?);
let current_device = devices::get_device(config_dir)?; let currrent_device = devices::get_device(config_dir)?;
if show_storage { if show_storage {
let storages = storages::Storages::read(config_dir)?; let storages = storages::Storages::read(config_dir)?;
let storage = util::min_parent_storage(&path, &storages, &current_device); let storage = util::min_parent_storage(&path, &storages, &currrent_device);
trace!("storage {:?}", storage); trace!("storage {:?}", storage);
// TODO: recursively trace all storages for subdirectory? // TODO: recursively trace all storages for subdirectory?
@ -41,39 +40,21 @@ pub(crate) fn cmd_status(
if show_backup { if show_backup {
let devices = devices::get_devices(config_dir)?; let devices = devices::get_devices(config_dir)?;
let storages = storages::Storages::read(config_dir)?; let storages = storages::Storages::read(config_dir)?;
let backups = devices.iter().map(|device| { let backups = Backups::read(config_dir, &currrent_device)?;
Backups::read(config_dir, device)
.context("Backups were not found")
.unwrap()
});
let (target_storage, target_diff_from_storage) = let (target_storage, target_diff_from_storage) =
util::min_parent_storage(&path, &storages, &current_device) util::min_parent_storage(&path, &storages, &currrent_device)
.context("Target path is not covered in any storage")?; .context("Target path is not covered in any storage")?;
let covering_backup: Vec<_> = devices let covering_backup: Vec<_> = devices
.iter() .iter()
.zip(backups) .map(|device| {
.map(|(device, backups)| {
debug!(
"dev {}, storage {:?}",
device.name(),
backups
.list
.iter()
.map(|(backup_name, backup)| format!(
"{} {}",
backup_name,
backup.source().storage
))
.collect::<Vec<_>>()
);
( (
device, device,
parent_backups( parent_backups(
&target_diff_from_storage, &target_diff_from_storage,
target_storage, target_storage,
backups, &backups,
&storages, &storages,
device, device,
), ),
@ -95,33 +76,17 @@ pub(crate) fn cmd_status(
.unwrap_or(5); .unwrap_or(5);
for (backup_device, covering_backups) in covering_backup { for (backup_device, covering_backups) in covering_backup {
if covering_backups.is_empty() {
continue;
}
println!("Device: {}", backup_device.name()); println!("Device: {}", backup_device.name());
for (backup, path_from_backup) in covering_backups { for (backup, path_from_backup) in covering_backups {
let (last_backup, style) = match backup.last_backup() {
Some(log) => {
let timediff = Local::now() - log.datetime;
(
util::format_summarized_duration(timediff),
util::duration_style(timediff),
)
}
None => ("---".to_string(), Style::new().red()),
};
println!( println!(
" {:<name_len$} {} {}", " {:<name_len$} {}",
console::style(backup.name()).bold(), console::style(backup.name()).bold(),
style.apply_to(last_backup),
path_from_backup.display(), path_from_backup.display(),
); );
} }
} }
} }
todo!()
Ok(())
} }
/// Get [`Backup`]s for `device` which covers `target_path`. /// Get [`Backup`]s for `device` which covers `target_path`.
@ -129,10 +94,10 @@ pub(crate) fn cmd_status(
fn parent_backups<'a>( fn parent_backups<'a>(
target_path_from_storage: &'a Path, target_path_from_storage: &'a Path,
target_storage: &'a Storage, target_storage: &'a Storage,
backups: Backups, backups: &'a Backups,
storages: &'a Storages, storages: &'a Storages,
device: &'a Device, device: &'a Device,
) -> Vec<(Backup, PathBuf)> { ) -> Vec<(&'a Backup, PathBuf)> {
trace!("Dev {:?}", device.name()); trace!("Dev {:?}", device.name());
let target_path = match target_storage.mount_path(device) { let target_path = match target_storage.mount_path(device) {
Some(target_path) => target_path.join(target_path_from_storage), Some(target_path) => target_path.join(target_path_from_storage),
@ -141,17 +106,11 @@ fn parent_backups<'a>(
trace!("Path on the device {:?}", target_path); trace!("Path on the device {:?}", target_path);
backups backups
.list .list
.into_iter() .iter()
.filter_map(|(_k, backup)| { .filter_map(|(_k, backup)| {
let backup_path = backup.source().path(storages, device)?; let backup_path = backup.source().path(storages, device)?;
trace!("{:?}", backup_path.components()); let diff = pathdiff::diff_paths(&target_path, backup_path)?;
let diff = pathdiff::diff_paths(&target_path, backup_path.clone())?; if diff.components().any(|c| c == path::Component::ParentDir) {
trace!("Backup: {:?}, Diff: {:?}", backup_path, diff);
// note: Should `RootDir` is included in this list?
if diff
.components()
.any(|c| matches!(c, path::Component::ParentDir | path::Component::Prefix(_)))
{
None None
} else { } else {
Some((backup, diff)) Some((backup, diff))
@ -162,7 +121,7 @@ fn parent_backups<'a>(
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::{path::PathBuf, vec}; use std::path::PathBuf;
use crate::{ use crate::{
backups::{self, ExternallyInvoked}, backups::{self, ExternallyInvoked},
@ -217,11 +176,11 @@ mod test {
device1.name().to_string(), device1.name().to_string(),
backups::BackupTarget { backups::BackupTarget {
storage: "storage_1".to_string(), storage: "storage_1".to_string(),
path: vec!["bar".to_string()], path: PathBuf::from("bar"),
}, },
backups::BackupTarget { backups::BackupTarget {
storage: "storage_1".to_string(), storage: "storage_1".to_string(),
path: vec!["hoge".to_string()], path: PathBuf::from("hoge"),
}, },
backups::BackupCommand::ExternallyInvoked(ExternallyInvoked::new( backups::BackupCommand::ExternallyInvoked(ExternallyInvoked::new(
"cmd".to_string(), "cmd".to_string(),
@ -233,11 +192,11 @@ mod test {
device2.name().to_string(), device2.name().to_string(),
backups::BackupTarget { backups::BackupTarget {
storage: "storage_1".to_string(), storage: "storage_1".to_string(),
path: vec!["".to_string()], path: PathBuf::from(""),
}, },
backups::BackupTarget { backups::BackupTarget {
storage: "storage_3".to_string(), storage: "storage_3".to_string(),
path: vec!["foo".to_string()], path: PathBuf::from("foo"),
}, },
backups::BackupCommand::ExternallyInvoked(ExternallyInvoked::new( backups::BackupCommand::ExternallyInvoked(ExternallyInvoked::new(
"cmd".to_string(), "cmd".to_string(),
@ -259,7 +218,7 @@ mod test {
let covering_backups_1 = parent_backups( let covering_backups_1 = parent_backups(
&target_path_from_storage1, &target_path_from_storage1,
target_storage1, target_storage1,
backups.clone(), &backups,
&storages, &storages,
&device1, &device1,
); );
@ -272,7 +231,7 @@ mod test {
let covering_backups_2 = parent_backups( let covering_backups_2 = parent_backups(
&target_path_from_storage2, &target_path_from_storage2,
target_storage2, target_storage2,
backups.clone(), &backups,
&storages, &storages,
&device2, &device2,
); );
@ -285,13 +244,12 @@ mod test {
let covering_backups_3 = parent_backups( let covering_backups_3 = parent_backups(
&target_path_from_storage3, &target_path_from_storage3,
target_storage3, target_storage3,
backups, &backups,
&storages, &storages,
&device2, &device2,
); );
assert_eq!(covering_backups_3.len(), 1); assert_eq!(covering_backups_3.len(), 1);
let mut covering_backup_names_3 = let mut covering_backup_names_3 = covering_backups_3.iter().map(|(backup, _)| backup.name());
covering_backups_3.iter().map(|(backup, _)| backup.name());
assert_eq!(covering_backup_names_3.next().unwrap(), "backup_2"); assert_eq!(covering_backup_names_3.next().unwrap(), "backup_2");
assert!(covering_backup_names_3.next().is_none()); assert!(covering_backup_names_3.next().is_none());
} }

View file

@ -2,7 +2,6 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::{collections::BTreeMap, fmt, path}; use std::{collections::BTreeMap, fmt, path};
use crate::devices; use crate::devices;
@ -18,7 +17,7 @@ pub struct Directory {
/// ID of parent storage. /// ID of parent storage.
parent: String, parent: String,
/// Relative path to the parent storage. /// Relative path to the parent storage.
relative_path: Vec<String>, relative_path: path::PathBuf,
pub notes: String, pub notes: String,
/// [`devices::Device`] name and localinfo pairs. /// [`devices::Device`] name and localinfo pairs.
local_infos: BTreeMap<String, LocalInfo>, local_infos: BTreeMap<String, LocalInfo>,
@ -35,19 +34,14 @@ impl Directory {
relative_path: path::PathBuf, relative_path: path::PathBuf,
notes: String, notes: String,
local_infos: BTreeMap<String, LocalInfo>, local_infos: BTreeMap<String, LocalInfo>,
) -> Result<Directory> { ) -> Directory {
let relative_path = relative_path Directory {
.components()
.map(|c| c.as_os_str().to_str().map(|s| s.to_owned()))
.collect::<Option<Vec<_>>>()
.context("Path contains non-utf8 character")?;
Ok(Directory {
name, name,
parent, parent,
relative_path, relative_path,
notes, notes,
local_infos, local_infos,
}) }
} }
pub fn try_from_device_path( pub fn try_from_device_path(
@ -62,23 +56,23 @@ impl Directory {
.context("Failed to compare diff of paths")?; .context("Failed to compare diff of paths")?;
trace!("Selected parent: {}", parent.name()); trace!("Selected parent: {}", parent.name());
let local_info = LocalInfo::new(alias, path); let local_info = LocalInfo::new(alias, path);
Directory::new( Ok(Directory::new(
name, name,
parent.name().to_string(), parent.name().to_string(),
diff_path, diff_path,
notes, notes,
BTreeMap::from([(device.name(), local_info)]), BTreeMap::from([(device.name(), local_info)]),
) ))
} }
pub fn update_note(self, notes: String) -> Directory { pub fn update_note(self, notes: String) -> Directory {
Directory { Directory::new(
name: self.name, self.name,
parent: self.parent, self.parent,
relative_path: self.relative_path, self.relative_path,
notes, notes,
local_infos: self.local_infos, self.local_infos,
} )
} }
/// Resolve mount path of directory with current device. /// Resolve mount path of directory with current device.
@ -88,7 +82,7 @@ impl Directory {
.context("Can't find parent storage")? .context("Can't find parent storage")?
.mount_path(device) .mount_path(device)
.context("Can't find mount path")?; .context("Can't find mount path")?;
Ok(parent_mount_path.join(self.relative_path.clone().iter().collect::<PathBuf>())) Ok(parent_mount_path.join(self.relative_path.clone()))
} }
} }
@ -127,7 +121,7 @@ impl StorageExt for Directory {
} }
// Get parent `&Storage` of directory. // Get parent `&Storage` of directory.
fn parent<'a>(&'a self, storages: &'a Storages) -> Option<&'a Storage> { fn parent<'a>(&'a self, storages: &'a Storages) -> Option<&Storage> {
storages.get(&self.parent) storages.get(&self.parent)
} }
} }
@ -139,7 +133,7 @@ impl fmt::Display for Directory {
"S {name:<10} < {parent:<10}{relative_path:<10} : {notes}", "S {name:<10} < {parent:<10}{relative_path:<10} : {notes}",
name = self.name(), name = self.name(),
parent = self.parent, parent = self.parent,
relative_path = self.relative_path.iter().collect::<PathBuf>().display(), relative_path = self.relative_path.display(),
notes = self.notes, notes = self.notes,
) )
} }
@ -183,8 +177,7 @@ mod test {
"subdir".into(), "subdir".into(),
"some note".to_string(), "some note".to_string(),
local_infos, local_infos,
) );
.unwrap();
let mut storages = Storages::new(); let mut storages = Storages::new();
storages.add(storages::Storage::Physical(physical)).unwrap(); storages.add(storages::Storage::Physical(physical)).unwrap();
storages.add(Storage::SubDirectory(directory)).unwrap(); storages.add(Storage::SubDirectory(directory)).unwrap();

View file

@ -1,7 +1,6 @@
use std::path::{self, PathBuf}; use std::path::{self, PathBuf};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use chrono::TimeDelta;
use console::Style; use console::Style;
use crate::{ use crate::{
@ -21,10 +20,7 @@ pub fn min_parent_storage<'a>(
.filter_map(|(k, storage)| { .filter_map(|(k, storage)| {
let storage_path = storage.mount_path(device)?; let storage_path = storage.mount_path(device)?;
let diff = pathdiff::diff_paths(path, storage_path)?; let diff = pathdiff::diff_paths(path, storage_path)?;
if diff if diff.components().any(|c| c == path::Component::ParentDir) {
.components()
.any(|c| matches!(c, path::Component::ParentDir | path::Component::Prefix(_)))
{
None None
} else { } else {
Some((k, diff)) Some((k, diff))
@ -63,17 +59,6 @@ pub fn format_summarized_duration(dt: chrono::Duration) -> String {
} }
} }
pub fn duration_style(time: TimeDelta) -> Style {
match time {
x if x < TimeDelta::days(7) => Style::new().green(),
x if x < TimeDelta::days(14) => Style::new().yellow(),
x if x < TimeDelta::days(28) => Style::new().magenta(),
x if x < TimeDelta::days(28 * 3) => Style::new().red(),
x if x < TimeDelta::days(180) => Style::new().red().bold(),
_ => Style::new().on_red().black(),
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use anyhow::Result; use anyhow::Result;

View file

@ -145,7 +145,7 @@ mod integrated_test {
// bare-repo // bare-repo
let bare_repo_dir = assert_fs::TempDir::new()?; let bare_repo_dir = assert_fs::TempDir::new()?;
let _bare_repo = Repository::init_bare(&bare_repo_dir)?; let bare_repo = Repository::init_bare(&bare_repo_dir)?;
// push to bare repository // push to bare repository
let repo_1 = Repository::open(&config_dir_1)?; let repo_1 = Repository::open(&config_dir_1)?;
let upstream_name = "remote"; let upstream_name = "remote";
@ -216,8 +216,6 @@ mod integrated_test {
#[test] #[test]
fn two_devices() -> Result<()> { fn two_devices() -> Result<()> {
// 1st device // 1st device
//
// devices: first
let config_dir_1 = assert_fs::TempDir::new()?; let config_dir_1 = assert_fs::TempDir::new()?;
setup_gitconfig()?; setup_gitconfig()?;
let mut cmd1 = Command::cargo_bin("xdbm")?; let mut cmd1 = Command::cargo_bin("xdbm")?;
@ -229,7 +227,7 @@ mod integrated_test {
// bare-repo // bare-repo
let bare_repo_dir = assert_fs::TempDir::new()?; let bare_repo_dir = assert_fs::TempDir::new()?;
let _bare_repo = Repository::init_bare(&bare_repo_dir)?; let bare_repo = Repository::init_bare(&bare_repo_dir)?;
// push to bare repository // push to bare repository
let repo_1 = Repository::open(&config_dir_1)?; let repo_1 = Repository::open(&config_dir_1)?;
let upstream_name = "remote"; let upstream_name = "remote";
@ -250,8 +248,6 @@ mod integrated_test {
))?; ))?;
// 2nd device // 2nd device
//
// devices: first, second
let config_dir_2 = assert_fs::TempDir::new()?; let config_dir_2 = assert_fs::TempDir::new()?;
let mut cmd2 = Command::cargo_bin("xdbm")?; let mut cmd2 = Command::cargo_bin("xdbm")?;
cmd2.arg("-c") cmd2.arg("-c")
@ -275,7 +271,6 @@ mod integrated_test {
assert!(config_dir_2.join("backups").join("first.yml").exists()); assert!(config_dir_2.join("backups").join("first.yml").exists());
assert!(config_dir_2.join("backups").join("second.yml").exists()); assert!(config_dir_2.join("backups").join("second.yml").exists());
// sync
std::process::Command::new("git") std::process::Command::new("git")
.arg("push") .arg("push")
.current_dir(&config_dir_2) .current_dir(&config_dir_2)
@ -292,11 +287,6 @@ mod integrated_test {
.success(); .success();
// Add storage // Add storage
//
// devices: first, second
// storages:
// - gdrive @ sample_storage (online)
// - first: sample_storage
let sample_storage = assert_fs::TempDir::new()?; let sample_storage = assert_fs::TempDir::new()?;
let mut cmd_add_storage_1 = Command::cargo_bin("xdbm")?; let mut cmd_add_storage_1 = Command::cargo_bin("xdbm")?;
cmd_add_storage_1 cmd_add_storage_1
@ -318,13 +308,6 @@ mod integrated_test {
.success() .success()
.stdout(predicate::str::contains("")); .stdout(predicate::str::contains(""));
// Add storage (directory) // Add storage (directory)
//
// devices: first, second
// storages:
// - gdrive (online)
// - first: sample_storage
// - gdrive_docs (subdir of sample_storage/foo/bar)
// - first
let sample_directory = &sample_storage.join("foo").join("bar"); let sample_directory = &sample_storage.join("foo").join("bar");
DirBuilder::new().recursive(true).create(sample_directory)?; DirBuilder::new().recursive(true).create(sample_directory)?;
Command::cargo_bin("xdbm")? Command::cargo_bin("xdbm")?
@ -356,14 +339,6 @@ mod integrated_test {
.success(); .success();
// bind // bind
//
// devices: first, second
// storages:
// - gdrive (online)
// - first: sample_storage
// - gdrive_docs (subdir of sample_storage/foo/bar)
// - first
// - second: sample_directory
Command::cargo_bin("xdbm")? Command::cargo_bin("xdbm")?
.arg("-c") .arg("-c")
.arg(config_dir_2.path()) .arg(config_dir_2.path())
@ -379,16 +354,6 @@ mod integrated_test {
.stdout(predicate::str::contains("")); .stdout(predicate::str::contains(""));
// storage 3 // storage 3
//
// devices: first, second
// storages:
// - gdrive (online)
// - first: sample_storage
// - gdrive_docs (subdir of sample_storage/foo/bar)
// - first
// - second: sample_directory
// - nas (online)
// - second: sample_storage_2
let sample_storage_2 = assert_fs::TempDir::new()?; let sample_storage_2 = assert_fs::TempDir::new()?;
Command::cargo_bin("xdbm")? Command::cargo_bin("xdbm")?
.arg("-c") .arg("-c")
@ -419,19 +384,6 @@ mod integrated_test {
.stdout(predicate::str::contains("gdrive_docs").and(predicate::str::contains("nas"))); .stdout(predicate::str::contains("gdrive_docs").and(predicate::str::contains("nas")));
// backup add // backup add
//
// devices: first, second
// storages:
// - gdrive (online)
// - first: sample_storage
// - gdrive_docs (subdir of sample_storage/foo/bar)
// - first
// - second: sample_directory
// - nas (online)
// - second: sample_storage_2
// backups:
// - foodoc: second
// - sample_storage_2/foo/bar -> sample_directory/docs
let backup_src = &sample_storage_2.join("foo").join("bar"); let backup_src = &sample_storage_2.join("foo").join("bar");
DirBuilder::new().recursive(true).create(backup_src)?; DirBuilder::new().recursive(true).create(backup_src)?;
let backup_dest = &sample_directory.join("docs"); let backup_dest = &sample_directory.join("docs");
@ -486,19 +438,6 @@ mod integrated_test {
); );
// backup done // backup done
//
// devices: first, second
// storages:
// - gdrive (online)
// - first: sample_storage
// - gdrive_docs (subdir of sample_storage/foo/bar)
// - first
// - second: sample_directory
// - nas (online)
// - second: sample_storage_2
// backups:
// - foodoc: second
// - sample_storage_2/foo/bar -> sample_directory/docs (done 1)
Command::cargo_bin("xdbm")? Command::cargo_bin("xdbm")?
.arg("-c") .arg("-c")
.arg(config_dir_2.path()) .arg(config_dir_2.path())
@ -509,274 +448,6 @@ mod integrated_test {
.assert() .assert()
.success(); .success();
// backup list after backup done
Command::cargo_bin("xdbm")?
.arg("-c")
.arg(config_dir_2.path())
.arg("backup")
.arg("list")
.assert()
.success()
.stdout(
predicate::str::contains("foodoc")
.and(predicate::str::contains("nas"))
.and(predicate::str::contains("gdrive_docs"))
.and(predicate::str::contains("---").not()),
);
// status
Command::cargo_bin("xdbm")?
.arg("-c")
.arg(config_dir_2.path())
.arg("status")
.assert()
.success();
Command::cargo_bin("xdbm")?
.arg("-c")
.arg(config_dir_2.path())
.arg("status")
.arg("-s")
.arg(backup_src.clone().join("foo"))
.assert()
.success()
.stdout(predicate::str::contains("nas").and(predicate::str::contains("foodoc").not()));
Command::cargo_bin("xdbm")?
.arg("-c")
.arg(config_dir_2.path())
.arg("status")
.arg("-sb")
.arg(backup_src.clone().join("foo"))
.assert()
.success()
.stdout(
predicate::str::contains("nas")
.and(predicate::str::contains("second"))
.and(predicate::str::contains("foodoc")),
);
Command::cargo_bin("xdbm")?
.arg("-c")
.arg(config_dir_2.path())
.arg("status")
.arg("-sb")
.arg(backup_src.clone().parent().unwrap())
.assert()
.success()
.stdout(
predicate::str::contains("nas")
.and(predicate::str::contains("second").not())
.and(predicate::str::contains("foodoc").not()),
);
std::process::Command::new("git")
.arg("push")
.current_dir(&config_dir_2)
.assert()
.success();
std::process::Command::new("git")
.arg("pull")
.current_dir(&config_dir_1)
.assert()
.success();
// bind
//
// devices: first, second
// storages:
// - gdrive (online)
// - first: sample_storage
// - gdrive_docs (subdir of sample_storage/foo/bar)
// - first
// - second: sample_directory
// - nas (online)
// - first: sample_storage_2_first_path
// - second: sample_storage_2
// backups:
// - foodoc: second
// - sample_storage_2/foo/bar -> sample_directory/docs (done 1)
let sample_storage_2_first_path = assert_fs::TempDir::new()?;
Command::cargo_bin("xdbm")?
.arg("-c")
.arg(config_dir_1.path())
.arg("storage")
.arg("bind")
.arg("--alias")
.arg("sample2")
.arg("--path")
.arg(sample_storage_2_first_path.path())
.arg("nas")
.assert()
.success()
.stdout(predicate::str::contains(""));
// backup add
//
// devices: first, second
// storages:
// - gdrive (online)
// - first: sample_storage
// - gdrive_docs (subdir of sample_storage/foo/bar)
// - first
// - second: sample_directory
// - nas (online)
// - first: sample_storage_2_first_path
// - second: sample_storage_2
// backups:
// - foodoc: second
// - sample_storage_2/foo/bar -> sample_directory/docs (done 1)
// - abcdbackup: first
// - sample_storage_2_first_path/abcd/efgh -> sample_storage/Downloads/abcd/efgh
let backup_src = &sample_storage_2_first_path.join("abcd").join("efgh");
DirBuilder::new().recursive(true).create(backup_src)?;
let backup_dest = &sample_storage.join("Downloads").join("abcd").join("efgh");
DirBuilder::new().recursive(true).create(backup_dest)?;
Command::cargo_bin("xdbm")?
.arg("-c")
.arg(config_dir_1.path())
.arg("backup")
.arg("add")
.arg("--src")
.arg(backup_src)
.arg("--dest")
.arg(backup_dest)
.arg("abcdbackup")
.arg("external")
.arg("rsync")
.arg("note: nonsense")
.assert()
.success();
// backup add
//
// devices: first, second
// storages:
// - gdrive (online)
// - first: sample_storage
// - gdrive_docs (subdir of sample_storage/foo/bar)
// - first
// - second: sample_directory
// - nas (online)
// - first: sample_storage_2_first_path
// - second: sample_storage_2
// backups:
// - foodoc: second
// - sample_storage_2/foo/bar -> sample_directory/docs (done 1)
// - abcdbackup: first
// - sample_storage_2_first_path/abcd/efgh -> sample_storage/Downloads/abcd/efgh
// - abcdsubbackup: first
// - sample_storage_2_first_path/abcd/efgh/sub -> sample_storage/Downloads/abcd/efgh/sub
let backup_src = &sample_storage_2_first_path
.join("abcd")
.join("efgh")
.join("sub");
DirBuilder::new().recursive(true).create(backup_src)?;
let backup_dest = &sample_storage
.join("Downloads")
.join("abcd")
.join("efgh")
.join("sub");
DirBuilder::new().recursive(true).create(backup_dest)?;
Command::cargo_bin("xdbm")?
.arg("-c")
.arg(config_dir_1.path())
.arg("backup")
.arg("add")
.arg("--src")
.arg(backup_src)
.arg("--dest")
.arg(backup_dest)
.arg("abcdsubbackup")
.arg("external")
.arg("rsync")
.arg("note: only subdirectory")
.assert()
.success();
std::process::Command::new("git")
.arg("push")
.current_dir(&config_dir_1)
.assert()
.success();
std::process::Command::new("git")
.arg("pull")
.current_dir(&config_dir_2)
.assert()
.success();
// backup add
//
// devices: first, second
// storages:
// - gdrive (online)
// - first: sample_storage
// - gdrive_docs (subdir of sample_storage/foo/bar)
// - first
// - second: sample_directory
// - nas (online)
// - first: sample_storage_2_first_path
// - second: sample_storage_2
// backups:
// - foodoc: second
// - sample_storage_2/foo/bar -> sample_directory/docs (done 1)
// - abcdbackup: first
// - sample_storage_2_first_path/abcd/efgh -> sample_storage/Downloads/abcd/efgh
// - abcdsubbackup: first
// - sample_storage_2_first_path/abcd/efgh/sub -> sample_storage/Downloads/abcd/efgh/sub
// - abcdbackup2: second
// - sample_storage_2/abcd/efgh -> sample_directory/Downloads/abcd/efgh
let backup_src = &sample_storage_2.join("abcd").join("efgh");
DirBuilder::new().recursive(true).create(backup_src)?;
let backup_dest = &sample_directory.join("Downloads").join("abcd").join("efgh");
DirBuilder::new().recursive(true).create(backup_dest)?;
Command::cargo_bin("xdbm")?
.arg("-c")
.arg(config_dir_2.path())
.arg("backup")
.arg("add")
.arg("--src")
.arg(backup_src)
.arg("--dest")
.arg(backup_dest)
.arg("abcdbackup2")
.arg("external")
.arg("rsync")
.arg("note: only subdirectory")
.assert()
.success();
// status
Command::cargo_bin("xdbm")?
.arg("-c")
.arg(config_dir_2.path())
.arg("status")
.arg("-sb")
.arg(backup_src)
.assert()
.success()
.stdout(
predicate::str::contains("nas")
.and(predicate::str::contains("first"))
.and(predicate::str::contains("abcdbackup"))
.and(predicate::str::contains("abcdsubbackup").not())
.and(predicate::str::contains("second"))
.and(predicate::str::contains("abcdbackup2")),
);
Command::cargo_bin("xdbm")?
.arg("-c")
.arg(config_dir_2.path())
.arg("status")
.arg("-sb")
.arg(backup_src.join("sub"))
.assert()
.success()
.stdout(
predicate::str::contains("nas")
.and(predicate::str::contains("first"))
.and(predicate::str::contains("abcdbackup"))
.and(predicate::str::contains("abcdsubbackup"))
.and(predicate::str::contains("second"))
.and(predicate::str::contains("abcdbackup2")),
);
Ok(()) Ok(())
} }
} }