pub mod fs_ops;
pub mod pattern;
pub mod history;
pub mod validation;
pub mod audit;
use std::path::PathBuf;
pub use crate::fs_ops::{FileRename, FrenError, RenameExecutionResult, find_matching_files, find_matching_files_recursive, perform_renames};
pub use crate::validation::{validate_renames, ValidationIssue, ValidationResult};
pub use crate::audit::{log_audit_entry, log_audit_from_result, read_audit_log, clear_audit_log, AuditEntry};
use crate::pattern::apply_rename_pattern;
use crate::history::{History, RenameAction};
use tokio::fs;
pub struct EnginePreviewResult {
pub renames: Vec<FileRename>,
pub warnings: Vec<String>,
pub has_empty_names: bool,
}
pub struct RenamingEngine;
impl RenamingEngine {
pub async fn generate_preview(
&self,
files: &[PathBuf],
pattern: &str,
) -> Result<EnginePreviewResult, FrenError> {
let mut renames = Vec::new();
let mut all_warnings = Vec::new();
let mut has_empty_names = false;
let preview_futures: Vec<_> = files.iter().enumerate().map(|(idx, file)| {
let pattern = pattern.to_string();
let file = file.clone();
async move {
let pattern_result = apply_rename_pattern(&file, &pattern, idx + 1).await
.map_err(|e| FrenError::PatternApplication(e.to_string()))?;
Ok((file, pattern_result))
}
}).collect();
let results: Vec<Result<_, FrenError>> = futures::future::join_all(preview_futures).await;
for result in results {
let (file, pattern_result) = result?;
let new_name = pattern_result.name;
for warning in pattern_result.warnings {
if !all_warnings.contains(&warning) {
all_warnings.push(warning);
}
}
if new_name.trim().is_empty() {
has_empty_names = true;
}
let parent = file.parent().ok_or_else(|| FrenError::Pattern("File has no parent directory".into()))?;
let new_path = parent.join(&new_name);
renames.push(FileRename {
old_path: file,
new_path,
new_name,
});
}
Ok(EnginePreviewResult {
renames,
warnings: all_warnings,
has_empty_names,
})
}
pub async fn check_undo(&self, history: &History) -> (Vec<RenameAction>, Vec<String>) {
let mut safe_actions: Vec<RenameAction> = Vec::new();
let mut conflicts = Vec::new();
let check_futures: Vec<_> = history.actions.iter().map(|action| {
let new_path = action.new_path.clone();
let old_path = action.old_path.clone();
async move {
let new_name = new_path.file_name().and_then(|n| n.to_str()).unwrap_or("?");
let old_name = old_path.file_name().and_then(|n| n.to_str()).unwrap_or("?");
let new_exists = fs::metadata(&new_path).await.is_ok();
let old_exists = fs::metadata(&old_path).await.is_ok();
let mut has_conflict = false;
let mut conflict_msg = None;
if !new_exists {
conflict_msg = Some(format!("File no longer exists: {}", new_name));
has_conflict = true;
} else if old_exists && old_path != new_path {
conflict_msg = Some(format!("Original location occupied: {}", old_name));
has_conflict = true;
}
(action.clone(), has_conflict, conflict_msg)
}
}).collect();
let results: Vec<_> = futures::future::join_all(check_futures).await;
for (action, has_conflict, conflict_msg) in results {
if has_conflict {
if let Some(msg) = conflict_msg {
conflicts.push(msg);
}
} else {
safe_actions.push(action);
}
}
(safe_actions, conflicts)
}
pub async fn apply_undo(&self, actions: Vec<RenameAction>) -> Result<usize, FrenError> {
let undo_futures: Vec<_> = actions.into_iter().map(|action| {
let new_path = action.new_path.clone();
let old_path = action.old_path.clone();
async move {
fs::rename(&new_path, &old_path).await?;
Ok::<(), FrenError>(())
}
}).collect();
let results: Vec<Result<_, FrenError>> = futures::future::join_all(undo_futures).await;
let mut count = 0;
for result in results {
result?;
count += 1;
}
Ok(count)
}
pub async fn validate(&self, renames: &[FileRename], overwrite: bool) -> ValidationResult {
validate_renames(renames, overwrite).await
}
}