From de7c91a52a011dfc332ca4c2b4e32f57cce3e379 Mon Sep 17 00:00:00 2001 From: jamulix Date: Tue, 10 Feb 2026 10:24:50 +0100 Subject: [PATCH] feat: Parallele Verarbeitung mit rayon bei vielen Dateien MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bei >= 100 Dateien wird rayon für parallele Berechnung genutzt - clean_filename() wird parallel ausgeführt (CPU-intensive Operation) - Tatsächliche Umbenennung bleibt sequenziell (I/O-bound, Race-Conditions vermeiden) - Bei < 100 Dateien sequenziell (Overhead vermeiden) - RenameOperation-Struct für geplante Umbenennungen Performance-Gewinn bei großen Verzeichnisbäumen mit vielen Dateien. Co-Authored-By: Claude Sonnet 4.5 --- src/main.rs | 126 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 84 insertions(+), 42 deletions(-) diff --git a/src/main.rs b/src/main.rs index 14655d3..ee9be11 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,8 +10,10 @@ use config::Config; use glob::Pattern; use indicatif::{ProgressBar, ProgressStyle}; use log::{debug, error, info}; +use rayon::prelude::*; use sanitizer::{clean_filename, is_excluded, is_safe_rename}; use std::fs; +use std::path::PathBuf; use walkdir::WalkDir; // Standard-Ausschlussmuster (ähnlich wie detox) @@ -28,6 +30,16 @@ const DEFAULT_EXCLUDES: &[&str] = &[ "__pycache__/**", ]; +// Schwellwert für parallele Verarbeitung (bei weniger Dateien lohnt sich Overhead nicht) +const PARALLEL_THRESHOLD: usize = 100; + +/// Repräsentiert eine geplante Umbenennungsoperation +#[derive(Debug)] +struct RenameOperation { + old_path: PathBuf, + new_path: PathBuf, +} + /// Startpunkt des Programms fn main() -> Result<()> { // Initialisiere Logger @@ -92,56 +104,86 @@ fn main() -> Result<()> { None }; - // Umbenennen in absteigender Tiefe - for entry in entries { + // Berechne Umbenennungen (parallel bei vielen Dateien) + let rename_ops: Vec = if entries.len() >= PARALLEL_THRESHOLD { + // Parallel mit rayon + entries + .par_iter() + .filter_map(|entry| { + let old_path = entry.path(); + + // Ebenentiefe 0 -> überspringen + if entry.depth() == 0 && entry.file_type().is_dir() && !args.modify_root { + return None; + } + + // Special Files nur mit --special + let file_type = entry.file_type(); + if !args.special && (!file_type.is_file() && !file_type.is_dir()) { + return None; + } + + // Dateiname ermitteln und bereinigen + let filename = old_path.file_name()?; + let new_name = clean_filename(filename, &config, false)?; + let new_path = old_path.with_file_name(&new_name); + + Some(RenameOperation { + old_path: old_path.to_path_buf(), + new_path, + }) + }) + .collect() + } else { + // Sequenziell bei wenigen Dateien + entries + .iter() + .filter_map(|entry| { + let old_path = entry.path(); + + if entry.depth() == 0 && entry.file_type().is_dir() && !args.modify_root { + return None; + } + + let file_type = entry.file_type(); + if !args.special && (!file_type.is_file() && !file_type.is_dir()) { + return None; + } + + let filename = old_path.file_name()?; + let new_name = clean_filename(filename, &config, false)?; + let new_path = old_path.with_file_name(&new_name); + + Some(RenameOperation { + old_path: old_path.to_path_buf(), + new_path, + }) + }) + .collect() + }; + + // Umbenennungen sequenziell ausführen + for op in rename_ops { if let Some(bar) = &progress_bar { bar.inc(1); } - let old_path = entry.path(); - - // Ebenentiefe 0 -> überspringen wir als Verzeichnis, außer --modify_root ist gesetzt - if entry.depth() == 0 && entry.file_type().is_dir() && !args.modify_root { - if args.verbose { - debug!("Skip root directory: {}", old_path.display()); - } - continue; + if !args.quiet { + info!("{} -> {}", op.old_path.display(), op.new_path.display()); } - // Special Files (Symlinks, etc.) nur mit --special verarbeiten - let file_type = entry.file_type(); - if !args.special && (!file_type.is_file() && !file_type.is_dir()) { - if args.verbose { - debug!("Skip special file: {}", old_path.display()); - } - continue; + if args.verbose { + debug!("Rename: {:?} -> {:?}", op.old_path, op.new_path); } - // Dateiname (oder Verzeichnisname) ermitteln - let filename = old_path.file_name().ok_or_else(|| { - anyhow::anyhow!( - "Konnte Dateinamen nicht ermitteln für: {}", - old_path.display() - ) - })?; - - // Verarbeiten und ggf. umbenennen - if let Some(new_name) = clean_filename(filename, &config, args.verbose) { - let new_path = old_path.with_file_name(&new_name); - - if !args.quiet { - info!("{} -> {}", old_path.display(), new_path.display()); - } - - if !args.dry_run && is_safe_rename(old_path, &new_path, args.force) { - fs::rename(old_path, &new_path).with_context(|| { - format!( - "Fehler beim Umbenennen: {} -> {}", - old_path.display(), - new_path.display() - ) - })?; - } + if !args.dry_run && is_safe_rename(&op.old_path, &op.new_path, args.force) { + fs::rename(&op.old_path, &op.new_path).with_context(|| { + format!( + "Fehler beim Umbenennen: {} -> {}", + op.old_path.display(), + op.new_path.display() + ) + })?; } }