1pub mod ChecksumUtil;
73
74pub mod PlatformDetect;
75
76pub mod Types;
77
78pub mod VersionCompare;
79
80use std::{
81 collections::HashMap,
82 path::{Path, PathBuf},
83 sync::Arc,
84 time::Duration,
85};
86
87use serde::{Deserialize, Serialize};
88use tokio::{
89 sync::{Mutex, RwLock},
90 time::{interval, sleep},
91};
92use sha2::{Digest, Sha256};
93use uuid::Uuid;
94use md5;
95
96use crate::{AirError, ApplicationState::ApplicationState, Configuration::ConfigurationManager, Result, dev_log};
97
98pub struct UpdateManager {
100 AppState:Arc<ApplicationState>,
102
103 update_status:Arc<RwLock<UpdateStatus>>,
105
106 cache_directory:PathBuf,
108
109 staging_directory:PathBuf,
111
112 backup_directory:PathBuf,
114
115 download_sessions:Arc<RwLock<HashMap<String, DownloadSession>>>,
117
118 rollback_history:Arc<Mutex<RollbackHistory>>,
120
121 update_channel:UpdateChannel,
123
124 platform_config:PlatformConfig,
126
127 background_task:Arc<Mutex<Option<tokio::task::JoinHandle<()>>>>,
129}
130
131#[derive(Debug, Clone)]
133struct DownloadSession {
134 #[allow(dead_code)]
136 session_id:String,
137
138 #[allow(dead_code)]
140 download_url:String,
141
142 #[allow(dead_code)]
144 temp_path:PathBuf,
145
146 downloaded_bytes:u64,
148
149 #[allow(dead_code)]
151 total_bytes:u64,
152
153 complete:bool,
155
156 cancelled:bool,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
162struct RollbackHistory {
163 versions:Vec<RollbackState>,
165
166 max_versions:usize,
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct RollbackState {
172 version:String,
173
174 backup_path:PathBuf,
175
176 timestamp:chrono::DateTime<chrono::Utc>,
177
178 checksum:String,
179}
180
181#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
183pub enum UpdateChannel {
184 Stable,
185
186 Insiders,
187
188 Preview,
189}
190
191impl UpdateChannel {
192 fn as_str(&self) -> &'static str {
193 match self {
194 UpdateChannel::Stable => "stable",
195
196 UpdateChannel::Insiders => "insiders",
197
198 UpdateChannel::Preview => "preview",
199 }
200 }
201}
202
203#[derive(Debug, Clone)]
205struct PlatformConfig {
206 platform:String,
207
208 arch:String,
209
210 package_format:PackageFormat,
211}
212
213#[derive(Debug, Clone, Copy)]
215#[allow(dead_code)]
216enum PackageFormat {
217 WindowsExe,
218
219 MacOsDmg,
220
221 LinuxAppImage,
222
223 LinuxDeb,
224
225 LinuxRpm,
226}
227
228#[derive(Debug, Clone, Serialize, Deserialize)]
230pub struct UpdateStatus {
231 pub last_check:Option<chrono::DateTime<chrono::Utc>>,
233
234 pub update_available:bool,
236
237 pub current_version:String,
239
240 pub available_version:Option<String>,
242
243 pub download_progress:Option<f32>,
245
246 pub installation_status:InstallationStatus,
248
249 pub update_channel:UpdateChannel,
251
252 pub update_size:Option<u64>,
254
255 pub release_notes:Option<String>,
257
258 pub requires_restart:bool,
260
261 pub download_speed:Option<f64>,
263
264 pub eta_seconds:Option<u64>,
266
267 pub last_error:Option<String>,
269}
270
271#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
273pub enum InstallationStatus {
274 NotStarted,
276
277 CheckingPrerequisites,
279
280 Downloading,
282
283 Paused,
285
286 VerifyingSignature,
288
289 VerifyingChecksums,
291
292 Staging,
294
295 CreatingBackup,
297
298 Installing,
300
301 Completed,
303
304 RollingBack,
306
307 Failed(String),
309}
310
311impl InstallationStatus {
312 pub fn is_cancellable(&self) -> bool {
314 matches!(
315 self,
316 InstallationStatus::Downloading
317 | InstallationStatus::Paused
318 | InstallationStatus::Staging
319 | InstallationStatus::NotStarted
320 )
321 }
322
323 pub fn is_error(&self) -> bool { matches!(self, InstallationStatus::Failed(_)) }
325
326 pub fn is_in_progress(&self) -> bool {
328 matches!(
329 self,
330 InstallationStatus::CheckingPrerequisites
331 | InstallationStatus::Downloading
332 | InstallationStatus::VerifyingSignature
333 | InstallationStatus::VerifyingChecksums
334 | InstallationStatus::Staging
335 | InstallationStatus::CreatingBackup
336 | InstallationStatus::Installing
337 )
338 }
339}
340
341#[derive(Debug, Clone, Serialize, Deserialize)]
343pub struct UpdateInfo {
344 pub version:String,
346
347 pub download_url:String,
349
350 pub release_notes:String,
352
353 pub checksum:String,
355
356 pub checksums:HashMap<String, String>,
358
359 pub size:u64,
361
362 pub published_at:chrono::DateTime<chrono::Utc>,
364
365 pub is_mandatory:bool,
367
368 pub requires_restart:bool,
370
371 pub min_compatible_version:Option<String>,
373
374 pub delta_url:Option<String>,
376
377 pub delta_checksum:Option<String>,
379
380 pub delta_size:Option<u64>,
382
383 pub signature:Option<String>,
385
386 pub platform_metadata:Option<PlatformMetadata>,
388}
389
390#[derive(Debug, Clone, Serialize, Deserialize)]
392pub struct PlatformMetadata {
393 pub package_format:String,
395
396 pub install_instructions:Vec<String>,
398
399 pub required_disk_space:u64,
401
402 pub requires_admin:bool,
404
405 pub additional_params:HashMap<String, serde_json::Value>,
407}
408
409#[derive(Debug, Clone, Serialize, Deserialize)]
411pub struct UpdateTelemetry {
412 pub event_id:String,
414
415 pub current_version:String,
417
418 pub target_version:String,
420
421 pub channel:String,
423
424 pub platform:String,
426
427 pub operation:String,
429
430 pub success:bool,
432
433 pub duration_ms:u64,
435
436 pub download_size:Option<u64>,
438
439 pub error_message:Option<String>,
441
442 pub timestamp:chrono::DateTime<chrono::Utc>,
444}
445
446impl UpdateManager {
447 pub async fn new(AppState:Arc<ApplicationState>) -> Result<Self> {
449 let config = &AppState.Configuration.Updates;
450
451 let cache_directory = ConfigurationManager::ExpandPath(&AppState.Configuration.Downloader.CacheDirectory)?;
453
454 tokio::fs::create_dir_all(&cache_directory)
456 .await
457 .map_err(|e| AirError::Configuration(format!("Failed to create cache directory: {}", e)))?;
458
459 let staging_directory = cache_directory.join("staging");
461
462 tokio::fs::create_dir_all(&staging_directory)
463 .await
464 .map_err(|e| AirError::Configuration(format!("Failed to create staging directory: {}", e)))?;
465
466 let backup_directory = cache_directory.join("backups");
468
469 tokio::fs::create_dir_all(&backup_directory)
470 .await
471 .map_err(|e| AirError::Configuration(format!("Failed to create backup directory: {}", e)))?;
472
473 let PlatformConfig = Self::detect_platform();
475
476 let PlatformConfigClone = PlatformConfig.clone();
477
478 let update_channel = if config.Channel == "insiders" {
480 UpdateChannel::Insiders
481 } else if config.Channel == "preview" {
482 UpdateChannel::Preview
483 } else {
484 UpdateChannel::Stable
485 };
486
487 let rollback_history_path = backup_directory.join("rollback_history.json");
489
490 let rollback_history = if rollback_history_path.exists() {
491 let content = tokio::fs::read_to_string(&rollback_history_path)
492 .await
493 .map_err(|e| AirError::FileSystem(format!("Failed to read rollback history: {}", e)))?;
494
495 serde_json::from_str(&content).unwrap_or_else(|_| RollbackHistory { versions:Vec::new(), max_versions:5 })
496 } else {
497 RollbackHistory { versions:Vec::new(), max_versions:5 }
498 };
499
500 let manager = Self {
501 AppState,
502
503 update_status:Arc::new(RwLock::new(UpdateStatus {
504 last_check:None,
505 update_available:false,
506 current_version:env!("CARGO_PKG_VERSION").to_string(),
507 available_version:None,
508 download_progress:None,
509 installation_status:InstallationStatus::NotStarted,
510 update_channel,
511 update_size:None,
512 release_notes:None,
513 requires_restart:true,
514 download_speed:None,
515 eta_seconds:None,
516 last_error:None,
517 })),
518
519 cache_directory,
520
521 staging_directory,
522
523 backup_directory,
524
525 download_sessions:Arc::new(RwLock::new(HashMap::new())),
526
527 rollback_history:Arc::new(Mutex::new(rollback_history)),
528
529 update_channel,
530
531 platform_config:PlatformConfigClone,
532
533 background_task:Arc::new(Mutex::new(None)),
534 };
535
536 manager
538 .AppState
539 .UpdateServiceStatus("updates", crate::ApplicationState::ServiceStatus::Running)
540 .await
541 .map_err(|e| AirError::Internal(e.to_string()))?;
542
543 dev_log!(
544 "update",
545 "[UpdateManager] Update service initialized for platform: {}/{}",
546 PlatformConfig.platform,
547 PlatformConfig.arch
548 );
549
550 Ok(manager)
551 }
552
553 fn detect_platform() -> PlatformConfig {
555 let platform = if cfg!(target_os = "windows") {
556 "windows"
557 } else if cfg!(target_os = "macos") {
558 "macos"
559 } else if cfg!(target_os = "linux") {
560 "linux"
561 } else {
562 "unknown"
563 };
564
565 let arch = if cfg!(target_arch = "x86_64") {
566 "x64"
567 } else if cfg!(target_arch = "aarch64") {
568 "arm64"
569 } else if cfg!(target_arch = "x86") {
570 "ia32"
571 } else {
572 "unknown"
573 };
574
575 let package_format = match (platform, arch) {
576 ("windows", _) => PackageFormat::WindowsExe,
577
578 ("macos", _) => PackageFormat::MacOsDmg,
579
580 ("linux", "x64") => PackageFormat::LinuxAppImage,
581
582 ("linux", "") => PackageFormat::LinuxAppImage,
583
584 _ => PackageFormat::LinuxAppImage,
585 };
586
587 PlatformConfig { platform:platform.to_string(), arch:arch.to_string(), package_format }
588 }
589
590 pub async fn CheckForUpdates(&self) -> Result<Option<UpdateInfo>> {
600 let config = &self.AppState.Configuration.Updates;
601
602 let start_time = std::time::Instant::now();
603
604 if !config.Enabled {
605 dev_log!("update", "[UpdateManager] Updates are disabled");
606
607 return Ok(None);
608 }
609
610 dev_log!(
611 "update",
612 "[UpdateManager] Checking for updates on {} channel",
613 self.update_channel.as_str()
614 );
615
616 {
618 let mut status = self.update_status.write().await;
619
620 status.last_check = Some(chrono::Utc::now());
621
622 status.last_error = None;
623 }
624
625 let update_info = match self.FetchUpdateInfo().await {
627 Ok(info) => info,
628
629 Err(e) => {
630 dev_log!("update", "error: [UpdateManager] Failed to fetch update info: {}", e);
631
632 let mut status = self.update_status.write().await;
633
634 status.last_error = Some(e.to_string());
635
636 self.record_telemetry(
637 "check",
638 false,
639 start_time.elapsed().as_millis() as u64,
640 None,
641 Some(e.to_string()),
642 )
643 .await;
644
645 return Err(e);
646 },
647 };
648
649 if let Some(ref info) = update_info {
650 if let Some(ref min_version) = info.min_compatible_version {
652 let current_version = env!("CARGO_PKG_VERSION");
653
654 if UpdateManager::CompareVersions(current_version, min_version) < 0 {
655 dev_log!(
656 "update",
657 "warn: [UpdateManager] Update requires minimum version {} but current is {}. Skipping.",
658 min_version,
659 current_version
660 );
661
662 let mut status = self.update_status.write().await;
663
664 status.last_error = Some(format!("Update requires minimum version {}", min_version));
665
666 return Ok(None);
667 }
668 }
669
670 dev_log!(
671 "update",
672 "[UpdateManager] Update available: {} ({})",
673 info.version,
674 self.format_size(info.size as f64)
675 );
676
677 {
679 let mut status = self.update_status.write().await;
680
681 status.update_available = true;
682
683 status.available_version = Some(info.version.clone());
684
685 status.update_size = Some(info.size);
686
687 status.release_notes = Some(info.release_notes.clone());
688
689 status.requires_restart = info.requires_restart;
690 }
691
692 dev_log!("update", "[UpdateManager] Notifying frontend about available update");
695
696 self.record_telemetry("check", true, start_time.elapsed().as_millis() as u64, None, None)
698 .await;
699
700 if config.AutoDownload {
702 if let Err(e) = self.DownloadUpdate(info).await {
703 dev_log!("update", "error: [UpdateManager] Auto-download failed: {}", e); }
705 }
706 } else {
707 dev_log!("update", "[UpdateManager] No updates available");
708
709 {
711 let mut status = self.update_status.write().await;
712
713 status.update_available = false;
714
715 status.available_version = None;
716
717 status.update_size = None;
718
719 status.release_notes = None;
720 }
721
722 self.record_telemetry("check", true, start_time.elapsed().as_millis() as u64, None, None)
724 .await;
725 }
726
727 Ok(update_info)
728 }
729
730 pub async fn DownloadUpdate(&self, update_info:&UpdateInfo) -> Result<()> {
746 let start_time = std::time::Instant::now();
747
748 let session_id = Uuid::new_v4().to_string();
749
750 dev_log!(
751 "update",
752 "[UpdateManager] Starting download for version {} (session: {})",
753 update_info.version,
754 session_id
755 );
756
757 let required_space = update_info.size * 2; self.ValidateDiskSpace(required_space).await?;
760
761 {
763 let mut status = self.update_status.write().await;
764
765 status.installation_status = InstallationStatus::CheckingPrerequisites;
766
767 status.last_error = None;
768 }
769
770 let temp_path = self.cache_directory.join(format!("update-{}-temp.bin", update_info.version));
772
773 let final_path = self.cache_directory.join(format!("update-{}.bin", update_info.version));
774
775 let (downloaded_bytes, resume_from_start) = if temp_path.exists() {
777 let metadata = tokio::fs::metadata(&temp_path)
778 .await
779 .map_err(|e| AirError::FileSystem(format!("Failed to check temp file: {}", e)))?;
780
781 dev_log!(
782 "update",
783 "[UpdateManager] Found partial download, resuming from {} bytes",
784 metadata.len()
785 );
786
787 (metadata.len(), false)
788 } else {
789 (0, true)
790 };
791
792 {
794 let mut sessions = self.download_sessions.write().await;
795
796 sessions.insert(
797 session_id.clone(),
798 DownloadSession {
799 session_id:session_id.clone(),
800 download_url:update_info.download_url.clone(),
801 temp_path:temp_path.clone(),
802 downloaded_bytes,
803 total_bytes:update_info.size,
804 complete:false,
805 cancelled:false,
806 },
807 );
808 }
809
810 let dns_port = Mist::dns_port();
812
813 let client = crate::HTTP::Client::secured_client_with_timeout(dns_port, Duration::from_secs(300))
814 .map_err(|e| AirError::Network(format!("Failed to create HTTP client: {}", e)))?;
815
816 let mut request_builder = client.get(&update_info.download_url);
817
818 if !resume_from_start {
820 request_builder = request_builder.header("Range", format!("bytes={}-", downloaded_bytes));
821 }
822
823 let response:reqwest::Response = request_builder
824 .send()
825 .await
826 .map_err(|e| AirError::Network(format!("Failed to start download: {}", e)))?;
827
828 if !response.status().is_success() && response.status() != 206 {
829 dev_log!(
830 "update",
831 "error: [UpdateManager] Download failed with status: {}",
832 response.status()
833 );
834
835 let mut status = self.update_status.write().await;
836
837 status.installation_status =
838 InstallationStatus::Failed(format!("Download failed with status: {}", response.status()));
839
840 status.last_error = Some(format!("Download failed with status: {}", response.status()));
841
842 self.record_telemetry(
843 "download",
844 false,
845 start_time.elapsed().as_millis() as u64,
846 None,
847 Some("Download failed".to_string()),
848 )
849 .await;
850
851 return Err(AirError::Network(format!("Download failed with status: {}", response.status())));
852 }
853
854 let total_size = response.content_length().unwrap_or(update_info.size);
855
856 let initial_downloaded = if resume_from_start { 0 } else { downloaded_bytes };
857
858 {
860 let mut status = self.update_status.write().await;
861
862 status.installation_status = InstallationStatus::Downloading;
863
864 status.download_progress = Some(((downloaded_bytes as f32 / total_size as f32) * 100.0).min(100.0));
865 }
866
867 let last_update = Arc::new(Mutex::new(std::time::Instant::now()));
869
870 let last_bytes = Arc::new(Mutex::new(downloaded_bytes));
871
872 let mut file = if resume_from_start {
874 tokio::fs::File::create(&temp_path)
875 .await
876 .map_err(|e| AirError::FileSystem(format!("Failed to create update file: {}", e)))?
877 } else {
878 tokio::fs::OpenOptions::new()
880 .append(true)
881 .open(&temp_path)
882 .await
883 .map_err(|e| AirError::FileSystem(format!("Failed to open update file for resume: {}", e)))?
884 };
885
886 use tokio::io::AsyncWriteExt;
887 use futures_util::StreamExt;
888
889 let mut byte_stream = response.bytes_stream();
890
891 let mut downloaded = initial_downloaded;
892
893 while let Some(chunk_result) = byte_stream.next().await {
894 match chunk_result {
895 Ok(chunk) => {
896 let chunk_bytes:&[u8] = &chunk;
897
898 file.write_all(chunk_bytes)
899 .await
900 .map_err(|e| AirError::FileSystem(format!("Failed to write update file: {}", e)))?;
901
902 downloaded += chunk.len() as u64;
903
904 {
906 let mut last_update_guard = last_update.lock().await;
907
908 let mut last_bytes_guard = last_bytes.lock().await;
909
910 if last_update_guard.elapsed() >= Duration::from_secs(1) {
911 let bytes_this_second = downloaded - *last_bytes_guard;
912
913 let download_speed = bytes_this_second as f64;
914
915 let progress = ((downloaded as f32 / total_size as f32) * 100.0).min(100.0);
916
917 let remaining_bytes = total_size - downloaded;
918
919 let eta_seconds = if download_speed > 0.0 {
920 Some(remaining_bytes as u64 / (download_speed as u64).max(1))
921 } else {
922 None
923 };
924
925 {
926 let mut status = self.update_status.write().await;
927
928 status.download_progress = Some(progress);
929
930 status.download_speed = Some(download_speed);
931
932 status.eta_seconds = eta_seconds;
933 }
934
935 dev_log!(
936 "update",
937 "[UpdateManager] Download progress: {:.1}% ({}/s, ETA: {:?})",
938 progress,
939 self.format_size(download_speed),
940 eta_seconds
941 );
942
943 *last_update_guard = std::time::Instant::now();
944 *last_bytes_guard = downloaded;
945 }
946 }
947
948 {
950 let mut sessions = self.download_sessions.write().await;
951
952 if let Some(session) = sessions.get_mut(&session_id) {
953 session.downloaded_bytes = downloaded;
954 }
955 }
956 },
957
958 Err(e) => {
959 dev_log!("update", "error: [UpdateManager] Download error: {}", e);
960
961 let mut status = self.update_status.write().await;
962
963 status.installation_status = InstallationStatus::Failed(format!("Network error: {}", e));
964
965 status.last_error = Some(format!("Network error: {}", e));
966
967 self.record_telemetry(
968 "download",
969 false,
970 start_time.elapsed().as_millis() as u64,
971 None,
972 Some(e.to_string()),
973 )
974 .await;
975
976 return Err(AirError::Network(format!("Download error: {}", e)));
977 },
978 }
979 }
980
981 {
983 let mut status = self.update_status.write().await;
984
985 status.installation_status = InstallationStatus::Downloading;
986
987 status.download_progress = Some(100.0);
988 }
989
990 dev_log!(
991 "update",
992 "[UpdateManager] Download completed: {} bytes in {:.2}s",
993 downloaded,
994 start_time.elapsed().as_secs_f64()
995 );
996
997 {
999 let mut status = self.update_status.write().await;
1000
1001 status.installation_status = InstallationStatus::VerifyingChecksums;
1002 }
1003
1004 self.VerifyChecksum(&temp_path, &update_info.checksum).await?;
1005
1006 for (algorithm, expected_checksum) in &update_info.checksums {
1008 self.VerifyChecksumWithAlgorithm(&temp_path, algorithm, expected_checksum)
1009 .await?;
1010 }
1011
1012 if let Some(ref signature) = update_info.signature {
1014 {
1015 let mut status = self.update_status.write().await;
1016
1017 status.installation_status = InstallationStatus::VerifyingSignature;
1018 }
1019
1020 self.VerifySignature(&temp_path, signature).await?;
1021 }
1022
1023 if temp_path.exists() {
1025 tokio::fs::rename(&temp_path, &final_path)
1026 .await
1027 .map_err(|e| AirError::FileSystem(format!("Failed to finalize download: {}", e)))?;
1028 }
1029
1030 {
1032 let mut sessions = self.download_sessions.write().await;
1033
1034 if let Some(session) = sessions.get_mut(&session_id) {
1035 session.complete = true;
1036 }
1037 }
1038
1039 {
1041 let mut status = self.update_status.write().await;
1042
1043 status.installation_status = InstallationStatus::Completed;
1044
1045 status.download_progress = Some(100.0);
1046 }
1047
1048 dev_log!(
1049 "update",
1050 "[UpdateManager] Update {} downloaded and verified successfully",
1051 update_info.version
1052 );
1053
1054 self.record_telemetry(
1056 "download",
1057 true,
1058 start_time.elapsed().as_millis() as u64,
1059 Some(downloaded),
1060 None,
1061 )
1062 .await;
1063
1064 Ok(())
1065 }
1066
1067 pub async fn ApplyUpdate(&self, update_info:&UpdateInfo) -> Result<()> {
1082 let start_time = std::time::Instant::now();
1083
1084 let current_version = env!("CARGO_PKG_VERSION");
1085
1086 dev_log!(
1087 "update",
1088 "[UpdateManager] Applying update: {} (from {})",
1089 update_info.version,
1090 current_version
1091 );
1092
1093 let file_path = self.cache_directory.join(format!("update-{}.bin", update_info.version));
1094
1095 if !file_path.exists() {
1097 dev_log!("update", "error: [UpdateManager] Update file not found: {:?}", file_path);
1098
1099 return Err(AirError::FileSystem(
1100 "Update file not found. Please download first.".to_string(),
1101 ));
1102 }
1103
1104 {
1106 let mut status = self.update_status.write().await;
1107
1108 status.installation_status = InstallationStatus::VerifyingChecksums;
1109
1110 status.last_error = None;
1111 }
1112
1113 self.VerifyChecksum(&file_path, &update_info.checksum).await?;
1115
1116 for (algorithm, expected_checksum) in &update_info.checksums {
1118 self.VerifyChecksumWithAlgorithm(&file_path, algorithm, expected_checksum)
1119 .await?;
1120 }
1121
1122 if let Some(ref signature) = update_info.signature {
1124 {
1125 let mut status = self.update_status.write().await;
1126
1127 status.installation_status = InstallationStatus::VerifyingSignature;
1128 }
1129
1130 self.VerifySignature(&file_path, signature).await?;
1131 }
1132
1133 {
1135 let mut status = self.update_status.write().await;
1136
1137 status.installation_status = InstallationStatus::CreatingBackup;
1138 }
1139
1140 let backup_info = self.CreateBackup(current_version).await?;
1141
1142 dev_log!("update", "[UpdateManager] Backup created: {:?}", backup_info.backup_path);
1143
1144 {
1146 let mut status = self.update_status.write().await;
1147
1148 status.installation_status = InstallationStatus::Installing;
1149 }
1150
1151 let result = match self.platform_config.package_format {
1153 #[cfg(target_os = "windows")]
1154 PackageFormat::WindowsExe => self.ApplyWindowsUpdate(&file_path).await,
1155
1156 #[cfg(not(target_os = "windows"))]
1157 PackageFormat::WindowsExe => Err(AirError::Internal("Windows update not available on this platform".to_string())),
1158
1159 PackageFormat::MacOsDmg => self.ApplyMacOsUpdate(&file_path).await,
1160
1161 #[cfg(all(target_os = "linux", feature = "appimage"))]
1162 PackageFormat::LinuxAppImage => self.ApplyLinuxAppImageUpdate(&file_path).await,
1163
1164 #[cfg(not(all(target_os = "linux", feature = "appimage")))]
1165 PackageFormat::LinuxAppImage => {
1166 Err(AirError::Internal(
1167 "Linux AppImage update not available on this platform".to_string(),
1168 ))
1169 },
1170
1171 #[cfg(all(target_os = "linux", feature = "deb"))]
1172 PackageFormat::LinuxDeb => self.ApplyLinuxDebUpdate(&file_path).await,
1173
1174 #[cfg(not(all(target_os = "linux", feature = "deb")))]
1175 PackageFormat::LinuxDeb => {
1176 Err(AirError::Internal(
1177 "Linux DEB update not available on this platform".to_string(),
1178 ))
1179 },
1180
1181 #[cfg(all(target_os = "linux", feature = "rpm"))]
1182 PackageFormat::LinuxRpm => self.ApplyLinuxRpmUpdate(&file_path).await,
1183
1184 #[cfg(not(all(target_os = "linux", feature = "rpm")))]
1185 PackageFormat::LinuxRpm => {
1186 Err(AirError::Internal(
1187 "Linux RPM update not available on this platform".to_string(),
1188 ))
1189 },
1190 };
1191
1192 if let Err(e) = result {
1193 dev_log!(
1194 "update",
1195 "error: [UpdateManager] Installation failed, initiating rollback: {}",
1196 e
1197 );
1198
1199 {
1201 let mut status = self.update_status.write().await;
1202
1203 status.installation_status = InstallationStatus::RollingBack;
1204 }
1205
1206 if let Err(rollback_err) = self.RollbackToBackup(&backup_info).await {
1208 dev_log!("update", "error: [UpdateManager] Rollback also failed: {}", rollback_err);
1209
1210 let mut status = self.update_status.write().await;
1212
1213 status.installation_status = InstallationStatus::Failed(format!(
1214 "Installation failed and rollback failed: {} / {}",
1215 e, rollback_err
1216 ));
1217
1218 status.last_error = Some(format!("Installation failed and rollback failed"));
1219
1220 self.record_telemetry(
1221 "install",
1222 false,
1223 start_time.elapsed().as_millis() as u64,
1224 None,
1225 Some(format!("Update and rollback failed: {}", rollback_err)),
1226 )
1227 .await;
1228
1229 return Err(AirError::Internal(format!(
1230 "Installation failed and rollback failed: {} / {}",
1231 e, rollback_err
1232 )));
1233 } else {
1234 dev_log!("update", "[UpdateManager] Rollback successful");
1235
1236 let mut status = self.update_status.write().await;
1237
1238 status.installation_status =
1239 InstallationStatus::Failed(format!("Installation failed, rollback successful: {}", e));
1240
1241 status.last_error = Some(e.to_string());
1242
1243 self.record_telemetry(
1244 "install",
1245 false,
1246 start_time.elapsed().as_millis() as u64,
1247 None,
1248 Some(e.to_string()),
1249 )
1250 .await;
1251
1252 return Err(AirError::Internal(format!("Installation failed, rollback successful: {}", e)));
1253 }
1254 }
1255
1256 {
1258 let mut history = self.rollback_history.lock().await;
1259
1260 history.versions.insert(0, backup_info);
1261
1262 while history.versions.len() > history.max_versions {
1264 if let Some(old_backup) = history.versions.pop() {
1265 let _ = tokio::fs::remove_dir_all(&old_backup.backup_path).await;
1267 }
1268 }
1269 }
1270
1271 let history_path = self.backup_directory.join("rollback_history.json");
1273
1274 let history = self.rollback_history.lock().await;
1275
1276 let history_json = serde_json::to_string(&*history)
1277 .map_err(|e| AirError::Internal(format!("Failed to serialize rollback history: {}", e)))?;
1278
1279 drop(history);
1280
1281 tokio::fs::write(&history_path, history_json)
1282 .await
1283 .map_err(|e| AirError::FileSystem(format!("Failed to save rollback history: {}", e)))?;
1284
1285 {
1287 let mut status = self.update_status.write().await;
1288
1289 status.current_version = update_info.version.clone();
1290
1291 status.installation_status = InstallationStatus::Completed;
1292 }
1293
1294 dev_log!(
1295 "update",
1296 "[UpdateManager] Update {} applied successfully in {:.2}s",
1297 update_info.version,
1298 start_time.elapsed().as_secs_f64()
1299 );
1300
1301 self.record_telemetry(
1303 "install",
1304 true,
1305 start_time.elapsed().as_millis() as u64,
1306 Some(update_info.size),
1307 None,
1308 )
1309 .await;
1310
1311 Ok(())
1312 }
1313
1314 async fn FetchUpdateInfo(&self) -> Result<Option<UpdateInfo>> {
1325 let config = &self.AppState.Configuration.Updates;
1326
1327 let retry_policy = crate::Resilience::RetryPolicy {
1329 MaxRetries:3,
1330
1331 InitialIntervalMs:1000,
1332
1333 MaxIntervalMs:16000,
1334
1335 BackoffMultiplier:2.0,
1336
1337 JitterFactor:0.1,
1338
1339 BudgetPerMinute:50,
1340
1341 ErrorClassification:std::collections::HashMap::new(),
1342 };
1343
1344 let _retry_manager = crate::Resilience::RetryManager::new(retry_policy.clone());
1345
1346 let circuit_breaker = crate::Resilience::CircuitBreaker::new(
1347 "updates".to_string(),
1348 crate::Resilience::CircuitBreakerConfig::default(),
1349 );
1350
1351 let current_version = env!("CARGO_PKG_VERSION");
1352
1353 let mut attempt = 0;
1354
1355 loop {
1356 if circuit_breaker.GetState().await == crate::Resilience::CircuitState::Open {
1358 if !circuit_breaker.AttemptRecovery().await {
1359 dev_log!("update", "warn: [UpdateManager] Circuit breaker is open, skipping update check");
1360
1361 return Ok(None);
1362 }
1363 }
1364
1365 let update_url = format!(
1367 "{}/check?version={}&platform={}&arch={}&channel={}",
1368 config.UpdateServerUrl,
1369 current_version,
1370 self.platform_config.platform,
1371 self.platform_config.arch,
1372 self.update_channel.as_str()
1373 );
1374
1375 let dns_port = Mist::dns_port();
1376
1377 let client = crate::HTTP::Client::secured_client_with_timeout(dns_port, Duration::from_secs(30))
1378 .map_err(|e| AirError::Network(format!("Failed to create HTTP client: {}", e)))?;
1379
1380 match client.get(&update_url).send().await {
1381 Ok(response) => {
1382 let status:reqwest::StatusCode = response.status();
1383
1384 match status {
1385 reqwest::StatusCode::NO_CONTENT => {
1386 circuit_breaker.RecordSuccess().await;
1388
1389 dev_log!("update", "[UpdateManager] Server reports no updates available");
1390
1391 return Ok(None);
1392 },
1393
1394 status if status.is_success() => {
1395 match response.json::<UpdateInfo>().await {
1397 Ok(update_info) => {
1398 circuit_breaker.RecordSuccess().await;
1399
1400 if UpdateManager::CompareVersions(current_version, &update_info.version) < 0 {
1402 dev_log!(
1403 "update",
1404 "[UpdateManager] Update available: {} -> {}",
1405 current_version,
1406 update_info.version
1407 );
1408
1409 return Ok(Some(update_info));
1410 } else {
1411 dev_log!(
1412 "update",
1413 "[UpdateManager] Server returned same or older version: {}",
1414 update_info.version
1415 );
1416
1417 return Ok(None);
1418 }
1419 },
1420
1421 Err(e) => {
1422 circuit_breaker.RecordFailure().await;
1423
1424 dev_log!("update", "error: [UpdateManager] Failed to parse update info: {}", e);
1425
1426 if attempt < retry_policy.MaxRetries {
1427 attempt += 1;
1428
1429 let delay = Duration::from_millis(
1430 retry_policy.InitialIntervalMs * 2_u32.pow(attempt as u32) as u64,
1431 );
1432
1433 sleep(delay).await;
1434
1435 continue;
1436 } else {
1437 return Err(AirError::Network(format!(
1438 "Failed to parse update info after retries: {}",
1439 e
1440 )));
1441 }
1442 },
1443 }
1444 },
1445
1446 status => {
1447 circuit_breaker.RecordFailure().await;
1448
1449 dev_log!("update", "warn: [UpdateManager] Update server returned status: {}", status);
1450
1451 if attempt < retry_policy.MaxRetries {
1452 attempt += 1;
1453
1454 let delay = Duration::from_millis(
1455 retry_policy.InitialIntervalMs * 2_u32.pow(attempt as u32) as u64,
1456 );
1457
1458 sleep(delay).await;
1459
1460 continue;
1461 } else {
1462 return Ok(None);
1463 }
1464 },
1465 }
1466 },
1467
1468 Err(e) => {
1469 circuit_breaker.RecordFailure().await;
1470
1471 dev_log!("update", "warn: [UpdateManager] Failed to check for updates: {}", e);
1472
1473 if attempt < retry_policy.MaxRetries {
1474 attempt += 1;
1475
1476 let delay =
1477 Duration::from_millis(retry_policy.InitialIntervalMs * 2_u32.pow(attempt as u32) as u64);
1478
1479 sleep(delay).await;
1480
1481 continue;
1482 } else {
1483 return Ok(None);
1484 }
1485 },
1486 }
1487 }
1488 }
1489
1490 async fn VerifyChecksum(&self, file_path:&Path, expected_checksum:&str) -> Result<()> {
1504 let content = tokio::fs::read(file_path)
1505 .await
1506 .map_err(|e| AirError::FileSystem(format!("Failed to read update file for checksum: {}", e)))?;
1507
1508 let actual_checksum = self.CalculateSha256(&content);
1509
1510 if actual_checksum.to_lowercase() != expected_checksum.to_lowercase() {
1511 dev_log!(
1512 "update",
1513 "error: [UpdateManager] Checksum verification failed: expected {}, got {}",
1514 expected_checksum,
1515 actual_checksum
1516 );
1517
1518 return Err(AirError::Network("Update checksum verification failed".to_string()));
1519 }
1520
1521 dev_log!("update", "[UpdateManager] Checksum verified: {}", actual_checksum);
1522
1523 Ok(())
1524 }
1525
1526 async fn VerifyChecksumWithAlgorithm(&self, file_path:&Path, algorithm:&str, expected_checksum:&str) -> Result<()> {
1539 let content = tokio::fs::read(file_path).await.map_err(|e| {
1540 AirError::FileSystem(format!("Failed to read update file for {} checksum: {}", algorithm, e))
1541 })?;
1542
1543 let actual_checksum = match algorithm.to_lowercase().as_str() {
1544 "sha256" => self.CalculateSha256(&content),
1545
1546 "sha512" => self.CalculateSha512(&content),
1547
1548 "md5" => self.CalculateMd5(&content),
1549
1550 "crc32" => self.CalculateCrc32(&content),
1551
1552 _ => {
1553 dev_log!(
1554 "update",
1555 "warn: [UpdateManager] Unknown checksum algorithm: {}, skipping",
1556 algorithm
1557 );
1558
1559 return Ok(());
1560 },
1561 };
1562
1563 if actual_checksum.to_lowercase() != expected_checksum.to_lowercase() {
1564 dev_log!(
1565 "update",
1566 "error: [UpdateManager] {} checksum verification failed: expected {}, got {}",
1567 algorithm,
1568 expected_checksum,
1569 actual_checksum
1570 );
1571
1572 return Err(AirError::Network(format!("{} checksum verification failed", algorithm)));
1573 }
1574
1575 dev_log!("update", "[UpdateManager] {} checksum verified: {}", algorithm, actual_checksum);
1576
1577 Ok(())
1578 }
1579
1580 async fn VerifySignature(&self, _file_path:&Path, _signature:&str) -> Result<()> {
1594 #[cfg(debug_assertions)]
1603 {
1604 dev_log!("update", "[UpdateManager] Development build: skipping signature verification");
1605
1606 return Ok(());
1607 }
1608
1609 #[cfg(not(debug_assertions))]
1612 {
1613 dev_log!(
1614 "update",
1615 "warn: [UpdateManager] WARNING: Cryptographic signature verification is not yet implemented"
1616 );
1617
1618 dev_log!(
1619 "update",
1620 "warn: [UpdateManager] Update packages should be cryptographically signed in production"
1621 );
1622
1623 dev_log!(
1624 "update",
1625 "[UpdateManager] Proceeding with update without signature verification"
1626 );
1627
1628 return Ok(());
1629 }
1630 }
1631
1632 async fn CreateBackup(&self, version:&str) -> Result<RollbackState> {
1645 let timestamp = chrono::Utc::now();
1646
1647 let backup_dir_name = format!("backup-{}-{}", version, timestamp.format("%Y%m%d_%H%M%S"));
1648
1649 let backup_path = self.backup_directory.join(&backup_dir_name);
1650
1651 dev_log!("update", "[UpdateManager] Creating backup: {}", backup_dir_name);
1652
1653 tokio::fs::create_dir_all(&backup_path)
1655 .await
1656 .map_err(|e| AirError::FileSystem(format!("Failed to create backup directory: {}", e)))?;
1657
1658 let exe_path = std::env::current_exe()
1660 .map_err(|e| AirError::FileSystem(format!("Failed to get executable path: {}", e)))?;
1661
1662 let backup_exe = backup_path.join(exe_path.file_name().unwrap_or_default());
1664
1665 tokio::fs::copy(&exe_path, &backup_exe)
1666 .await
1667 .map_err(|e| AirError::FileSystem(format!("Failed to backup executable: {}", e)))?;
1668
1669 let config_dirs = vec![
1672 dirs::config_dir().unwrap_or_default().join("FIDDEE"),
1673 dirs::home_dir().unwrap_or_default().join(".config/land"),
1674 ];
1675
1676 for config_dir in config_dirs {
1677 if config_dir.exists() {
1678 let backup_config = backup_path.join("config");
1679
1680 let _ = tokio::fs::create_dir_all(&backup_config).await;
1681
1682 let _ = Self::copy_directory_recursive(&config_dir, &backup_config).await;
1683
1684 dev_log!("update", "[UpdateManager] Backed up config directory: {:?}", config_dir);
1685 }
1686 }
1687
1688 let data_dirs = vec![
1690 dirs::data_local_dir().unwrap_or_default().join("FIDDEE"),
1691 dirs::home_dir().unwrap_or_default().join(".local/share/land"),
1692 ];
1693
1694 for data_dir in data_dirs {
1695 if data_dir.exists() {
1696 let backup_data = backup_path.join("data");
1697
1698 let _ = tokio::fs::create_dir_all(&backup_data).await;
1699
1700 let _ = Self::copy_directory_recursive(&data_dir, &backup_data).await;
1701
1702 dev_log!("update", "[UpdateManager] Backed up data directory: {:?}", data_dir);
1703 }
1704 }
1705
1706 let checksum = self.CalculateFileChecksum(&backup_path).await?;
1708
1709 dev_log!("update", "[UpdateManager] Backup created at: {:?}", backup_path);
1710
1711 Ok(RollbackState { version:version.to_string(), backup_path, timestamp, checksum })
1712 }
1713
1714 pub async fn RollbackToBackup(&self, backup_info:&RollbackState) -> Result<()> {
1727 dev_log!(
1728 "update",
1729 "[UpdateManager] Rolling back to version: {} from: {:?}",
1730 backup_info.version,
1731 backup_info.backup_path
1732 );
1733
1734 let current_checksum = self.CalculateFileChecksum(&backup_info.backup_path).await?;
1736
1737 if current_checksum != backup_info.checksum {
1738 return Err(AirError::Internal(format!(
1739 "Backup integrity check failed: expected {}, got {}",
1740 backup_info.checksum, current_checksum
1741 )));
1742 }
1743
1744 let exe_path = std::env::current_exe()
1746 .map_err(|e| AirError::FileSystem(format!("Failed to get executable path: {}", e)))?;
1747
1748 let backup_exe = backup_info.backup_path.join(exe_path.file_name().unwrap_or_default());
1749
1750 if !backup_exe.exists() {
1751 return Err(AirError::FileSystem("Backup executable not found".to_string()));
1752 }
1753
1754 match tokio::fs::copy(&backup_exe, &exe_path).await {
1758 Ok(_) => {
1759 dev_log!("update", "[UpdateManager] Executable restored from backup");
1760 },
1761
1762 Err(e) => {
1763 dev_log!("update", "error: [UpdateManager] Failed to restore executable: {}", e);
1764
1765 dev_log!("update", "warn: [UpdateManager] Rollback may require manual intervention");
1766 },
1767 }
1768
1769 let backup_config = backup_info.backup_path.join("config");
1771
1772 if backup_config.exists() {
1773 let config_dirs = vec![
1774 dirs::config_dir().unwrap_or_default().join("FIDDEE"),
1775 dirs::home_dir().unwrap_or_default().join(".config/land"),
1776 ];
1777
1778 for config_dir in config_dirs {
1779 if config_dir.exists() {
1781 let _ = tokio::fs::remove_dir_all(&config_dir).await;
1782 }
1783
1784 let _ = Self::copy_directory_recursive(&backup_config, &config_dir).await;
1785
1786 dev_log!("update", "[UpdateManager] Restored config directory: {:?}", config_dir);
1787 }
1788 }
1789
1790 let backup_data = backup_info.backup_path.join("data");
1792
1793 if backup_data.exists() {
1794 let data_dirs = vec![
1795 dirs::data_local_dir().unwrap_or_default().join("FIDDEE"),
1796 dirs::home_dir().unwrap_or_default().join(".local/share/land"),
1797 ];
1798
1799 for data_dir in data_dirs {
1800 if data_dir.exists() {
1802 let _ = tokio::fs::remove_dir_all(&data_dir).await;
1803 }
1804
1805 let _ = Self::copy_directory_recursive(&backup_data, &data_dir).await;
1806
1807 dev_log!("update", "[UpdateManager] Restored data directory: {:?}", data_dir);
1808 }
1809 }
1810
1811 dev_log!(
1812 "update",
1813 "[UpdateManager] Rollback to version {} completed",
1814 backup_info.version
1815 );
1816
1817 Ok(())
1818 }
1819
1820 pub async fn RollbackToVersion(&self, version:&str) -> Result<()> {
1832 let history = self.rollback_history.lock().await;
1833
1834 let backup_info = history
1835 .versions
1836 .iter()
1837 .find(|state| state.version == version)
1838 .ok_or_else(|| AirError::FileSystem(format!("No backup found for version {}", version)))?;
1839
1840 let info = backup_info.clone();
1841
1842 drop(history);
1843
1844 self.RollbackToBackup(&info).await
1845 }
1846
1847 pub async fn GetAvailableRollbackVersions(&self) -> Vec<String> {
1851 let history = self.rollback_history.lock().await;
1852
1853 history.versions.iter().map(|state| state.version.clone()).collect()
1854 }
1855
1856 async fn ValidateDiskSpace(&self, required_bytes:u64) -> Result<()> {
1866 let metadata = tokio::fs::metadata(&self.cache_directory)
1868 .await
1869 .map_err(|e| AirError::FileSystem(format!("Failed to get cache directory info: {}", e)))?;
1870
1871 if cfg!(target_os = "windows") {
1872 #[cfg(target_os = "windows")]
1874 {
1875 use std::os::windows::fs::MetadataExt;
1876
1877 let free_space = metadata.volume_serial_number() as u64; dev_log!(
1879 "update",
1880 "warn: [UpdateManager] Disk space validation not fully implemented on Windows"
1881 );
1882 }
1883 } else {
1884 #[cfg(not(target_os = "windows"))]
1886 {
1887 use std::os::unix::fs::MetadataExt;
1888
1889 let _device_id = metadata.dev();
1890
1891 let cache_path = self.cache_directory.to_string_lossy();
1893
1894 let free_space = unsafe {
1895 let mut stat:libc::statvfs = std::mem::zeroed();
1896
1897 if libc::statvfs(cache_path.as_ptr() as *const i8, &mut stat) == 0 {
1898 stat.f_bavail as u64 * stat.f_bsize as u64
1899 } else {
1900 u64::MAX }
1902 };
1903
1904 if free_space < required_bytes {
1905 return Err(AirError::Configuration(format!(
1906 "Insufficient disk space: required {} bytes, available {} bytes",
1907 required_bytes, free_space
1908 )));
1909 }
1910
1911 dev_log!(
1912 "update",
1913 "[UpdateManager] Disk space check passed: {} bytes available, {} bytes required",
1914 free_space,
1915 required_bytes
1916 );
1917 }
1918 }
1919
1920 dev_log!(
1921 "update",
1922 "[UpdateManager] Disk space validation passed for required {} bytes",
1923 self.format_size(required_bytes as f64)
1924 );
1925
1926 Ok(())
1927 }
1928
1929 pub async fn verify_update(&self, file_path:&str, update_info:Option<&UpdateInfo>) -> Result<bool> {
1943 let path = PathBuf::from(file_path);
1944
1945 if !path.exists() {
1946 return Ok(false);
1947 }
1948
1949 let metadata = tokio::fs::metadata(&path)
1950 .await
1951 .map_err(|e| AirError::FileSystem(format!("Failed to read update file metadata: {}", e)))?;
1952
1953 if metadata.len() == 0 {
1954 return Ok(false);
1955 }
1956
1957 if let Some(info) = update_info {
1959 if !info.checksum.is_empty() {
1960 let actual_checksum = self.CalculateFileChecksum(&path).await?;
1961
1962 if actual_checksum != info.checksum {
1963 return Err(AirError::Configuration(format!(
1964 "Checksum verification failed: expected {}, got {}",
1965 info.checksum, actual_checksum
1966 )));
1967 }
1968 }
1969
1970 for (algorithm, expected_checksum) in &info.checksums {
1972 self.VerifyChecksumWithAlgorithm(&path, algorithm, expected_checksum).await?;
1973 }
1974
1975 if let Some(expected_size) = Some(info.size) {
1977 if metadata.len() != expected_size {
1978 return Err(AirError::Configuration(format!(
1979 "File size mismatch: expected {}, got {}",
1980 expected_size,
1981 metadata.len()
1982 )));
1983 }
1984 }
1985 }
1986
1987 Ok(true)
1988 }
1989
1990 #[cfg(target_os = "windows")]
1992 async fn ApplyWindowsUpdate(&self, file_path:&Path) -> Result<()> {
1993 dev_log!("update", "[UpdateManager] Installing Windows update: {:?}", file_path);
1994
1995 dev_log!(
2004 "update",
2005 "warn: [UpdateManager] Windows installation: update package ready at {:?}",
2006 file_path
2007 );
2008
2009 dev_log!("update", "[UpdateManager] Manual installation may be required");
2010
2011 Ok(())
2012 }
2013
2014 #[cfg(target_os = "macos")]
2016 async fn ApplyMacOsUpdate(&self, file_path:&Path) -> Result<()> {
2017 dev_log!("update", "[UpdateManager] Installing macOS update: {:?}", file_path);
2018
2019 dev_log!(
2029 "update",
2030 "warn: [UpdateManager] macOS installation: update package ready at {:?}",
2031 file_path
2032 );
2033
2034 dev_log!("update", "[UpdateManager] Manual installation may be required");
2035
2036 Ok(())
2037 }
2038
2039 #[cfg(all(target_os = "linux", feature = "appimage"))]
2041 async fn ApplyLinuxAppImageUpdate(&self, file_path:&Path) -> Result<()> {
2042 dev_log!("update", "[UpdateManager] Installing Linux AppImage update: {:?}", file_path);
2043
2044 dev_log!(
2052 "update",
2053 "warn: [UpdateManager] Linux AppImage installation: update package ready at {:?}",
2054 file_path
2055 );
2056
2057 dev_log!("update", "[UpdateManager] Manual installation may be required");
2058
2059 Ok(())
2060 }
2061
2062 #[cfg(all(target_os = "linux", feature = "deb"))]
2064 async fn ApplyLinuxDebUpdate(&self, file_path:&Path) -> Result<()> {
2065 dev_log!("update", "[UpdateManager] Installing Linux DEB update: {:?}", file_path);
2066
2067 dev_log!(
2074 "update",
2075 "warn: [UpdateManager] Linux DEB installation: update package ready at {:?}",
2076 file_path
2077 );
2078
2079 dev_log!("update", "[UpdateManager] Manual installation may be required");
2080
2081 Ok(())
2082 }
2083
2084 #[cfg(all(target_os = "linux", feature = "rpm"))]
2086 async fn ApplyLinuxRpmUpdate(&self, file_path:&Path) -> Result<()> {
2087 dev_log!("update", "[UpdateManager] Installing Linux RPM update: {:?}", file_path);
2088
2089 dev_log!(
2096 "update",
2097 "warn: [UpdateManager] Linux RPM installation: update package ready at {:?}",
2098 file_path
2099 );
2100
2101 dev_log!("update", "[UpdateManager] Manual installation may be required");
2102
2103 Ok(())
2104 }
2105
2106 async fn record_telemetry(
2120 &self,
2121
2122 operation:&str,
2123
2124 success:bool,
2125
2126 duration_ms:u64,
2127
2128 download_size:Option<u64>,
2129
2130 error_message:Option<String>,
2131 ) {
2132 let telemetry = UpdateTelemetry {
2133 event_id:Uuid::new_v4().to_string(),
2134
2135 current_version:env!("CARGO_PKG_VERSION").to_string(),
2136
2137 target_version:self
2138 .update_status
2139 .read()
2140 .await
2141 .available_version
2142 .clone()
2143 .unwrap_or_else(|| "unknown".to_string()),
2144
2145 channel:self.update_channel.as_str().to_string(),
2146
2147 platform:format!("{}/{}", self.platform_config.platform, self.platform_config.arch),
2148
2149 operation:operation.to_string(),
2150
2151 success,
2152
2153 duration_ms,
2154
2155 download_size,
2156
2157 error_message,
2158
2159 timestamp:chrono::Utc::now(),
2160 };
2161
2162 dev_log!(
2163 "update",
2164 "[UpdateManager] Telemetry: {} {} in {}ms - size: {:?}, success: {}",
2165 operation,
2166 if success { "succeeded" } else { "failed" },
2167 duration_ms,
2168 download_size.map(|s| self.format_size(s as f64)),
2169 success
2170 );
2171
2172 #[cfg(debug_assertions)]
2175 {
2176 if let Ok(telemetry_json) = serde_json::to_string(&telemetry) {
2177 dev_log!("update", "[UpdateManager] Telemetry data: {}", telemetry_json); } else {
2181 dev_log!("update", "error: [UpdateManager] Failed to serialize telemetry");
2182 }
2183 }
2184
2185 #[cfg(not(debug_assertions))]
2187 {
2188 let _ = &telemetry; }
2192 }
2193
2194 fn CalculateSha256(&self, data:&[u8]) -> String {
2196 let mut hasher = Sha256::new();
2200
2201 hasher.update(data);
2202
2203 hex::encode(hasher.finalize())
2204 }
2205
2206 fn CalculateSha512(&self, data:&[u8]) -> String {
2208 use sha2::Sha512;
2209
2210 let mut hasher = Sha512::new();
2211
2212 hasher.update(data);
2213
2214 hex::encode(hasher.finalize())
2215 }
2216
2217 fn CalculateMd5(&self, data:&[u8]) -> String {
2219 let digest = md5::compute(data);
2220
2221 format!("{:x}", digest)
2222 }
2223
2224 fn CalculateCrc32(&self, data:&[u8]) -> String {
2226 let crc = crc32fast::hash(data);
2227
2228 format!("{:08x}", crc)
2229 }
2230
2231 async fn CalculateFileChecksum(&self, path:&Path) -> Result<String> {
2233 let content = tokio::fs::read(path)
2234 .await
2235 .map_err(|e| AirError::FileSystem(format!("Failed to read file for checksum: {}", e)))?;
2236
2237 Ok(self.CalculateSha256(&content))
2238 }
2239
2240 pub fn CompareVersions(v1:&str, v2:&str) -> i32 {
2254 let v1_parts:Vec<u32> = v1.split('.').filter_map(|s| s.parse().ok()).collect();
2255
2256 let v2_parts:Vec<u32> = v2.split('.').filter_map(|s| s.parse().ok()).collect();
2257
2258 for (i, part) in v1_parts.iter().enumerate() {
2259 if i >= v2_parts.len() {
2260 return 1;
2261 }
2262
2263 match part.cmp(&v2_parts[i]) {
2264 std::cmp::Ordering::Greater => return 1,
2265
2266 std::cmp::Ordering::Less => return -1,
2267
2268 std::cmp::Ordering::Equal => continue,
2269 }
2270 }
2271
2272 if v1_parts.len() < v2_parts.len() { -1 } else { 0 }
2273 }
2274
2275 pub async fn GetStatus(&self) -> UpdateStatus {
2279 let status = self.update_status.read().await;
2280
2281 status.clone()
2282 }
2283
2284 pub async fn CancelDownload(&self) -> Result<()> {
2291 let status = self.update_status.write().await;
2292
2293 if status.installation_status != InstallationStatus::Downloading {
2294 return Err(AirError::Internal("No download in progress".to_string()));
2295 }
2296
2297 {
2299 let mut sessions = self.download_sessions.write().await;
2300
2301 for session in sessions.values_mut() {
2302 session.cancelled = true;
2303 }
2304 }
2305
2306 let sessions = self.download_sessions.read().await;
2308
2309 for session in sessions.values() {
2310 if session.temp_path.exists() {
2311 if let Err(e) = tokio::fs::remove_file(&session.temp_path).await {
2312 dev_log!("update", "warn: [UpdateManager] Failed to remove partial file: {}", e);
2313 }
2314
2315 dev_log!("update", "[UpdateManager] Removed partial file: {:?}", session.temp_path);
2316 }
2317 }
2318
2319 drop(sessions);
2320
2321 {
2323 let mut sessions = self.download_sessions.write().await;
2324
2325 sessions.clear();
2326 }
2327
2328 dev_log!("update", "[UpdateManager] Download cancelled and cleaned up");
2329
2330 Ok(())
2331 }
2332
2333 pub async fn ResumeDownload(&self, update_info:&UpdateInfo) -> Result<()> {
2342 let Status = self.update_status.write().await;
2343
2344 if Status.installation_status != InstallationStatus::Paused {
2345 return Err(AirError::Internal("No paused download to resume".to_string()));
2346 }
2347
2348 drop(Status);
2349
2350 dev_log!(
2351 "update",
2352 "[UpdateManager] Resuming download for version {}",
2353 update_info.version
2354 );
2355
2356 self.DownloadUpdate(update_info).await
2357 }
2358
2359 pub async fn GetUpdateChannel(&self) -> UpdateChannel { self.update_channel }
2363
2364 pub async fn SetUpdateChannel(&mut self, channel:UpdateChannel) { self.update_channel = channel; }
2369
2370 async fn copy_directory_recursive(src:&Path, dst:&Path) -> Result<()> {
2382 let mut entries = tokio::fs::read_dir(src)
2383 .await
2384 .map_err(|e| AirError::FileSystem(format!("Failed to read directory {:?}: {}", src, e)))?;
2385
2386 tokio::fs::create_dir_all(dst)
2387 .await
2388 .map_err(|e| AirError::FileSystem(format!("Failed to create directory {:?}: {}", dst, e)))?;
2389
2390 while let Some(entry) = entries
2391 .next_entry()
2392 .await
2393 .map_err(|e| AirError::FileSystem(format!("Failed to read entry: {}", e)))?
2394 {
2395 let file_type = entry
2396 .file_type()
2397 .await
2398 .map_err(|e| AirError::FileSystem(format!("Failed to get file type: {}", e)))?;
2399
2400 let src_path = entry.path();
2401
2402 let dst_path = dst.join(entry.file_name());
2403
2404 if file_type.is_file() {
2405 tokio::fs::copy(&src_path, &dst_path)
2406 .await
2407 .map_err(|e| AirError::FileSystem(format!("Failed to copy file {:?}: {}", src_path, e)))?;
2408 } else if file_type.is_dir() {
2409 Box::pin(Self::copy_directory_recursive(&src_path, &dst_path)).await?;
2410 }
2411 }
2412
2413 Ok(())
2414 }
2415
2416 pub async fn StageUpdate(&self, update_info:&UpdateInfo) -> Result<()> {
2426 dev_log!("update", "[UpdateManager] Staging update for version {}", update_info.version);
2427
2428 let mut status = self.update_status.write().await;
2429
2430 status.installation_status = InstallationStatus::Staging;
2431
2432 drop(status);
2433
2434 let file_path = self.cache_directory.join(format!("update-{}.bin", update_info.version));
2435
2436 if !file_path.exists() {
2437 return Err(AirError::FileSystem("Update file not found. Download first.".to_string()));
2438 }
2439
2440 let stage_dir = self.staging_directory.join(&update_info.version);
2442
2443 tokio::fs::create_dir_all(&stage_dir)
2444 .await
2445 .map_err(|e| AirError::FileSystem(format!("Failed to create staging directory: {}", e)))?;
2446
2447 let staged_file = stage_dir.join("update.bin");
2449
2450 tokio::fs::copy(&file_path, &staged_file)
2451 .await
2452 .map_err(|e| AirError::FileSystem(format!("Failed to stage update package: {}", e)))?;
2453
2454 self.VerifyChecksum(&staged_file, &update_info.checksum).await?;
2456
2457 dev_log!("update", "[UpdateManager] Update staged successfully in: {:?}", stage_dir);
2458
2459 Ok(())
2460 }
2461
2462 pub async fn CleanupOldUpdates(&self) -> Result<()> {
2467 dev_log!("update", "[UpdateManager] Cleaning up old update files");
2468
2469 let mut entries = tokio::fs::read_dir(&self.cache_directory)
2470 .await
2471 .map_err(|e| AirError::FileSystem(format!("Failed to read cache directory: {}", e)))?;
2472
2473 let mut cleaned_count = 0;
2474
2475 let now = std::time::SystemTime::now();
2476
2477 while let Some(entry) = entries
2478 .next_entry()
2479 .await
2480 .map_err(|e| AirError::FileSystem(format!("Failed to read directory entry: {}", e)))?
2481 {
2482 let path = entry.path();
2483
2484 let metadata = entry
2485 .metadata()
2486 .await
2487 .map_err(|e| AirError::FileSystem(format!("Failed to get metadata: {}", e)))?;
2488
2489 if path.is_dir()
2491 || metadata.modified().unwrap_or(now)
2492 > now.checked_sub(Duration::from_secs(7 * 24 * 3600)).unwrap_or(now)
2493 {
2494 continue;
2495 }
2496
2497 dev_log!("update", "[UpdateManager] Removing old update file: {:?}", path);
2498
2499 tokio::fs::remove_file(&path)
2500 .await
2501 .map_err(|e| AirError::FileSystem(format!("Failed to remove {}: {}", path.display(), e)))?;
2502
2503 cleaned_count += 1;
2504 }
2505
2506 dev_log!("update", "[UpdateManager] Cleaned up {} old update files", cleaned_count);
2507
2508 Ok(())
2509 }
2510
2511 pub fn GetCacheDirectory(&self) -> &PathBuf { &self.cache_directory }
2513
2514 pub async fn StartBackgroundTasks(&self) -> Result<()> {
2524 let manager = self.clone();
2525
2526 let handle = tokio::spawn(async move {
2527 manager.BackgroundTask().await;
2528 });
2529
2530 let mut task_handle = self.background_task.lock().await;
2532
2533 *task_handle = Some(handle);
2534
2535 dev_log!("update", "[UpdateManager] Background update checking started");
2536
2537 Ok(())
2538 }
2539
2540 async fn BackgroundTask(&self) {
2547 let config = &self.AppState.Configuration.Updates;
2548
2549 if !config.Enabled {
2550 dev_log!("update", "[UpdateManager] Background task: Updates are disabled");
2551
2552 return;
2553 }
2554
2555 let check_interval = Duration::from_secs(config.CheckIntervalHours as u64 * 3600);
2556
2557 let mut interval = interval(check_interval);
2558
2559 dev_log!(
2560 "update",
2561 "[UpdateManager] Background task: Checking for updates every {} hours",
2562 config.CheckIntervalHours
2563 );
2564
2565 loop {
2566 interval.tick().await;
2567
2568 dev_log!("update", "[UpdateManager] Background task: Checking for updates...");
2569
2570 match self.CheckForUpdates().await {
2572 Ok(Some(update_info)) => {
2573 dev_log!(
2574 "update",
2575 "[UpdateManager] Background task: Update available: {}",
2576 update_info.version
2577 );
2578 },
2579
2580 Ok(None) => {
2581 dev_log!("update", "[UpdateManager] Background task: No updates available");
2582 },
2583
2584 Err(e) => {
2585 dev_log!("update", "error: [UpdateManager] Background task: Update check failed: {}", e);
2586 },
2587 }
2588 }
2589 }
2590
2591 pub async fn StopBackgroundTasks(&self) {
2597 dev_log!("update", "[UpdateManager] Stopping background tasks");
2598
2599 let mut task_handle = self.background_task.lock().await;
2601
2602 if let Some(handle) = task_handle.take() {
2603 handle.abort();
2604
2605 dev_log!("update", "[UpdateManager] Background task aborted");
2606 } else {
2607 dev_log!("update", "[UpdateManager] No background task to stop");
2608 }
2609 }
2610
2611 fn format_size(&self, bytes:f64) -> String {
2619 const KB:f64 = 1024.0;
2620
2621 const MB:f64 = KB * 1024.0;
2622
2623 const GB:f64 = MB * 1024.0;
2624
2625 if bytes >= GB {
2626 format!("{:.2} GB/s", bytes / GB)
2627 } else if bytes >= MB {
2628 format!("{:.2} MB/s", bytes / MB)
2629 } else if bytes >= KB {
2630 format!("{:.2} KB/s", bytes / KB)
2631 } else {
2632 format!("{:.0} B/s", bytes)
2633 }
2634 }
2635}
2636
2637impl Clone for UpdateManager {
2638 fn clone(&self) -> Self {
2639 Self {
2640 AppState:self.AppState.clone(),
2641
2642 update_status:self.update_status.clone(),
2643
2644 cache_directory:self.cache_directory.clone(),
2645
2646 staging_directory:self.staging_directory.clone(),
2647
2648 backup_directory:self.backup_directory.clone(),
2649
2650 download_sessions:self.download_sessions.clone(),
2651
2652 rollback_history:self.rollback_history.clone(),
2653
2654 update_channel:self.update_channel,
2655
2656 platform_config:self.platform_config.clone(),
2657
2658 background_task:self.background_task.clone(),
2659 }
2660 }
2661}