refactor: better state control for browser download

This commit is contained in:
zhom
2025-06-22 06:23:27 +04:00
parent 3d3a3b3816
commit f51aa9ed85
6 changed files with 88 additions and 20 deletions
+40
View File
@@ -1,8 +1,10 @@
use crate::proxy_manager::PROXY_MANAGER; use crate::proxy_manager::PROXY_MANAGER;
use directories::BaseDirs; use directories::BaseDirs;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::fs::{self, create_dir_all}; use std::fs::{self, create_dir_all};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
use sysinfo::{Pid, System}; use sysinfo::{Pid, System};
use tauri::Emitter; use tauri::Emitter;
@@ -35,6 +37,11 @@ fn default_release_type() -> String {
"stable".to_string() "stable".to_string()
} }
// Global state to track currently downloading browsers
lazy_static::lazy_static! {
static ref DOWNLOADING_BROWSERS: Arc<Mutex<HashSet<String>>> = Arc::new(Mutex::new(HashSet::new()));
}
// Platform-specific modules // Platform-specific modules
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
mod macos { mod macos {
@@ -2345,6 +2352,18 @@ impl BrowserRunner {
browser_str: String, browser_str: String,
version: String, version: String,
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
// Check if this browser type is already being downloaded
{
let mut downloading = DOWNLOADING_BROWSERS.lock().unwrap();
if downloading.contains(&browser_str) {
return Err(format!(
"Browser '{browser_str}' is already being downloaded. Please wait for the current download to complete."
).into());
}
// Mark this browser as being downloaded
downloading.insert(browser_str.clone());
}
let browser_type = let browser_type =
BrowserType::from_str(&browser_str).map_err(|e| format!("Invalid browser type: {e}"))?; BrowserType::from_str(&browser_str).map_err(|e| format!("Invalid browser type: {e}"))?;
let browser = create_browser(browser_type.clone()); let browser = create_browser(browser_type.clone());
@@ -2428,6 +2447,11 @@ impl BrowserRunner {
// Clean up failed download // Clean up failed download
let _ = registry.cleanup_failed_download(&browser_str, &version); let _ = registry.cleanup_failed_download(&browser_str, &version);
let _ = registry.save(); let _ = registry.save();
// Remove browser from downloading set on error
{
let mut downloading = DOWNLOADING_BROWSERS.lock().unwrap();
downloading.remove(&browser_str);
}
return Err(format!("Failed to download browser: {e}").into()); return Err(format!("Failed to download browser: {e}").into());
} }
}; };
@@ -2455,6 +2479,11 @@ impl BrowserRunner {
// Clean up failed download // Clean up failed download
let _ = registry.cleanup_failed_download(&browser_str, &version); let _ = registry.cleanup_failed_download(&browser_str, &version);
let _ = registry.save(); let _ = registry.save();
// Remove browser from downloading set on error
{
let mut downloading = DOWNLOADING_BROWSERS.lock().unwrap();
downloading.remove(&browser_str);
}
return Err(format!("Failed to extract browser: {e}").into()); return Err(format!("Failed to extract browser: {e}").into());
} }
} }
@@ -2484,6 +2513,11 @@ impl BrowserRunner {
if !browser.is_version_downloaded(&version, &binaries_dir) { if !browser.is_version_downloaded(&version, &binaries_dir) {
let _ = registry.cleanup_failed_download(&browser_str, &version); let _ = registry.cleanup_failed_download(&browser_str, &version);
let _ = registry.save(); let _ = registry.save();
// Remove browser from downloading set on verification failure
{
let mut downloading = DOWNLOADING_BROWSERS.lock().unwrap();
downloading.remove(&browser_str);
}
return Err("Browser download completed but verification failed".into()); return Err("Browser download completed but verification failed".into());
} }
@@ -2514,6 +2548,12 @@ impl BrowserRunner {
}; };
let _ = app_handle.emit("download-progress", &progress); let _ = app_handle.emit("download-progress", &progress);
// Remove browser from downloading set
{
let mut downloading = DOWNLOADING_BROWSERS.lock().unwrap();
downloading.remove(&browser_str);
}
Ok(version) Ok(version)
} }
+1 -1
View File
@@ -27,9 +27,9 @@ export default function RootLayout({
className={`${geistSans.variable} ${geistMono.variable} antialiased`} className={`${geistSans.variable} ${geistMono.variable} antialiased`}
> >
<CustomThemeProvider> <CustomThemeProvider>
<WindowDragArea />
<TooltipProvider>{children}</TooltipProvider> <TooltipProvider>{children}</TooltipProvider>
<Toaster /> <Toaster />
<WindowDragArea />
</CustomThemeProvider> </CustomThemeProvider>
</body> </body>
</html> </html>
+15 -5
View File
@@ -79,17 +79,27 @@ export default function Home() {
if (missingBinaries.length > 0) { if (missingBinaries.length > 0) {
console.log("Found missing binaries:", missingBinaries); console.log("Found missing binaries:", missingBinaries);
// Group missing binaries by browser type to avoid concurrent downloads
const browserMap = new Map<string, string[]>();
for (const [profileName, browser, version] of missingBinaries) {
if (!browserMap.has(browser)) {
browserMap.set(browser, []);
}
const versions = browserMap.get(browser);
if (versions) {
versions.push(`${version} (for ${profileName})`);
}
}
// Show a toast notification about missing binaries and auto-download them // Show a toast notification about missing binaries and auto-download them
const missingList = missingBinaries const missingList = Array.from(browserMap.entries())
.map( .map(([browser, versions]) => `${browser}: ${versions.join(", ")}`)
([profileName, browser, version]) =>
`${browser} ${version} (for ${profileName})`,
)
.join(", "); .join(", ");
console.log(`Downloading missing binaries: ${missingList}`); console.log(`Downloading missing binaries: ${missingList}`);
try { try {
// Download missing binaries sequentially by browser type to prevent conflicts
const downloaded = await invoke<string[]>( const downloaded = await invoke<string[]>(
"ensure_all_binaries_exist", "ensure_all_binaries_exist",
); );
+1 -1
View File
@@ -41,7 +41,7 @@ export function WindowDragArea() {
return ( return (
<button <button
type="button" type="button"
className="fixed top-0 right-0 left-0 h-10 bg-transparent border-0 pointer-events-none z-9999" className="fixed top-0 right-0 left-0 h-10 bg-transparent border-0 z-9999"
style={{ style={{
// Ensure it's above all other content // Ensure it's above all other content
zIndex: 9999, zIndex: 9999,
+22 -3
View File
@@ -48,7 +48,9 @@ export function useBrowserDownload() {
[], [],
); );
const [downloadedVersions, setDownloadedVersions] = useState<string[]>([]); const [downloadedVersions, setDownloadedVersions] = useState<string[]>([]);
const [isDownloading, setIsDownloading] = useState(false); const [downloadingBrowsers, setDownloadingBrowsers] = useState<Set<string>>(
new Set(),
);
const [downloadProgress, setDownloadProgress] = const [downloadProgress, setDownloadProgress] =
useState<DownloadProgress | null>(null); useState<DownloadProgress | null>(null);
@@ -181,7 +183,7 @@ export function useBrowserDownload() {
suppressNotifications = false, suppressNotifications = false,
) => { ) => {
const browserName = getBrowserDisplayName(browserStr); const browserName = getBrowserDisplayName(browserStr);
setIsDownloading(true); setDownloadingBrowsers((prev) => new Set(prev).add(browserStr));
try { try {
// Check browser compatibility before attempting download // Check browser compatibility before attempting download
@@ -215,7 +217,11 @@ export function useBrowserDownload() {
} }
throw error; throw error;
} finally { } finally {
setIsDownloading(false); setDownloadingBrowsers((prev) => {
const next = new Set(prev);
next.delete(browserStr);
return next;
});
} }
}, },
[loadDownloadedVersions], [loadDownloadedVersions],
@@ -228,6 +234,17 @@ export function useBrowserDownload() {
[downloadedVersions], [downloadedVersions],
); );
// Check if a browser type is currently downloading
const isBrowserDownloading = useCallback(
(browserStr: string) => {
return downloadingBrowsers.has(browserStr);
},
[downloadingBrowsers],
);
// Legacy isDownloading for backwards compatibility
const isDownloading = downloadingBrowsers.size > 0;
// Listen for download progress events // Listen for download progress events
useEffect(() => { useEffect(() => {
const unlisten = listen<DownloadProgress>("download-progress", (event) => { const unlisten = listen<DownloadProgress>("download-progress", (event) => {
@@ -272,6 +289,8 @@ export function useBrowserDownload() {
availableVersions, availableVersions,
downloadedVersions, downloadedVersions,
isDownloading, isDownloading,
isBrowserDownloading,
downloadingBrowsers,
downloadProgress, downloadProgress,
loadVersions, loadVersions,
loadVersionsWithNewCount, loadVersionsWithNewCount,
+9 -10
View File
@@ -32,22 +32,21 @@ export function useUpdateNotifications(
// Add refs to track ongoing operations to prevent duplicates // Add refs to track ongoing operations to prevent duplicates
const isCheckingForUpdates = useRef(false); const isCheckingForUpdates = useRef(false);
const activeDownloads = useRef<Set<string>>(new Set()); // Track "browser-version" keys // Track browser types being downloaded (not browser-version pairs)
const activeDownloads = useRef<Set<string>>(new Set()); // Track browser types
const handleAutoUpdate = useCallback( const handleAutoUpdate = useCallback(
async (browser: string, newVersion: string, notificationId: string) => { async (browser: string, newVersion: string, notificationId: string) => {
const downloadKey = `${browser}-${newVersion}`; // Check if this browser type is already being downloaded
if (activeDownloads.current.has(browser)) {
// Check if this download is already in progress
if (activeDownloads.current.has(downloadKey)) {
console.log( console.log(
`Download already in progress for ${downloadKey}, skipping duplicate`, `Download already in progress for browser type ${browser}, skipping duplicate auto-update`,
); );
return; return;
} }
// Mark download as active and disable browser // Mark browser type as active and disable browser
activeDownloads.current.add(downloadKey); activeDownloads.current.add(browser);
setUpdatingBrowsers((prev) => new Set(prev).add(browser)); setUpdatingBrowsers((prev) => new Set(prev).add(browser));
try { try {
@@ -158,8 +157,8 @@ export function useUpdateNotifications(
console.error("Failed to start auto-update:", error); console.error("Failed to start auto-update:", error);
throw error; throw error;
} finally { } finally {
// Clean up // Clean up - remove browser type from active downloads
activeDownloads.current.delete(downloadKey); activeDownloads.current.delete(browser);
setUpdatingBrowsers((prev) => { setUpdatingBrowsers((prev) => {
const next = new Set(prev); const next = new Set(prev);
next.delete(browser); next.delete(browser);