Skip to content

Commit

Permalink
Merge pull request #6 from qboileau/visit-then-apply
Browse files Browse the repository at this point in the history
List operations before running them on filesystem
  • Loading branch information
qboileau authored Oct 3, 2018
2 parents 78879e3 + 8b8b855 commit adca477
Show file tree
Hide file tree
Showing 9 changed files with 895 additions and 91 deletions.
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ version = "0.1.0"
authors = ["qboileau <quentin.boileau@gmail.com>"]

[dependencies]
quicli = "0.3"
quicli = "0.3"
im = "11.0.1"
failure = "0.1.2"
failure_derive = "0.1.2"
53 changes: 53 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@

use failure::Fail;
use std::path::{Path, PathBuf};
use std::clone::Clone;
use std::fmt::*;
use std::ops::Deref;

#[derive(Fail, Debug, Clone)]
pub(crate) enum AppError {
#[fail(display = "Unable to stow {} to {} cause : {}", source, target, cause)]
StowPathError {
source: ErrorPath,
target: ErrorPath,
cause: String
},

#[fail(display = "An IO error append : {}", msg)]
IOError {
msg: String
},

#[fail(display = "Unable to apply stow because of previous errors")]
ApplyError
}

#[derive(Debug, Clone)]
pub struct ErrorPath { path: PathBuf }

impl Deref for ErrorPath {
type Target = PathBuf;

fn deref(&self) -> &PathBuf {
&self.path
}
}

impl Display for ErrorPath {
fn fmt(&self, f: &mut Formatter) -> Result {
write!(f, "{}", self.display())
}
}

impl From<PathBuf> for ErrorPath {
fn from(path: PathBuf) -> ErrorPath {
ErrorPath { path }
}
}

impl<'a> From<&'a Path> for ErrorPath {
fn from(path: &Path) -> Self {
ErrorPath { path: path.to_path_buf() }
}
}
49 changes: 38 additions & 11 deletions src/fileutils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::os::unix::fs::symlink;

