rush_sync_server/server/
persistence.rs

1use crate::core::prelude::*;
2use crate::server::types::{ServerInfo, ServerStatus};
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::path::PathBuf;
6
7#[derive(Debug, Serialize, Deserialize, Clone)]
8pub struct PersistentServerInfo {
9    pub id: String,
10    pub name: String,
11    pub port: u16,
12    pub status: ServerStatus,
13    pub created_at: String,
14    pub created_timestamp: u64,
15    pub auto_start: bool,
16    pub last_started: Option<String>,
17    pub start_count: u32,
18}
19
20impl From<ServerInfo> for PersistentServerInfo {
21    fn from(info: ServerInfo) -> Self {
22        Self {
23            id: info.id,
24            name: info.name,
25            port: info.port,
26            status: info.status,
27            created_at: info.created_at,
28            created_timestamp: info.created_timestamp,
29            auto_start: false,
30            last_started: None,
31            start_count: 0,
32        }
33    }
34}
35
36impl From<PersistentServerInfo> for ServerInfo {
37    fn from(info: PersistentServerInfo) -> Self {
38        Self {
39            id: info.id,
40            name: info.name,
41            port: info.port,
42            status: info.status,
43            created_at: info.created_at,
44            created_timestamp: info.created_timestamp,
45        }
46    }
47}
48
49pub struct ServerRegistry {
50    file_path: PathBuf,
51}
52
53impl ServerRegistry {
54    pub fn new() -> Result<Self> {
55        let exe_path = std::env::current_exe().map_err(AppError::Io)?;
56        let base_dir = exe_path.parent().ok_or_else(|| {
57            AppError::Validation("Cannot determine executable directory".to_string())
58        })?;
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 { file_path })
67    }
68
69    pub fn get_file_path(&self) -> &PathBuf {
70        &self.file_path
71    }
72
73    pub async fn load_servers(&self) -> Result<HashMap<String, PersistentServerInfo>> {
74        if !self.file_path.exists() {
75            return Ok(HashMap::new());
76        }
77
78        let content = tokio::fs::read_to_string(&self.file_path)
79            .await
80            .map_err(AppError::Io)?;
81        if content.trim().is_empty() {
82            return Ok(HashMap::new());
83        }
84
85        let servers: Vec<PersistentServerInfo> = serde_json::from_str(&content)
86            .map_err(|e| AppError::Validation(format!("Failed to parse server registry: {}", e)))?;
87
88        Ok(servers.into_iter().map(|s| (s.id.clone(), s)).collect())
89    }
90
91    pub async fn save_servers(
92        &self,
93        servers: &HashMap<String, PersistentServerInfo>,
94    ) -> Result<()> {
95        let mut server_list: Vec<PersistentServerInfo> = servers.values().cloned().collect();
96        server_list.sort_by(|a, b| a.created_timestamp.cmp(&b.created_timestamp));
97
98        let content = serde_json::to_string_pretty(&server_list)
99            .map_err(|e| AppError::Validation(format!("Failed to serialize servers: {}", e)))?;
100
101        let temp_path = self.file_path.with_extension("tmp");
102        tokio::fs::write(&temp_path, content)
103            .await
104            .map_err(AppError::Io)?;
105        tokio::fs::rename(&temp_path, &self.file_path)
106            .await
107            .map_err(AppError::Io)?;
108
109        Ok(())
110    }
111
112    pub async fn add_server(
113        &self,
114        mut servers: HashMap<String, PersistentServerInfo>,
115        server_info: ServerInfo,
116    ) -> Result<HashMap<String, PersistentServerInfo>> {
117        let persistent_info = PersistentServerInfo::from(server_info);
118        servers.insert(persistent_info.id.clone(), persistent_info);
119        self.save_servers(&servers).await?;
120        Ok(servers)
121    }
122
123    pub async fn remove_server(
124        &self,
125        mut servers: HashMap<String, PersistentServerInfo>,
126        server_id: &str,
127    ) -> Result<HashMap<String, PersistentServerInfo>> {
128        servers.remove(server_id);
129        self.save_servers(&servers).await?;
130        Ok(servers)
131    }
132
133    pub async fn update_server_status(
134        &self,
135        mut servers: HashMap<String, PersistentServerInfo>,
136        server_id: &str,
137        status: ServerStatus,
138    ) -> Result<HashMap<String, PersistentServerInfo>> {
139        if let Some(server) = servers.get_mut(server_id) {
140            server.status = status;
141            if status == ServerStatus::Running {
142                server.last_started =
143                    Some(chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string());
144                server.start_count += 1;
145            }
146        }
147        self.save_servers(&servers).await?;
148        Ok(servers)
149    }
150
151    pub async fn cleanup_servers(
152        &self,
153        mut servers: HashMap<String, PersistentServerInfo>,
154        cleanup_type: CleanupType,
155    ) -> Result<(HashMap<String, PersistentServerInfo>, usize)> {
156        let initial_count = servers.len();
157
158        match cleanup_type {
159            CleanupType::Stopped => servers.retain(|_, s| s.status != ServerStatus::Stopped),
160            CleanupType::Failed => servers.retain(|_, s| s.status != ServerStatus::Failed),
161            CleanupType::All => servers.retain(|_, s| s.status == ServerStatus::Running),
162        }
163
164        let removed_count = initial_count - servers.len();
165        if removed_count > 0 {
166            self.save_servers(&servers).await?;
167        }
168
169        Ok((servers, removed_count))
170    }
171
172    pub fn get_auto_start_servers(
173        &self,
174        servers: &HashMap<String, PersistentServerInfo>,
175    ) -> Vec<PersistentServerInfo> {
176        servers
177            .values()
178            .filter(|s| s.auto_start && s.status != ServerStatus::Failed)
179            .cloned()
180            .collect()
181    }
182
183    pub async fn set_auto_start(
184        &self,
185        mut servers: HashMap<String, PersistentServerInfo>,
186        server_id: &str,
187        auto_start: bool,
188    ) -> Result<HashMap<String, PersistentServerInfo>> {
189        if let Some(server) = servers.get_mut(server_id) {
190            server.auto_start = auto_start;
191            self.save_servers(&servers).await?;
192        }
193        Ok(servers)
194    }
195}
196
197#[derive(Debug)]
198pub enum CleanupType {
199    Stopped,
200    Failed,
201    All,
202}