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