Fix cluster being stuck on downloading

This commit is contained in:
Wyvest 2025-12-27 02:21:15 +09:00
parent 60e5356120
commit a2329b59a5
4 changed files with 90 additions and 37 deletions

View file

@ -303,10 +303,18 @@ export type VersionType =
*/
"old_beta"
const ARGS_MAP = { 'oneclient':'{"getClustersGroupedByMajor":[],"checkForUpdate":[],"getVersions":[],"installUpdate":[],"getBundlesFor":["cluster_id"]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}', 'debug':'{"getArch":[],"getBuildTimestamp":[],"isInDev":[],"getPlatform":[],"getType":[],"getOsVersion":[],"getPackageVersion":[],"getFamily":[],"openDevTools":[],"getLocale":[],"getGitCommitHash":[]}', 'events':'{"ingress":["event"],"message":["event"],"process":["event"]}', 'core':'{"killProcess":["pid"],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"getClusterById":["id"],"setDiscordRPCMessage":["message"],"fetchMinecraftProfile":["uuid"],"getLoadersForVersion":["mc_version"],"installModpack":["modpack","cluster_id"],"openMsaLogin":[],"createCluster":["options"],"updateClusterProfile":["name","profile"],"getPackage":["provider","slug"],"readSettings":[],"updateClusterById":["id","request"],"removeCape":["access_token"],"removeCluster":["id"],"getWorlds":["id"],"removeUser":["uuid"],"writeSettings":["setting"],"launchCluster":["id","uuid","search_for_java"],"getUsers":[],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"setDefaultUser":["uuid"],"getMultiplePackages":["provider","slugs"],"getClusters":[],"getProfileOrDefault":["name"],"downloadExternalPackage":["package","cluster_id","force","skip_compatibility"],"getUsersFromAuthor":["provider","author"],"getGlobalProfile":[],"getRunningProcessesByClusterId":["cluster_id"],"createSettingsProfile":["name"],"getLinkedPackages":["cluster_id"],"removePackage":["cluster_id","package_hash"],"fetchLoggedInProfile":["access_token"],"changeSkin":["access_token","skin_url","skin_variant"],"convertUsernameUUID":["username_uuid"],"getDefaultUser":["fallback"],"searchPackages":["provider","query"],"getPackageBody":["provider","body"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getLogByName":["id","name"],"changeCape":["access_token","cape_uuid"],"getScreenshots":["id"],"getGameVersions":[],"open":["input"],"getUser":["uuid"],"isClusterRunning":["cluster_id"],"getLogs":["id"],"getRunningProcesses":[]}' }
export type Router = { 'events': { ingress: (event: IngressPayload) => Promise<void>,
message: (event: MessagePayload) => Promise<void>,
process: (event: ProcessPayload) => Promise<void> },
const ARGS_MAP = { 'debug':'{"getPackageVersion":[],"getPlatform":[],"getLocale":[],"openDevTools":[],"getGitCommitHash":[],"getFamily":[],"getArch":[],"getType":[],"getOsVersion":[],"isInDev":[],"getBuildTimestamp":[]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}', 'oneclient':'{"installUpdate":[],"getClustersGroupedByMajor":[],"getVersions":[],"getBundlesFor":["cluster_id"],"checkForUpdate":[]}', 'core':'{"readSettings":[],"getLinkedPackages":["cluster_id"],"createSettingsProfile":["name"],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"getMultiplePackages":["provider","slugs"],"searchPackages":["provider","query"],"isClusterRunning":["cluster_id"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getUsersFromAuthor":["provider","author"],"getProfileOrDefault":["name"],"createCluster":["options"],"getLoadersForVersion":["mc_version"],"getGlobalProfile":[],"getWorlds":["id"],"fetchMinecraftProfile":["uuid"],"getLogs":["id"],"fetchLoggedInProfile":["access_token"],"changeCape":["access_token","cape_uuid"],"launchCluster":["id","uuid","search_for_java"],"getUsers":[],"getGameVersions":[],"removePackage":["cluster_id","package_hash"],"removeCape":["access_token"],"convertUsernameUUID":["username_uuid"],"getRunningProcesses":[],"removeCluster":["id"],"getRunningProcessesByClusterId":["cluster_id"],"getClusters":[],"downloadExternalPackage":["package","cluster_id","force","skip_compatibility"],"setDefaultUser":["uuid"],"getUser":["uuid"],"getLogByName":["id","name"],"changeSkin":["access_token","skin_url","skin_variant"],"removeUser":["uuid"],"getScreenshots":["id"],"setClusterStage":["id","stage"],"setDiscordRPCMessage":["message"],"open":["input"],"updateClusterById":["id","request"],"installModpack":["modpack","cluster_id"],"getPackage":["provider","slug"],"getDefaultUser":["fallback"],"openMsaLogin":[],"writeSettings":["setting"],"getClusterById":["id"],"killProcess":["pid"],"updateClusterProfile":["name","profile"],"getPackageBody":["provider","body"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"]}', 'events':'{"message":["event"],"process":["event"],"ingress":["event"]}' }
export type Router = { 'debug': { openDevTools: () => Promise<void>,
isInDev: () => Promise<boolean>,
getArch: () => Promise<string>,
getFamily: () => Promise<string>,
getLocale: () => Promise<string | null>,
getType: () => Promise<string>,
getPlatform: () => Promise<string>,
getOsVersion: () => Promise<string>,
getGitCommitHash: () => Promise<string>,
getBuildTimestamp: () => Promise<string>,
getPackageVersion: () => Promise<string> },
'oneclient': { getClustersGroupedByMajor: () => Promise<Partial<{ [key in number]: ClusterModel[] }>>,
getBundlesFor: (clusterId: number) => Promise<ModpackArchive[]>,
getVersions: () => Promise<OnlineClusterManifest>,
@ -318,6 +326,7 @@ removeCluster: (id: number) => Promise<null>,
createCluster: (options: CreateCluster) => Promise<ClusterModel>,
launchCluster: (id: number, uuid: string | null, searchForJava: boolean | null) => Promise<null>,
updateClusterById: (id: number, request: ClusterUpdate) => Promise<null>,
setClusterStage: (id: number, stage: ClusterStage) => Promise<null>,
getScreenshots: (id: number) => Promise<string[]>,
getWorlds: (id: number) => Promise<string[]>,
getLogs: (id: number) => Promise<string[]>,
@ -360,19 +369,11 @@ removeCape: (accessToken: string) => Promise<MojangFullPlayerProfile>,
convertUsernameUUID: (usernameUuid: string) => Promise<MowojangProfile>,
setDiscordRPCMessage: (message: string) => Promise<null>,
open: (input: string) => Promise<null> },
'events': { ingress: (event: IngressPayload) => Promise<void>,
message: (event: MessagePayload) => Promise<void>,
process: (event: ProcessPayload) => Promise<void> },
'folders': { fromCluster: (folderName: string) => Promise<string>,
openCluster: (folderName: string) => Promise<null> },
'debug': { openDevTools: () => Promise<void>,
isInDev: () => Promise<boolean>,
getArch: () => Promise<string>,
getFamily: () => Promise<string>,
getLocale: () => Promise<string | null>,
getType: () => Promise<string>,
getPlatform: () => Promise<string>,
getOsVersion: () => Promise<string>,
getGitCommitHash: () => Promise<string>,
getBuildTimestamp: () => Promise<string>,
getPackageVersion: () => Promise<string> } };
openCluster: (folderName: string) => Promise<null> } };
export type { InferCommandOutput }

