158 lines
4.1 KiB
Python
158 lines
4.1 KiB
Python
"""
|
|
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)
|