"""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 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.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) 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 self.scheduler.add_job( self._check_job, trigger=IntervalTrigger(hours=interval_hours), id='episode_check', name='Check for new episodes', 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 # Global scheduler instance auto_download_scheduler = AutoDownloadScheduler()