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 base_dir = crate::core::helpers::get_base_dir()?;
57
58 let file_path = base_dir.join(".rss").join("servers.list");
59
60 if let Some(parent) = file_path.parent() {
61 std::fs::create_dir_all(parent).map_err(AppError::Io)?;
62 }
63
64 Ok(Self { file_path })
65 }
66
67 pub fn with_fallback() -> Self {
69 match Self::new() {
70 Ok(registry) => registry,
71 Err(_) => {
72 let path = std::env::temp_dir().join(".rss").join("servers.list");
73 let _ = std::fs::create_dir_all(path.parent().unwrap_or(&path));
74 Self { file_path: path }
75 }
76 }
77 }
78
79 pub fn get_file_path(&self) -> &PathBuf {
80 &self.file_path
81 }
82
83 pub async fn load_servers(&self) -> Result<HashMap<String, PersistentServerInfo>> {
84 if !self.file_path.exists() {
85 return Ok(HashMap::new());
86 }
87
88 let content = tokio::fs::read_to_string(&self.file_path)
89 .await
90 .map_err(AppError::Io)?;
91 if content.trim().is_empty() {
92 return Ok(HashMap::new());
93 }
94
95 let servers: Vec<PersistentServerInfo> = serde_json::from_str(&content)
96 .map_err(|e| AppError::Validation(format!("Failed to parse server registry: {}", e)))?;
97
98 Ok(servers.into_iter().map(|s| (s.id.clone(), s)).collect())
99 }
100
101 pub async fn save_servers(
102 &self,
103 servers: &HashMap<String, PersistentServerInfo>,
104 ) -> Result<()> {
105 let mut server_list: Vec<PersistentServerInfo> = servers.values().cloned().collect();
106 server_list.sort_by_key(|s| s.created_timestamp);
107
108 let content = serde_json::to_string_pretty(&server_list)
109 .map_err(|e| AppError::Validation(format!("Failed to serialize servers: {}", e)))?;
110
111 let temp_path = self.file_path.with_extension("tmp");
112 tokio::fs::write(&temp_path, content)
113 .await
114 .map_err(AppError::Io)?;
115 tokio::fs::rename(&temp_path, &self.file_path)
116 .await
117 .map_err(AppError::Io)?;
118
119 Ok(())
120 }
121
122 async fn update_server(
124 &self,
125 server_id: &str,
126 update_fn: impl Fn(&mut PersistentServerInfo),
127 ) -> Result<HashMap<String, PersistentServerInfo>> {
128 let mut servers = self.load_servers().await?;
129 if let Some(server) = servers.get_mut(server_id) {
130 update_fn(server);
131 self.save_servers(&servers).await?;
132 }
133 Ok(servers)
134 }
135
136 pub async fn update_server_status(
137 &self,
138 server_id: &str,
139 status: ServerStatus,
140 ) -> Result<HashMap<String, PersistentServerInfo>> {
141 self.update_server(server_id, |server| {
142 server.status = status;
143 if status == ServerStatus::Running {
144 server.last_started =
145 Some(chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string());
146 server.start_count += 1;
147 }
148 })
149 .await
150 }
151
152 pub async fn set_auto_start(
153 &self,
154 server_id: &str,
155 auto_start: bool,
156 ) -> Result<HashMap<String, PersistentServerInfo>> {
157 self.update_server(server_id, |server| {
158 server.auto_start = auto_start;
159 })
160 .await
161 }
162
163 pub async fn add_server(
164 &self,
165 server_info: ServerInfo,
166 ) -> Result<HashMap<String, PersistentServerInfo>> {
167 let mut servers = self.load_servers().await?;
168 let persistent_info = PersistentServerInfo::from(server_info);
169 servers.insert(persistent_info.id.clone(), persistent_info);
170 self.save_servers(&servers).await?;
171 Ok(servers)
172 }
173
174 pub async fn remove_server(
175 &self,
176 server_id: &str,
177 ) -> Result<HashMap<String, PersistentServerInfo>> {
178 let mut servers = self.load_servers().await?;
179 servers.remove(server_id);
180 self.save_servers(&servers).await?;
181 Ok(servers)
182 }
183
184 pub async fn cleanup_servers(
185 &self,
186 cleanup_type: CleanupType,
187 ) -> Result<(HashMap<String, PersistentServerInfo>, usize)> {
188 let mut servers = self.load_servers().await?;
189 let initial_count = servers.len();
190
191 servers.retain(|_, s| match cleanup_type {
192 CleanupType::Stopped => s.status != ServerStatus::Stopped,
193 CleanupType::Failed => s.status != ServerStatus::Failed,
194 CleanupType::All => s.status == ServerStatus::Running,
195 });
196
197 let removed_count = initial_count - servers.len();
198 if removed_count > 0 {
199 self.save_servers(&servers).await?;
200 }
201
202 Ok((servers, removed_count))
203 }
204
205 pub fn get_auto_start_servers(
206 &self,
207 servers: &HashMap<String, PersistentServerInfo>,
208 ) -> Vec<PersistentServerInfo> {
209 servers
210 .values()
211 .filter(|s| s.auto_start && s.status != ServerStatus::Failed)
212 .cloned()
213 .collect()
214 }
215
216 pub async fn cleanup_server_directory(&self, server_name: &str, port: u16) -> Result<()> {
218 let base_dir = crate::core::helpers::get_base_dir()?;
219
220 let server_dir = base_dir
221 .join("www")
222 .join(format!("{}-[{}]", server_name, port));
223
224 if server_dir.exists() {
225 std::fs::remove_dir_all(&server_dir).map_err(AppError::Io)?;
226 log::info!("Removed server directory: {:?}", server_dir);
227 }
228 Ok(())
229 }
230
231 pub fn list_www_directories(&self) -> Result<Vec<PathBuf>> {
232 let base_dir = crate::core::helpers::get_base_dir()?;
233
234 let www_dir = base_dir.join("www");
235 if !www_dir.exists() {
236 return Ok(vec![]);
237 }
238
239 let mut directories = Vec::new();
240 for entry in std::fs::read_dir(&www_dir).map_err(AppError::Io)? {
241 let entry = entry.map_err(AppError::Io)?;
242 if entry.file_type().map_err(AppError::Io)?.is_dir() {
243 directories.push(entry.path());
244 }
245 }
246 directories.sort();
247 Ok(directories)
248 }
249}
250
251#[derive(Debug)]
252pub enum CleanupType {
253 Stopped,
254 Failed,
255 All,
256}