40697a73e5
NEXT_STEPS item 1 called out that streamed install was still CLI-only because the Tauri app started the peer with no stream provider. Users can now choose an explicit "Low disk install" action from the game detail modal for remote-only games instead of taking the default archive-preserving download path. The GUI command queues a normal peer detail fetch first so the peer database has the file metadata needed for source validation. A small pending handoff in Tauri routes the resulting GotGameFiles event into StreamInstallGame instead of DownloadGameFiles, and clears that pending state on no-peer or download failure events. This keeps the existing download continuation untouched for the default action. The external unrar stream provider moved from the CLI harness into lanspread-peer so CLI and Tauri use the same implementation. Tauri resolves the bundled unrar sidecar path and injects that provider at peer startup; falling back to the noop provider keeps peer startup alive if the sidecar cannot be resolved, while the streamed install operation still fails safely. Test Plan: - just fmt - just test - just frontend-test - just clippy - just build - git diff --check Refs: NEXT_STEPS.md item 1
154 lines
5.3 KiB
TypeScript
154 lines
5.3 KiB
TypeScript
import { useCallback } from 'react';
|
|
import { invoke } from '@tauri-apps/api/core';
|
|
import { ask } from '@tauri-apps/plugin-dialog';
|
|
|
|
import { type UseGamesResult } from './useGames';
|
|
import { type UISettings } from './useSettings';
|
|
|
|
export interface GameActions {
|
|
play: (id: string) => Promise<void>;
|
|
startServer: (id: string) => Promise<void>;
|
|
install: (id: string) => Promise<void>;
|
|
streamInstall: (id: string) => Promise<void>;
|
|
update: (id: string) => Promise<void>;
|
|
uninstall: (id: string) => Promise<void>;
|
|
removeDownload: (id: string) => Promise<void>;
|
|
cancelDownload: (id: string) => Promise<void>;
|
|
viewFiles: (id: string) => Promise<void>;
|
|
}
|
|
|
|
/**
|
|
* Thin wrappers over the backend `run_game` / `install_game` / `update_game`
|
|
* / `uninstall_game` / `remove_downloaded_game` commands. Peer-backed downloads
|
|
* are marked as "checking peers" until the backend emits an authoritative
|
|
* operation snapshot; cancellation waits for the backend to clear that snapshot.
|
|
*/
|
|
export const useGameActions = (
|
|
games: UseGamesResult,
|
|
settings: Pick<UISettings, 'language' | 'username'>,
|
|
): GameActions => {
|
|
const play = useCallback(async (id: string) => {
|
|
try {
|
|
await invoke('run_game', {
|
|
id,
|
|
language: settings.language,
|
|
username: settings.username,
|
|
});
|
|
} catch (err) {
|
|
console.error('run_game failed:', err);
|
|
}
|
|
}, [settings.language, settings.username]);
|
|
|
|
const startServer = useCallback(async (id: string) => {
|
|
try {
|
|
await invoke('start_server', {
|
|
id,
|
|
language: settings.language,
|
|
username: settings.username,
|
|
});
|
|
} catch (err) {
|
|
console.error('start_server failed:', err);
|
|
}
|
|
}, [settings.language, settings.username]);
|
|
|
|
const install = useCallback(async (id: string) => {
|
|
try {
|
|
const success = await invoke<boolean>('install_game', {
|
|
id,
|
|
language: settings.language,
|
|
username: settings.username,
|
|
});
|
|
if (!success) return;
|
|
|
|
const game = games.games.find(item => item.id === id);
|
|
if (!game?.downloaded) {
|
|
games.markChecking(id);
|
|
}
|
|
} catch (err) {
|
|
console.error('install_game failed:', err);
|
|
}
|
|
}, [games, settings.language, settings.username]);
|
|
|
|
const streamInstall = useCallback(async (id: string) => {
|
|
try {
|
|
const success = await invoke<boolean>('stream_install_game', { id });
|
|
if (success) games.markChecking(id);
|
|
} catch (err) {
|
|
console.error('stream_install_game failed:', err);
|
|
}
|
|
}, [games]);
|
|
|
|
const update = useCallback(async (id: string) => {
|
|
try {
|
|
const game = games.games.find(item => item.id === id);
|
|
if (game && game.active_outbound_transfers && game.active_outbound_transfers > 0) {
|
|
const confirmed = await ask(
|
|
`Peers are currently downloading this game from you. Updating will abort their downloads. Do you want to proceed?`,
|
|
{ title: 'Active Transfers in Progress', kind: 'warning' }
|
|
);
|
|
if (!confirmed) return;
|
|
}
|
|
const success = await invoke<boolean>('update_game', {
|
|
id,
|
|
language: settings.language,
|
|
username: settings.username,
|
|
});
|
|
if (success) games.markChecking(id);
|
|
} catch (err) {
|
|
console.error('update_game failed:', err);
|
|
}
|
|
}, [games, settings.language, settings.username]);
|
|
|
|
const uninstall = useCallback(async (id: string) => {
|
|
try {
|
|
await invoke('uninstall_game', { id });
|
|
} catch (err) {
|
|
console.error('uninstall_game failed:', err);
|
|
}
|
|
}, []);
|
|
|
|
const removeDownload = useCallback(async (id: string) => {
|
|
try {
|
|
const game = games.games.find(item => item.id === id);
|
|
if (game && game.active_outbound_transfers && game.active_outbound_transfers > 0) {
|
|
const confirmed = await ask(
|
|
`Peers are currently downloading this game from you. Removing game files will abort their downloads. Do you want to proceed?`,
|
|
{ title: 'Active Transfers in Progress', kind: 'warning' }
|
|
);
|
|
if (!confirmed) return;
|
|
}
|
|
await invoke('remove_downloaded_game', { id });
|
|
} catch (err) {
|
|
console.error('remove_downloaded_game failed:', err);
|
|
}
|
|
}, [games]);
|
|
|
|
const cancelDownload = useCallback(async (id: string) => {
|
|
try {
|
|
await invoke('cancel_download', { id });
|
|
} catch (err) {
|
|
console.error('cancel_download failed:', err);
|
|
}
|
|
}, []);
|
|
|
|
const viewFiles = useCallback(async (id: string) => {
|
|
try {
|
|
await invoke('open_game_files', { id });
|
|
} catch (err) {
|
|
console.error('open_game_files failed:', err);
|
|
}
|
|
}, []);
|
|
|
|
return {
|
|
play,
|
|
startServer,
|
|
install,
|
|
streamInstall,
|
|
update,
|
|
uninstall,
|
|
removeDownload,
|
|
cancelDownload,
|
|
viewFiles,
|
|
};
|
|
};
|