mirror of
https://github.com/Drop-OSS/drop-app.git
synced 2025-11-15 17:21:19 +10:00
Compare commits
56 Commits
v0.3.0
...
beeth-appi
| Author | SHA1 | Date | |
|---|---|---|---|
| 97083db78d | |||
| ef2153611e | |||
| 48ccf638ce | |||
| 4361798661 | |||
| 732df1ddb9 | |||
| 4389d8beb8 | |||
| 38dd6a2c64 | |||
| 6038509a0c | |||
| 919148c6c2 | |||
| b3f73d6d1e | |||
| 41d23730cc | |||
| d19c5b8e97 | |||
| c78079e4be | |||
| 92e42262c1 | |||
| 106c229c8f | |||
| 527476e7c1 | |||
| 81d324a026 | |||
| 3d5de9bec8 | |||
| 380084bb95 | |||
| 50d76bb76f | |||
| de3be25914 | |||
| 95fc7d9605 | |||
| f9c45053cc | |||
| 683e3ac7f2 | |||
| fe586be658 | |||
| aa034c21a9 | |||
| 50f7955734 | |||
| 73608ab85b | |||
| 1ed29aaa12 | |||
| 849dc26677 | |||
| 7db2e42006 | |||
| 61fa43dd86 | |||
| 301152a69f | |||
| 5e945734c7 | |||
| 53211e94fe | |||
| af49a6751f | |||
| 9e93ec688c | |||
| a70c2c64e4 | |||
| d63d6efa3e | |||
| 51555e43ee | |||
| cb7c297443 | |||
| 4484de0507 | |||
| 4599e5713c | |||
| 263cceb17f | |||
| 0249a3248f | |||
| bf4b2e0ad6 | |||
| 7555cc3cdf | |||
| 6f30d248eb | |||
| cc562662f0 | |||
| dbe8c8df4d | |||
| 35f49b8811 | |||
| cc5339a389 | |||
| 6104bfda72 | |||
| be688cb18f | |||
| 13cc69f10e | |||
| 574782f445 |
64
.github/workflows/release.yml
vendored
64
.github/workflows/release.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
|||||||
- platform: 'ubuntu-22.04' # for Tauri v1 you could replace this with ubuntu-20.04.
|
- platform: 'ubuntu-22.04' # for Tauri v1 you could replace this with ubuntu-20.04.
|
||||||
args: ''
|
args: ''
|
||||||
- platform: 'ubuntu-22.04-arm'
|
- platform: 'ubuntu-22.04-arm'
|
||||||
args: '--target aarch64-unknown-linux-gnu'
|
args: ''
|
||||||
- platform: 'windows-latest'
|
- platform: 'windows-latest'
|
||||||
args: ''
|
args: ''
|
||||||
|
|
||||||
@ -40,6 +40,7 @@ jobs:
|
|||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: lts/*
|
node-version: lts/*
|
||||||
|
cache: 'yarn'
|
||||||
|
|
||||||
- name: install Rust nightly
|
- name: install Rust nightly
|
||||||
uses: dtolnay/rust-toolchain@nightly
|
uses: dtolnay/rust-toolchain@nightly
|
||||||
@ -51,19 +52,74 @@ jobs:
|
|||||||
if: matrix.platform == 'ubuntu-22.04' || matrix.platform == 'ubuntu-22.04-arm' # This must match the platform value defined above.
|
if: matrix.platform == 'ubuntu-22.04' || matrix.platform == 'ubuntu-22.04-arm' # This must match the platform value defined above.
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf binutils fuse
|
||||||
# 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: Rust cache
|
||||||
|
uses: swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
workspaces: './src-tauri -> target'
|
||||||
|
|
||||||
- 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
|
- id: build
|
||||||
|
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 }}
|
||||||
with:
|
with:
|
||||||
tagName: v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version.
|
tagName: v__VERSION__ # update the below "upload artifacts to GitHub" if this gets updated
|
||||||
releaseName: 'Auto-release v__VERSION__'
|
releaseName: 'Auto-release v__VERSION__'
|
||||||
releaseBody: 'See the assets to download this version and install. This release was created automatically.'
|
releaseBody: 'See the assets to download this version and install. This release was created automatically.'
|
||||||
releaseDraft: false
|
releaseDraft: false
|
||||||
prerelease: true
|
prerelease: true
|
||||||
args: ${{ matrix.args }}
|
args: ${{ matrix.args }}
|
||||||
|
|
||||||
|
- name: Bundle AppImage
|
||||||
|
if: matrix.platform == 'ubuntu-22.04' || matrix.platform == 'ubuntu-22.04-arm'
|
||||||
|
run: |
|
||||||
|
./build_appimage.sh --nobuild
|
||||||
|
|
||||||
|
- name: Upload binaries to release
|
||||||
|
if: matrix.platform == 'ubuntu-22.04' || matrix.platform == 'ubuntu-22.04-arm'
|
||||||
|
uses: svenstaro/upload-release-action@v2
|
||||||
|
with:
|
||||||
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
file: build/appimage/*.AppImage
|
||||||
|
file_glob: true
|
||||||
|
release_id: ${{ steps.build.outputs.releaseId }}
|
||||||
|
overwrite: true
|
||||||
|
|||||||
9
.gitignore
vendored
9
.gitignore
vendored
@ -27,3 +27,12 @@ dist-ssr
|
|||||||
|
|
||||||
src-tauri/flamegraph.svg
|
src-tauri/flamegraph.svg
|
||||||
src-tauri/perf*
|
src-tauri/perf*
|
||||||
|
|
||||||
|
build/appimage/*
|
||||||
|
!build/appimage/drop-app.d
|
||||||
|
|
||||||
|
build/appimage/drop-app.d/usr/bin/*
|
||||||
|
!build/appimage/drop-app.d/usr/bin/.gitkeep
|
||||||
|
|
||||||
|
build/appimage/drop-app.d/usr/lib/*
|
||||||
|
!build/appimage/drop-app.d/usr/lib/.gitkeep
|
||||||
1
app.vue
1
app.vue
@ -27,6 +27,7 @@ try {
|
|||||||
console.error("failed to parse state", e);
|
console.error("failed to parse state", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is inefficient but apparently we do it lol
|
||||||
router.beforeEach(async () => {
|
router.beforeEach(async () => {
|
||||||
try {
|
try {
|
||||||
state.value = JSON.parse(await invoke("fetch_state"));
|
state.value = JSON.parse(await invoke("fetch_state"));
|
||||||
|
|||||||
1
build/appimage/drop-app.d/.DirIcon
Symbolic link
1
build/appimage/drop-app.d/.DirIcon
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
drop-oss-app.png
|
||||||
8
build/appimage/drop-app.d/AppRun
Executable file
8
build/appimage/drop-app.d/AppRun
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
program="drop-app"
|
||||||
|
|
||||||
|
# point to libraries and run
|
||||||
|
LD_LIBRARY_PATH=$APPDIR/usr/lib:/usr/local/lib:/usr/lib $APPDIR/usr/bin/$program $@
|
||||||
|
|
||||||
|
# vim:ft=sh
|
||||||
8
build/appimage/drop-app.d/drop-oss-app.desktop
Normal file
8
build/appimage/drop-app.d/drop-oss-app.desktop
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Name=Drop Desktop App
|
||||||
|
Comment=The client application for the open-source, self-hosted game distribution platform Drop.
|
||||||
|
Exec=AppRun
|
||||||
|
Icon=drop-oss-app
|
||||||
|
Type=Application
|
||||||
|
Categories=Game;Network
|
||||||
|
MimeType=x-scheme-handler/drop
|
||||||
BIN
build/appimage/drop-app.d/drop-oss-app.png
Normal file
BIN
build/appimage/drop-app.d/drop-oss-app.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
0
build/appimage/drop-app.d/usr/bin/.gitkeep
Normal file
0
build/appimage/drop-app.d/usr/bin/.gitkeep
Normal file
0
build/appimage/drop-app.d/usr/lib/.gitkeep
Normal file
0
build/appimage/drop-app.d/usr/lib/.gitkeep
Normal file
42
build_appimage.sh
Executable file
42
build_appimage.sh
Executable file
@ -0,0 +1,42 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# run this from the root of the git repo to make this work
|
||||||
|
|
||||||
|
arch="$(uname -m)"
|
||||||
|
git_dir="$PWD"
|
||||||
|
target_dir="$git_dir/src-tauri/target"
|
||||||
|
appimage_dir="$git_dir/build/appimage"
|
||||||
|
appdir="$appimage_dir/drop-app.d"
|
||||||
|
|
||||||
|
build() {
|
||||||
|
# set up the repo
|
||||||
|
git submodule init
|
||||||
|
git submodule update --recursive
|
||||||
|
|
||||||
|
# set up yarn and build
|
||||||
|
yarn
|
||||||
|
yarn tauri build
|
||||||
|
}
|
||||||
|
|
||||||
|
rm -f $appdir/usr/bin/* $appdir/usr/lib/*
|
||||||
|
|
||||||
|
if [[ ! "$1" == "--nobuild" ]]; then
|
||||||
|
build
|
||||||
|
fi
|
||||||
|
|
||||||
|
# install binaries in the appdir, then the libraries
|
||||||
|
cp $target_dir/release/drop-app $appdir/usr/bin
|
||||||
|
for lib_name in $(ldd "$target_dir/release/drop-app" | grep '=>' | awk '{ print $(NF-1) }');
|
||||||
|
do
|
||||||
|
echo $lib_name
|
||||||
|
sudo install -g 1000 -o 1000 -Dm755 "$(ls -L1 $lib_name)" $appdir/usr/lib
|
||||||
|
done
|
||||||
|
|
||||||
|
wget -O $appimage_dir/appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-$arch.AppImage
|
||||||
|
|
||||||
|
cd $appimage_dir
|
||||||
|
chmod u+x appimagetool
|
||||||
|
./appimagetool $appdir
|
||||||
|
|
||||||
|
ls "$appimage_dir/"
|
||||||
@ -1,52 +1,78 @@
|
|||||||
<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
|
||||||
styles[props.status.type],
|
type="button"
|
||||||
showDropdown ? 'rounded-l-md' : 'rounded-md',
|
@click="() => buttonActions[props.status.type]()"
|
||||||
'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',
|
:class="[
|
||||||
]">
|
styles[props.status.type],
|
||||||
<component :is="buttonIcons[props.status.type]" class="-mr-0.5 size-5" aria-hidden="true" />
|
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',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<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
|
||||||
styles[props.status.type],
|
:class="[
|
||||||
'inline-flex w-full h-full justify-center items-center rounded-r-md px-1 py-2 text-sm font-semibold shadow-sm group',
|
styles[props.status.type],
|
||||||
'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2',
|
'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',
|
||||||
|
]"
|
||||||
|
>
|
||||||
<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
|
||||||
active
|
@click="() => emit('options')"
|
||||||
? 'bg-zinc-800 text-zinc-100 outline-none'
|
:class="[
|
||||||
: 'text-zinc-400',
|
active
|
||||||
'w-full block px-4 py-2 text-sm inline-flex justify-between',
|
? 'bg-zinc-800 text-zinc-100 outline-none'
|
||||||
]">
|
: 'text-zinc-400',
|
||||||
Options
|
'w-full block px-4 py-2 text-sm inline-flex justify-between',
|
||||||
<Cog6ToothIcon class="size-5" />
|
]"
|
||||||
</button>
|
>
|
||||||
|
Options
|
||||||
|
<Cog6ToothIcon class="size-5" />
|
||||||
|
</button>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem v-slot="{ active }">
|
<MenuItem v-slot="{ active }">
|
||||||
<button @click="() => emit('uninstall')" :class="[
|
<button
|
||||||
active
|
@click="() => emit('uninstall')"
|
||||||
? 'bg-zinc-800 text-zinc-100 outline-none'
|
:class="[
|
||||||
: 'text-zinc-400',
|
active
|
||||||
'w-full block px-4 py-2 text-sm inline-flex justify-between',
|
? 'bg-zinc-800 text-zinc-100 outline-none'
|
||||||
]">
|
: 'text-zinc-400',
|
||||||
Uninstall
|
'w-full block px-4 py-2 text-sm inline-flex justify-between',
|
||||||
<TrashIcon class="size-5" />
|
]"
|
||||||
</button>
|
>
|
||||||
|
Uninstall
|
||||||
|
<TrashIcon class="size-5" />
|
||||||
|
</button>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</div>
|
</div>
|
||||||
</MenuItems>
|
</MenuItems>
|
||||||
@ -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>
|
||||||
|
|||||||
@ -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
|
||||||
|
@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>
|
||||||
|
<NuxtLink href="/auth/code" class="text-zinc-100 text-sm hover:text-zinc-300">
|
||||||
|
Use a code →
|
||||||
|
</NuxtLink>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</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...",
|
||||||
|
|||||||
@ -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" />
|
||||||
|
|||||||
Submodule drop-base updated: 04125e89be...26698e5b06
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "drop-app",
|
"name": "drop-app",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.3.0",
|
"version": "0.3.1-appimage",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nuxt build",
|
"build": "nuxt build",
|
||||||
|
|||||||
37
pages/auth/code.vue
Normal file
37
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>
|
||||||
@ -368,6 +368,13 @@
|
|||||||
</template>
|
</template>
|
||||||
</ModalTemplate>
|
</ModalTemplate>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Dear future DecDuck,
|
||||||
|
This v-if is necessary for Vue rendering reasons
|
||||||
|
(it tries to access the game version for not installed games)
|
||||||
|
You have already tried to remove it
|
||||||
|
Don't.
|
||||||
|
-->
|
||||||
<GameOptionsModal
|
<GameOptionsModal
|
||||||
v-if="status.type === GameStatusEnum.Installed"
|
v-if="status.type === GameStatusEnum.Installed"
|
||||||
v-model="configureModalOpen"
|
v-model="configureModalOpen"
|
||||||
|
|||||||
67
src-tauri/Cargo.lock
generated
67
src-tauri/Cargo.lock
generated
@ -345,6 +345,22 @@ dependencies = [
|
|||||||
"syn 2.0.101",
|
"syn 2.0.101",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-tungstenite"
|
||||||
|
version = "0.29.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef0f7efedeac57d9b26170f72965ecfd31473ca52ca7a64e925b0b6f5f079886"
|
||||||
|
dependencies = [
|
||||||
|
"atomic-waker",
|
||||||
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
"log",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tungstenite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atk"
|
name = "atk"
|
||||||
version = "0.18.2"
|
version = "0.18.2"
|
||||||
@ -1268,7 +1284,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "drop-app"
|
name = "drop-app"
|
||||||
version = "0.3.0"
|
version = "0.3.1-appimage"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atomic-instant-full",
|
"atomic-instant-full",
|
||||||
"bitcode",
|
"bitcode",
|
||||||
@ -1280,6 +1296,7 @@ dependencies = [
|
|||||||
"droplet-rs",
|
"droplet-rs",
|
||||||
"dynfmt",
|
"dynfmt",
|
||||||
"filetime",
|
"filetime",
|
||||||
|
"futures-lite",
|
||||||
"gethostname",
|
"gethostname",
|
||||||
"hex 0.4.3",
|
"hex 0.4.3",
|
||||||
"http 1.3.1",
|
"http 1.3.1",
|
||||||
@ -1289,6 +1306,7 @@ dependencies = [
|
|||||||
"log4rs",
|
"log4rs",
|
||||||
"md5",
|
"md5",
|
||||||
"native_model",
|
"native_model",
|
||||||
|
"page_size",
|
||||||
"parking_lot 0.12.3",
|
"parking_lot 0.12.3",
|
||||||
"rand 0.9.1",
|
"rand 0.9.1",
|
||||||
"rayon",
|
"rayon",
|
||||||
@ -1296,6 +1314,7 @@ dependencies = [
|
|||||||
"reqwest 0.12.16",
|
"reqwest 0.12.16",
|
||||||
"reqwest-middleware 0.4.2",
|
"reqwest-middleware 0.4.2",
|
||||||
"reqwest-middleware-cache",
|
"reqwest-middleware-cache",
|
||||||
|
"reqwest-websocket",
|
||||||
"rustbreak",
|
"rustbreak",
|
||||||
"rustix 0.38.44",
|
"rustix 0.38.44",
|
||||||
"schemars",
|
"schemars",
|
||||||
@ -3505,6 +3524,16 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "page_size"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pango"
|
name = "pango"
|
||||||
version = "0.18.3"
|
version = "0.18.3"
|
||||||
@ -4357,6 +4386,24 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "reqwest-websocket"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9f91a811daaa8b54faeaec9d507a336897a3d243834a4965254a17d39da8b5c9"
|
||||||
|
dependencies = [
|
||||||
|
"async-tungstenite",
|
||||||
|
"bytes",
|
||||||
|
"futures-util",
|
||||||
|
"reqwest 0.12.16",
|
||||||
|
"thiserror 2.0.12",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
"tracing",
|
||||||
|
"tungstenite",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rfd"
|
name = "rfd"
|
||||||
version = "0.15.3"
|
version = "0.15.3"
|
||||||
@ -5831,6 +5878,7 @@ checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"tokio",
|
"tokio",
|
||||||
@ -6009,6 +6057,23 @@ version = "0.2.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tungstenite"
|
||||||
|
version = "0.26.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"data-encoding",
|
||||||
|
"http 1.3.1",
|
||||||
|
"httparse",
|
||||||
|
"log",
|
||||||
|
"rand 0.9.1",
|
||||||
|
"sha1",
|
||||||
|
"thiserror 2.0.12",
|
||||||
|
"utf-8",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typeid"
|
name = "typeid"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "drop-app"
|
name = "drop-app"
|
||||||
version = "0.3.0"
|
version = "0.3.1-appimage"
|
||||||
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"
|
||||||
@ -68,6 +68,9 @@ 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"
|
||||||
|
reqwest-websocket = "0.5.0"
|
||||||
|
futures-lite = "2.6.0"
|
||||||
|
page_size = "0.6.0"
|
||||||
# tailscale = { path = "./tailscale" }
|
# tailscale = { path = "./tailscale" }
|
||||||
|
|
||||||
[dependencies.dynfmt]
|
[dependencies.dynfmt]
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
fn main() {
|
fn main() {
|
||||||
tauri_build::build()
|
tauri_build::build();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,8 +13,8 @@ pub fn cleanup_and_exit(app: &AppHandle, state: &tauri::State<'_, std::sync::Mut
|
|||||||
let download_manager = state.lock().unwrap().download_manager.clone();
|
let download_manager = state.lock().unwrap().download_manager.clone();
|
||||||
match download_manager.ensure_terminated() {
|
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:?}"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ 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::{
|
||||||
@ -59,6 +59,8 @@ pub fn add_download_dir(new_dir: PathBuf) -> Result<(), DownloadManagerError<()>
|
|||||||
lock.applications.install_dirs.push(new_dir);
|
lock.applications.install_dirs.push(new_dir);
|
||||||
drop(lock);
|
drop(lock);
|
||||||
|
|
||||||
|
scan_install_dirs();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ use chrono::Utc;
|
|||||||
use log::{debug, error, info, warn};
|
use log::{debug, error, info, warn};
|
||||||
use native_model::{Decode, Encode};
|
use native_model::{Decode, Encode};
|
||||||
use rustbreak::{DeSerError, DeSerializer, PathDatabase, RustbreakError};
|
use rustbreak::{DeSerError, DeSerializer, PathDatabase, RustbreakError};
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{Serialize, de::DeserializeOwned};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::DB;
|
use crate::DB;
|
||||||
@ -67,20 +67,18 @@ 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()) {
|
match PathDatabase::load_from_path(db_path.clone()) {
|
||||||
Ok(db) => db,
|
Ok(db) => db,
|
||||||
Err(e) => handle_invalid_database(e, db_path, games_base_dir, cache_dir),
|
Err(e) => handle_invalid_database(e, db_path, games_base_dir, cache_dir),
|
||||||
},
|
|
||||||
false => {
|
|
||||||
let default = Database::new(games_base_dir, None, cache_dir);
|
|
||||||
debug!(
|
|
||||||
"Creating database at path {}",
|
|
||||||
db_path.as_os_str().to_str().unwrap()
|
|
||||||
);
|
|
||||||
PathDatabase::create_at_path(db_path, default)
|
|
||||||
.expect("Database could not be created")
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
let default = Database::new(games_base_dir, None, cache_dir);
|
||||||
|
debug!(
|
||||||
|
"Creating database at path {}",
|
||||||
|
db_path.as_os_str().to_str().unwrap()
|
||||||
|
);
|
||||||
|
PathDatabase::create_at_path(db_path, default).expect("Database could not be created")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,14 +122,7 @@ fn handle_invalid_database(
|
|||||||
pub struct DBRead<'a>(RwLockReadGuard<'a, Database>);
|
pub struct DBRead<'a>(RwLockReadGuard<'a, Database>);
|
||||||
pub struct DBWrite<'a>(ManuallyDrop<RwLockWriteGuard<'a, Database>>);
|
pub struct DBWrite<'a>(ManuallyDrop<RwLockWriteGuard<'a, Database>>);
|
||||||
impl<'a> Deref for DBWrite<'a> {
|
impl<'a> Deref for DBWrite<'a> {
|
||||||
type Target = 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
|
||||||
@ -142,14 +133,21 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
match DB.save() {
|
match DB.save() {
|
||||||
Ok(_) => {}
|
Ok(()) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("database failed to save with error {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}")
|
||||||
@ -157,6 +155,7 @@ impl<'a> Drop for DBWrite<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn borrow_db_checked<'a>() -> DBRead<'a> {
|
pub fn borrow_db_checked<'a>() -> DBRead<'a> {
|
||||||
match DB.borrow_data() {
|
match DB.borrow_data() {
|
||||||
Ok(data) => DBRead(data),
|
Ok(data) => DBRead(data),
|
||||||
|
|||||||
@ -2,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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,12 +4,12 @@ use crate::{database::models::data::DownloadableMetadata, AppState};
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn pause_downloads(state: tauri::State<'_, Mutex<AppState>>) {
|
pub fn pause_downloads(state: tauri::State<'_, Mutex<AppState>>) {
|
||||||
state.lock().unwrap().download_manager.pause_downloads()
|
state.lock().unwrap().download_manager.pause_downloads();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn resume_downloads(state: tauri::State<'_, Mutex<AppState>>) {
|
pub fn resume_downloads(state: tauri::State<'_, Mutex<AppState>>) {
|
||||||
state.lock().unwrap().download_manager.resume_downloads()
|
state.lock().unwrap().download_manager.resume_downloads();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@ -22,10 +22,10 @@ pub fn move_download_in_queue(
|
|||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.download_manager
|
.download_manager
|
||||||
.rearrange(old_index, new_index)
|
.rearrange(old_index, new_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn cancel_game(state: tauri::State<'_, Mutex<AppState>>, meta: DownloadableMetadata) {
|
pub fn cancel_game(state: tauri::State<'_, Mutex<AppState>>, meta: DownloadableMetadata) {
|
||||||
state.lock().unwrap().download_manager.cancel(meta)
|
state.lock().unwrap().download_manager.cancel(meta);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -176,7 +176,7 @@ impl DownloadManagerBuilder {
|
|||||||
DownloadManagerSignal::Cancel(meta) => {
|
DownloadManagerSignal::Cancel(meta) => {
|
||||||
self.manage_cancel_signal(&meta);
|
self.manage_cancel_signal(&meta);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn manage_queue_signal(&mut self, download_agent: DownloadAgent) {
|
fn manage_queue_signal(&mut self, download_agent: DownloadAgent) {
|
||||||
@ -254,13 +254,13 @@ impl DownloadManagerBuilder {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// If the download gets cancel
|
// If the download gets cancelled
|
||||||
|
// immediately return, on_cancelled gets called for us earlier
|
||||||
if !download_result {
|
if !download_result {
|
||||||
download_agent.on_incomplete(&app_handle);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let validate_result = match download_agent.validate() {
|
let validate_result = match download_agent.validate(&app_handle) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(
|
error!(
|
||||||
|
|||||||
@ -23,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
|
||||||
@ -70,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 {
|
||||||
@ -139,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 {
|
||||||
@ -183,7 +183,7 @@ impl DownloadManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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>>,
|
||||||
|
|||||||
@ -14,14 +14,14 @@ use super::{
|
|||||||
|
|
||||||
pub trait Downloadable: Send + Sync {
|
pub trait Downloadable: Send + Sync {
|
||||||
fn download(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError>;
|
fn download(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError>;
|
||||||
|
fn validate(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError>;
|
||||||
|
|
||||||
fn progress(&self) -> Arc<ProgressObject>;
|
fn progress(&self) -> Arc<ProgressObject>;
|
||||||
fn control_flag(&self) -> DownloadThreadControl;
|
fn control_flag(&self) -> DownloadThreadControl;
|
||||||
fn validate(&self) -> Result<bool, ApplicationDownloadError>;
|
|
||||||
fn status(&self) -> DownloadStatus;
|
fn status(&self) -> DownloadStatus;
|
||||||
fn metadata(&self) -> DownloadableMetadata;
|
fn metadata(&self) -> DownloadableMetadata;
|
||||||
fn on_initialised(&self, app_handle: &AppHandle);
|
fn on_initialised(&self, app_handle: &AppHandle);
|
||||||
fn on_error(&self, app_handle: &AppHandle, error: &ApplicationDownloadError);
|
fn on_error(&self, app_handle: &AppHandle, error: &ApplicationDownloadError);
|
||||||
fn on_complete(&self, app_handle: &AppHandle);
|
fn on_complete(&self, app_handle: &AppHandle);
|
||||||
fn on_incomplete(&self, app_handle: &AppHandle);
|
|
||||||
fn on_cancelled(&self, app_handle: &AppHandle);
|
fn on_cancelled(&self, app_handle: &AppHandle);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,10 +22,7 @@ impl From<DownloadThreadControlFlag> for bool {
|
|||||||
/// false => Stop
|
/// false => Stop
|
||||||
impl From<bool> for DownloadThreadControlFlag {
|
impl From<bool> for DownloadThreadControlFlag {
|
||||||
fn from(value: bool) -> Self {
|
fn from(value: bool) -> Self {
|
||||||
match value {
|
if value { DownloadThreadControlFlag::Go } else { DownloadThreadControlFlag::Stop }
|
||||||
true => DownloadThreadControlFlag::Go,
|
|
||||||
false => DownloadThreadControlFlag::Stop,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,9 +38,9 @@ impl DownloadThreadControl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn get(&self) -> DownloadThreadControlFlag {
|
pub fn get(&self) -> DownloadThreadControlFlag {
|
||||||
self.inner.load(Ordering::Relaxed).into()
|
self.inner.load(Ordering::Acquire).into()
|
||||||
}
|
}
|
||||||
pub fn set(&self, flag: DownloadThreadControlFlag) {
|
pub fn set(&self, flag: DownloadThreadControlFlag) {
|
||||||
self.inner.store(flag.into(), Ordering::Relaxed);
|
self.inner.store(flag.into(), Ordering::Release);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,20 +39,20 @@ impl ProgressHandle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn set(&self, amount: usize) {
|
pub fn set(&self, amount: usize) {
|
||||||
self.progress.store(amount, Ordering::Relaxed);
|
self.progress.store(amount, Ordering::Release);
|
||||||
}
|
}
|
||||||
pub fn add(&self, amount: usize) {
|
pub fn add(&self, amount: usize) {
|
||||||
self.progress
|
self.progress
|
||||||
.fetch_add(amount, std::sync::atomic::Ordering::Relaxed);
|
.fetch_add(amount, std::sync::atomic::Ordering::AcqRel);
|
||||||
calculate_update(&self.progress_object);
|
calculate_update(&self.progress_object);
|
||||||
}
|
}
|
||||||
pub fn skip(&self, amount: usize) {
|
pub fn skip(&self, amount: usize) {
|
||||||
self.progress
|
self.progress
|
||||||
.fetch_add(amount, std::sync::atomic::Ordering::Relaxed);
|
.fetch_add(amount, std::sync::atomic::Ordering::Acquire);
|
||||||
// Offset the bytes at last offset by this amount
|
// Offset the bytes at last offset by this amount
|
||||||
self.progress_object
|
self.progress_object
|
||||||
.bytes_last_update
|
.bytes_last_update
|
||||||
.fetch_add(amount, Ordering::Relaxed);
|
.fetch_add(amount, Ordering::Acquire);
|
||||||
// Dont' fire update
|
// Dont' fire update
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,7 +60,6 @@ impl ProgressHandle {
|
|||||||
impl ProgressObject {
|
impl ProgressObject {
|
||||||
pub fn new(max: usize, length: usize, sender: Sender<DownloadManagerSignal>) -> Self {
|
pub fn new(max: usize, length: usize, sender: Sender<DownloadManagerSignal>) -> Self {
|
||||||
let arr = Mutex::new((0..length).map(|_| Arc::new(AtomicUsize::new(0))).collect());
|
let arr = Mutex::new((0..length).map(|_| Arc::new(AtomicUsize::new(0))).collect());
|
||||||
// TODO: consolidate this calculation with the set_max function below
|
|
||||||
Self {
|
Self {
|
||||||
max: Arc::new(Mutex::new(max)),
|
max: Arc::new(Mutex::new(max)),
|
||||||
progress_instances: Arc::new(arr),
|
progress_instances: Arc::new(arr),
|
||||||
@ -81,19 +80,18 @@ impl ProgressObject {
|
|||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|instance| instance.load(Ordering::Relaxed))
|
.map(|instance| instance.load(Ordering::Acquire))
|
||||||
.sum()
|
.sum()
|
||||||
}
|
}
|
||||||
pub fn reset(&self, size: usize) {
|
pub fn reset(&self) {
|
||||||
self.set_time_now();
|
self.set_time_now();
|
||||||
self.set_size(size);
|
|
||||||
self.bytes_last_update.store(0, Ordering::Release);
|
self.bytes_last_update.store(0, Ordering::Release);
|
||||||
self.rolling.reset();
|
self.rolling.reset();
|
||||||
self.progress_instances
|
self.progress_instances
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.for_each(|x| x.store(0, Ordering::Release));
|
.for_each(|x| x.store(0, Ordering::SeqCst));
|
||||||
}
|
}
|
||||||
pub fn get_max(&self) -> usize {
|
pub fn get_max(&self) -> usize {
|
||||||
*self.max.lock().unwrap()
|
*self.max.lock().unwrap()
|
||||||
@ -127,7 +125,7 @@ pub fn calculate_update(progress: &ProgressObject) {
|
|||||||
let max = progress.get_max();
|
let max = progress.get_max();
|
||||||
let bytes_at_last_update = progress
|
let bytes_at_last_update = progress
|
||||||
.bytes_last_update
|
.bytes_last_update
|
||||||
.swap(current_bytes_downloaded, Ordering::Relaxed);
|
.swap(current_bytes_downloaded, Ordering::Acquire);
|
||||||
|
|
||||||
let bytes_since_last_update = current_bytes_downloaded - bytes_at_last_update;
|
let bytes_since_last_update = current_bytes_downloaded - bytes_at_last_update;
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,7 @@ pub struct RollingProgressWindow<const S: usize> {
|
|||||||
impl<const S: usize> RollingProgressWindow<S> {
|
impl<const S: usize> RollingProgressWindow<S> {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
window: Arc::new([(); S].map(|_| AtomicUsize::new(0))),
|
window: Arc::new([(); S].map(|()| AtomicUsize::new(0))),
|
||||||
current: Arc::new(AtomicUsize::new(0)),
|
current: Arc::new(AtomicUsize::new(0)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -26,7 +26,7 @@ impl<const S: usize> RollingProgressWindow<S> {
|
|||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter(|(i, _)| i < ¤t)
|
.filter(|(i, _)| i < ¤t)
|
||||||
.map(|(_, x)| x.load(Ordering::Relaxed))
|
.map(|(_, x)| x.load(Ordering::Acquire))
|
||||||
.sum::<usize>()
|
.sum::<usize>()
|
||||||
/ S
|
/ S
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,6 @@ use serde_with::SerializeDisplay;
|
|||||||
pub enum ProcessError {
|
pub enum ProcessError {
|
||||||
NotInstalled,
|
NotInstalled,
|
||||||
AlreadyRunning,
|
AlreadyRunning,
|
||||||
NotDownloaded,
|
|
||||||
InvalidID,
|
InvalidID,
|
||||||
InvalidVersion,
|
InvalidVersion,
|
||||||
IOError(Error),
|
IOError(Error),
|
||||||
@ -20,7 +19,6 @@ impl Display for ProcessError {
|
|||||||
let s = match self {
|
let s = match self {
|
||||||
ProcessError::NotInstalled => "Game not installed",
|
ProcessError::NotInstalled => "Game not installed",
|
||||||
ProcessError::AlreadyRunning => "Game already running",
|
ProcessError::AlreadyRunning => "Game already running",
|
||||||
ProcessError::NotDownloaded => "Game not downloaded",
|
|
||||||
ProcessError::InvalidID => "Invalid game ID",
|
ProcessError::InvalidID => "Invalid game ID",
|
||||||
ProcessError::InvalidVersion => "Invalid game version",
|
ProcessError::InvalidVersion => "Invalid game version",
|
||||||
ProcessError::IOError(error) => &error.to_string(),
|
ProcessError::IOError(error) => &error.to_string(),
|
||||||
|
|||||||
@ -13,6 +13,7 @@ use super::drop_server_error::DropServerError;
|
|||||||
#[derive(Debug, SerializeDisplay)]
|
#[derive(Debug, SerializeDisplay)]
|
||||||
pub enum RemoteAccessError {
|
pub enum RemoteAccessError {
|
||||||
FetchError(Arc<reqwest::Error>),
|
FetchError(Arc<reqwest::Error>),
|
||||||
|
FetchErrorWS(Arc<reqwest_websocket::Error>),
|
||||||
ParsingError(ParseError),
|
ParsingError(ParseError),
|
||||||
InvalidEndpoint,
|
InvalidEndpoint,
|
||||||
HandshakeFailed(String),
|
HandshakeFailed(String),
|
||||||
@ -29,7 +30,10 @@ impl Display for RemoteAccessError {
|
|||||||
match self {
|
match self {
|
||||||
RemoteAccessError::FetchError(error) => {
|
RemoteAccessError::FetchError(error) => {
|
||||||
if error.is_connect() {
|
if error.is_connect() {
|
||||||
return write!(f, "Failed to connect to Drop server. Check if you access Drop through a browser, and then try again.");
|
return write!(
|
||||||
|
f,
|
||||||
|
"Failed to connect to Drop server. Check if you access Drop through a browser, and then try again."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
write!(
|
write!(
|
||||||
@ -38,24 +42,44 @@ impl Display for RemoteAccessError {
|
|||||||
error,
|
error,
|
||||||
error
|
error
|
||||||
.source()
|
.source()
|
||||||
.map(|e| e.to_string())
|
.map(std::string::ToString::to_string)
|
||||||
.or_else(|| Some("Unknown error".to_string()))
|
.or_else(|| Some("Unknown error".to_string()))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
|
RemoteAccessError::FetchErrorWS(error) => write!(
|
||||||
|
f,
|
||||||
|
"{}: {}",
|
||||||
|
error,
|
||||||
|
error
|
||||||
|
.source()
|
||||||
|
.map(|e| e.to_string())
|
||||||
|
.or_else(|| Some("Unknown error".to_string()))
|
||||||
|
.unwrap()
|
||||||
|
),
|
||||||
RemoteAccessError::ParsingError(parse_error) => {
|
RemoteAccessError::ParsingError(parse_error) => {
|
||||||
write!(f, "{parse_error}")
|
write!(f, "{parse_error}")
|
||||||
}
|
}
|
||||||
RemoteAccessError::InvalidEndpoint => write!(f, "invalid drop endpoint"),
|
RemoteAccessError::InvalidEndpoint => write!(f, "invalid drop endpoint"),
|
||||||
RemoteAccessError::HandshakeFailed(message) => write!(f, "failed to complete handshake: {message}"),
|
RemoteAccessError::HandshakeFailed(message) => {
|
||||||
|
write!(f, "failed to complete handshake: {message}")
|
||||||
|
}
|
||||||
RemoteAccessError::GameNotFound(id) => write!(f, "could not find game on server: {id}"),
|
RemoteAccessError::GameNotFound(id) => write!(f, "could not find game on server: {id}"),
|
||||||
RemoteAccessError::InvalidResponse(error) => write!(f, "server returned an invalid response: {}, {}", error.status_code, error.status_message),
|
RemoteAccessError::InvalidResponse(error) => write!(
|
||||||
RemoteAccessError::UnparseableResponse(error) => write!(f, "server returned an invalid response: {error}"),
|
|
||||||
RemoteAccessError::ManifestDownloadFailed(status, response) => write!(
|
|
||||||
f,
|
f,
|
||||||
"failed to download game manifest: {status} {response}"
|
"server returned an invalid response: {}, {}",
|
||||||
|
error.status_code, error.status_message
|
||||||
|
),
|
||||||
|
RemoteAccessError::UnparseableResponse(error) => {
|
||||||
|
write!(f, "server returned an invalid response: {error}")
|
||||||
|
}
|
||||||
|
RemoteAccessError::ManifestDownloadFailed(status, response) => {
|
||||||
|
write!(f, "failed to download game manifest: {status} {response}")
|
||||||
|
}
|
||||||
|
RemoteAccessError::OutOfSync => write!(
|
||||||
|
f,
|
||||||
|
"server's and client's time are out of sync. Please ensure they are within at least 30 seconds of each other"
|
||||||
),
|
),
|
||||||
RemoteAccessError::OutOfSync => write!(f, "server's and client's time are out of sync. Please ensure they are within at least 30 seconds of each other"),
|
|
||||||
RemoteAccessError::Cache(error) => write!(f, "Cache Error: {error}"),
|
RemoteAccessError::Cache(error) => write!(f, "Cache Error: {error}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,6 +90,11 @@ impl From<reqwest::Error> for RemoteAccessError {
|
|||||||
RemoteAccessError::FetchError(Arc::new(err))
|
RemoteAccessError::FetchError(Arc::new(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl From<reqwest_websocket::Error> for RemoteAccessError {
|
||||||
|
fn from(err: reqwest_websocket::Error) -> Self {
|
||||||
|
RemoteAccessError::FetchErrorWS(Arc::new(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
impl From<ParseError> for RemoteAccessError {
|
impl From<ParseError> for RemoteAccessError {
|
||||||
fn from(err: ParseError) -> Self {
|
fn from(err: ParseError) -> Self {
|
||||||
RemoteAccessError::ParsingError(err)
|
RemoteAccessError::ParsingError(err)
|
||||||
|
|||||||
@ -1,19 +1,18 @@
|
|||||||
use reqwest::blocking::Client;
|
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
DB,
|
||||||
database::db::DatabaseImpls,
|
database::db::DatabaseImpls,
|
||||||
error::remote_access_error::RemoteAccessError,
|
error::remote_access_error::RemoteAccessError,
|
||||||
remote::{auth::generate_authorization_header, requests::make_request},
|
remote::{auth::generate_authorization_header, requests::make_request, utils::DROP_CLIENT_SYNC},
|
||||||
DB,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::collection::{Collection, Collections};
|
use super::collection::{Collection, Collections};
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn fetch_collections() -> Result<Collections, RemoteAccessError> {
|
pub fn fetch_collections() -> Result<Collections, RemoteAccessError> {
|
||||||
let client = Client::new();
|
let client = DROP_CLIENT_SYNC.clone();
|
||||||
let response = make_request(&client, &["/api/v1/client/collection"], &[], |r| {
|
let response = make_request(&client, &["/api/v1/client/collection"], &[], |r| {
|
||||||
r.header("Authorization", generate_authorization_header())
|
r.header("Authorization", generate_authorization_header())
|
||||||
})?
|
})?
|
||||||
@ -24,7 +23,7 @@ pub fn fetch_collections() -> Result<Collections, RemoteAccessError> {
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn fetch_collection(collection_id: String) -> Result<Collection, RemoteAccessError> {
|
pub fn fetch_collection(collection_id: String) -> Result<Collection, RemoteAccessError> {
|
||||||
let client = Client::new();
|
let client = DROP_CLIENT_SYNC.clone();
|
||||||
let response = make_request(
|
let response = make_request(
|
||||||
&client,
|
&client,
|
||||||
&["/api/v1/client/collection/", &collection_id],
|
&["/api/v1/client/collection/", &collection_id],
|
||||||
@ -38,7 +37,7 @@ pub fn fetch_collection(collection_id: String) -> Result<Collection, RemoteAcces
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn create_collection(name: String) -> Result<Collection, RemoteAccessError> {
|
pub fn create_collection(name: String) -> Result<Collection, RemoteAccessError> {
|
||||||
let client = Client::new();
|
let client = DROP_CLIENT_SYNC.clone();
|
||||||
let base_url = DB.fetch_base_url();
|
let base_url = DB.fetch_base_url();
|
||||||
|
|
||||||
let base_url = Url::parse(&format!("{base_url}api/v1/client/collection/"))?;
|
let base_url = Url::parse(&format!("{base_url}api/v1/client/collection/"))?;
|
||||||
@ -57,7 +56,7 @@ pub fn add_game_to_collection(
|
|||||||
collection_id: String,
|
collection_id: String,
|
||||||
game_id: String,
|
game_id: String,
|
||||||
) -> Result<(), RemoteAccessError> {
|
) -> Result<(), RemoteAccessError> {
|
||||||
let client = Client::new();
|
let client = DROP_CLIENT_SYNC.clone();
|
||||||
let url = Url::parse(&format!(
|
let url = Url::parse(&format!(
|
||||||
"{}api/v1/client/collection/{}/entry/",
|
"{}api/v1/client/collection/{}/entry/",
|
||||||
DB.fetch_base_url(),
|
DB.fetch_base_url(),
|
||||||
@ -74,7 +73,7 @@ pub fn add_game_to_collection(
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn delete_collection(collection_id: String) -> Result<bool, RemoteAccessError> {
|
pub fn delete_collection(collection_id: String) -> Result<bool, RemoteAccessError> {
|
||||||
let client = Client::new();
|
let client = DROP_CLIENT_SYNC.clone();
|
||||||
let base_url = Url::parse(&format!(
|
let base_url = Url::parse(&format!(
|
||||||
"{}api/v1/client/collection/{}",
|
"{}api/v1/client/collection/{}",
|
||||||
DB.fetch_base_url(),
|
DB.fetch_base_url(),
|
||||||
@ -93,7 +92,7 @@ pub fn delete_game_in_collection(
|
|||||||
collection_id: String,
|
collection_id: String,
|
||||||
game_id: String,
|
game_id: String,
|
||||||
) -> Result<(), RemoteAccessError> {
|
) -> Result<(), RemoteAccessError> {
|
||||||
let client = Client::new();
|
let client = DROP_CLIENT_SYNC.clone();
|
||||||
let base_url = Url::parse(&format!(
|
let base_url = Url::parse(&format!(
|
||||||
"{}api/v1/client/collection/{}/entry",
|
"{}api/v1/client/collection/{}/entry",
|
||||||
DB.fetch_base_url(),
|
DB.fetch_base_url(),
|
||||||
|
|||||||
@ -3,19 +3,23 @@ use std::sync::Mutex;
|
|||||||
use tauri::AppHandle;
|
use tauri::AppHandle;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
database::models::data::GameVersion,
|
AppState,
|
||||||
|
database::{
|
||||||
|
db::borrow_db_checked,
|
||||||
|
models::data::GameVersion,
|
||||||
|
},
|
||||||
error::{library_error::LibraryError, remote_access_error::RemoteAccessError},
|
error::{library_error::LibraryError, remote_access_error::RemoteAccessError},
|
||||||
games::library::{
|
games::library::{
|
||||||
fetch_game_logic_offline, fetch_library_logic_offline, get_current_meta,
|
fetch_game_logic_offline, fetch_library_logic_offline, get_current_meta,
|
||||||
uninstall_game_logic,
|
uninstall_game_logic,
|
||||||
},
|
},
|
||||||
offline, AppState,
|
offline,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
library::{
|
library::{
|
||||||
fetch_game_logic, fetch_game_verion_options_logic, fetch_library_logic, FetchGameStruct,
|
FetchGameStruct, Game, fetch_game_logic, fetch_game_verion_options_logic,
|
||||||
Game,
|
fetch_library_logic,
|
||||||
},
|
},
|
||||||
state::{GameStatusManager, GameStatusWithTransient},
|
state::{GameStatusManager, GameStatusWithTransient},
|
||||||
};
|
};
|
||||||
@ -48,7 +52,8 @@ pub fn fetch_game(
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn fetch_game_status(id: String) -> GameStatusWithTransient {
|
pub fn fetch_game_status(id: String) -> GameStatusWithTransient {
|
||||||
GameStatusManager::fetch_state(&id)
|
let db_handle = borrow_db_checked();
|
||||||
|
GameStatusManager::fetch_state(&id, &db_handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
|||||||
@ -12,9 +12,13 @@ use crate::download_manager::util::progress_object::{ProgressHandle, ProgressObj
|
|||||||
use crate::error::application_download_error::ApplicationDownloadError;
|
use crate::error::application_download_error::ApplicationDownloadError;
|
||||||
use crate::error::remote_access_error::RemoteAccessError;
|
use crate::error::remote_access_error::RemoteAccessError;
|
||||||
use crate::games::downloads::manifest::{DropDownloadContext, DropManifest};
|
use crate::games::downloads::manifest::{DropDownloadContext, DropManifest};
|
||||||
use crate::games::downloads::validate::game_validate_logic;
|
use crate::games::downloads::validate::validate_game_chunk;
|
||||||
use crate::games::library::{on_game_complete, on_game_incomplete, push_game_update};
|
use crate::games::library::{
|
||||||
|
on_game_complete, push_game_update, set_partially_installed,
|
||||||
|
};
|
||||||
|
use crate::games::state::GameStatusManager;
|
||||||
use crate::remote::requests::make_request;
|
use crate::remote::requests::make_request;
|
||||||
|
use crate::remote::utils::DROP_CLIENT_SYNC;
|
||||||
use log::{debug, error, info};
|
use log::{debug, error, info};
|
||||||
use rayon::ThreadPoolBuilder;
|
use rayon::ThreadPoolBuilder;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@ -40,7 +44,7 @@ pub struct GameDownloadAgent {
|
|||||||
pub manifest: Mutex<Option<DropManifest>>,
|
pub manifest: Mutex<Option<DropManifest>>,
|
||||||
pub progress: Arc<ProgressObject>,
|
pub progress: Arc<ProgressObject>,
|
||||||
sender: Sender<DownloadManagerSignal>,
|
sender: Sender<DownloadManagerSignal>,
|
||||||
pub stored_manifest: DropData,
|
pub dropdata: DropData,
|
||||||
status: Mutex<DownloadStatus>,
|
status: Mutex<DownloadStatus>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,41 +85,46 @@ impl GameDownloadAgent {
|
|||||||
context_map: Mutex::new(HashMap::new()),
|
context_map: Mutex::new(HashMap::new()),
|
||||||
progress: Arc::new(ProgressObject::new(0, 0, sender.clone())),
|
progress: Arc::new(ProgressObject::new(0, 0, sender.clone())),
|
||||||
sender,
|
sender,
|
||||||
stored_manifest,
|
dropdata: stored_manifest,
|
||||||
status: Mutex::new(DownloadStatus::Queued),
|
status: Mutex::new(DownloadStatus::Queued),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blocking
|
// Blocking
|
||||||
pub fn setup_download(&self) -> Result<(), ApplicationDownloadError> {
|
pub fn setup_download(&self, app_handle: &AppHandle) -> Result<(), ApplicationDownloadError> {
|
||||||
self.ensure_manifest_exists()?;
|
self.ensure_manifest_exists()?;
|
||||||
|
|
||||||
self.ensure_contexts()?;
|
self.ensure_contexts()?;
|
||||||
|
|
||||||
self.control_flag.set(DownloadThreadControlFlag::Go);
|
self.control_flag.set(DownloadThreadControlFlag::Go);
|
||||||
|
|
||||||
|
let mut db_lock = borrow_db_mut_checked();
|
||||||
|
db_lock.applications.transient_statuses.insert(
|
||||||
|
self.metadata(),
|
||||||
|
ApplicationTransientStatus::Downloading {
|
||||||
|
version_name: self.version.clone(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
push_game_update(
|
||||||
|
app_handle,
|
||||||
|
&self.metadata().id,
|
||||||
|
None,
|
||||||
|
GameStatusManager::fetch_state(&self.metadata().id, &db_lock),
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blocking
|
// Blocking
|
||||||
pub fn download(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError> {
|
pub fn download(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError> {
|
||||||
self.setup_download()?;
|
self.setup_download(app_handle)?;
|
||||||
self.set_progress_object_params();
|
|
||||||
let timer = Instant::now();
|
let timer = Instant::now();
|
||||||
push_game_update(
|
|
||||||
app_handle,
|
info!("beginning download for {}...", self.metadata().id);
|
||||||
&self.metadata().id,
|
|
||||||
None,
|
|
||||||
(
|
|
||||||
None,
|
|
||||||
Some(ApplicationTransientStatus::Downloading {
|
|
||||||
version_name: self.version.clone(),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
let res = self
|
let res = self
|
||||||
.run()
|
.run()
|
||||||
.map_err(|_| ApplicationDownloadError::DownloadError);
|
.map_err(|()| ApplicationDownloadError::DownloadError);
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
"{} took {}ms to download",
|
"{} took {}ms to download",
|
||||||
@ -135,7 +144,7 @@ impl GameDownloadAgent {
|
|||||||
|
|
||||||
fn download_manifest(&self) -> Result<(), ApplicationDownloadError> {
|
fn download_manifest(&self) -> Result<(), ApplicationDownloadError> {
|
||||||
let header = generate_authorization_header();
|
let header = generate_authorization_header();
|
||||||
let client = reqwest::blocking::Client::new();
|
let client = DROP_CLIENT_SYNC.clone();
|
||||||
let response = make_request(
|
let response = make_request(
|
||||||
&client,
|
&client,
|
||||||
&["/api/v1/client/game/manifest"],
|
&["/api/v1/client/game/manifest"],
|
||||||
@ -165,12 +174,8 @@ impl GameDownloadAgent {
|
|||||||
Err(ApplicationDownloadError::Lock)
|
Err(ApplicationDownloadError::Lock)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_progress_object_params(&self) {
|
// Sets it up for both download and validate
|
||||||
// Avoid re-setting it
|
fn setup_progress(&self) {
|
||||||
if self.progress.get_max() != 0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let contexts = self.contexts.lock().unwrap();
|
let contexts = self.contexts.lock().unwrap();
|
||||||
|
|
||||||
let length = contexts.len();
|
let length = contexts.len();
|
||||||
@ -179,7 +184,7 @@ impl GameDownloadAgent {
|
|||||||
|
|
||||||
self.progress.set_max(chunk_count);
|
self.progress.set_max(chunk_count);
|
||||||
self.progress.set_size(length);
|
self.progress.set_size(length);
|
||||||
self.progress.set_time_now();
|
self.progress.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ensure_contexts(&self) -> Result<(), ApplicationDownloadError> {
|
pub fn ensure_contexts(&self) -> Result<(), ApplicationDownloadError> {
|
||||||
@ -187,7 +192,7 @@ impl GameDownloadAgent {
|
|||||||
self.generate_contexts()?;
|
self.generate_contexts()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
*self.context_map.lock().unwrap() = self.stored_manifest.get_contexts();
|
*self.context_map.lock().unwrap() = self.dropdata.get_contexts();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -197,7 +202,7 @@ impl GameDownloadAgent {
|
|||||||
let game_id = self.id.clone();
|
let game_id = self.id.clone();
|
||||||
|
|
||||||
let mut contexts = Vec::new();
|
let mut contexts = Vec::new();
|
||||||
let base_path = Path::new(&self.stored_manifest.base_path);
|
let base_path = Path::new(&self.dropdata.base_path);
|
||||||
create_dir_all(base_path).unwrap();
|
create_dir_all(base_path).unwrap();
|
||||||
|
|
||||||
for (raw_path, chunk) in manifest {
|
for (raw_path, chunk) in manifest {
|
||||||
@ -206,11 +211,12 @@ impl GameDownloadAgent {
|
|||||||
let container = path.parent().unwrap();
|
let container = path.parent().unwrap();
|
||||||
create_dir_all(container).unwrap();
|
create_dir_all(container).unwrap();
|
||||||
|
|
||||||
|
let already_exists = path.exists();
|
||||||
let file = OpenOptions::new()
|
let file = OpenOptions::new()
|
||||||
.read(true)
|
.read(true)
|
||||||
.write(true)
|
.write(true)
|
||||||
.truncate(true)
|
|
||||||
.create(true)
|
.create(true)
|
||||||
|
.truncate(false)
|
||||||
.open(path.clone())
|
.open(path.clone())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut running_offset = 0;
|
let mut running_offset = 0;
|
||||||
@ -231,12 +237,12 @@ impl GameDownloadAgent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
if running_offset > 0 {
|
if running_offset > 0 && !already_exists {
|
||||||
let _ = fallocate(file, FallocateFlags::empty(), 0, running_offset);
|
let _ = fallocate(file, FallocateFlags::empty(), 0, running_offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let existing_contexts = self.stored_manifest.get_completed_contexts();
|
let existing_contexts = self.dropdata.get_completed_contexts();
|
||||||
self.stored_manifest.set_contexts(
|
self.dropdata.set_contexts(
|
||||||
&contexts
|
&contexts
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| (x.checksum.clone(), existing_contexts.contains(&x.checksum)))
|
.map(|x| (x.checksum.clone(), existing_contexts.contains(&x.checksum)))
|
||||||
@ -248,8 +254,8 @@ impl GameDownloadAgent {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Change return value on Err
|
fn run(&self) -> Result<bool, ()> {
|
||||||
pub fn run(&self) -> Result<bool, ()> {
|
self.setup_progress();
|
||||||
let max_download_threads = borrow_db_checked().settings.max_download_threads;
|
let max_download_threads = borrow_db_checked().settings.max_download_threads;
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
@ -265,9 +271,8 @@ impl GameDownloadAgent {
|
|||||||
let completed_indexes_loop_arc = completed_contexts.clone();
|
let completed_indexes_loop_arc = completed_contexts.clone();
|
||||||
|
|
||||||
let contexts = self.contexts.lock().unwrap();
|
let contexts = self.contexts.lock().unwrap();
|
||||||
debug!("{contexts:#?}");
|
|
||||||
pool.scope(|scope| {
|
pool.scope(|scope| {
|
||||||
let client = &reqwest::blocking::Client::new();
|
let client = &DROP_CLIENT_SYNC.clone();
|
||||||
let context_map = self.context_map.lock().unwrap();
|
let context_map = self.context_map.lock().unwrap();
|
||||||
for (index, context) in contexts.iter().enumerate() {
|
for (index, context) in contexts.iter().enumerate() {
|
||||||
let client = client.clone();
|
let client = client.clone();
|
||||||
@ -277,7 +282,10 @@ impl GameDownloadAgent {
|
|||||||
let progress_handle = ProgressHandle::new(progress, self.progress.clone());
|
let progress_handle = ProgressHandle::new(progress, self.progress.clone());
|
||||||
|
|
||||||
// If we've done this one already, skip it
|
// If we've done this one already, skip it
|
||||||
if Some(&true) == context_map.get(&context.checksum) {
|
// Note to future DecDuck, DropData gets loaded into context_map
|
||||||
|
if let Some(v) = context_map.get(&context.checksum)
|
||||||
|
&& *v
|
||||||
|
{
|
||||||
progress_handle.skip(context.length);
|
progress_handle.skip(context.length);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -322,7 +330,7 @@ impl GameDownloadAgent {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let newly_completed = completed_contexts.to_owned();
|
let newly_completed = completed_contexts.clone();
|
||||||
|
|
||||||
let completed_lock_len = {
|
let completed_lock_len = {
|
||||||
let mut context_map_lock = self.context_map.lock().unwrap();
|
let mut context_map_lock = self.context_map.lock().unwrap();
|
||||||
@ -338,14 +346,14 @@ impl GameDownloadAgent {
|
|||||||
.map(|x| {
|
.map(|x| {
|
||||||
(
|
(
|
||||||
x.checksum.clone(),
|
x.checksum.clone(),
|
||||||
context_map_lock.get(&x.checksum).cloned().unwrap_or(false),
|
context_map_lock.get(&x.checksum).copied().unwrap_or(false),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect::<Vec<(String, bool)>>();
|
.collect::<Vec<(String, bool)>>();
|
||||||
drop(context_map_lock);
|
drop(context_map_lock);
|
||||||
|
|
||||||
self.stored_manifest.set_contexts(&contexts);
|
self.dropdata.set_contexts(&contexts);
|
||||||
self.stored_manifest.write();
|
self.dropdata.write();
|
||||||
|
|
||||||
// If there are any contexts left which are false
|
// If there are any contexts left which are false
|
||||||
if !contexts.iter().all(|x| x.1) {
|
if !contexts.iter().all(|x| x.1) {
|
||||||
@ -360,6 +368,93 @@ impl GameDownloadAgent {
|
|||||||
|
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn setup_validate(&self, app_handle: &AppHandle) {
|
||||||
|
self.setup_progress();
|
||||||
|
|
||||||
|
self.control_flag.set(DownloadThreadControlFlag::Go);
|
||||||
|
|
||||||
|
let mut db_lock = borrow_db_mut_checked();
|
||||||
|
db_lock.applications.transient_statuses.insert(
|
||||||
|
self.metadata(),
|
||||||
|
ApplicationTransientStatus::Validating {
|
||||||
|
version_name: self.version.clone(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
push_game_update(
|
||||||
|
app_handle,
|
||||||
|
&self.metadata().id,
|
||||||
|
None,
|
||||||
|
GameStatusManager::fetch_state(&self.metadata().id, &db_lock),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError> {
|
||||||
|
self.setup_validate(app_handle);
|
||||||
|
|
||||||
|
let contexts = self.contexts.lock().unwrap();
|
||||||
|
let max_download_threads = borrow_db_checked().settings.max_download_threads;
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"validating game: {} with {} threads",
|
||||||
|
self.dropdata.game_id, max_download_threads
|
||||||
|
);
|
||||||
|
let pool = ThreadPoolBuilder::new()
|
||||||
|
.num_threads(max_download_threads)
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let invalid_chunks = Arc::new(boxcar::Vec::new());
|
||||||
|
pool.scope(|scope| {
|
||||||
|
for (index, context) in contexts.iter().enumerate() {
|
||||||
|
let current_progress = self.progress.get(index);
|
||||||
|
let progress_handle = ProgressHandle::new(current_progress, self.progress.clone());
|
||||||
|
let invalid_chunks_scoped = invalid_chunks.clone();
|
||||||
|
let sender = self.sender.clone();
|
||||||
|
|
||||||
|
scope.spawn(move |_| {
|
||||||
|
match validate_game_chunk(context, &self.control_flag, progress_handle) {
|
||||||
|
Ok(true) => {}
|
||||||
|
Ok(false) => {
|
||||||
|
invalid_chunks_scoped.push(context.checksum.clone());
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("{e}");
|
||||||
|
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// If there are any contexts left which are false
|
||||||
|
if !invalid_chunks.is_empty() {
|
||||||
|
info!("validation of game id {} failed", self.id);
|
||||||
|
|
||||||
|
for context in invalid_chunks.iter() {
|
||||||
|
self.dropdata.set_context(context.1.clone(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.dropdata.write();
|
||||||
|
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cancel(&self, app_handle: &AppHandle) {
|
||||||
|
// See docs on usage
|
||||||
|
set_partially_installed(
|
||||||
|
&self.metadata(),
|
||||||
|
self.dropdata.base_path.to_str().unwrap().to_string(),
|
||||||
|
Some(app_handle),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.dropdata.write();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Downloadable for GameDownloadAgent {
|
impl Downloadable for GameDownloadAgent {
|
||||||
@ -368,6 +463,11 @@ impl Downloadable for GameDownloadAgent {
|
|||||||
self.download(app_handle)
|
self.download(app_handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError> {
|
||||||
|
*self.status.lock().unwrap() = DownloadStatus::Validating;
|
||||||
|
self.validate(app_handle)
|
||||||
|
}
|
||||||
|
|
||||||
fn progress(&self) -> Arc<ProgressObject> {
|
fn progress(&self) -> Arc<ProgressObject> {
|
||||||
self.progress.clone()
|
self.progress.clone()
|
||||||
}
|
}
|
||||||
@ -406,37 +506,25 @@ impl Downloadable for GameDownloadAgent {
|
|||||||
fn on_complete(&self, app_handle: &tauri::AppHandle) {
|
fn on_complete(&self, app_handle: &tauri::AppHandle) {
|
||||||
on_game_complete(
|
on_game_complete(
|
||||||
&self.metadata(),
|
&self.metadata(),
|
||||||
self.stored_manifest.base_path.to_string_lossy().to_string(),
|
self.dropdata.base_path.to_string_lossy().to_string(),
|
||||||
app_handle,
|
app_handle,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: fix this function. It doesn't restart the download properly, nor does it reset the state properly
|
fn on_cancelled(&self, app_handle: &tauri::AppHandle) {
|
||||||
fn on_incomplete(&self, app_handle: &tauri::AppHandle) {
|
self.cancel(app_handle);
|
||||||
on_game_incomplete(
|
/*
|
||||||
&self.metadata(),
|
on_game_incomplete(
|
||||||
self.stored_manifest.base_path.to_string_lossy().to_string(),
|
&self.metadata(),
|
||||||
app_handle,
|
self.dropdata.base_path.to_string_lossy().to_string(),
|
||||||
)
|
app_handle,
|
||||||
.unwrap();
|
)
|
||||||
println!("Attempting to redownload");
|
.unwrap();
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_cancelled(&self, _app_handle: &tauri::AppHandle) {}
|
|
||||||
|
|
||||||
fn status(&self) -> DownloadStatus {
|
fn status(&self) -> DownloadStatus {
|
||||||
self.status.lock().unwrap().clone()
|
self.status.lock().unwrap().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate(&self) -> Result<bool, ApplicationDownloadError> {
|
|
||||||
*self.status.lock().unwrap() = DownloadStatus::Validating;
|
|
||||||
game_validate_logic(
|
|
||||||
&self.stored_manifest,
|
|
||||||
self.contexts.lock().unwrap().clone(),
|
|
||||||
self.progress.clone(),
|
|
||||||
self.sender.clone(),
|
|
||||||
&self.control_flag,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -151,7 +151,7 @@ pub fn download_game_chunk(
|
|||||||
return Err(ApplicationDownloadError::Communication(
|
return Err(ApplicationDownloadError::Communication(
|
||||||
RemoteAccessError::InvalidResponse(err),
|
RemoteAccessError::InvalidResponse(err),
|
||||||
));
|
));
|
||||||
};
|
}
|
||||||
return Err(ApplicationDownloadError::Communication(
|
return Err(ApplicationDownloadError::Communication(
|
||||||
RemoteAccessError::UnparseableResponse(raw_res),
|
RemoteAccessError::UnparseableResponse(raw_res),
|
||||||
));
|
));
|
||||||
@ -187,7 +187,7 @@ pub fn download_game_chunk(
|
|||||||
.map_err(|e| ApplicationDownloadError::IoError(e.kind()))?;
|
.map_err(|e| ApplicationDownloadError::IoError(e.kind()))?;
|
||||||
if !completed {
|
if !completed {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
};
|
}
|
||||||
|
|
||||||
// If we complete the file, set the permissions (if on Linux)
|
// If we complete the file, set the permissions (if on Linux)
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap, fs::File, io::{Read, Write}, path::PathBuf
|
collections::HashMap, fs::File, io::{self, Read, Write}, path::{Path, PathBuf}
|
||||||
};
|
};
|
||||||
|
|
||||||
use log::{debug, error, info, warn};
|
use log::error;
|
||||||
use native_model::{Decode, Encode};
|
use native_model::{Decode, Encode};
|
||||||
|
|
||||||
pub type DropData = v1::DropData;
|
pub type DropData = v1::DropData;
|
||||||
|
|
||||||
static DROP_DATA_PATH: &str = ".dropdata";
|
pub static DROP_DATA_PATH: &str = ".dropdata";
|
||||||
|
|
||||||
pub mod v1 {
|
pub mod v1 {
|
||||||
use std::{collections::HashMap, path::PathBuf, sync::Mutex};
|
use std::{collections::HashMap, path::PathBuf, sync::Mutex};
|
||||||
@ -38,30 +38,18 @@ pub mod v1 {
|
|||||||
|
|
||||||
impl DropData {
|
impl DropData {
|
||||||
pub fn generate(game_id: String, game_version: String, base_path: PathBuf) -> Self {
|
pub fn generate(game_id: String, game_version: String, base_path: PathBuf) -> Self {
|
||||||
let mut file = match File::open(base_path.join(DROP_DATA_PATH)) {
|
match DropData::read(&base_path) {
|
||||||
Ok(file) => file,
|
Ok(v) => v,
|
||||||
Err(_) => {
|
Err(_) => DropData::new(game_id, game_version, base_path),
|
||||||
debug!("Generating new dropdata for game {game_id}");
|
}
|
||||||
return DropData::new(game_id, game_version, base_path);
|
}
|
||||||
}
|
pub fn read(base_path: &Path) -> Result<Self, io::Error> {
|
||||||
};
|
let mut file = File::open(base_path.join(DROP_DATA_PATH))?;
|
||||||
|
|
||||||
let mut s = Vec::new();
|
let mut s = Vec::new();
|
||||||
match file.read_to_end(&mut s) {
|
file.read_to_end(&mut s)?;
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => {
|
|
||||||
error!("{e}");
|
|
||||||
return DropData::new(game_id, game_version, base_path);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match native_model::rmp_serde_1_3::RmpSerde::decode(s) {
|
Ok(native_model::rmp_serde_1_3::RmpSerde::decode(s).unwrap())
|
||||||
Ok(manifest) => manifest,
|
|
||||||
Err(e) => {
|
|
||||||
warn!("{e}");
|
|
||||||
DropData::new(game_id, game_version, base_path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
pub fn write(&self) {
|
pub fn write(&self) {
|
||||||
let manifest_raw = match native_model::rmp_serde_1_3::RmpSerde::encode(&self) {
|
let manifest_raw = match native_model::rmp_serde_1_3::RmpSerde::encode(&self) {
|
||||||
@ -78,9 +66,9 @@ impl DropData {
|
|||||||
};
|
};
|
||||||
|
|
||||||
match file.write_all(&manifest_raw) {
|
match file.write_all(&manifest_raw) {
|
||||||
Ok(_) => {}
|
Ok(()) => {}
|
||||||
Err(e) => error!("{e}"),
|
Err(e) => error!("{e}"),
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
pub fn set_contexts(&self, completed_contexts: &[(String, bool)]) {
|
pub fn set_contexts(&self, completed_contexts: &[(String, bool)]) {
|
||||||
*self.contexts.lock().unwrap() = completed_contexts.iter().map(|s| (s.0.clone(), s.1)).collect();
|
*self.contexts.lock().unwrap() = completed_contexts.iter().map(|s| (s.0.clone(), s.1)).collect();
|
||||||
@ -97,10 +85,6 @@ impl DropData {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
pub fn get_contexts(&self) -> HashMap<String, bool> {
|
pub fn get_contexts(&self) -> HashMap<String, bool> {
|
||||||
info!(
|
|
||||||
"Any contexts which are complete? {}",
|
|
||||||
self.contexts.lock().unwrap().iter().any(|x| *x.1)
|
|
||||||
);
|
|
||||||
self.contexts.lock().unwrap().clone()
|
self.contexts.lock().unwrap().clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
pub mod commands;
|
pub mod commands;
|
||||||
pub mod download_agent;
|
pub mod download_agent;
|
||||||
mod download_logic;
|
mod download_logic;
|
||||||
mod drop_data;
|
pub mod drop_data;
|
||||||
mod manifest;
|
mod manifest;
|
||||||
pub mod validate;
|
pub mod validate;
|
||||||
|
|||||||
@ -1,99 +1,22 @@
|
|||||||
use std::{
|
use std::{
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{self, BufWriter, Read, Seek, SeekFrom, Write},
|
io::{self, BufWriter, Read, Seek, SeekFrom, Write},
|
||||||
sync::{Arc, mpsc::Sender},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use log::{debug, error, info};
|
use log::debug;
|
||||||
use md5::Context;
|
use md5::Context;
|
||||||
use rayon::ThreadPoolBuilder;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
database::db::borrow_db_checked,
|
download_manager::
|
||||||
download_manager::{
|
|
||||||
download_manager_frontend::DownloadManagerSignal,
|
|
||||||
util::{
|
util::{
|
||||||
download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag},
|
download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag},
|
||||||
progress_object::{ProgressHandle, ProgressObject},
|
progress_object::ProgressHandle,
|
||||||
},
|
}
|
||||||
},
|
,
|
||||||
error::application_download_error::ApplicationDownloadError,
|
error::application_download_error::ApplicationDownloadError,
|
||||||
games::downloads::{drop_data::DropData, manifest::DropDownloadContext},
|
games::downloads::manifest::DropDownloadContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn game_validate_logic(
|
|
||||||
dropdata: &DropData,
|
|
||||||
contexts: Vec<DropDownloadContext>,
|
|
||||||
progress: Arc<ProgressObject>,
|
|
||||||
sender: Sender<DownloadManagerSignal>,
|
|
||||||
control_flag: &DownloadThreadControl,
|
|
||||||
) -> Result<bool, ApplicationDownloadError> {
|
|
||||||
progress.reset(contexts.len());
|
|
||||||
let max_download_threads = borrow_db_checked().settings.max_download_threads;
|
|
||||||
|
|
||||||
debug!(
|
|
||||||
"validating game: {} with {} threads",
|
|
||||||
dropdata.game_id, max_download_threads
|
|
||||||
);
|
|
||||||
let pool = ThreadPoolBuilder::new()
|
|
||||||
.num_threads(max_download_threads)
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
debug!("{contexts:#?}");
|
|
||||||
let invalid_chunks = Arc::new(boxcar::Vec::new());
|
|
||||||
pool.scope(|scope| {
|
|
||||||
for (index, context) in contexts.iter().enumerate() {
|
|
||||||
let current_progress = progress.get(index);
|
|
||||||
let progress_handle = ProgressHandle::new(current_progress, progress.clone());
|
|
||||||
let invalid_chunks_scoped = invalid_chunks.clone();
|
|
||||||
let sender = sender.clone();
|
|
||||||
|
|
||||||
scope.spawn(move |_| {
|
|
||||||
match validate_game_chunk(context, control_flag, progress_handle) {
|
|
||||||
Ok(true) => {
|
|
||||||
debug!(
|
|
||||||
"Finished context #{} with checksum {}",
|
|
||||||
index, context.checksum
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok(false) => {
|
|
||||||
debug!(
|
|
||||||
"Didn't finish context #{} with checksum {}",
|
|
||||||
index, &context.checksum
|
|
||||||
);
|
|
||||||
invalid_chunks_scoped.push(context.checksum.clone());
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("{e}");
|
|
||||||
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// If there are any contexts left which are false
|
|
||||||
if !invalid_chunks.is_empty() {
|
|
||||||
info!(
|
|
||||||
"validation of game id {} failed for chunks {:?}",
|
|
||||||
dropdata.game_id.clone(),
|
|
||||||
invalid_chunks
|
|
||||||
);
|
|
||||||
|
|
||||||
for context in invalid_chunks.iter() {
|
|
||||||
dropdata.set_context(context.1.clone(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
dropdata.write();
|
|
||||||
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn validate_game_chunk(
|
pub fn validate_game_chunk(
|
||||||
ctx: &DropDownloadContext,
|
ctx: &DropDownloadContext,
|
||||||
control_flag: &DownloadThreadControl,
|
control_flag: &DownloadThreadControl,
|
||||||
@ -125,14 +48,10 @@ pub fn validate_game_chunk(
|
|||||||
validate_copy(&mut source, &mut hasher, ctx.length, control_flag, progress).unwrap();
|
validate_copy(&mut source, &mut hasher, ctx.length, control_flag, progress).unwrap();
|
||||||
if !completed {
|
if !completed {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
};
|
}
|
||||||
|
|
||||||
let res = hex::encode(hasher.compute().0);
|
let res = hex::encode(hasher.compute().0);
|
||||||
if res != ctx.checksum {
|
if res != ctx.checksum {
|
||||||
println!(
|
|
||||||
"Checksum failed. Correct: {}, actual: {}",
|
|
||||||
&ctx.checksum, &res
|
|
||||||
);
|
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,9 @@ use serde::{Deserialize, Serialize};
|
|||||||
use tauri::AppHandle;
|
use tauri::AppHandle;
|
||||||
use tauri::Emitter;
|
use tauri::Emitter;
|
||||||
|
|
||||||
|
use crate::AppState;
|
||||||
use crate::database::db::{borrow_db_checked, borrow_db_mut_checked};
|
use crate::database::db::{borrow_db_checked, borrow_db_mut_checked};
|
||||||
|
use crate::database::models::data::Database;
|
||||||
use crate::database::models::data::{
|
use crate::database::models::data::{
|
||||||
ApplicationTransientStatus, DownloadableMetadata, GameDownloadStatus, GameVersion,
|
ApplicationTransientStatus, DownloadableMetadata, GameDownloadStatus, GameVersion,
|
||||||
};
|
};
|
||||||
@ -16,10 +18,11 @@ use crate::error::library_error::LibraryError;
|
|||||||
use crate::error::remote_access_error::RemoteAccessError;
|
use crate::error::remote_access_error::RemoteAccessError;
|
||||||
use crate::games::state::{GameStatusManager, GameStatusWithTransient};
|
use crate::games::state::{GameStatusManager, GameStatusWithTransient};
|
||||||
use crate::remote::auth::generate_authorization_header;
|
use crate::remote::auth::generate_authorization_header;
|
||||||
|
use crate::remote::cache::cache_object_db;
|
||||||
use crate::remote::cache::{cache_object, get_cached_object, get_cached_object_db};
|
use crate::remote::cache::{cache_object, get_cached_object, get_cached_object_db};
|
||||||
use crate::remote::requests::make_request;
|
use crate::remote::requests::make_request;
|
||||||
use crate::AppState;
|
use crate::remote::utils::DROP_CLIENT_SYNC;
|
||||||
use bitcode::{Encode, Decode};
|
use bitcode::{Decode, Encode};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct FetchGameStruct {
|
pub struct FetchGameStruct {
|
||||||
@ -78,7 +81,7 @@ pub fn fetch_library_logic(
|
|||||||
) -> Result<Vec<Game>, RemoteAccessError> {
|
) -> Result<Vec<Game>, RemoteAccessError> {
|
||||||
let header = generate_authorization_header();
|
let header = generate_authorization_header();
|
||||||
|
|
||||||
let client = reqwest::blocking::Client::new();
|
let client = DROP_CLIENT_SYNC.clone();
|
||||||
let response = make_request(&client, &["/api/v1/client/user/library"], &[], |f| {
|
let response = make_request(&client, &["/api/v1/client/user/library"], &[], |f| {
|
||||||
f.header("Authorization", header)
|
f.header("Authorization", header)
|
||||||
})?
|
})?
|
||||||
@ -96,7 +99,7 @@ pub fn fetch_library_logic(
|
|||||||
|
|
||||||
let mut db_handle = borrow_db_mut_checked();
|
let mut db_handle = borrow_db_mut_checked();
|
||||||
|
|
||||||
for game in games.iter() {
|
for game in &games {
|
||||||
handle.games.insert(game.id.clone(), game.clone());
|
handle.games.insert(game.id.clone(), game.clone());
|
||||||
if !db_handle.applications.game_statuses.contains_key(&game.id) {
|
if !db_handle.applications.game_statuses.contains_key(&game.id) {
|
||||||
db_handle
|
db_handle
|
||||||
@ -145,27 +148,22 @@ pub fn fetch_game_logic(
|
|||||||
) -> Result<FetchGameStruct, RemoteAccessError> {
|
) -> Result<FetchGameStruct, RemoteAccessError> {
|
||||||
let mut state_handle = state.lock().unwrap();
|
let mut state_handle = state.lock().unwrap();
|
||||||
|
|
||||||
let handle = borrow_db_checked();
|
let db_lock = borrow_db_checked();
|
||||||
|
|
||||||
let metadata_option = handle.applications.installed_game_version.get(&id);
|
let metadata_option = db_lock.applications.installed_game_version.get(&id);
|
||||||
let version = match metadata_option {
|
let version = match metadata_option {
|
||||||
None => None,
|
None => None,
|
||||||
Some(metadata) => Some(
|
Some(metadata) => db_lock
|
||||||
handle
|
.applications
|
||||||
.applications
|
.game_versions
|
||||||
.game_versions
|
.get(&metadata.id)
|
||||||
.get(&metadata.id)
|
.map(|v| v.get(metadata.version.as_ref().unwrap()).unwrap())
|
||||||
.unwrap()
|
.cloned(),
|
||||||
.get(metadata.version.as_ref().unwrap())
|
|
||||||
.unwrap()
|
|
||||||
.clone(),
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
drop(handle);
|
|
||||||
|
|
||||||
let game = state_handle.games.get(&id);
|
let game = state_handle.games.get(&id);
|
||||||
if let Some(game) = game {
|
if let Some(game) = game {
|
||||||
let status = GameStatusManager::fetch_state(&id);
|
let status = GameStatusManager::fetch_state(&id, &db_lock);
|
||||||
|
|
||||||
let data = FetchGameStruct {
|
let data = FetchGameStruct {
|
||||||
game: game.clone(),
|
game: game.clone(),
|
||||||
@ -173,11 +171,13 @@ pub fn fetch_game_logic(
|
|||||||
version,
|
version,
|
||||||
};
|
};
|
||||||
|
|
||||||
cache_object(&id, game)?;
|
cache_object_db(&id, game, &db_lock)?;
|
||||||
|
|
||||||
return Ok(data);
|
return Ok(data);
|
||||||
}
|
}
|
||||||
let client = reqwest::blocking::Client::new();
|
drop(db_lock);
|
||||||
|
|
||||||
|
let client = DROP_CLIENT_SYNC.clone();
|
||||||
let response = make_request(&client, &["/api/v1/client/game/", &id], &[], |r| {
|
let response = make_request(&client, &["/api/v1/client/game/", &id], &[], |r| {
|
||||||
r.header("Authorization", generate_authorization_header())
|
r.header("Authorization", generate_authorization_header())
|
||||||
})?
|
})?
|
||||||
@ -202,9 +202,10 @@ pub fn fetch_game_logic(
|
|||||||
.game_statuses
|
.game_statuses
|
||||||
.entry(id.clone())
|
.entry(id.clone())
|
||||||
.or_insert(GameDownloadStatus::Remote {});
|
.or_insert(GameDownloadStatus::Remote {});
|
||||||
drop(db_handle);
|
|
||||||
|
|
||||||
let status = GameStatusManager::fetch_state(&id);
|
let status = GameStatusManager::fetch_state(&id, &db_handle);
|
||||||
|
|
||||||
|
drop(db_handle);
|
||||||
|
|
||||||
let data = FetchGameStruct {
|
let data = FetchGameStruct {
|
||||||
game: game.clone(),
|
game: game.clone(),
|
||||||
@ -221,12 +222,12 @@ pub fn fetch_game_logic_offline(
|
|||||||
id: String,
|
id: String,
|
||||||
_state: tauri::State<'_, Mutex<AppState>>,
|
_state: tauri::State<'_, Mutex<AppState>>,
|
||||||
) -> Result<FetchGameStruct, RemoteAccessError> {
|
) -> Result<FetchGameStruct, RemoteAccessError> {
|
||||||
let handle = borrow_db_checked();
|
let db_handle = borrow_db_checked();
|
||||||
let metadata_option = handle.applications.installed_game_version.get(&id);
|
let metadata_option = db_handle.applications.installed_game_version.get(&id);
|
||||||
let version = match metadata_option {
|
let version = match metadata_option {
|
||||||
None => None,
|
None => None,
|
||||||
Some(metadata) => Some(
|
Some(metadata) => Some(
|
||||||
handle
|
db_handle
|
||||||
.applications
|
.applications
|
||||||
.game_versions
|
.game_versions
|
||||||
.get(&metadata.id)
|
.get(&metadata.id)
|
||||||
@ -236,11 +237,12 @@ pub fn fetch_game_logic_offline(
|
|||||||
.clone(),
|
.clone(),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
drop(handle);
|
|
||||||
|
|
||||||
let status = GameStatusManager::fetch_state(&id);
|
let status = GameStatusManager::fetch_state(&id, &db_handle);
|
||||||
let game = get_cached_object::<Game>(&id)?;
|
let game = get_cached_object::<Game>(&id)?;
|
||||||
|
|
||||||
|
drop(db_handle);
|
||||||
|
|
||||||
Ok(FetchGameStruct {
|
Ok(FetchGameStruct {
|
||||||
game,
|
game,
|
||||||
status,
|
status,
|
||||||
@ -252,7 +254,7 @@ pub fn fetch_game_verion_options_logic(
|
|||||||
game_id: String,
|
game_id: String,
|
||||||
state: tauri::State<'_, Mutex<AppState>>,
|
state: tauri::State<'_, Mutex<AppState>>,
|
||||||
) -> Result<Vec<GameVersion>, RemoteAccessError> {
|
) -> Result<Vec<GameVersion>, RemoteAccessError> {
|
||||||
let client = reqwest::blocking::Client::new();
|
let client = DROP_CLIENT_SYNC.clone();
|
||||||
|
|
||||||
let response = make_request(
|
let response = make_request(
|
||||||
&client,
|
&client,
|
||||||
@ -274,7 +276,11 @@ pub fn fetch_game_verion_options_logic(
|
|||||||
let process_manager_lock = state_lock.process_manager.lock().unwrap();
|
let process_manager_lock = state_lock.process_manager.lock().unwrap();
|
||||||
let data: Vec<GameVersion> = data
|
let data: Vec<GameVersion> = data
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|v| process_manager_lock.valid_platform(&v.platform).unwrap())
|
.filter(|v| {
|
||||||
|
process_manager_lock
|
||||||
|
.valid_platform(&v.platform, &state_lock)
|
||||||
|
.unwrap()
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
drop(process_manager_lock);
|
drop(process_manager_lock);
|
||||||
drop(state_lock);
|
drop(state_lock);
|
||||||
@ -282,6 +288,49 @@ pub fn fetch_game_verion_options_logic(
|
|||||||
Ok(data)
|
Ok(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by:
|
||||||
|
* - on_cancel, when cancelled, for obvious reasons
|
||||||
|
* - when downloading, so if drop unexpectedly quits, we can resume the download. hidden by the "Downloading..." transient state, though
|
||||||
|
* - when scanning, to import the game
|
||||||
|
*/
|
||||||
|
pub fn set_partially_installed(
|
||||||
|
meta: &DownloadableMetadata,
|
||||||
|
install_dir: String,
|
||||||
|
app_handle: Option<&AppHandle>,
|
||||||
|
) {
|
||||||
|
set_partially_installed_db(&mut borrow_db_mut_checked(), meta, install_dir, app_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_partially_installed_db(
|
||||||
|
db_lock: &mut Database,
|
||||||
|
meta: &DownloadableMetadata,
|
||||||
|
install_dir: String,
|
||||||
|
app_handle: Option<&AppHandle>,
|
||||||
|
) {
|
||||||
|
db_lock.applications.transient_statuses.remove(meta);
|
||||||
|
db_lock.applications.game_statuses.insert(
|
||||||
|
meta.id.clone(),
|
||||||
|
GameDownloadStatus::PartiallyInstalled {
|
||||||
|
version_name: meta.version.as_ref().unwrap().clone(),
|
||||||
|
install_dir,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
db_lock
|
||||||
|
.applications
|
||||||
|
.installed_game_version
|
||||||
|
.insert(meta.id.clone(), meta.clone());
|
||||||
|
|
||||||
|
if let Some(app_handle) = app_handle {
|
||||||
|
push_game_update(
|
||||||
|
app_handle,
|
||||||
|
&meta.id,
|
||||||
|
None,
|
||||||
|
GameStatusManager::fetch_state(&meta.id, db_lock),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle) {
|
pub fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle) {
|
||||||
debug!("triggered uninstall for agent");
|
debug!("triggered uninstall for agent");
|
||||||
let mut db_handle = borrow_db_mut_checked();
|
let mut db_handle = borrow_db_mut_checked();
|
||||||
@ -295,7 +344,7 @@ pub fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle)
|
|||||||
app_handle,
|
app_handle,
|
||||||
&meta.id,
|
&meta.id,
|
||||||
None,
|
None,
|
||||||
(None, Some(ApplicationTransientStatus::Uninstalling {})),
|
GameStatusManager::fetch_state(&meta.id, &db_handle),
|
||||||
);
|
);
|
||||||
|
|
||||||
let previous_state = db_handle.applications.game_statuses.get(&meta.id).cloned();
|
let previous_state = db_handle.applications.game_statuses.get(&meta.id).cloned();
|
||||||
@ -329,11 +378,10 @@ pub fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle)
|
|||||||
drop(db_handle);
|
drop(db_handle);
|
||||||
|
|
||||||
let app_handle = app_handle.clone();
|
let app_handle = app_handle.clone();
|
||||||
spawn(move || match remove_dir_all(install_dir) {
|
spawn(move || {
|
||||||
Err(e) => {
|
if let Err(e) = remove_dir_all(install_dir) {
|
||||||
error!("{e}");
|
error!("{e}");
|
||||||
}
|
} else {
|
||||||
Ok(_) => {
|
|
||||||
let mut db_handle = borrow_db_mut_checked();
|
let mut db_handle = borrow_db_mut_checked();
|
||||||
db_handle.applications.transient_statuses.remove(&meta);
|
db_handle.applications.transient_statuses.remove(&meta);
|
||||||
db_handle
|
db_handle
|
||||||
@ -345,21 +393,23 @@ pub fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle)
|
|||||||
.game_statuses
|
.game_statuses
|
||||||
.entry(meta.id.clone())
|
.entry(meta.id.clone())
|
||||||
.and_modify(|e| *e = GameDownloadStatus::Remote {});
|
.and_modify(|e| *e = GameDownloadStatus::Remote {});
|
||||||
drop(db_handle);
|
let _ = db_handle.applications.transient_statuses.remove(&meta);
|
||||||
|
|
||||||
debug!("uninstalled game id {}", &meta.id);
|
|
||||||
app_handle.emit("update_library", ()).unwrap();
|
|
||||||
|
|
||||||
push_game_update(
|
push_game_update(
|
||||||
&app_handle,
|
&app_handle,
|
||||||
&meta.id,
|
&meta.id,
|
||||||
None,
|
None,
|
||||||
(Some(GameDownloadStatus::Remote {}), None),
|
GameStatusManager::fetch_state(&meta.id, &db_handle),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
debug!("uninstalled game id {}", &meta.id);
|
||||||
|
app_handle.emit("update_library", ()).unwrap();
|
||||||
|
|
||||||
|
drop(db_handle);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
warn!("invalid previous state for uninstall, failing silently.")
|
warn!("invalid previous state for uninstall, failing silently.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -371,66 +421,6 @@ pub fn get_current_meta(game_id: &String) -> Option<DownloadableMetadata> {
|
|||||||
.cloned()
|
.cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_game_incomplete(
|
|
||||||
meta: &DownloadableMetadata,
|
|
||||||
install_dir: String,
|
|
||||||
app_handle: &AppHandle,
|
|
||||||
) -> Result<(), RemoteAccessError> {
|
|
||||||
// Fetch game version information from remote
|
|
||||||
if meta.version.is_none() {
|
|
||||||
return Err(RemoteAccessError::GameNotFound(meta.id.clone()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let client = reqwest::blocking::Client::new();
|
|
||||||
let response = make_request(
|
|
||||||
&client,
|
|
||||||
&["/api/v1/client/game/version"],
|
|
||||||
&[
|
|
||||||
("id", &meta.id),
|
|
||||||
("version", meta.version.as_ref().unwrap()),
|
|
||||||
],
|
|
||||||
|f| f.header("Authorization", generate_authorization_header()),
|
|
||||||
)?
|
|
||||||
.send()?;
|
|
||||||
|
|
||||||
let game_version: GameVersion = response.json()?;
|
|
||||||
|
|
||||||
let mut handle = borrow_db_mut_checked();
|
|
||||||
handle
|
|
||||||
.applications
|
|
||||||
.game_versions
|
|
||||||
.entry(meta.id.clone())
|
|
||||||
.or_default()
|
|
||||||
.insert(meta.version.clone().unwrap(), game_version.clone());
|
|
||||||
handle
|
|
||||||
.applications
|
|
||||||
.installed_game_version
|
|
||||||
.insert(meta.id.clone(), meta.clone());
|
|
||||||
|
|
||||||
let status = GameDownloadStatus::PartiallyInstalled {
|
|
||||||
version_name: meta.version.clone().unwrap(),
|
|
||||||
install_dir,
|
|
||||||
};
|
|
||||||
|
|
||||||
handle
|
|
||||||
.applications
|
|
||||||
.game_statuses
|
|
||||||
.insert(meta.id.clone(), status.clone());
|
|
||||||
drop(handle);
|
|
||||||
app_handle
|
|
||||||
.emit(
|
|
||||||
&format!("update_game/{}", meta.id),
|
|
||||||
GameUpdateEvent {
|
|
||||||
game_id: meta.id.clone(),
|
|
||||||
status: (Some(status), None),
|
|
||||||
version: Some(game_version),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_game_complete(
|
pub fn on_game_complete(
|
||||||
meta: &DownloadableMetadata,
|
meta: &DownloadableMetadata,
|
||||||
install_dir: String,
|
install_dir: String,
|
||||||
@ -443,7 +433,7 @@ pub fn on_game_complete(
|
|||||||
|
|
||||||
let header = generate_authorization_header();
|
let header = generate_authorization_header();
|
||||||
|
|
||||||
let client = reqwest::blocking::Client::new();
|
let client = DROP_CLIENT_SYNC.clone();
|
||||||
let response = make_request(
|
let response = make_request(
|
||||||
&client,
|
&client,
|
||||||
&["/api/v1/client/game/version"],
|
&["/api/v1/client/game/version"],
|
||||||
|
|||||||
@ -1,7 +1,4 @@
|
|||||||
use crate::database::{
|
use crate::database::models::data::{ApplicationTransientStatus, Database, GameDownloadStatus};
|
||||||
db::borrow_db_checked,
|
|
||||||
models::data::{ApplicationTransientStatus, GameDownloadStatus},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub type GameStatusWithTransient = (
|
pub type GameStatusWithTransient = (
|
||||||
Option<GameDownloadStatus>,
|
Option<GameDownloadStatus>,
|
||||||
@ -10,14 +7,12 @@ pub type GameStatusWithTransient = (
|
|||||||
pub struct GameStatusManager {}
|
pub struct GameStatusManager {}
|
||||||
|
|
||||||
impl GameStatusManager {
|
impl GameStatusManager {
|
||||||
pub fn fetch_state(game_id: &String) -> GameStatusWithTransient {
|
pub fn fetch_state(game_id: &String, database: &Database) -> GameStatusWithTransient {
|
||||||
let db_lock = borrow_db_checked();
|
let online_state = match database.applications.installed_game_version.get(game_id) {
|
||||||
let online_state = match db_lock.applications.installed_game_version.get(game_id) {
|
Some(meta) => database.applications.transient_statuses.get(meta).cloned(),
|
||||||
Some(meta) => db_lock.applications.transient_statuses.get(meta).cloned(),
|
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
let offline_state = db_lock.applications.game_statuses.get(game_id).cloned();
|
let offline_state = database.applications.game_statuses.get(game_id).cloned();
|
||||||
drop(db_lock);
|
|
||||||
|
|
||||||
if online_state.is_some() {
|
if online_state.is_some() {
|
||||||
return (None, online_state);
|
return (None, online_state);
|
||||||
|
|||||||
@ -11,7 +11,10 @@ mod error;
|
|||||||
mod process;
|
mod process;
|
||||||
mod remote;
|
mod remote;
|
||||||
|
|
||||||
|
use crate::database::scan::scan_install_dirs;
|
||||||
use crate::process::commands::open_process_logs;
|
use crate::process::commands::open_process_logs;
|
||||||
|
use crate::process::process_handlers::UMU_LAUNCHER_EXECUTABLE;
|
||||||
|
use crate::remote::commands::auth_initiate_code;
|
||||||
use crate::{database::db::DatabaseImpls, games::downloads::commands::resume_download};
|
use crate::{database::db::DatabaseImpls, games::downloads::commands::resume_download};
|
||||||
use bitcode::{Decode, Encode};
|
use bitcode::{Decode, Encode};
|
||||||
use client::commands::fetch_state;
|
use client::commands::fetch_state;
|
||||||
@ -59,6 +62,7 @@ use std::fs::File;
|
|||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::panic::PanicHookInfo;
|
use std::panic::PanicHookInfo;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
@ -94,6 +98,27 @@ pub struct User {
|
|||||||
profile_picture_object_id: String,
|
profile_picture_object_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CompatInfo {
|
||||||
|
umu_installed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_new_compat_info() -> Option<CompatInfo> {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
return None;
|
||||||
|
|
||||||
|
let has_umu_installed = Command::new(UMU_LAUNCHER_EXECUTABLE)
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.spawn();
|
||||||
|
if let Err(umu_error) = &has_umu_installed {
|
||||||
|
warn!("disabling windows support with error: {umu_error}");
|
||||||
|
}
|
||||||
|
let has_umu_installed = has_umu_installed.is_ok();
|
||||||
|
Some(CompatInfo {
|
||||||
|
umu_installed: has_umu_installed,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize)]
|
#[derive(Clone, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct AppState<'a> {
|
pub struct AppState<'a> {
|
||||||
@ -105,6 +130,8 @@ pub struct AppState<'a> {
|
|||||||
download_manager: Arc<DownloadManager>,
|
download_manager: Arc<DownloadManager>,
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip_serializing)]
|
||||||
process_manager: Arc<Mutex<ProcessManager<'a>>>,
|
process_manager: Arc<Mutex<ProcessManager<'a>>>,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
compat_info: Option<CompatInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(handle: AppHandle) -> AppState<'static> {
|
fn setup(handle: AppHandle) -> AppState<'static> {
|
||||||
@ -141,9 +168,13 @@ fn setup(handle: AppHandle) -> AppState<'static> {
|
|||||||
let games = HashMap::new();
|
let games = HashMap::new();
|
||||||
let download_manager = Arc::new(DownloadManagerBuilder::build(handle.clone()));
|
let download_manager = Arc::new(DownloadManagerBuilder::build(handle.clone()));
|
||||||
let process_manager = Arc::new(Mutex::new(ProcessManager::new(handle.clone())));
|
let process_manager = Arc::new(Mutex::new(ProcessManager::new(handle.clone())));
|
||||||
|
let compat_info = create_new_compat_info();
|
||||||
|
|
||||||
debug!("checking if database is set up");
|
debug!("checking if database is set up");
|
||||||
let is_set_up = DB.database_is_set_up();
|
let is_set_up = DB.database_is_set_up();
|
||||||
|
|
||||||
|
scan_install_dirs();
|
||||||
|
|
||||||
if !is_set_up {
|
if !is_set_up {
|
||||||
return AppState {
|
return AppState {
|
||||||
status: AppStatus::NotConfigured,
|
status: AppStatus::NotConfigured,
|
||||||
@ -151,6 +182,7 @@ fn setup(handle: AppHandle) -> AppState<'static> {
|
|||||||
games,
|
games,
|
||||||
download_manager,
|
download_manager,
|
||||||
process_manager,
|
process_manager,
|
||||||
|
compat_info,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,7 +195,8 @@ fn setup(handle: AppHandle) -> AppState<'static> {
|
|||||||
let mut missing_games = Vec::new();
|
let mut missing_games = Vec::new();
|
||||||
let statuses = db_handle.applications.game_statuses.clone();
|
let statuses = db_handle.applications.game_statuses.clone();
|
||||||
drop(db_handle);
|
drop(db_handle);
|
||||||
for (game_id, status) in statuses.into_iter() {
|
|
||||||
|
for (game_id, status) in statuses {
|
||||||
match status {
|
match status {
|
||||||
GameDownloadStatus::Remote {} => {}
|
GameDownloadStatus::Remote {} => {}
|
||||||
GameDownloadStatus::PartiallyInstalled { .. } => {}
|
GameDownloadStatus::PartiallyInstalled { .. } => {}
|
||||||
@ -214,6 +247,7 @@ fn setup(handle: AppHandle) -> AppState<'static> {
|
|||||||
games,
|
games,
|
||||||
download_manager,
|
download_manager,
|
||||||
process_manager,
|
process_manager,
|
||||||
|
compat_info,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,6 +301,7 @@ pub fn run() {
|
|||||||
fetch_settings,
|
fetch_settings,
|
||||||
// Auth
|
// Auth
|
||||||
auth_initiate,
|
auth_initiate,
|
||||||
|
auth_initiate_code,
|
||||||
retry_connect,
|
retry_connect,
|
||||||
manual_recieve_handshake,
|
manual_recieve_handshake,
|
||||||
sign_out,
|
sign_out,
|
||||||
@ -344,7 +379,7 @@ pub fn run() {
|
|||||||
let binding = event.urls();
|
let binding = event.urls();
|
||||||
let url = binding.first().unwrap();
|
let url = binding.first().unwrap();
|
||||||
if url.host_str().unwrap() == "handshake" {
|
if url.host_str().unwrap() == "handshake" {
|
||||||
recieve_handshake(handle.clone(), url.path().to_string())
|
recieve_handshake(handle.clone(), url.path().to_string());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -2,5 +2,5 @@
|
|||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
drop_app_lib::run()
|
drop_app_lib::run();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,10 +16,10 @@ pub fn launch_game(
|
|||||||
// download_type: DownloadType::Game,
|
// download_type: DownloadType::Game,
|
||||||
//};
|
//};
|
||||||
|
|
||||||
match process_manager_lock.launch_process(id) {
|
match process_manager_lock.launch_process(id, &state_lock) {
|
||||||
Ok(_) => {}
|
Ok(()) => {}
|
||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e),
|
||||||
};
|
}
|
||||||
|
|
||||||
drop(process_manager_lock);
|
drop(process_manager_lock);
|
||||||
drop(state_lock);
|
drop(state_lock);
|
||||||
|
|||||||
33
src-tauri/src/process/format.rs
Normal file
33
src-tauri/src/process/format.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use dynfmt::{Argument, FormatArgs};
|
||||||
|
|
||||||
|
pub struct DropFormatArgs {
|
||||||
|
positional: Vec<String>,
|
||||||
|
map: HashMap<&'static str, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DropFormatArgs {
|
||||||
|
pub fn new(launch_string: String, working_dir: &String, executable_name: &String, absolute_executable_name: String) -> Self {
|
||||||
|
let mut positional = Vec::new();
|
||||||
|
let mut map: HashMap<&'static str, String> = HashMap::new();
|
||||||
|
|
||||||
|
positional.push(launch_string);
|
||||||
|
|
||||||
|
map.insert("dir", working_dir.to_string());
|
||||||
|
map.insert("exe", executable_name.to_string());
|
||||||
|
map.insert("abs_exe", absolute_executable_name);
|
||||||
|
|
||||||
|
Self { positional, map }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FormatArgs for DropFormatArgs {
|
||||||
|
fn get_index(&self, index: usize) -> Result<Option<dynfmt::Argument<'_>>, ()> {
|
||||||
|
Ok(self.positional.get(index).map(|arg| arg as Argument<'_>))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_key(&self, key: &str) -> Result<Option<dynfmt::Argument<'_>>, ()> {
|
||||||
|
Ok(self.map.get(key).map(|arg| arg as Argument<'_>))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,3 +2,5 @@ pub mod commands;
|
|||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub mod compat;
|
pub mod compat;
|
||||||
pub mod process_manager;
|
pub mod process_manager;
|
||||||
|
pub mod process_handlers;
|
||||||
|
pub mod format;
|
||||||
109
src-tauri/src/process/process_handlers.rs
Normal file
109
src-tauri/src/process/process_handlers.rs
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
use log::debug;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
AppState,
|
||||||
|
database::models::data::{Database, DownloadableMetadata, GameVersion},
|
||||||
|
process::process_manager::{Platform, ProcessHandler},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct NativeGameLauncher;
|
||||||
|
impl ProcessHandler for NativeGameLauncher {
|
||||||
|
fn create_launch_process(
|
||||||
|
&self,
|
||||||
|
_meta: &DownloadableMetadata,
|
||||||
|
launch_command: String,
|
||||||
|
args: Vec<String>,
|
||||||
|
_game_version: &GameVersion,
|
||||||
|
_current_dir: &str,
|
||||||
|
) -> String {
|
||||||
|
format!("\"{}\" {}", launch_command, args.join(" "))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valid_for_platform(&self, _db: &Database, _state: &AppState, _target: &Platform) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const UMU_LAUNCHER_EXECUTABLE: &str = "umu-run";
|
||||||
|
pub struct UMULauncher;
|
||||||
|
impl ProcessHandler for UMULauncher {
|
||||||
|
fn create_launch_process(
|
||||||
|
&self,
|
||||||
|
_meta: &DownloadableMetadata,
|
||||||
|
launch_command: String,
|
||||||
|
args: Vec<String>,
|
||||||
|
game_version: &GameVersion,
|
||||||
|
_current_dir: &str,
|
||||||
|
) -> String {
|
||||||
|
debug!("Game override: \"{:?}\"", &game_version.umu_id_override);
|
||||||
|
let game_id = match &game_version.umu_id_override {
|
||||||
|
Some(game_override) => {
|
||||||
|
if game_override.is_empty() {
|
||||||
|
game_version.game_id.clone()
|
||||||
|
} else {
|
||||||
|
game_override.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => game_version.game_id.clone(),
|
||||||
|
};
|
||||||
|
format!(
|
||||||
|
"GAMEID={game_id} {umu} \"{launch}\" {args}",
|
||||||
|
umu = UMU_LAUNCHER_EXECUTABLE,
|
||||||
|
launch = launch_command,
|
||||||
|
args = args.join(" ")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valid_for_platform(&self, _db: &Database, state: &AppState, _target: &Platform) -> bool {
|
||||||
|
let Some(ref compat_info) = state.compat_info else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
compat_info.umu_installed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AsahiMuvmLauncher;
|
||||||
|
impl ProcessHandler for AsahiMuvmLauncher {
|
||||||
|
fn create_launch_process(
|
||||||
|
&self,
|
||||||
|
meta: &DownloadableMetadata,
|
||||||
|
launch_command: String,
|
||||||
|
args: Vec<String>,
|
||||||
|
game_version: &GameVersion,
|
||||||
|
current_dir: &str,
|
||||||
|
) -> String {
|
||||||
|
let umu_launcher = UMULauncher {};
|
||||||
|
let umu_string = umu_launcher.create_launch_process(
|
||||||
|
meta,
|
||||||
|
launch_command,
|
||||||
|
args,
|
||||||
|
game_version,
|
||||||
|
current_dir,
|
||||||
|
);
|
||||||
|
let mut args_cmd = umu_string.split("umu-run").collect::<Vec<&str>>().into_iter();
|
||||||
|
let args = args_cmd.next().unwrap().trim();
|
||||||
|
let cmd = format!("umu-run{}", args_cmd.next().unwrap());
|
||||||
|
|
||||||
|
format!("{args} muvm -- {cmd}")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unreachable_code)]
|
||||||
|
fn valid_for_platform(&self, _db: &Database, state: &AppState, _target: &Platform) -> bool {
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
return false;
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "aarch64"))]
|
||||||
|
return false;
|
||||||
|
|
||||||
|
let page_size = page_size::get();
|
||||||
|
if page_size != 16384 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(ref compat_info) = state.compat_info else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
compat_info.umu_installed
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -21,14 +21,18 @@ use tauri_plugin_opener::OpenerExt;
|
|||||||
use crate::{
|
use crate::{
|
||||||
AppState, DB,
|
AppState, DB,
|
||||||
database::{
|
database::{
|
||||||
db::{DATA_ROOT_DIR, borrow_db_mut_checked},
|
db::{DATA_ROOT_DIR, borrow_db_checked, borrow_db_mut_checked},
|
||||||
models::data::{
|
models::data::{
|
||||||
ApplicationTransientStatus, DownloadType, DownloadableMetadata, GameDownloadStatus,
|
ApplicationTransientStatus, Database, DownloadType, DownloadableMetadata,
|
||||||
GameVersion,
|
GameDownloadStatus, GameVersion,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
error::process_error::ProcessError,
|
error::process_error::ProcessError,
|
||||||
games::{library::push_game_update, state::GameStatusManager},
|
games::{library::push_game_update, state::GameStatusManager},
|
||||||
|
process::{
|
||||||
|
format::DropFormatArgs,
|
||||||
|
process_handlers::{AsahiMuvmLauncher, NativeGameLauncher, UMULauncher},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct RunningProcess {
|
pub struct RunningProcess {
|
||||||
@ -42,7 +46,10 @@ pub struct ProcessManager<'a> {
|
|||||||
log_output_dir: PathBuf,
|
log_output_dir: PathBuf,
|
||||||
processes: HashMap<String, RunningProcess>,
|
processes: HashMap<String, RunningProcess>,
|
||||||
app_handle: AppHandle,
|
app_handle: AppHandle,
|
||||||
game_launchers: HashMap<(Platform, Platform), &'a (dyn ProcessHandler + Sync + Send + 'static)>,
|
game_launchers: Vec<(
|
||||||
|
(Platform, Platform),
|
||||||
|
&'a (dyn ProcessHandler + Sync + Send + 'static),
|
||||||
|
)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProcessManager<'_> {
|
impl ProcessManager<'_> {
|
||||||
@ -62,7 +69,7 @@ impl ProcessManager<'_> {
|
|||||||
app_handle,
|
app_handle,
|
||||||
processes: HashMap::new(),
|
processes: HashMap::new(),
|
||||||
log_output_dir,
|
log_output_dir,
|
||||||
game_launchers: HashMap::from([
|
game_launchers: vec![
|
||||||
// Current platform to target platform
|
// Current platform to target platform
|
||||||
(
|
(
|
||||||
(Platform::Windows, Platform::Windows),
|
(Platform::Windows, Platform::Windows),
|
||||||
@ -76,11 +83,15 @@ impl ProcessManager<'_> {
|
|||||||
(Platform::MacOs, Platform::MacOs),
|
(Platform::MacOs, Platform::MacOs),
|
||||||
&NativeGameLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
|
&NativeGameLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
(Platform::Linux, Platform::Windows),
|
||||||
|
&AsahiMuvmLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
|
||||||
|
),
|
||||||
(
|
(
|
||||||
(Platform::Linux, Platform::Windows),
|
(Platform::Linux, Platform::Windows),
|
||||||
&UMULauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
|
&UMULauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
|
||||||
),
|
),
|
||||||
]),
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,8 +110,12 @@ impl ProcessManager<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_log_dir(&self, game_id: String) -> PathBuf {
|
||||||
|
self.log_output_dir.join(game_id)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn open_process_logs(&mut self, game_id: String) -> Result<(), ProcessError> {
|
pub fn open_process_logs(&mut self, game_id: String) -> Result<(), ProcessError> {
|
||||||
let dir = self.log_output_dir.join(game_id);
|
let dir = self.get_log_dir(game_id);
|
||||||
self.app_handle
|
self.app_handle
|
||||||
.opener()
|
.opener()
|
||||||
.open_path(dir.to_str().unwrap(), None::<&str>)
|
.open_path(dir.to_str().unwrap(), None::<&str>)
|
||||||
@ -145,7 +160,6 @@ impl ProcessManager<'_> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
drop(db_handle);
|
|
||||||
|
|
||||||
let elapsed = process.start.elapsed().unwrap_or(Duration::ZERO);
|
let elapsed = process.start.elapsed().unwrap_or(Duration::ZERO);
|
||||||
// If we started and ended really quickly, something might've gone wrong
|
// If we started and ended really quickly, something might've gone wrong
|
||||||
@ -158,16 +172,42 @@ impl ProcessManager<'_> {
|
|||||||
let _ = self.app_handle.emit("launch_external_error", &game_id);
|
let _ = self.app_handle.emit("launch_external_error", &game_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
let status = GameStatusManager::fetch_state(&game_id);
|
let status = GameStatusManager::fetch_state(&game_id, &db_handle);
|
||||||
|
drop(db_handle);
|
||||||
|
|
||||||
push_game_update(&self.app_handle, &game_id, None, status);
|
push_game_update(&self.app_handle, &game_id, None, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn valid_platform(&self, platform: &Platform) -> Result<bool, String> {
|
fn fetch_process_handler(
|
||||||
let current = &self.current_platform;
|
&self,
|
||||||
Ok(self.game_launchers.contains_key(&(*current, *platform)))
|
db_lock: &Database,
|
||||||
|
state: &AppState,
|
||||||
|
target_platform: &Platform,
|
||||||
|
) -> Result<&(dyn ProcessHandler + Send + Sync), ProcessError> {
|
||||||
|
Ok(self
|
||||||
|
.game_launchers
|
||||||
|
.iter()
|
||||||
|
.find(|e| {
|
||||||
|
let (e_current, e_target) = e.0;
|
||||||
|
e_current == self.current_platform
|
||||||
|
&& e_target == *target_platform
|
||||||
|
&& e.1.valid_for_platform(db_lock, state, target_platform)
|
||||||
|
})
|
||||||
|
.ok_or(ProcessError::InvalidPlatform)?
|
||||||
|
.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn launch_process(&mut self, game_id: String) -> Result<(), ProcessError> {
|
pub fn valid_platform(&self, platform: &Platform, state: &AppState) -> Result<bool, String> {
|
||||||
|
let db_lock = borrow_db_checked();
|
||||||
|
let process_handler = self.fetch_process_handler(&db_lock, state, platform);
|
||||||
|
Ok(process_handler.is_ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn launch_process(
|
||||||
|
&mut self,
|
||||||
|
game_id: String,
|
||||||
|
state: &AppState,
|
||||||
|
) -> Result<(), ProcessError> {
|
||||||
if self.processes.contains_key(&game_id) {
|
if self.processes.contains_key(&game_id) {
|
||||||
return Err(ProcessError::AlreadyRunning);
|
return Err(ProcessError::AlreadyRunning);
|
||||||
}
|
}
|
||||||
@ -191,10 +231,6 @@ impl ProcessManager<'_> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut db_lock = borrow_db_mut_checked();
|
let mut db_lock = borrow_db_mut_checked();
|
||||||
debug!(
|
|
||||||
"Launching process {:?} with games {:?}",
|
|
||||||
&game_id, db_lock.applications.game_versions
|
|
||||||
);
|
|
||||||
|
|
||||||
let game_status = db_lock
|
let game_status = db_lock
|
||||||
.applications
|
.applications
|
||||||
@ -211,13 +247,15 @@ impl ProcessManager<'_> {
|
|||||||
version_name,
|
version_name,
|
||||||
install_dir,
|
install_dir,
|
||||||
} => (version_name, install_dir),
|
} => (version_name, install_dir),
|
||||||
GameDownloadStatus::PartiallyInstalled {
|
_ => return Err(ProcessError::NotInstalled),
|
||||||
version_name,
|
|
||||||
install_dir,
|
|
||||||
} => (version_name, install_dir),
|
|
||||||
_ => return Err(ProcessError::NotDownloaded),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"Launching process {:?} with version {:?}",
|
||||||
|
&game_id,
|
||||||
|
db_lock.applications.game_versions.get(&game_id).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
let game_version = db_lock
|
let game_version = db_lock
|
||||||
.applications
|
.applications
|
||||||
.game_versions
|
.game_versions
|
||||||
@ -227,7 +265,7 @@ impl ProcessManager<'_> {
|
|||||||
.ok_or(ProcessError::InvalidVersion)?;
|
.ok_or(ProcessError::InvalidVersion)?;
|
||||||
|
|
||||||
// TODO: refactor this path with open_process_logs
|
// TODO: refactor this path with open_process_logs
|
||||||
let game_log_folder = &self.log_output_dir.join(game_id);
|
let game_log_folder = &self.get_log_dir(game_id);
|
||||||
create_dir_all(game_log_folder).map_err(ProcessError::IOError)?;
|
create_dir_all(game_log_folder).map_err(ProcessError::IOError)?;
|
||||||
|
|
||||||
let current_time = chrono::offset::Local::now();
|
let current_time = chrono::offset::Local::now();
|
||||||
@ -251,13 +289,9 @@ impl ProcessManager<'_> {
|
|||||||
)))
|
)))
|
||||||
.map_err(ProcessError::IOError)?;
|
.map_err(ProcessError::IOError)?;
|
||||||
|
|
||||||
let current_platform = self.current_platform;
|
|
||||||
let target_platform = game_version.platform;
|
let target_platform = game_version.platform;
|
||||||
|
|
||||||
let game_launcher = self
|
let process_handler = self.fetch_process_handler(&db_lock, state, &target_platform)?;
|
||||||
.game_launchers
|
|
||||||
.get(&(current_platform, target_platform))
|
|
||||||
.ok_or(ProcessError::InvalidPlatform)?;
|
|
||||||
|
|
||||||
let (launch, args) = match game_status {
|
let (launch, args) = match game_status {
|
||||||
GameDownloadStatus::Installed {
|
GameDownloadStatus::Installed {
|
||||||
@ -278,16 +312,23 @@ impl ProcessManager<'_> {
|
|||||||
let launch = PathBuf::from_str(install_dir).unwrap().join(launch);
|
let launch = PathBuf::from_str(install_dir).unwrap().join(launch);
|
||||||
let launch = launch.to_str().unwrap();
|
let launch = launch.to_str().unwrap();
|
||||||
|
|
||||||
let launch_string = game_launcher.create_launch_process(
|
let launch_string = process_handler.create_launch_process(
|
||||||
&meta,
|
&meta,
|
||||||
launch.to_string(),
|
launch.to_string(),
|
||||||
args.to_vec(),
|
args.clone(),
|
||||||
game_version,
|
game_version,
|
||||||
install_dir,
|
install_dir,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let format_args = DropFormatArgs::new(
|
||||||
|
launch_string,
|
||||||
|
install_dir,
|
||||||
|
&game_version.launch_command,
|
||||||
|
launch.to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
let launch_string = SimpleCurlyFormat
|
let launch_string = SimpleCurlyFormat
|
||||||
.format(&game_version.launch_command_template, &[launch_string])
|
.format(&game_version.launch_command_template, format_args)
|
||||||
.map_err(|e| ProcessError::FormatError(e.to_string()))?
|
.map_err(|e| ProcessError::FormatError(e.to_string()))?
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
@ -306,9 +347,12 @@ impl ProcessManager<'_> {
|
|||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
command.args(vec!["-c", &launch_string]);
|
command.args(vec!["-c", &launch_string]);
|
||||||
|
|
||||||
|
debug!("final launch string:\n\n{launch_string}\n");
|
||||||
|
|
||||||
command
|
command
|
||||||
.stderr(error_file)
|
.stderr(error_file)
|
||||||
.stdout(log_file)
|
.stdout(log_file)
|
||||||
|
.env_remove("RUST_LOG")
|
||||||
.current_dir(install_dir);
|
.current_dir(install_dir);
|
||||||
|
|
||||||
let child = command.spawn().map_err(ProcessError::IOError)?;
|
let child = command.spawn().map_err(ProcessError::IOError)?;
|
||||||
@ -413,49 +457,6 @@ pub trait ProcessHandler: Send + 'static {
|
|||||||
game_version: &GameVersion,
|
game_version: &GameVersion,
|
||||||
current_dir: &str,
|
current_dir: &str,
|
||||||
) -> String;
|
) -> String;
|
||||||
}
|
|
||||||
|
|
||||||
struct NativeGameLauncher;
|
fn valid_for_platform(&self, db: &Database, state: &AppState, target: &Platform) -> bool;
|
||||||
impl ProcessHandler for NativeGameLauncher {
|
|
||||||
fn create_launch_process(
|
|
||||||
&self,
|
|
||||||
_meta: &DownloadableMetadata,
|
|
||||||
launch_command: String,
|
|
||||||
args: Vec<String>,
|
|
||||||
_game_version: &GameVersion,
|
|
||||||
_current_dir: &str,
|
|
||||||
) -> String {
|
|
||||||
format!("\"{}\" {}", launch_command, args.join(" "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const UMU_LAUNCHER_EXECUTABLE: &str = "umu-run";
|
|
||||||
struct UMULauncher;
|
|
||||||
impl ProcessHandler for UMULauncher {
|
|
||||||
fn create_launch_process(
|
|
||||||
&self,
|
|
||||||
_meta: &DownloadableMetadata,
|
|
||||||
launch_command: String,
|
|
||||||
args: Vec<String>,
|
|
||||||
game_version: &GameVersion,
|
|
||||||
_current_dir: &str,
|
|
||||||
) -> String {
|
|
||||||
debug!("Game override: \"{:?}\"", &game_version.umu_id_override);
|
|
||||||
let game_id = match &game_version.umu_id_override {
|
|
||||||
Some(game_override) => {
|
|
||||||
if game_override.is_empty() {
|
|
||||||
game_version.game_id.clone()
|
|
||||||
} else {
|
|
||||||
game_override.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => game_version.game_id.clone(),
|
|
||||||
};
|
|
||||||
format!(
|
|
||||||
"GAMEID={game_id} {umu} \"{launch}\" {args}",
|
|
||||||
umu = UMU_LAUNCHER_EXECUTABLE,
|
|
||||||
launch = launch_command,
|
|
||||||
args = args.join(" ")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,9 +12,7 @@ use crate::{
|
|||||||
database::{
|
database::{
|
||||||
db::{borrow_db_checked, borrow_db_mut_checked},
|
db::{borrow_db_checked, borrow_db_mut_checked},
|
||||||
models::data::DatabaseAuth,
|
models::data::DatabaseAuth,
|
||||||
},
|
}, error::{drop_server_error::DropServerError, remote_access_error::RemoteAccessError}, remote::utils::DROP_CLIENT_SYNC, AppState, AppStatus, User
|
||||||
error::{drop_server_error::DropServerError, remote_access_error::RemoteAccessError},
|
|
||||||
AppState, AppStatus, User,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@ -32,6 +30,7 @@ struct InitiateRequestBody {
|
|||||||
name: String,
|
name: String,
|
||||||
platform: String,
|
platform: String,
|
||||||
capabilities: HashMap<String, CapabilityConfiguration>,
|
capabilities: HashMap<String, CapabilityConfiguration>,
|
||||||
|
mode: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
@ -65,7 +64,7 @@ pub fn generate_authorization_header() -> String {
|
|||||||
pub fn fetch_user() -> Result<User, RemoteAccessError> {
|
pub fn fetch_user() -> Result<User, RemoteAccessError> {
|
||||||
let header = generate_authorization_header();
|
let header = generate_authorization_header();
|
||||||
|
|
||||||
let client = reqwest::blocking::Client::new();
|
let client = DROP_CLIENT_SYNC.clone();
|
||||||
let response = make_request(&client, &["/api/v1/client/user"], &[], |f| {
|
let response = make_request(&client, &["/api/v1/client/user"], &[], |f| {
|
||||||
f.header("Authorization", header)
|
f.header("Authorization", header)
|
||||||
})?
|
})?
|
||||||
@ -81,11 +80,11 @@ pub fn fetch_user() -> Result<User, RemoteAccessError> {
|
|||||||
return Err(RemoteAccessError::InvalidResponse(err));
|
return Err(RemoteAccessError::InvalidResponse(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
response.json::<User>().map_err(|e| e.into())
|
response.json::<User>().map_err(std::convert::Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn recieve_handshake_logic(app: &AppHandle, path: String) -> Result<(), RemoteAccessError> {
|
fn recieve_handshake_logic(app: &AppHandle, path: String) -> Result<(), RemoteAccessError> {
|
||||||
let path_chunks: Vec<&str> = path.split("/").collect();
|
let path_chunks: Vec<&str> = path.split('/').collect();
|
||||||
if path_chunks.len() != 3 {
|
if path_chunks.len() != 3 {
|
||||||
app.emit("auth/failed", ()).unwrap();
|
app.emit("auth/failed", ()).unwrap();
|
||||||
return Err(RemoteAccessError::HandshakeFailed(
|
return Err(RemoteAccessError::HandshakeFailed(
|
||||||
@ -101,12 +100,12 @@ fn recieve_handshake_logic(app: &AppHandle, path: String) -> Result<(), RemoteAc
|
|||||||
let client_id = path_chunks.get(1).unwrap();
|
let client_id = path_chunks.get(1).unwrap();
|
||||||
let token = path_chunks.get(2).unwrap();
|
let token = path_chunks.get(2).unwrap();
|
||||||
let body = HandshakeRequestBody {
|
let body = HandshakeRequestBody {
|
||||||
client_id: client_id.to_string(),
|
client_id: (*client_id).to_string(),
|
||||||
token: token.to_string(),
|
token: (*token).to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let endpoint = base_url.join("/api/v1/client/auth/handshake")?;
|
let endpoint = base_url.join("/api/v1/client/auth/handshake")?;
|
||||||
let client = reqwest::blocking::Client::new();
|
let client = DROP_CLIENT_SYNC.clone();
|
||||||
let response = client.post(endpoint).json(&body).send()?;
|
let response = client.post(endpoint).json(&body).send()?;
|
||||||
debug!("handshake responsded with {}", response.status().as_u16());
|
debug!("handshake responsded with {}", response.status().as_u16());
|
||||||
if !response.status().is_success() {
|
if !response.status().is_success() {
|
||||||
@ -166,7 +165,7 @@ pub fn recieve_handshake(app: AppHandle, path: String) {
|
|||||||
app.emit("auth/finished", ()).unwrap();
|
app.emit("auth/finished", ()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn auth_initiate_logic() -> Result<(), RemoteAccessError> {
|
pub fn auth_initiate_logic(mode: String) -> Result<String, RemoteAccessError> {
|
||||||
let base_url = {
|
let base_url = {
|
||||||
let db_lock = borrow_db_checked();
|
let db_lock = borrow_db_checked();
|
||||||
Url::parse(&db_lock.base_url.clone())?
|
Url::parse(&db_lock.base_url.clone())?
|
||||||
@ -182,9 +181,10 @@ pub fn auth_initiate_logic() -> Result<(), RemoteAccessError> {
|
|||||||
("peerAPI".to_owned(), CapabilityConfiguration {}),
|
("peerAPI".to_owned(), CapabilityConfiguration {}),
|
||||||
("cloudSaves".to_owned(), CapabilityConfiguration {}),
|
("cloudSaves".to_owned(), CapabilityConfiguration {}),
|
||||||
]),
|
]),
|
||||||
|
mode,
|
||||||
};
|
};
|
||||||
|
|
||||||
let client = reqwest::blocking::Client::new();
|
let client = DROP_CLIENT_SYNC.clone();
|
||||||
let response = client.post(endpoint.to_string()).json(&body).send()?;
|
let response = client.post(endpoint.to_string()).json(&body).send()?;
|
||||||
|
|
||||||
if response.status() != 200 {
|
if response.status() != 200 {
|
||||||
@ -194,13 +194,9 @@ pub fn auth_initiate_logic() -> Result<(), RemoteAccessError> {
|
|||||||
return Err(RemoteAccessError::HandshakeFailed(data.status_message));
|
return Err(RemoteAccessError::HandshakeFailed(data.status_message));
|
||||||
}
|
}
|
||||||
|
|
||||||
let redir_url = response.text()?;
|
let response = response.text()?;
|
||||||
let complete_redir_url = base_url.join(&redir_url)?;
|
|
||||||
|
|
||||||
debug!("opening web browser to continue authentication");
|
Ok(response)
|
||||||
webbrowser::open(complete_redir_url.as_ref()).unwrap();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup() -> (AppStatus, Option<User>) {
|
pub fn setup() -> (AppStatus, Option<User>) {
|
||||||
|
|||||||
@ -51,8 +51,15 @@ fn read_sync(base: &Path, key: &str) -> io::Result<Vec<u8>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn cache_object<D: Encode>(key: &str, data: &D) -> Result<(), RemoteAccessError> {
|
pub fn cache_object<D: Encode>(key: &str, data: &D) -> Result<(), RemoteAccessError> {
|
||||||
|
cache_object_db(key, data, &borrow_db_checked())
|
||||||
|
}
|
||||||
|
pub fn cache_object_db<D: Encode>(
|
||||||
|
key: &str,
|
||||||
|
data: &D,
|
||||||
|
database: &Database,
|
||||||
|
) -> Result<(), RemoteAccessError> {
|
||||||
let bytes = bitcode::encode(data);
|
let bytes = bitcode::encode(data);
|
||||||
write_sync(&borrow_db_checked().cache_dir, key, bytes).map_err(RemoteAccessError::Cache)
|
write_sync(&database.cache_dir, key, bytes).map_err(RemoteAccessError::Cache)
|
||||||
}
|
}
|
||||||
pub fn get_cached_object<D: Encode + DecodeOwned>(key: &str) -> Result<D, RemoteAccessError> {
|
pub fn get_cached_object<D: Encode + DecodeOwned>(key: &str) -> Result<D, RemoteAccessError> {
|
||||||
get_cached_object_db::<D>(key, &borrow_db_checked())
|
get_cached_object_db::<D>(key, &borrow_db_checked())
|
||||||
|
|||||||
@ -1,15 +1,14 @@
|
|||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use log::debug;
|
use futures_lite::StreamExt;
|
||||||
use reqwest::blocking::Client;
|
use log::{debug, warn};
|
||||||
|
use reqwest_websocket::{Message, RequestBuilderExt};
|
||||||
|
use serde::Deserialize;
|
||||||
use tauri::{AppHandle, Emitter, Manager};
|
use tauri::{AppHandle, Emitter, Manager};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
database::db::{borrow_db_checked, borrow_db_mut_checked},
|
database::db::{borrow_db_checked, borrow_db_mut_checked}, error::remote_access_error::RemoteAccessError, remote::{auth::generate_authorization_header, requests::make_request, utils::DROP_CLIENT_SYNC}, AppState, AppStatus
|
||||||
error::remote_access_error::RemoteAccessError,
|
|
||||||
remote::{auth::generate_authorization_header, requests::make_request},
|
|
||||||
AppState, AppStatus,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@ -42,7 +41,7 @@ pub fn gen_drop_url(path: String) -> Result<String, RemoteAccessError> {
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn fetch_drop_object(path: String) -> Result<Vec<u8>, RemoteAccessError> {
|
pub fn fetch_drop_object(path: String) -> Result<Vec<u8>, RemoteAccessError> {
|
||||||
let _drop_url = gen_drop_url(path.clone())?;
|
let _drop_url = gen_drop_url(path.clone())?;
|
||||||
let req = make_request(&Client::new(), &[&path], &[], |r| {
|
let req = make_request(&DROP_CLIENT_SYNC, &[&path], &[], |r| {
|
||||||
r.header("Authorization", generate_authorization_header())
|
r.header("Authorization", generate_authorization_header())
|
||||||
})?
|
})?
|
||||||
.send();
|
.send();
|
||||||
@ -91,7 +90,78 @@ pub fn retry_connect(state: tauri::State<'_, Mutex<AppState>>) {
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn auth_initiate() -> Result<(), RemoteAccessError> {
|
pub fn auth_initiate() -> Result<(), RemoteAccessError> {
|
||||||
auth_initiate_logic()
|
let base_url = {
|
||||||
|
let db_lock = borrow_db_checked();
|
||||||
|
Url::parse(&db_lock.base_url.clone())?
|
||||||
|
};
|
||||||
|
|
||||||
|
let redir_url = auth_initiate_logic("callback".to_string())?;
|
||||||
|
let complete_redir_url = base_url.join(&redir_url)?;
|
||||||
|
|
||||||
|
debug!("opening web browser to continue authentication");
|
||||||
|
webbrowser::open(complete_redir_url.as_ref()).unwrap();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct CodeWebsocketResponse {
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
response_type: String,
|
||||||
|
value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn auth_initiate_code(app: AppHandle) -> Result<String, RemoteAccessError> {
|
||||||
|
let base_url = {
|
||||||
|
let db_lock = borrow_db_checked();
|
||||||
|
Url::parse(&db_lock.base_url.clone())?
|
||||||
|
};
|
||||||
|
|
||||||
|
let code = auth_initiate_logic("code".to_string())?;
|
||||||
|
let header_code = code.clone();
|
||||||
|
|
||||||
|
tauri::async_runtime::spawn(async move {
|
||||||
|
let load = async || -> Result<(), RemoteAccessError> {
|
||||||
|
let ws_url = base_url.join("/api/v1/client/auth/code/ws")?;
|
||||||
|
let response = reqwest::Client::default()
|
||||||
|
.get(ws_url)
|
||||||
|
.header("Authorization", header_code)
|
||||||
|
.upgrade()
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut websocket = response.into_websocket().await?;
|
||||||
|
|
||||||
|
while let Some(token) = websocket.try_next().await? {
|
||||||
|
if let Message::Text(response) = token {
|
||||||
|
let response = serde_json::from_str::<CodeWebsocketResponse>(&response)
|
||||||
|
.map_err(|e| RemoteAccessError::UnparseableResponse(e.to_string()))?;
|
||||||
|
match response.response_type.as_str() {
|
||||||
|
"token" => {
|
||||||
|
let recieve_app = app.clone();
|
||||||
|
tauri::async_runtime::spawn_blocking(move || {
|
||||||
|
manual_recieve_handshake(recieve_app, response.value);
|
||||||
|
});
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
_ => return Err(RemoteAccessError::HandshakeFailed(response.value)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(RemoteAccessError::HandshakeFailed(
|
||||||
|
"Failed to connect to websocket".to_string(),
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = load().await;
|
||||||
|
if let Err(err) = result {
|
||||||
|
warn!("{err}");
|
||||||
|
app.emit("auth/failed", err.to_string()).unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
|||||||
@ -2,7 +2,7 @@ use http::{header::CONTENT_TYPE, response::Builder as ResponseBuilder};
|
|||||||
use log::warn;
|
use log::warn;
|
||||||
use tauri::UriSchemeResponder;
|
use tauri::UriSchemeResponder;
|
||||||
|
|
||||||
use crate::{DB, database::db::DatabaseImpls};
|
use crate::{database::db::DatabaseImpls, remote::utils::DROP_CLIENT_ASYNC, DB};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
auth::generate_authorization_header,
|
auth::generate_authorization_header,
|
||||||
@ -22,7 +22,7 @@ pub async fn fetch_object(request: http::Request<Vec<u8>>, responder: UriSchemeR
|
|||||||
}
|
}
|
||||||
|
|
||||||
let header = generate_authorization_header();
|
let header = generate_authorization_header();
|
||||||
let client = reqwest::Client::new();
|
let client = DROP_CLIENT_ASYNC.clone();
|
||||||
let url = format!("{}api/v1/client/object/{object_id}", DB.fetch_base_url());
|
let url = format!("{}api/v1/client/object/{object_id}", DB.fetch_base_url());
|
||||||
let response = client.get(url).header("Authorization", header).send().await;
|
let response = client.get(url).header("Authorization", header).send().await;
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ pub async fn fetch_object(request: http::Request<Vec<u8>>, responder: UriSchemeR
|
|||||||
match cache_result {
|
match cache_result {
|
||||||
Ok(cache_result) => responder.respond(cache_result.into()),
|
Ok(cache_result) => responder.respond(cache_result.into()),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("{e}")
|
warn!("{e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use http::{uri::PathAndQuery, Request, Response, StatusCode, Uri};
|
use http::{uri::PathAndQuery, Request, Response, StatusCode, Uri};
|
||||||
use reqwest::blocking::Client;
|
|
||||||
use tauri::UriSchemeResponder;
|
use tauri::UriSchemeResponder;
|
||||||
|
|
||||||
use crate::database::db::borrow_db_checked;
|
use crate::{database::db::borrow_db_checked, remote::utils::DROP_CLIENT_SYNC};
|
||||||
|
|
||||||
pub fn handle_server_proto_offline(_request: Request<Vec<u8>>, responder: UriSchemeResponder) {
|
pub fn handle_server_proto_offline(_request: Request<Vec<u8>>, responder: UriSchemeResponder) {
|
||||||
let four_oh_four = Response::builder()
|
let four_oh_four = Response::builder()
|
||||||
@ -38,7 +37,7 @@ pub fn handle_server_proto(request: Request<Vec<u8>>, responder: UriSchemeRespon
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let client = Client::new();
|
let client = DROP_CLIENT_SYNC.clone();
|
||||||
let response = client
|
let response = client
|
||||||
.request(request.method().clone(), new_uri.to_string())
|
.request(request.method().clone(), new_uri.to_string())
|
||||||
.header("Authorization", format!("Bearer {web_token}"))
|
.header("Authorization", format!("Bearer {web_token}"))
|
||||||
|
|||||||
@ -1,12 +1,18 @@
|
|||||||
use std::sync::Mutex;
|
use std::{
|
||||||
|
fs::{self, File},
|
||||||
|
io::Read,
|
||||||
|
sync::{LazyLock, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
use log::{debug, warn};
|
use log::{debug, info, warn};
|
||||||
|
use reqwest::Certificate;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
database::db::borrow_db_mut_checked, error::remote_access_error::RemoteAccessError, AppState,
|
AppState, AppStatus,
|
||||||
AppStatus,
|
database::db::{DATA_ROOT_DIR, borrow_db_mut_checked},
|
||||||
|
error::remote_access_error::RemoteAccessError,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@ -15,6 +21,60 @@ struct DropHealthcheck {
|
|||||||
app_name: String,
|
app_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub static DROP_CLIENT_SYNC: LazyLock<reqwest::blocking::Client> = LazyLock::new(get_client_sync);
|
||||||
|
pub static DROP_CLIENT_ASYNC: LazyLock<reqwest::Client> = LazyLock::new(get_client_async);
|
||||||
|
|
||||||
|
fn fetch_certificates() -> Vec<Certificate> {
|
||||||
|
let certificate_dir = DATA_ROOT_DIR.join("certificates");
|
||||||
|
|
||||||
|
let mut certs = Vec::new();
|
||||||
|
match fs::read_dir(certificate_dir) {
|
||||||
|
Ok(c) => {
|
||||||
|
for entry in c {
|
||||||
|
match entry {
|
||||||
|
Ok(c) => {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
File::open(c.path()).unwrap().read_to_end(&mut buf).unwrap();
|
||||||
|
|
||||||
|
for cert in Certificate::from_pem_bundle(&buf).unwrap() {
|
||||||
|
certs.push(cert);
|
||||||
|
}
|
||||||
|
info!(
|
||||||
|
"added {} certificate(s) from {}",
|
||||||
|
certs.len(),
|
||||||
|
c.file_name().into_string().unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(_) => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
debug!("not loading certificates due to error: {e}");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
certs
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_client_sync() -> reqwest::blocking::Client {
|
||||||
|
let mut client = reqwest::blocking::ClientBuilder::new();
|
||||||
|
|
||||||
|
let certs = fetch_certificates();
|
||||||
|
for cert in certs {
|
||||||
|
client = client.add_root_certificate(cert);
|
||||||
|
}
|
||||||
|
client.build().unwrap()
|
||||||
|
}
|
||||||
|
pub fn get_client_async() -> reqwest::Client {
|
||||||
|
let mut client = reqwest::ClientBuilder::new();
|
||||||
|
|
||||||
|
let certs = fetch_certificates();
|
||||||
|
for cert in certs {
|
||||||
|
client = client.add_root_certificate(cert);
|
||||||
|
}
|
||||||
|
client.build().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn use_remote_logic(
|
pub fn use_remote_logic(
|
||||||
url: String,
|
url: String,
|
||||||
state: tauri::State<'_, Mutex<AppState<'_>>>,
|
state: tauri::State<'_, Mutex<AppState<'_>>>,
|
||||||
@ -24,7 +84,8 @@ pub fn use_remote_logic(
|
|||||||
|
|
||||||
// Test Drop url
|
// Test Drop url
|
||||||
let test_endpoint = base_url.join("/api/v1")?;
|
let test_endpoint = base_url.join("/api/v1")?;
|
||||||
let response = reqwest::blocking::get(test_endpoint.to_string())?;
|
let client = DROP_CLIENT_SYNC.clone();
|
||||||
|
let response = client.get(test_endpoint.to_string()).send()?;
|
||||||
|
|
||||||
let result: DropHealthcheck = response.json()?;
|
let result: DropHealthcheck = response.json()?;
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2.0.0",
|
"$schema": "https://schema.tauri.app/config/2.0.0",
|
||||||
"productName": "Drop Desktop Client",
|
"productName": "Drop Desktop Client",
|
||||||
"version": "0.3.0",
|
"version": "0.3.1-appimage",
|
||||||
"identifier": "dev.drop.app",
|
"identifier": "dev.drop.app",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "yarn dev --port 1432",
|
"beforeDevCommand": "yarn dev --port 1432",
|
||||||
@ -27,7 +27,7 @@
|
|||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"active": true,
|
"active": true,
|
||||||
"targets": ["nsis", "deb", "rpm", "dmg", "appimage"],
|
"targets": ["nsis", "deb", "rpm", "dmg"],
|
||||||
"windows": {
|
"windows": {
|
||||||
"nsis": {
|
"nsis": {
|
||||||
"installMode": "both"
|
"installMode": "both"
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
// https://nuxt.com/docs/guide/concepts/typescript
|
// https://nuxt.com/docs/guide/concepts/typescript
|
||||||
"extends": "./.nuxt/tsconfig.json"
|
"extends": "./.nuxt/tsconfig.json",
|
||||||
|
"exclude": ["src-tauri/**/*"]
|
||||||
}
|
}
|
||||||
|
|||||||
3
types.ts
3
types.ts
@ -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 = {
|
||||||
|
|||||||
Reference in New Issue
Block a user