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