1use std::{
73 collections::HashMap,
74 path::{Path, PathBuf},
75 sync::Arc,
76 time::Duration,
77};
78
79use serde::{Deserialize, Serialize};
80use tokio::{
81 sync::{Mutex, RwLock},
82 time::{interval, sleep},
83};
84use sha2::{Digest, Sha256};
85use uuid::Uuid;
86use md5;
87
88use crate::{AirError, ApplicationState::ApplicationState, Configuration::ConfigurationManager, Result};
89
90pub struct UpdateManager {
92 AppState:Arc<ApplicationState>,
94
95 update_status:Arc<RwLock<UpdateStatus>>,
97
98 cache_directory:PathBuf,
100
101 staging_directory:PathBuf,
103
104 backup_directory:PathBuf,
106
107 download_sessions:Arc<RwLock<HashMap<String, DownloadSession>>>,
109
110 rollback_history:Arc<Mutex<RollbackHistory>>,
112
113 update_channel:UpdateChannel,
115
116 platform_config:PlatformConfig,
118}
119
120#[derive(Debug, Clone)]
122struct DownloadSession {
123 session_id:String,
125
126 download_url:String,
128
129 temp_path:PathBuf,
131
132 downloaded_bytes:u64,
134
135 total_bytes:u64,
137
138 complete:bool,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
144struct RollbackHistory {
145 versions:Vec<RollbackState>,
147
148 max_versions:usize,
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize)]
153struct RollbackState {
154 version:String,
155 backup_path:PathBuf,
156 timestamp:chrono::DateTime<chrono::Utc>,
157 checksum:String,
158}
159
160#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
162pub enum UpdateChannel {
163 Stable,
164 Insiders,
165 Preview,
166}
167
168impl UpdateChannel {
169 fn as_str(&self) -> &'static str {
170 match self {
171 UpdateChannel::Stable => "stable",
172 UpdateChannel::Insiders => "insiders",
173 UpdateChannel::Preview => "preview",
174 }
175 }
176}
177
178#[derive(Debug, Clone)]
180struct PlatformConfig {
181 platform:String,
182 arch:String,
183 package_format:PackageFormat,
184}
185
186#[derive(Debug, Clone, Copy)]
188enum PackageFormat {
189 WindowsExe,
190 MacOsDmg,
191 LinuxAppImage,
192 LinuxDeb,
193 LinuxRpm,
194}
195
196#[derive(Debug, Clone, Serialize, Deserialize)]
198pub struct UpdateStatus {
199 pub last_check:Option<chrono::DateTime<chrono::Utc>>,
201
202 pub update_available:bool,
204
205 pub current_version:String,
207
208 pub available_version:Option<String>,
210
211 pub download_progress:Option<f32>,
213
214 pub installation_status:InstallationStatus,
216
217 pub update_channel:UpdateChannel,
219
220 pub update_size:Option<u64>,
222
223 pub release_notes:Option<String>,
225
226 pub requires_restart:bool,
228
229 pub download_speed:Option<f64>,
231
232 pub eta_seconds:Option<u64>,
234
235 pub last_error:Option<String>,
237}
238
239#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
241pub enum InstallationStatus {
242 NotStarted,
244
245 CheckingPrerequisites,
247
248 Downloading,
250
251 Paused,
253
254 VerifyingSignature,
256
257 VerifyingChecksums,
259
260 Staging,
262
263 CreatingBackup,
265
266 Installing,
268
269 Completed,
271
272 RollingBack,
274
275 Failed(String),
277}
278
279impl InstallationStatus {
280 pub fn is_cancellable(&self) -> bool {
282 matches!(
283 self,
284 InstallationStatus::Downloading
285 | InstallationStatus::Paused
286 | InstallationStatus::Staging
287 | InstallationStatus::NotStarted
288 )
289 }
290
291 pub fn is_error(&self) -> bool { matches!(self, InstallationStatus::Failed(_)) }
293
294 pub fn is_in_progress(&self) -> bool {
296 matches!(
297 self,
298 InstallationStatus::CheckingPrerequisites
299 | InstallationStatus::Downloading
300 | InstallationStatus::VerifyingSignature
301 | InstallationStatus::VerifyingChecksums
302 | InstallationStatus::Staging
303 | InstallationStatus::CreatingBackup
304 | InstallationStatus::Installing
305 )
306 }
307}
308
309#[derive(Debug, Clone, Serialize, Deserialize)]
311pub struct UpdateInfo {
312 pub version:String,
314
315 pub download_url:String,
317
318 pub release_notes:String,
320
321 pub checksum:String,
323
324 pub checksums:HashMap<String, String>,
326
327 pub size:u64,
329
330 pub published_at:chrono::DateTime<chrono::Utc>,
332
333 pub is_mandatory:bool,
335
336 pub requires_restart:bool,
338
339 pub min_compatible_version:Option<String>,
341
342 pub delta_url:Option<String>,
344
345 pub delta_checksum:Option<String>,
347
348 pub delta_size:Option<u64>,
350
351 pub signature:Option<String>,
353
354 pub platform_metadata:Option<PlatformMetadata>,
356}
357
358#[derive(Debug, Clone, Serialize, Deserialize)]
360pub struct PlatformMetadata {
361 pub package_format:String,
363
364 pub install_instructions:Vec<String>,
366
367 pub required_disk_space:u64,
369
370 pub requires_admin:bool,
372
373 pub additional_params:HashMap<String, serde_json::Value>,
375}
376
377#[derive(Debug, Clone, Serialize, Deserialize)]
379pub struct UpdateTelemetry {
380 pub event_id:String,
382
383 pub current_version:String,
385
386 pub target_version:String,
388
389 pub channel:String,
391
392 pub platform:String,
394
395 pub operation:String,
397
398 pub success:bool,
400
401 pub duration_ms:u64,
403
404 pub download_size:Option<u64>,
406
407 pub error_message:Option<String>,
409
410 pub timestamp:chrono::DateTime<chrono::Utc>,
412}
413
414impl UpdateManager {
415 pub async fn new(AppState:Arc<ApplicationState>) -> Result<Self> {
417 let config = &AppState.Configuration.Updates;
418
419 let cache_directory = ConfigurationManager::ExpandPath(&AppState.Configuration.Downloader.CacheDirectory)?;
421
422 tokio::fs::create_dir_all(&cache_directory)
424 .await
425 .map_err(|e| AirError::Configuration(format!("Failed to create cache directory: {}", e)))?;
426
427 let staging_directory = cache_directory.join("staging");
429 tokio::fs::create_dir_all(&staging_directory)
430 .await
431 .map_err(|e| AirError::Configuration(format!("Failed to create staging directory: {}", e)))?;
432
433 let backup_directory = cache_directory.join("backups");
435 tokio::fs::create_dir_all(&backup_directory)
436 .await
437 .map_err(|e| AirError::Configuration(format!("Failed to create backup directory: {}", e)))?;
438
439 let PlatformConfig = Self::detect_platform();
441 let PlatformConfigClone = PlatformConfig.clone();
442
443 let update_channel = if config.Channel == "insiders" {
445 UpdateChannel::Insiders
446 } else if config.Channel == "preview" {
447 UpdateChannel::Preview
448 } else {
449 UpdateChannel::Stable
450 };
451
452 let rollback_history_path = backup_directory.join("rollback_history.json");
454 let rollback_history = if rollback_history_path.exists() {
455 let content = tokio::fs::read_to_string(&rollback_history_path)
456 .await
457 .map_err(|e| AirError::FileSystem(format!("Failed to read rollback history: {}", e)))?;
458 serde_json::from_str(&content).unwrap_or_else(|_| RollbackHistory { versions:Vec::new(), max_versions:5 })
459 } else {
460 RollbackHistory { versions:Vec::new(), max_versions:5 }
461 };
462
463 let manager = Self {
464 AppState,
465 update_status:Arc::new(RwLock::new(UpdateStatus {
466 last_check:None,
467 update_available:false,
468 current_version:env!("CARGO_PKG_VERSION").to_string(),
469 available_version:None,
470 download_progress:None,
471 installation_status:InstallationStatus::NotStarted,
472 update_channel,
473 update_size:None,
474 release_notes:None,
475 requires_restart:true,
476 download_speed:None,
477 eta_seconds:None,
478 last_error:None,
479 })),
480 cache_directory,
481 staging_directory,
482 backup_directory,
483 download_sessions:Arc::new(RwLock::new(HashMap::new())),
484 rollback_history:Arc::new(Mutex::new(rollback_history)),
485 update_channel,
486 platform_config:PlatformConfigClone,
487 };
488
489 manager
491 .AppState
492 .UpdateServiceStatus("updates", crate::ApplicationState::ServiceStatus::Running)
493 .await
494 .map_err(|e| AirError::Internal(e.to_string()))?;
495
496 log::info!(
497 "[UpdateManager] Update service initialized for platform: {}/{}",
498 PlatformConfig.platform,
499 PlatformConfig.arch
500 );
501
502 Ok(manager)
503 }
504
505 fn detect_platform() -> PlatformConfig {
507 let platform = if cfg!(target_os = "windows") {
508 "windows"
509 } else if cfg!(target_os = "macos") {
510 "macos"
511 } else if cfg!(target_os = "linux") {
512 "linux"
513 } else {
514 "unknown"
515 };
516
517 let arch = if cfg!(target_arch = "x86_64") {
518 "x64"
519 } else if cfg!(target_arch = "aarch64") {
520 "arm64"
521 } else if cfg!(target_arch = "x86") {
522 "ia32"
523 } else {
524 "unknown"
525 };
526
527 let package_format = match (platform, arch) {
528 ("windows", _) => PackageFormat::WindowsExe,
529 ("macos", _) => PackageFormat::MacOsDmg,
530 ("linux", "x64") => PackageFormat::LinuxAppImage,
531 ("linux", "") => PackageFormat::LinuxAppImage,
532 _ => PackageFormat::LinuxAppImage,
533 };
534
535 PlatformConfig { platform:platform.to_string(), arch:arch.to_string(), package_format }
536 }
537
538 pub async fn CheckForUpdates(&self) -> Result<Option<UpdateInfo>> {
548 let config = &self.AppState.Configuration.Updates;
549 let start_time = std::time::Instant::now();
550
551 if !config.Enabled {
552 log::debug!("[UpdateManager] Updates are disabled");
553 return Ok(None);
554 }
555
556 log::info!(
557 "[UpdateManager] Checking for updates on {} channel",
558 self.update_channel.as_str()
559 );
560
561 {
563 let mut status = self.update_status.write().await;
564 status.last_check = Some(chrono::Utc::now());
565 status.last_error = None;
566 }
567
568 let update_info = match self.FetchUpdateInfo().await {
570 Ok(info) => info,
571 Err(e) => {
572 log::error!("[UpdateManager] Failed to fetch update info: {}", e);
573 let mut status = self.update_status.write().await;
574 status.last_error = Some(e.to_string());
575 self.record_telemetry(
576 "check",
577 false,
578 start_time.elapsed().as_millis() as u64,
579 None,
580 Some(e.to_string()),
581 )
582 .await;
583 return Err(e);
584 },
585 };
586
587 if let Some(ref info) = update_info {
588 if let Some(ref min_version) = info.min_compatible_version {
590 let current_version = env!("CARGO_PKG_VERSION");
591 if UpdateManager::CompareVersions(current_version, min_version) < 0 {
592 log::warn!(
593 "[UpdateManager] Update requires minimum version {} but current is {}. Skipping.",
594 min_version,
595 current_version
596 );
597 let mut status = self.update_status.write().await;
598 status.last_error = Some(format!("Update requires minimum version {}", min_version));
599 return Ok(None);
600 }
601 }
602
603 log::info!(
604 "[UpdateManager] Update available: {} ({})",
605 info.version,
606 self.format_size(info.size as f64)
607 );
608
609 {
611 let mut status = self.update_status.write().await;
612 status.update_available = true;
613 status.available_version = Some(info.version.clone());
614 status.update_size = Some(info.size);
615 status.release_notes = Some(info.release_notes.clone());
616 status.requires_restart = info.requires_restart;
617 }
618
619 log::info!("[UpdateManager] Notifying frontend about available update");
622
623 self.record_telemetry("check", true, start_time.elapsed().as_millis() as u64, None, None)
625 .await;
626
627 if config.AutoDownload {
629 if let Err(e) = self.DownloadUpdate(info).await {
630 log::error!("[UpdateManager] Auto-download failed: {}", e);
631 }
633 }
634 } else {
635 log::info!("[UpdateManager] No updates available");
636
637 {
639 let mut status = self.update_status.write().await;
640 status.update_available = false;
641 status.available_version = None;
642 status.update_size = None;
643 status.release_notes = None;
644 }
645
646 self.record_telemetry("check", true, start_time.elapsed().as_millis() as u64, None, None)
648 .await;
649 }
650
651 Ok(update_info)
652 }
653
654 pub async fn DownloadUpdate(&self, update_info:&UpdateInfo) -> Result<()> {
670 let start_time = std::time::Instant::now();
671 let session_id = Uuid::new_v4().to_string();
672
673 log::info!(
674 "[UpdateManager] Starting download for version {} (session: {})",
675 update_info.version,
676 session_id
677 );
678
679 let required_space = update_info.size * 2; self.ValidateDiskSpace(required_space).await?;
682
683 {
685 let mut status = self.update_status.write().await;
686 status.installation_status = InstallationStatus::CheckingPrerequisites;
687 status.last_error = None;
688 }
689
690 let temp_path = self.cache_directory.join(format!("update-{}-temp.bin", update_info.version));
692 let final_path = self.cache_directory.join(format!("update-{}.bin", update_info.version));
693
694 let (downloaded_bytes, resume_from_start) = if temp_path.exists() {
696 let metadata = tokio::fs::metadata(&temp_path)
697 .await
698 .map_err(|e| AirError::FileSystem(format!("Failed to check temp file: {}", e)))?;
699 log::info!("[UpdateManager] Found partial download, resuming from {} bytes", metadata.len());
700 (metadata.len(), false)
701 } else {
702 (0, true)
703 };
704
705 {
707 let mut sessions = self.download_sessions.write().await;
708 sessions.insert(
709 session_id.clone(),
710 DownloadSession {
711 session_id:session_id.clone(),
712 download_url:update_info.download_url.clone(),
713 temp_path:temp_path.clone(),
714 downloaded_bytes,
715 total_bytes:update_info.size,
716 complete:false,
717 },
718 );
719 }
720
721 let client = reqwest::Client::builder()
723 .timeout(Duration::from_secs(300))
724 .build()
725 .map_err(|e| AirError::Network(format!("Failed to create HTTP client: {}", e)))?;
726
727 let mut request_builder = client.get(&update_info.download_url);
728
729 if !resume_from_start {
731 request_builder = request_builder.header("Range", format!("bytes={}-", downloaded_bytes));
732 }
733
734 let response = request_builder
735 .send()
736 .await
737 .map_err(|e| AirError::Network(format!("Failed to start download: {}", e)))?;
738
739 if !response.status().is_success() && response.status() != 206 {
740 log::error!("[UpdateManager] Download failed with status: {}", response.status());
741 let mut status = self.update_status.write().await;
742 status.installation_status =
743 InstallationStatus::Failed(format!("Download failed with status: {}", response.status()));
744 status.last_error = Some(format!("Download failed with status: {}", response.status()));
745 self.record_telemetry(
746 "download",
747 false,
748 start_time.elapsed().as_millis() as u64,
749 None,
750 Some("Download failed".to_string()),
751 )
752 .await;
753 return Err(AirError::Network(format!("Download failed with status: {}", response.status())));
754 }
755
756 let total_size = response.content_length().unwrap_or(update_info.size);
757 let initial_downloaded = if resume_from_start { 0 } else { downloaded_bytes };
758
759 {
761 let mut status = self.update_status.write().await;
762 status.installation_status = InstallationStatus::Downloading;
763 status.download_progress = Some(((downloaded_bytes as f32 / total_size as f32) * 100.0).min(100.0));
764 }
765
766 let last_update = Arc::new(Mutex::new(std::time::Instant::now()));
768 let last_bytes = Arc::new(Mutex::new(downloaded_bytes));
769
770 let mut file = if resume_from_start {
772 tokio::fs::File::create(&temp_path)
773 .await
774 .map_err(|e| AirError::FileSystem(format!("Failed to create update file: {}", e)))?
775 } else {
776 tokio::fs::OpenOptions::new()
778 .append(true)
779 .open(&temp_path)
780 .await
781 .map_err(|e| AirError::FileSystem(format!("Failed to open update file for resume: {}", e)))?
782 };
783
784 use tokio::io::AsyncWriteExt;
785 use futures_util::StreamExt;
786
787 let mut byte_stream = response.bytes_stream();
788 let mut downloaded = initial_downloaded;
789
790 while let Some(chunk_result) = byte_stream.next().await {
791 match chunk_result {
792 Ok(chunk) => {
793 file.write_all(&chunk)
794 .await
795 .map_err(|e| AirError::FileSystem(format!("Failed to write update file: {}", e)))?;
796
797 downloaded += chunk.len() as u64;
798
799 {
801 let mut last_update_guard = last_update.lock().await;
802 let mut last_bytes_guard = last_bytes.lock().await;
803
804 if last_update_guard.elapsed() >= Duration::from_secs(1) {
805 let bytes_this_second = downloaded - *last_bytes_guard;
806 let download_speed = bytes_this_second as f64;
807
808 let progress = ((downloaded as f32 / total_size as f32) * 100.0).min(100.0);
809 let remaining_bytes = total_size - downloaded;
810 let eta_seconds = if download_speed > 0.0 {
811 Some(remaining_bytes as u64 / (download_speed as u64).max(1))
812 } else {
813 None
814 };
815
816 {
817 let mut status = self.update_status.write().await;
818 status.download_progress = Some(progress);
819 status.download_speed = Some(download_speed);
820 status.eta_seconds = eta_seconds;
821 }
822
823 log::debug!(
824 "[UpdateManager] Download progress: {:.1}% ({}/s, ETA: {:?})",
825 progress,
826 self.format_size(download_speed),
827 eta_seconds
828 );
829
830 *last_update_guard = std::time::Instant::now();
831 *last_bytes_guard = downloaded;
832 }
833 }
834
835 {
837 let mut sessions = self.download_sessions.write().await;
838 if let Some(session) = sessions.get_mut(&session_id) {
839 session.downloaded_bytes = downloaded;
840 }
841 }
842 },
843 Err(e) => {
844 log::error!("[UpdateManager] Download error: {}", e);
845 let mut status = self.update_status.write().await;
846 status.installation_status = InstallationStatus::Failed(format!("Network error: {}", e));
847 status.last_error = Some(format!("Network error: {}", e));
848 self.record_telemetry(
849 "download",
850 false,
851 start_time.elapsed().as_millis() as u64,
852 None,
853 Some(e.to_string()),
854 )
855 .await;
856 return Err(AirError::Network(format!("Download error: {}", e)));
857 },
858 }
859 }
860
861 {
863 let mut status = self.update_status.write().await;
864 status.installation_status = InstallationStatus::Downloading;
865 status.download_progress = Some(100.0);
866 }
867
868 log::info!(
869 "[UpdateManager] Download completed: {} bytes in {:.2}s",
870 downloaded,
871 start_time.elapsed().as_secs_f64()
872 );
873
874 {
876 let mut status = self.update_status.write().await;
877 status.installation_status = InstallationStatus::VerifyingChecksums;
878 }
879
880 self.VerifyChecksum(&temp_path, &update_info.checksum).await?;
881
882 for (algorithm, expected_checksum) in &update_info.checksums {
884 self.VerifyChecksumWithAlgorithm(&temp_path, algorithm, expected_checksum)
885 .await?;
886 }
887
888 if let Some(ref signature) = update_info.signature {
890 {
891 let mut status = self.update_status.write().await;
892 status.installation_status = InstallationStatus::VerifyingSignature;
893 }
894 self.VerifySignature(&temp_path, signature).await?;
895 }
896
897 if temp_path.exists() {
899 tokio::fs::rename(&temp_path, &final_path)
900 .await
901 .map_err(|e| AirError::FileSystem(format!("Failed to finalize download: {}", e)))?;
902 }
903
904 {
906 let mut sessions = self.download_sessions.write().await;
907 if let Some(session) = sessions.get_mut(&session_id) {
908 session.complete = true;
909 }
910 }
911
912 {
914 let mut status = self.update_status.write().await;
915 status.installation_status = InstallationStatus::Completed;
916 status.download_progress = Some(100.0);
917 }
918
919 log::info!(
920 "[UpdateManager] Update {} downloaded and verified successfully",
921 update_info.version
922 );
923
924 self.record_telemetry(
926 "download",
927 true,
928 start_time.elapsed().as_millis() as u64,
929 Some(downloaded),
930 None,
931 )
932 .await;
933
934 Ok(())
935 }
936
937 pub async fn ApplyUpdate(&self, update_info:&UpdateInfo) -> Result<()> {
952 let start_time = std::time::Instant::now();
953 let current_version = env!("CARGO_PKG_VERSION");
954
955 log::info!(
956 "[UpdateManager] Applying update: {} (from {})",
957 update_info.version,
958 current_version
959 );
960
961 let file_path = self.cache_directory.join(format!("update-{}.bin", update_info.version));
962
963 if !file_path.exists() {
965 log::error!("[UpdateManager] Update file not found: {:?}", file_path);
966 return Err(AirError::FileSystem(
967 "Update file not found. Please download first.".to_string(),
968 ));
969 }
970
971 {
973 let mut status = self.update_status.write().await;
974 status.installation_status = InstallationStatus::VerifyingChecksums;
975 status.last_error = None;
976 }
977
978 self.VerifyChecksum(&file_path, &update_info.checksum).await?;
980
981 for (algorithm, expected_checksum) in &update_info.checksums {
983 self.VerifyChecksumWithAlgorithm(&file_path, algorithm, expected_checksum)
984 .await?;
985 }
986
987 if let Some(ref signature) = update_info.signature {
989 {
990 let mut status = self.update_status.write().await;
991 status.installation_status = InstallationStatus::VerifyingSignature;
992 }
993 self.VerifySignature(&file_path, signature).await?;
994 }
995
996 {
998 let mut status = self.update_status.write().await;
999 status.installation_status = InstallationStatus::CreatingBackup;
1000 }
1001
1002 let backup_info = self.CreateBackup(current_version).await?;
1003 log::info!("[UpdateManager] Backup created: {:?}", backup_info.backup_path);
1004
1005 {
1007 let mut status = self.update_status.write().await;
1008 status.installation_status = InstallationStatus::Installing;
1009 }
1010
1011 let result = match self.platform_config.package_format {
1013 PackageFormat::WindowsExe => self.ApplyWindowsUpdate(&file_path).await,
1014 PackageFormat::MacOsDmg => self.ApplyMacOsUpdate(&file_path).await,
1015 PackageFormat::LinuxAppImage => self.ApplyLinuxAppImageUpdate(&file_path).await,
1016 PackageFormat::LinuxDeb => self.ApplyLinuxDebUpdate(&file_path).await,
1017 PackageFormat::LinuxRpm => self.ApplyLinuxRpmUpdate(&file_path).await,
1018 };
1019
1020 if let Err(e) = result {
1021 log::error!("[UpdateManager] Installation failed, initiating rollback: {}", e);
1022
1023 {
1025 let mut status = self.update_status.write().await;
1026 status.installation_status = InstallationStatus::RollingBack;
1027 }
1028
1029 if let Err(rollback_err) = self.RollbackToBackup(&backup_info).await {
1031 log::error!("[UpdateManager] Rollback also failed: {}", rollback_err);
1032
1033 let mut status = self.update_status.write().await;
1035 status.installation_status = InstallationStatus::Failed(format!(
1036 "Installation failed and rollback failed: {} / {}",
1037 e, rollback_err
1038 ));
1039 status.last_error = Some(format!("Installation failed and rollback failed"));
1040
1041 self.record_telemetry(
1042 "install",
1043 false,
1044 start_time.elapsed().as_millis() as u64,
1045 None,
1046 Some(format!("Update and rollback failed: {}", rollback_err)),
1047 )
1048 .await;
1049
1050 return Err(AirError::Internal(format!(
1051 "Installation failed and rollback failed: {} / {}",
1052 e, rollback_err
1053 )));
1054 } else {
1055 log::info!("[UpdateManager] Rollback successful");
1056
1057 let mut status = self.update_status.write().await;
1058 status.installation_status =
1059 InstallationStatus::Failed(format!("Installation failed, rollback successful: {}", e));
1060 status.last_error = Some(e.to_string());
1061
1062 self.record_telemetry(
1063 "install",
1064 false,
1065 start_time.elapsed().as_millis() as u64,
1066 None,
1067 Some(e.to_string()),
1068 )
1069 .await;
1070
1071 return Err(AirError::Internal(format!("Installation failed, rollback successful: {}", e)));
1072 }
1073 }
1074
1075 {
1077 let mut history = self.rollback_history.lock().await;
1078 history.versions.insert(0, backup_info);
1079
1080 while history.versions.len() > history.max_versions {
1082 if let Some(old_backup) = history.versions.pop() {
1083 let _ = tokio::fs::remove_dir_all(&old_backup.backup_path).await;
1085 }
1086 }
1087 }
1088
1089 let history_path = self.backup_directory.join("rollback_history.json");
1091 let history = self.rollback_history.lock().await;
1092 let history_json = serde_json::to_string(&*history)
1093 .map_err(|e| AirError::Internal(format!("Failed to serialize rollback history: {}", e)))?;
1094 drop(history);
1095 tokio::fs::write(&history_path, history_json)
1096 .await
1097 .map_err(|e| AirError::FileSystem(format!("Failed to save rollback history: {}", e)))?;
1098
1099 {
1101 let mut status = self.update_status.write().await;
1102 status.current_version = update_info.version.clone();
1103 status.installation_status = InstallationStatus::Completed;
1104 }
1105
1106 log::info!(
1107 "[UpdateManager] Update {} applied successfully in {:.2}s",
1108 update_info.version,
1109 start_time.elapsed().as_secs_f64()
1110 );
1111
1112 self.record_telemetry(
1114 "install",
1115 true,
1116 start_time.elapsed().as_millis() as u64,
1117 Some(update_info.size),
1118 None,
1119 )
1120 .await;
1121
1122 Ok(())
1123 }
1124
1125 async fn FetchUpdateInfo(&self) -> Result<Option<UpdateInfo>> {
1136 let config = &self.AppState.Configuration.Updates;
1137
1138 let retry_policy = crate::Resilience::RetryPolicy {
1140 MaxRetries:3,
1141 InitialIntervalMs:1000,
1142 MaxIntervalMs:16000,
1143 BackoffMultiplier:2.0,
1144 JitterFactor:0.1,
1145 BudgetPerMinute:50,
1146 ErrorClassification:std::collections::HashMap::new(),
1147 };
1148
1149 let _retry_manager = crate::Resilience::RetryManager::new(retry_policy.clone());
1150 let circuit_breaker = crate::Resilience::CircuitBreaker::new(
1151 "updates".to_string(),
1152 crate::Resilience::CircuitBreakerConfig::default(),
1153 );
1154
1155 let current_version = env!("CARGO_PKG_VERSION");
1156 let mut attempt = 0;
1157
1158 loop {
1159 if circuit_breaker.GetState().await == crate::Resilience::CircuitState::Open {
1161 if !circuit_breaker.AttemptRecovery().await {
1162 log::warn!("[UpdateManager] Circuit breaker is open, skipping update check");
1163 return Ok(None);
1164 }
1165 }
1166
1167 let update_url = format!(
1169 "{}/check?version={}&platform={}&arch={}&channel={}",
1170 config.UpdateServerUrl,
1171 current_version,
1172 self.platform_config.platform,
1173 self.platform_config.arch,
1174 self.update_channel.as_str()
1175 );
1176
1177 let client = reqwest::Client::builder()
1178 .timeout(Duration::from_secs(30))
1179 .build()
1180 .map_err(|e| AirError::Network(format!("Failed to create HTTP client: {}", e)))?;
1181
1182 match client.get(&update_url).send().await {
1183 Ok(response) => {
1184 match response.status() {
1185 reqwest::StatusCode::NO_CONTENT => {
1186 circuit_breaker.RecordSuccess().await;
1188 log::debug!("[UpdateManager] Server reports no updates available");
1189 return Ok(None);
1190 },
1191 status if status.is_success() => {
1192 match response.json::<UpdateInfo>().await {
1194 Ok(update_info) => {
1195 circuit_breaker.RecordSuccess().await;
1196
1197 if UpdateManager::CompareVersions(current_version, &update_info.version) < 0 {
1199 log::info!(
1200 "[UpdateManager] Update available: {} -> {}",
1201 current_version,
1202 update_info.version
1203 );
1204 return Ok(Some(update_info));
1205 } else {
1206 log::debug!(
1207 "[UpdateManager] Server returned same or older version: {}",
1208 update_info.version
1209 );
1210 return Ok(None);
1211 }
1212 },
1213 Err(e) => {
1214 circuit_breaker.RecordFailure().await;
1215 log::error!("[UpdateManager] Failed to parse update info: {}", e);
1216
1217 if attempt < retry_policy.MaxRetries {
1218 attempt += 1;
1219 let delay = Duration::from_millis(
1220 retry_policy.InitialIntervalMs * 2_u32.pow(attempt as u32) as u64,
1221 );
1222 sleep(delay).await;
1223 continue;
1224 } else {
1225 return Err(AirError::Network(format!(
1226 "Failed to parse update info after retries: {}",
1227 e
1228 )));
1229 }
1230 },
1231 }
1232 },
1233 status => {
1234 circuit_breaker.RecordFailure().await;
1235 log::warn!("[UpdateManager] Update server returned status: {}", status);
1236
1237 if attempt < retry_policy.MaxRetries {
1238 attempt += 1;
1239 let delay = Duration::from_millis(
1240 retry_policy.InitialIntervalMs * 2_u32.pow(attempt as u32) as u64,
1241 );
1242 sleep(delay).await;
1243 continue;
1244 } else {
1245 return Ok(None);
1246 }
1247 },
1248 }
1249 },
1250 Err(e) => {
1251 circuit_breaker.RecordFailure().await;
1252 log::warn!("[UpdateManager] Failed to check for updates: {}", e);
1253
1254 if attempt < retry_policy.MaxRetries {
1255 attempt += 1;
1256 let delay =
1257 Duration::from_millis(retry_policy.InitialIntervalMs * 2_u32.pow(attempt as u32) as u64);
1258 sleep(delay).await;
1259 continue;
1260 } else {
1261 return Ok(None);
1262 }
1263 },
1264 }
1265 }
1266 }
1267
1268 async fn VerifyChecksum(&self, file_path:&Path, expected_checksum:&str) -> Result<()> {
1282 let content = tokio::fs::read(file_path)
1283 .await
1284 .map_err(|e| AirError::FileSystem(format!("Failed to read update file for checksum: {}", e)))?;
1285
1286 let actual_checksum = self.CalculateSha256(&content);
1287
1288 if actual_checksum.to_lowercase() != expected_checksum.to_lowercase() {
1289 log::error!(
1290 "[UpdateManager] Checksum verification failed: expected {}, got {}",
1291 expected_checksum,
1292 actual_checksum
1293 );
1294 return Err(AirError::Network("Update checksum verification failed".to_string()));
1295 }
1296
1297 log::debug!("[UpdateManager] Checksum verified: {}", actual_checksum);
1298 Ok(())
1299 }
1300
1301 async fn VerifyChecksumWithAlgorithm(&self, file_path:&Path, algorithm:&str, expected_checksum:&str) -> Result<()> {
1314 let content = tokio::fs::read(file_path).await.map_err(|e| {
1315 AirError::FileSystem(format!("Failed to read update file for {} checksum: {}", algorithm, e))
1316 })?;
1317
1318 let actual_checksum = match algorithm.to_lowercase().as_str() {
1319 "sha256" => self.CalculateSha256(&content),
1320 "sha512" => self.CalculateSha512(&content),
1321 "md5" => self.CalculateMd5(&content),
1322 "crc32" => self.CalculateCrc32(&content),
1323 _ => {
1324 log::warn!("[UpdateManager] Unknown checksum algorithm: {}, skipping", algorithm);
1325 return Ok(());
1326 },
1327 };
1328
1329 if actual_checksum.to_lowercase() != expected_checksum.to_lowercase() {
1330 log::error!(
1331 "[UpdateManager] {} checksum verification failed: expected {}, got {}",
1332 algorithm,
1333 expected_checksum,
1334 actual_checksum
1335 );
1336 return Err(AirError::Network(format!("{} checksum verification failed", algorithm)));
1337 }
1338
1339 log::debug!("[UpdateManager] {} checksum verified: {}", algorithm, actual_checksum);
1340 Ok(())
1341 }
1342
1343 async fn VerifySignature(&self, file_path:&Path, signature:&str) -> Result<()> {
1357 log::info!("[UpdateManager] Signature verification not yet implemented, skipping");
1365
1366 log::warn!("[UpdateManager] WARNING: Cryptographic signature verification is not implemented");
1368 log::warn!("[UpdateManager] Update packages should be cryptographically signed in production");
1369
1370 Ok(())
1371 }
1372
1373 async fn CreateBackup(&self, version:&str) -> Result<RollbackState> {
1386 let timestamp = chrono::Utc::now();
1387 let backup_dir_name = format!("backup-{}-{}", version, timestamp.format("%Y%m%d_%H%M%S"));
1388 let backup_path = self.backup_directory.join(&backup_dir_name);
1389
1390 log::info!("[UpdateManager] Creating backup: {}", backup_dir_name);
1391
1392 tokio::fs::create_dir_all(&backup_path)
1394 .await
1395 .map_err(|e| AirError::FileSystem(format!("Failed to create backup directory: {}", e)))?;
1396
1397 let exe_path = std::env::current_exe()
1399 .map_err(|e| AirError::FileSystem(format!("Failed to get executable path: {}", e)))?;
1400
1401 let backup_exe = backup_path.join(exe_path.file_name().unwrap_or_default());
1403 tokio::fs::copy(&exe_path, &backup_exe)
1404 .await
1405 .map_err(|e| AirError::FileSystem(format!("Failed to backup executable: {}", e)))?;
1406
1407 let checksum = self.CalculateFileChecksum(&backup_path).await?;
1415
1416 log::info!("[UpdateManager] Backup created at: {:?}", backup_path);
1417
1418 Ok(RollbackState { version:version.to_string(), backup_path, timestamp, checksum })
1419 }
1420
1421 pub async fn RollbackToBackup(&self, backup_info:&RollbackState) -> Result<()> {
1434 log::info!(
1435 "[UpdateManager] Rolling back to version: {} from: {:?}",
1436 backup_info.version,
1437 backup_info.backup_path
1438 );
1439
1440 let current_checksum = self.CalculateFileChecksum(&backup_info.backup_path).await?;
1442 if current_checksum != backup_info.checksum {
1443 return Err(AirError::Internal(format!(
1444 "Backup integrity check failed: expected {}, got {}",
1445 backup_info.checksum, current_checksum
1446 )));
1447 }
1448
1449 let exe_path = std::env::current_exe()
1451 .map_err(|e| AirError::FileSystem(format!("Failed to get executable path: {}", e)))?;
1452
1453 let backup_exe = backup_info.backup_path.join(exe_path.file_name().unwrap_or_default());
1454
1455 if !backup_exe.exists() {
1456 return Err(AirError::FileSystem("Backup executable not found".to_string()));
1457 }
1458
1459 match tokio::fs::copy(&backup_exe, &exe_path).await {
1463 Ok(_) => {
1464 log::info!("[UpdateManager] Executable restored from backup");
1465 },
1466 Err(e) => {
1467 log::error!("[UpdateManager] Failed to restore executable: {}", e);
1468 log::warn!("[UpdateManager] Rollback may require manual intervention");
1469 },
1470 }
1471
1472 log::info!("[UpdateManager] Rollback to version {} completed", backup_info.version);
1475 Ok(())
1476 }
1477
1478 pub async fn RollbackToVersion(&self, version:&str) -> Result<()> {
1490 let history = self.rollback_history.lock().await;
1491
1492 let backup_info = history
1493 .versions
1494 .iter()
1495 .find(|state| state.version == version)
1496 .ok_or_else(|| AirError::FileSystem(format!("No backup found for version {}", version)))?;
1497
1498 let info = backup_info.clone();
1499 drop(history);
1500
1501 self.RollbackToBackup(&info).await
1502 }
1503
1504 pub async fn GetAvailableRollbackVersions(&self) -> Vec<String> {
1508 let history = self.rollback_history.lock().await;
1509 history.versions.iter().map(|state| state.version.clone()).collect()
1510 }
1511
1512 async fn ValidateDiskSpace(&self, required_bytes:u64) -> Result<()> {
1522 let metadata = tokio::fs::metadata(&self.cache_directory)
1524 .await
1525 .map_err(|e| AirError::FileSystem(format!("Failed to get cache directory info: {}", e)))?;
1526
1527 if cfg!(target_os = "windows") {
1528 #[cfg(target_os = "windows")]
1530 {
1531 use std::os::windows::fs::MetadataExt;
1532 let free_space = metadata.volume_serial_number() as u64; log::warn!("[UpdateManager] Disk space validation not fully implemented on Windows");
1534 }
1535 } else {
1536 #[cfg(not(target_os = "windows"))]
1538 {
1539 use std::os::unix::fs::MetadataExt;
1540 let _device_id = metadata.dev();
1541
1542 }
1545 }
1546
1547 log::info!(
1548 "[UpdateManager] Disk space validation: requiring {} bytes",
1549 self.format_size(required_bytes as f64)
1550 );
1551
1552 Ok(())
1555 }
1556
1557 pub async fn verify_update(&self, file_path:&str, update_info:Option<&UpdateInfo>) -> Result<bool> {
1571 let path = PathBuf::from(file_path);
1572
1573 if !path.exists() {
1574 return Ok(false);
1575 }
1576
1577 let metadata = tokio::fs::metadata(&path)
1578 .await
1579 .map_err(|e| AirError::FileSystem(format!("Failed to read update file metadata: {}", e)))?;
1580
1581 if metadata.len() == 0 {
1582 return Ok(false);
1583 }
1584
1585 if let Some(info) = update_info {
1587 if !info.checksum.is_empty() {
1588 let actual_checksum = self.CalculateFileChecksum(&path).await?;
1589 if actual_checksum != info.checksum {
1590 return Err(AirError::Configuration(format!(
1591 "Checksum verification failed: expected {}, got {}",
1592 info.checksum, actual_checksum
1593 )));
1594 }
1595 }
1596
1597 for (algorithm, expected_checksum) in &info.checksums {
1599 self.VerifyChecksumWithAlgorithm(&path, algorithm, expected_checksum).await?;
1600 }
1601
1602 if let Some(expected_size) = Some(info.size) {
1604 if metadata.len() != expected_size {
1605 return Err(AirError::Configuration(format!(
1606 "File size mismatch: expected {}, got {}",
1607 expected_size,
1608 metadata.len()
1609 )));
1610 }
1611 }
1612 }
1613
1614 Ok(true)
1615 }
1616
1617 async fn ApplyWindowsUpdate(&self, file_path:&Path) -> Result<()> {
1619 log::info!("[UpdateManager] Installing Windows update: {:?}", file_path);
1620
1621 log::warn!("[UpdateManager] Windows installation not fully implemented");
1630 log::info!("[UpdateManager] Update package ready for manual installation");
1631
1632 Ok(())
1633 }
1634
1635 async fn ApplyMacOsUpdate(&self, file_path:&Path) -> Result<()> {
1637 log::info!("[UpdateManager] Installing macOS update: {:?}", file_path);
1638
1639 log::warn!("[UpdateManager] macOS installation not fully implemented");
1649 log::info!("[UpdateManager] Update package ready for manual installation");
1650
1651 Ok(())
1652 }
1653
1654 async fn ApplyLinuxAppImageUpdate(&self, file_path:&Path) -> Result<()> {
1656 log::info!("[UpdateManager] Installing Linux AppImage update: {:?}", file_path);
1657
1658 log::warn!("[UpdateManager] Linux AppImage installation not fully implemented");
1666 log::info!("[UpdateManager] Update package ready for manual installation");
1667
1668 Ok(())
1669 }
1670
1671 async fn ApplyLinuxDebUpdate(&self, file_path:&Path) -> Result<()> {
1673 log::info!("[UpdateManager] Installing Linux DEB update: {:?}", file_path);
1674
1675 log::warn!("[UpdateManager] Linux DEB installation not fully implemented");
1679 Ok(())
1680 }
1681
1682 async fn ApplyLinuxRpmUpdate(&self, file_path:&Path) -> Result<()> {
1684 log::info!("[UpdateManager] Installing Linux RPM update: {:?}", file_path);
1685
1686 log::warn!("[UpdateManager] Linux RPM installation not fully implemented");
1690 Ok(())
1691 }
1692
1693 async fn record_telemetry(
1707 &self,
1708 operation:&str,
1709 success:bool,
1710 duration_ms:u64,
1711 download_size:Option<u64>,
1712 error_message:Option<String>,
1713 ) {
1714 let telemetry = UpdateTelemetry {
1715 event_id:Uuid::new_v4().to_string(),
1716 current_version:env!("CARGO_PKG_VERSION").to_string(),
1717 target_version:self
1718 .update_status
1719 .read()
1720 .await
1721 .available_version
1722 .clone()
1723 .unwrap_or_else(|| "unknown".to_string()),
1724 channel:self.update_channel.as_str().to_string(),
1725 platform:format!("{}/{}", self.platform_config.platform, self.platform_config.arch),
1726 operation:operation.to_string(),
1727 success,
1728 duration_ms,
1729 download_size,
1730 error_message,
1731 timestamp:chrono::Utc::now(),
1732 };
1733
1734 log::info!(
1735 "[UpdateManager] Telemetry: {} {} in {}ms - size: {:?}, success: {}",
1736 operation,
1737 if success { "succeeded" } else { "failed" },
1738 duration_ms,
1739 download_size.map(|s| self.format_size(s as f64)),
1740 success
1741 );
1742
1743 if let Err(e) = serde_json::to_string(&telemetry) {
1746 log::error!("[UpdateManager] Failed to serialize telemetry: {}", e);
1747 }
1748 }
1749
1750 fn CalculateSha256(&self, data:&[u8]) -> String {
1752 let mut hasher = Sha256::new();
1753 hasher.update(data);
1754 format!("{:x}", hasher.finalize())
1755 }
1756
1757 fn CalculateSha512(&self, data:&[u8]) -> String {
1759 use sha2::Sha512;
1760 let mut hasher = Sha512::new();
1761 hasher.update(data);
1762 format!("{:x}", hasher.finalize())
1763 }
1764
1765 fn CalculateMd5(&self, data:&[u8]) -> String {
1767 let digest = md5::compute(data);
1768 format!("{:x}", digest)
1769 }
1770
1771 fn CalculateCrc32(&self, data:&[u8]) -> String {
1773 let crc = crc32fast::hash(data);
1774 format!("{:08x}", crc)
1775 }
1776
1777 async fn CalculateFileChecksum(&self, path:&Path) -> Result<String> {
1779 let content = tokio::fs::read(path)
1780 .await
1781 .map_err(|e| AirError::FileSystem(format!("Failed to read file for checksum: {}", e)))?;
1782
1783 Ok(self.CalculateSha256(&content))
1784 }
1785
1786 pub fn CompareVersions(v1:&str, v2:&str) -> i32 {
1800 let v1_parts:Vec<u32> = v1.split('.').filter_map(|s| s.parse().ok()).collect();
1801 let v2_parts:Vec<u32> = v2.split('.').filter_map(|s| s.parse().ok()).collect();
1802
1803 for (i, part) in v1_parts.iter().enumerate() {
1804 if i >= v2_parts.len() {
1805 return 1;
1806 }
1807
1808 match part.cmp(&v2_parts[i]) {
1809 std::cmp::Ordering::Greater => return 1,
1810 std::cmp::Ordering::Less => return -1,
1811 std::cmp::Ordering::Equal => continue,
1812 }
1813 }
1814
1815 if v1_parts.len() < v2_parts.len() { -1 } else { 0 }
1816 }
1817
1818 pub async fn GetStatus(&self) -> UpdateStatus {
1822 let status = self.update_status.read().await;
1823 status.clone()
1824 }
1825
1826 pub async fn CancelDownload(&self) -> Result<()> {
1833 let mut status = self.update_status.write().await;
1834
1835 if status.installation_status != InstallationStatus::Downloading {
1836 return Err(AirError::Internal("No download in progress".to_string()));
1837 }
1838
1839 status.installation_status = InstallationStatus::Paused;
1840
1841 log::info!("[UpdateManager] Download cancelled");
1848 Ok(())
1849 }
1850
1851 pub async fn ResumeDownload(&self, update_info:&UpdateInfo) -> Result<()> {
1860 let Status = self.update_status.write().await;
1861
1862 if Status.installation_status != InstallationStatus::Paused {
1863 return Err(AirError::Internal("No paused download to resume".to_string()));
1864 }
1865
1866 drop(Status);
1867
1868 log::info!("[UpdateManager] Resuming download for version {}", update_info.version);
1869 self.DownloadUpdate(update_info).await
1870 }
1871
1872 pub async fn GetUpdateChannel(&self) -> UpdateChannel { self.update_channel }
1876
1877 pub async fn SetUpdateChannel(&mut self, channel:UpdateChannel) {
1882 self.update_channel = channel;
1883
1884 let mut status = self.update_status.write().await;
1885 status.update_channel = channel;
1886
1887 log::info!("[UpdateManager] Update channel changed to: {}", channel.as_str());
1888
1889 if let Err(e) = self.CheckForUpdates().await {
1891 log::error!("[UpdateManager] Failed to check for updates after channel change: {}", e);
1892 }
1893 }
1894
1895 pub async fn StageUpdate(&self, update_info:&UpdateInfo) -> Result<()> {
1905 log::info!("[UpdateManager] Staging update for version {}", update_info.version);
1906
1907 let mut status = self.update_status.write().await;
1908 status.installation_status = InstallationStatus::Staging;
1909 drop(status);
1910
1911 let file_path = self.cache_directory.join(format!("update-{}.bin", update_info.version));
1912
1913 if !file_path.exists() {
1914 return Err(AirError::FileSystem("Update file not found. Download first.".to_string()));
1915 }
1916
1917 let stage_dir = self.staging_directory.join(&update_info.version);
1919 tokio::fs::create_dir_all(&stage_dir)
1920 .await
1921 .map_err(|e| AirError::FileSystem(format!("Failed to create staging directory: {}", e)))?;
1922
1923 let staged_file = stage_dir.join("update.bin");
1925 tokio::fs::copy(&file_path, &staged_file)
1926 .await
1927 .map_err(|e| AirError::FileSystem(format!("Failed to stage update package: {}", e)))?;
1928
1929 self.VerifyChecksum(&staged_file, &update_info.checksum).await?;
1931
1932 log::info!("[UpdateManager] Update staged successfully in: {:?}", stage_dir);
1933 Ok(())
1934 }
1935
1936 pub async fn CleanupOldUpdates(&self) -> Result<()> {
1941 log::info!("[UpdateManager] Cleaning up old update files");
1942
1943 let mut entries = tokio::fs::read_dir(&self.cache_directory)
1944 .await
1945 .map_err(|e| AirError::FileSystem(format!("Failed to read cache directory: {}", e)))?;
1946
1947 let mut cleaned_count = 0;
1948 let now = std::time::SystemTime::now();
1949
1950 while let Some(entry) = entries
1951 .next_entry()
1952 .await
1953 .map_err(|e| AirError::FileSystem(format!("Failed to read directory entry: {}", e)))?
1954 {
1955 let path = entry.path();
1956 let metadata = entry
1957 .metadata()
1958 .await
1959 .map_err(|e| AirError::FileSystem(format!("Failed to get metadata: {}", e)))?;
1960
1961 if path.is_dir()
1963 || metadata.modified().unwrap_or(now)
1964 > now.checked_sub(Duration::from_secs(7 * 24 * 3600)).unwrap_or(now)
1965 {
1966 continue;
1967 }
1968
1969 log::debug!("[UpdateManager] Removing old update file: {:?}", path);
1970 tokio::fs::remove_file(&path)
1971 .await
1972 .map_err(|e| AirError::FileSystem(format!("Failed to remove {}: {}", path.display(), e)))?;
1973
1974 cleaned_count += 1;
1975 }
1976
1977 log::info!("[UpdateManager] Cleaned up {} old update files", cleaned_count);
1978 Ok(())
1979 }
1980
1981 pub fn GetCacheDirectory(&self) -> &PathBuf { &self.cache_directory }
1983
1984 pub async fn StartBackgroundTasks(&self) -> Result<tokio::task::JoinHandle<()>> {
1994 let manager = self.clone();
1995
1996 let handle = tokio::spawn(async move {
1997 manager.BackgroundTask().await;
1998 });
1999
2000 log::info!("[UpdateManager] Background update checking started");
2001 Ok(handle)
2002 }
2003
2004 async fn BackgroundTask(&self) {
2011 let config = &self.AppState.Configuration.Updates;
2012
2013 if !config.Enabled {
2014 log::info!("[UpdateManager] Background task: Updates are disabled");
2015 return;
2016 }
2017
2018 let check_interval = Duration::from_secs(config.CheckIntervalHours as u64 * 3600);
2019 let mut interval = interval(check_interval);
2020
2021 log::info!(
2022 "[UpdateManager] Background task: Checking for updates every {} hours",
2023 config.CheckIntervalHours
2024 );
2025
2026 loop {
2027 interval.tick().await;
2028
2029 log::debug!("[UpdateManager] Background task: Checking for updates...");
2030
2031 match self.CheckForUpdates().await {
2033 Ok(Some(update_info)) => {
2034 log::info!("[UpdateManager] Background task: Update available: {}", update_info.version);
2035 },
2036 Ok(None) => {
2037 log::debug!("[UpdateManager] Background task: No updates available");
2038 },
2039 Err(e) => {
2040 log::error!("[UpdateManager] Background task: Update check failed: {}", e);
2041 },
2042 }
2043 }
2044 }
2045
2046 pub async fn StopBackgroundTasks(&self) {
2052 log::info!("[UpdateManager] Stopping background tasks");
2053
2054 }
2057
2058 fn format_size(&self, bytes:f64) -> String {
2066 const KB:f64 = 1024.0;
2067 const MB:f64 = KB * 1024.0;
2068 const GB:f64 = MB * 1024.0;
2069
2070 if bytes >= GB {
2071 format!("{:.2} GB/s", bytes / GB)
2072 } else if bytes >= MB {
2073 format!("{:.2} MB/s", bytes / MB)
2074 } else if bytes >= KB {
2075 format!("{:.2} KB/s", bytes / KB)
2076 } else {
2077 format!("{:.0} B/s", bytes)
2078 }
2079 }
2080}
2081
2082impl Clone for UpdateManager {
2083 fn clone(&self) -> Self {
2084 Self {
2085 AppState:self.AppState.clone(),
2086 update_status:self.update_status.clone(),
2087 cache_directory:self.cache_directory.clone(),
2088 staging_directory:self.staging_directory.clone(),
2089 backup_directory:self.backup_directory.clone(),
2090 download_sessions:self.download_sessions.clone(),
2091 rollback_history:self.rollback_history.clone(),
2092 update_channel:self.update_channel,
2093 platform_config:self.platform_config.clone(),
2094 }
2095 }
2096}