"Refactor job scheduling in backend using Conventional Commits (feat)"

This commit is contained in:
2025-05-29 23:10:52 +02:00
parent 4f4de4a84c
commit f5b4fdd296
2 changed files with 265 additions and 26 deletions

View File

@@ -392,34 +392,54 @@ class BackgroundTaskScheduler:
def _check_jobs(self) -> None:
"""
Überprüft und verwaltet Druckjobs:
- Startet anstehende Jobs
- Beendet abgelaufene Jobs
Überprüft und verwaltet Druckjobs mit intelligentem Power Management:
- Startet anstehende Jobs (geplante Jobs)
- Beendet abgelaufene Jobs (schaltet Steckdose aus)
- Schaltet Drucker automatisch aus bei Leerlauf
- Schaltet Drucker automatisch ein bei neuen Jobs
"""
db_session = get_db_session()
try:
now = datetime.now()
# 1. Anstehende Jobs starten
# 1. Anstehende Jobs starten (geplante Jobs)
pending_jobs = db_session.query(Job).filter(
Job.status == "scheduled",
Job.start_at <= now
).all()
for job in pending_jobs:
self.logger.info(f"🚀 Starte Job {job.id}: {job.name}")
self.logger.info(f"🚀 Starte geplanten Job {job.id}: {job.name}")
# Steckdose einschalten
if self.toggle_printer_plug(job.printer_id, True):
# Job als laufend markieren
job.status = "running"
db_session.commit()
self.logger.info(f"✅ Job {job.id} gestartet")
self.logger.info(f"✅ Job {job.id} gestartet - Drucker eingeschaltet")
else:
self.logger.error(f"❌ Konnte Steckdose für Job {job.id} nicht einschalten")
# 2. Abgelaufene Jobs beenden
# 2. Sofort-Jobs starten (Jobs die bereits hätten starten sollen)
immediate_jobs = db_session.query(Job).filter(
Job.status == "waiting_for_printer",
Job.start_at <= now
).all()
for job in immediate_jobs:
self.logger.info(f"⚡ Starte Sofort-Job {job.id}: {job.name}")
# Steckdose einschalten
if self.toggle_printer_plug(job.printer_id, True):
# Job als laufend markieren
job.status = "running"
db_session.commit()
self.logger.info(f"✅ Sofort-Job {job.id} gestartet - Drucker automatisch eingeschaltet")
else:
self.logger.error(f"❌ Konnte Steckdose für Sofort-Job {job.id} nicht einschalten")
# 3. Abgelaufene Jobs beenden
running_jobs = db_session.query(Job).filter(
Job.status == "running",
Job.end_at <= now
@@ -428,15 +448,51 @@ class BackgroundTaskScheduler:
for job in running_jobs:
self.logger.info(f"🏁 Beende Job {job.id}: {job.name}")
# Steckdose ausschalten
if self.toggle_printer_plug(job.printer_id, False):
# Job als beendet markieren
job.status = "finished"
job.actual_end_time = now
db_session.commit()
self.logger.info(f"✅ Job {job.id} beendet")
# Job als beendet markieren
job.status = "finished"
job.actual_end_time = now
db_session.commit()
self.logger.info(f"✅ Job {job.id} beendet")
# Prüfen ob weitere Jobs für diesen Drucker anstehen
pending_jobs_for_printer = db_session.query(Job).filter(
Job.printer_id == job.printer_id,
Job.status.in_(["scheduled", "running", "waiting_for_printer"])
).count()
if pending_jobs_for_printer == 0:
# Keine weiteren Jobs - Drucker ausschalten (Leerlauf-Management)
if self.toggle_printer_plug(job.printer_id, False):
self.logger.info(f"💤 Drucker {job.printer_id} automatisch ausgeschaltet - Leerlauf erkannt")
else:
self.logger.warning(f"⚠️ Konnte Drucker {job.printer_id} nicht ausschalten")
else:
self.logger.error(f"❌ Konnte Steckdose für Job {job.id} nicht ausschalten")
self.logger.info(f"🔄 Drucker {job.printer_id} bleibt eingeschaltet - {pending_jobs_for_printer} weitere Jobs anstehend")
# 4. Intelligentes Leerlauf-Management für alle aktiven Drucker
active_printers = db_session.query(Printer).filter(
Printer.active == True,
Printer.plug_ip.isnot(None),
Printer.status == "online"
).all()
for printer in active_printers:
# Prüfen ob Jobs für diesen Drucker anstehen
active_jobs_count = db_session.query(Job).filter(
Job.printer_id == printer.id,
Job.status.in_(["scheduled", "running", "waiting_for_printer"])
).count()
if active_jobs_count == 0:
# Keine Jobs anstehend - prüfen ob Drucker schon längere Zeit im Leerlauf ist
if printer.last_checked:
idle_time = now - printer.last_checked
# Drucker ausschalten wenn länger als 5 Minuten im Leerlauf
if idle_time.total_seconds() > 300: # 5 Minuten
if self.toggle_printer_plug(printer.id, False):
self.logger.info(f"💤 Drucker {printer.name} nach {idle_time.total_seconds()//60:.0f} Min Leerlauf ausgeschaltet")
else:
self.logger.warning(f"⚠️ Konnte Drucker {printer.name} nach Leerlauf nicht ausschalten")
except Exception as e:
self.logger.error(f"❌ Fehler bei Überprüfung der Jobs: {str(e)}")
@@ -448,6 +504,142 @@ class BackgroundTaskScheduler:
finally:
db_session.close()
def handle_immediate_job(self, job_id: int) -> bool:
"""
Behandelt einen Job sofort (für Sofort-Start bei Job-Erstellung).
Args:
job_id: ID des zu startenden Jobs
Returns:
bool: True wenn Job erfolgreich gestartet wurde
"""
db_session = get_db_session()
try:
now = datetime.now()
# Job aus Datenbank laden
job = db_session.query(Job).get(job_id)
if not job:
self.logger.error(f"❌ Job {job_id} nicht gefunden")
db_session.close()
return False
# Nur Jobs behandeln die sofort starten sollen
if job.start_at > now:
self.logger.info(f"⏰ Job {job_id} ist für später geplant ({job.start_at}) - kein Sofort-Start")
db_session.close()
return False
# Nur Jobs in passenden Status
if job.status not in ["scheduled", "waiting_for_printer"]:
self.logger.info(f" Job {job_id} hat Status '{job.status}' - kein Sofort-Start nötig")
db_session.close()
return False
self.logger.info(f"⚡ Starte Sofort-Job {job_id}: {job.name} für Drucker {job.printer_id}")
# Steckdose einschalten
if self.toggle_printer_plug(job.printer_id, True):
# Job als laufend markieren
job.status = "running"
db_session.commit()
db_session.close()
self.logger.info(f"✅ Sofort-Job {job_id} erfolgreich gestartet - Drucker automatisch eingeschaltet")
return True
else:
self.logger.error(f"❌ Konnte Steckdose für Sofort-Job {job_id} nicht einschalten")
db_session.close()
return False
except Exception as e:
self.logger.error(f"❌ Fehler beim Starten von Sofort-Job {job_id}: {str(e)}")
try:
db_session.rollback()
db_session.close()
except:
pass
return False
def check_and_manage_printer_power(self, printer_id: int) -> bool:
"""
Prüft und verwaltet die Stromversorgung eines spezifischen Druckers.
Args:
printer_id: ID des zu prüfenden Druckers
Returns:
bool: True wenn Power-Management erfolgreich
"""
db_session = get_db_session()
try:
now = datetime.now()
# Drucker laden
printer = db_session.query(Printer).get(printer_id)
if not printer or not printer.plug_ip:
db_session.close()
return False
# Aktive Jobs für diesen Drucker prüfen
active_jobs = db_session.query(Job).filter(
Job.printer_id == printer_id,
Job.status.in_(["scheduled", "running", "waiting_for_printer"])
).all()
current_jobs = [job for job in active_jobs if job.start_at <= now]
future_jobs = [job for job in active_jobs if job.start_at > now]
if current_jobs:
# Jobs laufen oder sollten laufen - Drucker einschalten
self.logger.info(f"🔋 Drucker {printer.name} benötigt Strom - {len(current_jobs)} aktive Jobs")
success = self.toggle_printer_plug(printer_id, True)
# Jobs von waiting_for_printer auf running umstellen
for job in current_jobs:
if job.status == "waiting_for_printer":
job.status = "running"
self.logger.info(f"🚀 Job {job.id} von 'waiting_for_printer' auf 'running' umgestellt")
db_session.commit()
db_session.close()
return success
elif future_jobs:
# Nur zukünftige Jobs - Drucker kann ausgeschaltet bleiben
next_job_time = min(job.start_at for job in future_jobs)
time_until_next = (next_job_time - now).total_seconds() / 60
self.logger.info(f"⏳ Drucker {printer.name} hat {len(future_jobs)} zukünftige Jobs, nächster in {time_until_next:.1f} Min")
# Drucker ausschalten wenn nächster Job erst in mehr als 10 Minuten
if time_until_next > 10:
success = self.toggle_printer_plug(printer_id, False)
db_session.close()
return success
else:
self.logger.info(f"🔄 Drucker {printer.name} bleibt eingeschaltet - nächster Job bald")
db_session.close()
return True
else:
# Keine Jobs - Drucker ausschalten (Leerlauf)
self.logger.info(f"💤 Drucker {printer.name} hat keine anstehenden Jobs - ausschalten")
success = self.toggle_printer_plug(printer_id, False)
db_session.close()
return success
except Exception as e:
self.logger.error(f"❌ Fehler beim Power-Management für Drucker {printer_id}: {str(e)}")
try:
db_session.close()
except:
pass
return False
def test_tapo_connection(ip_address: str, username: str = None, password: str = None) -> dict:
"""