mirror of
https://github.com/qwjyh/xdbm
synced 2024-11-21 22:30:12 +09:00
add backup add
- change Storage::parent - split path diff calc to util - test for backup add
This commit is contained in:
parent
41b2924ad7
commit
905d392419
13 changed files with 387 additions and 63 deletions
|
@ -18,7 +18,7 @@
|
|||
- [x] use subcommand
|
||||
- [ ] backup subcommands
|
||||
- [ ] backup add
|
||||
- [ ] test for backup add
|
||||
- [?] test for backup add
|
||||
- [ ] backup list
|
||||
- [ ] backup done
|
||||
- [ ] no commit option
|
||||
|
|
125
src/backups.rs
125
src/backups.rs
|
@ -1,17 +1,47 @@
|
|||
use std::path::PathBuf;
|
||||
use core::panic;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs, io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use chrono::{DateTime, Local};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::storages::Storage;
|
||||
use crate::{
|
||||
devices::Device,
|
||||
storages::{self, Storage},
|
||||
};
|
||||
|
||||
/// Directory to store backup configs for each devices.
|
||||
pub const BACKUPSDIR: &str = "backups";
|
||||
|
||||
/// File to store backups for the `device`.
|
||||
/// Relative path from the config directory.
|
||||
pub fn backups_file(device: &Device) -> PathBuf {
|
||||
PathBuf::from(BACKUPSDIR).join(format!("{}.yml", device.name()))
|
||||
}
|
||||
|
||||
/// Targets for backup source or destination.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct BackupTarget {
|
||||
storage: Storage,
|
||||
/// `name()` of [`Storage`].
|
||||
/// Use `String` for serialization/deserialization.
|
||||
storage: String,
|
||||
/// Relative path to the `storage`.
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl BackupTarget {
|
||||
pub fn new(storage_name: String, relative_path: PathBuf) -> Self {
|
||||
BackupTarget {
|
||||
storage: storage_name,
|
||||
path: relative_path,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Type of backup commands.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum BackupCommand {
|
||||
|
@ -26,6 +56,12 @@ pub struct ExternallyInvoked {
|
|||
pub note: String,
|
||||
}
|
||||
|
||||
impl ExternallyInvoked {
|
||||
pub fn new(name: String, note: String) -> Self {
|
||||
ExternallyInvoked { name, note }
|
||||
}
|
||||
}
|
||||
|
||||
/// Backup execution log.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct BackupLog {
|
||||
|
@ -53,3 +89,86 @@ pub struct Backup {
|
|||
command: BackupCommand,
|
||||
logs: Vec<BackupLog>,
|
||||
}
|
||||
|
||||
impl Backup {
|
||||
/// With empty logs.
|
||||
pub fn new(
|
||||
name: String,
|
||||
device_name: String,
|
||||
from: BackupTarget,
|
||||
to: BackupTarget,
|
||||
command: BackupCommand,
|
||||
) -> Self {
|
||||
Backup {
|
||||
name,
|
||||
device: device_name,
|
||||
from,
|
||||
to,
|
||||
command,
|
||||
logs: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &String {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Backups {
|
||||
pub list: HashMap<String, Backup>,
|
||||
}
|
||||
|
||||
impl Backups {
|
||||
/// Empty [`Backups`].
|
||||
pub fn new() -> Backups {
|
||||
Backups {
|
||||
list: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &String) -> Option<&Backup> {
|
||||
self.list.get(name)
|
||||
}
|
||||
|
||||
/// Add new [`Backup`].
|
||||
/// New `backup` must has new unique name.
|
||||
pub fn add(&mut self, backup: Backup) -> Result<()> {
|
||||
if self.list.keys().any(|name| name == &backup.name) {
|
||||
return Err(anyhow::anyhow!(format!(
|
||||
"Backup with name {} already exists",
|
||||
backup.name
|
||||
)));
|
||||
}
|
||||
match self.list.insert(backup.name.clone(), backup) {
|
||||
Some(v) => {
|
||||
error!("Inserted backup with existing name: {}", v.name);
|
||||
panic!("unexpected behavior (unreachable)")
|
||||
}
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(config_dir: &Path, device: &Device) -> Result<Backups> {
|
||||
let backups_file = config_dir.join(backups_file(device));
|
||||
if !backups_file.exists() {
|
||||
return Err(anyhow!("Couldn't find backups file: {:?}", backups_file));
|
||||
}
|
||||
trace!("Reading {}", backups_file.display());
|
||||
let f = fs::File::open(backups_file)?;
|
||||
let reader = io::BufReader::new(f);
|
||||
let yaml: Backups =
|
||||
serde_yaml::from_reader(reader).context("Failed to parse backups file")?;
|
||||
Ok(yaml)
|
||||
}
|
||||
|
||||
pub fn write(self, config_dir: &Path, device: &Device) -> Result<()> {
|
||||
let f = fs::File::create(config_dir.join(backups_file(device)))
|
||||
.context("Failed to open backups file")?;
|
||||
let writer = io::BufWriter::new(f);
|
||||
serde_yaml::to_writer(writer, &self).context(format!(
|
||||
"Failed writing to {}",
|
||||
config_dir.join(backups_file(device)).display()
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,6 +86,10 @@ pub(crate) enum StorageCommands {
|
|||
#[arg(short, long)]
|
||||
path: path::PathBuf,
|
||||
},
|
||||
// /// Remove storage from the storage list
|
||||
// Remove {
|
||||
// storage: String,
|
||||
// }
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
|
|
81
src/cmd_backup.rs
Normal file
81
src/cmd_backup.rs
Normal file
|
@ -0,0 +1,81 @@
|
|||
use std::{io::stdout, path::{Path, PathBuf}};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use git2::Repository;
|
||||
|
||||
use crate::{
|
||||
add_and_commit,
|
||||
backups::{self, Backup, BackupCommand, BackupTarget, Backups, ExternallyInvoked},
|
||||
cmd_args::BackupAddCommands,
|
||||
devices::{self, Device},
|
||||
storages::{StorageExt, Storages},
|
||||
util,
|
||||
};
|
||||
|
||||
pub(crate) fn cmd_backup_add(
|
||||
name: String,
|
||||
src: PathBuf,
|
||||
dest: PathBuf,
|
||||
cmd: BackupAddCommands,
|
||||
repo: Repository,
|
||||
config_dir: &PathBuf,
|
||||
storages: &Storages,
|
||||
) -> Result<()> {
|
||||
let device = devices::get_device(&config_dir)?;
|
||||
let new_backup = new_backup(name, src, dest, cmd, &device, storages)?;
|
||||
let new_backup_name = new_backup.name().clone();
|
||||
let mut backups = Backups::read(&config_dir, &device)?;
|
||||
println!("Backup config:");
|
||||
serde_yaml::to_writer(stdout(), &new_backup)?;
|
||||
backups.add(new_backup)?;
|
||||
backups.write(&config_dir, &device)?;
|
||||
|
||||
add_and_commit(
|
||||
&repo,
|
||||
&backups::backups_file(&device),
|
||||
&format!("Add new backup: {}", new_backup_name),
|
||||
)?;
|
||||
|
||||
println!("Added new backup.");
|
||||
trace!("Finished adding backup");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn new_backup(
|
||||
name: String,
|
||||
src: PathBuf,
|
||||
dest: PathBuf,
|
||||
cmd: BackupAddCommands,
|
||||
device: &Device,
|
||||
storages: &Storages,
|
||||
) -> Result<Backup> {
|
||||
let (src_parent, src_diff) =
|
||||
util::min_parent_storage(&src, &storages, &device).context(format!(
|
||||
"Coundn't find parent storage for src directory {}",
|
||||
src.display()
|
||||
))?;
|
||||
let (dest_parent, dest_diff) =
|
||||
util::min_parent_storage(&dest, &storages, &device).context(format!(
|
||||
"Couldn't find parent storage for dest directory: {}",
|
||||
dest.display()
|
||||
))?;
|
||||
let src_target = BackupTarget::new(src_parent.name().to_string(), src_diff);
|
||||
trace!("Backup source target: {:?}", src_target);
|
||||
let dest_target = BackupTarget::new(dest_parent.name().to_string(), dest_diff);
|
||||
trace!("Backup destination target: {:?}", dest_target);
|
||||
|
||||
let command: BackupCommand = match cmd {
|
||||
BackupAddCommands::External { name, note } => {
|
||||
BackupCommand::ExternallyInvoked(ExternallyInvoked::new(name, note))
|
||||
}
|
||||
};
|
||||
trace!("Backup command: {:?}", command);
|
||||
|
||||
Ok(Backup::new(
|
||||
name,
|
||||
device.name(),
|
||||
src_target,
|
||||
dest_target,
|
||||
command,
|
||||
))
|
||||
}
|
|
@ -1,14 +1,16 @@
|
|||
//! Init subcommand.
|
||||
//! Initialize xdbm for the device.
|
||||
|
||||
use crate::backups::{backups_file, Backups};
|
||||
use crate::storages::{Storages, STORAGESFILE};
|
||||
use crate::{add_and_commit, full_status, get_devices, write_devices, Device, DEVICESFILE};
|
||||
use crate::{
|
||||
add_and_commit, backups, full_status, get_devices, write_devices, Device, DEVICESFILE,
|
||||
};
|
||||
use anyhow::{anyhow, Context, Ok, Result};
|
||||
use core::panic;
|
||||
use git2::{Cred, RemoteCallbacks, Repository};
|
||||
use inquire::Password;
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::fs::{DirBuilder, File};
|
||||
use std::io::{BufWriter, Write};
|
||||
use std::path::{self, Path, PathBuf};
|
||||
|
||||
|
@ -128,6 +130,9 @@ pub(crate) fn cmd_init(
|
|||
&format!("Initialize {}", STORAGESFILE),
|
||||
)?;
|
||||
|
||||
// set up directory for backups
|
||||
DirBuilder::new().create(&config_dir.join(backups::BACKUPSDIR))?;
|
||||
|
||||
repo
|
||||
}
|
||||
};
|
||||
|
@ -153,6 +158,8 @@ pub(crate) fn cmd_init(
|
|||
let mut devices: Vec<Device> = get_devices(&config_dir)?;
|
||||
trace!("devices: {:?}", devices);
|
||||
if devices.iter().any(|x| x.name() == device.name()) {
|
||||
error!("Device name `{}` is already used.", device.name());
|
||||
error!("Clear the config directory and try again with different name");
|
||||
return Err(anyhow!("device name is already used."));
|
||||
}
|
||||
devices.push(device.clone());
|
||||
|
@ -167,6 +174,18 @@ pub(crate) fn cmd_init(
|
|||
&Path::new(DEVICESFILE),
|
||||
&format!("Add new device: {}", &device.name()),
|
||||
)?;
|
||||
|
||||
// backups/[device].yml
|
||||
{
|
||||
let backups = Backups::new();
|
||||
backups.write(&config_dir, &device)?;
|
||||
}
|
||||
add_and_commit(
|
||||
&repo,
|
||||
&backups::backups_file(&device),
|
||||
&format!("Add new backups for device: {}", &device.name()),
|
||||
)?;
|
||||
|
||||
println!("Device added");
|
||||
full_status(&repo)?;
|
||||
Ok(())
|
||||
|
|
|
@ -1,17 +1,14 @@
|
|||
//! Storage subcommands.
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io::{self, Write},
|
||||
path::{Path, PathBuf},
|
||||
string,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use byte_unit::Byte;
|
||||
use clap::{error::ErrorKind, CommandFactory};
|
||||
use git2::Repository;
|
||||
use inquire::{min_length, Confirm, CustomType, Select, Text};
|
||||
use inquire::{Confirm, CustomType, Text};
|
||||
use unicode_width::{self, UnicodeWidthStr};
|
||||
|
||||
use crate::{
|
||||
|
@ -215,7 +212,7 @@ fn write_storages_list(
|
|||
|v| v.display().to_string(),
|
||||
);
|
||||
let parent_name = if let Storage::SubDirectory(s) = storage {
|
||||
s.parent(&storages)?
|
||||
s.parent(&storages)
|
||||
.context(format!("Failed to get parent of storage {}", s))?
|
||||
.name()
|
||||
} else {
|
||||
|
|
26
src/main.rs
26
src/main.rs
|
@ -19,7 +19,7 @@ use std::path::Path;
|
|||
use std::path::{self, PathBuf};
|
||||
use storages::Storages;
|
||||
|
||||
use crate::cmd_args::{Cli, Commands, StorageCommands};
|
||||
use crate::cmd_args::{BackupSubCommands, Cli, Commands, StorageCommands};
|
||||
use crate::storages::{
|
||||
directory, local_info, online_storage, physical_drive_partition, Storage, StorageExt,
|
||||
StorageType, STORAGESFILE,
|
||||
|
@ -28,12 +28,14 @@ use devices::{Device, DEVICESFILE, *};
|
|||
|
||||
mod backups;
|
||||
mod cmd_args;
|
||||
mod cmd_backup;
|
||||
mod cmd_init;
|
||||
mod cmd_storage;
|
||||
mod cmd_sync;
|
||||
mod devices;
|
||||
mod inquire_filepath_completer;
|
||||
mod storages;
|
||||
mod util;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
@ -93,7 +95,27 @@ fn main() -> Result<()> {
|
|||
let _storages = Storages::read(&config_dir)?;
|
||||
todo!()
|
||||
}
|
||||
Commands::Backup(_) => todo!(),
|
||||
Commands::Backup(backup) => {
|
||||
trace!("backup subcommand with args: {:?}", backup);
|
||||
let repo = Repository::open(&config_dir).context(
|
||||
"Repository doesn't exist on the config path. Please run init to initialize the repository.",
|
||||
)?;
|
||||
let storages = Storages::read(&config_dir)?;
|
||||
match backup {
|
||||
BackupSubCommands::Add {
|
||||
name,
|
||||
src,
|
||||
dest,
|
||||
cmd,
|
||||
} => cmd_backup::cmd_backup_add(name, src, dest, cmd, repo, &config_dir, &storages)?,
|
||||
BackupSubCommands::List {} => todo!(),
|
||||
BackupSubCommands::Done {
|
||||
name,
|
||||
exit_status,
|
||||
log,
|
||||
} => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
full_status(&Repository::open(&config_dir)?)?;
|
||||
Ok(())
|
||||
|
|
|
@ -97,7 +97,7 @@ impl StorageExt for Storage {
|
|||
}
|
||||
}
|
||||
|
||||
fn parent<'a>(&'a self, storages: &'a Storages) -> Result<Option<&Storage>> {
|
||||
fn parent<'a>(&'a self, storages: &'a Storages) -> Option<&'a Storage> {
|
||||
match self {
|
||||
Storage::PhysicalStorage(s) => s.parent(storages),
|
||||
Storage::SubDirectory(s) => s.parent(storages),
|
||||
|
@ -146,7 +146,7 @@ pub trait StorageExt {
|
|||
) -> Result<()>;
|
||||
|
||||
/// Get parent
|
||||
fn parent<'a>(&'a self, storages: &'a Storages) -> Result<Option<&Storage>>;
|
||||
fn parent<'a>(&'a self, storages: &'a Storages) -> Option<&Storage>;
|
||||
}
|
||||
|
||||
pub mod directory;
|
||||
|
@ -196,7 +196,6 @@ impl Storages {
|
|||
// dependency check
|
||||
if self.list.iter().any(|(_k, v)| {
|
||||
v.parent(&self)
|
||||
.unwrap()
|
||||
.is_some_and(|parent| parent.name() == storage.name())
|
||||
}) {
|
||||
return Err(anyhow!(
|
||||
|
|
|
@ -9,6 +9,7 @@ use std::{
|
|||
};
|
||||
|
||||
use crate::devices;
|
||||
use crate::util;
|
||||
|
||||
use super::{local_info::LocalInfo, Storage, StorageExt, Storages};
|
||||
|
||||
|
@ -55,29 +56,13 @@ impl Directory {
|
|||
device: &devices::Device,
|
||||
storages: &Storages,
|
||||
) -> Result<Directory> {
|
||||
let (parent_name, diff_path) = storages
|
||||
.list
|
||||
.iter()
|
||||
.filter(|(_k, v)| v.has_alias(&device))
|
||||
.filter_map(|(k, v)| {
|
||||
let diff = pathdiff::diff_paths(&path, v.mount_path(&device, &storages).unwrap())?;
|
||||
trace!("Pathdiff: {:?}", diff);
|
||||
if diff.components().any(|c| c == path::Component::ParentDir) {
|
||||
None
|
||||
} else {
|
||||
Some((k, diff))
|
||||
}
|
||||
})
|
||||
.min_by_key(|(_k, v)| {
|
||||
let diff_paths: Vec<path::Component<'_>> = v.components().collect();
|
||||
diff_paths.len()
|
||||
})
|
||||
.context(format!("Failed to compare diff of paths"))?;
|
||||
trace!("Selected parent: {}", parent_name);
|
||||
let (parent, diff_path) = util::min_parent_storage(&path, storages, &device)
|
||||
.context("Failed to compare diff of paths")?;
|
||||
trace!("Selected parent: {}", parent.name());
|
||||
let local_info = LocalInfo::new(alias, path);
|
||||
Ok(Directory::new(
|
||||
name,
|
||||
parent_name.clone(),
|
||||
parent.name().to_string(),
|
||||
diff_path,
|
||||
notes,
|
||||
HashMap::from([(device.name(), local_info)]),
|
||||
|
@ -97,7 +82,7 @@ impl Directory {
|
|||
/// Resolve mount path of directory with current device.
|
||||
fn mount_path(&self, device: &devices::Device, storages: &Storages) -> Result<path::PathBuf> {
|
||||
let parent_mount_path = self
|
||||
.parent(&storages)?
|
||||
.parent(&storages)
|
||||
.context("Can't find parent storage")?
|
||||
.mount_path(&device, &storages)?;
|
||||
Ok(parent_mount_path.join(self.relative_path.clone()))
|
||||
|
@ -117,12 +102,12 @@ impl StorageExt for Directory {
|
|||
self.local_infos.get(&device.name())
|
||||
}
|
||||
|
||||
fn mount_path(
|
||||
&self,
|
||||
device: &devices::Device,
|
||||
storages: &Storages,
|
||||
) -> Result<path::PathBuf> {
|
||||
Ok(self.mount_path(device, storages)?)
|
||||
fn mount_path(&self, device: &devices::Device, storages: &Storages) -> Result<path::PathBuf> {
|
||||
Ok(self
|
||||
.local_infos
|
||||
.get(&device.name())
|
||||
.context(format!("LocalInfo for storage: {} not found", &self.name()))?
|
||||
.mount_path())
|
||||
}
|
||||
|
||||
/// This method doesn't use `mount_path`.
|
||||
|
@ -141,14 +126,8 @@ impl StorageExt for Directory {
|
|||
}
|
||||
|
||||
// Get parent `&Storage` of directory.
|
||||
fn parent<'a>(&'a self, storages: &'a Storages) -> Result<Option<&Storage>> {
|
||||
match storages.get(&self.parent).context(format!(
|
||||
"No parent {} exists for directory {}",
|
||||
&self.parent, &self.name
|
||||
)) {
|
||||
Ok(s) => Ok(Some(s)),
|
||||
Err(e) => Err(anyhow!(e)),
|
||||
}
|
||||
fn parent<'a>(&'a self, storages: &'a Storages) -> Option<&Storage> {
|
||||
storages.get(&self.parent)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -205,10 +184,15 @@ mod test {
|
|||
local_infos,
|
||||
);
|
||||
let mut storages = Storages::new();
|
||||
storages.add(storages::Storage::PhysicalStorage(physical)).unwrap();
|
||||
storages
|
||||
.add(storages::Storage::PhysicalStorage(physical))
|
||||
.unwrap();
|
||||
storages.add(Storage::SubDirectory(directory)).unwrap();
|
||||
// assert_eq!(directory.name(), "test_name");
|
||||
assert_eq!(storages.get(&"test_name".to_string()).unwrap().name(), "test_name");
|
||||
assert_eq!(
|
||||
storages.get(&"test_name".to_string()).unwrap().name(),
|
||||
"test_name"
|
||||
);
|
||||
assert_eq!(
|
||||
storages
|
||||
.get(&"test_name".to_string())
|
||||
|
|
|
@ -11,7 +11,7 @@ use crate::devices;
|
|||
|
||||
use super::{
|
||||
local_info::{self, LocalInfo},
|
||||
StorageExt, Storages,
|
||||
Storage, StorageExt, Storages,
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
@ -88,11 +88,8 @@ impl StorageExt for OnlineStorage {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn parent(
|
||||
&self,
|
||||
storages: &Storages,
|
||||
) -> Result<Option<&super::Storage>> {
|
||||
Ok(None)
|
||||
fn parent(&self, storages: &Storages) -> Option<&Storage> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -165,8 +165,8 @@ impl StorageExt for PhysicalDrivePartition {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn parent(&self, storages: &Storages) -> Result<Option<&Storage>> {
|
||||
Ok(None)
|
||||
fn parent(&self, storages: &Storages) -> Option<&Storage> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
|
37
src/util.rs
Normal file
37
src/util.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
use std::path::{self, PathBuf};
|
||||
|
||||
use crate::{
|
||||
devices::Device,
|
||||
storages::{Storage, StorageExt, Storages},
|
||||
};
|
||||
|
||||
/// Find the closest parent storage from the `path`.
|
||||
pub fn min_parent_storage<'a>(
|
||||
path: &PathBuf,
|
||||
storages: &'a Storages,
|
||||
device: &'a Device,
|
||||
) -> Option<(&'a Storage, PathBuf)> {
|
||||
let (name, pathdiff) = storages
|
||||
.list
|
||||
.iter()
|
||||
.filter_map(|(k, storage)| {
|
||||
let storage_path = match storage.mount_path(device, storages) {
|
||||
Ok(path) => path,
|
||||
Err(_) => return None,
|
||||
};
|
||||
let diff = pathdiff::diff_paths(&path, storage_path)?;
|
||||
if diff.components().any(|c| c == path::Component::ParentDir) {
|
||||
None
|
||||
} else {
|
||||
Some((k, diff))
|
||||
}
|
||||
})
|
||||
.min_by_key(|(k, pathdiff)| {
|
||||
pathdiff
|
||||
.components()
|
||||
.collect::<Vec<path::Component>>()
|
||||
.len()
|
||||
})?;
|
||||
let storage = storages.get(name)?;
|
||||
Some((storage, pathdiff))
|
||||
}
|
67
tests/cli.rs
67
tests/cli.rs
|
@ -181,7 +181,7 @@ mod cmd_init {
|
|||
.success()
|
||||
.stdout(predicate::str::contains(""));
|
||||
// Add storage (directory)
|
||||
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)?;
|
||||
|
@ -228,6 +228,71 @@ mod cmd_init {
|
|||
.success()
|
||||
.stdout(predicate::str::contains(""));
|
||||
|
||||
// storage 3
|
||||
let sample_storage_2 = assert_fs::TempDir::new()?;
|
||||
Command::cargo_bin("xdbm")?
|
||||
.arg("-c")
|
||||
.arg(&config_dir_2.path())
|
||||
.arg("storage")
|
||||
.arg("add")
|
||||
.arg("online")
|
||||
.arg("--provider")
|
||||
.arg("me")
|
||||
.arg("--capacity")
|
||||
.arg("1000000000000")
|
||||
.arg("--alias")
|
||||
.arg("nas")
|
||||
.arg("nas")
|
||||
.arg(&sample_storage_2.path())
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
Command::cargo_bin("xdbm")?
|
||||
.arg("-c")
|
||||
.arg(&config_dir_2.path())
|
||||
.arg("storage")
|
||||
.arg("list")
|
||||
.arg("-l")
|
||||
.assert()
|
||||
.success();
|
||||
// backup add
|
||||
let backup_src = &sample_storage_2.join("foo").join("bar");
|
||||
DirBuilder::new().recursive(true).create(&backup_src)?;
|
||||
let backup_dest = &sample_directory.join("docs");
|
||||
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("foodoc")
|
||||
.arg("external")
|
||||
.arg("rsync")
|
||||
.arg("note: nonsense")
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
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("foodoc")
|
||||
.arg("external")
|
||||
.arg("rsync")
|
||||
.arg("note: nonsense")
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains("already"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue