rush_sync_server/commands/recovery/
command.rs1use 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 }
43}
44
45impl RecoveryCommand {
46 fn auto_recover(&self, ctx: &ServerContext) -> String {
48 let mut fixes = Vec::new();
49 let registry = crate::server::shared::get_persistent_registry();
50
51 let (orphaned_handles, missing_handles) = {
53 let servers = ctx.servers.read().unwrap();
54 let handles = ctx.handles.read().unwrap();
55
56 let orphaned: Vec<String> = handles
58 .keys()
59 .filter(|id| !servers.contains_key(*id))
60 .cloned()
61 .collect();
62
63 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 let port_fixes = self.validate_and_fix_ports(ctx);
80 fixes.extend(port_fixes);
81
82 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 tokio::spawn(async move {
90 let _ = handle.stop(true).await;
91 });
92 }
93 }
94 fixes.push(format!("🧹 {} orphaned handles cleaned", count));
95 }
96
97 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 tokio::spawn(async move {
110 if let Ok(persistent_servers) = registry.load_servers().await {
111 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 fn recover_single_server(&self, ctx: &ServerContext, identifier: &str) -> String {
125 let servers = ctx.servers.read().unwrap();
126
127 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); 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 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 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 let has_handle = {
190 let handles = ctx.handles.read().unwrap();
191 handles.contains_key(&server_info.id)
192 };
193
194 let port_available = is_port_available(server_info.port);
196
197 match (server_info.status, has_handle, port_available) {
198 (ServerStatus::Running, false, _) => {
200 if port_available {
201 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 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 (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 (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 }
226 }
227
228 fixes
229 }
230
231 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 match (server_info.status, has_handle, port_available) {
248 (ServerStatus::Running, true, false) => {
249 }
251 (ServerStatus::Running, false, false) => {
252 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 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 fn fix_missing_handle(&self, ctx: &ServerContext, server_id: &str) {
276 self.update_server_status(ctx, server_id, ServerStatus::Stopped);
278
279 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 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}