Source code for OrganiserPro.sorter

import shutil
from datetime import datetime
from pathlib import Path

from rich.console import Console

console = Console()


[docs] def get_file_extension(file_path: Path) -> str: """Get the file extension without the dot. Args: file_path: Path to the file Returns: str: The file extension without the dot, or the full name if no extension, or empty string for hidden files """ if file_path.name.startswith("."): return "" if not file_path.suffix: return file_path.name return file_path.suffix[1:].lower()
[docs] def sort_by_type(directory: str, dry_run: bool = False) -> None: """Sort files in the given directory into subdirectories by file type. Args: directory: Path to the directory containing files to sort dry_run: If True, only show what would be done without making changes """ source_dir = Path(directory).expanduser().resolve() # Get all files (excluding hidden files) all_files = [] for file_path in source_dir.glob("*"): # Only process top-level files if file_path.is_file() and not file_path.name.startswith("."): all_files.append(file_path) if not all_files: console.print("[yellow]No files found to sort![/]") return # Create a progress bar with console.status("Sorting files..."): files_processed = 0 extensions_created = set() for file_path in all_files: try: # Get file extension and create target directory ext = get_file_extension(file_path) ext_dir = source_dir / ext if ext not in extensions_created: ext_dir.mkdir(exist_ok=True) extensions_created.add(ext) # Create the target path target_path = ext_dir / file_path.name # Handle duplicate filenames by appending a counter counter = 1 original_target = target_path while target_path.exists(): # If file is the same (same inode), skip it if target_path.samefile(file_path): break # Otherwise, append a counter to make the filename unique new_name = ( f"{original_target.stem}_{counter}{original_target.suffix}" ) target_path = original_target.with_name(new_name) counter += 1 # Move the file if it's not already in the right place if not target_path.exists() or not target_path.samefile(file_path): shutil.move(str(file_path), str(target_path)) files_processed += 1 except Exception as e: console.print(f"[red]Error processing {file_path.name}: {e}") console.print( f"✅ Sorted {files_processed} files into {len(extensions_created)} directories" )
[docs] def sort_by_date( directory: str, date_format: str = "%Y-%m", dry_run: bool = False ) -> None: """ Sort files into subdirectories based on file type, size, or date. Args: directory: Directory to sort date_format: Format string for date-based sorting """ source_dir = Path(directory).expanduser().resolve() if not source_dir.exists() or not source_dir.is_dir(): console.print(f"[red]Error: {directory} is not a valid directory") return # Get all files (only in the top-level directory, not subdirectories) all_files = [] for file_path in source_dir.glob("*"): if file_path.is_file() and not file_path.name.startswith("."): all_files.append(file_path) if not all_files: console.print("[yellow]No files found to sort![/]") return # Track processed files and created directories files_processed = 0 date_dirs_created = set() # Process files with progress with console.status("Sorting files..."): for file_path in all_files: if file_path.is_file() and not file_path.name.startswith("."): try: # Get the file's last modified time and format it mtime = file_path.stat().st_mtime dt = datetime.fromtimestamp(mtime) date_str = dt.strftime( date_format.replace("YYYY", "%Y") .replace("MM", "%m") .replace("DD", "%d") ) # Create target directory if it doesn't exist target_dir = source_dir / date_str if date_str not in date_dirs_created: target_dir.mkdir(exist_ok=True) date_dirs_created.add(date_str) target_path = target_dir / file_path.name # Handle filename conflicts counter = 1 while target_path.exists(): target_path = target_dir / ( f"{file_path.stem}_{counter}{file_path.suffix}" ) counter += 1 # Move the file file_path.rename(target_path) files_processed += 1 except (OSError, PermissionError) as e: msg = f"[yellow]Warning: Could not process {file_path}: {e}" console.print(msg) continue console.print( f"✅ Sorted {files_processed} files into " f"{len(date_dirs_created)} date-based directories" )