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 session_id:String,
136
137 download_url:String,
139
140 temp_path:PathBuf,
142
143 downloaded_bytes:u64,
145
146 total_bytes:u64,
148
149 complete:bool,
151
152 cancelled:bool,
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
158struct RollbackHistory {
159 versions:Vec<RollbackState>,
161
162 max_versions:usize,
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct RollbackState {
168 version:String,
169
170 backup_path:PathBuf,
171
172 timestamp:chrono::DateTime<chrono::Utc>,
173
174 checksum:String,
175}
176
177#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
179pub enum UpdateChannel {
180 Stable,
181
182 Insiders,
183
184 Preview,
185}
186
187impl UpdateChannel {
188 fn as_str(&self) -> &'static str {
189 match self {
190 UpdateChannel::Stable => "stable",
191
192 UpdateChannel::Insiders => "insiders",
193
194 UpdateChannel::Preview => "preview",
195 }
196 }
197}
198
199#[derive(Debug, Clone)]
201struct PlatformConfig {
202 platform:String,
203
204 arch:String,
205
206 package_format:PackageFormat,
207}
208
209#[derive(Debug, Clone, Copy)]
211enum PackageFormat {
212 WindowsExe,
213
214 MacOsDmg,
215
216 LinuxAppImage,
217
218 LinuxDeb,
219
220 LinuxRpm,
221}
222
223#[derive(Debug, Clone, Serialize, Deserialize)]
225pub struct UpdateStatus {
226 pub last_check:Option<chrono::DateTime<chrono::Utc>>,
228
229 pub update_available:bool,
231
232 pub current_version:String,
234
235 pub available_version:Option<String>,
237
238 pub download_progress:Option<f32>,
240
241 pub installation_status:InstallationStatus,
243
244 pub update_channel:UpdateChannel,
246
247 pub update_size:Option<u64>,
249
250 pub release_notes:Option<String>,
252
253 pub requires_restart:bool,
255
256 pub download_speed:Option<f64>,
258
259 pub eta_seconds:Option<u64>,
261
262 pub last_error:Option<String>,
264}
265
266#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
268pub enum InstallationStatus {
269 NotStarted,
271
272 CheckingPrerequisites,
274
275 Downloading,
277
278 Paused,
280
281 VerifyingSignature,
283
284 VerifyingChecksums,
286
287 Staging,
289
290 CreatingBackup,
292
293 Installing,
295
296 Completed,
298
299 RollingBack,
301
302 Failed(String),
304}
305
306impl InstallationStatus {
307 pub fn is_cancellable(&self) -> bool {
309 matches!(
310 self,
311 InstallationStatus::Downloading
312 | InstallationStatus::Paused
313 | InstallationStatus::Staging
314 | InstallationStatus::NotStarted
315 )
316 }
317
318 pub fn is_error(&self) -> bool { matches!(self, InstallationStatus::Failed(_)) }
320
321 pub fn is_in_progress(&self) -> bool {
323 matches!(
324 self,
325 InstallationStatus::CheckingPrerequisites
326 | InstallationStatus::Downloading
327 | InstallationStatus::VerifyingSignature
328 | InstallationStatus::VerifyingChecksums
329 | InstallationStatus::Staging
330 | InstallationStatus::CreatingBackup
331 | InstallationStatus::Installing
332 )
333 }
334}
335
336#[derive(Debug, Clone, Serialize, Deserialize)]
338pub struct UpdateInfo {
339 pub version:String,
341
342 pub download_url:String,
344
345 pub release_notes:String,
347
348 pub checksum:String,
350
351 pub checksums:HashMap<String, String>,
353
354 pub size:u64,
356
357 pub published_at:chrono::DateTime<chrono::Utc>,
359
360 pub is_mandatory:bool,
362
363 pub requires_restart:bool,
365
366 pub min_compatible_version:Option<String>,
368
369 pub delta_url:Option<String>,
371
372 pub delta_checksum:Option<String>,
374
375 pub delta_size:Option<u64>,
377
378 pub signature:Option<String>,
380
381 pub platform_metadata:Option<PlatformMetadata>,
383}
384
385#[derive(Debug, Clone, Serialize, Deserialize)]
387pub struct PlatformMetadata {
388 pub package_format:String,
390
391 pub install_instructions:Vec<String>,
393
394 pub required_disk_space:u64,
396
397 pub requires_admin:bool,
399
400 pub additional_params:HashMap<String, serde_json::Value>,
402}
403
404#[derive(Debug, Clone, Serialize, Deserialize)]
406pub struct UpdateTelemetry {
407 pub event_id:String,
409
410 pub current_version:String,
412
413 pub target_version:String,
415
416 pub channel:String,
418
419 pub platform:String,
421
422 pub operation:String,
424
425 pub success:bool,
427
428 pub duration_ms:u64,
430
431 pub download_size:Option<u64>,
433
434 pub error_message:Option<String>,
436
437 pub timestamp:chrono::DateTime<chrono::Utc>,
439}
440
441impl UpdateManager {
442 pub async fn new(AppState:Arc<ApplicationState>) -> Result<Self> {
444 let config = &AppState.Configuration.Updates;
445
446 let cache_directory = ConfigurationManager::ExpandPath(&AppState.Configuration.Downloader.CacheDirectory)?;
448
449 tokio::fs::create_dir_all(&cache_directory)
451 .await
452 .map_err(|e| AirError::Configuration(format!("Failed to create cache directory: {}", e)))?;
453
454 let staging_directory = cache_directory.join("staging");
456
457 tokio::fs::create_dir_all(&staging_directory)
458 .await
459 .map_err(|e| AirError::Configuration(format!("Failed to create staging directory: {}", e)))?;
460
461 let backup_directory = cache_directory.join("backups");
463
464 tokio::fs::create_dir_all(&backup_directory)
465 .await
466 .map_err(|e| AirError::Configuration(format!("Failed to create backup directory: {}", e)))?;
467
468 let PlatformConfig = Self::detect_platform();
470
471 let PlatformConfigClone = PlatformConfig.clone();
472
473 let update_channel = if config.Channel == "insiders" {
475 UpdateChannel::Insiders
476 } else if config.Channel == "preview" {
477 UpdateChannel::Preview
478 } else {
479 UpdateChannel::Stable
480 };
481
482 let rollback_history_path = backup_directory.join("rollback_history.json");
484
485 let rollback_history = if rollback_history_path.exists() {
486 let content = tokio::fs::read_to_string(&rollback_history_path)
487 .await
488 .map_err(|e| AirError::FileSystem(format!("Failed to read rollback history: {}", e)))?;
489
490 serde_json::from_str(&content).unwrap_or_else(|_| RollbackHistory { versions:Vec::new(), max_versions:5 })
491 } else {
492 RollbackHistory { versions:Vec::new(), max_versions:5 }
493 };
494
495 let manager = Self {
496 AppState,
497
498 update_status:Arc::new(RwLock::new(UpdateStatus {
499 last_check:None,
500 update_available:false,
501 current_version:env!("CARGO_PKG_VERSION").to_string(),
502 available_version:None,
503 download_progress:None,
504 installation_status:InstallationStatus::NotStarted,
505 update_channel,
506 update_size:None,
507 release_notes:None,
508 requires_restart:true,
509 download_speed:None,
510 eta_seconds:None,
511 last_error:None,
512 })),
513
514 cache_directory,
515
516 staging_directory,
517
518 backup_directory,
519
520 download_sessions:Arc::new(RwLock::new(HashMap::new())),
521
522 rollback_history:Arc::new(Mutex::new(rollback_history)),
523
524 update_channel,
525
526 platform_config:PlatformConfigClone,
527
528 background_task:Arc::new(Mutex::new(None)),
529 };
530
531 manager
533 .AppState
534 .UpdateServiceStatus("updates", crate::ApplicationState::ServiceStatus::Running)
535 .await
536 .map_err(|e| AirError::Internal(e.to_string()))?;
537
538 dev_log!(
539 "update",
540 "[UpdateManager] Update service initialized for platform: {}/{}",
541 PlatformConfig.platform,
542 PlatformConfig.arch
543 );
544
545 Ok(manager)
546 }
547
548 fn detect_platform() -> PlatformConfig {
550 let platform = if cfg!(target_os = "windows") {
551 "windows"
552 } else if cfg!(target_os = "macos") {
553 "macos"
554 } else if cfg!(target_os = "linux") {
555 "linux"
556 } else {
557 "unknown"
558 };
559
560 let arch = if cfg!(target_arch = "x86_64") {
561 "x64"
562 } else if cfg!(target_arch = "aarch64") {
563 "arm64"
564 } else if cfg!(target_arch = "x86") {
565 "ia32"
566 } else {
567 "unknown"
568 };
569
570 let package_format = match (platform, arch) {
571 ("windows", _) => PackageFormat::WindowsExe,
572
573 ("macos", _) => PackageFormat::MacOsDmg,
574
575 ("linux", "x64") => PackageFormat::LinuxAppImage,
576
577 ("linux", "") => PackageFormat::LinuxAppImage,
578
579 _ => PackageFormat::LinuxAppImage,
580 };
581
582 PlatformConfig { platform:platform.to_string(), arch:arch.to_string(), package_format }
583 }
584
585 pub async fn CheckForUpdates(&self) -> Result<Option<UpdateInfo>> {
595 let config = &self.AppState.Configuration.Updates;
596
597 let start_time = std::time::Instant::now();
598
599 if !config.Enabled {
600 dev_log!("update", "[UpdateManager] Updates are disabled");
601
602 return Ok(None);
603 }
604
605 dev_log!(
606 "update",
607 "[UpdateManager] Checking for updates on {} channel",
608 self.update_channel.as_str()
609 );
610
611 {
613 let mut status = self.update_status.write().await;
614
615 status.last_check = Some(chrono::Utc::now());
616
617 status.last_error = None;
618 }
619
620 let update_info = match self.FetchUpdateInfo().await {
622 Ok(info) => info,
623
624 Err(e) => {
625 dev_log!("update", "error: [UpdateManager] Failed to fetch update info: {}", e);
626
627 let mut status = self.update_status.write().await;
628
629 status.last_error = Some(e.to_string());
630
631 self.record_telemetry(
632 "check",
633 false,
634 start_time.elapsed().as_millis() as u64,
635 None,
636 Some(e.to_string()),
637 )
638 .await;
639
640 return Err(e);
641 },
642 };
643
644 if let Some(ref info) = update_info {
645 if let Some(ref min_version) = info.min_compatible_version {
647 let current_version = env!("CARGO_PKG_VERSION");
648
649 if UpdateManager::CompareVersions(current_version, min_version) < 0 {
650 dev_log!(
651 "update",
652 "warn: [UpdateManager] Update requires minimum version {} but current is {}. Skipping.",
653 min_version,
654 current_version
655 );
656
657 let mut status = self.update_status.write().await;
658
659 status.last_error = Some(format!("Update requires minimum version {}", min_version));
660
661 return Ok(None);
662 }
663 }
664
665 dev_log!(
666 "update",
667 "[UpdateManager] Update available: {} ({})",
668 info.version,
669 self.format_size(info.size as f64)
670 );
671
672 {
674 let mut status = self.update_status.write().await;
675
676 status.update_available = true;
677
678 status.available_version = Some(info.version.clone());
679
680 status.update_size = Some(info.size);
681
682 status.release_notes = Some(info.release_notes.clone());
683
684 status.requires_restart = info.requires_restart;
685 }
686
687 dev_log!("update", "[UpdateManager] Notifying frontend about available update");
690
691 self.record_telemetry("check", true, start_time.elapsed().as_millis() as u64, None, None)
693 .await;
694
695 if config.AutoDownload {
697 if let Err(e) = self.DownloadUpdate(info).await {
698 dev_log!("update", "error: [UpdateManager] Auto-download failed: {}", e); }
700 }
701 } else {
702 dev_log!("update", "[UpdateManager] No updates available");
703
704 {
706 let mut status = self.update_status.write().await;
707
708 status.update_available = false;
709
710 status.available_version = None;
711
712 status.update_size = None;
713
714 status.release_notes = None;
715 }
716
717 self.record_telemetry("check", true, start_time.elapsed().as_millis() as u64, None, None)
719 .await;
720 }
721
722 Ok(update_info)
723 }
724
725 pub async fn DownloadUpdate(&self, update_info:&UpdateInfo) -> Result<()> {
741 let start_time = std::time::Instant::now();
742
743 let session_id = Uuid::new_v4().to_string();
744
745 dev_log!(
746 "update",
747 "[UpdateManager] Starting download for version {} (session: {})",
748 update_info.version,
749 session_id
750 );
751
752 let required_space = update_info.size * 2; self.ValidateDiskSpace(required_space).await?;
756
757 {
759 let mut status = self.update_status.write().await;
760
761 status.installation_status = InstallationStatus::CheckingPrerequisites;
762
763 status.last_error = None;
764 }
765
766 let temp_path = self.cache_directory.join(format!("update-{}-temp.bin", update_info.version));
768
769 let final_path = self.cache_directory.join(format!("update-{}.bin", update_info.version));
770
771 let (downloaded_bytes, resume_from_start) = if temp_path.exists() {
773 let metadata = tokio::fs::metadata(&temp_path)
774 .await
775 .map_err(|e| AirError::FileSystem(format!("Failed to check temp file: {}", e)))?;
776
777 dev_log!(
778 "update",
779 "[UpdateManager] Found partial download, resuming from {} bytes",
780 metadata.len()
781 );
782
783 (metadata.len(), false)
784 } else {
785 (0, true)
786 };
787
788 {
790 let mut sessions = self.download_sessions.write().await;
791
792 sessions.insert(
793 session_id.clone(),
794 DownloadSession {
795 session_id:session_id.clone(),
796 download_url:update_info.download_url.clone(),
797 temp_path:temp_path.clone(),
798 downloaded_bytes,
799 total_bytes:update_info.size,
800 complete:false,
801 cancelled:false,
802 },
803 );
804 }
805
806 let dns_port = Mist::dns_port();
808
809 let client = crate::HTTP::Client::secured_client_with_timeout(dns_port, Duration::from_secs(300))
810 .map_err(|e| AirError::Network(format!("Failed to create HTTP client: {}", e)))?;
811
812 let mut request_builder = client.get(&update_info.download_url);
813
814 if !resume_from_start {
816 request_builder = request_builder.header("Range", format!("bytes={}-", downloaded_bytes));
817 }
818
819 let response:reqwest::Response = request_builder
820 .send()
821 .await
822 .map_err(|e| AirError::Network(format!("Failed to start download: {}", e)))?;
823
824 if !response.status().is_success() && response.status() != 206 {
825 dev_log!(
826 "update",
827 "error: [UpdateManager] Download failed with status: {}",
828 response.status()
829 );
830
831 let mut status = self.update_status.write().await;
832
833 status.installation_status =
834 InstallationStatus::Failed(format!("Download failed with status: {}", response.status()));
835
836 status.last_error = Some(format!("Download failed with status: {}", response.status()));
837
838 self.record_telemetry(
839 "download",
840 false,
841 start_time.elapsed().as_millis() as u64,
842 None,
843 Some("Download failed".to_string()),
844 )
845 .await;
846
847 return Err(AirError::Network(format!("Download failed with status: {}", response.status())));
848 }
849
850 let total_size = response.content_length().unwrap_or(update_info.size);
851
852 let initial_downloaded = if resume_from_start { 0 } else { downloaded_bytes };
853
854 {
856 let mut status = self.update_status.write().await;
857
858 status.installation_status = InstallationStatus::Downloading;
859
860 status.download_progress = Some(((downloaded_bytes as f32 / total_size as f32) * 100.0).min(100.0));
861 }
862
863 let last_update = Arc::new(Mutex::new(std::time::Instant::now()));
865
866 let last_bytes = Arc::new(Mutex::new(downloaded_bytes));
867
868 let mut file = if resume_from_start {
870 tokio::fs::File::create(&temp_path)
871 .await
872 .map_err(|e| AirError::FileSystem(format!("Failed to create update file: {}", e)))?
873 } else {
874 tokio::fs::OpenOptions::new()
876 .append(true)
877 .open(&temp_path)
878 .await
879 .map_err(|e| AirError::FileSystem(format!("Failed to open update file for resume: {}", e)))?
880 };
881
882 use tokio::io::AsyncWriteExt;
883 use futures_util::StreamExt;
884
885 let mut byte_stream = response.bytes_stream();
886
887 let mut downloaded = initial_downloaded;
888
889 while let Some(chunk_result) = byte_stream.next().await {
890 match chunk_result {
891 Ok(chunk) => {
892 let chunk_bytes:&[u8] = &chunk;
893
894 file.write_all(chunk_bytes)
895 .await
896 .map_err(|e| AirError::FileSystem(format!("Failed to write update file: {}", e)))?;
897
898 downloaded += chunk.len() as u64;
899
900 {
902 let mut last_update_guard = last_update.lock().await;
903
904 let mut last_bytes_guard = last_bytes.lock().await;
905
906 if last_update_guard.elapsed() >= Duration::from_secs(1) {
907 let bytes_this_second = downloaded - *last_bytes_guard;
908
909 let download_speed = bytes_this_second as f64;
910
911 let progress = ((downloaded as f32 / total_size as f32) * 100.0).min(100.0);
912
913 let remaining_bytes = total_size - downloaded;
914
915 let eta_seconds = if download_speed > 0.0 {
916 Some(remaining_bytes as u64 / (download_speed as u64).max(1))
917 } else {
918 None
919 };
920
921 {
922 let mut status = self.update_status.write().await;
923
924 status.download_progress = Some(progress);
925
926 status.download_speed = Some(download_speed);
927
928 status.eta_seconds = eta_seconds;
929 }
930
931 dev_log!(
932 "update",
933 "[UpdateManager] Download progress: {:.1}% ({}/s, ETA: {:?})",
934 progress,
935 self.format_size(download_speed),
936 eta_seconds
937 );
938
939 *last_update_guard = std::time::Instant::now();
940 *last_bytes_guard = downloaded;
941 }
942 }
943
944 {
946 let mut sessions = self.download_sessions.write().await;
947
948 if let Some(session) = sessions.get_mut(&session_id) {
949 session.downloaded_bytes = downloaded;
950 }
951 }
952 },
953
954 Err(e) => {
955 dev_log!("update", "error: [UpdateManager] Download error: {}", e);
956
957 let mut status = self.update_status.write().await;
958
959 status.installation_status = InstallationStatus::Failed(format!("Network error: {}", e));
960
961 status.last_error = Some(format!("Network error: {}", e));
962
963 self.record_telemetry(
964 "download",
965 false,
966 start_time.elapsed().as_millis() as u64,
967 None,
968 Some(e.to_string()),
969 )
970 .await;
971
972 return Err(AirError::Network(format!("Download error: {}", e)));
973 },
974 }
975 }
976
977 {
979 let mut status = self.update_status.write().await;
980
981 status.installation_status = InstallationStatus::Downloading;
982
983 status.download_progress = Some(100.0);
984 }
985
986 dev_log!(
987 "update",
988 "[UpdateManager] Download completed: {} bytes in {:.2}s",
989 downloaded,
990 start_time.elapsed().as_secs_f64()
991 );
992
993 {
995 let mut status = self.update_status.write().await;
996
997 status.installation_status = InstallationStatus::VerifyingChecksums;
998 }
999
1000 self.VerifyChecksum(&temp_path, &update_info.checksum).await?;
1001
1002 for (algorithm, expected_checksum) in &update_info.checksums {
1004 self.VerifyChecksumWithAlgorithm(&temp_path, algorithm, expected_checksum)
1005 .await?;
1006 }
1007
1008 if let Some(ref signature) = update_info.signature {
1010 {
1011 let mut status = self.update_status.write().await;
1012
1013 status.installation_status = InstallationStatus::VerifyingSignature;
1014 }
1015
1016 self.VerifySignature(&temp_path, signature).await?;
1017 }
1018
1019 if temp_path.exists() {
1021 tokio::fs::rename(&temp_path, &final_path)
1022 .await
1023 .map_err(|e| AirError::FileSystem(format!("Failed to finalize download: {}", e)))?;
1024 }
1025
1026 {
1028 let mut sessions = self.download_sessions.write().await;
1029
1030 if let Some(session) = sessions.get_mut(&session_id) {
1031 session.complete = true;
1032 }
1033 }
1034
1035 {
1037 let mut status = self.update_status.write().await;
1038
1039 status.installation_status = InstallationStatus::Completed;
1040
1041 status.download_progress = Some(100.0);
1042 }
1043
1044 dev_log!(
1045 "update",
1046 "[UpdateManager] Update {} downloaded and verified successfully",
1047 update_info.version
1048 );
1049
1050 self.record_telemetry(
1052 "download",
1053 true,
1054 start_time.elapsed().as_millis() as u64,
1055 Some(downloaded),
1056 None,
1057 )
1058 .await;
1059
1060 Ok(())
1061 }
1062
1063 pub async fn ApplyUpdate(&self, update_info:&UpdateInfo) -> Result<()> {
1078 let start_time = std::time::Instant::now();
1079
1080 let current_version = env!("CARGO_PKG_VERSION");
1081
1082 dev_log!(
1083 "update",
1084 "[UpdateManager] Applying update: {} (from {})",
1085 update_info.version,
1086 current_version
1087 );
1088
1089 let file_path = self.cache_directory.join(format!("update-{}.bin", update_info.version));
1090
1091 if !file_path.exists() {
1093 dev_log!("update", "error: [UpdateManager] Update file not found: {:?}", file_path);
1094
1095 return Err(AirError::FileSystem(
1096 "Update file not found. Please download first.".to_string(),
1097 ));
1098 }
1099
1100 {
1102 let mut status = self.update_status.write().await;
1103
1104 status.installation_status = InstallationStatus::VerifyingChecksums;
1105
1106 status.last_error = None;
1107 }
1108
1109 self.VerifyChecksum(&file_path, &update_info.checksum).await?;
1111
1112 for (algorithm, expected_checksum) in &update_info.checksums {
1114 self.VerifyChecksumWithAlgorithm(&file_path, algorithm, expected_checksum)
1115 .await?;
1116 }
1117
1118 if let Some(ref signature) = update_info.signature {
1120 {
1121 let mut status = self.update_status.write().await;
1122
1123 status.installation_status = InstallationStatus::VerifyingSignature;
1124 }
1125
1126 self.VerifySignature(&file_path, signature).await?;
1127 }
1128
1129 {
1131 let mut status = self.update_status.write().await;
1132
1133 status.installation_status = InstallationStatus::CreatingBackup;
1134 }
1135
1136 let backup_info = self.CreateBackup(current_version).await?;
1137
1138 dev_log!("update", "[UpdateManager] Backup created: {:?}", backup_info.backup_path);
1139
1140 {
1142 let mut status = self.update_status.write().await;
1143
1144 status.installation_status = InstallationStatus::Installing;
1145 }
1146
1147 let result = match self.platform_config.package_format {
1149 #[cfg(target_os = "windows")]
1150 PackageFormat::WindowsExe => self.ApplyWindowsUpdate(&file_path).await,
1151
1152 #[cfg(not(target_os = "windows"))]
1153 PackageFormat::WindowsExe => Err(AirError::Internal("Windows update not available on this platform".to_string())),
1154
1155 PackageFormat::MacOsDmg => self.ApplyMacOsUpdate(&file_path).await,
1156
1157 #[cfg(all(target_os = "linux", feature = "appimage"))]
1158 PackageFormat::LinuxAppImage => self.ApplyLinuxAppImageUpdate(&file_path).await,
1159
1160 #[cfg(not(all(target_os = "linux", feature = "appimage")))]
1161 PackageFormat::LinuxAppImage => {
1162 Err(AirError::Internal(
1163 "Linux AppImage update not available on this platform".to_string(),
1164 ))
1165 },
1166
1167 #[cfg(all(target_os = "linux", feature = "deb"))]
1168 PackageFormat::LinuxDeb => self.ApplyLinuxDebUpdate(&file_path).await,
1169
1170 #[cfg(not(all(target_os = "linux", feature = "deb")))]
1171 PackageFormat::LinuxDeb => {
1172 Err(AirError::Internal(
1173 "Linux DEB update not available on this platform".to_string(),
1174 ))
1175 },
1176
1177 #[cfg(all(target_os = "linux", feature = "rpm"))]
1178 PackageFormat::LinuxRpm => self.ApplyLinuxRpmUpdate(&file_path).await,
1179
1180 #[cfg(not(all(target_os = "linux", feature = "rpm")))]
1181 PackageFormat::LinuxRpm => {
1182 Err(AirError::Internal(
1183 "Linux RPM update not available on this platform".to_string(),
1184 ))
1185 },
1186 };
1187
1188 if let Err(e) = result {
1189 dev_log!(
1190 "update",
1191 "error: [UpdateManager] Installation failed, initiating rollback: {}",
1192 e
1193 );
1194
1195 {
1197 let mut status = self.update_status.write().await;
1198
1199 status.installation_status = InstallationStatus::RollingBack;
1200 }
1201
1202 if let Err(rollback_err) = self.RollbackToBackup(&backup_info).await {
1204 dev_log!("update", "error: [UpdateManager] Rollback also failed: {}", rollback_err);
1205
1206 let mut status = self.update_status.write().await;
1208
1209 status.installation_status = InstallationStatus::Failed(format!(
1210 "Installation failed and rollback failed: {} / {}",
1211 e, rollback_err
1212 ));
1213
1214 status.last_error = Some(format!("Installation failed and rollback failed"));
1215
1216 self.record_telemetry(
1217 "install",
1218 false,
1219 start_time.elapsed().as_millis() as u64,
1220 None,
1221 Some(format!("Update and rollback failed: {}", rollback_err)),
1222 )
1223 .await;
1224
1225 return Err(AirError::Internal(format!(
1226 "Installation failed and rollback failed: {} / {}",
1227 e, rollback_err
1228 )));
1229 } else {
1230 dev_log!("update", "[UpdateManager] Rollback successful");
1231
1232 let mut status = self.update_status.write().await;
1233
1234 status.installation_status =
1235 InstallationStatus::Failed(format!("Installation failed, rollback successful: {}", e));
1236
1237 status.last_error = Some(e.to_string());
1238
1239 self.record_telemetry(
1240 "install",
1241 false,
1242 start_time.elapsed().as_millis() as u64,
1243 None,
1244 Some(e.to_string()),
1245 )
1246 .await;
1247
1248 return Err(AirError::Internal(format!("Installation failed, rollback successful: {}", e)));
1249 }
1250 }
1251
1252 {
1254 let mut history = self.rollback_history.lock().await;
1255
1256 history.versions.insert(0, backup_info);
1257
1258 while history.versions.len() > history.max_versions {
1260 if let Some(old_backup) = history.versions.pop() {
1261 let _ = tokio::fs::remove_dir_all(&old_backup.backup_path).await;
1263 }
1264 }
1265 }
1266
1267 let history_path = self.backup_directory.join("rollback_history.json");
1269
1270 let history = self.rollback_history.lock().await;
1271
1272 let history_json = serde_json::to_string(&*history)
1273 .map_err(|e| AirError::Internal(format!("Failed to serialize rollback history: {}", e)))?;
1274
1275 drop(history);
1276
1277 tokio::fs::write(&history_path, history_json)
1278 .await
1279 .map_err(|e| AirError::FileSystem(format!("Failed to save rollback history: {}", e)))?;
1280
1281 {
1283 let mut status = self.update_status.write().await;
1284
1285 status.current_version = update_info.version.clone();
1286
1287 status.installation_status = InstallationStatus::Completed;
1288 }
1289
1290 dev_log!(
1291 "update",
1292 "[UpdateManager] Update {} applied successfully in {:.2}s",
1293 update_info.version,
1294 start_time.elapsed().as_secs_f64()
1295 );
1296
1297 self.record_telemetry(
1299 "install",
1300 true,
1301 start_time.elapsed().as_millis() as u64,
1302 Some(update_info.size),
1303 None,
1304 )
1305 .await;
1306
1307 Ok(())
1308 }
1309
1310 async fn FetchUpdateInfo(&self) -> Result<Option<UpdateInfo>> {
1321 let config = &self.AppState.Configuration.Updates;
1322
1323 let retry_policy = crate::Resilience::RetryPolicy {
1325 MaxRetries:3,
1326
1327 InitialIntervalMs:1000,
1328
1329 MaxIntervalMs:16000,
1330
1331 BackoffMultiplier:2.0,
1332
1333 JitterFactor:0.1,
1334
1335 BudgetPerMinute:50,
1336
1337 ErrorClassification:std::collections::HashMap::new(),
1338 };
1339
1340 let _retry_manager = crate::Resilience::RetryManager::new(retry_policy.clone());
1341
1342 let circuit_breaker = crate::Resilience::CircuitBreaker::new(
1343 "updates".to_string(),
1344 crate::Resilience::CircuitBreakerConfig::default(),
1345 );
1346
1347 let current_version = env!("CARGO_PKG_VERSION");
1348
1349 let mut attempt = 0;
1350
1351 loop {
1352 if circuit_breaker.GetState().await == crate::Resilience::CircuitState::Open {
1354 if !circuit_breaker.AttemptRecovery().await {
1355 dev_log!("update", "warn: [UpdateManager] Circuit breaker is open, skipping update check");
1356
1357 return Ok(None);
1358 }
1359 }
1360
1361 let update_url = format!(
1363 "{}/check?version={}&platform={}&arch={}&channel={}",
1364 config.UpdateServerUrl,
1365 current_version,
1366 self.platform_config.platform,
1367 self.platform_config.arch,
1368 self.update_channel.as_str()
1369 );
1370
1371 let dns_port = Mist::dns_port();
1372
1373 let client = crate::HTTP::Client::secured_client_with_timeout(dns_port, Duration::from_secs(30))
1374 .map_err(|e| AirError::Network(format!("Failed to create HTTP client: {}", e)))?;
1375
1376 match client.get(&update_url).send().await {
1377 Ok(response) => {
1378 let status:reqwest::StatusCode = response.status();
1379
1380 match status {
1381 reqwest::StatusCode::NO_CONTENT => {
1382 circuit_breaker.RecordSuccess().await;
1384
1385 dev_log!("update", "[UpdateManager] Server reports no updates available");
1386
1387 return Ok(None);
1388 },
1389
1390 status if status.is_success() => {
1391 match response.json::<UpdateInfo>().await {
1393 Ok(update_info) => {
1394 circuit_breaker.RecordSuccess().await;
1395
1396 if UpdateManager::CompareVersions(current_version, &update_info.version) < 0 {
1398 dev_log!(
1399 "update",
1400 "[UpdateManager] Update available: {} -> {}",
1401 current_version,
1402 update_info.version
1403 );
1404
1405 return Ok(Some(update_info));
1406 } else {
1407 dev_log!(
1408 "update",
1409 "[UpdateManager] Server returned same or older version: {}",
1410 update_info.version
1411 );
1412
1413 return Ok(None);
1414 }
1415 },
1416
1417 Err(e) => {
1418 circuit_breaker.RecordFailure().await;
1419
1420 dev_log!("update", "error: [UpdateManager] Failed to parse update info: {}", e);
1421
1422 if attempt < retry_policy.MaxRetries {
1423 attempt += 1;
1424
1425 let delay = Duration::from_millis(
1426 retry_policy.InitialIntervalMs * 2_u32.pow(attempt as u32) as u64,
1427 );
1428
1429 sleep(delay).await;
1430
1431 continue;
1432 } else {
1433 return Err(AirError::Network(format!(
1434 "Failed to parse update info after retries: {}",
1435 e
1436 )));
1437 }
1438 },
1439 }
1440 },
1441
1442 status => {
1443 circuit_breaker.RecordFailure().await;
1444
1445 dev_log!("update", "warn: [UpdateManager] Update server returned status: {}", status);
1446
1447 if attempt < retry_policy.MaxRetries {
1448 attempt += 1;
1449
1450 let delay = Duration::from_millis(
1451 retry_policy.InitialIntervalMs * 2_u32.pow(attempt as u32) as u64,
1452 );
1453
1454 sleep(delay).await;
1455
1456 continue;
1457 } else {
1458 return Ok(None);
1459 }
1460 },
1461 }
1462 },
1463
1464 Err(e) => {
1465 circuit_breaker.RecordFailure().await;
1466
1467 dev_log!("update", "warn: [UpdateManager] Failed to check for updates: {}", e);
1468
1469 if attempt < retry_policy.MaxRetries {
1470 attempt += 1;
1471
1472 let delay =
1473 Duration::from_millis(retry_policy.InitialIntervalMs * 2_u32.pow(attempt as u32) as u64);
1474
1475 sleep(delay).await;
1476
1477 continue;
1478 } else {
1479 return Ok(None);
1480 }
1481 },
1482 }
1483 }
1484 }
1485
1486 async fn VerifyChecksum(&self, file_path:&Path, expected_checksum:&str) -> Result<()> {
1500 let content = tokio::fs::read(file_path)
1501 .await
1502 .map_err(|e| AirError::FileSystem(format!("Failed to read update file for checksum: {}", e)))?;
1503
1504 let actual_checksum = self.CalculateSha256(&content);
1505
1506 if actual_checksum.to_lowercase() != expected_checksum.to_lowercase() {
1507 dev_log!(
1508 "update",
1509 "error: [UpdateManager] Checksum verification failed: expected {}, got {}",
1510 expected_checksum,
1511 actual_checksum
1512 );
1513
1514 return Err(AirError::Network("Update checksum verification failed".to_string()));
1515 }
1516
1517 dev_log!("update", "[UpdateManager] Checksum verified: {}", actual_checksum);
1518
1519 Ok(())
1520 }
1521
1522 async fn VerifyChecksumWithAlgorithm(&self, file_path:&Path, algorithm:&str, expected_checksum:&str) -> Result<()> {
1535 let content = tokio::fs::read(file_path).await.map_err(|e| {
1536 AirError::FileSystem(format!("Failed to read update file for {} checksum: {}", algorithm, e))
1537 })?;
1538
1539 let actual_checksum = match algorithm.to_lowercase().as_str() {
1540 "sha256" => self.CalculateSha256(&content),
1541
1542 "sha512" => self.CalculateSha512(&content),
1543
1544 "md5" => self.CalculateMd5(&content),
1545
1546 "crc32" => self.CalculateCrc32(&content),
1547
1548 _ => {
1549 dev_log!(
1550 "update",
1551 "warn: [UpdateManager] Unknown checksum algorithm: {}, skipping",
1552 algorithm
1553 );
1554
1555 return Ok(());
1556 },
1557 };
1558
1559 if actual_checksum.to_lowercase() != expected_checksum.to_lowercase() {
1560 dev_log!(
1561 "update",
1562 "error: [UpdateManager] {} checksum verification failed: expected {}, got {}",
1563 algorithm,
1564 expected_checksum,
1565 actual_checksum
1566 );
1567
1568 return Err(AirError::Network(format!("{} checksum verification failed", algorithm)));
1569 }
1570
1571 dev_log!("update", "[UpdateManager] {} checksum verified: {}", algorithm, actual_checksum);
1572
1573 Ok(())
1574 }
1575
1576 async fn VerifySignature(&self, _file_path:&Path, _signature:&str) -> Result<()> {
1590 #[cfg(debug_assertions)]
1599 {
1600 dev_log!("update", "[UpdateManager] Development build: skipping signature verification");
1601
1602 return Ok(());
1603 }
1604
1605 #[cfg(not(debug_assertions))]
1608 {
1609 dev_log!(
1610 "update",
1611 "warn: [UpdateManager] WARNING: Cryptographic signature verification is not yet implemented"
1612 );
1613
1614 dev_log!(
1615 "update",
1616 "warn: [UpdateManager] Update packages should be cryptographically signed in production"
1617 );
1618
1619 dev_log!(
1620 "update",
1621 "[UpdateManager] Proceeding with update without signature verification"
1622 );
1623
1624 return Ok(());
1625 }
1626 }
1627
1628 async fn CreateBackup(&self, version:&str) -> Result<RollbackState> {
1641 let timestamp = chrono::Utc::now();
1642
1643 let backup_dir_name = format!("backup-{}-{}", version, timestamp.format("%Y%m%d_%H%M%S"));
1644
1645 let backup_path = self.backup_directory.join(&backup_dir_name);
1646
1647 dev_log!("update", "[UpdateManager] Creating backup: {}", backup_dir_name);
1648
1649 tokio::fs::create_dir_all(&backup_path)
1651 .await
1652 .map_err(|e| AirError::FileSystem(format!("Failed to create backup directory: {}", e)))?;
1653
1654 let exe_path = std::env::current_exe()
1656 .map_err(|e| AirError::FileSystem(format!("Failed to get executable path: {}", e)))?;
1657
1658 let backup_exe = backup_path.join(exe_path.file_name().unwrap_or_default());
1660
1661 tokio::fs::copy(&exe_path, &backup_exe)
1662 .await
1663 .map_err(|e| AirError::FileSystem(format!("Failed to backup executable: {}", e)))?;
1664
1665 let config_dirs = vec![
1668 dirs::config_dir().unwrap_or_default().join("FIDDEE"),
1669 dirs::home_dir().unwrap_or_default().join(".config/land"),
1670 ];
1671
1672 for config_dir in config_dirs {
1673 if config_dir.exists() {
1674 let backup_config = backup_path.join("config");
1675
1676 let _ = tokio::fs::create_dir_all(&backup_config).await;
1677
1678 let _ = Self::copy_directory_recursive(&config_dir, &backup_config).await;
1679
1680 dev_log!("update", "[UpdateManager] Backed up config directory: {:?}", config_dir);
1681 }
1682 }
1683
1684 let data_dirs = vec![
1686 dirs::data_local_dir().unwrap_or_default().join("FIDDEE"),
1687 dirs::home_dir().unwrap_or_default().join(".local/share/land"),
1688 ];
1689
1690 for data_dir in data_dirs {
1691 if data_dir.exists() {
1692 let backup_data = backup_path.join("data");
1693
1694 let _ = tokio::fs::create_dir_all(&backup_data).await;
1695
1696 let _ = Self::copy_directory_recursive(&data_dir, &backup_data).await;
1697
1698 dev_log!("update", "[UpdateManager] Backed up data directory: {:?}", data_dir);
1699 }
1700 }
1701
1702 let checksum = self.CalculateFileChecksum(&backup_path).await?;
1704
1705 dev_log!("update", "[UpdateManager] Backup created at: {:?}", backup_path);
1706
1707 Ok(RollbackState { version:version.to_string(), backup_path, timestamp, checksum })
1708 }
1709
1710 pub async fn RollbackToBackup(&self, backup_info:&RollbackState) -> Result<()> {
1723 dev_log!(
1724 "update",
1725 "[UpdateManager] Rolling back to version: {} from: {:?}",
1726 backup_info.version,
1727 backup_info.backup_path
1728 );
1729
1730 let current_checksum = self.CalculateFileChecksum(&backup_info.backup_path).await?;
1732
1733 if current_checksum != backup_info.checksum {
1734 return Err(AirError::Internal(format!(
1735 "Backup integrity check failed: expected {}, got {}",
1736 backup_info.checksum, current_checksum
1737 )));
1738 }
1739
1740 let exe_path = std::env::current_exe()
1742 .map_err(|e| AirError::FileSystem(format!("Failed to get executable path: {}", e)))?;
1743
1744 let backup_exe = backup_info.backup_path.join(exe_path.file_name().unwrap_or_default());
1745
1746 if !backup_exe.exists() {
1747 return Err(AirError::FileSystem("Backup executable not found".to_string()));
1748 }
1749
1750 match tokio::fs::copy(&backup_exe, &exe_path).await {
1754 Ok(_) => {
1755 dev_log!("update", "[UpdateManager] Executable restored from backup");
1756 },
1757
1758 Err(e) => {
1759 dev_log!("update", "error: [UpdateManager] Failed to restore executable: {}", e);
1760
1761 dev_log!("update", "warn: [UpdateManager] Rollback may require manual intervention");
1762 },
1763 }
1764
1765 let backup_config = backup_info.backup_path.join("config");
1767
1768 if backup_config.exists() {
1769 let config_dirs = vec![
1770 dirs::config_dir().unwrap_or_default().join("FIDDEE"),
1771 dirs::home_dir().unwrap_or_default().join(".config/land"),
1772 ];
1773
1774 for config_dir in config_dirs {
1775 if config_dir.exists() {
1777 let _ = tokio::fs::remove_dir_all(&config_dir).await;
1778 }
1779
1780 let _ = Self::copy_directory_recursive(&backup_config, &config_dir).await;
1781
1782 dev_log!("update", "[UpdateManager] Restored config directory: {:?}", config_dir);
1783 }
1784 }
1785
1786 let backup_data = backup_info.backup_path.join("data");
1788
1789 if backup_data.exists() {
1790 let data_dirs = vec![
1791 dirs::data_local_dir().unwrap_or_default().join("FIDDEE"),
1792 dirs::home_dir().unwrap_or_default().join(".local/share/land"),
1793 ];
1794
1795 for data_dir in data_dirs {
1796 if data_dir.exists() {
1798 let _ = tokio::fs::remove_dir_all(&data_dir).await;
1799 }
1800
1801 let _ = Self::copy_directory_recursive(&backup_data, &data_dir).await;
1802
1803 dev_log!("update", "[UpdateManager] Restored data directory: {:?}", data_dir);
1804 }
1805 }
1806
1807 dev_log!(
1808 "update",
1809 "[UpdateManager] Rollback to version {} completed",
1810 backup_info.version
1811 );
1812
1813 Ok(())
1814 }
1815
1816 pub async fn RollbackToVersion(&self, version:&str) -> Result<()> {
1828 let history = self.rollback_history.lock().await;
1829
1830 let backup_info = history
1831 .versions
1832 .iter()
1833 .find(|state| state.version == version)
1834 .ok_or_else(|| AirError::FileSystem(format!("No backup found for version {}", version)))?;
1835
1836 let info = backup_info.clone();
1837
1838 drop(history);
1839
1840 self.RollbackToBackup(&info).await
1841 }
1842
1843 pub async fn GetAvailableRollbackVersions(&self) -> Vec<String> {
1847 let history = self.rollback_history.lock().await;
1848
1849 history.versions.iter().map(|state| state.version.clone()).collect()
1850 }
1851
1852 async fn ValidateDiskSpace(&self, required_bytes:u64) -> Result<()> {
1862 let metadata = tokio::fs::metadata(&self.cache_directory)
1864 .await
1865 .map_err(|e| AirError::FileSystem(format!("Failed to get cache directory info: {}", e)))?;
1866
1867 if cfg!(target_os = "windows") {
1868 #[cfg(target_os = "windows")]
1870 {
1871 use std::os::windows::fs::MetadataExt;
1872
1873 let free_space = metadata.volume_serial_number() as u64; dev_log!(
1876 "update",
1877 "warn: [UpdateManager] Disk space validation not fully implemented on Windows"
1878 );
1879 }
1880 } else {
1881 #[cfg(not(target_os = "windows"))]
1883 {
1884 use std::os::unix::fs::MetadataExt;
1885
1886 let _device_id = metadata.dev();
1887
1888 let cache_path = self.cache_directory.to_string_lossy();
1890
1891 let free_space = unsafe {
1892 let mut stat:libc::statvfs = std::mem::zeroed();
1893
1894 if libc::statvfs(cache_path.as_ptr() as *const i8, &mut stat) == 0 {
1895 stat.f_bavail as u64 * stat.f_bsize as u64
1896 } else {
1897 u64::MAX }
1899 };
1900
1901 if free_space < required_bytes {
1902 return Err(AirError::Configuration(format!(
1903 "Insufficient disk space: required {} bytes, available {} bytes",
1904 required_bytes, free_space
1905 )));
1906 }
1907
1908 dev_log!(
1909 "update",
1910 "[UpdateManager] Disk space check passed: {} bytes available, {} bytes required",
1911 free_space,
1912 required_bytes
1913 );
1914 }
1915 }
1916
1917 dev_log!(
1918 "update",
1919 "[UpdateManager] Disk space validation passed for required {} bytes",
1920 self.format_size(required_bytes as f64)
1921 );
1922
1923 Ok(())
1924 }
1925
1926 pub async fn verify_update(&self, file_path:&str, update_info:Option<&UpdateInfo>) -> Result<bool> {
1940 let path = PathBuf::from(file_path);
1941
1942 if !path.exists() {
1943 return Ok(false);
1944 }
1945
1946 let metadata = tokio::fs::metadata(&path)
1947 .await
1948 .map_err(|e| AirError::FileSystem(format!("Failed to read update file metadata: {}", e)))?;
1949
1950 if metadata.len() == 0 {
1951 return Ok(false);
1952 }
1953
1954 if let Some(info) = update_info {
1956 if !info.checksum.is_empty() {
1957 let actual_checksum = self.CalculateFileChecksum(&path).await?;
1958
1959 if actual_checksum != info.checksum {
1960 return Err(AirError::Configuration(format!(
1961 "Checksum verification failed: expected {}, got {}",
1962 info.checksum, actual_checksum
1963 )));
1964 }
1965 }
1966
1967 for (algorithm, expected_checksum) in &info.checksums {
1969 self.VerifyChecksumWithAlgorithm(&path, algorithm, expected_checksum).await?;
1970 }
1971
1972 if let Some(expected_size) = Some(info.size) {
1974 if metadata.len() != expected_size {
1975 return Err(AirError::Configuration(format!(
1976 "File size mismatch: expected {}, got {}",
1977 expected_size,
1978 metadata.len()
1979 )));
1980 }
1981 }
1982 }
1983
1984 Ok(true)
1985 }
1986
1987 #[cfg(target_os = "windows")]
1989 async fn ApplyWindowsUpdate(&self, file_path:&Path) -> Result<()> {
1990 dev_log!("update", "[UpdateManager] Installing Windows update: {:?}", file_path);
1991
1992 dev_log!(
2001 "update",
2002 "warn: [UpdateManager] Windows installation: update package ready at {:?}",
2003 file_path
2004 );
2005
2006 dev_log!("update", "[UpdateManager] Manual installation may be required");
2007
2008 Ok(())
2009 }
2010
2011 #[cfg(target_os = "macos")]
2013 async fn ApplyMacOsUpdate(&self, file_path:&Path) -> Result<()> {
2014 dev_log!("update", "[UpdateManager] Installing macOS update: {:?}", file_path);
2015
2016 dev_log!(
2026 "update",
2027 "warn: [UpdateManager] macOS installation: update package ready at {:?}",
2028 file_path
2029 );
2030
2031 dev_log!("update", "[UpdateManager] Manual installation may be required");
2032
2033 Ok(())
2034 }
2035
2036 #[cfg(all(target_os = "linux", feature = "appimage"))]
2038 async fn ApplyLinuxAppImageUpdate(&self, file_path:&Path) -> Result<()> {
2039 dev_log!("update", "[UpdateManager] Installing Linux AppImage update: {:?}", file_path);
2040
2041 dev_log!(
2049 "update",
2050 "warn: [UpdateManager] Linux AppImage installation: update package ready at {:?}",
2051 file_path
2052 );
2053
2054 dev_log!("update", "[UpdateManager] Manual installation may be required");
2055
2056 Ok(())
2057 }
2058
2059 #[cfg(all(target_os = "linux", feature = "deb"))]
2061 async fn ApplyLinuxDebUpdate(&self, file_path:&Path) -> Result<()> {
2062 dev_log!("update", "[UpdateManager] Installing Linux DEB update: {:?}", file_path);
2063
2064 dev_log!(
2071 "update",
2072 "warn: [UpdateManager] Linux DEB installation: update package ready at {:?}",
2073 file_path
2074 );
2075
2076 dev_log!("update", "[UpdateManager] Manual installation may be required");
2077
2078 Ok(())
2079 }
2080
2081 #[cfg(all(target_os = "linux", feature = "rpm"))]
2083 async fn ApplyLinuxRpmUpdate(&self, file_path:&Path) -> Result<()> {
2084 dev_log!("update", "[UpdateManager] Installing Linux RPM update: {:?}", file_path);
2085
2086 dev_log!(
2093 "update",
2094 "warn: [UpdateManager] Linux RPM installation: update package ready at {:?}",
2095 file_path
2096 );
2097
2098 dev_log!("update", "[UpdateManager] Manual installation may be required");
2099
2100 Ok(())
2101 }
2102
2103 async fn record_telemetry(
2117 &self,
2118
2119 operation:&str,
2120
2121 success:bool,
2122
2123 duration_ms:u64,
2124
2125 download_size:Option<u64>,
2126
2127 error_message:Option<String>,
2128 ) {
2129 let telemetry = UpdateTelemetry {
2130 event_id:Uuid::new_v4().to_string(),
2131
2132 current_version:env!("CARGO_PKG_VERSION").to_string(),
2133
2134 target_version:self
2135 .update_status
2136 .read()
2137 .await
2138 .available_version
2139 .clone()
2140 .unwrap_or_else(|| "unknown".to_string()),
2141
2142 channel:self.update_channel.as_str().to_string(),
2143
2144 platform:format!("{}/{}", self.platform_config.platform, self.platform_config.arch),
2145
2146 operation:operation.to_string(),
2147
2148 success,
2149
2150 duration_ms,
2151
2152 download_size,
2153
2154 error_message,
2155
2156 timestamp:chrono::Utc::now(),
2157 };
2158
2159 dev_log!(
2160 "update",
2161 "[UpdateManager] Telemetry: {} {} in {}ms - size: {:?}, success: {}",
2162 operation,
2163 if success { "succeeded" } else { "failed" },
2164 duration_ms,
2165 download_size.map(|s| self.format_size(s as f64)),
2166 success
2167 );
2168
2169 #[cfg(debug_assertions)]
2172 {
2173 if let Ok(telemetry_json) = serde_json::to_string(&telemetry) {
2174 dev_log!("update", "[UpdateManager] Telemetry data: {}", telemetry_json); } else {
2179 dev_log!("update", "error: [UpdateManager] Failed to serialize telemetry");
2180 }
2181 }
2182
2183 #[cfg(not(debug_assertions))]
2185 {
2186 let _ = &telemetry; }
2190 }
2191
2192 fn CalculateSha256(&self, data:&[u8]) -> String {
2194 let mut hasher = Sha256::new();
2198
2199 hasher.update(data);
2200
2201 hex::encode(hasher.finalize())
2202 }
2203
2204 fn CalculateSha512(&self, data:&[u8]) -> String {
2206 use sha2::Sha512;
2207
2208 let mut hasher = Sha512::new();
2209
2210 hasher.update(data);
2211
2212 hex::encode(hasher.finalize())
2213 }
2214
2215 fn CalculateMd5(&self, data:&[u8]) -> String {
2217 let digest = md5::compute(data);
2218
2219 format!("{:x}", digest)
2220 }
2221
2222 fn CalculateCrc32(&self, data:&[u8]) -> String {
2224 let crc = crc32fast::hash(data);
2225
2226 format!("{:08x}", crc)
2227 }
2228
2229 async fn CalculateFileChecksum(&self, path:&Path) -> Result<String> {
2231 let content = tokio::fs::read(path)
2232 .await
2233 .map_err(|e| AirError::FileSystem(format!("Failed to read file for checksum: {}", e)))?;
2234
2235 Ok(self.CalculateSha256(&content))
2236 }
2237
2238 pub fn CompareVersions(v1:&str, v2:&str) -> i32 {
2252 let v1_parts:Vec<u32> = v1.split('.').filter_map(|s| s.parse().ok()).collect();
2253
2254 let v2_parts:Vec<u32> = v2.split('.').filter_map(|s| s.parse().ok()).collect();
2255
2256 for (i, part) in v1_parts.iter().enumerate() {
2257 if i >= v2_parts.len() {
2258 return 1;
2259 }
2260
2261 match part.cmp(&v2_parts[i]) {
2262 std::cmp::Ordering::Greater => return 1,
2263
2264 std::cmp::Ordering::Less => return -1,
2265
2266 std::cmp::Ordering::Equal => continue,
2267 }
2268 }
2269
2270 if v1_parts.len() < v2_parts.len() { -1 } else { 0 }
2271 }
2272
2273 pub async fn GetStatus(&self) -> UpdateStatus {
2277 let status = self.update_status.read().await;
2278
2279 status.clone()
2280 }
2281
2282 pub async fn CancelDownload(&self) -> Result<()> {
2289 let status = self.update_status.write().await;
2290
2291 if status.installation_status != InstallationStatus::Downloading {
2292 return Err(AirError::Internal("No download in progress".to_string()));
2293 }
2294
2295 {
2297 let mut sessions = self.download_sessions.write().await;
2298
2299 for session in sessions.values_mut() {
2300 session.cancelled = true;
2301 }
2302 }
2303
2304 let sessions = self.download_sessions.read().await;
2306
2307 for session in sessions.values() {
2308 if session.temp_path.exists() {
2309 if let Err(e) = tokio::fs::remove_file(&session.temp_path).await {
2310 dev_log!("update", "warn: [UpdateManager] Failed to remove partial file: {}", e);
2311 }
2312
2313 dev_log!("update", "[UpdateManager] Removed partial file: {:?}", session.temp_path);
2314 }
2315 }
2316
2317 drop(sessions);
2318
2319 {
2321 let mut sessions = self.download_sessions.write().await;
2322
2323 sessions.clear();
2324 }
2325
2326 dev_log!("update", "[UpdateManager] Download cancelled and cleaned up");
2327
2328 Ok(())
2329 }
2330
2331 pub async fn ResumeDownload(&self, update_info:&UpdateInfo) -> Result<()> {
2340 let Status = self.update_status.write().await;
2341
2342 if Status.installation_status != InstallationStatus::Paused {
2343 return Err(AirError::Internal("No paused download to resume".to_string()));
2344 }
2345
2346 drop(Status);
2347
2348 dev_log!(
2349 "update",
2350 "[UpdateManager] Resuming download for version {}",
2351 update_info.version
2352 );
2353
2354 self.DownloadUpdate(update_info).await
2355 }
2356
2357 pub async fn GetUpdateChannel(&self) -> UpdateChannel { self.update_channel }
2361
2362 pub async fn SetUpdateChannel(&mut self, channel:UpdateChannel) { self.update_channel = channel; }
2367
2368 async fn copy_directory_recursive(src:&Path, dst:&Path) -> Result<()> {
2380 let mut entries = tokio::fs::read_dir(src)
2381 .await
2382 .map_err(|e| AirError::FileSystem(format!("Failed to read directory {:?}: {}", src, e)))?;
2383
2384 tokio::fs::create_dir_all(dst)
2385 .await
2386 .map_err(|e| AirError::FileSystem(format!("Failed to create directory {:?}: {}", dst, e)))?;
2387
2388 while let Some(entry) = entries
2389 .next_entry()
2390 .await
2391 .map_err(|e| AirError::FileSystem(format!("Failed to read entry: {}", e)))?
2392 {
2393 let file_type = entry
2394 .file_type()
2395 .await
2396 .map_err(|e| AirError::FileSystem(format!("Failed to get file type: {}", e)))?;
2397
2398 let src_path = entry.path();
2399
2400 let dst_path = dst.join(entry.file_name());
2401
2402 if file_type.is_file() {
2403 tokio::fs::copy(&src_path, &dst_path)
2404 .await
2405 .map_err(|e| AirError::FileSystem(format!("Failed to copy file {:?}: {}", src_path, e)))?;
2406 } else if file_type.is_dir() {
2407 Box::pin(Self::copy_directory_recursive(&src_path, &dst_path)).await?;
2408 }
2409 }
2410
2411 Ok(())
2412 }
2413
2414 pub async fn StageUpdate(&self, update_info:&UpdateInfo) -> Result<()> {
2424 dev_log!("update", "[UpdateManager] Staging update for version {}", update_info.version);
2425
2426 let mut status = self.update_status.write().await;
2427
2428 status.installation_status = InstallationStatus::Staging;
2429
2430 drop(status);
2431
2432 let file_path = self.cache_directory.join(format!("update-{}.bin", update_info.version));
2433
2434 if !file_path.exists() {
2435 return Err(AirError::FileSystem("Update file not found. Download first.".to_string()));
2436 }
2437
2438 let stage_dir = self.staging_directory.join(&update_info.version);
2440
2441 tokio::fs::create_dir_all(&stage_dir)
2442 .await
2443 .map_err(|e| AirError::FileSystem(format!("Failed to create staging directory: {}", e)))?;
2444
2445 let staged_file = stage_dir.join("update.bin");
2447
2448 tokio::fs::copy(&file_path, &staged_file)
2449 .await
2450 .map_err(|e| AirError::FileSystem(format!("Failed to stage update package: {}", e)))?;
2451
2452 self.VerifyChecksum(&staged_file, &update_info.checksum).await?;
2454
2455 dev_log!("update", "[UpdateManager] Update staged successfully in: {:?}", stage_dir);
2456
2457 Ok(())
2458 }
2459
2460 pub async fn CleanupOldUpdates(&self) -> Result<()> {
2465 dev_log!("update", "[UpdateManager] Cleaning up old update files");
2466
2467 let mut entries = tokio::fs::read_dir(&self.cache_directory)
2468 .await
2469 .map_err(|e| AirError::FileSystem(format!("Failed to read cache directory: {}", e)))?;
2470
2471 let mut cleaned_count = 0;
2472
2473 let now = std::time::SystemTime::now();
2474
2475 while let Some(entry) = entries
2476 .next_entry()
2477 .await
2478 .map_err(|e| AirError::FileSystem(format!("Failed to read directory entry: {}", e)))?
2479 {
2480 let path = entry.path();
2481
2482 let metadata = entry
2483 .metadata()
2484 .await
2485 .map_err(|e| AirError::FileSystem(format!("Failed to get metadata: {}", e)))?;
2486
2487 if path.is_dir()
2489 || metadata.modified().unwrap_or(now)
2490 > now.checked_sub(Duration::from_secs(7 * 24 * 3600)).unwrap_or(now)
2491 {
2492 continue;
2493 }
2494
2495 dev_log!("update", "[UpdateManager] Removing old update file: {:?}", path);
2496
2497 tokio::fs::remove_file(&path)
2498 .await
2499 .map_err(|e| AirError::FileSystem(format!("Failed to remove {}: {}", path.display(), e)))?;
2500
2501 cleaned_count += 1;
2502 }
2503
2504 dev_log!("update", "[UpdateManager] Cleaned up {} old update files", cleaned_count);
2505
2506 Ok(())
2507 }
2508
2509 pub fn GetCacheDirectory(&self) -> &PathBuf { &self.cache_directory }
2511
2512 pub async fn StartBackgroundTasks(&self) -> Result<()> {
2522 let manager = self.clone();
2523
2524 let handle = tokio::spawn(async move {
2525 manager.BackgroundTask().await;
2526 });
2527
2528 let mut task_handle = self.background_task.lock().await;
2530
2531 *task_handle = Some(handle);
2532
2533 dev_log!("update", "[UpdateManager] Background update checking started");
2534
2535 Ok(())
2536 }
2537
2538 async fn BackgroundTask(&self) {
2545 let config = &self.AppState.Configuration.Updates;
2546
2547 if !config.Enabled {
2548 dev_log!("update", "[UpdateManager] Background task: Updates are disabled");
2549
2550 return;
2551 }
2552
2553 let check_interval = Duration::from_secs(config.CheckIntervalHours as u64 * 3600);
2554
2555 let mut interval = interval(check_interval);
2556
2557 dev_log!(
2558 "update",
2559 "[UpdateManager] Background task: Checking for updates every {} hours",
2560 config.CheckIntervalHours
2561 );
2562
2563 loop {
2564 interval.tick().await;
2565
2566 dev_log!("update", "[UpdateManager] Background task: Checking for updates...");
2567
2568 match self.CheckForUpdates().await {
2570 Ok(Some(update_info)) => {
2571 dev_log!(
2572 "update",
2573 "[UpdateManager] Background task: Update available: {}",
2574 update_info.version
2575 );
2576 },
2577
2578 Ok(None) => {
2579 dev_log!("update", "[UpdateManager] Background task: No updates available");
2580 },
2581
2582 Err(e) => {
2583 dev_log!("update", "error: [UpdateManager] Background task: Update check failed: {}", e);
2584 },
2585 }
2586 }
2587 }
2588
2589 pub async fn StopBackgroundTasks(&self) {
2595 dev_log!("update", "[UpdateManager] Stopping background tasks");
2596
2597 let mut task_handle = self.background_task.lock().await;
2599
2600 if let Some(handle) = task_handle.take() {
2601 handle.abort();
2602
2603 dev_log!("update", "[UpdateManager] Background task aborted");
2604 } else {
2605 dev_log!("update", "[UpdateManager] No background task to stop");
2606 }
2607 }
2608
2609 fn format_size(&self, bytes:f64) -> String {
2617 const KB:f64 = 1024.0;
2618
2619 const MB:f64 = KB * 1024.0;
2620
2621 const GB:f64 = MB * 1024.0;
2622
2623 if bytes >= GB {
2624 format!("{:.2} GB/s", bytes / GB)
2625 } else if bytes >= MB {
2626 format!("{:.2} MB/s", bytes / MB)
2627 } else if bytes >= KB {
2628 format!("{:.2} KB/s", bytes / KB)
2629 } else {
2630 format!("{:.0} B/s", bytes)
2631 }
2632 }
2633}
2634
2635impl Clone for UpdateManager {
2636 fn clone(&self) -> Self {
2637 Self {
2638 AppState:self.AppState.clone(),
2639
2640 update_status:self.update_status.clone(),
2641
2642 cache_directory:self.cache_directory.clone(),
2643
2644 staging_directory:self.staging_directory.clone(),
2645
2646 backup_directory:self.backup_directory.clone(),
2647
2648 download_sessions:self.download_sessions.clone(),
2649
2650 rollback_history:self.rollback_history.clone(),
2651
2652 update_channel:self.update_channel,
2653
2654 platform_config:self.platform_config.clone(),
2655
2656 background_task:self.background_task.clone(),
2657 }
2658 }
2659}