mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-04-25 05:16:18 +02:00
refactor: migrate to singleton pattern
This commit is contained in:
Vendored
+3
@@ -31,6 +31,7 @@
|
||||
"datas",
|
||||
"dconf",
|
||||
"devedition",
|
||||
"distro",
|
||||
"doctest",
|
||||
"doesn",
|
||||
"domcontentloaded",
|
||||
@@ -85,6 +86,7 @@
|
||||
"mullvadbrowser",
|
||||
"mypy",
|
||||
"noarchive",
|
||||
"nobrowse",
|
||||
"noconfirm",
|
||||
"nodecar",
|
||||
"nodemon",
|
||||
@@ -96,6 +98,7 @@
|
||||
"orhun",
|
||||
"orjson",
|
||||
"osascript",
|
||||
"outpath",
|
||||
"pathex",
|
||||
"pathlib",
|
||||
"peerconnection",
|
||||
|
||||
@@ -6,8 +6,6 @@ use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use tauri::Emitter;
|
||||
|
||||
use crate::extraction::Extractor;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct AppReleaseAsset {
|
||||
pub name: String,
|
||||
@@ -488,7 +486,7 @@ impl AppAutoUpdater {
|
||||
archive_path: &Path,
|
||||
dest_dir: &Path,
|
||||
) -> Result<PathBuf, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let extractor = Extractor::new();
|
||||
let extractor = crate::extraction::Extractor::instance();
|
||||
|
||||
let extension = archive_path
|
||||
.extension()
|
||||
@@ -699,7 +697,7 @@ impl AppAutoUpdater {
|
||||
fs::create_dir_all(&temp_extract_dir)?;
|
||||
|
||||
// Extract ZIP file
|
||||
let extractor = crate::extraction::Extractor::new();
|
||||
let extractor = crate::extraction::Extractor::instance();
|
||||
let extracted_path = extractor
|
||||
.extract_zip(installer_path, &temp_extract_dir)
|
||||
.await?;
|
||||
|
||||
@@ -53,7 +53,7 @@ impl AutoUpdater {
|
||||
let mut browser_versions: HashMap<String, Vec<BrowserVersionInfo>> = HashMap::new();
|
||||
|
||||
// Group profiles by browser
|
||||
let profile_manager = crate::profile::ProfileManager::new();
|
||||
let profile_manager = crate::profile::ProfileManager::instance();
|
||||
let profiles = profile_manager
|
||||
.list_profiles()
|
||||
.map_err(|e| format!("Failed to list profiles: {e}"))?;
|
||||
@@ -296,7 +296,7 @@ impl AutoUpdater {
|
||||
browser: &str,
|
||||
new_version: &str,
|
||||
) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let profile_manager = crate::profile::ProfileManager::new();
|
||||
let profile_manager = crate::profile::ProfileManager::instance();
|
||||
let profiles = profile_manager
|
||||
.list_profiles()
|
||||
.map_err(|e| format!("Failed to list profiles: {e}"))?;
|
||||
@@ -360,14 +360,13 @@ impl AutoUpdater {
|
||||
&self,
|
||||
) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
// Load current profiles
|
||||
let profile_manager = crate::profile::ProfileManager::new();
|
||||
let profile_manager = crate::profile::ProfileManager::instance();
|
||||
let profiles = profile_manager
|
||||
.list_profiles()
|
||||
.map_err(|e| format!("Failed to load profiles: {e}"))?;
|
||||
|
||||
// Load registry
|
||||
let mut registry = crate::downloaded_browsers::DownloadedBrowsersRegistry::load()
|
||||
.map_err(|e| format!("Failed to load browser registry: {e}"))?;
|
||||
// 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);
|
||||
|
||||
+31
-10
@@ -789,17 +789,33 @@ impl Browser for CamoufoxBrowser {
|
||||
}
|
||||
}
|
||||
|
||||
// Factory function to create browser instances
|
||||
pub fn create_browser(browser_type: BrowserType) -> Box<dyn Browser> {
|
||||
match browser_type {
|
||||
BrowserType::MullvadBrowser
|
||||
| BrowserType::Firefox
|
||||
| BrowserType::FirefoxDeveloper
|
||||
| BrowserType::Zen
|
||||
| BrowserType::TorBrowser => Box::new(FirefoxBrowser::new(browser_type)),
|
||||
BrowserType::Chromium | BrowserType::Brave => Box::new(ChromiumBrowser::new(browser_type)),
|
||||
BrowserType::Camoufox => Box::new(CamoufoxBrowser::new()),
|
||||
pub struct BrowserFactory;
|
||||
|
||||
impl BrowserFactory {
|
||||
fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
pub fn instance() -> &'static BrowserFactory {
|
||||
&BROWSER_FACTORY
|
||||
}
|
||||
|
||||
pub fn create_browser(&self, browser_type: BrowserType) -> Box<dyn Browser> {
|
||||
match browser_type {
|
||||
BrowserType::MullvadBrowser
|
||||
| BrowserType::Firefox
|
||||
| BrowserType::FirefoxDeveloper
|
||||
| BrowserType::Zen
|
||||
| BrowserType::TorBrowser => Box::new(FirefoxBrowser::new(browser_type)),
|
||||
BrowserType::Chromium | BrowserType::Brave => Box::new(ChromiumBrowser::new(browser_type)),
|
||||
BrowserType::Camoufox => Box::new(CamoufoxBrowser::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Factory function to create browser instances (kept for backward compatibility)
|
||||
pub fn create_browser(browser_type: BrowserType) -> Box<dyn Browser> {
|
||||
BrowserFactory::instance().create_browser(browser_type)
|
||||
}
|
||||
|
||||
// Add GithubRelease and GithubAsset structs to browser.rs if they don't already exist
|
||||
@@ -1097,3 +1113,8 @@ mod tests {
|
||||
assert_eq!(deserialized.port, proxy.port);
|
||||
}
|
||||
}
|
||||
|
||||
// Global singleton instance
|
||||
lazy_static::lazy_static! {
|
||||
static ref BROWSER_FACTORY: BrowserFactory = BrowserFactory::new();
|
||||
}
|
||||
|
||||
+68
-112
@@ -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();
|
||||
}
|
||||
|
||||
@@ -30,21 +30,21 @@ pub struct DownloadInfo {
|
||||
pub is_archive: bool, // true for .dmg, .zip, etc.
|
||||
}
|
||||
|
||||
pub struct BrowserVersionService;
|
||||
pub struct BrowserVersionService {
|
||||
api_client: &'static ApiClient,
|
||||
}
|
||||
|
||||
impl BrowserVersionService {
|
||||
fn new() -> Self {
|
||||
Self
|
||||
Self {
|
||||
api_client: ApiClient::instance(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn instance() -> &'static BrowserVersionService {
|
||||
&BROWSER_VERSION_SERVICE
|
||||
}
|
||||
|
||||
fn api_client(&self) -> &'static ApiClient {
|
||||
ApiClient::instance()
|
||||
}
|
||||
|
||||
/// Check if a browser is supported on the current platform and architecture
|
||||
pub fn is_browser_supported(
|
||||
&self,
|
||||
@@ -116,7 +116,7 @@ impl BrowserVersionService {
|
||||
|
||||
/// Get cached browser versions immediately (returns None if no cache exists)
|
||||
pub fn get_cached_browser_versions(&self, browser: &str) -> Option<Vec<String>> {
|
||||
self.api_client().load_cached_versions(browser)
|
||||
self.api_client.load_cached_versions(browser)
|
||||
}
|
||||
|
||||
/// Get cached detailed browser version information immediately
|
||||
@@ -124,7 +124,7 @@ impl BrowserVersionService {
|
||||
&self,
|
||||
browser: &str,
|
||||
) -> Option<Vec<BrowserVersionInfo>> {
|
||||
let cached_versions = self.api_client().load_cached_versions(browser)?;
|
||||
let cached_versions = self.api_client.load_cached_versions(browser)?;
|
||||
|
||||
// Convert cached versions to detailed info (without dates since cache doesn't store them)
|
||||
let detailed_info: Vec<BrowserVersionInfo> = cached_versions
|
||||
@@ -143,7 +143,7 @@ impl BrowserVersionService {
|
||||
|
||||
/// Check if cache should be updated (expired or doesn't exist)
|
||||
pub fn should_update_cache(&self, browser: &str) -> bool {
|
||||
self.api_client().is_cache_expired(browser)
|
||||
self.api_client.is_cache_expired(browser)
|
||||
}
|
||||
|
||||
/// Get latest stable and nightly versions for a browser (cached first)
|
||||
@@ -227,7 +227,7 @@ impl BrowserVersionService {
|
||||
) -> Result<BrowserVersionsResult, Box<dyn std::error::Error + Send + Sync>> {
|
||||
// Get existing cached versions to compare and merge
|
||||
let existing_versions = self
|
||||
.api_client()
|
||||
.api_client
|
||||
.load_cached_versions(browser)
|
||||
.unwrap_or_default();
|
||||
let existing_set: HashSet<String> = existing_versions.into_iter().collect();
|
||||
@@ -264,7 +264,7 @@ impl BrowserVersionService {
|
||||
// Save the merged cache (unless explicitly bypassing cache)
|
||||
if !no_caching {
|
||||
if let Err(e) = self
|
||||
.api_client()
|
||||
.api_client
|
||||
.save_cached_versions(browser, &merged_versions)
|
||||
{
|
||||
eprintln!("Failed to save merged cache for {browser}: {e}");
|
||||
@@ -495,7 +495,7 @@ impl BrowserVersionService {
|
||||
) -> Result<usize, Box<dyn std::error::Error + Send + Sync>> {
|
||||
// Get existing cached versions
|
||||
let existing_versions = self
|
||||
.api_client()
|
||||
.api_client
|
||||
.load_cached_versions(browser)
|
||||
.unwrap_or_default();
|
||||
let existing_set: HashSet<String> = existing_versions.into_iter().collect();
|
||||
@@ -515,10 +515,7 @@ impl BrowserVersionService {
|
||||
sort_versions(&mut all_versions);
|
||||
|
||||
// Save the updated cache
|
||||
if let Err(e) = self
|
||||
.api_client()
|
||||
.save_cached_versions(browser, &all_versions)
|
||||
{
|
||||
if let Err(e) = self.api_client.save_cached_versions(browser, &all_versions) {
|
||||
eprintln!("Failed to save updated cache for {browser}: {e}");
|
||||
}
|
||||
|
||||
@@ -824,7 +821,7 @@ impl BrowserVersionService {
|
||||
no_caching: bool,
|
||||
) -> Result<Vec<BrowserRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
self
|
||||
.api_client()
|
||||
.api_client
|
||||
.fetch_firefox_releases_with_caching(no_caching)
|
||||
.await
|
||||
}
|
||||
@@ -844,7 +841,7 @@ impl BrowserVersionService {
|
||||
no_caching: bool,
|
||||
) -> Result<Vec<BrowserRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
self
|
||||
.api_client()
|
||||
.api_client
|
||||
.fetch_firefox_developer_releases_with_caching(no_caching)
|
||||
.await
|
||||
}
|
||||
@@ -862,7 +859,7 @@ impl BrowserVersionService {
|
||||
no_caching: bool,
|
||||
) -> Result<Vec<GithubRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
self
|
||||
.api_client()
|
||||
.api_client
|
||||
.fetch_mullvad_releases_with_caching(no_caching)
|
||||
.await
|
||||
}
|
||||
@@ -886,7 +883,7 @@ impl BrowserVersionService {
|
||||
no_caching: bool,
|
||||
) -> Result<Vec<GithubRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
self
|
||||
.api_client()
|
||||
.api_client
|
||||
.fetch_zen_releases_with_caching(no_caching)
|
||||
.await
|
||||
}
|
||||
@@ -904,7 +901,7 @@ impl BrowserVersionService {
|
||||
no_caching: bool,
|
||||
) -> Result<Vec<GithubRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
self
|
||||
.api_client()
|
||||
.api_client
|
||||
.fetch_brave_releases_with_caching(no_caching)
|
||||
.await
|
||||
}
|
||||
@@ -922,7 +919,7 @@ impl BrowserVersionService {
|
||||
no_caching: bool,
|
||||
) -> Result<Vec<BrowserRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
self
|
||||
.api_client()
|
||||
.api_client
|
||||
.fetch_chromium_releases_with_caching(no_caching)
|
||||
.await
|
||||
}
|
||||
@@ -940,7 +937,7 @@ impl BrowserVersionService {
|
||||
no_caching: bool,
|
||||
) -> Result<Vec<BrowserRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
self
|
||||
.api_client()
|
||||
.api_client
|
||||
.fetch_tor_releases_with_caching(no_caching)
|
||||
.await
|
||||
}
|
||||
@@ -958,7 +955,7 @@ impl BrowserVersionService {
|
||||
no_caching: bool,
|
||||
) -> Result<Vec<GithubRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
self
|
||||
.api_client()
|
||||
.api_client
|
||||
.fetch_camoufox_releases_with_caching(no_caching)
|
||||
.await
|
||||
}
|
||||
|
||||
+33
-39
@@ -120,18 +120,8 @@ pub struct CamoufoxNodecarLauncher {
|
||||
inner: Arc<AsyncMutex<CamoufoxNodecarLauncherInner>>,
|
||||
}
|
||||
|
||||
// Global singleton instance
|
||||
lazy_static::lazy_static! {
|
||||
static ref GLOBAL_NODECAR_LAUNCHER: CamoufoxNodecarLauncher = CamoufoxNodecarLauncher::new_singleton();
|
||||
}
|
||||
|
||||
impl CamoufoxNodecarLauncher {
|
||||
pub fn new(_app_handle: AppHandle) -> Self {
|
||||
// Return a reference to the global singleton
|
||||
GLOBAL_NODECAR_LAUNCHER.clone()
|
||||
}
|
||||
|
||||
pub fn new_singleton() -> Self {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
inner: Arc::new(AsyncMutex::new(CamoufoxNodecarLauncherInner {
|
||||
instances: HashMap::new(),
|
||||
@@ -139,10 +129,8 @@ impl CamoufoxNodecarLauncher {
|
||||
}
|
||||
}
|
||||
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: Arc::clone(&self.inner),
|
||||
}
|
||||
pub fn instance() -> &'static CamoufoxNodecarLauncher {
|
||||
&CAMOUFOX_NODECAR_LAUNCHER
|
||||
}
|
||||
|
||||
/// Create a test configuration to verify anti-fingerprinting is working
|
||||
@@ -619,33 +607,34 @@ impl CamoufoxNodecarLauncher {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn launch_camoufox_profile_nodecar(
|
||||
app_handle: AppHandle,
|
||||
profile: BrowserProfile,
|
||||
config: CamoufoxConfig,
|
||||
url: Option<String>,
|
||||
) -> Result<CamoufoxLaunchResult, String> {
|
||||
let launcher = CamoufoxNodecarLauncher::new(app_handle.clone());
|
||||
impl CamoufoxNodecarLauncher {
|
||||
pub async fn launch_camoufox_profile_nodecar(
|
||||
&self,
|
||||
app_handle: AppHandle,
|
||||
profile: BrowserProfile,
|
||||
config: CamoufoxConfig,
|
||||
url: Option<String>,
|
||||
) -> Result<CamoufoxLaunchResult, String> {
|
||||
// Get profile path
|
||||
let browser_runner = crate::browser_runner::BrowserRunner::instance();
|
||||
let profiles_dir = browser_runner.get_profiles_dir();
|
||||
let profile_path = profile.get_profile_data_path(&profiles_dir);
|
||||
let profile_path_str = profile_path.to_string_lossy();
|
||||
|
||||
// Get profile path
|
||||
let browser_runner = crate::browser_runner::BrowserRunner::new();
|
||||
let profiles_dir = browser_runner.get_profiles_dir();
|
||||
let profile_path = profile.get_profile_data_path(&profiles_dir);
|
||||
let profile_path_str = profile_path.to_string_lossy();
|
||||
// Check if there's already a running instance for this profile
|
||||
if let Ok(Some(existing)) = self.find_camoufox_by_profile(&profile_path_str).await {
|
||||
// If there's an existing instance, stop it first to avoid conflicts
|
||||
let _ = self.stop_camoufox(&app_handle, &existing.id).await;
|
||||
}
|
||||
|
||||
// Check if there's already a running instance for this profile
|
||||
if let Ok(Some(existing)) = launcher.find_camoufox_by_profile(&profile_path_str).await {
|
||||
// If there's an existing instance, stop it first to avoid conflicts
|
||||
let _ = launcher.stop_camoufox(&app_handle, &existing.id).await;
|
||||
// Clean up any dead instances before launching
|
||||
let _ = self.cleanup_dead_instances().await;
|
||||
|
||||
self
|
||||
.launch_camoufox(&app_handle, &profile_path_str, &config, url.as_deref())
|
||||
.await
|
||||
.map_err(|e| format!("Failed to launch Camoufox via nodecar: {e}"))
|
||||
}
|
||||
|
||||
// Clean up any dead instances before launching
|
||||
let _ = launcher.cleanup_dead_instances().await;
|
||||
|
||||
launcher
|
||||
.launch_camoufox(&app_handle, &profile_path_str, &config, url.as_deref())
|
||||
.await
|
||||
.map_err(|e| format!("Failed to launch Camoufox via nodecar: {e}"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -686,3 +675,8 @@ mod tests {
|
||||
assert_eq!(default_config.headless, None);
|
||||
}
|
||||
}
|
||||
|
||||
// Global singleton instance
|
||||
lazy_static::lazy_static! {
|
||||
static ref CAMOUFOX_NODECAR_LAUNCHER: CamoufoxNodecarLauncher = CamoufoxNodecarLauncher::new();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,77 @@
|
||||
use tauri::command;
|
||||
|
||||
pub struct DefaultBrowser;
|
||||
|
||||
impl DefaultBrowser {
|
||||
fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
pub fn instance() -> &'static DefaultBrowser {
|
||||
&DEFAULT_BROWSER
|
||||
}
|
||||
|
||||
pub async fn is_default_browser(&self) -> Result<bool, String> {
|
||||
#[cfg(target_os = "macos")]
|
||||
return macos::is_default_browser();
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
return windows::is_default_browser();
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
return linux::is_default_browser();
|
||||
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
|
||||
Err("Unsupported platform".to_string())
|
||||
}
|
||||
|
||||
pub async fn set_as_default_browser(&self) -> Result<(), String> {
|
||||
#[cfg(target_os = "macos")]
|
||||
return macos::set_as_default_browser();
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
return windows::set_as_default_browser();
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
return linux::set_as_default_browser();
|
||||
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
|
||||
Err("Unsupported platform".to_string())
|
||||
}
|
||||
|
||||
pub async fn open_url_with_profile(
|
||||
&self,
|
||||
app_handle: tauri::AppHandle,
|
||||
profile_name: String,
|
||||
url: String,
|
||||
) -> Result<(), String> {
|
||||
let runner = crate::browser_runner::BrowserRunner::instance();
|
||||
|
||||
// Get the profile by name
|
||||
let profiles = runner
|
||||
.list_profiles()
|
||||
.map_err(|e| format!("Failed to list profiles: {e}"))?;
|
||||
let profile = profiles
|
||||
.into_iter()
|
||||
.find(|p| p.name == profile_name)
|
||||
.ok_or_else(|| format!("Profile '{profile_name}' not found"))?;
|
||||
|
||||
println!("Opening URL '{url}' with profile '{profile_name}'");
|
||||
|
||||
// Use launch_or_open_url which handles both launching new instances and opening in existing ones
|
||||
runner
|
||||
.launch_or_open_url(app_handle, &profile, Some(url.clone()), None)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
println!("Failed to open URL with profile '{profile_name}': {e}");
|
||||
format!("Failed to open URL with profile: {e}")
|
||||
})?;
|
||||
|
||||
println!("Successfully opened URL '{url}' with profile '{profile_name}'");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos {
|
||||
use core_foundation::base::OSStatus;
|
||||
@@ -482,34 +554,21 @@ mod linux {
|
||||
}
|
||||
}
|
||||
|
||||
// Global singleton instance
|
||||
lazy_static::lazy_static! {
|
||||
static ref DEFAULT_BROWSER: DefaultBrowser = DefaultBrowser::new();
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub async fn is_default_browser() -> Result<bool, String> {
|
||||
#[cfg(target_os = "macos")]
|
||||
return macos::is_default_browser();
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
return windows::is_default_browser();
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
return linux::is_default_browser();
|
||||
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
|
||||
Err("Unsupported platform".to_string())
|
||||
let default_browser = DefaultBrowser::instance();
|
||||
default_browser.is_default_browser().await
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub async fn set_as_default_browser() -> Result<(), String> {
|
||||
#[cfg(target_os = "macos")]
|
||||
return macos::set_as_default_browser();
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
return windows::set_as_default_browser();
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
return linux::set_as_default_browser();
|
||||
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
|
||||
Err("Unsupported platform".to_string())
|
||||
let default_browser = DefaultBrowser::instance();
|
||||
default_browser.set_as_default_browser().await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -518,30 +577,8 @@ pub async fn open_url_with_profile(
|
||||
profile_name: String,
|
||||
url: String,
|
||||
) -> Result<(), String> {
|
||||
use crate::browser_runner::BrowserRunner;
|
||||
|
||||
let runner = BrowserRunner::new();
|
||||
|
||||
// Get the profile by name
|
||||
let profiles = runner
|
||||
.list_profiles()
|
||||
.map_err(|e| format!("Failed to list profiles: {e}"))?;
|
||||
let profile = profiles
|
||||
.into_iter()
|
||||
.find(|p| p.name == profile_name)
|
||||
.ok_or_else(|| format!("Profile '{profile_name}' not found"))?;
|
||||
|
||||
println!("Opening URL '{url}' with profile '{profile_name}'");
|
||||
|
||||
// Use launch_or_open_url which handles both launching new instances and opening in existing ones
|
||||
runner
|
||||
.launch_or_open_url(app_handle, &profile, Some(url.clone()), None)
|
||||
let default_browser = DefaultBrowser::instance();
|
||||
default_browser
|
||||
.open_url_with_profile(app_handle, profile_name, url)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
println!("Failed to open URL with profile '{profile_name}': {e}");
|
||||
format!("Failed to open URL with profile: {e}")
|
||||
})?;
|
||||
|
||||
println!("Successfully opened URL '{url}' with profile '{profile_name}'");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -27,13 +27,17 @@ pub struct Downloader {
|
||||
}
|
||||
|
||||
impl Downloader {
|
||||
pub fn new() -> Self {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
client: Client::new(),
|
||||
api_client: ApiClient::instance(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn instance() -> &'static Downloader {
|
||||
&DOWNLOADER
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn new_with_api_client(_api_client: ApiClient) -> Self {
|
||||
Self {
|
||||
@@ -716,3 +720,8 @@ mod tests {
|
||||
assert_eq!(downloaded_content.len(), test_content.len());
|
||||
}
|
||||
}
|
||||
|
||||
// Global singleton instance
|
||||
lazy_static::lazy_static! {
|
||||
static ref DOWNLOADER: Downloader = Downloader::new();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Mutex;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct DownloadedBrowserInfo {
|
||||
@@ -12,25 +13,38 @@ pub struct DownloadedBrowserInfo {
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||
pub struct DownloadedBrowsersRegistry {
|
||||
struct RegistryData {
|
||||
pub browsers: HashMap<String, HashMap<String, DownloadedBrowserInfo>>, // browser -> version -> info
|
||||
}
|
||||
|
||||
pub struct DownloadedBrowsersRegistry {
|
||||
data: Mutex<RegistryData>,
|
||||
}
|
||||
|
||||
impl DownloadedBrowsersRegistry {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
data: Mutex::new(RegistryData::default()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load() -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
||||
pub fn instance() -> &'static DownloadedBrowsersRegistry {
|
||||
&DOWNLOADED_BROWSERS_REGISTRY
|
||||
}
|
||||
|
||||
pub fn load(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let registry_path = Self::get_registry_path()?;
|
||||
|
||||
if !registry_path.exists() {
|
||||
return Ok(Self::new());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let content = fs::read_to_string(®istry_path)?;
|
||||
let registry: DownloadedBrowsersRegistry = serde_json::from_str(&content)?;
|
||||
Ok(registry)
|
||||
let registry_data: RegistryData = serde_json::from_str(&content)?;
|
||||
|
||||
let mut data = self.data.lock().unwrap();
|
||||
*data = registry_data;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn save(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
@@ -41,7 +55,8 @@ impl DownloadedBrowsersRegistry {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
let content = serde_json::to_string_pretty(self)?;
|
||||
let data = self.data.lock().unwrap();
|
||||
let content = serde_json::to_string_pretty(&*data)?;
|
||||
fs::write(®istry_path, content)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -59,20 +74,23 @@ impl DownloadedBrowsersRegistry {
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
pub fn add_browser(&mut self, info: DownloadedBrowserInfo) {
|
||||
self
|
||||
pub fn add_browser(&self, info: DownloadedBrowserInfo) {
|
||||
let mut data = self.data.lock().unwrap();
|
||||
data
|
||||
.browsers
|
||||
.entry(info.browser.clone())
|
||||
.or_default()
|
||||
.insert(info.version.clone(), info);
|
||||
}
|
||||
|
||||
pub fn remove_browser(&mut self, browser: &str, version: &str) -> Option<DownloadedBrowserInfo> {
|
||||
self.browsers.get_mut(browser)?.remove(version)
|
||||
pub fn remove_browser(&self, browser: &str, version: &str) -> Option<DownloadedBrowserInfo> {
|
||||
let mut data = self.data.lock().unwrap();
|
||||
data.browsers.get_mut(browser)?.remove(version)
|
||||
}
|
||||
|
||||
pub fn is_browser_downloaded(&self, browser: &str, version: &str) -> bool {
|
||||
self
|
||||
let data = self.data.lock().unwrap();
|
||||
data
|
||||
.browsers
|
||||
.get(browser)
|
||||
.and_then(|versions| versions.get(version))
|
||||
@@ -80,14 +98,15 @@ impl DownloadedBrowsersRegistry {
|
||||
}
|
||||
|
||||
pub fn get_downloaded_versions(&self, browser: &str) -> Vec<String> {
|
||||
self
|
||||
let data = self.data.lock().unwrap();
|
||||
data
|
||||
.browsers
|
||||
.get(browser)
|
||||
.map(|versions| versions.keys().cloned().collect())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn mark_download_started(&mut self, browser: &str, version: &str, file_path: PathBuf) {
|
||||
pub fn mark_download_started(&self, browser: &str, version: &str, file_path: PathBuf) {
|
||||
let info = DownloadedBrowserInfo {
|
||||
browser: browser.to_string(),
|
||||
version: version.to_string(),
|
||||
@@ -96,8 +115,9 @@ impl DownloadedBrowsersRegistry {
|
||||
self.add_browser(info);
|
||||
}
|
||||
|
||||
pub fn mark_download_completed(&mut self, browser: &str, version: &str) -> Result<(), String> {
|
||||
if self
|
||||
pub fn mark_download_completed(&self, browser: &str, version: &str) -> Result<(), String> {
|
||||
let data = self.data.lock().unwrap();
|
||||
if data
|
||||
.browsers
|
||||
.get(browser)
|
||||
.and_then(|versions| versions.get(version))
|
||||
@@ -110,7 +130,7 @@ impl DownloadedBrowsersRegistry {
|
||||
}
|
||||
|
||||
pub fn cleanup_failed_download(
|
||||
&mut self,
|
||||
&self,
|
||||
browser: &str,
|
||||
version: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
@@ -145,7 +165,7 @@ impl DownloadedBrowsersRegistry {
|
||||
|
||||
/// Find and remove unused browser binaries that are not referenced by any active profiles
|
||||
pub fn cleanup_unused_binaries(
|
||||
&mut self,
|
||||
&self,
|
||||
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>> {
|
||||
@@ -157,25 +177,28 @@ impl DownloadedBrowsersRegistry {
|
||||
|
||||
// Collect all downloaded browsers that are not in active profiles
|
||||
let mut to_remove = Vec::new();
|
||||
for (browser, versions) in &self.browsers {
|
||||
for version in versions.keys() {
|
||||
let browser_version = (browser.clone(), version.clone());
|
||||
{
|
||||
let data = self.data.lock().unwrap();
|
||||
for (browser, versions) in &data.browsers {
|
||||
for version in versions.keys() {
|
||||
let browser_version = (browser.clone(), version.clone());
|
||||
|
||||
// Don't remove if it's used by any active profile
|
||||
if active_set.contains(&browser_version) {
|
||||
println!("Keeping: {browser} {version} (in use by profile)");
|
||||
continue;
|
||||
// Don't remove if it's used by any active profile
|
||||
if active_set.contains(&browser_version) {
|
||||
println!("Keeping: {browser} {version} (in use by profile)");
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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)");
|
||||
}
|
||||
|
||||
// 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)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,22 +234,25 @@ impl DownloadedBrowsersRegistry {
|
||||
|
||||
/// Verify that all registered browsers actually exist on disk and clean up stale entries
|
||||
pub fn verify_and_cleanup_stale_entries(
|
||||
&mut self,
|
||||
&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();
|
||||
let browsers_to_check: Vec<(String, String)> = {
|
||||
let data = self.data.lock().unwrap();
|
||||
data
|
||||
.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) {
|
||||
@@ -263,7 +289,7 @@ impl DownloadedBrowsersRegistry {
|
||||
/// Scan the binaries directory and sync with registry
|
||||
/// This ensures the registry reflects what's actually on disk
|
||||
pub fn sync_with_binaries_directory(
|
||||
&mut self,
|
||||
&self,
|
||||
binaries_dir: &std::path::Path,
|
||||
) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let mut changes = Vec::new();
|
||||
@@ -331,7 +357,7 @@ impl DownloadedBrowsersRegistry {
|
||||
|
||||
/// Comprehensive cleanup that removes unused binaries and syncs registry
|
||||
pub fn comprehensive_cleanup(
|
||||
&mut self,
|
||||
&self,
|
||||
binaries_dir: &std::path::Path,
|
||||
active_profiles: &[(String, String)],
|
||||
running_profiles: &[(String, String)],
|
||||
@@ -359,18 +385,21 @@ impl DownloadedBrowsersRegistry {
|
||||
|
||||
/// Simplified version of verify_and_cleanup_stale_entries that doesn't need BrowserRunner
|
||||
pub fn verify_and_cleanup_stale_entries_simple(
|
||||
&mut self,
|
||||
&self,
|
||||
binaries_dir: &std::path::Path,
|
||||
) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let mut cleaned_up = Vec::new();
|
||||
let mut browsers_to_remove = Vec::new();
|
||||
|
||||
for (browser_str, versions) in &self.browsers {
|
||||
for version in versions.keys() {
|
||||
// Check if the browser directory actually exists
|
||||
let browser_dir = binaries_dir.join(browser_str).join(version);
|
||||
if !browser_dir.exists() {
|
||||
browsers_to_remove.push((browser_str.clone(), version.clone()));
|
||||
{
|
||||
let data = self.data.lock().unwrap();
|
||||
for (browser_str, versions) in &data.browsers {
|
||||
for version in versions.keys() {
|
||||
// Check if the browser directory actually exists
|
||||
let browser_dir = binaries_dir.join(browser_str).join(version);
|
||||
if !browser_dir.exists() {
|
||||
browsers_to_remove.push((browser_str.clone(), version.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -388,6 +417,17 @@ impl DownloadedBrowsersRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
// Global singleton instance
|
||||
lazy_static::lazy_static! {
|
||||
static ref DOWNLOADED_BROWSERS_REGISTRY: DownloadedBrowsersRegistry = {
|
||||
let registry = DownloadedBrowsersRegistry::new();
|
||||
if let Err(e) = registry.load() {
|
||||
eprintln!("Warning: Failed to load downloaded browsers registry: {e}");
|
||||
}
|
||||
registry
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -395,12 +435,13 @@ mod tests {
|
||||
#[test]
|
||||
fn test_registry_creation() {
|
||||
let registry = DownloadedBrowsersRegistry::new();
|
||||
assert!(registry.browsers.is_empty());
|
||||
let data = registry.data.lock().unwrap();
|
||||
assert!(data.browsers.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_and_get_browser() {
|
||||
let mut registry = DownloadedBrowsersRegistry::new();
|
||||
let registry = DownloadedBrowsersRegistry::new();
|
||||
let info = DownloadedBrowserInfo {
|
||||
browser: "firefox".to_string(),
|
||||
version: "139.0".to_string(),
|
||||
@@ -416,7 +457,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_get_downloaded_versions() {
|
||||
let mut registry = DownloadedBrowsersRegistry::new();
|
||||
let registry = DownloadedBrowsersRegistry::new();
|
||||
|
||||
let info1 = DownloadedBrowserInfo {
|
||||
browser: "firefox".to_string(),
|
||||
@@ -449,7 +490,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_mark_download_lifecycle() {
|
||||
let mut registry = DownloadedBrowsersRegistry::new();
|
||||
let registry = DownloadedBrowsersRegistry::new();
|
||||
|
||||
// Mark download started
|
||||
registry.mark_download_started("firefox", "139.0", PathBuf::from("/test/path"));
|
||||
@@ -468,7 +509,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_remove_browser() {
|
||||
let mut registry = DownloadedBrowsersRegistry::new();
|
||||
let registry = DownloadedBrowsersRegistry::new();
|
||||
let info = DownloadedBrowserInfo {
|
||||
browser: "firefox".to_string(),
|
||||
version: "139.0".to_string(),
|
||||
@@ -485,7 +526,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_twilight_download() {
|
||||
let mut registry = DownloadedBrowsersRegistry::new();
|
||||
let registry = DownloadedBrowsersRegistry::new();
|
||||
|
||||
// Mark twilight download started
|
||||
registry.mark_download_started("zen", "twilight", PathBuf::from("/test/zen-twilight"));
|
||||
|
||||
+22
-19
@@ -9,10 +9,14 @@ use crate::download::DownloadProgress;
|
||||
pub struct Extractor;
|
||||
|
||||
impl Extractor {
|
||||
pub fn new() -> Self {
|
||||
fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
pub fn instance() -> &'static Extractor {
|
||||
&EXTRACTOR
|
||||
}
|
||||
|
||||
pub async fn extract_browser(
|
||||
&self,
|
||||
app_handle: &tauri::AppHandle,
|
||||
@@ -1329,15 +1333,9 @@ mod tests {
|
||||
use std::io::Write;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
fn test_extractor_creation() {
|
||||
let _ = Extractor::new();
|
||||
// Just verify we can create an extractor instance
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unsupported_archive_format() {
|
||||
let extractor = Extractor::new();
|
||||
let extractor = Extractor::instance();
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let fake_archive = temp_dir.path().join("test.rar");
|
||||
|
||||
@@ -1353,7 +1351,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_format_detection_zip() {
|
||||
let extractor = Extractor::new();
|
||||
let extractor = Extractor::instance();
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let zip_path = temp_dir.path().join("test.zip");
|
||||
|
||||
@@ -1369,7 +1367,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_format_detection_dmg_by_extension() {
|
||||
let extractor = Extractor::new();
|
||||
let extractor = Extractor::instance();
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let dmg_path = temp_dir.path().join("test.dmg");
|
||||
|
||||
@@ -1384,7 +1382,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_format_detection_exe() {
|
||||
let extractor = Extractor::new();
|
||||
let extractor = Extractor::instance();
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let exe_path = temp_dir.path().join("test.exe");
|
||||
|
||||
@@ -1400,7 +1398,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_format_detection_tar_gz() {
|
||||
let extractor = Extractor::new();
|
||||
let extractor = Extractor::instance();
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let tar_gz_path = temp_dir.path().join("test.tar.gz");
|
||||
|
||||
@@ -1443,7 +1441,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
#[cfg(target_os = "macos")]
|
||||
async fn test_find_app_at_root_level() {
|
||||
let extractor = Extractor::new();
|
||||
let extractor = Extractor::instance();
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
|
||||
// Create a Firefox.app directory
|
||||
@@ -1471,7 +1469,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
#[cfg(target_os = "macos")]
|
||||
async fn test_find_app_in_subdirectory() {
|
||||
let extractor = Extractor::new();
|
||||
let extractor = Extractor::instance();
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
|
||||
// Create a nested structure like some browsers have
|
||||
@@ -1503,7 +1501,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
#[cfg(target_os = "macos")]
|
||||
async fn test_find_app_multiple_levels_deep() {
|
||||
let extractor = Extractor::new();
|
||||
let extractor = Extractor::instance();
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
|
||||
// Create a deeply nested structure
|
||||
@@ -1536,7 +1534,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
#[cfg(target_os = "macos")]
|
||||
async fn test_find_app_no_app_found() {
|
||||
let extractor = Extractor::new();
|
||||
let extractor = Extractor::instance();
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
|
||||
// Create some files and directories that are NOT .app bundles
|
||||
@@ -1559,7 +1557,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
#[cfg(target_os = "macos")]
|
||||
async fn test_find_app_recursive_depth_limit() {
|
||||
let extractor = Extractor::new();
|
||||
let extractor = Extractor::instance();
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
|
||||
// Create a very deep nested structure (deeper than our limit of 4)
|
||||
@@ -1581,7 +1579,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
#[cfg(target_os = "macos")]
|
||||
async fn test_find_macos_app_and_move_from_subdir() {
|
||||
let extractor = Extractor::new();
|
||||
let extractor = Extractor::instance();
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
|
||||
// Create a nested structure where the app is in a subdirectory
|
||||
@@ -1619,7 +1617,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
#[cfg(target_os = "macos")]
|
||||
async fn test_multiple_apps_found_returns_first() {
|
||||
let extractor = Extractor::new();
|
||||
let extractor = Extractor::instance();
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
|
||||
// Create multiple .app directories
|
||||
@@ -1684,3 +1682,8 @@ mod tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Global singleton instance
|
||||
lazy_static::lazy_static! {
|
||||
static ref EXTRACTOR: Extractor = Extractor::new();
|
||||
}
|
||||
|
||||
@@ -21,12 +21,16 @@ pub struct GeoIPDownloader {
|
||||
}
|
||||
|
||||
impl GeoIPDownloader {
|
||||
pub fn new() -> Self {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
client: Client::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn instance() -> &'static GeoIPDownloader {
|
||||
&GEOIP_DOWNLOADER
|
||||
}
|
||||
|
||||
/// Create a new downloader with custom client (for testing)
|
||||
#[cfg(test)]
|
||||
pub fn new_with_client(client: Client) -> Self {
|
||||
@@ -297,3 +301,8 @@ mod tests {
|
||||
println!("GeoIP database available: {is_available}");
|
||||
}
|
||||
}
|
||||
|
||||
// Global singleton instance
|
||||
lazy_static::lazy_static! {
|
||||
static ref GEOIP_DOWNLOADER: GeoIPDownloader = GeoIPDownloader::new();
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ pub async fn get_profile_groups() -> Result<Vec<ProfileGroup>, String> {
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_groups_with_profile_counts() -> Result<Vec<GroupWithCount>, String> {
|
||||
let profile_manager = crate::profile::ProfileManager::new();
|
||||
let profile_manager = crate::profile::ProfileManager::instance();
|
||||
let profiles = profile_manager
|
||||
.list_profiles()
|
||||
.map_err(|e| format!("Failed to list profiles: {e}"))?;
|
||||
@@ -240,7 +240,7 @@ pub async fn assign_profiles_to_group(
|
||||
profile_names: Vec<String>,
|
||||
group_id: Option<String>,
|
||||
) -> Result<(), String> {
|
||||
let profile_manager = crate::profile::ProfileManager::new();
|
||||
let profile_manager = crate::profile::ProfileManager::instance();
|
||||
profile_manager
|
||||
.assign_profiles_to_group(profile_names, group_id)
|
||||
.map_err(|e| format!("Failed to assign profiles to group: {e}"))
|
||||
@@ -248,7 +248,7 @@ pub async fn assign_profiles_to_group(
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn delete_selected_profiles(profile_names: Vec<String>) -> Result<(), String> {
|
||||
let profile_manager = crate::profile::ProfileManager::new();
|
||||
let profile_manager = crate::profile::ProfileManager::instance();
|
||||
profile_manager
|
||||
.delete_multiple_profiles(profile_names)
|
||||
.map_err(|e| format!("Failed to delete profiles: {e}"))
|
||||
|
||||
@@ -222,7 +222,7 @@ pub fn run() {
|
||||
|
||||
// Migrate profiles to UUID format if needed (async)
|
||||
println!("Checking for profile migration...");
|
||||
let browser_runner = browser_runner::BrowserRunner::new();
|
||||
let browser_runner = browser_runner::BrowserRunner::instance();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
match browser_runner.migrate_profiles_to_uuid().await {
|
||||
Ok(migrated) => {
|
||||
@@ -354,7 +354,7 @@ pub fn run() {
|
||||
loop {
|
||||
interval.tick().await;
|
||||
|
||||
let browser_runner = crate::browser_runner::BrowserRunner::new();
|
||||
let browser_runner = crate::browser_runner::BrowserRunner::instance();
|
||||
if let Err(e) = browser_runner.cleanup_unused_binaries_internal() {
|
||||
eprintln!("Periodic cleanup failed: {e}");
|
||||
} else {
|
||||
@@ -390,9 +390,9 @@ pub fn run() {
|
||||
});
|
||||
|
||||
// Start Camoufox cleanup task
|
||||
let app_handle_cleanup = app.handle().clone();
|
||||
let _app_handle_cleanup = app.handle().clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let launcher = crate::camoufox::CamoufoxNodecarLauncher::new(app_handle_cleanup);
|
||||
let launcher = crate::camoufox::CamoufoxNodecarLauncher::instance();
|
||||
let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(5));
|
||||
|
||||
loop {
|
||||
|
||||
@@ -13,12 +13,16 @@ pub struct ProfileManager {
|
||||
}
|
||||
|
||||
impl ProfileManager {
|
||||
pub fn new() -> Self {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
base_dirs: BaseDirs::new().expect("Failed to get base directories"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn instance() -> &'static ProfileManager {
|
||||
&PROFILE_MANAGER
|
||||
}
|
||||
|
||||
pub fn get_profiles_dir(&self) -> PathBuf {
|
||||
let mut path = self.base_dirs.data_local_dir().to_path_buf();
|
||||
path.push(if cfg!(debug_assertions) {
|
||||
@@ -673,7 +677,7 @@ impl ProfileManager {
|
||||
) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
|
||||
use crate::camoufox::CamoufoxNodecarLauncher;
|
||||
|
||||
let launcher = CamoufoxNodecarLauncher::new(app_handle.clone());
|
||||
let launcher = CamoufoxNodecarLauncher::instance();
|
||||
let profiles_dir = self.get_profiles_dir();
|
||||
let profile_data_path = profile.get_profile_data_path(&profiles_dir);
|
||||
let profile_path_str = profile_data_path.to_string_lossy();
|
||||
@@ -1140,13 +1144,13 @@ mod tests {
|
||||
use crate::browser::ProxySettings;
|
||||
use tempfile::TempDir;
|
||||
|
||||
fn create_test_profile_manager() -> (ProfileManager, TempDir) {
|
||||
fn create_test_profile_manager() -> (&'static ProfileManager, TempDir) {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
|
||||
// Mock the base directories by setting environment variables
|
||||
std::env::set_var("HOME", temp_dir.path());
|
||||
|
||||
let profile_manager = ProfileManager::new();
|
||||
let profile_manager = ProfileManager::instance();
|
||||
(profile_manager, temp_dir)
|
||||
}
|
||||
|
||||
@@ -1184,60 +1188,75 @@ mod tests {
|
||||
fn test_save_and_load_profile() {
|
||||
let (manager, _temp_dir) = create_test_profile_manager();
|
||||
|
||||
let unique_name = format!("Test Save Load {}", uuid::Uuid::new_v4());
|
||||
let profile = manager
|
||||
.create_profile("Test Save Load", "firefox", "139.0", "stable", None, None)
|
||||
.create_profile(&unique_name, "firefox", "139.0", "stable", None, None)
|
||||
.unwrap();
|
||||
|
||||
// Save the profile
|
||||
manager.save_profile(&profile).unwrap();
|
||||
|
||||
// Load profiles and verify
|
||||
// Load profiles and verify our profile exists
|
||||
let profiles = manager.list_profiles().unwrap();
|
||||
assert_eq!(profiles.len(), 1);
|
||||
assert_eq!(profiles[0].name, "Test Save Load");
|
||||
assert_eq!(profiles[0].browser, "firefox");
|
||||
assert_eq!(profiles[0].version, "139.0");
|
||||
let our_profile = profiles.iter().find(|p| p.name == unique_name).unwrap();
|
||||
assert_eq!(our_profile.name, unique_name);
|
||||
assert_eq!(our_profile.browser, "firefox");
|
||||
assert_eq!(our_profile.version, "139.0");
|
||||
|
||||
// Clean up
|
||||
let _ = manager.delete_profile(&unique_name);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rename_profile() {
|
||||
let (manager, _temp_dir) = create_test_profile_manager();
|
||||
|
||||
let original_name = format!("Original Name {}", uuid::Uuid::new_v4());
|
||||
let new_name = format!("New Name {}", uuid::Uuid::new_v4());
|
||||
|
||||
// Create profile
|
||||
let _ = manager
|
||||
.create_profile("Original Name", "firefox", "139.0", "stable", None, None)
|
||||
.create_profile(&original_name, "firefox", "139.0", "stable", None, None)
|
||||
.unwrap();
|
||||
|
||||
// Rename profile
|
||||
let renamed_profile = manager.rename_profile("Original Name", "New Name").unwrap();
|
||||
let renamed_profile = manager.rename_profile(&original_name, &new_name).unwrap();
|
||||
|
||||
assert_eq!(renamed_profile.name, "New Name");
|
||||
assert_eq!(renamed_profile.name, new_name);
|
||||
|
||||
// Verify old profile is gone and new one exists
|
||||
let profiles = manager.list_profiles().unwrap();
|
||||
assert_eq!(profiles.len(), 1);
|
||||
assert_eq!(profiles[0].name, "New Name");
|
||||
assert!(profiles.iter().any(|p| p.name == new_name));
|
||||
assert!(!profiles.iter().any(|p| p.name == original_name));
|
||||
|
||||
// Clean up
|
||||
let _ = manager.delete_profile(&new_name);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_profile() {
|
||||
let (manager, _temp_dir) = create_test_profile_manager();
|
||||
|
||||
let unique_name = format!("To Delete {}", uuid::Uuid::new_v4());
|
||||
|
||||
// Create profile
|
||||
let _ = manager
|
||||
.create_profile("To Delete", "firefox", "139.0", "stable", None, None)
|
||||
.create_profile(&unique_name, "firefox", "139.0", "stable", None, None)
|
||||
.unwrap();
|
||||
|
||||
// Verify profile exists
|
||||
let profiles = manager.list_profiles().unwrap();
|
||||
assert_eq!(profiles.len(), 1);
|
||||
let profiles_before = manager.list_profiles().unwrap();
|
||||
assert!(profiles_before.iter().any(|p| p.name == unique_name));
|
||||
|
||||
// Delete profile
|
||||
manager.delete_profile("To Delete").unwrap();
|
||||
let delete_result = manager.delete_profile(&unique_name);
|
||||
if let Err(e) = &delete_result {
|
||||
println!("Delete profile error (may be expected in tests): {e}");
|
||||
}
|
||||
|
||||
// Verify profile is gone
|
||||
let profiles = manager.list_profiles().unwrap();
|
||||
assert_eq!(profiles.len(), 0);
|
||||
let profiles_after = manager.list_profiles().unwrap();
|
||||
assert!(!profiles_after.iter().any(|p| p.name == unique_name));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1273,25 +1292,36 @@ mod tests {
|
||||
fn test_multiple_profiles() {
|
||||
let (manager, _temp_dir) = create_test_profile_manager();
|
||||
|
||||
let profile1_name = format!("Profile 1 {}", uuid::Uuid::new_v4());
|
||||
let profile2_name = format!("Profile 2 {}", uuid::Uuid::new_v4());
|
||||
let profile3_name = format!("Profile 3 {}", uuid::Uuid::new_v4());
|
||||
|
||||
// Create multiple profiles
|
||||
let _ = manager
|
||||
.create_profile("Profile 1", "firefox", "139.0", "stable", None, None)
|
||||
.create_profile(&profile1_name, "firefox", "139.0", "stable", None, None)
|
||||
.unwrap();
|
||||
let _ = manager
|
||||
.create_profile("Profile 2", "chromium", "1465660", "stable", None, None)
|
||||
.create_profile(&profile2_name, "chromium", "1465660", "stable", None, None)
|
||||
.unwrap();
|
||||
let _ = manager
|
||||
.create_profile("Profile 3", "brave", "v1.81.9", "stable", None, None)
|
||||
.create_profile(&profile3_name, "brave", "v1.81.9", "stable", None, None)
|
||||
.unwrap();
|
||||
|
||||
// List profiles
|
||||
// List profiles and verify our profiles exist
|
||||
let profiles = manager.list_profiles().unwrap();
|
||||
assert_eq!(profiles.len(), 3);
|
||||
|
||||
let profile_names: Vec<&str> = profiles.iter().map(|p| p.name.as_str()).collect();
|
||||
assert!(profile_names.contains(&"Profile 1"));
|
||||
assert!(profile_names.contains(&"Profile 2"));
|
||||
assert!(profile_names.contains(&"Profile 3"));
|
||||
|
||||
println!("Created profiles: {profile1_name}, {profile2_name}, {profile3_name}");
|
||||
println!("Found profiles: {profile_names:?}");
|
||||
|
||||
assert!(profiles.iter().any(|p| p.name == profile1_name));
|
||||
assert!(profiles.iter().any(|p| p.name == profile2_name));
|
||||
assert!(profiles.iter().any(|p| p.name == profile3_name));
|
||||
|
||||
// Clean up
|
||||
let _ = manager.delete_profile(&profile1_name);
|
||||
let _ = manager.delete_profile(&profile2_name);
|
||||
let _ = manager.delete_profile(&profile3_name);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1299,17 +1329,24 @@ mod tests {
|
||||
let (manager, _temp_dir) = create_test_profile_manager();
|
||||
|
||||
// Test that we can't rename to an existing profile name
|
||||
let profile1_name = format!("Profile 1 {}", uuid::Uuid::new_v4());
|
||||
let profile2_name = format!("Profile 2 {}", uuid::Uuid::new_v4());
|
||||
|
||||
let _ = manager
|
||||
.create_profile("Profile 1", "firefox", "139.0", "stable", None, None)
|
||||
.create_profile(&profile1_name, "firefox", "139.0", "stable", None, None)
|
||||
.unwrap();
|
||||
let _ = manager
|
||||
.create_profile("Profile 2", "firefox", "139.0", "stable", None, None)
|
||||
.create_profile(&profile2_name, "firefox", "139.0", "stable", None, None)
|
||||
.unwrap();
|
||||
|
||||
// Try to rename profile2 to profile1's name (should fail)
|
||||
let result = manager.rename_profile("Profile 2", "Profile 1");
|
||||
let result = manager.rename_profile(&profile2_name, &profile1_name);
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().to_string().contains("already exists"));
|
||||
|
||||
// Clean up
|
||||
let _ = manager.delete_profile(&profile1_name);
|
||||
let _ = manager.delete_profile(&profile2_name);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1376,3 +1413,8 @@ mod tests {
|
||||
assert!(user_js_content_proxy.contains("app.update.auto"));
|
||||
}
|
||||
}
|
||||
|
||||
// Global singleton instance
|
||||
lazy_static::lazy_static! {
|
||||
static ref PROFILE_MANAGER: ProfileManager = ProfileManager::new();
|
||||
}
|
||||
|
||||
@@ -17,17 +17,19 @@ pub struct DetectedProfile {
|
||||
|
||||
pub struct ProfileImporter {
|
||||
base_dirs: BaseDirs,
|
||||
browser_runner: BrowserRunner,
|
||||
}
|
||||
|
||||
impl ProfileImporter {
|
||||
pub fn new() -> Self {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
base_dirs: BaseDirs::new().expect("Failed to get base directories"),
|
||||
browser_runner: BrowserRunner::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn instance() -> &'static ProfileImporter {
|
||||
&PROFILE_IMPORTER
|
||||
}
|
||||
|
||||
/// Detect existing browser profiles on the system
|
||||
pub fn detect_existing_profiles(
|
||||
&self,
|
||||
@@ -656,7 +658,7 @@ impl ProfileImporter {
|
||||
.map_err(|_| format!("Invalid browser type: {browser_type}"))?;
|
||||
|
||||
// Check if a profile with this name already exists
|
||||
let existing_profiles = self.browser_runner.list_profiles()?;
|
||||
let existing_profiles = BrowserRunner::instance().list_profiles()?;
|
||||
if existing_profiles
|
||||
.iter()
|
||||
.any(|p| p.name.to_lowercase() == new_profile_name.to_lowercase())
|
||||
@@ -666,7 +668,7 @@ impl ProfileImporter {
|
||||
|
||||
// Generate UUID for new profile and create the directory structure
|
||||
let profile_id = uuid::Uuid::new_v4();
|
||||
let profiles_dir = self.browser_runner.get_profiles_dir();
|
||||
let profiles_dir = BrowserRunner::instance().get_profiles_dir();
|
||||
let new_profile_uuid_dir = profiles_dir.join(profile_id.to_string());
|
||||
let new_profile_data_dir = new_profile_uuid_dir.join("profile");
|
||||
|
||||
@@ -694,7 +696,7 @@ impl ProfileImporter {
|
||||
};
|
||||
|
||||
// Save the profile metadata
|
||||
self.browser_runner.save_profile(&profile)?;
|
||||
BrowserRunner::instance().save_profile(&profile)?;
|
||||
|
||||
println!(
|
||||
"Successfully imported profile '{}' from '{}'",
|
||||
@@ -711,8 +713,7 @@ impl ProfileImporter {
|
||||
browser_type: &str,
|
||||
) -> Result<String, Box<dyn std::error::Error>> {
|
||||
// Check if any version of the browser is downloaded
|
||||
let registry =
|
||||
crate::downloaded_browsers::DownloadedBrowsersRegistry::load().unwrap_or_default();
|
||||
let registry = crate::downloaded_browsers::DownloadedBrowsersRegistry::instance();
|
||||
let downloaded_versions = registry.get_downloaded_versions(browser_type);
|
||||
|
||||
if let Some(version) = downloaded_versions.first() {
|
||||
@@ -755,7 +756,7 @@ impl ProfileImporter {
|
||||
// Tauri commands
|
||||
#[tauri::command]
|
||||
pub async fn detect_existing_profiles() -> Result<Vec<DetectedProfile>, String> {
|
||||
let importer = ProfileImporter::new();
|
||||
let importer = ProfileImporter::instance();
|
||||
importer
|
||||
.detect_existing_profiles()
|
||||
.map_err(|e| format!("Failed to detect existing profiles: {e}"))
|
||||
@@ -767,8 +768,13 @@ pub async fn import_browser_profile(
|
||||
browser_type: String,
|
||||
new_profile_name: String,
|
||||
) -> Result<(), String> {
|
||||
let importer = ProfileImporter::new();
|
||||
let importer = ProfileImporter::instance();
|
||||
importer
|
||||
.import_profile(&source_path, &browser_type, &new_profile_name)
|
||||
.map_err(|e| format!("Failed to import profile: {e}"))
|
||||
}
|
||||
|
||||
// Global singleton instance
|
||||
lazy_static::lazy_static! {
|
||||
static ref PROFILE_IMPORTER: ProfileImporter = ProfileImporter::new();
|
||||
}
|
||||
|
||||
@@ -9,10 +9,14 @@ pub struct SystemTheme {
|
||||
pub struct ThemeDetector;
|
||||
|
||||
impl ThemeDetector {
|
||||
pub fn new() -> Self {
|
||||
fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
pub fn instance() -> &'static ThemeDetector {
|
||||
&THEME_DETECTOR
|
||||
}
|
||||
|
||||
/// Detect the system theme preference
|
||||
pub fn detect_system_theme(&self) -> SystemTheme {
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -514,7 +518,7 @@ mod windows {
|
||||
// Command to expose this functionality to the frontend
|
||||
#[tauri::command]
|
||||
pub fn get_system_theme() -> SystemTheme {
|
||||
let detector = ThemeDetector::new();
|
||||
let detector = ThemeDetector::instance();
|
||||
detector.detect_system_theme()
|
||||
}
|
||||
|
||||
@@ -524,7 +528,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_theme_detector_creation() {
|
||||
let detector = ThemeDetector::new();
|
||||
let detector = ThemeDetector::instance();
|
||||
let theme = detector.detect_system_theme();
|
||||
|
||||
// Should return a valid theme string
|
||||
@@ -537,3 +541,8 @@ mod tests {
|
||||
assert!(matches!(theme.theme.as_str(), "light" | "dark" | "unknown"));
|
||||
}
|
||||
}
|
||||
|
||||
// Global singleton instance
|
||||
lazy_static::lazy_static! {
|
||||
static ref THEME_DETECTOR: ThemeDetector = ThemeDetector::new();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user