87f245d3fc
- Sunset Glitch color palette applied to all templates - Font Awesome icons throughout UI - Download manager with parallel queue and progress tracking - Settings page with dynamic configuration - Recommendations router enhanced with scoring - Local vendor libs (Alpine.js, HTMX) for offline support - Auto test suite with screenshots - Series releases list component - New download model
181 lines
5.5 KiB
Python
181 lines
5.5 KiB
Python
"""
|
|
Ohm Stream Downloader - FastAPI Application
|
|
|
|
Main application file with startup configuration and middleware.
|
|
All API routes have been migrated to app/routers/ for better maintainability.
|
|
"""
|
|
|
|
import logging
|
|
import uuid
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
from fastapi import FastAPI, Request, Response
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.staticfiles import StaticFiles
|
|
from fastapi.templating import Jinja2Templates
|
|
|
|
from app.download_manager import DownloadManager
|
|
from app.models import DownloadTask, DownloadStatus
|
|
|
|
# Configure logging
|
|
logger = logging.getLogger(__name__)
|
|
|
|
PERMISSIONS_POLICY_VALUE = (
|
|
"accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), "
|
|
"camera=(), display-capture=(), document-domain=(), encrypted-media=(), "
|
|
"fullscreen=*, gamepad=(), geolocation=(), gyroscope=(), "
|
|
"magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=*, "
|
|
"publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), "
|
|
"usb=(), web-share=(), xr-spatial-tracking=()"
|
|
)
|
|
|
|
# Initialize FastAPI app
|
|
app = FastAPI(title="Ohm Stream Downloader")
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=[
|
|
"http://localhost:3000",
|
|
"http://127.0.0.1:3000",
|
|
"http://192.168.1.204:3000",
|
|
"http://192.168.1.204",
|
|
"http://192.168.1.200:3000",
|
|
"http://192.168.1.200",
|
|
],
|
|
allow_credentials=True,
|
|
allow_methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
|
|
@app.middleware("http")
|
|
async def permissions_policy_middleware(request: Request, call_next):
|
|
response: Response = await call_next(request)
|
|
response.headers["Permissions-Policy"] = PERMISSIONS_POLICY_VALUE
|
|
return response
|
|
|
|
|
|
# Initialize download manager
|
|
download_manager = DownloadManager(download_dir="downloads", max_parallel=3)
|
|
|
|
# Initialize episode checker with download manager
|
|
from app.episode_checker import episode_checker
|
|
|
|
episode_checker.set_download_manager(download_manager)
|
|
|
|
|
|
@app.on_event("startup")
|
|
async def startup_event():
|
|
"""Initialize services on application startup"""
|
|
# Create database tables if they don't exist
|
|
from app.database import create_db_and_tables
|
|
|
|
create_db_and_tables()
|
|
logger.info("Database tables initialized")
|
|
|
|
from app.sonarr_handler import get_sonarr_handler
|
|
|
|
sonarr_handler = get_sonarr_handler()
|
|
sonarr_handler.set_download_manager(download_manager)
|
|
|
|
from app.auto_download_scheduler import auto_download_scheduler
|
|
|
|
auto_download_scheduler.start()
|
|
logger.info("Application started: Sonarr handler and scheduler initialized")
|
|
|
|
|
|
def restore_completed_downloads():
|
|
"""Restore download tasks: first from the database, then scan for untracked files."""
|
|
# Step 1: Load persisted tasks from database
|
|
download_manager._load_tasks_from_db()
|
|
|
|
# Step 2: Scan downloads directory for files not yet tracked in the database
|
|
download_dir = Path("downloads")
|
|
if not download_dir.exists():
|
|
return
|
|
|
|
video_extensions = {".mp4", ".mkv", ".avi", ".mov", ".wmv", ".flv", ".webm"}
|
|
tracked_filenames = {t.filename for t in download_manager.tasks.values()}
|
|
|
|
for file_path in download_dir.iterdir():
|
|
if file_path.is_file() and file_path.suffix.lower() in video_extensions:
|
|
if file_path.stat().st_size < 1024 * 1024:
|
|
continue
|
|
|
|
filename = file_path.name
|
|
|
|
# Skip if already tracked in DB
|
|
if filename in tracked_filenames:
|
|
continue
|
|
|
|
file_size = file_path.stat().st_size
|
|
|
|
task_id = str(uuid.uuid4())
|
|
task = DownloadTask(
|
|
id=task_id,
|
|
url="",
|
|
filename=filename,
|
|
host="other",
|
|
status=DownloadStatus.COMPLETED,
|
|
progress=100.0,
|
|
downloaded_bytes=file_size,
|
|
total_bytes=file_size,
|
|
speed=0.0,
|
|
file_path=str(file_path),
|
|
created_at=datetime.fromtimestamp(file_path.stat().st_ctime),
|
|
completed_at=datetime.fromtimestamp(file_path.stat().st_mtime),
|
|
)
|
|
|
|
download_manager.tasks[task_id] = task
|
|
download_manager._save_task_to_db(task)
|
|
logger.info(f"Restored untracked completed download: {filename}")
|
|
|
|
|
|
# Restore completed downloads on startup
|
|
restore_completed_downloads()
|
|
|
|
# Mount static files and templates
|
|
app.mount("/static", StaticFiles(directory="static"), name="static")
|
|
app.mount("/downloads", StaticFiles(directory="downloads"), name="downloads")
|
|
templates = Jinja2Templates(directory="templates")
|
|
|
|
|
|
# ==================== INCLUDE ROUTERS ====================
|
|
|
|
from app.routers import (
|
|
auth_router,
|
|
downloads_router,
|
|
anime_router,
|
|
favorites_router,
|
|
recommendations_router,
|
|
watchlist_router,
|
|
sonarr_router,
|
|
player_router,
|
|
static_router,
|
|
root_router,
|
|
settings_router,
|
|
admin_router,
|
|
)
|
|
|
|
|
|
# Include routers
|
|
app.include_router(root_router)
|
|
app.include_router(auth_router)
|
|
app.include_router(downloads_router)
|
|
app.include_router(anime_router)
|
|
app.include_router(favorites_router)
|
|
app.include_router(recommendations_router)
|
|
app.include_router(watchlist_router)
|
|
app.include_router(sonarr_router)
|
|
app.include_router(player_router)
|
|
app.include_router(static_router)
|
|
app.include_router(settings_router)
|
|
app.include_router(admin_router)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
|
|
uvicorn.run("main:app", host="0.0.0.0", port=3000, reload=True)
|