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(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(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 for LinkerError { fn from(e: io::Error) -> Self { Self::Io(e) } } fn create_symlink_for_file(src: &Path, dst: &Path) -> io::Result<()> { let absolute_path = fs::canonicalize(src)?; #[cfg(unix)] { unix::fs::symlink(absolute_path, dst) } #[cfg(windows)] { std::os::windows::fs::symlink_file(src, dst) } }