mirror of
https://github.com/Drop-OSS/drop.git
synced 2026-07-27 18:24:43 +10:00
feat(install ui): ui to install games
This commit is contained in:
@@ -1,29 +1,45 @@
|
|||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
@click="() => buttonActions[props.status]()"
|
||||||
:class="[
|
:class="[
|
||||||
styles[props.status],
|
styles[props.status],
|
||||||
'inline-flex uppercase font-display items-center gap-x-2 rounded-md px-4 py-3 text-md font-semibold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2',
|
'inline-flex uppercase font-display items-center gap-x-2 rounded-md px-4 py-3 text-md font-semibold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2',
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<component :is="buttonIcons[props.status]" class="-mr-0.5 size-5" aria-hidden="true" />
|
<component
|
||||||
|
:is="buttonIcons[props.status]"
|
||||||
|
class="-mr-0.5 size-5"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
{{ buttonNames[props.status] }}
|
{{ buttonNames[props.status] }}
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ArrowDownTrayIcon, PlayIcon, QueueListIcon, TrashIcon } from "@heroicons/vue/20/solid";
|
import {
|
||||||
|
ArrowDownTrayIcon,
|
||||||
|
PlayIcon,
|
||||||
|
QueueListIcon,
|
||||||
|
TrashIcon,
|
||||||
|
} from "@heroicons/vue/20/solid";
|
||||||
import type { Component } from "vue";
|
import type { Component } from "vue";
|
||||||
import { GameStatus } from "~/types.js";
|
import { GameStatus } from "~/types.js";
|
||||||
|
|
||||||
const props = defineProps<{ status: GameStatus }>();
|
const props = defineProps<{ status: GameStatus }>();
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "install"): void;
|
||||||
|
(e: "cancel"): void;
|
||||||
|
(e: "play"): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
const styles: { [key in GameStatus]: string } = {
|
const styles: { [key in GameStatus]: string } = {
|
||||||
[GameStatus.Remote]:
|
[GameStatus.Remote]:
|
||||||
"bg-blue-600 text-white hover:bg-blue-500 focus-visible:outline-blue-600",
|
"bg-blue-600 text-white hover:bg-blue-500 focus-visible:outline-blue-600",
|
||||||
[GameStatus.Queued]:
|
[GameStatus.Queued]:
|
||||||
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700",
|
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700",
|
||||||
[GameStatus.Downloading]: "bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700",
|
[GameStatus.Downloading]:
|
||||||
|
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700",
|
||||||
[GameStatus.Installed]:
|
[GameStatus.Installed]:
|
||||||
"bg-green-600 text-white hover:bg-green-500 focus-visible:outline-green-600",
|
"bg-green-600 text-white hover:bg-green-500 focus-visible:outline-green-600",
|
||||||
[GameStatus.Updating]: "",
|
[GameStatus.Updating]: "",
|
||||||
@@ -39,12 +55,21 @@ const buttonNames: { [key in GameStatus]: string } = {
|
|||||||
[GameStatus.Uninstalling]: "Uninstalling",
|
[GameStatus.Uninstalling]: "Uninstalling",
|
||||||
};
|
};
|
||||||
|
|
||||||
const buttonIcons: {[key in GameStatus]: Component} = {
|
const buttonIcons: { [key in GameStatus]: Component } = {
|
||||||
[GameStatus.Remote]: ArrowDownTrayIcon,
|
[GameStatus.Remote]: ArrowDownTrayIcon,
|
||||||
[GameStatus.Queued]: QueueListIcon,
|
[GameStatus.Queued]: QueueListIcon,
|
||||||
[GameStatus.Downloading]: ArrowDownTrayIcon,
|
[GameStatus.Downloading]: ArrowDownTrayIcon,
|
||||||
[GameStatus.Installed]: PlayIcon,
|
[GameStatus.Installed]: PlayIcon,
|
||||||
[GameStatus.Updating]: ArrowDownTrayIcon,
|
[GameStatus.Updating]: ArrowDownTrayIcon,
|
||||||
[GameStatus.Uninstalling]: TrashIcon
|
[GameStatus.Uninstalling]: TrashIcon,
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const buttonActions: { [key in GameStatus]: () => void } = {
|
||||||
|
[GameStatus.Remote]: () => emit("install"),
|
||||||
|
[GameStatus.Queued]: () => emit("cancel"),
|
||||||
|
[GameStatus.Downloading]: () => emit("cancel"),
|
||||||
|
[GameStatus.Installed]: () => emit("play"),
|
||||||
|
[GameStatus.Updating]: () => emit("cancel"),
|
||||||
|
[GameStatus.Uninstalling]: () => {},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="inline-flex h-9 items-center justify-center rounded-md bg-blue-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
|
class="inline-flex h-9 items-center justify-center rounded-md bg-blue-600 hover:bg-blue-500 disabled:text-zinc-500 disabled:bg-blue-900 disabled:hover:bg-blue-900 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
|
||||||
>
|
>
|
||||||
<div v-if="props.loading" role="status">
|
<div v-if="props.loading" role="status">
|
||||||
<svg
|
<svg
|
||||||
|
|||||||
@@ -18,13 +18,303 @@
|
|||||||
<div class="w-full min-h-screen mx-auto bg-zinc-900 px-5 py-6">
|
<div class="w-full min-h-screen mx-auto bg-zinc-900 px-5 py-6">
|
||||||
<!-- game toolbar -->
|
<!-- game toolbar -->
|
||||||
<div>
|
<div>
|
||||||
<GameStatusButton :status="status" />
|
<GameStatusButton @install="() => installFlow()" :status="status" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<TransitionRoot as="template" :show="installFlowOpen">
|
||||||
|
<Dialog class="relative z-50" @close="installFlowOpen = false">
|
||||||
|
<TransitionChild
|
||||||
|
as="template"
|
||||||
|
enter="ease-out duration-300"
|
||||||
|
enter-from="opacity-0"
|
||||||
|
enter-to="opacity-100"
|
||||||
|
leave="ease-in duration-200"
|
||||||
|
leave-from="opacity-100"
|
||||||
|
leave-to="opacity-0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="fixed inset-0 bg-zinc-950 bg-opacity-75 transition-opacity"
|
||||||
|
/>
|
||||||
|
</TransitionChild>
|
||||||
|
|
||||||
|
<div class="fixed inset-0 z-10 w-screen overflow-y-auto">
|
||||||
|
<div
|
||||||
|
class="flex min-h-full items-start justify-center p-4 text-center sm:items-center sm:p-0"
|
||||||
|
>
|
||||||
|
<TransitionChild
|
||||||
|
as="template"
|
||||||
|
enter="ease-out duration-300"
|
||||||
|
enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
enter-to="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
leave="ease-in duration-200"
|
||||||
|
leave-from="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
@submit.prevent="() => install()"
|
||||||
|
class="relative transform rounded-lg bg-zinc-900 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg"
|
||||||
|
>
|
||||||
|
<div class="px-4 pb-4 pt-5 space-y-4 sm:p-6 sm:pb-4">
|
||||||
|
<div class="sm:flex sm:items-start">
|
||||||
|
<div class="mt-3 text-center sm:mt-0 sm:text-left">
|
||||||
|
<DialogTitle
|
||||||
|
as="h3"
|
||||||
|
class="text-base font-semibold text-zinc-100"
|
||||||
|
>Install {{ game.mName }}?
|
||||||
|
</DialogTitle>
|
||||||
|
<div class="mt-2">
|
||||||
|
<p class="text-sm text-zinc-400">
|
||||||
|
Drop will add {{ game.mName }} to the queue to be
|
||||||
|
downloaded. While downloading, Drop may use up a large
|
||||||
|
amount of resources, particularly network bandwidth and
|
||||||
|
CPU utilisation.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-6">
|
||||||
|
<div v-if="versionOptions && versionOptions.length > 0">
|
||||||
|
<Listbox as="div" v-model="installVersionIndex">
|
||||||
|
<ListboxLabel
|
||||||
|
class="block text-sm/6 font-medium text-zinc-100"
|
||||||
|
>Version</ListboxLabel
|
||||||
|
>
|
||||||
|
<div class="relative mt-2">
|
||||||
|
<ListboxButton
|
||||||
|
class="relative w-full cursor-default rounded-md bg-zinc-800 py-1.5 pl-3 pr-10 text-left text-zinc-100 shadow-sm ring-1 ring-inset ring-zinc-700 focus:outline-none focus:ring-2 focus:ring-blue-600 sm:text-sm/6"
|
||||||
|
>
|
||||||
|
<span class="block truncate"
|
||||||
|
>{{
|
||||||
|
versionOptions[installVersionIndex].versionName
|
||||||
|
}}
|
||||||
|
on
|
||||||
|
{{
|
||||||
|
versionOptions[installVersionIndex].platform
|
||||||
|
}}</span
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
|
||||||
|
>
|
||||||
|
<ChevronUpDownIcon
|
||||||
|
class="h-5 w-5 text-gray-400"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</ListboxButton>
|
||||||
|
|
||||||
|
<transition
|
||||||
|
leave-active-class="transition ease-in duration-100"
|
||||||
|
leave-from-class="opacity-100"
|
||||||
|
leave-to-class="opacity-0"
|
||||||
|
>
|
||||||
|
<ListboxOptions
|
||||||
|
class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-zinc-900 py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"
|
||||||
|
>
|
||||||
|
<ListboxOption
|
||||||
|
as="template"
|
||||||
|
v-for="(version, versionIdx) in versionOptions"
|
||||||
|
:key="version.versionName"
|
||||||
|
:value="versionIdx"
|
||||||
|
v-slot="{ active, selected }"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
:class="[
|
||||||
|
active
|
||||||
|
? 'bg-blue-600 text-white'
|
||||||
|
: 'text-zinc-300',
|
||||||
|
'relative cursor-default select-none py-2 pl-3 pr-9',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
:class="[
|
||||||
|
selected
|
||||||
|
? 'font-semibold text-zinc-100'
|
||||||
|
: 'font-normal',
|
||||||
|
'block truncate',
|
||||||
|
]"
|
||||||
|
>{{ version.versionName }} on
|
||||||
|
{{ version.platform }}</span
|
||||||
|
>
|
||||||
|
|
||||||
|
<span
|
||||||
|
v-if="selected"
|
||||||
|
:class="[
|
||||||
|
active ? 'text-white' : 'text-blue-600',
|
||||||
|
'absolute inset-y-0 right-0 flex items-center pr-4',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<CheckIcon
|
||||||
|
class="h-5 w-5"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ListboxOption>
|
||||||
|
</ListboxOptions>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</Listbox>
|
||||||
|
</div>
|
||||||
|
<div v-else class="mt-1 rounded-md bg-red-600/10 p-4">
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<XCircleIcon
|
||||||
|
class="h-5 w-5 text-red-600"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="ml-3">
|
||||||
|
<h3 class="text-sm font-medium text-red-600">
|
||||||
|
There are no versions to install. Please contact your
|
||||||
|
server admin or try again later.
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="installDirs">
|
||||||
|
<Listbox as="div" v-model="installDir">
|
||||||
|
<ListboxLabel
|
||||||
|
class="block text-sm/6 font-medium text-zinc-100"
|
||||||
|
>Install to</ListboxLabel
|
||||||
|
>
|
||||||
|
<div class="relative mt-2">
|
||||||
|
<ListboxButton
|
||||||
|
class="relative w-full cursor-default rounded-md bg-zinc-800 py-1.5 pl-3 pr-10 text-left text-zinc-100 shadow-sm ring-1 ring-inset ring-zinc-700 focus:outline-none focus:ring-2 focus:ring-blue-600 sm:text-sm/6"
|
||||||
|
>
|
||||||
|
<span class="block truncate">{{
|
||||||
|
installDirs[installDir]
|
||||||
|
}}</span>
|
||||||
|
<span
|
||||||
|
class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
|
||||||
|
>
|
||||||
|
<ChevronUpDownIcon
|
||||||
|
class="h-5 w-5 text-gray-400"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</ListboxButton>
|
||||||
|
|
||||||
|
<transition
|
||||||
|
leave-active-class="transition ease-in duration-100"
|
||||||
|
leave-from-class="opacity-100"
|
||||||
|
leave-to-class="opacity-0"
|
||||||
|
>
|
||||||
|
<ListboxOptions
|
||||||
|
class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-zinc-900 py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"
|
||||||
|
>
|
||||||
|
<ListboxOption
|
||||||
|
as="template"
|
||||||
|
v-for="(dir, dirIdx) in installDirs"
|
||||||
|
:key="dir"
|
||||||
|
:value="dirIdx"
|
||||||
|
v-slot="{ active, selected }"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
:class="[
|
||||||
|
active
|
||||||
|
? 'bg-blue-600 text-white'
|
||||||
|
: 'text-zinc-300',
|
||||||
|
'relative cursor-default select-none py-2 pl-3 pr-9',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
:class="[
|
||||||
|
selected
|
||||||
|
? 'font-semibold text-zinc-100'
|
||||||
|
: 'font-normal',
|
||||||
|
'block truncate',
|
||||||
|
]"
|
||||||
|
>{{ dir }}}</span
|
||||||
|
>
|
||||||
|
|
||||||
|
<span
|
||||||
|
v-if="selected"
|
||||||
|
:class="[
|
||||||
|
active ? 'text-white' : 'text-blue-600',
|
||||||
|
'absolute inset-y-0 right-0 flex items-center pr-4',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<CheckIcon
|
||||||
|
class="h-5 w-5"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ListboxOption>
|
||||||
|
</ListboxOptions>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</Listbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="installError"
|
||||||
|
class="mt-1 rounded-md bg-red-600/10 p-4"
|
||||||
|
>
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<XCircleIcon
|
||||||
|
class="h-5 w-5 text-red-600"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="ml-3">
|
||||||
|
<h3 class="text-sm font-medium text-red-600">
|
||||||
|
{{ installError }}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="rounded-b-lg bg-zinc-800 px-4 py-3 sm:flex sm:gap-x-2 sm:flex-row-reverse sm:px-6"
|
||||||
|
>
|
||||||
|
<LoadingButton
|
||||||
|
:disabled="
|
||||||
|
!(versionOptions && versionOptions.length > 0 && !installDir)
|
||||||
|
"
|
||||||
|
:loading="installLoading"
|
||||||
|
type="submit"
|
||||||
|
class="w-full sm:w-fit"
|
||||||
|
>
|
||||||
|
Install
|
||||||
|
</LoadingButton>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="mt-3 inline-flex w-full justify-center rounded-md bg-zinc-800 px-3 py-2 text-sm font-semibold text-zinc-100 shadow-sm ring-1 ring-inset ring-zinc-700 hover:bg-zinc-900 sm:mt-0 sm:w-auto"
|
||||||
|
@click="installFlowOpen = false"
|
||||||
|
ref="cancelButtonRef"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</TransitionChild>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
</TransitionRoot>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
TransitionChild,
|
||||||
|
TransitionRoot,
|
||||||
|
Listbox,
|
||||||
|
ListboxButton,
|
||||||
|
ListboxLabel,
|
||||||
|
ListboxOption,
|
||||||
|
ListboxOptions,
|
||||||
|
} from "@headlessui/vue";
|
||||||
|
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
|
||||||
|
import { XCircleIcon } from "@heroicons/vue/24/solid";
|
||||||
|
|
||||||
import type { Game } from "@prisma/client";
|
import type { Game } from "@prisma/client";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
@@ -45,4 +335,44 @@ listen(`update_game/${game.value.id}`, (event) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const bannerUrl = await useObject(game.value.mBannerId);
|
const bannerUrl = await useObject(game.value.mBannerId);
|
||||||
|
|
||||||
|
const installFlowOpen = ref(false);
|
||||||
|
const versionOptions = ref<
|
||||||
|
undefined | Array<{ versionName: string; platform: string }>
|
||||||
|
>();
|
||||||
|
const installDirs = ref<undefined | Array<string>>();
|
||||||
|
async function installFlow() {
|
||||||
|
installFlowOpen.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
versionOptions.value = await invoke("fetch_game_verion_options", {
|
||||||
|
gameId: game.value.id,
|
||||||
|
});
|
||||||
|
installDirs.value = await invoke("fetch_download_dir_stats");
|
||||||
|
} catch (error) {
|
||||||
|
installError.value = (error as string).toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const installLoading = ref(false);
|
||||||
|
const installError = ref<string | undefined>();
|
||||||
|
const installVersionIndex = ref(0);
|
||||||
|
const installDir = ref(0);
|
||||||
|
async function install() {
|
||||||
|
try {
|
||||||
|
if (!versionOptions.value)
|
||||||
|
throw new Error("Versions have not been loaded.");
|
||||||
|
installLoading.value = true;
|
||||||
|
await invoke("download_game", {
|
||||||
|
gameId: game.value.id,
|
||||||
|
gameVersion: versionOptions.value[installVersionIndex.value].versionName,
|
||||||
|
installDir: installDir.value,
|
||||||
|
});
|
||||||
|
installLoading.value = false;
|
||||||
|
|
||||||
|
installFlowOpen.value = false;
|
||||||
|
} catch (error) {
|
||||||
|
installError.value = (error as string).toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -39,11 +39,6 @@ const versionName = ref("");
|
|||||||
const progress = ref(0);
|
const progress = ref(0);
|
||||||
|
|
||||||
async function startGameDownload() {
|
async function startGameDownload() {
|
||||||
await invoke("download_game", {
|
|
||||||
gameId: gameId.value,
|
|
||||||
gameVersion: versionName.value,
|
|
||||||
});
|
|
||||||
|
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const currentProgress = await invoke<number>(
|
const currentProgress = await invoke<number>(
|
||||||
|
|||||||
@@ -172,7 +172,6 @@ impl GameDownloadAgent {
|
|||||||
|
|
||||||
pub fn ensure_contexts(&self) -> Result<(), GameDownloadError> {
|
pub fn ensure_contexts(&self) -> Result<(), GameDownloadError> {
|
||||||
let context_lock = self.contexts.lock().unwrap();
|
let context_lock = self.contexts.lock().unwrap();
|
||||||
info!("{:?} {}", context_lock, context_lock.is_empty());
|
|
||||||
if !context_lock.is_empty() {
|
if !context_lock.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -209,7 +208,7 @@ impl GameDownloadAgent {
|
|||||||
for (i, length) in chunk.lengths.iter().enumerate() {
|
for (i, length) in chunk.lengths.iter().enumerate() {
|
||||||
contexts.push(DropDownloadContext {
|
contexts.push(DropDownloadContext {
|
||||||
file_name: raw_path.to_string(),
|
file_name: raw_path.to_string(),
|
||||||
version: version.to_string(),
|
version: chunk.versionName.to_string(),
|
||||||
offset: running_offset,
|
offset: running_offset,
|
||||||
index: i,
|
index: i,
|
||||||
game_id: game_id.to_string(),
|
game_id: game_id.to_string(),
|
||||||
|
|||||||
@@ -8,13 +8,14 @@ use crate::AppState;
|
|||||||
pub fn download_game(
|
pub fn download_game(
|
||||||
game_id: String,
|
game_id: String,
|
||||||
game_version: String,
|
game_version: String,
|
||||||
|
install_dir: usize,
|
||||||
state: tauri::State<'_, Mutex<AppState>>,
|
state: tauri::State<'_, Mutex<AppState>>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
state
|
state
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.download_manager
|
.download_manager
|
||||||
.queue_game(game_id, game_version, 0)
|
.queue_game(game_id, game_version, install_dir)
|
||||||
.map_err(|_| "An error occurred while communicating with the download manager.".to_string())
|
.map_err(|_| "An error occurred while communicating with the download manager.".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::db::DatabaseImpls;
|
|||||||
use crate::downloads::manifest::DropDownloadContext;
|
use crate::downloads::manifest::DropDownloadContext;
|
||||||
use crate::remote::RemoteAccessError;
|
use crate::remote::RemoteAccessError;
|
||||||
use crate::DB;
|
use crate::DB;
|
||||||
use log::info;
|
use log::{info, warn};
|
||||||
use md5::{Context, Digest};
|
use md5::{Context, Digest};
|
||||||
use reqwest::blocking::Response;
|
use reqwest::blocking::Response;
|
||||||
|
|
||||||
@@ -150,6 +150,13 @@ pub fn download_game_chunk(
|
|||||||
.send()
|
.send()
|
||||||
.map_err(|e| GameDownloadError::Communication(e.into()))?;
|
.map_err(|e| GameDownloadError::Communication(e.into()))?;
|
||||||
|
|
||||||
|
if response.status() != 200 {
|
||||||
|
warn!("{}", response.text().unwrap());
|
||||||
|
return Err(GameDownloadError::Communication(
|
||||||
|
RemoteAccessError::InvalidCodeError(400),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let mut destination = DropWriter::new(ctx.path);
|
let mut destination = DropWriter::new(ctx.path);
|
||||||
|
|
||||||
if ctx.offset != 0 {
|
if ctx.offset != 0 {
|
||||||
@@ -160,7 +167,9 @@ pub fn download_game_chunk(
|
|||||||
|
|
||||||
let content_length = response.content_length();
|
let content_length = response.content_length();
|
||||||
if content_length.is_none() {
|
if content_length.is_none() {
|
||||||
return Err(GameDownloadError::Communication(RemoteAccessError::InvalidResponse));
|
return Err(GameDownloadError::Communication(
|
||||||
|
RemoteAccessError::InvalidResponse,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut pipeline = DropDownloadPipeline::new(
|
let mut pipeline = DropDownloadPipeline::new(
|
||||||
@@ -176,7 +185,9 @@ pub fn download_game_chunk(
|
|||||||
return Ok(false);
|
return Ok(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
let checksum = pipeline.finish().map_err(|e| GameDownloadError::IoError(e))?;
|
let checksum = pipeline
|
||||||
|
.finish()
|
||||||
|
.map_err(|e| GameDownloadError::IoError(e))?;
|
||||||
|
|
||||||
let res = hex::encode(checksum.0);
|
let res = hex::encode(checksum.0);
|
||||||
if res != ctx.checksum {
|
if res != ctx.checksum {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
|
use rustbreak::Database;
|
||||||
use tauri::{AppHandle, Emitter};
|
use tauri::{AppHandle, Emitter};
|
||||||
|
|
||||||
use crate::{db::DatabaseGameStatus, library::GameUpdateEvent, DB};
|
use crate::{db::DatabaseGameStatus, library::GameUpdateEvent, DB};
|
||||||
@@ -247,6 +248,11 @@ impl DownloadManagerBuilder {
|
|||||||
let mut lock = current_status.status.lock().unwrap();
|
let mut lock = current_status.status.lock().unwrap();
|
||||||
*lock = GameDownloadStatus::Error;
|
*lock = GameDownloadStatus::Error;
|
||||||
self.set_status(DownloadManagerStatus::Error(error));
|
self.set_status(DownloadManagerStatus::Error(error));
|
||||||
|
|
||||||
|
self.set_game_status(
|
||||||
|
self.current_game_interface.as_ref().unwrap().id.clone(),
|
||||||
|
DatabaseGameStatus::Remote,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
fn manage_cancel_signal(&mut self, game_id: String) {
|
fn manage_cancel_signal(&mut self, game_id: String) {
|
||||||
if let Some(current_flag) = &self.active_control_flag {
|
if let Some(current_flag) = &self.active_control_flag {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ pub struct DropChunk {
|
|||||||
pub ids: Vec<String>,
|
pub ids: Vec<String>,
|
||||||
pub checksums: Vec<String>,
|
pub checksums: Vec<String>,
|
||||||
pub lengths: Vec<usize>,
|
pub lengths: Vec<usize>,
|
||||||
|
pub versionName: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ use downloads::download_manager::DownloadManager;
|
|||||||
use downloads::download_manager_builder::DownloadManagerBuilder;
|
use downloads::download_manager_builder::DownloadManagerBuilder;
|
||||||
use env_logger::Env;
|
use env_logger::Env;
|
||||||
use http::{header::*, response::Builder as ResponseBuilder};
|
use http::{header::*, response::Builder as ResponseBuilder};
|
||||||
use library::{fetch_game, fetch_game_status, fetch_library, Game};
|
use library::{fetch_game, fetch_game_status, fetch_game_verion_options, fetch_library, Game};
|
||||||
use log::{debug, info};
|
use log::{debug, info};
|
||||||
use remote::{gen_drop_url, use_remote, RemoteAccessError};
|
use remote::{gen_drop_url, use_remote, RemoteAccessError};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -131,6 +131,7 @@ pub fn run() {
|
|||||||
delete_download_dir,
|
delete_download_dir,
|
||||||
fetch_download_dir_stats,
|
fetch_download_dir_stats,
|
||||||
fetch_game_status,
|
fetch_game_status,
|
||||||
|
fetch_game_verion_options,
|
||||||
// Downloads
|
// Downloads
|
||||||
download_game,
|
download_game,
|
||||||
get_current_game_download_progress,
|
get_current_game_download_progress,
|
||||||
|
|||||||
@@ -39,6 +39,19 @@ pub struct GameUpdateEvent {
|
|||||||
pub status: DatabaseGameStatus,
|
pub status: DatabaseGameStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Game version with some fields missing and size information
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct GameVersionOption {
|
||||||
|
version_index: usize,
|
||||||
|
version_name: String,
|
||||||
|
platform: String,
|
||||||
|
setup_command: String,
|
||||||
|
launch_command: String,
|
||||||
|
delta: bool,
|
||||||
|
// total_size: usize,
|
||||||
|
}
|
||||||
|
|
||||||
fn fetch_library_logic(app: AppHandle) -> Result<String, RemoteAccessError> {
|
fn fetch_library_logic(app: AppHandle) -> Result<String, RemoteAccessError> {
|
||||||
let base_url = DB.fetch_base_url();
|
let base_url = DB.fetch_base_url();
|
||||||
let library_url = base_url.join("/api/v1/client/user/library")?;
|
let library_url = base_url.join("/api/v1/client/user/library")?;
|
||||||
@@ -172,3 +185,32 @@ pub fn fetch_game_status(id: String) -> Result<DatabaseGameStatus, String> {
|
|||||||
|
|
||||||
return Ok(status);
|
return Ok(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fetch_game_verion_options_logic(game_id: String) -> Result<Vec<GameVersionOption>, RemoteAccessError> {
|
||||||
|
let base_url = DB.fetch_base_url();
|
||||||
|
|
||||||
|
let endpoint =
|
||||||
|
base_url.join(format!("/api/v1/client/metadata/versions?id={}", game_id).as_str())?;
|
||||||
|
let header = generate_authorization_header();
|
||||||
|
|
||||||
|
let client = reqwest::blocking::Client::new();
|
||||||
|
let response = client
|
||||||
|
.get(endpoint.to_string())
|
||||||
|
.header("Authorization", header)
|
||||||
|
.send()?;
|
||||||
|
|
||||||
|
if response.status() != 200 {
|
||||||
|
return Err(RemoteAccessError::InvalidCodeError(
|
||||||
|
response.status().into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = response.json::<Vec<GameVersionOption>>()?;
|
||||||
|
|
||||||
|
return Ok(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn fetch_game_verion_options(game_id: String) -> Result<Vec<GameVersionOption>, String> {
|
||||||
|
fetch_game_verion_options_logic(game_id).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user