From a2329b59a5f28917f1f3f33170755fa054be9a8f Mon Sep 17 00:00:00 2001 From: Wyvest Date: Sat, 27 Dec 2025 02:21:15 +0900 Subject: [PATCH] Fix cluster being stuck on downloading --- apps/oneclient/frontend/src/bindings.gen.ts | 33 ++++++------ .../frontend/src/components/LaunchButton.tsx | 53 +++++++++++++------ .../frontend/src/hooks/useLaunchCluster.ts | 28 ++++++++-- packages/core/src/api/tauri/commands.rs | 13 ++++- 4 files changed, 90 insertions(+), 37 deletions(-) diff --git a/apps/oneclient/frontend/src/bindings.gen.ts b/apps/oneclient/frontend/src/bindings.gen.ts index cb914917..4fb3f52d 100644 --- a/apps/oneclient/frontend/src/bindings.gen.ts +++ b/apps/oneclient/frontend/src/bindings.gen.ts @@ -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, -message: (event: MessagePayload) => Promise, -process: (event: ProcessPayload) => Promise }, +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, +isInDev: () => Promise, +getArch: () => Promise, +getFamily: () => Promise, +getLocale: () => Promise, +getType: () => Promise, +getPlatform: () => Promise, +getOsVersion: () => Promise, +getGitCommitHash: () => Promise, +getBuildTimestamp: () => Promise, +getPackageVersion: () => Promise }, 'oneclient': { getClustersGroupedByMajor: () => Promise>, getBundlesFor: (clusterId: number) => Promise, getVersions: () => Promise, @@ -318,6 +326,7 @@ removeCluster: (id: number) => Promise, createCluster: (options: CreateCluster) => Promise, launchCluster: (id: number, uuid: string | null, searchForJava: boolean | null) => Promise, updateClusterById: (id: number, request: ClusterUpdate) => Promise, +setClusterStage: (id: number, stage: ClusterStage) => Promise, getScreenshots: (id: number) => Promise, getWorlds: (id: number) => Promise, getLogs: (id: number) => Promise, @@ -360,19 +369,11 @@ removeCape: (accessToken: string) => Promise, convertUsernameUUID: (usernameUuid: string) => Promise, setDiscordRPCMessage: (message: string) => Promise, open: (input: string) => Promise }, +'events': { ingress: (event: IngressPayload) => Promise, +message: (event: MessagePayload) => Promise, +process: (event: ProcessPayload) => Promise }, 'folders': { fromCluster: (folderName: string) => Promise, -openCluster: (folderName: string) => Promise }, -'debug': { openDevTools: () => Promise, -isInDev: () => Promise, -getArch: () => Promise, -getFamily: () => Promise, -getLocale: () => Promise, -getType: () => Promise, -getPlatform: () => Promise, -getOsVersion: () => Promise, -getGitCommitHash: () => Promise, -getBuildTimestamp: () => Promise, -getPackageVersion: () => Promise } }; +openCluster: (folderName: string) => Promise } }; export type { InferCommandOutput } diff --git a/apps/oneclient/frontend/src/components/LaunchButton.tsx b/apps/oneclient/frontend/src/components/LaunchButton.tsx index e064d23b..430ba0a3 100644 --- a/apps/oneclient/frontend/src/components/LaunchButton.tsx +++ b/apps/oneclient/frontend/src/components/LaunchButton.tsx @@ -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 ( - !open && setReason(null)}> - + <> + !open && setReason(null)}> + - - {reason === 'kill' && setReason(null)} />} - {reason === 'packages' && } - {reason === 'account' && } + + {reason === 'kill' && setReason(null)} />} + {reason === 'packages' && } + {reason === 'account' && } + + + + + + Cluster is downloading +
+

The cluster is currently downloading. Launching it now might cause issues or corruption. Are you sure you want to proceed?

+
+ setShowDownloadWarning(false) }, + { + color: 'primary', + key: 'Proceed', + children: 'Proceed Anyways', + size: 'normal', + onClick: forceLaunch, + }, + ]} + /> +
-
+ ); } diff --git a/apps/oneclient/frontend/src/hooks/useLaunchCluster.ts b/apps/oneclient/frontend/src/hooks/useLaunchCluster.ts index f6e272f5..792823f3 100644 --- a/apps/oneclient/frontend/src/hooks/useLaunchCluster.ts +++ b/apps/oneclient/frontend/src/hooks/useLaunchCluster.ts @@ -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(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 }; } diff --git a/packages/core/src/api/tauri/commands.rs b/packages/core/src/api/tauri/commands.rs index 14a1e6e0..e70b7227 100644 --- a/packages/core/src/api/tauri/commands.rs +++ b/packages/core/src/api/tauri/commands.rs @@ -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>; @@ -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> { let cluster = api::cluster::dao::get_cluster_by_id(id) .await?