Compare commits

..

2 Commits

Author SHA1 Message Date
DecDuck
f1bc7bf418 fix: update all versions to -mac specific 2025-08-02 12:02:34 +10:00
DecDuck
cb2295dd7c feat: add macos signing args 2025-08-02 12:00:25 +10:00
121 changed files with 8889 additions and 9726 deletions

View File

@@ -54,37 +54,6 @@ jobs:
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
# webkitgtk 4.0 is for Tauri v1 - webkitgtk 4.1 is for Tauri v2.
- name: Import Apple Developer Certificate
if: matrix.platform == 'macos-latest'
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
echo $APPLE_CERTIFICATE | base64 --decode > certificate.p12
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security set-keychain-settings -t 3600 -u build.keychain
curl https://droposs.org/drop.crt --output drop.pem
sudo security authorizationdb write com.apple.trust-settings.admin allow
sudo security add-trusted-cert -d -r trustRoot -k build.keychain -p codeSign -u -1 drop.pem
sudo security authorizationdb remove com.apple.trust-settings.admin
security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain
security find-identity -v -p codesigning build.keychain
- name: Verify Certificate
if: matrix.platform == 'macos-latest'
run: |
CERT_INFO=$(security find-identity -v -p codesigning build.keychain | grep "Drop OSS")
CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}')
echo "CERT_ID=$CERT_ID" >> $GITHUB_ENV
echo "Certificate imported. Using identity: $CERT_ID"
- name: install frontend dependencies
run: yarn install # change this to npm, pnpm or bun depending on which one you use.
@@ -93,12 +62,11 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ env.CERT_ID }}
NO_STRIP: true
APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY }}
with:
tagName: v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version.
releaseName: 'Auto-release v__VERSION__'
releaseBody: 'See the assets to download this version and install. This release was created automatically.'
releaseDraft: false
prerelease: true
args: ${{ matrix.args }}
args: ${{ matrix.args }}

5
.gitignore vendored
View File

