180 lines
5.2 KiB
Python
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!")
|