refactor: properly cleanup unused binaries and simplify downloaded browser registry

This commit is contained in:
zhom
2025-07-26 19:03:42 +04:00
parent 328e6f16ee
commit df78e22650
4 changed files with 94 additions and 101 deletions
+6 -3
View File
@@ -370,12 +370,15 @@ impl AutoUpdater {
let mut registry = crate::downloaded_browsers::DownloadedBrowsersRegistry::load() let mut registry = crate::downloaded_browsers::DownloadedBrowsersRegistry::load()
.map_err(|e| format!("Failed to load browser registry: {e}"))?; .map_err(|e| format!("Failed to load browser registry: {e}"))?;
// Get active browser versions // Get active browser versions (all profiles)
let active_versions = registry.get_active_browser_versions(&profiles); let active_versions = registry.get_active_browser_versions(&profiles);
// Cleanup unused binaries // Get running browser versions (only running profiles)
let running_versions = registry.get_running_browser_versions(&profiles);
// Cleanup unused binaries (but keep running ones)
let cleaned_up = registry let cleaned_up = registry
.cleanup_unused_binaries(&active_versions) .cleanup_unused_binaries(&active_versions, &running_versions)
.map_err(|e| format!("Failed to cleanup unused binaries: {e}"))?; .map_err(|e| format!("Failed to cleanup unused binaries: {e}"))?;
// Save updated registry // Save updated registry
+22 -6
View File
@@ -1434,6 +1434,11 @@ impl BrowserRunner {
self.disable_proxy_settings_in_profile(&profile_data_dir)?; self.disable_proxy_settings_in_profile(&profile_data_dir)?;
} }
// Perform cleanup after profile creation to remove any unused binaries
if let Err(e) = self.cleanup_unused_binaries_internal() {
println!("Warning: Failed to cleanup unused binaries after profile creation: {e}");
}
Ok(profile) Ok(profile)
} }
@@ -1616,7 +1621,7 @@ impl BrowserRunner {
} }
/// Internal method to cleanup unused binaries (used by auto-cleanup) /// Internal method to cleanup unused binaries (used by auto-cleanup)
fn cleanup_unused_binaries_internal( pub fn cleanup_unused_binaries_internal(
&self, &self,
) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> {
// Load current profiles // Load current profiles
@@ -1627,11 +1632,14 @@ impl BrowserRunner {
// Load registry // Load registry
let mut registry = crate::downloaded_browsers::DownloadedBrowsersRegistry::load()?; let mut registry = crate::downloaded_browsers::DownloadedBrowsersRegistry::load()?;
// Get active browser versions // Get active browser versions (all profiles)
let active_versions = registry.get_active_browser_versions(&profiles); let active_versions = registry.get_active_browser_versions(&profiles);
// Cleanup unused binaries // Get running browser versions (only running profiles)
let cleaned_up = registry.cleanup_unused_binaries(&active_versions)?; let running_versions = registry.get_running_browser_versions(&profiles);
// Cleanup unused binaries (but keep running ones)
let cleaned_up = registry.cleanup_unused_binaries(&active_versions, &running_versions)?;
// Save updated registry // Save updated registry
registry.save()?; registry.save()?;
@@ -3190,14 +3198,14 @@ impl BrowserRunner {
} }
// Mark download as completed in registry // Mark download as completed in registry
let actual_version = if browser_str == "chromium" { let _actual_version = if browser_str == "chromium" {
Some(version.clone()) Some(version.clone())
} else { } else {
None None
}; };
registry registry
.mark_download_completed_with_actual_version(&browser_str, &version, actual_version) .mark_download_completed(&browser_str, &version)
.map_err(|e| format!("Failed to mark download as completed: {e}"))?; .map_err(|e| format!("Failed to mark download as completed: {e}"))?;
registry registry
.save() .save()
@@ -3651,6 +3659,14 @@ pub async fn ensure_all_binaries_exist(
.map_err(|e| format!("Failed to ensure all binaries exist: {e}")) .map_err(|e| format!("Failed to ensure all binaries exist: {e}"))
} }
#[tauri::command]
pub async fn cleanup_unused_binaries() -> Result<Vec<String>, String> {
let browser_runner = BrowserRunner::new();
browser_runner
.cleanup_unused_binaries_internal()
.map_err(|e| format!("Failed to cleanup unused binaries: {e}"))
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
+47 -92
View File
@@ -8,13 +8,7 @@ use std::path::PathBuf;
pub struct DownloadedBrowserInfo { pub struct DownloadedBrowserInfo {
pub browser: String, pub browser: String,
pub version: String, pub version: String,
pub download_date: u64,
pub file_path: PathBuf, pub file_path: PathBuf,
pub verified: bool,
pub actual_version: Option<String>, // For browsers like Chromium where we track the actual version
pub file_size: Option<u64>, // For tracking file size changes (useful for rolling releases)
#[serde(default)] // Add default value (false) for backwards compatibility
pub is_rolling_release: bool, // True for Zen's twilight releases and other rolling releases
} }
#[derive(Debug, Serialize, Deserialize, Default)] #[derive(Debug, Serialize, Deserialize, Default)]
@@ -82,66 +76,39 @@ impl DownloadedBrowsersRegistry {
.browsers .browsers
.get(browser) .get(browser)
.and_then(|versions| versions.get(version)) .and_then(|versions| versions.get(version))
.map(|info| info.verified) .is_some()
.unwrap_or(false)
} }
pub fn get_downloaded_versions(&self, browser: &str) -> Vec<String> { pub fn get_downloaded_versions(&self, browser: &str) -> Vec<String> {
self self
.browsers .browsers
.get(browser) .get(browser)
.map(|versions| { .map(|versions| versions.keys().cloned().collect())
versions
.iter()
.filter(|(_, info)| info.verified)
.map(|(version, _)| version.clone())
.collect()
})
.unwrap_or_default() .unwrap_or_default()
} }
pub fn mark_download_started(&mut self, browser: &str, version: &str, file_path: PathBuf) { pub fn mark_download_started(&mut self, browser: &str, version: &str, file_path: PathBuf) {
let is_rolling = Self::is_rolling_release(browser, version);
let info = DownloadedBrowserInfo { let info = DownloadedBrowserInfo {
browser: browser.to_string(), browser: browser.to_string(),
version: version.to_string(), version: version.to_string(),
download_date: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
file_path, file_path,
verified: false,
actual_version: None,
file_size: None,
is_rolling_release: is_rolling,
}; };
self.add_browser(info); self.add_browser(info);
} }
pub fn mark_download_completed_with_actual_version( pub fn mark_download_completed(&mut self, browser: &str, version: &str) -> Result<(), String> {
&mut self, if self
browser: &str,
version: &str,
actual_version: Option<String>,
) -> Result<(), String> {
if let Some(info) = self
.browsers .browsers
.get_mut(browser) .get(browser)
.and_then(|versions| versions.get_mut(version)) .and_then(|versions| versions.get(version))
.is_some()
{ {
info.verified = true;
info.actual_version = actual_version;
Ok(()) Ok(())
} else { } else {
Err(format!("Browser {browser}:{version} not found in registry")) Err(format!("Browser {browser}:{version} not found in registry"))
} }
} }
fn is_rolling_release(browser: &str, version: &str) -> bool {
// Check if this is a rolling release like twilight
browser == "zen" && version.to_lowercase() == "twilight"
}
pub fn cleanup_failed_download( pub fn cleanup_failed_download(
&mut self, &mut self,
browser: &str, browser: &str,
@@ -180,32 +147,35 @@ impl DownloadedBrowsersRegistry {
pub fn cleanup_unused_binaries( pub fn cleanup_unused_binaries(
&mut self, &mut self,
active_profiles: &[(String, String)], // (browser, version) pairs active_profiles: &[(String, String)], // (browser, version) pairs
running_profiles: &[(String, String)], // (browser, version) pairs for running profiles
) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> {
let active_set: std::collections::HashSet<(String, String)> = let active_set: std::collections::HashSet<(String, String)> =
active_profiles.iter().cloned().collect(); active_profiles.iter().cloned().collect();
let running_set: std::collections::HashSet<(String, String)> =
running_profiles.iter().cloned().collect();
let mut cleaned_up = Vec::new(); let mut cleaned_up = Vec::new();
// Collect all downloaded browsers that are not in active profiles // Collect all downloaded browsers that are not in active profiles
let mut to_remove = Vec::new(); let mut to_remove = Vec::new();
for (browser, versions) in &self.browsers { for (browser, versions) in &self.browsers {
for (version, info) in versions { for version in versions.keys() {
// Only remove verified downloads that are not used by any active profile let browser_version = (browser.clone(), version.clone());
if info.verified && !active_set.contains(&(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 { // Don't remove if it's used by any active profile
to_remove.push((browser.clone(), version.clone())); if active_set.contains(&browser_version) {
println!("Marking for removal: {browser} {version} (not used by any profile)"); println!("Keeping: {browser} {version} (in use by profile)");
} else { continue;
println!("Keeping: {browser} {version} (in use by profile)");
}
} }
// Don't remove if it's currently running (even if not in active profiles)
if running_set.contains(&browser_version) {
println!("Keeping: {browser} {version} (currently running)");
continue;
}
// Mark for removal
to_remove.push(browser_version);
println!("Marking for removal: {browser} {version} (not used by any profile)");
} }
} }
@@ -277,6 +247,18 @@ impl DownloadedBrowsersRegistry {
Ok(cleaned_up) Ok(cleaned_up)
} }
/// Get all browsers and versions that are currently running
pub fn get_running_browser_versions(
&self,
profiles: &[crate::browser_runner::BrowserProfile],
) -> Vec<(String, String)> {
profiles
.iter()
.filter(|profile| profile.process_id.is_some())
.map(|profile| (profile.browser.clone(), profile.version.clone()))
.collect()
}
} }
#[cfg(test)] #[cfg(test)]
@@ -295,12 +277,7 @@ mod tests {
let info = DownloadedBrowserInfo { let info = DownloadedBrowserInfo {
browser: "firefox".to_string(), browser: "firefox".to_string(),
version: "139.0".to_string(), version: "139.0".to_string(),
download_date: 1234567890,
file_path: PathBuf::from("/test/path"), file_path: PathBuf::from("/test/path"),
verified: true,
actual_version: None,
file_size: None,
is_rolling_release: false,
}; };
registry.add_browser(info.clone()); registry.add_browser(info.clone());
@@ -317,34 +294,19 @@ mod tests {
let info1 = DownloadedBrowserInfo { let info1 = DownloadedBrowserInfo {
browser: "firefox".to_string(), browser: "firefox".to_string(),
version: "139.0".to_string(), version: "139.0".to_string(),
download_date: 1234567890,
file_path: PathBuf::from("/test/path1"), file_path: PathBuf::from("/test/path1"),
verified: true,
actual_version: None,
file_size: None,
is_rolling_release: false,
}; };
let info2 = DownloadedBrowserInfo { let info2 = DownloadedBrowserInfo {
browser: "firefox".to_string(), browser: "firefox".to_string(),
version: "140.0".to_string(), version: "140.0".to_string(),
download_date: 1234567891,
file_path: PathBuf::from("/test/path2"), file_path: PathBuf::from("/test/path2"),
verified: false, // Not verified, should not be included
actual_version: None,
file_size: None,
is_rolling_release: false,
}; };
let info3 = DownloadedBrowserInfo { let info3 = DownloadedBrowserInfo {
browser: "firefox".to_string(), browser: "firefox".to_string(),
version: "141.0".to_string(), version: "141.0".to_string(),
download_date: 1234567892,
file_path: PathBuf::from("/test/path3"), file_path: PathBuf::from("/test/path3"),
verified: true,
actual_version: None,
file_size: None,
is_rolling_release: false,
}; };
registry.add_browser(info1); registry.add_browser(info1);
@@ -352,10 +314,10 @@ mod tests {
registry.add_browser(info3); registry.add_browser(info3);
let versions = registry.get_downloaded_versions("firefox"); let versions = registry.get_downloaded_versions("firefox");
assert_eq!(versions.len(), 2); assert_eq!(versions.len(), 3);
assert!(versions.contains(&"139.0".to_string())); assert!(versions.contains(&"139.0".to_string()));
assert!(versions.contains(&"140.0".to_string()));
assert!(versions.contains(&"141.0".to_string())); assert!(versions.contains(&"141.0".to_string()));
assert!(!versions.contains(&"140.0".to_string()));
} }
#[test] #[test]
@@ -365,15 +327,15 @@ mod tests {
// Mark download started // Mark download started
registry.mark_download_started("firefox", "139.0", PathBuf::from("/test/path")); registry.mark_download_started("firefox", "139.0", PathBuf::from("/test/path"));
// Should not be considered downloaded yet // Should be considered downloaded immediately
assert!(!registry.is_browser_downloaded("firefox", "139.0")); assert!(registry.is_browser_downloaded("firefox", "139.0"));
// Mark as completed // Mark as completed
registry registry
.mark_download_completed_with_actual_version("firefox", "139.0", Some("139.0".to_string())) .mark_download_completed("firefox", "139.0")
.unwrap(); .unwrap();
// Now should be considered downloaded // Should still be considered downloaded
assert!(registry.is_browser_downloaded("firefox", "139.0")); assert!(registry.is_browser_downloaded("firefox", "139.0"));
} }
@@ -383,12 +345,7 @@ mod tests {
let info = DownloadedBrowserInfo { let info = DownloadedBrowserInfo {
browser: "firefox".to_string(), browser: "firefox".to_string(),
version: "139.0".to_string(), version: "139.0".to_string(),
download_date: 1234567890,
file_path: PathBuf::from("/test/path"), file_path: PathBuf::from("/test/path"),
verified: true,
actual_version: None,
file_size: None,
is_rolling_release: false,
}; };
registry.add_browser(info); registry.add_browser(info);
@@ -400,15 +357,13 @@ mod tests {
} }
#[test] #[test]
fn test_twilight_rolling_release() { fn test_twilight_download() {
let mut registry = DownloadedBrowsersRegistry::new(); let mut registry = DownloadedBrowsersRegistry::new();
// Mark twilight download started // Mark twilight download started
registry.mark_download_started("zen", "twilight", PathBuf::from("/test/zen-twilight")); registry.mark_download_started("zen", "twilight", PathBuf::from("/test/zen-twilight"));
// Check that it's marked as rolling release // Check that it's registered
let zen_versions = &registry.browsers["zen"]; assert!(registry.is_browser_downloaded("zen", "twilight"));
let twilight_info = &zen_versions["twilight"];
assert!(twilight_info.is_rolling_release);
} }
} }
+19
View File
@@ -72,6 +72,8 @@ use group_manager::{
get_groups_with_profile_counts, get_profile_groups, update_profile_group, get_groups_with_profile_counts, get_profile_groups, update_profile_group,
}; };
use browser_runner::cleanup_unused_binaries;
// Trait to extend WebviewWindow with transparent titlebar functionality // Trait to extend WebviewWindow with transparent titlebar functionality
pub trait WindowExt { pub trait WindowExt {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
@@ -337,6 +339,22 @@ pub fn run() {
auto_updater::check_for_updates_with_progress(app_handle_auto_updater).await; auto_updater::check_for_updates_with_progress(app_handle_auto_updater).await;
}); });
// Start periodic cleanup task for unused binaries
tauri::async_runtime::spawn(async move {
let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(300)); // Every 5 minutes
loop {
interval.tick().await;
let browser_runner = crate::browser_runner::BrowserRunner::new();
if let Err(e) = browser_runner.cleanup_unused_binaries_internal() {
eprintln!("Periodic cleanup failed: {e}");
} else {
println!("Periodic cleanup completed successfully");
}
}
});
let app_handle_update = app.handle().clone(); let app_handle_update = app.handle().clone();
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
println!("Starting app update check at startup..."); println!("Starting app update check at startup...");
@@ -447,6 +465,7 @@ pub fn run() {
delete_profile_group, delete_profile_group,
assign_profiles_to_group, assign_profiles_to_group,
delete_selected_profiles, delete_selected_profiles,
cleanup_unused_binaries,
]) ])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");