170 lines
5.5 KiB
Python
170 lines
5.5 KiB
Python
"""Scheduler service for managing background tasks with APScheduler."""
|
|
|
|
import logging
|
|
|
|
from apscheduler.schedulers.background import BackgroundScheduler
|
|
from apscheduler.triggers.cron import CronTrigger
|
|
from flask import current_app
|
|
|
|
from app.services.credit_service import CreditService
|
|
from app.services.sound_scanner_service import SoundScannerService
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class SchedulerService:
|
|
"""Service for managing scheduled background tasks."""
|
|
|
|
def __init__(self, app=None) -> None:
|
|
"""Initialize the scheduler service."""
|
|
self.scheduler: BackgroundScheduler | None = None
|
|
self.app = app
|
|
|
|
def start(self) -> None:
|
|
"""Start the scheduler and add all scheduled jobs."""
|
|
if self.scheduler is not None:
|
|
logger.warning("Scheduler is already running")
|
|
return
|
|
|
|
self.scheduler = BackgroundScheduler()
|
|
|
|
# Add daily credit refill job
|
|
self._add_daily_credit_refill_job()
|
|
|
|
# Add sound scanning job
|
|
self._add_sound_scanning_job()
|
|
|
|
# Start the scheduler
|
|
self.scheduler.start()
|
|
logger.info("Scheduler started successfully")
|
|
|
|
def stop(self) -> None:
|
|
"""Stop the scheduler."""
|
|
if self.scheduler is not None:
|
|
self.scheduler.shutdown()
|
|
self.scheduler = None
|
|
logger.info("Scheduler stopped")
|
|
|
|
def _add_daily_credit_refill_job(self) -> None:
|
|
"""Add the daily credit refill job."""
|
|
if self.scheduler is None:
|
|
raise RuntimeError("Scheduler not initialized")
|
|
|
|
# Schedule daily at 00:00 UTC
|
|
trigger = CronTrigger(hour=0, minute=0)
|
|
|
|
self.scheduler.add_job(
|
|
func=self._run_daily_credit_refill,
|
|
trigger=trigger,
|
|
id="daily_credit_refill",
|
|
name="Daily Credit Refill",
|
|
replace_existing=True,
|
|
)
|
|
|
|
logger.info("Daily credit refill job scheduled for 00:00 UTC")
|
|
|
|
def _add_sound_scanning_job(self) -> None:
|
|
"""Add the sound scanning job."""
|
|
if self.scheduler is None:
|
|
raise RuntimeError("Scheduler not initialized")
|
|
|
|
# Schedule every 5 minutes for sound scanning
|
|
trigger = CronTrigger(minute="*/5")
|
|
|
|
self.scheduler.add_job(
|
|
func=self._run_sound_scan,
|
|
trigger=trigger,
|
|
id="sound_scan",
|
|
name="Sound Directory Scan",
|
|
replace_existing=True,
|
|
)
|
|
|
|
logger.info("Sound scanning job scheduled every 5 minutes")
|
|
|
|
def _run_daily_credit_refill(self) -> None:
|
|
"""Execute the daily credit refill task."""
|
|
logger.info("Starting daily credit refill task")
|
|
|
|
app = self.app or current_app
|
|
with app.app_context():
|
|
try:
|
|
result = CreditService.refill_all_users_credits()
|
|
|
|
if result["success"]:
|
|
logger.info(
|
|
f"Daily credit refill completed successfully: "
|
|
f"{result['users_processed']} users processed, "
|
|
f"{result['credits_added']} credits added",
|
|
)
|
|
else:
|
|
logger.error(
|
|
f"Daily credit refill failed: {result['message']}",
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.exception(f"Error during daily credit refill: {e}")
|
|
|
|
def _run_sound_scan(self) -> None:
|
|
"""Execute the sound scanning task."""
|
|
logger.info("Starting sound directory scan")
|
|
|
|
app = self.app or current_app
|
|
with app.app_context():
|
|
try:
|
|
result = SoundScannerService.scan_soundboard_directory()
|
|
|
|
if result["success"]:
|
|
if result["files_added"] > 0:
|
|
logger.info(
|
|
f"Sound scan completed: {result['files_added']} new sounds added",
|
|
)
|
|
else:
|
|
logger.debug("Sound scan completed: no new files found")
|
|
else:
|
|
logger.error(
|
|
f"Sound scan failed: {result.get('error', 'Unknown error')}",
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.exception(f"Error during sound scan: {e}")
|
|
|
|
def trigger_credit_refill_now(self) -> dict:
|
|
"""Manually trigger credit refill for testing purposes."""
|
|
logger.info("Manually triggering credit refill")
|
|
app = self.app or current_app
|
|
with app.app_context():
|
|
return CreditService.refill_all_users_credits()
|
|
|
|
def trigger_sound_scan_now(self) -> dict:
|
|
"""Manually trigger sound scan for testing purposes."""
|
|
logger.info("Manually triggering sound scan")
|
|
app = self.app or current_app
|
|
with app.app_context():
|
|
return SoundScannerService.scan_soundboard_directory()
|
|
|
|
def get_scheduler_status(self) -> dict:
|
|
"""Get the current status of the scheduler."""
|
|
if self.scheduler is None:
|
|
return {"running": False, "jobs": []}
|
|
|
|
jobs = [
|
|
{
|
|
"id": job.id,
|
|
"name": job.name,
|
|
"next_run": job.next_run_time.isoformat()
|
|
if job.next_run_time
|
|
else None,
|
|
"trigger": str(job.trigger),
|
|
}
|
|
for job in self.scheduler.get_jobs()
|
|
]
|
|
|
|
return {
|
|
"running": self.scheduler.running,
|
|
"jobs": jobs,
|
|
}
|
|
|
|
|
|
# Global scheduler instance
|
|
scheduler_service = SchedulerService()
|