Have Claude Code make downloads a proper queue system
This commit is contained in:
@@ -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)} •
|
{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}
|
• 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
|
||||||
|
|||||||
Reference in New Issue
Block a user