Files
fomod-manager/src/linker.rs

151 lines
4.1 KiB
Rust

use std::{
fs, io,
os::unix,
path::{Path, PathBuf},
};
use crate::{Mod, ModFile, fomod::FileTypeEnum};
pub struct Linker {
target: PathBuf,
game_dir: PathBuf,
}
impl Linker {
pub fn new(target_path: &Path, game_dir: &Path) -> Self {
Self {
target: target_path.to_owned(),
game_dir: game_dir.to_owned(),
}
}
fn link_file(&self, from: &Path, to: &Path) -> Result<(), LinkerError> {
let target = self.target.join(path_to_lowercase(to));
if let Some(parent) = target.parent() {
fs::create_dir_all(parent)?;
}
create_symlink_for_file(from, &target)?;
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(())
}
pub fn link_mod_file(&self, file: &ModFile, from_mod: &Mod) -> Result<(), LinkerError> {
let src = from_mod.source.join(&file.source);
self.link_file(&src, &file.dest)
}
pub fn link_plugin_files(
&self,
entries: &[FileTypeEnum],
mod_dir: &Path,
) -> Result<(), LinkerError> {
let mut sorted_entries = entries.to_owned();
sorted_entries.sort_by_cached_key(|e| match e {
FileTypeEnum::File(file_type) => file_type.priority.unwrap_or(0),
FileTypeEnum::Folder(file_type) => file_type.priority.unwrap_or(0),
});
for entry in sorted_entries {
match entry {
FileTypeEnum::File(file) => {
let from = mod_dir.join(file.source);
let to = Path::new("Data").join(file.destination.unwrap_or("".to_owned()));
self.link_file(&from, &to)?;
}
FileTypeEnum::Folder(folder) => {
let from = mod_dir.join(folder.source);
let to = Path::new("Data").join(folder.destination.unwrap_or("".to_owned()));
self.link_recursive(&from, &to)?;
}
}
}
Ok(())
}
pub fn link_install_to_target(&self) -> Result<(), LinkerError> {
fn symlink_tree(src: &Path, dst: &Path) -> Result<(), LinkerError> {
if !dst.exists() {
fs::create_dir_all(dst)?;
}
for entry in fs::read_dir(src)? {
let entry = entry?;
let src_path = entry.path();
let dst_path = dst.join(path_to_lowercase(&entry.path()).file_name().unwrap());
let meta = entry.metadata()?;
if meta.is_dir() {
symlink_tree(&src_path, &dst_path)?;
} else if meta.is_file() {
create_symlink_for_file(&src_path, &dst_path)?;
}
}
Ok(())
}
symlink_tree(&self.game_dir, &self.target)?;
Ok(())
}
}
#[derive(Debug)]
pub enum LinkerError {
Io(io::Error),
}
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),
}
}
}
impl std::error::Error for LinkerError {}
impl From<io::Error> for LinkerError {
fn from(e: io::Error) -> Self {
Self::Io(e)
}
}
fn path_to_lowercase(path: &Path) -> PathBuf {
PathBuf::from(path.to_string_lossy().to_lowercase())
}
fn create_symlink_for_file(src: &Path, dst: &Path) -> io::Result<()> {
#[cfg(unix)]
{
unix::fs::symlink(src, dst)
}
#[cfg(windows)]
{
std::os::windows::fs::symlink_file(src, dst)
}
}