114 lines
4.1 KiB
Python
114 lines
4.1 KiB
Python
"""Non-blocking controls for the system Syncthing instance."""
|
||
import queue
|
||
import subprocess
|
||
import threading
|
||
from tkinter import ttk, messagebox
|
||
|
||
SERVICE = "syncthing@sebastien.service"
|
||
|
||
|
||
def service_status():
|
||
result = subprocess.run(
|
||
["systemctl", "show", SERVICE, "--property=ActiveState", "--value"],
|
||
capture_output=True, text=True, timeout=10,
|
||
)
|
||
if result.returncode:
|
||
raise RuntimeError(result.stderr.strip() or "Statut indisponible")
|
||
return result.stdout.strip() or "unknown"
|
||
|
||
|
||
def control_service(action):
|
||
if action == "ensure" and service_status() == "active":
|
||
return "active"
|
||
command = "start" if action == "ensure" else action
|
||
result = subprocess.run(
|
||
["systemctl", "--no-ask-password", command, SERVICE],
|
||
capture_output=True, text=True, timeout=60,
|
||
)
|
||
if result.returncode:
|
||
raise RuntimeError(result.stderr.strip() or f"Échec de systemctl {command}")
|
||
status = service_status()
|
||
if command == "start" and status != "active":
|
||
raise RuntimeError(f"Syncthing n’est pas actif : {status}")
|
||
return status
|
||
|
||
|
||
class SyncthingPanel(ttk.Frame):
|
||
def __init__(self, parent):
|
||
super().__init__(parent)
|
||
self.events = queue.Queue()
|
||
self.busy = False
|
||
self.pending_export = None
|
||
self.status = ttk.Label(self, text=f"{SERVICE} : vérification…")
|
||
self.status.pack(anchor="w")
|
||
controls = ttk.Frame(self)
|
||
controls.pack(anchor="w", pady=4)
|
||
self.start_button = ttk.Button(controls, text="Démarrer Syncthing",
|
||
command=lambda: self.request("start"))
|
||
self.start_button.pack(side="left")
|
||
self.stop_button = ttk.Button(controls, text="Arrêter Syncthing",
|
||
command=lambda: self.request("stop"))
|
||
self.stop_button.pack(side="left", padx=6)
|
||
self.timer = self.after(100, self.poll)
|
||
self.refresh_timer = None
|
||
self.bind("<Destroy>", self.cleanup, add=True)
|
||
self.request("status")
|
||
|
||
def ensure_started(self, callback):
|
||
self.pending_export = callback
|
||
if not self.busy:
|
||
self.request("ensure")
|
||
|
||
def request(self, action):
|
||
if self.busy:
|
||
return
|
||
self.busy = True
|
||
self.start_button.configure(state="disabled")
|
||
self.stop_button.configure(state="disabled")
|
||
if action != "status":
|
||
self.status.configure(text=f"{SERVICE} : opération en cours…")
|
||
|
||
def work():
|
||
try:
|
||
status = service_status() if action == "status" else control_service(action)
|
||
self.events.put((action, status, None))
|
||
except (OSError, RuntimeError, subprocess.TimeoutExpired) as exc:
|
||
self.events.put((action, None, str(exc)))
|
||
|
||
threading.Thread(target=work, daemon=True).start()
|
||
|
||
def poll(self):
|
||
try:
|
||
action, status, error = self.events.get_nowait()
|
||
except queue.Empty:
|
||
pass
|
||
else:
|
||
self.busy = False
|
||
self.start_button.configure(state="normal")
|
||
self.stop_button.configure(state="normal")
|
||
self.status.configure(text=f"{SERVICE} : {error or status}")
|
||
if error and action != "status":
|
||
self.pending_export = None
|
||
messagebox.showerror("Syncthing", error)
|
||
elif action == "ensure":
|
||
callback, self.pending_export = self.pending_export, None
|
||
if callback:
|
||
callback()
|
||
elif self.pending_export:
|
||
self.request("ensure")
|
||
if self.winfo_exists():
|
||
self.timer = self.after(100, self.poll)
|
||
if self.refresh_timer is None:
|
||
self.refresh_timer = self.after(5000, self.refresh)
|
||
|
||
def refresh(self):
|
||
self.refresh_timer = None
|
||
self.request("status")
|
||
|
||
def cleanup(self, event):
|
||
if event.widget == self:
|
||
self.pending_export = None
|
||
for timer in (self.timer, self.refresh_timer):
|
||
if timer:
|
||
self.after_cancel(timer)
|