161 lines
5.5 KiB
Python
161 lines
5.5 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import logging
|
|
import threading
|
|
import time
|
|
import tkinter as tk
|
|
from tkinter import messagebox
|
|
from pathlib import Path
|
|
|
|
from PIL import Image, ImageDraw
|
|
import pystray
|
|
|
|
from .backup import run_backup, shutdown_after_backup
|
|
from .backup_nogui import run_nogui_backup
|
|
from .config import AppConfig, load_config
|
|
from .logging_setup import configure_logging
|
|
from .registry import ChangeRegistry
|
|
|
|
|
|
class BakRestTrayApp:
|
|
def __init__(self, config: AppConfig, logger: logging.Logger) -> None:
|
|
self.config = config
|
|
self.logger = logger
|
|
self.registry = ChangeRegistry(config.storage.change_registry)
|
|
self.stop_event = threading.Event()
|
|
self.last_alert_reachable: bool | None = None
|
|
self.icon = pystray.Icon(
|
|
"BakRestTray",
|
|
_create_icon_image(),
|
|
"Bak&Rest",
|
|
self._build_menu(),
|
|
)
|
|
|
|
def run(self) -> None:
|
|
self.logger.info("BakRestTray started")
|
|
threading.Thread(target=self._server_status_monitor, daemon=True).start()
|
|
self.icon.run()
|
|
|
|
def _build_menu(self) -> pystray.Menu:
|
|
return pystray.Menu(
|
|
pystray.MenuItem(lambda item: self._status_text(), None, enabled=False),
|
|
pystray.Menu.SEPARATOR,
|
|
pystray.MenuItem("Backup e spegni", self._backup_and_shutdown),
|
|
pystray.MenuItem("Aggiorna stato", self._refresh),
|
|
pystray.MenuItem("Esci", self._quit),
|
|
)
|
|
|
|
def _status_text(self) -> str:
|
|
pending = self.registry.pending_count()
|
|
server = self._read_server_status()
|
|
return f"Server: {server} | File in attesa: {pending}"
|
|
|
|
def _read_server_status(self) -> str:
|
|
status = self._read_server_status_data()
|
|
if status is None:
|
|
return "sconosciuto"
|
|
return "raggiungibile" if status.get("reachable") else "non raggiungibile"
|
|
|
|
def _read_server_status_data(self) -> dict[str, object] | None:
|
|
path = self.config.storage.status_file
|
|
if not path.exists():
|
|
return None
|
|
try:
|
|
return json.loads(path.read_text(encoding="utf-8"))
|
|
except (OSError, json.JSONDecodeError):
|
|
return None
|
|
|
|
def _backup_and_shutdown(self, icon: pystray.Icon, item: pystray.MenuItem) -> None:
|
|
threading.Thread(target=self._backup_and_shutdown_worker, daemon=True).start()
|
|
|
|
def _backup_and_shutdown_worker(self) -> None:
|
|
self.logger.info("User requested Backup e spegni")
|
|
self.icon.title = "Bak&Rest - backup in corso"
|
|
result = run_backup(self.config, self.logger)
|
|
if result.success:
|
|
self.icon.title = "Bak&Rest - backup completato"
|
|
shutdown_after_backup(self.config, self.logger)
|
|
else:
|
|
self.icon.title = "Bak&Rest - backup fallito"
|
|
if result.error_code == "server_unreachable":
|
|
_show_error_message("Bak&Rest", "Server non raggiungibile. E' spento?")
|
|
else:
|
|
_show_error_message("Bak&Rest", result.message)
|
|
self.icon.update_menu()
|
|
|
|
def _refresh(self, icon: pystray.Icon, item: pystray.MenuItem) -> None:
|
|
self.icon.update_menu()
|
|
|
|
def _quit(self, icon: pystray.Icon, item: pystray.MenuItem) -> None:
|
|
self.logger.info("BakRestTray stopped")
|
|
self.stop_event.set()
|
|
self.icon.stop()
|
|
|
|
def _server_status_monitor(self) -> None:
|
|
interval = min(max(self.config.backup.server_check.interval_seconds, 60), 600)
|
|
while not self.stop_event.wait(interval):
|
|
status = self._read_server_status_data()
|
|
if status is None:
|
|
continue
|
|
|
|
reachable = bool(status.get("reachable"))
|
|
if reachable:
|
|
self.last_alert_reachable = True
|
|
continue
|
|
|
|
if self.last_alert_reachable is not False:
|
|
self.last_alert_reachable = False
|
|
self.logger.warning("Showing backup server unreachable alert")
|
|
_show_error_message(
|
|
"Bak&Rest",
|
|
"Accendi il server di backup o verifica che sia connesso alla rete",
|
|
)
|
|
|
|
|
|
def _create_icon_image() -> Image.Image:
|
|
image = Image.new("RGBA", (64, 64), (26, 38, 52, 255))
|
|
draw = ImageDraw.Draw(image)
|
|
draw.rounded_rectangle((10, 14, 54, 50), radius=8, fill=(77, 163, 255, 255))
|
|
draw.rectangle((18, 24, 46, 44), fill=(245, 248, 252, 255))
|
|
draw.line((22, 34, 32, 42, 46, 26), fill=(26, 38, 52, 255), width=5)
|
|
return image
|
|
|
|
|
|
def _show_error_message(title: str, message: str) -> None:
|
|
root = tk.Tk()
|
|
root.withdraw()
|
|
root.attributes("-topmost", True)
|
|
try:
|
|
messagebox.showerror(title, message, parent=root)
|
|
finally:
|
|
root.destroy()
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
parser = argparse.ArgumentParser(description="Bak&Rest tray app")
|
|
parser.add_argument(
|
|
"--nogui",
|
|
action="store_true",
|
|
help="Run backup and shutdown without tray icon",
|
|
)
|
|
parser.add_argument(
|
|
"--no-shutdown",
|
|
action="store_true",
|
|
help="With --nogui, run the backup but do not shut down the computer",
|
|
)
|
|
args = parser.parse_args(argv)
|
|
if args.nogui:
|
|
return run_nogui_backup(shutdown=not args.no_shutdown)
|
|
|
|
config = load_config()
|
|
logger = configure_logging(config.storage.logs_dir, "tray")
|
|
app = BakRestTrayApp(config, logger)
|
|
app.run()
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|