diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 1abef62..b6813d4 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -75,6 +75,8 @@ export default function RootLayout ({ const [downloadProgress, setDownloadProgress] = useState( [] ) + const [downloadQueue, setDownloadQueue] = useState([]) + const [isProcessingQueue, setIsProcessingQueue] = useState(false) const [managingVersion, setManagingVersion] = useState(null) const [viewingInfoFromDownloads, setViewingInfoFromDownloads] = useState(false) @@ -84,6 +86,7 @@ export default function RootLayout ({ const pathname = usePathname() const revisionCheck = useRef(false) + const previousQueueLength = useRef(0) function getSpecialVersionsList (game?: number): GameVersion[] { if (!normalConfig || !serverVersionList) return [] @@ -184,7 +187,7 @@ export default function RootLayout ({ return `${d}d ${h}h` } - function closePopup () { + const closePopup = useCallback(() => { if (popupMode == 0 && selectedGame && pathname === '/') { setSelectedGame(null) setSelectedVersionList([]) @@ -197,7 +200,7 @@ export default function RootLayout ({ setFadeOut(true) setTimeout(() => setShowPopup(false), 200) } - } + }, [popupMode, selectedGame, pathname, viewingInfoFromDownloads]) useEffect(() => { let unlistenProgress: (() => void) | null = null @@ -305,91 +308,129 @@ export default function RootLayout ({ if (list.length === 0) return setSelectedVersionList([]) - const newDownloads = list.map( + const newVersions = list.filter( + version => + !downloadQueue.includes(version) && + !downloadProgress.some(d => d.version === version) + ) + + if (newVersions.length === 0) return + + const newDownloads = newVersions.map( version => new DownloadProgress(version, 0, 0, false, true, false, false, 0, 0) ) - setDownloadProgress(newDownloads) + setDownloadProgress(prev => [...prev, ...newDownloads]) + setDownloadQueue(prev => [...prev, ...newVersions]) + }, + [downloadQueue, downloadProgress] + ) - for (const download of newDownloads) { - const info = getVersionInfo(download.version) - if (!info) { - setDownloadProgress(prev => - prev.filter(d => d.version !== download.version) - ) - continue - } + useEffect(() => { + if (isProcessingQueue || downloadQueue.length === 0) return - const gameInfo = getGameInfo(info.game) - if (!gameInfo) { - setDownloadProgress(prev => - prev.filter(d => d.version !== download.version) - ) - continue - } + const processNextDownload = async () => { + setIsProcessingQueue(true) - setDownloadProgress(prev => - prev.map(d => - d.version === download.version ? { ...d, queued: false } : d - ) - ) + const versionId = downloadQueue[0] + const info = getVersionInfo(versionId) - try { - await axios.get( - 'https://games.lncvrt.xyz/api/launcher/download?id=' + info.id - ) - } catch {} - - const res = await invoke('download', { - url: info.downloadUrl, - name: info.id, - executable: info.executable, - hash: info.sha512sum - }) - - if (res === '1') { - setDownloadProgress(prev => - prev.filter(d => d.version !== download.version) - ) - setDownloadedVersionsConfig(prev => { - if (!prev) return prev - - const updated = { - ...prev, - list: { - ...prev.list, - [download.version]: Date.now() - } - } - - writeVersionsConfig(updated) - return updated - }) - } else { - setDownloadProgress(prev => - prev.map(d => - d.version === download.version - ? { ...d, queued: false, failed: true, progress: 0 } - : d - ) - ) - if (normalConfig?.settings.allowNotifications) - await notifyUser( - 'Download Failed', - `The download for version ${info.displayName} has failed.` - ) - } + if (!info) { + setDownloadProgress(prev => prev.filter(d => d.version !== versionId)) + setDownloadQueue(prev => prev.slice(1)) + setIsProcessingQueue(false) + return } - if (normalConfig?.settings.allowNotifications) - await notifyUser('Downloads Finished', 'All downloads have finished.') + const gameInfo = getGameInfo(info.game) + if (!gameInfo) { + setDownloadProgress(prev => prev.filter(d => d.version !== versionId)) + setDownloadQueue(prev => prev.slice(1)) + setIsProcessingQueue(false) + return + } - setFadeOut(true) - setTimeout(() => setShowPopup(false), 200) - }, - [getGameInfo, getVersionInfo, normalConfig] - ) + setDownloadProgress(prev => + prev.map(d => (d.version === versionId ? { ...d, queued: false } : d)) + ) + + try { + await axios.get( + 'https://games.lncvrt.xyz/api/launcher/download?id=' + info.id + ) + } catch {} + + const res = await invoke('download', { + url: info.downloadUrl, + name: info.id, + executable: info.executable, + hash: info.sha512sum + }) + + if (res === '1') { + setDownloadProgress(prev => prev.filter(d => d.version !== versionId)) + setDownloadedVersionsConfig(prev => { + if (!prev) return prev + + const updated = { + ...prev, + list: { + ...prev.list, + [versionId]: Date.now() + } + } + + writeVersionsConfig(updated) + return updated + }) + } else { + setDownloadProgress(prev => + prev.map(d => + d.version === versionId + ? { ...d, queued: false, failed: true, progress: 0 } + : d + ) + ) + if (normalConfig?.settings.allowNotifications) + await notifyUser( + 'Download Failed', + `The download for version ${info.displayName} has failed.` + ) + } + + setDownloadQueue(prev => prev.slice(1)) + setIsProcessingQueue(false) + } + + processNextDownload() + }, [ + downloadQueue, + isProcessingQueue, + getVersionInfo, + getGameInfo, + normalConfig + ]) + + useEffect(() => { + if ( + downloadQueue.length === 0 && + downloadProgress.length === 0 && + !isProcessingQueue && + previousQueueLength.current > 0 && + normalConfig?.settings.allowNotifications + ) { + notifyUser('Downloads Finished', 'All downloads have finished.') + setTimeout(() => closePopup(), 0) + } + previousQueueLength.current = downloadQueue.length + downloadProgress.length + }, [ + downloadQueue, + downloadProgress, + isProcessingQueue, + normalConfig, + closePopup + ]) useEffect(() => { if (revisionCheck.current) return @@ -696,85 +737,111 @@ export default function RootLayout ({ <>

Downloads

- {downloadProgress.map((v, i) => ( -
-

- {getVersionInfo(v.version)?.displayName} -

-
- {v.failed ? ( - <> -
- - Download failed + {downloadProgress.map((v, i) => { + const queuePosition = downloadQueue.indexOf( + v.version + ) + return ( +
+

+ {getVersionInfo(v.version)?.displayName} +

+
+ {v.failed ? ( + <> +
+ + Download failed + + +
+ + ) : v.queued ? ( +
+ + {queuePosition === 0 + ? 'Starting soon...' + : `Queued (Position ${ + queuePosition + 1 + })`}
- - ) : v.queued ? ( - - Queued… - - ) : v.queued ? ( - - Queued… - - ) : v.hash_checking ? ( - - Checking hash... - - ) : v.finishing ? ( - - Finishing... - - ) : ( -
- - Downloaded{' '} - {prettyBytes(v.progressBytes, { - minimumFractionDigits: 1, - maximumFractionDigits: 1 - })}{' '} - of{' '} - {prettyBytes( - getVersionInfo(v.version)?.size ?? 0, - { + ) : v.hash_checking ? ( + + Checking hash... + + ) : v.finishing ? ( + + Finishing... + + ) : ( +
+ + Downloaded{' '} + {prettyBytes(v.progressBytes, { minimumFractionDigits: 1, maximumFractionDigits: 1 - } - )}{' '} - (ETA: {formatEtaSmart(v.etaSecs)} • - Speed:{' '} - {prettyBytes(v.speed, { - minimumFractionDigits: 1, - maximumFractionDigits: 1 - })} - /s) - - -
- )} + })}{' '} + of{' '} + {prettyBytes( + getVersionInfo(v.version)?.size ?? + 0, + { + minimumFractionDigits: 1, + maximumFractionDigits: 1 + } + )}{' '} + (ETA: {formatEtaSmart(v.etaSecs)}{' '} + • Speed:{' '} + {prettyBytes(v.speed, { + minimumFractionDigits: 1, + maximumFractionDigits: 1 + })} + /s) +
+ +
+ )} +
-
- ))} + ) + })}
) : popupMode === 2 ? ( @@ -787,7 +854,6 @@ export default function RootLayout ({