Files
ohm_streaming/main.py
T
Kimi Agent 520be53901
CI / Test (Python 3.11) (push) Has been cancelled
CI / Test (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Type Check (push) Has been cancelled
CI / Summary (push) Has been cancelled
fix: migrations, auth, providers health check, E2E tests, remove neko-sama
- Add proper Alembic initial migration (0001_initial_schema.py)
- Migrate refresh tokens from JSON file to SQLite (RefreshTokenTable)
- Remove Neko-Sama provider entirely (redirects to Gupy, not a host)
- Fix provider health check always showing UNKNOWN
  - Run check_all_health() on startup
  - Fix POST /providers/health/check background task bug
  - Add HTMX refresh after manual health check trigger
- Fix anime search relevance scoring with MIN_RELEVANCE_THRESHOLD=0.5
- Replace bare 'except:' with 'except Exception:' across codebase
- Add Playwright E2E test suite (12 tests, auth setup, helpers)
- Fix toast container blocking clicks via pointer-events: none
- Remove obsolete Jest/Vite test files and config
- Clean up obsolete test_watchlist scripts
- Update sonarr model comment for active providers
2026-05-12 11:45:56 +00:00

178 lines
5.3 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 asyncio
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",
"http://192.168.5.127:3000",
"http://192.168.5.127",
],
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()
# Run initial provider health check in background
from app.providers_manager import providers_manager
asyncio.create_task(providers_manager.check_all_health())
logger.info("Application started: Sonarr handler and scheduler initialized")
def restore_completed_downloads():
"""Scan downloads directory and restore completed download tasks"""
download_dir = Path("downloads")
if not download_dir.exists():
return
video_extensions = {".mp4", ".mkv", ".avi", ".mov", ".wmv", ".flv", ".webm"}
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
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
logger.info(f"Restored 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)