left4me/l4d2web/services/job_worker.py
2026-05-05 23:47:06 +02:00

64 lines
1.9 KiB
Python

from dataclasses import dataclass, field
from datetime import UTC, datetime
from sqlalchemy import func, select
from sqlalchemy.orm import Session
from l4d2web.db import session_scope
from l4d2web.models import Job, JobLog, Server
@dataclass
class SchedulerState:
install_running: bool = False
running_servers: set[int] = field(default_factory=set)
def can_start(job, state: SchedulerState) -> bool:
if job.operation == "install":
return (not state.install_running) and (len(state.running_servers) == 0)
if state.install_running:
return False
if job.server_id is None:
return False
return job.server_id not in state.running_servers
def recover_stale_jobs() -> int:
now = datetime.now(UTC)
with session_scope() as db:
jobs = db.scalars(select(Job).where(Job.state == "running")).all()
for job in jobs:
job.state = "failed"
job.finished_at = now
job.updated_at = now
return len(jobs)
def append_job_log(
session: Session,
job_id: int,
stream: str,
line: str,
max_chars: int = 4096,
) -> int:
last_seq = session.scalar(select(func.max(JobLog.seq)).where(JobLog.job_id == job_id)) or 0
next_seq = int(last_seq) + 1
session.add(JobLog(job_id=job_id, seq=next_seq, stream=stream, line=line[:max_chars]))
session.flush()
return next_seq
def refresh_server_actual_state(server_id: int) -> str:
from l4d2web.services import l4d2_facade
now = datetime.now(UTC)
with session_scope() as db:
server = db.scalar(select(Server).where(Server.id == server_id))
if server is None:
return "unknown"
status = l4d2_facade.server_status(server.name)
server.actual_state = status.state
server.actual_state_updated_at = now
server.updated_at = now
return server.actual_state