@@ -26,7 +26,4 @@ dist-ssr
.output
src-tauri/flamegraph.svg
src-tauri/perf*
/*.AppImage
/squashfs-root
src-tauri/perf*

9
.gitmodules vendored
View File

@@ -1,6 +1,9 @@
[submodule "drop-base"]
path = drop-base
url = https://github.com/drop-oss/drop-base
[submodule "src-tauri/tailscale/libtailscale"]
path = src-tauri/tailscale/libtailscale
url = https://github.com/tailscale/libtailscale.git
[submodule "libs/drop-base"]
path = libs/drop-base
url = https://github.com/drop-oss/drop-base.git
[submodule "src-tauri/umu/umu-launcher"]
path = src-tauri/umu/umu-launcher
url = https://github.com/Open-Wine-Components/umu-launcher.git

View File

@@ -10,6 +10,8 @@
import "~/composables/downloads.js";
import { invoke } from "@tauri-apps/api/core";
import { AppStatus } from "~/types";
import { listen } from "@tauri-apps/api/event";
import { useAppState } from "./composables/app-state.js";
import {
initialNavigation,
@@ -19,26 +21,18 @@ import {
const router = useRouter();
const state = useAppState();
try {
state.value = JSON.parse(await invoke("fetch_state"));
} catch (e) {
console.error("failed to parse state", e);
}
async function fetchState() {
router.beforeEach(async () => {
try {
state.value = JSON.parse(await invoke("fetch_state"));
if (!state.value)
throw createError({
statusCode: 500,
statusMessage: `App state is: ${state.value}`,
fatal: true,
});
} catch (e) {
console.error("failed to parse state", e);
throw e;
}
}
await fetchState();
// This is inefficient but apparently we do it lol
router.beforeEach(async () => {
await fetchState();
});
setupHooks();

View File

Before

Width:  |  Height:  |  Size: 6.5 MiB

After

Width:  |  Height:  |  Size: 6.5 MiB

View File

@@ -1,48 +0,0 @@
import fs from "fs";
import process from "process";
import childProcess from "child_process";
import createLogger from "pino";
const OUTPUT = "./.output";
const logger = createLogger({ transport: { target: "pino-pretty" } });
async function spawn(exec, opts) {
const output = childProcess.spawn(exec, { ...opts, shell: true });
output.stdout.on("data", (data) => {
process.stdout.write(data);
});
output.stderr.on("data", (data) => {
process.stderr.write(data);
});
return await new Promise((resolve, reject) => {
output.on("error", (err) => reject(err));
output.on("exit", () => resolve());
});
}
const views = fs.readdirSync(".").filter((view) => {
const expectedPath = `./${view}/package.json`;
return fs.existsSync(expectedPath);
});
fs.mkdirSync(OUTPUT, { recursive: true });
for (const view of views) {
const loggerChild = logger.child({});
process.chdir(`./${view}`);
loggerChild.info(`Install deps for "${view}"`);
await spawn("yarn");
loggerChild.info(`Building "${view}"`);
await spawn("yarn build", {
env: { ...process.env, NUXT_APP_BASE_URL: `/${view}/` },
});
process.chdir("..");
fs.cpSync(`./${view}/.output/public`, `${OUTPUT}/${view}`, {
recursive: true,
});
}

View File

@@ -1,78 +1,52 @@
<template>
<!-- Do not add scale animations to this: https://stackoverflow.com/a/35683068 -->
<div class="inline-flex divide-x divide-zinc-900">
<button
type="button"
@click="() => buttonActions[props.status.type]()"
:class="[
styles[props.status.type],
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"
/>
<button type="button" @click="() => buttonActions[props.status.type]()" :class="[
styles[props.status.type],
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] }}
</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">
<MenuButton
:class="[
styles[props.status.type],
'inline-flex w-full h-full justify-center items-center rounded-r-md px-1 py-2 text-sm font-semibold shadow-sm group',
'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2',
]"
>
<MenuButton :class="[
styles[props.status.type],
'inline-flex w-full h-full justify-center items-center rounded-r-md px-1 py-2 text-sm font-semibold shadow-sm group',
'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2',
]">
<ChevronDownIcon class="size-5" aria-hidden="true" />
</MenuButton>
</div>
<transition
enter-active-class="transition ease-out duration-100"
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"
>
<transition enter-active-class="transition ease-out duration-100" 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
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">
<MenuItem v-if="showOptions" v-slot="{ active }">
<button
@click="() => emit('options')"
:class="[
active
? 'bg-zinc-800 text-zinc-100 outline-none'
: 'text-zinc-400',
'w-full block px-4 py-2 text-sm inline-flex justify-between',
]"
>
Options
<Cog6ToothIcon class="size-5" />
</button>
<MenuItem v-slot="{ active }">
<button @click="() => emit('options')" :class="[
active
? 'bg-zinc-800 text-zinc-100 outline-none'
: 'text-zinc-400',
'w-full block px-4 py-2 text-sm inline-flex justify-between',
]">
Options
<Cog6ToothIcon class="size-5" />
</button>
</MenuItem>
<MenuItem v-slot="{ active }">
<button
@click="() => emit('uninstall')"
:class="[
active
? 'bg-zinc-800 text-zinc-100 outline-none'
: 'text-zinc-400',
'w-full block px-4 py-2 text-sm inline-flex justify-between',
]"
>
Uninstall
<TrashIcon class="size-5" />
</button>
<button @click="() => emit('uninstall')" :class="[
active
? 'bg-zinc-800 text-zinc-100 outline-none'
: 'text-zinc-400',
'w-full block px-4 py-2 text-sm inline-flex justify-between',
]">
Uninstall
<TrashIcon class="size-5" />
</button>
</MenuItem>
</div>
</MenuItems>
@@ -87,7 +61,6 @@ import {
ChevronDownIcon,
PlayIcon,
QueueListIcon,
ServerIcon,
StopIcon,
WrenchIcon,
} from "@heroicons/vue/20/solid";
@@ -105,7 +78,7 @@ const emit = defineEmits<{
(e: "uninstall"): void;
(e: "kill"): void;
(e: "options"): void;
(e: "resume"): void;
(e: "resume"): void
}>();
const showDropdown = computed(
@@ -115,10 +88,6 @@ const showDropdown = computed(
props.status.type === GameStatusEnum.PartiallyInstalled
);
const showOptions = computed(
() => props.status.type === GameStatusEnum.Installed
);
const styles: { [key in GameStatusEnum]: string } = {
[GameStatusEnum.Remote]:
"bg-blue-600 text-white hover:bg-blue-500 focus-visible:outline-blue-600 hover:bg-blue-500",
@@ -126,8 +95,6 @@ const styles: { [key in GameStatusEnum]: string } = {
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
[GameStatusEnum.Downloading]:
"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]:
"bg-yellow-600 text-white hover:bg-yellow-500 focus-visible:outline-yellow-600 hover:bg-yellow-500",
[GameStatusEnum.Installed]:
@@ -139,45 +106,42 @@ const styles: { [key in GameStatusEnum]: string } = {
[GameStatusEnum.Running]:
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
[GameStatusEnum.PartiallyInstalled]:
"bg-blue-600 text-white hover:bg-blue-500 focus-visible:outline-blue-600 hover:bg-blue-500",
"bg-gray-600 text-white hover:bg-gray-500 focus-visible:outline-gray-600 hover:bg-gray-500"
};
const buttonNames: { [key in GameStatusEnum]: string } = {
[GameStatusEnum.Remote]: "Install",
[GameStatusEnum.Queued]: "Queued",
[GameStatusEnum.Downloading]: "Downloading",
[GameStatusEnum.Validating]: "Validating",
[GameStatusEnum.SetupRequired]: "Setup",
[GameStatusEnum.Installed]: "Play",
[GameStatusEnum.Updating]: "Updating",
[GameStatusEnum.Uninstalling]: "Uninstalling",
[GameStatusEnum.Running]: "Stop",
[GameStatusEnum.PartiallyInstalled]: "Resume",
[GameStatusEnum.PartiallyInstalled]: "Resume"
};
const buttonIcons: { [key in GameStatusEnum]: Component } = {
[GameStatusEnum.Remote]: ArrowDownTrayIcon,
[GameStatusEnum.Queued]: QueueListIcon,
[GameStatusEnum.Downloading]: ArrowDownTrayIcon,
[GameStatusEnum.Validating]: ServerIcon,
[GameStatusEnum.SetupRequired]: WrenchIcon,
[GameStatusEnum.Installed]: PlayIcon,
[GameStatusEnum.Updating]: ArrowDownTrayIcon,
[GameStatusEnum.Uninstalling]: TrashIcon,
[GameStatusEnum.Running]: StopIcon,
[GameStatusEnum.PartiallyInstalled]: ArrowDownTrayIcon,
[GameStatusEnum.PartiallyInstalled]: ArrowDownTrayIcon
};
const buttonActions: { [key in GameStatusEnum]: () => void } = {
[GameStatusEnum.Remote]: () => emit("install"),
[GameStatusEnum.Queued]: () => emit("queue"),
[GameStatusEnum.Downloading]: () => emit("queue"),
[GameStatusEnum.Validating]: () => emit("queue"),
[GameStatusEnum.SetupRequired]: () => emit("launch"),
[GameStatusEnum.Installed]: () => emit("launch"),
[GameStatusEnum.Updating]: () => emit("queue"),
[GameStatusEnum.Uninstalling]: () => {},
[GameStatusEnum.Uninstalling]: () => { },
[GameStatusEnum.Running]: () => emit("kill"),
[GameStatusEnum.PartiallyInstalled]: () => emit("resume"),
[GameStatusEnum.PartiallyInstalled]: () => emit("resume")
};
</script>

View File

@@ -37,7 +37,7 @@
<component class="h-5" :is="item.icon" />
</HeaderWidget>
</li>
<OfflineHeaderWidget v-if="state?.status === AppStatus.Offline" />
<OfflineHeaderWidget v-if="state.status === AppStatus.Offline" />
<HeaderUserWidget />
</ol>
</div>

View File

@@ -1,5 +1,5 @@
<template>
<Menu v-if="state?.user" as="div" class="relative inline-block">
<Menu v-if="state.user" as="div" class="relative inline-block">
<MenuButton>
<HeaderWidget>
<div class="inline-flex items-center text-zinc-300 hover:text-white">
@@ -87,7 +87,7 @@ router.afterEach(() => {
const state = useAppState();
const profilePictureUrl: string = await useObject(
state.value?.user?.profilePictureObjectId ?? ""
state.value.user?.profilePictureObjectId ?? ""
);
const adminUrl: string = await invoke("gen_drop_url", {
path: "/admin",

View File

@@ -81,20 +81,18 @@ import { listen } from "@tauri-apps/api/event";
const gameStatusTextStyle: { [key in GameStatusEnum]: string } = {
[GameStatusEnum.Installed]: "text-green-500",
[GameStatusEnum.Downloading]: "text-blue-500",
[GameStatusEnum.Validating]: "text-blue-300",
[GameStatusEnum.Running]: "text-green-500",
[GameStatusEnum.Remote]: "text-zinc-500",
[GameStatusEnum.Queued]: "text-blue-500",
[GameStatusEnum.Updating]: "text-blue-500",
[GameStatusEnum.Uninstalling]: "text-zinc-100",
[GameStatusEnum.SetupRequired]: "text-yellow-500",
[GameStatusEnum.PartiallyInstalled]: "text-gray-400",
[GameStatusEnum.PartiallyInstalled]: "text-gray-600",
};
const gameStatusText: { [key in GameStatusEnum]: string } = {
[GameStatusEnum.Remote]: "Not installed",
[GameStatusEnum.Queued]: "Queued",
[GameStatusEnum.Downloading]: "Downloading...",
[GameStatusEnum.Validating]: "Validating...",
[GameStatusEnum.Installed]: "Installed",
[GameStatusEnum.Updating]: "Updating...",
[GameStatusEnum.Uninstalling]: "Uninstalling...",

3
composables/app-state.ts Normal file
View File

@@ -0,0 +1,3 @@
import type { AppState } from "~/types";
export const useAppState = () => useState<AppState>("state");

View File

@@ -65,13 +65,7 @@ export function setupHooks() {
*/
}
export function initialNavigation(state: ReturnType<typeof useAppState>) {
if (!state.value)
throw createError({
statusCode: 500,
statusMessage: "App state not valid",
fatal: true,
});
export function initialNavigation(state: Ref<AppState>) {
const router = useRouter();
switch (state.value.status) {

View File

@@ -7,7 +7,6 @@
class="mx-auto w-full max-w-7xl px-6 pt-6 sm:pt-10 lg:col-span-2 lg:col-start-1 lg:row-start-1 lg:px-8"
>
<Logo class="h-10 w-auto sm:h-12" />
</header>
<main
class="mx-auto w-full max-w-7xl px-6 py-24 sm:py-32 lg:col-span-2 lg:col-start-1 lg:row-start-2 lg:px-8"

View File

@@ -1,3 +0,0 @@
import type { AppState } from "~/types";
export const useAppState = () => useState<AppState | undefined>("state");

View File

@@ -1,37 +0,0 @@
{
"name": "view",
"private": true,
"version": "0.3.2-dl",
"type": "module",
"scripts": {
"build": "nuxt generate",
"dev": "nuxt dev",
"postinstall": "nuxt prepare",
"tauri": "tauri"
},
"dependencies": {
"@headlessui/vue": "^1.7.23",
"@heroicons/vue": "^2.1.5",
"@nuxtjs/tailwindcss": "^6.12.2",
"@tauri-apps/api": "^2.7.0",
"koa": "^2.16.1",
"markdown-it": "^14.1.0",
"micromark": "^4.0.1",
"nuxt": "^3.16.0",
"scss": "^0.2.4",
"vue-router": "latest",
"vuedraggable": "^4.1.0"
},
"devDependencies": {
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"@types/markdown-it": "^14.1.2",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.47",
"sass-embedded": "^1.79.4",
"tailwindcss": "^3.4.13",
"typescript": "^5.8.3",
"vue-tsc": "^2.2.10"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

View File

@@ -1,5 +0,0 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json",
"exclude": ["src-tauri/**/*"]
}

File diff suppressed because it is too large Load Diff

View File

@@ -13,9 +13,5 @@ export default defineNuxtConfig({
ssr: false,
extends: [["../libs/drop-base"]],
app: {
baseURL: "/main",
}
extends: [["./drop-base"]],
});

View File

@@ -1,22 +0,0 @@
## This script is largely useless, because there's not much we can do about AppImage size
ARCH=$(uname -m)
# build tauri apps
# NO_STRIP=true yarn tauri build -- --verbose
# unpack appimage
APPIMAGE=$(ls ./src-tauri/target/release/bundle/appimage/*.AppImage)
"$APPIMAGE" --appimage-extract
# strip binary
APPIMAGE_UNPACK="./squashfs-root"
find $APPIMAGE_UNPACK -type f -exec strip -s {} \;
APPIMAGETOOL=$(echo "obsolete-appimagetool-$ARCH.AppImage")
wget -O $APPIMAGETOOL "https://github.com/AppImage/AppImageKit/releases/download/13/$APPIMAGETOOL"
chmod +x $APPIMAGETOOL
APPIMAGE_OUTPUT=$(./$APPIMAGETOOL $APPIMAGE_UNPACK | grep ".AppImage" | grep squashfs-root | awk '{ print $6 }')
mv $APPIMAGE_OUTPUT "$APPIMAGE"

View File

@@ -1,22 +1,46 @@
{
"name": "drop-app",
"private": true,
"version": "0.3.1-mac",
"type": "module",
"scripts": {
"build": "node ./build.mjs",
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare",
"tauri": "tauri"
},
"dependencies": {
"@tauri-apps/api": "^2.7.0",
"@tauri-apps/plugin-deep-link": "^2.4.1",
"@tauri-apps/plugin-dialog": "^2.3.2",
"@headlessui/vue": "^1.7.23",
"@heroicons/vue": "^2.1.5",
"@nuxtjs/tailwindcss": "^6.12.2",
"@tauri-apps/api": ">=2.0.0",
"@tauri-apps/plugin-deep-link": "~2",
"@tauri-apps/plugin-dialog": "^2.0.1",
"@tauri-apps/plugin-opener": "^2.4.0",
"@tauri-apps/plugin-os": "^2.3.0",
"@tauri-apps/plugin-shell": "^2.3.0",
"pino": "^9.7.0",
"pino-pretty": "^13.1.1"
"@tauri-apps/plugin-os": "~2",
"@tauri-apps/plugin-shell": "^2.2.1",
"koa": "^2.16.1",
"markdown-it": "^14.1.0",
"micromark": "^4.0.1",
"nuxt": "^3.16.0",
"scss": "^0.2.4",
"vue": "latest",
"vue-router": "latest",
"vuedraggable": "^4.1.0"
},
"devDependencies": {
"@tauri-apps/cli": "^2.7.1"
}
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"@tauri-apps/cli": ">=2.0.0",
"@types/markdown-it": "^14.1.2",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.47",
"sass-embedded": "^1.79.4",
"tailwindcss": "^3.4.13",
"typescript": "^5.8.3",
"vue-tsc": "^2.2.10"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

View File

@@ -368,13 +368,6 @@
</template>
</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
v-if="status.type === GameStatusEnum.Installed"
v-model="configureModalOpen"

244
src-tauri/Cargo.lock generated
View File

@@ -898,7 +898,7 @@ dependencies = [
"bitflags 2.9.1",
"core-foundation 0.10.1",
"core-graphics-types",
"foreign-types 0.5.0",
"foreign-types",
"libc",
]
@@ -1284,7 +1284,7 @@ dependencies = [
[[package]]
name = "drop-app"
version = "0.3.2-dl"
version = "0.3.1-mac"
dependencies = [
"atomic-instant-full",
"bitcode",
@@ -1301,13 +1301,11 @@ dependencies = [
"hex 0.4.3",
"http 1.3.1",
"http-serde 2.1.1",
"humansize",
"known-folders",
"log",
"log4rs",
"md5",
"native_model",
"page_size",
"parking_lot 0.12.3",
"rand 0.9.1",
"rayon",
@@ -1325,7 +1323,6 @@ dependencies = [
"sha1",
"shared_child",
"slice-deque",
"sysinfo",
"tar",
"tauri",
"tauri-build",
@@ -1588,15 +1585,6 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared 0.1.1",
]
[[package]]
name = "foreign-types"
version = "0.5.0"
@@ -1604,7 +1592,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
dependencies = [
"foreign-types-macros",
"foreign-types-shared 0.3.1",
"foreign-types-shared",
]
[[package]]
@@ -1618,12 +1606,6 @@ dependencies = [
"syn 2.0.101",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "foreign-types-shared"
version = "0.3.1"
@@ -2312,15 +2294,6 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "humansize"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
dependencies = [
"libm",
]
[[package]]
name = "humantime"
version = "2.2.0"
@@ -2388,22 +2361,6 @@ dependencies = [
"webpki-roots",
]
[[package]]
name = "hyper-tls"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [
"bytes",
"http-body-util",
"hyper 1.6.0",
"hyper-util",
"native-tls",
"tokio",
"tokio-native-tls",
"tower-service",
]
[[package]]
name = "hyper-util"
version = "0.1.13"
@@ -2816,9 +2773,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.174"
version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "libloading"
@@ -2830,12 +2787,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "libm"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[package]]
name = "libredox"
version = "0.1.3"
@@ -3098,23 +3049,6 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "native-tls"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
dependencies = [
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "native_model"
version = "0.6.1"
@@ -3207,15 +3141,6 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "ntapi"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
dependencies = [
"winapi",
]
[[package]]
name = "num-bigint"
version = "0.4.6"
@@ -3412,16 +3337,6 @@ dependencies = [
"objc2-core-foundation",
]
[[package]]
name = "objc2-io-kit"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a"
dependencies = [
"libc",
"objc2-core-foundation",
]
[[package]]
name = "objc2-io-surface"
version = "0.3.1"
@@ -3552,50 +3467,6 @@ dependencies = [
"pathdiff",
]
[[package]]
name = "openssl"
version = "0.10.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
dependencies = [
"bitflags 2.9.1",
"cfg-if",
"foreign-types 0.3.2",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
]
[[package]]
name = "openssl-probe"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
name = "openssl-sys"
version = "0.9.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "option-ext"
version = "0.2.0"
@@ -3652,16 +3523,6 @@ dependencies = [
"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]]
name = "pango"
version = "0.18.3"
@@ -4434,13 +4295,11 @@ dependencies = [
"http-body-util",
"hyper 1.6.0",
"hyper-rustls",
"hyper-tls",
"hyper-util",
"ipnet",
"js-sys",
"log",
"mime",
"native-tls",
"once_cell",
"percent-encoding",
"pin-project-lite",
@@ -4452,7 +4311,6 @@ dependencies = [
"serde_urlencoded",
"sync_wrapper 1.0.2",
"tokio",
"tokio-native-tls",
"tokio-rustls",
"tokio-util",
"tower",
@@ -4731,15 +4589,6 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "schannel"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "schemars"
version = "0.8.22"
@@ -4773,29 +4622,6 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "security-framework"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
"bitflags 2.9.1",
"core-foundation 0.9.4",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "selectors"
version = "0.24.0"
@@ -5165,7 +4991,7 @@ dependencies = [
"bytemuck",
"cfg_aliases",
"core-graphics",
"foreign-types 0.5.0",
"foreign-types",
"js-sys",
"log",
"objc2 0.5.2",
@@ -5353,20 +5179,6 @@ dependencies = [
"libc",
]
[[package]]
name = "sysinfo"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "252800745060e7b9ffb7b2badbd8b31cfa4aa2e61af879d0a3bf2a317c20217d"
dependencies = [
"libc",
"memchr",
"ntapi",
"objc2-core-foundation",
"objc2-io-kit",
"windows",
]
[[package]]
name = "system-configuration"
version = "0.5.1"
@@ -5479,9 +5291,9 @@ dependencies = [
[[package]]
name = "tauri"
version = "2.7.0"
version = "2.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "352a4bc7bf6c25f5624227e3641adf475a6535707451b09bb83271df8b7a6ac7"
checksum = "124e129c9c0faa6bec792c5948c89e86c90094133b0b9044df0ce5f0a8efaa0d"
dependencies = [
"anyhow",
"bytes",
@@ -5530,9 +5342,9 @@ dependencies = [
[[package]]
name = "tauri-build"
version = "2.3.1"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "182d688496c06bf08ea896459bf483eb29cdff35c1c4c115fb14053514303064"
checksum = "12f025c389d3adb83114bec704da973142e82fc6ec799c7c750c5e21cefaec83"
dependencies = [
"anyhow",
"cargo_toml",
@@ -5552,9 +5364,9 @@ dependencies = [
[[package]]
name = "tauri-codegen"
version = "2.3.1"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b54a99a6cd8e01abcfa61508177e6096a4fe2681efecee9214e962f2f073ae4a"
checksum = "f5df493a1075a241065bc865ed5ef8d0fbc1e76c7afdc0bf0eccfaa7d4f0e406"
dependencies = [
"base64 0.22.1",
"brotli",
@@ -5579,9 +5391,9 @@ dependencies = [
[[package]]
name = "tauri-macros"
version = "2.3.2"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7945b14dc45e23532f2ded6e120170bbdd4af5ceaa45784a6b33d250fbce3f9e"
checksum = "f237fbea5866fa5f2a60a21bea807a2d6e0379db070d89c3a10ac0f2d4649bbc"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@@ -5761,9 +5573,9 @@ dependencies = [
[[package]]
name = "tauri-runtime"
version = "2.7.1"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b1cc885be806ea15ff7b0eb47098a7b16323d9228876afda329e34e2d6c4676"
checksum = "9e7bb73d1bceac06c20b3f755b2c8a2cb13b20b50083084a8cf3700daf397ba4"
dependencies = [
"cookie",
"dpi",
@@ -5783,9 +5595,9 @@ dependencies = [
[[package]]
name = "tauri-runtime-wry"
version = "2.7.2"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe653a2fbbef19fe898efc774bc52c8742576342a33d3d028c189b57eb1d2439"
checksum = "902b5aa9035e16f342eb64f8bf06ccdc2808e411a2525ed1d07672fa4e780bad"
dependencies = [
"gtk",
"http 1.3.1",
@@ -5810,9 +5622,9 @@ dependencies = [
[[package]]
name = "tauri-utils"
version = "2.6.0"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330c15cabfe1d9f213478c9e8ec2b0c76dab26bb6f314b8ad1c8a568c1d186e"
checksum = "41743bbbeb96c3a100d234e5a0b60a46d5aa068f266160862c7afdbf828ca02e"
dependencies = [
"anyhow",
"brotli",
@@ -6037,16 +5849,6 @@ dependencies = [
"syn 2.0.101",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.26.2"
@@ -6458,12 +6260,6 @@ version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version-compare"
version = "0.2.0"

View File

@@ -1,6 +1,6 @@
[package]
name = "drop-app"
version = "0.3.2-dl"
version = "0.3.1-mac"
description = "The client application for the open-source, self-hosted game distribution platform Drop"
authors = ["Drop OSS"]
edition = "2024"
@@ -70,9 +70,6 @@ tauri-plugin-opener = "2.4.0"
bitcode = "0.6.6"
reqwest-websocket = "0.5.0"
futures-lite = "2.6.0"
page_size = "0.6.0"
sysinfo = "0.36.1"
humansize = "2.1.3"
# tailscale = { path = "./tailscale" }
[dependencies.dynfmt]
@@ -80,7 +77,7 @@ version = "0.1.5"
features = ["curly"]
[dependencies.tauri]
version = "2.7.0"
version = "2.1.1"
features = ["protocol-asset", "tray-icon"]
[dependencies.tokio]
@@ -106,7 +103,7 @@ features = ["other_errors"] # You can also use "yaml_enc" or "bin_enc"
[dependencies.reqwest]
version = "0.12"
default-features = false
features = ["json", "http2", "blocking", "rustls-tls", "native-tls-alpn", "rustls-tls-webpki-roots"]
features = ["json", "http2", "blocking", "rustls-tls-webpki-roots"]
[dependencies.serde]
version = "1"

View File

@@ -7,7 +7,7 @@ use std::{
use serde_json::Value;
use crate::{
database::{db::borrow_db_mut_checked, scan::scan_install_dirs}, error::download_manager_error::DownloadManagerError,
database::db::borrow_db_mut_checked, error::download_manager_error::DownloadManagerError,
};
use super::{
@@ -59,8 +59,6 @@ pub fn add_download_dir(new_dir: PathBuf) -> Result<(), DownloadManagerError<()>
lock.applications.install_dirs.push(new_dir);
drop(lock);
scan_install_dirs();
Ok(())
}

View File

@@ -10,7 +10,7 @@ use chrono::Utc;
use log::{debug, error, info, warn};
use native_model::{Decode, Encode};
use rustbreak::{DeSerError, DeSerializer, PathDatabase, RustbreakError};
use serde::{Serialize, de::DeserializeOwned};
use serde::{de::DeserializeOwned, Serialize};
use url::Url;
use crate::DB;
@@ -67,18 +67,17 @@ impl DatabaseImpls for DatabaseInterface {
let exists = fs::exists(db_path.clone()).unwrap();
if exists {
match PathDatabase::load_from_path(db_path.clone()) {
Ok(db) => db,
Err(e) => handle_invalid_database(e, db_path, games_base_dir, cache_dir),
}
} else {
if exists { match PathDatabase::load_from_path(db_path.clone()) {
Ok(db) => db,
Err(e) => handle_invalid_database(e, db_path, games_base_dir, cache_dir),
} } 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")
PathDatabase::create_at_path(db_path, default)
.expect("Database could not be created")
}
}
@@ -122,24 +121,24 @@ fn handle_invalid_database(
pub struct DBRead<'a>(RwLockReadGuard<'a, Database>);
pub struct DBWrite<'a>(ManuallyDrop<RwLockWriteGuard<'a, Database>>);
impl<'a> Deref for DBWrite<'a> {
type Target = Database;
type Target = RwLockWriteGuard<'a, Database>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'a> DerefMut for DBWrite<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<'a> Deref for DBRead<'a> {
type Target = Database;
type Target = RwLockReadGuard<'a, Database>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for DBWrite<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Drop for DBWrite<'_> {
fn drop(&mut self) {
unsafe {
@@ -155,7 +154,6 @@ impl Drop for DBWrite<'_> {
}
}
}
pub fn borrow_db_checked<'a>() -> DBRead<'a> {
match DB.borrow_data() {
Ok(data) => DBRead(data),

View File

@@ -2,4 +2,3 @@ pub mod commands;
pub mod db;
pub mod debug;
pub mod models;
pub mod scan;

View File

@@ -1,14 +1,8 @@
/**
* NEXT BREAKING CHANGE
*
* UPDATE DATABASE TO USE RPMSERDENAMED
*
* WE CAN'T DELETE ANY FIELDS
*/
use crate::database::models::data::Database;
pub mod data {
use std::path::PathBuf;
use native_model::native_model;
use serde::{Deserialize, Serialize};
@@ -24,14 +18,16 @@ pub mod data {
pub type DatabaseApplications = v2::DatabaseApplications;
pub type DatabaseCompatInfo = v2::DatabaseCompatInfo;
use std::collections::HashMap;
use std::{collections::HashMap, process::Command};
use crate::process::process_manager::UMU_LAUNCHER_EXECUTABLE;
pub mod v1 {
use crate::process::process_manager::Platform;
use serde_with::serde_as;
use std::{collections::HashMap, path::PathBuf};
use super::{Deserialize, Serialize, native_model};
use super::{Serialize, Deserialize, native_model};
fn default_template() -> String {
"{}".to_owned()
@@ -119,7 +115,6 @@ pub mod data {
Downloading { version_name: String },
Uninstalling {},
Updating { version_name: String },
Validating { version_name: String },
Running {},
}
@@ -179,10 +174,7 @@ pub mod data {
use serde_with::serde_as;
use super::{
ApplicationTransientStatus, DatabaseAuth, Deserialize, DownloadableMetadata,
GameVersion, Serialize, Settings, native_model, v1,
};
use super::{Serialize, Deserialize, native_model, Settings, DatabaseAuth, v1, GameVersion, DownloadableMetadata, ApplicationTransientStatus};
#[native_model(id = 1, version = 2, with = native_model::rmp_serde_1_3::RmpSerde)]
#[derive(Serialize, Deserialize, Clone, Default)]
@@ -214,7 +206,7 @@ pub mod data {
applications: value.applications,
prev_database: value.prev_database,
cache_dir: value.cache_dir,
compat_info: None,
compat_info: crate::database::models::Database::create_new_compat_info(),
}
}
}
@@ -291,10 +283,7 @@ pub mod data {
mod v3 {
use std::path::PathBuf;
use super::{
DatabaseApplications, DatabaseAuth, DatabaseCompatInfo, Deserialize, Serialize,
Settings, native_model, v2,
};
use super::{Serialize, Deserialize, native_model, Settings, DatabaseAuth, DatabaseApplications, DatabaseCompatInfo, v2};
#[native_model(id = 1, version = 3, with = native_model::rmp_serde_1_3::RmpSerde)]
#[derive(Serialize, Deserialize, Clone, Default)]
pub struct Database {
@@ -308,7 +297,6 @@ pub mod data {
pub cache_dir: PathBuf,
pub compat_info: Option<DatabaseCompatInfo>,
}
impl From<v2::Database> for Database {
fn from(value: v2::Database) -> Self {
Self {
@@ -318,13 +306,22 @@ pub mod data {
applications: value.applications.into(),
prev_database: value.prev_database,
cache_dir: value.cache_dir,
compat_info: None,
compat_info: Database::create_new_compat_info(),
}
}
}
}
impl Database {
fn create_new_compat_info() -> Option<DatabaseCompatInfo> {
#[cfg(target_os = "windows")]
return None;
let has_umu_installed = Command::new(UMU_LAUNCHER_EXECUTABLE).spawn().is_ok();
Some(DatabaseCompatInfo {
umu_installed: has_umu_installed,
})
}
pub fn new<T: Into<PathBuf>>(
games_base_dir: T,
prev_database: Option<PathBuf>,
@@ -343,7 +340,7 @@ pub mod data {
auth: None,
settings: Settings::default(),
cache_dir,
compat_info: None,
compat_info: Database::create_new_compat_info(),
}
}
}

View File

@@ -1,52 +0,0 @@
use std::fs;
use log::warn;
use crate::{
database::{
db::borrow_db_mut_checked,
models::data::v1::{DownloadType, DownloadableMetadata},
},
games::{
downloads::drop_data::{v1::DropData, DROP_DATA_PATH},
library::set_partially_installed_db,
},
};
pub fn scan_install_dirs() {
let mut db_lock = borrow_db_mut_checked();
for install_dir in db_lock.applications.install_dirs.clone() {
let Ok(files) = fs::read_dir(install_dir) else {
continue;
};
for game in files.into_iter().flatten() {
let drop_data_file = game.path().join(DROP_DATA_PATH);
if !drop_data_file.exists() {
continue;
}
let game_id = game.file_name().into_string().unwrap();
let Ok(drop_data) = DropData::read(&game.path()) else {
warn!(
".dropdata exists for {}, but couldn't read it. is it corrupted?",
game.file_name().into_string().unwrap()
);
continue;
};
if db_lock.applications.game_statuses.contains_key(&game_id) {
continue;
}
let metadata = DownloadableMetadata::new(
drop_data.game_id,
Some(drop_data.game_version),
DownloadType::Game,
);
set_partially_installed_db(
&mut db_lock,
&metadata,
drop_data.base_path.to_str().unwrap().to_string(),
None,
);
}
}
}

View File

@@ -254,13 +254,13 @@ impl DownloadManagerBuilder {
}
};
// If the download gets cancelled
// immediately return, on_cancelled gets called for us earlier
// If the download gets cancel
if !download_result {
download_agent.on_incomplete(&app_handle);
return;
}
let validate_result = match download_agent.validate(&app_handle) {
let validate_result = match download_agent.validate() {
Ok(v) => v,
Err(e) => {
error!(

View File

@@ -14,14 +14,14 @@ use super::{
pub trait Downloadable: Send + Sync {
fn download(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError>;
fn validate(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError>;
fn progress(&self) -> Arc<ProgressObject>;
fn control_flag(&self) -> DownloadThreadControl;
fn validate(&self) -> Result<bool, ApplicationDownloadError>;
fn status(&self) -> DownloadStatus;
fn metadata(&self) -> DownloadableMetadata;
fn on_initialised(&self, app_handle: &AppHandle);
fn on_error(&self, app_handle: &AppHandle, error: &ApplicationDownloadError);
fn on_complete(&self, app_handle: &AppHandle);
fn on_incomplete(&self, app_handle: &AppHandle);
fn on_cancelled(&self, app_handle: &AppHandle);
}

View File

@@ -38,9 +38,9 @@ impl DownloadThreadControl {
}
}
pub fn get(&self) -> DownloadThreadControlFlag {
self.inner.load(Ordering::Acquire).into()
self.inner.load(Ordering::Relaxed).into()
}
pub fn set(&self, flag: DownloadThreadControlFlag) {
self.inner.store(flag.into(), Ordering::Release);
self.inner.store(flag.into(), Ordering::Relaxed);
}
}

View File

@@ -23,10 +23,9 @@ pub struct ProgressObject {
//last_update: Arc<RwLock<Instant>>,
last_update_time: Arc<AtomicInstant>,
bytes_last_update: Arc<AtomicUsize>,
rolling: RollingProgressWindow<10>,
rolling: RollingProgressWindow<250>,
}
#[derive(Clone)]
pub struct ProgressHandle {
progress: Arc<AtomicUsize>,
progress_object: Arc<ProgressObject>,
@@ -40,20 +39,20 @@ impl ProgressHandle {
}
}
pub fn set(&self, amount: usize) {
self.progress.store(amount, Ordering::Release);
self.progress.store(amount, Ordering::Relaxed);
}
pub fn add(&self, amount: usize) {
self.progress
.fetch_add(amount, std::sync::atomic::Ordering::AcqRel);
.fetch_add(amount, std::sync::atomic::Ordering::Relaxed);
calculate_update(&self.progress_object);
}
pub fn skip(&self, amount: usize) {
self.progress
.fetch_add(amount, std::sync::atomic::Ordering::Acquire);
.fetch_add(amount, std::sync::atomic::Ordering::Relaxed);
// Offset the bytes at last offset by this amount
self.progress_object
.bytes_last_update
.fetch_add(amount, Ordering::Acquire);
.fetch_add(amount, Ordering::Relaxed);
// Dont' fire update
}
}
@@ -61,6 +60,7 @@ impl ProgressHandle {
impl ProgressObject {
pub fn new(max: usize, length: usize, sender: Sender<DownloadManagerSignal>) -> Self {
let arr = Mutex::new((0..length).map(|_| Arc::new(AtomicUsize::new(0))).collect());
// TODO: consolidate this calculation with the set_max function below
Self {
max: Arc::new(Mutex::new(max)),
progress_instances: Arc::new(arr),
@@ -81,18 +81,19 @@ impl ProgressObject {
.lock()
.unwrap()
.iter()
.map(|instance| instance.load(Ordering::Acquire))
.map(|instance| instance.load(Ordering::Relaxed))
.sum()
}
pub fn reset(&self) {
pub fn reset(&self, size: usize) {
self.set_time_now();
self.set_size(size);
self.bytes_last_update.store(0, Ordering::Release);
self.rolling.reset();
self.progress_instances
.lock()
.unwrap()
.iter()
.for_each(|x| x.store(0, Ordering::SeqCst));
.for_each(|x| x.store(0, Ordering::Release));
}
pub fn get_max(&self) -> usize {
*self.max.lock().unwrap()
@@ -126,7 +127,7 @@ pub fn calculate_update(progress: &ProgressObject) {
let max = progress.get_max();
let bytes_at_last_update = progress
.bytes_last_update
.swap(current_bytes_downloaded, Ordering::Acquire);
.swap(current_bytes_downloaded, Ordering::Relaxed);
let bytes_since_last_update = current_bytes_downloaded - bytes_at_last_update;

View File

@@ -26,7 +26,7 @@ impl<const S: usize> RollingProgressWindow<S> {
.iter()
.enumerate()
.filter(|(i, _)| i < &current)
.map(|(_, x)| x.load(Ordering::Acquire))
.map(|(_, x)| x.load(Ordering::Relaxed))
.sum::<usize>()
/ S
}

View File

@@ -4,7 +4,6 @@ use std::{
};
use serde_with::SerializeDisplay;
use humansize::{format_size, BINARY};
use super::remote_access_error::RemoteAccessError;
@@ -12,7 +11,6 @@ use super::remote_access_error::RemoteAccessError;
#[derive(Debug, SerializeDisplay)]
pub enum ApplicationDownloadError {
Communication(RemoteAccessError),
DiskFull(u64, u64),
Checksum,
Lock,
IoError(io::ErrorKind),
@@ -22,25 +20,11 @@ pub enum ApplicationDownloadError {
impl Display for ApplicationDownloadError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ApplicationDownloadError::DiskFull(required, available) => write!(
f,
"Game requires {}, {} remaining left on disk.",
format_size(*required, BINARY),
format_size(*available, BINARY),
),
ApplicationDownloadError::Communication(error) => write!(f, "{error}"),
ApplicationDownloadError::Lock => write!(
f,
"failed to acquire lock. Something has gone very wrong internally. Please restart the application"
),
ApplicationDownloadError::Checksum => {
write!(f, "checksum failed to validate for download")
}
ApplicationDownloadError::Lock => write!(f, "failed to acquire lock. Something has gone very wrong internally. Please restart the application"),
ApplicationDownloadError::Checksum => write!(f, "checksum failed to validate for download"),
ApplicationDownloadError::IoError(error) => write!(f, "io error: {error}"),
ApplicationDownloadError::DownloadError => write!(
f,
"download failed. See Download Manager status for specific error"
),
ApplicationDownloadError::DownloadError => write!(f, "download failed. See Download Manager status for specific error"),
}
}
}

View File

@@ -6,6 +6,7 @@ use serde_with::SerializeDisplay;
pub enum ProcessError {
NotInstalled,
AlreadyRunning,
NotDownloaded,
InvalidID,
InvalidVersion,
IOError(Error),
@@ -19,6 +20,7 @@ impl Display for ProcessError {
let s = match self {
ProcessError::NotInstalled => "Game not installed",
ProcessError::AlreadyRunning => "Game already running",
ProcessError::NotDownloaded => "Game not downloaded",
ProcessError::InvalidID => "Invalid game ID",
ProcessError::InvalidVersion => "Invalid game version",
ProcessError::IOError(error) => &error.to_string(),

View File

@@ -3,23 +3,19 @@ use std::sync::Mutex;
use tauri::AppHandle;
use crate::{
AppState,
database::{
db::borrow_db_checked,
models::data::GameVersion,
},
database::models::data::GameVersion,
error::{library_error::LibraryError, remote_access_error::RemoteAccessError},
games::library::{
fetch_game_logic_offline, fetch_library_logic_offline, get_current_meta,
uninstall_game_logic,
},
offline,
offline, AppState,
};
use super::{
library::{
FetchGameStruct, Game, fetch_game_logic, fetch_game_verion_options_logic,
fetch_library_logic,
fetch_game_logic, fetch_game_verion_options_logic, fetch_library_logic, FetchGameStruct,
Game,
},
state::{GameStatusManager, GameStatusWithTransient},
};
@@ -52,8 +48,7 @@ pub fn fetch_game(
#[tauri::command]
pub fn fetch_game_status(id: String) -> GameStatusWithTransient {
let db_handle = borrow_db_checked();
GameStatusManager::fetch_state(&id, &db_handle)
GameStatusManager::fetch_state(&id)
}
#[tauri::command]

View File

@@ -5,10 +5,10 @@ use std::{
use crate::{
database::{db::borrow_db_checked, models::data::GameDownloadStatus},
download_manager::
downloadable::Downloadable
,
error::application_download_error::ApplicationDownloadError,
download_manager::{
download_manager_frontend::DownloadManagerSignal, downloadable::Downloadable,
},
error::download_manager_error::DownloadManagerError,
AppState,
};
@@ -20,28 +20,26 @@ pub fn download_game(
game_version: String,
install_dir: usize,
state: tauri::State<'_, Mutex<AppState>>,
) -> Result<(), ApplicationDownloadError> {
) -> Result<(), DownloadManagerError<DownloadManagerSignal>> {
let sender = state.lock().unwrap().download_manager.get_sender();
let game_download_agent = GameDownloadAgent::new_from_index(
let game_download_agent = Arc::new(Box::new(GameDownloadAgent::new_from_index(
game_id,
game_version,
install_dir,
sender,
)?;
let game_download_agent = Arc::new(Box::new(game_download_agent) as Box<dyn Downloadable + Send + Sync>);
state
.lock()
.unwrap()
.download_manager
.queue_download(game_download_agent).unwrap();
Ok(())
)) as Box<dyn Downloadable + Send + Sync>);
Ok(state
.lock()
.unwrap()
.download_manager
.queue_download(game_download_agent)?)
}
#[tauri::command]
pub fn resume_download(
game_id: String,
state: tauri::State<'_, Mutex<AppState>>,
) -> Result<(), ApplicationDownloadError> {
) -> Result<(), DownloadManagerError<DownloadManagerSignal>> {
let s = borrow_db_checked()
.applications
.game_statuses
@@ -58,21 +56,17 @@ pub fn resume_download(
install_dir,
} => (version_name, install_dir),
};
let sender = state.lock().unwrap().download_manager.get_sender();
let parent_dir: PathBuf = install_dir.into();
let game_download_agent = Arc::new(Box::new(GameDownloadAgent::new(
game_id,
version_name.clone(),
parent_dir.parent().unwrap().to_path_buf(),
sender,
)?) as Box<dyn Downloadable + Send + Sync>);
state
.lock()
.unwrap()
.download_manager
.queue_download(game_download_agent).unwrap();
Ok(())
)) as Box<dyn Downloadable + Send + Sync>);
Ok(state
.lock()
.unwrap()
.download_manager
.queue_download(game_download_agent)?)
}

View File

@@ -12,13 +12,11 @@ use crate::download_manager::util::progress_object::{ProgressHandle, ProgressObj
use crate::error::application_download_error::ApplicationDownloadError;
use crate::error::remote_access_error::RemoteAccessError;
use crate::games::downloads::manifest::{DropDownloadContext, DropManifest};
use crate::games::downloads::validate::validate_game_chunk;
use crate::games::library::{on_game_complete, push_game_update, set_partially_installed};
use crate::games::state::GameStatusManager;
use crate::process::utils::get_disk_available;
use crate::games::downloads::validate::game_validate_logic;
use crate::games::library::{on_game_complete, on_game_incomplete, push_game_update};
use crate::remote::requests::make_request;
use crate::remote::utils::DROP_CLIENT_SYNC;
use log::{debug, error, info, warn};
use log::{debug, error, info};
use rayon::ThreadPoolBuilder;
use std::collections::HashMap;
use std::fs::{OpenOptions, create_dir_all};
@@ -34,8 +32,6 @@ use rustix::fs::{FallocateFlags, fallocate};
use super::download_logic::download_game_chunk;
use super::drop_data::DropData;
static RETRY_COUNT: usize = 3;
pub struct GameDownloadAgent {
pub id: String,
pub version: String,
@@ -45,7 +41,7 @@ pub struct GameDownloadAgent {
pub manifest: Mutex<Option<DropManifest>>,
pub progress: Arc<ProgressObject>,
sender: Sender<DownloadManagerSignal>,
pub dropdata: DropData,
pub stored_manifest: DropData,
status: Mutex<DownloadStatus>,
}
@@ -55,7 +51,7 @@ impl GameDownloadAgent {
version: String,
target_download_dir: usize,
sender: Sender<DownloadManagerSignal>,
) -> Result<Self, ApplicationDownloadError> {
) -> Self {
let db_lock = borrow_db_checked();
let base_dir = db_lock.applications.install_dirs[target_download_dir].clone();
drop(db_lock);
@@ -67,7 +63,7 @@ impl GameDownloadAgent {
version: String,
base_dir: PathBuf,
sender: Sender<DownloadManagerSignal>,
) -> Result<Self, ApplicationDownloadError> {
) -> Self {
// Don't run by default
let control_flag = DownloadThreadControl::new(DownloadThreadControlFlag::Stop);
@@ -77,7 +73,7 @@ impl GameDownloadAgent {
let stored_manifest =
DropData::generate(id.clone(), version.clone(), data_base_dir_path.clone());
let result = Self {
Self {
id,
version,
control_flag,
@@ -86,67 +82,38 @@ impl GameDownloadAgent {
context_map: Mutex::new(HashMap::new()),
progress: Arc::new(ProgressObject::new(0, 0, sender.clone())),
sender,
dropdata: stored_manifest,
stored_manifest,
status: Mutex::new(DownloadStatus::Queued),
};
result.ensure_manifest_exists()?;
let required_space = result
.manifest
.lock()
.unwrap()
.as_ref()
.unwrap()
.values()
.map(|e| e.lengths.iter().sum::<usize>())
.sum::<usize>()
as u64;
let available_space = get_disk_available(data_base_dir_path)? as u64;
if required_space > available_space {
return Err(ApplicationDownloadError::DiskFull(
required_space,
available_space,
));
}
Ok(result)
}
// Blocking
pub fn setup_download(&self, app_handle: &AppHandle) -> Result<(), ApplicationDownloadError> {
pub fn setup_download(&self) -> Result<(), ApplicationDownloadError> {
self.ensure_manifest_exists()?;
self.ensure_contexts()?;
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(())
}
// Blocking
pub fn download(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError> {
self.setup_download(app_handle)?;
self.setup_download()?;
self.set_progress_object_params();
let timer = Instant::now();
info!("beginning download for {}...", self.metadata().id);
push_game_update(
app_handle,
&self.metadata().id,
None,
(
None,
Some(ApplicationTransientStatus::Downloading {
version_name: self.version.clone(),
}),
),
);
let res = self
.run()
.map_err(|()| ApplicationDownloadError::DownloadError);
@@ -199,8 +166,12 @@ impl GameDownloadAgent {
Err(ApplicationDownloadError::Lock)
}
// Sets it up for both download and validate
fn setup_progress(&self) {
fn set_progress_object_params(&self) {
// Avoid re-setting it
if self.progress.get_max() != 0 {
return;
}
let contexts = self.contexts.lock().unwrap();
let length = contexts.len();
@@ -209,7 +180,7 @@ impl GameDownloadAgent {
self.progress.set_max(chunk_count);
self.progress.set_size(length);
self.progress.reset();
self.progress.set_time_now();
}
pub fn ensure_contexts(&self) -> Result<(), ApplicationDownloadError> {
@@ -217,7 +188,7 @@ impl GameDownloadAgent {
self.generate_contexts()?;
}
*self.context_map.lock().unwrap() = self.dropdata.get_contexts();
*self.context_map.lock().unwrap() = self.stored_manifest.get_contexts();
Ok(())
}
@@ -227,7 +198,7 @@ impl GameDownloadAgent {
let game_id = self.id.clone();
let mut contexts = Vec::new();
let base_path = Path::new(&self.dropdata.base_path);
let base_path = Path::new(&self.stored_manifest.base_path);
create_dir_all(base_path).unwrap();
for (raw_path, chunk) in manifest {
@@ -236,12 +207,11 @@ impl GameDownloadAgent {
let container = path.parent().unwrap();
create_dir_all(container).unwrap();
let already_exists = path.exists();
let file = OpenOptions::new()
.read(true)
.write(true)
.truncate(true)
.create(true)
.truncate(false)
.open(path.clone())
.unwrap();
let mut running_offset = 0;
@@ -262,12 +232,12 @@ impl GameDownloadAgent {
}
#[cfg(target_os = "linux")]
if running_offset > 0 && !already_exists {
if running_offset > 0 {
let _ = fallocate(file, FallocateFlags::empty(), 0, running_offset);
}
}
let existing_contexts = self.dropdata.get_completed_contexts();
self.dropdata.set_contexts(
let existing_contexts = self.stored_manifest.get_completed_contexts();
self.stored_manifest.set_contexts(
&contexts
.iter()
.map(|x| (x.checksum.clone(), existing_contexts.contains(&x.checksum)))
@@ -279,8 +249,8 @@ impl GameDownloadAgent {
Ok(())
}
fn run(&self) -> Result<bool, ()> {
self.setup_progress();
// TODO: Change return value on Err
pub fn run(&self) -> Result<bool, ()> {
let max_download_threads = borrow_db_checked().settings.max_download_threads;
debug!(
@@ -296,6 +266,7 @@ impl GameDownloadAgent {
let completed_indexes_loop_arc = completed_contexts.clone();
let contexts = self.contexts.lock().unwrap();
debug!("{contexts:#?}");
pool.scope(|scope| {
let client = &DROP_CLIENT_SYNC.clone();
let context_map = self.context_map.lock().unwrap();
@@ -307,10 +278,7 @@ impl GameDownloadAgent {
let progress_handle = ProgressHandle::new(progress, self.progress.clone());
// If we've done this one already, skip it
// Note to future DecDuck, DropData gets loaded into context_map
if let Some(v) = context_map.get(&context.checksum)
&& *v
{
if Some(&true) == context_map.get(&context.checksum) {
progress_handle.skip(context.length);
continue;
}
@@ -340,40 +308,15 @@ impl GameDownloadAgent {
};
scope.spawn(move |_| {
// 3 attempts
for i in 0..RETRY_COUNT {
let loop_progress_handle = progress_handle.clone();
match download_game_chunk(
context,
&self.control_flag,
loop_progress_handle,
request.try_clone().unwrap(),
) {
Ok(true) => {
completed_indexes.push(context.checksum.clone());
return;
}
Ok(false) => return,
Err(e) => {
warn!("game download agent error: {e}");
let retry = match &e {
ApplicationDownloadError::Communication(
_remote_access_error,
) => true,
ApplicationDownloadError::Checksum => true,
ApplicationDownloadError::Lock => true,
ApplicationDownloadError::IoError(_error_kind) => false,
ApplicationDownloadError::DownloadError => false,
ApplicationDownloadError::DiskFull(_, _) => false,
};
if i == RETRY_COUNT - 1 || !retry {
warn!("retry logic failed, not re-attempting.");
sender.send(DownloadManagerSignal::Error(e)).unwrap();
return;
}
}
match download_game_chunk(context, &self.control_flag, progress_handle, request)
{
Ok(true) => {
completed_indexes.push(context.checksum.clone());
}
Ok(false) => {}
Err(e) => {
error!("{e}");
sender.send(DownloadManagerSignal::Error(e)).unwrap();
}
}
});
@@ -402,8 +345,8 @@ impl GameDownloadAgent {
.collect::<Vec<(String, bool)>>();
drop(context_map_lock);
self.dropdata.set_contexts(&contexts);
self.dropdata.write();
self.stored_manifest.set_contexts(&contexts);
self.stored_manifest.write();
// If there are any contexts left which are false
if !contexts.iter().all(|x| x.1) {
@@ -418,91 +361,6 @@ impl GameDownloadAgent {
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 {
@@ -511,11 +369,6 @@ impl Downloadable for GameDownloadAgent {
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> {
self.progress.clone()
}
@@ -554,25 +407,37 @@ impl Downloadable for GameDownloadAgent {
fn on_complete(&self, app_handle: &tauri::AppHandle) {
on_game_complete(
&self.metadata(),
self.dropdata.base_path.to_string_lossy().to_string(),
self.stored_manifest.base_path.to_string_lossy().to_string(),
app_handle,
)
.unwrap();
}
fn on_cancelled(&self, app_handle: &tauri::AppHandle) {
self.cancel(app_handle);
/*
on_game_incomplete(
&self.metadata(),
self.dropdata.base_path.to_string_lossy().to_string(),
app_handle,
)
.unwrap();
*/
// TODO: fix this function. It doesn't restart the download properly, nor does it reset the state properly
fn on_incomplete(&self, app_handle: &tauri::AppHandle) {
on_game_incomplete(
&self.metadata(),
self.stored_manifest.base_path.to_string_lossy().to_string(),
app_handle,
)
.unwrap();
println!("Attempting to redownload");
}
fn on_cancelled(&self, _app_handle: &tauri::AppHandle) {}
fn status(&self) -> DownloadStatus {
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,
)
}
}

View File

@@ -7,18 +7,14 @@ use crate::error::drop_server_error::DropServerError;
use crate::error::remote_access_error::RemoteAccessError;
use crate::games::downloads::manifest::DropDownloadContext;
use crate::remote::auth::generate_authorization_header;
use http::response;
use log::{debug, info, warn};
use log::{debug, warn};
use md5::{Context, Digest};
use reqwest::blocking::{RequestBuilder, Response};
use std::fs::{Permissions, set_permissions};
use std::fs::{set_permissions, Permissions};
use std::io::Read;
use std::ops::Sub;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use std::thread;
use std::time::Instant;
use std::{
fs::{File, OpenOptions},
io::{self, BufWriter, Seek, SeekFrom, Write},
@@ -27,21 +23,14 @@ use std::{
pub struct DropWriter<W: Write> {
hasher: Context,
destination: BufWriter<W>,
progress: ProgressHandle,
destination: W,
}
impl DropWriter<File> {
fn new(path: PathBuf, progress: ProgressHandle) -> Self {
let destination = OpenOptions::new()
.write(true)
.create(true)
.truncate(false)
.open(&path)
.unwrap();
fn new(path: PathBuf) -> Self {
let destination = OpenOptions::new().write(true).create(true).truncate(false).open(&path).unwrap();
Self {
destination: BufWriter::with_capacity(1 * 1024 * 1024, destination),
destination,
hasher: Context::new(),
progress,
}
}
@@ -56,10 +45,7 @@ impl Write for DropWriter<File> {
self.hasher
.write_all(buf)
.map_err(|e| io::Error::other(format!("Unable to write to hasher: {e}")))?;
let bytes_written = self.destination.write(buf)?;
self.progress.add(bytes_written);
Ok(bytes_written)
self.destination.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
@@ -78,33 +64,60 @@ pub struct DropDownloadPipeline<'a, R: Read, W: Write> {
pub source: R,
pub destination: DropWriter<W>,
pub control_flag: &'a DownloadThreadControl,
pub progress: ProgressHandle,
pub size: usize,
}
impl<'a> Seek for DropDownloadPipeline<'a, Response, File> {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
self.destination.seek(pos)
}
}
impl<'a> DropDownloadPipeline<'a, Response, File> {
fn new(
source: Response,
destination: PathBuf,
destination: DropWriter<File>,
control_flag: &'a DownloadThreadControl,
progress: ProgressHandle,
size: usize,
) -> Self {
Self {
source,
destination: DropWriter::new(destination, progress),
destination,
control_flag,
progress,
size,
}
}
fn copy(&mut self) -> Result<bool, io::Error> {
io::copy(&mut self.source, &mut self.destination).unwrap();
let copy_buf_size = 512;
let mut copy_buf = vec![0; copy_buf_size];
let mut buf_writer = BufWriter::with_capacity(1024 * 1024, &mut self.destination);
let mut current_size = 0;
loop {
if self.control_flag.get() == DownloadThreadControlFlag::Stop {
buf_writer.flush()?;
return Ok(false);
}
let mut bytes_read = self.source.read(&mut copy_buf)?;
current_size += bytes_read;
if current_size > self.size {
let over = current_size - self.size;
warn!("server sent too many bytes... {over} over");
bytes_read -= over;
current_size = self.size;
}
buf_writer.write_all(&copy_buf[0..bytes_read])?;
self.progress.add(bytes_read);
if current_size >= self.size {
debug!(
"finished with final size of {} vs {}",
current_size, self.size
);
break;
}
}
buf_writer.flush()?;
Ok(true)
}
@@ -126,21 +139,11 @@ pub fn download_game_chunk(
progress.set(0);
return Ok(false);
}
let start = Instant::now();
debug!("started chunk {}", ctx.checksum);
let header = generate_authorization_header();
let header_time = start.elapsed();
let response = request
.header("Authorization", header)
.header("Authorization", generate_authorization_header())
.send()
.map_err(|e| ApplicationDownloadError::Communication(e.into()))?;
let response_time = start.elapsed();
if response.status() != 200 {
debug!("chunk request got status code: {}", response.status());
let raw_res = response.text().unwrap();
@@ -154,6 +157,14 @@ pub fn download_game_chunk(
));
}
let mut destination = DropWriter::new(ctx.path.clone());
if ctx.offset != 0 {
destination
.seek(SeekFrom::Start(ctx.offset))
.expect("Failed to seek to file offset");
}
let content_length = response.content_length();
if content_length.is_none() {
warn!("recieved 0 length content from server");
@@ -168,18 +179,8 @@ pub fn download_game_chunk(
return Err(ApplicationDownloadError::DownloadError);
}
let pipeline_start = start.elapsed();
let mut pipeline =
DropDownloadPipeline::new(response, ctx.path.clone(), control_flag, progress, length);
if ctx.offset != 0 {
pipeline
.seek(SeekFrom::Start(ctx.offset))
.expect("Failed to seek to file offset");
}
let pipeline_setup = start.elapsed();
DropDownloadPipeline::new(response, destination, control_flag, progress, length);
let completed = pipeline
.copy()
@@ -188,8 +189,6 @@ pub fn download_game_chunk(
return Ok(false);
}
let pipeline_finish = start.elapsed();
// If we complete the file, set the permissions (if on Linux)
#[cfg(unix)]
{
@@ -201,31 +200,15 @@ pub fn download_game_chunk(
.finish()
.map_err(|e| ApplicationDownloadError::IoError(e.kind()))?;
let checksum_finish = start.elapsed();
let res = hex::encode(checksum.0);
if res != ctx.checksum {
return Err(ApplicationDownloadError::Checksum);
}
let header_update = header_time.as_millis();
let response_update = response_time.sub(header_time).as_millis();
let pipeline_start_update = pipeline_start.sub(response_time).as_millis();
let pipeline_setup_update = pipeline_setup.sub(pipeline_start).as_millis();
let pipeline_finish_update = pipeline_finish.sub(pipeline_setup).as_millis();
let checksum_update = checksum_finish.sub(pipeline_finish).as_millis();
debug!(
"\nheader: {}\nresponse: {}\npipeline start: {}\npipeline setup: {}\npipeline finish: {}\nchecksum finish: {}",
header_update,
response_update,
pipeline_start_update,
pipeline_setup_update,
pipeline_finish_update,
checksum_update
"Successfully finished download #{}, copied {} bytes",
ctx.checksum, length
);
debug!("finished chunk {}", ctx.checksum);
Ok(true)
}

Some files were not shown because too many files have changed in this diff Show More