mirror of
https://github.com/Drop-OSS/drop-app.git
synced 2025-11-13 16:22:43 +10:00
Compare commits
15 Commits
async
...
346ee1dddc
| Author | SHA1 | Date | |
|---|---|---|---|
| 346ee1dddc | |||
| 3b830e2a44 | |||
| 75a4b73ee1 | |||
| 339d707092 | |||
| 776dc8fe7a | |||
| dbe8c8df4d | |||
| 35f49b8811 | |||
| cc5339a389 | |||
| 6104bfda72 | |||
| be688cb18f | |||
| 13cc69f10e | |||
| 574782f445 | |||
| b5a8543194 | |||
| d0e4aea5ce | |||
| 739e6166c5 |
35
.github/workflows/release.yml
vendored
35
.github/workflows/release.yml
vendored
@ -54,12 +54,47 @@ jobs:
|
|||||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
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.admin allow
|
||||||
|
sudo security add-trusted-cert -d -r trustRoot -k build.keychain -p codeSign -u -1 drop.pem
|
||||||
|
sudo security authorizationdb remove com.apple.trust-settings.admin
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
- uses: tauri-apps/tauri-action@v0
|
- uses: tauri-apps/tauri-action@v0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||||
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||||
|
APPLE_SIGNING_IDENTITY: ${{ env.CERT_ID }}
|
||||||
|
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,3 +27,6 @@ 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,9 +1,6 @@
|
|||||||
[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 "src-tauri/umu/umu-launcher"]
|
[submodule "libs/drop-base"]
|
||||||
path = src-tauri/umu/umu-launcher
|
path = libs/drop-base
|
||||||
url = https://github.com/Open-Wine-Components/umu-launcher.git
|
url = https://github.com/drop-oss/drop-base.git
|
||||||
|
|||||||
48
build.mjs
Normal file
48
build.mjs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
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,3 +0,0 @@
|
|||||||
import type { AppState } from "~/types";
|
|
||||||
|
|
||||||
export const useAppState = () => useState<AppState>("state");
|
|
||||||
Submodule drop-base deleted from 26698e5b06
1
libs/drop-base
Submodule
1
libs/drop-base
Submodule
Submodule libs/drop-base added at 04125e89be
@ -1,4 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<LoadingIndicator />
|
||||||
<NuxtLayout class="select-none w-screen h-screen">
|
<NuxtLayout class="select-none w-screen h-screen">
|
||||||
<NuxtPage />
|
<NuxtPage />
|
||||||
<ModalStack />
|
<ModalStack />
|
||||||
@ -9,8 +10,6 @@
|
|||||||
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,
|
||||||
@ -20,19 +19,26 @@ import {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const state = useAppState();
|
const state = useAppState();
|
||||||
try {
|
|
||||||
state.value = JSON.parse(await invoke("fetch_state"));
|
|
||||||
console.log(state.value)
|
|
||||||
} catch (e) {
|
|
||||||
console.error("failed to parse state", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
router.beforeEach(async () => {
|
async function fetchState() {
|
||||||
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 |
@ -1,49 +1,75 @@
|
|||||||
<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 type="button" @click="() => buttonActions[props.status.type]()" :class="[
|
<button
|
||||||
|
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 v-if="showDropdown" as="div" class="relative inline-block text-left grow">
|
<Menu
|
||||||
|
v-if="showDropdown"
|
||||||
|
as="div"
|
||||||
|
class="relative inline-block text-left grow"
|
||||||
|
>
|
||||||
<div class="h-full">
|
<div class="h-full">
|
||||||
<MenuButton :class="[
|
<MenuButton
|
||||||
|
: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 enter-active-class="transition ease-out duration-100" enter-from-class="transform opacity-0 scale-95"
|
<transition
|
||||||
enter-to-class="transform opacity-100 scale-100" leave-active-class="transition ease-in duration-75"
|
enter-active-class="transition ease-out duration-100"
|
||||||
leave-from-class="transform opacity-100 scale-100" leave-to-class="transform opacity-0 scale-95">
|
enter-from-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-slot="{ active }">
|
<MenuItem v-if="showOptions" v-slot="{ active }">
|
||||||
<button @click="() => emit('options')" :class="[
|
<button
|
||||||
|
@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 @click="() => emit('uninstall')" :class="[
|
<button
|
||||||
|
@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>
|
||||||
@ -61,6 +87,7 @@ import {
|
|||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
PlayIcon,
|
PlayIcon,
|
||||||
QueueListIcon,
|
QueueListIcon,
|
||||||
|
ServerIcon,
|
||||||
StopIcon,
|
StopIcon,
|
||||||
WrenchIcon,
|
WrenchIcon,
|
||||||
} from "@heroicons/vue/20/solid";
|
} from "@heroicons/vue/20/solid";
|
||||||
@ -78,7 +105,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(
|
||||||
@ -88,6 +115,10 @@ 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",
|
||||||
@ -95,6 +126,8 @@ 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]:
|
||||||
@ -106,42 +139,45 @@ 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-gray-600 text-white hover:bg-gray-500 focus-visible:outline-gray-600 hover:bg-gray-500"
|
"bg-blue-600 text-white hover:bg-blue-500 focus-visible:outline-blue-600 hover:bg-blue-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">
|
||||||
@ -23,7 +23,7 @@
|
|||||||
<MenuItems
|
<MenuItems
|
||||||
class="absolute bg-zinc-900 right-0 top-10 z-50 w-56 origin-top-right focus:outline-none shadow-md"
|
class="absolute bg-zinc-900 right-0 top-10 z-50 w-56 origin-top-right focus:outline-none shadow-md"
|
||||||
>
|
>
|
||||||
<PanelWidget class="flex-col gap-y-2">
|
<div class="flex-col gap-y-2">
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
to="/id/me"
|
to="/id/me"
|
||||||
class="transition inline-flex items-center w-full py-3 px-4 hover:bg-zinc-800"
|
class="transition inline-flex items-center w-full py-3 px-4 hover:bg-zinc-800"
|
||||||
@ -65,7 +65,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</div>
|
</div>
|
||||||
</PanelWidget>
|
</div>
|
||||||
</MenuItems>
|
</MenuItems>
|
||||||
</transition>
|
</transition>
|
||||||
</Menu>
|
</Menu>
|
||||||
@ -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",
|
||||||
@ -13,11 +13,7 @@
|
|||||||
<div class="max-w-lg">
|
<div class="max-w-lg">
|
||||||
<slot />
|
<slot />
|
||||||
<div class="mt-10">
|
<div class="mt-10">
|
||||||
<button
|
<div>
|
||||||
@click="() => authWrapper_wrapper()"
|
|
||||||
:disabled="loading"
|
|
||||||
class="text-sm text-left font-semibold leading-7 text-blue-600"
|
|
||||||
>
|
|
||||||
<div v-if="loading" role="status">
|
<div v-if="loading" role="status">
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@ -37,10 +33,19 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<span class="sr-only">Loading...</span>
|
<span class="sr-only">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
<span v-else>
|
<span class="inline-flex gap-x-8 items-center" v-else>
|
||||||
Sign in with your browser <span aria-hidden="true">→</span>
|
<button
|
||||||
</span>
|
@click="() => authWrapper_wrapper()"
|
||||||
|
:disabled="loading"
|
||||||
|
class="px-3 py-1 inline-flex items-center gap-x-2 bg-zinc-700 rounded text-sm text-left font-semibold leading-7 text-white"
|
||||||
|
>
|
||||||
|
Sign in with your browser <ArrowTopRightOnSquareIcon class="size-4" />
|
||||||
</button>
|
</button>
|
||||||
|
<NuxtLink href="/auth/code" class="text-zinc-100 text-sm hover:text-zinc-300">
|
||||||
|
Use a code →
|
||||||
|
</NuxtLink>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mt-5" v-if="offerManual">
|
<div class="mt-5" v-if="offerManual">
|
||||||
<h1 class="text-zinc-100 font-semibold">Having trouble?</h1>
|
<h1 class="text-zinc-100 font-semibold">Having trouble?</h1>
|
||||||
@ -121,6 +126,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { XCircleIcon } from "@heroicons/vue/16/solid";
|
import { XCircleIcon } from "@heroicons/vue/16/solid";
|
||||||
|
import { ArrowTopRightOnSquareIcon } from "@heroicons/vue/20/solid";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
@ -81,18 +81,20 @@ import { listen } from "@tauri-apps/api/event";
|
|||||||
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-blue-500",
|
[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-blue-500",
|
[GameStatusEnum.Queued]: "text-blue-500",
|
||||||
[GameStatusEnum.Updating]: "text-blue-500",
|
[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-600",
|
[GameStatusEnum.PartiallyInstalled]: "text-gray-400",
|
||||||
};
|
};
|
||||||
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...",
|
||||||
7
main/components/LoadingIndicator.vue
Normal file
7
main/components/LoadingIndicator.vue
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<template></template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const loading = useLoadingIndicator();
|
||||||
|
|
||||||
|
watch(loading.isLoading, console.log);
|
||||||
|
</script>
|
||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="h-10 cursor-pointer flex flex-row items-center justify-between bg-zinc-950"
|
class="h-16 cursor-pointer flex flex-row items-center justify-between bg-zinc-950"
|
||||||
>
|
>
|
||||||
<div class="px-5 py-3 grow" @mousedown="() => window.startDragging()">
|
<div class="px-5 py-3 grow" @mousedown="() => window.startDragging()">
|
||||||
<Wordmark class="mt-1" />
|
<Wordmark class="mt-1" />
|
||||||
3
main/composables/app-state.ts
Normal file
3
main/composables/app-state.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import type { AppState } from "~/types";
|
||||||
|
|
||||||
|
export const useAppState = () => useState<AppState | undefined>("state");
|
||||||
@ -65,7 +65,13 @@ export function setupHooks() {
|
|||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initialNavigation(state: Ref<AppState>) {
|
export function initialNavigation(state: ReturnType<typeof useAppState>) {
|
||||||
|
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,6 +7,7 @@
|
|||||||
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"
|
||||||
@ -13,5 +13,9 @@ export default defineNuxtConfig({
|
|||||||
|
|
||||||
ssr: false,
|
ssr: false,
|
||||||
|
|
||||||
extends: [["./drop-base"]],
|
extends: [["../libs/drop-base"]],
|
||||||
|
|
||||||
|
app: {
|
||||||
|
baseURL: "/main",
|
||||||
|
}
|
||||||
});
|
});
|
||||||
37
main/package.json
Normal file
37
main/package.json
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"name": "view",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.3.1",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
37
main/pages/auth/code.vue
Normal file
37
main/pages/auth/code.vue
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<template>
|
||||||
|
<div class="min-h-full w-full flex items-center justify-center">
|
||||||
|
<div class="flex flex-col items-center">
|
||||||
|
<div class="text-center">
|
||||||
|
<h1 class="text-3xl font-semibold font-display leading-6 text-zinc-100">
|
||||||
|
Device authorization
|
||||||
|
</h1>
|
||||||
|
<div class="mt-4">
|
||||||
|
<p class="text-sm text-zinc-400 max-w-md mx-auto">
|
||||||
|
Open Drop on another one of your devices, and use your account
|
||||||
|
dropdown to "Authorize client", and enter the code below.
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="mt-8 flex items-center justify-center gap-x-5 text-8xl font-bold text-zinc-100"
|
||||||
|
>
|
||||||
|
<span v-for="letter in code.split('')">{{ letter }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-10 flex items-center justify-center gap-x-6">
|
||||||
|
<NuxtLink href="/auth" class="text-sm font-semibold text-blue-600"
|
||||||
|
><span aria-hidden="true">←</span> Use a different method
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
|
||||||
|
const code = await invoke<string>("auth_initiate_code");
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
layout: "mini",
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@ -76,9 +76,8 @@
|
|||||||
<TransitionGroup name="slide" tag="div" class="h-full">
|
<TransitionGroup name="slide" tag="div" class="h-full">
|
||||||
<img
|
<img
|
||||||
v-for="(url, index) in mediaUrls"
|
v-for="(url, index) in mediaUrls"
|
||||||
:key="url"
|
:key="index"
|
||||||
:src="url"
|
:src="url"
|
||||||
loading="lazy"
|
|
||||||
class="absolute inset-0 w-full h-full object-cover"
|
class="absolute inset-0 w-full h-full object-cover"
|
||||||
v-show="index === currentImageIndex"
|
v-show="index === currentImageIndex"
|
||||||
/>
|
/>
|
||||||
@ -158,8 +157,8 @@
|
|||||||
<template #default>
|
<template #default>
|
||||||
<div class="sm:flex sm:items-start">
|
<div class="sm:flex sm:items-start">
|
||||||
<div class="mt-3 text-center sm:mt-0 sm:text-left">
|
<div class="mt-3 text-center sm:mt-0 sm:text-left">
|
||||||
<h3 class="text-base font-semibold text-zinc-100"
|
<h3 class="text-base font-semibold text-zinc-100">
|
||||||
>Install {{ game.mName }}?
|
Install {{ game.mName }}?
|
||||||
</h3>
|
</h3>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<p class="text-sm text-zinc-400">
|
<p class="text-sm text-zinc-400">
|
||||||
@ -351,9 +350,7 @@
|
|||||||
<template #buttons>
|
<template #buttons>
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
@click="() => install()"
|
@click="() => install()"
|
||||||
:disabled="
|
:disabled="!(versionOptions && versionOptions.length > 0)"
|
||||||
!(versionOptions && versionOptions.length > 0)
|
|
||||||
"
|
|
||||||
:loading="installLoading"
|
:loading="installLoading"
|
||||||
type="submit"
|
type="submit"
|
||||||
class="ml-2 w-full sm:w-fit"
|
class="ml-2 w-full sm:w-fit"
|
||||||
@ -371,7 +368,18 @@
|
|||||||
</template>
|
</template>
|
||||||
</ModalTemplate>
|
</ModalTemplate>
|
||||||
|
|
||||||
<GameOptionsModal v-if="status.type === GameStatusEnum.Installed" v-model="configureModalOpen" :game-id="game.id" />
|
<!--
|
||||||
|
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
|
||||||
|
v-if="status.type === GameStatusEnum.Installed"
|
||||||
|
v-model="configureModalOpen"
|
||||||
|
:game-id="game.id"
|
||||||
|
/>
|
||||||
|
|
||||||
<Transition
|
<Transition
|
||||||
enter="transition ease-out duration-300"
|
enter="transition ease-out duration-300"
|
||||||
@ -421,7 +429,7 @@
|
|||||||
<img
|
<img
|
||||||
v-for="(url, index) in mediaUrls"
|
v-for="(url, index) in mediaUrls"
|
||||||
v-show="currentImageIndex === index"
|
v-show="currentImageIndex === index"
|
||||||
:key="url"
|
:key="index"
|
||||||
:src="url"
|
:src="url"
|
||||||
class="max-h-[90vh] max-w-[90vw] object-contain"
|
class="max-h-[90vh] max-w-[90vw] object-contain"
|
||||||
:alt="`${game.mName} screenshot ${index + 1}`"
|
:alt="`${game.mName} screenshot ${index + 1}`"
|
||||||
@ -442,10 +450,6 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
Dialog,
|
|
||||||
DialogTitle,
|
|
||||||
TransitionChild,
|
|
||||||
TransitionRoot,
|
|
||||||
Listbox,
|
Listbox,
|
||||||
ListboxButton,
|
ListboxButton,
|
||||||
ListboxLabel,
|
ListboxLabel,
|
||||||
@ -483,7 +487,10 @@ const bannerUrl = await useObject(game.value.mBannerObjectId);
|
|||||||
|
|
||||||
// Get all available images
|
// Get all available images
|
||||||
const mediaUrls = await Promise.all(
|
const mediaUrls = await Promise.all(
|
||||||
game.value.mImageCarouselObjectIds.map((id) => useObject(id))
|
game.value.mImageCarouselObjectIds.map(async (v) => {
|
||||||
|
const src = await useObject(v);
|
||||||
|
return src;
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const htmlDescription = micromark(game.value.mDescription);
|
const htmlDescription = micromark(game.value.mDescription);
|
||||||
@ -497,7 +504,6 @@ const currentImageIndex = ref(0);
|
|||||||
|
|
||||||
const configureModalOpen = ref(false);
|
const configureModalOpen = ref(false);
|
||||||
|
|
||||||
|
|
||||||
async function installFlow() {
|
async function installFlow() {
|
||||||
installFlowOpen.value = true;
|
installFlowOpen.value = true;
|
||||||
versionOptions.value = undefined;
|
versionOptions.value = undefined;
|
||||||
@ -537,10 +543,9 @@ async function install() {
|
|||||||
|
|
||||||
async function resumeDownload() {
|
async function resumeDownload() {
|
||||||
try {
|
try {
|
||||||
await invoke("resume_download", { gameId: game.value.id })
|
await invoke("resume_download", { gameId: game.value.id });
|
||||||
}
|
} catch (e) {
|
||||||
catch(e) {
|
console.error(e);
|
||||||
console.error(e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
5
main/tsconfig.json
Normal file
5
main/tsconfig.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
// https://nuxt.com/docs/guide/concepts/typescript
|
||||||
|
"extends": "./.nuxt/tsconfig.json",
|
||||||
|
"exclude": ["src-tauri/**/*"]
|
||||||
|
}
|
||||||
@ -54,12 +54,13 @@ export enum GameStatusEnum {
|
|||||||
Remote = "Remote",
|
Remote = "Remote",
|
||||||
Queued = "Queued",
|
Queued = "Queued",
|
||||||
Downloading = "Downloading",
|
Downloading = "Downloading",
|
||||||
|
Validating = "Validating",
|
||||||
Installed = "Installed",
|
Installed = "Installed",
|
||||||
Updating = "Updating",
|
Updating = "Updating",
|
||||||
Uninstalling = "Uninstalling",
|
Uninstalling = "Uninstalling",
|
||||||
SetupRequired = "SetupRequired",
|
SetupRequired = "SetupRequired",
|
||||||
Running = "Running",
|
Running = "Running",
|
||||||
PartiallyInstalled = "PartiallyInstalled"
|
PartiallyInstalled = "PartiallyInstalled",
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GameStatus = {
|
export type GameStatus = {
|
||||||
8091
main/yarn.lock
Normal file
8091
main/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
22
optimize-appimage.sh
Executable file
22
optimize-appimage.sh
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
## 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,46 +1,22 @@
|
|||||||
{
|
{
|
||||||
"name": "drop-app",
|
"name": "drop-app",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.3.0-rc-8",
|
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nuxt build",
|
"build": "node ./build.mjs",
|
||||||
"dev": "nuxt dev",
|
|
||||||
"generate": "nuxt generate",
|
|
||||||
"preview": "nuxt preview",
|
|
||||||
"postinstall": "nuxt prepare",
|
|
||||||
"tauri": "tauri"
|
"tauri": "tauri"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/vue": "^1.7.23",
|
"@tauri-apps/api": "^2.7.0",
|
||||||
"@heroicons/vue": "^2.1.5",
|
"@tauri-apps/plugin-deep-link": "^2.4.1",
|
||||||
"@nuxtjs/tailwindcss": "^6.12.2",
|
"@tauri-apps/plugin-dialog": "^2.3.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",
|
"@tauri-apps/plugin-os": "^2.3.0",
|
||||||
"@tauri-apps/plugin-shell": "^2.2.1",
|
"@tauri-apps/plugin-shell": "^2.3.0",
|
||||||
"koa": "^2.16.1",
|
"pino": "^9.7.0",
|
||||||
"markdown-it": "^14.1.0",
|
"pino-pretty": "^13.1.1"
|
||||||
"micromark": "^4.0.1",
|
|
||||||
"nuxt": "^3.16.0",
|
|
||||||
"scss": "^0.2.4",
|
|
||||||
"vue": "latest",
|
|
||||||
"vue-router": "latest",
|
|
||||||
"vuedraggable": "^4.1.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/forms": "^0.5.9",
|
"@tauri-apps/cli": "^2.7.1"
|
||||||
"@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"
|
|
||||||
}
|
}
|
||||||
|
|||||||
1402
src-tauri/Cargo.lock
generated
1402
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "drop-app"
|
name = "drop-app"
|
||||||
version = "0.3.0-rc-8"
|
version = "0.3.1"
|
||||||
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"
|
||||||
@ -25,6 +25,7 @@ tauri-build = { version = "2.0.0", features = [] }
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
tauri-plugin-shell = "2.2.1"
|
tauri-plugin-shell = "2.2.1"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
rayon = "1.10.0"
|
||||||
webbrowser = "1.0.2"
|
webbrowser = "1.0.2"
|
||||||
url = "2.5.2"
|
url = "2.5.2"
|
||||||
tauri-plugin-deep-link = "2"
|
tauri-plugin-deep-link = "2"
|
||||||
@ -67,11 +68,11 @@ known-folders = "1.2.0"
|
|||||||
native_model = { version = "0.6.1", features = ["rmp_serde_1_3"] }
|
native_model = { version = "0.6.1", features = ["rmp_serde_1_3"] }
|
||||||
tauri-plugin-opener = "2.4.0"
|
tauri-plugin-opener = "2.4.0"
|
||||||
bitcode = "0.6.6"
|
bitcode = "0.6.6"
|
||||||
async-trait = "0.1.88"
|
reqwest-websocket = "0.5.0"
|
||||||
futures = "0.3.31"
|
futures-lite = "2.6.0"
|
||||||
tokio-util = { version = "0.7.15", features = ["io"] }
|
page_size = "0.6.0"
|
||||||
async-scoped = { version = "0.9.0", features = ["use-tokio"] }
|
sysinfo = "0.36.1"
|
||||||
async-once-cell = "0.5.4"
|
humansize = "2.1.3"
|
||||||
# tailscale = { path = "./tailscale" }
|
# tailscale = { path = "./tailscale" }
|
||||||
|
|
||||||
[dependencies.dynfmt]
|
[dependencies.dynfmt]
|
||||||
@ -79,7 +80,7 @@ version = "0.1.5"
|
|||||||
features = ["curly"]
|
features = ["curly"]
|
||||||
|
|
||||||
[dependencies.tauri]
|
[dependencies.tauri]
|
||||||
version = "2.1.1"
|
version = "2.7.0"
|
||||||
features = ["protocol-asset", "tray-icon"]
|
features = ["protocol-asset", "tray-icon"]
|
||||||
|
|
||||||
[dependencies.tokio]
|
[dependencies.tokio]
|
||||||
@ -98,14 +99,14 @@ features = ["fs"]
|
|||||||
version = "1.10.0"
|
version = "1.10.0"
|
||||||
features = ["v4", "fast-rng", "macro-diagnostics"]
|
features = ["v4", "fast-rng", "macro-diagnostics"]
|
||||||
|
|
||||||
[dependencies.dropbreak]
|
[dependencies.rustbreak]
|
||||||
git = "https://github.com/Drop-OSS/dropbreak.git"
|
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"
|
version = "0.12"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["json", "http2", "rustls-tls-webpki-roots", "stream"]
|
features = ["json", "http2", "blocking", "rustls-tls", "native-tls-alpn", "rustls-tls-webpki-roots"]
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies.serde]
|
||||||
version = "1"
|
version = "1"
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
fn main() {
|
fn main() {
|
||||||
tauri_build::build()
|
tauri_build::build();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ use log::debug;
|
|||||||
use tauri::AppHandle;
|
use tauri::AppHandle;
|
||||||
use tauri_plugin_autostart::ManagerExt;
|
use tauri_plugin_autostart::ManagerExt;
|
||||||
|
|
||||||
pub async fn toggle_autostart_logic(app: AppHandle, enabled: bool) -> Result<(), String> {
|
pub fn toggle_autostart_logic(app: AppHandle, enabled: bool) -> Result<(), String> {
|
||||||
let manager = app.autolaunch();
|
let manager = app.autolaunch();
|
||||||
if enabled {
|
if enabled {
|
||||||
manager.enable().map_err(|e| e.to_string())?;
|
manager.enable().map_err(|e| e.to_string())?;
|
||||||
@ -14,18 +14,16 @@ pub async fn toggle_autostart_logic(app: AppHandle, enabled: bool) -> Result<(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Store the state in DB
|
// Store the state in DB
|
||||||
let mut db_handle = borrow_db_mut_checked().await;
|
let mut db_handle = borrow_db_mut_checked();
|
||||||
db_handle.settings.autostart = enabled;
|
db_handle.settings.autostart = enabled;
|
||||||
drop(db_handle);
|
drop(db_handle);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_autostart_enabled_logic(
|
pub fn get_autostart_enabled_logic(app: AppHandle) -> Result<bool, tauri_plugin_autostart::Error> {
|
||||||
app: AppHandle,
|
|
||||||
) -> Result<bool, tauri_plugin_autostart::Error> {
|
|
||||||
// First check DB state
|
// First check DB state
|
||||||
let db_handle = borrow_db_checked().await;
|
let db_handle = borrow_db_checked();
|
||||||
let db_state = db_handle.settings.autostart;
|
let db_state = db_handle.settings.autostart;
|
||||||
drop(db_handle);
|
drop(db_handle);
|
||||||
|
|
||||||
@ -46,8 +44,8 @@ pub async fn get_autostart_enabled_logic(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New function to sync state on startup
|
// New function to sync state on startup
|
||||||
pub async fn sync_autostart_on_startup(app: &AppHandle) -> Result<(), String> {
|
pub fn sync_autostart_on_startup(app: &AppHandle) -> Result<(), String> {
|
||||||
let db_handle = borrow_db_checked().await;
|
let db_handle = borrow_db_checked();
|
||||||
let should_be_enabled = db_handle.settings.autostart;
|
let should_be_enabled = db_handle.settings.autostart;
|
||||||
drop(db_handle);
|
drop(db_handle);
|
||||||
|
|
||||||
@ -67,11 +65,11 @@ pub async fn sync_autostart_on_startup(app: &AppHandle) -> Result<(), String> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn toggle_autostart(app: AppHandle, enabled: bool) -> Result<(), String> {
|
pub fn toggle_autostart(app: AppHandle, enabled: bool) -> Result<(), String> {
|
||||||
toggle_autostart_logic(app, enabled).await
|
toggle_autostart_logic(app, enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_autostart_enabled(app: AppHandle) -> Result<bool, tauri_plugin_autostart::Error> {
|
pub fn get_autostart_enabled(app: AppHandle) -> Result<bool, tauri_plugin_autostart::Error> {
|
||||||
get_autostart_enabled_logic(app).await
|
get_autostart_enabled_logic(app)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,25 +1,20 @@
|
|||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
use tauri::AppHandle;
|
use tauri::AppHandle;
|
||||||
|
|
||||||
use crate::DropFunctionState;
|
use crate::AppState;
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn quit<>(app: tauri::AppHandle, state: tauri::State<'_, DropFunctionState<'_>>) -> Result<(), ()> {
|
pub fn quit(app: tauri::AppHandle, state: tauri::State<'_, std::sync::Mutex<AppState<'_>>>) {
|
||||||
cleanup_and_exit(&app, &state).await;
|
cleanup_and_exit(&app, &state);
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn cleanup_and_exit(
|
pub fn cleanup_and_exit(app: &AppHandle, state: &tauri::State<'_, std::sync::Mutex<AppState<'_>>>) {
|
||||||
app: &AppHandle,
|
|
||||||
state: &tauri::State<'_, DropFunctionState<'_>>,
|
|
||||||
) {
|
|
||||||
debug!("cleaning up and exiting application");
|
debug!("cleaning up and exiting application");
|
||||||
let download_manager = state.lock().await.download_manager.clone();
|
let download_manager = state.lock().unwrap().download_manager.clone();
|
||||||
match download_manager.ensure_terminated().await {
|
match download_manager.ensure_terminated() {
|
||||||
Ok(res) => match res {
|
Ok(res) => match res {
|
||||||
Ok(_) => debug!("download manager terminated correctly"),
|
Ok(()) => debug!("download manager terminated correctly"),
|
||||||
Err(_) => error!("download manager failed to terminate correctly"),
|
Err(()) => error!("download manager failed to terminate correctly"),
|
||||||
},
|
},
|
||||||
Err(e) => panic!("{e:?}"),
|
Err(e) => panic!("{e:?}"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
use crate::DropFunctionState;
|
use crate::AppState;
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn fetch_state(
|
pub fn fetch_state(
|
||||||
state: tauri::State<'_, DropFunctionState<'_>>,
|
state: tauri::State<'_, std::sync::Mutex<AppState<'_>>>,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
let guard = state.lock().await;
|
let guard = state.lock().unwrap();
|
||||||
let cloned_state = serde_json::to_string(&guard.clone()).map_err(|e| e.to_string())?;
|
let cloned_state = serde_json::to_string(&guard.clone()).map_err(|e| e.to_string())?;
|
||||||
drop(guard);
|
drop(guard);
|
||||||
Ok(cloned_state)
|
Ok(cloned_state)
|
||||||
|
|||||||
@ -7,11 +7,11 @@ use std::{
|
|||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
database::db::borrow_db_mut_checked, error::download_manager_error::DownloadManagerError,
|
database::{db::borrow_db_mut_checked, scan::scan_install_dirs}, error::download_manager_error::DownloadManagerError,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
db::{DATA_ROOT_DIR, borrow_db_checked},
|
db::{borrow_db_checked, DATA_ROOT_DIR},
|
||||||
debug::SystemData,
|
debug::SystemData,
|
||||||
models::data::Settings,
|
models::data::Settings,
|
||||||
};
|
};
|
||||||
@ -19,19 +19,19 @@ use super::{
|
|||||||
// Will, in future, return disk/remaining size
|
// Will, in future, return disk/remaining size
|
||||||
// Just returns the directories that have been set up
|
// Just returns the directories that have been set up
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn fetch_download_dir_stats() -> Vec<PathBuf> {
|
pub fn fetch_download_dir_stats() -> Vec<PathBuf> {
|
||||||
let lock = borrow_db_checked().await;
|
let lock = borrow_db_checked();
|
||||||
lock.applications.install_dirs.clone()
|
lock.applications.install_dirs.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn delete_download_dir(index: usize) {
|
pub fn delete_download_dir(index: usize) {
|
||||||
let mut lock = borrow_db_mut_checked().await;
|
let mut lock = borrow_db_mut_checked();
|
||||||
lock.applications.install_dirs.remove(index);
|
lock.applications.install_dirs.remove(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn add_download_dir(new_dir: PathBuf) -> Result<(), DownloadManagerError<()>> {
|
pub fn add_download_dir(new_dir: PathBuf) -> Result<(), DownloadManagerError<()>> {
|
||||||
// Check the new directory is all good
|
// Check the new directory is all good
|
||||||
let new_dir_path = Path::new(&new_dir);
|
let new_dir_path = Path::new(&new_dir);
|
||||||
if new_dir_path.exists() {
|
if new_dir_path.exists() {
|
||||||
@ -48,7 +48,7 @@ pub async fn add_download_dir(new_dir: PathBuf) -> Result<(), DownloadManagerErr
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add it to the dictionary
|
// Add it to the dictionary
|
||||||
let mut lock = borrow_db_mut_checked().await;
|
let mut lock = borrow_db_mut_checked();
|
||||||
if lock.applications.install_dirs.contains(&new_dir) {
|
if lock.applications.install_dirs.contains(&new_dir) {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
ErrorKind::AlreadyExists,
|
ErrorKind::AlreadyExists,
|
||||||
@ -59,12 +59,14 @@ pub async fn add_download_dir(new_dir: PathBuf) -> Result<(), DownloadManagerErr
|
|||||||
lock.applications.install_dirs.push(new_dir);
|
lock.applications.install_dirs.push(new_dir);
|
||||||
drop(lock);
|
drop(lock);
|
||||||
|
|
||||||
|
scan_install_dirs();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn update_settings(new_settings: Value) {
|
pub fn update_settings(new_settings: Value) {
|
||||||
let mut db_lock = borrow_db_mut_checked().await;
|
let mut db_lock = borrow_db_mut_checked();
|
||||||
let mut current_settings = serde_json::to_value(db_lock.settings.clone()).unwrap();
|
let mut current_settings = serde_json::to_value(db_lock.settings.clone()).unwrap();
|
||||||
for (key, value) in new_settings.as_object().unwrap() {
|
for (key, value) in new_settings.as_object().unwrap() {
|
||||||
current_settings[key] = value.clone();
|
current_settings[key] = value.clone();
|
||||||
@ -73,12 +75,12 @@ pub async fn update_settings(new_settings: Value) {
|
|||||||
db_lock.settings = new_settings;
|
db_lock.settings = new_settings;
|
||||||
}
|
}
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn fetch_settings() -> Settings {
|
pub fn fetch_settings() -> Settings {
|
||||||
borrow_db_checked().await.settings.clone()
|
borrow_db_checked().settings.clone()
|
||||||
}
|
}
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn fetch_system_data() -> SystemData {
|
pub fn fetch_system_data() -> SystemData {
|
||||||
let db_handle = borrow_db_checked().await;
|
let db_handle = borrow_db_checked();
|
||||||
SystemData::new(
|
SystemData::new(
|
||||||
db_handle.auth.as_ref().unwrap().client_id.clone(),
|
db_handle.auth.as_ref().unwrap().client_id.clone(),
|
||||||
db_handle.base_url.clone(),
|
db_handle.base_url.clone(),
|
||||||
|
|||||||
@ -3,19 +3,14 @@ use std::{
|
|||||||
mem::ManuallyDrop,
|
mem::ManuallyDrop,
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::{Arc, LazyLock},
|
sync::{Arc, LazyLock, RwLockReadGuard, RwLockWriteGuard},
|
||||||
};
|
};
|
||||||
|
|
||||||
use async_once_cell::OnceCell;
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use dropbreak::{DeSerError, DeSerializer, PathDatabase, RustbreakError};
|
use log::{debug, error, info, warn};
|
||||||
use log::{debug, info, warn};
|
|
||||||
use native_model::{Decode, Encode};
|
use native_model::{Decode, Encode};
|
||||||
|
use rustbreak::{DeSerError, DeSerializer, PathDatabase, RustbreakError};
|
||||||
use serde::{Serialize, de::DeserializeOwned};
|
use serde::{Serialize, de::DeserializeOwned};
|
||||||
use tokio::{
|
|
||||||
spawn,
|
|
||||||
sync::{RwLockReadGuard, RwLockWriteGuard},
|
|
||||||
};
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::DB;
|
use crate::DB;
|
||||||
@ -32,15 +27,15 @@ pub struct DropDatabaseSerializer;
|
|||||||
impl<T: native_model::Model + Serialize + DeserializeOwned> DeSerializer<T>
|
impl<T: native_model::Model + Serialize + DeserializeOwned> DeSerializer<T>
|
||||||
for DropDatabaseSerializer
|
for DropDatabaseSerializer
|
||||||
{
|
{
|
||||||
fn serialize(&self, val: &T) -> dropbreak::error::DeSerResult<Vec<u8>> {
|
fn serialize(&self, val: &T) -> rustbreak::error::DeSerResult<Vec<u8>> {
|
||||||
native_model::rmp_serde_1_3::RmpSerde::encode(val)
|
native_model::rmp_serde_1_3::RmpSerde::encode(val)
|
||||||
.map_err(|e| DeSerError::Internal(e.to_string()))
|
.map_err(|e| DeSerError::Internal(e.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize<R: std::io::Read>(&self, mut s: R) -> dropbreak::error::DeSerResult<T> {
|
fn deserialize<R: std::io::Read>(&self, mut s: R) -> rustbreak::error::DeSerResult<T> {
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
s.read_to_end(&mut buf)
|
s.read_to_end(&mut buf)
|
||||||
.map_err(|e| dropbreak::error::DeSerError::Other(e.into()))?;
|
.map_err(|e| rustbreak::error::DeSerError::Other(e.into()))?;
|
||||||
let val = native_model::rmp_serde_1_3::RmpSerde::decode(buf)
|
let val = native_model::rmp_serde_1_3::RmpSerde::decode(buf)
|
||||||
.map_err(|e| DeSerError::Internal(e.to_string()))?;
|
.map_err(|e| DeSerError::Internal(e.to_string()))?;
|
||||||
Ok(val)
|
Ok(val)
|
||||||
@ -48,33 +43,15 @@ impl<T: native_model::Model + Serialize + DeserializeOwned> DeSerializer<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub type DatabaseInterface =
|
pub type DatabaseInterface =
|
||||||
dropbreak::Database<Database, dropbreak::backend::PathBackend, DropDatabaseSerializer>;
|
rustbreak::Database<Database, rustbreak::backend::PathBackend, DropDatabaseSerializer>;
|
||||||
|
|
||||||
pub struct OnceCellDatabase(OnceCell<DatabaseInterface>);
|
|
||||||
impl OnceCellDatabase {
|
|
||||||
pub const fn new() -> Self {
|
|
||||||
Self(OnceCell::new())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn init(&self, init: impl Future<Output = DatabaseInterface>) {
|
|
||||||
self.0.get_or_init(init).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a> Deref for OnceCellDatabase {
|
|
||||||
type Target = DatabaseInterface;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
self.0.get().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait DatabaseImpls {
|
pub trait DatabaseImpls {
|
||||||
async fn set_up_database() -> DatabaseInterface;
|
fn set_up_database() -> DatabaseInterface;
|
||||||
async fn database_is_set_up(&self) -> bool;
|
fn database_is_set_up(&self) -> bool;
|
||||||
async fn fetch_base_url(&self) -> Url;
|
fn fetch_base_url(&self) -> Url;
|
||||||
}
|
}
|
||||||
impl DatabaseImpls for DatabaseInterface {
|
impl DatabaseImpls for DatabaseInterface {
|
||||||
async fn set_up_database() -> DatabaseInterface {
|
fn set_up_database() -> DatabaseInterface {
|
||||||
let db_path = DATA_ROOT_DIR.join("drop.db");
|
let db_path = DATA_ROOT_DIR.join("drop.db");
|
||||||
let games_base_dir = DATA_ROOT_DIR.join("games");
|
let games_base_dir = DATA_ROOT_DIR.join("games");
|
||||||
let logs_root_dir = DATA_ROOT_DIR.join("logs");
|
let logs_root_dir = DATA_ROOT_DIR.join("logs");
|
||||||
@ -90,40 +67,38 @@ impl DatabaseImpls for DatabaseInterface {
|
|||||||
|
|
||||||
let exists = fs::exists(db_path.clone()).unwrap();
|
let exists = fs::exists(db_path.clone()).unwrap();
|
||||||
|
|
||||||
match exists {
|
if exists {
|
||||||
true => match PathDatabase::load_from_path(db_path.clone()).await {
|
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).await,
|
Err(e) => handle_invalid_database(e, db_path, games_base_dir, cache_dir),
|
||||||
},
|
}
|
||||||
false => {
|
} 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)
|
PathDatabase::create_at_path(db_path, default).expect("Database could not be created")
|
||||||
.await
|
|
||||||
.expect("Database could not be created")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn database_is_set_up(&self) -> bool {
|
fn database_is_set_up(&self) -> bool {
|
||||||
!self.borrow_data().await.base_url.is_empty()
|
!self.borrow_data().unwrap().base_url.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_base_url(&self) -> Url {
|
fn fetch_base_url(&self) -> Url {
|
||||||
let handle = self.borrow_data().await;
|
let handle = self.borrow_data().unwrap();
|
||||||
Url::parse(&handle.base_url).unwrap()
|
Url::parse(&handle.base_url).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_invalid_database(
|
// TODO: Make the error relelvant rather than just assume that it's a Deserialize error
|
||||||
|
fn handle_invalid_database(
|
||||||
_e: RustbreakError,
|
_e: RustbreakError,
|
||||||
db_path: PathBuf,
|
db_path: PathBuf,
|
||||||
games_base_dir: PathBuf,
|
games_base_dir: PathBuf,
|
||||||
cache_dir: PathBuf,
|
cache_dir: PathBuf,
|
||||||
) -> dropbreak::Database<Database, dropbreak::backend::PathBackend, DropDatabaseSerializer> {
|
) -> rustbreak::Database<Database, rustbreak::backend::PathBackend, DropDatabaseSerializer> {
|
||||||
warn!("{_e}");
|
warn!("{_e}");
|
||||||
let new_path = {
|
let new_path = {
|
||||||
let time = Utc::now().timestamp();
|
let time = Utc::now().timestamp();
|
||||||
@ -140,23 +115,14 @@ async fn handle_invalid_database(
|
|||||||
cache_dir,
|
cache_dir,
|
||||||
);
|
);
|
||||||
|
|
||||||
PathDatabase::create_at_path(db_path, db)
|
PathDatabase::create_at_path(db_path, db).expect("Database could not be created")
|
||||||
.await
|
|
||||||
.expect("Database could not be created")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// To automatically save the database upon drop
|
// To automatically save the database upon drop
|
||||||
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 = RwLockWriteGuard<'a, Database>;
|
type Target = Database;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a> Deref for DBRead<'a> {
|
|
||||||
type Target = RwLockReadGuard<'a, Database>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.0
|
&self.0
|
||||||
@ -167,26 +133,45 @@ impl<'a> DerefMut for DBWrite<'a> {
|
|||||||
&mut self.0
|
&mut self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<'a> Drop for DBWrite<'a> {
|
impl<'a> Deref for DBRead<'a> {
|
||||||
|
type Target = Database;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Drop for DBWrite<'_> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
ManuallyDrop::drop(&mut self.0);
|
ManuallyDrop::drop(&mut self.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
spawn(async {
|
match DB.save() {
|
||||||
match DB.save().await {
|
Ok(()) => {}
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
error!("database failed to save with error {e}");
|
||||||
panic!("database failed to save with error {e}")
|
panic!("database failed to save with error {e}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub async fn borrow_db_checked<'a>() -> DBRead<'a> {
|
|
||||||
DBRead(DB.borrow_data().await)
|
pub fn borrow_db_checked<'a>() -> DBRead<'a> {
|
||||||
|
match DB.borrow_data() {
|
||||||
|
Ok(data) => DBRead(data),
|
||||||
|
Err(e) => {
|
||||||
|
error!("database borrow failed with error {e}");
|
||||||
|
panic!("database borrow failed with error {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn borrow_db_mut_checked<'a>() -> DBWrite<'a> {
|
pub fn borrow_db_mut_checked<'a>() -> DBWrite<'a> {
|
||||||
DBWrite(ManuallyDrop::new(DB.borrow_data_mut().await))
|
match DB.borrow_data_mut() {
|
||||||
|
Ok(data) => DBWrite(ManuallyDrop::new(data)),
|
||||||
|
Err(e) => {
|
||||||
|
error!("database borrow mut failed with error {e}");
|
||||||
|
panic!("database borrow mut failed with error {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,3 +2,4 @@ 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,8 +1,14 @@
|
|||||||
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};
|
||||||
|
|
||||||
@ -18,16 +24,14 @@ 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, process::Command};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
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::*;
|
use super::{Deserialize, Serialize, native_model};
|
||||||
|
|
||||||
fn default_template() -> String {
|
fn default_template() -> String {
|
||||||
"{}".to_owned()
|
"{}".to_owned()
|
||||||
@ -115,6 +119,7 @@ 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 {},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,7 +179,10 @@ pub mod data {
|
|||||||
|
|
||||||
use serde_with::serde_as;
|
use serde_with::serde_as;
|
||||||
|
|
||||||
use super::*;
|
use super::{
|
||||||
|
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)]
|
||||||
@ -206,7 +214,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: crate::database::models::Database::create_new_compat_info(),
|
compat_info: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -283,7 +291,10 @@ pub mod data {
|
|||||||
mod v3 {
|
mod v3 {
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use super::*;
|
use super::{
|
||||||
|
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 {
|
||||||
@ -297,6 +308,7 @@ 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 {
|
||||||
@ -306,22 +318,13 @@ 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: Database::create_new_compat_info(),
|
compat_info: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Database {
|
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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>,
|
||||||
@ -336,11 +339,11 @@ pub mod data {
|
|||||||
transient_statuses: HashMap::new(),
|
transient_statuses: HashMap::new(),
|
||||||
},
|
},
|
||||||
prev_database,
|
prev_database,
|
||||||
base_url: "".to_owned(),
|
base_url: String::new(),
|
||||||
auth: None,
|
auth: None,
|
||||||
settings: Settings::default(),
|
settings: Settings::default(),
|
||||||
cache_dir,
|
cache_dir,
|
||||||
compat_info: Database::create_new_compat_info(),
|
compat_info: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
52
src-tauri/src/database/scan.rs
Normal file
52
src-tauri/src/database/scan.rs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,33 +1,31 @@
|
|||||||
use crate::{database::models::data::DownloadableMetadata, DropFunctionState};
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::{database::models::data::DownloadableMetadata, AppState};
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn pause_downloads(state: tauri::State<'_, DropFunctionState<'_>>) -> Result<(), ()> {
|
pub fn pause_downloads(state: tauri::State<'_, Mutex<AppState>>) {
|
||||||
state.lock().await.download_manager.pause_downloads();
|
state.lock().unwrap().download_manager.pause_downloads();
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn resume_downloads(state: tauri::State<'_, DropFunctionState<'_>>) -> Result<(), ()> {
|
pub fn resume_downloads(state: tauri::State<'_, Mutex<AppState>>) {
|
||||||
state.lock().await.download_manager.resume_downloads();
|
state.lock().unwrap().download_manager.resume_downloads();
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn move_download_in_queue(
|
pub fn move_download_in_queue(
|
||||||
state: tauri::State<'_, DropFunctionState<'_>>,
|
state: tauri::State<'_, Mutex<AppState>>,
|
||||||
old_index: usize,
|
old_index: usize,
|
||||||
new_index: usize,
|
new_index: usize,
|
||||||
) -> Result<(), ()> {
|
) {
|
||||||
state
|
state
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.unwrap()
|
||||||
.download_manager
|
.download_manager
|
||||||
.rearrange(old_index, new_index);
|
.rearrange(old_index, new_index);
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn cancel_game(state: tauri::State<'_, DropFunctionState<'_>>, meta: DownloadableMetadata) -> Result<(), ()> {
|
pub fn cancel_game(state: tauri::State<'_, Mutex<AppState>>, meta: DownloadableMetadata) {
|
||||||
state.lock().await.download_manager.cancel(meta);
|
state.lock().unwrap().download_manager.cancel(meta);
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +1,14 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
sync::{
|
sync::{
|
||||||
Arc,
|
Arc, Mutex,
|
||||||
mpsc::{Receiver, Sender, channel},
|
mpsc::{Receiver, Sender, channel},
|
||||||
},
|
},
|
||||||
|
thread::{JoinHandle, spawn},
|
||||||
};
|
};
|
||||||
|
|
||||||
use ::futures::future::join_all;
|
|
||||||
use log::{debug, error, info, warn};
|
use log::{debug, error, info, warn};
|
||||||
use tauri::{AppHandle, Emitter};
|
use tauri::{AppHandle, Emitter};
|
||||||
use tokio::{runtime::Runtime, sync::Mutex};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
database::models::data::DownloadableMetadata,
|
database::models::data::DownloadableMetadata,
|
||||||
@ -70,15 +69,14 @@ Behold, my madness - quexeky
|
|||||||
pub struct DownloadManagerBuilder {
|
pub struct DownloadManagerBuilder {
|
||||||
download_agent_registry: HashMap<DownloadableMetadata, DownloadAgent>,
|
download_agent_registry: HashMap<DownloadableMetadata, DownloadAgent>,
|
||||||
download_queue: Queue,
|
download_queue: Queue,
|
||||||
command_receiver: Mutex<Receiver<DownloadManagerSignal>>,
|
command_receiver: Receiver<DownloadManagerSignal>,
|
||||||
sender: Sender<DownloadManagerSignal>,
|
sender: Sender<DownloadManagerSignal>,
|
||||||
progress: CurrentProgressObject,
|
progress: CurrentProgressObject,
|
||||||
status: Arc<Mutex<DownloadManagerStatus>>,
|
status: Arc<Mutex<DownloadManagerStatus>>,
|
||||||
app_handle: AppHandle,
|
app_handle: AppHandle,
|
||||||
runtime: Runtime,
|
|
||||||
|
|
||||||
current_download_agent: Option<DownloadAgent>, // Should be the only download agent in the map with the "Go" flag
|
current_download_agent: Option<DownloadAgent>, // Should be the only download agent in the map with the "Go" flag
|
||||||
current_download_thread: Mutex<Option<tokio::task::JoinHandle<()>>>,
|
current_download_thread: Mutex<Option<JoinHandle<()>>>,
|
||||||
active_control_flag: Option<DownloadThreadControl>,
|
active_control_flag: Option<DownloadThreadControl>,
|
||||||
}
|
}
|
||||||
impl DownloadManagerBuilder {
|
impl DownloadManagerBuilder {
|
||||||
@ -91,110 +89,97 @@ impl DownloadManagerBuilder {
|
|||||||
let manager = Self {
|
let manager = Self {
|
||||||
download_agent_registry: HashMap::new(),
|
download_agent_registry: HashMap::new(),
|
||||||
download_queue: queue.clone(),
|
download_queue: queue.clone(),
|
||||||
command_receiver: Mutex::new(command_receiver),
|
command_receiver,
|
||||||
status: status.clone(),
|
status: status.clone(),
|
||||||
sender: command_sender.clone(),
|
sender: command_sender.clone(),
|
||||||
progress: active_progress.clone(),
|
progress: active_progress.clone(),
|
||||||
app_handle,
|
app_handle,
|
||||||
runtime: tokio::runtime::Builder::new_multi_thread()
|
|
||||||
.worker_threads(1)
|
|
||||||
.enable_all()
|
|
||||||
.build()
|
|
||||||
.unwrap(),
|
|
||||||
|
|
||||||
current_download_agent: None,
|
current_download_agent: None,
|
||||||
current_download_thread: Mutex::new(None),
|
current_download_thread: Mutex::new(None),
|
||||||
active_control_flag: None,
|
active_control_flag: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let terminator = tauri::async_runtime::spawn(async {
|
let terminator = spawn(|| manager.manage_queue());
|
||||||
if let Err(_err) = manager.manage_queue().await {
|
|
||||||
panic!("download manager exited with error");
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
DownloadManager::new(terminator, queue, active_progress, command_sender)
|
DownloadManager::new(terminator, queue, active_progress, command_sender)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_status(&self, status: DownloadManagerStatus) {
|
fn set_status(&self, status: DownloadManagerStatus) {
|
||||||
*self.status.lock().await = status;
|
*self.status.lock().unwrap() = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn remove_and_cleanup_front_download(
|
fn remove_and_cleanup_front_download(&mut self, meta: &DownloadableMetadata) -> DownloadAgent {
|
||||||
&mut self,
|
|
||||||
meta: &DownloadableMetadata,
|
|
||||||
) -> DownloadAgent {
|
|
||||||
self.download_queue.pop_front();
|
self.download_queue.pop_front();
|
||||||
let download_agent = self.download_agent_registry.remove(meta).unwrap();
|
let download_agent = self.download_agent_registry.remove(meta).unwrap();
|
||||||
self.cleanup_current_download().await;
|
self.cleanup_current_download();
|
||||||
download_agent
|
download_agent
|
||||||
}
|
}
|
||||||
|
|
||||||
// CAREFUL WITH THIS FUNCTION
|
// CAREFUL WITH THIS FUNCTION
|
||||||
// Make sure the download thread is terminated
|
// Make sure the download thread is terminated
|
||||||
async fn cleanup_current_download(&mut self) {
|
fn cleanup_current_download(&mut self) {
|
||||||
self.active_control_flag = None;
|
self.active_control_flag = None;
|
||||||
*self.progress.lock().await = None;
|
*self.progress.lock().unwrap() = None;
|
||||||
self.current_download_agent = None;
|
self.current_download_agent = None;
|
||||||
|
|
||||||
let mut download_thread_lock = self.current_download_thread.lock().await;
|
let mut download_thread_lock = self.current_download_thread.lock().unwrap();
|
||||||
*download_thread_lock = None;
|
*download_thread_lock = None;
|
||||||
|
drop(download_thread_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn stop_and_wait_current_download(&self) {
|
fn stop_and_wait_current_download(&self) {
|
||||||
self.set_status(DownloadManagerStatus::Paused).await;
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut download_thread_lock = self.current_download_thread.lock().await;
|
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() {
|
||||||
current_download_thread.await.unwrap();
|
current_download_thread.join().unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn manage_queue(mut self) -> Result<(), ()> {
|
fn manage_queue(mut self) -> Result<(), ()> {
|
||||||
loop {
|
loop {
|
||||||
let signal = match self.command_receiver.lock().await.recv() {
|
let signal = match self.command_receiver.recv() {
|
||||||
Ok(signal) => signal,
|
Ok(signal) => signal,
|
||||||
Err(_) => return Err(()),
|
Err(_) => return Err(()),
|
||||||
};
|
};
|
||||||
|
|
||||||
match signal {
|
match signal {
|
||||||
DownloadManagerSignal::Go => {
|
DownloadManagerSignal::Go => {
|
||||||
self.manage_go_signal().await;
|
self.manage_go_signal();
|
||||||
}
|
}
|
||||||
DownloadManagerSignal::Stop => {
|
DownloadManagerSignal::Stop => {
|
||||||
self.manage_stop_signal().await;
|
self.manage_stop_signal();
|
||||||
}
|
}
|
||||||
DownloadManagerSignal::Completed(meta) => {
|
DownloadManagerSignal::Completed(meta) => {
|
||||||
self.manage_completed_signal(meta).await;
|
self.manage_completed_signal(meta);
|
||||||
}
|
}
|
||||||
DownloadManagerSignal::Queue(download_agent) => {
|
DownloadManagerSignal::Queue(download_agent) => {
|
||||||
self.manage_queue_signal(download_agent).await;
|
self.manage_queue_signal(download_agent);
|
||||||
}
|
}
|
||||||
DownloadManagerSignal::Error(e) => {
|
DownloadManagerSignal::Error(e) => {
|
||||||
self.manage_error_signal(e).await;
|
self.manage_error_signal(e);
|
||||||
}
|
}
|
||||||
DownloadManagerSignal::UpdateUIQueue => {
|
DownloadManagerSignal::UpdateUIQueue => {
|
||||||
self.push_ui_queue_update().await;
|
self.push_ui_queue_update();
|
||||||
}
|
}
|
||||||
DownloadManagerSignal::UpdateUIStats(kbs, time) => {
|
DownloadManagerSignal::UpdateUIStats(kbs, time) => {
|
||||||
self.push_ui_stats_update(kbs, time);
|
self.push_ui_stats_update(kbs, time);
|
||||||
}
|
}
|
||||||
DownloadManagerSignal::Finish => {
|
DownloadManagerSignal::Finish => {
|
||||||
self.stop_and_wait_current_download().await;
|
self.stop_and_wait_current_download();
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
DownloadManagerSignal::Cancel(meta) => {
|
DownloadManagerSignal::Cancel(meta) => {
|
||||||
self.manage_cancel_signal(&meta).await;
|
self.manage_cancel_signal(&meta);
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn manage_queue_signal(&mut self, download_agent: DownloadAgent) {
|
}
|
||||||
|
}
|
||||||
|
fn manage_queue_signal(&mut self, download_agent: DownloadAgent) {
|
||||||
debug!("got signal Queue");
|
debug!("got signal Queue");
|
||||||
let meta = download_agent.metadata();
|
let meta = download_agent.metadata();
|
||||||
|
|
||||||
@ -205,7 +190,7 @@ impl DownloadManagerBuilder {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
download_agent.on_initialised(&self.app_handle).await;
|
download_agent.on_initialised(&self.app_handle);
|
||||||
self.download_queue.append(meta.clone());
|
self.download_queue.append(meta.clone());
|
||||||
self.download_agent_registry.insert(meta, download_agent);
|
self.download_agent_registry.insert(meta, download_agent);
|
||||||
|
|
||||||
@ -214,7 +199,7 @@ impl DownloadManagerBuilder {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn manage_go_signal(&mut self) {
|
fn manage_go_signal(&mut self) {
|
||||||
debug!("got signal Go");
|
debug!("got signal Go");
|
||||||
if self.download_agent_registry.is_empty() {
|
if self.download_agent_registry.is_empty() {
|
||||||
debug!(
|
debug!(
|
||||||
@ -248,117 +233,110 @@ impl DownloadManagerBuilder {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
self.active_control_flag = Some(download_agent.control_flag().await);
|
self.active_control_flag = Some(download_agent.control_flag());
|
||||||
self.current_download_agent = Some(download_agent.clone());
|
self.current_download_agent = Some(download_agent.clone());
|
||||||
|
|
||||||
info!("fetched control flag");
|
|
||||||
|
|
||||||
let sender = self.sender.clone();
|
let sender = self.sender.clone();
|
||||||
|
|
||||||
info!("cloned sender");
|
let mut download_thread_lock = self.current_download_thread.lock().unwrap();
|
||||||
|
|
||||||
let mut download_thread_lock = self.current_download_thread.lock().await;
|
|
||||||
info!("acquired download thread lock");
|
|
||||||
let app_handle = self.app_handle.clone();
|
let app_handle = self.app_handle.clone();
|
||||||
|
|
||||||
info!("starting download agent thread");
|
*download_thread_lock = Some(spawn(move || {
|
||||||
|
loop {
|
||||||
*download_thread_lock = Some(self.runtime.spawn(async move {
|
let download_result = match download_agent.download(&app_handle) {
|
||||||
info!("started download agent thread");
|
|
||||||
match download_agent.download(&app_handle).await {
|
|
||||||
// Ok(true) is for completed and exited properly
|
// Ok(true) is for completed and exited properly
|
||||||
Ok(true) => {
|
Ok(v) => v,
|
||||||
debug!("download {:?} has completed", download_agent.metadata());
|
Err(e) => {
|
||||||
match download_agent.validate().await {
|
error!("download {:?} has error {}", download_agent.metadata(), &e);
|
||||||
Ok(true) => {
|
download_agent.on_error(&app_handle, &e);
|
||||||
download_agent.on_complete(&app_handle).await;
|
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
||||||
sender
|
return;
|
||||||
.send(DownloadManagerSignal::Completed(download_agent.metadata()))
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
Ok(false) => {
|
};
|
||||||
download_agent.on_incomplete(&app_handle).await;
|
|
||||||
|
// If the download gets cancelled
|
||||||
|
// immediately return, on_cancelled gets called for us earlier
|
||||||
|
if !download_result {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let validate_result = match download_agent.validate(&app_handle) {
|
||||||
|
Ok(v) => v,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(
|
error!(
|
||||||
"download {:?} has validation error {}",
|
"download {:?} has validation error {}",
|
||||||
download_agent.metadata(),
|
download_agent.metadata(),
|
||||||
&e
|
&e
|
||||||
);
|
);
|
||||||
download_agent.on_error(&app_handle, &e).await;
|
download_agent.on_error(&app_handle, &e);
|
||||||
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
|
||||||
// Ok(false) is for incomplete but exited properly
|
if validate_result {
|
||||||
Ok(false) => {
|
download_agent.on_complete(&app_handle);
|
||||||
debug!("Donwload agent finished incomplete");
|
sender
|
||||||
download_agent.on_incomplete(&app_handle).await;
|
.send(DownloadManagerSignal::Completed(download_agent.metadata()))
|
||||||
}
|
.unwrap();
|
||||||
Err(e) => {
|
|
||||||
error!("download {:?} has error {}", download_agent.metadata(), &e);
|
|
||||||
download_agent.on_error(&app_handle, &e).await;
|
|
||||||
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sender.send(DownloadManagerSignal::UpdateUIQueue).unwrap();
|
sender.send(DownloadManagerSignal::UpdateUIQueue).unwrap();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
self.set_status(DownloadManagerStatus::Downloading).await;
|
self.set_status(DownloadManagerStatus::Downloading);
|
||||||
let active_control_flag = self.active_control_flag.clone().unwrap();
|
let active_control_flag = self.active_control_flag.clone().unwrap();
|
||||||
active_control_flag.set(DownloadThreadControlFlag::Go);
|
active_control_flag.set(DownloadThreadControlFlag::Go);
|
||||||
|
|
||||||
// download_thread_lock.take().unwrap().await;
|
|
||||||
}
|
}
|
||||||
async fn manage_stop_signal(&mut self) {
|
fn manage_stop_signal(&mut self) {
|
||||||
debug!("got signal Stop");
|
debug!("got signal Stop");
|
||||||
|
|
||||||
if let Some(active_control_flag) = self.active_control_flag.clone() {
|
if let Some(active_control_flag) = self.active_control_flag.clone() {
|
||||||
self.set_status(DownloadManagerStatus::Paused).await;
|
self.set_status(DownloadManagerStatus::Paused);
|
||||||
active_control_flag.set(DownloadThreadControlFlag::Stop);
|
active_control_flag.set(DownloadThreadControlFlag::Stop);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn manage_completed_signal(&mut self, meta: DownloadableMetadata) {
|
fn manage_completed_signal(&mut self, meta: DownloadableMetadata) {
|
||||||
debug!("got signal Completed");
|
debug!("got signal Completed");
|
||||||
if let Some(interface) = &self.current_download_agent
|
if let Some(interface) = &self.current_download_agent
|
||||||
&& interface.metadata() == meta
|
&& interface.metadata() == meta
|
||||||
{
|
{
|
||||||
self.remove_and_cleanup_front_download(&meta).await;
|
self.remove_and_cleanup_front_download(&meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.push_ui_queue_update().await;
|
self.push_ui_queue_update();
|
||||||
self.sender.send(DownloadManagerSignal::Go).unwrap();
|
self.sender.send(DownloadManagerSignal::Go).unwrap();
|
||||||
}
|
}
|
||||||
async fn manage_error_signal(&mut self, error: ApplicationDownloadError) {
|
fn manage_error_signal(&mut self, error: ApplicationDownloadError) {
|
||||||
debug!("got signal Error");
|
debug!("got signal Error");
|
||||||
if let Some(current_agent) = self.current_download_agent.clone() {
|
if let Some(current_agent) = self.current_download_agent.clone() {
|
||||||
current_agent.on_error(&self.app_handle, &error).await;
|
current_agent.on_error(&self.app_handle, &error);
|
||||||
|
|
||||||
self.stop_and_wait_current_download().await;
|
self.stop_and_wait_current_download();
|
||||||
self.remove_and_cleanup_front_download(¤t_agent.metadata())
|
self.remove_and_cleanup_front_download(¤t_agent.metadata());
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
self.set_status(DownloadManagerStatus::Error).await;
|
self.set_status(DownloadManagerStatus::Error);
|
||||||
}
|
}
|
||||||
async fn manage_cancel_signal(&mut self, meta: &DownloadableMetadata) {
|
fn manage_cancel_signal(&mut self, meta: &DownloadableMetadata) {
|
||||||
debug!("got signal Cancel");
|
debug!("got signal Cancel");
|
||||||
|
|
||||||
if let Some(current_download) = &self.current_download_agent {
|
if let Some(current_download) = &self.current_download_agent {
|
||||||
if ¤t_download.metadata() == meta {
|
if ¤t_download.metadata() == meta {
|
||||||
self.set_status(DownloadManagerStatus::Paused).await;
|
self.set_status(DownloadManagerStatus::Paused);
|
||||||
current_download.on_cancelled(&self.app_handle).await;
|
current_download.on_cancelled(&self.app_handle);
|
||||||
self.stop_and_wait_current_download().await;
|
self.stop_and_wait_current_download();
|
||||||
|
|
||||||
self.download_queue.pop_front();
|
self.download_queue.pop_front();
|
||||||
|
|
||||||
self.cleanup_current_download().await;
|
self.cleanup_current_download();
|
||||||
debug!("current download queue: {:?}", self.download_queue.read());
|
debug!("current download queue: {:?}", self.download_queue.read());
|
||||||
}
|
}
|
||||||
// TODO: Collapse these two into a single if statement somehow
|
// TODO: Collapse these two into a single if statement somehow
|
||||||
else if let Some(download_agent) = self.download_agent_registry.get(meta) {
|
else if let Some(download_agent) = self.download_agent_registry.get(meta) {
|
||||||
let index = self.download_queue.get_by_meta(meta);
|
let index = self.download_queue.get_by_meta(meta);
|
||||||
if let Some(index) = index {
|
if let Some(index) = index {
|
||||||
download_agent.on_cancelled(&self.app_handle).await;
|
download_agent.on_cancelled(&self.app_handle);
|
||||||
let _ = self.download_queue.edit().remove(index).unwrap();
|
let _ = self.download_queue.edit().remove(index).unwrap();
|
||||||
let removed = self.download_agent_registry.remove(meta);
|
let removed = self.download_agent_registry.remove(meta);
|
||||||
debug!(
|
debug!(
|
||||||
@ -371,7 +349,7 @@ impl DownloadManagerBuilder {
|
|||||||
} else if let Some(download_agent) = self.download_agent_registry.get(meta) {
|
} else if let Some(download_agent) = self.download_agent_registry.get(meta) {
|
||||||
let index = self.download_queue.get_by_meta(meta);
|
let index = self.download_queue.get_by_meta(meta);
|
||||||
if let Some(index) = index {
|
if let Some(index) = index {
|
||||||
download_agent.on_cancelled(&self.app_handle).await;
|
download_agent.on_cancelled(&self.app_handle);
|
||||||
let _ = self.download_queue.edit().remove(index).unwrap();
|
let _ = self.download_queue.edit().remove(index).unwrap();
|
||||||
let removed = self.download_agent_registry.remove(meta);
|
let removed = self.download_agent_registry.remove(meta);
|
||||||
debug!(
|
debug!(
|
||||||
@ -381,26 +359,28 @@ impl DownloadManagerBuilder {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.push_ui_queue_update().await;
|
self.push_ui_queue_update();
|
||||||
}
|
}
|
||||||
fn push_ui_stats_update(&self, kbs: usize, time: usize) {
|
fn push_ui_stats_update(&self, kbs: usize, time: usize) {
|
||||||
let event_data = StatsUpdateEvent { speed: kbs, time };
|
let event_data = StatsUpdateEvent { speed: kbs, time };
|
||||||
|
|
||||||
self.app_handle.emit("update_stats", event_data).unwrap();
|
self.app_handle.emit("update_stats", event_data).unwrap();
|
||||||
}
|
}
|
||||||
async fn push_ui_queue_update(&self) {
|
fn push_ui_queue_update(&self) {
|
||||||
let queue = &self.download_queue.read();
|
let queue = &self.download_queue.read();
|
||||||
let queue_objs = join_all(queue.iter().map(async |key| {
|
let queue_objs = queue
|
||||||
|
.iter()
|
||||||
|
.map(|key| {
|
||||||
let val = self.download_agent_registry.get(key).unwrap();
|
let val = self.download_agent_registry.get(key).unwrap();
|
||||||
QueueUpdateEventQueueData {
|
QueueUpdateEventQueueData {
|
||||||
meta: DownloadableMetadata::clone(key),
|
meta: DownloadableMetadata::clone(key),
|
||||||
status: val.status().await,
|
status: val.status(),
|
||||||
progress: val.progress().await.get_progress(),
|
progress: val.progress().get_progress(),
|
||||||
current: val.progress().await.sum(),
|
current: val.progress().sum(),
|
||||||
max: val.progress().await.get_max(),
|
max: val.progress().get_max(),
|
||||||
}
|
}
|
||||||
}))
|
})
|
||||||
.await;
|
.collect();
|
||||||
|
|
||||||
let event_data = QueueUpdateEvent { queue: queue_objs };
|
let event_data = QueueUpdateEvent { queue: queue_objs };
|
||||||
self.app_handle.emit("update_queue", event_data).unwrap();
|
self.app_handle.emit("update_queue", event_data).unwrap();
|
||||||
|
|||||||
@ -1,15 +1,16 @@
|
|||||||
use std::{
|
use std::{
|
||||||
|
any::Any,
|
||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
sync::{
|
sync::{
|
||||||
mpsc::{SendError, Sender},
|
mpsc::{SendError, Sender},
|
||||||
Mutex, MutexGuard,
|
Mutex, MutexGuard,
|
||||||
},
|
},
|
||||||
|
thread::JoinHandle,
|
||||||
};
|
};
|
||||||
|
|
||||||
use log::{debug, info};
|
use log::{debug, info};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tauri::async_runtime::JoinHandle;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
database::models::data::DownloadableMetadata,
|
database::models::data::DownloadableMetadata,
|
||||||
@ -22,13 +23,13 @@ use super::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub enum DownloadManagerSignal {
|
pub enum DownloadManagerSignal {
|
||||||
/// Resumes (or starts) the DownloadManager
|
/// Resumes (or starts) the `DownloadManager`
|
||||||
Go,
|
Go,
|
||||||
/// Pauses the DownloadManager
|
/// Pauses the `DownloadManager`
|
||||||
Stop,
|
Stop,
|
||||||
/// Called when a DownloadAgent has fully completed a download.
|
/// Called when a `DownloadAgent` has fully completed a download.
|
||||||
Completed(DownloadableMetadata),
|
Completed(DownloadableMetadata),
|
||||||
/// Generates and appends a DownloadAgent
|
/// Generates and appends a `DownloadAgent`
|
||||||
/// to the registry and queue
|
/// to the registry and queue
|
||||||
Queue(DownloadAgent),
|
Queue(DownloadAgent),
|
||||||
/// Tells the Manager to stop the current
|
/// Tells the Manager to stop the current
|
||||||
@ -69,14 +70,14 @@ pub enum DownloadStatus {
|
|||||||
Error,
|
Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Accessible front-end for the DownloadManager
|
/// Accessible front-end for the `DownloadManager`
|
||||||
///
|
///
|
||||||
/// The system works entirely through signals, both internally and externally,
|
/// The system works entirely through signals, both internally and externally,
|
||||||
/// all of which are accessible through the DownloadManagerSignal type, but
|
/// all of which are accessible through the `DownloadManagerSignal` type, but
|
||||||
/// should not be used directly. Rather, signals are abstracted through this
|
/// should not be used directly. Rather, signals are abstracted through this
|
||||||
/// interface.
|
/// interface.
|
||||||
///
|
///
|
||||||
/// The actual download queue may be accessed through the .edit() function,
|
/// The actual download queue may be accessed through the .`edit()` function,
|
||||||
/// which provides raw access to the underlying queue.
|
/// which provides raw access to the underlying queue.
|
||||||
/// THIS EDITING IS BLOCKING!!!
|
/// THIS EDITING IS BLOCKING!!!
|
||||||
pub struct DownloadManager {
|
pub struct DownloadManager {
|
||||||
@ -117,8 +118,8 @@ impl DownloadManager {
|
|||||||
pub fn read_queue(&self) -> VecDeque<DownloadableMetadata> {
|
pub fn read_queue(&self) -> VecDeque<DownloadableMetadata> {
|
||||||
self.download_queue.read()
|
self.download_queue.read()
|
||||||
}
|
}
|
||||||
pub async fn get_current_download_progress(&self) -> Option<f64> {
|
pub fn get_current_download_progress(&self) -> Option<f64> {
|
||||||
let progress_object = (*self.progress.lock().await).clone()?;
|
let progress_object = (*self.progress.lock().unwrap()).clone()?;
|
||||||
Some(progress_object.get_progress())
|
Some(progress_object.get_progress())
|
||||||
}
|
}
|
||||||
pub fn rearrange_string(&self, meta: &DownloadableMetadata, new_index: usize) {
|
pub fn rearrange_string(&self, meta: &DownloadableMetadata, new_index: usize) {
|
||||||
@ -138,7 +139,7 @@ impl DownloadManager {
|
|||||||
pub fn rearrange(&self, current_index: usize, new_index: usize) {
|
pub fn rearrange(&self, current_index: usize, new_index: usize) {
|
||||||
if current_index == new_index {
|
if current_index == new_index {
|
||||||
return;
|
return;
|
||||||
};
|
}
|
||||||
|
|
||||||
let needs_pause = current_index == 0 || new_index == 0;
|
let needs_pause = current_index == 0 || new_index == 0;
|
||||||
if needs_pause {
|
if needs_pause {
|
||||||
@ -170,19 +171,19 @@ impl DownloadManager {
|
|||||||
pub fn resume_downloads(&self) {
|
pub fn resume_downloads(&self) {
|
||||||
self.command_sender.send(DownloadManagerSignal::Go).unwrap();
|
self.command_sender.send(DownloadManagerSignal::Go).unwrap();
|
||||||
}
|
}
|
||||||
pub async fn ensure_terminated(&self) -> Result<Result<(), ()>, tauri::Error> {
|
pub fn ensure_terminated(&self) -> Result<Result<(), ()>, Box<dyn Any + Send>> {
|
||||||
self.command_sender
|
self.command_sender
|
||||||
.send(DownloadManagerSignal::Finish)
|
.send(DownloadManagerSignal::Finish)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let terminator = self.terminator.lock().unwrap().take();
|
let terminator = self.terminator.lock().unwrap().take();
|
||||||
terminator.unwrap().await
|
terminator.unwrap().join()
|
||||||
}
|
}
|
||||||
pub fn get_sender(&self) -> Sender<DownloadManagerSignal> {
|
pub fn get_sender(&self) -> Sender<DownloadManagerSignal> {
|
||||||
self.command_sender.clone()
|
self.command_sender.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Takes in the locked value from .edit() and attempts to
|
/// Takes in the locked value from .`edit()` and attempts to
|
||||||
/// get the index of whatever id is passed in
|
/// get the index of whatever id is passed in
|
||||||
fn get_index_from_id(
|
fn get_index_from_id(
|
||||||
queue: &mut MutexGuard<'_, VecDeque<DownloadableMetadata>>,
|
queue: &mut MutexGuard<'_, VecDeque<DownloadableMetadata>>,
|
||||||
|
|||||||
@ -12,17 +12,16 @@ use super::{
|
|||||||
util::{download_thread_control_flag::DownloadThreadControl, progress_object::ProgressObject},
|
util::{download_thread_control_flag::DownloadThreadControl, progress_object::ProgressObject},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
pub trait Downloadable: Send + Sync {
|
pub trait Downloadable: Send + Sync {
|
||||||
async fn download(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError>;
|
fn download(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError>;
|
||||||
async fn progress(&self) -> Arc<ProgressObject>;
|
fn validate(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError>;
|
||||||
async fn control_flag(&self) -> DownloadThreadControl;
|
|
||||||
async fn validate(&self) -> Result<bool, ApplicationDownloadError>;
|
fn progress(&self) -> Arc<ProgressObject>;
|
||||||
async fn status(&self) -> DownloadStatus;
|
fn control_flag(&self) -> DownloadThreadControl;
|
||||||
|
fn status(&self) -> DownloadStatus;
|
||||||
fn metadata(&self) -> DownloadableMetadata;
|
fn metadata(&self) -> DownloadableMetadata;
|
||||||
async fn on_initialised(&self, app_handle: &AppHandle);
|
fn on_initialised(&self, app_handle: &AppHandle);
|
||||||
async fn on_error(&self, app_handle: &AppHandle, error: &ApplicationDownloadError);
|
fn on_error(&self, app_handle: &AppHandle, error: &ApplicationDownloadError);
|
||||||
async fn on_complete(&self, app_handle: &AppHandle);
|
fn on_complete(&self, app_handle: &AppHandle);
|
||||||
async fn on_incomplete(&self, app_handle: &AppHandle);
|
fn on_cancelled(&self, app_handle: &AppHandle);
|
||||||
async fn on_cancelled(&self, app_handle: &AppHandle);
|
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user