rush_sync_server/server/
persistence.rs1use 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 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 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}