mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-06-11 17:27:54 +02:00
229 lines
7.5 KiB
TypeScript
229 lines
7.5 KiB
TypeScript
import { getBrowserDisplayName } from "@/lib/browser-utils";
|
|
import { showAutoUpdateToast, showToast } from "@/lib/toast-utils";
|
|
import { invoke } from "@tauri-apps/api/core";
|
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
|
|
interface UpdateNotification {
|
|
id: string;
|
|
browser: string;
|
|
current_version: string;
|
|
new_version: string;
|
|
affected_profiles: string[];
|
|
is_stable_update: boolean;
|
|
timestamp: number;
|
|
is_rolling_release: boolean;
|
|
}
|
|
|
|
export function useUpdateNotifications(
|
|
onProfilesUpdated?: () => Promise<void>,
|
|
) {
|
|
const [notifications, setNotifications] = useState<UpdateNotification[]>([]);
|
|
const [updatingBrowsers, setUpdatingBrowsers] = useState<Set<string>>(
|
|
new Set(),
|
|
);
|
|
const [processedNotifications, setProcessedNotifications] = useState<
|
|
Set<string>
|
|
>(new Set());
|
|
|
|
// Add refs to track ongoing operations to prevent duplicates
|
|
const isCheckingForUpdates = useRef(false);
|
|
const activeDownloads = useRef<Set<string>>(new Set()); // Track "browser-version" keys
|
|
|
|
const checkForUpdates = useCallback(async () => {
|
|
// Prevent multiple simultaneous calls
|
|
if (isCheckingForUpdates.current) {
|
|
console.log("Already checking for updates, skipping duplicate call");
|
|
return;
|
|
}
|
|
|
|
isCheckingForUpdates.current = true;
|
|
|
|
try {
|
|
const updates = await invoke<UpdateNotification[]>(
|
|
"check_for_browser_updates",
|
|
);
|
|
|
|
// Filter out already processed notifications
|
|
const newUpdates = updates.filter((notification) => {
|
|
return !processedNotifications.has(notification.id);
|
|
});
|
|
|
|
setNotifications(newUpdates);
|
|
|
|
// Automatically start downloads for new update notifications
|
|
for (const notification of newUpdates) {
|
|
if (!processedNotifications.has(notification.id)) {
|
|
setProcessedNotifications((prev) =>
|
|
new Set(prev).add(notification.id),
|
|
);
|
|
// Start automatic update without user interaction
|
|
void handleAutoUpdate(
|
|
notification.browser,
|
|
notification.new_version,
|
|
notification.id,
|
|
);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to check for updates:", error);
|
|
} finally {
|
|
isCheckingForUpdates.current = false;
|
|
}
|
|
}, [processedNotifications]);
|
|
|
|
const handleAutoUpdate = useCallback(
|
|
async (browser: string, newVersion: string, notificationId: string) => {
|
|
const downloadKey = `${browser}-${newVersion}`;
|
|
|
|
// Check if this download is already in progress
|
|
if (activeDownloads.current.has(downloadKey)) {
|
|
console.log(
|
|
`Download already in progress for ${downloadKey}, skipping duplicate`,
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Mark download as active
|
|
activeDownloads.current.add(downloadKey);
|
|
|
|
try {
|
|
setUpdatingBrowsers((prev) => new Set(prev).add(browser));
|
|
const browserDisplayName = getBrowserDisplayName(browser);
|
|
|
|
// Dismiss the notification in the backend
|
|
await invoke("dismiss_update_notification", {
|
|
notificationId,
|
|
});
|
|
|
|
// Show auto-update started toast
|
|
showAutoUpdateToast(browserDisplayName, newVersion);
|
|
|
|
try {
|
|
// Check if browser already exists before downloading
|
|
const isDownloaded = await invoke<boolean>("check_browser_exists", {
|
|
browserStr: browser,
|
|
version: newVersion,
|
|
});
|
|
|
|
if (isDownloaded) {
|
|
// Browser already exists, skip download and go straight to profile update
|
|
console.log(
|
|
`${browserDisplayName} ${newVersion} already exists, skipping download`,
|
|
);
|
|
} else {
|
|
// Mark download as auto-update in the backend for toast suppression
|
|
await invoke("mark_auto_update_download", {
|
|
browser,
|
|
version: newVersion,
|
|
});
|
|
|
|
// Download the browser (progress will be handled by use-browser-download hook)
|
|
await invoke("download_browser", {
|
|
browserStr: browser,
|
|
version: newVersion,
|
|
});
|
|
}
|
|
|
|
// Complete the update with auto-update of profile versions
|
|
const updatedProfiles = await invoke<string[]>(
|
|
"complete_browser_update_with_auto_update",
|
|
{
|
|
browser,
|
|
newVersion,
|
|
},
|
|
);
|
|
|
|
// Show success message based on whether profiles were updated
|
|
if (updatedProfiles.length > 0) {
|
|
const profileText =
|
|
updatedProfiles.length === 1
|
|
? `Profile "${updatedProfiles[0]}" has been updated`
|
|
: `${updatedProfiles.length} profiles have been updated`;
|
|
|
|
showToast({
|
|
id: `auto-update-success-${browser}-${newVersion}`,
|
|
type: "success",
|
|
title: `${browserDisplayName} update completed`,
|
|
description: `${profileText} to version ${newVersion}. To update running profiles, restart them.`,
|
|
duration: 5000,
|
|
});
|
|
} else {
|
|
showToast({
|
|
id: `auto-update-success-${browser}-${newVersion}`,
|
|
type: "success",
|
|
title: `${browserDisplayName} update ready`,
|
|
description:
|
|
"All affected profiles are currently running. To update them, restart them.",
|
|
duration: 5000,
|
|
});
|
|
}
|
|
|
|
// Trigger profile refresh to update UI with new versions
|
|
if (onProfilesUpdated) {
|
|
void onProfilesUpdated();
|
|
}
|
|
} catch (downloadError) {
|
|
console.error("Failed to download browser:", downloadError);
|
|
|
|
// Clean up auto-update tracking on error
|
|
try {
|
|
await invoke("remove_auto_update_download", {
|
|
browser,
|
|
version: newVersion,
|
|
});
|
|
} catch (e) {
|
|
console.error("Failed to clean up auto-update tracking:", e);
|
|
}
|
|
|
|
showToast({
|
|
id: `auto-update-error-${browser}-${newVersion}`,
|
|
type: "error",
|
|
title: `Failed to download ${browserDisplayName} ${newVersion}`,
|
|
description: String(downloadError),
|
|
duration: 6000,
|
|
});
|
|
throw downloadError;
|
|
}
|
|
|
|
// Don't call checkForUpdates() again here as it can cause recursion and duplicates
|
|
// The periodic checks will handle finding any remaining updates
|
|
} catch (error) {
|
|
console.error("Failed to start auto-update:", error);
|
|
const browserDisplayName = getBrowserDisplayName(browser);
|
|
showToast({
|
|
id: `auto-update-error-${browser}-${newVersion}`,
|
|
type: "error",
|
|
title: `Failed to update ${browserDisplayName}`,
|
|
description: String(error),
|
|
duration: 6000,
|
|
});
|
|
} finally {
|
|
// Remove from active downloads and updating browsers
|
|
activeDownloads.current.delete(downloadKey);
|
|
setUpdatingBrowsers((prev) => {
|
|
const next = new Set(prev);
|
|
next.delete(browser);
|
|
return next;
|
|
});
|
|
}
|
|
},
|
|
[onProfilesUpdated],
|
|
);
|
|
|
|
// Clean up notifications when they're no longer needed
|
|
useEffect(() => {
|
|
// Remove notifications that have been processed
|
|
setNotifications((prev) =>
|
|
prev.filter(
|
|
(notification) => !processedNotifications.has(notification.id),
|
|
),
|
|
);
|
|
}, [processedNotifications]);
|
|
|
|
return {
|
|
notifications,
|
|
checkForUpdates,
|
|
isUpdating: (browser: string) => updatingBrowsers.has(browser),
|
|
};
|
|
}
|