Files
serv_benchmark/backend/migrate_file_organization.py
2026-01-11 23:41:30 +01:00

180 lines
5.2 KiB
Python

#!/usr/bin/env python3
"""
Migrate existing uploads to organized structure
Moves files from uploads/ to uploads/{hostname}/images or uploads/{hostname}/files
"""
import os
import shutil
import sys
from pathlib import Path
# Add parent directory to path
sys.path.insert(0, str(Path(__file__).parent))
from sqlalchemy.orm import Session
from app.db.session import SessionLocal
from app.core.config import settings
from app.models.device import Device
from app.models.document import Document
from app.utils.file_organizer import (
sanitize_hostname,
is_image_file,
ensure_device_directories
)
def migrate_files(dry_run: bool = True):
"""
Migrate existing files to organized structure
Args:
dry_run: If True, only print what would be done
"""
db: Session = SessionLocal()
try:
# Get all documents
documents = db.query(Document).all()
print(f"Found {len(documents)} documents to migrate")
print(f"Mode: {'DRY RUN' if dry_run else 'ACTUAL MIGRATION'}")
print("-" * 80)
migrated_count = 0
error_count = 0
skipped_count = 0
for doc in documents:
# Get device
device = db.query(Device).filter(Device.id == doc.device_id).first()
if not device:
print(f"❌ Document {doc.id}: Device {doc.device_id} not found - SKIPPING")
error_count += 1
continue
# Check if file exists
if not os.path.exists(doc.stored_path):
print(f"⚠️ Document {doc.id}: File not found at {doc.stored_path} - SKIPPING")
skipped_count += 1
continue
# Determine if image
is_image = is_image_file(doc.filename, doc.mime_type)
file_type = "image" if is_image else "file"
# Get new path
sanitized_hostname = sanitize_hostname(device.hostname)
subdir = "images" if is_image else "files"
filename = os.path.basename(doc.stored_path)
new_path = os.path.join(
settings.UPLOAD_DIR,
sanitized_hostname,
subdir,
filename
)
# Check if already in correct location
if doc.stored_path == new_path:
print(f"✓ Document {doc.id}: Already in correct location")
skipped_count += 1
continue
print(f"📄 Document {doc.id} ({file_type}):")
print(f" Device: {device.hostname} (ID: {device.id})")
print(f" From: {doc.stored_path}")
print(f" To: {new_path}")
if not dry_run:
try:
# Create target directory
os.makedirs(os.path.dirname(new_path), exist_ok=True)
# Move file
shutil.move(doc.stored_path, new_path)
# Update database
doc.stored_path = new_path
db.add(doc)
print(f" ✅ Migrated successfully")
migrated_count += 1
except Exception as e:
print(f" ❌ Error: {e}")
error_count += 1
else:
print(f" [DRY RUN - would migrate]")
migrated_count += 1
print()
if not dry_run:
db.commit()
print("Database updated")
print("-" * 80)
print(f"Summary:")
print(f" Migrated: {migrated_count}")
print(f" Skipped: {skipped_count}")
print(f" Errors: {error_count}")
print(f" Total: {len(documents)}")
if dry_run:
print()
print("This was a DRY RUN. To actually migrate files, run:")
print(" python backend/migrate_file_organization.py --execute")
finally:
db.close()
def cleanup_empty_directories(base_dir: str):
"""Remove empty directories after migration"""
for root, dirs, files in os.walk(base_dir, topdown=False):
for dir_name in dirs:
dir_path = os.path.join(root, dir_name)
try:
if not os.listdir(dir_path): # Directory is empty
os.rmdir(dir_path)
print(f"Removed empty directory: {dir_path}")
except Exception as e:
print(f"Could not remove {dir_path}: {e}")
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Migrate uploads to organized structure")
parser.add_argument(
"--execute",
action="store_true",
help="Actually perform the migration (default is dry-run)"
)
parser.add_argument(
"--cleanup",
action="store_true",
help="Clean up empty directories after migration"
)
args = parser.parse_args()
print("=" * 80)
print("File Organization Migration")
print("=" * 80)
print()
migrate_files(dry_run=not args.execute)
if args.execute and args.cleanup:
print()
print("=" * 80)
print("Cleaning up empty directories")
print("=" * 80)
cleanup_empty_directories(settings.UPLOAD_DIR)
print()
print("Done!")