View file

@ -65,7 +65,7 @@ export function LaunchButton({
}: LaunchButtonProps) {
const { data: currentAccount } = useCommandSuspense(['getDefaultUser'], () => bindings.core.getDefaultUser(true));
const { data: installedPackages } = useCommandSuspense(['getLinkedPackages', cluster.id], () => bindings.core.getLinkedPackages(cluster.id));
const launchCluster = useLaunchCluster(cluster.id);
const { launch: launchCluster, showDownloadWarning, setShowDownloadWarning, forceLaunch } = useLaunchCluster(cluster.id);
const isRunning = useIsRunning(cluster.id);
const [reason, setReason] = useState<'account' | 'packages' | 'kill' | null>(null);
@ -85,21 +85,44 @@ export function LaunchButton({
};
return (
<Overlay.Trigger isOpen={reason !== null} onOpenChange={open => !open && setReason(null)}>
<Button
className={launchButtonVariants({ isRunning, className })}
isDisabled={isDisabled}
onPress={launch}
{...rest}
>
{isRunning ? 'Running' : 'Launch'}
</Button>
<>
<Overlay.Trigger isOpen={reason !== null} onOpenChange={open => !open && setReason(null)}>
<Button
className={launchButtonVariants({ isRunning, className })}
isDisabled={isDisabled}
onPress={launch}
{...rest}
>
{isRunning ? 'Running' : 'Launch'}
</Button>
<Overlay>
{reason === 'kill' && <KillMinecraft setOpen={() => setReason(null)} />}
{reason === 'packages' && <PromptForOnboarding cluster={cluster} launch={launch} setSkipPackagesCheck={setSkipPackagesCheck} />}
{reason === 'account' && <NoAccountPopup />}
<Overlay>
{reason === 'kill' && <KillMinecraft setOpen={() => setReason(null)} />}
{reason === 'packages' && <PromptForOnboarding cluster={cluster} launch={launch} setSkipPackagesCheck={setSkipPackagesCheck} />}
{reason === 'account' && <NoAccountPopup />}
</Overlay>
</Overlay.Trigger>
<Overlay isOpen={showDownloadWarning} onOpenChange={setShowDownloadWarning}>
<Overlay.Dialog>
<Overlay.Title>Cluster is downloading</Overlay.Title>
<div className="flex flex-col items-center">
<p className="max-w-sm text-fg-secondary">The cluster is currently downloading. Launching it now might cause issues or corruption. Are you sure you want to proceed?</p>
</div>
<Overlay.Buttons
buttons={[
{ color: 'secondary', key: 'Cancel', children: 'Cancel', size: 'normal', onClick: () => setShowDownloadWarning(false) },
{
color: 'primary',
key: 'Proceed',
children: 'Proceed Anyways',
size: 'normal',
onClick: forceLaunch,
},
]}
/>
</Overlay.Dialog>
</Overlay>
</Overlay.Trigger>
</>
);
}

View file

@ -2,13 +2,15 @@ import { bindings } from '@/main';
import { toast } from '@/utils/toast';
import { getMessageFromError, isLauncherError } from '@onelauncher/common';
import { useNavigate } from '@tanstack/react-router';
import { useCallback } from 'react';
import { useCallback, useState } from 'react';
export function useLaunchCluster(): (clusterId: number | undefined | null) => void;
export function useLaunchCluster(clusterId: number | undefined | null): () => void;
export function useLaunchCluster(): { launch: (clusterId: number | undefined | null) => void; showDownloadWarning: boolean; setShowDownloadWarning: (show: boolean) => void; forceLaunch: () => void };
export function useLaunchCluster(clusterId: number | undefined | null): { launch: () => void; showDownloadWarning: boolean; setShowDownloadWarning: (show: boolean) => void; forceLaunch: () => void };
export function useLaunchCluster(clusterId?: number | undefined | null) {
const navigate = useNavigate();
const [showDownloadWarning, setShowDownloadWarning] = useState(false);
const [lastAttemptedId, setLastAttemptedId] = useState<number | null>(null);
const launch = useCallback(
async (id?: number | null) => {
@ -17,6 +19,8 @@ export function useLaunchCluster(clusterId?: number | undefined | null) {
if (!targetId)
return;
setLastAttemptedId(targetId);
try {
await bindings.core.launchCluster(targetId, null, false);
@ -25,7 +29,12 @@ export function useLaunchCluster(clusterId?: number | undefined | null) {
search: { clusterId: targetId },
});
}
catch (err) {
catch (err: any) {
if (err.type === 'ClusterError' && err.data.type === 'ClusterDownloading') {
setShowDownloadWarning(true);
return;
}
if (isLauncherError(err))
toast({
type: 'error',
@ -37,5 +46,14 @@ export function useLaunchCluster(clusterId?: number | undefined | null) {
[clusterId, navigate],
);
return launch;
const forceLaunch = useCallback(async () => {
if (!lastAttemptedId)
return;
// @ts-ignore - ignoring
await bindings.core.setClusterStage(lastAttemptedId, 'notready');
setShowDownloadWarning(false);
launch(lastAttemptedId);
}, [lastAttemptedId, launch]);
return { launch, showDownloadWarning, setShowDownloadWarning, forceLaunch };
}

View file

@ -9,6 +9,7 @@ use crate::store::processes::Process;
use crate::store::{Settings, State};
use crate::utils::pagination::Paginated;
use interpulse::api::minecraft::Version;
use onelauncher_entity::cluster_stage::ClusterStage;
use onelauncher_entity::icon::Icon;
use onelauncher_entity::loader::GameLoader;
use onelauncher_entity::package::Provider;
@ -51,6 +52,9 @@ pub trait TauriLauncherApi {
#[taurpc(alias = "updateClusterById")]
async fn update_cluster_by_id(id: ClusterId, request: ClusterUpdate) -> LauncherResult<()>;
#[taurpc(alias = "setClusterStage")]
async fn set_cluster_stage(id: ClusterId, stage: ClusterStage) -> LauncherResult<()>;
#[taurpc(alias = "getScreenshots")]
async fn get_screenshots(id: ClusterId) -> LauncherResult<Vec<String>>;
@ -407,7 +411,14 @@ impl TauriLauncherApi for TauriLauncherApiImpl {
Ok(())
}
async fn set_cluster_stage(self, id: ClusterId, stage: ClusterStage) -> LauncherResult<()> {
api::cluster::dao::update_cluster_by_id(id, |mut model: ClusterPartial| async move {
model.stage = Set(stage);
Ok(model)
})
.await?;
Ok(())
}
async fn get_screenshots(self, id: ClusterId) -> LauncherResult<Vec<String>> {
let cluster = api::cluster::dao::get_cluster_by_id(id)
.await?