"""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.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()