extern crate uuid;
use std::io;
use std::fs;
use std::path;
use error::{Result, Error};
pub fn xch<A: AsRef<path::Path>, B: AsRef<path::Path>>(path1: A, path2: B) -> Result<()> {
let path1 = path1.as_ref();
let path2 = path2.as_ref();
let one_parent = path1.parent()
.ok_or_else::<Error, _>(|| format!("Could not find parent directory for {}", path1.display()).into())
.or_else(|_|
path2.parent()
.ok_or_else::<Error, _>(|| format!("Could not find parent directory for {}", path2.display()).into())
)?;
let unique_name = format!("{}", uuid::Uuid::new_v4().hyphenated());
let temp_name = one_parent.join(unique_name);
let mut transaction = Transaction::new();
transaction.record_rename(path1, &temp_name);
transaction.record_rename(path2, path1);
transaction.record_rename(&temp_name, path2);
transaction.commit()
}
struct RenameCmd<'a> {
from: &'a path::Path,
to: &'a path::Path,
}
impl<'a> RenameCmd<'a> {
fn exec(&self) -> io::Result<()> {
fs::rename(self.from, self.to)
}
fn rollback(self) -> io::Result<()> {
fs::rename(self.to, self.from)
}
}
struct Transaction<'a> {
to_exec: Vec<RenameCmd<'a>>,
successful_exec: Vec<RenameCmd<'a>>,
}
impl<'a> Transaction<'a> {
fn new() -> Transaction<'a> {
Transaction {
to_exec: Vec::new(),
successful_exec: Vec::new(),
}
}
fn record_rename(&mut self, from: &'a path::Path, to: &'a path::Path) {
self.to_exec.push(RenameCmd {
from,
to,
})
}
fn rollback(mut self) -> Result<()> {
loop {
if let Some(cmd) = self.successful_exec.pop() {
if let Err(e) = cmd.rollback() {
return Err(e.into());
}
} else {
return Ok(());
}
}
}
fn commit(mut self) -> Result<()> {
let res = {
let mut drain = self.to_exec.drain(..);
loop {
if let Some(cmd) = drain.next() {
let ex = cmd.exec();
if let Err(e) = ex {
break Err(e);
} else {
self.successful_exec.push(cmd);
}
} else {
break Ok(());
}
}
};
if let Err(rename_err) = res {
let rollback_res = self.rollback();
if let Err(e) = rollback_res {
let rename_err: Error = rename_err.into();
Err(rename_err.chain_err(|| e))
} else {
Err(rename_err.into())
}
} else {
res.map_err(Into::into)
}
}
}