refactor: migrate to singleton pattern

This commit is contained in:
zhom
2025-08-02 16:29:40 +04:00
parent 57a36a5fc2
commit 2c57920d44
17 changed files with 501 additions and 377 deletions
+68 -112
View File
@@ -15,9 +15,8 @@ use crate::browser_version_service::{
BrowserVersionInfo, BrowserVersionService, BrowserVersionsResult,
};
use crate::camoufox::CamoufoxConfig;
use crate::download::{DownloadProgress, Downloader};
use crate::download::DownloadProgress;
use crate::downloaded_browsers::DownloadedBrowsersRegistry;
use crate::extraction::Extractor;
// Global state to track currently downloading browser-version pairs
lazy_static::lazy_static! {
@@ -29,15 +28,19 @@ pub struct BrowserRunner {
}
impl BrowserRunner {
pub fn new() -> Self {
fn new() -> Self {
Self {
base_dirs: BaseDirs::new().expect("Failed to get base directories"),
}
}
pub fn instance() -> &'static BrowserRunner {
&BROWSER_RUNNER
}
/// Migrate old profile structure to new UUID-based structure
pub async fn migrate_profiles_to_uuid(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
let profile_manager = ProfileManager::new();
let profile_manager = ProfileManager::instance();
profile_manager.migrate_profiles_to_uuid().await
}
@@ -76,7 +79,7 @@ impl BrowserRunner {
}
pub fn get_profiles_dir(&self) -> PathBuf {
let profile_manager = ProfileManager::new();
let profile_manager = ProfileManager::instance();
profile_manager.get_profiles_dir()
}
@@ -89,8 +92,8 @@ impl BrowserRunner {
.list_profiles()
.map_err(|e| format!("Failed to list profiles: {e}"))?;
// Load registry
let mut registry = crate::downloaded_browsers::DownloadedBrowsersRegistry::load()?;
// Get registry instance
let registry = crate::downloaded_browsers::DownloadedBrowsersRegistry::instance();
// Get active browser versions (all profiles)
let active_versions = registry.get_active_browser_versions(&profiles);
@@ -115,17 +118,17 @@ impl BrowserRunner {
proxy: &ProxySettings,
internal_proxy: Option<&ProxySettings>,
) -> Result<(), Box<dyn std::error::Error>> {
let profile_manager = ProfileManager::new();
let profile_manager = ProfileManager::instance();
profile_manager.apply_proxy_settings_to_profile(profile_data_path, proxy, internal_proxy)
}
pub fn save_profile(&self, profile: &BrowserProfile) -> Result<(), Box<dyn std::error::Error>> {
let profile_manager = ProfileManager::new();
let profile_manager = ProfileManager::instance();
profile_manager.save_profile(profile)
}
pub fn list_profiles(&self) -> Result<Vec<BrowserProfile>, Box<dyn std::error::Error>> {
let profile_manager = ProfileManager::new();
let profile_manager = ProfileManager::instance();
profile_manager.list_profiles()
}
@@ -228,16 +231,13 @@ impl BrowserRunner {
"Launching Camoufox via nodecar for profile: {}",
profile.name
);
let camoufox_result = crate::camoufox::launch_camoufox_profile_nodecar(
app_handle.clone(),
profile.clone(),
final_config,
url,
)
.await
.map_err(|e| -> Box<dyn std::error::Error + Send + Sync> {
format!("Failed to launch camoufox via nodecar: {e}").into()
})?;
let camoufox_launcher = crate::camoufox::CamoufoxNodecarLauncher::instance();
let camoufox_result = camoufox_launcher
.launch_camoufox_profile_nodecar(app_handle.clone(), profile.clone(), final_config, url)
.await
.map_err(|e| -> Box<dyn std::error::Error + Send + Sync> {
format!("Failed to launch camoufox via nodecar: {e}").into()
})?;
// For server-based Camoufox, we use the process_id
let process_id = camoufox_result.processId.unwrap_or(0);
@@ -436,7 +436,7 @@ impl BrowserRunner {
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// Handle camoufox profiles using nodecar launcher
if profile.browser == "camoufox" {
let camoufox_launcher = crate::camoufox::CamoufoxNodecarLauncher::new(app_handle.clone());
let camoufox_launcher = crate::camoufox::CamoufoxNodecarLauncher::instance();
// Get the profile path based on the UUID
let profiles_dir = self.get_profiles_dir();
@@ -760,7 +760,7 @@ impl BrowserRunner {
}
pub fn delete_profile(&self, profile_name: &str) -> Result<(), Box<dyn std::error::Error>> {
let profile_manager = ProfileManager::new();
let profile_manager = ProfileManager::instance();
profile_manager.delete_profile(profile_name)?;
// Always perform cleanup after profile deletion to remove unused binaries
@@ -776,7 +776,7 @@ impl BrowserRunner {
app_handle: tauri::AppHandle,
profile: &BrowserProfile,
) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
let profile_manager = ProfileManager::new();
let profile_manager = ProfileManager::instance();
profile_manager
.check_browser_status(app_handle, profile)
.await
@@ -789,7 +789,7 @@ impl BrowserRunner {
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// Handle camoufox profiles using nodecar launcher
if profile.browser == "camoufox" {
let camoufox_launcher = crate::camoufox::CamoufoxNodecarLauncher::new(app_handle.clone());
let camoufox_launcher = crate::camoufox::CamoufoxNodecarLauncher::instance();
// Search by profile path to find the running Camoufox instance
let profiles_dir = self.get_profiles_dir();
@@ -1013,15 +1013,14 @@ impl BrowserRunner {
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 registry = DownloadedBrowsersRegistry::instance();
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(", ")
);
}
}
@@ -1072,9 +1071,8 @@ impl BrowserRunner {
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}"))?;
// Get registry instance and check if already downloaded
let registry = DownloadedBrowsersRegistry::instance();
// Check if registry thinks it's downloaded, but also verify files actually exist
if registry.is_browser_downloaded(&browser_str, &version) {
@@ -1134,8 +1132,8 @@ impl BrowserRunner {
.save()
.map_err(|e| format!("Failed to save registry: {e}"))?;
// Use the new download module
let downloader = Downloader::new();
// Use the download module
let downloader = crate::download::Downloader::instance();
let download_path = match downloader
.download_browser(
&app_handle,
@@ -1160,9 +1158,9 @@ impl BrowserRunner {
}
};
// Use the new extraction module
// Use the extraction module
if download_info.is_archive {
let extractor = Extractor::new();
let extractor = crate::extraction::Extractor::instance();
match extractor
.extract_browser(
&app_handle,
@@ -1225,13 +1223,6 @@ impl BrowserRunner {
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(&browser_str, &version)
.map_err(|e| format!("Failed to mark download as completed: {e}"))?;
@@ -1247,7 +1238,7 @@ impl BrowserRunner {
if !GeoIPDownloader::is_geoip_database_available() {
println!("Downloading GeoIP database for Camoufox...");
let geoip_downloader = GeoIPDownloader::new();
let geoip_downloader = GeoIPDownloader::instance();
if let Err(e) = geoip_downloader.download_geoip_database(&app_handle).await {
eprintln!("Warning: Failed to download GeoIP database: {e}");
// Don't fail the browser download if GeoIP download fails
@@ -1297,12 +1288,11 @@ impl BrowserRunner {
// 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
}
let registry = DownloadedBrowsersRegistry::instance();
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
}
}
@@ -1319,7 +1309,7 @@ pub fn create_browser_profile(
proxy_id: Option<String>,
camoufox_config: Option<CamoufoxConfig>,
) -> Result<BrowserProfile, String> {
let profile_manager = ProfileManager::new();
let profile_manager = ProfileManager::instance();
profile_manager
.create_profile(
&name,
@@ -1334,7 +1324,7 @@ pub fn create_browser_profile(
#[tauri::command]
pub fn list_browser_profiles() -> Result<Vec<BrowserProfile>, String> {
let profile_manager = ProfileManager::new();
let profile_manager = ProfileManager::instance();
profile_manager
.list_profiles()
.map_err(|e| format!("Failed to list profiles: {e}"))
@@ -1346,7 +1336,7 @@ pub async fn launch_browser_profile(
profile: BrowserProfile,
url: Option<String>,
) -> Result<BrowserProfile, String> {
let browser_runner = BrowserRunner::new();
let browser_runner = BrowserRunner::instance();
// Store the internal proxy settings for passing to launch_browser
let mut internal_proxy_settings: Option<ProxySettings> = None;
@@ -1365,7 +1355,7 @@ pub async fn launch_browser_profile(
.await
{
Ok(internal_proxy) => {
let browser_runner = BrowserRunner::new();
let browser_runner = BrowserRunner::instance();
let profiles_dir = browser_runner.get_profiles_dir();
let profile_path = profiles_dir.join(profile.id.to_string()).join("profile");
@@ -1385,7 +1375,7 @@ pub async fn launch_browser_profile(
Err(e) => {
eprintln!("Failed to start proxy: {e}");
// Still continue with browser launch, but without proxy
let browser_runner = BrowserRunner::new();
let browser_runner = BrowserRunner::instance();
let profiles_dir = browser_runner.get_profiles_dir();
let profile_path = profiles_dir.join(profile.id.to_string()).join("profile");
@@ -1439,7 +1429,7 @@ pub async fn update_profile_proxy(
profile_name: String,
proxy_id: Option<String>,
) -> Result<BrowserProfile, String> {
let profile_manager = ProfileManager::new();
let profile_manager = ProfileManager::instance();
profile_manager
.update_profile_proxy(app_handle, &profile_name, proxy_id)
.await
@@ -1451,7 +1441,7 @@ pub fn update_profile_version(
profile_name: String,
version: String,
) -> Result<BrowserProfile, String> {
let profile_manager = ProfileManager::new();
let profile_manager = ProfileManager::instance();
profile_manager
.update_profile_version(&profile_name, &version)
.map_err(|e| format!("Failed to update profile version: {e}"))
@@ -1462,7 +1452,7 @@ pub async fn check_browser_status(
app_handle: tauri::AppHandle,
profile: BrowserProfile,
) -> Result<bool, String> {
let profile_manager = ProfileManager::new();
let profile_manager = ProfileManager::instance();
profile_manager
.check_browser_status(app_handle, &profile)
.await
@@ -1475,7 +1465,7 @@ pub fn rename_profile(
old_name: &str,
new_name: &str,
) -> Result<BrowserProfile, String> {
let profile_manager = ProfileManager::new();
let profile_manager = ProfileManager::instance();
profile_manager
.rename_profile(old_name, new_name)
.map_err(|e| format!("Failed to rename profile: {e}"))
@@ -1483,7 +1473,7 @@ pub fn rename_profile(
#[tauri::command]
pub fn delete_profile(_app_handle: tauri::AppHandle, profile_name: String) -> Result<(), String> {
let browser_runner = BrowserRunner::new();
let browser_runner = BrowserRunner::instance();
browser_runner
.delete_profile(profile_name.as_str())
.map_err(|e| format!("Failed to delete profile: {e}"))
@@ -1579,7 +1569,7 @@ pub async fn download_browser(
browser_str: String,
version: String,
) -> Result<String, String> {
let browser_runner = BrowserRunner::new();
let browser_runner = BrowserRunner::instance();
browser_runner
.download_browser_impl(app_handle, browser_str, version)
.await
@@ -1588,7 +1578,7 @@ pub async fn download_browser(
#[tauri::command]
pub fn is_browser_downloaded(browser_str: String, version: String) -> bool {
let browser_runner = BrowserRunner::new();
let browser_runner = BrowserRunner::instance();
browser_runner.is_browser_downloaded(&browser_str, &version)
}
@@ -1603,7 +1593,7 @@ pub async fn kill_browser_profile(
app_handle: tauri::AppHandle,
profile: BrowserProfile,
) -> Result<(), String> {
let browser_runner = BrowserRunner::new();
let browser_runner = BrowserRunner::instance();
browser_runner
.kill_browser_process(app_handle, &profile)
.await
@@ -1637,7 +1627,7 @@ pub async fn update_camoufox_config(
profile_name: String,
config: CamoufoxConfig,
) -> Result<(), String> {
let profile_manager = ProfileManager::new();
let profile_manager = ProfileManager::instance();
profile_manager
.update_camoufox_config(app_handle, &profile_name, config)
.await
@@ -1657,8 +1647,7 @@ pub async fn fetch_browser_versions_with_count(
#[tauri::command]
pub fn get_downloaded_browser_versions(browser_str: String) -> Result<Vec<String>, String> {
let registry = DownloadedBrowsersRegistry::load()
.map_err(|e| format!("Failed to load browser registry: {e}"))?;
let registry = DownloadedBrowsersRegistry::instance();
Ok(registry.get_downloaded_versions(&browser_str))
}
@@ -1675,7 +1664,7 @@ pub async fn get_browser_release_types(
#[tauri::command]
pub async fn check_missing_binaries() -> Result<Vec<(String, String, String)>, String> {
let browser_runner = BrowserRunner::new();
let browser_runner = BrowserRunner::instance();
browser_runner
.check_missing_binaries()
.await
@@ -1686,7 +1675,7 @@ pub async fn check_missing_binaries() -> Result<Vec<(String, String, String)>, S
pub async fn ensure_all_binaries_exist(
app_handle: tauri::AppHandle,
) -> Result<Vec<String>, String> {
let browser_runner = BrowserRunner::new();
let browser_runner = BrowserRunner::instance();
browser_runner
.ensure_all_binaries_exist(&app_handle)
.await
@@ -1698,22 +1687,16 @@ mod tests {
use super::*;
use tempfile::TempDir;
fn create_test_browser_runner() -> (BrowserRunner, TempDir) {
fn create_test_browser_runner() -> (&'static BrowserRunner, TempDir) {
let temp_dir = TempDir::new().unwrap();
// Mock the base directories by setting environment variables
std::env::set_var("HOME", temp_dir.path());
let browser_runner = BrowserRunner::new();
let browser_runner = BrowserRunner::instance();
(browser_runner, temp_dir)
}
#[test]
fn test_browser_runner_creation() {
let (_runner, _temp_dir) = create_test_browser_runner();
// If we get here without panicking, the test passes
}
#[test]
fn test_get_binaries_dir() {
let (runner, _temp_dir) = create_test_browser_runner();
@@ -1731,36 +1714,9 @@ mod tests {
assert!(profiles_dir.to_string_lossy().contains("DonutBrowser"));
assert!(profiles_dir.to_string_lossy().contains("profiles"));
}
#[test]
fn test_profile_operations_via_profile_manager() {
let (_runner, _temp_dir) = create_test_browser_runner();
let profile_manager = ProfileManager::new();
let profile = profile_manager
.create_profile("Test Profile", "firefox", "139.0", "stable", None, None)
.unwrap();
assert_eq!(profile.name, "Test Profile");
assert_eq!(profile.browser, "firefox");
assert_eq!(profile.version, "139.0");
assert!(profile.proxy_id.is_none());
assert!(profile.process_id.is_none());
// Test listing profiles
let profiles = profile_manager.list_profiles().unwrap();
assert_eq!(profiles.len(), 1);
assert_eq!(profiles[0].name, "Test Profile");
// Test renaming profile
let renamed_profile = profile_manager
.rename_profile("Test Profile", "Renamed Profile")
.unwrap();
assert_eq!(renamed_profile.name, "Renamed Profile");
// Test deleting profile
profile_manager.delete_profile("Renamed Profile").unwrap();
let profiles = profile_manager.list_profiles().unwrap();
assert_eq!(profiles.len(), 0);
}
}
// Global singleton instance
lazy_static::lazy_static! {
static ref BROWSER_RUNNER: BrowserRunner = BrowserRunner::new();
}