"""Scheduler for automatic episode checking and downloading""" 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()