rush_sync_server/server/
persistence.rs

1// ## FILE: src/server/persistence.rs - OPTIMIERT
2use crate::core::prelude::*;
3use crate::server::types::{ServerInfo, ServerStatus};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::path::PathBuf;
7
8#[derive(Debug, Serialize, Deserialize, Clone)]
9pub struct PersistentServerInfo {
10    pub id: String,
11    pub name: String,
12    pub port: u16,
13    pub status: ServerStatus,
14    pub created_at: String,
15    pub created_timestamp: u64,
16    pub auto_start: bool,
17    pub last_started: Option<String>,
18    pub start_count: u32,
19}
20
21impl From<ServerInfo> for PersistentServerInfo {
22    fn from(info: ServerInfo) -> Self {
23        Self {
24            id: info.id,
25            name: info.name,
26            port: info.port,
27            status: info.status,
28            created_at: info.created_at,
29            created_timestamp: info.created_timestamp,
30            auto_start: false,
31            last_started: None,
32            start_count: 0,
33        }
34    }
35}
36
37impl From<PersistentServerInfo> for ServerInfo {
38    fn from(info: PersistentServerInfo) -> Self {
39        Self {
40            id: info.id,
41            name: info.name,
42            port: info.port,
43            status: info.status,
44            created_at: info.created_at,
45            created_timestamp: info.created_timestamp,
46        }
47    }
48}
49
50pub struct ServerRegistry {
51    file_path: PathBuf,
52}
53
54impl ServerRegistry {
55    pub fn new() -> Result<Self> {
56        let exe_path = std::env::current_exe().map_err(AppError::Io)?;
57        let base_dir = exe_path.parent().ok_or_else(|| {
58            AppError::Validation("Cannot determine executable directory".to_string())
59        })?;
60
61        let file_path = base_dir.join(".rss").join("servers.list");
62
63        if let Some(parent) = file_path.parent() {
64            std::fs::create_dir_all(parent).map_err(AppError::Io)?;
65        }
66
67        Ok(Self { file_path })
68    }
69
70    pub fn get_file_path(&self) -> &PathBuf {
71        &self.file_path
72    }
73
74    pub async fn load_servers(&self) -> Result<HashMap<String, PersistentServerInfo>> {
75        if !self.file_path.exists() {
76            return Ok(HashMap::new());
77        }
78
79        let content = tokio::fs::read_to_string(&self.file_path)
80            .await
81            .map_err(AppError::Io)?;
82        if content.trim().is_empty() {
83            return Ok(HashMap::new());
84        }
85
86        let servers: Vec<PersistentServerInfo> = serde_json::from_str(&content)
87            .map_err(|e| AppError::Validation(format!("Failed to parse server registry: {}", e)))?;
88
89        Ok(servers.into_iter().map(|s| (s.id.clone(), s)).collect())
90    }
91
92    pub async fn save_servers(
93        &self,
94        servers: &HashMap<String, PersistentServerInfo>,
95    ) -> Result<()> {
96        let mut server_list: Vec<PersistentServerInfo> = servers.values().cloned().collect();
97        server_list.sort_by_key(|s| s.created_timestamp);
98
99        let content = serde_json::to_string_pretty(&server_list)
100            .map_err(|e| AppError::Validation(format!("Failed to serialize servers: {}", e)))?;
101
102        let temp_path = self.file_path.with_extension("tmp");
103        tokio::fs::write(&temp_path, content)
104            .await
105            .map_err(AppError::Io)?;
106        tokio::fs::rename(&temp_path, &self.file_path)
107            .await
108            .map_err(AppError::Io)?;
109
110        Ok(())
111    }
112
113    // Vereinfachte Update-Methoden mit weniger Boilerplate
114    async fn update_server(
115        &self,
116        server_id: &str,
117        update_fn: impl Fn(&mut PersistentServerInfo),
118    ) -> Result<HashMap<String, PersistentServerInfo>> {
119        let mut servers = self.load_servers().await?;
120        if let Some(server) = servers.get_mut(server_id) {
121            update_fn(server);
122            self.save_servers(&servers).await?;
123        }
124        Ok(servers)
125    }
126
127    pub async fn update_server_status(
128        &self,
129        server_id: &str,
130        status: ServerStatus,
131    ) -> Result<HashMap<String, PersistentServerInfo>> {
132        self.update_server(server_id, |server| {
133            server.status = status;
134            if status == ServerStatus::Running {
135                server.last_started =
136                    Some(chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string());
137                server.start_count += 1;
138            }
139        })
140        .await
141    }
142
143    pub async fn set_auto_start(
144        &self,
145        server_id: &str,
146        auto_start: bool,
147    ) -> Result<HashMap<String, PersistentServerInfo>> {
148        self.update_server(server_id, |server| {
149            server.auto_start = auto_start;
150        })
151        .await
152    }
153
154    pub async fn add_server(
155        &self,
156        server_info: ServerInfo,
157    ) -> Result<HashMap<String, PersistentServerInfo>> {
158        let mut servers = self.load_servers().await?;
159        let persistent_info = PersistentServerInfo::from(server_info);
160        servers.insert(persistent_info.id.clone(), persistent_info);
161        self.save_servers(&servers).await?;
162        Ok(servers)
163    }
164
165    pub async fn remove_server(
166        &self,
167        server_id: &str,
168    ) -> Result<HashMap<String, PersistentServerInfo>> {
169        let mut servers = self.load_servers().await?;
170        servers.remove(server_id);
171        self.save_servers(&servers).await?;
172        Ok(servers)
173    }
174
175    pub async fn cleanup_servers(
176        &self,
177        cleanup_type: CleanupType,
178    ) -> Result<(HashMap<String, PersistentServerInfo>, usize)> {
179        let mut servers = self.load_servers().await?;
180        let initial_count = servers.len();
181
182        servers.retain(|_, s| match cleanup_type {
183            CleanupType::Stopped => s.status != ServerStatus::Stopped,
184            CleanupType::Failed => s.status != ServerStatus::Failed,
185            CleanupType::All => s.status == ServerStatus::Running,
186        });
187
188        let removed_count = initial_count - servers.len();
189        if removed_count > 0 {
190            self.save_servers(&servers).await?;
191        }
192
193        Ok((servers, removed_count))
194    }
195
196    pub fn get_auto_start_servers(
197        &self,
198        servers: &HashMap<String, PersistentServerInfo>,
199    ) -> Vec<PersistentServerInfo> {
200        servers
201            .values()
202            .filter(|s| s.auto_start && s.status != ServerStatus::Failed)
203            .cloned()
204            .collect()
205    }
206
207    // Utility-Methoden mit besserer Error-Behandlung
208    pub async fn cleanup_server_directory(&self, server_name: &str, port: u16) -> Result<()> {
209        let exe_path = std::env::current_exe().map_err(AppError::Io)?;
210        let base_dir = exe_path.parent().ok_or_else(|| {
211            AppError::Validation("Cannot determine executable directory".to_string())
212        })?;
213
214        let server_dir = base_dir
215            .join("www")
216            .join(format!("{}-[{}]", server_name, port));
217
218        if server_dir.exists() {
219            std::fs::remove_dir_all(&server_dir).map_err(AppError::Io)?;
220            log::info!("Removed server directory: {:?}", server_dir);
221        }
222        Ok(())
223    }
224
225    pub fn list_www_directories(&self) -> Result<Vec<PathBuf>> {
226        let exe_path = std::env::current_exe().map_err(AppError::Io)?;
227        let base_dir = exe_path.parent().ok_or_else(|| {
228            AppError::Validation("Cannot determine executable directory".to_string())
229        })?;
230
231        let www_dir = base_dir.join("www");
232        if !www_dir.exists() {
233            return Ok(vec![]);
234        }
235
236        let mut directories = Vec::new();
237        for entry in std::fs::read_dir(&www_dir).map_err(AppError::Io)? {
238            let entry = entry.map_err(AppError::Io)?;
239            if entry.file_type().map_err(AppError::Io)?.is_dir() {
240                directories.push(entry.path());
241            }
242        }
243        directories.sort();
244        Ok(directories)
245    }
246}
247
248#[derive(Debug)]
249pub enum CleanupType {
250    Stopped,
251    Failed,
252    All,
253}