rush_sync_server/commands/recovery/
command.rs

1// NEW: src/commands/recovery/command.rs
2use crate::commands::command::Command;
3use crate::core::prelude::*;
4use crate::server::types::{ServerContext, ServerStatus};
5use crate::server::utils::port::is_port_available;
6
7#[derive(Debug, Default)]
8pub struct RecoveryCommand;
9
10impl RecoveryCommand {
11    pub fn new() -> Self {
12        Self
13    }
14}
15
16impl Command for RecoveryCommand {
17    fn name(&self) -> &'static str {
18        "recover"
19    }
20
21    fn description(&self) -> &'static str {
22        "Recover and fix server status inconsistencies"
23    }
24
25    fn matches(&self, command: &str) -> bool {
26        let cmd = command.trim().to_lowercase();
27        cmd.starts_with("recover") || cmd.starts_with("fix") || cmd == "status-fix"
28    }
29
30    fn execute_sync(&self, args: &[&str]) -> Result<String> {
31        let ctx = crate::server::shared::get_shared_context();
32
33        match args.first() {
34            Some(&"all") => Ok(self.recover_all_servers(ctx)),
35            Some(&server_id) => Ok(self.recover_single_server(ctx, server_id)),
36            None => Ok(self.auto_recover(ctx)),
37        }
38    }
39
40    fn priority(&self) -> u8 {
41        80 // Hohe Priorität für Recovery
42    }
43}
44
45impl RecoveryCommand {
46    // ✅ AUTO-RECOVERY: Analysiert und repariert alle inkonsistenten Server
47    fn auto_recover(&self, ctx: &ServerContext) -> String {
48        let mut fixes = Vec::new();
49        let registry = crate::server::shared::get_persistent_registry();
50
51        // 1. Handle-Status-Synchronisation
52        let (orphaned_handles, missing_handles) = {
53            let servers = ctx.servers.read().unwrap();
54            let handles = ctx.handles.read().unwrap();
55
56            // Handles ohne entsprechende Server (Orphaned)
57            let orphaned: Vec<String> = handles
58                .keys()
59                .filter(|id| !servers.contains_key(*id))
60                .cloned()
61                .collect();
62
63            // Running-Server ohne Handle (Missing)
64            let missing: Vec<String> = servers
65                .iter()
66                .filter_map(|(id, server)| {
67                    if server.status == ServerStatus::Running && !handles.contains_key(id) {
68                        Some(id.clone())
69                    } else {
70                        None
71                    }
72                })
73                .collect();
74
75            (orphaned, missing)
76        };
77
78        // 2. Port-Status-Validierung
79        let port_fixes = self.validate_and_fix_ports(ctx);
80        fixes.extend(port_fixes);
81
82        // 3. Orphaned Handles bereinigen
83        if !orphaned_handles.is_empty() {
84            let count = orphaned_handles.len();
85            for handle_id in orphaned_handles {
86                let mut handles = ctx.handles.write().unwrap();
87                if let Some(handle) = handles.remove(&handle_id) {
88                    // Handle graceful stoppen
89                    tokio::spawn(async move {
90                        let _ = handle.stop(true).await;
91                    });
92                }
93            }
94            fixes.push(format!("🧹 {} orphaned handles cleaned", count));
95        }
96
97        // 4. Missing Handles reparieren
98        if !missing_handles.is_empty() {
99            for server_id in &missing_handles {
100                self.fix_missing_handle(ctx, &server_id);
101            }
102            fixes.push(format!(
103                "🔧 {} missing handles fixed",
104                missing_handles.len()
105            ));
106        }
107
108        // 5. Persistence synchronisieren
109        tokio::spawn(async move {
110            if let Ok(persistent_servers) = registry.load_servers().await {
111                // Hier würde man die aktuelle Runtime-State in die Persistence schreiben
112                let _ = registry.save_servers(&persistent_servers).await;
113            }
114        });
115
116        if fixes.is_empty() {
117            "✅ All servers are in consistent state".to_string()
118        } else {
119            format!("🛠️ Recovery completed:\n{}", fixes.join("\n"))
120        }
121    }
122
123    // ✅ EINZELNEN SERVER REPARIEREN
124    fn recover_single_server(&self, ctx: &ServerContext, identifier: &str) -> String {
125        let servers = ctx.servers.read().unwrap();
126
127        // Server finden
128        let server_info = match servers
129            .values()
130            .find(|s| s.id.starts_with(identifier) || s.name == identifier)
131        {
132            Some(server) => server.clone(),
133            None => return format!("❌ Server '{}' not found", identifier),
134        };
135
136        drop(servers); // Lock freigeben
137
138        let fixes = self.diagnose_and_fix_server(ctx, &server_info);
139
140        if fixes.is_empty() {
141            format!(
142                "✅ Server '{}' is already in consistent state",
143                server_info.name
144            )
145        } else {
146            format!(
147                "🛠️ Fixed server '{}':\n{}",
148                server_info.name,
149                fixes.join("\n")
150            )
151        }
152    }
153
154    // ✅ ALLE SERVER DURCHGEHEN
155    fn recover_all_servers(&self, ctx: &ServerContext) -> String {
156        let mut total_fixes = Vec::new();
157        let servers: Vec<_> = {
158            let servers = ctx.servers.read().unwrap();
159            servers.values().cloned().collect()
160        };
161
162        for server_info in servers {
163            let fixes = self.diagnose_and_fix_server(ctx, &server_info);
164            if !fixes.is_empty() {
165                total_fixes.push(format!(
166                    "Server '{}': {}",
167                    server_info.name,
168                    fixes.join(", ")
169                ));
170            }
171        }
172
173        if total_fixes.is_empty() {
174            "✅ All servers are in consistent state".to_string()
175        } else {
176            format!("🛠️ Recovery results:\n{}", total_fixes.join("\n"))
177        }
178    }
179
180    // ✅ SERVER DIAGNOSE UND REPARATUR
181    fn diagnose_and_fix_server(
182        &self,
183        ctx: &ServerContext,
184        server_info: &crate::server::types::ServerInfo,
185    ) -> Vec<String> {
186        let mut fixes = Vec::new();
187
188        // Handle-Status prüfen
189        let has_handle = {
190            let handles = ctx.handles.read().unwrap();
191            handles.contains_key(&server_info.id)
192        };
193
194        // Port-Status prüfen
195        let port_available = is_port_available(server_info.port);
196
197        match (server_info.status, has_handle, port_available) {
198            // INKONSISTENZ: Server soll laufen, aber kein Handle
199            (ServerStatus::Running, false, _) => {
200                if port_available {
201                    // Port frei, aber Status Running → Korrigieren zu Stopped
202                    self.update_server_status(ctx, &server_info.id, ServerStatus::Stopped);
203                    fixes.push("Status: Running → Stopped (no handle, port free)".to_string());
204                } else {
205                    // Port belegt, Handle fehlt → Neustart versuchen oder Failed setzen
206                    self.update_server_status(ctx, &server_info.id, ServerStatus::Failed);
207                    fixes.push("Status: Running → Failed (no handle, port occupied)".to_string());
208                }
209            }
210
211            // INKONSISTENZ: Server hat Handle, aber Status nicht Running
212            (status, true, _) if status != ServerStatus::Running => {
213                self.update_server_status(ctx, &server_info.id, ServerStatus::Running);
214                fixes.push(format!("Status: {} → Running (handle exists)", status));
215            }
216
217            // INKONSISTENZ: Server Failed, aber Port ist frei
218            (ServerStatus::Failed, false, true) => {
219                self.update_server_status(ctx, &server_info.id, ServerStatus::Stopped);
220                fixes.push("Status: Failed → Stopped (port now free)".to_string());
221            }
222
223            _ => {
224                // Server ist konsistent
225            }
226        }
227
228        fixes
229    }
230
231    // ✅ PORT-VALIDIERUNG FÜR ALLE SERVER
232    fn validate_and_fix_ports(&self, ctx: &ServerContext) -> Vec<String> {
233        let mut fixes = Vec::new();
234        let servers: Vec<_> = {
235            let servers = ctx.servers.read().unwrap();
236            servers.values().cloned().collect()
237        };
238
239        for server_info in servers {
240            let port_available = is_port_available(server_info.port);
241            let has_handle = {
242                let handles = ctx.handles.read().unwrap();
243                handles.contains_key(&server_info.id)
244            };
245
246            // Logik-Matrix für Port-Fixes
247            match (server_info.status, has_handle, port_available) {
248                (ServerStatus::Running, true, false) => {
249                    // OK: Server läuft, Handle da, Port belegt
250                }
251                (ServerStatus::Running, false, false) => {
252                    // Problem: Server soll laufen, aber kein Handle und Port belegt
253                    self.update_server_status(ctx, &server_info.id, ServerStatus::Failed);
254                    fixes.push(format!(
255                        "Fixed '{}': Running → Failed (orphaned)",
256                        server_info.name
257                    ));
258                }
259                (ServerStatus::Running, false, true) => {
260                    // Problem: Server soll laufen, kein Handle, Port frei
261                    self.update_server_status(ctx, &server_info.id, ServerStatus::Stopped);
262                    fixes.push(format!(
263                        "Fixed '{}': Running → Stopped (no handle)",
264                        server_info.name
265                    ));
266                }
267                _ => {}
268            }
269        }
270
271        fixes
272    }
273
274    // ✅ MISSING HANDLE REPARIEREN
275    fn fix_missing_handle(&self, ctx: &ServerContext, server_id: &str) {
276        // Server auf Stopped setzen, da wir kein Handle haben
277        self.update_server_status(ctx, server_id, ServerStatus::Stopped);
278
279        // Async persistence update
280        let server_id_clone = server_id.to_string();
281        tokio::spawn(async move {
282            crate::server::shared::persist_server_update(&server_id_clone, ServerStatus::Stopped)
283                .await;
284        });
285    }
286
287    // ✅ STATUS UPDATE
288    fn update_server_status(&self, ctx: &ServerContext, server_id: &str, status: ServerStatus) {
289        if let Ok(mut servers) = ctx.servers.write() {
290            if let Some(server) = servers.get_mut(server_id) {
291                server.status = status;
292            }
293        }
294    }
295}