import os import sys import threading import time from pathlib import Path from PIL import Image import pystray from pystray import MenuItem as item # Add agent root to sys.path sys.path.insert(0, str(Path(__file__).resolve().parent)) from agent.config import load_config, AgentConfig from agent.service import AgentDaemon from agent.uploader import ChunkUploader from create_icons import generate_app_icons class WindowsTrayAgent: """Windows System Tray (Área de Notificaciones) Application for OnEver Drive.""" def __init__(self): self.config = load_config() self.daemon = AgentDaemon(self.config) self.icon = None self.is_paused = False self.icon_image = self._load_icon() def _load_icon(self) -> Image.Image: assets_dir = Path(__file__).resolve().parent / "assets" png_path = assets_dir / "icon.png" if not png_path.exists(): _, png_path = generate_app_icons() return Image.open(png_path) def _get_status_text(self) -> str: if not self.config.client_code: return "Estado: Sin Registrar" if self.is_paused: return "Estado: Pausado" return f"Estado: Conectado ({self.config.client_code})" def _toggle_pause(self, icon, item_obj): self.is_paused = not self.is_paused if self.is_paused: self.daemon.stop() self.notify("Sincronización en pausa", "El servicio de backup ha sido pausado.") else: self.daemon.start() self.notify("Sincronización activa", "El servicio de backup se ha reanudado.") def _open_gui(self, icon=None, item_obj=None): def run_gui(): from agent_gui import launch_gui launch_gui() threading.Thread(target=run_gui, daemon=True).start() def _manual_backup(self, icon=None, item_obj=None): import tkinter as tk from tkinter import filedialog, messagebox def run_picker(): root = tk.Tk() root.withdraw() filepath = filedialog.askopenfilename( title="Seleccionar archivo para backup", filetypes=[("Archivos SQL / Datos", "*.bak;*.mdf;*.*")] ) if not filepath: root.destroy() return self.notify("Iniciando Backup", f"Preparando transferencia por chunks: {Path(filepath).name}") def upload_worker(): try: uploader = ChunkUploader(self.config) res = uploader.upload_file(Path(filepath)) self.notify("Backup Completado ✓", f"{Path(filepath).name} verificado con éxito en el servidor.") except Exception as ex: self.notify("Error en Backup ✗", f"Fallo al subir {Path(filepath).name}: {str(ex)}") threading.Thread(target=upload_worker, daemon=True).start() root.destroy() threading.Thread(target=run_picker, daemon=True).start() def notify(self, title: str, message: str): """Displays a native Windows Notification Balloon.""" if self.icon: try: self.icon.notify(message, title) except Exception: pass def _on_exit(self, icon, item_obj): self.daemon.stop() icon.stop() def build_menu(self): client_title = f"OnEver Drive ({self.config.client_name or 'Agente'})" return pystray.Menu( item(client_title, lambda: None, enabled=False), item(lambda text: self._get_status_text(), lambda: None, enabled=False), pystray.Menu.SEPARATOR, item("Abrir Panel de Control...", self._open_gui, default=True), item("Hacer Backup Manual...", self._manual_backup), item(lambda text: "Reanudar Servicio" if self.is_paused else "Pausar Servicio", self._toggle_pause), pystray.Menu.SEPARATOR, item("Salir", self._on_exit) ) def run(self): # Start background daemon worker if self.config.device_id: self.daemon.start() # Create tray icon self.icon = pystray.Icon( name="OnEverDrive", icon=self.icon_image, title="OnEver Drive — Agente de Backup", menu=self.build_menu() ) # Notify on startup if self.config.client_code: self.notify("OnEver Drive Activo", f"Agente en ejecución ({self.config.client_code})") else: self.notify("OnEver Drive", "Agente iniciado. Requiere registro en el servidor.") # Run system tray event loop self.icon.run() def main(): agent = WindowsTrayAgent() agent.run() if __name__ == "__main__": main()