from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.cron import CronTrigger from app.utils.config import server_config from app.utils.logger import logger from app.utils.playlist_merge import sync_all_playlists, SyncMode import asyncio scheduler = BackgroundScheduler() def validate_cron_expression(expression: str) -> bool: try: parts = expression.split() if len(parts) != 5: return False # Try to create a trigger to validate CronTrigger( minute=parts[0], hour=parts[1], day=parts[2], month=parts[3], day_of_week=parts[4] ) return True except Exception: return False def job_function(): logger.info("Executing scheduled sync job...") try: # Re-read config to ensure latest values server_config.load() mode_str = server_config.sync_mode regex_rules = server_config.path_rules local_path = server_config.local_path asyncio.run(sync_all_playlists( sync_mode=SyncMode(mode_str), path_rules=regex_rules, local_dir=local_path )) logger.info("Scheduled sync job completed.") except Exception as e: logger.error(f"Scheduled sync job failed: {e}") def start_scheduler(): if not scheduler.running: scheduler.start() logger.info("Scheduler started.") update_scheduler_job() def update_scheduler_job(): scheduler.remove_all_jobs() # Reload config to get latest schedule settings server_config.load() mode = server_config.schedule_mode if mode == "DISABLED": logger.info("Schedule is disabled.") return trigger = None if mode == "CRON": cron_exp = server_config.schedule_cron if cron_exp: try: # 5 parts: minute hour day month day_of_week parts = cron_exp.split() if len(parts) == 5: trigger = CronTrigger( minute=parts[0], hour=parts[1], day=parts[2], month=parts[3], day_of_week=parts[4] ) except Exception as e: logger.error(f"Invalid cron expression: {cron_exp}, error: {e}") elif mode == "DAILY": time_str = server_config.schedule_daily_time try: hour, minute = map(int, time_str.split(':')) trigger = CronTrigger(hour=hour, minute=minute) except ValueError: logger.error(f"Invalid daily time: {time_str}") elif mode == "WEEKLY": days = server_config.schedule_weekly_days # list of ints 0-6 (Sun-Sat) time_str = server_config.schedule_weekly_time # Frontend: 0(Sun), 1(Mon)... 6(Sat) # APScheduler: 0(Mon)... 6(Sun) aps_days = [] for d in days: if d == 0: aps_days.append(6) else: aps_days.append(d - 1) days_str = ",".join(map(str, aps_days)) try: hour, minute = map(int, time_str.split(':')) trigger = CronTrigger(day_of_week=days_str, hour=hour, minute=minute) except ValueError: logger.error(f"Invalid weekly time: {time_str}") if trigger: scheduler.add_job(job_function, trigger) logger.info(f"Added scheduled job with mode {mode} and trigger {trigger}") else: logger.warning(f"Failed to create trigger for mode {mode}") def get_next_run_time(): jobs = scheduler.get_jobs() if not jobs: return None # Assuming only one job job = jobs[0] return job.next_run_time