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