Skip to main content

aster/updater/
manager.rs

1//! 更新管理器
2//!
3//! 管理更新检查、下载和安装,支持:
4//! - 自动检查更新
5//! - 事件通知
6//! - 版本回滚
7//! - 多更新通道
8
9use 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/// 更新配置
17#[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/// 更新通道
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
42pub enum UpdateChannel {
43    Stable,
44    Beta,
45    Canary,
46}
47
48/// 更新状态
49#[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/// 更新选项
61#[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/// 更新事件
72#[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
87/// 更新管理器
88pub 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
298// ============ 便捷函数 ============
299
300pub 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        // 可能为空,但不应该 panic(versions.len() 是 usize,总是 >= 0)
464        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        // versions.len() 是 usize,总是 >= 0
542        let _ = versions;
543    }
544}