import React, { useState, useEffect, useRef } from "react"; import { useTranslation } from "react-i18next"; import { check } from "@tauri-apps/plugin-updater"; import { relaunch } from "@tauri-apps/plugin-process"; import { listen } from "@tauri-apps/api/event"; import { ProgressBar } from "../shared"; import { useSettings } from "../../hooks/useSettings"; interface UpdateCheckerProps { className?: string; } const UpdateChecker: React.FC = ({ className = "" }) => { const { t } = useTranslation(); // Update checking state const [isChecking, setIsChecking] = useState(false); const [updateAvailable, setUpdateAvailable] = useState(false); const [isInstalling, setIsInstalling] = useState(false); const [downloadProgress, setDownloadProgress] = useState(0); const [showUpToDate, setShowUpToDate] = useState(false); const { settings, isLoading } = useSettings(); const settingsLoaded = !isLoading && settings !== null; const updateChecksEnabled = settings?.update_checks_enabled ?? false; const upToDateTimeoutRef = useRef>(); const isManualCheckRef = useRef(false); const downloadedBytesRef = useRef(0); const contentLengthRef = useRef(0); useEffect(() => { // Wait for settings to load before doing anything if (!settingsLoaded) return; if (!updateChecksEnabled) { if (upToDateTimeoutRef.current) { clearTimeout(upToDateTimeoutRef.current); } setIsChecking(false); setUpdateAvailable(false); setShowUpToDate(false); return; } checkForUpdates(); // Listen for update check events const updateUnlisten = listen("check-for-updates", () => { handleManualUpdateCheck(); }); return () => { if (upToDateTimeoutRef.current) { clearTimeout(upToDateTimeoutRef.current); } updateUnlisten.then((fn) => fn()); }; }, [settingsLoaded, updateChecksEnabled]); // Update checking functions const checkForUpdates = async () => { if (!updateChecksEnabled || isChecking) return; try { setIsChecking(true); const update = await check(); if (update) { setUpdateAvailable(true); setShowUpToDate(false); } else { setUpdateAvailable(false); if (isManualCheckRef.current) { setShowUpToDate(true); if (upToDateTimeoutRef.current) { clearTimeout(upToDateTimeoutRef.current); } upToDateTimeoutRef.current = setTimeout(() => { setShowUpToDate(false); }, 3000); } } } catch (error) { console.error("Failed to check for updates:", error); } finally { setIsChecking(false); isManualCheckRef.current = false; } }; const handleManualUpdateCheck = () => { if (!updateChecksEnabled) return; isManualCheckRef.current = true; checkForUpdates(); }; const installUpdate = async () => { if (!updateChecksEnabled) return; try { setIsInstalling(true); setDownloadProgress(0); downloadedBytesRef.current = 0; contentLengthRef.current = 0; const update = await check(); if (!update) { console.log("No update available during install attempt"); return; } await update.downloadAndInstall((event) => { switch (event.event) { case "Started": downloadedBytesRef.current = 0; contentLengthRef.current = event.data.contentLength ?? 0; break; case "Progress": downloadedBytesRef.current += event.data.chunkLength; const progress = contentLengthRef.current > 0 ? Math.round( (downloadedBytesRef.current / contentLengthRef.current) * 100, ) : 0; setDownloadProgress(Math.min(progress, 100)); break; } }); await relaunch(); } catch (error) { console.error("Failed to install update:", error); } finally { setIsInstalling(false); setDownloadProgress(0); downloadedBytesRef.current = 0; contentLengthRef.current = 0; } }; // Update status functions const getUpdateStatusText = () => { if (!updateChecksEnabled) { return t("footer.updateCheckingDisabled"); } if (isInstalling) { return downloadProgress > 0 && downloadProgress < 100 ? t("footer.downloading", { progress: downloadProgress.toString().padStart(3), }) : downloadProgress === 100 ? t("footer.installing") : t("footer.preparing"); } if (isChecking) return t("footer.checkingUpdates"); if (showUpToDate) return t("footer.upToDate"); if (updateAvailable) return t("footer.updateAvailableShort"); return t("footer.checkForUpdates"); }; const getUpdateStatusAction = () => { if (!updateChecksEnabled) return undefined; if (updateAvailable && !isInstalling) return installUpdate; if (!isChecking && !isInstalling && !updateAvailable) return handleManualUpdateCheck; return undefined; }; const isUpdateDisabled = !updateChecksEnabled || isChecking || isInstalling; const isUpdateClickable = !isUpdateDisabled && (updateAvailable || (!isChecking && !showUpToDate)); return (
{isUpdateClickable ? ( ) : ( {getUpdateStatusText()} )} {isInstalling && downloadProgress > 0 && downloadProgress < 100 && ( )}
); }; export default UpdateChecker;