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, dev_log};
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 background_task:Arc<Mutex<Option<tokio::task::JoinHandle<()>>>>,
121}
122
123#[derive(Debug, Clone)]
125struct DownloadSession {
126 #[allow(dead_code)]
128 session_id:String,
129
130 #[allow(dead_code)]
132 download_url:String,
133
134 #[allow(dead_code)]
136 temp_path:PathBuf,
137
138 downloaded_bytes:u64,
140
141 #[allow(dead_code)]
143 total_bytes:u64,
144
145 complete:bool,
147
148 cancelled:bool,
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize)]
154struct RollbackHistory {
155 versions:Vec<RollbackState>,
157
158 max_versions:usize,
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct RollbackState {
164 version:String,
165 backup_path:PathBuf,
166 timestamp:chrono::DateTime<chrono::Utc>,
167 checksum:String,
168}
169
170#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
172pub enum UpdateChannel {
173 Stable,
174 Insiders,
175 Preview,
176}
177
178impl UpdateChannel {
179 fn as_str(&self) -> &'static str {
180 match self {
181 UpdateChannel::Stable => "stable",
182 UpdateChannel::Insiders => "insiders",
183 UpdateChannel::Preview => "preview",
184 }
185 }
186}
187
188#[derive(Debug, Clone)]
190struct PlatformConfig {
191 platform:String,
192 arch:String,
193 package_format:PackageFormat,
194}
195
196#[derive(Debug, Clone, Copy)]
198#[allow(dead_code)]
199enum PackageFormat {
200 WindowsExe,
201 MacOsDmg,
202 LinuxAppImage,
203 LinuxDeb,
204 LinuxRpm,
205}
206
207#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct UpdateStatus {
210 pub last_check:Option<chrono::DateTime<chrono::Utc>>,
212
213 pub update_available:bool,
215
216 pub current_version:String,
218
219 pub available_version:Option<String>,
221
222 pub download_progress:Option<f32>,
224
225 pub installation_status:InstallationStatus,
227
228 pub update_channel:UpdateChannel,
230
231 pub update_size:Option<u64>,
233
234 pub release_notes:Option<String>,
236
237 pub requires_restart:bool,
239
240 pub download_speed:Option<f64>,
242
243 pub eta_seconds:Option<u64>,
245
246 pub last_error:Option<String>,
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
252pub enum InstallationStatus {
253 NotStarted,
255
256 CheckingPrerequisites,
258
259 Downloading,
261
262 Paused,
264
265 VerifyingSignature,
267
268 VerifyingChecksums,
270
271 Staging,
273
274 CreatingBackup,
276
277 Installing,
279
280 Completed,
282
283 RollingBack,
285
286 Failed(String),
288}
289
290impl InstallationStatus {
291 pub fn is_cancellable(&self) -> bool {
293 matches!(
294 self,
295 InstallationStatus::Downloading
296 | InstallationStatus::Paused
297 | InstallationStatus::Staging
298 | InstallationStatus::NotStarted
299 )
300 }
301
302 pub fn is_error(&self) -> bool { matches!(self, InstallationStatus::Failed(_)) }
304
305 pub fn is_in_progress(&self) -> bool {
307 matches!(
308 self,
309 InstallationStatus::CheckingPrerequisites
310 | InstallationStatus::Downloading
311 | InstallationStatus::VerifyingSignature
312 | InstallationStatus::VerifyingChecksums
313 | InstallationStatus::Staging
314 | InstallationStatus::CreatingBackup
315 | InstallationStatus::Installing
316 )
317 }
318}
319
320#[derive(Debug, Clone, Serialize, Deserialize)]
322pub struct UpdateInfo {
323 pub version:String,
325
326 pub download_url:String,
328
329 pub release_notes:String,
331
332 pub checksum:String,
334
335 pub checksums:HashMap<String, String>,
337
338 pub size:u64,
340
341 pub published_at:chrono::DateTime<chrono::Utc>,
343
344 pub is_mandatory:bool,
346
347 pub requires_restart:bool,
349
350 pub min_compatible_version:Option<String>,
352
353 pub delta_url:Option<String>,
355
356 pub delta_checksum:Option<String>,
358
359 pub delta_size:Option<u64>,
361
362 pub signature:Option<String>,
364
365 pub platform_metadata:Option<PlatformMetadata>,
367}
368
369#[derive(Debug, Clone, Serialize, Deserialize)]
371pub struct PlatformMetadata {
372 pub package_format:String,
374
375 pub install_instructions:Vec<String>,
377
378 pub required_disk_space:u64,
380
381 pub requires_admin:bool,
383
384 pub additional_params:HashMap<String, serde_json::Value>,
386}
387
388#[derive(Debug, Clone, Serialize, Deserialize)]
390pub struct UpdateTelemetry {
391 pub event_id:String,
393
394 pub current_version:String,
396
397 pub target_version:String,
399
400 pub channel:String,
402
403 pub platform:String,
405
406 pub operation:String,
408
409 pub success:bool,
411
412 pub duration_ms:u64,
414
415 pub download_size:Option<u64>,
417
418 pub error_message:Option<String>,
420
421 pub timestamp:chrono::DateTime<chrono::Utc>,
423}
424
425impl UpdateManager {
426 pub async fn new(AppState:Arc<ApplicationState>) -> Result<Self> {
428 let config = &AppState.Configuration.Updates;
429
430 let cache_directory = ConfigurationManager::ExpandPath(&AppState.Configuration.Downloader.CacheDirectory)?;
432
433 tokio::fs::create_dir_all(&cache_directory)
435 .await
436 .map_err(|e| AirError::Configuration(format!("Failed to create cache directory: {}", e)))?;
437
438 let staging_directory = cache_directory.join("staging");
440 tokio::fs::create_dir_all(&staging_directory)
441 .await
442 .map_err(|e| AirError::Configuration(format!("Failed to create staging directory: {}", e)))?;
443
444 let backup_directory = cache_directory.join("backups");
446 tokio::fs::create_dir_all(&backup_directory)
447 .await
448 .map_err(|e| AirError::Configuration(format!("Failed to create backup directory: {}", e)))?;
449
450 let PlatformConfig = Self::detect_platform();
452 let PlatformConfigClone = PlatformConfig.clone();
453
454 let update_channel = if config.Channel == "insiders" {
456 UpdateChannel::Insiders
457 } else if config.Channel == "preview" {
458 UpdateChannel::Preview
459 } else {
460 UpdateChannel::Stable
461 };
462
463 let rollback_history_path = backup_directory.join("rollback_history.json");
465 let rollback_history = if rollback_history_path.exists() {
466 let content = tokio::fs::read_to_string(&rollback_history_path)
467 .await
468 .map_err(|e| AirError::FileSystem(format!("Failed to read rollback history: {}", e)))?;
469 serde_json::from_str(&content).unwrap_or_else(|_| RollbackHistory { versions:Vec::new(), max_versions:5 })
470 } else {
471 RollbackHistory { versions:Vec::new(), max_versions:5 }
472 };
473
474 let manager = Self {
475 AppState,
476 update_status:Arc::new(RwLock::new(UpdateStatus {
477 last_check:None,
478 update_available:false,
479 current_version:env!("CARGO_PKG_VERSION").to_string(),
480 available_version:None,
481 download_progress:None,
482 installation_status:InstallationStatus::NotStarted,
483 update_channel,
484 update_size:None,
485 release_notes:None,
486 requires_restart:true,
487 download_speed:None,
488 eta_seconds:None,
489 last_error:None,
490 })),
491 cache_directory,
492 staging_directory,
493 backup_directory,
494 download_sessions:Arc::new(RwLock::new(HashMap::new())),
495 rollback_history:Arc::new(Mutex::new(rollback_history)),
496 update_channel,
497 platform_config:PlatformConfigClone,
498 background_task:Arc::new(Mutex::new(None)),
499 };
500
501 manager
503 .AppState
504 .UpdateServiceStatus("updates", crate::ApplicationState::ServiceStatus::Running)
505 .await
506 .map_err(|e| AirError::Internal(e.to_string()))?;
507
508 dev_log!(
509 "update",
510 "[UpdateManager] Update service initialized for platform: {}/{}",
511 PlatformConfig.platform,
512 PlatformConfig.arch
513 );
514
515 Ok(manager)
516 }
517
518 fn detect_platform() -> PlatformConfig {
520 let platform = if cfg!(target_os = "windows") {
521 "windows"
522 } else if cfg!(target_os = "macos") {
523 "macos"
524 } else if cfg!(target_os = "linux") {
525 "linux"
526 } else {
527 "unknown"
528 };
529
530 let arch = if cfg!(target_arch = "x86_64") {
531 "x64"
532 } else if cfg!(target_arch = "aarch64") {
533 "arm64"
534 } else if cfg!(target_arch = "x86") {
535 "ia32"
536 } else {
537 "unknown"
538 };
539
540 let package_format = match (platform, arch) {
541 ("windows", _) => PackageFormat::WindowsExe,
542 ("macos", _) => PackageFormat::MacOsDmg,
543 ("linux", "x64") => PackageFormat::LinuxAppImage,
544 ("linux", "") => PackageFormat::LinuxAppImage,
545 _ => PackageFormat::LinuxAppImage,
546 };
547
548 PlatformConfig { platform:platform.to_string(), arch:arch.to_string(), package_format }
549 }
550
551 pub async fn CheckForUpdates(&self) -> Result<Option<UpdateInfo>> {
561 let config = &self.AppState.Configuration.Updates;
562 let start_time = std::time::Instant::now();
563
564 if !config.Enabled {
565 dev_log!("update", "[UpdateManager] Updates are disabled");
566 return Ok(None);
567 }
568
569 dev_log!(
570 "update",
571 "[UpdateManager] Checking for updates on {} channel",
572 self.update_channel.as_str()
573 );
574
575 {
577 let mut status = self.update_status.write().await;
578 status.last_check = Some(chrono::Utc::now());
579 status.last_error = None;
580 }
581
582 let update_info = match self.FetchUpdateInfo().await {
584 Ok(info) => info,
585 Err(e) => {
586 dev_log!("update", "error: [UpdateManager] Failed to fetch update info: {}", e);
587 let mut status = self.update_status.write().await;
588 status.last_error = Some(e.to_string());
589 self.record_telemetry(
590 "check",
591 false,
592 start_time.elapsed().as_millis() as u64,
593 None,
594 Some(e.to_string()),
595 )
596 .await;
597 return Err(e);
598 },
599 };
600
601 if let Some(ref info) = update_info {
602 if let Some(ref min_version) = info.min_compatible_version {
604 let current_version = env!("CARGO_PKG_VERSION");
605 if UpdateManager::CompareVersions(current_version, min_version) < 0 {
606 dev_log!(
607 "update",
608 "warn: [UpdateManager] Update requires minimum version {} but current is {}. Skipping.",
609 min_version,
610 current_version
611 );
612 let mut status = self.update_status.write().await;
613 status.last_error = Some(format!("Update requires minimum version {}", min_version));
614 return Ok(None);
615 }
616 }
617
618 dev_log!(
619 "update",
620 "[UpdateManager] Update available: {} ({})",
621 info.version,
622 self.format_size(info.size as f64)
623 );
624
625 {
627 let mut status = self.update_status.write().await;
628 status.update_available = true;
629 status.available_version = Some(info.version.clone());
630 status.update_size = Some(info.size);
631 status.release_notes = Some(info.release_notes.clone());
632 status.requires_restart = info.requires_restart;
633 }
634
635 dev_log!("update", "[UpdateManager] Notifying frontend about available update");
638 self.record_telemetry("check", true, start_time.elapsed().as_millis() as u64, None, None)
640 .await;
641
642 if config.AutoDownload {
644 if let Err(e) = self.DownloadUpdate(info).await {
645 dev_log!("update", "error: [UpdateManager] Auto-download failed: {}", e); }
647 }
648 } else {
649 dev_log!("update", "[UpdateManager] No updates available");
650 {
652 let mut status = self.update_status.write().await;
653 status.update_available = false;
654 status.available_version = None;
655 status.update_size = None;
656 status.release_notes = None;
657 }
658
659 self.record_telemetry("check", true, start_time.elapsed().as_millis() as u64, None, None)
661 .await;
662 }
663
664 Ok(update_info)
665 }
666
667 pub async fn DownloadUpdate(&self, update_info:&UpdateInfo) -> Result<()> {
683 let start_time = std::time::Instant::now();
684 let session_id = Uuid::new_v4().to_string();
685
686 dev_log!(
687 "update",
688 "[UpdateManager] Starting download for version {} (session: {})",
689 update_info.version,
690 session_id
691 );
692
693 let required_space = update_info.size * 2; self.ValidateDiskSpace(required_space).await?;
696
697 {
699 let mut status = self.update_status.write().await;
700 status.installation_status = InstallationStatus::CheckingPrerequisites;
701 status.last_error = None;
702 }
703
704 let temp_path = self.cache_directory.join(format!("update-{}-temp.bin", update_info.version));
706 let final_path = self.cache_directory.join(format!("update-{}.bin", update_info.version));
707
708 let (downloaded_bytes, resume_from_start) = if temp_path.exists() {
710 let metadata = tokio::fs::metadata(&temp_path)
711 .await
712 .map_err(|e| AirError::FileSystem(format!("Failed to check temp file: {}", e)))?;
713 dev_log!(
714 "update",
715 "[UpdateManager] Found partial download, resuming from {} bytes",
716 metadata.len()
717 );
718 (metadata.len(), false)
719 } else {
720 (0, true)
721 };
722
723 {
725 let mut sessions = self.download_sessions.write().await;
726 sessions.insert(
727 session_id.clone(),
728 DownloadSession {
729 session_id:session_id.clone(),
730 download_url:update_info.download_url.clone(),
731 temp_path:temp_path.clone(),
732 downloaded_bytes,
733 total_bytes:update_info.size,
734 complete:false,
735 cancelled:false,
736 },
737 );
738 }
739
740 let dns_port = Mist::dns_port();
742 let client = crate::HTTP::Client::secured_client_with_timeout(dns_port, Duration::from_secs(300))
743 .map_err(|e| AirError::Network(format!("Failed to create HTTP client: {}", e)))?;
744
745 let mut request_builder = client.get(&update_info.download_url);
746
747 if !resume_from_start {
749 request_builder = request_builder.header("Range", format!("bytes={}-", downloaded_bytes));
750 }
751
752 let response:reqwest::Response = request_builder
753 .send()
754 .await
755 .map_err(|e| AirError::Network(format!("Failed to start download: {}", e)))?;
756
757 if !response.status().is_success() && response.status() != 206 {
758 dev_log!(
759 "update",
760 "error: [UpdateManager] Download failed with status: {}",
761 response.status()
762 );
763 let mut status = self.update_status.write().await;
764 status.installation_status =
765 InstallationStatus::Failed(format!("Download failed with status: {}", response.status()));
766 status.last_error = Some(format!("Download failed with status: {}", response.status()));
767 self.record_telemetry(
768 "download",
769 false,
770 start_time.elapsed().as_millis() as u64,
771 None,
772 Some("Download failed".to_string()),
773 )
774 .await;
775 return Err(AirError::Network(format!("Download failed with status: {}", response.status())));
776 }
777
778 let total_size = response.content_length().unwrap_or(update_info.size);
779 let initial_downloaded = if resume_from_start { 0 } else { downloaded_bytes };
780
781 {
783 let mut status = self.update_status.write().await;
784 status.installation_status = InstallationStatus::Downloading;
785 status.download_progress = Some(((downloaded_bytes as f32 / total_size as f32) * 100.0).min(100.0));
786 }
787
788 let last_update = Arc::new(Mutex::new(std::time::Instant::now()));
790 let last_bytes = Arc::new(Mutex::new(downloaded_bytes));
791
792 let mut file = if resume_from_start {
794 tokio::fs::File::create(&temp_path)
795 .await
796 .map_err(|e| AirError::FileSystem(format!("Failed to create update file: {}", e)))?
797 } else {
798 tokio::fs::OpenOptions::new()
800 .append(true)
801 .open(&temp_path)
802 .await
803 .map_err(|e| AirError::FileSystem(format!("Failed to open update file for resume: {}", e)))?
804 };
805
806 use tokio::io::AsyncWriteExt;
807 use futures_util::StreamExt;
808
809 let mut byte_stream = response.bytes_stream();
810 let mut downloaded = initial_downloaded;
811
812 while let Some(chunk_result) = byte_stream.next().await {
813 match chunk_result {
814 Ok(chunk) => {
815 let chunk_bytes:&[u8] = &chunk;
816 file.write_all(chunk_bytes)
817 .await
818 .map_err(|e| AirError::FileSystem(format!("Failed to write update file: {}", e)))?;
819
820 downloaded += chunk.len() as u64;
821
822 {
824 let mut last_update_guard = last_update.lock().await;
825 let mut last_bytes_guard = last_bytes.lock().await;
826
827 if last_update_guard.elapsed() >= Duration::from_secs(1) {
828 let bytes_this_second = downloaded - *last_bytes_guard;
829 let download_speed = bytes_this_second as f64;
830
831 let progress = ((downloaded as f32 / total_size as f32) * 100.0).min(100.0);
832 let remaining_bytes = total_size - downloaded;
833 let eta_seconds = if download_speed > 0.0 {
834 Some(remaining_bytes as u64 / (download_speed as u64).max(1))
835 } else {
836 None
837 };
838
839 {
840 let mut status = self.update_status.write().await;
841 status.download_progress = Some(progress);
842 status.download_speed = Some(download_speed);
843 status.eta_seconds = eta_seconds;
844 }
845
846 dev_log!(
847 "update",
848 "[UpdateManager] Download progress: {:.1}% ({}/s, ETA: {:?})",
849 progress,
850 self.format_size(download_speed),
851 eta_seconds
852 );
853
854 *last_update_guard = std::time::Instant::now();
855 *last_bytes_guard = downloaded;
856 }
857 }
858
859 {
861 let mut sessions = self.download_sessions.write().await;
862 if let Some(session) = sessions.get_mut(&session_id) {
863 session.downloaded_bytes = downloaded;
864 }
865 }
866 },
867 Err(e) => {
868 dev_log!("update", "error: [UpdateManager] Download error: {}", e);
869 let mut status = self.update_status.write().await;
870 status.installation_status = InstallationStatus::Failed(format!("Network error: {}", e));
871 status.last_error = Some(format!("Network error: {}", e));
872 self.record_telemetry(
873 "download",
874 false,
875 start_time.elapsed().as_millis() as u64,
876 None,
877 Some(e.to_string()),
878 )
879 .await;
880 return Err(AirError::Network(format!("Download error: {}", e)));
881 },
882 }
883 }
884
885 {
887 let mut status = self.update_status.write().await;
888 status.installation_status = InstallationStatus::Downloading;
889 status.download_progress = Some(100.0);
890 }
891
892 dev_log!(
893 "update",
894 "[UpdateManager] Download completed: {} bytes in {:.2}s",
895 downloaded,
896 start_time.elapsed().as_secs_f64()
897 );
898
899 {
901 let mut status = self.update_status.write().await;
902 status.installation_status = InstallationStatus::VerifyingChecksums;
903 }
904
905 self.VerifyChecksum(&temp_path, &update_info.checksum).await?;
906
907 for (algorithm, expected_checksum) in &update_info.checksums {
909 self.VerifyChecksumWithAlgorithm(&temp_path, algorithm, expected_checksum)
910 .await?;
911 }
912
913 if let Some(ref signature) = update_info.signature {
915 {
916 let mut status = self.update_status.write().await;
917 status.installation_status = InstallationStatus::VerifyingSignature;
918 }
919 self.VerifySignature(&temp_path, signature).await?;
920 }
921
922 if temp_path.exists() {
924 tokio::fs::rename(&temp_path, &final_path)
925 .await
926 .map_err(|e| AirError::FileSystem(format!("Failed to finalize download: {}", e)))?;
927 }
928
929 {
931 let mut sessions = self.download_sessions.write().await;
932 if let Some(session) = sessions.get_mut(&session_id) {
933 session.complete = true;
934 }
935 }
936
937 {
939 let mut status = self.update_status.write().await;
940 status.installation_status = InstallationStatus::Completed;
941 status.download_progress = Some(100.0);
942 }
943
944 dev_log!(
945 "update",
946 "[UpdateManager] Update {} downloaded and verified successfully",
947 update_info.version
948 );
949
950 self.record_telemetry(
952 "download",
953 true,
954 start_time.elapsed().as_millis() as u64,
955 Some(downloaded),
956 None,
957 )
958 .await;
959
960 Ok(())
961 }
962
963 pub async fn ApplyUpdate(&self, update_info:&UpdateInfo) -> Result<()> {
978 let start_time = std::time::Instant::now();
979 let current_version = env!("CARGO_PKG_VERSION");
980
981 dev_log!(
982 "update",
983 "[UpdateManager] Applying update: {} (from {})",
984 update_info.version,
985 current_version
986 );
987
988 let file_path = self.cache_directory.join(format!("update-{}.bin", update_info.version));
989
990 if !file_path.exists() {
992 dev_log!("update", "error: [UpdateManager] Update file not found: {:?}", file_path);
993 return Err(AirError::FileSystem(
994 "Update file not found. Please download first.".to_string(),
995 ));
996 }
997
998 {
1000 let mut status = self.update_status.write().await;
1001 status.installation_status = InstallationStatus::VerifyingChecksums;
1002 status.last_error = None;
1003 }
1004
1005 self.VerifyChecksum(&file_path, &update_info.checksum).await?;
1007
1008 for (algorithm, expected_checksum) in &update_info.checksums {
1010 self.VerifyChecksumWithAlgorithm(&file_path, algorithm, expected_checksum)
1011 .await?;
1012 }
1013
1014 if let Some(ref signature) = update_info.signature {
1016 {
1017 let mut status = self.update_status.write().await;
1018 status.installation_status = InstallationStatus::VerifyingSignature;
1019 }
1020 self.VerifySignature(&file_path, signature).await?;
1021 }
1022
1023 {
1025 let mut status = self.update_status.write().await;
1026 status.installation_status = InstallationStatus::CreatingBackup;
1027 }
1028
1029 let backup_info = self.CreateBackup(current_version).await?;
1030 dev_log!("update", "[UpdateManager] Backup created: {:?}", backup_info.backup_path);
1031 {
1033 let mut status = self.update_status.write().await;
1034 status.installation_status = InstallationStatus::Installing;
1035 }
1036
1037 let result = match self.platform_config.package_format {
1039 #[cfg(target_os = "windows")]
1040 PackageFormat::WindowsExe => self.ApplyWindowsUpdate(&file_path).await,
1041 #[cfg(not(target_os = "windows"))]
1042 PackageFormat::WindowsExe => Err(AirError::Internal("Windows update not available on this platform".to_string())),
1043 PackageFormat::MacOsDmg => self.ApplyMacOsUpdate(&file_path).await,
1044 #[cfg(all(target_os = "linux", feature = "appimage"))]
1045 PackageFormat::LinuxAppImage => self.ApplyLinuxAppImageUpdate(&file_path).await,
1046 #[cfg(not(all(target_os = "linux", feature = "appimage")))]
1047 PackageFormat::LinuxAppImage => {
1048 Err(AirError::Internal(
1049 "Linux AppImage update not available on this platform".to_string(),
1050 ))
1051 },
1052 #[cfg(all(target_os = "linux", feature = "deb"))]
1053 PackageFormat::LinuxDeb => self.ApplyLinuxDebUpdate(&file_path).await,
1054 #[cfg(not(all(target_os = "linux", feature = "deb")))]
1055 PackageFormat::LinuxDeb => {
1056 Err(AirError::Internal(
1057 "Linux DEB update not available on this platform".to_string(),
1058 ))
1059 },
1060 #[cfg(all(target_os = "linux", feature = "rpm"))]
1061 PackageFormat::LinuxRpm => self.ApplyLinuxRpmUpdate(&file_path).await,
1062 #[cfg(not(all(target_os = "linux", feature = "rpm")))]
1063 PackageFormat::LinuxRpm => {
1064 Err(AirError::Internal(
1065 "Linux RPM update not available on this platform".to_string(),
1066 ))
1067 },
1068 };
1069
1070 if let Err(e) = result {
1071 dev_log!(
1072 "update",
1073 "error: [UpdateManager] Installation failed, initiating rollback: {}",
1074 e
1075 );
1076 {
1078 let mut status = self.update_status.write().await;
1079 status.installation_status = InstallationStatus::RollingBack;
1080 }
1081
1082 if let Err(rollback_err) = self.RollbackToBackup(&backup_info).await {
1084 dev_log!("update", "error: [UpdateManager] Rollback also failed: {}", rollback_err);
1085 let mut status = self.update_status.write().await;
1087 status.installation_status = InstallationStatus::Failed(format!(
1088 "Installation failed and rollback failed: {} / {}",
1089 e, rollback_err
1090 ));
1091 status.last_error = Some(format!("Installation failed and rollback failed"));
1092
1093 self.record_telemetry(
1094 "install",
1095 false,
1096 start_time.elapsed().as_millis() as u64,
1097 None,
1098 Some(format!("Update and rollback failed: {}", rollback_err)),
1099 )
1100 .await;
1101
1102 return Err(AirError::Internal(format!(
1103 "Installation failed and rollback failed: {} / {}",
1104 e, rollback_err
1105 )));
1106 } else {
1107 dev_log!("update", "[UpdateManager] Rollback successful");
1108 let mut status = self.update_status.write().await;
1109 status.installation_status =
1110 InstallationStatus::Failed(format!("Installation failed, rollback successful: {}", e));
1111 status.last_error = Some(e.to_string());
1112
1113 self.record_telemetry(
1114 "install",
1115 false,
1116 start_time.elapsed().as_millis() as u64,
1117 None,
1118 Some(e.to_string()),
1119 )
1120 .await;
1121
1122 return Err(AirError::Internal(format!("Installation failed, rollback successful: {}", e)));
1123 }
1124 }
1125
1126 {
1128 let mut history = self.rollback_history.lock().await;
1129 history.versions.insert(0, backup_info);
1130
1131 while history.versions.len() > history.max_versions {
1133 if let Some(old_backup) = history.versions.pop() {
1134 let _ = tokio::fs::remove_dir_all(&old_backup.backup_path).await;
1136 }
1137 }
1138 }
1139
1140 let history_path = self.backup_directory.join("rollback_history.json");
1142 let history = self.rollback_history.lock().await;
1143 let history_json = serde_json::to_string(&*history)
1144 .map_err(|e| AirError::Internal(format!("Failed to serialize rollback history: {}", e)))?;
1145 drop(history);
1146 tokio::fs::write(&history_path, history_json)
1147 .await
1148 .map_err(|e| AirError::FileSystem(format!("Failed to save rollback history: {}", e)))?;
1149
1150 {
1152 let mut status = self.update_status.write().await;
1153 status.current_version = update_info.version.clone();
1154 status.installation_status = InstallationStatus::Completed;
1155 }
1156
1157 dev_log!(
1158 "update",
1159 "[UpdateManager] Update {} applied successfully in {:.2}s",
1160 update_info.version,
1161 start_time.elapsed().as_secs_f64()
1162 );
1163
1164 self.record_telemetry(
1166 "install",
1167 true,
1168 start_time.elapsed().as_millis() as u64,
1169 Some(update_info.size),
1170 None,
1171 )
1172 .await;
1173
1174 Ok(())
1175 }
1176
1177 async fn FetchUpdateInfo(&self) -> Result<Option<UpdateInfo>> {
1188 let config = &self.AppState.Configuration.Updates;
1189
1190 let retry_policy = crate::Resilience::RetryPolicy {
1192 MaxRetries:3,
1193 InitialIntervalMs:1000,
1194 MaxIntervalMs:16000,
1195 BackoffMultiplier:2.0,
1196 JitterFactor:0.1,
1197 BudgetPerMinute:50,
1198 ErrorClassification:std::collections::HashMap::new(),
1199 };
1200
1201 let _retry_manager = crate::Resilience::RetryManager::new(retry_policy.clone());
1202 let circuit_breaker = crate::Resilience::CircuitBreaker::new(
1203 "updates".to_string(),
1204 crate::Resilience::CircuitBreakerConfig::default(),
1205 );
1206
1207 let current_version = env!("CARGO_PKG_VERSION");
1208 let mut attempt = 0;
1209
1210 loop {
1211 if circuit_breaker.GetState().await == crate::Resilience::CircuitState::Open {
1213 if !circuit_breaker.AttemptRecovery().await {
1214 dev_log!("update", "warn: [UpdateManager] Circuit breaker is open, skipping update check");
1215 return Ok(None);
1216 }
1217 }
1218
1219 let update_url = format!(
1221 "{}/check?version={}&platform={}&arch={}&channel={}",
1222 config.UpdateServerUrl,
1223 current_version,
1224 self.platform_config.platform,
1225 self.platform_config.arch,
1226 self.update_channel.as_str()
1227 );
1228
1229 let dns_port = Mist::dns_port();
1230 let client = crate::HTTP::Client::secured_client_with_timeout(dns_port, Duration::from_secs(30))
1231 .map_err(|e| AirError::Network(format!("Failed to create HTTP client: {}", e)))?;
1232
1233 match client.get(&update_url).send().await {
1234 Ok(response) => {
1235 let status:reqwest::StatusCode = response.status();
1236 match status {
1237 reqwest::StatusCode::NO_CONTENT => {
1238 circuit_breaker.RecordSuccess().await;
1240 dev_log!("update", "[UpdateManager] Server reports no updates available");
1241 return Ok(None);
1242 },
1243 status if status.is_success() => {
1244 match response.json::<UpdateInfo>().await {
1246 Ok(update_info) => {
1247 circuit_breaker.RecordSuccess().await;
1248
1249 if UpdateManager::CompareVersions(current_version, &update_info.version) < 0 {
1251 dev_log!(
1252 "update",
1253 "[UpdateManager] Update available: {} -> {}",
1254 current_version,
1255 update_info.version
1256 );
1257 return Ok(Some(update_info));
1258 } else {
1259 dev_log!(
1260 "update",
1261 "[UpdateManager] Server returned same or older version: {}",
1262 update_info.version
1263 );
1264 return Ok(None);
1265 }
1266 },
1267 Err(e) => {
1268 circuit_breaker.RecordFailure().await;
1269 dev_log!("update", "error: [UpdateManager] Failed to parse update info: {}", e);
1270 if attempt < retry_policy.MaxRetries {
1271 attempt += 1;
1272 let delay = Duration::from_millis(
1273 retry_policy.InitialIntervalMs * 2_u32.pow(attempt as u32) as u64,
1274 );
1275 sleep(delay).await;
1276 continue;
1277 } else {
1278 return Err(AirError::Network(format!(
1279 "Failed to parse update info after retries: {}",
1280 e
1281 )));
1282 }
1283 },
1284 }
1285 },
1286 status => {
1287 circuit_breaker.RecordFailure().await;
1288 dev_log!("update", "warn: [UpdateManager] Update server returned status: {}", status);
1289 if attempt < retry_policy.MaxRetries {
1290 attempt += 1;
1291 let delay = Duration::from_millis(
1292 retry_policy.InitialIntervalMs * 2_u32.pow(attempt as u32) as u64,
1293 );
1294 sleep(delay).await;
1295 continue;
1296 } else {
1297 return Ok(None);
1298 }
1299 },
1300 }
1301 },
1302 Err(e) => {
1303 circuit_breaker.RecordFailure().await;
1304 dev_log!("update", "warn: [UpdateManager] Failed to check for updates: {}", e);
1305 if attempt < retry_policy.MaxRetries {
1306 attempt += 1;
1307 let delay =
1308 Duration::from_millis(retry_policy.InitialIntervalMs * 2_u32.pow(attempt as u32) as u64);
1309 sleep(delay).await;
1310 continue;
1311 } else {
1312 return Ok(None);
1313 }
1314 },
1315 }
1316 }
1317 }
1318
1319 async fn VerifyChecksum(&self, file_path:&Path, expected_checksum:&str) -> Result<()> {
1333 let content = tokio::fs::read(file_path)
1334 .await
1335 .map_err(|e| AirError::FileSystem(format!("Failed to read update file for checksum: {}", e)))?;
1336
1337 let actual_checksum = self.CalculateSha256(&content);
1338
1339 if actual_checksum.to_lowercase() != expected_checksum.to_lowercase() {
1340 dev_log!(
1341 "update",
1342 "error: [UpdateManager] Checksum verification failed: expected {}, got {}",
1343 expected_checksum,
1344 actual_checksum
1345 );
1346 return Err(AirError::Network("Update checksum verification failed".to_string()));
1347 }
1348
1349 dev_log!("update", "[UpdateManager] Checksum verified: {}", actual_checksum);
1350 Ok(())
1351 }
1352
1353 async fn VerifyChecksumWithAlgorithm(&self, file_path:&Path, algorithm:&str, expected_checksum:&str) -> Result<()> {
1366 let content = tokio::fs::read(file_path).await.map_err(|e| {
1367 AirError::FileSystem(format!("Failed to read update file for {} checksum: {}", algorithm, e))
1368 })?;
1369
1370 let actual_checksum = match algorithm.to_lowercase().as_str() {
1371 "sha256" => self.CalculateSha256(&content),
1372 "sha512" => self.CalculateSha512(&content),
1373 "md5" => self.CalculateMd5(&content),
1374 "crc32" => self.CalculateCrc32(&content),
1375 _ => {
1376 dev_log!(
1377 "update",
1378 "warn: [UpdateManager] Unknown checksum algorithm: {}, skipping",
1379 algorithm
1380 );
1381 return Ok(());
1382 },
1383 };
1384
1385 if actual_checksum.to_lowercase() != expected_checksum.to_lowercase() {
1386 dev_log!(
1387 "update",
1388 "error: [UpdateManager] {} checksum verification failed: expected {}, got {}",
1389 algorithm,
1390 expected_checksum,
1391 actual_checksum
1392 );
1393 return Err(AirError::Network(format!("{} checksum verification failed", algorithm)));
1394 }
1395
1396 dev_log!("update", "[UpdateManager] {} checksum verified: {}", algorithm, actual_checksum);
1397 Ok(())
1398 }
1399
1400 async fn VerifySignature(&self, _file_path:&Path, _signature:&str) -> Result<()> {
1414 #[cfg(debug_assertions)]
1423 {
1424 dev_log!("update", "[UpdateManager] Development build: skipping signature verification");
1425 return Ok(());
1426 }
1427
1428 #[cfg(not(debug_assertions))]
1431 {
1432 dev_log!(
1433 "update",
1434 "warn: [UpdateManager] WARNING: Cryptographic signature verification is not yet implemented"
1435 );
1436 dev_log!(
1437 "update",
1438 "warn: [UpdateManager] Update packages should be cryptographically signed in production"
1439 );
1440 dev_log!(
1441 "update",
1442 "[UpdateManager] Proceeding with update without signature verification"
1443 );
1444 return Ok(());
1445 }
1446 }
1447
1448 async fn CreateBackup(&self, version:&str) -> Result<RollbackState> {
1461 let timestamp = chrono::Utc::now();
1462 let backup_dir_name = format!("backup-{}-{}", version, timestamp.format("%Y%m%d_%H%M%S"));
1463 let backup_path = self.backup_directory.join(&backup_dir_name);
1464
1465 dev_log!("update", "[UpdateManager] Creating backup: {}", backup_dir_name);
1466 tokio::fs::create_dir_all(&backup_path)
1468 .await
1469 .map_err(|e| AirError::FileSystem(format!("Failed to create backup directory: {}", e)))?;
1470
1471 let exe_path = std::env::current_exe()
1473 .map_err(|e| AirError::FileSystem(format!("Failed to get executable path: {}", e)))?;
1474
1475 let backup_exe = backup_path.join(exe_path.file_name().unwrap_or_default());
1477 tokio::fs::copy(&exe_path, &backup_exe)
1478 .await
1479 .map_err(|e| AirError::FileSystem(format!("Failed to backup executable: {}", e)))?;
1480
1481 let config_dirs = vec![
1484 dirs::config_dir().unwrap_or_default().join("Land"),
1485 dirs::home_dir().unwrap_or_default().join(".config/land"),
1486 ];
1487
1488 for config_dir in config_dirs {
1489 if config_dir.exists() {
1490 let backup_config = backup_path.join("config");
1491 let _ = tokio::fs::create_dir_all(&backup_config).await;
1492 let _ = Self::copy_directory_recursive(&config_dir, &backup_config).await;
1493 dev_log!("update", "[UpdateManager] Backed up config directory: {:?}", config_dir);
1494 }
1495 }
1496
1497 let data_dirs = vec![
1499 dirs::data_local_dir().unwrap_or_default().join("Land"),
1500 dirs::home_dir().unwrap_or_default().join(".local/share/land"),
1501 ];
1502
1503 for data_dir in data_dirs {
1504 if data_dir.exists() {
1505 let backup_data = backup_path.join("data");
1506 let _ = tokio::fs::create_dir_all(&backup_data).await;
1507 let _ = Self::copy_directory_recursive(&data_dir, &backup_data).await;
1508 dev_log!("update", "[UpdateManager] Backed up data directory: {:?}", data_dir);
1509 }
1510 }
1511
1512 let checksum = self.CalculateFileChecksum(&backup_path).await?;
1514
1515 dev_log!("update", "[UpdateManager] Backup created at: {:?}", backup_path);
1516 Ok(RollbackState { version:version.to_string(), backup_path, timestamp, checksum })
1517 }
1518
1519 pub async fn RollbackToBackup(&self, backup_info:&RollbackState) -> Result<()> {
1532 dev_log!(
1533 "update",
1534 "[UpdateManager] Rolling back to version: {} from: {:?}",
1535 backup_info.version,
1536 backup_info.backup_path
1537 );
1538
1539 let current_checksum = self.CalculateFileChecksum(&backup_info.backup_path).await?;
1541 if current_checksum != backup_info.checksum {
1542 return Err(AirError::Internal(format!(
1543 "Backup integrity check failed: expected {}, got {}",
1544 backup_info.checksum, current_checksum
1545 )));
1546 }
1547
1548 let exe_path = std::env::current_exe()
1550 .map_err(|e| AirError::FileSystem(format!("Failed to get executable path: {}", e)))?;
1551
1552 let backup_exe = backup_info.backup_path.join(exe_path.file_name().unwrap_or_default());
1553
1554 if !backup_exe.exists() {
1555 return Err(AirError::FileSystem("Backup executable not found".to_string()));
1556 }
1557
1558 match tokio::fs::copy(&backup_exe, &exe_path).await {
1562 Ok(_) => {
1563 dev_log!("update", "[UpdateManager] Executable restored from backup");
1564 },
1565 Err(e) => {
1566 dev_log!("update", "error: [UpdateManager] Failed to restore executable: {}", e);
1567 dev_log!("update", "warn: [UpdateManager] Rollback may require manual intervention");
1568 },
1569 }
1570
1571 let backup_config = backup_info.backup_path.join("config");
1573 if backup_config.exists() {
1574 let config_dirs = vec![
1575 dirs::config_dir().unwrap_or_default().join("Land"),
1576 dirs::home_dir().unwrap_or_default().join(".config/land"),
1577 ];
1578 for config_dir in config_dirs {
1579 if config_dir.exists() {
1581 let _ = tokio::fs::remove_dir_all(&config_dir).await;
1582 }
1583 let _ = Self::copy_directory_recursive(&backup_config, &config_dir).await;
1584 dev_log!("update", "[UpdateManager] Restored config directory: {:?}", config_dir);
1585 }
1586 }
1587
1588 let backup_data = backup_info.backup_path.join("data");
1590 if backup_data.exists() {
1591 let data_dirs = vec![
1592 dirs::data_local_dir().unwrap_or_default().join("Land"),
1593 dirs::home_dir().unwrap_or_default().join(".local/share/land"),
1594 ];
1595 for data_dir in data_dirs {
1596 if data_dir.exists() {
1598 let _ = tokio::fs::remove_dir_all(&data_dir).await;
1599 }
1600 let _ = Self::copy_directory_recursive(&backup_data, &data_dir).await;
1601 dev_log!("update", "[UpdateManager] Restored data directory: {:?}", data_dir);
1602 }
1603 }
1604
1605 dev_log!(
1606 "update",
1607 "[UpdateManager] Rollback to version {} completed",
1608 backup_info.version
1609 );
1610 Ok(())
1611 }
1612
1613 pub async fn RollbackToVersion(&self, version:&str) -> Result<()> {
1625 let history = self.rollback_history.lock().await;
1626
1627 let backup_info = history
1628 .versions
1629 .iter()
1630 .find(|state| state.version == version)
1631 .ok_or_else(|| AirError::FileSystem(format!("No backup found for version {}", version)))?;
1632
1633 let info = backup_info.clone();
1634 drop(history);
1635
1636 self.RollbackToBackup(&info).await
1637 }
1638
1639 pub async fn GetAvailableRollbackVersions(&self) -> Vec<String> {
1643 let history = self.rollback_history.lock().await;
1644 history.versions.iter().map(|state| state.version.clone()).collect()
1645 }
1646
1647 async fn ValidateDiskSpace(&self, required_bytes:u64) -> Result<()> {
1657 let metadata = tokio::fs::metadata(&self.cache_directory)
1659 .await
1660 .map_err(|e| AirError::FileSystem(format!("Failed to get cache directory info: {}", e)))?;
1661
1662 if cfg!(target_os = "windows") {
1663 #[cfg(target_os = "windows")]
1665 {
1666 use std::os::windows::fs::MetadataExt;
1667 let free_space = metadata.volume_serial_number() as u64; dev_log!(
1669 "update",
1670 "warn: [UpdateManager] Disk space validation not fully implemented on Windows"
1671 );
1672 }
1673 } else {
1674 #[cfg(not(target_os = "windows"))]
1676 {
1677 use std::os::unix::fs::MetadataExt;
1678 let _device_id = metadata.dev();
1679
1680 let cache_path = self.cache_directory.to_string_lossy();
1682 let free_space = unsafe {
1683 let mut stat:libc::statvfs = std::mem::zeroed();
1684 if libc::statvfs(cache_path.as_ptr() as *const i8, &mut stat) == 0 {
1685 stat.f_bavail as u64 * stat.f_bsize as u64
1686 } else {
1687 u64::MAX }
1689 };
1690
1691 if free_space < required_bytes {
1692 return Err(AirError::Configuration(format!(
1693 "Insufficient disk space: required {} bytes, available {} bytes",
1694 required_bytes, free_space
1695 )));
1696 }
1697
1698 dev_log!(
1699 "update",
1700 "[UpdateManager] Disk space check passed: {} bytes available, {} bytes required",
1701 free_space,
1702 required_bytes
1703 );
1704 }
1705 }
1706
1707 dev_log!(
1708 "update",
1709 "[UpdateManager] Disk space validation passed for required {} bytes",
1710 self.format_size(required_bytes as f64)
1711 );
1712
1713 Ok(())
1714 }
1715
1716 pub async fn verify_update(&self, file_path:&str, update_info:Option<&UpdateInfo>) -> Result<bool> {
1730 let path = PathBuf::from(file_path);
1731
1732 if !path.exists() {
1733 return Ok(false);
1734 }
1735
1736 let metadata = tokio::fs::metadata(&path)
1737 .await
1738 .map_err(|e| AirError::FileSystem(format!("Failed to read update file metadata: {}", e)))?;
1739
1740 if metadata.len() == 0 {
1741 return Ok(false);
1742 }
1743
1744 if let Some(info) = update_info {
1746 if !info.checksum.is_empty() {
1747 let actual_checksum = self.CalculateFileChecksum(&path).await?;
1748 if actual_checksum != info.checksum {
1749 return Err(AirError::Configuration(format!(
1750 "Checksum verification failed: expected {}, got {}",
1751 info.checksum, actual_checksum
1752 )));
1753 }
1754 }
1755
1756 for (algorithm, expected_checksum) in &info.checksums {
1758 self.VerifyChecksumWithAlgorithm(&path, algorithm, expected_checksum).await?;
1759 }
1760
1761 if let Some(expected_size) = Some(info.size) {
1763 if metadata.len() != expected_size {
1764 return Err(AirError::Configuration(format!(
1765 "File size mismatch: expected {}, got {}",
1766 expected_size,
1767 metadata.len()
1768 )));
1769 }
1770 }
1771 }
1772
1773 Ok(true)
1774 }
1775
1776 #[cfg(target_os = "windows")]
1778 async fn ApplyWindowsUpdate(&self, file_path:&Path) -> Result<()> {
1779 dev_log!("update", "[UpdateManager] Installing Windows update: {:?}", file_path);
1780 dev_log!(
1789 "update",
1790 "warn: [UpdateManager] Windows installation: update package ready at {:?}",
1791 file_path
1792 );
1793 dev_log!("update", "[UpdateManager] Manual installation may be required");
1794 Ok(())
1795 }
1796
1797 #[cfg(target_os = "macos")]
1799 async fn ApplyMacOsUpdate(&self, file_path:&Path) -> Result<()> {
1800 dev_log!("update", "[UpdateManager] Installing macOS update: {:?}", file_path);
1801 dev_log!(
1811 "update",
1812 "warn: [UpdateManager] macOS installation: update package ready at {:?}",
1813 file_path
1814 );
1815 dev_log!("update", "[UpdateManager] Manual installation may be required");
1816 Ok(())
1817 }
1818
1819 #[cfg(all(target_os = "linux", feature = "appimage"))]
1821 async fn ApplyLinuxAppImageUpdate(&self, file_path:&Path) -> Result<()> {
1822 dev_log!("update", "[UpdateManager] Installing Linux AppImage update: {:?}", file_path);
1823 dev_log!(
1831 "update",
1832 "warn: [UpdateManager] Linux AppImage installation: update package ready at {:?}",
1833 file_path
1834 );
1835 dev_log!("update", "[UpdateManager] Manual installation may be required");
1836 Ok(())
1837 }
1838
1839 #[cfg(all(target_os = "linux", feature = "deb"))]
1841 async fn ApplyLinuxDebUpdate(&self, file_path:&Path) -> Result<()> {
1842 dev_log!("update", "[UpdateManager] Installing Linux DEB update: {:?}", file_path);
1843 dev_log!(
1850 "update",
1851 "warn: [UpdateManager] Linux DEB installation: update package ready at {:?}",
1852 file_path
1853 );
1854 dev_log!("update", "[UpdateManager] Manual installation may be required");
1855 Ok(())
1856 }
1857
1858 #[cfg(all(target_os = "linux", feature = "rpm"))]
1860 async fn ApplyLinuxRpmUpdate(&self, file_path:&Path) -> Result<()> {
1861 dev_log!("update", "[UpdateManager] Installing Linux RPM update: {:?}", file_path);
1862 dev_log!(
1869 "update",
1870 "warn: [UpdateManager] Linux RPM installation: update package ready at {:?}",
1871 file_path
1872 );
1873 dev_log!("update", "[UpdateManager] Manual installation may be required");
1874 Ok(())
1875 }
1876
1877 async fn record_telemetry(
1891 &self,
1892 operation:&str,
1893 success:bool,
1894 duration_ms:u64,
1895 download_size:Option<u64>,
1896 error_message:Option<String>,
1897 ) {
1898 let telemetry = UpdateTelemetry {
1899 event_id:Uuid::new_v4().to_string(),
1900 current_version:env!("CARGO_PKG_VERSION").to_string(),
1901 target_version:self
1902 .update_status
1903 .read()
1904 .await
1905 .available_version
1906 .clone()
1907 .unwrap_or_else(|| "unknown".to_string()),
1908 channel:self.update_channel.as_str().to_string(),
1909 platform:format!("{}/{}", self.platform_config.platform, self.platform_config.arch),
1910 operation:operation.to_string(),
1911 success,
1912 duration_ms,
1913 download_size,
1914 error_message,
1915 timestamp:chrono::Utc::now(),
1916 };
1917
1918 dev_log!(
1919 "update",
1920 "[UpdateManager] Telemetry: {} {} in {}ms - size: {:?}, success: {}",
1921 operation,
1922 if success { "succeeded" } else { "failed" },
1923 duration_ms,
1924 download_size.map(|s| self.format_size(s as f64)),
1925 success
1926 );
1927
1928 #[cfg(debug_assertions)]
1931 {
1932 if let Ok(telemetry_json) = serde_json::to_string(&telemetry) {
1933 dev_log!("update", "[UpdateManager] Telemetry data: {}", telemetry_json); } else {
1937 dev_log!("update", "error: [UpdateManager] Failed to serialize telemetry");
1938 }
1939 }
1940
1941 #[cfg(not(debug_assertions))]
1943 {
1944 let _ = &telemetry; }
1948 }
1949
1950 fn CalculateSha256(&self, data:&[u8]) -> String {
1952 let mut hasher = Sha256::new();
1953 hasher.update(data);
1954 format!("{:x}", hasher.finalize())
1955 }
1956
1957 fn CalculateSha512(&self, data:&[u8]) -> String {
1959 use sha2::Sha512;
1960 let mut hasher = Sha512::new();
1961 hasher.update(data);
1962 format!("{:x}", hasher.finalize())
1963 }
1964
1965 fn CalculateMd5(&self, data:&[u8]) -> String {
1967 let digest = md5::compute(data);
1968 format!("{:x}", digest)
1969 }
1970
1971 fn CalculateCrc32(&self, data:&[u8]) -> String {
1973 let crc = crc32fast::hash(data);
1974 format!("{:08x}", crc)
1975 }
1976
1977 async fn CalculateFileChecksum(&self, path:&Path) -> Result<String> {
1979 let content = tokio::fs::read(path)
1980 .await
1981 .map_err(|e| AirError::FileSystem(format!("Failed to read file for checksum: {}", e)))?;
1982
1983 Ok(self.CalculateSha256(&content))
1984 }
1985
1986 pub fn CompareVersions(v1:&str, v2:&str) -> i32 {
2000 let v1_parts:Vec<u32> = v1.split('.').filter_map(|s| s.parse().ok()).collect();
2001 let v2_parts:Vec<u32> = v2.split('.').filter_map(|s| s.parse().ok()).collect();
2002
2003 for (i, part) in v1_parts.iter().enumerate() {
2004 if i >= v2_parts.len() {
2005 return 1;
2006 }
2007
2008 match part.cmp(&v2_parts[i]) {
2009 std::cmp::Ordering::Greater => return 1,
2010 std::cmp::Ordering::Less => return -1,
2011 std::cmp::Ordering::Equal => continue,
2012 }
2013 }
2014
2015 if v1_parts.len() < v2_parts.len() { -1 } else { 0 }
2016 }
2017
2018 pub async fn GetStatus(&self) -> UpdateStatus {
2022 let status = self.update_status.read().await;
2023 status.clone()
2024 }
2025
2026 pub async fn CancelDownload(&self) -> Result<()> {
2033 let status = self.update_status.write().await;
2034
2035 if status.installation_status != InstallationStatus::Downloading {
2036 return Err(AirError::Internal("No download in progress".to_string()));
2037 }
2038
2039 {
2041 let mut sessions = self.download_sessions.write().await;
2042 for session in sessions.values_mut() {
2043 session.cancelled = true;
2044 }
2045 }
2046
2047 let sessions = self.download_sessions.read().await;
2049 for session in sessions.values() {
2050 if session.temp_path.exists() {
2051 if let Err(e) = tokio::fs::remove_file(&session.temp_path).await {
2052 dev_log!("update", "warn: [UpdateManager] Failed to remove partial file: {}", e);
2053 }
2054 dev_log!("update", "[UpdateManager] Removed partial file: {:?}", session.temp_path);
2055 }
2056 }
2057 drop(sessions);
2058
2059 {
2061 let mut sessions = self.download_sessions.write().await;
2062 sessions.clear();
2063 }
2064
2065 dev_log!("update", "[UpdateManager] Download cancelled and cleaned up");
2066 Ok(())
2067 }
2068
2069 pub async fn ResumeDownload(&self, update_info:&UpdateInfo) -> Result<()> {
2078 let Status = self.update_status.write().await;
2079
2080 if Status.installation_status != InstallationStatus::Paused {
2081 return Err(AirError::Internal("No paused download to resume".to_string()));
2082 }
2083
2084 drop(Status);
2085
2086 dev_log!(
2087 "update",
2088 "[UpdateManager] Resuming download for version {}",
2089 update_info.version
2090 );
2091 self.DownloadUpdate(update_info).await
2092 }
2093
2094 pub async fn GetUpdateChannel(&self) -> UpdateChannel { self.update_channel }
2098
2099 pub async fn SetUpdateChannel(&mut self, channel:UpdateChannel) { self.update_channel = channel; }
2104
2105 async fn copy_directory_recursive(src:&Path, dst:&Path) -> Result<()> {
2117 let mut entries = tokio::fs::read_dir(src)
2118 .await
2119 .map_err(|e| AirError::FileSystem(format!("Failed to read directory {:?}: {}", src, e)))?;
2120
2121 tokio::fs::create_dir_all(dst)
2122 .await
2123 .map_err(|e| AirError::FileSystem(format!("Failed to create directory {:?}: {}", dst, e)))?;
2124
2125 while let Some(entry) = entries
2126 .next_entry()
2127 .await
2128 .map_err(|e| AirError::FileSystem(format!("Failed to read entry: {}", e)))?
2129 {
2130 let file_type = entry
2131 .file_type()
2132 .await
2133 .map_err(|e| AirError::FileSystem(format!("Failed to get file type: {}", e)))?;
2134 let src_path = entry.path();
2135 let dst_path = dst.join(entry.file_name());
2136
2137 if file_type.is_file() {
2138 tokio::fs::copy(&src_path, &dst_path)
2139 .await
2140 .map_err(|e| AirError::FileSystem(format!("Failed to copy file {:?}: {}", src_path, e)))?;
2141 } else if file_type.is_dir() {
2142 Box::pin(Self::copy_directory_recursive(&src_path, &dst_path)).await?;
2143 }
2144 }
2145
2146 Ok(())
2147 }
2148
2149 pub async fn StageUpdate(&self, update_info:&UpdateInfo) -> Result<()> {
2159 dev_log!("update", "[UpdateManager] Staging update for version {}", update_info.version);
2160 let mut status = self.update_status.write().await;
2161 status.installation_status = InstallationStatus::Staging;
2162 drop(status);
2163
2164 let file_path = self.cache_directory.join(format!("update-{}.bin", update_info.version));
2165
2166 if !file_path.exists() {
2167 return Err(AirError::FileSystem("Update file not found. Download first.".to_string()));
2168 }
2169
2170 let stage_dir = self.staging_directory.join(&update_info.version);
2172 tokio::fs::create_dir_all(&stage_dir)
2173 .await
2174 .map_err(|e| AirError::FileSystem(format!("Failed to create staging directory: {}", e)))?;
2175
2176 let staged_file = stage_dir.join("update.bin");
2178 tokio::fs::copy(&file_path, &staged_file)
2179 .await
2180 .map_err(|e| AirError::FileSystem(format!("Failed to stage update package: {}", e)))?;
2181
2182 self.VerifyChecksum(&staged_file, &update_info.checksum).await?;
2184
2185 dev_log!("update", "[UpdateManager] Update staged successfully in: {:?}", stage_dir);
2186 Ok(())
2187 }
2188
2189 pub async fn CleanupOldUpdates(&self) -> Result<()> {
2194 dev_log!("update", "[UpdateManager] Cleaning up old update files");
2195 let mut entries = tokio::fs::read_dir(&self.cache_directory)
2196 .await
2197 .map_err(|e| AirError::FileSystem(format!("Failed to read cache directory: {}", e)))?;
2198
2199 let mut cleaned_count = 0;
2200 let now = std::time::SystemTime::now();
2201
2202 while let Some(entry) = entries
2203 .next_entry()
2204 .await
2205 .map_err(|e| AirError::FileSystem(format!("Failed to read directory entry: {}", e)))?
2206 {
2207 let path = entry.path();
2208 let metadata = entry
2209 .metadata()
2210 .await
2211 .map_err(|e| AirError::FileSystem(format!("Failed to get metadata: {}", e)))?;
2212
2213 if path.is_dir()
2215 || metadata.modified().unwrap_or(now)
2216 > now.checked_sub(Duration::from_secs(7 * 24 * 3600)).unwrap_or(now)
2217 {
2218 continue;
2219 }
2220
2221 dev_log!("update", "[UpdateManager] Removing old update file: {:?}", path);
2222 tokio::fs::remove_file(&path)
2223 .await
2224 .map_err(|e| AirError::FileSystem(format!("Failed to remove {}: {}", path.display(), e)))?;
2225
2226 cleaned_count += 1;
2227 }
2228
2229 dev_log!("update", "[UpdateManager] Cleaned up {} old update files", cleaned_count);
2230 Ok(())
2231 }
2232
2233 pub fn GetCacheDirectory(&self) -> &PathBuf { &self.cache_directory }
2235
2236 pub async fn StartBackgroundTasks(&self) -> Result<()> {
2246 let manager = self.clone();
2247
2248 let handle = tokio::spawn(async move {
2249 manager.BackgroundTask().await;
2250 });
2251
2252 let mut task_handle = self.background_task.lock().await;
2254 *task_handle = Some(handle);
2255
2256 dev_log!("update", "[UpdateManager] Background update checking started");
2257 Ok(())
2258 }
2259
2260 async fn BackgroundTask(&self) {
2267 let config = &self.AppState.Configuration.Updates;
2268
2269 if !config.Enabled {
2270 dev_log!("update", "[UpdateManager] Background task: Updates are disabled");
2271 return;
2272 }
2273
2274 let check_interval = Duration::from_secs(config.CheckIntervalHours as u64 * 3600);
2275 let mut interval = interval(check_interval);
2276
2277 dev_log!(
2278 "update",
2279 "[UpdateManager] Background task: Checking for updates every {} hours",
2280 config.CheckIntervalHours
2281 );
2282
2283 loop {
2284 interval.tick().await;
2285
2286 dev_log!("update", "[UpdateManager] Background task: Checking for updates...");
2287 match self.CheckForUpdates().await {
2289 Ok(Some(update_info)) => {
2290 dev_log!(
2291 "update",
2292 "[UpdateManager] Background task: Update available: {}",
2293 update_info.version
2294 );
2295 },
2296 Ok(None) => {
2297 dev_log!("update", "[UpdateManager] Background task: No updates available");
2298 },
2299 Err(e) => {
2300 dev_log!("update", "error: [UpdateManager] Background task: Update check failed: {}", e);
2301 },
2302 }
2303 }
2304 }
2305
2306 pub async fn StopBackgroundTasks(&self) {
2312 dev_log!("update", "[UpdateManager] Stopping background tasks");
2313 let mut task_handle = self.background_task.lock().await;
2315 if let Some(handle) = task_handle.take() {
2316 handle.abort();
2317 dev_log!("update", "[UpdateManager] Background task aborted");
2318 } else {
2319 dev_log!("update", "[UpdateManager] No background task to stop");
2320 }
2321 }
2322
2323 fn format_size(&self, bytes:f64) -> String {
2331 const KB:f64 = 1024.0;
2332 const MB:f64 = KB * 1024.0;
2333 const GB:f64 = MB * 1024.0;
2334
2335 if bytes >= GB {
2336 format!("{:.2} GB/s", bytes / GB)
2337 } else if bytes >= MB {
2338 format!("{:.2} MB/s", bytes / MB)
2339 } else if bytes >= KB {
2340 format!("{:.2} KB/s", bytes / KB)
2341 } else {
2342 format!("{:.0} B/s", bytes)
2343 }
2344 }
2345}
2346
2347impl Clone for UpdateManager {
2348 fn clone(&self) -> Self {
2349 Self {
2350 AppState:self.AppState.clone(),
2351 update_status:self.update_status.clone(),
2352 cache_directory:self.cache_directory.clone(),
2353 staging_directory:self.staging_directory.clone(),
2354 backup_directory:self.backup_directory.clone(),
2355 download_sessions:self.download_sessions.clone(),
2356 rollback_history:self.rollback_history.clone(),
2357 update_channel:self.update_channel,
2358 platform_config:self.platform_config.clone(),
2359 background_task:self.background_task.clone(),
2360 }
2361 }
2362}