from fastapi import APIRouter, HTTPException, WebSocket, WebSocketDisconnect from pydantic import BaseModel from typing import List, Optional import asyncio import os from ..comics_manager import ComicsManager from ..config import settings from ..samba_manager import SambaManager from ..tasks import task_organize_comics, task_move_comics, task_update_metadata, task_sort_by_artist router = APIRouter() class MoveRequest(BaseModel): series_names: List[str] destination_share: Optional[str] = "comics" destination_path: Optional[str] = "/manga" class MetadataUpdateRequest(BaseModel): target_path: Optional[str] = "/comics/manga" force_update: Optional[bool] = False class FolderScanRequest(BaseModel): root_path: Optional[str] = "/comics/manga" threshold: float = 0.9 class DeleteFolderRequest(BaseModel): folder_path: str class SortRequest(BaseModel): root_path: Optional[str] = "/comics/manga" def get_samba_manager(share="isolation"): return SambaManager( settings.samba_server_ip, share, settings.samba_username, settings.samba_password, ) @router.post("/organize") def organize_comics(): task = task_organize_comics() return {"message": f"Organization queued (Task ID: {task.id}). Check logs."} @router.get("/pending") def get_pending_comics(): manager = ComicsManager() samba_manager = get_samba_manager("isolation") try: return manager.get_pending_comics(samba_manager) finally: samba_manager.close() @router.post("/move") def move_comics(request: MoveRequest): task = task_move_comics(request.series_names, request.destination_share, request.destination_path) return {"message": f"Move queued (Task ID: {task.id}). Check logs."} @router.post("/update_metadata") def update_metadata(request: MetadataUpdateRequest): task = task_update_metadata(request.target_path, request.force_update) return {"message": f"Metadata update queued (Task ID: {task.id}). Check logs."} @router.post("/folders/scan") def scan_folders(request: FolderScanRequest): # This remains synchronous to return results immediately to UI manager = ComicsManager() samba_manager = get_samba_manager("isolation") try: groups = manager.find_similar_folders(samba_manager, request.root_path, request.threshold) return groups finally: samba_manager.close() @router.post("/folders/delete") def delete_folder(request: DeleteFolderRequest): manager = ComicsManager() samba_manager = get_samba_manager("isolation") try: return manager.delete_folder(samba_manager, request.folder_path) finally: samba_manager.close() @router.post("/sort_by_artist") def sort_by_artist(request: SortRequest): task = task_sort_by_artist(request.root_path) return {"message": f"Sort by Artist queued (Task ID: {task.id}). Check logs."} @router.websocket("/ws/logs") async def websocket_endpoint(websocket: WebSocket): await websocket.accept() file_path = "logs/comics_organization.log" # Ensure file exists if not os.path.exists(file_path): os.makedirs(os.path.dirname(file_path), exist_ok=True) with open(file_path, "w") as f: f.write("") try: with open(file_path, "r") as f: # Send last 20 lines for context lines = f.readlines() for line in lines[-20:]: await websocket.send_text(line) # Follow the file f.seek(0, 2) while True: line = f.readline() if line: await websocket.send_text(line) else: await asyncio.sleep(0.1) except WebSocketDisconnect: pass