mod tests;
use crate::errors::{io::IoError, Result};
use itertools::Itertools;
use std::{
fs,
os::unix::fs as unix_fs,
path::{Path, PathBuf},
};
pub struct Files {
home_dir: PathBuf,
file_dir: PathBuf,
}
impl Files {
pub fn init(home_dir: PathBuf, file_dir: PathBuf) -> Self {
Self { home_dir, file_dir }
}
pub fn is_managed_in_home<P>(&self, file: P) -> bool
where
P: AsRef<Path>,
{
let home_path = self.home_dir.join(file);
fs::symlink_metadata(&home_path)
.and_then(|meta| match meta.file_type().is_symlink() {
true => {
let symlink_path = fs::read_link(&home_path)?;
Ok(symlink_path.starts_with(&self.file_dir))
}
false => Ok(false),
})
.unwrap_or(false)
}
pub fn is_managed_in_repo<P>(&self, file: &P) -> bool
where
P: AsRef<Path>,
{
let repo_path = self.file_dir.join(file);
repo_path.exists()
}
pub fn move_and_symlink<P>(&self, files: &[P]) -> Result<()>
where
P: AsRef<Path>,
{
let files = files
.iter()
.filter(|file| !self.is_managed_in_home(file) && !self.is_managed_in_repo(file))
.collect_vec();
move_files(&files, &self.home_dir, &self.file_dir)?;
self.symlink_back_home(&files)?;
Ok(())
}
pub fn symlink_back_home<P>(&self, files: &[P]) -> Result<()>
where
P: AsRef<Path>,
{
let files = files
.iter()
.filter(|file| !self.is_managed_in_home(file) && self.is_managed_in_repo(file))
.collect_vec();
symlink_files(&files, &self.file_dir, &self.home_dir)?;
Ok(())
}
pub fn remove_and_restore<P>(&self, files: &[P]) -> Result<()>
where
P: AsRef<Path>,
{
delete_files(files, &self.home_dir)?;
move_files(files, &self.file_dir, &self.home_dir)?;
Ok(())
}
}
fn delete_files<P1, P2>(files: &[P1], dir: P2) -> Result<()>
where
P1: AsRef<Path>,
P2: AsRef<Path>,
{
let paths = files.iter().map(|file| dir.as_ref().join(file));
for path in paths {
fs::remove_file(&path).map_err(|err| IoError::Delete { source: err, path })?;
}
Ok(())
}
fn symlink_files<P1, P2, P3>(files: &[P1], from: P2, to: P3) -> Result<()>
where
P1: AsRef<Path>,
P2: AsRef<Path>,
P3: AsRef<Path>,
{
let from_paths = files.iter().map(|file| from.as_ref().join(file));
let to_paths = files.iter().map(|file| to.as_ref().join(file));
for (from_path, to_path) in from_paths.zip(to_paths) {
match unix_fs::symlink(&from_path, &to_path) {
Ok(_) => {}
Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => {
fs::remove_file(&to_path).map_err(|err| IoError::Delete {
source: err,
path: to_path.clone(),
})?;
unix_fs::symlink(&from_path, &to_path).map_err(|err| IoError::Symlink {
source: err,
to: to_path,
from: from_path,
})?;
}
Err(err) => {
return Err(IoError::Symlink {
from: from_path,
to: to_path,
source: err,
}
.into())
}
}
}
Ok(())
}
fn move_files<P1, P2, P3>(files: &[P1], from: P2, to: P3) -> Result<()>
where
P1: AsRef<Path>,
P2: AsRef<Path>,
P3: AsRef<Path>,
{
let from_paths = files.iter().map(|file| from.as_ref().join(file));
let to_paths = files.iter().map(|file| to.as_ref().join(file));
for (from_path, to_path) in from_paths.zip(to_paths) {
fs::create_dir_all(to_path.parent().unwrap()).map_err(|err| IoError::Create {
source: err,
path: to_path.parent().unwrap().to_path_buf(),
})?;
fs::rename(&from_path, &to_path).map_err(|err| IoError::Move {
source: err,
from: from_path.clone(),
to: to_path.clone(),
})?;
}
Ok(())
}