use futures_util::StreamExt; use tauri::Emitter; use tokio::io::AsyncWriteExt; use crate::installer; #[tauri::command] pub async fn download_and_install( app_handle: tauri::AppHandle, url: String, name: String, ) -> Result { if !(url.starts_with("https://") || url.starts_with("http://")) { return Err("Only http:// and https:// downloads are allowed.".into()); } let _ = app_handle.emit("install-progress", format!("Downloading {}...", name)); let response = reqwest::get(&url).await.map_err(|e| e.to_string())?; let status = response.status(); if !status.is_success() { return Err(format!("Download failed with HTTP status {}.", status)); } let total_size = response.content_length().unwrap_or(0); let temp_path = std::env::temp_dir().join(format!("{}.AppImage", uuid::Uuid::new_v4())); let mut file = tokio::fs::File::create(&temp_path) .await .map_err(|e| e.to_string())?; let mut downloaded: u64 = 0; let mut stream = response.bytes_stream(); while let Some(item) = stream.next().await { let chunk = item.map_err(|e| e.to_string())?; if chunk.is_empty() { continue; } file.write_all(&chunk).await.map_err(|e| e.to_string())?; downloaded += chunk.len() as u64; if total_size > 0 { let percentage = (downloaded as f64 / total_size as f64) * 100.0; let _ = app_handle.emit( "install-progress", format!("Downloading: {:.1}%", percentage), ); } } file.flush().await.map_err(|e| e.to_string())?; if downloaded == 0 { let _ = std::fs::remove_file(&temp_path); return Err("Download completed with no data.".into()); } let _ = app_handle.emit("install-progress", "Download complete. Installing..."); let result = installer::install_appimage_internal(temp_path.to_string_lossy().to_string()).await; let _ = std::fs::remove_file(&temp_path); result }