pub fn create_symlink(source_path: &Path, target_path: &Path) -> io::Result<()> {
pub(crate) fn create_symlink(source_path: &Path, target_path: &Path) -> io::Result<()> {
if cfg!(target_family = "unix") {
info!("create symbolic link {} -> {}", source_path.display(), target_path.display());
symlink(source_path, target_path)
Expand All @@ -17,7 +17,7 @@ pub fn create_symlink(source_path: &Path, target_path: &Path) -> io::Result<()>
}
}

pub fn build_backup_path(path: &Path) ->io::Result<PathBuf> {
pub(crate) fn build_backup_path(path: &Path) ->io::Result<PathBuf> {
let file_name = path.file_name()
.and_then(|x: &OsStr| x.to_str())
.expect("Unable to get filename");
Expand All @@ -26,19 +26,19 @@ pub fn build_backup_path(path: &Path) ->io::Result<PathBuf> {
Ok(parent_path.join("backup-".to_owned()+file_name))
}

pub fn backup_path(path: &Path) -> io::Result<()> {
pub(crate) fn backup_path(path: &Path) -> io::Result<()> {
let backup_path = build_backup_path(path)?;

info!("backup {} into {}", path.display(), backup_path.as_path().display());
fs::rename(path, backup_path.as_path())
}

pub fn restore_path(backup: &Path, target: &Path) -> io::Result<()> {
pub(crate) fn restore_path(backup: &Path, target: &Path) -> io::Result<()> {
info!("restore backup {} into {}", backup.display(), target.display());
fs::rename(backup, target)
}

pub fn delete_path(path: &Path) -> io::Result<()> {
pub(crate) fn delete_path(path: &Path) -> io::Result<()> {
if path.is_dir() {
info!("delete directory recursively {}", path.display());
fs::remove_dir_all(path)
Expand All @@ -48,16 +48,43 @@ pub fn delete_path(path: &Path) -> io::Result<()> {
}
}

pub fn is_symlink(path: &Path) -> bool {
match path.symlink_metadata() {
pub(crate) fn is_symlink(path: &Path) -> bool {
match path.symlink_metadata() {
Ok(data) => data.file_type().is_symlink(),
Err(_e) => false
}
}

pub fn check_symlink(symlink_path: &Path, valid_dest: &Path) -> bool {
match fs::read_link(symlink_path) {
Ok(real) => valid_dest.eq(real.as_path()),
Err(_e) => false
pub(crate) fn check_symlink(symlink_path: &Path, valid_dest: &Path) -> bool {
match get_symlink_target(symlink_path) {
Some(target) => valid_dest.eq(target.as_path()),
None => false
}
}

pub(crate) fn get_symlink_target(symlink_path: &Path) -> Option<PathBuf> {
if is_symlink(symlink_path) {
let error = format!("Unable to find absolute path of symlink target {}", symlink_path.display());
let absolute_target = symlink_path.canonicalize().expect(error.as_str());
Some(absolute_target.to_owned())
} else {
None
}
}

pub(crate) fn break_directory_link(directory: &Path) -> io::Result<()> {
let target = get_symlink_target(directory).unwrap();

// replace symlink with real directory file
delete_path(directory)?;
create_dir(directory)?;

let source_paths = fs::read_dir(target.as_path())?;
for src_dir_entry in source_paths {
let source_child = src_dir_entry.unwrap().path();
let target_child = target.join(source_child.as_path().file_name().expect("Unable to get path filename"));
create_symlink(source_child.as_path(), target_child.as_path())?;
}

Ok(())
}
62 changes: 40 additions & 22 deletions src/interpreters.rs
Original file line number Diff line number Diff line change
@@ -1,44 +1,62 @@

use quicli::prelude::*;
use im::vector::*;

use std::io;
use std::path::{Path, PathBuf};
use std::collections::LinkedList;
use std::result::Result;

use fileutils::*;
use operations::FSOperation;
use errors::AppError;

pub enum FSOperation {
Backup(PathBuf),
Restore { backup: PathBuf, target: PathBuf },
CreateSymlink { source: PathBuf, target: PathBuf },
Delete(PathBuf)
}

pub fn dryrun_interpreter(operations: &LinkedList<FSOperation>) -> io::Result<()> {
for op in operations {
match op {
FSOperation::Backup(p) => info!("DRY-RUN : backup {}", p.display()),
FSOperation::Restore {backup, target} => info!("DRY-RUN : restore {} -> {}", backup.display(), target.display()),
FSOperation::Delete(p) => {
if p.is_dir() {
info!("DRY-RUN : delete directory recursively {}", p.display());
} else {
info!("DRY-RUN : delete file {}", p.display());
}
pub(crate) fn dryrun_interpreter(operations: &Vector<Result<FSOperation, AppError>>) -> Result<(), AppError> {
let mut has_error = false;
for result in operations.iter() {
match result {
Ok(op) => {
match op {
FSOperation::Nothing{path, cause} => println!("DRY-RUN : nothing to do on {} ({})", path.display(), cause),
FSOperation::Backup(p) => println!("DRY-RUN : backup {}", p.display()),
FSOperation::Restore {backup, target} => println!("DRY-RUN : restore {} -> {}", backup.display(), target.display()),
FSOperation::BreakDirectoryLink(p) => println!("DRY-RUN : Break directory link {} and rebuild children links", p.display()),
FSOperation::Delete(p) => {
if p.is_dir() {
println!("DRY-RUN : delete directory recursively {}", p.display());
} else {
println!("DRY-RUN : delete file {}", p.display());
}
}
FSOperation::CreateSymlink{source, target} => println!("DRY-RUN : create symbolic link {} -> {}", source.display(), target.display()),
};
},
Err(err) => {
has_error = true;
eprintln!("DRY-RUN : Error {}", err)
}
FSOperation::CreateSymlink{source, target} => info!("DRY-RUN : create symbolic link {} -> {}", source.display(), target.display())
};
}
};

if has_error {
error!("{}", AppError::ApplyError)
}
Ok(())
}

pub fn filesystem_interpreter(operations: &LinkedList<FSOperation>) -> io::Result<()> {
for op in operations {
pub(crate) fn filesystem_interpreter(operations: &Vector<&FSOperation>) -> Result<(), AppError> {
for op in operations.iter() {
match op {
FSOperation::Nothing {path, cause} => {
info!("Nothing to do on {} ({})", path.display(), cause);
Ok(())
},
FSOperation::Backup(p) => backup_path(p.as_path()),
FSOperation::Delete(p) => delete_path(p.as_path()),
FSOperation::Restore {backup, target} => restore_path(backup.as_path(), target.as_path()),
FSOperation::CreateSymlink{source, target} => create_symlink(source.as_path(), target.as_path())
FSOperation::CreateSymlink{source, target} => create_symlink(source.as_path(), target.as_path()),
FSOperation::BreakDirectoryLink(p) => break_directory_link(p.as_path()),
};
};
Ok(())
Expand Down
Loading

0 comments on commit adca477

Please sign in to comment.