Files
rippy/docker/worker/tray.py
T
Hitonabi 404a002d5c
Ampel / ampel (push) Successful in 27s
Windows-Worker: Tray-Symbol + rueckstandsfreie Deinstallation
- tray.py (nur nativer Windows-Worker; Docker unberuehrt): pystray+Pillow-
  Tray neben der Uhr — Status, Worker starten/stoppen, Rippy oeffnen,
  Log anzeigen, Beenden (stoppt den Worker mit). Celery laeuft als
  Kind-Prozess ohne Konsolenfenster (CREATE_NO_WINDOW), Log in worker.log.
- install.ps1 erzeugt jetzt start-tray.bat (pythonw, empfohlen),
  start-worker.bat (Konsole/Debug) und uninstall.ps1: stoppt alle
  Prozesse aus dem Ordner, loescht die Autostart-Aufgabe, meldet den
  Worker per DELETE /workers/{name} in Rippy ab und entfernt den Ordner
  komplett. -Autostart registriert die Tray-Variante (onlogon).
- UI-Beschreibung + Anleitung entsprechend ergaenzt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 15:44:29 +02:00

122 lines
3.5 KiB
Python

"""Tray-Symbol für den NATIVEN Windows-Worker (nicht im Docker-Image genutzt).
Zeigt den Worker-Status unten rechts neben der Uhr und bietet:
Start/Stopp, Rippy im Browser öffnen, Log anzeigen, Beenden. Gestartet
wird es über start-tray.bat (setzt die Verbindungs-Umgebung und ruft
pythonw.exe auf — kein Konsolenfenster). Der Worker-Prozess (celery,
Queue transcode) läuft als Kind-Prozess und stoppt mit dem Tray.
Abhängigkeiten pystray + Pillow installiert NUR der Windows-Installer
(install.ps1) — das Docker-Image bleibt davon unberührt.
"""
import os
import subprocess
import sys
import webbrowser
try:
import pystray
from PIL import Image, ImageDraw
except ImportError:
print("Tray braucht pystray + Pillow — install.ps1 erneut ausführen.")
sys.exit(1)
BASIS = os.path.dirname(os.path.abspath(__file__))
RIPPY_HOST = os.getenv("RIPPY_TRAY_HOST", "")
WORKER_NAME = os.getenv("WORKER_NAME", "windows-worker")
LOG_PFAD = os.path.join(BASIS, "worker.log")
prozess = None
def disc_bild(laeuft: bool) -> Image.Image:
"""Disc-Symbol im Rippy-Look: Indigo wenn der Worker läuft, grau sonst."""
bild = Image.new("RGBA", (64, 64), (0, 0, 0, 0))
zeichner = ImageDraw.Draw(bild)
farbe = (99, 102, 241, 255) if laeuft else (125, 132, 145, 255)
zeichner.ellipse([4, 4, 60, 60], fill=farbe)
zeichner.ellipse([24, 24, 40, 40], fill=(255, 255, 255, 255))
return bild
def worker_laeuft() -> bool:
return prozess is not None and prozess.poll() is None
def worker_starten(icon=None, item=None):
global prozess
if worker_laeuft():
return
celery = os.path.join(BASIS, "venv", "Scripts", "celery.exe")
log = open(LOG_PFAD, "a", encoding="utf-8", errors="replace")
prozess = subprocess.Popen(
[celery, "-A", "celery_app", "worker", "--loglevel=info",
"-Q", "transcode", "--pool=solo", "-n", f"{WORKER_NAME}@%h"],
cwd=BASIS, stdout=log, stderr=subprocess.STDOUT,
creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
)
if icon:
icon.icon = disc_bild(True)
icon.update_menu()
def worker_stoppen(icon=None, item=None):
global prozess
if worker_laeuft():
prozess.terminate()
try:
prozess.wait(15)
except subprocess.TimeoutExpired:
prozess.kill()
prozess = None
if icon:
icon.icon = disc_bild(False)
icon.update_menu()
def umschalten(icon, item):
if worker_laeuft():
worker_stoppen(icon)
else:
worker_starten(icon)
def rippy_oeffnen(icon, item):
if RIPPY_HOST:
webbrowser.open(f"http://{RIPPY_HOST}")
def log_anzeigen(icon, item):
if os.path.exists(LOG_PFAD):
os.startfile(LOG_PFAD) # noqa: S606 — bewusst: Standard-Editor des Nutzers
def beenden(icon, item):
worker_stoppen()
icon.stop()
menue = pystray.Menu(
pystray.MenuItem(
lambda item: f'Rippy-Worker "{WORKER_NAME}": {"läuft" if worker_laeuft() else "gestoppt"}',
None, enabled=False,
),
pystray.Menu.SEPARATOR,
pystray.MenuItem(
lambda item: "Worker stoppen" if worker_laeuft() else "Worker starten",
umschalten,
),
pystray.MenuItem("Rippy öffnen", rippy_oeffnen),
pystray.MenuItem("Log anzeigen", log_anzeigen),
pystray.Menu.SEPARATOR,
pystray.MenuItem("Beenden (stoppt den Worker)", beenden),
)
if __name__ == "__main__":
tray = pystray.Icon("rippy-worker", disc_bild(False), "Rippy Worker", menue)
worker_starten()
tray.icon = disc_bild(worker_laeuft())
tray.run()