feat: Add cross-platform symlink helper module with robust error handling
This commit is contained in:
parent
2809b6e818
commit
aa18a35f8d
2 changed files with 135 additions and 0 deletions
|
|
@ -1,4 +1,5 @@
|
|||
mod tag_engine;
|
||||
mod symlink;
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::error::Error;
|
||||
|
|
|
|||
134
src/symlink.rs
Normal file
134
src/symlink.rs
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
use thiserror::Error;
|
||||
use fs_err as fs;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum SymlinkError {
|
||||
#[error("Failed to create directory {path}: {source}")]
|
||||
CreateDir {
|
||||
path: String,
|
||||
source: std::io::Error,
|
||||
},
|
||||
#[error("Failed to create symlink from {from} to {to}: {source}")]
|
||||
CreateLink {
|
||||
from: String,
|
||||
to: String,
|
||||
source: std::io::Error,
|
||||
},
|
||||
#[error("Invalid path: {0}")]
|
||||
InvalidPath(String),
|
||||
}
|
||||
|
||||
pub fn create_symlink_tree(paths: Vec<PathBuf>, target_dir: &Path) -> Result<(), SymlinkError> {
|
||||
for path in paths {
|
||||
// Ensure path is absolute and clean
|
||||
let abs_path = fs::canonicalize(&path).map_err(|_| {
|
||||
SymlinkError::InvalidPath(path.to_string_lossy().into_owned())
|
||||
})?;
|
||||
|
||||
// Get the file name for the symlink
|
||||
let file_name = abs_path.file_name().ok_or_else(|| {
|
||||
SymlinkError::InvalidPath(abs_path.to_string_lossy().into_owned())
|
||||
})?;
|
||||
|
||||
// Create target directory if it doesn't exist
|
||||
fs::create_dir_all(target_dir).map_err(|e| SymlinkError::CreateDir {
|
||||
path: target_dir.to_string_lossy().into_owned(),
|
||||
source: e,
|
||||
})?;
|
||||
|
||||
// Create the symlink
|
||||
let link_path = target_dir.join(file_name);
|
||||
#[cfg(unix)]
|
||||
std::os::unix::fs::symlink(&abs_path, &link_path).map_err(|e| SymlinkError::CreateLink {
|
||||
from: abs_path.to_string_lossy().into_owned(),
|
||||
to: link_path.to_string_lossy().into_owned(),
|
||||
source: e,
|
||||
})?;
|
||||
|
||||
#[cfg(windows)]
|
||||
std::os::windows::fs::symlink_file(&abs_path, &link_path).map_err(|e| SymlinkError::CreateLink {
|
||||
from: abs_path.to_string_lossy().into_owned(),
|
||||
to: link_path.to_string_lossy().into_owned(),
|
||||
source: e,
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
fn test_create_symlink_tree_basic() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let source_dir = TempDir::new()?;
|
||||
let target_dir = TempDir::new()?;
|
||||
|
||||
// Create a test file
|
||||
let test_file = source_dir.path().join("test.txt");
|
||||
fs::write(&test_file, "test content")?;
|
||||
|
||||
// Create symlink tree
|
||||
create_symlink_tree(
|
||||
vec![test_file.clone()],
|
||||
target_dir.path()
|
||||
)?;
|
||||
|
||||
// Verify symlink exists and points to correct file
|
||||
let symlink = target_dir.path().join("test.txt");
|
||||
assert!(symlink.exists());
|
||||
assert!(symlink.is_symlink());
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
assert_eq!(
|
||||
fs::metadata(&test_file)?.ino(),
|
||||
fs::metadata(&symlink)?.ino()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_symlink_tree_nested() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let source_dir = TempDir::new()?;
|
||||
let target_dir = TempDir::new()?;
|
||||
|
||||
// Create nested test file
|
||||
let nested_dir = source_dir.path().join("nested");
|
||||
fs::create_dir_all(&nested_dir)?;
|
||||
let test_file = nested_dir.join("test.txt");
|
||||
fs::write(&test_file, "test content")?;
|
||||
|
||||
// Create symlink tree
|
||||
create_symlink_tree(
|
||||
vec![test_file],
|
||||
&target_dir.path().join("nested")
|
||||
)?;
|
||||
|
||||
// Verify directory and symlink were created
|
||||
let symlink = target_dir.path().join("nested/test.txt");
|
||||
assert!(symlink.exists());
|
||||
assert!(symlink.is_symlink());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_symlink_tree_invalid_path() {
|
||||
let target_dir = TempDir::new().unwrap();
|
||||
|
||||
// Try to create symlink with non-existent source
|
||||
let result = create_symlink_tree(
|
||||
vec![PathBuf::from("/nonexistent/path")],
|
||||
target_dir.path()
|
||||
);
|
||||
|
||||
assert!(matches!(result, Err(SymlinkError::InvalidPath(_))));
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue