mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-04-23 20:36:09 +02:00
refactor: simplify browser runner
This commit is contained in:
Vendored
+5
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"ABORTIFHUNG",
|
||||
"adwaita",
|
||||
"ahooks",
|
||||
"akhilmhdh",
|
||||
@@ -83,6 +84,7 @@
|
||||
"libwebkit",
|
||||
"libxdo",
|
||||
"localtime",
|
||||
"lpdw",
|
||||
"lxml",
|
||||
"lzma",
|
||||
"Matchalk",
|
||||
@@ -114,6 +116,7 @@
|
||||
"peerconnection",
|
||||
"pids",
|
||||
"pixbuf",
|
||||
"pkexec",
|
||||
"pkill",
|
||||
"plasmohq",
|
||||
"platformdirs",
|
||||
@@ -136,6 +139,7 @@
|
||||
"screeninfo",
|
||||
"selectables",
|
||||
"serde",
|
||||
"SETTINGCHANGE",
|
||||
"setuptools",
|
||||
"shadcn",
|
||||
"showcursor",
|
||||
@@ -143,6 +147,7 @@
|
||||
"signon",
|
||||
"signum",
|
||||
"sklearn",
|
||||
"SMTO",
|
||||
"sonner",
|
||||
"splitn",
|
||||
"sspi",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::camoufox_manager::CamoufoxConfig;
|
||||
use crate::group_manager::GROUP_MANAGER;
|
||||
use crate::profile::manager::ProfileManager;
|
||||
use crate::proxy_manager::PROXY_MANAGER;
|
||||
@@ -509,8 +510,7 @@ async fn update_profile(
|
||||
}
|
||||
|
||||
if let Some(camoufox_config) = request.camoufox_config {
|
||||
let config: Result<crate::camoufox::CamoufoxConfig, _> =
|
||||
serde_json::from_value(camoufox_config);
|
||||
let config: Result<CamoufoxConfig, _> = serde_json::from_value(camoufox_config);
|
||||
match config {
|
||||
Ok(config) => {
|
||||
if profile_manager
|
||||
@@ -830,15 +830,12 @@ async fn download_browser_api(
|
||||
State(state): State<ApiServerState>,
|
||||
Json(request): Json<DownloadBrowserRequest>,
|
||||
) -> Result<Json<DownloadBrowserResponse>, StatusCode> {
|
||||
let browser_runner = crate::browser_runner::BrowserRunner::instance();
|
||||
|
||||
match browser_runner
|
||||
.download_browser_impl(
|
||||
state.app_handle.clone(),
|
||||
request.browser.clone(),
|
||||
request.version.clone(),
|
||||
)
|
||||
.await
|
||||
match crate::downloader::download_browser(
|
||||
state.app_handle.clone(),
|
||||
request.browser.clone(),
|
||||
request.version.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => Ok(Json(DownloadBrowserResponse {
|
||||
browser: request.browser,
|
||||
@@ -870,7 +867,6 @@ async fn check_browser_downloaded(
|
||||
Path((browser, version)): Path<(String, String)>,
|
||||
State(_state): State<ApiServerState>,
|
||||
) -> Result<Json<bool>, StatusCode> {
|
||||
let browser_runner = crate::browser_runner::BrowserRunner::instance();
|
||||
let is_downloaded = browser_runner.is_browser_downloaded(&browser, &version);
|
||||
let is_downloaded = crate::downloaded_browsers_registry::is_browser_downloaded(browser, version);
|
||||
Ok(Json(is_downloaded))
|
||||
}
|
||||
|
||||
@@ -120,12 +120,14 @@ pub struct AppUpdateProgress {
|
||||
|
||||
pub struct AppAutoUpdater {
|
||||
client: Client,
|
||||
extractor: &'static crate::extraction::Extractor,
|
||||
}
|
||||
|
||||
impl AppAutoUpdater {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
client: Client::new(),
|
||||
extractor: crate::extraction::Extractor::instance(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -829,8 +831,6 @@ impl AppAutoUpdater {
|
||||
archive_path: &Path,
|
||||
dest_dir: &Path,
|
||||
) -> Result<PathBuf, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let extractor = crate::extraction::Extractor::instance();
|
||||
|
||||
let file_name = archive_path
|
||||
.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
@@ -838,7 +838,7 @@ impl AppAutoUpdater {
|
||||
|
||||
// Handle compound extensions like .tar.gz
|
||||
if file_name.ends_with(".tar.gz") {
|
||||
return extractor.extract_tar_gz(archive_path, dest_dir).await;
|
||||
return self.extractor.extract_tar_gz(archive_path, dest_dir).await;
|
||||
}
|
||||
|
||||
let extension = archive_path
|
||||
@@ -850,7 +850,7 @@ impl AppAutoUpdater {
|
||||
"dmg" => {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
extractor.extract_dmg(archive_path, dest_dir).await
|
||||
self.extractor.extract_dmg(archive_path, dest_dir).await
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
@@ -914,7 +914,7 @@ impl AppAutoUpdater {
|
||||
Err("AppImage installation is only supported on Linux".into())
|
||||
}
|
||||
}
|
||||
"zip" => extractor.extract_zip(archive_path, dest_dir).await,
|
||||
"zip" => self.extractor.extract_zip(archive_path, dest_dir).await,
|
||||
_ => Err(format!("Unsupported archive format: {extension}").into()),
|
||||
}
|
||||
}
|
||||
@@ -1083,8 +1083,8 @@ impl AppAutoUpdater {
|
||||
fs::create_dir_all(&temp_extract_dir)?;
|
||||
|
||||
// Extract ZIP file
|
||||
let extractor = crate::extraction::Extractor::instance();
|
||||
let extracted_path = extractor
|
||||
let extracted_path = self
|
||||
.extractor
|
||||
.extract_zip(installer_path, &temp_extract_dir)
|
||||
.await?;
|
||||
|
||||
@@ -1314,8 +1314,8 @@ impl AppAutoUpdater {
|
||||
fs::create_dir_all(&temp_extract_dir)?;
|
||||
|
||||
// Extract tarball
|
||||
let extractor = crate::extraction::Extractor::instance();
|
||||
let extracted_path = extractor
|
||||
let extracted_path = self
|
||||
.extractor
|
||||
.extract_tar_gz(tarball_path, &temp_extract_dir)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::api_client::is_browser_version_nightly;
|
||||
use crate::browser_version_manager::{BrowserVersionInfo, BrowserVersionManager};
|
||||
use crate::profile::BrowserProfile;
|
||||
use crate::profile::{BrowserProfile, ProfileManager};
|
||||
use crate::settings_manager::SettingsManager;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
@@ -29,15 +28,17 @@ pub struct AutoUpdateState {
|
||||
}
|
||||
|
||||
pub struct AutoUpdater {
|
||||
version_service: &'static BrowserVersionManager,
|
||||
browser_version_manager: &'static BrowserVersionManager,
|
||||
settings_manager: &'static SettingsManager,
|
||||
profile_manager: &'static ProfileManager,
|
||||
}
|
||||
|
||||
impl AutoUpdater {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
version_service: BrowserVersionManager::instance(),
|
||||
browser_version_manager: BrowserVersionManager::instance(),
|
||||
settings_manager: SettingsManager::instance(),
|
||||
profile_manager: ProfileManager::instance(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,8 +54,8 @@ impl AutoUpdater {
|
||||
let mut browser_versions: HashMap<String, Vec<BrowserVersionInfo>> = HashMap::new();
|
||||
|
||||
// Group profiles by browser
|
||||
let profile_manager = crate::profile::ProfileManager::instance();
|
||||
let profiles = profile_manager
|
||||
let profiles = self
|
||||
.profile_manager
|
||||
.list_profiles()
|
||||
.map_err(|e| format!("Failed to list profiles: {e}"))?;
|
||||
let mut browser_profiles: HashMap<String, Vec<BrowserProfile>> = HashMap::new();
|
||||
@@ -62,7 +63,7 @@ impl AutoUpdater {
|
||||
for profile in profiles {
|
||||
// Only check supported browsers
|
||||
if !self
|
||||
.version_service
|
||||
.browser_version_manager
|
||||
.is_browser_supported(&profile.browser)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
@@ -78,14 +79,14 @@ impl AutoUpdater {
|
||||
for (browser, profiles) in browser_profiles {
|
||||
// Get cached versions first, then try to fetch if needed
|
||||
let versions = if let Some(cached) = self
|
||||
.version_service
|
||||
.browser_version_manager
|
||||
.get_cached_browser_versions_detailed(&browser)
|
||||
{
|
||||
cached
|
||||
} else if self.version_service.should_update_cache(&browser) {
|
||||
} else if self.browser_version_manager.should_update_cache(&browser) {
|
||||
// Try to fetch fresh versions
|
||||
match self
|
||||
.version_service
|
||||
.browser_version_manager
|
||||
.fetch_browser_versions_detailed(&browser, false)
|
||||
.await
|
||||
{
|
||||
@@ -156,8 +157,9 @@ impl AutoUpdater {
|
||||
|
||||
// Spawn async task to handle the download and auto-update
|
||||
tokio::spawn(async move {
|
||||
// TODO: update the logic to use the downloaded browsers registry instance instead of the static method
|
||||
// First, check if browser already exists
|
||||
match crate::browser_runner::is_browser_downloaded(
|
||||
match crate::downloaded_browsers_registry::is_browser_downloaded(
|
||||
browser.clone(),
|
||||
new_version.clone(),
|
||||
) {
|
||||
@@ -165,12 +167,13 @@ impl AutoUpdater {
|
||||
println!("Browser {browser} {new_version} already downloaded, proceeding to auto-update profiles");
|
||||
|
||||
// Browser already exists, go straight to profile update
|
||||
match crate::auto_updater::complete_browser_update_with_auto_update(
|
||||
app_handle_clone,
|
||||
browser.clone(),
|
||||
new_version.clone(),
|
||||
)
|
||||
.await
|
||||
match AutoUpdater::instance()
|
||||
.complete_browser_update_with_auto_update(
|
||||
&app_handle_clone,
|
||||
&browser.clone(),
|
||||
&new_version.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(updated_profiles) => {
|
||||
println!(
|
||||
@@ -223,7 +226,8 @@ impl AutoUpdater {
|
||||
available_versions: &[BrowserVersionInfo],
|
||||
) -> Result<Option<UpdateNotification>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let current_version = &profile.version;
|
||||
let is_current_nightly = is_browser_version_nightly(&profile.browser, current_version, None);
|
||||
let is_current_nightly =
|
||||
crate::api_client::is_browser_version_nightly(&profile.browser, current_version, None);
|
||||
|
||||
// Find the best available update
|
||||
let best_update = available_versions
|
||||
@@ -231,7 +235,8 @@ impl AutoUpdater {
|
||||
.filter(|v| {
|
||||
// Only consider versions newer than current
|
||||
self.is_version_newer(&v.version, current_version)
|
||||
&& is_browser_version_nightly(&profile.browser, &v.version, None) == is_current_nightly
|
||||
&& crate::api_client::is_browser_version_nightly(&profile.browser, &v.version, None)
|
||||
== is_current_nightly
|
||||
})
|
||||
.max_by(|a, b| self.compare_versions(&a.version, &b.version));
|
||||
|
||||
@@ -298,8 +303,8 @@ impl AutoUpdater {
|
||||
browser: &str,
|
||||
new_version: &str,
|
||||
) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let profile_manager = crate::profile::ProfileManager::instance();
|
||||
let profiles = profile_manager
|
||||
let profiles = self
|
||||
.profile_manager
|
||||
.list_profiles()
|
||||
.map_err(|e| format!("Failed to list profiles: {e}"))?;
|
||||
|
||||
@@ -316,7 +321,10 @@ impl AutoUpdater {
|
||||
// Check if this is an update (newer version)
|
||||
if self.is_version_newer(new_version, &profile.version) {
|
||||
// Update the profile version
|
||||
match profile_manager.update_profile_version(app_handle, &profile.name, new_version) {
|
||||
match self
|
||||
.profile_manager
|
||||
.update_profile_version(app_handle, &profile.name, new_version)
|
||||
{
|
||||
Ok(_) => {
|
||||
updated_profiles.push(profile.name);
|
||||
}
|
||||
@@ -350,46 +358,9 @@ impl AutoUpdater {
|
||||
state.auto_update_downloads.remove(&download_key);
|
||||
self.save_auto_update_state(&state)?;
|
||||
|
||||
// Always perform cleanup after auto-update - don't fail the update if cleanup fails
|
||||
if let Err(e) = self.cleanup_unused_binaries_internal() {
|
||||
eprintln!("Warning: Failed to cleanup unused binaries after auto-update: {e}");
|
||||
}
|
||||
|
||||
Ok(updated_profiles)
|
||||
}
|
||||
|
||||
/// Internal method to cleanup unused binaries (used by auto-cleanup)
|
||||
fn cleanup_unused_binaries_internal(
|
||||
&self,
|
||||
) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
// Load current profiles
|
||||
let profile_manager = crate::profile::ProfileManager::instance();
|
||||
let profiles = profile_manager
|
||||
.list_profiles()
|
||||
.map_err(|e| format!("Failed to load profiles: {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);
|
||||
|
||||
// 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
|
||||
.cleanup_unused_binaries(&active_versions, &running_versions)
|
||||
.map_err(|e| format!("Failed to cleanup unused binaries: {e}"))?;
|
||||
|
||||
// Save updated registry
|
||||
registry
|
||||
.save()
|
||||
.map_err(|e| format!("Failed to save registry: {e}"))?;
|
||||
|
||||
Ok(cleaned_up)
|
||||
}
|
||||
|
||||
/// Check if browser is disabled due to ongoing update
|
||||
pub fn is_browser_disabled(
|
||||
&self,
|
||||
|
||||
+114
-921
File diff suppressed because it is too large
Load Diff
@@ -117,7 +117,8 @@ impl BrowserVersionManager {
|
||||
/// Get cached browser versions immediately (returns None if no cache exists)
|
||||
pub fn get_cached_browser_versions(&self, browser: &str) -> Option<Vec<String>> {
|
||||
if browser == "brave" {
|
||||
return ApiClient::instance()
|
||||
return self
|
||||
.api_client
|
||||
.get_cached_github_releases("brave")
|
||||
.map(|releases| releases.into_iter().map(|r| r.tag_name).collect());
|
||||
}
|
||||
@@ -134,7 +135,7 @@ impl BrowserVersionManager {
|
||||
browser: &str,
|
||||
) -> Option<Vec<BrowserVersionInfo>> {
|
||||
if browser == "brave" {
|
||||
if let Some(releases) = ApiClient::instance().get_cached_github_releases("brave") {
|
||||
if let Some(releases) = self.api_client.get_cached_github_releases("brave") {
|
||||
let detailed_info: Vec<BrowserVersionInfo> = releases
|
||||
.into_iter()
|
||||
.map(|r| BrowserVersionInfo {
|
||||
@@ -1274,6 +1275,101 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_supported_browsers() -> Result<Vec<String>, String> {
|
||||
let service = BrowserVersionManager::instance();
|
||||
Ok(service.get_supported_browsers())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn is_browser_supported_on_platform(browser_str: String) -> Result<bool, String> {
|
||||
let service = BrowserVersionManager::instance();
|
||||
service
|
||||
.is_browser_supported(&browser_str)
|
||||
.map_err(|e| format!("Failed to check browser support: {e}"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn fetch_browser_versions_cached_first(
|
||||
browser_str: String,
|
||||
) -> Result<Vec<BrowserVersionInfo>, String> {
|
||||
let service = BrowserVersionManager::instance();
|
||||
|
||||
// Get cached versions immediately if available
|
||||
if let Some(cached_versions) = service.get_cached_browser_versions_detailed(&browser_str) {
|
||||
// Check if we should update cache in background
|
||||
if service.should_update_cache(&browser_str) {
|
||||
// Start background update but return cached data immediately
|
||||
let service_clone = BrowserVersionManager::instance();
|
||||
let browser_str_clone = browser_str.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = service_clone
|
||||
.fetch_browser_versions_detailed(&browser_str_clone, false)
|
||||
.await
|
||||
{
|
||||
eprintln!("Background version update failed for {browser_str_clone}: {e}");
|
||||
}
|
||||
});
|
||||
}
|
||||
Ok(cached_versions)
|
||||
} else {
|
||||
// No cache available, fetch fresh
|
||||
service
|
||||
.fetch_browser_versions_detailed(&browser_str, false)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to fetch detailed browser versions: {e}"))
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn fetch_browser_versions_with_count_cached_first(
|
||||
browser_str: String,
|
||||
) -> Result<BrowserVersionsResult, String> {
|
||||
let service = BrowserVersionManager::instance();
|
||||
|
||||
// Get cached versions immediately if available
|
||||
if let Some(cached_versions) = service.get_cached_browser_versions(&browser_str) {
|
||||
// Check if we should update cache in background
|
||||
if service.should_update_cache(&browser_str) {
|
||||
// Start background update but return cached data immediately
|
||||
let service_clone = BrowserVersionManager::instance();
|
||||
let browser_str_clone = browser_str.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = service_clone
|
||||
.fetch_browser_versions_with_count(&browser_str_clone, false)
|
||||
.await
|
||||
{
|
||||
eprintln!("Background version update failed for {browser_str_clone}: {e}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Return cached data in the expected format
|
||||
Ok(BrowserVersionsResult {
|
||||
versions: cached_versions.clone(),
|
||||
new_versions_count: None, // No new versions when returning cached data
|
||||
total_versions_count: cached_versions.len(),
|
||||
})
|
||||
} else {
|
||||
// No cache available, fetch fresh
|
||||
service
|
||||
.fetch_browser_versions_with_count(&browser_str, false)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to fetch browser versions: {e}"))
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn fetch_browser_versions_with_count(
|
||||
browser_str: String,
|
||||
) -> Result<BrowserVersionsResult, String> {
|
||||
let service = BrowserVersionManager::instance();
|
||||
service
|
||||
.fetch_browser_versions_with_count(&browser_str, false)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to fetch browser versions: {e}"))
|
||||
}
|
||||
|
||||
// Global singleton instance
|
||||
lazy_static::lazy_static! {
|
||||
static ref BROWSER_VERSION_SERVICE: BrowserVersionManager = BrowserVersionManager::new();
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use crate::browser_runner::BrowserRunner;
|
||||
use crate::profile::BrowserProfile;
|
||||
use directories::BaseDirs;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use tauri::AppHandle;
|
||||
use tauri_plugin_shell::ShellExt;
|
||||
use tokio::sync::Mutex as AsyncMutex;
|
||||
@@ -60,27 +62,40 @@ struct CamoufoxInstance {
|
||||
url: Option<String>,
|
||||
}
|
||||
|
||||
struct CamoufoxNodecarLauncherInner {
|
||||
struct CamoufoxManagerInner {
|
||||
instances: HashMap<String, CamoufoxInstance>,
|
||||
}
|
||||
|
||||
pub struct CamoufoxNodecarLauncher {
|
||||
inner: Arc<AsyncMutex<CamoufoxNodecarLauncherInner>>,
|
||||
pub struct CamoufoxManager {
|
||||
inner: Arc<AsyncMutex<CamoufoxManagerInner>>,
|
||||
base_dirs: BaseDirs,
|
||||
}
|
||||
|
||||
impl CamoufoxNodecarLauncher {
|
||||
impl CamoufoxManager {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
inner: Arc::new(AsyncMutex::new(CamoufoxNodecarLauncherInner {
|
||||
inner: Arc::new(AsyncMutex::new(CamoufoxManagerInner {
|
||||
instances: HashMap::new(),
|
||||
})),
|
||||
base_dirs: BaseDirs::new().expect("Failed to get base directories"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn instance() -> &'static CamoufoxNodecarLauncher {
|
||||
pub fn instance() -> &'static CamoufoxManager {
|
||||
&CAMOUFOX_NODECAR_LAUNCHER
|
||||
}
|
||||
|
||||
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) {
|
||||
"DonutBrowserDev"
|
||||
} else {
|
||||
"DonutBrowser"
|
||||
});
|
||||
path.push("profiles");
|
||||
path
|
||||
}
|
||||
|
||||
/// Generate Camoufox fingerprint configuration during profile creation
|
||||
pub async fn generate_fingerprint_config(
|
||||
&self,
|
||||
@@ -95,8 +110,8 @@ impl CamoufoxNodecarLauncher {
|
||||
path.clone()
|
||||
} else {
|
||||
// Use the browser runner helper with the real profile
|
||||
let browser_runner = crate::browser_runner::BrowserRunner::instance();
|
||||
browser_runner
|
||||
// Use self.browser_runner instead of instance()
|
||||
BrowserRunner::instance()
|
||||
.get_browser_executable_path(profile)
|
||||
.map_err(|e| format!("Failed to get Camoufox executable path: {e}"))?
|
||||
.to_string_lossy()
|
||||
@@ -202,8 +217,8 @@ impl CamoufoxNodecarLauncher {
|
||||
path.clone()
|
||||
} else {
|
||||
// Use the browser runner helper with the real profile
|
||||
let browser_runner = crate::browser_runner::BrowserRunner::instance();
|
||||
browser_runner
|
||||
// Use self.browser_runner instead of instance()
|
||||
BrowserRunner::instance()
|
||||
.get_browser_executable_path(profile)
|
||||
.map_err(|e| format!("Failed to get Camoufox executable path: {e}"))?
|
||||
.to_string_lossy()
|
||||
@@ -431,7 +446,7 @@ impl CamoufoxNodecarLauncher {
|
||||
}
|
||||
}
|
||||
|
||||
impl CamoufoxNodecarLauncher {
|
||||
impl CamoufoxManager {
|
||||
pub async fn launch_camoufox_profile_nodecar(
|
||||
&self,
|
||||
app_handle: AppHandle,
|
||||
@@ -440,8 +455,7 @@ impl CamoufoxNodecarLauncher {
|
||||
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 profiles_dir = self.get_profiles_dir();
|
||||
let profile_path = profile.get_profile_data_path(&profiles_dir);
|
||||
let profile_path_str = profile_path.to_string_lossy();
|
||||
|
||||
@@ -484,5 +498,5 @@ mod tests {
|
||||
|
||||
// Global singleton instance
|
||||
lazy_static::lazy_static! {
|
||||
static ref CAMOUFOX_NODECAR_LAUNCHER: CamoufoxNodecarLauncher = CamoufoxNodecarLauncher::new();
|
||||
static ref CAMOUFOX_NODECAR_LAUNCHER: CamoufoxManager = CamoufoxManager::new();
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
use tauri::command;
|
||||
|
||||
pub struct DefaultBrowser;
|
||||
pub struct DefaultBrowser {}
|
||||
|
||||
impl DefaultBrowser {
|
||||
fn new() -> Self {
|
||||
Self
|
||||
Self {}
|
||||
}
|
||||
|
||||
pub fn instance() -> &'static DefaultBrowser {
|
||||
@@ -38,38 +38,6 @@ impl DefaultBrowser {
|
||||
#[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_id: 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.id.to_string() == profile_id)
|
||||
.ok_or_else(|| format!("Profile '{profile_id}' not found"))?;
|
||||
|
||||
println!("Opening URL '{url}' with profile '{profile_id}'");
|
||||
|
||||
// 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_id}': {e}");
|
||||
format!("Failed to open URL with profile: {e}")
|
||||
})?;
|
||||
|
||||
println!("Successfully opened URL '{url}' with profile '{profile_id}'");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -570,15 +538,3 @@ pub async fn set_as_default_browser() -> Result<(), String> {
|
||||
let default_browser = DefaultBrowser::instance();
|
||||
default_browser.set_as_default_browser().await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn open_url_with_profile(
|
||||
app_handle: tauri::AppHandle,
|
||||
profile_id: String,
|
||||
url: String,
|
||||
) -> Result<(), String> {
|
||||
let default_browser = DefaultBrowser::instance();
|
||||
default_browser
|
||||
.open_url_with_profile(app_handle, profile_id, url)
|
||||
.await
|
||||
}
|
||||
|
||||
+300
-63
@@ -5,6 +5,9 @@ use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::geoip_downloader::GeoIPDownloader;
|
||||
use crate::profile::{BrowserProfile, ProfileManager};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct DownloadedBrowserInfo {
|
||||
pub browser: String,
|
||||
@@ -19,12 +22,18 @@ struct RegistryData {
|
||||
|
||||
pub struct DownloadedBrowsersRegistry {
|
||||
data: Mutex<RegistryData>,
|
||||
profile_manager: &'static ProfileManager,
|
||||
auto_updater: &'static crate::auto_updater::AutoUpdater,
|
||||
geoip_downloader: &'static GeoIPDownloader,
|
||||
}
|
||||
|
||||
impl DownloadedBrowsersRegistry {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
data: Mutex::new(RegistryData::default()),
|
||||
profile_manager: ProfileManager::instance(),
|
||||
auto_updater: crate::auto_updater::AutoUpdater::instance(),
|
||||
geoip_downloader: GeoIPDownloader::instance(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +97,9 @@ impl DownloadedBrowsersRegistry {
|
||||
data.browsers.get_mut(browser)?.remove(version)
|
||||
}
|
||||
|
||||
pub fn is_browser_downloaded(&self, browser: &str, version: &str) -> bool {
|
||||
/// Check if browser is registered in the registry (without disk validation)
|
||||
/// This method only checks the in-memory registry and does not validate file existence
|
||||
pub fn is_browser_registered(&self, browser: &str, version: &str) -> bool {
|
||||
let data = self.data.lock().unwrap();
|
||||
data
|
||||
.browsers
|
||||
@@ -97,6 +108,52 @@ impl DownloadedBrowsersRegistry {
|
||||
.is_some()
|
||||
}
|
||||
|
||||
/// Check if browser is downloaded and files exist on disk
|
||||
/// This method validates both registry entry and actual file existence
|
||||
pub fn is_browser_downloaded(&self, browser: &str, version: &str) -> bool {
|
||||
use crate::browser::{create_browser, BrowserType};
|
||||
|
||||
// First check if browser is registered
|
||||
if !self.is_browser_registered(browser, version) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Always check if files actually exist on disk
|
||||
let browser_type = match BrowserType::from_str(browser) {
|
||||
Ok(bt) => bt,
|
||||
Err(_) => {
|
||||
println!("Invalid browser type: {browser}");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
let browser_instance = create_browser(browser_type.clone());
|
||||
|
||||
// Get binaries directory
|
||||
let binaries_dir = if let Some(base_dirs) = directories::BaseDirs::new() {
|
||||
let mut path = base_dirs.data_local_dir().to_path_buf();
|
||||
path.push(if cfg!(debug_assertions) {
|
||||
"DonutBrowserDev"
|
||||
} else {
|
||||
"DonutBrowser"
|
||||
});
|
||||
path.push("binaries");
|
||||
path
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let files_exist = browser_instance.is_version_downloaded(version, &binaries_dir);
|
||||
|
||||
// If files don't exist but registry thinks they do, clean up the registry
|
||||
if !files_exist {
|
||||
println!("Cleaning up stale registry entry for {browser} {version}");
|
||||
self.remove_browser(browser, version);
|
||||
let _ = self.save(); // Don't fail if save fails, just log
|
||||
}
|
||||
|
||||
files_exist
|
||||
}
|
||||
|
||||
pub fn get_downloaded_versions(&self, browser: &str) -> Vec<String> {
|
||||
let data = self.data.lock().unwrap();
|
||||
data
|
||||
@@ -196,7 +253,7 @@ impl DownloadedBrowsersRegistry {
|
||||
}
|
||||
|
||||
/// Find and remove unused browser binaries that are not referenced by any active profiles
|
||||
pub fn cleanup_unused_binaries(
|
||||
fn cleanup_unused_binaries_internal(
|
||||
&self,
|
||||
active_profiles: &[(String, String)], // (browser, version) pairs
|
||||
running_profiles: &[(String, String)], // (browser, version) pairs for running profiles
|
||||
@@ -208,14 +265,13 @@ impl DownloadedBrowsersRegistry {
|
||||
let mut cleaned_up = Vec::new();
|
||||
|
||||
// Get pending update versions from auto updater
|
||||
let pending_updates =
|
||||
match crate::auto_updater::AutoUpdater::instance().get_pending_update_versions() {
|
||||
Ok(updates) => updates,
|
||||
Err(e) => {
|
||||
eprintln!("Warning: Failed to get pending updates for cleanup: {e}");
|
||||
std::collections::HashSet::new()
|
||||
}
|
||||
};
|
||||
let pending_updates = match self.auto_updater.get_pending_update_versions() {
|
||||
Ok(updates) => updates,
|
||||
Err(e) => {
|
||||
eprintln!("Warning: Failed to get pending updates for cleanup: {e}");
|
||||
std::collections::HashSet::new()
|
||||
}
|
||||
};
|
||||
|
||||
// Collect all downloaded browsers that are not in active profiles
|
||||
let mut to_remove = Vec::new();
|
||||
@@ -293,11 +349,10 @@ impl DownloadedBrowsersRegistry {
|
||||
/// Verify that all registered browsers actually exist on disk and clean up stale entries
|
||||
pub fn verify_and_cleanup_stale_entries(
|
||||
&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 binaries_dir = self.profile_manager.get_binaries_dir();
|
||||
|
||||
let browsers_to_check: Vec<(String, String)> = {
|
||||
let data = self.data.lock().unwrap();
|
||||
@@ -418,7 +473,7 @@ impl DownloadedBrowsersRegistry {
|
||||
}
|
||||
|
||||
/// Comprehensive cleanup that removes unused binaries and syncs registry
|
||||
pub fn comprehensive_cleanup(
|
||||
fn comprehensive_cleanup(
|
||||
&self,
|
||||
binaries_dir: &std::path::Path,
|
||||
active_profiles: &[(String, String)],
|
||||
@@ -431,11 +486,12 @@ impl DownloadedBrowsersRegistry {
|
||||
cleanup_results.extend(sync_results);
|
||||
|
||||
// Then perform the regular cleanup
|
||||
let regular_cleanup = self.cleanup_unused_binaries(active_profiles, running_profiles)?;
|
||||
let regular_cleanup =
|
||||
self.cleanup_unused_binaries_internal(active_profiles, running_profiles)?;
|
||||
cleanup_results.extend(regular_cleanup);
|
||||
|
||||
// Verify and cleanup stale entries
|
||||
let stale_cleanup = self.verify_and_cleanup_stale_entries_simple(binaries_dir)?;
|
||||
let stale_cleanup = self.verify_and_cleanup_stale_entries()?;
|
||||
cleanup_results.extend(stale_cleanup);
|
||||
|
||||
// Clean up any remaining empty folders
|
||||
@@ -599,19 +655,19 @@ impl DownloadedBrowsersRegistry {
|
||||
pub fn consolidate_browser_versions(
|
||||
&self,
|
||||
app_handle: &tauri::AppHandle,
|
||||
browser_runner: &crate::browser_runner::BrowserRunner,
|
||||
) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
println!("Starting browser version consolidation...");
|
||||
|
||||
let profiles = browser_runner
|
||||
let profiles = self
|
||||
.profile_manager
|
||||
.list_profiles()
|
||||
.map_err(|e| format!("Failed to list profiles: {e}"))?;
|
||||
|
||||
let binaries_dir = browser_runner.get_binaries_dir();
|
||||
let binaries_dir = self.profile_manager.get_binaries_dir();
|
||||
let mut consolidated = Vec::new();
|
||||
|
||||
// Group profiles by browser
|
||||
let mut browser_profiles: std::collections::HashMap<String, Vec<_>> =
|
||||
let mut browser_profiles: std::collections::HashMap<String, Vec<&BrowserProfile>> =
|
||||
std::collections::HashMap::new();
|
||||
for profile in &profiles {
|
||||
browser_profiles
|
||||
@@ -620,7 +676,7 @@ impl DownloadedBrowsersRegistry {
|
||||
.push(profile);
|
||||
}
|
||||
|
||||
for (browser_name, browser_profiles) in browser_profiles {
|
||||
for (browser_name, browser_profiles) in browser_profiles.iter() {
|
||||
// Find the latest version among all profiles for this browser
|
||||
let mut versions: Vec<&str> = browser_profiles
|
||||
.iter()
|
||||
@@ -636,9 +692,9 @@ impl DownloadedBrowsersRegistry {
|
||||
|
||||
// Check which profiles need to be updated to the latest version
|
||||
let mut profiles_to_update = Vec::new();
|
||||
let mut older_versions_to_remove = std::collections::HashSet::new();
|
||||
let mut older_versions_to_remove = std::collections::HashSet::<String>::new();
|
||||
|
||||
for profile in &browser_profiles {
|
||||
for profile in browser_profiles {
|
||||
if profile.version != latest_version {
|
||||
// Only update if profile is not currently running
|
||||
if profile.process_id.is_none() {
|
||||
@@ -655,7 +711,7 @@ impl DownloadedBrowsersRegistry {
|
||||
|
||||
// Update profiles to latest version
|
||||
for profile in profiles_to_update {
|
||||
match browser_runner.update_profile_version(
|
||||
match self.profile_manager.update_profile_version(
|
||||
app_handle,
|
||||
&profile.id.to_string(),
|
||||
latest_version,
|
||||
@@ -673,14 +729,14 @@ impl DownloadedBrowsersRegistry {
|
||||
}
|
||||
|
||||
// Remove older version binaries that are no longer needed
|
||||
for old_version in older_versions_to_remove {
|
||||
let old_version_dir = binaries_dir.join(&browser_name).join(&old_version);
|
||||
for old_version in &older_versions_to_remove {
|
||||
let old_version_dir = binaries_dir.join(browser_name).join(old_version);
|
||||
if old_version_dir.exists() {
|
||||
match std::fs::remove_dir_all(&old_version_dir) {
|
||||
Ok(_) => {
|
||||
consolidated.push(format!("Removed old version: {browser_name} {old_version}"));
|
||||
// Also remove from registry
|
||||
self.remove_browser(&browser_name, &old_version);
|
||||
self.remove_browser(browser_name, old_version);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
@@ -707,36 +763,157 @@ impl DownloadedBrowsersRegistry {
|
||||
Ok(consolidated)
|
||||
}
|
||||
|
||||
/// Simplified version of verify_and_cleanup_stale_entries that doesn't need BrowserRunner
|
||||
pub fn verify_and_cleanup_stale_entries_simple(
|
||||
/// Check if browser binaries exist for all profiles and return missing binaries
|
||||
pub async fn check_missing_binaries(
|
||||
&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();
|
||||
) -> Result<Vec<(String, String, String)>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
use crate::browser::{create_browser, BrowserType};
|
||||
// Get all profiles
|
||||
let profiles = self
|
||||
.profile_manager
|
||||
.list_profiles()
|
||||
.map_err(|e| format!("Failed to list profiles: {e}"))?;
|
||||
let mut missing_binaries = Vec::new();
|
||||
|
||||
{
|
||||
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()));
|
||||
}
|
||||
for profile in profiles {
|
||||
let browser_type = match BrowserType::from_str(&profile.browser) {
|
||||
Ok(bt) => bt,
|
||||
Err(_) => {
|
||||
println!(
|
||||
"Warning: Invalid browser type '{}' for profile '{}'",
|
||||
profile.browser, profile.name
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let browser = create_browser(browser_type.clone());
|
||||
|
||||
// Get binaries directory
|
||||
let binaries_dir = if let Some(base_dirs) = directories::BaseDirs::new() {
|
||||
let mut path = base_dirs.data_local_dir().to_path_buf();
|
||||
path.push(if cfg!(debug_assertions) {
|
||||
"DonutBrowserDev"
|
||||
} else {
|
||||
"DonutBrowser"
|
||||
});
|
||||
path.push("binaries");
|
||||
path
|
||||
} else {
|
||||
return Err("Failed to get base directories".into());
|
||||
};
|
||||
|
||||
println!(
|
||||
"binaries_dir: {binaries_dir:?} for profile: {}",
|
||||
profile.name
|
||||
);
|
||||
|
||||
// Check if the version is downloaded
|
||||
if !browser.is_version_downloaded(&profile.version, &binaries_dir) {
|
||||
missing_binaries.push((profile.name, profile.browser, profile.version));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(missing_binaries)
|
||||
}
|
||||
|
||||
/// Automatically download missing binaries for all profiles
|
||||
pub async fn ensure_all_binaries_exist(
|
||||
&self,
|
||||
app_handle: &tauri::AppHandle,
|
||||
) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
// First, clean up any stale registry entries
|
||||
if let Ok(cleaned_up) = self.verify_and_cleanup_stale_entries() {
|
||||
if !cleaned_up.is_empty() {
|
||||
println!(
|
||||
"Cleaned up {} stale registry entries: {}",
|
||||
cleaned_up.len(),
|
||||
cleaned_up.join(", ")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Consolidate browser versions - keep only latest version per browser
|
||||
if let Ok(consolidated) = self.consolidate_browser_versions(app_handle) {
|
||||
if !consolidated.is_empty() {
|
||||
println!("Version consolidation results:");
|
||||
for action in &consolidated {
|
||||
println!(" {action}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove stale entries
|
||||
for (browser_str, version) in browsers_to_remove {
|
||||
if let Some(_removed) = self.remove_browser(&browser_str, &version) {
|
||||
cleaned_up.push(format!(
|
||||
"Removed stale registry entry for {browser_str} {version}"
|
||||
));
|
||||
let missing_binaries = self.check_missing_binaries().await?;
|
||||
let mut downloaded = Vec::new();
|
||||
|
||||
for (profile_name, browser, version) in missing_binaries {
|
||||
println!("Downloading missing binary for profile '{profile_name}': {browser} {version}");
|
||||
|
||||
match crate::downloader::download_browser(
|
||||
app_handle.clone(),
|
||||
browser.clone(),
|
||||
version.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
downloaded.push(format!(
|
||||
"{browser} {version} (for profile '{profile_name}')"
|
||||
));
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to download {browser} {version} for profile '{profile_name}': {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if GeoIP database is missing for Camoufox profiles
|
||||
if self.geoip_downloader.check_missing_geoip_database()? {
|
||||
println!("GeoIP database is missing for Camoufox profiles, downloading...");
|
||||
|
||||
match self
|
||||
.geoip_downloader
|
||||
.download_geoip_database(app_handle)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
downloaded.push("GeoIP database for Camoufox".to_string());
|
||||
println!("GeoIP database downloaded successfully");
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to download GeoIP database: {e}");
|
||||
// Don't fail the entire operation if GeoIP download fails
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(downloaded)
|
||||
}
|
||||
|
||||
/// Cleanup unused binaries based on active and running profiles
|
||||
pub fn cleanup_unused_binaries(
|
||||
&self,
|
||||
) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
// Load current profiles using injected ProfileManager
|
||||
let profiles = self
|
||||
.profile_manager
|
||||
.list_profiles()
|
||||
.map_err(|e| format!("Failed to list profiles: {e}"))?;
|
||||
|
||||
// Get active browser versions (all profiles)
|
||||
let active_versions = self.get_active_browser_versions(&profiles);
|
||||
|
||||
// Get running browser versions (only running profiles)
|
||||
let running_versions = self.get_running_browser_versions(&profiles);
|
||||
|
||||
// Get binaries directory from profile manager
|
||||
let binaries_dir = self.profile_manager.get_binaries_dir();
|
||||
|
||||
// Use comprehensive cleanup that syncs registry with disk and removes unused binaries
|
||||
let cleaned_up =
|
||||
self.comprehensive_cleanup(&binaries_dir, &active_versions, &running_versions)?;
|
||||
|
||||
// Registry is already saved by comprehensive_cleanup
|
||||
Ok(cleaned_up)
|
||||
}
|
||||
}
|
||||
@@ -758,6 +935,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_registry_creation() {
|
||||
// Create a mock profile manager for testing
|
||||
let registry = DownloadedBrowsersRegistry::new();
|
||||
let data = registry.data.lock().unwrap();
|
||||
assert!(data.browsers.is_empty());
|
||||
@@ -774,9 +952,9 @@ mod tests {
|
||||
|
||||
registry.add_browser(info.clone());
|
||||
|
||||
assert!(registry.is_browser_downloaded("firefox", "139.0"));
|
||||
assert!(!registry.is_browser_downloaded("firefox", "140.0"));
|
||||
assert!(!registry.is_browser_downloaded("chrome", "139.0"));
|
||||
assert!(registry.is_browser_registered("firefox", "139.0"));
|
||||
assert!(!registry.is_browser_registered("firefox", "140.0"));
|
||||
assert!(!registry.is_browser_registered("chrome", "139.0"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -819,10 +997,10 @@ mod tests {
|
||||
// Mark download started
|
||||
registry.mark_download_started("firefox", "139.0", PathBuf::from("/test/path"));
|
||||
|
||||
// Should NOT be considered downloaded until verification completes
|
||||
// Should NOT be registered until verification completes
|
||||
assert!(
|
||||
!registry.is_browser_downloaded("firefox", "139.0"),
|
||||
"Browser should NOT be considered downloaded after marking as started (only after verification)"
|
||||
!registry.is_browser_registered("firefox", "139.0"),
|
||||
"Browser should NOT be registered after marking as started (only after verification)"
|
||||
);
|
||||
|
||||
// Mark as completed (after verification)
|
||||
@@ -830,10 +1008,10 @@ mod tests {
|
||||
.mark_download_completed("firefox", "139.0", PathBuf::from("/test/path"))
|
||||
.expect("Failed to mark download as completed");
|
||||
|
||||
// Should now be considered downloaded
|
||||
// Should now be registered
|
||||
assert!(
|
||||
registry.is_browser_downloaded("firefox", "139.0"),
|
||||
"Browser should be considered downloaded after verification completes"
|
||||
registry.is_browser_registered("firefox", "139.0"),
|
||||
"Browser should be registered after verification completes"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -848,8 +1026,8 @@ mod tests {
|
||||
|
||||
registry.add_browser(info);
|
||||
assert!(
|
||||
registry.is_browser_downloaded("firefox", "139.0"),
|
||||
"Browser should be downloaded after adding"
|
||||
registry.is_browser_registered("firefox", "139.0"),
|
||||
"Browser should be registered after adding"
|
||||
);
|
||||
|
||||
let removed = registry.remove_browser("firefox", "139.0");
|
||||
@@ -858,8 +1036,8 @@ mod tests {
|
||||
"Remove operation should return the removed browser info"
|
||||
);
|
||||
assert!(
|
||||
!registry.is_browser_downloaded("firefox", "139.0"),
|
||||
"Browser should not be downloaded after removal"
|
||||
!registry.is_browser_registered("firefox", "139.0"),
|
||||
"Browser should not be registered after removal"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -872,18 +1050,77 @@ mod tests {
|
||||
|
||||
// Should NOT be registered until verification completes
|
||||
assert!(
|
||||
!registry.is_browser_downloaded("zen", "twilight"),
|
||||
!registry.is_browser_registered("zen", "twilight"),
|
||||
"Zen twilight version should NOT be registered until verification completes"
|
||||
);
|
||||
|
||||
// Mark as completed (after verification)
|
||||
registry.mark_download_completed("zen", "twilight", PathBuf::from("/test/zen-twilight"))
|
||||
registry
|
||||
.mark_download_completed("zen", "twilight", PathBuf::from("/test/zen-twilight"))
|
||||
.expect("Failed to mark twilight download as completed");
|
||||
|
||||
// Now it should be registered
|
||||
assert!(
|
||||
registry.is_browser_downloaded("zen", "twilight"),
|
||||
registry.is_browser_registered("zen", "twilight"),
|
||||
"Zen twilight version should be registered after verification completes"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_browser_registered_vs_downloaded() {
|
||||
let registry = DownloadedBrowsersRegistry::new();
|
||||
let info = DownloadedBrowserInfo {
|
||||
browser: "firefox".to_string(),
|
||||
version: "139.0".to_string(),
|
||||
file_path: PathBuf::from("/test/path"),
|
||||
};
|
||||
|
||||
// Add browser to registry
|
||||
registry.add_browser(info);
|
||||
|
||||
// Should be registered (in-memory check)
|
||||
assert!(
|
||||
registry.is_browser_registered("firefox", "139.0"),
|
||||
"Browser should be registered after adding to registry"
|
||||
);
|
||||
|
||||
// is_browser_downloaded should return false in test environment because files don't exist
|
||||
// This tests the difference between registered (in registry) vs downloaded (files exist)
|
||||
assert!(
|
||||
!registry.is_browser_downloaded("firefox", "139.0"),
|
||||
"Browser should not be considered downloaded when files don't exist on disk"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_downloaded_browser_versions(browser_str: String) -> Result<Vec<String>, String> {
|
||||
let registry = DownloadedBrowsersRegistry::instance();
|
||||
Ok(registry.get_downloaded_versions(&browser_str))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn is_browser_downloaded(browser_str: String, version: String) -> bool {
|
||||
let registry = DownloadedBrowsersRegistry::instance();
|
||||
registry.is_browser_downloaded(&browser_str, &version)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn check_missing_binaries() -> Result<Vec<(String, String, String)>, String> {
|
||||
let registry = DownloadedBrowsersRegistry::instance();
|
||||
registry
|
||||
.check_missing_binaries()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to check missing binaries: {e}"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn ensure_all_binaries_exist(
|
||||
app_handle: tauri::AppHandle,
|
||||
) -> Result<Vec<String>, String> {
|
||||
let registry = DownloadedBrowsersRegistry::instance();
|
||||
registry
|
||||
.ensure_all_binaries_exist(&app_handle)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to ensure all binaries exist: {e}"))
|
||||
}
|
||||
@@ -2,12 +2,19 @@ use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Mutex;
|
||||
use tauri::Emitter;
|
||||
|
||||
use crate::api_client::ApiClient;
|
||||
use crate::browser::BrowserType;
|
||||
use crate::browser::{create_browser, BrowserType};
|
||||
use crate::browser_version_manager::DownloadInfo;
|
||||
|
||||
// Global state to track currently downloading browser-version pairs
|
||||
lazy_static::lazy_static! {
|
||||
static ref DOWNLOADING_BROWSERS: std::sync::Arc<Mutex<std::collections::HashSet<String>>> =
|
||||
std::sync::Arc::new(Mutex::new(std::collections::HashSet::new()));
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct DownloadProgress {
|
||||
pub browser: String,
|
||||
@@ -23,6 +30,10 @@ pub struct DownloadProgress {
|
||||
pub struct Downloader {
|
||||
client: Client,
|
||||
api_client: &'static ApiClient,
|
||||
registry: &'static crate::downloaded_browsers_registry::DownloadedBrowsersRegistry,
|
||||
version_service: &'static crate::browser_version_manager::BrowserVersionManager,
|
||||
extractor: &'static crate::extraction::Extractor,
|
||||
geoip_downloader: &'static crate::geoip_downloader::GeoIPDownloader,
|
||||
}
|
||||
|
||||
impl Downloader {
|
||||
@@ -30,6 +41,10 @@ impl Downloader {
|
||||
Self {
|
||||
client: Client::new(),
|
||||
api_client: ApiClient::instance(),
|
||||
registry: crate::downloaded_browsers_registry::DownloadedBrowsersRegistry::instance(),
|
||||
version_service: crate::browser_version_manager::BrowserVersionManager::instance(),
|
||||
extractor: crate::extraction::Extractor::instance(),
|
||||
geoip_downloader: crate::geoip_downloader::GeoIPDownloader::instance(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +57,10 @@ impl Downloader {
|
||||
Self {
|
||||
client: Client::new(),
|
||||
api_client: ApiClient::instance(),
|
||||
registry: crate::downloaded_browsers_registry::DownloadedBrowsersRegistry::instance(),
|
||||
version_service: crate::browser_version_manager::BrowserVersionManager::instance(),
|
||||
extractor: crate::extraction::Extractor::instance(),
|
||||
geoip_downloader: crate::geoip_downloader::GeoIPDownloader::instance(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -573,6 +592,327 @@ impl Downloader {
|
||||
|
||||
Ok(file_path)
|
||||
}
|
||||
|
||||
/// Download a browser binary, verify it, and register it in the downloaded browsers registry
|
||||
pub async fn download_browser_full(
|
||||
&self,
|
||||
app_handle: &tauri::AppHandle,
|
||||
browser_str: String,
|
||||
version: String,
|
||||
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
|
||||
// Check if this browser-version pair is already being downloaded
|
||||
let download_key = format!("{browser_str}-{version}");
|
||||
{
|
||||
let mut downloading = DOWNLOADING_BROWSERS.lock().unwrap();
|
||||
if downloading.contains(&download_key) {
|
||||
return Err(format!("Browser '{browser_str}' version '{version}' is already being downloaded. Please wait for the current download to complete.").into());
|
||||
}
|
||||
// Mark this browser-version pair as being downloaded
|
||||
downloading.insert(download_key.clone());
|
||||
}
|
||||
|
||||
let browser_type =
|
||||
BrowserType::from_str(&browser_str).map_err(|e| format!("Invalid browser type: {e}"))?;
|
||||
let browser = create_browser(browser_type.clone());
|
||||
|
||||
// Use injected registry instance
|
||||
|
||||
// Get binaries directory - we need to get it from somewhere
|
||||
// This is a bit tricky since we don't have access to BrowserRunner's get_binaries_dir
|
||||
// We'll need to replicate this logic
|
||||
let binaries_dir = if let Some(base_dirs) = directories::BaseDirs::new() {
|
||||
let mut path = base_dirs.data_local_dir().to_path_buf();
|
||||
path.push(if cfg!(debug_assertions) {
|
||||
"DonutBrowserDev"
|
||||
} else {
|
||||
"DonutBrowser"
|
||||
});
|
||||
path.push("binaries");
|
||||
path
|
||||
} else {
|
||||
return Err("Failed to get base directories".into());
|
||||
};
|
||||
|
||||
// Check if registry thinks it's downloaded, but also verify files actually exist
|
||||
if self.registry.is_browser_downloaded(&browser_str, &version) {
|
||||
let actually_exists = browser.is_version_downloaded(&version, &binaries_dir);
|
||||
|
||||
if actually_exists {
|
||||
// Remove from downloading set since it's already downloaded
|
||||
let mut downloading = DOWNLOADING_BROWSERS.lock().unwrap();
|
||||
downloading.remove(&download_key);
|
||||
return Ok(version);
|
||||
} else {
|
||||
// Registry says it's downloaded but files don't exist - clean up registry
|
||||
println!("Registry indicates {browser_str} {version} is downloaded, but files are missing. Cleaning up registry entry.");
|
||||
self.registry.remove_browser(&browser_str, &version);
|
||||
self
|
||||
.registry
|
||||
.save()
|
||||
.map_err(|e| format!("Failed to save cleaned registry: {e}"))?;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if browser is supported on current platform before attempting download
|
||||
if !self
|
||||
.version_service
|
||||
.is_browser_supported(&browser_str)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
// Remove from downloading set on error
|
||||
let mut downloading = DOWNLOADING_BROWSERS.lock().unwrap();
|
||||
downloading.remove(&download_key);
|
||||
return Err(
|
||||
format!(
|
||||
"Browser '{}' is not supported on your platform ({} {}). Supported browsers: {}",
|
||||
browser_str,
|
||||
std::env::consts::OS,
|
||||
std::env::consts::ARCH,
|
||||
self.version_service.get_supported_browsers().join(", ")
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
let download_info = self
|
||||
.version_service
|
||||
.get_download_info(&browser_str, &version)
|
||||
.map_err(|e| format!("Failed to get download info: {e}"))?;
|
||||
|
||||
// Create browser directory
|
||||
let mut browser_dir = binaries_dir.clone();
|
||||
browser_dir.push(&browser_str);
|
||||
browser_dir.push(&version);
|
||||
|
||||
std::fs::create_dir_all(&browser_dir)
|
||||
.map_err(|e| format!("Failed to create browser directory: {e}"))?;
|
||||
|
||||
// Mark download as started (but don't add to registry yet)
|
||||
self
|
||||
.registry
|
||||
.mark_download_started(&browser_str, &version, browser_dir.clone());
|
||||
|
||||
// Attempt to download the archive. If the download fails but an archive with the
|
||||
// expected filename already exists (manual download), continue using that file.
|
||||
let download_path: PathBuf = match self
|
||||
.download_browser(
|
||||
app_handle,
|
||||
browser_type.clone(),
|
||||
&version,
|
||||
&download_info,
|
||||
&browser_dir,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(path) => path,
|
||||
Err(e) => {
|
||||
// Do NOT continue with extraction on failed downloads. Partial files may exist but are invalid.
|
||||
// Clean registry entry and stop here so the UI can show a single, clear error.
|
||||
let _ = self.registry.remove_browser(&browser_str, &version);
|
||||
let _ = self.registry.save();
|
||||
let mut downloading = DOWNLOADING_BROWSERS.lock().unwrap();
|
||||
downloading.remove(&download_key);
|
||||
return Err(format!("Failed to download browser: {e}").into());
|
||||
}
|
||||
};
|
||||
|
||||
// Use the extraction module
|
||||
if download_info.is_archive {
|
||||
match self
|
||||
.extractor
|
||||
.extract_browser(
|
||||
app_handle,
|
||||
browser_type.clone(),
|
||||
&version,
|
||||
&download_path,
|
||||
&browser_dir,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
// Do not remove the archive here. We keep it until verification succeeds.
|
||||
}
|
||||
Err(e) => {
|
||||
// Do not remove the archive or extracted files. Just drop the registry entry
|
||||
// so it won't be reported as downloaded.
|
||||
let _ = self.registry.remove_browser(&browser_str, &version);
|
||||
let _ = self.registry.save();
|
||||
// Remove browser-version pair from downloading set on error
|
||||
{
|
||||
let mut downloading = DOWNLOADING_BROWSERS.lock().unwrap();
|
||||
downloading.remove(&download_key);
|
||||
}
|
||||
return Err(format!("Failed to extract browser: {e}").into());
|
||||
}
|
||||
}
|
||||
|
||||
// Give filesystem a moment to settle after extraction
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
|
||||
}
|
||||
|
||||
// Emit verification progress
|
||||
let progress = DownloadProgress {
|
||||
browser: browser_str.clone(),
|
||||
version: version.clone(),
|
||||
downloaded_bytes: 0,
|
||||
total_bytes: None,
|
||||
percentage: 100.0,
|
||||
speed_bytes_per_sec: 0.0,
|
||||
eta_seconds: None,
|
||||
stage: "verifying".to_string(),
|
||||
};
|
||||
let _ = app_handle.emit("download-progress", &progress);
|
||||
|
||||
// Verify the browser was downloaded correctly
|
||||
println!("Verifying download for browser: {browser_str}, version: {version}");
|
||||
|
||||
// Use the browser's own verification method
|
||||
if !browser.is_version_downloaded(&version, &binaries_dir) {
|
||||
// Provide detailed error information for debugging
|
||||
let browser_dir = binaries_dir.join(&browser_str).join(&version);
|
||||
let mut error_details = format!(
|
||||
"Browser download completed but verification failed for {} {}. Expected directory: {}",
|
||||
browser_str,
|
||||
version,
|
||||
browser_dir.display()
|
||||
);
|
||||
|
||||
// List what files actually exist
|
||||
if browser_dir.exists() {
|
||||
error_details.push_str("\nFiles found in directory:");
|
||||
if let Ok(entries) = std::fs::read_dir(&browser_dir) {
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
let file_type = if path.is_dir() { "DIR" } else { "FILE" };
|
||||
error_details.push_str(&format!("\n {} {}", file_type, path.display()));
|
||||
}
|
||||
} else {
|
||||
error_details.push_str("\n (Could not read directory contents)");
|
||||
}
|
||||
} else {
|
||||
error_details.push_str("\nDirectory does not exist!");
|
||||
}
|
||||
|
||||
// For Camoufox on Linux, provide specific expected files
|
||||
if browser_str == "camoufox" && cfg!(target_os = "linux") {
|
||||
let camoufox_subdir = browser_dir.join("camoufox");
|
||||
error_details.push_str("\nExpected Camoufox executable locations:");
|
||||
error_details.push_str(&format!("\n {}/camoufox-bin", camoufox_subdir.display()));
|
||||
error_details.push_str(&format!("\n {}/camoufox", camoufox_subdir.display()));
|
||||
|
||||
if camoufox_subdir.exists() {
|
||||
error_details.push_str(&format!(
|
||||
"\nCamoufox subdirectory exists: {}",
|
||||
camoufox_subdir.display()
|
||||
));
|
||||
if let Ok(entries) = std::fs::read_dir(&camoufox_subdir) {
|
||||
error_details.push_str("\nFiles in camoufox subdirectory:");
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
let file_type = if path.is_dir() { "DIR" } else { "FILE" };
|
||||
error_details.push_str(&format!("\n {} {}", file_type, path.display()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error_details.push_str(&format!(
|
||||
"\nCamoufox subdirectory does not exist: {}",
|
||||
camoufox_subdir.display()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Do not delete files on verification failure; keep archive for manual retry.
|
||||
let _ = self.registry.remove_browser(&browser_str, &version);
|
||||
let _ = self.registry.save();
|
||||
// Remove browser-version pair from downloading set on verification failure
|
||||
{
|
||||
let mut downloading = DOWNLOADING_BROWSERS.lock().unwrap();
|
||||
downloading.remove(&download_key);
|
||||
}
|
||||
return Err(error_details.into());
|
||||
}
|
||||
|
||||
// Mark completion in registry - only now add to registry after verification
|
||||
if let Err(e) =
|
||||
self
|
||||
.registry
|
||||
.mark_download_completed(&browser_str, &version, browser_dir.clone())
|
||||
{
|
||||
eprintln!("Warning: Could not mark {browser_str} {version} as completed in registry: {e}");
|
||||
}
|
||||
self
|
||||
.registry
|
||||
.save()
|
||||
.map_err(|e| format!("Failed to save registry: {e}"))?;
|
||||
|
||||
// Now that verification succeeded, remove the archive file if it exists
|
||||
if download_info.is_archive {
|
||||
let archive_path = browser_dir.join(&download_info.filename);
|
||||
if archive_path.exists() {
|
||||
if let Err(e) = std::fs::remove_file(&archive_path) {
|
||||
println!("Warning: Could not delete archive file after verification: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If this is Camoufox, automatically download GeoIP database
|
||||
if browser_str == "camoufox" {
|
||||
// Check if GeoIP database is already available
|
||||
if !crate::geoip_downloader::GeoIPDownloader::is_geoip_database_available() {
|
||||
println!("Downloading GeoIP database for Camoufox...");
|
||||
|
||||
match self
|
||||
.geoip_downloader
|
||||
.download_geoip_database(app_handle)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
println!("GeoIP database downloaded successfully");
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to download GeoIP database: {e}");
|
||||
// Don't fail the browser download if GeoIP download fails
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("GeoIP database already available");
|
||||
}
|
||||
}
|
||||
|
||||
// Emit completion
|
||||
let progress = DownloadProgress {
|
||||
browser: browser_str.clone(),
|
||||
version: version.clone(),
|
||||
downloaded_bytes: 0,
|
||||
total_bytes: None,
|
||||
percentage: 100.0,
|
||||
speed_bytes_per_sec: 0.0,
|
||||
eta_seconds: Some(0.0),
|
||||
stage: "completed".to_string(),
|
||||
};
|
||||
let _ = app_handle.emit("download-progress", &progress);
|
||||
|
||||
// Remove browser-version pair from downloading set
|
||||
{
|
||||
let mut downloading = DOWNLOADING_BROWSERS.lock().unwrap();
|
||||
downloading.remove(&download_key);
|
||||
}
|
||||
|
||||
Ok(version)
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn download_browser(
|
||||
app_handle: tauri::AppHandle,
|
||||
browser_str: String,
|
||||
version: String,
|
||||
) -> Result<String, String> {
|
||||
let downloader = Downloader::instance();
|
||||
downloader
|
||||
.download_browser_full(&app_handle, browser_str, version)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to download browser: {e}"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -4,7 +4,7 @@ use std::path::{Path, PathBuf};
|
||||
use tauri::Emitter;
|
||||
|
||||
use crate::browser::BrowserType;
|
||||
use crate::download::DownloadProgress;
|
||||
use crate::downloader::DownloadProgress;
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "windows"))]
|
||||
use std::process::Command;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::browser::GithubRelease;
|
||||
use crate::profile::manager::ProfileManager;
|
||||
use directories::BaseDirs;
|
||||
use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -75,6 +76,25 @@ impl GeoIPDownloader {
|
||||
false
|
||||
}
|
||||
}
|
||||
/// Check if GeoIP database is missing for Camoufox profiles
|
||||
pub fn check_missing_geoip_database(
|
||||
&self,
|
||||
) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
|
||||
// Get all profiles
|
||||
let profiles = ProfileManager::instance()
|
||||
.list_profiles()
|
||||
.map_err(|e| format!("Failed to list profiles: {e}"))?;
|
||||
|
||||
// Check if there are any Camoufox profiles
|
||||
let has_camoufox_profiles = profiles.iter().any(|profile| profile.browser == "camoufox");
|
||||
|
||||
if has_camoufox_profiles {
|
||||
// Check if GeoIP database is available
|
||||
return Ok(!Self::is_geoip_database_available());
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn find_city_mmdb_asset(&self, release: &GithubRelease) -> Option<String> {
|
||||
for asset in &release.assets {
|
||||
@@ -218,6 +238,19 @@ impl GeoIPDownloader {
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn check_missing_geoip_database() -> Result<bool, String> {
|
||||
let geoip_downloader = GeoIPDownloader::instance();
|
||||
geoip_downloader
|
||||
.check_missing_geoip_database()
|
||||
.map_err(|e| format!("Failed to check missing GeoIP database: {e}"))
|
||||
}
|
||||
|
||||
// Global singleton instance
|
||||
lazy_static::lazy_static! {
|
||||
static ref GEOIP_DOWNLOADER: GeoIPDownloader = GeoIPDownloader::new();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -353,8 +386,3 @@ mod tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Global singleton instance
|
||||
lazy_static::lazy_static! {
|
||||
static ref GEOIP_DOWNLOADER: GeoIPDownloader = GeoIPDownloader::new();
|
||||
}
|
||||
|
||||
+38
-24
@@ -14,10 +14,10 @@ mod auto_updater;
|
||||
mod browser;
|
||||
mod browser_runner;
|
||||
mod browser_version_manager;
|
||||
mod camoufox;
|
||||
mod camoufox_manager;
|
||||
mod default_browser;
|
||||
mod download;
|
||||
mod downloaded_browsers;
|
||||
mod downloaded_browsers_registry;
|
||||
mod downloader;
|
||||
mod extraction;
|
||||
mod geoip_downloader;
|
||||
mod group_manager;
|
||||
@@ -31,24 +31,38 @@ mod tag_manager;
|
||||
mod version_updater;
|
||||
|
||||
use browser_runner::{
|
||||
check_browser_exists, check_browser_status, check_missing_binaries, check_missing_geoip_database,
|
||||
create_browser_profile_new, delete_profile, download_browser, ensure_all_binaries_exist,
|
||||
fetch_browser_versions_cached_first, fetch_browser_versions_with_count,
|
||||
fetch_browser_versions_with_count_cached_first, get_all_tags, get_downloaded_browser_versions,
|
||||
get_supported_browsers, is_browser_supported_on_platform, kill_browser_profile,
|
||||
launch_browser_profile, list_browser_profiles, rename_profile, update_camoufox_config,
|
||||
update_profile_proxy, update_profile_tags,
|
||||
check_browser_exists, kill_browser_profile, launch_browser_profile, open_url_with_profile,
|
||||
};
|
||||
|
||||
use profile::manager::{
|
||||
check_browser_status, create_browser_profile_new, delete_profile, list_browser_profiles,
|
||||
rename_profile, update_camoufox_config, update_profile_proxy, update_profile_tags,
|
||||
};
|
||||
|
||||
use browser_version_manager::{
|
||||
fetch_browser_versions_cached_first, fetch_browser_versions_with_count,
|
||||
fetch_browser_versions_with_count_cached_first, get_supported_browsers,
|
||||
is_browser_supported_on_platform,
|
||||
};
|
||||
|
||||
use downloaded_browsers_registry::{
|
||||
check_missing_binaries, ensure_all_binaries_exist, get_downloaded_browser_versions,
|
||||
};
|
||||
|
||||
use downloader::download_browser;
|
||||
|
||||
use settings_manager::{
|
||||
clear_all_version_cache_and_refetch, get_app_settings, get_table_sorting_settings,
|
||||
save_app_settings, save_table_sorting_settings, should_show_settings_on_startup,
|
||||
get_app_settings, get_table_sorting_settings, save_app_settings, save_table_sorting_settings,
|
||||
should_show_settings_on_startup,
|
||||
};
|
||||
|
||||
use default_browser::{is_default_browser, open_url_with_profile, set_as_default_browser};
|
||||
use tag_manager::get_all_tags;
|
||||
|
||||
use default_browser::{is_default_browser, set_as_default_browser};
|
||||
|
||||
use version_updater::{
|
||||
get_version_update_status, get_version_updater, trigger_manual_version_update,
|
||||
clear_all_version_cache_and_refetch, get_version_update_status, get_version_updater,
|
||||
trigger_manual_version_update,
|
||||
};
|
||||
|
||||
use auto_updater::{
|
||||
@@ -66,7 +80,7 @@ use group_manager::{
|
||||
get_groups_with_profile_counts, get_profile_groups, update_profile_group,
|
||||
};
|
||||
|
||||
use geoip_downloader::GeoIPDownloader;
|
||||
use geoip_downloader::{check_missing_geoip_database, GeoIPDownloader};
|
||||
|
||||
use browser_version_manager::get_browser_release_types;
|
||||
|
||||
@@ -379,8 +393,9 @@ pub fn run() {
|
||||
loop {
|
||||
interval.tick().await;
|
||||
|
||||
let browser_runner = crate::browser_runner::BrowserRunner::instance();
|
||||
if let Err(e) = browser_runner.cleanup_unused_binaries_internal() {
|
||||
let registry =
|
||||
crate::downloaded_browsers_registry::DownloadedBrowsersRegistry::instance();
|
||||
if let Err(e) = registry.cleanup_unused_binaries() {
|
||||
eprintln!("Periodic cleanup failed: {e}");
|
||||
} else {
|
||||
println!("Periodic cleanup completed successfully");
|
||||
@@ -417,14 +432,14 @@ pub fn run() {
|
||||
// Start Camoufox cleanup task
|
||||
let _app_handle_cleanup = app.handle().clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let launcher = crate::camoufox::CamoufoxNodecarLauncher::instance();
|
||||
let camoufox_manager = crate::camoufox_manager::CamoufoxManager::instance();
|
||||
let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(5));
|
||||
|
||||
loop {
|
||||
interval.tick().await;
|
||||
|
||||
match launcher.cleanup_dead_instances().await {
|
||||
Ok(_dead_instances) => {
|
||||
match camoufox_manager.cleanup_dead_instances().await {
|
||||
Ok(_) => {
|
||||
// Cleanup completed silently
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -440,8 +455,8 @@ pub fn run() {
|
||||
// Wait a bit for the app to fully initialize
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
|
||||
|
||||
let browser_runner = crate::browser_runner::BrowserRunner::instance();
|
||||
match browser_runner.check_missing_geoip_database() {
|
||||
let geoip_downloader = crate::geoip_downloader::GeoIPDownloader::instance();
|
||||
match geoip_downloader.check_missing_geoip_database() {
|
||||
Ok(true) => {
|
||||
println!("GeoIP database is missing for Camoufox profiles, downloading at startup...");
|
||||
let geoip_downloader = GeoIPDownloader::instance();
|
||||
@@ -502,7 +517,7 @@ pub fn run() {
|
||||
|
||||
let runner = crate::browser_runner::BrowserRunner::instance();
|
||||
// If listing profiles fails, skip this tick
|
||||
let profiles = match runner.list_profiles() {
|
||||
let profiles = match runner.profile_manager.list_profiles() {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
println!("Warning: Failed to list profiles in status checker: {e}");
|
||||
@@ -658,7 +673,6 @@ pub fn run() {
|
||||
check_for_app_updates,
|
||||
check_for_app_updates_manual,
|
||||
download_and_install_app_update,
|
||||
// get_system_theme, // removed
|
||||
detect_existing_profiles,
|
||||
import_browser_profile,
|
||||
check_missing_binaries,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use crate::api_client::is_browser_version_nightly;
|
||||
use crate::browser::{create_browser, BrowserType, ProxySettings};
|
||||
use crate::camoufox::CamoufoxConfig;
|
||||
use crate::camoufox_manager::CamoufoxConfig;
|
||||
use crate::downloaded_browsers_registry::DownloadedBrowsersRegistry;
|
||||
use crate::profile::types::BrowserProfile;
|
||||
use crate::proxy_manager::PROXY_MANAGER;
|
||||
use directories::BaseDirs;
|
||||
@@ -10,12 +12,14 @@ use tauri::Emitter;
|
||||
|
||||
pub struct ProfileManager {
|
||||
base_dirs: BaseDirs,
|
||||
camoufox_manager: &'static crate::camoufox_manager::CamoufoxManager,
|
||||
}
|
||||
|
||||
impl ProfileManager {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
base_dirs: BaseDirs::new().expect("Failed to get base directories"),
|
||||
camoufox_manager: crate::camoufox_manager::CamoufoxManager::instance(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +38,17 @@ impl ProfileManager {
|
||||
path
|
||||
}
|
||||
|
||||
pub fn get_binaries_dir(&self) -> PathBuf {
|
||||
let mut path = self.base_dirs.data_local_dir().to_path_buf();
|
||||
path.push(if cfg!(debug_assertions) {
|
||||
"DonutBrowserDev"
|
||||
} else {
|
||||
"DonutBrowser"
|
||||
});
|
||||
path.push("binaries");
|
||||
path
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn create_profile_with_group(
|
||||
&self,
|
||||
@@ -72,13 +87,12 @@ impl ProfileManager {
|
||||
let final_camoufox_config = if browser == "camoufox" {
|
||||
let mut config = camoufox_config.unwrap_or_else(|| {
|
||||
println!("Creating default Camoufox config for profile: {name}");
|
||||
crate::camoufox::CamoufoxConfig::default()
|
||||
crate::camoufox_manager::CamoufoxConfig::default()
|
||||
});
|
||||
|
||||
// Always ensure executable_path is set to the user's binary location
|
||||
if config.executable_path.is_none() {
|
||||
let browser_runner = crate::browser_runner::BrowserRunner::instance();
|
||||
let mut browser_dir = browser_runner.get_binaries_dir();
|
||||
let mut browser_dir = self.get_binaries_dir();
|
||||
browser_dir.push(browser);
|
||||
browser_dir.push(version);
|
||||
|
||||
@@ -137,7 +151,6 @@ impl ProfileManager {
|
||||
println!("Generating fingerprint for Camoufox profile: {name}");
|
||||
|
||||
// Use the camoufox launcher to generate the config
|
||||
let camoufox_launcher = crate::camoufox::CamoufoxNodecarLauncher::instance();
|
||||
|
||||
// Create a temporary profile for fingerprint generation
|
||||
let temp_profile = BrowserProfile {
|
||||
@@ -154,7 +167,8 @@ impl ProfileManager {
|
||||
tags: Vec::new(),
|
||||
};
|
||||
|
||||
match camoufox_launcher
|
||||
match self
|
||||
.camoufox_manager
|
||||
.generate_fingerprint_config(app_handle, &temp_profile, &config)
|
||||
.await
|
||||
{
|
||||
@@ -237,6 +251,11 @@ impl ProfileManager {
|
||||
let json = serde_json::to_string_pretty(profile)?;
|
||||
fs::write(profile_file, json)?;
|
||||
|
||||
// Update tag suggestions after any save
|
||||
let _ = crate::tag_manager::TAG_MANAGER.lock().map(|tm| {
|
||||
let _ = tm.rebuild_from_profiles(&self.list_profiles().unwrap_or_default());
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -355,6 +374,11 @@ impl ProfileManager {
|
||||
let _ = tm.rebuild_from_profiles(&self.list_profiles().unwrap_or_default());
|
||||
});
|
||||
|
||||
// Always perform cleanup after profile deletion to remove unused binaries
|
||||
if let Err(e) = DownloadedBrowsersRegistry::instance().cleanup_unused_binaries() {
|
||||
println!("Warning: Failed to cleanup unused binaries after profile deletion: {e}");
|
||||
}
|
||||
|
||||
// Emit profile deletion event
|
||||
if let Err(e) = app_handle.emit("profiles-changed", ()) {
|
||||
println!("Warning: Failed to emit profiles-changed event: {e}");
|
||||
@@ -399,12 +423,11 @@ impl ProfileManager {
|
||||
profile.version = version.to_string();
|
||||
|
||||
// Update the release_type based on the version and browser
|
||||
profile.release_type =
|
||||
if crate::api_client::is_browser_version_nightly(&profile.browser, version, None) {
|
||||
"nightly".to_string()
|
||||
} else {
|
||||
"stable".to_string()
|
||||
};
|
||||
profile.release_type = if is_browser_version_nightly(&profile.browser, version, None) {
|
||||
"nightly".to_string()
|
||||
} else {
|
||||
"stable".to_string()
|
||||
};
|
||||
|
||||
// Save the updated profile
|
||||
self.save_profile(&profile)?;
|
||||
@@ -866,9 +889,7 @@ impl ProfileManager {
|
||||
app_handle: &tauri::AppHandle,
|
||||
profile: &BrowserProfile,
|
||||
) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
|
||||
use crate::camoufox::CamoufoxNodecarLauncher;
|
||||
|
||||
let launcher = CamoufoxNodecarLauncher::instance();
|
||||
let launcher = self.camoufox_manager;
|
||||
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();
|
||||
@@ -1012,17 +1033,6 @@ impl ProfileManager {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_binaries_dir(&self) -> PathBuf {
|
||||
let mut path = self.base_dirs.data_local_dir().to_path_buf();
|
||||
path.push(if cfg!(debug_assertions) {
|
||||
"DonutBrowserDev"
|
||||
} else {
|
||||
"DonutBrowser"
|
||||
});
|
||||
path.push("binaries");
|
||||
path
|
||||
}
|
||||
|
||||
fn get_common_firefox_preferences(&self) -> Vec<String> {
|
||||
vec![
|
||||
// Disable default browser updates
|
||||
@@ -1202,23 +1212,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_profiles_empty() {
|
||||
let (manager, _temp_dir) = create_test_profile_manager();
|
||||
|
||||
let result = manager.list_profiles();
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Should successfully list profiles even when empty"
|
||||
);
|
||||
|
||||
let profiles = result.unwrap();
|
||||
assert!(
|
||||
profiles.is_empty(),
|
||||
"Should return empty vector when no profiles exist"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_common_firefox_preferences() {
|
||||
let (manager, _temp_dir) = create_test_profile_manager();
|
||||
@@ -1324,7 +1317,139 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[tauri::command]
|
||||
pub async fn create_browser_profile_with_group(
|
||||
app_handle: tauri::AppHandle,
|
||||
name: String,
|
||||
browser: String,
|
||||
version: String,
|
||||
release_type: String,
|
||||
proxy_id: Option<String>,
|
||||
camoufox_config: Option<CamoufoxConfig>,
|
||||
group_id: Option<String>,
|
||||
) -> Result<BrowserProfile, String> {
|
||||
let profile_manager = ProfileManager::instance();
|
||||
profile_manager
|
||||
.create_profile_with_group(
|
||||
&app_handle,
|
||||
&name,
|
||||
&browser,
|
||||
&version,
|
||||
&release_type,
|
||||
proxy_id,
|
||||
camoufox_config,
|
||||
group_id,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to create profile: {e}"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn list_browser_profiles() -> Result<Vec<BrowserProfile>, String> {
|
||||
let profile_manager = ProfileManager::instance();
|
||||
profile_manager
|
||||
.list_profiles()
|
||||
.map_err(|e| format!("Failed to list profiles: {e}"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn update_profile_proxy(
|
||||
app_handle: tauri::AppHandle,
|
||||
profile_id: String,
|
||||
proxy_id: Option<String>,
|
||||
) -> Result<BrowserProfile, String> {
|
||||
let profile_manager = ProfileManager::instance();
|
||||
profile_manager
|
||||
.update_profile_proxy(app_handle, &profile_id, proxy_id)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to update profile: {e}"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn update_profile_tags(
|
||||
app_handle: tauri::AppHandle,
|
||||
profile_id: String,
|
||||
tags: Vec<String>,
|
||||
) -> Result<BrowserProfile, String> {
|
||||
let profile_manager = ProfileManager::instance();
|
||||
profile_manager
|
||||
.update_profile_tags(&app_handle, &profile_id, tags)
|
||||
.map_err(|e| format!("Failed to update profile tags: {e}"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn check_browser_status(
|
||||
app_handle: tauri::AppHandle,
|
||||
profile: BrowserProfile,
|
||||
) -> Result<bool, String> {
|
||||
let profile_manager = ProfileManager::instance();
|
||||
profile_manager
|
||||
.check_browser_status(app_handle, &profile)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to check browser status: {e}"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn rename_profile(
|
||||
app_handle: tauri::AppHandle,
|
||||
profile_id: String,
|
||||
new_name: String,
|
||||
) -> Result<BrowserProfile, String> {
|
||||
let profile_manager = ProfileManager::instance();
|
||||
profile_manager
|
||||
.rename_profile(&app_handle, &profile_id, &new_name)
|
||||
.map_err(|e| format!("Failed to rename profile: {e}"))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[tauri::command]
|
||||
pub async fn create_browser_profile_new(
|
||||
app_handle: tauri::AppHandle,
|
||||
name: String,
|
||||
browser_str: String,
|
||||
version: String,
|
||||
release_type: String,
|
||||
proxy_id: Option<String>,
|
||||
camoufox_config: Option<CamoufoxConfig>,
|
||||
group_id: Option<String>,
|
||||
) -> Result<BrowserProfile, String> {
|
||||
let browser_type =
|
||||
BrowserType::from_str(&browser_str).map_err(|e| format!("Invalid browser type: {e}"))?;
|
||||
create_browser_profile_with_group(
|
||||
app_handle,
|
||||
name,
|
||||
browser_type.as_str().to_string(),
|
||||
version,
|
||||
release_type,
|
||||
proxy_id,
|
||||
camoufox_config,
|
||||
group_id,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn update_camoufox_config(
|
||||
app_handle: tauri::AppHandle,
|
||||
profile_id: String,
|
||||
config: CamoufoxConfig,
|
||||
) -> Result<(), String> {
|
||||
let profile_manager = ProfileManager::instance();
|
||||
profile_manager
|
||||
.update_camoufox_config(app_handle, &profile_id, config)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to update Camoufox config: {e}"))
|
||||
}
|
||||
|
||||
// Global singleton instance
|
||||
#[tauri::command]
|
||||
pub fn delete_profile(app_handle: tauri::AppHandle, profile_id: String) -> Result<(), String> {
|
||||
ProfileManager::instance()
|
||||
.delete_profile(&app_handle, &profile_id)
|
||||
.map_err(|e| format!("Failed to delete profile: {e}"))
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref PROFILE_MANAGER: ProfileManager = ProfileManager::new();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::camoufox::CamoufoxConfig;
|
||||
use crate::camoufox_manager::CamoufoxConfig;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@ use std::fs::{self, create_dir_all};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::browser::BrowserType;
|
||||
use crate::browser_runner::BrowserRunner;
|
||||
use crate::downloaded_browsers_registry::DownloadedBrowsersRegistry;
|
||||
use crate::profile::ProfileManager;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct DetectedProfile {
|
||||
@@ -17,12 +18,16 @@ pub struct DetectedProfile {
|
||||
|
||||
pub struct ProfileImporter {
|
||||
base_dirs: BaseDirs,
|
||||
downloaded_browsers_registry: &'static DownloadedBrowsersRegistry,
|
||||
profile_manager: &'static ProfileManager,
|
||||
}
|
||||
|
||||
impl ProfileImporter {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
base_dirs: BaseDirs::new().expect("Failed to get base directories"),
|
||||
downloaded_browsers_registry: DownloadedBrowsersRegistry::instance(),
|
||||
profile_manager: ProfileManager::instance(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -520,7 +525,7 @@ impl ProfileImporter {
|
||||
.map_err(|_| format!("Invalid browser type: {browser_type}"))?;
|
||||
|
||||
// Check if a profile with this name already exists
|
||||
let existing_profiles = BrowserRunner::instance().list_profiles()?;
|
||||
let existing_profiles = self.profile_manager.list_profiles()?;
|
||||
if existing_profiles
|
||||
.iter()
|
||||
.any(|p| p.name.to_lowercase() == new_profile_name.to_lowercase())
|
||||
@@ -530,7 +535,7 @@ impl ProfileImporter {
|
||||
|
||||
// Generate UUID for new profile and create the directory structure
|
||||
let profile_id = uuid::Uuid::new_v4();
|
||||
let profiles_dir = BrowserRunner::instance().get_profiles_dir();
|
||||
let profiles_dir = self.profile_manager.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");
|
||||
|
||||
@@ -559,7 +564,7 @@ impl ProfileImporter {
|
||||
};
|
||||
|
||||
// Save the profile metadata
|
||||
BrowserRunner::instance().save_profile(&profile)?;
|
||||
self.profile_manager.save_profile(&profile)?;
|
||||
|
||||
println!(
|
||||
"Successfully imported profile '{}' from '{}'",
|
||||
@@ -576,8 +581,9 @@ 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::instance();
|
||||
let downloaded_versions = registry.get_downloaded_versions(browser_type);
|
||||
let downloaded_versions = self
|
||||
.downloaded_browsers_registry
|
||||
.get_downloaded_versions(browser_type);
|
||||
|
||||
if let Some(version) = downloaded_versions.first() {
|
||||
return Ok(version.clone());
|
||||
|
||||
@@ -3,9 +3,6 @@ use serde::{Deserialize, Serialize};
|
||||
use std::fs::{self, create_dir_all};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::api_client::ApiClient;
|
||||
use crate::version_updater;
|
||||
|
||||
use aes_gcm::{
|
||||
aead::{Aead, AeadCore, KeyInit, OsRng},
|
||||
Aes256Gcm, Key, Nonce,
|
||||
@@ -442,54 +439,6 @@ pub async fn save_table_sorting_settings(sorting: TableSortingSettings) -> Resul
|
||||
.map_err(|e| format!("Failed to save table sorting settings: {e}"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn clear_all_version_cache_and_refetch(
|
||||
app_handle: tauri::AppHandle,
|
||||
) -> Result<(), String> {
|
||||
let api_client = ApiClient::instance();
|
||||
|
||||
// Clear all cache first
|
||||
api_client
|
||||
.clear_all_cache()
|
||||
.map_err(|e| format!("Failed to clear version cache: {e}"))?;
|
||||
|
||||
// Disable all browsers during the update process
|
||||
let auto_updater = crate::auto_updater::AutoUpdater::instance();
|
||||
let supported_browsers =
|
||||
crate::browser_version_manager::BrowserVersionManager::instance().get_supported_browsers();
|
||||
|
||||
// Load current state and disable all browsers
|
||||
let mut state = auto_updater
|
||||
.load_auto_update_state()
|
||||
.map_err(|e| format!("Failed to load auto update state: {e}"))?;
|
||||
for browser in &supported_browsers {
|
||||
state.disabled_browsers.insert(browser.clone());
|
||||
}
|
||||
auto_updater
|
||||
.save_auto_update_state(&state)
|
||||
.map_err(|e| format!("Failed to save auto update state: {e}"))?;
|
||||
|
||||
let updater = version_updater::get_version_updater();
|
||||
let updater_guard = updater.lock().await;
|
||||
|
||||
let result = updater_guard
|
||||
.trigger_manual_update(&app_handle)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to trigger version update: {e}"));
|
||||
|
||||
// Re-enable all browsers after the update completes (regardless of success/failure)
|
||||
let mut final_state = auto_updater.load_auto_update_state().unwrap_or_default();
|
||||
for browser in &supported_browsers {
|
||||
final_state.disabled_browsers.remove(browser);
|
||||
}
|
||||
if let Err(e) = auto_updater.save_auto_update_state(&final_state) {
|
||||
eprintln!("Warning: Failed to re-enable browsers after cache clear: {e}");
|
||||
}
|
||||
|
||||
result?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Global singleton instance
|
||||
lazy_static::lazy_static! {
|
||||
static ref SETTINGS_MANAGER: SettingsManager = SettingsManager::new();
|
||||
|
||||
@@ -101,6 +101,14 @@ impl TagManager {
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_all_tags() -> Result<Vec<String>, String> {
|
||||
let tag_manager = crate::tag_manager::TAG_MANAGER.lock().unwrap();
|
||||
tag_manager
|
||||
.get_all_tags()
|
||||
.map_err(|e| format!("Failed to get tags: {e}"))
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref TAG_MANAGER: std::sync::Mutex<TagManager> = std::sync::Mutex::new(TagManager::new());
|
||||
}
|
||||
|
||||
@@ -46,8 +46,9 @@ impl Default for BackgroundUpdateState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension of auto_updater.rs for background updates
|
||||
pub struct VersionUpdater {
|
||||
version_service: &'static BrowserVersionManager,
|
||||
browser_version_manager: &'static BrowserVersionManager,
|
||||
auto_updater: &'static AutoUpdater,
|
||||
app_handle: Option<tauri::AppHandle>,
|
||||
}
|
||||
@@ -55,7 +56,7 @@ pub struct VersionUpdater {
|
||||
impl VersionUpdater {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
version_service: BrowserVersionManager::instance(),
|
||||
browser_version_manager: BrowserVersionManager::instance(),
|
||||
auto_updater: AutoUpdater::instance(),
|
||||
app_handle: None,
|
||||
}
|
||||
@@ -263,7 +264,7 @@ impl VersionUpdater {
|
||||
&self,
|
||||
app_handle: &tauri::AppHandle,
|
||||
) -> Result<Vec<BackgroundUpdateResult>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let supported_browsers = self.version_service.get_supported_browsers();
|
||||
let supported_browsers = self.browser_version_manager.get_supported_browsers();
|
||||
let total_browsers = supported_browsers.len();
|
||||
let mut results = Vec::new();
|
||||
let mut total_new_versions = 0;
|
||||
@@ -374,7 +375,7 @@ impl VersionUpdater {
|
||||
browser: &str,
|
||||
) -> Result<usize, Box<dyn std::error::Error + Send + Sync>> {
|
||||
self
|
||||
.version_service
|
||||
.browser_version_manager
|
||||
.update_browser_versions_incrementally(browser)
|
||||
.await
|
||||
}
|
||||
@@ -455,6 +456,63 @@ pub async fn get_version_update_status() -> Result<(Option<u64>, u64), String> {
|
||||
Ok((last_update, time_until_next))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn clear_all_version_cache_and_refetch(
|
||||
app_handle: tauri::AppHandle,
|
||||
) -> Result<(), String> {
|
||||
let api_client = crate::api_client::ApiClient::instance();
|
||||
let version_updater = VersionUpdater::new();
|
||||
|
||||
// Clear all cache first
|
||||
api_client
|
||||
.clear_all_cache()
|
||||
.map_err(|e| format!("Failed to clear version cache: {e}"))?;
|
||||
|
||||
// Disable all browsers during the update process
|
||||
let supported_browsers = version_updater
|
||||
.browser_version_manager
|
||||
.get_supported_browsers();
|
||||
|
||||
// Load current state and disable all browsers
|
||||
let mut state = version_updater
|
||||
.auto_updater
|
||||
.load_auto_update_state()
|
||||
.map_err(|e| format!("Failed to load auto update state: {e}"))?;
|
||||
for browser in &supported_browsers {
|
||||
state.disabled_browsers.insert(browser.clone());
|
||||
}
|
||||
version_updater
|
||||
.auto_updater
|
||||
.save_auto_update_state(&state)
|
||||
.map_err(|e| format!("Failed to save auto update state: {e}"))?;
|
||||
|
||||
let updater = get_version_updater();
|
||||
let updater_guard = updater.lock().await;
|
||||
|
||||
let result = updater_guard
|
||||
.trigger_manual_update(&app_handle)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to trigger version update: {e}"));
|
||||
|
||||
// Re-enable all browsers after the update completes (regardless of success/failure)
|
||||
let mut final_state = version_updater
|
||||
.auto_updater
|
||||
.load_auto_update_state()
|
||||
.unwrap_or_default();
|
||||
for browser in &supported_browsers {
|
||||
final_state.disabled_browsers.remove(browser);
|
||||
}
|
||||
if let Err(e) = version_updater
|
||||
.auto_updater
|
||||
.save_auto_update_state(&final_state)
|
||||
{
|
||||
eprintln!("Warning: Failed to re-enable browsers after cache clear: {e}");
|
||||
}
|
||||
|
||||
result?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -607,7 +665,10 @@ mod tests {
|
||||
|
||||
// Should have valid references to services
|
||||
assert!(
|
||||
!std::ptr::eq(updater.version_service as *const _, std::ptr::null()),
|
||||
!std::ptr::eq(
|
||||
updater.browser_version_manager as *const _,
|
||||
std::ptr::null()
|
||||
),
|
||||
"Version service should not be null"
|
||||
);
|
||||
assert!(
|
||||
|
||||
Reference in New Issue
Block a user