Have Claude Code make downloads a proper queue system

This commit is contained in:
2026-02-14 02:53:42 -07:00
parent 118fad49d8
commit a618c76292

View File

@@ -75,6 +75,8 @@ export default function RootLayout ({
const [downloadProgress, setDownloadProgress] = useState<DownloadProgress[]>( const [downloadProgress, setDownloadProgress] = useState<DownloadProgress[]>(
[] []
) )
const [downloadQueue, setDownloadQueue] = useState<string[]>([])
const [isProcessingQueue, setIsProcessingQueue] = useState<boolean>(false)
const [managingVersion, setManagingVersion] = useState<string | null>(null) const [managingVersion, setManagingVersion] = useState<string | null>(null)
const [viewingInfoFromDownloads, setViewingInfoFromDownloads] = const [viewingInfoFromDownloads, setViewingInfoFromDownloads] =
useState<boolean>(false) useState<boolean>(false)
@@ -84,6 +86,7 @@ export default function RootLayout ({
const pathname = usePathname() const pathname = usePathname()
const revisionCheck = useRef(false) const revisionCheck = useRef(false)
const previousQueueLength = useRef(0)
function getSpecialVersionsList (game?: number): GameVersion[] { function getSpecialVersionsList (game?: number): GameVersion[] {
if (!normalConfig || !serverVersionList) return [] if (!normalConfig || !serverVersionList) return []
@@ -184,7 +187,7 @@ export default function RootLayout ({
return `${d}d ${h}h` return `${d}d ${h}h`
} }
function closePopup () { const closePopup = useCallback(() => {
if (popupMode == 0 && selectedGame && pathname === '/') { if (popupMode == 0 && selectedGame && pathname === '/') {
setSelectedGame(null) setSelectedGame(null)
setSelectedVersionList([]) setSelectedVersionList([])
@@ -197,7 +200,7 @@ export default function RootLayout ({
setFadeOut(true) setFadeOut(true)
setTimeout(() => setShowPopup(false), 200) setTimeout(() => setShowPopup(false), 200)
} }
} }, [popupMode, selectedGame, pathname, viewingInfoFromDownloads])
useEffect(() => { useEffect(() => {
let unlistenProgress: (() => void) | null = null let unlistenProgress: (() => void) | null = null
@@ -305,91 +308,129 @@ export default function RootLayout ({
if (list.length === 0) return if (list.length === 0) return
setSelectedVersionList([]) 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 => version =>
new DownloadProgress(version, 0, 0, false, true, false, false, 0, 0) 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) { useEffect(() => {
const info = getVersionInfo(download.version) if (isProcessingQueue || downloadQueue.length === 0) return
if (!info) {
setDownloadProgress(prev =>
prev.filter(d => d.version !== download.version)
)
continue
}
const gameInfo = getGameInfo(info.game) const processNextDownload = async () => {
if (!gameInfo) { setIsProcessingQueue(true)
setDownloadProgress(prev =>
prev.filter(d => d.version !== download.version)
)
continue
}
setDownloadProgress(prev => const versionId = downloadQueue[0]
prev.map(d => const info = getVersionInfo(versionId)
d.version === download.version ? { ...d, queued: false } : d
)
)
try { if (!info) {
await axios.get( setDownloadProgress(prev => prev.filter(d => d.version !== versionId))
'https://games.lncvrt.xyz/api/launcher/download?id=' + info.id setDownloadQueue(prev => prev.slice(1))
) setIsProcessingQueue(false)
} catch {} return
const res = await invoke<string>('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 (normalConfig?.settings.allowNotifications) const gameInfo = getGameInfo(info.game)
await notifyUser('Downloads Finished', 'All downloads have finished.') if (!gameInfo) {
setDownloadProgress(prev => prev.filter(d => d.version !== versionId))
setDownloadQueue(prev => prev.slice(1))
setIsProcessingQueue(false)
return
}
setFadeOut(true) setDownloadProgress(prev =>
setTimeout(() => setShowPopup(false), 200) prev.map(d => (d.version === versionId ? { ...d, queued: false } : d))
}, )
[getGameInfo, getVersionInfo, normalConfig]
) try {
await axios.get(
'https://games.lncvrt.xyz/api/launcher/download?id=' + info.id
)
} catch {}
const res = await invoke<string>('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(() => { useEffect(() => {
if (revisionCheck.current) return if (revisionCheck.current) return
@@ -696,85 +737,111 @@ export default function RootLayout ({
<> <>
<p className='text-xl text-center'>Downloads</p> <p className='text-xl text-center'>Downloads</p>
<div className='popup-content'> <div className='popup-content'>
{downloadProgress.map((v, i) => ( {downloadProgress.map((v, i) => {
<div const queuePosition = downloadQueue.indexOf(
key={i} v.version
className='popup-entry flex flex-col justify-between' )
> return (
<p className='text-2xl text-center'> <div
{getVersionInfo(v.version)?.displayName} key={i}
</p> className='popup-entry flex flex-col justify-between'
<div className='mt-6.25 flex items-center justify-between'> >
{v.failed ? ( <p className='text-2xl text-center'>
<> {getVersionInfo(v.version)?.displayName}
<div className='flex items-center'> </p>
<span className='text-red-500 inline-block w-full text-center'> <div className='mt-6.25 flex items-center justify-between'>
Download failed {v.failed ? (
<>
<div className='flex items-center'>
<span className='text-red-500 inline-block w-full text-center'>
Download failed
</span>
<button
className='button btntheme3 ml-30 mb-2'
onClick={() => {
setDownloadProgress(prev =>
prev.filter(
d => d.version !== v.version
)
)
}}
title='Click to remove this version from this menu.'
>
Remove
</button>
</div>
</>
) : v.queued ? (
<div className='flex items-center justify-between w-full'>
<span className='text-yellow-500 inline-block text-center flex-1'>
{queuePosition === 0
? 'Starting soon...'
: `Queued (Position ${
queuePosition + 1
})`}
</span> </span>
<button <button
className='button btntheme3 ml-30 mb-2' className='button btntheme3 -ml-1.25'
onClick={() => { onClick={() => {
setDownloadQueue(prev =>
prev.filter(
id => id !== v.version
)
)
setDownloadProgress(prev => setDownloadProgress(prev =>
prev.filter( prev.filter(
d => d.version !== v.version d => d.version !== v.version
) )
) )
}} }}
title='Click to remove this version from this menu.' title='Click to remove this version from the download queue.'
> >
Cancel Cancel
</button> </button>
</div> </div>
</> ) : v.hash_checking ? (
) : v.queued ? ( <span className='text-blue-500 inline-block w-full text-center'>
<span className='text-yellow-500 inline-block w-full text-center'> Checking hash...
Queued </span>
</span> ) : v.finishing ? (
) : v.queued ? ( <span className='text-green-500 inline-block w-full text-center'>
<span className='text-yellow-500 inline-block w-full text-center'> Finishing...
Queued </span>
</span> ) : (
) : v.hash_checking ? ( <div className='flex flex-col gap-1 w-full'>
<span className='text-blue-500 inline-block w-full text-center'> <span className='text-center'>
Checking hash... Downloaded{' '}
</span> {prettyBytes(v.progressBytes, {
) : v.finishing ? (
<span className='text-green-500 inline-block w-full text-center'>
Finishing...
</span>
) : (
<div className='flex flex-col gap-1 w-full'>
<span className='text-center'>
Downloaded{' '}
{prettyBytes(v.progressBytes, {
minimumFractionDigits: 1,
maximumFractionDigits: 1
})}{' '}
of{' '}
{prettyBytes(
getVersionInfo(v.version)?.size ?? 0,
{
minimumFractionDigits: 1, minimumFractionDigits: 1,
maximumFractionDigits: 1 maximumFractionDigits: 1
} })}{' '}
)}{' '} of{' '}
(ETA: {formatEtaSmart(v.etaSecs)} &bull; {prettyBytes(
Speed:{' '} getVersionInfo(v.version)?.size ??
{prettyBytes(v.speed, { 0,
minimumFractionDigits: 1, {
maximumFractionDigits: 1 minimumFractionDigits: 1,
})} maximumFractionDigits: 1
/s) }
</span> )}{' '}
<ProgressBar (ETA: {formatEtaSmart(v.etaSecs)}{' '}
progress={v.progress} &bull; Speed:{' '}
className='w-full' {prettyBytes(v.speed, {
/> minimumFractionDigits: 1,
</div> maximumFractionDigits: 1
)} })}
/s)
</span>
<ProgressBar
progress={v.progress}
className='w-full'
/>
</div>
)}
</div>
</div> </div>
</div> )
))} })}
</div> </div>
</> </>
) : popupMode === 2 ? ( ) : popupMode === 2 ? (
@@ -787,7 +854,6 @@ export default function RootLayout ({
<div className='popup-content flex flex-col items-center justify-center gap-2 h-full'> <div className='popup-content flex flex-col items-center justify-center gap-2 h-full'>
<button <button
className='button btntheme2' className='button btntheme2'
disabled={downloadProgress.length != 0}
onClick={async () => { onClick={async () => {
closePopup() closePopup()
@@ -822,7 +888,6 @@ export default function RootLayout ({
</button> </button>
<button <button
className='button btntheme2' className='button btntheme2'
disabled={downloadProgress.length != 0}
onClick={async () => { onClick={async () => {
//change popup to downloads //change popup to downloads
setManagingVersion(null) setManagingVersion(null)
@@ -896,23 +961,31 @@ export default function RootLayout ({
<button <button
className='button btntheme1 w-fit mt-2 -mb-4' className='button btntheme1 w-fit mt-2 -mb-4'
onClick={() => { onClick={() => {
setFadeOut(true) if (downloadedVersionsConfig) {
setTimeout(() => setShowPopup(false), 200)
if (downloadedVersionsConfig)
downloadVersions(selectedVersionList) downloadVersions(selectedVersionList)
}
}} }}
disabled={downloadProgress.length != 0} disabled={selectedVersionList.length === 0}
title={ title={
downloadProgress.length != 0 selectedVersionList.length === 0
? "You cannot download the versions as you have another download that hasn't completed." ? 'Select at least one version to download'
: `Click to download ${ : downloadProgress.length > 0 ||
downloadQueue.length > 0
? `Add ${selectedVersionList.length} version${
selectedVersionList.length == 1 ? '' : 's'
} to download queue`
: `Download ${
selectedVersionList.length selectedVersionList.length
} version${ } version${
selectedVersionList.length == 1 ? '' : 's' selectedVersionList.length == 1 ? '' : 's'
} of ${getGameInfo(selectedGame)?.name}` } of ${getGameInfo(selectedGame)?.name}`
} }
> >
Download {selectedVersionList.length} version {downloadProgress.length > 0 ||
downloadQueue.length > 0
? `Add ${selectedVersionList.length} to Queue`
: `Download ${selectedVersionList.length}`}{' '}
version
{selectedVersionList.length == 1 ? '' : 's'} {selectedVersionList.length == 1 ? '' : 's'}
</button> </button>
<button <button