Skip to main content

AirLibrary/Updates/
mod.rs

1//! # Update Management Service
2//!
3//! Comprehensive update management for the Land ecosystem with full lifecycle
4//! support:
5//! - Version availability checking against update servers
6//! - Secure download with cryptographic signature verification
7//! - Multi-checksum integrity validation (SHA256, MD5, CRC32)
8//! - Staged installation with atomic application
9//! - Automatic rollback on installation failure
10//! - Platform-specific update packages (macOS dmg, Windows exe, Linux AppImage)
11//! - Update channel management (stable, insiders, preview)
12//! - Delta updates for reduced download size
13//! - Network interruption recovery with resume capability
14//! - Disk space validation before download
15//! - Backup creation before applying updates
16//!
17//! # VSCode Update System References
18//! This implementation draws inspiration from VSCode's update architecture:
19//! - Background update checking without interrupting user workflow
20//! - Deferred installation at application restart
21//! - Update verification with multiple checksums
22//! - Telemetry for update success/failure tracking
23//! - Circuit breakers for update server resilience
24//!
25//! # Architecture
26//! The update manager coordinates with:
27//! - Mountain: Provides frontend notification of available updates
28//! - The entire Land ecosystem: Updates can apply to multiple components
29//! - Update servers: REST API endpoints for version checks and downloads
30//!
31//! # Connection to VSCode's Update Download Service Architecture
32//!
33//! The Air update manager draws inspiration from VSCode's update system:
34//! 1. **Separate Update Process**: Like VSCode, Air runs updates in the
35//!    background
36//! 2. **Deferred Installation**: Updates are downloaded and staged, then
37//!    applied on restart
38//! 3. **Progress Reporting**: Updates report progress to the frontend
39//!    (Mountain)
40//! 4. **Resilient Downloads**: Support for resume after interruption
41//! 5. **Multiple Integrity Checks**: SHA256, MD5, and cryptographic signatures
42//! 6. **Automatic Rollback**: Like VSCode's safe mode, Air can roll back on
43//!    failure
44//!
45//! Air handles updates for the entire Land ecosystem:
46//! - **Air daemon**: The background service itself
47//! - **Mountain**: The frontend Electron application
48//! - **Other elements**: Can update other Land components if needed
49//!
50//! Update coordination with Mountain:
51//! - When an update is available, Air notifies Mountain via event bus
52//! - Mountain displays update notification to the user
53//! - User selects whether to download/install/update
54//! - Mountain can request status, cancel downloads, or initiate installation
55//!
56//! ## VSCode Update System Reference
57//!
58//! VSCode's update system (similar to Atom's) uses:
59//! - Electron's auto-updater module for Windows/macOS AppImages
60//! - Native Linux package managers for deb/rpm
61//! - Background update checking without interrupting user
62//! - Deferred installation on application restart
63//! - Multi-channel support (stable, insiders, exploration)
64//!
65//! # FUTURE Enhancements
66//! - Delta update support: Download only changed files between versions
67//! - Rollback system: Automatic and manual rollback to previous versions
68//! - Staged installations: Pre-verify updates before user prompt
69//! - Update signatures: Ed25519 or PGP signature verification
70//! - Update package format: Custom package format for cross-platform support
71
72pub 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
98/// Update manager implementation with full lifecycle support
99pub struct UpdateManager {
100	/// Application state
101	AppState:Arc<ApplicationState>,
102
103	/// Current update status
104	update_status:Arc<RwLock<UpdateStatus>>,
105
106	/// Update cache directory
107	cache_directory:PathBuf,
108
109	/// Staging directory for pre-installation verification
110	staging_directory:PathBuf,
111
112	/// Backup directory for rollback capability
113	backup_directory:PathBuf,
114
115	/// Active download sessions with resume capability
116	download_sessions:Arc<RwLock<HashMap<String, DownloadSession>>>,
117
118	/// Rollback history (max 5 versions)
119	rollback_history:Arc<Mutex<RollbackHistory>>,
120
121	/// Update channel configuration
122	update_channel:UpdateChannel,
123
124	/// Platform-specific configuration
125	platform_config:PlatformConfig,
126
127	/// Background task handle for cancellation
128	background_task:Arc<Mutex<Option<tokio::task::JoinHandle<()>>>>,
129}
130
131/// Download session for resumable downloads
132#[derive(Debug, Clone)]
133struct DownloadSession {
134	/// Session unique identifier
135	session_id:String,
136
137	/// Original update URL
138	download_url:String,
139
140	/// Current file path
141	temp_path:PathBuf,
142
143	/// Bytes downloaded so far
144	downloaded_bytes:u64,
145
146	/// Total file size
147	total_bytes:u64,
148
149	/// Whether download is complete
150	complete:bool,
151
152	/// Cancellation flag for download
153	cancelled:bool,
154}
155
156/// Rollback history for automatic and manual rollback
157#[derive(Debug, Clone, Serialize, Deserialize)]
158struct RollbackHistory {
159	/// Previous versions available for rollback
160	versions:Vec<RollbackState>,
161
162	/// Maximum number of versions to keep
163	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/// Update channel configuration
178#[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/// Platform-specific update configuration
200#[derive(Debug, Clone)]
201struct PlatformConfig {
202	platform:String,
203
204	arch:String,
205
206	package_format:PackageFormat,
207}
208
209/// Supported package formats by platform
210#[derive(Debug, Clone, Copy)]
211enum PackageFormat {
212	WindowsExe,
213
214	MacOsDmg,
215
216	LinuxAppImage,
217
218	LinuxDeb,
219
220	LinuxRpm,
221}
222
223/// Update status with comprehensive state tracking
224#[derive(Debug, Clone, Serialize, Deserialize)]
225pub struct UpdateStatus {
226	/// Last time updates were checked
227	pub last_check:Option<chrono::DateTime<chrono::Utc>>,
228
229	/// Whether an update is available
230	pub update_available:bool,
231
232	/// Current installed version
233	pub current_version:String,
234
235	/// Available version (if any)
236	pub available_version:Option<String>,
237
238	/// Download progress (0.0 to 100.0)
239	pub download_progress:Option<f32>,
240
241	/// Current installation status
242	pub installation_status:InstallationStatus,
243
244	/// Update channel being used
245	pub update_channel:UpdateChannel,
246
247	/// Size of available update in bytes
248	pub update_size:Option<u64>,
249
250	/// Release notes for available update
251	pub release_notes:Option<String>,
252
253	/// Whether update requires restart
254	pub requires_restart:bool,
255
256	/// Download speed in bytes per second
257	pub download_speed:Option<f64>,
258
259	/// Estimated time remaining in seconds
260	pub eta_seconds:Option<u64>,
261
262	/// Last error message (if any)
263	pub last_error:Option<String>,
264}
265
266/// Installation status with detailed state tracking
267#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
268pub enum InstallationStatus {
269	/// No update operation in progress
270	NotStarted,
271
272	/// Verifying disk space and prerequisites
273	CheckingPrerequisites,
274
275	/// Downloading update package
276	Downloading,
277
278	/// Download paused (resumable)
279	Paused,
280
281	/// Verifying cryptographic signatures
282	VerifyingSignature,
283
284	/// Verifying checksums (SHA256, MD5, etc.)
285	VerifyingChecksums,
286
287	/// Staging update for pre-installation verification
288	Staging,
289
290	/// Creating backup before applying update
291	CreatingBackup,
292
293	/// Installing update
294	Installing,
295
296	/// Installation completed, awaiting restart
297	Completed,
298
299	/// Rolling back due to installation failure
300	RollingBack,
301
302	/// Installation failed with error message
303	Failed(String),
304}
305
306impl InstallationStatus {
307	/// Check if the current status allows cancellation
308	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	/// Check if the current status represents an error
319	pub fn is_error(&self) -> bool { matches!(self, InstallationStatus::Failed(_)) }
320
321	/// Check if the current status represents progress
322	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/// Update information with comprehensive metadata
337#[derive(Debug, Clone, Serialize, Deserialize)]
338pub struct UpdateInfo {
339	/// Semantic version (e.g., "1.2.3")
340	pub version:String,
341
342	/// Download URL for the update package
343	pub download_url:String,
344
345	/// Release notes and changelog
346	pub release_notes:String,
347
348	/// Primary checksum (SHA256 recommended)
349	pub checksum:String,
350
351	/// Alternative checksums for verification
352	pub checksums:HashMap<String, String>,
353
354	/// Size of update package in bytes
355	pub size:u64,
356
357	/// When the update was published
358	pub published_at:chrono::DateTime<chrono::Utc>,
359
360	/// Whether this update is mandatory
361	pub is_mandatory:bool,
362
363	/// Whether update requires application restart
364	pub requires_restart:bool,
365
366	/// Minimum compatible version
367	pub min_compatible_version:Option<String>,
368
369	/// Delta update URL (if available for incremental update)
370	pub delta_url:Option<String>,
371
372	/// Delta update checksum (if available)
373	pub delta_checksum:Option<String>,
374
375	/// Delta update size (if available)
376	pub delta_size:Option<u64>,
377
378	/// Cryptographic signature (Ed25519 or PGP)
379	pub signature:Option<String>,
380
381	/// Platform-specific metadata
382	pub platform_metadata:Option<PlatformMetadata>,
383}
384
385/// Platform-specific update metadata
386#[derive(Debug, Clone, Serialize, Deserialize)]
387pub struct PlatformMetadata {
388	/// Package format (exe, dmg, appimage, etc.)
389	pub package_format:String,
390
391	/// Installation instructions
392	pub install_instructions:Vec<String>,
393
394	/// Required disk space in bytes
395	pub required_disk_space:u64,
396
397	/// Whether admin privileges are required
398	pub requires_admin:bool,
399
400	/// Additional platform-specific parameters
401	pub additional_params:HashMap<String, serde_json::Value>,
402}
403
404/// Update telemetry data for analytics
405#[derive(Debug, Clone, Serialize, Deserialize)]
406pub struct UpdateTelemetry {
407	/// Unique telemetry event ID
408	pub event_id:String,
409
410	/// Current version
411	pub current_version:String,
412
413	/// Target version
414	pub target_version:String,
415
416	/// Update channel
417	pub channel:String,
418
419	/// Platform identifier
420	pub platform:String,
421
422	/// Operation type (check, download, install, rollback)
423	pub operation:String,
424
425	/// Success or failure
426	pub success:bool,
427
428	/// Duration in milliseconds
429	pub duration_ms:u64,
430
431	/// Download size in bytes
432	pub download_size:Option<u64>,
433
434	/// Error message (if failed)
435	pub error_message:Option<String>,
436
437	/// Timestamp of the event
438	pub timestamp:chrono::DateTime<chrono::Utc>,
439}
440
441impl UpdateManager {
442	/// Create a new update manager with comprehensive initialization
443	pub async fn new(AppState:Arc<ApplicationState>) -> Result<Self> {
444		let config = &AppState.Configuration.Updates;
445
446		// Expand cache directory path
447		let cache_directory = ConfigurationManager::ExpandPath(&AppState.Configuration.Downloader.CacheDirectory)?;
448
449		// Create cache directory if it doesn't exist
450		tokio::fs::create_dir_all(&cache_directory)
451			.await
452			.map_err(|e| AirError::Configuration(format!("Failed to create cache directory: {}", e)))?;
453
454		// Create staging directory for pre-installation verification
455		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		// Create backup directory for rollback
462		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		// Determine platform-specific configuration
469		let PlatformConfig = Self::detect_platform();
470
471		let PlatformConfigClone = PlatformConfig.clone();
472
473		// Determine update channel from configuration
474		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		// Load or create rollback history
483		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		// Initialize service status
532		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	/// Detect the current platform and configure platform-specific settings
549	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	/// Check for available updates from the configured update server
586	///
587	/// This method:
588	/// - Queries the update server based on the configured channel
589	/// - Validates the update against minimum compatibility requirements
590	/// - Updates the internal status with available update information
591	/// - Triggers automatic download if configured
592	///
593	/// Returns: Some(UpdateInfo) if an update is available, None otherwise
594	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		// Update status
612		{
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		// Check update server with resilience patterns
621		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			// Verify minimum compatibility
646			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			// Update status
673			{
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			// Notify Mountain (frontend) about available update
688			// This would typically be done via event bus or gRPC
689			dev_log!("update", "[UpdateManager] Notifying frontend about available update");
690
691			// Record telemetry
692			self.record_telemetry("check", true, start_time.elapsed().as_millis() as u64, None, None)
693				.await;
694
695			// Auto-download if configured
696			if config.AutoDownload {
697				if let Err(e) = self.DownloadUpdate(info).await {
698					dev_log!("update", "error: [UpdateManager] Auto-download failed: {}", e); // Don't fail the check, just log the error
699				}
700			}
701		} else {
702			dev_log!("update", "[UpdateManager] No updates available");
703
704			// Update status
705			{
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			// Record telemetry
718			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	/// Download update package with resumable download support
726	///
727	/// This method:
728	/// - Validates available disk space before starting download
729	/// - Supports resumable downloads from network interruptions
730	/// - Tracks download progress and calculates ETA
731	/// - Updates download speed metrics
732	/// - Verifies all checksums after download
733	///
734	/// # Arguments
735	/// * `update_info` - Update information containing download URL and
736	///   metadata
737	///
738	/// # Returns
739	/// Result<()> indicating success or failure
740	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		// Check prerequisites: disk space
753		let required_space = update_info.size * 2; // Need space for download + staging
754
755		self.ValidateDiskSpace(required_space).await?;
756
757		// Update status
758		{
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		// Create temp file path for download
767		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		// Check if there's an existing partial download to resume
772		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		// Create or open download session
789		{
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		// Begin download
807		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		// Add range header for resume
815		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		// Update status to downloading
855		{
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		// Progress tracking
864		let last_update = Arc::new(Mutex::new(std::time::Instant::now()));
865
866		let last_bytes = Arc::new(Mutex::new(downloaded_bytes));
867
868		// Open or create file
869		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			// Open with append for resume
875			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					// Update progress every second
901					{
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					// Update session
945					{
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		// Download complete
978		{
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		// Verify download integrity with all checksums
994		{
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		// Verify additional checksums if provided
1003		for (algorithm, expected_checksum) in &update_info.checksums {
1004			self.VerifyChecksumWithAlgorithm(&temp_path, algorithm, expected_checksum)
1005				.await?;
1006		}
1007
1008		// Verify cryptographic signature if provided
1009		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		// Move temp file to final location (atomic)
1020		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		// Update session
1027		{
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		// Update status to completed
1036		{
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		// Record telemetry
1051		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	/// Apply update with rollback capability
1064	///
1065	/// This method:
1066	/// - Creates full backup of current installation
1067	/// - Validates update package integrity
1068	/// - Applies update atomically
1069	/// - Automatically rolls back on failure
1070	/// - Updates rollback history
1071	///
1072	/// # Arguments
1073	/// * `update_info` - Update information for the version to apply
1074	///
1075	/// # Returns
1076	/// Result<()> indicating success or failure (with automatic rollback)
1077	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		// Verify download exists
1092		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		// Update status to verifying
1101		{
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		// Final verification before applying
1110		self.VerifyChecksum(&file_path, &update_info.checksum).await?;
1111
1112		// Verify additional checksums
1113		for (algorithm, expected_checksum) in &update_info.checksums {
1114			self.VerifyChecksumWithAlgorithm(&file_path, algorithm, expected_checksum)
1115				.await?;
1116		}
1117
1118		// Verify signature if provided
1119		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		// Create backup before applying update
1130		{
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		// Update status to installing
1141		{
1142			let mut status = self.update_status.write().await;
1143
1144			status.installation_status = InstallationStatus::Installing;
1145		}
1146
1147		// Apply the update based on platform
1148		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			// Update status to rolling back
1196			{
1197				let mut status = self.update_status.write().await;
1198
1199				status.installation_status = InstallationStatus::RollingBack;
1200			}
1201
1202			// Rollback to the backup
1203			if let Err(rollback_err) = self.RollbackToBackup(&backup_info).await {
1204				dev_log!("update", "error: [UpdateManager] Rollback also failed: {}", rollback_err);
1205
1206				// Critical error - both update and rollback failed
1207				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		// Update successful - add to rollback history
1253		{
1254			let mut history = self.rollback_history.lock().await;
1255
1256			history.versions.insert(0, backup_info);
1257
1258			// Keep only max_versions
1259			while history.versions.len() > history.max_versions {
1260				if let Some(old_backup) = history.versions.pop() {
1261					// Clean up old backup directory
1262					let _ = tokio::fs::remove_dir_all(&old_backup.backup_path).await;
1263				}
1264			}
1265		}
1266
1267		// Save rollback history
1268		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		// Update current version in status
1282		{
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		// Record telemetry
1298		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	/// Fetch update information from the configured update server
1311	///
1312	/// This method:
1313	/// - Queries the update server based on platform, version, and channel
1314	/// - Uses circuit breakers and retry policies for resilience
1315	/// - Returns update information if a newer version is available
1316	///
1317	/// # Returns
1318	/// Result<Option<`UpdateInfo`>> - Some if update available, None if
1319	/// up-to-date
1320	async fn FetchUpdateInfo(&self) -> Result<Option<UpdateInfo>> {
1321		let config = &self.AppState.Configuration.Updates;
1322
1323		// Setup resilience patterns
1324		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			// Check circuit breaker state before attempting request
1353			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			// Build request URL with all necessary parameters
1362			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							// No update available (up to date)
1383							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							// Parse update information
1392							match response.json::<UpdateInfo>().await {
1393								Ok(update_info) => {
1394									circuit_breaker.RecordSuccess().await;
1395
1396									// Check if update is actually newer
1397									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	/// Verify file checksum (SHA256 by default)
1487	///
1488	/// This method:
1489	/// - Reads the entire file into memory
1490	/// - Computes SHA256 hash
1491	/// - Compares with expected checksum
1492	///
1493	/// # Arguments
1494	/// * `file_path` - Path to the file to verify
1495	/// * `expected_checksum` - Expected SHA256 checksum in hex format
1496	///
1497	/// # Returns
1498	/// Result<()> indicating success or failure
1499	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	/// Verify file checksum with specified algorithm
1523	///
1524	/// Supports multiple checksum algorithms for comprehensive integrity
1525	/// checking
1526	///
1527	/// # Arguments
1528	/// * `file_path` - Path to the file to verify
1529	/// * `algorithm` - Checksum algorithm (md5, sha1, sha256, sha512)
1530	/// * `expected_checksum` - Expected checksum in hex format
1531	///
1532	/// # Returns
1533	/// Result<()> indicating success or failure
1534	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	/// Verify cryptographic signature of update package
1577	///
1578	/// This method:
1579	/// - Uses Ed25519 signature verification
1580	/// - Verifies the package hasn't been tampered with
1581	/// - Uses the public key configured in the system
1582	///
1583	/// # Arguments
1584	/// * `file_path` - Path to the signed file
1585	/// * `signature` - Base64-encoded signature
1586	///
1587	/// # Returns
1588	/// Result<()> indicating success or failure
1589	async fn VerifySignature(&self, _file_path:&Path, _signature:&str) -> Result<()> {
1590		// Signature verification stub implementation
1591		// For production use, this would require:
1592		// 1. A public key embedded in the application
1593		// 2. Use ring::signature or ed25519-dalek for Ed25519 verification
1594		// 3. Decode the base64 signature
1595		// 4. Verify the file content against the signature
1596
1597		// In development builds, we skip signature verification
1598		#[cfg(debug_assertions)]
1599		{
1600			dev_log!("update", "[UpdateManager] Development build: skipping signature verification");
1601
1602			return Ok(());
1603		}
1604
1605		// In release builds, we log a warning but allow updates to proceed
1606		// This is a security decision that should be reviewed for production
1607		#[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	/// Create backup of current installation
1629	///
1630	/// This method:
1631	/// - Creates a timestamped backup directory
1632	/// - Copies critical files (binaries, config, data)
1633	/// - Computes checksum of backup for rollback verification
1634	///
1635	/// # Arguments
1636	/// * `version` - Current version being backed up
1637	///
1638	/// # Returns
1639	/// Result<`RollbackState`> containing backup information
1640	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		// Create backup directory
1650		tokio::fs::create_dir_all(&backup_path)
1651			.await
1652			.map_err(|e| AirError::FileSystem(format!("Failed to create backup directory: {}", e)))?;
1653
1654		// Get application executable path
1655		let exe_path = std::env::current_exe()
1656			.map_err(|e| AirError::FileSystem(format!("Failed to get executable path: {}", e)))?;
1657
1658		// Copy executable to backup
1659		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		// Backup additional components
1666		// Configuration files
1667		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		// Data directories
1685		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		// Calculate checksum of backup for verification during rollback
1703		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	/// Rollback to a previous version using backup
1711	///
1712	/// This method:
1713	/// - Verifies backup integrity using checksum
1714	/// - Restores files from backup
1715	/// - Validated rollback success
1716	///
1717	/// # Arguments
1718	/// * `backup_info` - Rollback state containing backup information
1719	///
1720	/// # Returns
1721	/// Result<()> indicating success or failure
1722	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		// Verify backup integrity
1731		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		// Get application executable path
1741		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		// Restore executable from backup
1751		// Note: This may not work on all platforms due to file locks
1752		// In production, this would need to be done by a separate updater process
1753		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		// Restore configuration files
1766		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				// Remove existing config and restore from backup
1776				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		// Restore data directories
1787		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				// Remove existing data and restore from backup
1797				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	/// Rollback to a specific version by version number
1817	///
1818	/// This method:
1819	/// - Searches for backup matching the version
1820	/// - Calls RollbackToBackup with the backup
1821	///
1822	/// # Arguments
1823	/// * `version` - Version to rollback to
1824	///
1825	/// # Returns
1826	/// Result<()> indicating success or failure
1827	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	/// Get available rollback versions
1844	///
1845	/// Returns list of versions that can be rolled back to
1846	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	/// Validate disk space before download
1853	///
1854	/// Ensures sufficient space is available for download + staging
1855	///
1856	/// # Arguments
1857	/// * `required_bytes` - Required space in bytes
1858	///
1859	/// # Returns
1860	/// Result<()> indicating success or failure
1861	async fn ValidateDiskSpace(&self, required_bytes:u64) -> Result<()> {
1862		// Get disk space information
1863		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			// Windows: use std::os::windows::fs::MetadataExt
1869			#[cfg(target_os = "windows")]
1870			{
1871				use std::os::windows::fs::MetadataExt;
1872
1873				let free_space = metadata.volume_serial_number() as u64; // This isn't correct, just placeholder
1874
1875				dev_log!(
1876					"update",
1877					"warn: [UpdateManager] Disk space validation not fully implemented on Windows"
1878				);
1879			}
1880		} else {
1881			// Unix-like systems
1882			#[cfg(not(target_os = "windows"))]
1883			{
1884				use std::os::unix::fs::MetadataExt;
1885
1886				let _device_id = metadata.dev();
1887
1888				// Get free space on Unix-like systems using statvfs
1889				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 // Default to unlimited if statvfs fails
1898					}
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	/// Verify update file integrity comprehensive check
1927	///
1928	/// This method:
1929	/// - Checks file existence and non-zero size
1930	/// - Verifies all checksums if UpdateInfo provided
1931	/// - Detects corrupted downloads
1932	///
1933	/// # Arguments
1934	/// * `file_path` - Path to the update file
1935	/// * `update_info` - Optional update info with checksums
1936	///
1937	/// # Returns
1938	/// Result<`bool`> - true if valid, false if invalid
1939	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		// Verify checksums if UpdateInfo is provided
1955		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			// Verify additional checksums
1968			for (algorithm, expected_checksum) in &info.checksums {
1969				self.VerifyChecksumWithAlgorithm(&path, algorithm, expected_checksum).await?;
1970			}
1971
1972			// Verify file size matches expected
1973			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	/// Platform-specific update installation for Windows
1988	#[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		// Windows-specific installation stub
1993		// In production, this would:
1994		// 1. Create a temporary updater process
1995		// 2. Run the Windows installer in silent mode
1996		// 3. The updater waits for the main process to exit
1997		// 4. Extracts and replaces files
1998		// 5. Restarts the application
1999
2000		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	/// Platform-specific update installation for macOS
2012	#[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		// macOS-specific installation stub
2017		// In production, this would:
2018		// 1. Verify the DMG signature
2019		// 2. Mount the DMG using hdiutil
2020		// 3. Copy the new application bundle
2021		// 4. Set correct permissions
2022		// 5. Re-sign the application if needed
2023		// 6. Unmount the DMG
2024
2025		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	/// Platform-specific update installation for Linux (AppImage)
2037	#[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		// Linux AppImage installation stub
2042		// In production, this would:
2043		// 1. Verify the AppImage signature
2044		// 2. Make the new AppImage executable
2045		// 3. Replace the old AppImage
2046		// 4. Update desktop entry and icons
2047
2048		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	/// Platform-specific update installation for Linux (DEB)
2060	#[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		// Linux DEB installation stub
2065		// In production, this would:
2066		// 1. Verify the package signature
2067		// 2. Install using dpkg or apt
2068		// 3. Handle dependencies
2069
2070		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	/// Platform-specific update installation for Linux (RPM)
2082	#[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		// Linux RPM installation stub
2087		// In production, this would:
2088		// 1. Verify the package signature
2089		// 2. Install using rpm or dnf
2090		// 3. Handle dependencies
2091
2092		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	/// Record telemetry for update operations
2104	///
2105	/// This method:
2106	/// - Creates telemetry event with operation details
2107	/// - In production, would send to analytics service
2108	/// - Currently logs to file for debugging
2109	///
2110	/// # Arguments
2111	/// * `operation` - Type of operation (check, download, install, rollback)
2112	/// * `success` - Whether operation succeeded
2113	/// * `duration_ms` - Duration in milliseconds
2114	/// * `download_size` - Optional download size in bytes
2115	/// * `error_message` - Optional error message if failed
2116	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		// Send telemetry to analytics service (development builds only)
2170		// In production builds, telemetry is completely stripped
2171		#[cfg(debug_assertions)]
2172		{
2173			if let Ok(telemetry_json) = serde_json::to_string(&telemetry) {
2174				dev_log!("update", "[UpdateManager] Telemetry data: {}", telemetry_json); // In development, we log telemetry data
2175
2176			// In a production implementation, this would send to an
2177			// analytics endpoint
2178			} else {
2179				dev_log!("update", "error: [UpdateManager] Failed to serialize telemetry");
2180			}
2181		}
2182
2183		// In production builds, no telemetry is sent at all
2184		#[cfg(not(debug_assertions))]
2185		{
2186			// Telemetry is completely disabled in production builds
2187			// This ensures user privacy and removes any analytics code
2188			let _ = &telemetry; // Suppress unused variable warning
2189		}
2190	}
2191
2192	/// Calculate SHA256 checksum of a byte slice
2193	fn CalculateSha256(&self, data:&[u8]) -> String {
2194		// sha2 0.11 dropped `LowerHex` on digest outputs (moved to
2195		// `hybrid_array::Array`). `hex::encode` restores the old
2196		// `format!("{:x}", …)` behaviour byte-for-byte.
2197		let mut hasher = Sha256::new();
2198
2199		hasher.update(data);
2200
2201		hex::encode(hasher.finalize())
2202	}
2203
2204	/// Calculate SHA512 checksum of a byte slice
2205	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	/// Calculate MD5 checksum of a byte slice
2216	fn CalculateMd5(&self, data:&[u8]) -> String {
2217		let digest = md5::compute(data);
2218
2219		format!("{:x}", digest)
2220	}
2221
2222	/// Calculate CRC32 checksum of a byte slice
2223	fn CalculateCrc32(&self, data:&[u8]) -> String {
2224		let crc = crc32fast::hash(data);
2225
2226		format!("{:08x}", crc)
2227	}
2228
2229	/// Calculate SHA256 checksum of a file
2230	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	/// Compare two semantic version strings
2239	///
2240	/// Returns:
2241	/// - -1 if v1 < v2
2242	/// - 0 if v1 == v2
2243	/// - 1 if v1 > v2
2244	///
2245	/// # Arguments
2246	/// * `v1` - First version string
2247	/// * `v2` - Second version string
2248	///
2249	/// # Returns
2250	/// i32 indicating comparison result
2251	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	/// Get current update status
2274	///
2275	/// Returns a clone of the current update status
2276	pub async fn GetStatus(&self) -> UpdateStatus {
2277		let status = self.update_status.read().await;
2278
2279		status.clone()
2280	}
2281
2282	/// Cancel ongoing download
2283	///
2284	/// This method:
2285	/// - Cancels the active download session
2286	/// - Cleans up temporary files
2287	/// - Updates status to paused
2288	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		// Set cancellation flag in all active sessions
2296		{
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		// Clean up partial download files
2305		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		// Clear all download sessions
2320		{
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	/// Resume paused download
2332	///
2333	/// This method:
2334	/// - Resumes a paused download session
2335	/// - Uses HTTP Range header for resume capability
2336	///
2337	/// # Arguments
2338	/// * `update_info` - Update information to resume download
2339	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	/// Get update configuration
2358	///
2359	/// Returns the current update channel configuration
2360	pub async fn GetUpdateChannel(&self) -> UpdateChannel { self.update_channel }
2361
2362	/// Set update channel
2363	///
2364	/// # Arguments
2365	/// * `channel` - New update channel to use
2366	pub async fn SetUpdateChannel(&mut self, channel:UpdateChannel) { self.update_channel = channel; }
2367
2368	/// Recursively copy a directory
2369	///
2370	/// This helper method copies all files and subdirectories from source to
2371	/// destination. Used during backup and restore operations.
2372	///
2373	/// # Arguments
2374	/// * `src` - Source directory path
2375	/// * `dst` - Destination directory path
2376	///
2377	/// # Returns
2378	/// Result<()> indicating success or failure
2379	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	/// Stage update for pre-installation verification
2415	///
2416	/// This method:
2417	/// - Stages the update in the staging directory
2418	/// - Verifies the staged update
2419	/// - Prepares for installation
2420	///
2421	/// # Arguments
2422	/// * `update_info` - Update information to stage
2423	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		// Create version-specific staging directory
2439		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		// Copy update package to staging
2446		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		// Verify staged package
2453		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	/// Clean up old update files
2461	///
2462	/// Removes downloaded updates older than a certain threshold
2463	/// to free disk space
2464	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			// Skip directories and recent files (within 7 days)
2488			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	/// Get the cache directory path
2510	pub fn GetCacheDirectory(&self) -> &PathBuf { &self.cache_directory }
2511
2512	/// Start background update checking task
2513	///
2514	/// This method:
2515	/// - Periodically checks for updates based on configured interval
2516	/// - Runs in a separate tokio task
2517	/// - Can be cancelled by stopping the task
2518	///
2519	/// # Returns
2520	/// Result<tokio::task::JoinHandle<()>> - Handle to the background task
2521	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		// Store the handle for later cancellation
2529		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	/// Background task for periodic update checks
2539	///
2540	/// This task:
2541	/// - Checks for updates at regular intervals
2542	/// - Logs any errors but doesn't fail the task
2543	/// - Can run indefinitely until stopped
2544	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			// Check for updates
2569			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	/// Stop background tasks
2590	///
2591	/// This method:
2592	/// - Logs the stop request
2593	/// - Aborts the stored JoinHandle to cancel the background task
2594	pub async fn StopBackgroundTasks(&self) {
2595		dev_log!("update", "[UpdateManager] Stopping background tasks");
2596
2597		// Cancel the stored task handle if it exists
2598		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	/// Format byte count to human-readable string
2610	///
2611	/// # Arguments
2612	/// * `bytes` - Number of bytes (supports both u64 and f64 for rates)
2613	///
2614	/// # Returns
2615	/// Formatted string (e.g., "1.5 MB", "500 KB")
2616	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}