//! Etat partage thread-safe de l'agent. //! //! Centralise l'etat courant (enregistrement, replay, connexion, etc.) //! accessible depuis tous les threads (systray, heartbeat, replay, recorder). //! Equivalent de agent_v1/ui/shared_state.py. use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use std::sync::{Arc, Mutex}; /// Etats possibles de l'icone systray #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TrayState { /// Gris — en attente, pas de session active Idle, /// Rouge — enregistrement en cours Recording, /// Vert — connecte au serveur, pret Connected, /// Bleu — replay en cours Replay, } /// Etat partage de l'agent, thread-safe via Arc + atomics. /// /// Les booleens utilisent AtomicBool pour un acces lock-free. /// Le nom de session utilise un Mutex car c'est une String. #[derive(Debug)] pub struct AgentState { /// Enregistrement en cours (session de capture) pub recording: AtomicBool, /// Nom de la session d'enregistrement courante pub recording_name: Mutex, /// Replay en cours (execution d'actions) pub replay_active: AtomicBool, /// Connecte au serveur streaming pub connected: AtomicBool, /// Nombre d'actions capturees dans la session courante pub actions_count: AtomicU32, /// L'agent est en cours d'execution (false = arret demande) pub running: AtomicBool, /// Fenetre de chat visible pub chat_visible: AtomicBool, /// Arret d'urgence active pub emergency_stop: AtomicBool, /// Dernier message de notification (pour eviter les doublons) #[allow(dead_code)] pub last_notification: Mutex, } impl AgentState { /// Cree un nouvel etat avec les valeurs par defaut. pub fn new() -> Arc { Arc::new(Self { recording: AtomicBool::new(false), recording_name: Mutex::new(String::new()), replay_active: AtomicBool::new(false), connected: AtomicBool::new(false), actions_count: AtomicU32::new(0), running: AtomicBool::new(true), chat_visible: AtomicBool::new(false), emergency_stop: AtomicBool::new(false), last_notification: Mutex::new(String::new()), }) } /// Demarre un enregistrement avec le nom donne. pub fn start_recording(&self, name: &str) { self.recording.store(true, Ordering::SeqCst); self.actions_count.store(0, Ordering::SeqCst); if let Ok(mut n) = self.recording_name.lock() { *n = name.to_string(); } println!("[STATE] Enregistrement demarre : '{}'", name); } /// Arrete l'enregistrement en cours. pub fn stop_recording(&self) -> (String, u32) { self.recording.store(false, Ordering::SeqCst); let count = self.actions_count.load(Ordering::SeqCst); let name = self .recording_name .lock() .map(|n| n.clone()) .unwrap_or_default(); println!("[STATE] Enregistrement arrete : '{}' ({} actions)", name, count); (name, count) } /// Incremente le compteur d'actions capturees. pub fn increment_actions(&self) -> u32 { self.actions_count.fetch_add(1, Ordering::SeqCst) + 1 } /// Verifie si l'agent est en cours d'execution. pub fn is_running(&self) -> bool { self.running.load(Ordering::SeqCst) } /// Demande l'arret de l'agent. pub fn request_shutdown(&self) { self.running.store(false, Ordering::SeqCst); println!("[STATE] Arret demande"); } /// Active/desactive le replay. pub fn set_replay_active(&self, active: bool) { self.replay_active.store(active, Ordering::SeqCst); } /// Met a jour le statut de connexion au serveur. pub fn set_connected(&self, connected: bool) { let was_connected = self.connected.swap(connected, Ordering::SeqCst); if was_connected != connected { println!( "[STATE] Connexion serveur : {}", if connected { "CONNECTE" } else { "DECONNECTE" } ); } } /// Active l'arret d'urgence — stoppe tout immediatement. pub fn emergency_stop(&self) { self.emergency_stop.store(true, Ordering::SeqCst); self.recording.store(false, Ordering::SeqCst); self.replay_active.store(false, Ordering::SeqCst); println!("[STATE] === ARRET D'URGENCE ACTIVE ==="); } /// Retourne l'etat courant du systray. pub fn tray_state(&self) -> TrayState { if self.recording.load(Ordering::SeqCst) { TrayState::Recording } else if self.replay_active.load(Ordering::SeqCst) { TrayState::Replay } else if self.connected.load(Ordering::SeqCst) { TrayState::Connected } else { TrayState::Idle } } /// Retourne le nom de la session d'enregistrement courante. pub fn current_recording_name(&self) -> String { self.recording_name .lock() .map(|n| n.clone()) .unwrap_or_default() } } impl Default for AgentState { fn default() -> Self { // Note: on ne peut pas retourner Arc depuis Default, // donc on fournit les valeurs brutes. Utiliser new() de preference. Self { recording: AtomicBool::new(false), recording_name: Mutex::new(String::new()), replay_active: AtomicBool::new(false), connected: AtomicBool::new(false), actions_count: AtomicU32::new(0), running: AtomicBool::new(true), chat_visible: AtomicBool::new(false), emergency_stop: AtomicBool::new(false), last_notification: Mutex::new(String::new()), } } }