refactor: improve auto-delete and auto-install browser logic

This commit is contained in:
zhom
2025-06-19 03:31:41 +04:00
parent cdf30b7baa
commit 8005ec90b6
11 changed files with 763 additions and 332 deletions
+81 -9
View File
@@ -637,15 +637,39 @@ impl ApiClient {
"{}/repos/mullvad/mullvad-browser/releases?per_page=100",
self.github_api_base
);
let releases = self
let response = self
.client
.get(url)
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36")
.send()
.await?
.json::<Vec<GithubRelease>>()
.await?;
if !response.status().is_success() {
return Err(format!("GitHub API returned status: {}", response.status()).into());
}
// Get the response text first for better error reporting
let response_text = response.text().await?;
// Try to parse the JSON with better error handling
let releases: Vec<GithubRelease> = match serde_json::from_str(&response_text) {
Ok(releases) => releases,
Err(e) => {
eprintln!("Failed to parse GitHub API response for Mullvad releases:");
eprintln!("Error: {e}");
eprintln!(
"Response text (first 500 chars): {}",
if response_text.len() > 500 {
&response_text[..500]
} else {
&response_text
}
);
return Err(format!("Failed to parse GitHub API response: {e}").into());
}
};
let mut releases: Vec<GithubRelease> = releases
.into_iter()
.map(|mut release| {
@@ -683,15 +707,39 @@ impl ApiClient {
"{}/repos/zen-browser/desktop/releases?per_page=100",
self.github_api_base
);
let mut releases = self
let response = self
.client
.get(url)
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36")
.send()
.await?
.json::<Vec<GithubRelease>>()
.await?;
if !response.status().is_success() {
return Err(format!("GitHub API returned status: {}", response.status()).into());
}
// Get the response text first for better error reporting
let response_text = response.text().await?;
// Try to parse the JSON with better error handling
let mut releases: Vec<GithubRelease> = match serde_json::from_str(&response_text) {
Ok(releases) => releases,
Err(e) => {
eprintln!("Failed to parse GitHub API response for Zen releases:");
eprintln!("Error: {e}");
eprintln!(
"Response text (first 500 chars): {}",
if response_text.len() > 500 {
&response_text[..500]
} else {
&response_text
}
);
return Err(format!("Failed to parse GitHub API response: {e}").into());
}
};
// Check for twilight updates and mark alpha releases
for release in &mut releases {
// Use browser-specific alpha detection for Zen Browser - only "twilight" is nightly
@@ -740,15 +788,39 @@ impl ApiClient {
"{}/repos/brave/brave-browser/releases?per_page=100",
self.github_api_base
);
let releases = self
let response = self
.client
.get(url)
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36")
.send()
.await?
.json::<Vec<GithubRelease>>()
.await?;
if !response.status().is_success() {
return Err(format!("GitHub API returned status: {}", response.status()).into());
}
// Get the response text first for better error reporting
let response_text = response.text().await?;
// Try to parse the JSON with better error handling
let releases: Vec<GithubRelease> = match serde_json::from_str(&response_text) {
Ok(releases) => releases,
Err(e) => {
eprintln!("Failed to parse GitHub API response for Brave releases:");
eprintln!("Error: {e}");
eprintln!(
"Response text (first 500 chars): {}",
if response_text.len() > 500 {
&response_text[..500]
} else {
&response_text
}
);
return Err(format!("Failed to parse GitHub API response: {e}").into());
}
};
// Get platform info to filter appropriate releases
let (os, arch) = Self::get_platform_info();
+3 -10
View File
@@ -348,16 +348,9 @@ impl AutoUpdater {
state.auto_update_downloads.remove(&download_key);
self.save_auto_update_state(&state)?;
// Check if auto-delete of unused binaries is enabled and perform cleanup
let settings = self
.settings_manager
.load_settings()
.map_err(|e| format!("Failed to load settings: {e}"))?;
if settings.auto_delete_unused_binaries {
// Perform cleanup in the background - don't fail the update if cleanup fails
if let Err(e) = self.cleanup_unused_binaries_internal() {
eprintln!("Warning: Failed to cleanup unused binaries after auto-update: {e}");
}
// Always perform cleanup after auto-update - don't fail the update if cleanup fails
if let Err(e) = self.cleanup_unused_binaries_internal() {
eprintln!("Warning: Failed to cleanup unused binaries after auto-update: {e}");
}
Ok(updated_profiles)
+34
View File
@@ -720,6 +720,24 @@ pub struct GithubRelease {
pub is_nightly: bool,
#[serde(default)]
pub prerelease: bool,
#[serde(default)]
pub draft: bool,
#[serde(default)]
pub body: Option<String>,
#[serde(default)]
pub html_url: Option<String>,
#[serde(default)]
pub id: Option<u64>,
#[serde(default)]
pub node_id: Option<String>,
#[serde(default)]
pub target_commitish: Option<String>,
#[serde(default)]
pub created_at: Option<String>,
#[serde(default)]
pub tarball_url: Option<String>,
#[serde(default)]
pub zipball_url: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
@@ -728,6 +746,22 @@ pub struct GithubAsset {
pub browser_download_url: String,
#[serde(default)]
pub size: u64,
#[serde(default)]
pub download_count: Option<u64>,
#[serde(default)]
pub id: Option<u64>,
#[serde(default)]
pub node_id: Option<String>,
#[serde(default)]
pub label: Option<String>,
#[serde(default)]
pub content_type: Option<String>,
#[serde(default)]
pub state: Option<String>,
#[serde(default)]
pub created_at: Option<String>,
#[serde(default)]
pub updated_at: Option<String>,
}
#[cfg(test)]
+369 -192
View File
@@ -1058,6 +1058,8 @@ impl BrowserRunner {
release_type: &str,
proxy: Option<ProxySettings>,
) -> Result<BrowserProfile, Box<dyn std::error::Error>> {
println!("Attempting to create profile: {name}");
// Check if a profile with this name already exists (case insensitive)
let existing_profiles = self.list_profiles()?;
if existing_profiles
@@ -1068,10 +1070,16 @@ impl BrowserRunner {
}
let snake_case_name = name.to_lowercase().replace(" ", "_");
let profiles_dir = self.get_profiles_dir();
let profile_file = profiles_dir.join(format!("{snake_case_name}.json"));
let profile_path = profiles_dir.join(&snake_case_name);
// Double-check that the profile file doesn't exist
if profile_file.exists() {
return Err(format!("Profile file for '{name}' already exists").into());
}
// Create profile directory
let mut profile_path = self.get_profiles_dir();
profile_path.push(&snake_case_name);
create_dir_all(&profile_path)?;
let profile = BrowserProfile {
@@ -1088,6 +1096,13 @@ impl BrowserRunner {
// Save profile info
self.save_profile(&profile)?;
// Verify the profile was saved correctly
if !profile_file.exists() {
return Err(format!("Failed to create profile file for '{name}'").into());
}
println!("Profile '{name}' created successfully");
// Create user.js with common Firefox preferences and apply proxy settings if provided
if let Some(proxy_settings) = &proxy {
self.apply_proxy_settings_to_profile(&profile_path, proxy_settings, None)?;
@@ -1264,14 +1279,8 @@ impl BrowserRunner {
// Save the updated profile
self.save_profile(&profile)?;
// Check if auto-delete of unused binaries is enabled
let settings_manager = crate::settings_manager::SettingsManager::new();
if let Ok(settings) = settings_manager.load_settings() {
if settings.auto_delete_unused_binaries {
// Perform cleanup in the background
let _ = self.cleanup_unused_binaries_internal();
}
}
// Always perform cleanup after profile version update to remove unused binaries
let _ = self.cleanup_unused_binaries_internal();
Ok(profile)
}
@@ -1911,33 +1920,53 @@ impl BrowserRunner {
}
pub fn delete_profile(&self, profile_name: &str) -> Result<(), Box<dyn std::error::Error>> {
let profiles_dir = self.get_profiles_dir();
let profile_file = profiles_dir.join(format!(
"{}.json",
profile_name.to_lowercase().replace(" ", "_")
));
let profile_path = profiles_dir.join(profile_name.to_lowercase().replace(" ", "_"));
println!("Attempting to delete profile: {profile_name}");
// Delete profile directory
let profiles_dir = self.get_profiles_dir();
let snake_case_name = profile_name.to_lowercase().replace(" ", "_");
let profile_file = profiles_dir.join(format!("{snake_case_name}.json"));
let profile_path = profiles_dir.join(&snake_case_name);
// Verify the profile exists before attempting to delete
if !profile_file.exists() {
return Err(format!("Profile '{profile_name}' not found").into());
}
// Read the profile to check if browser is running
if let Ok(content) = fs::read_to_string(&profile_file) {
if let Ok(profile) = serde_json::from_str::<BrowserProfile>(&content) {
if profile.process_id.is_some() {
return Err(
"Cannot delete profile while browser is running. Please stop the browser first.".into(),
);
}
}
}
// Delete profile directory first (if it exists)
if profile_path.exists() {
fs::remove_dir_all(profile_path)?
println!("Deleting profile directory: {}", profile_path.display());
fs::remove_dir_all(&profile_path)?;
println!("Profile directory deleted successfully");
}
// Delete profile JSON file
if profile_file.exists() {
fs::remove_file(profile_file)?
println!("Deleting profile file: {}", profile_file.display());
fs::remove_file(&profile_file)?;
println!("Profile file deleted successfully");
}
// Check if auto-delete of unused binaries is enabled
let settings_manager = crate::settings_manager::SettingsManager::new();
if let Ok(settings) = settings_manager.load_settings() {
if settings.auto_delete_unused_binaries {
// Perform cleanup in the background after profile deletion
// Ignore errors since this is not critical for profile deletion
if let Err(e) = self.cleanup_unused_binaries_internal() {
println!("Warning: Failed to cleanup unused binaries: {e}");
}
}
// Verify deletion was successful
if profile_file.exists() || profile_path.exists() {
return Err(format!("Failed to completely delete profile '{profile_name}'").into());
}
println!("Profile '{profile_name}' deleted successfully");
// Always perform cleanup after profile deletion to remove unused binaries
if let Err(e) = self.cleanup_unused_binaries_internal() {
println!("Warning: Failed to cleanup unused binaries: {e}");
}
Ok(())
@@ -2229,6 +2258,292 @@ impl BrowserRunner {
Ok(())
}
/// Check if browser binaries exist for all profiles and return missing binaries
pub async fn check_missing_binaries(
&self,
) -> Result<Vec<(String, String, String)>, Box<dyn std::error::Error + Send + Sync>> {
// Get all profiles
let profiles = self
.list_profiles()
.map_err(|e| format!("Failed to list profiles: {e}"))?;
let mut missing_binaries = Vec::new();
for profile in profiles {
let browser_type = match BrowserType::from_str(&profile.browser) {
Ok(bt) => bt,
Err(_) => {
println!(
"Warning: Invalid browser type '{}' for profile '{}'",
profile.browser, profile.name
);
continue;
}
};
let browser = create_browser(browser_type.clone());
let binaries_dir = self.get_binaries_dir();
println!(
"binaries_dir: {binaries_dir:?} for profile: {}",
profile.name
);
// Check if the version is downloaded
if !browser.is_version_downloaded(&profile.version, &binaries_dir) {
missing_binaries.push((profile.name, profile.browser, profile.version));
}
}
Ok(missing_binaries)
}
/// Automatically download missing binaries for all profiles
pub async fn ensure_all_binaries_exist(
&self,
app_handle: &tauri::AppHandle,
) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> {
// First, clean up any stale registry entries
if let Ok(mut registry) = DownloadedBrowsersRegistry::load() {
if let Ok(cleaned_up) = registry.verify_and_cleanup_stale_entries(self) {
if !cleaned_up.is_empty() {
println!(
"Cleaned up {} stale registry entries: {}",
cleaned_up.len(),
cleaned_up.join(", ")
);
}
}
}
let missing_binaries = self.check_missing_binaries().await?;
let mut downloaded = Vec::new();
for (profile_name, browser, version) in missing_binaries {
println!("Downloading missing binary for profile '{profile_name}': {browser} {version}");
match self
.download_browser_impl(app_handle.clone(), browser.clone(), version.clone())
.await
{
Ok(_) => {
downloaded.push(format!(
"{browser} {version} (for profile '{profile_name}')"
));
}
Err(e) => {
eprintln!("Failed to download {browser} {version} for profile '{profile_name}': {e}");
}
}
}
Ok(downloaded)
}
pub async fn download_browser_impl(
&self,
app_handle: tauri::AppHandle,
browser_str: String,
version: String,
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
let browser_type =
BrowserType::from_str(&browser_str).map_err(|e| format!("Invalid browser type: {e}"))?;
let browser = create_browser(browser_type.clone());
// Load registry and check if already downloaded
let mut registry = DownloadedBrowsersRegistry::load()
.map_err(|e| format!("Failed to load browser registry: {e}"))?;
// Check if registry thinks it's downloaded, but also verify files actually exist
if registry.is_browser_downloaded(&browser_str, &version) {
let binaries_dir = self.get_binaries_dir();
let actually_exists = browser.is_version_downloaded(&version, &binaries_dir);
if actually_exists {
return Ok(version);
} else {
// Registry says it's downloaded but files don't exist - clean up registry
println!("Registry indicates {browser_str} {version} is downloaded, but files are missing. Cleaning up registry entry.");
registry.remove_browser(&browser_str, &version);
registry
.save()
.map_err(|e| format!("Failed to save cleaned registry: {e}"))?;
}
}
// Check if browser is supported on current platform before attempting download
let version_service = BrowserVersionService::new();
if !version_service
.is_browser_supported(&browser_str)
.unwrap_or(false)
{
return Err(
format!(
"Browser '{}' is not supported on your platform ({} {}). Supported browsers: {}",
browser_str,
std::env::consts::OS,
std::env::consts::ARCH,
version_service.get_supported_browsers().join(", ")
)
.into(),
);
}
let download_info = version_service
.get_download_info(&browser_str, &version)
.map_err(|e| format!("Failed to get download info: {e}"))?;
// Create browser directory
let mut browser_dir = self.get_binaries_dir();
browser_dir.push(browser_type.as_str());
browser_dir.push(&version);
// Clean up any failed previous download
if let Err(e) = registry.cleanup_failed_download(&browser_str, &version) {
println!("Warning: Failed to cleanup previous download: {e}");
}
create_dir_all(&browser_dir).map_err(|e| format!("Failed to create browser directory: {e}"))?;
// Mark download as started in registry
registry.mark_download_started(&browser_str, &version, browser_dir.clone());
registry
.save()
.map_err(|e| format!("Failed to save registry: {e}"))?;
// Use the new download module
let downloader = Downloader::new();
let download_path = match downloader
.download_browser(
&app_handle,
browser_type.clone(),
&version,
&download_info,
&browser_dir,
)
.await
{
Ok(path) => path,
Err(e) => {
// Clean up failed download
let _ = registry.cleanup_failed_download(&browser_str, &version);
let _ = registry.save();
return Err(format!("Failed to download browser: {e}").into());
}
};
// Use the new extraction module
if download_info.is_archive {
let extractor = Extractor::new();
match extractor
.extract_browser(
&app_handle,
browser_type.clone(),
&version,
&download_path,
&browser_dir,
)
.await
{
Ok(_) => {
// Clean up the downloaded archive
if let Err(e) = std::fs::remove_file(&download_path) {
println!("Warning: Could not delete archive file: {e}");
}
}
Err(e) => {
// Clean up failed download
let _ = registry.cleanup_failed_download(&browser_str, &version);
let _ = registry.save();
return Err(format!("Failed to extract browser: {e}").into());
}
}
// Give filesystem a moment to settle after extraction
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
}
// Emit verification progress
let progress = DownloadProgress {
browser: browser_str.clone(),
version: version.clone(),
downloaded_bytes: 0,
total_bytes: None,
percentage: 100.0,
speed_bytes_per_sec: 0.0,
eta_seconds: None,
stage: "verifying".to_string(),
};
let _ = app_handle.emit("download-progress", &progress);
// Verify the browser was downloaded correctly
println!("Verifying download for browser: {browser_str}, version: {version}");
// Use the browser's own verification method
let binaries_dir = self.get_binaries_dir();
if !browser.is_version_downloaded(&version, &binaries_dir) {
let _ = registry.cleanup_failed_download(&browser_str, &version);
let _ = registry.save();
return Err("Browser download completed but verification failed".into());
}
// Mark download as completed in registry
let actual_version = if browser_str == "chromium" {
Some(version.clone())
} else {
None
};
registry
.mark_download_completed_with_actual_version(&browser_str, &version, actual_version)
.map_err(|e| format!("Failed to mark download as completed: {e}"))?;
registry
.save()
.map_err(|e| format!("Failed to save registry: {e}"))?;
// Emit completion
let progress = DownloadProgress {
browser: browser_str.clone(),
version: version.clone(),
downloaded_bytes: 0,
total_bytes: None,
percentage: 100.0,
speed_bytes_per_sec: 0.0,
eta_seconds: Some(0.0),
stage: "completed".to_string(),
};
let _ = app_handle.emit("download-progress", &progress);
Ok(version)
}
/// Check if a browser version is downloaded
pub fn is_browser_downloaded(&self, browser_str: &str, version: &str) -> bool {
// Always check if files actually exist on disk
let browser_type = match BrowserType::from_str(browser_str) {
Ok(bt) => bt,
Err(_) => {
println!("Invalid browser type: {browser_str}");
return false;
}
};
let browser = create_browser(browser_type.clone());
let binaries_dir = self.get_binaries_dir();
let files_exist = browser.is_version_downloaded(version, &binaries_dir);
// If files don't exist but registry thinks they do, clean up the registry
if !files_exist {
if let Ok(mut registry) = DownloadedBrowsersRegistry::load() {
if registry.is_browser_downloaded(browser_str, version) {
println!("Cleaning up stale registry entry for {browser_str} {version}");
registry.remove_browser(browser_str, version);
let _ = registry.save(); // Don't fail if save fails, just log
}
}
}
files_exist
}
}
#[tauri::command]
@@ -2493,174 +2808,16 @@ pub async fn download_browser(
version: String,
) -> Result<String, String> {
let browser_runner = BrowserRunner::new();
let browser_type =
BrowserType::from_str(&browser_str).map_err(|e| format!("Invalid browser type: {e}"))?;
let browser = create_browser(browser_type.clone());
// Load registry and check if already downloaded
let mut registry = DownloadedBrowsersRegistry::load()
.map_err(|e| format!("Failed to load browser registry: {e}"))?;
if registry.is_browser_downloaded(&browser_str, &version) {
return Ok(version);
}
// Check if browser is supported on current platform before attempting download
let version_service = BrowserVersionService::new();
if !version_service
.is_browser_supported(&browser_str)
.unwrap_or(false)
{
return Err(format!(
"Browser '{}' is not supported on your platform ({} {}). Supported browsers: {}",
browser_str,
std::env::consts::OS,
std::env::consts::ARCH,
version_service.get_supported_browsers().join(", ")
));
}
let download_info = version_service
.get_download_info(&browser_str, &version)
.map_err(|e| format!("Failed to get download info: {e}"))?;
// Create browser directory
let mut browser_dir = browser_runner.get_binaries_dir();
browser_dir.push(browser_type.as_str());
browser_dir.push(&version);
// Clean up any failed previous download
if let Err(e) = registry.cleanup_failed_download(&browser_str, &version) {
println!("Warning: Failed to cleanup previous download: {e}");
}
create_dir_all(&browser_dir).map_err(|e| format!("Failed to create browser directory: {e}"))?;
// Mark download as started in registry
registry.mark_download_started(&browser_str, &version, browser_dir.clone());
registry
.save()
.map_err(|e| format!("Failed to save registry: {e}"))?;
// Use the new download module
let downloader = Downloader::new();
let download_path = match downloader
.download_browser(
&app_handle,
browser_type.clone(),
&version,
&download_info,
&browser_dir,
)
browser_runner
.download_browser_impl(app_handle, browser_str, version)
.await
{
Ok(path) => path,
Err(e) => {
// Clean up failed download
let _ = registry.cleanup_failed_download(&browser_str, &version);
let _ = registry.save();
return Err(format!("Failed to download browser: {e}"));
}
};
// Use the new extraction module
if download_info.is_archive {
let extractor = Extractor::new();
match extractor
.extract_browser(
&app_handle,
browser_type.clone(),
&version,
&download_path,
&browser_dir,
)
.await
{
Ok(_) => {
// Clean up the downloaded archive
if let Err(e) = std::fs::remove_file(&download_path) {
println!("Warning: Could not delete archive file: {e}");
}
}
Err(e) => {
// Clean up failed download
let _ = registry.cleanup_failed_download(&browser_str, &version);
let _ = registry.save();
return Err(format!("Failed to extract browser: {e}"));
}
}
// Give filesystem a moment to settle after extraction
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
}
// Emit verification progress
let progress = DownloadProgress {
browser: browser_str.clone(),
version: version.clone(),
downloaded_bytes: 0,
total_bytes: None,
percentage: 100.0,
speed_bytes_per_sec: 0.0,
eta_seconds: None,
stage: "verifying".to_string(),
};
let _ = app_handle.emit("download-progress", &progress);
// Verify the browser was downloaded correctly
println!("Verifying download for browser: {browser_str}, version: {version}");
// Use the browser's own verification method
let binaries_dir = browser_runner.get_binaries_dir();
if !browser.is_version_downloaded(&version, &binaries_dir) {
let _ = registry.cleanup_failed_download(&browser_str, &version);
let _ = registry.save();
return Err("Browser download completed but verification failed".to_string());
}
// Mark download as completed in registry
let actual_version = if browser_str == "chromium" {
Some(version.clone())
} else {
None
};
registry
.mark_download_completed_with_actual_version(&browser_str, &version, actual_version)
.map_err(|e| format!("Failed to mark download as completed: {e}"))?;
registry
.save()
.map_err(|e| format!("Failed to save registry: {e}"))?;
// Emit completion
let progress = DownloadProgress {
browser: browser_str.clone(),
version: version.clone(),
downloaded_bytes: 0,
total_bytes: None,
percentage: 100.0,
speed_bytes_per_sec: 0.0,
eta_seconds: Some(0.0),
stage: "completed".to_string(),
};
let _ = app_handle.emit("download-progress", &progress);
Ok(version)
.map_err(|e| format!("Failed to download browser: {e}"))
}
#[tauri::command]
pub fn is_browser_downloaded(browser_str: String, version: String) -> bool {
if let Ok(registry) = DownloadedBrowsersRegistry::load() {
if registry.is_browser_downloaded(&browser_str, &version) {
return true;
}
}
let browser_type = BrowserType::from_str(&browser_str).expect("Invalid browser type");
let browser_runner = BrowserRunner::new();
let browser = create_browser(browser_type.clone());
let binaries_dir = browser_runner.get_binaries_dir();
browser.is_version_downloaded(&version, &binaries_dir)
browser_runner.is_browser_downloaded(&browser_str, &version)
}
#[tauri::command]
@@ -2726,7 +2883,27 @@ pub async fn get_browser_release_types(
service
.get_browser_release_types(&browser_str)
.await
.map_err(|e| format!("Failed to get browser release types: {e}"))
.map_err(|e| format!("Failed to get release types: {e}"))
}
#[tauri::command]
pub async fn check_missing_binaries() -> Result<Vec<(String, String, String)>, String> {
let browser_runner = BrowserRunner::new();
browser_runner
.check_missing_binaries()
.await
.map_err(|e| format!("Failed to check missing binaries: {e}"))
}
#[tauri::command]
pub async fn ensure_all_binaries_exist(
app_handle: tauri::AppHandle,
) -> Result<Vec<String>, String> {
let browser_runner = BrowserRunner::new();
browser_runner
.ensure_all_binaries_exist(&app_handle)
.await
.map_err(|e| format!("Failed to ensure all binaries exist: {e}"))
}
#[cfg(test)]
+30 -8
View File
@@ -79,15 +79,29 @@ impl Downloader {
}
BrowserType::Zen => {
// For Zen, verify the asset exists and handle different naming patterns
let releases = self
.api_client
.fetch_zen_releases_with_caching(true)
.await?;
let releases = match self.api_client.fetch_zen_releases_with_caching(true).await {
Ok(releases) => releases,
Err(e) => {
eprintln!("Failed to fetch Zen releases: {e}");
return Err(format!("Failed to fetch Zen releases from GitHub API: {e}. This might be due to GitHub API rate limiting or network issues. Please try again later.").into());
}
};
let release = releases
.iter()
.find(|r| r.tag_name == version)
.ok_or(format!("Zen version {version} not found"))?;
.ok_or_else(|| {
format!(
"Zen version {} not found. Available versions: {}",
version,
releases
.iter()
.take(5)
.map(|r| r.tag_name.as_str())
.collect::<Vec<_>>()
.join(", ")
)
})?;
// Get platform and architecture info
let (os, arch) = Self::get_platform_info();
@@ -95,9 +109,17 @@ impl Downloader {
// Find the appropriate asset
let asset_url = self
.find_zen_asset(&release.assets, &os, &arch)
.ok_or(format!(
"No compatible asset found for Zen version {version} on {os}/{arch}"
))?;
.ok_or_else(|| {
let available_assets: Vec<&str> =
release.assets.iter().map(|a| a.name.as_str()).collect();
format!(
"No compatible asset found for Zen version {} on {}/{}. Available assets: {}",
version,
os,
arch,
available_assets.join(", ")
)
})?;
Ok(asset_url)
}
+61 -1
View File
@@ -189,8 +189,22 @@ impl DownloadedBrowsersRegistry {
let mut to_remove = Vec::new();
for (browser, versions) in &self.browsers {
for (version, info) in versions {
// Only remove verified downloads that are not used by any active profile
if info.verified && !active_set.contains(&(browser.clone(), version.clone())) {
to_remove.push((browser.clone(), version.clone()));
// Double-check that this browser+version is truly not in use
// by looking for exact matches in the active profiles
let is_in_use = active_profiles
.iter()
.any(|(active_browser, active_version)| {
active_browser == browser && active_version == version
});
if !is_in_use {
to_remove.push((browser.clone(), version.clone()));
println!("Marking for removal: {browser} {version} (not used by any profile)");
} else {
println!("Keeping: {browser} {version} (in use by profile)");
}
}
}
}
@@ -201,9 +215,16 @@ impl DownloadedBrowsersRegistry {
eprintln!("Failed to cleanup unused binary {browser}:{version}: {e}");
} else {
cleaned_up.push(format!("{browser} {version}"));
println!("Successfully removed unused binary: {browser} {version}");
}
}
if cleaned_up.is_empty() {
println!("No unused binaries found to clean up");
} else {
println!("Cleaned up {} unused binaries", cleaned_up.len());
}
Ok(cleaned_up)
}
@@ -217,6 +238,45 @@ impl DownloadedBrowsersRegistry {
.map(|profile| (profile.browser.clone(), profile.version.clone()))
.collect()
}
/// Verify that all registered browsers actually exist on disk and clean up stale entries
pub fn verify_and_cleanup_stale_entries(
&mut self,
browser_runner: &crate::browser_runner::BrowserRunner,
) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> {
use crate::browser::{create_browser, BrowserType};
let mut cleaned_up = Vec::new();
let binaries_dir = browser_runner.get_binaries_dir();
let browsers_to_check: Vec<(String, String)> = self
.browsers
.iter()
.flat_map(|(browser, versions)| {
versions
.keys()
.map(|version| (browser.clone(), version.clone()))
})
.collect();
for (browser_str, version) in browsers_to_check {
if let Ok(browser_type) = BrowserType::from_str(&browser_str) {
let browser = create_browser(browser_type);
if !browser.is_version_downloaded(&version, &binaries_dir) {
// Files don't exist, remove from registry
if let Some(_removed) = self.remove_browser(&browser_str, &version) {
cleaned_up.push(format!("{browser_str} {version}"));
println!("Removed stale registry entry for {browser_str} {version}");
}
}
}
}
if !cleaned_up.is_empty() {
self.save()?;
}
Ok(cleaned_up)
}
}
#[cfg(test)]
+8 -6
View File
@@ -26,12 +26,12 @@ mod version_updater;
extern crate lazy_static;
use browser_runner::{
check_browser_exists, check_browser_status, create_browser_profile_new, delete_profile,
download_browser, fetch_browser_versions_cached_first, fetch_browser_versions_with_count,
fetch_browser_versions_with_count_cached_first, get_browser_release_types,
get_downloaded_browser_versions, get_supported_browsers, is_browser_supported_on_platform,
kill_browser_profile, launch_browser_profile, list_browser_profiles, rename_profile,
update_profile_proxy, update_profile_version,
check_browser_exists, check_browser_status, check_missing_binaries, create_browser_profile_new,
delete_profile, download_browser, ensure_all_binaries_exist, fetch_browser_versions_cached_first,
fetch_browser_versions_with_count, fetch_browser_versions_with_count_cached_first,
get_browser_release_types, get_downloaded_browser_versions, get_supported_browsers,
is_browser_supported_on_platform, kill_browser_profile, launch_browser_profile,
list_browser_profiles, rename_profile, update_profile_proxy, update_profile_version,
};
use settings_manager::{
@@ -374,6 +374,8 @@ pub fn run() {
get_system_theme,
detect_existing_profiles,
import_browser_profile,
check_missing_binaries,
ensure_all_binaries_exist,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
-3
View File
@@ -29,8 +29,6 @@ pub struct AppSettings {
pub show_settings_on_startup: bool,
#[serde(default = "default_theme")]
pub theme: String, // "light", "dark", or "system"
#[serde(default)]
pub auto_delete_unused_binaries: bool,
}
fn default_theme() -> String {
@@ -43,7 +41,6 @@ impl Default for AppSettings {
set_as_default_browser: false,
show_settings_on_startup: true,
theme: "system".to_string(),
auto_delete_unused_binaries: true,
}
}
}