""" File Organizer - Organize uploads by hostname """ import os import re from pathlib import Path from typing import Tuple def sanitize_hostname(hostname: str) -> str: """ Sanitize hostname for use as directory name Args: hostname: The hostname to sanitize Returns: Sanitized hostname safe for use as directory name """ # Remove invalid characters sanitized = re.sub(r'[^\w\-.]', '_', hostname) # Remove leading/trailing dots and underscores sanitized = sanitized.strip('._') # Replace multiple underscores with single sanitized = re.sub(r'_+', '_', sanitized) # Limit length sanitized = sanitized[:100] # Default if empty return sanitized if sanitized else 'unknown' def get_device_upload_paths(base_upload_dir: str, hostname: str) -> Tuple[str, str]: """ Get organized upload paths for a device Args: base_upload_dir: Base upload directory (e.g., "./uploads") hostname: Device hostname Returns: Tuple of (images_path, files_path) """ sanitized_hostname = sanitize_hostname(hostname) images_path = os.path.join(base_upload_dir, sanitized_hostname, "images") files_path = os.path.join(base_upload_dir, sanitized_hostname, "files") return images_path, files_path def ensure_device_directories(base_upload_dir: str, hostname: str) -> Tuple[str, str]: """ Ensure device upload directories exist Args: base_upload_dir: Base upload directory hostname: Device hostname Returns: Tuple of (images_path, files_path) """ images_path, files_path = get_device_upload_paths(base_upload_dir, hostname) # Create directories if they don't exist Path(images_path).mkdir(parents=True, exist_ok=True) Path(files_path).mkdir(parents=True, exist_ok=True) return images_path, files_path def get_upload_path(base_upload_dir: str, hostname: str, is_image: bool, filename: str) -> str: """ Get the full upload path for a file Args: base_upload_dir: Base upload directory hostname: Device hostname is_image: True if file is an image, False for documents filename: The filename to store Returns: Full path where file should be stored """ images_path, files_path = ensure_device_directories(base_upload_dir, hostname) target_dir = images_path if is_image else files_path return os.path.join(target_dir, filename) def is_image_file(filename: str, mime_type: str = None) -> bool: """ Check if a file is an image based on extension and/or mime type Args: filename: The filename mime_type: Optional MIME type Returns: True if file is an image """ # Check extension image_extensions = {'.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.svg'} ext = os.path.splitext(filename)[1].lower() if ext in image_extensions: return True # Check MIME type if provided if mime_type and mime_type.startswith('image/'): return True return False def migrate_existing_files(base_upload_dir: str, hostname: str, file_list: list) -> dict: """ Migrate existing files to new organized structure Args: base_upload_dir: Base upload directory hostname: Device hostname file_list: List of tuples (filename, is_image) Returns: Dictionary mapping old paths to new paths """ images_path, files_path = ensure_device_directories(base_upload_dir, hostname) migrations = {} for filename, is_image in file_list: old_path = os.path.join(base_upload_dir, filename) if is_image: new_path = os.path.join(images_path, filename) else: new_path = os.path.join(files_path, filename) migrations[old_path] = new_path return migrations def get_relative_path(full_path: str, base_upload_dir: str) -> str: """ Get relative path from base upload directory Args: full_path: Full file path base_upload_dir: Base upload directory Returns: Relative path from base directory """ return os.path.relpath(full_path, base_upload_dir)