mirror of
https://github.com/Drop-OSS/drop-app.git
synced 2025-11-15 17:21:19 +10:00
Compare commits
2 Commits
102-featur
...
v0.3.1-mac
| Author | SHA1 | Date | |
|---|---|---|---|
| f1bc7bf418 | |||
| cb2295dd7c |
36
.github/workflows/release.yml
vendored
36
.github/workflows/release.yml
vendored
@ -51,40 +51,9 @@ jobs:
|
|||||||
if: matrix.platform == 'ubuntu-22.04' || matrix.platform == 'ubuntu-22.04-arm' # This must match the platform value defined above.
|
if: matrix.platform == 'ubuntu-22.04' || matrix.platform == 'ubuntu-22.04-arm' # This must match the platform value defined above.
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf xdg-utils
|
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
||||||
# webkitgtk 4.0 is for Tauri v1 - webkitgtk 4.1 is for Tauri v2.
|
# webkitgtk 4.0 is for Tauri v1 - webkitgtk 4.1 is for Tauri v2.
|
||||||
|
|
||||||
|
|
||||||
- name: Import Apple Developer Certificate
|
|
||||||
if: matrix.platform == 'macos-latest'
|
|
||||||
env:
|
|
||||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
|
||||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
|
||||||
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
|
||||||
run: |
|
|
||||||
echo $APPLE_CERTIFICATE | base64 --decode > certificate.p12
|
|
||||||
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
|
||||||
security default-keychain -s build.keychain
|
|
||||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
|
||||||
security set-keychain-settings -t 3600 -u build.keychain
|
|
||||||
|
|
||||||
curl https://droposs.org/drop.crt --output drop.pem
|
|
||||||
sudo security authorizationdb write com.apple.trust-settings.user allow
|
|
||||||
security add-trusted-cert -r trustRoot -k build.keychain -p codeSign -u -1 drop.pem
|
|
||||||
sudo security authorizationdb remove com.apple.trust-settings.user
|
|
||||||
|
|
||||||
security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
|
|
||||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain
|
|
||||||
security find-identity -v -p codesigning build.keychain
|
|
||||||
|
|
||||||
- name: Verify Certificate
|
|
||||||
if: matrix.platform == 'macos-latest'
|
|
||||||
run: |
|
|
||||||
CERT_INFO=$(security find-identity -v -p codesigning build.keychain | grep "Drop OSS")
|
|
||||||
CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}')
|
|
||||||
echo "CERT_ID=$CERT_ID" >> $GITHUB_ENV
|
|
||||||
echo "Certificate imported. Using identity: $CERT_ID"
|
|
||||||
|
|
||||||
- name: install frontend dependencies
|
- name: install frontend dependencies
|
||||||
run: yarn install # change this to npm, pnpm or bun depending on which one you use.
|
run: yarn install # change this to npm, pnpm or bun depending on which one you use.
|
||||||
|
|
||||||
@ -93,8 +62,7 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||||
APPLE_SIGNING_IDENTITY: ${{ env.CERT_ID }}
|
APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY }}
|
||||||
NO_STRIP: true
|
|
||||||
with:
|
with:
|
||||||
tagName: v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version.
|
tagName: v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version.
|
||||||
releaseName: 'Auto-release v__VERSION__'
|
releaseName: 'Auto-release v__VERSION__'
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -27,6 +27,3 @@ dist-ssr
|
|||||||
|
|
||||||
src-tauri/flamegraph.svg
|
src-tauri/flamegraph.svg
|
||||||
src-tauri/perf*
|
src-tauri/perf*
|
||||||
|
|
||||||
/*.AppImage
|
|
||||||
/squashfs-root
|
|
||||||
9
.gitmodules
vendored
9
.gitmodules
vendored
@ -1,6 +1,9 @@
|
|||||||
|
[submodule "drop-base"]
|
||||||
|
path = drop-base
|
||||||
|
url = https://github.com/drop-oss/drop-base
|
||||||
[submodule "src-tauri/tailscale/libtailscale"]
|
[submodule "src-tauri/tailscale/libtailscale"]
|
||||||
path = src-tauri/tailscale/libtailscale
|
path = src-tauri/tailscale/libtailscale
|
||||||
url = https://github.com/tailscale/libtailscale.git
|
url = https://github.com/tailscale/libtailscale.git
|
||||||
[submodule "libs/drop-base"]
|
[submodule "src-tauri/umu/umu-launcher"]
|
||||||
path = libs/drop-base
|
path = src-tauri/umu/umu-launcher
|
||||||
url = https://github.com/drop-oss/drop-base.git
|
url = https://github.com/Open-Wine-Components/umu-launcher.git
|
||||||
|
|||||||
@ -10,6 +10,8 @@
|
|||||||
import "~/composables/downloads.js";
|
import "~/composables/downloads.js";
|
||||||
|
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import { AppStatus } from "~/types";
|
||||||
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { useAppState } from "./composables/app-state.js";
|
import { useAppState } from "./composables/app-state.js";
|
||||||
import {
|
import {
|
||||||
initialNavigation,
|
initialNavigation,
|
||||||
@ -19,26 +21,18 @@ import {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const state = useAppState();
|
const state = useAppState();
|
||||||
|
try {
|
||||||
|
state.value = JSON.parse(await invoke("fetch_state"));
|
||||||
|
} catch (e) {
|
||||||
|
console.error("failed to parse state", e);
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchState() {
|
router.beforeEach(async () => {
|
||||||
try {
|
try {
|
||||||
state.value = JSON.parse(await invoke("fetch_state"));
|
state.value = JSON.parse(await invoke("fetch_state"));
|
||||||
if (!state.value)
|
|
||||||
throw createError({
|
|
||||||
statusCode: 500,
|
|
||||||
statusMessage: `App state is: ${state.value}`,
|
|
||||||
fatal: true,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("failed to parse state", e);
|
console.error("failed to parse state", e);
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
await fetchState();
|
|
||||||
|
|
||||||
// This is inefficient but apparently we do it lol
|
|
||||||
router.beforeEach(async () => {
|
|
||||||
await fetchState();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setupHooks();
|
setupHooks();
|
||||||
|
Before Width: | Height: | Size: 6.5 MiB After Width: | Height: | Size: 6.5 MiB |
48
build.mjs
48
build.mjs
@ -1,48 +0,0 @@
|
|||||||
import fs from "fs";
|
|
||||||
import process from "process";
|
|
||||||
import childProcess from "child_process";
|
|
||||||
import createLogger from "pino";
|
|
||||||
|
|
||||||
const OUTPUT = "./.output";
|
|
||||||
const logger = createLogger({ transport: { target: "pino-pretty" } });
|
|
||||||
|
|
||||||
async function spawn(exec, opts) {
|
|
||||||
const output = childProcess.spawn(exec, { ...opts, shell: true });
|
|
||||||
output.stdout.on("data", (data) => {
|
|
||||||
process.stdout.write(data);
|
|
||||||
});
|
|
||||||
output.stderr.on("data", (data) => {
|
|
||||||
process.stderr.write(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
output.on("error", (err) => reject(err));
|
|
||||||
output.on("exit", () => resolve());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const views = fs.readdirSync(".").filter((view) => {
|
|
||||||
const expectedPath = `./${view}/package.json`;
|
|
||||||
return fs.existsSync(expectedPath);
|
|
||||||
});
|
|
||||||
|
|
||||||
fs.mkdirSync(OUTPUT, { recursive: true });
|
|
||||||
|
|
||||||
for (const view of views) {
|
|
||||||
const loggerChild = logger.child({});
|
|
||||||
process.chdir(`./${view}`);
|
|
||||||
|
|
||||||
loggerChild.info(`Install deps for "${view}"`);
|
|
||||||
await spawn("yarn");
|
|
||||||
|
|
||||||
loggerChild.info(`Building "${view}"`);
|
|
||||||
await spawn("yarn build", {
|
|
||||||
env: { ...process.env, NUXT_APP_BASE_URL: `/${view}/` },
|
|
||||||
});
|
|
||||||
|
|
||||||
process.chdir("..");
|
|
||||||
|
|
||||||
fs.cpSync(`./${view}/.output/public`, `${OUTPUT}/${view}`, {
|
|
||||||
recursive: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -1,75 +1,49 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- Do not add scale animations to this: https://stackoverflow.com/a/35683068 -->
|
<!-- Do not add scale animations to this: https://stackoverflow.com/a/35683068 -->
|
||||||
<div class="inline-flex divide-x divide-zinc-900">
|
<div class="inline-flex divide-x divide-zinc-900">
|
||||||
<button
|
<button type="button" @click="() => buttonActions[props.status.type]()" :class="[
|
||||||
type="button"
|
|
||||||
@click="() => buttonActions[props.status.type]()"
|
|
||||||
:class="[
|
|
||||||
styles[props.status.type],
|
styles[props.status.type],
|
||||||
showDropdown ? 'rounded-l-md' : 'rounded-md',
|
showDropdown ? 'rounded-l-md' : 'rounded-md',
|
||||||
'inline-flex uppercase font-display items-center gap-x-2 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 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.type]" class="-mr-0.5 size-5" aria-hidden="true" />
|
||||||
<component
|
|
||||||
:is="buttonIcons[props.status.type]"
|
|
||||||
class="-mr-0.5 size-5"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
{{ buttonNames[props.status.type] }}
|
{{ buttonNames[props.status.type] }}
|
||||||
</button>
|
</button>
|
||||||
<Menu
|
<Menu v-if="showDropdown" as="div" class="relative inline-block text-left grow">
|
||||||
v-if="showDropdown"
|
|
||||||
as="div"
|
|
||||||
class="relative inline-block text-left grow"
|
|
||||||
>
|
|
||||||
<div class="h-full">
|
<div class="h-full">
|
||||||
<MenuButton
|
<MenuButton :class="[
|
||||||
:class="[
|
|
||||||
styles[props.status.type],
|
styles[props.status.type],
|
||||||
'inline-flex w-full h-full justify-center items-center rounded-r-md px-1 py-2 text-sm font-semibold shadow-sm group',
|
'inline-flex w-full h-full justify-center items-center rounded-r-md px-1 py-2 text-sm font-semibold shadow-sm group',
|
||||||
'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2',
|
'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2',
|
||||||
]"
|
]">
|
||||||
>
|
|
||||||
<ChevronDownIcon class="size-5" aria-hidden="true" />
|
<ChevronDownIcon class="size-5" aria-hidden="true" />
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<transition
|
<transition enter-active-class="transition ease-out duration-100" enter-from-class="transform opacity-0 scale-95"
|
||||||
enter-active-class="transition ease-out duration-100"
|
enter-to-class="transform opacity-100 scale-100" leave-active-class="transition ease-in duration-75"
|
||||||
enter-from-class="transform opacity-0 scale-95"
|
leave-from-class="transform opacity-100 scale-100" leave-to-class="transform opacity-0 scale-95">
|
||||||
enter-to-class="transform opacity-100 scale-100"
|
|
||||||
leave-active-class="transition ease-in duration-75"
|
|
||||||
leave-from-class="transform opacity-100 scale-100"
|
|
||||||
leave-to-class="transform opacity-0 scale-95"
|
|
||||||
>
|
|
||||||
<MenuItems
|
<MenuItems
|
||||||
class="absolute right-0 z-[500] mt-2 w-32 origin-top-right rounded-md bg-zinc-900 shadow-lg ring-1 ring-zinc-100/5 focus:outline-none"
|
class="absolute right-0 z-[500] mt-2 w-32 origin-top-right rounded-md bg-zinc-900 shadow-lg ring-1 ring-zinc-100/5 focus:outline-none">
|
||||||
>
|
|
||||||
<div class="py-1">
|
<div class="py-1">
|
||||||
<MenuItem v-if="showOptions" v-slot="{ active }">
|
<MenuItem v-slot="{ active }">
|
||||||
<button
|
<button @click="() => emit('options')" :class="[
|
||||||
@click="() => emit('options')"
|
|
||||||
:class="[
|
|
||||||
active
|
active
|
||||||
? 'bg-zinc-800 text-zinc-100 outline-none'
|
? 'bg-zinc-800 text-zinc-100 outline-none'
|
||||||
: 'text-zinc-400',
|
: 'text-zinc-400',
|
||||||
'w-full block px-4 py-2 text-sm inline-flex justify-between',
|
'w-full block px-4 py-2 text-sm inline-flex justify-between',
|
||||||
]"
|
]">
|
||||||
>
|
|
||||||
Options
|
Options
|
||||||
<Cog6ToothIcon class="size-5" />
|
<Cog6ToothIcon class="size-5" />
|
||||||
</button>
|
</button>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem v-slot="{ active }">
|
<MenuItem v-slot="{ active }">
|
||||||
<button
|
<button @click="() => emit('uninstall')" :class="[
|
||||||
@click="() => emit('uninstall')"
|
|
||||||
:class="[
|
|
||||||
active
|
active
|
||||||
? 'bg-zinc-800 text-zinc-100 outline-none'
|
? 'bg-zinc-800 text-zinc-100 outline-none'
|
||||||
: 'text-zinc-400',
|
: 'text-zinc-400',
|
||||||
'w-full block px-4 py-2 text-sm inline-flex justify-between',
|
'w-full block px-4 py-2 text-sm inline-flex justify-between',
|
||||||
]"
|
]">
|
||||||
>
|
|
||||||
Uninstall
|
Uninstall
|
||||||
<TrashIcon class="size-5" />
|
<TrashIcon class="size-5" />
|
||||||
</button>
|
</button>
|
||||||
@ -87,7 +61,6 @@ import {
|
|||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
PlayIcon,
|
PlayIcon,
|
||||||
QueueListIcon,
|
QueueListIcon,
|
||||||
ServerIcon,
|
|
||||||
StopIcon,
|
StopIcon,
|
||||||
WrenchIcon,
|
WrenchIcon,
|
||||||
} from "@heroicons/vue/20/solid";
|
} from "@heroicons/vue/20/solid";
|
||||||
@ -105,7 +78,7 @@ const emit = defineEmits<{
|
|||||||
(e: "uninstall"): void;
|
(e: "uninstall"): void;
|
||||||
(e: "kill"): void;
|
(e: "kill"): void;
|
||||||
(e: "options"): void;
|
(e: "options"): void;
|
||||||
(e: "resume"): void;
|
(e: "resume"): void
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const showDropdown = computed(
|
const showDropdown = computed(
|
||||||
@ -115,10 +88,6 @@ const showDropdown = computed(
|
|||||||
props.status.type === GameStatusEnum.PartiallyInstalled
|
props.status.type === GameStatusEnum.PartiallyInstalled
|
||||||
);
|
);
|
||||||
|
|
||||||
const showOptions = computed(
|
|
||||||
() => props.status.type === GameStatusEnum.Installed
|
|
||||||
);
|
|
||||||
|
|
||||||
const styles: { [key in GameStatusEnum]: string } = {
|
const styles: { [key in GameStatusEnum]: string } = {
|
||||||
[GameStatusEnum.Remote]:
|
[GameStatusEnum.Remote]:
|
||||||
"bg-blue-600 text-white hover:bg-blue-500 focus-visible:outline-blue-600 hover:bg-blue-500",
|
"bg-blue-600 text-white hover:bg-blue-500 focus-visible:outline-blue-600 hover:bg-blue-500",
|
||||||
@ -126,8 +95,6 @@ const styles: { [key in GameStatusEnum]: string } = {
|
|||||||
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
|
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
|
||||||
[GameStatusEnum.Downloading]:
|
[GameStatusEnum.Downloading]:
|
||||||
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
|
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
|
||||||
[GameStatusEnum.Validating]:
|
|
||||||
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
|
|
||||||
[GameStatusEnum.SetupRequired]:
|
[GameStatusEnum.SetupRequired]:
|
||||||
"bg-yellow-600 text-white hover:bg-yellow-500 focus-visible:outline-yellow-600 hover:bg-yellow-500",
|
"bg-yellow-600 text-white hover:bg-yellow-500 focus-visible:outline-yellow-600 hover:bg-yellow-500",
|
||||||
[GameStatusEnum.Installed]:
|
[GameStatusEnum.Installed]:
|
||||||
@ -139,45 +106,42 @@ const styles: { [key in GameStatusEnum]: string } = {
|
|||||||
[GameStatusEnum.Running]:
|
[GameStatusEnum.Running]:
|
||||||
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
|
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
|
||||||
[GameStatusEnum.PartiallyInstalled]:
|
[GameStatusEnum.PartiallyInstalled]:
|
||||||
"bg-blue-600 text-white hover:bg-blue-500 focus-visible:outline-blue-600 hover:bg-blue-500",
|
"bg-gray-600 text-white hover:bg-gray-500 focus-visible:outline-gray-600 hover:bg-gray-500"
|
||||||
};
|
};
|
||||||
|
|
||||||
const buttonNames: { [key in GameStatusEnum]: string } = {
|
const buttonNames: { [key in GameStatusEnum]: string } = {
|
||||||
[GameStatusEnum.Remote]: "Install",
|
[GameStatusEnum.Remote]: "Install",
|
||||||
[GameStatusEnum.Queued]: "Queued",
|
[GameStatusEnum.Queued]: "Queued",
|
||||||
[GameStatusEnum.Downloading]: "Downloading",
|
[GameStatusEnum.Downloading]: "Downloading",
|
||||||
[GameStatusEnum.Validating]: "Validating",
|
|
||||||
[GameStatusEnum.SetupRequired]: "Setup",
|
[GameStatusEnum.SetupRequired]: "Setup",
|
||||||
[GameStatusEnum.Installed]: "Play",
|
[GameStatusEnum.Installed]: "Play",
|
||||||
[GameStatusEnum.Updating]: "Updating",
|
[GameStatusEnum.Updating]: "Updating",
|
||||||
[GameStatusEnum.Uninstalling]: "Uninstalling",
|
[GameStatusEnum.Uninstalling]: "Uninstalling",
|
||||||
[GameStatusEnum.Running]: "Stop",
|
[GameStatusEnum.Running]: "Stop",
|
||||||
[GameStatusEnum.PartiallyInstalled]: "Resume",
|
[GameStatusEnum.PartiallyInstalled]: "Resume"
|
||||||
};
|
};
|
||||||
|
|
||||||
const buttonIcons: { [key in GameStatusEnum]: Component } = {
|
const buttonIcons: { [key in GameStatusEnum]: Component } = {
|
||||||
[GameStatusEnum.Remote]: ArrowDownTrayIcon,
|
[GameStatusEnum.Remote]: ArrowDownTrayIcon,
|
||||||
[GameStatusEnum.Queued]: QueueListIcon,
|
[GameStatusEnum.Queued]: QueueListIcon,
|
||||||
[GameStatusEnum.Downloading]: ArrowDownTrayIcon,
|
[GameStatusEnum.Downloading]: ArrowDownTrayIcon,
|
||||||
[GameStatusEnum.Validating]: ServerIcon,
|
|
||||||
[GameStatusEnum.SetupRequired]: WrenchIcon,
|
[GameStatusEnum.SetupRequired]: WrenchIcon,
|
||||||
[GameStatusEnum.Installed]: PlayIcon,
|
[GameStatusEnum.Installed]: PlayIcon,
|
||||||
[GameStatusEnum.Updating]: ArrowDownTrayIcon,
|
[GameStatusEnum.Updating]: ArrowDownTrayIcon,
|
||||||
[GameStatusEnum.Uninstalling]: TrashIcon,
|
[GameStatusEnum.Uninstalling]: TrashIcon,
|
||||||
[GameStatusEnum.Running]: StopIcon,
|
[GameStatusEnum.Running]: StopIcon,
|
||||||
[GameStatusEnum.PartiallyInstalled]: ArrowDownTrayIcon,
|
[GameStatusEnum.PartiallyInstalled]: ArrowDownTrayIcon
|
||||||
};
|
};
|
||||||
|
|
||||||
const buttonActions: { [key in GameStatusEnum]: () => void } = {
|
const buttonActions: { [key in GameStatusEnum]: () => void } = {
|
||||||
[GameStatusEnum.Remote]: () => emit("install"),
|
[GameStatusEnum.Remote]: () => emit("install"),
|
||||||
[GameStatusEnum.Queued]: () => emit("queue"),
|
[GameStatusEnum.Queued]: () => emit("queue"),
|
||||||
[GameStatusEnum.Downloading]: () => emit("queue"),
|
[GameStatusEnum.Downloading]: () => emit("queue"),
|
||||||
[GameStatusEnum.Validating]: () => emit("queue"),
|
|
||||||
[GameStatusEnum.SetupRequired]: () => emit("launch"),
|
[GameStatusEnum.SetupRequired]: () => emit("launch"),
|
||||||
[GameStatusEnum.Installed]: () => emit("launch"),
|
[GameStatusEnum.Installed]: () => emit("launch"),
|
||||||
[GameStatusEnum.Updating]: () => emit("queue"),
|
[GameStatusEnum.Updating]: () => emit("queue"),
|
||||||
[GameStatusEnum.Uninstalling]: () => {},
|
[GameStatusEnum.Uninstalling]: () => { },
|
||||||
[GameStatusEnum.Running]: () => emit("kill"),
|
[GameStatusEnum.Running]: () => emit("kill"),
|
||||||
[GameStatusEnum.PartiallyInstalled]: () => emit("resume"),
|
[GameStatusEnum.PartiallyInstalled]: () => emit("resume")
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -37,7 +37,7 @@
|
|||||||
<component class="h-5" :is="item.icon" />
|
<component class="h-5" :is="item.icon" />
|
||||||
</HeaderWidget>
|
</HeaderWidget>
|
||||||
</li>
|
</li>
|
||||||
<OfflineHeaderWidget v-if="state?.status === AppStatus.Offline" />
|
<OfflineHeaderWidget v-if="state.status === AppStatus.Offline" />
|
||||||
<HeaderUserWidget />
|
<HeaderUserWidget />
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<Menu v-if="state?.user" as="div" class="relative inline-block">
|
<Menu v-if="state.user" as="div" class="relative inline-block">
|
||||||
<MenuButton>
|
<MenuButton>
|
||||||
<HeaderWidget>
|
<HeaderWidget>
|
||||||
<div class="inline-flex items-center text-zinc-300 hover:text-white">
|
<div class="inline-flex items-center text-zinc-300 hover:text-white">
|
||||||
@ -87,7 +87,7 @@ router.afterEach(() => {
|
|||||||
|
|
||||||
const state = useAppState();
|
const state = useAppState();
|
||||||
const profilePictureUrl: string = await useObject(
|
const profilePictureUrl: string = await useObject(
|
||||||
state.value?.user?.profilePictureObjectId ?? ""
|
state.value.user?.profilePictureObjectId ?? ""
|
||||||
);
|
);
|
||||||
const adminUrl: string = await invoke("gen_drop_url", {
|
const adminUrl: string = await invoke("gen_drop_url", {
|
||||||
path: "/admin",
|
path: "/admin",
|
||||||
@ -1,59 +1,72 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col h-full">
|
<div>
|
||||||
<div class="mb-3 inline-flex gap-x-2">
|
<div class="mb-3 inline-flex gap-x-2">
|
||||||
<div class="relative transition-transform duration-300 hover:scale-105 active:scale-95">
|
<div
|
||||||
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
class="relative transition-transform duration-300 hover:scale-105 active:scale-95"
|
||||||
<MagnifyingGlassIcon class="h-5 w-5 text-zinc-400" aria-hidden="true" />
|
>
|
||||||
|
<div
|
||||||
|
class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3"
|
||||||
|
>
|
||||||
|
<MagnifyingGlassIcon
|
||||||
|
class="h-5 w-5 text-zinc-400"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" v-model="searchQuery"
|
<input
|
||||||
|
type="text"
|
||||||
|
v-model="searchQuery"
|
||||||
class="block w-full rounded-lg border-0 bg-zinc-800/50 py-2 pl-10 pr-3 text-zinc-100 placeholder:text-zinc-500 focus:bg-zinc-800 focus:ring-2 focus:ring-inset focus:ring-blue-500 sm:text-sm sm:leading-6"
|
class="block w-full rounded-lg border-0 bg-zinc-800/50 py-2 pl-10 pr-3 text-zinc-100 placeholder:text-zinc-500 focus:bg-zinc-800 focus:ring-2 focus:ring-inset focus:ring-blue-500 sm:text-sm sm:leading-6"
|
||||||
placeholder="Search library..." />
|
placeholder="Search library..."
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button @click="() => calculateGames(true)"
|
<button
|
||||||
class="p-1 flex items-center justify-center transition-transform duration-300 size-10 hover:scale-110 active:scale-90 rounded-lg bg-zinc-800/50 text-zinc-100">
|
@click="() => calculateGames(true)"
|
||||||
|
class="p-1 flex items-center justify-center transition-transform duration-300 size-10 hover:scale-110 active:scale-90 rounded-lg bg-zinc-800/50 text-zinc-100"
|
||||||
|
>
|
||||||
<ArrowPathIcon class="size-4" />
|
<ArrowPathIcon class="size-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TransitionGroup name="list" tag="ul" class="flex flex-col gap-y-1.5">
|
<TransitionGroup name="list" tag="ul" class="flex flex-col gap-y-1.5">
|
||||||
<NuxtLink v-for="(nav, navIndex) in filteredNavigation" :key="nav.id" :class="[
|
<NuxtLink
|
||||||
|
v-for="nav in filteredNavigation"
|
||||||
|
:key="nav.id"
|
||||||
|
:class="[
|
||||||
'transition-all duration-300 rounded-lg flex items-center py-2 px-3 hover:scale-105 active:scale-95 hover:shadow-lg hover:shadow-zinc-950/50',
|
'transition-all duration-300 rounded-lg flex items-center py-2 px-3 hover:scale-105 active:scale-95 hover:shadow-lg hover:shadow-zinc-950/50',
|
||||||
navIndex === currentNavigation
|
nav.index === currentNavigation
|
||||||
? 'bg-zinc-800 text-zinc-100 shadow-md shadow-zinc-950/20'
|
? 'bg-zinc-800 text-zinc-100 shadow-md shadow-zinc-950/20'
|
||||||
: nav.isInstalled.value
|
: nav.isInstalled.value
|
||||||
? 'text-zinc-300 hover:bg-zinc-800/90 hover:text-zinc-200'
|
? 'text-zinc-300 hover:bg-zinc-800/90 hover:text-zinc-200'
|
||||||
: 'text-zinc-500 hover:bg-zinc-800/70 hover:text-zinc-300',
|
: 'text-zinc-500 hover:bg-zinc-800/70 hover:text-zinc-300',
|
||||||
]" :href="nav.route">
|
]"
|
||||||
|
:href="nav.route"
|
||||||
|
>
|
||||||
<div class="flex items-center w-full gap-x-3">
|
<div class="flex items-center w-full gap-x-3">
|
||||||
<div class="flex-none transition-transform duration-300 hover:-rotate-2">
|
<div
|
||||||
<img class="size-8 object-cover bg-zinc-900 rounded-lg transition-all duration-300 shadow-sm"
|
class="flex-none transition-transform duration-300 hover:-rotate-2"
|
||||||
:src="icons[nav.id]" alt="" />
|
>
|
||||||
|
<img
|
||||||
|
class="size-8 object-cover bg-zinc-900 rounded-lg transition-all duration-300 shadow-sm"
|
||||||
|
:src="icons[nav.id]"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col flex-1">
|
<div class="flex flex-col flex-1">
|
||||||
<p class="truncate text-xs font-display leading-5 flex-1 font-semibold">
|
<p
|
||||||
|
class="truncate text-xs font-display leading-5 flex-1 font-semibold"
|
||||||
|
>
|
||||||
{{ nav.label }}
|
{{ nav.label }}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-xs font-medium" :class="[gameStatusTextStyle[games[nav.id].status.value.type]]">
|
<p
|
||||||
|
class="text-xs font-medium"
|
||||||
|
:class="[gameStatusTextStyle[games[nav.id].status.value.type]]"
|
||||||
|
>
|
||||||
{{ gameStatusText[games[nav.id].status.value.type] }}
|
{{ gameStatusText[games[nav.id].status.value.type] }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</TransitionGroup>
|
</TransitionGroup>
|
||||||
<div v-if="loading" class="h-full grow flex p-8 justify-center text-zinc-100">
|
|
||||||
<div role="status">
|
|
||||||
<svg aria-hidden="true" class="w-6 h-6 text-transparent animate-spin fill-zinc-600" viewBox="0 0 100 101"
|
|
||||||
fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path
|
|
||||||
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
|
||||||
fill="currentColor" />
|
|
||||||
<path
|
|
||||||
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
|
|
||||||
fill="currentFill" />
|
|
||||||
</svg>
|
|
||||||
<span class="sr-only">Loading...</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -67,21 +80,19 @@ import { listen } from "@tauri-apps/api/event";
|
|||||||
// Style information
|
// Style information
|
||||||
const gameStatusTextStyle: { [key in GameStatusEnum]: string } = {
|
const gameStatusTextStyle: { [key in GameStatusEnum]: string } = {
|
||||||
[GameStatusEnum.Installed]: "text-green-500",
|
[GameStatusEnum.Installed]: "text-green-500",
|
||||||
[GameStatusEnum.Downloading]: "text-zinc-400",
|
[GameStatusEnum.Downloading]: "text-blue-500",
|
||||||
[GameStatusEnum.Validating]: "text-blue-300",
|
|
||||||
[GameStatusEnum.Running]: "text-green-500",
|
[GameStatusEnum.Running]: "text-green-500",
|
||||||
[GameStatusEnum.Remote]: "text-zinc-500",
|
[GameStatusEnum.Remote]: "text-zinc-500",
|
||||||
[GameStatusEnum.Queued]: "text-zinc-400",
|
[GameStatusEnum.Queued]: "text-blue-500",
|
||||||
[GameStatusEnum.Updating]: "text-zinc-400",
|
[GameStatusEnum.Updating]: "text-blue-500",
|
||||||
[GameStatusEnum.Uninstalling]: "text-zinc-100",
|
[GameStatusEnum.Uninstalling]: "text-zinc-100",
|
||||||
[GameStatusEnum.SetupRequired]: "text-yellow-500",
|
[GameStatusEnum.SetupRequired]: "text-yellow-500",
|
||||||
[GameStatusEnum.PartiallyInstalled]: "text-gray-400",
|
[GameStatusEnum.PartiallyInstalled]: "text-gray-600",
|
||||||
};
|
};
|
||||||
const gameStatusText: { [key in GameStatusEnum]: string } = {
|
const gameStatusText: { [key in GameStatusEnum]: string } = {
|
||||||
[GameStatusEnum.Remote]: "Not installed",
|
[GameStatusEnum.Remote]: "Not installed",
|
||||||
[GameStatusEnum.Queued]: "Queued",
|
[GameStatusEnum.Queued]: "Queued",
|
||||||
[GameStatusEnum.Downloading]: "Downloading...",
|
[GameStatusEnum.Downloading]: "Downloading...",
|
||||||
[GameStatusEnum.Validating]: "Validating...",
|
|
||||||
[GameStatusEnum.Installed]: "Installed",
|
[GameStatusEnum.Installed]: "Installed",
|
||||||
[GameStatusEnum.Updating]: "Updating...",
|
[GameStatusEnum.Updating]: "Updating...",
|
||||||
[GameStatusEnum.Uninstalling]: "Uninstalling...",
|
[GameStatusEnum.Uninstalling]: "Uninstalling...",
|
||||||
@ -94,7 +105,6 @@ const router = useRouter();
|
|||||||
|
|
||||||
const searchQuery = ref("");
|
const searchQuery = ref("");
|
||||||
|
|
||||||
const loading = ref(false);
|
|
||||||
const games: {
|
const games: {
|
||||||
[key: string]: { game: Game; status: Ref<GameStatus, GameStatus> };
|
[key: string]: { game: Game; status: Ref<GameStatus, GameStatus> };
|
||||||
} = {};
|
} = {};
|
||||||
@ -103,10 +113,7 @@ const icons: { [key: string]: string } = {};
|
|||||||
const rawGames: Ref<Game[], Game[]> = ref([]);
|
const rawGames: Ref<Game[], Game[]> = ref([]);
|
||||||
|
|
||||||
async function calculateGames(clearAll = false) {
|
async function calculateGames(clearAll = false) {
|
||||||
if (clearAll) {
|
if (clearAll) rawGames.value = [];
|
||||||
rawGames.value = [];
|
|
||||||
loading.value = true;
|
|
||||||
}
|
|
||||||
// If we update immediately, the navigation gets re-rendered before we
|
// If we update immediately, the navigation gets re-rendered before we
|
||||||
// add all the necessary state, and it freaks tf out
|
// add all the necessary state, and it freaks tf out
|
||||||
const newGames = await invoke<typeof rawGames.value>("fetch_library");
|
const newGames = await invoke<typeof rawGames.value>("fetch_library");
|
||||||
@ -118,22 +125,10 @@ async function calculateGames(clearAll = false) {
|
|||||||
if (icons[game.id]) continue;
|
if (icons[game.id]) continue;
|
||||||
icons[game.id] = await useObject(game.mIconObjectId);
|
icons[game.id] = await useObject(game.mIconObjectId);
|
||||||
}
|
}
|
||||||
loading.value = false;
|
|
||||||
rawGames.value = newGames;
|
rawGames.value = newGames;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait up to 300 ms for the library to load, otherwise
|
await calculateGames();
|
||||||
// show the loading state while we while
|
|
||||||
await new Promise<void>((r) => {
|
|
||||||
let hasResolved = false;
|
|
||||||
const resolveFunc = () => {
|
|
||||||
if (!hasResolved) r();
|
|
||||||
hasResolved = true
|
|
||||||
|
|
||||||
}
|
|
||||||
calculateGames(true).then(resolveFunc);
|
|
||||||
setTimeout(resolveFunc, 300);
|
|
||||||
})
|
|
||||||
|
|
||||||
const navigation = computed(() =>
|
const navigation = computed(() =>
|
||||||
rawGames.value.map((game) => {
|
rawGames.value.map((game) => {
|
||||||
@ -141,7 +136,8 @@ const navigation = computed(() =>
|
|||||||
|
|
||||||
const isInstalled = computed(
|
const isInstalled = computed(
|
||||||
() =>
|
() =>
|
||||||
status.value.type != GameStatusEnum.Remote
|
status.value.type == GameStatusEnum.Installed ||
|
||||||
|
status.value.type == GameStatusEnum.SetupRequired
|
||||||
);
|
);
|
||||||
|
|
||||||
const item = {
|
const item = {
|
||||||
@ -154,11 +150,9 @@ const navigation = computed(() =>
|
|||||||
return item;
|
return item;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
const { currentNavigation, recalculateNavigation } = useCurrentNavigationIndex(
|
||||||
const route = useRoute();
|
navigation.value
|
||||||
const currentNavigation = computed(() => {
|
);
|
||||||
return navigation.value.findIndex((e) => e.route == route.path)
|
|
||||||
});
|
|
||||||
|
|
||||||
const filteredNavigation = computed(() => {
|
const filteredNavigation = computed(() => {
|
||||||
if (!searchQuery.value)
|
if (!searchQuery.value)
|
||||||
@ -173,7 +167,9 @@ listen("update_library", async (event) => {
|
|||||||
console.log("Updating library");
|
console.log("Updating library");
|
||||||
let oldNavigation = navigation.value[currentNavigation.value];
|
let oldNavigation = navigation.value[currentNavigation.value];
|
||||||
await calculateGames();
|
await calculateGames();
|
||||||
if (oldNavigation.route !== navigation.value[currentNavigation.value].route) {
|
recalculateNavigation();
|
||||||
|
if (oldNavigation !== navigation.value[currentNavigation.value]) {
|
||||||
|
console.log("Triggered");
|
||||||
router.push("/library");
|
router.push("/library");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
3
composables/app-state.ts
Normal file
3
composables/app-state.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import type { AppState } from "~/types";
|
||||||
|
|
||||||
|
export const useAppState = () => useState<AppState>("state");
|
||||||
@ -43,7 +43,6 @@ export const useGame = async (gameId: string) => {
|
|||||||
gameStatusRegistry[gameId] = ref(parseStatus(data.status));
|
gameStatusRegistry[gameId] = ref(parseStatus(data.status));
|
||||||
|
|
||||||
listen(`update_game/${gameId}`, (event) => {
|
listen(`update_game/${gameId}`, (event) => {
|
||||||
console.log(event);
|
|
||||||
const payload: {
|
const payload: {
|
||||||
status: SerializedGameStatus;
|
status: SerializedGameStatus;
|
||||||
version?: GameVersion;
|
version?: GameVersion;
|
||||||
@ -65,13 +65,7 @@ export function setupHooks() {
|
|||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initialNavigation(state: ReturnType<typeof useAppState>) {
|
export function initialNavigation(state: Ref<AppState>) {
|
||||||
if (!state.value)
|
|
||||||
throw createError({
|
|
||||||
statusCode: 500,
|
|
||||||
statusMessage: "App state not valid",
|
|
||||||
fatal: true,
|
|
||||||
});
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
switch (state.value.status) {
|
switch (state.value.status) {
|
||||||
@ -7,7 +7,6 @@
|
|||||||
class="mx-auto w-full max-w-7xl px-6 pt-6 sm:pt-10 lg:col-span-2 lg:col-start-1 lg:row-start-1 lg:px-8"
|
class="mx-auto w-full max-w-7xl px-6 pt-6 sm:pt-10 lg:col-span-2 lg:col-start-1 lg:row-start-1 lg:px-8"
|
||||||
>
|
>
|
||||||
<Logo class="h-10 w-auto sm:h-12" />
|
<Logo class="h-10 w-auto sm:h-12" />
|
||||||
|
|
||||||
</header>
|
</header>
|
||||||
<main
|
<main
|
||||||
class="mx-auto w-full max-w-7xl px-6 py-24 sm:py-32 lg:col-span-2 lg:col-start-1 lg:row-start-2 lg:px-8"
|
class="mx-auto w-full max-w-7xl px-6 py-24 sm:py-32 lg:col-span-2 lg:col-start-1 lg:row-start-2 lg:px-8"
|
||||||
@ -1,3 +0,0 @@
|
|||||||
import type { AppState } from "~/types";
|
|
||||||
|
|
||||||
export const useAppState = () => useState<AppState | undefined>("state");
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "view",
|
|
||||||
"private": true,
|
|
||||||
"version": "0.3.2",
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"build": "nuxt generate",
|
|
||||||
"dev": "nuxt dev",
|
|
||||||
"postinstall": "nuxt prepare",
|
|
||||||
"tauri": "tauri"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@headlessui/vue": "^1.7.23",
|
|
||||||
"@heroicons/vue": "^2.1.5",
|
|
||||||
"@nuxtjs/tailwindcss": "^6.12.2",
|
|
||||||
"@tauri-apps/api": "^2.7.0",
|
|
||||||
"koa": "^2.16.1",
|
|
||||||
"markdown-it": "^14.1.0",
|
|
||||||
"micromark": "^4.0.1",
|
|
||||||
"nuxt": "^3.16.0",
|
|
||||||
"scss": "^0.2.4",
|
|
||||||
"vue-router": "latest",
|
|
||||||
"vuedraggable": "^4.1.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@tailwindcss/forms": "^0.5.9",
|
|
||||||
"@tailwindcss/typography": "^0.5.15",
|
|
||||||
"@types/markdown-it": "^14.1.2",
|
|
||||||
"autoprefixer": "^10.4.20",
|
|
||||||
"postcss": "^8.4.47",
|
|
||||||
"sass-embedded": "^1.79.4",
|
|
||||||
"tailwindcss": "^3.4.13",
|
|
||||||
"typescript": "^5.8.3",
|
|
||||||
"vue-tsc": "^2.2.10"
|
|
||||||
},
|
|
||||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
|
||||||
}
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
// https://nuxt.com/docs/guide/concepts/typescript
|
|
||||||
"extends": "./.nuxt/tsconfig.json",
|
|
||||||
"exclude": ["src-tauri/**/*"]
|
|
||||||
}
|
|
||||||
8091
main/yarn.lock
8091
main/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -13,9 +13,5 @@ export default defineNuxtConfig({
|
|||||||
|
|
||||||
ssr: false,
|
ssr: false,
|
||||||
|
|
||||||
extends: [["../libs/drop-base"]],
|
extends: [["./drop-base"]],
|
||||||
|
|
||||||
app: {
|
|
||||||
baseURL: "/main",
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
@ -1,22 +0,0 @@
|
|||||||
## This script is largely useless, because there's not much we can do about AppImage size
|
|
||||||
|
|
||||||
ARCH=$(uname -m)
|
|
||||||
|
|
||||||
# build tauri apps
|
|
||||||
# NO_STRIP=true yarn tauri build -- --verbose
|
|
||||||
|
|
||||||
# unpack appimage
|
|
||||||
APPIMAGE=$(ls ./src-tauri/target/release/bundle/appimage/*.AppImage)
|
|
||||||
"$APPIMAGE" --appimage-extract
|
|
||||||
|
|
||||||
# strip binary
|
|
||||||
APPIMAGE_UNPACK="./squashfs-root"
|
|
||||||
find $APPIMAGE_UNPACK -type f -exec strip -s {} \;
|
|
||||||
|
|
||||||
APPIMAGETOOL=$(echo "obsolete-appimagetool-$ARCH.AppImage")
|
|
||||||
wget -O $APPIMAGETOOL "https://github.com/AppImage/AppImageKit/releases/download/13/$APPIMAGETOOL"
|
|
||||||
chmod +x $APPIMAGETOOL
|
|
||||||
|
|
||||||
APPIMAGE_OUTPUT=$(./$APPIMAGETOOL $APPIMAGE_UNPACK | grep ".AppImage" | grep squashfs-root | awk '{ print $6 }')
|
|
||||||
|
|
||||||
mv $APPIMAGE_OUTPUT "$APPIMAGE"
|
|
||||||
44
package.json
44
package.json
@ -1,22 +1,46 @@
|
|||||||
{
|
{
|
||||||
"name": "drop-app",
|
"name": "drop-app",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"version": "0.3.1-mac",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node ./build.mjs",
|
"build": "nuxt build",
|
||||||
|
"dev": "nuxt dev",
|
||||||
|
"generate": "nuxt generate",
|
||||||
|
"preview": "nuxt preview",
|
||||||
|
"postinstall": "nuxt prepare",
|
||||||
"tauri": "tauri"
|
"tauri": "tauri"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "^2.7.0",
|
"@headlessui/vue": "^1.7.23",
|
||||||
"@tauri-apps/plugin-deep-link": "^2.4.1",
|
"@heroicons/vue": "^2.1.5",
|
||||||
"@tauri-apps/plugin-dialog": "^2.3.2",
|
"@nuxtjs/tailwindcss": "^6.12.2",
|
||||||
|
"@tauri-apps/api": ">=2.0.0",
|
||||||
|
"@tauri-apps/plugin-deep-link": "~2",
|
||||||
|
"@tauri-apps/plugin-dialog": "^2.0.1",
|
||||||
"@tauri-apps/plugin-opener": "^2.4.0",
|
"@tauri-apps/plugin-opener": "^2.4.0",
|
||||||
"@tauri-apps/plugin-os": "^2.3.0",
|
"@tauri-apps/plugin-os": "~2",
|
||||||
"@tauri-apps/plugin-shell": "^2.3.0",
|
"@tauri-apps/plugin-shell": "^2.2.1",
|
||||||
"pino": "^9.7.0",
|
"koa": "^2.16.1",
|
||||||
"pino-pretty": "^13.1.1"
|
"markdown-it": "^14.1.0",
|
||||||
|
"micromark": "^4.0.1",
|
||||||
|
"nuxt": "^3.16.0",
|
||||||
|
"scss": "^0.2.4",
|
||||||
|
"vue": "latest",
|
||||||
|
"vue-router": "latest",
|
||||||
|
"vuedraggable": "^4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "^2.7.1"
|
"@tailwindcss/forms": "^0.5.9",
|
||||||
}
|
"@tailwindcss/typography": "^0.5.15",
|
||||||
|
"@tauri-apps/cli": ">=2.0.0",
|
||||||
|
"@types/markdown-it": "^14.1.2",
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"postcss": "^8.4.47",
|
||||||
|
"sass-embedded": "^1.79.4",
|
||||||
|
"tailwindcss": "^3.4.13",
|
||||||
|
"typescript": "^5.8.3",
|
||||||
|
"vue-tsc": "^2.2.10"
|
||||||
|
},
|
||||||
|
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -243,10 +243,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div v-else class="mt-1 rounded-md bg-red-600/10 p-4">
|
||||||
v-else-if="versionOptions === null || versionOptions?.length == 0"
|
|
||||||
class="mt-1 rounded-md bg-red-600/10 p-4"
|
|
||||||
>
|
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<XCircleIcon class="h-5 w-5 text-red-600" aria-hidden="true" />
|
<XCircleIcon class="h-5 w-5 text-red-600" aria-hidden="true" />
|
||||||
@ -259,27 +256,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="w-full flex items-center justify-center p-4">
|
|
||||||
<div role="status">
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
class="w-7 h-7 text-transparent animate-spin fill-white"
|
|
||||||
viewBox="0 0 100 101"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
|
|
||||||
fill="currentFill"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<span class="sr-only">Loading...</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="installDirs">
|
<div v-if="installDirs">
|
||||||
<Listbox as="div" v-model="installDir">
|
<Listbox as="div" v-model="installDir">
|
||||||
<ListboxLabel class="block text-sm/6 font-medium text-zinc-100"
|
<ListboxLabel class="block text-sm/6 font-medium text-zinc-100"
|
||||||
@ -392,13 +368,6 @@
|
|||||||
</template>
|
</template>
|
||||||
</ModalTemplate>
|
</ModalTemplate>
|
||||||
|
|
||||||
<!--
|
|
||||||
Dear future DecDuck,
|
|
||||||
This v-if is necessary for Vue rendering reasons
|
|
||||||
(it tries to access the game version for not installed games)
|
|
||||||
You have already tried to remove it
|
|
||||||
Don't.
|
|
||||||
-->
|
|
||||||
<GameOptionsModal
|
<GameOptionsModal
|
||||||
v-if="status.type === GameStatusEnum.Installed"
|
v-if="status.type === GameStatusEnum.Installed"
|
||||||
v-model="configureModalOpen"
|
v-model="configureModalOpen"
|
||||||
@ -534,13 +503,13 @@ async function installFlow() {
|
|||||||
installDirs.value = undefined;
|
installDirs.value = undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
versionOptions.value = await invoke("fetch_game_version_options", {
|
versionOptions.value = await invoke("fetch_game_verion_options", {
|
||||||
gameId: game.value.id,
|
gameId: game.value.id,
|
||||||
});
|
});
|
||||||
|
console.log(versionOptions.value);
|
||||||
installDirs.value = await invoke("fetch_download_dir_stats");
|
installDirs.value = await invoke("fetch_download_dir_stats");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
installError.value = (error as string).toString();
|
installError.value = (error as string).toString();
|
||||||
versionOptions.value = undefined;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
290
src-tauri/Cargo.lock
generated
290
src-tauri/Cargo.lock
generated
@ -898,7 +898,7 @@ dependencies = [
|
|||||||
"bitflags 2.9.1",
|
"bitflags 2.9.1",
|
||||||
"core-foundation 0.10.1",
|
"core-foundation 0.10.1",
|
||||||
"core-graphics-types",
|
"core-graphics-types",
|
||||||
"foreign-types 0.5.0",
|
"foreign-types",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1284,7 +1284,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "drop-app"
|
name = "drop-app"
|
||||||
version = "0.3.2"
|
version = "0.3.1-mac"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atomic-instant-full",
|
"atomic-instant-full",
|
||||||
"bitcode",
|
"bitcode",
|
||||||
@ -1301,18 +1301,16 @@ dependencies = [
|
|||||||
"hex 0.4.3",
|
"hex 0.4.3",
|
||||||
"http 1.3.1",
|
"http 1.3.1",
|
||||||
"http-serde 2.1.1",
|
"http-serde 2.1.1",
|
||||||
"humansize",
|
|
||||||
"known-folders",
|
"known-folders",
|
||||||
"log",
|
"log",
|
||||||
"log4rs",
|
"log4rs",
|
||||||
"md5",
|
"md5",
|
||||||
"native_model",
|
"native_model",
|
||||||
"page_size",
|
|
||||||
"parking_lot 0.12.3",
|
"parking_lot 0.12.3",
|
||||||
"rand 0.9.1",
|
"rand 0.9.1",
|
||||||
"rayon",
|
"rayon",
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest 0.12.22",
|
"reqwest 0.12.16",
|
||||||
"reqwest-middleware 0.4.2",
|
"reqwest-middleware 0.4.2",
|
||||||
"reqwest-middleware-cache",
|
"reqwest-middleware-cache",
|
||||||
"reqwest-websocket",
|
"reqwest-websocket",
|
||||||
@ -1325,7 +1323,6 @@ dependencies = [
|
|||||||
"sha1",
|
"sha1",
|
||||||
"shared_child",
|
"shared_child",
|
||||||
"slice-deque",
|
"slice-deque",
|
||||||
"sysinfo",
|
|
||||||
"tar",
|
"tar",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
@ -1588,15 +1585,6 @@ version = "1.0.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "foreign-types"
|
|
||||||
version = "0.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
|
||||||
dependencies = [
|
|
||||||
"foreign-types-shared 0.1.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foreign-types"
|
name = "foreign-types"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@ -1604,7 +1592,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
|
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"foreign-types-macros",
|
"foreign-types-macros",
|
||||||
"foreign-types-shared 0.3.1",
|
"foreign-types-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1618,12 +1606,6 @@ dependencies = [
|
|||||||
"syn 2.0.101",
|
"syn 2.0.101",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "foreign-types-shared"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foreign-types-shared"
|
name = "foreign-types-shared"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@ -2312,15 +2294,6 @@ version = "1.0.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "humansize"
|
|
||||||
version = "2.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
|
|
||||||
dependencies = [
|
|
||||||
"libm",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "humantime"
|
name = "humantime"
|
||||||
version = "2.2.0"
|
version = "2.2.0"
|
||||||
@ -2381,7 +2354,6 @@ dependencies = [
|
|||||||
"hyper 1.6.0",
|
"hyper 1.6.0",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-native-certs",
|
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
@ -2389,22 +2361,6 @@ dependencies = [
|
|||||||
"webpki-roots",
|
"webpki-roots",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hyper-tls"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
|
|
||||||
dependencies = [
|
|
||||||
"bytes",
|
|
||||||
"http-body-util",
|
|
||||||
"hyper 1.6.0",
|
|
||||||
"hyper-util",
|
|
||||||
"native-tls",
|
|
||||||
"tokio",
|
|
||||||
"tokio-native-tls",
|
|
||||||
"tower-service",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-util"
|
name = "hyper-util"
|
||||||
version = "0.1.13"
|
version = "0.1.13"
|
||||||
@ -2817,9 +2773,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.174"
|
version = "0.2.172"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
|
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libloading"
|
name = "libloading"
|
||||||
@ -2831,12 +2787,6 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "libm"
|
|
||||||
version = "0.2.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libredox"
|
name = "libredox"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
@ -3099,23 +3049,6 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "native-tls"
|
|
||||||
version = "0.2.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"log",
|
|
||||||
"openssl",
|
|
||||||
"openssl-probe",
|
|
||||||
"openssl-sys",
|
|
||||||
"schannel",
|
|
||||||
"security-framework 2.11.1",
|
|
||||||
"security-framework-sys",
|
|
||||||
"tempfile",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "native_model"
|
name = "native_model"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
@ -3208,15 +3141,6 @@ dependencies = [
|
|||||||
"minimal-lexical",
|
"minimal-lexical",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ntapi"
|
|
||||||
version = "0.4.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
|
|
||||||
dependencies = [
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-bigint"
|
name = "num-bigint"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
@ -3413,16 +3337,6 @@ dependencies = [
|
|||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "objc2-io-kit"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"objc2-core-foundation",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-io-surface"
|
name = "objc2-io-surface"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@ -3553,50 +3467,6 @@ dependencies = [
|
|||||||
"pathdiff",
|
"pathdiff",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl"
|
|
||||||
version = "0.10.73"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.9.1",
|
|
||||||
"cfg-if",
|
|
||||||
"foreign-types 0.3.2",
|
|
||||||
"libc",
|
|
||||||
"once_cell",
|
|
||||||
"openssl-macros",
|
|
||||||
"openssl-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl-macros"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.101",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl-probe"
|
|
||||||
version = "0.1.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl-sys"
|
|
||||||
version = "0.9.109"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
"libc",
|
|
||||||
"pkg-config",
|
|
||||||
"vcpkg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "option-ext"
|
name = "option-ext"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@ -3653,16 +3523,6 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "page_size"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pango"
|
name = "pango"
|
||||||
version = "0.18.3"
|
version = "0.18.3"
|
||||||
@ -4420,9 +4280,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.12.22"
|
version = "0.12.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531"
|
checksum = "2bf597b113be201cb2269b4c39b39a804d01b99ee95a4278f0ed04e45cff1c71"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -4435,23 +4295,22 @@ dependencies = [
|
|||||||
"http-body-util",
|
"http-body-util",
|
||||||
"hyper 1.6.0",
|
"hyper 1.6.0",
|
||||||
"hyper-rustls",
|
"hyper-rustls",
|
||||||
"hyper-tls",
|
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
|
"ipnet",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"native-tls",
|
"mime",
|
||||||
|
"once_cell",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"quinn",
|
"quinn",
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-native-certs",
|
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"sync_wrapper 1.0.2",
|
"sync_wrapper 1.0.2",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-native-tls",
|
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tower",
|
"tower",
|
||||||
@ -4490,7 +4349,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"http 1.3.1",
|
"http 1.3.1",
|
||||||
"reqwest 0.12.22",
|
"reqwest 0.12.16",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
@ -4525,7 +4384,7 @@ dependencies = [
|
|||||||
"async-tungstenite",
|
"async-tungstenite",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"reqwest 0.12.22",
|
"reqwest 0.12.16",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
@ -4688,18 +4547,6 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustls-native-certs"
|
|
||||||
version = "0.8.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3"
|
|
||||||
dependencies = [
|
|
||||||
"openssl-probe",
|
|
||||||
"rustls-pki-types",
|
|
||||||
"schannel",
|
|
||||||
"security-framework 3.2.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-pki-types"
|
name = "rustls-pki-types"
|
||||||
version = "1.12.0"
|
version = "1.12.0"
|
||||||
@ -4742,15 +4589,6 @@ dependencies = [
|
|||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "schannel"
|
|
||||||
version = "0.1.27"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
|
|
||||||
dependencies = [
|
|
||||||
"windows-sys 0.59.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "schemars"
|
name = "schemars"
|
||||||
version = "0.8.22"
|
version = "0.8.22"
|
||||||
@ -4784,42 +4622,6 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "security-framework"
|
|
||||||
version = "2.11.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.9.1",
|
|
||||||
"core-foundation 0.9.4",
|
|
||||||
"core-foundation-sys",
|
|
||||||
"libc",
|
|
||||||
"security-framework-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "security-framework"
|
|
||||||
version = "3.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.9.1",
|
|
||||||
"core-foundation 0.10.1",
|
|
||||||
"core-foundation-sys",
|
|
||||||
"libc",
|
|
||||||
"security-framework-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "security-framework-sys"
|
|
||||||
version = "2.14.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
|
|
||||||
dependencies = [
|
|
||||||
"core-foundation-sys",
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "selectors"
|
name = "selectors"
|
||||||
version = "0.24.0"
|
version = "0.24.0"
|
||||||
@ -5189,7 +4991,7 @@ dependencies = [
|
|||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
"core-graphics",
|
"core-graphics",
|
||||||
"foreign-types 0.5.0",
|
"foreign-types",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
@ -5377,20 +5179,6 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sysinfo"
|
|
||||||
version = "0.36.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "252800745060e7b9ffb7b2badbd8b31cfa4aa2e61af879d0a3bf2a317c20217d"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"memchr",
|
|
||||||
"ntapi",
|
|
||||||
"objc2-core-foundation",
|
|
||||||
"objc2-io-kit",
|
|
||||||
"windows",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "system-configuration"
|
name = "system-configuration"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@ -5503,9 +5291,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri"
|
name = "tauri"
|
||||||
version = "2.7.0"
|
version = "2.6.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "352a4bc7bf6c25f5624227e3641adf475a6535707451b09bb83271df8b7a6ac7"
|
checksum = "124e129c9c0faa6bec792c5948c89e86c90094133b0b9044df0ce5f0a8efaa0d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -5530,7 +5318,7 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"plist",
|
"plist",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"reqwest 0.12.22",
|
"reqwest 0.12.16",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_repr",
|
"serde_repr",
|
||||||
@ -5554,9 +5342,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-build"
|
name = "tauri-build"
|
||||||
version = "2.3.1"
|
version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "182d688496c06bf08ea896459bf483eb29cdff35c1c4c115fb14053514303064"
|
checksum = "12f025c389d3adb83114bec704da973142e82fc6ec799c7c750c5e21cefaec83"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cargo_toml",
|
"cargo_toml",
|
||||||
@ -5576,9 +5364,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-codegen"
|
name = "tauri-codegen"
|
||||||
version = "2.3.1"
|
version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b54a99a6cd8e01abcfa61508177e6096a4fe2681efecee9214e962f2f073ae4a"
|
checksum = "f5df493a1075a241065bc865ed5ef8d0fbc1e76c7afdc0bf0eccfaa7d4f0e406"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"brotli",
|
"brotli",
|
||||||
@ -5603,9 +5391,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-macros"
|
name = "tauri-macros"
|
||||||
version = "2.3.2"
|
version = "2.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7945b14dc45e23532f2ded6e120170bbdd4af5ceaa45784a6b33d250fbce3f9e"
|
checksum = "f237fbea5866fa5f2a60a21bea807a2d6e0379db070d89c3a10ac0f2d4649bbc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@ -5785,9 +5573,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime"
|
name = "tauri-runtime"
|
||||||
version = "2.7.1"
|
version = "2.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b1cc885be806ea15ff7b0eb47098a7b16323d9228876afda329e34e2d6c4676"
|
checksum = "9e7bb73d1bceac06c20b3f755b2c8a2cb13b20b50083084a8cf3700daf397ba4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cookie",
|
"cookie",
|
||||||
"dpi",
|
"dpi",
|
||||||
@ -5807,9 +5595,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime-wry"
|
name = "tauri-runtime-wry"
|
||||||
version = "2.7.2"
|
version = "2.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fe653a2fbbef19fe898efc774bc52c8742576342a33d3d028c189b57eb1d2439"
|
checksum = "902b5aa9035e16f342eb64f8bf06ccdc2808e411a2525ed1d07672fa4e780bad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gtk",
|
"gtk",
|
||||||
"http 1.3.1",
|
"http 1.3.1",
|
||||||
@ -5834,9 +5622,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-utils"
|
name = "tauri-utils"
|
||||||
version = "2.6.0"
|
version = "2.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9330c15cabfe1d9f213478c9e8ec2b0c76dab26bb6f314b8ad1c8a568c1d186e"
|
checksum = "41743bbbeb96c3a100d234e5a0b60a46d5aa068f266160862c7afdbf828ca02e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"brotli",
|
"brotli",
|
||||||
@ -6061,16 +5849,6 @@ dependencies = [
|
|||||||
"syn 2.0.101",
|
"syn 2.0.101",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tokio-native-tls"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
|
||||||
dependencies = [
|
|
||||||
"native-tls",
|
|
||||||
"tokio",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-rustls"
|
name = "tokio-rustls"
|
||||||
version = "0.26.2"
|
version = "0.26.2"
|
||||||
@ -6175,9 +5953,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-http"
|
name = "tower-http"
|
||||||
version = "0.6.6"
|
version = "0.6.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
|
checksum = "0fdb0c213ca27a9f57ab69ddb290fd80d970922355b83ae380b395d3986b8a2e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.1",
|
"bitflags 2.9.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -6482,12 +6260,6 @@ version = "1.11.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5"
|
checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "vcpkg"
|
|
||||||
version = "0.2.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version-compare"
|
name = "version-compare"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "drop-app"
|
name = "drop-app"
|
||||||
version = "0.3.2"
|
version = "0.3.1-mac"
|
||||||
description = "The client application for the open-source, self-hosted game distribution platform Drop"
|
description = "The client application for the open-source, self-hosted game distribution platform Drop"
|
||||||
authors = ["Drop OSS"]
|
authors = ["Drop OSS"]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
@ -70,9 +70,6 @@ tauri-plugin-opener = "2.4.0"
|
|||||||
bitcode = "0.6.6"
|
bitcode = "0.6.6"
|
||||||
reqwest-websocket = "0.5.0"
|
reqwest-websocket = "0.5.0"
|
||||||
futures-lite = "2.6.0"
|
futures-lite = "2.6.0"
|
||||||
page_size = "0.6.0"
|
|
||||||
sysinfo = "0.36.1"
|
|
||||||
humansize = "2.1.3"
|
|
||||||
# tailscale = { path = "./tailscale" }
|
# tailscale = { path = "./tailscale" }
|
||||||
|
|
||||||
[dependencies.dynfmt]
|
[dependencies.dynfmt]
|
||||||
@ -80,7 +77,7 @@ version = "0.1.5"
|
|||||||
features = ["curly"]
|
features = ["curly"]
|
||||||
|
|
||||||
[dependencies.tauri]
|
[dependencies.tauri]
|
||||||
version = "2.7.0"
|
version = "2.1.1"
|
||||||
features = ["protocol-asset", "tray-icon"]
|
features = ["protocol-asset", "tray-icon"]
|
||||||
|
|
||||||
[dependencies.tokio]
|
[dependencies.tokio]
|
||||||
@ -104,9 +101,9 @@ version = "2"
|
|||||||
features = ["other_errors"] # You can also use "yaml_enc" or "bin_enc"
|
features = ["other_errors"] # You can also use "yaml_enc" or "bin_enc"
|
||||||
|
|
||||||
[dependencies.reqwest]
|
[dependencies.reqwest]
|
||||||
version = "0.12.22"
|
version = "0.12"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["json", "http2", "blocking", "rustls-tls", "native-tls-alpn", "rustls-tls-native-roots"]
|
features = ["json", "http2", "blocking", "rustls-tls-webpki-roots"]
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies.serde]
|
||||||
version = "1"
|
version = "1"
|
||||||
|
|||||||
@ -7,7 +7,7 @@ use std::{
|
|||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
database::{db::borrow_db_mut_checked, scan::scan_install_dirs}, error::download_manager_error::DownloadManagerError,
|
database::db::borrow_db_mut_checked, error::download_manager_error::DownloadManagerError,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@ -59,8 +59,6 @@ pub fn add_download_dir(new_dir: PathBuf) -> Result<(), DownloadManagerError<()>
|
|||||||
lock.applications.install_dirs.push(new_dir);
|
lock.applications.install_dirs.push(new_dir);
|
||||||
drop(lock);
|
drop(lock);
|
||||||
|
|
||||||
scan_install_dirs();
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ use chrono::Utc;
|
|||||||
use log::{debug, error, info, warn};
|
use log::{debug, error, info, warn};
|
||||||
use native_model::{Decode, Encode};
|
use native_model::{Decode, Encode};
|
||||||
use rustbreak::{DeSerError, DeSerializer, PathDatabase, RustbreakError};
|
use rustbreak::{DeSerError, DeSerializer, PathDatabase, RustbreakError};
|
||||||
use serde::{Serialize, de::DeserializeOwned};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::DB;
|
use crate::DB;
|
||||||
@ -67,18 +67,17 @@ impl DatabaseImpls for DatabaseInterface {
|
|||||||
|
|
||||||
let exists = fs::exists(db_path.clone()).unwrap();
|
let exists = fs::exists(db_path.clone()).unwrap();
|
||||||
|
|
||||||
if exists {
|
if exists { match PathDatabase::load_from_path(db_path.clone()) {
|
||||||
match PathDatabase::load_from_path(db_path.clone()) {
|
|
||||||
Ok(db) => db,
|
Ok(db) => db,
|
||||||
Err(e) => handle_invalid_database(e, db_path, games_base_dir, cache_dir),
|
Err(e) => handle_invalid_database(e, db_path, games_base_dir, cache_dir),
|
||||||
}
|
} } else {
|
||||||
} else {
|
|
||||||
let default = Database::new(games_base_dir, None, cache_dir);
|
let default = Database::new(games_base_dir, None, cache_dir);
|
||||||
debug!(
|
debug!(
|
||||||
"Creating database at path {}",
|
"Creating database at path {}",
|
||||||
db_path.as_os_str().to_str().unwrap()
|
db_path.as_os_str().to_str().unwrap()
|
||||||
);
|
);
|
||||||
PathDatabase::create_at_path(db_path, default).expect("Database could not be created")
|
PathDatabase::create_at_path(db_path, default)
|
||||||
|
.expect("Database could not be created")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,24 +121,24 @@ fn handle_invalid_database(
|
|||||||
pub struct DBRead<'a>(RwLockReadGuard<'a, Database>);
|
pub struct DBRead<'a>(RwLockReadGuard<'a, Database>);
|
||||||
pub struct DBWrite<'a>(ManuallyDrop<RwLockWriteGuard<'a, Database>>);
|
pub struct DBWrite<'a>(ManuallyDrop<RwLockWriteGuard<'a, Database>>);
|
||||||
impl<'a> Deref for DBWrite<'a> {
|
impl<'a> Deref for DBWrite<'a> {
|
||||||
type Target = Database;
|
type Target = RwLockWriteGuard<'a, Database>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<'a> DerefMut for DBWrite<'a> {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a> Deref for DBRead<'a> {
|
impl<'a> Deref for DBRead<'a> {
|
||||||
type Target = Database;
|
type Target = RwLockReadGuard<'a, Database>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl DerefMut for DBWrite<'_> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
impl Drop for DBWrite<'_> {
|
impl Drop for DBWrite<'_> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -155,7 +154,6 @@ impl Drop for DBWrite<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn borrow_db_checked<'a>() -> DBRead<'a> {
|
pub fn borrow_db_checked<'a>() -> DBRead<'a> {
|
||||||
match DB.borrow_data() {
|
match DB.borrow_data() {
|
||||||
Ok(data) => DBRead(data),
|
Ok(data) => DBRead(data),
|
||||||
|
|||||||
@ -2,4 +2,3 @@ pub mod commands;
|
|||||||
pub mod db;
|
pub mod db;
|
||||||
pub mod debug;
|
pub mod debug;
|
||||||
pub mod models;
|
pub mod models;
|
||||||
pub mod scan;
|
|
||||||
@ -1,14 +1,8 @@
|
|||||||
/**
|
use crate::database::models::data::Database;
|
||||||
* NEXT BREAKING CHANGE
|
|
||||||
*
|
|
||||||
* UPDATE DATABASE TO USE RPMSERDENAMED
|
|
||||||
*
|
|
||||||
* WE CAN'T DELETE ANY FIELDS
|
|
||||||
*/
|
|
||||||
pub mod data {
|
pub mod data {
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
|
||||||
use native_model::native_model;
|
use native_model::native_model;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -24,14 +18,16 @@ pub mod data {
|
|||||||
pub type DatabaseApplications = v2::DatabaseApplications;
|
pub type DatabaseApplications = v2::DatabaseApplications;
|
||||||
pub type DatabaseCompatInfo = v2::DatabaseCompatInfo;
|
pub type DatabaseCompatInfo = v2::DatabaseCompatInfo;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, process::Command};
|
||||||
|
|
||||||
|
use crate::process::process_manager::UMU_LAUNCHER_EXECUTABLE;
|
||||||
|
|
||||||
pub mod v1 {
|
pub mod v1 {
|
||||||
use crate::process::process_manager::Platform;
|
use crate::process::process_manager::Platform;
|
||||||
use serde_with::serde_as;
|
use serde_with::serde_as;
|
||||||
use std::{collections::HashMap, path::PathBuf};
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
|
||||||
use super::{Deserialize, Serialize, native_model};
|
use super::{Serialize, Deserialize, native_model};
|
||||||
|
|
||||||
fn default_template() -> String {
|
fn default_template() -> String {
|
||||||
"{}".to_owned()
|
"{}".to_owned()
|
||||||
@ -119,7 +115,6 @@ pub mod data {
|
|||||||
Downloading { version_name: String },
|
Downloading { version_name: String },
|
||||||
Uninstalling {},
|
Uninstalling {},
|
||||||
Updating { version_name: String },
|
Updating { version_name: String },
|
||||||
Validating { version_name: String },
|
|
||||||
Running {},
|
Running {},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,10 +174,7 @@ pub mod data {
|
|||||||
|
|
||||||
use serde_with::serde_as;
|
use serde_with::serde_as;
|
||||||
|
|
||||||
use super::{
|
use super::{Serialize, Deserialize, native_model, Settings, DatabaseAuth, v1, GameVersion, DownloadableMetadata, ApplicationTransientStatus};
|
||||||
ApplicationTransientStatus, DatabaseAuth, Deserialize, DownloadableMetadata,
|
|
||||||
GameVersion, Serialize, Settings, native_model, v1,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[native_model(id = 1, version = 2, with = native_model::rmp_serde_1_3::RmpSerde)]
|
#[native_model(id = 1, version = 2, with = native_model::rmp_serde_1_3::RmpSerde)]
|
||||||
#[derive(Serialize, Deserialize, Clone, Default)]
|
#[derive(Serialize, Deserialize, Clone, Default)]
|
||||||
@ -214,7 +206,7 @@ pub mod data {
|
|||||||
applications: value.applications,
|
applications: value.applications,
|
||||||
prev_database: value.prev_database,
|
prev_database: value.prev_database,
|
||||||
cache_dir: value.cache_dir,
|
cache_dir: value.cache_dir,
|
||||||
compat_info: None,
|
compat_info: crate::database::models::Database::create_new_compat_info(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -266,7 +258,6 @@ pub mod data {
|
|||||||
pub install_dirs: Vec<PathBuf>,
|
pub install_dirs: Vec<PathBuf>,
|
||||||
// Guaranteed to exist if the game also exists in the app state map
|
// Guaranteed to exist if the game also exists in the app state map
|
||||||
pub game_statuses: HashMap<String, GameDownloadStatus>,
|
pub game_statuses: HashMap<String, GameDownloadStatus>,
|
||||||
|
|
||||||
pub game_versions: HashMap<String, HashMap<String, GameVersion>>,
|
pub game_versions: HashMap<String, HashMap<String, GameVersion>>,
|
||||||
pub installed_game_version: HashMap<String, DownloadableMetadata>,
|
pub installed_game_version: HashMap<String, DownloadableMetadata>,
|
||||||
|
|
||||||
@ -292,10 +283,7 @@ pub mod data {
|
|||||||
mod v3 {
|
mod v3 {
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use super::{
|
use super::{Serialize, Deserialize, native_model, Settings, DatabaseAuth, DatabaseApplications, DatabaseCompatInfo, v2};
|
||||||
DatabaseApplications, DatabaseAuth, DatabaseCompatInfo, Deserialize, Serialize,
|
|
||||||
Settings, native_model, v2,
|
|
||||||
};
|
|
||||||
#[native_model(id = 1, version = 3, with = native_model::rmp_serde_1_3::RmpSerde)]
|
#[native_model(id = 1, version = 3, with = native_model::rmp_serde_1_3::RmpSerde)]
|
||||||
#[derive(Serialize, Deserialize, Clone, Default)]
|
#[derive(Serialize, Deserialize, Clone, Default)]
|
||||||
pub struct Database {
|
pub struct Database {
|
||||||
@ -309,7 +297,6 @@ pub mod data {
|
|||||||
pub cache_dir: PathBuf,
|
pub cache_dir: PathBuf,
|
||||||
pub compat_info: Option<DatabaseCompatInfo>,
|
pub compat_info: Option<DatabaseCompatInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<v2::Database> for Database {
|
impl From<v2::Database> for Database {
|
||||||
fn from(value: v2::Database) -> Self {
|
fn from(value: v2::Database) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -319,13 +306,22 @@ pub mod data {
|
|||||||
applications: value.applications.into(),
|
applications: value.applications.into(),
|
||||||
prev_database: value.prev_database,
|
prev_database: value.prev_database,
|
||||||
cache_dir: value.cache_dir,
|
cache_dir: value.cache_dir,
|
||||||
compat_info: None,
|
compat_info: Database::create_new_compat_info(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl Database {
|
||||||
|
fn create_new_compat_info() -> Option<DatabaseCompatInfo> {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
return None;
|
||||||
|
|
||||||
|
let has_umu_installed = Command::new(UMU_LAUNCHER_EXECUTABLE).spawn().is_ok();
|
||||||
|
Some(DatabaseCompatInfo {
|
||||||
|
umu_installed: has_umu_installed,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
impl Database {
|
|
||||||
pub fn new<T: Into<PathBuf>>(
|
pub fn new<T: Into<PathBuf>>(
|
||||||
games_base_dir: T,
|
games_base_dir: T,
|
||||||
prev_database: Option<PathBuf>,
|
prev_database: Option<PathBuf>,
|
||||||
@ -344,7 +340,7 @@ pub mod data {
|
|||||||
auth: None,
|
auth: None,
|
||||||
settings: Settings::default(),
|
settings: Settings::default(),
|
||||||
cache_dir,
|
cache_dir,
|
||||||
compat_info: None,
|
compat_info: Database::create_new_compat_info(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,52 +0,0 @@
|
|||||||
use std::fs;
|
|
||||||
|
|
||||||
use log::warn;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
database::{
|
|
||||||
db::borrow_db_mut_checked,
|
|
||||||
models::data::v1::{DownloadType, DownloadableMetadata},
|
|
||||||
},
|
|
||||||
games::{
|
|
||||||
downloads::drop_data::{v1::DropData, DROP_DATA_PATH},
|
|
||||||
library::set_partially_installed_db,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn scan_install_dirs() {
|
|
||||||
let mut db_lock = borrow_db_mut_checked();
|
|
||||||
for install_dir in db_lock.applications.install_dirs.clone() {
|
|
||||||
let Ok(files) = fs::read_dir(install_dir) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
for game in files.into_iter().flatten() {
|
|
||||||
let drop_data_file = game.path().join(DROP_DATA_PATH);
|
|
||||||
if !drop_data_file.exists() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let game_id = game.file_name().into_string().unwrap();
|
|
||||||
let Ok(drop_data) = DropData::read(&game.path()) else {
|
|
||||||
warn!(
|
|
||||||
".dropdata exists for {}, but couldn't read it. is it corrupted?",
|
|
||||||
game.file_name().into_string().unwrap()
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
if db_lock.applications.game_statuses.contains_key(&game_id) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let metadata = DownloadableMetadata::new(
|
|
||||||
drop_data.game_id,
|
|
||||||
Some(drop_data.game_version),
|
|
||||||
DownloadType::Game,
|
|
||||||
);
|
|
||||||
set_partially_installed_db(
|
|
||||||
&mut db_lock,
|
|
||||||
&metadata,
|
|
||||||
drop_data.base_path.to_str().unwrap().to_string(),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -124,16 +124,11 @@ impl DownloadManagerBuilder {
|
|||||||
self.current_download_agent = None;
|
self.current_download_agent = None;
|
||||||
|
|
||||||
let mut download_thread_lock = self.current_download_thread.lock().unwrap();
|
let mut download_thread_lock = self.current_download_thread.lock().unwrap();
|
||||||
|
*download_thread_lock = None;
|
||||||
if let Some(unfinished_thread) = download_thread_lock.take()
|
|
||||||
&& !unfinished_thread.is_finished()
|
|
||||||
{
|
|
||||||
unfinished_thread.join().unwrap();
|
|
||||||
}
|
|
||||||
drop(download_thread_lock);
|
drop(download_thread_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stop_and_wait_current_download(&self) -> bool {
|
fn stop_and_wait_current_download(&self) {
|
||||||
self.set_status(DownloadManagerStatus::Paused);
|
self.set_status(DownloadManagerStatus::Paused);
|
||||||
if let Some(current_flag) = &self.active_control_flag {
|
if let Some(current_flag) = &self.active_control_flag {
|
||||||
current_flag.set(DownloadThreadControlFlag::Stop);
|
current_flag.set(DownloadThreadControlFlag::Stop);
|
||||||
@ -141,10 +136,8 @@ impl DownloadManagerBuilder {
|
|||||||
|
|
||||||
let mut download_thread_lock = self.current_download_thread.lock().unwrap();
|
let mut download_thread_lock = self.current_download_thread.lock().unwrap();
|
||||||
if let Some(current_download_thread) = download_thread_lock.take() {
|
if let Some(current_download_thread) = download_thread_lock.take() {
|
||||||
return current_download_thread.join().is_ok();
|
current_download_thread.join().unwrap();
|
||||||
};
|
}
|
||||||
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn manage_queue(mut self) -> Result<(), ()> {
|
fn manage_queue(mut self) -> Result<(), ()> {
|
||||||
@ -220,6 +213,10 @@ impl DownloadManagerBuilder {
|
|||||||
&& self.download_queue.read().front().unwrap()
|
&& self.download_queue.read().front().unwrap()
|
||||||
== &self.current_download_agent.as_ref().unwrap().metadata()
|
== &self.current_download_agent.as_ref().unwrap().metadata()
|
||||||
{
|
{
|
||||||
|
debug!(
|
||||||
|
"Current download agent: {:?}",
|
||||||
|
self.current_download_agent.as_ref().unwrap().metadata()
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,17 +254,13 @@ impl DownloadManagerBuilder {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// If the download gets canceled
|
// If the download gets cancel
|
||||||
// immediately return, on_cancelled gets called for us earlier
|
|
||||||
if !download_result {
|
if !download_result {
|
||||||
|
download_agent.on_incomplete(&app_handle);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if download_agent.control_flag().get() == DownloadThreadControlFlag::Stop {
|
let validate_result = match download_agent.validate() {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let validate_result = match download_agent.validate(&app_handle) {
|
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(
|
error!(
|
||||||
@ -281,10 +274,6 @@ impl DownloadManagerBuilder {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if download_agent.control_flag().get() == DownloadThreadControlFlag::Stop {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if validate_result {
|
if validate_result {
|
||||||
download_agent.on_complete(&app_handle);
|
download_agent.on_complete(&app_handle);
|
||||||
sender
|
sender
|
||||||
@ -327,7 +316,6 @@ impl DownloadManagerBuilder {
|
|||||||
self.stop_and_wait_current_download();
|
self.stop_and_wait_current_download();
|
||||||
self.remove_and_cleanup_front_download(¤t_agent.metadata());
|
self.remove_and_cleanup_front_download(¤t_agent.metadata());
|
||||||
}
|
}
|
||||||
self.push_ui_queue_update();
|
|
||||||
self.set_status(DownloadManagerStatus::Error);
|
self.set_status(DownloadManagerStatus::Error);
|
||||||
}
|
}
|
||||||
fn manage_cancel_signal(&mut self, meta: &DownloadableMetadata) {
|
fn manage_cancel_signal(&mut self, meta: &DownloadableMetadata) {
|
||||||
|
|||||||
@ -14,14 +14,14 @@ use super::{
|
|||||||
|
|
||||||
pub trait Downloadable: Send + Sync {
|
pub trait Downloadable: Send + Sync {
|
||||||
fn download(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError>;
|
fn download(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError>;
|
||||||
fn validate(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError>;
|
|
||||||
|
|
||||||
fn progress(&self) -> Arc<ProgressObject>;
|
fn progress(&self) -> Arc<ProgressObject>;
|
||||||
fn control_flag(&self) -> DownloadThreadControl;
|
fn control_flag(&self) -> DownloadThreadControl;
|
||||||
|
fn validate(&self) -> Result<bool, ApplicationDownloadError>;
|
||||||
fn status(&self) -> DownloadStatus;
|
fn status(&self) -> DownloadStatus;
|
||||||
fn metadata(&self) -> DownloadableMetadata;
|
fn metadata(&self) -> DownloadableMetadata;
|
||||||
fn on_initialised(&self, app_handle: &AppHandle);
|
fn on_initialised(&self, app_handle: &AppHandle);
|
||||||
fn on_error(&self, app_handle: &AppHandle, error: &ApplicationDownloadError);
|
fn on_error(&self, app_handle: &AppHandle, error: &ApplicationDownloadError);
|
||||||
fn on_complete(&self, app_handle: &AppHandle);
|
fn on_complete(&self, app_handle: &AppHandle);
|
||||||
|
fn on_incomplete(&self, app_handle: &AppHandle);
|
||||||
fn on_cancelled(&self, app_handle: &AppHandle);
|
fn on_cancelled(&self, app_handle: &AppHandle);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,9 +38,9 @@ impl DownloadThreadControl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn get(&self) -> DownloadThreadControlFlag {
|
pub fn get(&self) -> DownloadThreadControlFlag {
|
||||||
self.inner.load(Ordering::Acquire).into()
|
self.inner.load(Ordering::Relaxed).into()
|
||||||
}
|
}
|
||||||
pub fn set(&self, flag: DownloadThreadControlFlag) {
|
pub fn set(&self, flag: DownloadThreadControlFlag) {
|
||||||
self.inner.store(flag.into(), Ordering::Release);
|
self.inner.store(flag.into(), Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
use std::{
|
use std::{
|
||||||
sync::{
|
sync::{
|
||||||
Arc, Mutex,
|
|
||||||
atomic::{AtomicUsize, Ordering},
|
atomic::{AtomicUsize, Ordering},
|
||||||
mpsc::Sender,
|
mpsc::Sender,
|
||||||
|
Arc, Mutex,
|
||||||
},
|
},
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
@ -23,10 +23,9 @@ pub struct ProgressObject {
|
|||||||
//last_update: Arc<RwLock<Instant>>,
|
//last_update: Arc<RwLock<Instant>>,
|
||||||
last_update_time: Arc<AtomicInstant>,
|
last_update_time: Arc<AtomicInstant>,
|
||||||
bytes_last_update: Arc<AtomicUsize>,
|
bytes_last_update: Arc<AtomicUsize>,
|
||||||
rolling: RollingProgressWindow<1>,
|
rolling: RollingProgressWindow<250>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ProgressHandle {
|
pub struct ProgressHandle {
|
||||||
progress: Arc<AtomicUsize>,
|
progress: Arc<AtomicUsize>,
|
||||||
progress_object: Arc<ProgressObject>,
|
progress_object: Arc<ProgressObject>,
|
||||||
@ -40,20 +39,20 @@ impl ProgressHandle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn set(&self, amount: usize) {
|
pub fn set(&self, amount: usize) {
|
||||||
self.progress.store(amount, Ordering::Release);
|
self.progress.store(amount, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
pub fn add(&self, amount: usize) {
|
pub fn add(&self, amount: usize) {
|
||||||
self.progress
|
self.progress
|
||||||
.fetch_add(amount, std::sync::atomic::Ordering::AcqRel);
|
.fetch_add(amount, std::sync::atomic::Ordering::Relaxed);
|
||||||
calculate_update(&self.progress_object);
|
calculate_update(&self.progress_object);
|
||||||
}
|
}
|
||||||
pub fn skip(&self, amount: usize) {
|
pub fn skip(&self, amount: usize) {
|
||||||
self.progress
|
self.progress
|
||||||
.fetch_add(amount, std::sync::atomic::Ordering::Acquire);
|
.fetch_add(amount, std::sync::atomic::Ordering::Relaxed);
|
||||||
// Offset the bytes at last offset by this amount
|
// Offset the bytes at last offset by this amount
|
||||||
self.progress_object
|
self.progress_object
|
||||||
.bytes_last_update
|
.bytes_last_update
|
||||||
.fetch_add(amount, Ordering::Acquire);
|
.fetch_add(amount, Ordering::Relaxed);
|
||||||
// Dont' fire update
|
// Dont' fire update
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -61,6 +60,7 @@ impl ProgressHandle {
|
|||||||
impl ProgressObject {
|
impl ProgressObject {
|
||||||
pub fn new(max: usize, length: usize, sender: Sender<DownloadManagerSignal>) -> Self {
|
pub fn new(max: usize, length: usize, sender: Sender<DownloadManagerSignal>) -> Self {
|
||||||
let arr = Mutex::new((0..length).map(|_| Arc::new(AtomicUsize::new(0))).collect());
|
let arr = Mutex::new((0..length).map(|_| Arc::new(AtomicUsize::new(0))).collect());
|
||||||
|
// TODO: consolidate this calculation with the set_max function below
|
||||||
Self {
|
Self {
|
||||||
max: Arc::new(Mutex::new(max)),
|
max: Arc::new(Mutex::new(max)),
|
||||||
progress_instances: Arc::new(arr),
|
progress_instances: Arc::new(arr),
|
||||||
@ -81,18 +81,19 @@ impl ProgressObject {
|
|||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|instance| instance.load(Ordering::Acquire))
|
.map(|instance| instance.load(Ordering::Relaxed))
|
||||||
.sum()
|
.sum()
|
||||||
}
|
}
|
||||||
pub fn reset(&self) {
|
pub fn reset(&self, size: usize) {
|
||||||
self.set_time_now();
|
self.set_time_now();
|
||||||
|
self.set_size(size);
|
||||||
self.bytes_last_update.store(0, Ordering::Release);
|
self.bytes_last_update.store(0, Ordering::Release);
|
||||||
self.rolling.reset();
|
self.rolling.reset();
|
||||||
self.progress_instances
|
self.progress_instances
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.for_each(|x| x.store(0, Ordering::SeqCst));
|
.for_each(|x| x.store(0, Ordering::Release));
|
||||||
}
|
}
|
||||||
pub fn get_max(&self) -> usize {
|
pub fn get_max(&self) -> usize {
|
||||||
*self.max.lock().unwrap()
|
*self.max.lock().unwrap()
|
||||||
@ -126,9 +127,9 @@ pub fn calculate_update(progress: &ProgressObject) {
|
|||||||
let max = progress.get_max();
|
let max = progress.get_max();
|
||||||
let bytes_at_last_update = progress
|
let bytes_at_last_update = progress
|
||||||
.bytes_last_update
|
.bytes_last_update
|
||||||
.swap(current_bytes_downloaded, Ordering::Acquire);
|
.swap(current_bytes_downloaded, Ordering::Relaxed);
|
||||||
|
|
||||||
let bytes_since_last_update = current_bytes_downloaded.saturating_sub(bytes_at_last_update);
|
let bytes_since_last_update = current_bytes_downloaded - bytes_at_last_update;
|
||||||
|
|
||||||
let kilobytes_per_second = bytes_since_last_update / (time_since_last_update as usize).max(1);
|
let kilobytes_per_second = bytes_since_last_update / (time_since_last_update as usize).max(1);
|
||||||
|
|
||||||
|
|||||||
@ -26,7 +26,7 @@ impl<const S: usize> RollingProgressWindow<S> {
|
|||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter(|(i, _)| i < ¤t)
|
.filter(|(i, _)| i < ¤t)
|
||||||
.map(|(_, x)| x.load(Ordering::Acquire))
|
.map(|(_, x)| x.load(Ordering::Relaxed))
|
||||||
.sum::<usize>()
|
.sum::<usize>()
|
||||||
/ S
|
/ S
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,49 +1,30 @@
|
|||||||
use std::{
|
use std::{
|
||||||
fmt::{Display, Formatter},
|
fmt::{Display, Formatter},
|
||||||
io, sync::Arc,
|
io,
|
||||||
};
|
};
|
||||||
|
|
||||||
use serde_with::SerializeDisplay;
|
use serde_with::SerializeDisplay;
|
||||||
use humansize::{format_size, BINARY};
|
|
||||||
|
|
||||||
use super::remote_access_error::RemoteAccessError;
|
use super::remote_access_error::RemoteAccessError;
|
||||||
|
|
||||||
// TODO: Rename / separate from downloads
|
// TODO: Rename / separate from downloads
|
||||||
#[derive(Debug, SerializeDisplay)]
|
#[derive(Debug, SerializeDisplay)]
|
||||||
pub enum ApplicationDownloadError {
|
pub enum ApplicationDownloadError {
|
||||||
NotInitialized,
|
|
||||||
Communication(RemoteAccessError),
|
Communication(RemoteAccessError),
|
||||||
DiskFull(u64, u64),
|
|
||||||
#[allow(dead_code)]
|
|
||||||
Checksum,
|
Checksum,
|
||||||
Lock,
|
Lock,
|
||||||
IoError(Arc<io::Error>),
|
IoError(io::ErrorKind),
|
||||||
DownloadError,
|
DownloadError,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ApplicationDownloadError {
|
impl Display for ApplicationDownloadError {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
ApplicationDownloadError::NotInitialized => write!(f, "Download not initalized, did something go wrong?"),
|
|
||||||
ApplicationDownloadError::DiskFull(required, available) => write!(
|
|
||||||
f,
|
|
||||||
"Game requires {}, {} remaining left on disk.",
|
|
||||||
format_size(*required, BINARY),
|
|
||||||
format_size(*available, BINARY),
|
|
||||||
),
|
|
||||||
ApplicationDownloadError::Communication(error) => write!(f, "{error}"),
|
ApplicationDownloadError::Communication(error) => write!(f, "{error}"),
|
||||||
ApplicationDownloadError::Lock => write!(
|
ApplicationDownloadError::Lock => write!(f, "failed to acquire lock. Something has gone very wrong internally. Please restart the application"),
|
||||||
f,
|
ApplicationDownloadError::Checksum => write!(f, "checksum failed to validate for download"),
|
||||||
"failed to acquire lock. Something has gone very wrong internally. Please restart the application"
|
|
||||||
),
|
|
||||||
ApplicationDownloadError::Checksum => {
|
|
||||||
write!(f, "checksum failed to validate for download")
|
|
||||||
}
|
|
||||||
ApplicationDownloadError::IoError(error) => write!(f, "io error: {error}"),
|
ApplicationDownloadError::IoError(error) => write!(f, "io error: {error}"),
|
||||||
ApplicationDownloadError::DownloadError => write!(
|
ApplicationDownloadError::DownloadError => write!(f, "download failed. See Download Manager status for specific error"),
|
||||||
f,
|
|
||||||
"Download failed. See Download Manager status for specific error"
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ use serde_with::SerializeDisplay;
|
|||||||
pub enum ProcessError {
|
pub enum ProcessError {
|
||||||
NotInstalled,
|
NotInstalled,
|
||||||
AlreadyRunning,
|
AlreadyRunning,
|
||||||
|
NotDownloaded,
|
||||||
InvalidID,
|
InvalidID,
|
||||||
InvalidVersion,
|
InvalidVersion,
|
||||||
IOError(Error),
|
IOError(Error),
|
||||||
@ -19,6 +20,7 @@ impl Display for ProcessError {
|
|||||||
let s = match self {
|
let s = match self {
|
||||||
ProcessError::NotInstalled => "Game not installed",
|
ProcessError::NotInstalled => "Game not installed",
|
||||||
ProcessError::AlreadyRunning => "Game already running",
|
ProcessError::AlreadyRunning => "Game already running",
|
||||||
|
ProcessError::NotDownloaded => "Game not downloaded",
|
||||||
ProcessError::InvalidID => "Invalid game ID",
|
ProcessError::InvalidID => "Invalid game ID",
|
||||||
ProcessError::InvalidVersion => "Invalid game version",
|
ProcessError::InvalidVersion => "Invalid game version",
|
||||||
ProcessError::IOError(error) => &error.to_string(),
|
ProcessError::IOError(error) => &error.to_string(),
|
||||||
|
|||||||
@ -1,96 +1,109 @@
|
|||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
DB,
|
||||||
|
database::db::DatabaseImpls,
|
||||||
error::remote_access_error::RemoteAccessError,
|
error::remote_access_error::RemoteAccessError,
|
||||||
remote::{
|
remote::{auth::generate_authorization_header, requests::make_request, utils::DROP_CLIENT_SYNC},
|
||||||
auth::generate_authorization_header,
|
|
||||||
requests::{generate_url, make_authenticated_get},
|
|
||||||
utils::DROP_CLIENT_ASYNC,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::collection::{Collection, Collections};
|
use super::collection::{Collection, Collections};
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn fetch_collections() -> Result<Collections, RemoteAccessError> {
|
pub fn fetch_collections() -> Result<Collections, RemoteAccessError> {
|
||||||
let response =
|
let client = DROP_CLIENT_SYNC.clone();
|
||||||
make_authenticated_get(generate_url(&["/api/v1/client/collection"], &[])?).await?;
|
let response = make_request(&client, &["/api/v1/client/collection"], &[], |r| {
|
||||||
|
r.header("Authorization", generate_authorization_header())
|
||||||
|
})?
|
||||||
|
.send()?;
|
||||||
|
|
||||||
Ok(response.json().await?)
|
Ok(response.json()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn fetch_collection(collection_id: String) -> Result<Collection, RemoteAccessError> {
|
pub fn fetch_collection(collection_id: String) -> Result<Collection, RemoteAccessError> {
|
||||||
let response = make_authenticated_get(generate_url(
|
let client = DROP_CLIENT_SYNC.clone();
|
||||||
|
let response = make_request(
|
||||||
|
&client,
|
||||||
&["/api/v1/client/collection/", &collection_id],
|
&["/api/v1/client/collection/", &collection_id],
|
||||||
&[],
|
&[],
|
||||||
)?)
|
|r| r.header("Authorization", generate_authorization_header()),
|
||||||
.await?;
|
)?
|
||||||
|
.send()?;
|
||||||
|
|
||||||
Ok(response.json().await?)
|
Ok(response.json()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn create_collection(name: String) -> Result<Collection, RemoteAccessError> {
|
pub fn create_collection(name: String) -> Result<Collection, RemoteAccessError> {
|
||||||
let client = DROP_CLIENT_ASYNC.clone();
|
let client = DROP_CLIENT_SYNC.clone();
|
||||||
let url = generate_url(&["/api/v1/client/collection"], &[])?;
|
let base_url = DB.fetch_base_url();
|
||||||
|
|
||||||
|
let base_url = Url::parse(&format!("{base_url}api/v1/client/collection/"))?;
|
||||||
|
|
||||||
let response = client
|
let response = client
|
||||||
.post(url)
|
.post(base_url)
|
||||||
.header("Authorization", generate_authorization_header())
|
.header("Authorization", generate_authorization_header())
|
||||||
.json(&json!({"name": name}))
|
.json(&json!({"name": name}))
|
||||||
.send()
|
.send()?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(response.json().await?)
|
Ok(response.json()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn add_game_to_collection(
|
pub fn add_game_to_collection(
|
||||||
collection_id: String,
|
collection_id: String,
|
||||||
game_id: String,
|
game_id: String,
|
||||||
) -> Result<(), RemoteAccessError> {
|
) -> Result<(), RemoteAccessError> {
|
||||||
let client = DROP_CLIENT_ASYNC.clone();
|
let client = DROP_CLIENT_SYNC.clone();
|
||||||
|
let url = Url::parse(&format!(
|
||||||
let url = generate_url(&["/api/v1/client/collection", &collection_id, "entry"], &[])?;
|
"{}api/v1/client/collection/{}/entry/",
|
||||||
|
DB.fetch_base_url(),
|
||||||
|
collection_id
|
||||||
|
))?;
|
||||||
|
|
||||||
client
|
client
|
||||||
.post(url)
|
.post(url)
|
||||||
.header("Authorization", generate_authorization_header())
|
.header("Authorization", generate_authorization_header())
|
||||||
.json(&json!({"id": game_id}))
|
.json(&json!({"id": game_id}))
|
||||||
.send()
|
.send()?;
|
||||||
.await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn delete_collection(collection_id: String) -> Result<bool, RemoteAccessError> {
|
pub fn delete_collection(collection_id: String) -> Result<bool, RemoteAccessError> {
|
||||||
let client = DROP_CLIENT_ASYNC.clone();
|
let client = DROP_CLIENT_SYNC.clone();
|
||||||
|
let base_url = Url::parse(&format!(
|
||||||
let url = generate_url(&["/api/v1/client/collection", &collection_id], &[])?;
|
"{}api/v1/client/collection/{}",
|
||||||
|
DB.fetch_base_url(),
|
||||||
|
collection_id
|
||||||
|
))?;
|
||||||
|
|
||||||
let response = client
|
let response = client
|
||||||
.delete(url)
|
.delete(base_url)
|
||||||
.header("Authorization", generate_authorization_header())
|
.header("Authorization", generate_authorization_header())
|
||||||
.send()
|
.send()?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(response.json().await?)
|
Ok(response.json()?)
|
||||||
}
|
}
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn delete_game_in_collection(
|
pub fn delete_game_in_collection(
|
||||||
collection_id: String,
|
collection_id: String,
|
||||||
game_id: String,
|
game_id: String,
|
||||||
) -> Result<(), RemoteAccessError> {
|
) -> Result<(), RemoteAccessError> {
|
||||||
let client = DROP_CLIENT_ASYNC.clone();
|
let client = DROP_CLIENT_SYNC.clone();
|
||||||
|
let base_url = Url::parse(&format!(
|
||||||
let url = generate_url(&["/api/v1/client/collection", &collection_id, "entry"], &[])?;
|
"{}api/v1/client/collection/{}/entry",
|
||||||
|
DB.fetch_base_url(),
|
||||||
|
collection_id
|
||||||
|
))?;
|
||||||
|
|
||||||
client
|
client
|
||||||
.delete(url)
|
.delete(base_url)
|
||||||
.header("Authorization", generate_authorization_header())
|
.header("Authorization", generate_authorization_header())
|
||||||
.json(&json!({"id": game_id}))
|
.json(&json!({"id": game_id}))
|
||||||
.send().await?;
|
.send()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,43 +3,39 @@ use std::sync::Mutex;
|
|||||||
use tauri::AppHandle;
|
use tauri::AppHandle;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AppState,
|
database::models::data::GameVersion,
|
||||||
database::{
|
|
||||||
db::borrow_db_checked,
|
|
||||||
models::data::GameVersion,
|
|
||||||
},
|
|
||||||
error::{library_error::LibraryError, remote_access_error::RemoteAccessError},
|
error::{library_error::LibraryError, remote_access_error::RemoteAccessError},
|
||||||
games::library::{
|
games::library::{
|
||||||
fetch_game_logic_offline, fetch_library_logic_offline, get_current_meta,
|
fetch_game_logic_offline, fetch_library_logic_offline, get_current_meta,
|
||||||
uninstall_game_logic,
|
uninstall_game_logic,
|
||||||
},
|
},
|
||||||
offline,
|
offline, AppState,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
library::{
|
library::{
|
||||||
FetchGameStruct, Game, fetch_game_logic, fetch_game_version_options_logic,
|
fetch_game_logic, fetch_game_verion_options_logic, fetch_library_logic, FetchGameStruct,
|
||||||
fetch_library_logic,
|
Game,
|
||||||
},
|
},
|
||||||
state::{GameStatusManager, GameStatusWithTransient},
|
state::{GameStatusManager, GameStatusWithTransient},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn fetch_library(
|
pub fn fetch_library(
|
||||||
state: tauri::State<'_, Mutex<AppState<'_>>>,
|
state: tauri::State<'_, Mutex<AppState>>,
|
||||||
) -> Result<Vec<Game>, RemoteAccessError> {
|
) -> Result<Vec<Game>, RemoteAccessError> {
|
||||||
offline!(
|
offline!(
|
||||||
state,
|
state,
|
||||||
fetch_library_logic,
|
fetch_library_logic,
|
||||||
fetch_library_logic_offline,
|
fetch_library_logic_offline,
|
||||||
state
|
state
|
||||||
).await
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn fetch_game(
|
pub fn fetch_game(
|
||||||
game_id: String,
|
game_id: String,
|
||||||
state: tauri::State<'_, Mutex<AppState<'_>>>,
|
state: tauri::State<'_, Mutex<AppState>>,
|
||||||
) -> Result<FetchGameStruct, RemoteAccessError> {
|
) -> Result<FetchGameStruct, RemoteAccessError> {
|
||||||
offline!(
|
offline!(
|
||||||
state,
|
state,
|
||||||
@ -47,13 +43,12 @@ pub async fn fetch_game(
|
|||||||
fetch_game_logic_offline,
|
fetch_game_logic_offline,
|
||||||
game_id,
|
game_id,
|
||||||
state
|
state
|
||||||
).await
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn fetch_game_status(id: String) -> GameStatusWithTransient {
|
pub fn fetch_game_status(id: String) -> GameStatusWithTransient {
|
||||||
let db_handle = borrow_db_checked();
|
GameStatusManager::fetch_state(&id)
|
||||||
GameStatusManager::fetch_state(&id, &db_handle)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@ -68,9 +63,9 @@ pub fn uninstall_game(game_id: String, app_handle: AppHandle) -> Result<(), Libr
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn fetch_game_version_options(
|
pub fn fetch_game_verion_options(
|
||||||
game_id: String,
|
game_id: String,
|
||||||
state: tauri::State<'_, Mutex<AppState<'_>>>,
|
state: tauri::State<'_, Mutex<AppState>>,
|
||||||
) -> Result<Vec<GameVersion>, RemoteAccessError> {
|
) -> Result<Vec<GameVersion>, RemoteAccessError> {
|
||||||
fetch_game_version_options_logic(game_id, state).await
|
fetch_game_verion_options_logic(game_id, state)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,48 +3,43 @@ use std::{
|
|||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AppState,
|
database::{db::borrow_db_checked, models::data::GameDownloadStatus},
|
||||||
database::{
|
download_manager::{
|
||||||
db::borrow_db_checked,
|
download_manager_frontend::DownloadManagerSignal, downloadable::Downloadable,
|
||||||
models::data::GameDownloadStatus,
|
|
||||||
},
|
},
|
||||||
download_manager::downloadable::Downloadable,
|
error::download_manager_error::DownloadManagerError,
|
||||||
error::application_download_error::ApplicationDownloadError,
|
AppState,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::download_agent::GameDownloadAgent;
|
use super::download_agent::GameDownloadAgent;
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn download_game(
|
pub fn download_game(
|
||||||
game_id: String,
|
game_id: String,
|
||||||
game_version: String,
|
game_version: String,
|
||||||
install_dir: usize,
|
install_dir: usize,
|
||||||
state: tauri::State<'_, Mutex<AppState<'_>>>,
|
state: tauri::State<'_, Mutex<AppState>>,
|
||||||
) -> Result<(), ApplicationDownloadError> {
|
) -> Result<(), DownloadManagerError<DownloadManagerSignal>> {
|
||||||
let sender = { state.lock().unwrap().download_manager.get_sender().clone() };
|
let sender = state.lock().unwrap().download_manager.get_sender();
|
||||||
|
let game_download_agent = Arc::new(Box::new(GameDownloadAgent::new_from_index(
|
||||||
let game_download_agent =
|
game_id,
|
||||||
GameDownloadAgent::new_from_index(game_id.clone(), game_version.clone(), install_dir, sender).await?;
|
game_version,
|
||||||
|
install_dir,
|
||||||
let game_download_agent =
|
sender,
|
||||||
Arc::new(Box::new(game_download_agent) as Box<dyn Downloadable + Send + Sync>);
|
)) as Box<dyn Downloadable + Send + Sync>);
|
||||||
state
|
Ok(state
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.download_manager
|
.download_manager
|
||||||
.queue_download(game_download_agent.clone())
|
.queue_download(game_download_agent)?)
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn resume_download(
|
pub fn resume_download(
|
||||||
game_id: String,
|
game_id: String,
|
||||||
state: tauri::State<'_, Mutex<AppState<'_>>>,
|
state: tauri::State<'_, Mutex<AppState>>,
|
||||||
) -> Result<(), ApplicationDownloadError> {
|
) -> Result<(), DownloadManagerError<DownloadManagerSignal>> {
|
||||||
let s = borrow_db_checked()
|
let s = borrow_db_checked()
|
||||||
.applications
|
.applications
|
||||||
.game_statuses
|
.game_statuses
|
||||||
@ -61,25 +56,17 @@ pub async fn resume_download(
|
|||||||
install_dir,
|
install_dir,
|
||||||
} => (version_name, install_dir),
|
} => (version_name, install_dir),
|
||||||
};
|
};
|
||||||
|
|
||||||
let sender = state.lock().unwrap().download_manager.get_sender();
|
let sender = state.lock().unwrap().download_manager.get_sender();
|
||||||
let parent_dir: PathBuf = install_dir.into();
|
let parent_dir: PathBuf = install_dir.into();
|
||||||
|
let game_download_agent = Arc::new(Box::new(GameDownloadAgent::new(
|
||||||
let game_download_agent = Arc::new(Box::new(
|
|
||||||
GameDownloadAgent::new(
|
|
||||||
game_id,
|
game_id,
|
||||||
version_name.clone(),
|
version_name.clone(),
|
||||||
parent_dir.parent().unwrap().to_path_buf(),
|
parent_dir.parent().unwrap().to_path_buf(),
|
||||||
sender,
|
sender,
|
||||||
)
|
)) as Box<dyn Downloadable + Send + Sync>);
|
||||||
.await?,
|
Ok(state
|
||||||
) as Box<dyn Downloadable + Send + Sync>);
|
|
||||||
|
|
||||||
state
|
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.download_manager
|
.download_manager
|
||||||
.queue_download(game_download_agent)
|
.queue_download(game_download_agent)?)
|
||||||
.unwrap();
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,16 +11,12 @@ use crate::download_manager::util::download_thread_control_flag::{
|
|||||||
use crate::download_manager::util::progress_object::{ProgressHandle, ProgressObject};
|
use crate::download_manager::util::progress_object::{ProgressHandle, ProgressObject};
|
||||||
use crate::error::application_download_error::ApplicationDownloadError;
|
use crate::error::application_download_error::ApplicationDownloadError;
|
||||||
use crate::error::remote_access_error::RemoteAccessError;
|
use crate::error::remote_access_error::RemoteAccessError;
|
||||||
use crate::games::downloads::manifest::{
|
use crate::games::downloads::manifest::{DropDownloadContext, DropManifest};
|
||||||
DownloadBucket, DownloadContext, DownloadDrop, DropManifest, DropValidateContext, ManifestBody,
|
use crate::games::downloads::validate::game_validate_logic;
|
||||||
};
|
use crate::games::library::{on_game_complete, on_game_incomplete, push_game_update};
|
||||||
use crate::games::downloads::validate::validate_game_chunk;
|
use crate::remote::requests::make_request;
|
||||||
use crate::games::library::{on_game_complete, push_game_update, set_partially_installed};
|
use crate::remote::utils::DROP_CLIENT_SYNC;
|
||||||
use crate::games::state::GameStatusManager;
|
use log::{debug, error, info};
|
||||||
use crate::process::utils::get_disk_available;
|
|
||||||
use crate::remote::requests::generate_url;
|
|
||||||
use crate::remote::utils::{DROP_CLIENT_ASYNC, DROP_CLIENT_SYNC};
|
|
||||||
use log::{debug, error, info, warn};
|
|
||||||
use rayon::ThreadPoolBuilder;
|
use rayon::ThreadPoolBuilder;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs::{OpenOptions, create_dir_all};
|
use std::fs::{OpenOptions, create_dir_all};
|
||||||
@ -33,47 +29,41 @@ use tauri::{AppHandle, Emitter};
|
|||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
use rustix::fs::{FallocateFlags, fallocate};
|
use rustix::fs::{FallocateFlags, fallocate};
|
||||||
|
|
||||||
use super::download_logic::download_game_bucket;
|
use super::download_logic::download_game_chunk;
|
||||||
use super::drop_data::DropData;
|
use super::drop_data::DropData;
|
||||||
|
|
||||||
static RETRY_COUNT: usize = 3;
|
|
||||||
|
|
||||||
const TARGET_BUCKET_SIZE: usize = 63 * 1000 * 1000;
|
|
||||||
|
|
||||||
pub struct GameDownloadAgent {
|
pub struct GameDownloadAgent {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub version: String,
|
pub version: String,
|
||||||
pub control_flag: DownloadThreadControl,
|
pub control_flag: DownloadThreadControl,
|
||||||
buckets: Mutex<Vec<DownloadBucket>>,
|
contexts: Mutex<Vec<DropDownloadContext>>,
|
||||||
context_map: Mutex<HashMap<String, bool>>,
|
context_map: Mutex<HashMap<String, bool>>,
|
||||||
pub manifest: Mutex<Option<DropManifest>>,
|
pub manifest: Mutex<Option<DropManifest>>,
|
||||||
pub progress: Arc<ProgressObject>,
|
pub progress: Arc<ProgressObject>,
|
||||||
sender: Sender<DownloadManagerSignal>,
|
sender: Sender<DownloadManagerSignal>,
|
||||||
pub dropdata: DropData,
|
pub stored_manifest: DropData,
|
||||||
status: Mutex<DownloadStatus>,
|
status: Mutex<DownloadStatus>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GameDownloadAgent {
|
impl GameDownloadAgent {
|
||||||
pub async fn new_from_index(
|
pub fn new_from_index(
|
||||||
id: String,
|
id: String,
|
||||||
version: String,
|
version: String,
|
||||||
target_download_dir: usize,
|
target_download_dir: usize,
|
||||||
sender: Sender<DownloadManagerSignal>,
|
sender: Sender<DownloadManagerSignal>,
|
||||||
) -> Result<Self, ApplicationDownloadError> {
|
) -> Self {
|
||||||
let base_dir = {
|
|
||||||
let db_lock = borrow_db_checked();
|
let db_lock = borrow_db_checked();
|
||||||
|
let base_dir = db_lock.applications.install_dirs[target_download_dir].clone();
|
||||||
|
drop(db_lock);
|
||||||
|
|
||||||
db_lock.applications.install_dirs[target_download_dir].clone()
|
Self::new(id, version, base_dir, sender)
|
||||||
};
|
|
||||||
|
|
||||||
Self::new(id, version, base_dir, sender).await
|
|
||||||
}
|
}
|
||||||
pub async fn new(
|
pub fn new(
|
||||||
id: String,
|
id: String,
|
||||||
version: String,
|
version: String,
|
||||||
base_dir: PathBuf,
|
base_dir: PathBuf,
|
||||||
sender: Sender<DownloadManagerSignal>,
|
sender: Sender<DownloadManagerSignal>,
|
||||||
) -> Result<Self, ApplicationDownloadError> {
|
) -> Self {
|
||||||
// Don't run by default
|
// Don't run by default
|
||||||
let control_flag = DownloadThreadControl::new(DownloadThreadControlFlag::Stop);
|
let control_flag = DownloadThreadControl::new(DownloadThreadControlFlag::Stop);
|
||||||
|
|
||||||
@ -83,61 +73,25 @@ impl GameDownloadAgent {
|
|||||||
let stored_manifest =
|
let stored_manifest =
|
||||||
DropData::generate(id.clone(), version.clone(), data_base_dir_path.clone());
|
DropData::generate(id.clone(), version.clone(), data_base_dir_path.clone());
|
||||||
|
|
||||||
let result = Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
version,
|
version,
|
||||||
control_flag,
|
control_flag,
|
||||||
manifest: Mutex::new(None),
|
manifest: Mutex::new(None),
|
||||||
buckets: Mutex::new(Vec::new()),
|
contexts: Mutex::new(Vec::new()),
|
||||||
context_map: Mutex::new(HashMap::new()),
|
context_map: Mutex::new(HashMap::new()),
|
||||||
progress: Arc::new(ProgressObject::new(0, 0, sender.clone())),
|
progress: Arc::new(ProgressObject::new(0, 0, sender.clone())),
|
||||||
sender,
|
sender,
|
||||||
dropdata: stored_manifest,
|
stored_manifest,
|
||||||
status: Mutex::new(DownloadStatus::Queued),
|
status: Mutex::new(DownloadStatus::Queued),
|
||||||
};
|
|
||||||
|
|
||||||
result.ensure_manifest_exists().await?;
|
|
||||||
|
|
||||||
let required_space = result
|
|
||||||
.manifest
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.as_ref()
|
|
||||||
.unwrap()
|
|
||||||
.values()
|
|
||||||
.map(|e| e.lengths.iter().sum::<usize>())
|
|
||||||
.sum::<usize>() as u64;
|
|
||||||
|
|
||||||
let available_space = get_disk_available(data_base_dir_path)? as u64;
|
|
||||||
|
|
||||||
if required_space > available_space {
|
|
||||||
return Err(ApplicationDownloadError::DiskFull(
|
|
||||||
required_space,
|
|
||||||
available_space,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blocking
|
// Blocking
|
||||||
pub fn setup_download(&self, app_handle: &AppHandle) -> Result<(), ApplicationDownloadError> {
|
pub fn setup_download(&self) -> Result<(), ApplicationDownloadError> {
|
||||||
let mut db_lock = borrow_db_mut_checked();
|
self.ensure_manifest_exists()?;
|
||||||
let status = ApplicationTransientStatus::Downloading {
|
|
||||||
version_name: self.version.clone(),
|
|
||||||
};
|
|
||||||
db_lock
|
|
||||||
.applications
|
|
||||||
.transient_statuses
|
|
||||||
.insert(self.metadata(), status.clone());
|
|
||||||
// Don't use GameStatusManager because this game isn't installed
|
|
||||||
push_game_update(app_handle, &self.metadata().id, None, (None, Some(status)));
|
|
||||||
|
|
||||||
if !self.check_manifest_exists() {
|
self.ensure_contexts()?;
|
||||||
return Err(ApplicationDownloadError::NotInitialized);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.ensure_buckets()?;
|
|
||||||
|
|
||||||
self.control_flag.set(DownloadThreadControlFlag::Go);
|
self.control_flag.set(DownloadThreadControlFlag::Go);
|
||||||
|
|
||||||
@ -146,12 +100,23 @@ impl GameDownloadAgent {
|
|||||||
|
|
||||||
// Blocking
|
// Blocking
|
||||||
pub fn download(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError> {
|
pub fn download(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError> {
|
||||||
self.setup_download(app_handle)?;
|
self.setup_download()?;
|
||||||
|
self.set_progress_object_params();
|
||||||
let timer = Instant::now();
|
let timer = Instant::now();
|
||||||
|
push_game_update(
|
||||||
info!("beginning download for {}...", self.metadata().id);
|
app_handle,
|
||||||
|
&self.metadata().id,
|
||||||
let res = self.run().map_err(ApplicationDownloadError::Communication);
|
None,
|
||||||
|
(
|
||||||
|
None,
|
||||||
|
Some(ApplicationTransientStatus::Downloading {
|
||||||
|
version_name: self.version.clone(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
let res = self
|
||||||
|
.run()
|
||||||
|
.map_err(|()| ApplicationDownloadError::DownloadError);
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
"{} took {}ms to download",
|
"{} took {}ms to download",
|
||||||
@ -161,43 +126,37 @@ impl GameDownloadAgent {
|
|||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_manifest_exists(&self) -> bool {
|
pub fn ensure_manifest_exists(&self) -> Result<(), ApplicationDownloadError> {
|
||||||
self.manifest.lock().unwrap().is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn ensure_manifest_exists(&self) -> Result<(), ApplicationDownloadError> {
|
|
||||||
if self.manifest.lock().unwrap().is_some() {
|
if self.manifest.lock().unwrap().is_some() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.download_manifest().await
|
self.download_manifest()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn download_manifest(&self) -> Result<(), ApplicationDownloadError> {
|
fn download_manifest(&self) -> Result<(), ApplicationDownloadError> {
|
||||||
let client = DROP_CLIENT_ASYNC.clone();
|
let header = generate_authorization_header();
|
||||||
let url = generate_url(
|
let client = DROP_CLIENT_SYNC.clone();
|
||||||
|
let response = make_request(
|
||||||
|
&client,
|
||||||
&["/api/v1/client/game/manifest"],
|
&["/api/v1/client/game/manifest"],
|
||||||
&[("id", &self.id), ("version", &self.version)],
|
&[("id", &self.id), ("version", &self.version)],
|
||||||
|
|f| f.header("Authorization", header),
|
||||||
)
|
)
|
||||||
.map_err(ApplicationDownloadError::Communication)?;
|
.map_err(ApplicationDownloadError::Communication)?
|
||||||
|
|
||||||
let response = client
|
|
||||||
.get(url)
|
|
||||||
.header("Authorization", generate_authorization_header())
|
|
||||||
.send()
|
.send()
|
||||||
.await
|
|
||||||
.map_err(|e| ApplicationDownloadError::Communication(e.into()))?;
|
.map_err(|e| ApplicationDownloadError::Communication(e.into()))?;
|
||||||
|
|
||||||
if response.status() != 200 {
|
if response.status() != 200 {
|
||||||
return Err(ApplicationDownloadError::Communication(
|
return Err(ApplicationDownloadError::Communication(
|
||||||
RemoteAccessError::ManifestDownloadFailed(
|
RemoteAccessError::ManifestDownloadFailed(
|
||||||
response.status(),
|
response.status(),
|
||||||
response.text().await.unwrap(),
|
response.text().unwrap(),
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let manifest_download: DropManifest = response.json().await.unwrap();
|
let manifest_download: DropManifest = response.json().unwrap();
|
||||||
|
|
||||||
if let Ok(mut manifest) = self.manifest.lock() {
|
if let Ok(mut manifest) = self.manifest.lock() {
|
||||||
*manifest = Some(manifest_download);
|
*manifest = Some(manifest_download);
|
||||||
@ -207,136 +166,91 @@ impl GameDownloadAgent {
|
|||||||
Err(ApplicationDownloadError::Lock)
|
Err(ApplicationDownloadError::Lock)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets it up for both download and validate
|
fn set_progress_object_params(&self) {
|
||||||
fn setup_progress(&self) {
|
// Avoid re-setting it
|
||||||
let buckets = self.buckets.lock().unwrap();
|
if self.progress.get_max() != 0 {
|
||||||
|
return;
|
||||||
let chunk_count = buckets.iter().map(|e| e.drops.len()).sum();
|
|
||||||
|
|
||||||
let total_length = buckets
|
|
||||||
.iter()
|
|
||||||
.map(|bucket| bucket.drops.iter().map(|e| e.length).sum::<usize>())
|
|
||||||
.sum();
|
|
||||||
|
|
||||||
self.progress.set_max(total_length);
|
|
||||||
self.progress.set_size(chunk_count);
|
|
||||||
self.progress.reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ensure_buckets(&self) -> Result<(), ApplicationDownloadError> {
|
let contexts = self.contexts.lock().unwrap();
|
||||||
if self.buckets.lock().unwrap().is_empty() {
|
|
||||||
self.generate_buckets()?;
|
let length = contexts.len();
|
||||||
|
|
||||||
|
let chunk_count = contexts.iter().map(|chunk| chunk.length).sum();
|
||||||
|
|
||||||
|
self.progress.set_max(chunk_count);
|
||||||
|
self.progress.set_size(length);
|
||||||
|
self.progress.set_time_now();
|
||||||
}
|
}
|
||||||
|
|
||||||
*self.context_map.lock().unwrap() = self.dropdata.get_contexts();
|
pub fn ensure_contexts(&self) -> Result<(), ApplicationDownloadError> {
|
||||||
|
if self.contexts.lock().unwrap().is_empty() {
|
||||||
|
self.generate_contexts()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
*self.context_map.lock().unwrap() = self.stored_manifest.get_contexts();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_buckets(&self) -> Result<(), ApplicationDownloadError> {
|
pub fn generate_contexts(&self) -> Result<(), ApplicationDownloadError> {
|
||||||
let manifest = self.manifest.lock().unwrap().clone().unwrap();
|
let manifest = self.manifest.lock().unwrap().clone().unwrap();
|
||||||
let game_id = self.id.clone();
|
let game_id = self.id.clone();
|
||||||
|
|
||||||
let base_path = Path::new(&self.dropdata.base_path);
|
let mut contexts = Vec::new();
|
||||||
|
let base_path = Path::new(&self.stored_manifest.base_path);
|
||||||
create_dir_all(base_path).unwrap();
|
create_dir_all(base_path).unwrap();
|
||||||
|
|
||||||
let mut buckets = Vec::new();
|
|
||||||
|
|
||||||
let mut current_bucket = DownloadBucket {
|
|
||||||
game_id: game_id.clone(),
|
|
||||||
version: self.version.clone(),
|
|
||||||
drops: Vec::new(),
|
|
||||||
};
|
|
||||||
let mut current_bucket_size = 0;
|
|
||||||
|
|
||||||
for (raw_path, chunk) in manifest {
|
for (raw_path, chunk) in manifest {
|
||||||
let path = base_path.join(Path::new(&raw_path));
|
let path = base_path.join(Path::new(&raw_path));
|
||||||
|
|
||||||
let container = path.parent().unwrap();
|
let container = path.parent().unwrap();
|
||||||
create_dir_all(container).unwrap();
|
create_dir_all(container).unwrap();
|
||||||
|
|
||||||
let already_exists = path.exists();
|
|
||||||
let file = OpenOptions::new()
|
let file = OpenOptions::new()
|
||||||
.read(true)
|
.read(true)
|
||||||
.write(true)
|
.write(true)
|
||||||
|
.truncate(true)
|
||||||
.create(true)
|
.create(true)
|
||||||
.truncate(false)
|
|
||||||
.open(path.clone())
|
.open(path.clone())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut file_running_offset = 0;
|
let mut running_offset = 0;
|
||||||
|
|
||||||
for (index, length) in chunk.lengths.iter().enumerate() {
|
for (index, length) in chunk.lengths.iter().enumerate() {
|
||||||
let drop = DownloadDrop {
|
contexts.push(DropDownloadContext {
|
||||||
filename: raw_path.to_string(),
|
file_name: raw_path.to_string(),
|
||||||
start: file_running_offset,
|
version: chunk.version_name.to_string(),
|
||||||
length: *length,
|
offset: running_offset,
|
||||||
checksum: chunk.checksums[index].clone(),
|
|
||||||
permissions: chunk.permissions,
|
|
||||||
path: path.clone(),
|
|
||||||
index,
|
index,
|
||||||
};
|
game_id: game_id.to_string(),
|
||||||
file_running_offset += *length;
|
path: path.clone(),
|
||||||
|
checksum: chunk.checksums[index].clone(),
|
||||||
if *length >= TARGET_BUCKET_SIZE {
|
length: *length,
|
||||||
// They get their own bucket
|
permissions: chunk.permissions,
|
||||||
|
|
||||||
buckets.push(DownloadBucket {
|
|
||||||
game_id: game_id.clone(),
|
|
||||||
version: self.version.clone(),
|
|
||||||
drops: vec![drop],
|
|
||||||
});
|
});
|
||||||
|
running_offset += *length as u64;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if current_bucket_size + *length >= TARGET_BUCKET_SIZE
|
|
||||||
&& !current_bucket.drops.is_empty()
|
|
||||||
{
|
|
||||||
// Move current bucket into list and make a new one
|
|
||||||
buckets.push(current_bucket);
|
|
||||||
current_bucket = DownloadBucket {
|
|
||||||
game_id: game_id.clone(),
|
|
||||||
version: self.version.clone(),
|
|
||||||
drops: Vec::new(),
|
|
||||||
};
|
|
||||||
current_bucket_size = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
current_bucket.drops.push(drop);
|
|
||||||
current_bucket_size += *length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
if file_running_offset > 0 && !already_exists {
|
if running_offset > 0 {
|
||||||
let _ = fallocate(file, FallocateFlags::empty(), 0, file_running_offset as u64);
|
let _ = fallocate(file, FallocateFlags::empty(), 0, running_offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let existing_contexts = self.stored_manifest.get_completed_contexts();
|
||||||
if !current_bucket.drops.is_empty() {
|
self.stored_manifest.set_contexts(
|
||||||
buckets.push(current_bucket);
|
&contexts
|
||||||
}
|
|
||||||
|
|
||||||
info!("buckets: {}", buckets.len());
|
|
||||||
|
|
||||||
let existing_contexts = self.dropdata.get_contexts();
|
|
||||||
self.dropdata.set_contexts(
|
|
||||||
&buckets
|
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|x| x.drops.iter().map(|v| v.checksum.clone()))
|
.map(|x| (x.checksum.clone(), existing_contexts.contains(&x.checksum)))
|
||||||
.map(|x| {
|
|
||||||
let contains = existing_contexts.get(&x).unwrap_or(&false);
|
|
||||||
(x, *contains)
|
|
||||||
})
|
|
||||||
.collect::<Vec<(String, bool)>>(),
|
.collect::<Vec<(String, bool)>>(),
|
||||||
);
|
);
|
||||||
|
|
||||||
*self.buckets.lock().unwrap() = buckets;
|
*self.contexts.lock().unwrap() = contexts;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self) -> Result<bool, RemoteAccessError> {
|
// TODO: Change return value on Err
|
||||||
self.setup_progress();
|
pub fn run(&self) -> Result<bool, ()> {
|
||||||
let max_download_threads = borrow_db_checked().settings.max_download_threads;
|
let max_download_threads = borrow_db_checked().settings.max_download_threads;
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
@ -351,88 +265,58 @@ impl GameDownloadAgent {
|
|||||||
let completed_contexts = Arc::new(boxcar::Vec::new());
|
let completed_contexts = Arc::new(boxcar::Vec::new());
|
||||||
let completed_indexes_loop_arc = completed_contexts.clone();
|
let completed_indexes_loop_arc = completed_contexts.clone();
|
||||||
|
|
||||||
let download_context = DROP_CLIENT_SYNC
|
let contexts = self.contexts.lock().unwrap();
|
||||||
.post(generate_url(&["/api/v2/client/context"], &[]).unwrap())
|
debug!("{contexts:#?}");
|
||||||
.json(&ManifestBody {
|
|
||||||
game: self.id.clone(),
|
|
||||||
version: self.version.clone(),
|
|
||||||
})
|
|
||||||
.header("Authorization", generate_authorization_header())
|
|
||||||
.send()?;
|
|
||||||
|
|
||||||
if download_context.status() != 200 {
|
|
||||||
return Err(RemoteAccessError::InvalidResponse(download_context.json()?));
|
|
||||||
}
|
|
||||||
|
|
||||||
let download_context = &download_context.json::<DownloadContext>()?;
|
|
||||||
|
|
||||||
info!("download context: {}", download_context.context);
|
|
||||||
|
|
||||||
let buckets = self.buckets.lock().unwrap();
|
|
||||||
pool.scope(|scope| {
|
pool.scope(|scope| {
|
||||||
|
let client = &DROP_CLIENT_SYNC.clone();
|
||||||
let context_map = self.context_map.lock().unwrap();
|
let context_map = self.context_map.lock().unwrap();
|
||||||
for (index, bucket) in buckets.iter().enumerate() {
|
for (index, context) in contexts.iter().enumerate() {
|
||||||
let mut bucket = (*bucket).clone();
|
let client = client.clone();
|
||||||
let completed_contexts = completed_indexes_loop_arc.clone();
|
let completed_indexes = completed_indexes_loop_arc.clone();
|
||||||
|
|
||||||
let progress = self.progress.get(index);
|
let progress = self.progress.get(index);
|
||||||
let progress_handle = ProgressHandle::new(progress, self.progress.clone());
|
let progress_handle = ProgressHandle::new(progress, self.progress.clone());
|
||||||
|
|
||||||
// If we've done this one already, skip it
|
// If we've done this one already, skip it
|
||||||
// Note to future DecDuck, DropData gets loaded into context_map
|
if Some(&true) == context_map.get(&context.checksum) {
|
||||||
let todo_drops = bucket
|
progress_handle.skip(context.length);
|
||||||
.drops
|
|
||||||
.into_iter()
|
|
||||||
.filter(|e| {
|
|
||||||
let todo = !*context_map.get(&e.checksum).unwrap_or(&false);
|
|
||||||
if !todo {
|
|
||||||
progress_handle.skip(e.length);
|
|
||||||
}
|
|
||||||
todo
|
|
||||||
})
|
|
||||||
.collect::<Vec<DownloadDrop>>();
|
|
||||||
|
|
||||||
if todo_drops.is_empty() {
|
|
||||||
continue;
|
continue;
|
||||||
};
|
}
|
||||||
|
|
||||||
bucket.drops = todo_drops;
|
|
||||||
|
|
||||||
let sender = self.sender.clone();
|
let sender = self.sender.clone();
|
||||||
|
|
||||||
scope.spawn(move |_| {
|
let request = match make_request(
|
||||||
// 3 attempts
|
&client,
|
||||||
for i in 0..RETRY_COUNT {
|
&["/api/v1/client/chunk"],
|
||||||
let loop_progress_handle = progress_handle.clone();
|
&[
|
||||||
match download_game_bucket(
|
("id", &context.game_id),
|
||||||
&bucket,
|
("version", &context.version),
|
||||||
download_context,
|
("name", &context.file_name),
|
||||||
&self.control_flag,
|
("chunk", &context.index.to_string()),
|
||||||
loop_progress_handle,
|
],
|
||||||
|
|r| r,
|
||||||
) {
|
) {
|
||||||
Ok(true) => {
|
Ok(request) => request,
|
||||||
for drop in bucket.drops {
|
|
||||||
completed_contexts.push(drop.checksum);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Ok(false) => return,
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("game download agent error: {e}");
|
sender
|
||||||
|
.send(DownloadManagerSignal::Error(
|
||||||
|
ApplicationDownloadError::Communication(e),
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let retry = matches!(
|
scope.spawn(move |_| {
|
||||||
&e,
|
match download_game_chunk(context, &self.control_flag, progress_handle, request)
|
||||||
ApplicationDownloadError::Communication(_)
|
{
|
||||||
| ApplicationDownloadError::Checksum
|
Ok(true) => {
|
||||||
| ApplicationDownloadError::Lock
|
completed_indexes.push(context.checksum.clone());
|
||||||
);
|
}
|
||||||
|
Ok(false) => {}
|
||||||
if i == RETRY_COUNT - 1 || !retry {
|
Err(e) => {
|
||||||
warn!("retry logic failed, not re-attempting.");
|
error!("{e}");
|
||||||
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -449,119 +333,34 @@ impl GameDownloadAgent {
|
|||||||
|
|
||||||
context_map_lock.values().filter(|x| **x).count()
|
context_map_lock.values().filter(|x| **x).count()
|
||||||
};
|
};
|
||||||
|
|
||||||
let context_map_lock = self.context_map.lock().unwrap();
|
let context_map_lock = self.context_map.lock().unwrap();
|
||||||
let contexts = buckets
|
let contexts = contexts
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|x| x.drops.iter().map(|e| e.checksum.clone()))
|
|
||||||
.map(|x| {
|
.map(|x| {
|
||||||
let completed = context_map_lock.get(&x).unwrap_or(&false);
|
(
|
||||||
(x, *completed)
|
x.checksum.clone(),
|
||||||
|
context_map_lock.get(&x.checksum).copied().unwrap_or(false),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.collect::<Vec<(String, bool)>>();
|
.collect::<Vec<(String, bool)>>();
|
||||||
drop(context_map_lock);
|
drop(context_map_lock);
|
||||||
|
|
||||||
self.dropdata.set_contexts(&contexts);
|
self.stored_manifest.set_contexts(&contexts);
|
||||||
self.dropdata.write();
|
self.stored_manifest.write();
|
||||||
|
|
||||||
// If there are any contexts left which are false
|
// If there are any contexts left which are false
|
||||||
if !contexts.iter().all(|x| x.1) {
|
if !contexts.iter().all(|x| x.1) {
|
||||||
info!(
|
info!(
|
||||||
"download agent for {} exited without completing ({}/{}) ({} buckets)",
|
"download agent for {} exited without completing ({}/{})",
|
||||||
self.id.clone(),
|
self.id.clone(),
|
||||||
completed_lock_len,
|
completed_lock_len,
|
||||||
contexts.len(),
|
contexts.len(),
|
||||||
buckets.len()
|
|
||||||
);
|
);
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_validate(&self, app_handle: &AppHandle) {
|
|
||||||
self.setup_progress();
|
|
||||||
|
|
||||||
self.control_flag.set(DownloadThreadControlFlag::Go);
|
|
||||||
|
|
||||||
let status = ApplicationTransientStatus::Validating {
|
|
||||||
version_name: self.version.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut db_lock = borrow_db_mut_checked();
|
|
||||||
db_lock
|
|
||||||
.applications
|
|
||||||
.transient_statuses
|
|
||||||
.insert(self.metadata(), status.clone());
|
|
||||||
push_game_update(app_handle, &self.metadata().id, None, (None, Some(status)));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn validate(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError> {
|
|
||||||
self.setup_validate(app_handle);
|
|
||||||
|
|
||||||
let buckets = self.buckets.lock().unwrap();
|
|
||||||
let contexts: Vec<DropValidateContext> = buckets
|
|
||||||
.clone()
|
|
||||||
.into_iter()
|
|
||||||
.flat_map(|e| -> Vec<DropValidateContext> { e.into() })
|
|
||||||
.collect();
|
|
||||||
let max_download_threads = borrow_db_checked().settings.max_download_threads;
|
|
||||||
|
|
||||||
info!("{} validation contexts", contexts.len());
|
|
||||||
let pool = ThreadPoolBuilder::new()
|
|
||||||
.num_threads(max_download_threads)
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let invalid_chunks = Arc::new(boxcar::Vec::new());
|
|
||||||
pool.scope(|scope| {
|
|
||||||
for (index, context) in contexts.iter().enumerate() {
|
|
||||||
let current_progress = self.progress.get(index);
|
|
||||||
let progress_handle = ProgressHandle::new(current_progress, self.progress.clone());
|
|
||||||
let invalid_chunks_scoped = invalid_chunks.clone();
|
|
||||||
let sender = self.sender.clone();
|
|
||||||
|
|
||||||
scope.spawn(move |_| {
|
|
||||||
match validate_game_chunk(context, &self.control_flag, progress_handle) {
|
|
||||||
Ok(true) => {}
|
|
||||||
Ok(false) => {
|
|
||||||
invalid_chunks_scoped.push(context.checksum.clone());
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("{e}");
|
|
||||||
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// If there are any contexts left which are false
|
|
||||||
if !invalid_chunks.is_empty() {
|
|
||||||
info!("validation of game id {} failed", self.id);
|
|
||||||
|
|
||||||
for context in invalid_chunks.iter() {
|
|
||||||
self.dropdata.set_context(context.1.clone(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.dropdata.write();
|
|
||||||
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cancel(&self, app_handle: &AppHandle) {
|
|
||||||
// See docs on usage
|
|
||||||
set_partially_installed(
|
|
||||||
&self.metadata(),
|
|
||||||
self.dropdata.base_path.to_str().unwrap().to_string(),
|
|
||||||
Some(app_handle),
|
|
||||||
);
|
|
||||||
|
|
||||||
self.dropdata.write();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Downloadable for GameDownloadAgent {
|
impl Downloadable for GameDownloadAgent {
|
||||||
@ -570,11 +369,6 @@ impl Downloadable for GameDownloadAgent {
|
|||||||
self.download(app_handle)
|
self.download(app_handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError> {
|
|
||||||
*self.status.lock().unwrap() = DownloadStatus::Validating;
|
|
||||||
self.validate(app_handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn progress(&self) -> Arc<ProgressObject> {
|
fn progress(&self) -> Arc<ProgressObject> {
|
||||||
self.progress.clone()
|
self.progress.clone()
|
||||||
}
|
}
|
||||||
@ -608,37 +402,42 @@ impl Downloadable for GameDownloadAgent {
|
|||||||
.applications
|
.applications
|
||||||
.transient_statuses
|
.transient_statuses
|
||||||
.remove(&self.metadata());
|
.remove(&self.metadata());
|
||||||
|
|
||||||
push_game_update(
|
|
||||||
app_handle,
|
|
||||||
&self.id,
|
|
||||||
None,
|
|
||||||
GameStatusManager::fetch_state(&self.id, &handle),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_complete(&self, app_handle: &tauri::AppHandle) {
|
fn on_complete(&self, app_handle: &tauri::AppHandle) {
|
||||||
on_game_complete(
|
on_game_complete(
|
||||||
&self.metadata(),
|
&self.metadata(),
|
||||||
self.dropdata.base_path.to_string_lossy().to_string(),
|
self.stored_manifest.base_path.to_string_lossy().to_string(),
|
||||||
app_handle,
|
app_handle,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_cancelled(&self, app_handle: &tauri::AppHandle) {
|
// TODO: fix this function. It doesn't restart the download properly, nor does it reset the state properly
|
||||||
self.cancel(app_handle);
|
fn on_incomplete(&self, app_handle: &tauri::AppHandle) {
|
||||||
/*
|
|
||||||
on_game_incomplete(
|
on_game_incomplete(
|
||||||
&self.metadata(),
|
&self.metadata(),
|
||||||
self.dropdata.base_path.to_string_lossy().to_string(),
|
self.stored_manifest.base_path.to_string_lossy().to_string(),
|
||||||
app_handle,
|
app_handle,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
*/
|
println!("Attempting to redownload");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_cancelled(&self, _app_handle: &tauri::AppHandle) {}
|
||||||
|
|
||||||
fn status(&self) -> DownloadStatus {
|
fn status(&self) -> DownloadStatus {
|
||||||
self.status.lock().unwrap().clone()
|
self.status.lock().unwrap().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate(&self) -> Result<bool, ApplicationDownloadError> {
|
||||||
|
*self.status.lock().unwrap() = DownloadStatus::Validating;
|
||||||
|
game_validate_logic(
|
||||||
|
&self.stored_manifest,
|
||||||
|
self.contexts.lock().unwrap().clone(),
|
||||||
|
self.progress.clone(),
|
||||||
|
self.sender.clone(),
|
||||||
|
&self.control_flag,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user