2b4cc617cb
- Implemented YAML-driven GenericScraper for resilient scraping - Added ProvidersManager to manage scraper health and active providers - Modernized unified search with systematic Kitsu metadata enrichment - Integrated automated health checks in the scheduler - Added comprehensive tests for scraping DSL and provider health
183 lines
5.9 KiB
Python
183 lines
5.9 KiB
Python
"""Scheduler for automatic episode checking and downloading"""
|
|
import asyncio
|
|
import logging
|
|
from datetime import datetime
|
|
from typing import Optional
|
|
|
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
|
from apscheduler.triggers.interval import IntervalTrigger
|
|
|
|
from app.watchlist import watchlist_manager, WatchlistManager
|
|
from app.episode_checker import EpisodeChecker, episode_checker
|
|
from app.providers_manager import providers_manager
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class AutoDownloadScheduler:
|
|
"""Manages automatic episode checking and downloading on a schedule"""
|
|
|
|
def __init__(
|
|
self,
|
|
wlm: Optional[WatchlistManager] = None,
|
|
checker: Optional[EpisodeChecker] = None
|
|
):
|
|
self.wlm = wlm or watchlist_manager
|
|
self.checker = checker or episode_checker
|
|
self.providers_mgr = providers_manager
|
|
self.scheduler: Optional[AsyncIOScheduler] = None
|
|
self._running = False
|
|
|
|
async def _check_job(self):
|
|
"""Job function that runs periodically to check for new episodes"""
|
|
try:
|
|
logger.info("Running scheduled episode check...")
|
|
results = await self.checker.check_all_due()
|
|
|
|
# Log summary
|
|
for result in results:
|
|
if result.new_episodes_found > 0:
|
|
logger.info(
|
|
f"✓ {result.anime_title}: "
|
|
f"{result.new_episodes_found} new, "
|
|
f"{len(result.episodes_downloaded)} downloaded"
|
|
)
|
|
|
|
logger.info(f"Scheduled check complete: processed {len(results)} items")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error in scheduled check job: {e}", exc_info=True)
|
|
|
|
async def _health_check_job(self):
|
|
"""Job function that runs periodically to check provider health"""
|
|
try:
|
|
logger.info("Running scheduled provider health check...")
|
|
await self.providers_mgr.check_all_health()
|
|
except Exception as e:
|
|
logger.error(f"Error in health check job: {e}")
|
|
|
|
def start(self):
|
|
"""Start the scheduler"""
|
|
if self._running:
|
|
logger.warning("Scheduler already running")
|
|
return
|
|
|
|
try:
|
|
self.scheduler = AsyncIOScheduler()
|
|
|
|
# Get initial check interval from settings
|
|
settings = self.wlm.get_settings()
|
|
interval_hours = settings.check_interval_hours
|
|
|
|
# Add the job for episode checking
|
|
self.scheduler.add_job(
|
|
self._check_job,
|
|
trigger=IntervalTrigger(hours=interval_hours),
|
|
id='episode_check',
|
|
name='Check for new episodes',
|
|
replace_existing=True
|
|
)
|
|
|
|
# Add the job for provider health check (every 6 hours)
|
|
self.scheduler.add_job(
|
|
self._health_check_job,
|
|
trigger=IntervalTrigger(hours=6),
|
|
id='provider_health',
|
|
name='Check provider health',
|
|
replace_existing=True
|
|
)
|
|
|
|
# Start the scheduler
|
|
self.scheduler.start()
|
|
self._running = True
|
|
|
|
logger.info(
|
|
f"Auto-download scheduler started (checking every {interval_hours}h)"
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error starting scheduler: {e}", exc_info=True)
|
|
raise
|
|
|
|
def stop(self):
|
|
"""Stop the scheduler"""
|
|
if not self._running:
|
|
logger.warning("Scheduler not running")
|
|
return
|
|
|
|
try:
|
|
if self.scheduler:
|
|
self.scheduler.shutdown(wait=False)
|
|
self.scheduler = None
|
|
|
|
self._running = False
|
|
logger.info("Auto-download scheduler stopped")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error stopping scheduler: {e}", exc_info=True)
|
|
|
|
def restart(self):
|
|
"""Restart the scheduler with updated settings"""
|
|
logger.info("Restarting scheduler with new settings...")
|
|
self.stop()
|
|
self.start()
|
|
|
|
def update_interval(self, hours: int):
|
|
"""Update the check interval"""
|
|
if not self._running:
|
|
logger.warning("Scheduler not running, interval will be applied on start")
|
|
return
|
|
|
|
try:
|
|
settings = self.wlm.get_settings()
|
|
settings.check_interval_hours = hours
|
|
self.wlm.update_settings(settings)
|
|
|
|
# Restart to apply new interval
|
|
self.restart()
|
|
|
|
logger.info(f"Updated check interval to {hours}h")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error updating interval: {e}", exc_info=True)
|
|
|
|
def get_next_run_time(self) -> Optional[datetime]:
|
|
"""Get the next scheduled run time"""
|
|
if not self._running or not self.scheduler:
|
|
return None
|
|
|
|
try:
|
|
job = self.scheduler.get_job('episode_check')
|
|
if job:
|
|
return job.next_run_time
|
|
except Exception as e:
|
|
logger.error(f"Error getting next run time: {e}")
|
|
|
|
return None
|
|
|
|
def is_running(self) -> bool:
|
|
"""Check if scheduler is running"""
|
|
return self._running
|
|
|
|
async def trigger_check_now(self):
|
|
"""Manually trigger an episode check now"""
|
|
logger.info("Manually triggering episode check...")
|
|
try:
|
|
await self._check_job()
|
|
except Exception as e:
|
|
logger.error(f"Error in manual check: {e}", exc_info=True)
|
|
raise
|
|
|
|
async def trigger_health_check_now(self):
|
|
"""Manually trigger a health check now"""
|
|
logger.info("Manually triggering provider health check...")
|
|
try:
|
|
await self._health_check_job()
|
|
except Exception as e:
|
|
logger.error(f"Error in manual health check: {e}")
|
|
raise
|
|
|
|
|
|
# Global scheduler instance
|
|
auto_download_scheduler = AutoDownloadScheduler()
|