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	#[allow(dead_code)]
136	session_id:String,
137
138	/// Original update URL
139	#[allow(dead_code)]
140	download_url:String,
141
142	/// Current file path
143	#[allow(dead_code)]
144	temp_path:PathBuf,
145
146	/// Bytes downloaded so far
147	downloaded_bytes:u64,
148
149	/// Total file size
150	#[allow(dead_code)]
151	total_bytes:u64,
152
153	/// Whether download is complete
154	complete:bool,
155
156	/// Cancellation flag for download
157	cancelled:bool,
158}
159
160/// Rollback history for automatic and manual rollback
161#[derive(Debug, Clone, Serialize, Deserialize)]
162struct RollbackHistory {
163	/// Previous versions available for rollback
164	versions:Vec<RollbackState>,
165
166	/// Maximum number of versions to keep
167	max_versions:usize,
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct RollbackState {
172	version:String,
173
174	backup_path:PathBuf,
175
176	timestamp:chrono::DateTime<chrono::Utc>,
177
178	checksum:String,
179}
180
181/// Update channel configuration
182#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
183pub enum UpdateChannel {
184	Stable,
185
186	Insiders,
187
188	Preview,
189}
190
191impl UpdateChannel {
192	fn as_str(&self) -> &'static str {
193		match self {
194			UpdateChannel::Stable => "stable",
195
196			UpdateChannel::Insiders => "insiders",
197
198			UpdateChannel::Preview => "preview",
199		}
200	}
201}
202
203/// Platform-specific update configuration
204#[derive(Debug, Clone)]
205struct PlatformConfig {
206	platform:String,
207
208	arch:String,
209
210	package_format:PackageFormat,
211}
212
213/// Supported package formats by platform
214#[derive(Debug, Clone, Copy)]
215#[allow(dead_code)]
216enum PackageFormat {
217	WindowsExe,
218
219	MacOsDmg,
220
221	LinuxAppImage,
222
223	LinuxDeb,
224
225	LinuxRpm,
226}
227
228/// Update status with comprehensive state tracking
229#[derive(Debug, Clone, Serialize, Deserialize)]
230pub struct UpdateStatus {
231	/// Last time updates were checked
232	pub last_check:Option<chrono::DateTime<chrono::Utc>>,
233
234	/// Whether an update is available
235	pub update_available:bool,
236
237	/// Current installed version
238	pub current_version:String,
239
240	/// Available version (if any)
241	pub available_version:Option<String>,
242
243	/// Download progress (0.0 to 100.0)
244	pub download_progress:Option<f32>,
245
246	/// Current installation status
247	pub installation_status:InstallationStatus,
248
249	/// Update channel being used
250	pub update_channel:UpdateChannel,
251
252	/// Size of available update in bytes
253	pub update_size:Option<u64>,
254
255	/// Release notes for available update
256	pub release_notes:Option<String>,
257
258	/// Whether update requires restart
259	pub requires_restart:bool,
260
261	/// Download speed in bytes per second
262	pub download_speed:Option<f64>,
263
264	/// Estimated time remaining in seconds
265	pub eta_seconds:Option<u64>,
266
267	/// Last error message (if any)
268	pub last_error:Option<String>,
269}
270
271/// Installation status with detailed state tracking
272#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
273pub enum InstallationStatus {
274	/// No update operation in progress
275	NotStarted,
276
277	/// Verifying disk space and prerequisites
278	CheckingPrerequisites,
279
280	/// Downloading update package
281	Downloading,
282
283	/// Download paused (resumable)
284	Paused,
285
286	/// Verifying cryptographic signatures
287	VerifyingSignature,
288
289	/// Verifying checksums (SHA256, MD5, etc.)
290	VerifyingChecksums,
291
292	/// Staging update for pre-installation verification
293	Staging,
294
295	/// Creating backup before applying update
296	CreatingBackup,
297
298	/// Installing update
299	Installing,
300
301	/// Installation completed, awaiting restart
302	Completed,
303
304	/// Rolling back due to installation failure
305	RollingBack,
306
307	/// Installation failed with error message
308	Failed(String),
309}
310
311impl InstallationStatus {
312	/// Check if the current status allows cancellation
313	pub fn is_cancellable(&self) -> bool {
314		matches!(
315			self,
316			InstallationStatus::Downloading
317				| InstallationStatus::Paused
318				| InstallationStatus::Staging
319				| InstallationStatus::NotStarted
320		)
321	}
322
323	/// Check if the current status represents an error
324	pub fn is_error(&self) -> bool { matches!(self, InstallationStatus::Failed(_)) }
325
326	/// Check if the current status represents progress
327	pub fn is_in_progress(&self) -> bool {
328		matches!(
329			self,
330			InstallationStatus::CheckingPrerequisites
331				| InstallationStatus::Downloading
332				| InstallationStatus::VerifyingSignature
333				| InstallationStatus::VerifyingChecksums
334				| InstallationStatus::Staging
335				| InstallationStatus::CreatingBackup
336				| InstallationStatus::Installing
337		)
338	}
339}
340
341/// Update information with comprehensive metadata
342#[derive(Debug, Clone, Serialize, Deserialize)]
343pub struct UpdateInfo {
344	/// Semantic version (e.g., "1.2.3")
345	pub version:String,
346
347	/// Download URL for the update package
348	pub download_url:String,
349
350	/// Release notes and changelog
351	pub release_notes:String,
352
353	/// Primary checksum (SHA256 recommended)
354	pub checksum:String,
355
356	/// Alternative checksums for verification
357	pub checksums:HashMap<String, String>,
358
359	/// Size of update package in bytes
360	pub size:u64,
361
362	/// When the update was published
363	pub published_at:chrono::DateTime<chrono::Utc>,
364
365	/// Whether this update is mandatory
366	pub is_mandatory:bool,
367
368	/// Whether update requires application restart
369	pub requires_restart:bool,
370
371	/// Minimum compatible version
372	pub min_compatible_version:Option<String>,
373
374	/// Delta update URL (if available for incremental update)
375	pub delta_url:Option<String>,
376
377	/// Delta update checksum (if available)
378	pub delta_checksum:Option<String>,
379
380	/// Delta update size (if available)
381	pub delta_size:Option<u64>,
382
383	/// Cryptographic signature (Ed25519 or PGP)
384	pub signature:Option<String>,
385
386	/// Platform-specific metadata
387	pub platform_metadata:Option<PlatformMetadata>,
388}
389
390/// Platform-specific update metadata
391#[derive(Debug, Clone, Serialize, Deserialize)]
392pub struct PlatformMetadata {
393	/// Package format (exe, dmg, appimage, etc.)
394	pub package_format:String,
395
396	/// Installation instructions
397	pub install_instructions:Vec<String>,
398
399	/// Required disk space in bytes
400	pub required_disk_space:u64,
401
402	/// Whether admin privileges are required
403	pub requires_admin:bool,
404
405	/// Additional platform-specific parameters
406	pub additional_params:HashMap<String, serde_json::Value>,
407}
408
409/// Update telemetry data for analytics
410#[derive(Debug, Clone, Serialize, Deserialize)]
411pub struct UpdateTelemetry {
412	/// Unique telemetry event ID
413	pub event_id:String,
414
415	/// Current version
416	pub current_version:String,
417
418	/// Target version
419	pub target_version:String,
420
421	/// Update channel
422	pub channel:String,
423
424	/// Platform identifier
425	pub platform:String,
426
427	/// Operation type (check, download, install, rollback)
428	pub operation:String,
429
430	/// Success or failure
431	pub success:bool,
432
433	/// Duration in milliseconds
434	pub duration_ms:u64,
435
436	/// Download size in bytes
437	pub download_size:Option<u64>,
438
439	/// Error message (if failed)
440	pub error_message:Option<String>,
441
442	/// Timestamp of the event
443	pub timestamp:chrono::DateTime<chrono::Utc>,
444}
445
446impl UpdateManager {
447	/// Create a new update manager with comprehensive initialization
448	pub async fn new(AppState:Arc<ApplicationState>) -> Result<Self> {
449		let config = &AppState.Configuration.Updates;
450
451		// Expand cache directory path
452		let cache_directory = ConfigurationManager::ExpandPath(&AppState.Configuration.Downloader.CacheDirectory)?;
453
454		// Create cache directory if it doesn't exist
455		tokio::fs::create_dir_all(&cache_directory)
456			.await
457			.map_err(|e| AirError::Configuration(format!("Failed to create cache directory: {}", e)))?;
458
459		// Create staging directory for pre-installation verification
460		let staging_directory = cache_directory.join("staging");
461
462		tokio::fs::create_dir_all(&staging_directory)
463			.await
464			.map_err(|e| AirError::Configuration(format!("Failed to create staging directory: {}", e)))?;
465
466		// Create backup directory for rollback
467		let backup_directory = cache_directory.join("backups");
468
469		tokio::fs::create_dir_all(&backup_directory)
470			.await
471			.map_err(|e| AirError::Configuration(format!("Failed to create backup directory: {}", e)))?;
472
473		// Determine platform-specific configuration
474		let PlatformConfig = Self::detect_platform();
475
476		let PlatformConfigClone = PlatformConfig.clone();
477
478		// Determine update channel from configuration
479		let update_channel = if config.Channel == "insiders" {
480			UpdateChannel::Insiders
481		} else if config.Channel == "preview" {
482			UpdateChannel::Preview
483		} else {
484			UpdateChannel::Stable
485		};
486
487		// Load or create rollback history
488		let rollback_history_path = backup_directory.join("rollback_history.json");
489
490		let rollback_history = if rollback_history_path.exists() {
491			let content = tokio::fs::read_to_string(&rollback_history_path)
492				.await
493				.map_err(|e| AirError::FileSystem(format!("Failed to read rollback history: {}", e)))?;
494
495			serde_json::from_str(&content).unwrap_or_else(|_| RollbackHistory { versions:Vec::new(), max_versions:5 })
496		} else {
497			RollbackHistory { versions:Vec::new(), max_versions:5 }
498		};
499
500		let manager = Self {
501			AppState,
502
503			update_status:Arc::new(RwLock::new(UpdateStatus {
504				last_check:None,
505				update_available:false,
506				current_version:env!("CARGO_PKG_VERSION").to_string(),
507				available_version:None,
508				download_progress:None,
509				installation_status:InstallationStatus::NotStarted,
510				update_channel,
511				update_size:None,
512				release_notes:None,
513				requires_restart:true,
514				download_speed:None,
515				eta_seconds:None,
516				last_error:None,
517			})),
518
519			cache_directory,
520
521			staging_directory,
522
523			backup_directory,
524
525			download_sessions:Arc::new(RwLock::new(HashMap::new())),
526
527			rollback_history:Arc::new(Mutex::new(rollback_history)),
528
529			update_channel,
530
531			platform_config:PlatformConfigClone,
532
533			background_task:Arc::new(Mutex::new(None)),
534		};
535
536		// Initialize service status
537		manager
538			.AppState
539			.UpdateServiceStatus("updates", crate::ApplicationState::ServiceStatus::Running)
540			.await
541			.map_err(|e| AirError::Internal(e.to_string()))?;
542
543		dev_log!(
544			"update",
545			"[UpdateManager] Update service initialized for platform: {}/{}",
546			PlatformConfig.platform,
547			PlatformConfig.arch
548		);
549
550		Ok(manager)
551	}
552
553	/// Detect the current platform and configure platform-specific settings
554	fn detect_platform() -> PlatformConfig {
555		let platform = if cfg!(target_os = "windows") {
556			"windows"
557		} else if cfg!(target_os = "macos") {
558			"macos"
559		} else if cfg!(target_os = "linux") {
560			"linux"
561		} else {
562			"unknown"
563		};
564
565		let arch = if cfg!(target_arch = "x86_64") {
566			"x64"
567		} else if cfg!(target_arch = "aarch64") {
568			"arm64"
569		} else if cfg!(target_arch = "x86") {
570			"ia32"
571		} else {
572			"unknown"
573		};
574
575		let package_format = match (platform, arch) {
576			("windows", _) => PackageFormat::WindowsExe,
577
578			("macos", _) => PackageFormat::MacOsDmg,
579
580			("linux", "x64") => PackageFormat::LinuxAppImage,
581
582			("linux", "") => PackageFormat::LinuxAppImage,
583
584			_ => PackageFormat::LinuxAppImage,
585		};
586
587		PlatformConfig { platform:platform.to_string(), arch:arch.to_string(), package_format }
588	}
589
590	/// Check for available updates from the configured update server
591	///
592	/// This method:
593	/// - Queries the update server based on the configured channel
594	/// - Validates the update against minimum compatibility requirements
595	/// - Updates the internal status with available update information
596	/// - Triggers automatic download if configured
597	///
598	/// Returns: Some(UpdateInfo) if an update is available, None otherwise
599	pub async fn CheckForUpdates(&self) -> Result<Option<UpdateInfo>> {
600		let config = &self.AppState.Configuration.Updates;
601
602		let start_time = std::time::Instant::now();
603
604		if !config.Enabled {
605			dev_log!("update", "[UpdateManager] Updates are disabled");
606
607			return Ok(None);
608		}
609
610		dev_log!(
611			"update",
612			"[UpdateManager] Checking for updates on {} channel",
613			self.update_channel.as_str()
614		);
615
616		// Update status
617		{
618			let mut status = self.update_status.write().await;
619
620			status.last_check = Some(chrono::Utc::now());
621
622			status.last_error = None;
623		}
624
625		// Check update server with resilience patterns
626		let update_info = match self.FetchUpdateInfo().await {
627			Ok(info) => info,
628
629			Err(e) => {
630				dev_log!("update", "error: [UpdateManager] Failed to fetch update info: {}", e);
631
632				let mut status = self.update_status.write().await;
633
634				status.last_error = Some(e.to_string());
635
636				self.record_telemetry(
637					"check",
638					false,
639					start_time.elapsed().as_millis() as u64,
640					None,
641					Some(e.to_string()),
642				)
643				.await;
644
645				return Err(e);
646			},
647		};
648
649		if let Some(ref info) = update_info {
650			// Verify minimum compatibility
651			if let Some(ref min_version) = info.min_compatible_version {
652				let current_version = env!("CARGO_PKG_VERSION");
653
654				if UpdateManager::CompareVersions(current_version, min_version) < 0 {
655					dev_log!(
656						"update",
657						"warn: [UpdateManager] Update requires minimum version {} but current is {}. Skipping.",
658						min_version,
659						current_version
660					);
661
662					let mut status = self.update_status.write().await;
663
664					status.last_error = Some(format!("Update requires minimum version {}", min_version));
665
666					return Ok(None);
667				}
668			}
669
670			dev_log!(
671				"update",
672				"[UpdateManager] Update available: {} ({})",
673				info.version,
674				self.format_size(info.size as f64)
675			);
676
677			// Update status
678			{
679				let mut status = self.update_status.write().await;
680
681				status.update_available = true;
682
683				status.available_version = Some(info.version.clone());
684
685				status.update_size = Some(info.size);
686
687				status.release_notes = Some(info.release_notes.clone());
688
689				status.requires_restart = info.requires_restart;
690			}
691
692			// Notify Mountain (frontend) about available update
693			// This would typically be done via event bus or gRPC
694			dev_log!("update", "[UpdateManager] Notifying frontend about available update");
695
696			// Record telemetry
697			self.record_telemetry("check", true, start_time.elapsed().as_millis() as u64, None, None)
698				.await;
699
700			// Auto-download if configured
701			if config.AutoDownload {
702				if let Err(e) = self.DownloadUpdate(info).await {
703					dev_log!("update", "error: [UpdateManager] Auto-download failed: {}", e); // Don't fail the check, just log the error
704				}
705			}
706		} else {
707			dev_log!("update", "[UpdateManager] No updates available");
708
709			// Update status
710			{
711				let mut status = self.update_status.write().await;
712
713				status.update_available = false;
714
715				status.available_version = None;
716
717				status.update_size = None;
718
719				status.release_notes = None;
720			}
721
722			// Record telemetry
723			self.record_telemetry("check", true, start_time.elapsed().as_millis() as u64, None, None)
724				.await;
725		}
726
727		Ok(update_info)
728	}
729
730	/// Download update package with resumable download support
731	///
732	/// This method:
733	/// - Validates available disk space before starting download
734	/// - Supports resumable downloads from network interruptions
735	/// - Tracks download progress and calculates ETA
736	/// - Updates download speed metrics
737	/// - Verifies all checksums after download
738	///
739	/// # Arguments
740	/// * `update_info` - Update information containing download URL and
741	///   metadata
742	///
743	/// # Returns
744	/// Result<()> indicating success or failure
745	pub async fn DownloadUpdate(&self, update_info:&UpdateInfo) -> Result<()> {
746		let start_time = std::time::Instant::now();
747
748		let session_id = Uuid::new_v4().to_string();
749
750		dev_log!(
751			"update",
752			"[UpdateManager] Starting download for version {} (session: {})",
753			update_info.version,
754			session_id
755		);
756
757		// Check prerequisites: disk space
758		let required_space = update_info.size * 2; // Need space for download + staging
759		self.ValidateDiskSpace(required_space).await?;
760
761		// Update status
762		{
763			let mut status = self.update_status.write().await;
764
765			status.installation_status = InstallationStatus::CheckingPrerequisites;
766
767			status.last_error = None;
768		}
769
770		// Create temp file path for download
771		let temp_path = self.cache_directory.join(format!("update-{}-temp.bin", update_info.version));
772
773		let final_path = self.cache_directory.join(format!("update-{}.bin", update_info.version));
774
775		// Check if there's an existing partial download to resume
776		let (downloaded_bytes, resume_from_start) = if temp_path.exists() {
777			let metadata = tokio::fs::metadata(&temp_path)
778				.await
779				.map_err(|e| AirError::FileSystem(format!("Failed to check temp file: {}", e)))?;
780
781			dev_log!(
782				"update",
783				"[UpdateManager] Found partial download, resuming from {} bytes",
784				metadata.len()
785			);
786
787			(metadata.len(), false)
788		} else {
789			(0, true)
790		};
791
792		// Create or open download session
793		{
794			let mut sessions = self.download_sessions.write().await;
795
796			sessions.insert(
797				session_id.clone(),
798				DownloadSession {
799					session_id:session_id.clone(),
800					download_url:update_info.download_url.clone(),
801					temp_path:temp_path.clone(),
802					downloaded_bytes,
803					total_bytes:update_info.size,
804					complete:false,
805					cancelled:false,
806				},
807			);
808		}
809
810		// Begin download
811		let dns_port = Mist::dns_port();
812
813		let client = crate::HTTP::Client::secured_client_with_timeout(dns_port, Duration::from_secs(300))
814			.map_err(|e| AirError::Network(format!("Failed to create HTTP client: {}", e)))?;
815
816		let mut request_builder = client.get(&update_info.download_url);
817
818		// Add range header for resume
819		if !resume_from_start {
820			request_builder = request_builder.header("Range", format!("bytes={}-", downloaded_bytes));
821		}
822
823		let response:reqwest::Response = request_builder
824			.send()
825			.await
826			.map_err(|e| AirError::Network(format!("Failed to start download: {}", e)))?;
827
828		if !response.status().is_success() && response.status() != 206 {
829			dev_log!(
830				"update",
831				"error: [UpdateManager] Download failed with status: {}",
832				response.status()
833			);
834
835			let mut status = self.update_status.write().await;
836
837			status.installation_status =
838				InstallationStatus::Failed(format!("Download failed with status: {}", response.status()));
839
840			status.last_error = Some(format!("Download failed with status: {}", response.status()));
841
842			self.record_telemetry(
843				"download",
844				false,
845				start_time.elapsed().as_millis() as u64,
846				None,
847				Some("Download failed".to_string()),
848			)
849			.await;
850
851			return Err(AirError::Network(format!("Download failed with status: {}", response.status())));
852		}
853
854		let total_size = response.content_length().unwrap_or(update_info.size);
855
856		let initial_downloaded = if resume_from_start { 0 } else { downloaded_bytes };
857
858		// Update status to downloading
859		{
860			let mut status = self.update_status.write().await;
861
862			status.installation_status = InstallationStatus::Downloading;
863
864			status.download_progress = Some(((downloaded_bytes as f32 / total_size as f32) * 100.0).min(100.0));
865		}
866
867		// Progress tracking
868		let last_update = Arc::new(Mutex::new(std::time::Instant::now()));
869
870		let last_bytes = Arc::new(Mutex::new(downloaded_bytes));
871
872		// Open or create file
873		let mut file = if resume_from_start {
874			tokio::fs::File::create(&temp_path)
875				.await
876				.map_err(|e| AirError::FileSystem(format!("Failed to create update file: {}", e)))?
877		} else {
878			// Open with append for resume
879			tokio::fs::OpenOptions::new()
880				.append(true)
881				.open(&temp_path)
882				.await
883				.map_err(|e| AirError::FileSystem(format!("Failed to open update file for resume: {}", e)))?
884		};
885
886		use tokio::io::AsyncWriteExt;
887		use futures_util::StreamExt;
888
889		let mut byte_stream = response.bytes_stream();
890
891		let mut downloaded = initial_downloaded;
892
893		while let Some(chunk_result) = byte_stream.next().await {
894			match chunk_result {
895				Ok(chunk) => {
896					let chunk_bytes:&[u8] = &chunk;
897
898					file.write_all(chunk_bytes)
899						.await
900						.map_err(|e| AirError::FileSystem(format!("Failed to write update file: {}", e)))?;
901
902					downloaded += chunk.len() as u64;
903
904					// Update progress every second
905					{
906						let mut last_update_guard = last_update.lock().await;
907
908						let mut last_bytes_guard = last_bytes.lock().await;
909
910						if last_update_guard.elapsed() >= Duration::from_secs(1) {
911							let bytes_this_second = downloaded - *last_bytes_guard;
912
913							let download_speed = bytes_this_second as f64;
914
915							let progress = ((downloaded as f32 / total_size as f32) * 100.0).min(100.0);
916
917							let remaining_bytes = total_size - downloaded;
918
919							let eta_seconds = if download_speed > 0.0 {
920								Some(remaining_bytes as u64 / (download_speed as u64).max(1))
921							} else {
922								None
923							};
924
925							{
926								let mut status = self.update_status.write().await;
927
928								status.download_progress = Some(progress);
929
930								status.download_speed = Some(download_speed);
931
932								status.eta_seconds = eta_seconds;
933							}
934
935							dev_log!(
936								"update",
937								"[UpdateManager] Download progress: {:.1}% ({}/s, ETA: {:?})",
938								progress,
939								self.format_size(download_speed),
940								eta_seconds
941							);
942
943							*last_update_guard = std::time::Instant::now();
944							*last_bytes_guard = downloaded;
945						}
946					}
947
948					// Update session
949					{
950						let mut sessions = self.download_sessions.write().await;
951
952						if let Some(session) = sessions.get_mut(&session_id) {
953							session.downloaded_bytes = downloaded;
954						}
955					}
956				},
957
958				Err(e) => {
959					dev_log!("update", "error: [UpdateManager] Download error: {}", e);
960
961					let mut status = self.update_status.write().await;
962
963					status.installation_status = InstallationStatus::Failed(format!("Network error: {}", e));
964
965					status.last_error = Some(format!("Network error: {}", e));
966
967					self.record_telemetry(
968						"download",
969						false,
970						start_time.elapsed().as_millis() as u64,
971						None,
972						Some(e.to_string()),
973					)
974					.await;
975
976					return Err(AirError::Network(format!("Download error: {}", e)));
977				},
978			}
979		}
980
981		// Download complete
982		{
983			let mut status = self.update_status.write().await;
984
985			status.installation_status = InstallationStatus::Downloading;
986
987			status.download_progress = Some(100.0);
988		}
989
990		dev_log!(
991			"update",
992			"[UpdateManager] Download completed: {} bytes in {:.2}s",
993			downloaded,
994			start_time.elapsed().as_secs_f64()
995		);
996
997		// Verify download integrity with all checksums
998		{
999			let mut status = self.update_status.write().await;
1000
1001			status.installation_status = InstallationStatus::VerifyingChecksums;
1002		}
1003
1004		self.VerifyChecksum(&temp_path, &update_info.checksum).await?;
1005
1006		// Verify additional checksums if provided
1007		for (algorithm, expected_checksum) in &update_info.checksums {
1008			self.VerifyChecksumWithAlgorithm(&temp_path, algorithm, expected_checksum)
1009				.await?;
1010		}
1011
1012		// Verify cryptographic signature if provided
1013		if let Some(ref signature) = update_info.signature {
1014			{
1015				let mut status = self.update_status.write().await;
1016
1017				status.installation_status = InstallationStatus::VerifyingSignature;
1018			}
1019
1020			self.VerifySignature(&temp_path, signature).await?;
1021		}
1022
1023		// Move temp file to final location (atomic)
1024		if temp_path.exists() {
1025			tokio::fs::rename(&temp_path, &final_path)
1026				.await
1027				.map_err(|e| AirError::FileSystem(format!("Failed to finalize download: {}", e)))?;
1028		}
1029
1030		// Update session
1031		{
1032			let mut sessions = self.download_sessions.write().await;
1033
1034			if let Some(session) = sessions.get_mut(&session_id) {
1035				session.complete = true;
1036			}
1037		}
1038
1039		// Update status to completed
1040		{
1041			let mut status = self.update_status.write().await;
1042
1043			status.installation_status = InstallationStatus::Completed;
1044
1045			status.download_progress = Some(100.0);
1046		}
1047
1048		dev_log!(
1049			"update",
1050			"[UpdateManager] Update {} downloaded and verified successfully",
1051			update_info.version
1052		);
1053
1054		// Record telemetry
1055		self.record_telemetry(
1056			"download",
1057			true,
1058			start_time.elapsed().as_millis() as u64,
1059			Some(downloaded),
1060			None,
1061		)
1062		.await;
1063
1064		Ok(())
1065	}
1066
1067	/// Apply update with rollback capability
1068	///
1069	/// This method:
1070	/// - Creates full backup of current installation
1071	/// - Validates update package integrity
1072	/// - Applies update atomically
1073	/// - Automatically rolls back on failure
1074	/// - Updates rollback history
1075	///
1076	/// # Arguments
1077	/// * `update_info` - Update information for the version to apply
1078	///
1079	/// # Returns
1080	/// Result<()> indicating success or failure (with automatic rollback)
1081	pub async fn ApplyUpdate(&self, update_info:&UpdateInfo) -> Result<()> {
1082		let start_time = std::time::Instant::now();
1083
1084		let current_version = env!("CARGO_PKG_VERSION");
1085
1086		dev_log!(
1087			"update",
1088			"[UpdateManager] Applying update: {} (from {})",
1089			update_info.version,
1090			current_version
1091		);
1092
1093		let file_path = self.cache_directory.join(format!("update-{}.bin", update_info.version));
1094
1095		// Verify download exists
1096		if !file_path.exists() {
1097			dev_log!("update", "error: [UpdateManager] Update file not found: {:?}", file_path);
1098
1099			return Err(AirError::FileSystem(
1100				"Update file not found. Please download first.".to_string(),
1101			));
1102		}
1103
1104		// Update status to verifying
1105		{
1106			let mut status = self.update_status.write().await;
1107
1108			status.installation_status = InstallationStatus::VerifyingChecksums;
1109
1110			status.last_error = None;
1111		}
1112
1113		// Final verification before applying
1114		self.VerifyChecksum(&file_path, &update_info.checksum).await?;
1115
1116		// Verify additional checksums
1117		for (algorithm, expected_checksum) in &update_info.checksums {
1118			self.VerifyChecksumWithAlgorithm(&file_path, algorithm, expected_checksum)
1119				.await?;
1120		}
1121
1122		// Verify signature if provided
1123		if let Some(ref signature) = update_info.signature {
1124			{
1125				let mut status = self.update_status.write().await;
1126
1127				status.installation_status = InstallationStatus::VerifyingSignature;
1128			}
1129
1130			self.VerifySignature(&file_path, signature).await?;
1131		}
1132
1133		// Create backup before applying update
1134		{
1135			let mut status = self.update_status.write().await;
1136
1137			status.installation_status = InstallationStatus::CreatingBackup;
1138		}
1139
1140		let backup_info = self.CreateBackup(current_version).await?;
1141
1142		dev_log!("update", "[UpdateManager] Backup created: {:?}", backup_info.backup_path);
1143
1144		// Update status to installing
1145		{
1146			let mut status = self.update_status.write().await;
1147
1148			status.installation_status = InstallationStatus::Installing;
1149		}
1150
1151		// Apply the update based on platform
1152		let result = match self.platform_config.package_format {
1153			#[cfg(target_os = "windows")]
1154			PackageFormat::WindowsExe => self.ApplyWindowsUpdate(&file_path).await,
1155
1156			#[cfg(not(target_os = "windows"))]
1157			PackageFormat::WindowsExe => Err(AirError::Internal("Windows update not available on this platform".to_string())),
1158
1159			PackageFormat::MacOsDmg => self.ApplyMacOsUpdate(&file_path).await,
1160
1161			#[cfg(all(target_os = "linux", feature = "appimage"))]
1162			PackageFormat::LinuxAppImage => self.ApplyLinuxAppImageUpdate(&file_path).await,
1163
1164			#[cfg(not(all(target_os = "linux", feature = "appimage")))]
1165			PackageFormat::LinuxAppImage => {
1166				Err(AirError::Internal(
1167					"Linux AppImage update not available on this platform".to_string(),
1168				))
1169			},
1170
1171			#[cfg(all(target_os = "linux", feature = "deb"))]
1172			PackageFormat::LinuxDeb => self.ApplyLinuxDebUpdate(&file_path).await,
1173
1174			#[cfg(not(all(target_os = "linux", feature = "deb")))]
1175			PackageFormat::LinuxDeb => {
1176				Err(AirError::Internal(
1177					"Linux DEB update not available on this platform".to_string(),
1178				))
1179			},
1180
1181			#[cfg(all(target_os = "linux", feature = "rpm"))]
1182			PackageFormat::LinuxRpm => self.ApplyLinuxRpmUpdate(&file_path).await,
1183
1184			#[cfg(not(all(target_os = "linux", feature = "rpm")))]
1185			PackageFormat::LinuxRpm => {
1186				Err(AirError::Internal(
1187					"Linux RPM update not available on this platform".to_string(),
1188				))
1189			},
1190		};
1191
1192		if let Err(e) = result {
1193			dev_log!(
1194				"update",
1195				"error: [UpdateManager] Installation failed, initiating rollback: {}",
1196				e
1197			);
1198
1199			// Update status to rolling back
1200			{
1201				let mut status = self.update_status.write().await;
1202
1203				status.installation_status = InstallationStatus::RollingBack;
1204			}
1205
1206			// Rollback to the backup
1207			if let Err(rollback_err) = self.RollbackToBackup(&backup_info).await {
1208				dev_log!("update", "error: [UpdateManager] Rollback also failed: {}", rollback_err);
1209
1210				// Critical error - both update and rollback failed
1211				let mut status = self.update_status.write().await;
1212
1213				status.installation_status = InstallationStatus::Failed(format!(
1214					"Installation failed and rollback failed: {} / {}",
1215					e, rollback_err
1216				));
1217
1218				status.last_error = Some(format!("Installation failed and rollback failed"));
1219
1220				self.record_telemetry(
1221					"install",
1222					false,
1223					start_time.elapsed().as_millis() as u64,
1224					None,
1225					Some(format!("Update and rollback failed: {}", rollback_err)),
1226				)
1227				.await;
1228
1229				return Err(AirError::Internal(format!(
1230					"Installation failed and rollback failed: {} / {}",
1231					e, rollback_err
1232				)));
1233			} else {
1234				dev_log!("update", "[UpdateManager] Rollback successful");
1235
1236				let mut status = self.update_status.write().await;
1237
1238				status.installation_status =
1239					InstallationStatus::Failed(format!("Installation failed, rollback successful: {}", e));
1240
1241				status.last_error = Some(e.to_string());
1242
1243				self.record_telemetry(
1244					"install",
1245					false,
1246					start_time.elapsed().as_millis() as u64,
1247					None,
1248					Some(e.to_string()),
1249				)
1250				.await;
1251
1252				return Err(AirError::Internal(format!("Installation failed, rollback successful: {}", e)));
1253			}
1254		}
1255
1256		// Update successful - add to rollback history
1257		{
1258			let mut history = self.rollback_history.lock().await;
1259
1260			history.versions.insert(0, backup_info);
1261
1262			// Keep only max_versions
1263			while history.versions.len() > history.max_versions {
1264				if let Some(old_backup) = history.versions.pop() {
1265					// Clean up old backup directory
1266					let _ = tokio::fs::remove_dir_all(&old_backup.backup_path).await;
1267				}
1268			}
1269		}
1270
1271		// Save rollback history
1272		let history_path = self.backup_directory.join("rollback_history.json");
1273
1274		let history = self.rollback_history.lock().await;
1275
1276		let history_json = serde_json::to_string(&*history)
1277			.map_err(|e| AirError::Internal(format!("Failed to serialize rollback history: {}", e)))?;
1278
1279		drop(history);
1280
1281		tokio::fs::write(&history_path, history_json)
1282			.await
1283			.map_err(|e| AirError::FileSystem(format!("Failed to save rollback history: {}", e)))?;
1284
1285		// Update current version in status
1286		{
1287			let mut status = self.update_status.write().await;
1288
1289			status.current_version = update_info.version.clone();
1290
1291			status.installation_status = InstallationStatus::Completed;
1292		}
1293
1294		dev_log!(
1295			"update",
1296			"[UpdateManager] Update {} applied successfully in {:.2}s",
1297			update_info.version,
1298			start_time.elapsed().as_secs_f64()
1299		);
1300
1301		// Record telemetry
1302		self.record_telemetry(
1303			"install",
1304			true,
1305			start_time.elapsed().as_millis() as u64,
1306			Some(update_info.size),
1307			None,
1308		)
1309		.await;
1310
1311		Ok(())
1312	}
1313
1314	/// Fetch update information from the configured update server
1315	///
1316	/// This method:
1317	/// - Queries the update server based on platform, version, and channel
1318	/// - Uses circuit breakers and retry policies for resilience
1319	/// - Returns update information if a newer version is available
1320	///
1321	/// # Returns
1322	/// Result<Option<`UpdateInfo`>> - Some if update available, None if
1323	/// up-to-date
1324	async fn FetchUpdateInfo(&self) -> Result<Option<UpdateInfo>> {
1325		let config = &self.AppState.Configuration.Updates;
1326
1327		// Setup resilience patterns
1328		let retry_policy = crate::Resilience::RetryPolicy {
1329			MaxRetries:3,
1330
1331			InitialIntervalMs:1000,
1332
1333			MaxIntervalMs:16000,
1334
1335			BackoffMultiplier:2.0,
1336
1337			JitterFactor:0.1,
1338
1339			BudgetPerMinute:50,
1340
1341			ErrorClassification:std::collections::HashMap::new(),
1342		};
1343
1344		let _retry_manager = crate::Resilience::RetryManager::new(retry_policy.clone());
1345
1346		let circuit_breaker = crate::Resilience::CircuitBreaker::new(
1347			"updates".to_string(),
1348			crate::Resilience::CircuitBreakerConfig::default(),
1349		);
1350
1351		let current_version = env!("CARGO_PKG_VERSION");
1352
1353		let mut attempt = 0;
1354
1355		loop {
1356			// Check circuit breaker state before attempting request
1357			if circuit_breaker.GetState().await == crate::Resilience::CircuitState::Open {
1358				if !circuit_breaker.AttemptRecovery().await {
1359					dev_log!("update", "warn: [UpdateManager] Circuit breaker is open, skipping update check");
1360
1361					return Ok(None);
1362				}
1363			}
1364
1365			// Build request URL with all necessary parameters
1366			let update_url = format!(
1367				"{}/check?version={}&platform={}&arch={}&channel={}",
1368				config.UpdateServerUrl,
1369				current_version,
1370				self.platform_config.platform,
1371				self.platform_config.arch,
1372				self.update_channel.as_str()
1373			);
1374
1375			let dns_port = Mist::dns_port();
1376
1377			let client = crate::HTTP::Client::secured_client_with_timeout(dns_port, Duration::from_secs(30))
1378				.map_err(|e| AirError::Network(format!("Failed to create HTTP client: {}", e)))?;
1379
1380			match client.get(&update_url).send().await {
1381				Ok(response) => {
1382					let status:reqwest::StatusCode = response.status();
1383
1384					match status {
1385						reqwest::StatusCode::NO_CONTENT => {
1386							// No update available (up to date)
1387							circuit_breaker.RecordSuccess().await;
1388
1389							dev_log!("update", "[UpdateManager] Server reports no updates available");
1390
1391							return Ok(None);
1392						},
1393
1394						status if status.is_success() => {
1395							// Parse update information
1396							match response.json::<UpdateInfo>().await {
1397								Ok(update_info) => {
1398									circuit_breaker.RecordSuccess().await;
1399
1400									// Check if update is actually newer
1401									if UpdateManager::CompareVersions(current_version, &update_info.version) < 0 {
1402										dev_log!(
1403											"update",
1404											"[UpdateManager] Update available: {} -> {}",
1405											current_version,
1406											update_info.version
1407										);
1408
1409										return Ok(Some(update_info));
1410									} else {
1411										dev_log!(
1412											"update",
1413											"[UpdateManager] Server returned same or older version: {}",
1414											update_info.version
1415										);
1416
1417										return Ok(None);
1418									}
1419								},
1420
1421								Err(e) => {
1422									circuit_breaker.RecordFailure().await;
1423
1424									dev_log!("update", "error: [UpdateManager] Failed to parse update info: {}", e);
1425
1426									if attempt < retry_policy.MaxRetries {
1427										attempt += 1;
1428
1429										let delay = Duration::from_millis(
1430											retry_policy.InitialIntervalMs * 2_u32.pow(attempt as u32) as u64,
1431										);
1432
1433										sleep(delay).await;
1434
1435										continue;
1436									} else {
1437										return Err(AirError::Network(format!(
1438											"Failed to parse update info after retries: {}",
1439											e
1440										)));
1441									}
1442								},
1443							}
1444						},
1445
1446						status => {
1447							circuit_breaker.RecordFailure().await;
1448
1449							dev_log!("update", "warn: [UpdateManager] Update server returned status: {}", status);
1450
1451							if attempt < retry_policy.MaxRetries {
1452								attempt += 1;
1453
1454								let delay = Duration::from_millis(
1455									retry_policy.InitialIntervalMs * 2_u32.pow(attempt as u32) as u64,
1456								);
1457
1458								sleep(delay).await;
1459
1460								continue;
1461							} else {
1462								return Ok(None);
1463							}
1464						},
1465					}
1466				},
1467
1468				Err(e) => {
1469					circuit_breaker.RecordFailure().await;
1470
1471					dev_log!("update", "warn: [UpdateManager] Failed to check for updates: {}", e);
1472
1473					if attempt < retry_policy.MaxRetries {
1474						attempt += 1;
1475
1476						let delay =
1477							Duration::from_millis(retry_policy.InitialIntervalMs * 2_u32.pow(attempt as u32) as u64);
1478
1479						sleep(delay).await;
1480
1481						continue;
1482					} else {
1483						return Ok(None);
1484					}
1485				},
1486			}
1487		}
1488	}
1489
1490	/// Verify file checksum (SHA256 by default)
1491	///
1492	/// This method:
1493	/// - Reads the entire file into memory
1494	/// - Computes SHA256 hash
1495	/// - Compares with expected checksum
1496	///
1497	/// # Arguments
1498	/// * `file_path` - Path to the file to verify
1499	/// * `expected_checksum` - Expected SHA256 checksum in hex format
1500	///
1501	/// # Returns
1502	/// Result<()> indicating success or failure
1503	async fn VerifyChecksum(&self, file_path:&Path, expected_checksum:&str) -> Result<()> {
1504		let content = tokio::fs::read(file_path)
1505			.await
1506			.map_err(|e| AirError::FileSystem(format!("Failed to read update file for checksum: {}", e)))?;
1507
1508		let actual_checksum = self.CalculateSha256(&content);
1509
1510		if actual_checksum.to_lowercase() != expected_checksum.to_lowercase() {
1511			dev_log!(
1512				"update",
1513				"error: [UpdateManager] Checksum verification failed: expected {}, got {}",
1514				expected_checksum,
1515				actual_checksum
1516			);
1517
1518			return Err(AirError::Network("Update checksum verification failed".to_string()));
1519		}
1520
1521		dev_log!("update", "[UpdateManager] Checksum verified: {}", actual_checksum);
1522
1523		Ok(())
1524	}
1525
1526	/// Verify file checksum with specified algorithm
1527	///
1528	/// Supports multiple checksum algorithms for comprehensive integrity
1529	/// checking
1530	///
1531	/// # Arguments
1532	/// * `file_path` - Path to the file to verify
1533	/// * `algorithm` - Checksum algorithm (md5, sha1, sha256, sha512)
1534	/// * `expected_checksum` - Expected checksum in hex format
1535	///
1536	/// # Returns
1537	/// Result<()> indicating success or failure
1538	async fn VerifyChecksumWithAlgorithm(&self, file_path:&Path, algorithm:&str, expected_checksum:&str) -> Result<()> {
1539		let content = tokio::fs::read(file_path).await.map_err(|e| {
1540			AirError::FileSystem(format!("Failed to read update file for {} checksum: {}", algorithm, e))
1541		})?;
1542
1543		let actual_checksum = match algorithm.to_lowercase().as_str() {
1544			"sha256" => self.CalculateSha256(&content),
1545
1546			"sha512" => self.CalculateSha512(&content),
1547
1548			"md5" => self.CalculateMd5(&content),
1549
1550			"crc32" => self.CalculateCrc32(&content),
1551
1552			_ => {
1553				dev_log!(
1554					"update",
1555					"warn: [UpdateManager] Unknown checksum algorithm: {}, skipping",
1556					algorithm
1557				);
1558
1559				return Ok(());
1560			},
1561		};
1562
1563		if actual_checksum.to_lowercase() != expected_checksum.to_lowercase() {
1564			dev_log!(
1565				"update",
1566				"error: [UpdateManager] {} checksum verification failed: expected {}, got {}",
1567				algorithm,
1568				expected_checksum,
1569				actual_checksum
1570			);
1571
1572			return Err(AirError::Network(format!("{} checksum verification failed", algorithm)));
1573		}
1574
1575		dev_log!("update", "[UpdateManager] {} checksum verified: {}", algorithm, actual_checksum);
1576
1577		Ok(())
1578	}
1579
1580	/// Verify cryptographic signature of update package
1581	///
1582	/// This method:
1583	/// - Uses Ed25519 signature verification
1584	/// - Verifies the package hasn't been tampered with
1585	/// - Uses the public key configured in the system
1586	///
1587	/// # Arguments
1588	/// * `file_path` - Path to the signed file
1589	/// * `signature` - Base64-encoded signature
1590	///
1591	/// # Returns
1592	/// Result<()> indicating success or failure
1593	async fn VerifySignature(&self, _file_path:&Path, _signature:&str) -> Result<()> {
1594		// Signature verification stub implementation
1595		// For production use, this would require:
1596		// 1. A public key embedded in the application
1597		// 2. Use ring::signature or ed25519-dalek for Ed25519 verification
1598		// 3. Decode the base64 signature
1599		// 4. Verify the file content against the signature
1600
1601		// In development builds, we skip signature verification
1602		#[cfg(debug_assertions)]
1603		{
1604			dev_log!("update", "[UpdateManager] Development build: skipping signature verification");
1605
1606			return Ok(());
1607		}
1608
1609		// In release builds, we log a warning but allow updates to proceed
1610		// This is a security decision that should be reviewed for production
1611		#[cfg(not(debug_assertions))]
1612		{
1613			dev_log!(
1614				"update",
1615				"warn: [UpdateManager] WARNING: Cryptographic signature verification is not yet implemented"
1616			);
1617
1618			dev_log!(
1619				"update",
1620				"warn: [UpdateManager] Update packages should be cryptographically signed in production"
1621			);
1622
1623			dev_log!(
1624				"update",
1625				"[UpdateManager] Proceeding with update without signature verification"
1626			);
1627
1628			return Ok(());
1629		}
1630	}
1631
1632	/// Create backup of current installation
1633	///
1634	/// This method:
1635	/// - Creates a timestamped backup directory
1636	/// - Copies critical files (binaries, config, data)
1637	/// - Computes checksum of backup for rollback verification
1638	///
1639	/// # Arguments
1640	/// * `version` - Current version being backed up
1641	///
1642	/// # Returns
1643	/// Result<`RollbackState`> containing backup information
1644	async fn CreateBackup(&self, version:&str) -> Result<RollbackState> {
1645		let timestamp = chrono::Utc::now();
1646
1647		let backup_dir_name = format!("backup-{}-{}", version, timestamp.format("%Y%m%d_%H%M%S"));
1648
1649		let backup_path = self.backup_directory.join(&backup_dir_name);
1650
1651		dev_log!("update", "[UpdateManager] Creating backup: {}", backup_dir_name);
1652
1653		// Create backup directory
1654		tokio::fs::create_dir_all(&backup_path)
1655			.await
1656			.map_err(|e| AirError::FileSystem(format!("Failed to create backup directory: {}", e)))?;
1657
1658		// Get application executable path
1659		let exe_path = std::env::current_exe()
1660			.map_err(|e| AirError::FileSystem(format!("Failed to get executable path: {}", e)))?;
1661
1662		// Copy executable to backup
1663		let backup_exe = backup_path.join(exe_path.file_name().unwrap_or_default());
1664
1665		tokio::fs::copy(&exe_path, &backup_exe)
1666			.await
1667			.map_err(|e| AirError::FileSystem(format!("Failed to backup executable: {}", e)))?;
1668
1669		// Backup additional components
1670		// Configuration files
1671		let config_dirs = vec![
1672			dirs::config_dir().unwrap_or_default().join("FIDDEE"),
1673			dirs::home_dir().unwrap_or_default().join(".config/land"),
1674		];
1675
1676		for config_dir in config_dirs {
1677			if config_dir.exists() {
1678				let backup_config = backup_path.join("config");
1679
1680				let _ = tokio::fs::create_dir_all(&backup_config).await;
1681
1682				let _ = Self::copy_directory_recursive(&config_dir, &backup_config).await;
1683
1684				dev_log!("update", "[UpdateManager] Backed up config directory: {:?}", config_dir);
1685			}
1686		}
1687
1688		// Data directories
1689		let data_dirs = vec![
1690			dirs::data_local_dir().unwrap_or_default().join("FIDDEE"),
1691			dirs::home_dir().unwrap_or_default().join(".local/share/land"),
1692		];
1693
1694		for data_dir in data_dirs {
1695			if data_dir.exists() {
1696				let backup_data = backup_path.join("data");
1697
1698				let _ = tokio::fs::create_dir_all(&backup_data).await;
1699
1700				let _ = Self::copy_directory_recursive(&data_dir, &backup_data).await;
1701
1702				dev_log!("update", "[UpdateManager] Backed up data directory: {:?}", data_dir);
1703			}
1704		}
1705
1706		// Calculate checksum of backup for verification during rollback
1707		let checksum = self.CalculateFileChecksum(&backup_path).await?;
1708
1709		dev_log!("update", "[UpdateManager] Backup created at: {:?}", backup_path);
1710
1711		Ok(RollbackState { version:version.to_string(), backup_path, timestamp, checksum })
1712	}
1713
1714	/// Rollback to a previous version using backup
1715	///
1716	/// This method:
1717	/// - Verifies backup integrity using checksum
1718	/// - Restores files from backup
1719	/// - Validated rollback success
1720	///
1721	/// # Arguments
1722	/// * `backup_info` - Rollback state containing backup information
1723	///
1724	/// # Returns
1725	/// Result<()> indicating success or failure
1726	pub async fn RollbackToBackup(&self, backup_info:&RollbackState) -> Result<()> {
1727		dev_log!(
1728			"update",
1729			"[UpdateManager] Rolling back to version: {} from: {:?}",
1730			backup_info.version,
1731			backup_info.backup_path
1732		);
1733
1734		// Verify backup integrity
1735		let current_checksum = self.CalculateFileChecksum(&backup_info.backup_path).await?;
1736
1737		if current_checksum != backup_info.checksum {
1738			return Err(AirError::Internal(format!(
1739				"Backup integrity check failed: expected {}, got {}",
1740				backup_info.checksum, current_checksum
1741			)));
1742		}
1743
1744		// Get application executable path
1745		let exe_path = std::env::current_exe()
1746			.map_err(|e| AirError::FileSystem(format!("Failed to get executable path: {}", e)))?;
1747
1748		let backup_exe = backup_info.backup_path.join(exe_path.file_name().unwrap_or_default());
1749
1750		if !backup_exe.exists() {
1751			return Err(AirError::FileSystem("Backup executable not found".to_string()));
1752		}
1753
1754		// Restore executable from backup
1755		// Note: This may not work on all platforms due to file locks
1756		// In production, this would need to be done by a separate updater process
1757		match tokio::fs::copy(&backup_exe, &exe_path).await {
1758			Ok(_) => {
1759				dev_log!("update", "[UpdateManager] Executable restored from backup");
1760			},
1761
1762			Err(e) => {
1763				dev_log!("update", "error: [UpdateManager] Failed to restore executable: {}", e);
1764
1765				dev_log!("update", "warn: [UpdateManager] Rollback may require manual intervention");
1766			},
1767		}
1768
1769		// Restore configuration files
1770		let backup_config = backup_info.backup_path.join("config");
1771
1772		if backup_config.exists() {
1773			let config_dirs = vec![
1774				dirs::config_dir().unwrap_or_default().join("FIDDEE"),
1775				dirs::home_dir().unwrap_or_default().join(".config/land"),
1776			];
1777
1778			for config_dir in config_dirs {
1779				// Remove existing config and restore from backup
1780				if config_dir.exists() {
1781					let _ = tokio::fs::remove_dir_all(&config_dir).await;
1782				}
1783
1784				let _ = Self::copy_directory_recursive(&backup_config, &config_dir).await;
1785
1786				dev_log!("update", "[UpdateManager] Restored config directory: {:?}", config_dir);
1787			}
1788		}
1789
1790		// Restore data directories
1791		let backup_data = backup_info.backup_path.join("data");
1792
1793		if backup_data.exists() {
1794			let data_dirs = vec![
1795				dirs::data_local_dir().unwrap_or_default().join("FIDDEE"),
1796				dirs::home_dir().unwrap_or_default().join(".local/share/land"),
1797			];
1798
1799			for data_dir in data_dirs {
1800				// Remove existing data and restore from backup
1801				if data_dir.exists() {
1802					let _ = tokio::fs::remove_dir_all(&data_dir).await;
1803				}
1804
1805				let _ = Self::copy_directory_recursive(&backup_data, &data_dir).await;
1806
1807				dev_log!("update", "[UpdateManager] Restored data directory: {:?}", data_dir);
1808			}
1809		}
1810
1811		dev_log!(
1812			"update",
1813			"[UpdateManager] Rollback to version {} completed",
1814			backup_info.version
1815		);
1816
1817		Ok(())
1818	}
1819
1820	/// Rollback to a specific version by version number
1821	///
1822	/// This method:
1823	/// - Searches for backup matching the version
1824	/// - Calls RollbackToBackup with the backup
1825	///
1826	/// # Arguments
1827	/// * `version` - Version to rollback to
1828	///
1829	/// # Returns
1830	/// Result<()> indicating success or failure
1831	pub async fn RollbackToVersion(&self, version:&str) -> Result<()> {
1832		let history = self.rollback_history.lock().await;
1833
1834		let backup_info = history
1835			.versions
1836			.iter()
1837			.find(|state| state.version == version)
1838			.ok_or_else(|| AirError::FileSystem(format!("No backup found for version {}", version)))?;
1839
1840		let info = backup_info.clone();
1841
1842		drop(history);
1843
1844		self.RollbackToBackup(&info).await
1845	}
1846
1847	/// Get available rollback versions
1848	///
1849	/// Returns list of versions that can be rolled back to
1850	pub async fn GetAvailableRollbackVersions(&self) -> Vec<String> {
1851		let history = self.rollback_history.lock().await;
1852
1853		history.versions.iter().map(|state| state.version.clone()).collect()
1854	}
1855
1856	/// Validate disk space before download
1857	///
1858	/// Ensures sufficient space is available for download + staging
1859	///
1860	/// # Arguments
1861	/// * `required_bytes` - Required space in bytes
1862	///
1863	/// # Returns
1864	/// Result<()> indicating success or failure
1865	async fn ValidateDiskSpace(&self, required_bytes:u64) -> Result<()> {
1866		// Get disk space information
1867		let metadata = tokio::fs::metadata(&self.cache_directory)
1868			.await
1869			.map_err(|e| AirError::FileSystem(format!("Failed to get cache directory info: {}", e)))?;
1870
1871		if cfg!(target_os = "windows") {
1872			// Windows: use std::os::windows::fs::MetadataExt
1873			#[cfg(target_os = "windows")]
1874			{
1875				use std::os::windows::fs::MetadataExt;
1876
1877				let free_space = metadata.volume_serial_number() as u64; // This isn't correct, just placeholder
1878				dev_log!(
1879					"update",
1880					"warn: [UpdateManager] Disk space validation not fully implemented on Windows"
1881				);
1882			}
1883		} else {
1884			// Unix-like systems
1885			#[cfg(not(target_os = "windows"))]
1886			{
1887				use std::os::unix::fs::MetadataExt;
1888
1889				let _device_id = metadata.dev();
1890
1891				// Get free space on Unix-like systems using statvfs
1892				let cache_path = self.cache_directory.to_string_lossy();
1893
1894				let free_space = unsafe {
1895					let mut stat:libc::statvfs = std::mem::zeroed();
1896
1897					if libc::statvfs(cache_path.as_ptr() as *const i8, &mut stat) == 0 {
1898						stat.f_bavail as u64 * stat.f_bsize as u64
1899					} else {
1900						u64::MAX // Default to unlimited if statvfs fails
1901					}
1902				};
1903
1904				if free_space < required_bytes {
1905					return Err(AirError::Configuration(format!(
1906						"Insufficient disk space: required {} bytes, available {} bytes",
1907						required_bytes, free_space
1908					)));
1909				}
1910
1911				dev_log!(
1912					"update",
1913					"[UpdateManager] Disk space check passed: {} bytes available, {} bytes required",
1914					free_space,
1915					required_bytes
1916				);
1917			}
1918		}
1919
1920		dev_log!(
1921			"update",
1922			"[UpdateManager] Disk space validation passed for required {} bytes",
1923			self.format_size(required_bytes as f64)
1924		);
1925
1926		Ok(())
1927	}
1928
1929	/// Verify update file integrity comprehensive check
1930	///
1931	/// This method:
1932	/// - Checks file existence and non-zero size
1933	/// - Verifies all checksums if UpdateInfo provided
1934	/// - Detects corrupted downloads
1935	///
1936	/// # Arguments
1937	/// * `file_path` - Path to the update file
1938	/// * `update_info` - Optional update info with checksums
1939	///
1940	/// # Returns
1941	/// Result<`bool`> - true if valid, false if invalid
1942	pub async fn verify_update(&self, file_path:&str, update_info:Option<&UpdateInfo>) -> Result<bool> {
1943		let path = PathBuf::from(file_path);
1944
1945		if !path.exists() {
1946			return Ok(false);
1947		}
1948
1949		let metadata = tokio::fs::metadata(&path)
1950			.await
1951			.map_err(|e| AirError::FileSystem(format!("Failed to read update file metadata: {}", e)))?;
1952
1953		if metadata.len() == 0 {
1954			return Ok(false);
1955		}
1956
1957		// Verify checksums if UpdateInfo is provided
1958		if let Some(info) = update_info {
1959			if !info.checksum.is_empty() {
1960				let actual_checksum = self.CalculateFileChecksum(&path).await?;
1961
1962				if actual_checksum != info.checksum {
1963					return Err(AirError::Configuration(format!(
1964						"Checksum verification failed: expected {}, got {}",
1965						info.checksum, actual_checksum
1966					)));
1967				}
1968			}
1969
1970			// Verify additional checksums
1971			for (algorithm, expected_checksum) in &info.checksums {
1972				self.VerifyChecksumWithAlgorithm(&path, algorithm, expected_checksum).await?;
1973			}
1974
1975			// Verify file size matches expected
1976			if let Some(expected_size) = Some(info.size) {
1977				if metadata.len() != expected_size {
1978					return Err(AirError::Configuration(format!(
1979						"File size mismatch: expected {}, got {}",
1980						expected_size,
1981						metadata.len()
1982					)));
1983				}
1984			}
1985		}
1986
1987		Ok(true)
1988	}
1989
1990	/// Platform-specific update installation for Windows
1991	#[cfg(target_os = "windows")]
1992	async fn ApplyWindowsUpdate(&self, file_path:&Path) -> Result<()> {
1993		dev_log!("update", "[UpdateManager] Installing Windows update: {:?}", file_path);
1994
1995		// Windows-specific installation stub
1996		// In production, this would:
1997		// 1. Create a temporary updater process
1998		// 2. Run the Windows installer in silent mode
1999		// 3. The updater waits for the main process to exit
2000		// 4. Extracts and replaces files
2001		// 5. Restarts the application
2002
2003		dev_log!(
2004			"update",
2005			"warn: [UpdateManager] Windows installation: update package ready at {:?}",
2006			file_path
2007		);
2008
2009		dev_log!("update", "[UpdateManager] Manual installation may be required");
2010
2011		Ok(())
2012	}
2013
2014	/// Platform-specific update installation for macOS
2015	#[cfg(target_os = "macos")]
2016	async fn ApplyMacOsUpdate(&self, file_path:&Path) -> Result<()> {
2017		dev_log!("update", "[UpdateManager] Installing macOS update: {:?}", file_path);
2018
2019		// macOS-specific installation stub
2020		// In production, this would:
2021		// 1. Verify the DMG signature
2022		// 2. Mount the DMG using hdiutil
2023		// 3. Copy the new application bundle
2024		// 4. Set correct permissions
2025		// 5. Re-sign the application if needed
2026		// 6. Unmount the DMG
2027
2028		dev_log!(
2029			"update",
2030			"warn: [UpdateManager] macOS installation: update package ready at {:?}",
2031			file_path
2032		);
2033
2034		dev_log!("update", "[UpdateManager] Manual installation may be required");
2035
2036		Ok(())
2037	}
2038
2039	/// Platform-specific update installation for Linux (AppImage)
2040	#[cfg(all(target_os = "linux", feature = "appimage"))]
2041	async fn ApplyLinuxAppImageUpdate(&self, file_path:&Path) -> Result<()> {
2042		dev_log!("update", "[UpdateManager] Installing Linux AppImage update: {:?}", file_path);
2043
2044		// Linux AppImage installation stub
2045		// In production, this would:
2046		// 1. Verify the AppImage signature
2047		// 2. Make the new AppImage executable
2048		// 3. Replace the old AppImage
2049		// 4. Update desktop entry and icons
2050
2051		dev_log!(
2052			"update",
2053			"warn: [UpdateManager] Linux AppImage installation: update package ready at {:?}",
2054			file_path
2055		);
2056
2057		dev_log!("update", "[UpdateManager] Manual installation may be required");
2058
2059		Ok(())
2060	}
2061
2062	/// Platform-specific update installation for Linux (DEB)
2063	#[cfg(all(target_os = "linux", feature = "deb"))]
2064	async fn ApplyLinuxDebUpdate(&self, file_path:&Path) -> Result<()> {
2065		dev_log!("update", "[UpdateManager] Installing Linux DEB update: {:?}", file_path);
2066
2067		// Linux DEB installation stub
2068		// In production, this would:
2069		// 1. Verify the package signature
2070		// 2. Install using dpkg or apt
2071		// 3. Handle dependencies
2072
2073		dev_log!(
2074			"update",
2075			"warn: [UpdateManager] Linux DEB installation: update package ready at {:?}",
2076			file_path
2077		);
2078
2079		dev_log!("update", "[UpdateManager] Manual installation may be required");
2080
2081		Ok(())
2082	}
2083
2084	/// Platform-specific update installation for Linux (RPM)
2085	#[cfg(all(target_os = "linux", feature = "rpm"))]
2086	async fn ApplyLinuxRpmUpdate(&self, file_path:&Path) -> Result<()> {
2087		dev_log!("update", "[UpdateManager] Installing Linux RPM update: {:?}", file_path);
2088
2089		// Linux RPM installation stub
2090		// In production, this would:
2091		// 1. Verify the package signature
2092		// 2. Install using rpm or dnf
2093		// 3. Handle dependencies
2094
2095		dev_log!(
2096			"update",
2097			"warn: [UpdateManager] Linux RPM installation: update package ready at {:?}",
2098			file_path
2099		);
2100
2101		dev_log!("update", "[UpdateManager] Manual installation may be required");
2102
2103		Ok(())
2104	}
2105
2106	/// Record telemetry for update operations
2107	///
2108	/// This method:
2109	/// - Creates telemetry event with operation details
2110	/// - In production, would send to analytics service
2111	/// - Currently logs to file for debugging
2112	///
2113	/// # Arguments
2114	/// * `operation` - Type of operation (check, download, install, rollback)
2115	/// * `success` - Whether operation succeeded
2116	/// * `duration_ms` - Duration in milliseconds
2117	/// * `download_size` - Optional download size in bytes
2118	/// * `error_message` - Optional error message if failed
2119	async fn record_telemetry(
2120		&self,
2121
2122		operation:&str,
2123
2124		success:bool,
2125
2126		duration_ms:u64,
2127
2128		download_size:Option<u64>,
2129
2130		error_message:Option<String>,
2131	) {
2132		let telemetry = UpdateTelemetry {
2133			event_id:Uuid::new_v4().to_string(),
2134
2135			current_version:env!("CARGO_PKG_VERSION").to_string(),
2136
2137			target_version:self
2138				.update_status
2139				.read()
2140				.await
2141				.available_version
2142				.clone()
2143				.unwrap_or_else(|| "unknown".to_string()),
2144
2145			channel:self.update_channel.as_str().to_string(),
2146
2147			platform:format!("{}/{}", self.platform_config.platform, self.platform_config.arch),
2148
2149			operation:operation.to_string(),
2150
2151			success,
2152
2153			duration_ms,
2154
2155			download_size,
2156
2157			error_message,
2158
2159			timestamp:chrono::Utc::now(),
2160		};
2161
2162		dev_log!(
2163			"update",
2164			"[UpdateManager] Telemetry: {} {} in {}ms - size: {:?}, success: {}",
2165			operation,
2166			if success { "succeeded" } else { "failed" },
2167			duration_ms,
2168			download_size.map(|s| self.format_size(s as f64)),
2169			success
2170		);
2171
2172		// Send telemetry to analytics service (development builds only)
2173		// In production builds, telemetry is completely stripped
2174		#[cfg(debug_assertions)]
2175		{
2176			if let Ok(telemetry_json) = serde_json::to_string(&telemetry) {
2177				dev_log!("update", "[UpdateManager] Telemetry data: {}", telemetry_json); // In development, we log telemetry data
2178			// In a production implementation, this would send to an
2179			// analytics endpoint
2180			} else {
2181				dev_log!("update", "error: [UpdateManager] Failed to serialize telemetry");
2182			}
2183		}
2184
2185		// In production builds, no telemetry is sent at all
2186		#[cfg(not(debug_assertions))]
2187		{
2188			// Telemetry is completely disabled in production builds
2189			// This ensures user privacy and removes any analytics code
2190			let _ = &telemetry; // Suppress unused variable warning
2191		}
2192	}
2193
2194	/// Calculate SHA256 checksum of a byte slice
2195	fn CalculateSha256(&self, data:&[u8]) -> String {
2196		// sha2 0.11 dropped `LowerHex` on digest outputs (moved to
2197		// `hybrid_array::Array`). `hex::encode` restores the old
2198		// `format!("{:x}", …)` behaviour byte-for-byte.
2199		let mut hasher = Sha256::new();
2200
2201		hasher.update(data);
2202
2203		hex::encode(hasher.finalize())
2204	}
2205
2206	/// Calculate SHA512 checksum of a byte slice
2207	fn CalculateSha512(&self, data:&[u8]) -> String {
2208		use sha2::Sha512;
2209
2210		let mut hasher = Sha512::new();
2211
2212		hasher.update(data);
2213
2214		hex::encode(hasher.finalize())
2215	}
2216
2217	/// Calculate MD5 checksum of a byte slice
2218	fn CalculateMd5(&self, data:&[u8]) -> String {
2219		let digest = md5::compute(data);
2220
2221		format!("{:x}", digest)
2222	}
2223
2224	/// Calculate CRC32 checksum of a byte slice
2225	fn CalculateCrc32(&self, data:&[u8]) -> String {
2226		let crc = crc32fast::hash(data);
2227
2228		format!("{:08x}", crc)
2229	}
2230
2231	/// Calculate SHA256 checksum of a file
2232	async fn CalculateFileChecksum(&self, path:&Path) -> Result<String> {
2233		let content = tokio::fs::read(path)
2234			.await
2235			.map_err(|e| AirError::FileSystem(format!("Failed to read file for checksum: {}", e)))?;
2236
2237		Ok(self.CalculateSha256(&content))
2238	}
2239
2240	/// Compare two semantic version strings
2241	///
2242	/// Returns:
2243	/// - -1 if v1 < v2
2244	/// - 0 if v1 == v2
2245	/// - 1 if v1 > v2
2246	///
2247	/// # Arguments
2248	/// * `v1` - First version string
2249	/// * `v2` - Second version string
2250	///
2251	/// # Returns
2252	/// i32 indicating comparison result
2253	pub fn CompareVersions(v1:&str, v2:&str) -> i32 {
2254		let v1_parts:Vec<u32> = v1.split('.').filter_map(|s| s.parse().ok()).collect();
2255
2256		let v2_parts:Vec<u32> = v2.split('.').filter_map(|s| s.parse().ok()).collect();
2257
2258		for (i, part) in v1_parts.iter().enumerate() {
2259			if i >= v2_parts.len() {
2260				return 1;
2261			}
2262
2263			match part.cmp(&v2_parts[i]) {
2264				std::cmp::Ordering::Greater => return 1,
2265
2266				std::cmp::Ordering::Less => return -1,
2267
2268				std::cmp::Ordering::Equal => continue,
2269			}
2270		}
2271
2272		if v1_parts.len() < v2_parts.len() { -1 } else { 0 }
2273	}
2274
2275	/// Get current update status
2276	///
2277	/// Returns a clone of the current update status
2278	pub async fn GetStatus(&self) -> UpdateStatus {
2279		let status = self.update_status.read().await;
2280
2281		status.clone()
2282	}
2283
2284	/// Cancel ongoing download
2285	///
2286	/// This method:
2287	/// - Cancels the active download session
2288	/// - Cleans up temporary files
2289	/// - Updates status to paused
2290	pub async fn CancelDownload(&self) -> Result<()> {
2291		let status = self.update_status.write().await;
2292
2293		if status.installation_status != InstallationStatus::Downloading {
2294			return Err(AirError::Internal("No download in progress".to_string()));
2295		}
2296
2297		// Set cancellation flag in all active sessions
2298		{
2299			let mut sessions = self.download_sessions.write().await;
2300
2301			for session in sessions.values_mut() {
2302				session.cancelled = true;
2303			}
2304		}
2305
2306		// Clean up partial download files
2307		let sessions = self.download_sessions.read().await;
2308
2309		for session in sessions.values() {
2310			if session.temp_path.exists() {
2311				if let Err(e) = tokio::fs::remove_file(&session.temp_path).await {
2312					dev_log!("update", "warn: [UpdateManager] Failed to remove partial file: {}", e);
2313				}
2314
2315				dev_log!("update", "[UpdateManager] Removed partial file: {:?}", session.temp_path);
2316			}
2317		}
2318
2319		drop(sessions);
2320
2321		// Clear all download sessions
2322		{
2323			let mut sessions = self.download_sessions.write().await;
2324
2325			sessions.clear();
2326		}
2327
2328		dev_log!("update", "[UpdateManager] Download cancelled and cleaned up");
2329
2330		Ok(())
2331	}
2332
2333	/// Resume paused download
2334	///
2335	/// This method:
2336	/// - Resumes a paused download session
2337	/// - Uses HTTP Range header for resume capability
2338	///
2339	/// # Arguments
2340	/// * `update_info` - Update information to resume download
2341	pub async fn ResumeDownload(&self, update_info:&UpdateInfo) -> Result<()> {
2342		let Status = self.update_status.write().await;
2343
2344		if Status.installation_status != InstallationStatus::Paused {
2345			return Err(AirError::Internal("No paused download to resume".to_string()));
2346		}
2347
2348		drop(Status);
2349
2350		dev_log!(
2351			"update",
2352			"[UpdateManager] Resuming download for version {}",
2353			update_info.version
2354		);
2355
2356		self.DownloadUpdate(update_info).await
2357	}
2358
2359	/// Get update configuration
2360	///
2361	/// Returns the current update channel configuration
2362	pub async fn GetUpdateChannel(&self) -> UpdateChannel { self.update_channel }
2363
2364	/// Set update channel
2365	///
2366	/// # Arguments
2367	/// * `channel` - New update channel to use
2368	pub async fn SetUpdateChannel(&mut self, channel:UpdateChannel) { self.update_channel = channel; }
2369
2370	/// Recursively copy a directory
2371	///
2372	/// This helper method copies all files and subdirectories from source to
2373	/// destination. Used during backup and restore operations.
2374	///
2375	/// # Arguments
2376	/// * `src` - Source directory path
2377	/// * `dst` - Destination directory path
2378	///
2379	/// # Returns
2380	/// Result<()> indicating success or failure
2381	async fn copy_directory_recursive(src:&Path, dst:&Path) -> Result<()> {
2382		let mut entries = tokio::fs::read_dir(src)
2383			.await
2384			.map_err(|e| AirError::FileSystem(format!("Failed to read directory {:?}: {}", src, e)))?;
2385
2386		tokio::fs::create_dir_all(dst)
2387			.await
2388			.map_err(|e| AirError::FileSystem(format!("Failed to create directory {:?}: {}", dst, e)))?;
2389
2390		while let Some(entry) = entries
2391			.next_entry()
2392			.await
2393			.map_err(|e| AirError::FileSystem(format!("Failed to read entry: {}", e)))?
2394		{
2395			let file_type = entry
2396				.file_type()
2397				.await
2398				.map_err(|e| AirError::FileSystem(format!("Failed to get file type: {}", e)))?;
2399
2400			let src_path = entry.path();
2401
2402			let dst_path = dst.join(entry.file_name());
2403
2404			if file_type.is_file() {
2405				tokio::fs::copy(&src_path, &dst_path)
2406					.await
2407					.map_err(|e| AirError::FileSystem(format!("Failed to copy file {:?}: {}", src_path, e)))?;
2408			} else if file_type.is_dir() {
2409				Box::pin(Self::copy_directory_recursive(&src_path, &dst_path)).await?;
2410			}
2411		}
2412
2413		Ok(())
2414	}
2415
2416	/// Stage update for pre-installation verification
2417	///
2418	/// This method:
2419	/// - Stages the update in the staging directory
2420	/// - Verifies the staged update
2421	/// - Prepares for installation
2422	///
2423	/// # Arguments
2424	/// * `update_info` - Update information to stage
2425	pub async fn StageUpdate(&self, update_info:&UpdateInfo) -> Result<()> {
2426		dev_log!("update", "[UpdateManager] Staging update for version {}", update_info.version);
2427
2428		let mut status = self.update_status.write().await;
2429
2430		status.installation_status = InstallationStatus::Staging;
2431
2432		drop(status);
2433
2434		let file_path = self.cache_directory.join(format!("update-{}.bin", update_info.version));
2435
2436		if !file_path.exists() {
2437			return Err(AirError::FileSystem("Update file not found. Download first.".to_string()));
2438		}
2439
2440		// Create version-specific staging directory
2441		let stage_dir = self.staging_directory.join(&update_info.version);
2442
2443		tokio::fs::create_dir_all(&stage_dir)
2444			.await
2445			.map_err(|e| AirError::FileSystem(format!("Failed to create staging directory: {}", e)))?;
2446
2447		// Copy update package to staging
2448		let staged_file = stage_dir.join("update.bin");
2449
2450		tokio::fs::copy(&file_path, &staged_file)
2451			.await
2452			.map_err(|e| AirError::FileSystem(format!("Failed to stage update package: {}", e)))?;
2453
2454		// Verify staged package
2455		self.VerifyChecksum(&staged_file, &update_info.checksum).await?;
2456
2457		dev_log!("update", "[UpdateManager] Update staged successfully in: {:?}", stage_dir);
2458
2459		Ok(())
2460	}
2461
2462	/// Clean up old update files
2463	///
2464	/// Removes downloaded updates older than a certain threshold
2465	/// to free disk space
2466	pub async fn CleanupOldUpdates(&self) -> Result<()> {
2467		dev_log!("update", "[UpdateManager] Cleaning up old update files");
2468
2469		let mut entries = tokio::fs::read_dir(&self.cache_directory)
2470			.await
2471			.map_err(|e| AirError::FileSystem(format!("Failed to read cache directory: {}", e)))?;
2472
2473		let mut cleaned_count = 0;
2474
2475		let now = std::time::SystemTime::now();
2476
2477		while let Some(entry) = entries
2478			.next_entry()
2479			.await
2480			.map_err(|e| AirError::FileSystem(format!("Failed to read directory entry: {}", e)))?
2481		{
2482			let path = entry.path();
2483
2484			let metadata = entry
2485				.metadata()
2486				.await
2487				.map_err(|e| AirError::FileSystem(format!("Failed to get metadata: {}", e)))?;
2488
2489			// Skip directories and recent files (within 7 days)
2490			if path.is_dir()
2491				|| metadata.modified().unwrap_or(now)
2492					> now.checked_sub(Duration::from_secs(7 * 24 * 3600)).unwrap_or(now)
2493			{
2494				continue;
2495			}
2496
2497			dev_log!("update", "[UpdateManager] Removing old update file: {:?}", path);
2498
2499			tokio::fs::remove_file(&path)
2500				.await
2501				.map_err(|e| AirError::FileSystem(format!("Failed to remove {}: {}", path.display(), e)))?;
2502
2503			cleaned_count += 1;
2504		}
2505
2506		dev_log!("update", "[UpdateManager] Cleaned up {} old update files", cleaned_count);
2507
2508		Ok(())
2509	}
2510
2511	/// Get the cache directory path
2512	pub fn GetCacheDirectory(&self) -> &PathBuf { &self.cache_directory }
2513
2514	/// Start background update checking task
2515	///
2516	/// This method:
2517	/// - Periodically checks for updates based on configured interval
2518	/// - Runs in a separate tokio task
2519	/// - Can be cancelled by stopping the task
2520	///
2521	/// # Returns
2522	/// Result<tokio::task::JoinHandle<()>> - Handle to the background task
2523	pub async fn StartBackgroundTasks(&self) -> Result<()> {
2524		let manager = self.clone();
2525
2526		let handle = tokio::spawn(async move {
2527			manager.BackgroundTask().await;
2528		});
2529
2530		// Store the handle for later cancellation
2531		let mut task_handle = self.background_task.lock().await;
2532
2533		*task_handle = Some(handle);
2534
2535		dev_log!("update", "[UpdateManager] Background update checking started");
2536
2537		Ok(())
2538	}
2539
2540	/// Background task for periodic update checks
2541	///
2542	/// This task:
2543	/// - Checks for updates at regular intervals
2544	/// - Logs any errors but doesn't fail the task
2545	/// - Can run indefinitely until stopped
2546	async fn BackgroundTask(&self) {
2547		let config = &self.AppState.Configuration.Updates;
2548
2549		if !config.Enabled {
2550			dev_log!("update", "[UpdateManager] Background task: Updates are disabled");
2551
2552			return;
2553		}
2554
2555		let check_interval = Duration::from_secs(config.CheckIntervalHours as u64 * 3600);
2556
2557		let mut interval = interval(check_interval);
2558
2559		dev_log!(
2560			"update",
2561			"[UpdateManager] Background task: Checking for updates every {} hours",
2562			config.CheckIntervalHours
2563		);
2564
2565		loop {
2566			interval.tick().await;
2567
2568			dev_log!("update", "[UpdateManager] Background task: Checking for updates...");
2569
2570			// Check for updates
2571			match self.CheckForUpdates().await {
2572				Ok(Some(update_info)) => {
2573					dev_log!(
2574						"update",
2575						"[UpdateManager] Background task: Update available: {}",
2576						update_info.version
2577					);
2578				},
2579
2580				Ok(None) => {
2581					dev_log!("update", "[UpdateManager] Background task: No updates available");
2582				},
2583
2584				Err(e) => {
2585					dev_log!("update", "error: [UpdateManager] Background task: Update check failed: {}", e);
2586				},
2587			}
2588		}
2589	}
2590
2591	/// Stop background tasks
2592	///
2593	/// This method:
2594	/// - Logs the stop request
2595	/// - Aborts the stored JoinHandle to cancel the background task
2596	pub async fn StopBackgroundTasks(&self) {
2597		dev_log!("update", "[UpdateManager] Stopping background tasks");
2598
2599		// Cancel the stored task handle if it exists
2600		let mut task_handle = self.background_task.lock().await;
2601
2602		if let Some(handle) = task_handle.take() {
2603			handle.abort();
2604
2605			dev_log!("update", "[UpdateManager] Background task aborted");
2606		} else {
2607			dev_log!("update", "[UpdateManager] No background task to stop");
2608		}
2609	}
2610
2611	/// Format byte count to human-readable string
2612	///
2613	/// # Arguments
2614	/// * `bytes` - Number of bytes (supports both u64 and f64 for rates)
2615	///
2616	/// # Returns
2617	/// Formatted string (e.g., "1.5 MB", "500 KB")
2618	fn format_size(&self, bytes:f64) -> String {
2619		const KB:f64 = 1024.0;
2620
2621		const MB:f64 = KB * 1024.0;
2622
2623		const GB:f64 = MB * 1024.0;
2624
2625		if bytes >= GB {
2626			format!("{:.2} GB/s", bytes / GB)
2627		} else if bytes >= MB {
2628			format!("{:.2} MB/s", bytes / MB)
2629		} else if bytes >= KB {
2630			format!("{:.2} KB/s", bytes / KB)
2631		} else {
2632			format!("{:.0} B/s", bytes)
2633		}
2634	}
2635}
2636
2637impl Clone for UpdateManager {
2638	fn clone(&self) -> Self {
2639		Self {
2640			AppState:self.AppState.clone(),
2641
2642			update_status:self.update_status.clone(),
2643
2644			cache_directory:self.cache_directory.clone(),
2645
2646			staging_directory:self.staging_directory.clone(),
2647
2648			backup_directory:self.backup_directory.clone(),
2649
2650			download_sessions:self.download_sessions.clone(),
2651
2652			rollback_history:self.rollback_history.clone(),
2653
2654			update_channel:self.update_channel,
2655
2656			platform_config:self.platform_config.clone(),
2657
2658			background_task:self.background_task.clone(),
2659		}
2660	}
2661}