mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-09-23 09:50:58 +02:00
fix: linter and formatting
This commit is contained in:
+294
-153
@@ -1,10 +1,10 @@
|
||||
use directories::BaseDirs;
|
||||
use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use directories::BaseDirs;
|
||||
|
||||
use crate::browser::GithubRelease;
|
||||
|
||||
@@ -34,7 +34,7 @@ enum PreReleaseKind {
|
||||
impl VersionComponent {
|
||||
fn parse(version: &str) -> Self {
|
||||
let version = version.trim();
|
||||
|
||||
|
||||
// Handle special case for Zen Browser twilight releases
|
||||
if version.to_lowercase().contains("twilight") {
|
||||
return VersionComponent {
|
||||
@@ -47,20 +47,22 @@ impl VersionComponent {
|
||||
|
||||
// Split version into numeric and pre-release parts
|
||||
let (numeric_part, pre_release_part) = Self::split_version(version);
|
||||
|
||||
|
||||
// Parse numeric parts (major.minor.patch)
|
||||
let parts: Vec<u32> = numeric_part
|
||||
.split('.')
|
||||
.filter_map(|part| part.parse().ok())
|
||||
.collect();
|
||||
|
||||
|
||||
let major = parts.get(0).copied().unwrap_or(0);
|
||||
let minor = parts.get(1).copied().unwrap_or(0);
|
||||
let patch = parts.get(2).copied().unwrap_or(0);
|
||||
|
||||
|
||||
// Parse pre-release part
|
||||
let pre_release = pre_release_part.as_deref().and_then(Self::parse_pre_release);
|
||||
|
||||
let pre_release = pre_release_part
|
||||
.as_deref()
|
||||
.and_then(Self::parse_pre_release);
|
||||
|
||||
VersionComponent {
|
||||
major,
|
||||
minor,
|
||||
@@ -68,39 +70,49 @@ impl VersionComponent {
|
||||
pre_release,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn split_version(version: &str) -> (String, Option<String>) {
|
||||
let version = version.to_lowercase();
|
||||
|
||||
|
||||
// Look for pre-release indicators
|
||||
for (i, ch) in version.char_indices() {
|
||||
if ch.is_alphabetic() && i > 0 {
|
||||
// Check if this is a pre-release indicator
|
||||
let remaining = &version[i..];
|
||||
if remaining.starts_with('a') || remaining.starts_with('b') ||
|
||||
remaining.starts_with("alpha") || remaining.starts_with("beta") ||
|
||||
remaining.starts_with("rc") || remaining.starts_with("dev") ||
|
||||
remaining.starts_with("pre") {
|
||||
if remaining.starts_with('a')
|
||||
|| remaining.starts_with('b')
|
||||
|| remaining.starts_with("alpha")
|
||||
|| remaining.starts_with("beta")
|
||||
|| remaining.starts_with("rc")
|
||||
|| remaining.starts_with("dev")
|
||||
|| remaining.starts_with("pre")
|
||||
{
|
||||
return (version[..i].to_string(), Some(remaining.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
(version, None)
|
||||
}
|
||||
|
||||
|
||||
fn parse_pre_release(pre_release: &str) -> Option<PreRelease> {
|
||||
let pre_release = pre_release.trim().to_lowercase();
|
||||
|
||||
|
||||
if pre_release.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
||||
// Extract kind and number
|
||||
let (kind, number) = if pre_release.starts_with("alpha") {
|
||||
(PreReleaseKind::Alpha, Self::extract_number(&pre_release[5..]))
|
||||
(
|
||||
PreReleaseKind::Alpha,
|
||||
Self::extract_number(&pre_release[5..]),
|
||||
)
|
||||
} else if pre_release.starts_with("beta") {
|
||||
(PreReleaseKind::Beta, Self::extract_number(&pre_release[4..]))
|
||||
(
|
||||
PreReleaseKind::Beta,
|
||||
Self::extract_number(&pre_release[4..]),
|
||||
)
|
||||
} else if pre_release.starts_with("rc") {
|
||||
(PreReleaseKind::RC, Self::extract_number(&pre_release[2..]))
|
||||
} else if pre_release.starts_with("dev") {
|
||||
@@ -108,16 +120,22 @@ impl VersionComponent {
|
||||
} else if pre_release.starts_with("pre") {
|
||||
(PreReleaseKind::Pre, Self::extract_number(&pre_release[3..]))
|
||||
} else if pre_release.starts_with('a') {
|
||||
(PreReleaseKind::Alpha, Self::extract_number(&pre_release[1..]))
|
||||
(
|
||||
PreReleaseKind::Alpha,
|
||||
Self::extract_number(&pre_release[1..]),
|
||||
)
|
||||
} else if pre_release.starts_with('b') {
|
||||
(PreReleaseKind::Beta, Self::extract_number(&pre_release[1..]))
|
||||
(
|
||||
PreReleaseKind::Beta,
|
||||
Self::extract_number(&pre_release[1..]),
|
||||
)
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
|
||||
Some(PreRelease { kind, number })
|
||||
}
|
||||
|
||||
|
||||
fn extract_number(s: &str) -> Option<u32> {
|
||||
let numeric_part: String = s.chars().filter(|c| c.is_ascii_digit()).collect();
|
||||
numeric_part.parse().ok()
|
||||
@@ -133,7 +151,7 @@ impl PartialOrd for VersionComponent {
|
||||
impl Ord for VersionComponent {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
use std::cmp::Ordering;
|
||||
|
||||
|
||||
// Compare major.minor.patch first
|
||||
match (self.major, self.minor, self.patch).cmp(&(other.major, other.minor, other.patch)) {
|
||||
Ordering::Equal => {
|
||||
@@ -182,8 +200,6 @@ pub fn sort_github_releases(releases: &mut [GithubRelease]) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub fn is_alpha_version(version: &str) -> bool {
|
||||
let version_comp = VersionComponent::parse(version);
|
||||
version_comp.pre_release.is_some()
|
||||
@@ -264,14 +280,14 @@ impl ApiClient {
|
||||
pub fn load_cached_versions(&self, browser: &str) -> Option<Vec<String>> {
|
||||
let cache_dir = Self::get_cache_dir().ok()?;
|
||||
let cache_file = cache_dir.join(format!("{}_versions.json", browser));
|
||||
|
||||
|
||||
if !cache_file.exists() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let content = fs::read_to_string(&cache_file).ok()?;
|
||||
let cached_data: CachedVersionData = serde_json::from_str(&content).ok()?;
|
||||
|
||||
|
||||
// Always return cached versions regardless of age - they're always valid
|
||||
println!("Using cached versions for {}", browser);
|
||||
Some(cached_data.versions)
|
||||
@@ -283,7 +299,7 @@ impl ApiClient {
|
||||
Err(_) => return true, // If we can't get cache dir, consider expired
|
||||
};
|
||||
let cache_file = cache_dir.join(format!("{}_versions.json", browser));
|
||||
|
||||
|
||||
if !cache_file.exists() {
|
||||
return true; // No cache file means expired
|
||||
}
|
||||
@@ -297,20 +313,24 @@ impl ApiClient {
|
||||
Ok(data) => data,
|
||||
Err(_) => return true, // Can't parse cache, consider expired
|
||||
};
|
||||
|
||||
|
||||
// Check if cache is older than 10 minutes
|
||||
!Self::is_cache_valid(cached_data.timestamp)
|
||||
}
|
||||
|
||||
pub fn save_cached_versions(&self, browser: &str, versions: &[String]) -> Result<(), Box<dyn std::error::Error>> {
|
||||
pub fn save_cached_versions(
|
||||
&self,
|
||||
browser: &str,
|
||||
versions: &[String],
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let cache_dir = Self::get_cache_dir()?;
|
||||
let cache_file = cache_dir.join(format!("{}_versions.json", browser));
|
||||
|
||||
|
||||
let cached_data = CachedVersionData {
|
||||
versions: versions.to_vec(),
|
||||
timestamp: Self::get_current_timestamp(),
|
||||
};
|
||||
|
||||
|
||||
let content = serde_json::to_string_pretty(&cached_data)?;
|
||||
fs::write(&cache_file, content)?;
|
||||
println!("Cached {} versions for {}", versions.len(), browser);
|
||||
@@ -320,56 +340,69 @@ impl ApiClient {
|
||||
fn load_cached_github_releases(&self, browser: &str) -> Option<Vec<GithubRelease>> {
|
||||
let cache_dir = Self::get_cache_dir().ok()?;
|
||||
let cache_file = cache_dir.join(format!("{}_github.json", browser));
|
||||
|
||||
|
||||
if !cache_file.exists() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let content = fs::read_to_string(&cache_file).ok()?;
|
||||
let cached_data: CachedGithubData = serde_json::from_str(&content).ok()?;
|
||||
|
||||
|
||||
// Always use cached GitHub releases - cache never expires, only gets updated with new versions
|
||||
println!("Using cached GitHub releases for {}", browser);
|
||||
Some(cached_data.releases)
|
||||
}
|
||||
|
||||
fn save_cached_github_releases(&self, browser: &str, releases: &[GithubRelease]) -> Result<(), Box<dyn std::error::Error>> {
|
||||
fn save_cached_github_releases(
|
||||
&self,
|
||||
browser: &str,
|
||||
releases: &[GithubRelease],
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let cache_dir = Self::get_cache_dir()?;
|
||||
let cache_file = cache_dir.join(format!("{}_github.json", browser));
|
||||
|
||||
|
||||
let cached_data = CachedGithubData {
|
||||
releases: releases.to_vec(),
|
||||
timestamp: Self::get_current_timestamp(),
|
||||
};
|
||||
|
||||
|
||||
let content = serde_json::to_string_pretty(&cached_data)?;
|
||||
fs::write(&cache_file, content)?;
|
||||
println!("Cached {} GitHub releases for {}", releases.len(), browser);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn fetch_firefox_releases_with_caching(&self, no_caching: bool) -> Result<Vec<BrowserRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
pub async fn fetch_firefox_releases_with_caching(
|
||||
&self,
|
||||
no_caching: bool,
|
||||
) -> Result<Vec<BrowserRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
// Check cache first (unless bypassing)
|
||||
if !no_caching {
|
||||
if let Some(cached_versions) = self.load_cached_versions("firefox") {
|
||||
return Ok(cached_versions.into_iter().map(|version| {
|
||||
BrowserRelease {
|
||||
version: version.clone(),
|
||||
date: "".to_string(), // Cache doesn't store dates
|
||||
is_prerelease: is_alpha_version(&version),
|
||||
download_url: Some(format!(
|
||||
"https://download.mozilla.org/?product=firefox-{}&os=osx&lang=en-US",
|
||||
version
|
||||
)),
|
||||
}
|
||||
}).collect());
|
||||
return Ok(
|
||||
cached_versions
|
||||
.into_iter()
|
||||
.map(|version| {
|
||||
BrowserRelease {
|
||||
version: version.clone(),
|
||||
date: "".to_string(), // Cache doesn't store dates
|
||||
is_prerelease: is_alpha_version(&version),
|
||||
download_url: Some(format!(
|
||||
"https://download.mozilla.org/?product=firefox-{}&os=osx&lang=en-US",
|
||||
version
|
||||
)),
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
println!("Fetching Firefox releases from Mozilla API...");
|
||||
let url = "https://product-details.mozilla.org/1.0/firefox.json";
|
||||
|
||||
let response = self.client
|
||||
|
||||
let response = self
|
||||
.client
|
||||
.get(url)
|
||||
.header("User-Agent", "donutbrowser")
|
||||
.send()
|
||||
@@ -380,7 +413,7 @@ impl ApiClient {
|
||||
}
|
||||
|
||||
let firefox_response: FirefoxApiResponse = response.json().await?;
|
||||
|
||||
|
||||
// Extract releases and filter for stable versions
|
||||
let mut releases: Vec<BrowserRelease> = firefox_response
|
||||
.releases
|
||||
@@ -413,7 +446,7 @@ impl ApiClient {
|
||||
|
||||
// Extract versions for caching
|
||||
let versions: Vec<String> = releases.iter().map(|r| r.version.clone()).collect();
|
||||
|
||||
|
||||
// Cache the results (unless bypassing cache)
|
||||
if !no_caching {
|
||||
if let Err(e) = self.save_cached_versions("firefox", &versions) {
|
||||
@@ -424,35 +457,50 @@ impl ApiClient {
|
||||
Ok(releases)
|
||||
}
|
||||
|
||||
pub async fn fetch_firefox_developer_releases_with_caching(&self, no_caching: bool) -> Result<Vec<BrowserRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
pub async fn fetch_firefox_developer_releases_with_caching(
|
||||
&self,
|
||||
no_caching: bool,
|
||||
) -> Result<Vec<BrowserRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
// Check cache first (unless bypassing)
|
||||
if !no_caching {
|
||||
if let Some(cached_versions) = self.load_cached_versions("firefox-developer") {
|
||||
return Ok(cached_versions.into_iter().map(|version| {
|
||||
BrowserRelease {
|
||||
version: version.clone(),
|
||||
date: "".to_string(), // Cache doesn't store dates
|
||||
is_prerelease: is_alpha_version(&version),
|
||||
download_url: Some(format!(
|
||||
"https://download.mozilla.org/?product=devedition-{}&os=osx&lang=en-US",
|
||||
version
|
||||
)),
|
||||
}
|
||||
}).collect());
|
||||
return Ok(
|
||||
cached_versions
|
||||
.into_iter()
|
||||
.map(|version| {
|
||||
BrowserRelease {
|
||||
version: version.clone(),
|
||||
date: "".to_string(), // Cache doesn't store dates
|
||||
is_prerelease: is_alpha_version(&version),
|
||||
download_url: Some(format!(
|
||||
"https://download.mozilla.org/?product=devedition-{}&os=osx&lang=en-US",
|
||||
version
|
||||
)),
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
println!("Fetching Firefox Developer Edition releases from Mozilla API...");
|
||||
let url = "https://product-details.mozilla.org/1.0/devedition.json";
|
||||
|
||||
let response = self.client
|
||||
|
||||
let response = self
|
||||
.client
|
||||
.get(url)
|
||||
.header("User-Agent", "donutbrowser")
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(format!("Failed to fetch Firefox Developer Edition versions: {}", response.status()).into());
|
||||
return Err(
|
||||
format!(
|
||||
"Failed to fetch Firefox Developer Edition versions: {}",
|
||||
response.status()
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
let firefox_response: FirefoxApiResponse = response.json().await?;
|
||||
@@ -489,7 +537,7 @@ impl ApiClient {
|
||||
|
||||
// Extract versions for caching
|
||||
let versions: Vec<String> = releases.iter().map(|r| r.version.clone()).collect();
|
||||
|
||||
|
||||
// Cache the results (unless bypassing cache)
|
||||
if !no_caching {
|
||||
if let Err(e) = self.save_cached_versions("firefox-developer", &versions) {
|
||||
@@ -500,11 +548,16 @@ impl ApiClient {
|
||||
Ok(releases)
|
||||
}
|
||||
|
||||
pub async fn fetch_mullvad_releases(&self) -> Result<Vec<GithubRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
pub async fn fetch_mullvad_releases(
|
||||
&self,
|
||||
) -> Result<Vec<GithubRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
self.fetch_mullvad_releases_with_caching(false).await
|
||||
}
|
||||
|
||||
pub async fn fetch_mullvad_releases_with_caching(&self, no_caching: bool) -> Result<Vec<GithubRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
pub async fn fetch_mullvad_releases_with_caching(
|
||||
&self,
|
||||
no_caching: bool,
|
||||
) -> Result<Vec<GithubRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
// Check cache first (unless bypassing)
|
||||
if !no_caching {
|
||||
if let Some(cached_releases) = self.load_cached_github_releases("mullvad") {
|
||||
@@ -544,11 +597,16 @@ impl ApiClient {
|
||||
Ok(releases)
|
||||
}
|
||||
|
||||
pub async fn fetch_zen_releases(&self) -> Result<Vec<GithubRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
pub async fn fetch_zen_releases(
|
||||
&self,
|
||||
) -> Result<Vec<GithubRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
self.fetch_zen_releases_with_caching(false).await
|
||||
}
|
||||
|
||||
pub async fn fetch_zen_releases_with_caching(&self, no_caching: bool) -> Result<Vec<GithubRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
pub async fn fetch_zen_releases_with_caching(
|
||||
&self,
|
||||
no_caching: bool,
|
||||
) -> Result<Vec<GithubRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
// Check cache first (unless bypassing)
|
||||
if !no_caching {
|
||||
if let Some(cached_releases) = self.load_cached_github_releases("zen") {
|
||||
@@ -580,11 +638,16 @@ impl ApiClient {
|
||||
Ok(releases)
|
||||
}
|
||||
|
||||
pub async fn fetch_brave_releases(&self) -> Result<Vec<GithubRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
pub async fn fetch_brave_releases(
|
||||
&self,
|
||||
) -> Result<Vec<GithubRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
self.fetch_brave_releases_with_caching(false).await
|
||||
}
|
||||
|
||||
pub async fn fetch_brave_releases_with_caching(&self, no_caching: bool) -> Result<Vec<GithubRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
pub async fn fetch_brave_releases_with_caching(
|
||||
&self,
|
||||
no_caching: bool,
|
||||
) -> Result<Vec<GithubRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
// Check cache first (unless bypassing)
|
||||
if !no_caching {
|
||||
if let Some(cached_releases) = self.load_cached_github_releases("brave") {
|
||||
@@ -608,10 +671,11 @@ impl ApiClient {
|
||||
.into_iter()
|
||||
.filter_map(|mut release| {
|
||||
// Check if this release has a universal DMG asset
|
||||
let has_universal_dmg = release.assets.iter().any(|asset| {
|
||||
asset.name.contains(".dmg") && asset.name.contains("universal")
|
||||
});
|
||||
|
||||
let has_universal_dmg = release
|
||||
.assets
|
||||
.iter()
|
||||
.any(|asset| asset.name.contains(".dmg") && asset.name.contains("universal"));
|
||||
|
||||
if has_universal_dmg {
|
||||
// Set is_alpha based on the release name
|
||||
// Nightly releases contain "Nightly", stable contain "Release"
|
||||
@@ -636,10 +700,19 @@ impl ApiClient {
|
||||
Ok(filtered_releases)
|
||||
}
|
||||
|
||||
pub async fn fetch_chromium_latest_version(&self) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
|
||||
pub async fn fetch_chromium_latest_version(
|
||||
&self,
|
||||
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
|
||||
// Use architecture-aware URL for Chromium
|
||||
let arch = if cfg!(target_arch = "aarch64") { "Mac_Arm" } else { "Mac" };
|
||||
let url = format!("https://commondatastorage.googleapis.com/chromium-browser-snapshots/{}/LAST_CHANGE", arch);
|
||||
let arch = if cfg!(target_arch = "aarch64") {
|
||||
"Mac_Arm"
|
||||
} else {
|
||||
"Mac"
|
||||
};
|
||||
let url = format!(
|
||||
"https://commondatastorage.googleapis.com/chromium-browser-snapshots/{}/LAST_CHANGE",
|
||||
arch
|
||||
);
|
||||
let version = self
|
||||
.client
|
||||
.get(&url)
|
||||
@@ -654,27 +727,35 @@ impl ApiClient {
|
||||
Ok(version)
|
||||
}
|
||||
|
||||
pub async fn fetch_chromium_releases_with_caching(&self, no_caching: bool) -> Result<Vec<BrowserRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
pub async fn fetch_chromium_releases_with_caching(
|
||||
&self,
|
||||
no_caching: bool,
|
||||
) -> Result<Vec<BrowserRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
// Check cache first (unless bypassing)
|
||||
if !no_caching {
|
||||
if let Some(cached_versions) = self.load_cached_versions("chromium") {
|
||||
return Ok(cached_versions.into_iter().map(|version| {
|
||||
BrowserRelease {
|
||||
version: version.clone(),
|
||||
date: "".to_string(), // Cache doesn't store dates
|
||||
is_prerelease: false, // Chromium versions are generally stable builds
|
||||
download_url: None,
|
||||
}
|
||||
}).collect());
|
||||
return Ok(
|
||||
cached_versions
|
||||
.into_iter()
|
||||
.map(|version| {
|
||||
BrowserRelease {
|
||||
version: version.clone(),
|
||||
date: "".to_string(), // Cache doesn't store dates
|
||||
is_prerelease: false, // Chromium versions are generally stable builds
|
||||
download_url: None,
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
println!("Fetching Chromium releases...");
|
||||
|
||||
|
||||
// Get the latest version first
|
||||
let latest_version = self.fetch_chromium_latest_version().await?;
|
||||
let latest_num: u32 = latest_version.parse().unwrap_or(0);
|
||||
|
||||
|
||||
// Generate a list of recent versions (last 20 builds, going back by 1000 each time)
|
||||
let mut versions = Vec::new();
|
||||
for i in 0..20 {
|
||||
@@ -683,7 +764,7 @@ impl ApiClient {
|
||||
versions.push(version_num.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Cache the results (unless bypassing cache)
|
||||
if !no_caching {
|
||||
if let Err(e) = self.save_cached_versions("chromium", &versions) {
|
||||
@@ -691,17 +772,23 @@ impl ApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(versions.into_iter().map(|version| {
|
||||
BrowserRelease {
|
||||
version: version.clone(),
|
||||
date: "".to_string(),
|
||||
is_prerelease: false,
|
||||
download_url: None,
|
||||
}
|
||||
}).collect())
|
||||
Ok(
|
||||
versions
|
||||
.into_iter()
|
||||
.map(|version| BrowserRelease {
|
||||
version: version.clone(),
|
||||
date: "".to_string(),
|
||||
is_prerelease: false,
|
||||
download_url: None,
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn fetch_tor_releases_with_caching(&self, no_caching: bool) -> Result<Vec<BrowserRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
pub async fn fetch_tor_releases_with_caching(
|
||||
&self,
|
||||
no_caching: bool,
|
||||
) -> Result<Vec<BrowserRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
// Check cache first (unless bypassing)
|
||||
if !no_caching {
|
||||
if let Some(cached_versions) = self.load_cached_versions("tor-browser") {
|
||||
@@ -732,7 +819,7 @@ impl ApiClient {
|
||||
|
||||
// Parse HTML to extract version directories
|
||||
let mut version_candidates = Vec::new();
|
||||
|
||||
|
||||
// Look for directory links in the HTML
|
||||
for line in html.lines() {
|
||||
if line.contains("<a href=\"") && line.contains("/\">") {
|
||||
@@ -741,9 +828,12 @@ impl ApiClient {
|
||||
let start = start + 9; // Length of "<a href=\""
|
||||
if let Some(end) = line[start..].find("/\">") {
|
||||
let version = &line[start..start + end];
|
||||
|
||||
|
||||
// Skip parent directory and non-version entries
|
||||
if version != ".." && !version.is_empty() && version.chars().next().unwrap_or('a').is_ascii_digit() {
|
||||
if version != ".."
|
||||
&& !version.is_empty()
|
||||
&& version.chars().next().unwrap_or('a').is_ascii_digit()
|
||||
{
|
||||
version_candidates.push(version.to_string());
|
||||
}
|
||||
}
|
||||
@@ -763,7 +853,7 @@ impl ApiClient {
|
||||
version_strings.push(version);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Add a small delay to avoid overwhelming the server
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
|
||||
}
|
||||
@@ -788,8 +878,14 @@ impl ApiClient {
|
||||
}).collect())
|
||||
}
|
||||
|
||||
async fn check_tor_version_has_macos(&self, version: &str) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let url = format!("https://archive.torproject.org/tor-package-archive/torbrowser/{}/", version);
|
||||
async fn check_tor_version_has_macos(
|
||||
&self,
|
||||
version: &str,
|
||||
) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let url = format!(
|
||||
"https://archive.torproject.org/tor-package-archive/torbrowser/{}/",
|
||||
version
|
||||
);
|
||||
let html = self
|
||||
.client
|
||||
.get(&url)
|
||||
@@ -908,16 +1004,22 @@ mod tests {
|
||||
async fn test_firefox_api() {
|
||||
let client = ApiClient::new();
|
||||
let result = client.fetch_firefox_releases_with_caching(false).await;
|
||||
|
||||
|
||||
match result {
|
||||
Ok(releases) => {
|
||||
assert!(!releases.is_empty(), "Should have Firefox releases");
|
||||
|
||||
|
||||
// Check that releases have required fields
|
||||
let first_release = &releases[0];
|
||||
assert!(!first_release.version.is_empty(), "Version should not be empty");
|
||||
assert!(first_release.download_url.is_some(), "Should have download URL");
|
||||
|
||||
assert!(
|
||||
!first_release.version.is_empty(),
|
||||
"Version should not be empty"
|
||||
);
|
||||
assert!(
|
||||
first_release.download_url.is_some(),
|
||||
"Should have download URL"
|
||||
);
|
||||
|
||||
println!("Firefox API test passed. Found {} releases", releases.len());
|
||||
println!("Latest version: {}", first_release.version);
|
||||
}
|
||||
@@ -931,19 +1033,33 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_firefox_developer_api() {
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; // Rate limiting
|
||||
|
||||
|
||||
let client = ApiClient::new();
|
||||
let result = client.fetch_firefox_developer_releases_with_caching(false).await;
|
||||
|
||||
let result = client
|
||||
.fetch_firefox_developer_releases_with_caching(false)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(releases) => {
|
||||
assert!(!releases.is_empty(), "Should have Firefox Developer releases");
|
||||
|
||||
assert!(
|
||||
!releases.is_empty(),
|
||||
"Should have Firefox Developer releases"
|
||||
);
|
||||
|
||||
let first_release = &releases[0];
|
||||
assert!(!first_release.version.is_empty(), "Version should not be empty");
|
||||
assert!(first_release.download_url.is_some(), "Should have download URL");
|
||||
|
||||
println!("Firefox Developer API test passed. Found {} releases", releases.len());
|
||||
assert!(
|
||||
!first_release.version.is_empty(),
|
||||
"Version should not be empty"
|
||||
);
|
||||
assert!(
|
||||
first_release.download_url.is_some(),
|
||||
"Should have download URL"
|
||||
);
|
||||
|
||||
println!(
|
||||
"Firefox Developer API test passed. Found {} releases",
|
||||
releases.len()
|
||||
);
|
||||
println!("Latest version: {}", first_release.version);
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -956,17 +1072,20 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_mullvad_api() {
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await; // Rate limiting
|
||||
|
||||
|
||||
let client = ApiClient::new();
|
||||
let result = client.fetch_mullvad_releases().await;
|
||||
|
||||
|
||||
match result {
|
||||
Ok(releases) => {
|
||||
assert!(!releases.is_empty(), "Should have Mullvad releases");
|
||||
|
||||
|
||||
let first_release = &releases[0];
|
||||
assert!(!first_release.tag_name.is_empty(), "Tag name should not be empty");
|
||||
|
||||
assert!(
|
||||
!first_release.tag_name.is_empty(),
|
||||
"Tag name should not be empty"
|
||||
);
|
||||
|
||||
println!("Mullvad API test passed. Found {} releases", releases.len());
|
||||
println!("Latest version: {}", first_release.tag_name);
|
||||
}
|
||||
@@ -980,17 +1099,20 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_zen_api() {
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(1500)).await; // Rate limiting
|
||||
|
||||
|
||||
let client = ApiClient::new();
|
||||
let result = client.fetch_zen_releases().await;
|
||||
|
||||
|
||||
match result {
|
||||
Ok(releases) => {
|
||||
assert!(!releases.is_empty(), "Should have Zen releases");
|
||||
|
||||
|
||||
let first_release = &releases[0];
|
||||
assert!(!first_release.tag_name.is_empty(), "Tag name should not be empty");
|
||||
|
||||
assert!(
|
||||
!first_release.tag_name.is_empty(),
|
||||
"Tag name should not be empty"
|
||||
);
|
||||
|
||||
println!("Zen API test passed. Found {} releases", releases.len());
|
||||
println!("Latest version: {}", first_release.tag_name);
|
||||
}
|
||||
@@ -1004,14 +1126,17 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_brave_api() {
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(2000)).await; // Rate limiting
|
||||
|
||||
|
||||
let client = ApiClient::new();
|
||||
let result = client.fetch_brave_releases().await;
|
||||
|
||||
|
||||
match result {
|
||||
Ok(releases) => {
|
||||
// Note: Brave might not always have macOS releases, so we don't assert non-empty
|
||||
println!("Brave API test passed. Found {} releases with macOS assets", releases.len());
|
||||
println!(
|
||||
"Brave API test passed. Found {} releases with macOS assets",
|
||||
releases.len()
|
||||
);
|
||||
if !releases.is_empty() {
|
||||
println!("Latest version: {}", releases[0].tag_name);
|
||||
}
|
||||
@@ -1026,15 +1151,18 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_chromium_api() {
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(2500)).await; // Rate limiting
|
||||
|
||||
|
||||
let client = ApiClient::new();
|
||||
let result = client.fetch_chromium_latest_version().await;
|
||||
|
||||
|
||||
match result {
|
||||
Ok(version) => {
|
||||
assert!(!version.is_empty(), "Version should not be empty");
|
||||
assert!(version.chars().all(|c| c.is_ascii_digit()), "Version should be numeric");
|
||||
|
||||
assert!(
|
||||
version.chars().all(|c| c.is_ascii_digit()),
|
||||
"Version should be numeric"
|
||||
);
|
||||
|
||||
println!("Chromium API test passed. Latest version: {}", version);
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -1047,21 +1175,31 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_tor_api() {
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(3000)).await; // Rate limiting
|
||||
|
||||
|
||||
let client = ApiClient::new();
|
||||
|
||||
|
||||
// Use a timeout for this test since TOR API can be slow
|
||||
let timeout_duration = tokio::time::Duration::from_secs(30);
|
||||
let result = tokio::time::timeout(timeout_duration, client.fetch_tor_releases_with_caching(false)).await;
|
||||
|
||||
let result = tokio::time::timeout(
|
||||
timeout_duration,
|
||||
client.fetch_tor_releases_with_caching(false),
|
||||
)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(Ok(releases)) => {
|
||||
assert!(!releases.is_empty(), "Should have TOR releases");
|
||||
|
||||
|
||||
let first_release = &releases[0];
|
||||
assert!(!first_release.version.is_empty(), "Version should not be empty");
|
||||
assert!(first_release.download_url.is_some(), "Should have download URL");
|
||||
|
||||
assert!(
|
||||
!first_release.version.is_empty(),
|
||||
"Version should not be empty"
|
||||
);
|
||||
assert!(
|
||||
first_release.download_url.is_some(),
|
||||
"Should have download URL"
|
||||
);
|
||||
|
||||
println!("TOR API test passed. Found {} releases", releases.len());
|
||||
println!("Latest version: {}", first_release.version);
|
||||
}
|
||||
@@ -1081,14 +1219,17 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_tor_version_check() {
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(3500)).await; // Rate limiting
|
||||
|
||||
|
||||
let client = ApiClient::new();
|
||||
let result = client.check_tor_version_has_macos("14.0.4").await;
|
||||
|
||||
|
||||
match result {
|
||||
Ok(has_macos) => {
|
||||
assert!(has_macos, "Version 14.0.4 should have macOS support");
|
||||
println!("TOR version check test passed. Version 14.0.4 has macOS: {}", has_macos);
|
||||
println!(
|
||||
"TOR version check test passed. Version 14.0.4 has macOS: {}",
|
||||
has_macos
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("TOR version check test failed: {}", e);
|
||||
@@ -1096,4 +1237,4 @@ mod tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+763
-672
File diff suppressed because it is too large
Load Diff
+95
-51
@@ -61,8 +61,6 @@ pub trait Browser: Send + Sync {
|
||||
fn is_version_downloaded(&self, version: &str, binaries_dir: &Path) -> bool;
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub struct FirefoxBrowser {
|
||||
browser_type: BrowserType,
|
||||
}
|
||||
@@ -71,7 +69,6 @@ impl FirefoxBrowser {
|
||||
pub fn new(browser_type: BrowserType) -> Self {
|
||||
Self { browser_type }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Browser for FirefoxBrowser {
|
||||
@@ -79,8 +76,6 @@ impl Browser for FirefoxBrowser {
|
||||
self.browser_type.clone()
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn get_executable_path(&self, install_dir: &Path) -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||
// Find the .app directory
|
||||
let app_path = std::fs::read_dir(install_dir)?
|
||||
@@ -99,7 +94,11 @@ impl Browser for FirefoxBrowser {
|
||||
.find(|entry| {
|
||||
let binding = entry.file_name();
|
||||
let name = binding.to_string_lossy();
|
||||
name.starts_with("firefox") || name.starts_with("mullvad") || name.starts_with("zen") || name.starts_with("tor") || name.contains("Browser")
|
||||
name.starts_with("firefox")
|
||||
|| name.starts_with("mullvad")
|
||||
|| name.starts_with("zen")
|
||||
|| name.starts_with("tor")
|
||||
|| name.contains("Browser")
|
||||
})
|
||||
.map(|entry| entry.path())
|
||||
.ok_or("No executable found in MacOS directory")?;
|
||||
@@ -113,11 +112,8 @@ impl Browser for FirefoxBrowser {
|
||||
_proxy_settings: Option<&ProxySettings>,
|
||||
url: Option<String>,
|
||||
) -> Result<Vec<String>, Box<dyn std::error::Error>> {
|
||||
let mut args = vec![
|
||||
"-profile".to_string(),
|
||||
profile_path.to_string(),
|
||||
];
|
||||
|
||||
let mut args = vec!["-profile".to_string(), profile_path.to_string()];
|
||||
|
||||
// Only use -no-remote for browsers that require it for security (Mullvad, Tor)
|
||||
// Regular Firefox browsers can use remote commands for better URL handling
|
||||
match self.browser_type {
|
||||
@@ -129,7 +125,7 @@ impl Browser for FirefoxBrowser {
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
||||
// Firefox-based browsers use profile directory and user.js for proxy configuration
|
||||
if let Some(url) = url {
|
||||
args.push(url);
|
||||
@@ -143,7 +139,10 @@ impl Browser for FirefoxBrowser {
|
||||
.join(self.browser_type().as_str())
|
||||
.join(version);
|
||||
|
||||
println!("Firefox browser checking version {} in directory: {:?}", version, browser_dir);
|
||||
println!(
|
||||
"Firefox browser checking version {} in directory: {:?}",
|
||||
version, browser_dir
|
||||
);
|
||||
|
||||
// Only check if directory exists and contains a .app file
|
||||
if browser_dir.exists() {
|
||||
@@ -183,7 +182,6 @@ impl Browser for ChromiumBrowser {
|
||||
self.browser_type.clone()
|
||||
}
|
||||
|
||||
|
||||
fn get_executable_path(&self, install_dir: &Path) -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||
// Find the .app directory
|
||||
let app_path = std::fs::read_dir(install_dir)?
|
||||
@@ -253,7 +251,10 @@ impl Browser for ChromiumBrowser {
|
||||
.join(self.browser_type().as_str())
|
||||
.join(version);
|
||||
|
||||
println!("Chromium browser checking version {} in directory: {:?}", version, browser_dir);
|
||||
println!(
|
||||
"Chromium browser checking version {} in directory: {:?}",
|
||||
version, browser_dir
|
||||
);
|
||||
|
||||
// Check if directory exists and contains at least one .app file
|
||||
if browser_dir.exists() {
|
||||
@@ -286,9 +287,11 @@ impl Browser for ChromiumBrowser {
|
||||
// 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::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)),
|
||||
}
|
||||
}
|
||||
@@ -332,13 +335,28 @@ mod tests {
|
||||
assert_eq!(BrowserType::TorBrowser.as_str(), "tor-browser");
|
||||
|
||||
// Test from_str
|
||||
assert_eq!(BrowserType::from_str("mullvad-browser").unwrap(), BrowserType::MullvadBrowser);
|
||||
assert_eq!(BrowserType::from_str("firefox").unwrap(), BrowserType::Firefox);
|
||||
assert_eq!(BrowserType::from_str("firefox-developer").unwrap(), BrowserType::FirefoxDeveloper);
|
||||
assert_eq!(BrowserType::from_str("chromium").unwrap(), BrowserType::Chromium);
|
||||
assert_eq!(
|
||||
BrowserType::from_str("mullvad-browser").unwrap(),
|
||||
BrowserType::MullvadBrowser
|
||||
);
|
||||
assert_eq!(
|
||||
BrowserType::from_str("firefox").unwrap(),
|
||||
BrowserType::Firefox
|
||||
);
|
||||
assert_eq!(
|
||||
BrowserType::from_str("firefox-developer").unwrap(),
|
||||
BrowserType::FirefoxDeveloper
|
||||
);
|
||||
assert_eq!(
|
||||
BrowserType::from_str("chromium").unwrap(),
|
||||
BrowserType::Chromium
|
||||
);
|
||||
assert_eq!(BrowserType::from_str("brave").unwrap(), BrowserType::Brave);
|
||||
assert_eq!(BrowserType::from_str("zen").unwrap(), BrowserType::Zen);
|
||||
assert_eq!(BrowserType::from_str("tor-browser").unwrap(), BrowserType::TorBrowser);
|
||||
assert_eq!(
|
||||
BrowserType::from_str("tor-browser").unwrap(),
|
||||
BrowserType::TorBrowser
|
||||
);
|
||||
|
||||
// Test invalid browser type
|
||||
assert!(BrowserType::from_str("invalid").is_err());
|
||||
@@ -353,10 +371,10 @@ mod tests {
|
||||
|
||||
let browser = FirefoxBrowser::new(BrowserType::MullvadBrowser);
|
||||
assert_eq!(browser.browser_type(), BrowserType::MullvadBrowser);
|
||||
|
||||
|
||||
let browser = FirefoxBrowser::new(BrowserType::TorBrowser);
|
||||
assert_eq!(browser.browser_type(), BrowserType::TorBrowser);
|
||||
|
||||
|
||||
let browser = FirefoxBrowser::new(BrowserType::Zen);
|
||||
assert_eq!(browser.browser_type(), BrowserType::Zen);
|
||||
}
|
||||
@@ -381,10 +399,10 @@ mod tests {
|
||||
|
||||
let browser = create_browser(BrowserType::Zen);
|
||||
assert_eq!(browser.browser_type(), BrowserType::Zen);
|
||||
|
||||
|
||||
let browser = create_browser(BrowserType::TorBrowser);
|
||||
assert_eq!(browser.browser_type(), BrowserType::TorBrowser);
|
||||
|
||||
|
||||
let browser = create_browser(BrowserType::FirefoxDeveloper);
|
||||
assert_eq!(browser.browser_type(), BrowserType::FirefoxDeveloper);
|
||||
|
||||
@@ -400,47 +418,71 @@ mod tests {
|
||||
fn test_firefox_launch_args() {
|
||||
// Test regular Firefox (should not use -no-remote)
|
||||
let browser = FirefoxBrowser::new(BrowserType::Firefox);
|
||||
let args = browser.create_launch_args("/path/to/profile", None, None).unwrap();
|
||||
let args = browser
|
||||
.create_launch_args("/path/to/profile", None, None)
|
||||
.unwrap();
|
||||
assert_eq!(args, vec!["-profile", "/path/to/profile"]);
|
||||
assert!(!args.contains(&"-no-remote".to_string()));
|
||||
|
||||
let args = browser.create_launch_args("/path/to/profile", None, Some("https://example.com".to_string())).unwrap();
|
||||
assert_eq!(args, vec!["-profile", "/path/to/profile", "https://example.com"]);
|
||||
let args = browser
|
||||
.create_launch_args(
|
||||
"/path/to/profile",
|
||||
None,
|
||||
Some("https://example.com".to_string()),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
args,
|
||||
vec!["-profile", "/path/to/profile", "https://example.com"]
|
||||
);
|
||||
|
||||
// Test Mullvad Browser (should use -no-remote)
|
||||
let browser = FirefoxBrowser::new(BrowserType::MullvadBrowser);
|
||||
let args = browser.create_launch_args("/path/to/profile", None, None).unwrap();
|
||||
let args = browser
|
||||
.create_launch_args("/path/to/profile", None, None)
|
||||
.unwrap();
|
||||
assert_eq!(args, vec!["-profile", "/path/to/profile", "-no-remote"]);
|
||||
|
||||
// Test Tor Browser (should use -no-remote)
|
||||
let browser = FirefoxBrowser::new(BrowserType::TorBrowser);
|
||||
let args = browser.create_launch_args("/path/to/profile", None, None).unwrap();
|
||||
let args = browser
|
||||
.create_launch_args("/path/to/profile", None, None)
|
||||
.unwrap();
|
||||
assert_eq!(args, vec!["-profile", "/path/to/profile", "-no-remote"]);
|
||||
|
||||
|
||||
// Test Zen Browser (should not use -no-remote)
|
||||
let browser = FirefoxBrowser::new(BrowserType::Zen);
|
||||
let args = browser.create_launch_args("/path/to/profile", None, None).unwrap();
|
||||
let args = browser
|
||||
.create_launch_args("/path/to/profile", None, None)
|
||||
.unwrap();
|
||||
assert_eq!(args, vec!["-profile", "/path/to/profile"]);
|
||||
assert!(!args.contains(&"-no-remote".to_string()));
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_chromium_launch_args() {
|
||||
let browser = ChromiumBrowser::new(BrowserType::Chromium);
|
||||
let args = browser.create_launch_args("/path/to/profile", None, None).unwrap();
|
||||
|
||||
let args = browser
|
||||
.create_launch_args("/path/to/profile", None, None)
|
||||
.unwrap();
|
||||
|
||||
// Test that basic required arguments are present
|
||||
assert!(args.contains(&"--user-data-dir=/path/to/profile".to_string()));
|
||||
assert!(args.contains(&"--no-default-browser-check".to_string()));
|
||||
|
||||
|
||||
// Test that automatic update disabling arguments are present
|
||||
assert!(args.contains(&"--disable-background-mode".to_string()));
|
||||
assert!(args.contains(&"--disable-component-update".to_string()));
|
||||
|
||||
let args_with_url = browser.create_launch_args("/path/to/profile", None, Some("https://example.com".to_string())).unwrap();
|
||||
let args_with_url = browser
|
||||
.create_launch_args(
|
||||
"/path/to/profile",
|
||||
None,
|
||||
Some("https://example.com".to_string()),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(args_with_url.contains(&"https://example.com".to_string()));
|
||||
|
||||
|
||||
// Verify URL is at the end
|
||||
assert_eq!(args_with_url.last().unwrap(), "https://example.com");
|
||||
}
|
||||
@@ -458,7 +500,7 @@ mod tests {
|
||||
assert_eq!(proxy.proxy_type, "http");
|
||||
assert_eq!(proxy.host, "127.0.0.1");
|
||||
assert_eq!(proxy.port, 8080);
|
||||
|
||||
|
||||
// Test different proxy types
|
||||
let socks_proxy = ProxySettings {
|
||||
enabled: true,
|
||||
@@ -466,13 +508,12 @@ mod tests {
|
||||
host: "proxy.example.com".to_string(),
|
||||
port: 1080,
|
||||
};
|
||||
|
||||
|
||||
assert_eq!(socks_proxy.proxy_type, "socks5");
|
||||
assert_eq!(socks_proxy.host, "proxy.example.com");
|
||||
assert_eq!(socks_proxy.port, 1080);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_version_downloaded_check() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
@@ -489,17 +530,20 @@ mod tests {
|
||||
let browser = FirefoxBrowser::new(BrowserType::Firefox);
|
||||
assert!(browser.is_version_downloaded("139.0", binaries_dir));
|
||||
assert!(!browser.is_version_downloaded("140.0", binaries_dir));
|
||||
|
||||
|
||||
// Test with Chromium browser
|
||||
let chromium_dir = binaries_dir.join("chromium").join("1465660");
|
||||
fs::create_dir_all(&chromium_dir).unwrap();
|
||||
let chromium_app_dir = chromium_dir.join("Chromium.app");
|
||||
fs::create_dir_all(&chromium_app_dir.join("Contents").join("MacOS")).unwrap();
|
||||
|
||||
|
||||
// Create a mock executable
|
||||
let executable_path = chromium_app_dir.join("Contents").join("MacOS").join("Chromium");
|
||||
let executable_path = chromium_app_dir
|
||||
.join("Contents")
|
||||
.join("MacOS")
|
||||
.join("Chromium");
|
||||
fs::write(&executable_path, "mock executable").unwrap();
|
||||
|
||||
|
||||
let chromium_browser = ChromiumBrowser::new(BrowserType::Chromium);
|
||||
assert!(chromium_browser.is_version_downloaded("1465660", binaries_dir));
|
||||
assert!(!chromium_browser.is_version_downloaded("1465661", binaries_dir));
|
||||
@@ -513,7 +557,7 @@ mod tests {
|
||||
// Create browser directory but no .app directory
|
||||
let browser_dir = binaries_dir.join("firefox").join("139.0");
|
||||
fs::create_dir_all(&browser_dir).unwrap();
|
||||
|
||||
|
||||
// Create some other files but no .app
|
||||
fs::write(browser_dir.join("readme.txt"), "Some content").unwrap();
|
||||
|
||||
@@ -526,7 +570,7 @@ mod tests {
|
||||
let browser_type = BrowserType::Firefox;
|
||||
let cloned = browser_type.clone();
|
||||
assert_eq!(browser_type, cloned);
|
||||
|
||||
|
||||
// Test Debug trait
|
||||
let debug_str = format!("{:?}", browser_type);
|
||||
assert!(debug_str.contains("Firefox"));
|
||||
@@ -540,13 +584,13 @@ mod tests {
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 8080,
|
||||
};
|
||||
|
||||
|
||||
// Test that it can be serialized (implements Serialize)
|
||||
let json = serde_json::to_string(&proxy).unwrap();
|
||||
assert!(json.contains("127.0.0.1"));
|
||||
assert!(json.contains("8080"));
|
||||
assert!(json.contains("http"));
|
||||
|
||||
|
||||
// Test that it can be deserialized (implements Deserialize)
|
||||
let deserialized: ProxySettings = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(deserialized.enabled, proxy.enabled);
|
||||
|
||||
+698
-520
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -4,10 +4,7 @@ use tauri::command;
|
||||
mod macos {
|
||||
use core_foundation::base::OSStatus;
|
||||
use core_foundation::string::CFStringRef;
|
||||
use core_foundation::{
|
||||
base::TCFType,
|
||||
string::CFString,
|
||||
};
|
||||
use core_foundation::{base::TCFType, string::CFString};
|
||||
|
||||
#[link(name = "CoreServices", kind = "framework")]
|
||||
extern "C" {
|
||||
@@ -18,7 +15,7 @@ mod macos {
|
||||
pub fn is_default_browser() -> Result<bool, String> {
|
||||
let schemes = ["http", "https"];
|
||||
let bundle_id = "com.donutbrowser";
|
||||
|
||||
|
||||
for scheme in schemes {
|
||||
let scheme_str = CFString::new(scheme);
|
||||
unsafe {
|
||||
@@ -26,10 +23,10 @@ mod macos {
|
||||
if current_handler.is_null() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
|
||||
let current_handler_cf = CFString::wrap_under_create_rule(current_handler);
|
||||
let current_handler_str = current_handler_cf.to_string();
|
||||
|
||||
|
||||
if current_handler_str != bundle_id {
|
||||
return Ok(false);
|
||||
}
|
||||
@@ -123,14 +120,21 @@ pub async fn set_as_default_browser() -> Result<(), String> {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn open_url_with_profile(app_handle: tauri::AppHandle, profile_name: String, url: String) -> Result<(), String> {
|
||||
pub async fn open_url_with_profile(
|
||||
app_handle: tauri::AppHandle,
|
||||
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()
|
||||
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 '{}' not found", profile_name))?;
|
||||
|
||||
@@ -145,25 +149,37 @@ pub async fn open_url_with_profile(app_handle: tauri::AppHandle, profile_name: S
|
||||
format!("Failed to open URL with profile: {}", e)
|
||||
})?;
|
||||
|
||||
println!("Successfully opened URL '{}' with profile '{}'", url, profile_name);
|
||||
println!(
|
||||
"Successfully opened URL '{}' with profile '{}'",
|
||||
url, profile_name
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn smart_open_url(_app_handle: tauri::AppHandle, _url: String, _is_startup: Option<bool>) -> Result<String, String> {
|
||||
pub async fn smart_open_url(
|
||||
_app_handle: tauri::AppHandle,
|
||||
_url: String,
|
||||
_is_startup: Option<bool>,
|
||||
) -> Result<String, String> {
|
||||
use crate::browser_runner::BrowserRunner;
|
||||
|
||||
let runner = BrowserRunner::new();
|
||||
|
||||
|
||||
// Get all profiles
|
||||
let profiles = runner.list_profiles().map_err(|e| format!("Failed to list profiles: {}", e))?;
|
||||
|
||||
let profiles = runner
|
||||
.list_profiles()
|
||||
.map_err(|e| format!("Failed to list profiles: {}", e))?;
|
||||
|
||||
if profiles.is_empty() {
|
||||
return Err("no_profiles".to_string());
|
||||
}
|
||||
|
||||
println!("URL opening - Total profiles: {}, showing profile selector", profiles.len());
|
||||
|
||||
println!(
|
||||
"URL opening - Total profiles: {}, showing profile selector",
|
||||
profiles.len()
|
||||
);
|
||||
|
||||
// Always show the profile selector so the user can choose
|
||||
Err("show_selector".to_string())
|
||||
}
|
||||
|
||||
+86
-76
@@ -45,60 +45,67 @@ impl Downloader {
|
||||
BrowserType::Brave => {
|
||||
// For Brave, we need to find the actual macOS asset
|
||||
let releases = self.api_client.fetch_brave_releases().await?;
|
||||
|
||||
|
||||
// Find the release with the matching version
|
||||
let release = releases
|
||||
.iter()
|
||||
.find(|r| r.tag_name == version || r.tag_name == format!("v{}", version.trim_start_matches('v')))
|
||||
.find(|r| {
|
||||
r.tag_name == version || r.tag_name == format!("v{}", version.trim_start_matches('v'))
|
||||
})
|
||||
.ok_or(format!("Brave version {} not found", version))?;
|
||||
|
||||
|
||||
// Find the universal macOS DMG asset
|
||||
let asset = release
|
||||
.assets
|
||||
.iter()
|
||||
.find(|asset| {
|
||||
asset.name.contains(".dmg") && asset.name.contains("universal")
|
||||
})
|
||||
.ok_or(format!("No universal macOS DMG asset found for Brave version {}", version))?;
|
||||
|
||||
.find(|asset| asset.name.contains(".dmg") && asset.name.contains("universal"))
|
||||
.ok_or(format!(
|
||||
"No universal macOS DMG asset found for Brave version {}",
|
||||
version
|
||||
))?;
|
||||
|
||||
Ok(asset.browser_download_url.clone())
|
||||
}
|
||||
BrowserType::Zen => {
|
||||
// For Zen, verify the asset exists
|
||||
let releases = self.api_client.fetch_zen_releases().await?;
|
||||
|
||||
|
||||
let release = releases
|
||||
.iter()
|
||||
.find(|r| r.tag_name == version)
|
||||
.ok_or(format!("Zen version {} not found", version))?;
|
||||
|
||||
|
||||
// Find the macOS universal DMG asset
|
||||
let asset = release
|
||||
.assets
|
||||
.iter()
|
||||
.find(|asset| asset.name == "zen.macos-universal.dmg")
|
||||
.ok_or(format!("No macOS universal asset found for Zen version {}", version))?;
|
||||
|
||||
.ok_or(format!(
|
||||
"No macOS universal asset found for Zen version {}",
|
||||
version
|
||||
))?;
|
||||
|
||||
Ok(asset.browser_download_url.clone())
|
||||
}
|
||||
BrowserType::MullvadBrowser => {
|
||||
// For Mullvad, verify the asset exists
|
||||
let releases = self.api_client.fetch_mullvad_releases().await?;
|
||||
|
||||
|
||||
let release = releases
|
||||
.iter()
|
||||
.find(|r| r.tag_name == version)
|
||||
.ok_or(format!("Mullvad version {} not found", version))?;
|
||||
|
||||
|
||||
// Find the macOS DMG asset
|
||||
let asset = release
|
||||
.assets
|
||||
.iter()
|
||||
.find(|asset| {
|
||||
asset.name.contains(".dmg") && asset.name.contains("mac")
|
||||
})
|
||||
.ok_or(format!("No macOS asset found for Mullvad version {}", version))?;
|
||||
|
||||
.find(|asset| asset.name.contains(".dmg") && asset.name.contains("mac"))
|
||||
.ok_or(format!(
|
||||
"No macOS asset found for Mullvad version {}",
|
||||
version
|
||||
))?;
|
||||
|
||||
Ok(asset.browser_download_url.clone())
|
||||
}
|
||||
_ => {
|
||||
@@ -117,10 +124,12 @@ impl Downloader {
|
||||
dest_path: &Path,
|
||||
) -> Result<PathBuf, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let file_path = dest_path.join(&download_info.filename);
|
||||
|
||||
|
||||
// Resolve the actual download URL
|
||||
let download_url = self.resolve_download_url(browser_type.clone(), version, download_info).await?;
|
||||
|
||||
let download_url = self
|
||||
.resolve_download_url(browser_type.clone(), version, download_info)
|
||||
.await?;
|
||||
|
||||
// Emit initial progress
|
||||
let progress = DownloadProgress {
|
||||
browser: browser_type.as_str().to_string(),
|
||||
@@ -132,7 +141,7 @@ impl Downloader {
|
||||
eta_seconds: None,
|
||||
stage: "downloading".to_string(),
|
||||
};
|
||||
|
||||
|
||||
let _ = app_handle.emit("download-progress", &progress);
|
||||
|
||||
// Start download
|
||||
@@ -161,7 +170,11 @@ impl Downloader {
|
||||
// Update progress every 100ms to avoid too many events
|
||||
if now.duration_since(last_update).as_millis() >= 100 {
|
||||
let elapsed = start_time.elapsed().as_secs_f64();
|
||||
let speed = if elapsed > 0.0 { downloaded as f64 / elapsed } else { 0.0 };
|
||||
let speed = if elapsed > 0.0 {
|
||||
downloaded as f64 / elapsed
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let percentage = if let Some(total) = total_size {
|
||||
(downloaded as f64 / total as f64) * 100.0
|
||||
} else {
|
||||
@@ -201,20 +214,18 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_resolve_brave_download_url() {
|
||||
let downloader = Downloader::new();
|
||||
|
||||
|
||||
// Test with a known Brave version
|
||||
let download_info = DownloadInfo {
|
||||
url: "placeholder".to_string(),
|
||||
filename: "brave-test.dmg".to_string(),
|
||||
is_archive: true,
|
||||
};
|
||||
|
||||
let result = downloader.resolve_download_url(
|
||||
BrowserType::Brave,
|
||||
"v1.81.9",
|
||||
&download_info
|
||||
).await;
|
||||
|
||||
|
||||
let result = downloader
|
||||
.resolve_download_url(BrowserType::Brave, "v1.81.9", &download_info)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(url) => {
|
||||
assert!(url.contains("github.com/brave/brave-browser"));
|
||||
@@ -223,7 +234,10 @@ mod tests {
|
||||
println!("Brave download URL resolved: {}", url);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Brave URL resolution failed (expected if version doesn't exist): {}", e);
|
||||
println!(
|
||||
"Brave URL resolution failed (expected if version doesn't exist): {}",
|
||||
e
|
||||
);
|
||||
// This might fail if the version doesn't exist, which is okay for testing
|
||||
}
|
||||
}
|
||||
@@ -232,19 +246,17 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_resolve_zen_download_url() {
|
||||
let downloader = Downloader::new();
|
||||
|
||||
|
||||
let download_info = DownloadInfo {
|
||||
url: "placeholder".to_string(),
|
||||
filename: "zen-test.dmg".to_string(),
|
||||
is_archive: true,
|
||||
};
|
||||
|
||||
let result = downloader.resolve_download_url(
|
||||
BrowserType::Zen,
|
||||
"1.11b",
|
||||
&download_info
|
||||
).await;
|
||||
|
||||
|
||||
let result = downloader
|
||||
.resolve_download_url(BrowserType::Zen, "1.11b", &download_info)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(url) => {
|
||||
assert!(url.contains("github.com/zen-browser/desktop"));
|
||||
@@ -252,7 +264,10 @@ mod tests {
|
||||
println!("Zen download URL resolved: {}", url);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Zen URL resolution failed (expected if version doesn't exist): {}", e);
|
||||
println!(
|
||||
"Zen URL resolution failed (expected if version doesn't exist): {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,19 +275,17 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_resolve_mullvad_download_url() {
|
||||
let downloader = Downloader::new();
|
||||
|
||||
|
||||
let download_info = DownloadInfo {
|
||||
url: "placeholder".to_string(),
|
||||
filename: "mullvad-test.dmg".to_string(),
|
||||
is_archive: true,
|
||||
};
|
||||
|
||||
let result = downloader.resolve_download_url(
|
||||
BrowserType::MullvadBrowser,
|
||||
"14.5a6",
|
||||
&download_info
|
||||
).await;
|
||||
|
||||
|
||||
let result = downloader
|
||||
.resolve_download_url(BrowserType::MullvadBrowser, "14.5a6", &download_info)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(url) => {
|
||||
assert!(url.contains("github.com/mullvad/mullvad-browser"));
|
||||
@@ -280,7 +293,10 @@ mod tests {
|
||||
println!("Mullvad download URL resolved: {}", url);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Mullvad URL resolution failed (expected if version doesn't exist): {}", e);
|
||||
println!(
|
||||
"Mullvad URL resolution failed (expected if version doesn't exist): {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -288,19 +304,17 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_resolve_firefox_download_url() {
|
||||
let downloader = Downloader::new();
|
||||
|
||||
|
||||
let download_info = DownloadInfo {
|
||||
url: "https://download.mozilla.org/?product=firefox-139.0&os=osx&lang=en-US".to_string(),
|
||||
filename: "firefox-test.dmg".to_string(),
|
||||
is_archive: true,
|
||||
};
|
||||
|
||||
let result = downloader.resolve_download_url(
|
||||
BrowserType::Firefox,
|
||||
"139.0",
|
||||
&download_info
|
||||
).await;
|
||||
|
||||
|
||||
let result = downloader
|
||||
.resolve_download_url(BrowserType::Firefox, "139.0", &download_info)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(url) => {
|
||||
assert_eq!(url, download_info.url);
|
||||
@@ -315,19 +329,17 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_resolve_chromium_download_url() {
|
||||
let downloader = Downloader::new();
|
||||
|
||||
|
||||
let download_info = DownloadInfo {
|
||||
url: "https://commondatastorage.googleapis.com/chromium-browser-snapshots/Mac/1465660/chrome-mac.zip".to_string(),
|
||||
filename: "chromium-test.zip".to_string(),
|
||||
is_archive: true,
|
||||
};
|
||||
|
||||
let result = downloader.resolve_download_url(
|
||||
BrowserType::Chromium,
|
||||
"1465660",
|
||||
&download_info
|
||||
).await;
|
||||
|
||||
|
||||
let result = downloader
|
||||
.resolve_download_url(BrowserType::Chromium, "1465660", &download_info)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(url) => {
|
||||
assert_eq!(url, download_info.url);
|
||||
@@ -342,19 +354,17 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_resolve_tor_download_url() {
|
||||
let downloader = Downloader::new();
|
||||
|
||||
|
||||
let download_info = DownloadInfo {
|
||||
url: "https://archive.torproject.org/tor-package-archive/torbrowser/14.0.4/tor-browser-macos-14.0.4.dmg".to_string(),
|
||||
filename: "tor-test.dmg".to_string(),
|
||||
is_archive: true,
|
||||
};
|
||||
|
||||
let result = downloader.resolve_download_url(
|
||||
BrowserType::TorBrowser,
|
||||
"14.0.4",
|
||||
&download_info
|
||||
).await;
|
||||
|
||||
|
||||
let result = downloader
|
||||
.resolve_download_url(BrowserType::TorBrowser, "14.0.4", &download_info)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(url) => {
|
||||
assert_eq!(url, download_info.url);
|
||||
@@ -365,4 +375,4 @@ mod tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,258 +1,281 @@
|
||||
use directories::BaseDirs;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use directories::BaseDirs;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct DownloadedBrowserInfo {
|
||||
pub browser: String,
|
||||
pub version: String,
|
||||
pub download_date: u64,
|
||||
pub file_path: PathBuf,
|
||||
pub verified: bool,
|
||||
pub actual_version: Option<String>, // For browsers like Chromium where we track the actual version
|
||||
pub browser: String,
|
||||
pub version: String,
|
||||
pub download_date: u64,
|
||||
pub file_path: PathBuf,
|
||||
pub verified: bool,
|
||||
pub actual_version: Option<String>, // For browsers like Chromium where we track the actual version
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||
pub struct DownloadedBrowsersRegistry {
|
||||
pub browsers: HashMap<String, HashMap<String, DownloadedBrowserInfo>>, // browser -> version -> info
|
||||
pub browsers: HashMap<String, HashMap<String, DownloadedBrowserInfo>>, // browser -> version -> info
|
||||
}
|
||||
|
||||
impl DownloadedBrowsersRegistry {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn load() -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let registry_path = Self::get_registry_path()?;
|
||||
|
||||
if !registry_path.exists() {
|
||||
return Ok(Self::new());
|
||||
}
|
||||
|
||||
pub fn load() -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let registry_path = Self::get_registry_path()?;
|
||||
|
||||
if !registry_path.exists() {
|
||||
return Ok(Self::new());
|
||||
}
|
||||
let content = fs::read_to_string(®istry_path)?;
|
||||
let registry: DownloadedBrowsersRegistry = serde_json::from_str(&content)?;
|
||||
Ok(registry)
|
||||
}
|
||||
|
||||
let content = fs::read_to_string(®istry_path)?;
|
||||
let registry: DownloadedBrowsersRegistry = serde_json::from_str(&content)?;
|
||||
Ok(registry)
|
||||
pub fn save(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let registry_path = Self::get_registry_path()?;
|
||||
|
||||
// Ensure parent directory exists
|
||||
if let Some(parent) = registry_path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
pub fn save(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let registry_path = Self::get_registry_path()?;
|
||||
|
||||
// Ensure parent directory exists
|
||||
if let Some(parent) = registry_path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
let content = serde_json::to_string_pretty(self)?;
|
||||
fs::write(®istry_path, content)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
let content = serde_json::to_string_pretty(self)?;
|
||||
fs::write(®istry_path, content)?;
|
||||
Ok(())
|
||||
fn get_registry_path() -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||
let base_dirs = BaseDirs::new().ok_or("Failed to get base directories")?;
|
||||
let mut path = base_dirs.data_local_dir().to_path_buf();
|
||||
path.push(if cfg!(debug_assertions) {
|
||||
"DonutBrowserDev"
|
||||
} else {
|
||||
"DonutBrowser"
|
||||
});
|
||||
path.push("data");
|
||||
path.push("downloaded_browsers.json");
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
pub fn add_browser(&mut self, info: DownloadedBrowserInfo) {
|
||||
self
|
||||
.browsers
|
||||
.entry(info.browser.clone())
|
||||
.or_insert_with(HashMap::new)
|
||||
.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 is_browser_downloaded(&self, browser: &str, version: &str) -> bool {
|
||||
self
|
||||
.browsers
|
||||
.get(browser)
|
||||
.and_then(|versions| versions.get(version))
|
||||
.map(|info| info.verified)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn get_downloaded_versions(&self, browser: &str) -> Vec<String> {
|
||||
self
|
||||
.browsers
|
||||
.get(browser)
|
||||
.map(|versions| {
|
||||
versions
|
||||
.iter()
|
||||
.filter(|(_, info)| info.verified)
|
||||
.map(|(version, _)| version.clone())
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn mark_download_started(&mut self, browser: &str, version: &str, file_path: PathBuf) {
|
||||
let info = DownloadedBrowserInfo {
|
||||
browser: browser.to_string(),
|
||||
version: version.to_string(),
|
||||
download_date: std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs(),
|
||||
file_path,
|
||||
verified: false,
|
||||
actual_version: None,
|
||||
};
|
||||
self.add_browser(info);
|
||||
}
|
||||
|
||||
pub fn mark_download_completed_with_actual_version(
|
||||
&mut self,
|
||||
browser: &str,
|
||||
version: &str,
|
||||
actual_version: Option<String>,
|
||||
) -> Result<(), String> {
|
||||
if let Some(info) = self
|
||||
.browsers
|
||||
.get_mut(browser)
|
||||
.and_then(|versions| versions.get_mut(version))
|
||||
{
|
||||
info.verified = true;
|
||||
info.actual_version = actual_version;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!(
|
||||
"Browser {}:{} not found in registry",
|
||||
browser, version
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_registry_path() -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||
let base_dirs = BaseDirs::new().ok_or("Failed to get base directories")?;
|
||||
let mut path = base_dirs.data_local_dir().to_path_buf();
|
||||
path.push(if cfg!(debug_assertions) { "DonutBrowserDev" } else { "DonutBrowser" });
|
||||
path.push("data");
|
||||
path.push("downloaded_browsers.json");
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
pub fn add_browser(&mut self, info: DownloadedBrowserInfo) {
|
||||
self.browsers
|
||||
.entry(info.browser.clone())
|
||||
.or_insert_with(HashMap::new)
|
||||
.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 is_browser_downloaded(&self, browser: &str, version: &str) -> bool {
|
||||
self.browsers
|
||||
.get(browser)
|
||||
.and_then(|versions| versions.get(version))
|
||||
.map(|info| info.verified)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn get_downloaded_versions(&self, browser: &str) -> Vec<String> {
|
||||
self.browsers
|
||||
.get(browser)
|
||||
.map(|versions| {
|
||||
versions
|
||||
.iter()
|
||||
.filter(|(_, info)| info.verified)
|
||||
.map(|(version, _)| version.clone())
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn mark_download_started(&mut self, browser: &str, version: &str, file_path: PathBuf) {
|
||||
let info = DownloadedBrowserInfo {
|
||||
browser: browser.to_string(),
|
||||
version: version.to_string(),
|
||||
download_date: std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs(),
|
||||
file_path,
|
||||
verified: false,
|
||||
actual_version: None,
|
||||
};
|
||||
self.add_browser(info);
|
||||
}
|
||||
|
||||
pub fn mark_download_completed_with_actual_version(&mut self, browser: &str, version: &str, actual_version: Option<String>) -> Result<(), String> {
|
||||
if let Some(info) = self.browsers
|
||||
.get_mut(browser)
|
||||
.and_then(|versions| versions.get_mut(version))
|
||||
{
|
||||
info.verified = true;
|
||||
info.actual_version = actual_version;
|
||||
Ok(())
|
||||
pub fn cleanup_failed_download(
|
||||
&mut self,
|
||||
browser: &str,
|
||||
version: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if let Some(info) = self.remove_browser(browser, version) {
|
||||
// Clean up any files that might have been left behind
|
||||
if info.file_path.exists() {
|
||||
if info.file_path.is_dir() {
|
||||
fs::remove_dir_all(&info.file_path)?;
|
||||
} else {
|
||||
Err(format!("Browser {}:{} not found in registry", browser, version))
|
||||
fs::remove_file(&info.file_path)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cleanup_failed_download(&mut self, browser: &str, version: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if let Some(info) = self.remove_browser(browser, version) {
|
||||
// Clean up any files that might have been left behind
|
||||
if info.file_path.exists() {
|
||||
if info.file_path.is_dir() {
|
||||
fs::remove_dir_all(&info.file_path)?;
|
||||
} else {
|
||||
fs::remove_file(&info.file_path)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Also clean up the browser directory if it exists
|
||||
let base_dirs = BaseDirs::new().ok_or("Failed to get base directories")?;
|
||||
let mut browser_dir = base_dirs.data_local_dir().to_path_buf();
|
||||
browser_dir.push(if cfg!(debug_assertions) { "DonutBrowserDev" } else { "DonutBrowser" });
|
||||
browser_dir.push("binaries");
|
||||
browser_dir.push(browser);
|
||||
browser_dir.push(version);
|
||||
|
||||
if browser_dir.exists() {
|
||||
fs::remove_dir_all(&browser_dir)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
// Also clean up the browser directory if it exists
|
||||
let base_dirs = BaseDirs::new().ok_or("Failed to get base directories")?;
|
||||
let mut browser_dir = base_dirs.data_local_dir().to_path_buf();
|
||||
browser_dir.push(if cfg!(debug_assertions) {
|
||||
"DonutBrowserDev"
|
||||
} else {
|
||||
"DonutBrowser"
|
||||
});
|
||||
browser_dir.push("binaries");
|
||||
browser_dir.push(browser);
|
||||
browser_dir.push(version);
|
||||
|
||||
if browser_dir.exists() {
|
||||
fs::remove_dir_all(&browser_dir)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_registry_creation() {
|
||||
let registry = DownloadedBrowsersRegistry::new();
|
||||
assert!(registry.browsers.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_registry_creation() {
|
||||
let registry = DownloadedBrowsersRegistry::new();
|
||||
assert!(registry.browsers.is_empty());
|
||||
}
|
||||
#[test]
|
||||
fn test_add_and_get_browser() {
|
||||
let mut registry = DownloadedBrowsersRegistry::new();
|
||||
let info = DownloadedBrowserInfo {
|
||||
browser: "firefox".to_string(),
|
||||
version: "139.0".to_string(),
|
||||
download_date: 1234567890,
|
||||
file_path: PathBuf::from("/test/path"),
|
||||
verified: true,
|
||||
actual_version: None,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_add_and_get_browser() {
|
||||
let mut registry = DownloadedBrowsersRegistry::new();
|
||||
let info = DownloadedBrowserInfo {
|
||||
browser: "firefox".to_string(),
|
||||
version: "139.0".to_string(),
|
||||
download_date: 1234567890,
|
||||
file_path: PathBuf::from("/test/path"),
|
||||
verified: true,
|
||||
actual_version: None,
|
||||
};
|
||||
registry.add_browser(info.clone());
|
||||
|
||||
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_downloaded("firefox", "139.0"));
|
||||
assert!(!registry.is_browser_downloaded("firefox", "140.0"));
|
||||
assert!(!registry.is_browser_downloaded("chrome", "139.0"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_downloaded_versions() {
|
||||
let mut registry = DownloadedBrowsersRegistry::new();
|
||||
|
||||
let info1 = DownloadedBrowserInfo {
|
||||
browser: "firefox".to_string(),
|
||||
version: "139.0".to_string(),
|
||||
download_date: 1234567890,
|
||||
file_path: PathBuf::from("/test/path1"),
|
||||
verified: true,
|
||||
actual_version: None,
|
||||
};
|
||||
|
||||
let info2 = DownloadedBrowserInfo {
|
||||
browser: "firefox".to_string(),
|
||||
version: "140.0".to_string(),
|
||||
download_date: 1234567891,
|
||||
file_path: PathBuf::from("/test/path2"),
|
||||
verified: false, // Not verified, should not be included
|
||||
actual_version: None,
|
||||
};
|
||||
#[test]
|
||||
fn test_get_downloaded_versions() {
|
||||
let mut registry = DownloadedBrowsersRegistry::new();
|
||||
|
||||
let info3 = DownloadedBrowserInfo {
|
||||
browser: "firefox".to_string(),
|
||||
version: "141.0".to_string(),
|
||||
download_date: 1234567892,
|
||||
file_path: PathBuf::from("/test/path3"),
|
||||
verified: true,
|
||||
actual_version: None,
|
||||
};
|
||||
let info1 = DownloadedBrowserInfo {
|
||||
browser: "firefox".to_string(),
|
||||
version: "139.0".to_string(),
|
||||
download_date: 1234567890,
|
||||
file_path: PathBuf::from("/test/path1"),
|
||||
verified: true,
|
||||
actual_version: None,
|
||||
};
|
||||
|
||||
registry.add_browser(info1);
|
||||
registry.add_browser(info2);
|
||||
registry.add_browser(info3);
|
||||
let info2 = DownloadedBrowserInfo {
|
||||
browser: "firefox".to_string(),
|
||||
version: "140.0".to_string(),
|
||||
download_date: 1234567891,
|
||||
file_path: PathBuf::from("/test/path2"),
|
||||
verified: false, // Not verified, should not be included
|
||||
actual_version: None,
|
||||
};
|
||||
|
||||
let versions = registry.get_downloaded_versions("firefox");
|
||||
assert_eq!(versions.len(), 2);
|
||||
assert!(versions.contains(&"139.0".to_string()));
|
||||
assert!(versions.contains(&"141.0".to_string()));
|
||||
assert!(!versions.contains(&"140.0".to_string()));
|
||||
}
|
||||
let info3 = DownloadedBrowserInfo {
|
||||
browser: "firefox".to_string(),
|
||||
version: "141.0".to_string(),
|
||||
download_date: 1234567892,
|
||||
file_path: PathBuf::from("/test/path3"),
|
||||
verified: true,
|
||||
actual_version: None,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_mark_download_lifecycle() {
|
||||
let mut registry = DownloadedBrowsersRegistry::new();
|
||||
|
||||
// Mark download started
|
||||
registry.mark_download_started("firefox", "139.0", PathBuf::from("/test/path"));
|
||||
|
||||
// Should not be considered downloaded yet
|
||||
assert!(!registry.is_browser_downloaded("firefox", "139.0"));
|
||||
|
||||
// Mark as completed
|
||||
registry.mark_download_completed_with_actual_version("firefox", "139.0", Some("139.0".to_string())).unwrap();
|
||||
|
||||
// Now should be considered downloaded
|
||||
assert!(registry.is_browser_downloaded("firefox", "139.0"));
|
||||
}
|
||||
registry.add_browser(info1);
|
||||
registry.add_browser(info2);
|
||||
registry.add_browser(info3);
|
||||
|
||||
#[test]
|
||||
fn test_remove_browser() {
|
||||
let mut registry = DownloadedBrowsersRegistry::new();
|
||||
let info = DownloadedBrowserInfo {
|
||||
browser: "firefox".to_string(),
|
||||
version: "139.0".to_string(),
|
||||
download_date: 1234567890,
|
||||
file_path: PathBuf::from("/test/path"),
|
||||
verified: true,
|
||||
actual_version: None,
|
||||
};
|
||||
let versions = registry.get_downloaded_versions("firefox");
|
||||
assert_eq!(versions.len(), 2);
|
||||
assert!(versions.contains(&"139.0".to_string()));
|
||||
assert!(versions.contains(&"141.0".to_string()));
|
||||
assert!(!versions.contains(&"140.0".to_string()));
|
||||
}
|
||||
|
||||
registry.add_browser(info);
|
||||
assert!(registry.is_browser_downloaded("firefox", "139.0"));
|
||||
#[test]
|
||||
fn test_mark_download_lifecycle() {
|
||||
let mut registry = DownloadedBrowsersRegistry::new();
|
||||
|
||||
let removed = registry.remove_browser("firefox", "139.0");
|
||||
assert!(removed.is_some());
|
||||
assert!(!registry.is_browser_downloaded("firefox", "139.0"));
|
||||
}
|
||||
}
|
||||
// Mark download started
|
||||
registry.mark_download_started("firefox", "139.0", PathBuf::from("/test/path"));
|
||||
|
||||
// Should not be considered downloaded yet
|
||||
assert!(!registry.is_browser_downloaded("firefox", "139.0"));
|
||||
|
||||
// Mark as completed
|
||||
registry
|
||||
.mark_download_completed_with_actual_version("firefox", "139.0", Some("139.0".to_string()))
|
||||
.unwrap();
|
||||
|
||||
// Now should be considered downloaded
|
||||
assert!(registry.is_browser_downloaded("firefox", "139.0"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_browser() {
|
||||
let mut registry = DownloadedBrowsersRegistry::new();
|
||||
let info = DownloadedBrowserInfo {
|
||||
browser: "firefox".to_string(),
|
||||
version: "139.0".to_string(),
|
||||
download_date: 1234567890,
|
||||
file_path: PathBuf::from("/test/path"),
|
||||
verified: true,
|
||||
actual_version: None,
|
||||
};
|
||||
|
||||
registry.add_browser(info);
|
||||
assert!(registry.is_browser_downloaded("firefox", "139.0"));
|
||||
|
||||
let removed = registry.remove_browser("firefox", "139.0");
|
||||
assert!(removed.is_some());
|
||||
assert!(!registry.is_browser_downloaded("firefox", "139.0"));
|
||||
}
|
||||
}
|
||||
|
||||
+22
-22
@@ -3,8 +3,8 @@ use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use tauri::Emitter;
|
||||
|
||||
use crate::download::DownloadProgress;
|
||||
use crate::browser::BrowserType;
|
||||
use crate::download::DownloadProgress;
|
||||
|
||||
pub struct Extractor;
|
||||
|
||||
@@ -176,7 +176,7 @@ impl Extractor {
|
||||
|
||||
// Find the extracted .app directory or Chromium.app specifically
|
||||
let mut app_path: Option<PathBuf> = None;
|
||||
|
||||
|
||||
// First, try to find any .app file in the destination directory
|
||||
if let Ok(entries) = fs::read_dir(dest_dir) {
|
||||
for entry in entries {
|
||||
@@ -197,7 +197,7 @@ impl Extractor {
|
||||
let target_path = dest_dir.join(sub_path.file_name().unwrap());
|
||||
fs::rename(&sub_path, &target_path)?;
|
||||
app_path = Some(target_path);
|
||||
|
||||
|
||||
// Clean up the now-empty subdirectory
|
||||
let _ = fs::remove_dir_all(&path);
|
||||
break;
|
||||
@@ -247,16 +247,16 @@ mod tests {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let fake_archive = temp_dir.path().join("test.rar");
|
||||
File::create(&fake_archive).unwrap();
|
||||
|
||||
|
||||
// Create a mock app handle (this won't work in real tests without Tauri runtime)
|
||||
// For now, we'll just test the logic without the actual extraction
|
||||
|
||||
|
||||
// Test that unsupported formats return an error
|
||||
let extension = fake_archive
|
||||
.extension()
|
||||
.and_then(|ext| ext.to_str())
|
||||
.unwrap_or("");
|
||||
|
||||
|
||||
assert_eq!(extension, "rar");
|
||||
// We know this would fail with "Unsupported archive format: rar"
|
||||
}
|
||||
@@ -265,13 +265,13 @@ mod tests {
|
||||
fn test_dmg_path_validation() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let dmg_path = temp_dir.path().join("test.dmg");
|
||||
|
||||
|
||||
// Test that we can identify DMG files correctly
|
||||
let extension = dmg_path
|
||||
.extension()
|
||||
.and_then(|ext| ext.to_str())
|
||||
.unwrap_or("");
|
||||
|
||||
|
||||
assert_eq!(extension, "dmg");
|
||||
}
|
||||
|
||||
@@ -279,13 +279,13 @@ mod tests {
|
||||
fn test_zip_path_validation() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let zip_path = temp_dir.path().join("test.zip");
|
||||
|
||||
|
||||
// Test that we can identify ZIP files correctly
|
||||
let extension = zip_path
|
||||
.extension()
|
||||
.and_then(|ext| ext.to_str())
|
||||
.unwrap_or("");
|
||||
|
||||
|
||||
assert_eq!(extension, "zip");
|
||||
}
|
||||
|
||||
@@ -299,9 +299,9 @@ mod tests {
|
||||
.unwrap()
|
||||
.as_secs()
|
||||
));
|
||||
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
|
||||
|
||||
let mount_point2 = std::env::temp_dir().join(format!(
|
||||
"donut_mount_{}",
|
||||
std::time::SystemTime::now()
|
||||
@@ -309,7 +309,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.as_secs()
|
||||
));
|
||||
|
||||
|
||||
// They should be different (or at least have the potential to be)
|
||||
assert!(mount_point1.to_string_lossy().contains("donut_mount_"));
|
||||
assert!(mount_point2.to_string_lossy().contains("donut_mount_"));
|
||||
@@ -318,18 +318,18 @@ mod tests {
|
||||
#[test]
|
||||
fn test_app_path_detection() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
|
||||
|
||||
// Create a fake .app directory
|
||||
let app_dir = temp_dir.path().join("TestApp.app");
|
||||
std::fs::create_dir_all(&app_dir).unwrap();
|
||||
|
||||
|
||||
// Test finding .app directories
|
||||
let entries: Vec<_> = fs::read_dir(temp_dir.path())
|
||||
.unwrap()
|
||||
.filter_map(Result::ok)
|
||||
.filter(|entry| entry.path().extension().map_or(false, |ext| ext == "app"))
|
||||
.collect();
|
||||
|
||||
|
||||
assert_eq!(entries.len(), 1);
|
||||
assert_eq!(entries[0].file_name(), "TestApp.app");
|
||||
}
|
||||
@@ -337,17 +337,17 @@ mod tests {
|
||||
#[test]
|
||||
fn test_nested_app_detection() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
|
||||
|
||||
// Create a nested structure like Chromium
|
||||
let chrome_dir = temp_dir.path().join("chrome-mac");
|
||||
std::fs::create_dir_all(&chrome_dir).unwrap();
|
||||
|
||||
|
||||
let app_dir = chrome_dir.join("Chromium.app");
|
||||
std::fs::create_dir_all(&app_dir).unwrap();
|
||||
|
||||
|
||||
// Test finding nested .app directories
|
||||
let mut found_app = false;
|
||||
|
||||
|
||||
if let Ok(entries) = fs::read_dir(temp_dir.path()) {
|
||||
for entry in entries {
|
||||
if let Ok(entry) = entry {
|
||||
@@ -368,7 +368,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
assert!(found_app);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+40
-31
@@ -1,7 +1,7 @@
|
||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
||||
use std::sync::Mutex;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use tauri::{Manager, Emitter};
|
||||
use tauri::{Emitter, Manager};
|
||||
use tauri_plugin_deep_link::DeepLinkExt;
|
||||
|
||||
// Store pending URLs that need to be handled when the window is ready
|
||||
@@ -23,28 +23,33 @@ mod version_updater;
|
||||
extern crate lazy_static;
|
||||
|
||||
use browser_runner::{
|
||||
check_browser_status, create_browser_profile, create_browser_profile_new, delete_profile,
|
||||
download_browser, fetch_browser_versions, fetch_browser_versions_cached_first,
|
||||
fetch_browser_versions_detailed, fetch_browser_versions_with_count, fetch_browser_versions_with_count_cached_first,
|
||||
get_cached_browser_versions_detailed, get_downloaded_browser_versions, get_saved_mullvad_releases, get_supported_browsers, is_browser_downloaded, check_browser_exists,
|
||||
kill_browser_profile, launch_browser_profile, list_browser_profiles, rename_profile, should_update_browser_cache, update_profile_proxy,
|
||||
update_profile_version,
|
||||
check_browser_exists, check_browser_status, create_browser_profile, create_browser_profile_new,
|
||||
delete_profile, download_browser, fetch_browser_versions, fetch_browser_versions_cached_first,
|
||||
fetch_browser_versions_detailed, fetch_browser_versions_with_count,
|
||||
fetch_browser_versions_with_count_cached_first, get_cached_browser_versions_detailed,
|
||||
get_downloaded_browser_versions, get_saved_mullvad_releases, get_supported_browsers,
|
||||
is_browser_downloaded, kill_browser_profile, launch_browser_profile, list_browser_profiles,
|
||||
rename_profile, should_update_browser_cache, update_profile_proxy, update_profile_version,
|
||||
};
|
||||
|
||||
use settings_manager::{
|
||||
disable_default_browser_prompt, get_app_settings, save_app_settings,
|
||||
should_show_settings_on_startup, get_table_sorting_settings, save_table_sorting_settings,
|
||||
disable_default_browser_prompt, 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, smart_open_url};
|
||||
use default_browser::{
|
||||
is_default_browser, open_url_with_profile, set_as_default_browser, smart_open_url,
|
||||
};
|
||||
|
||||
use version_updater::{trigger_manual_version_update, get_version_update_status, get_version_updater, check_version_update_needed, force_version_update_check};
|
||||
use version_updater::{
|
||||
check_version_update_needed, force_version_update_check, get_version_update_status,
|
||||
get_version_updater, trigger_manual_version_update,
|
||||
};
|
||||
|
||||
use auto_updater::{
|
||||
check_for_browser_updates, start_browser_update, complete_browser_update,
|
||||
is_browser_disabled_for_update, dismiss_update_notification,
|
||||
complete_browser_update_with_auto_update,
|
||||
mark_auto_update_download, remove_auto_update_download, is_auto_update_download,
|
||||
check_for_browser_updates, complete_browser_update, complete_browser_update_with_auto_update,
|
||||
dismiss_update_notification, is_auto_update_download, is_browser_disabled_for_update,
|
||||
mark_auto_update_download, remove_auto_update_download, start_browser_update,
|
||||
};
|
||||
|
||||
#[tauri::command]
|
||||
@@ -57,13 +62,14 @@ fn greet() -> String {
|
||||
#[tauri::command]
|
||||
async fn handle_url_open(app: tauri::AppHandle, url: String) -> Result<(), String> {
|
||||
println!("handle_url_open called with URL: {}", url);
|
||||
|
||||
|
||||
// Check if the main window exists and is ready
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
if window.is_visible().unwrap_or(false) {
|
||||
// Window is visible, emit event directly
|
||||
println!("Main window is visible, emitting show-profile-selector event");
|
||||
app.emit("show-profile-selector", url.clone())
|
||||
app
|
||||
.emit("show-profile-selector", url.clone())
|
||||
.map_err(|e| format!("Failed to emit URL open event: {}", e))?;
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
@@ -79,7 +85,7 @@ async fn handle_url_open(app: tauri::AppHandle, url: String) -> Result<(), Strin
|
||||
let mut pending = PENDING_URLS.lock().unwrap();
|
||||
pending.push(url);
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -91,10 +97,13 @@ async fn check_and_handle_startup_url(app_handle: tauri::AppHandle) -> Result<bo
|
||||
pending.clear(); // Clear after getting them
|
||||
urls
|
||||
};
|
||||
|
||||
|
||||
if !pending_urls.is_empty() {
|
||||
println!("Handling {} pending URLs from frontend request", pending_urls.len());
|
||||
|
||||
println!(
|
||||
"Handling {} pending URLs from frontend request",
|
||||
pending_urls.len()
|
||||
);
|
||||
|
||||
for url in pending_urls {
|
||||
println!("Emitting show-profile-selector event for URL: {}", url);
|
||||
if let Err(e) = app_handle.emit("show-profile-selector", url.clone()) {
|
||||
@@ -102,10 +111,10 @@ async fn check_and_handle_startup_url(app_handle: tauri::AppHandle) -> Result<bo
|
||||
return Err(format!("Failed to emit URL event: {}", e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
@@ -119,13 +128,13 @@ pub fn run() {
|
||||
.setup(|app| {
|
||||
// Set up deep link handler
|
||||
let handle = app.handle().clone();
|
||||
|
||||
|
||||
#[cfg(any(windows, target_os = "linux"))]
|
||||
{
|
||||
// For Windows and Linux, register all deep links at runtime for development
|
||||
app.deep_link().register_all()?;
|
||||
}
|
||||
|
||||
|
||||
// Handle deep links - this works for both scenarios:
|
||||
// 1. App is running and URL is opened
|
||||
// 2. App is not running and URL causes app to launch
|
||||
@@ -136,10 +145,10 @@ pub fn run() {
|
||||
for url in urls {
|
||||
let url_string = url.to_string();
|
||||
println!("Deep link received: {}", url_string);
|
||||
|
||||
|
||||
// Clone the handle for each async task
|
||||
let handle_clone = handle.clone();
|
||||
|
||||
|
||||
// Handle the URL asynchronously
|
||||
tauri::async_runtime::spawn(async move {
|
||||
if let Err(e) = handle_url_open(handle_clone, url_string.clone()).await {
|
||||
@@ -155,14 +164,14 @@ pub fn run() {
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let version_updater = get_version_updater();
|
||||
let mut updater_guard = version_updater.lock().await;
|
||||
|
||||
|
||||
// Set the app handle
|
||||
updater_guard.set_app_handle(app_handle).await;
|
||||
|
||||
|
||||
// Start the background updates
|
||||
updater_guard.start_background_updates().await;
|
||||
});
|
||||
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
@@ -173,7 +182,7 @@ pub fn run() {
|
||||
is_browser_downloaded,
|
||||
check_browser_exists,
|
||||
create_browser_profile_new,
|
||||
create_browser_profile, // Keep for backward compatibility
|
||||
create_browser_profile, // Keep for backward compatibility
|
||||
list_browser_profiles,
|
||||
launch_browser_profile,
|
||||
fetch_browser_versions,
|
||||
|
||||
@@ -174,8 +174,6 @@ impl ProxyManager {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Get stored proxy info for a profile
|
||||
pub fn get_profile_proxy_info(&self, profile_name: &str) -> Option<(String, u16)> {
|
||||
let profile_proxies = self.profile_proxies.lock().unwrap();
|
||||
|
||||
@@ -66,7 +66,11 @@ impl SettingsManager {
|
||||
|
||||
pub fn get_settings_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(if cfg!(debug_assertions) {
|
||||
"DonutBrowserDev"
|
||||
} else {
|
||||
"DonutBrowser"
|
||||
});
|
||||
path.push("settings");
|
||||
path
|
||||
}
|
||||
@@ -88,25 +92,31 @@ impl SettingsManager {
|
||||
}
|
||||
|
||||
let content = fs::read_to_string(&settings_file)?;
|
||||
|
||||
|
||||
// Parse the settings file - serde will use default values for missing fields
|
||||
match serde_json::from_str::<AppSettings>(&content) {
|
||||
Ok(settings) => {
|
||||
// Save the settings back to ensure any missing fields are written with defaults
|
||||
if let Err(e) = self.save_settings(&settings) {
|
||||
eprintln!("Warning: Failed to update settings file with defaults: {}", e);
|
||||
eprintln!(
|
||||
"Warning: Failed to update settings file with defaults: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
Ok(settings)
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Warning: Failed to parse settings file, using defaults: {}", e);
|
||||
eprintln!(
|
||||
"Warning: Failed to parse settings file, using defaults: {}",
|
||||
e
|
||||
);
|
||||
let default_settings = AppSettings::default();
|
||||
|
||||
|
||||
// Try to save default settings to fix the corrupted file
|
||||
if let Err(save_error) = self.save_settings(&default_settings) {
|
||||
eprintln!("Warning: Failed to save default settings: {}", save_error);
|
||||
}
|
||||
|
||||
|
||||
Ok(default_settings)
|
||||
}
|
||||
}
|
||||
@@ -136,7 +146,10 @@ impl SettingsManager {
|
||||
Ok(sorting)
|
||||
}
|
||||
|
||||
pub fn save_table_sorting(&self, sorting: &TableSortingSettings) -> Result<(), Box<dyn std::error::Error>> {
|
||||
pub fn save_table_sorting(
|
||||
&self,
|
||||
sorting: &TableSortingSettings,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let settings_dir = self.get_settings_dir();
|
||||
create_dir_all(&settings_dir)?;
|
||||
|
||||
|
||||
+483
-450
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user