1use serde::{Deserialize, Serialize};
10use std::sync::Arc;
11use tokio::sync::RwLock;
12
13use super::checker::{compare_versions, UpdateCheckResult};
14use super::installer::{InstallOptions, Installer};
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct UpdateConfig {
19 pub check_interval: u64,
20 pub auto_download: bool,
21 pub auto_install: bool,
22 pub channel: UpdateChannel,
23 pub registry_url: String,
24 pub package_name: String,
25}
26
27impl Default for UpdateConfig {
28 fn default() -> Self {
29 Self {
30 check_interval: 24 * 60 * 60,
31 auto_download: false,
32 auto_install: false,
33 channel: UpdateChannel::Stable,
34 registry_url: "https://github.com/astercloud/aster-rust/releases".to_string(),
35 package_name: "aster".to_string(),
36 }
37 }
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
42pub enum UpdateChannel {
43 Stable,
44 Beta,
45 Canary,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
50pub enum UpdateStatus {
51 Idle,
52 Checking,
53 Available,
54 Downloading,
55 Ready,
56 Installing,
57 Error,
58}
59
60#[derive(Debug, Clone, Default)]
62pub struct UpdateOptions {
63 pub version: Option<String>,
64 pub force: bool,
65 pub dry_run: bool,
66 pub beta: bool,
67 pub canary: bool,
68 pub show_progress: bool,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
73pub enum UpdateEvent {
74 Checking,
75 UpdateAvailable { current: String, latest: String },
76 UpdateNotAvailable,
77 Downloading { version: String },
78 Downloaded { version: String },
79 Installing { version: String },
80 Installed { version: String },
81 Progress { phase: String, percent: u8 },
82 Error { message: String },
83 RollbackStarted { version: String },
84 RollbackComplete { version: String },
85}
86
87pub struct UpdateManager {
89 config: UpdateConfig,
90 status: Arc<RwLock<UpdateStatus>>,
91 current_version: String,
92 last_check: Arc<RwLock<Option<i64>>>,
93 installer: Installer,
94 event_sender: Option<tokio::sync::mpsc::Sender<UpdateEvent>>,
95}
96
97impl UpdateManager {
98 pub fn new(config: UpdateConfig) -> Self {
99 Self {
100 config,
101 status: Arc::new(RwLock::new(UpdateStatus::Idle)),
102 current_version: env!("CARGO_PKG_VERSION").to_string(),
103 last_check: Arc::new(RwLock::new(None)),
104 installer: Installer::new(),
105 event_sender: None,
106 }
107 }
108
109 pub fn with_event_sender(mut self, sender: tokio::sync::mpsc::Sender<UpdateEvent>) -> Self {
110 self.event_sender = Some(sender);
111 self
112 }
113
114 async fn emit(&self, event: UpdateEvent) {
115 if let Some(sender) = &self.event_sender {
116 let _ = sender.send(event).await;
117 }
118 }
119
120 pub async fn get_status(&self) -> UpdateStatus {
121 *self.status.read().await
122 }
123 pub fn get_current_version(&self) -> &str {
124 &self.current_version
125 }
126 pub fn get_config(&self) -> &UpdateConfig {
127 &self.config
128 }
129
130 pub async fn check_for_updates(&self) -> Result<UpdateCheckResult, String> {
131 *self.status.write().await = UpdateStatus::Checking;
132 self.emit(UpdateEvent::Checking).await;
133
134 let latest_version = self.fetch_latest_version().await?;
135 let has_update = compare_versions(&latest_version, &self.current_version) > 0;
136
137 *self.last_check.write().await = Some(chrono::Utc::now().timestamp());
138
139 if has_update {
140 *self.status.write().await = UpdateStatus::Available;
141 self.emit(UpdateEvent::UpdateAvailable {
142 current: self.current_version.clone(),
143 latest: latest_version.clone(),
144 })
145 .await;
146 } else {
147 *self.status.write().await = UpdateStatus::Idle;
148 self.emit(UpdateEvent::UpdateNotAvailable).await;
149 }
150
151 Ok(UpdateCheckResult {
152 has_update,
153 current_version: self.current_version.clone(),
154 latest_version,
155 version_info: None,
156 changelog: None,
157 })
158 }
159
160 async fn fetch_latest_version(&self) -> Result<String, String> {
161 Ok(self.current_version.clone())
162 }
163
164 pub async fn download(
165 &self,
166 version: Option<&str>,
167 options: &UpdateOptions,
168 ) -> Result<(), String> {
169 let target_version = version.unwrap_or(&self.current_version);
170
171 if options.dry_run {
172 tracing::info!("[DRY-RUN] 将下载版本 {}", target_version);
173 return Ok(());
174 }
175
176 *self.status.write().await = UpdateStatus::Downloading;
177 self.emit(UpdateEvent::Downloading {
178 version: target_version.to_string(),
179 })
180 .await;
181
182 let download_url = format!(
183 "{}/download/v{}/aster-{}.tar.gz",
184 self.config.registry_url,
185 target_version,
186 std::env::consts::OS
187 );
188
189 let install_options = InstallOptions {
190 version: Some(target_version.to_string()),
191 dry_run: options.dry_run,
192 show_progress: options.show_progress,
193 ..Default::default()
194 };
195
196 self.installer
197 .download(&download_url, &install_options)
198 .await?;
199
200 *self.status.write().await = UpdateStatus::Ready;
201 self.emit(UpdateEvent::Downloaded {
202 version: target_version.to_string(),
203 })
204 .await;
205 Ok(())
206 }
207
208 pub async fn install(
209 &self,
210 version: Option<&str>,
211 options: &UpdateOptions,
212 ) -> Result<(), String> {
213 let target_version = version.unwrap_or("latest");
214
215 if options.dry_run {
216 tracing::info!("[DRY-RUN] 将安装版本 {}", target_version);
217 return Ok(());
218 }
219
220 *self.status.write().await = UpdateStatus::Installing;
221 self.emit(UpdateEvent::Installing {
222 version: target_version.to_string(),
223 })
224 .await;
225
226 let install_options = InstallOptions {
227 version: Some(target_version.to_string()),
228 force: options.force,
229 dry_run: options.dry_run,
230 show_progress: options.show_progress,
231 ..Default::default()
232 };
233
234 let package_path = dirs::data_dir()
235 .unwrap_or_default()
236 .join("aster/downloads")
237 .join(format!("aster-{}.tar.gz", std::env::consts::OS));
238
239 self.installer
240 .install(&package_path, &install_options)
241 .await?;
242
243 self.emit(UpdateEvent::Installed {
244 version: target_version.to_string(),
245 })
246 .await;
247 *self.status.write().await = UpdateStatus::Idle;
248 Ok(())
249 }
250
251 pub async fn rollback(&self, version: &str, options: &UpdateOptions) -> Result<(), String> {
252 *self.status.write().await = UpdateStatus::Installing;
253 self.emit(UpdateEvent::RollbackStarted {
254 version: version.to_string(),
255 })
256 .await;
257
258 if options.dry_run {
259 tracing::info!("[DRY-RUN] 将回滚到版本 {}", version);
260 return Ok(());
261 }
262
263 let available = self.installer.list_backups();
264 if !available.contains(&version.to_string()) {
265 return Err(format!("版本 {} 不存在", version));
266 }
267
268 let install_options = InstallOptions {
269 version: Some(version.to_string()),
270 dry_run: options.dry_run,
271 ..Default::default()
272 };
273
274 self.installer.rollback(version, &install_options).await?;
275
276 self.emit(UpdateEvent::RollbackComplete {
277 version: version.to_string(),
278 })
279 .await;
280 *self.status.write().await = UpdateStatus::Idle;
281 Ok(())
282 }
283
284 pub fn list_available_versions(&self) -> Vec<String> {
285 self.installer.list_backups()
286 }
287 pub fn cleanup(&self, keep_versions: usize) -> Result<(), String> {
288 self.installer.cleanup(keep_versions)
289 }
290}
291
292impl Default for UpdateManager {
293 fn default() -> Self {
294 Self::new(UpdateConfig::default())
295 }
296}
297
298pub async fn check_for_updates(config: Option<UpdateConfig>) -> Result<UpdateCheckResult, String> {
301 let manager = UpdateManager::new(config.unwrap_or_default());
302 manager.check_for_updates().await
303}
304
305pub async fn perform_update(options: UpdateOptions) -> Result<bool, String> {
306 let channel = if options.beta {
307 UpdateChannel::Beta
308 } else if options.canary {
309 UpdateChannel::Canary
310 } else {
311 UpdateChannel::Stable
312 };
313
314 let config = UpdateConfig {
315 channel,
316 ..Default::default()
317 };
318 let manager = UpdateManager::new(config);
319
320 let result = manager.check_for_updates().await?;
321 if !result.has_update {
322 return Ok(true);
323 }
324
325 manager
326 .download(options.version.as_deref(), &options)
327 .await?;
328 if !options.dry_run {
329 manager
330 .install(options.version.as_deref(), &options)
331 .await?;
332 }
333 Ok(true)
334}
335
336pub async fn rollback_version(version: &str, options: UpdateOptions) -> Result<bool, String> {
337 let manager = UpdateManager::new(UpdateConfig::default());
338 manager.rollback(version, &options).await?;
339 Ok(true)
340}
341
342pub fn list_versions() -> Vec<String> {
343 UpdateManager::new(UpdateConfig::default()).list_available_versions()
344}
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349
350 #[test]
351 fn test_update_config_default() {
352 let config = UpdateConfig::default();
353 assert_eq!(config.check_interval, 24 * 60 * 60);
354 assert!(!config.auto_download);
355 assert!(!config.auto_install);
356 assert_eq!(config.channel, UpdateChannel::Stable);
357 }
358
359 #[test]
360 fn test_update_channel_variants() {
361 assert_ne!(UpdateChannel::Stable, UpdateChannel::Beta);
362 assert_ne!(UpdateChannel::Beta, UpdateChannel::Canary);
363 }
364
365 #[test]
366 fn test_update_status_variants() {
367 let statuses = [
368 UpdateStatus::Idle,
369 UpdateStatus::Checking,
370 UpdateStatus::Available,
371 UpdateStatus::Downloading,
372 UpdateStatus::Ready,
373 UpdateStatus::Installing,
374 UpdateStatus::Error,
375 ];
376 assert_eq!(statuses.len(), 7);
377 }
378
379 #[test]
380 fn test_update_options_default() {
381 let options = UpdateOptions::default();
382 assert!(options.version.is_none());
383 assert!(!options.force);
384 assert!(!options.dry_run);
385 assert!(!options.beta);
386 assert!(!options.canary);
387 assert!(!options.show_progress);
388 }
389
390 #[test]
391 fn test_update_manager_new() {
392 let manager = UpdateManager::new(UpdateConfig::default());
393 assert!(!manager.get_current_version().is_empty());
394 }
395
396 #[test]
397 fn test_update_manager_default() {
398 let manager = UpdateManager::default();
399 assert_eq!(manager.get_config().channel, UpdateChannel::Stable);
400 }
401
402 #[test]
403 fn test_update_manager_get_config() {
404 let config = UpdateConfig {
405 channel: UpdateChannel::Beta,
406 ..Default::default()
407 };
408 let manager = UpdateManager::new(config);
409 assert_eq!(manager.get_config().channel, UpdateChannel::Beta);
410 }
411
412 #[tokio::test]
413 async fn test_update_manager_get_status() {
414 let manager = UpdateManager::default();
415 let status = manager.get_status().await;
416 assert_eq!(status, UpdateStatus::Idle);
417 }
418
419 #[tokio::test]
420 async fn test_update_manager_check_for_updates() {
421 let manager = UpdateManager::default();
422 let result = manager.check_for_updates().await;
423 assert!(result.is_ok());
424 }
425
426 #[tokio::test]
427 async fn test_update_manager_download_dry_run() {
428 let manager = UpdateManager::default();
429 let options = UpdateOptions {
430 dry_run: true,
431 ..Default::default()
432 };
433 let result = manager.download(Some("1.0.0"), &options).await;
434 assert!(result.is_ok());
435 }
436
437 #[tokio::test]
438 async fn test_update_manager_install_dry_run() {
439 let manager = UpdateManager::default();
440 let options = UpdateOptions {
441 dry_run: true,
442 ..Default::default()
443 };
444 let result = manager.install(Some("1.0.0"), &options).await;
445 assert!(result.is_ok());
446 }
447
448 #[tokio::test]
449 async fn test_update_manager_rollback_dry_run() {
450 let manager = UpdateManager::default();
451 let options = UpdateOptions {
452 dry_run: true,
453 ..Default::default()
454 };
455 let result = manager.rollback("1.0.0", &options).await;
456 assert!(result.is_ok());
457 }
458
459 #[test]
460 fn test_update_manager_list_available_versions() {
461 let manager = UpdateManager::default();
462 let versions = manager.list_available_versions();
463 let _ = versions;
465 }
466
467 #[test]
468 fn test_update_manager_cleanup() {
469 let manager = UpdateManager::default();
470 let result = manager.cleanup(3);
471 assert!(result.is_ok());
472 }
473
474 #[test]
475 fn test_update_event_variants() {
476 let events = vec![
477 UpdateEvent::Checking,
478 UpdateEvent::UpdateAvailable {
479 current: "1.0".to_string(),
480 latest: "1.1".to_string(),
481 },
482 UpdateEvent::UpdateNotAvailable,
483 UpdateEvent::Downloading {
484 version: "1.1".to_string(),
485 },
486 UpdateEvent::Downloaded {
487 version: "1.1".to_string(),
488 },
489 UpdateEvent::Installing {
490 version: "1.1".to_string(),
491 },
492 UpdateEvent::Installed {
493 version: "1.1".to_string(),
494 },
495 UpdateEvent::Progress {
496 phase: "download".to_string(),
497 percent: 50,
498 },
499 UpdateEvent::Error {
500 message: "error".to_string(),
501 },
502 UpdateEvent::RollbackStarted {
503 version: "1.0".to_string(),
504 },
505 UpdateEvent::RollbackComplete {
506 version: "1.0".to_string(),
507 },
508 ];
509 assert_eq!(events.len(), 11);
510 }
511
512 #[tokio::test]
513 async fn test_check_for_updates_function() {
514 let result = check_for_updates(None).await;
515 assert!(result.is_ok());
516 }
517
518 #[tokio::test]
519 async fn test_perform_update_dry_run() {
520 let options = UpdateOptions {
521 dry_run: true,
522 ..Default::default()
523 };
524 let result = perform_update(options).await;
525 assert!(result.is_ok());
526 }
527
528 #[tokio::test]
529 async fn test_rollback_version_dry_run() {
530 let options = UpdateOptions {
531 dry_run: true,
532 ..Default::default()
533 };
534 let result = rollback_version("1.0.0", options).await;
535 assert!(result.is_ok());
536 }
537
538 #[test]
539 fn test_list_versions_function() {
540 let versions = list_versions();
541 let _ = versions;
543 }
544}