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
72use 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
90/// Update manager implementation with full lifecycle support
91pub struct UpdateManager {
92	/// Application state
93	AppState:Arc<ApplicationState>,
94
95	/// Current update status
96	update_status:Arc<RwLock<UpdateStatus>>,
97
98	/// Update cache directory
99	cache_directory:PathBuf,
100
101	/// Staging directory for pre-installation verification
102	staging_directory:PathBuf,
103
104	/// Backup directory for rollback capability
105	backup_directory:PathBuf,
106
107	/// Active download sessions with resume capability
108	download_sessions:Arc<RwLock<HashMap<String, DownloadSession>>>,
109
110	/// Rollback history (max 5 versions)
111	rollback_history:Arc<Mutex<RollbackHistory>>,
112
113	/// Update channel configuration
114	update_channel:UpdateChannel,
115
116	/// Platform-specific configuration
117	platform_config:PlatformConfig,
118
119	/// Background task handle for cancellation
120	background_task:Arc<Mutex<Option<tokio::task::JoinHandle<()>>>>,
121}
122
123/// Download session for resumable downloads
124#[derive(Debug, Clone)]
125struct DownloadSession {
126	/// Session unique identifier
127	#[allow(dead_code)]
128	session_id:String,
129
130	/// Original update URL
131	#[allow(dead_code)]
132	download_url:String,
133
134	/// Current file path
135	#[allow(dead_code)]
136	temp_path:PathBuf,
137
138	/// Bytes downloaded so far
139	downloaded_bytes:u64,
140
141	/// Total file size
142	#[allow(dead_code)]
143	total_bytes:u64,
144
145	/// Whether download is complete
146	complete:bool,
147
148	/// Cancellation flag for download
149	cancelled:bool,
150}
151
152/// Rollback history for automatic and manual rollback
153#[derive(Debug, Clone, Serialize, Deserialize)]
154struct RollbackHistory {
155	/// Previous versions available for rollback
156	versions:Vec<RollbackState>,
157
158	/// Maximum number of versions to keep
159	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/// Update channel configuration
174#[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/// Platform-specific update configuration
196#[derive(Debug, Clone)]
197struct PlatformConfig {
198	platform:String,
199
200	arch:String,
201
202	package_format:PackageFormat,
203}
204
205/// Supported package formats by platform
206#[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/// Update status with comprehensive state tracking
221#[derive(Debug, Clone, Serialize, Deserialize)]
222pub struct UpdateStatus {
223	/// Last time updates were checked
224	pub last_check:Option<chrono::DateTime<chrono::Utc>>,
225
226	/// Whether an update is available
227	pub update_available:bool,
228
229	/// Current installed version
230	pub current_version:String,
231
232	/// Available version (if any)
233	pub available_version:Option<String>,
234
235	/// Download progress (0.0 to 100.0)
236	pub download_progress:Option<f32>,
237
238	/// Current installation status
239	pub installation_status:InstallationStatus,
240
241	/// Update channel being used
242	pub update_channel:UpdateChannel,
243
244	/// Size of available update in bytes
245	pub update_size:Option<u64>,
246
247	/// Release notes for available update
248	pub release_notes:Option<String>,
249
250	/// Whether update requires restart
251	pub requires_restart:bool,
252
253	/// Download speed in bytes per second
254	pub download_speed:Option<f64>,
255
256	/// Estimated time remaining in seconds
257	pub eta_seconds:Option<u64>,
258
259	/// Last error message (if any)
260	pub last_error:Option<String>,
261}
262
263/// Installation status with detailed state tracking
264#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
265pub enum InstallationStatus {
266	/// No update operation in progress
267	NotStarted,
268
269	/// Verifying disk space and prerequisites
270	CheckingPrerequisites,
271
272	/// Downloading update package
273	Downloading,
274
275	/// Download paused (resumable)
276	Paused,
277
278	/// Verifying cryptographic signatures
279	VerifyingSignature,
280
281	/// Verifying checksums (SHA256, MD5, etc.)
282	VerifyingChecksums,
283
284	/// Staging update for pre-installation verification
285	Staging,
286
287	/// Creating backup before applying update
288	CreatingBackup,
289
290	/// Installing update
291	Installing,
292
293	/// Installation completed, awaiting restart
294	Completed,
295
296	/// Rolling back due to installation failure
297	RollingBack,
298
299	/// Installation failed with error message
300	Failed(String),
301}
302
303impl InstallationStatus {
304	/// Check if the current status allows cancellation
305	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	/// Check if the current status represents an error
316	pub fn is_error(&self) -> bool { matches!(self, InstallationStatus::Failed(_)) }
317
318	/// Check if the current status represents progress
319	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/// Update information with comprehensive metadata
334#[derive(Debug, Clone, Serialize, Deserialize)]
335pub struct UpdateInfo {
336	/// Semantic version (e.g., "1.2.3")
337	pub version:String,
338
339	/// Download URL for the update package
340	pub download_url:String,
341
342	/// Release notes and changelog
343	pub release_notes:String,
344
345	/// Primary checksum (SHA256 recommended)
346	pub checksum:String,
347
348	/// Alternative checksums for verification
349	pub checksums:HashMap<String, String>,
350
351	/// Size of update package in bytes
352	pub size:u64,
353
354	/// When the update was published
355	pub published_at:chrono::DateTime<chrono::Utc>,
356
357	/// Whether this update is mandatory
358	pub is_mandatory:bool,
359
360	/// Whether update requires application restart
361	pub requires_restart:bool,
362
363	/// Minimum compatible version
364	pub min_compatible_version:Option<String>,
365
366	/// Delta update URL (if available for incremental update)
367	pub delta_url:Option<String>,
368
369	/// Delta update checksum (if available)
370	pub delta_checksum:Option<String>,
371
372	/// Delta update size (if available)
373	pub delta_size:Option<u64>,
374
375	/// Cryptographic signature (Ed25519 or PGP)
376	pub signature:Option<String>,
377
378	/// Platform-specific metadata
379	pub platform_metadata:Option<PlatformMetadata>,
380}
381
382/// Platform-specific update metadata
383#[derive(Debug, Clone, Serialize, Deserialize)]
384pub struct PlatformMetadata {
385	/// Package format (exe, dmg, appimage, etc.)
386	pub package_format:String,
387
388	/// Installation instructions
389	pub install_instructions:Vec<String>,
390
391	/// Required disk space in bytes
392	pub required_disk_space:u64,
393
394	/// Whether admin privileges are required
395	pub requires_admin:bool,
396
397	/// Additional platform-specific parameters
398	pub additional_params:HashMap<String, serde_json::Value>,
399}
400
401/// Update telemetry data for analytics
402#[derive(Debug, Clone, Serialize, Deserialize)]
403pub struct UpdateTelemetry {
404	/// Unique telemetry event ID
405	pub event_id:String,
406
407	/// Current version
408	pub current_version:String,
409
410	/// Target version
411	pub target_version:String,
412
413	/// Update channel
414	pub channel:String,
415
416	/// Platform identifier
417	pub platform:String,
418
419	/// Operation type (check, download, install, rollback)
420	pub operation:String,
421
422	/// Success or failure
423	pub success:bool,
424
425	/// Duration in milliseconds
426	pub duration_ms:u64,
427
428	/// Download size in bytes
429	pub download_size:Option<u64>,
430
431	/// Error message (if failed)
432	pub error_message:Option<String>,
433
434	/// Timestamp of the event
435	pub timestamp:chrono::DateTime<chrono::Utc>,
436}
437
438impl UpdateManager {
439	/// Create a new update manager with comprehensive initialization
440	pub async fn new(AppState:Arc<ApplicationState>) -> Result<Self> {
441		let config = &AppState.Configuration.Updates;
442
443		// Expand cache directory path
444		let cache_directory = ConfigurationManager::ExpandPath(&AppState.Configuration.Downloader.CacheDirectory)?;
445
446		// Create cache directory if it doesn't exist
447		tokio::fs::create_dir_all(&cache_directory)
448			.await
449			.map_err(|e| AirError::Configuration(format!("Failed to create cache directory: {}", e)))?;
450
451		// Create staging directory for pre-installation verification
452		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		// Create backup directory for rollback
459		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		// Determine platform-specific configuration
466		let PlatformConfig = Self::detect_platform();
467
468		let PlatformConfigClone = PlatformConfig.clone();
469
470		// Determine update channel from configuration
471		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		// Load or create rollback history
480		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		// Initialize service status
529		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	/// Detect the current platform and configure platform-specific settings
546	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	/// Check for available updates from the configured update server
583	///
584	/// This method:
585	/// - Queries the update server based on the configured channel
586	/// - Validates the update against minimum compatibility requirements
587	/// - Updates the internal status with available update information
588	/// - Triggers automatic download if configured
589	///
590	/// Returns: Some(UpdateInfo) if an update is available, None otherwise
591	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		// Update status
609		{
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		// Check update server with resilience patterns
618		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			// Verify minimum compatibility
643			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			// Update status
670			{
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			// Notify Mountain (frontend) about available update
685			// This would typically be done via event bus or gRPC
686			dev_log!("update", "[UpdateManager] Notifying frontend about available update");
687
688			// Record telemetry
689			self.record_telemetry("check", true, start_time.elapsed().as_millis() as u64, None, None)
690				.await;
691
692			// Auto-download if configured
693			if config.AutoDownload {
694				if let Err(e) = self.DownloadUpdate(info).await {
695					dev_log!("update", "error: [UpdateManager] Auto-download failed: {}", e); // Don't fail the check, just log the error
696				}
697			}
698		} else {
699			dev_log!("update", "[UpdateManager] No updates available");
700
701			// Update status
702			{
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			// Record telemetry
715			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	/// Download update package with resumable download support
723	///
724	/// This method:
725	/// - Validates available disk space before starting download
726	/// - Supports resumable downloads from network interruptions
727	/// - Tracks download progress and calculates ETA
728	/// - Updates download speed metrics
729	/// - Verifies all checksums after download
730	///
731	/// # Arguments
732	/// * `update_info` - Update information containing download URL and
733	///   metadata
734	///
735	/// # Returns
736	/// Result<()> indicating success or failure
737	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		// Check prerequisites: disk space
750		let required_space = update_info.size * 2; // Need space for download + staging
751		self.ValidateDiskSpace(required_space).await?;
752
753		// Update status
754		{
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		// Create temp file path for download
763		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		// Check if there's an existing partial download to resume
768		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		// Create or open download session
785		{
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		// Begin download
803		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		// Add range header for resume
811		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		// Update status to downloading
851		{
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		// Progress tracking
860		let last_update = Arc::new(Mutex::new(std::time::Instant::now()));
861
862		let last_bytes = Arc::new(Mutex::new(downloaded_bytes));
863
864		// Open or create file
865		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			// Open with append for resume
871			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					// Update progress every second
897					{
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					// Update session
941					{
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		// Download complete
974		{
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		// Verify download integrity with all checksums
990		{
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		// Verify additional checksums if provided
999		for (algorithm, expected_checksum) in &update_info.checksums {
1000			self.VerifyChecksumWithAlgorithm(&temp_path, algorithm, expected_checksum)
1001				.await?;
1002		}
1003
1004		// Verify cryptographic signature if provided
1005		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		// Move temp file to final location (atomic)
1016		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		// Update session
1023		{
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		// Update status to completed
1032		{
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		// Record telemetry
1047		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	/// Apply update with rollback capability
1060	///
1061	/// This method:
1062	/// - Creates full backup of current installation
1063	/// - Validates update package integrity
1064	/// - Applies update atomically
1065	/// - Automatically rolls back on failure
1066	/// - Updates rollback history
1067	///
1068	/// # Arguments
1069	/// * `update_info` - Update information for the version to apply
1070	///
1071	/// # Returns
1072	/// Result<()> indicating success or failure (with automatic rollback)
1073	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		// Verify download exists
1088		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		// Update status to verifying
1097		{
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		// Final verification before applying
1106		self.VerifyChecksum(&file_path, &update_info.checksum).await?;
1107
1108		// Verify additional checksums
1109		for (algorithm, expected_checksum) in &update_info.checksums {
1110			self.VerifyChecksumWithAlgorithm(&file_path, algorithm, expected_checksum)
1111				.await?;
1112		}
1113
1114		// Verify signature if provided
1115		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		// Create backup before applying update
1126		{
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		// Update status to installing
1137		{
1138			let mut status = self.update_status.write().await;
1139
1140			status.installation_status = InstallationStatus::Installing;
1141		}
1142
1143		// Apply the update based on platform
1144		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			// Update status to rolling back
1192			{
1193				let mut status = self.update_status.write().await;
1194
1195				status.installation_status = InstallationStatus::RollingBack;
1196			}
1197
1198			// Rollback to the backup
1199			if let Err(rollback_err) = self.RollbackToBackup(&backup_info).await {
1200				dev_log!("update", "error: [UpdateManager] Rollback also failed: {}", rollback_err);
1201
1202				// Critical error - both update and rollback failed
1203				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		// Update successful - add to rollback history
1249		{
1250			let mut history = self.rollback_history.lock().await;
1251
1252			history.versions.insert(0, backup_info);
1253
1254			// Keep only max_versions
1255			while history.versions.len() > history.max_versions {
1256				if let Some(old_backup) = history.versions.pop() {
1257					// Clean up old backup directory
1258					let _ = tokio::fs::remove_dir_all(&old_backup.backup_path).await;
1259				}
1260			}
1261		}
1262
1263		// Save rollback history
1264		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		// Update current version in status
1278		{
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		// Record telemetry
1294		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	/// Fetch update information from the configured update server
1307	///
1308	/// This method:
1309	/// - Queries the update server based on platform, version, and channel
1310	/// - Uses circuit breakers and retry policies for resilience
1311	/// - Returns update information if a newer version is available
1312	///
1313	/// # Returns
1314	/// Result<Option<`UpdateInfo`>> - Some if update available, None if
1315	/// up-to-date
1316	async fn FetchUpdateInfo(&self) -> Result<Option<UpdateInfo>> {
1317		let config = &self.AppState.Configuration.Updates;
1318
1319		// Setup resilience patterns
1320		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			// Check circuit breaker state before attempting request
1349			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			// Build request URL with all necessary parameters
1358			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							// No update available (up to date)
1379							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							// Parse update information
1388							match response.json::<UpdateInfo>().await {
1389								Ok(update_info) => {
1390									circuit_breaker.RecordSuccess().await;
1391
1392									// Check if update is actually newer
1393									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	/// Verify file checksum (SHA256 by default)
1483	///
1484	/// This method:
1485	/// - Reads the entire file into memory
1486	/// - Computes SHA256 hash
1487	/// - Compares with expected checksum
1488	///
1489	/// # Arguments
1490	/// * `file_path` - Path to the file to verify
1491	/// * `expected_checksum` - Expected SHA256 checksum in hex format
1492	///
1493	/// # Returns
1494	/// Result<()> indicating success or failure
1495	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	/// Verify file checksum with specified algorithm
1519	///
1520	/// Supports multiple checksum algorithms for comprehensive integrity
1521	/// checking
1522	///
1523	/// # Arguments
1524	/// * `file_path` - Path to the file to verify
1525	/// * `algorithm` - Checksum algorithm (md5, sha1, sha256, sha512)
1526	/// * `expected_checksum` - Expected checksum in hex format
1527	///
1528	/// # Returns
1529	/// Result<()> indicating success or failure
1530	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	/// Verify cryptographic signature of update package
1573	///
1574	/// This method:
1575	/// - Uses Ed25519 signature verification
1576	/// - Verifies the package hasn't been tampered with
1577	/// - Uses the public key configured in the system
1578	///
1579	/// # Arguments
1580	/// * `file_path` - Path to the signed file
1581	/// * `signature` - Base64-encoded signature
1582	///
1583	/// # Returns
1584	/// Result<()> indicating success or failure
1585	async fn VerifySignature(&self, _file_path:&Path, _signature:&str) -> Result<()> {
1586		// Signature verification stub implementation
1587		// For production use, this would require:
1588		// 1. A public key embedded in the application
1589		// 2. Use ring::signature or ed25519-dalek for Ed25519 verification
1590		// 3. Decode the base64 signature
1591		// 4. Verify the file content against the signature
1592
1593		// In development builds, we skip signature verification
1594		#[cfg(debug_assertions)]
1595		{
1596			dev_log!("update", "[UpdateManager] Development build: skipping signature verification");
1597
1598			return Ok(());
1599		}
1600
1601		// In release builds, we log a warning but allow updates to proceed
1602		// This is a security decision that should be reviewed for production
1603		#[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	/// Create backup of current installation
1625	///
1626	/// This method:
1627	/// - Creates a timestamped backup directory
1628	/// - Copies critical files (binaries, config, data)
1629	/// - Computes checksum of backup for rollback verification
1630	///
1631	/// # Arguments
1632	/// * `version` - Current version being backed up
1633	///
1634	/// # Returns
1635	/// Result<`RollbackState`> containing backup information
1636	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		// Create backup directory
1646		tokio::fs::create_dir_all(&backup_path)
1647			.await
1648			.map_err(|e| AirError::FileSystem(format!("Failed to create backup directory: {}", e)))?;
1649
1650		// Get application executable path
1651		let exe_path = std::env::current_exe()
1652			.map_err(|e| AirError::FileSystem(format!("Failed to get executable path: {}", e)))?;
1653
1654		// Copy executable to backup
1655		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		// Backup additional components
1662		// Configuration files
1663		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		// Data directories
1681		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		// Calculate checksum of backup for verification during rollback
1699		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	/// Rollback to a previous version using backup
1707	///
1708	/// This method:
1709	/// - Verifies backup integrity using checksum
1710	/// - Restores files from backup
1711	/// - Validated rollback success
1712	///
1713	/// # Arguments
1714	/// * `backup_info` - Rollback state containing backup information
1715	///
1716	/// # Returns
1717	/// Result<()> indicating success or failure
1718	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		// Verify backup integrity
1727		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		// Get application executable path
1737		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		// Restore executable from backup
1747		// Note: This may not work on all platforms due to file locks
1748		// In production, this would need to be done by a separate updater process
1749		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		// Restore configuration files
1762		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				// Remove existing config and restore from backup
1772				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		// Restore data directories
1783		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				// Remove existing data and restore from backup
1793				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	/// Rollback to a specific version by version number
1813	///
1814	/// This method:
1815	/// - Searches for backup matching the version
1816	/// - Calls RollbackToBackup with the backup
1817	///
1818	/// # Arguments
1819	/// * `version` - Version to rollback to
1820	///
1821	/// # Returns
1822	/// Result<()> indicating success or failure
1823	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	/// Get available rollback versions
1840	///
1841	/// Returns list of versions that can be rolled back to
1842	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	/// Validate disk space before download
1849	///
1850	/// Ensures sufficient space is available for download + staging
1851	///
1852	/// # Arguments
1853	/// * `required_bytes` - Required space in bytes
1854	///
1855	/// # Returns
1856	/// Result<()> indicating success or failure
1857	async fn ValidateDiskSpace(&self, required_bytes:u64) -> Result<()> {
1858		// Get disk space information
1859		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			// Windows: use std::os::windows::fs::MetadataExt
1865			#[cfg(target_os = "windows")]
1866			{
1867				use std::os::windows::fs::MetadataExt;
1868
1869				let free_space = metadata.volume_serial_number() as u64; // This isn't correct, just placeholder
1870				dev_log!(
1871					"update",
1872					"warn: [UpdateManager] Disk space validation not fully implemented on Windows"
1873				);
1874			}
1875		} else {
1876			// Unix-like systems
1877			#[cfg(not(target_os = "windows"))]
1878			{
1879				use std::os::unix::fs::MetadataExt;
1880
1881				let _device_id = metadata.dev();
1882
1883				// Get free space on Unix-like systems using statvfs
1884				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 // Default to unlimited if statvfs fails
1893					}
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	/// Verify update file integrity comprehensive check
1922	///
1923	/// This method:
1924	/// - Checks file existence and non-zero size
1925	/// - Verifies all checksums if UpdateInfo provided
1926	/// - Detects corrupted downloads
1927	///
1928	/// # Arguments
1929	/// * `file_path` - Path to the update file
1930	/// * `update_info` - Optional update info with checksums
1931	///
1932	/// # Returns
1933	/// Result<`bool`> - true if valid, false if invalid
1934	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		// Verify checksums if UpdateInfo is provided
1950		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			// Verify additional checksums
1963			for (algorithm, expected_checksum) in &info.checksums {
1964				self.VerifyChecksumWithAlgorithm(&path, algorithm, expected_checksum).await?;
1965			}
1966
1967			// Verify file size matches expected
1968			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	/// Platform-specific update installation for Windows
1983	#[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		// Windows-specific installation stub
1988		// In production, this would:
1989		// 1. Create a temporary updater process
1990		// 2. Run the Windows installer in silent mode
1991		// 3. The updater waits for the main process to exit
1992		// 4. Extracts and replaces files
1993		// 5. Restarts the application
1994
1995		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	/// Platform-specific update installation for macOS
2007	#[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		// macOS-specific installation stub
2012		// In production, this would:
2013		// 1. Verify the DMG signature
2014		// 2. Mount the DMG using hdiutil
2015		// 3. Copy the new application bundle
2016		// 4. Set correct permissions
2017		// 5. Re-sign the application if needed
2018		// 6. Unmount the DMG
2019
2020		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	/// Platform-specific update installation for Linux (AppImage)
2032	#[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		// Linux AppImage installation stub
2037		// In production, this would:
2038		// 1. Verify the AppImage signature
2039		// 2. Make the new AppImage executable
2040		// 3. Replace the old AppImage
2041		// 4. Update desktop entry and icons
2042
2043		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	/// Platform-specific update installation for Linux (DEB)
2055	#[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		// Linux DEB installation stub
2060		// In production, this would:
2061		// 1. Verify the package signature
2062		// 2. Install using dpkg or apt
2063		// 3. Handle dependencies
2064
2065		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	/// Platform-specific update installation for Linux (RPM)
2077	#[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		// Linux RPM installation stub
2082		// In production, this would:
2083		// 1. Verify the package signature
2084		// 2. Install using rpm or dnf
2085		// 3. Handle dependencies
2086
2087		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	/// Record telemetry for update operations
2099	///
2100	/// This method:
2101	/// - Creates telemetry event with operation details
2102	/// - In production, would send to analytics service
2103	/// - Currently logs to file for debugging
2104	///
2105	/// # Arguments
2106	/// * `operation` - Type of operation (check, download, install, rollback)
2107	/// * `success` - Whether operation succeeded
2108	/// * `duration_ms` - Duration in milliseconds
2109	/// * `download_size` - Optional download size in bytes
2110	/// * `error_message` - Optional error message if failed
2111	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		// Send telemetry to analytics service (development builds only)
2165		// In production builds, telemetry is completely stripped
2166		#[cfg(debug_assertions)]
2167		{
2168			if let Ok(telemetry_json) = serde_json::to_string(&telemetry) {
2169				dev_log!("update", "[UpdateManager] Telemetry data: {}", telemetry_json); // In development, we log telemetry data
2170			// In a production implementation, this would send to an
2171			// analytics endpoint
2172			} else {
2173				dev_log!("update", "error: [UpdateManager] Failed to serialize telemetry");
2174			}
2175		}
2176
2177		// In production builds, no telemetry is sent at all
2178		#[cfg(not(debug_assertions))]
2179		{
2180			// Telemetry is completely disabled in production builds
2181			// This ensures user privacy and removes any analytics code
2182			let _ = &telemetry; // Suppress unused variable warning
2183		}
2184	}
2185
2186	/// Calculate SHA256 checksum of a byte slice
2187	fn CalculateSha256(&self, data:&[u8]) -> String {
2188		// sha2 0.11 dropped `LowerHex` on digest outputs (moved to
2189		// `hybrid_array::Array`). `hex::encode` restores the old
2190		// `format!("{:x}", …)` behaviour byte-for-byte.
2191		let mut hasher = Sha256::new();
2192
2193		hasher.update(data);
2194
2195		hex::encode(hasher.finalize())
2196	}
2197
2198	/// Calculate SHA512 checksum of a byte slice
2199	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	/// Calculate MD5 checksum of a byte slice
2210	fn CalculateMd5(&self, data:&[u8]) -> String {
2211		let digest = md5::compute(data);
2212
2213		format!("{:x}", digest)
2214	}
2215
2216	/// Calculate CRC32 checksum of a byte slice
2217	fn CalculateCrc32(&self, data:&[u8]) -> String {
2218		let crc = crc32fast::hash(data);
2219
2220		format!("{:08x}", crc)
2221	}
2222
2223	/// Calculate SHA256 checksum of a file
2224	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	/// Compare two semantic version strings
2233	///
2234	/// Returns:
2235	/// - -1 if v1 < v2
2236	/// - 0 if v1 == v2
2237	/// - 1 if v1 > v2
2238	///
2239	/// # Arguments
2240	/// * `v1` - First version string
2241	/// * `v2` - Second version string
2242	///
2243	/// # Returns
2244	/// i32 indicating comparison result
2245	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	/// Get current update status
2268	///
2269	/// Returns a clone of the current update status
2270	pub async fn GetStatus(&self) -> UpdateStatus {
2271		let status = self.update_status.read().await;
2272
2273		status.clone()
2274	}
2275
2276	/// Cancel ongoing download
2277	///
2278	/// This method:
2279	/// - Cancels the active download session
2280	/// - Cleans up temporary files
2281	/// - Updates status to paused
2282	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		// Set cancellation flag in all active sessions
2290		{
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		// Clean up partial download files
2299		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		// Clear all download sessions
2314		{
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	/// Resume paused download
2326	///
2327	/// This method:
2328	/// - Resumes a paused download session
2329	/// - Uses HTTP Range header for resume capability
2330	///
2331	/// # Arguments
2332	/// * `update_info` - Update information to resume download
2333	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	/// Get update configuration
2352	///
2353	/// Returns the current update channel configuration
2354	pub async fn GetUpdateChannel(&self) -> UpdateChannel { self.update_channel }
2355
2356	/// Set update channel
2357	///
2358	/// # Arguments
2359	/// * `channel` - New update channel to use
2360	pub async fn SetUpdateChannel(&mut self, channel:UpdateChannel) { self.update_channel = channel; }
2361
2362	/// Recursively copy a directory
2363	///
2364	/// This helper method copies all files and subdirectories from source to
2365	/// destination. Used during backup and restore operations.
2366	///
2367	/// # Arguments
2368	/// * `src` - Source directory path
2369	/// * `dst` - Destination directory path
2370	///
2371	/// # Returns
2372	/// Result<()> indicating success or failure
2373	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	/// Stage update for pre-installation verification
2409	///
2410	/// This method:
2411	/// - Stages the update in the staging directory
2412	/// - Verifies the staged update
2413	/// - Prepares for installation
2414	///
2415	/// # Arguments
2416	/// * `update_info` - Update information to stage
2417	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		// Create version-specific staging directory
2433		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		// Copy update package to staging
2440		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		// Verify staged package
2447		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	/// Clean up old update files
2455	///
2456	/// Removes downloaded updates older than a certain threshold
2457	/// to free disk space
2458	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			// Skip directories and recent files (within 7 days)
2482			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	/// Get the cache directory path
2504	pub fn GetCacheDirectory(&self) -> &PathBuf { &self.cache_directory }
2505
2506	/// Start background update checking task
2507	///
2508	/// This method:
2509	/// - Periodically checks for updates based on configured interval
2510	/// - Runs in a separate tokio task
2511	/// - Can be cancelled by stopping the task
2512	///
2513	/// # Returns
2514	/// Result<tokio::task::JoinHandle<()>> - Handle to the background task
2515	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		// Store the handle for later cancellation
2523		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	/// Background task for periodic update checks
2533	///
2534	/// This task:
2535	/// - Checks for updates at regular intervals
2536	/// - Logs any errors but doesn't fail the task
2537	/// - Can run indefinitely until stopped
2538	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			// Check for updates
2563			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	/// Stop background tasks
2584	///
2585	/// This method:
2586	/// - Logs the stop request
2587	/// - Aborts the stored JoinHandle to cancel the background task
2588	pub async fn StopBackgroundTasks(&self) {
2589		dev_log!("update", "[UpdateManager] Stopping background tasks");
2590
2591		// Cancel the stored task handle if it exists
2592		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	/// Format byte count to human-readable string
2604	///
2605	/// # Arguments
2606	/// * `bytes` - Number of bytes (supports both u64 and f64 for rates)
2607	///
2608	/// # Returns
2609	/// Formatted string (e.g., "1.5 MB", "500 KB")
2610	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}