initial commit
This commit is contained in:
111
src/linker.rs
Normal file
111
src/linker.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
use std::{
|
||||
fs, io,
|
||||
os::unix,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
pub struct Linker {
|
||||
target: PathBuf,
|
||||
}
|
||||
|
||||
impl Linker {
|
||||
pub fn new(target_path: &Path) -> Self {
|
||||
Self {
|
||||
target: target_path.to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn install_path(&self) -> &Path {
|
||||
&self.target
|
||||
}
|
||||
|
||||
fn link_file(&self, from: &Path, to: &Path) -> Result<(), LinkerError> {
|
||||
let target = self.install_path().join(to);
|
||||
|
||||
if let Some(parent) = target.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
unix::fs::symlink(from, target)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_link(&self, to: &Path) -> Result<(), LinkerError> {
|
||||
let file = self.install_path().join(to);
|
||||
let metadata = fs::symlink_metadata(&file)?;
|
||||
|
||||
if !metadata.file_type().is_symlink() {
|
||||
return Err(LinkerError::NotASymlink(file));
|
||||
}
|
||||
|
||||
fs::remove_file(&file)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn link_recursive(&self, from: &Path, to: &Path) -> Result<(), LinkerError> {
|
||||
for entry in fs::read_dir(from)? {
|
||||
let entry = entry?;
|
||||
let entry_path = entry.path();
|
||||
let relative = entry_path
|
||||
.strip_prefix(from)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
|
||||
let target_path = to.join(relative);
|
||||
|
||||
if entry_path.is_dir() {
|
||||
self.link_recursive(&entry_path, &target_path)?;
|
||||
} else {
|
||||
self.link_file(&entry_path, &target_path)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unlink_recursive(&self, from: &Path, to: &Path) -> Result<(), LinkerError> {
|
||||
for entry in fs::read_dir(from)? {
|
||||
let entry = entry?;
|
||||
let entry_path = entry.path();
|
||||
let relative = entry_path
|
||||
.strip_prefix(from)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
|
||||
let target_path = to.join(relative);
|
||||
|
||||
if entry_path.is_dir() {
|
||||
self.unlink_recursive(&entry_path, &target_path)?;
|
||||
|
||||
let install_target = self.install_path().join(&target_path);
|
||||
if install_target.exists() && fs::read_dir(&install_target)?.next().is_none() {
|
||||
fs::remove_dir(&install_target)?;
|
||||
}
|
||||
} else {
|
||||
self.remove_link(&target_path)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LinkerError {
|
||||
Io(io::Error),
|
||||
NotASymlink(PathBuf),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for LinkerError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Io(e) => write!(f, "IO error: {}", e),
|
||||
Self::NotASymlink(path) => write!(f, "Tried to remove a non symlink: {:?}", path),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for LinkerError {}
|
||||
|
||||
impl From<io::Error> for LinkerError {
|
||||
fn from(e: io::Error) -> Self {
|
||||
Self::Io(e)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user