mirror of
https://github.com/Drop-OSS/drop-app.git
synced 2026-06-22 04:11:37 +10:00
In-app store, delta version support (#179)
* fix: windows launch * feat: add necessary client fixes for store * fix: keyring fix * feat: delta version support * feat: dl/disk progress * feat: move to jwt auth * fix: lint
This commit is contained in:
+1
-1
Submodule libs/drop-base updated: 14f4e3e20b...04125e89be
@@ -5,9 +5,12 @@ export type QueueState = {
|
||||
queue: Array<{
|
||||
meta: DownloadableMetadata;
|
||||
status: string;
|
||||
progress: number | null;
|
||||
current: number;
|
||||
max: number;
|
||||
dl_progress: number | null;
|
||||
dl_current: number;
|
||||
dl_max: number;
|
||||
disk_progress: number | null;
|
||||
disk_current: number;
|
||||
disk_max: number;
|
||||
}>;
|
||||
status: string;
|
||||
};
|
||||
@@ -33,7 +36,8 @@ listen("update_stats", (event) => {
|
||||
stats.value = event.payload as StatsState;
|
||||
});
|
||||
|
||||
export const useDownloadHistory = () => useState<Array<number>>('history', () => []);
|
||||
export const useDownloadHistory = () =>
|
||||
useState<Array<number>>("history", () => []);
|
||||
|
||||
export function formatKilobytes(bytes: number): string {
|
||||
const units = ["K", "M", "G", "T", "P"];
|
||||
@@ -47,4 +51,4 @@ export function formatKilobytes(bytes: number): string {
|
||||
}
|
||||
|
||||
return `${value.toFixed(1)} ${units[unitIndex]}`;
|
||||
}
|
||||
}
|
||||
|
||||
+47
-14
@@ -6,12 +6,16 @@
|
||||
<div
|
||||
class="bg-zinc-900 z-10 w-32 flex flex-col gap-x-2 font-display items-left justify-center pl-2"
|
||||
>
|
||||
<span class="font-bold text-zinc-100">{{ formatKilobytes(stats.speed) }}B/s</span>
|
||||
<span class="font-bold text-zinc-100"
|
||||
>{{ formatKilobytes(stats.speed) }}B/s</span
|
||||
>
|
||||
<span class="text-xs text-zinc-400"
|
||||
>{{ formatTime(stats.time) }} left</span
|
||||
>
|
||||
</div>
|
||||
<div class="absolute inset-0 h-full flex flex-row items-end justify-end space-x-[1px]">
|
||||
<div
|
||||
class="absolute inset-0 h-full flex flex-row items-end justify-end space-x-[1px]"
|
||||
>
|
||||
<div
|
||||
v-for="bar in speedHistory"
|
||||
:style="{ height: `${(bar / speedMax) * 100}%` }"
|
||||
@@ -20,7 +24,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<draggable v-model="queue.queue" @end="onEnd">
|
||||
<template #item="{ element }: { element: (typeof queue.value.queue)[0] }">
|
||||
<template #item="{ element }: ListIterable">
|
||||
<li
|
||||
v-if="games[element.meta.id]"
|
||||
:key="element.meta.id"
|
||||
@@ -50,21 +54,48 @@
|
||||
{{ element.status }}
|
||||
</p>
|
||||
<div
|
||||
v-if="element.progress"
|
||||
v-if="element.dl_progress"
|
||||
class="mt-1 w-96 bg-zinc-800 rounded-lg overflow-hidden"
|
||||
>
|
||||
<div
|
||||
class="h-2 bg-blue-600"
|
||||
:style="{ width: `${element.progress * 100}%` }"
|
||||
:style="{ width: `${element.dl_progress * 100}%` }"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
class="mt-2 inline-flex items-center gap-x-1 text-zinc-400 text-sm font-display"
|
||||
><span class="text-zinc-300">{{
|
||||
formatKilobytes(element.current / 1000)
|
||||
}}B</span>
|
||||
><span class="text-zinc-300"
|
||||
>{{ formatKilobytes(element.dl_current / 1000) }}B</span
|
||||
>
|
||||
/
|
||||
<span class="">{{ formatKilobytes(element.max / 1000) }}B</span
|
||||
<span class=""
|
||||
>{{ formatKilobytes(element.dl_max / 1000) }}B</span
|
||||
><CloudIcon class="size-5"
|
||||
/></span>
|
||||
<div
|
||||
v-if="element.dl_max !== element.disk_max"
|
||||
class="h-[1px] my-2 w-full bg-zinc-700"
|
||||
/>
|
||||
<div
|
||||
v-if="
|
||||
element.disk_progress && element.dl_max !== element.disk_max
|
||||
"
|
||||
class="mt-1 w-96 bg-zinc-800 rounded-lg overflow-hidden"
|
||||
>
|
||||
<div
|
||||
class="h-2 bg-blue-600"
|
||||
:style="{ width: `${element.disk_progress * 100}%` }"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
v-if="element.dl_max !== element.disk_max"
|
||||
class="mt-2 inline-flex items-center gap-x-1 text-zinc-400 text-sm font-display"
|
||||
><span class="text-zinc-300"
|
||||
>{{ formatKilobytes(element.disk_current / 1000) }}B</span
|
||||
>
|
||||
/
|
||||
<span class=""
|
||||
>{{ formatKilobytes(element.disk_max / 1000) }}B</span
|
||||
><ServerIcon class="size-5"
|
||||
/></span>
|
||||
</div>
|
||||
@@ -89,7 +120,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ServerIcon, XMarkIcon } from "@heroicons/vue/20/solid";
|
||||
import { ServerIcon, XMarkIcon, CloudIcon } from "@heroicons/vue/20/solid";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { type DownloadableMetadata, type Game, type GameStatus } from "~/types";
|
||||
|
||||
@@ -108,9 +139,11 @@ const stats = useStatsState();
|
||||
const speedHistory = useDownloadHistory();
|
||||
const speedHistoryMax = computed(() => windowWidth.value / 4);
|
||||
const speedMax = computed(
|
||||
() => speedHistory.value.reduce((a, b) => (a > b ? a : b)) * 1.1
|
||||
() => speedHistory.value.reduce((a, b) => (a > b ? a : b)) * 1.1,
|
||||
);
|
||||
const previousGameId = useState<string | undefined>('previous_game');
|
||||
const previousGameId = useState<string | undefined>("previous_game");
|
||||
|
||||
type ListIterable = { element: (typeof queue.value.queue)[0] };
|
||||
|
||||
const games: Ref<{
|
||||
[key: string]: { game: Game; status: Ref<GameStatus>; cover: string };
|
||||
@@ -122,7 +155,7 @@ function resetHistoryGraph() {
|
||||
}
|
||||
function checkReset(v: QueueState) {
|
||||
const currentGame = v.queue.at(0)?.meta.id;
|
||||
// If we don't have a game
|
||||
// If we don't have a game
|
||||
if (!currentGame) return;
|
||||
|
||||
// If we're finished
|
||||
@@ -150,7 +183,7 @@ watch(queue, (v) => {
|
||||
});
|
||||
|
||||
watch(stats, (v) => {
|
||||
if(v.speed == 0) return;
|
||||
if (v.speed == 0) return;
|
||||
const newLength = speedHistory.value.push(v.speed);
|
||||
if (newLength > speedHistoryMax.value) {
|
||||
speedHistory.value.splice(0, newLength - speedHistoryMax.value);
|
||||
|
||||
Generated
+345
@@ -403,6 +403,12 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "base16ct"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.10.1"
|
||||
@@ -424,6 +430,12 @@ version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
@@ -852,6 +864,12 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.18"
|
||||
@@ -996,6 +1014,18 @@ version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-bigint"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
|
||||
dependencies = [
|
||||
"generic-array 0.14.7",
|
||||
"rand_core 0.6.4",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
@@ -1052,6 +1082,33 @@ dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "4.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"curve25519-dalek-derive",
|
||||
"digest 0.10.7",
|
||||
"fiat-crypto",
|
||||
"rustc_version",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek-derive"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.21.3"
|
||||
@@ -1141,6 +1198,17 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"pem-rfc7468",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der-parser"
|
||||
version = "9.0.0"
|
||||
@@ -1251,6 +1319,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer 0.10.4",
|
||||
"const-oid",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
@@ -1548,12 +1617,71 @@ dependencies = [
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ecdsa"
|
||||
version = "0.16.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
|
||||
dependencies = [
|
||||
"der",
|
||||
"digest 0.10.7",
|
||||
"elliptic-curve",
|
||||
"rfc6979",
|
||||
"signature",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519"
|
||||
version = "2.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
|
||||
dependencies = [
|
||||
"pkcs8",
|
||||
"signature",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519-dalek"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"ed25519",
|
||||
"serde",
|
||||
"sha2 0.10.9",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "elliptic-curve"
|
||||
version = "0.13.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
|
||||
dependencies = [
|
||||
"base16ct",
|
||||
"crypto-bigint",
|
||||
"digest 0.10.7",
|
||||
"ff",
|
||||
"generic-array 0.14.7",
|
||||
"group",
|
||||
"hkdf",
|
||||
"pem-rfc7468",
|
||||
"pkcs8",
|
||||
"rand_core 0.6.4",
|
||||
"sec1",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embed-resource"
|
||||
version = "3.0.6"
|
||||
@@ -1694,6 +1822,22 @@ dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ff"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
|
||||
dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fiat-crypto"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
||||
|
||||
[[package]]
|
||||
name = "field-offset"
|
||||
version = "0.3.6"
|
||||
@@ -2061,6 +2205,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2225,6 +2370,17 @@ dependencies = [
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "group"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
|
||||
dependencies = [
|
||||
"ff",
|
||||
"rand_core 0.6.4",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gtk"
|
||||
version = "0.18.2"
|
||||
@@ -2941,6 +3097,29 @@ dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonwebtoken"
|
||||
version = "10.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"ed25519-dalek",
|
||||
"getrandom 0.2.17",
|
||||
"hmac",
|
||||
"js-sys",
|
||||
"p256",
|
||||
"p384",
|
||||
"pem",
|
||||
"rand 0.8.5",
|
||||
"rsa",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"signature",
|
||||
"simple_asn1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keyboard-types"
|
||||
version = "0.7.0"
|
||||
@@ -3004,6 +3183,9 @@ name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
dependencies = [
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libappindicator"
|
||||
@@ -3498,6 +3680,22 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint-dig"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"libm",
|
||||
"num-integer",
|
||||
"num-iter",
|
||||
"num-traits",
|
||||
"rand 0.8.5",
|
||||
"smallvec",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-complex"
|
||||
version = "0.4.6"
|
||||
@@ -3551,6 +3749,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3985,6 +4184,30 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "p256"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
|
||||
dependencies = [
|
||||
"ecdsa",
|
||||
"elliptic-curve",
|
||||
"primeorder",
|
||||
"sha2 0.10.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "p384"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6"
|
||||
dependencies = [
|
||||
"ecdsa",
|
||||
"elliptic-curve",
|
||||
"primeorder",
|
||||
"sha2 0.10.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "page_size"
|
||||
version = "0.6.0"
|
||||
@@ -4090,6 +4313,15 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem-rfc7468"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.2"
|
||||
@@ -4273,6 +4505,27 @@ dependencies = [
|
||||
"futures-io",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs1"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
|
||||
dependencies = [
|
||||
"der",
|
||||
"pkcs8",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
|
||||
dependencies = [
|
||||
"der",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.32"
|
||||
@@ -4349,6 +4602,15 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||
|
||||
[[package]]
|
||||
name = "primeorder"
|
||||
version = "0.13.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
|
||||
dependencies = [
|
||||
"elliptic-curve",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "1.3.1"
|
||||
@@ -4790,6 +5052,7 @@ dependencies = [
|
||||
"gethostname",
|
||||
"hex 0.4.3",
|
||||
"http 1.4.0",
|
||||
"jsonwebtoken",
|
||||
"log",
|
||||
"md5 0.8.0",
|
||||
"reqwest 0.12.28",
|
||||
@@ -4957,6 +5220,16 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rfc6979"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
|
||||
dependencies = [
|
||||
"hmac",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rfd"
|
||||
version = "0.16.0"
|
||||
@@ -5028,6 +5301,26 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rsa"
|
||||
version = "0.9.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"digest 0.10.7",
|
||||
"num-bigint-dig",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"pkcs1",
|
||||
"pkcs8",
|
||||
"rand_core 0.6.4",
|
||||
"signature",
|
||||
"spki",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-ini"
|
||||
version = "0.21.3"
|
||||
@@ -5234,6 +5527,20 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "sec1"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
|
||||
dependencies = [
|
||||
"base16ct",
|
||||
"der",
|
||||
"generic-array 0.14.7",
|
||||
"pkcs8",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "secret-service"
|
||||
version = "4.0.0"
|
||||
@@ -5647,12 +5954,34 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
|
||||
dependencies = [
|
||||
"digest 0.10.7",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
|
||||
|
||||
[[package]]
|
||||
name = "simple_asn1"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"thiserror 2.0.17",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.11"
|
||||
@@ -5756,6 +6085,22 @@ dependencies = [
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"der",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ssri"
|
||||
version = "7.0.0"
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::{
|
||||
use keyring::Entry;
|
||||
use log::info;
|
||||
|
||||
use crate::interface::{DatabaseInterface};
|
||||
use crate::interface::DatabaseInterface;
|
||||
|
||||
pub static DB: LazyLock<DatabaseInterface> = LazyLock::new(DatabaseInterface::set_up_database);
|
||||
|
||||
@@ -32,6 +32,9 @@ pub(crate) static KEY_IV: LazyLock<([u8; 16], [u8; 16])> = LazyLock::new(|| {
|
||||
info!("created new database key");
|
||||
buffer.to_vec()
|
||||
});
|
||||
let new = key.split_off(16);
|
||||
(new.try_into().expect("failed to extract key"), key.try_into().expect("failed to extract iv"))
|
||||
});
|
||||
let iv: Vec<u8> = key.split_off(16);
|
||||
(
|
||||
key[0..16].try_into().expect("key wrong length"),
|
||||
iv[0..16].try_into().expect("iv wrong length"),
|
||||
)
|
||||
});
|
||||
|
||||
@@ -238,9 +238,10 @@ pub mod data {
|
||||
pub auth: Option<DatabaseAuth>,
|
||||
pub base_url: String,
|
||||
pub applications: DatabaseApplications,
|
||||
pub cache_dir: PathBuf,
|
||||
|
||||
#[serde(skip)]
|
||||
pub prev_database: Option<PathBuf>,
|
||||
pub cache_dir: PathBuf,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::{
|
||||
};
|
||||
|
||||
use futures_util::StreamExt;
|
||||
use log::warn;
|
||||
use remote::{
|
||||
error::RemoteAccessError,
|
||||
requests::{generate_url, make_authenticated_get},
|
||||
@@ -33,6 +34,7 @@ struct Depot {
|
||||
manifest: Option<DepotManifest>,
|
||||
latest_speed: Option<usize>, // bytes per second
|
||||
current_downloads: SyncSemaphore,
|
||||
enabled: bool
|
||||
}
|
||||
|
||||
pub struct DepotManager {
|
||||
@@ -53,6 +55,38 @@ impl DepotManager {
|
||||
}
|
||||
}
|
||||
|
||||
async fn sync_depot(&self, depot: &mut Depot) -> Result<(), RemoteAccessError> {
|
||||
let manifest_url = Url::parse(&depot.endpoint)?.join("manifest.json")?;
|
||||
let manifest = DROP_CLIENT_ASYNC.get(manifest_url).send().await?;
|
||||
let manifest: DepotManifest = manifest.json().await?;
|
||||
depot.manifest.replace(manifest);
|
||||
|
||||
let speedtest_url = Url::parse(&depot.endpoint)?.join("speedtest")?;
|
||||
let speedtest = DROP_CLIENT_ASYNC.get(speedtest_url).send().await?;
|
||||
|
||||
let mut stream = speedtest.bytes_stream();
|
||||
let start = Instant::now();
|
||||
let mut total_length = 0;
|
||||
|
||||
while let Some(chunk) = stream.next().await {
|
||||
let length = chunk?.len();
|
||||
total_length += length;
|
||||
if SPEEDTEST_TIMEOUT <= start.elapsed() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let elapsed = start.elapsed().as_millis() as usize;
|
||||
let speed = if elapsed == 0 {
|
||||
usize::MAX
|
||||
} else {
|
||||
(total_length / elapsed) * 1000
|
||||
};
|
||||
depot.latest_speed.replace(speed);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn sync_depots(&self) -> Result<(), RemoteAccessError> {
|
||||
let depots = make_authenticated_get(generate_url(&["/api/v1/client/depots"], &[])?).await?;
|
||||
let depots: Vec<ServersideDepot> = depots.json().await?;
|
||||
@@ -68,33 +102,20 @@ impl DepotManager {
|
||||
manifest: None,
|
||||
latest_speed: None,
|
||||
current_downloads: SyncSemaphore::new(),
|
||||
enabled: true,
|
||||
})
|
||||
.collect::<Vec<Depot>>();
|
||||
|
||||
for depot in &mut new_depots {
|
||||
let manifest_url = Url::parse(&depot.endpoint)?.join("manifest.json")?;
|
||||
let manifest = DROP_CLIENT_ASYNC.get(manifest_url).send().await?;
|
||||
let manifest: DepotManifest = manifest.json().await?;
|
||||
depot.manifest.replace(manifest);
|
||||
|
||||
let speedtest_url = Url::parse(&depot.endpoint)?.join("speedtest")?;
|
||||
let speedtest = DROP_CLIENT_ASYNC.get(speedtest_url).send().await?;
|
||||
|
||||
let mut stream = speedtest.bytes_stream();
|
||||
let start = Instant::now();
|
||||
let mut total_length = 0;
|
||||
|
||||
while let Some(chunk) = stream.next().await {
|
||||
let length = chunk?.len();
|
||||
total_length += length;
|
||||
if SPEEDTEST_TIMEOUT <= start.elapsed() {
|
||||
break;
|
||||
}
|
||||
if let Err(sync_error) = self.sync_depot(depot).await {
|
||||
warn!("failed to sync depot {}: {:?}", depot.endpoint, sync_error);
|
||||
depot.enabled = false;
|
||||
}
|
||||
|
||||
let elapsed = start.elapsed().as_millis() as usize;
|
||||
let speed = if elapsed == 0 { usize::MAX } else { (total_length / elapsed) * 1000 };
|
||||
depot.latest_speed.replace(speed);
|
||||
}
|
||||
|
||||
let enabled = new_depots.iter().filter(|v| v.enabled).count();
|
||||
if enabled == 0 {
|
||||
return Err(RemoteAccessError::NoDepots);
|
||||
}
|
||||
|
||||
let mut depot_lock = self.depots.write().unwrap();
|
||||
|
||||
@@ -16,7 +16,9 @@ use crate::{
|
||||
depot_manager::DepotManager,
|
||||
download_manager_frontend::DownloadStatus,
|
||||
error::ApplicationDownloadError,
|
||||
frontend_updates::{QueueUpdateEvent, QueueUpdateEventQueueData, StatsUpdateEvent},
|
||||
frontend_updates::{
|
||||
DiskStatsUpdateEvent, DownloadStatsUpdateEvent, QueueUpdateEvent, QueueUpdateEventQueueData,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{
|
||||
@@ -107,7 +109,13 @@ impl DownloadManagerBuilder {
|
||||
info!("download manager exited with result: {:?}", result);
|
||||
});
|
||||
|
||||
DownloadManager::new(terminator, queue, active_progress, command_sender, depot_manager)
|
||||
DownloadManager::new(
|
||||
terminator,
|
||||
queue,
|
||||
active_progress,
|
||||
command_sender,
|
||||
depot_manager,
|
||||
)
|
||||
}
|
||||
|
||||
fn set_status(&self, status: DownloadManagerStatus) {
|
||||
@@ -187,8 +195,8 @@ impl DownloadManagerBuilder {
|
||||
DownloadManagerSignal::UpdateUIQueue => {
|
||||
self.push_ui_queue_update();
|
||||
}
|
||||
DownloadManagerSignal::UpdateUIStats(kbs, time) => {
|
||||
self.push_ui_stats_update(kbs, time);
|
||||
DownloadManagerSignal::UpdateUIDownloadStats(kbs, time) => {
|
||||
self.push_ui_download_stats_update(kbs, time);
|
||||
}
|
||||
DownloadManagerSignal::Finish => {
|
||||
self.stop_and_wait_current_download().await;
|
||||
@@ -266,17 +274,16 @@ impl DownloadManagerBuilder {
|
||||
|
||||
*download_thread_lock = Some(tauri::async_runtime::spawn(async move {
|
||||
loop {
|
||||
let download_result =
|
||||
match download_agent.download(&app_handle).await {
|
||||
// Ok(true) is for completed and exited properly
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("download {:?} has error {}", download_agent.metadata(), &e);
|
||||
download_agent.on_error(&app_handle, &e);
|
||||
send!(sender, DownloadManagerSignal::Error(e));
|
||||
return;
|
||||
}
|
||||
};
|
||||
let download_result = match download_agent.download(&app_handle).await {
|
||||
// Ok(true) is for completed and exited properly
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("download {:?} has error {}", download_agent.metadata(), &e);
|
||||
download_agent.on_error(&app_handle, &e);
|
||||
send!(sender, DownloadManagerSignal::Error(e));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// If the download gets canceled
|
||||
// immediately return, on_cancelled gets called for us earlier
|
||||
@@ -339,7 +346,7 @@ impl DownloadManagerBuilder {
|
||||
send!(self.sender, DownloadManagerSignal::Go);
|
||||
}
|
||||
async fn manage_error_signal(&mut self, error: ApplicationDownloadError) {
|
||||
info!("got signal Error");
|
||||
warn!("got signal Error");
|
||||
if let Some(metadata) = self.download_queue.read().front()
|
||||
&& let Some(current_agent) = self.download_agent_registry.get(metadata)
|
||||
{
|
||||
@@ -384,9 +391,8 @@ impl DownloadManagerBuilder {
|
||||
self.push_ui_queue_update();
|
||||
send!(self.sender, DownloadManagerSignal::Go);
|
||||
}
|
||||
fn push_ui_stats_update(&self, kbs: usize, time: usize) {
|
||||
let event_data = StatsUpdateEvent { speed: kbs, time };
|
||||
|
||||
fn push_ui_download_stats_update(&self, kbs: usize, time: usize) {
|
||||
let event_data = DownloadStatsUpdateEvent { speed: kbs, time };
|
||||
app_emit!(&self.app_handle, "update_stats", event_data);
|
||||
}
|
||||
fn push_ui_queue_update(&self) {
|
||||
@@ -398,9 +404,12 @@ impl DownloadManagerBuilder {
|
||||
QueueUpdateEventQueueData {
|
||||
meta: DownloadableMetadata::clone(key),
|
||||
status: val.status(),
|
||||
progress: val.progress().get_progress(),
|
||||
current: val.progress().sum(),
|
||||
max: val.progress().get_max(),
|
||||
dl_progress: val.dl_progress().get_progress(),
|
||||
dl_current: val.dl_progress().sum(),
|
||||
dl_max: val.dl_progress().get_max(),
|
||||
disk_progress: val.disk_progress().get_progress(),
|
||||
disk_current: val.disk_progress().sum(),
|
||||
disk_max: val.disk_progress().get_max(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -40,7 +40,7 @@ pub enum DownloadManagerSignal {
|
||||
Error(ApplicationDownloadError),
|
||||
/// Pushes UI update
|
||||
UpdateUIQueue,
|
||||
UpdateUIStats(usize, usize), //kb/s and seconds
|
||||
UpdateUIDownloadStats(usize, usize), //kb/s and seconds
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -106,7 +106,7 @@ impl DownloadManager {
|
||||
download_queue,
|
||||
progress,
|
||||
command_sender,
|
||||
depot_manager
|
||||
depot_manager,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ pub trait Downloadable: Send + Sync + Debug {
|
||||
async fn download(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError>;
|
||||
fn validate(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError>;
|
||||
|
||||
fn progress(&self) -> Arc<ProgressObject>;
|
||||
fn dl_progress(&self) -> &Arc<ProgressObject>;
|
||||
fn disk_progress(&self) -> &Arc<ProgressObject>;
|
||||
fn control_flag(&self) -> DownloadThreadControl;
|
||||
fn status(&self) -> DownloadStatus;
|
||||
fn metadata(&self) -> DownloadableMetadata;
|
||||
|
||||
@@ -7,9 +7,12 @@ use crate::download_manager_frontend::DownloadStatus;
|
||||
pub struct QueueUpdateEventQueueData {
|
||||
pub meta: DownloadableMetadata,
|
||||
pub status: DownloadStatus,
|
||||
pub progress: f64,
|
||||
pub current: usize,
|
||||
pub max: usize,
|
||||
pub dl_progress: f64,
|
||||
pub dl_current: usize,
|
||||
pub dl_max: usize,
|
||||
pub disk_progress: f64,
|
||||
pub disk_current: usize,
|
||||
pub disk_max: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
@@ -18,7 +21,12 @@ pub struct QueueUpdateEvent {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct StatsUpdateEvent {
|
||||
pub struct DownloadStatsUpdateEvent {
|
||||
pub speed: usize,
|
||||
pub time: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct DiskStatsUpdateEvent {
|
||||
pub speed: usize,
|
||||
}
|
||||
@@ -7,15 +7,22 @@ use std::{
|
||||
};
|
||||
|
||||
use atomic_instant_full::AtomicInstant;
|
||||
use utils::{lock, send};
|
||||
use tokio::sync::mpsc::Sender;
|
||||
use utils::{lock, send};
|
||||
|
||||
use crate::download_manager_frontend::DownloadManagerSignal;
|
||||
use crate::{download_manager_frontend::DownloadManagerSignal, util::progress_object};
|
||||
|
||||
use super::rolling_progress_updates::RollingProgressWindow;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ProgressType {
|
||||
Download,
|
||||
Disk,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ProgressObject {
|
||||
progress_type: ProgressType,
|
||||
max: Arc<Mutex<usize>>,
|
||||
progress_instances: Arc<Mutex<Vec<Arc<AtomicUsize>>>>,
|
||||
start: Arc<Mutex<Instant>>,
|
||||
@@ -45,7 +52,7 @@ impl ProgressHandle {
|
||||
pub fn add(&self, amount: usize) {
|
||||
self.progress
|
||||
.fetch_add(amount, std::sync::atomic::Ordering::AcqRel);
|
||||
tauri::async_runtime::spawn(calculate_update(self.progress_object.clone()));
|
||||
spawn_update(&self.progress_object);
|
||||
}
|
||||
pub fn skip(&self, amount: usize) {
|
||||
self.progress
|
||||
@@ -59,7 +66,12 @@ impl ProgressHandle {
|
||||
}
|
||||
|
||||
impl ProgressObject {
|
||||
pub fn new(max: usize, length: usize, sender: Sender<DownloadManagerSignal>) -> Self {
|
||||
pub fn new(
|
||||
max: usize,
|
||||
length: usize,
|
||||
sender: Sender<DownloadManagerSignal>,
|
||||
progress_type: ProgressType,
|
||||
) -> Self {
|
||||
let arr = Mutex::new((0..length).map(|_| Arc::new(AtomicUsize::new(0))).collect());
|
||||
Self {
|
||||
max: Arc::new(Mutex::new(max)),
|
||||
@@ -70,6 +82,7 @@ impl ProgressObject {
|
||||
last_update_time: Arc::new(AtomicInstant::now()),
|
||||
bytes_last_update: Arc::new(AtomicUsize::new(0)),
|
||||
rolling: RollingProgressWindow::new(),
|
||||
progress_type,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,17 +124,21 @@ impl ProgressObject {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn calculate_update(progress: Arc<ProgressObject>) {
|
||||
let last_update_time = progress
|
||||
.last_update_time
|
||||
.load(Ordering::SeqCst);
|
||||
pub fn spawn_update(progress: &Arc<ProgressObject>) {
|
||||
let last_update_time = progress.last_update_time.load(Ordering::SeqCst);
|
||||
let time_since_last_update = Instant::now()
|
||||
.duration_since(last_update_time)
|
||||
.as_millis_f64();
|
||||
if time_since_last_update < 250.0 {
|
||||
return;
|
||||
}
|
||||
progress.last_update_time.swap(Instant::now(), Ordering::SeqCst);
|
||||
tauri::async_runtime::spawn(calculate_update(progress.clone(), time_since_last_update));
|
||||
}
|
||||
|
||||
pub async fn calculate_update(progress: Arc<ProgressObject>, time_since_last_update: f64) {
|
||||
progress
|
||||
.last_update_time
|
||||
.swap(Instant::now(), Ordering::SeqCst);
|
||||
|
||||
let current_bytes_downloaded = progress.sum();
|
||||
let max = progress.get_max();
|
||||
@@ -148,11 +165,18 @@ pub async fn push_update(progress: &ProgressObject, bytes_remaining: usize) {
|
||||
update_queue(progress).await;
|
||||
}
|
||||
|
||||
async fn update_ui(progress_object: &ProgressObject, kilobytes_per_second: usize, time_remaining: usize) {
|
||||
send!(
|
||||
progress_object.sender,
|
||||
DownloadManagerSignal::UpdateUIStats(kilobytes_per_second, time_remaining)
|
||||
);
|
||||
async fn update_ui(
|
||||
progress_object: &ProgressObject,
|
||||
kilobytes_per_second: usize,
|
||||
time_remaining: usize,
|
||||
) {
|
||||
match progress_object.progress_type {
|
||||
ProgressType::Download => send!(
|
||||
progress_object.sender,
|
||||
DownloadManagerSignal::UpdateUIDownloadStats(kilobytes_per_second, time_remaining)
|
||||
),
|
||||
ProgressType::Disk => (),
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_queue(progress: &ProgressObject) {
|
||||
|
||||
@@ -9,13 +9,15 @@ use download_manager::error::ApplicationDownloadError;
|
||||
use download_manager::util::download_thread_control_flag::{
|
||||
DownloadThreadControl, DownloadThreadControlFlag,
|
||||
};
|
||||
use download_manager::util::progress_object::{ProgressHandle, ProgressObject};
|
||||
use droplet_rs::manifest::Manifest;
|
||||
use download_manager::util::progress_object::{ProgressHandle, ProgressObject, ProgressType};
|
||||
use droplet_rs::manifest::{ChunkData, Manifest};
|
||||
use log::{debug, error, info, warn};
|
||||
use remote::auth::generate_authorization_header;
|
||||
use remote::error::RemoteAccessError;
|
||||
use remote::requests::generate_url;
|
||||
use remote::utils::DROP_CLIENT_ASYNC;
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::mem;
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -34,11 +36,21 @@ use super::drop_data::DropData;
|
||||
|
||||
static RETRY_COUNT: usize = 3;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DownloadInformation {
|
||||
file_list: HashMap<String, String>,
|
||||
manifests: HashMap<String, Manifest>,
|
||||
install_size: u64,
|
||||
download_size: u64,
|
||||
}
|
||||
|
||||
pub struct GameDownloadAgent {
|
||||
pub metadata: DownloadableMetadata,
|
||||
pub control_flag: DownloadThreadControl,
|
||||
pub manifest: Mutex<Option<Manifest>>,
|
||||
pub progress: Arc<ProgressObject>,
|
||||
pub dl_info: Mutex<Option<DownloadInformation>>,
|
||||
pub download_progress: Arc<ProgressObject>,
|
||||
pub disk_progress: Arc<ProgressObject>,
|
||||
depot_manager: Arc<DepotManager>,
|
||||
sender: Sender<DownloadManagerSignal>,
|
||||
pub dropdata: DropData,
|
||||
@@ -86,12 +98,23 @@ impl GameDownloadAgent {
|
||||
metadata.target_platform,
|
||||
data_base_dir_path.clone(),
|
||||
);
|
||||
|
||||
|
||||
let result = Self {
|
||||
metadata,
|
||||
control_flag,
|
||||
manifest: Mutex::new(None),
|
||||
progress: Arc::new(ProgressObject::new(0, 0, sender.clone())),
|
||||
dl_info: Mutex::new(None),
|
||||
download_progress: Arc::new(ProgressObject::new(
|
||||
0,
|
||||
0,
|
||||
sender.clone(),
|
||||
ProgressType::Download,
|
||||
)),
|
||||
disk_progress: Arc::new(ProgressObject::new(
|
||||
0,
|
||||
0,
|
||||
sender.clone(),
|
||||
ProgressType::Disk,
|
||||
)),
|
||||
sender,
|
||||
dropdata: stored_manifest,
|
||||
status: Mutex::new(DownloadStatus::Queued),
|
||||
@@ -100,7 +123,7 @@ impl GameDownloadAgent {
|
||||
|
||||
result.ensure_manifest_exists().await?;
|
||||
|
||||
let required_space = lock!(result.manifest).as_ref().unwrap().size;
|
||||
let required_space = lock!(result.dl_info).as_ref().unwrap().install_size;
|
||||
|
||||
let available_space = get_disk_available(data_base_dir_path)? as u64;
|
||||
|
||||
@@ -157,11 +180,11 @@ impl GameDownloadAgent {
|
||||
}
|
||||
|
||||
pub fn check_manifest_exists(&self) -> bool {
|
||||
lock!(self.manifest).is_some()
|
||||
lock!(self.dl_info).is_some()
|
||||
}
|
||||
|
||||
pub async fn ensure_manifest_exists(&self) -> Result<(), ApplicationDownloadError> {
|
||||
if lock!(self.manifest).is_some() {
|
||||
if lock!(self.dl_info).is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -195,12 +218,12 @@ impl GameDownloadAgent {
|
||||
));
|
||||
}
|
||||
|
||||
let manifest_download: Manifest = response
|
||||
let manifest_download: DownloadInformation = response
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| ApplicationDownloadError::Communication(e.into()))?;
|
||||
|
||||
if let Ok(mut manifest) = self.manifest.lock() {
|
||||
if let Ok(mut manifest) = self.dl_info.lock() {
|
||||
*manifest = Some(manifest_download);
|
||||
return Ok(());
|
||||
}
|
||||
@@ -208,34 +231,62 @@ impl GameDownloadAgent {
|
||||
Err(ApplicationDownloadError::Lock)
|
||||
}
|
||||
|
||||
// Sets it up for both download and validate
|
||||
// Sets up progress for download writes
|
||||
fn setup_progress(&self) {
|
||||
let manifest = lock!(self.manifest);
|
||||
let manifest = manifest.as_ref().unwrap();
|
||||
let dl_info = lock!(self.dl_info);
|
||||
let dl_info = dl_info.as_ref().unwrap();
|
||||
|
||||
self.progress.set_max(manifest.size.try_into().unwrap());
|
||||
self.progress.set_size(manifest.chunks.len());
|
||||
self.progress.reset();
|
||||
let total_chunks = dl_info
|
||||
.manifests
|
||||
.iter()
|
||||
.map(|v| v.1.chunks.len())
|
||||
.sum::<usize>();
|
||||
|
||||
self.download_progress
|
||||
.set_max(dl_info.download_size.try_into().unwrap());
|
||||
self.download_progress
|
||||
.set_size(total_chunks);
|
||||
self.download_progress.reset();
|
||||
|
||||
self.disk_progress.set_max(dl_info.install_size.try_into().unwrap());
|
||||
self.disk_progress
|
||||
.set_size(total_chunks);
|
||||
self.disk_progress.reset();
|
||||
}
|
||||
|
||||
async fn run(&self) -> Result<bool, RemoteAccessError> {
|
||||
self.depot_manager.sync_depots().await?;
|
||||
info!("synced depots");
|
||||
self.setup_progress();
|
||||
let (chunks, key) = {
|
||||
let manifest = lock!(self.manifest);
|
||||
let manifest = manifest.as_ref().unwrap();
|
||||
(manifest.chunks.clone(), manifest.key)
|
||||
info!("setup progress objects");
|
||||
let manifests_chunks: Vec<(String, HashMap<String, ChunkData>, [u8; 16])> = {
|
||||
let dl_info = lock!(self.dl_info);
|
||||
dl_info
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.manifests
|
||||
.iter()
|
||||
.map(|v| (v.0.clone(), v.1.chunks.clone(), v.1.key))
|
||||
.collect()
|
||||
};
|
||||
let file_list = {
|
||||
let dl_info = lock!(self.dl_info);
|
||||
dl_info.as_ref().unwrap().file_list.clone()
|
||||
};
|
||||
let chunk_len = chunks.len();
|
||||
let mut completed_chunks = {
|
||||
let completed_chunks = lock!(self.dropdata.contexts);
|
||||
completed_chunks.clone()
|
||||
};
|
||||
let chunk_len = manifests_chunks.iter().map(|v| v.1.len()).sum::<usize>();
|
||||
let max_download_threads = borrow_db_checked().settings.max_download_threads;
|
||||
|
||||
let (sender, recv) = crossbeam_channel::bounded(16);
|
||||
|
||||
// SAFETY: I pinky-promise
|
||||
// (the scope keeps these in scope)
|
||||
let unsafe_self: &'static GameDownloadAgent = unsafe { mem::transmute(self) };
|
||||
let file_list: &'static HashMap<String, String> = unsafe { mem::transmute(&file_list) };
|
||||
|
||||
let local_completed_chunks = completed_chunks.clone();
|
||||
|
||||
let download_join_handle = tauri::async_runtime::spawn_blocking(move || {
|
||||
@@ -244,77 +295,95 @@ impl GameDownloadAgent {
|
||||
.build()
|
||||
.unwrap();
|
||||
thread_pool.scope(move |s| {
|
||||
for (index, (chunk_id, chunk_data)) in chunks.into_iter().enumerate() {
|
||||
let local_sender = sender.clone();
|
||||
let progress = unsafe_self.progress.get(index);
|
||||
let progress_handle =
|
||||
ProgressHandle::new(progress, unsafe_self.progress.clone());
|
||||
let mut index = 0;
|
||||
for (version_id, chunks, key) in manifests_chunks.into_iter() {
|
||||
let version_id = &version_id;
|
||||
for (chunk_id, chunk_data) in chunks.into_iter() {
|
||||
let local_sender = sender.clone();
|
||||
let download_progress_handle = ProgressHandle::new(
|
||||
unsafe_self.download_progress.get(index),
|
||||
unsafe_self.download_progress.clone(),
|
||||
);
|
||||
let disk_progress_handle = ProgressHandle::new(
|
||||
unsafe_self.disk_progress.get(index),
|
||||
unsafe_self.disk_progress.clone(),
|
||||
);
|
||||
index += 1;
|
||||
|
||||
let chunk_length = chunk_data.files.iter().map(|v| v.length).sum();
|
||||
let chunk_length = chunk_data.files.iter().map(|v| v.length).sum();
|
||||
|
||||
if *local_completed_chunks.get(&chunk_id).unwrap_or(&false) {
|
||||
progress_handle.skip(chunk_length);
|
||||
continue;
|
||||
}
|
||||
|
||||
let sender = unsafe_self.sender.clone();
|
||||
let (depot, permit) = match unsafe_self
|
||||
.depot_manager
|
||||
.next_depot(&unsafe_self.metadata.id, &unsafe_self.metadata.version)
|
||||
{
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
tauri::async_runtime::spawn(async move {
|
||||
send!(sender, DownloadManagerSignal::Error(ApplicationDownloadError::Communication(err)));
|
||||
});
|
||||
return;
|
||||
if *local_completed_chunks.get(&chunk_id).unwrap_or(&false) {
|
||||
download_progress_handle.skip(chunk_length);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
s.spawn(move |_| {
|
||||
for i in 0..RETRY_COUNT {
|
||||
let loop_progress_handle = progress_handle.clone();
|
||||
let base_path = unsafe_self.dropdata.base_path.clone();
|
||||
match download_game_chunk(
|
||||
&unsafe_self.metadata.id,
|
||||
&unsafe_self.metadata.version,
|
||||
&chunk_id,
|
||||
&depot,
|
||||
&key,
|
||||
&chunk_data,
|
||||
base_path,
|
||||
&unsafe_self.control_flag,
|
||||
loop_progress_handle,
|
||||
) {
|
||||
Ok(true) => {
|
||||
local_sender.send(chunk_id.clone()).unwrap();
|
||||
drop(permit); // Take ownership
|
||||
return;
|
||||
}
|
||||
Ok(false) => return,
|
||||
Err(e) => {
|
||||
warn!("got error for chunk id {}: {e:?}", chunk_id);
|
||||
let sender = unsafe_self.sender.clone();
|
||||
let (depot, permit) = match unsafe_self
|
||||
.depot_manager
|
||||
.next_depot(&unsafe_self.metadata.id, &unsafe_self.metadata.version)
|
||||
{
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
tauri::async_runtime::spawn(async move {
|
||||
send!(
|
||||
sender,
|
||||
DownloadManagerSignal::Error(
|
||||
ApplicationDownloadError::Communication(err)
|
||||
)
|
||||
);
|
||||
});
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let retry = true; /*matches!(
|
||||
&e,
|
||||
ApplicationDownloadError::Communication(_)
|
||||
| ApplicationDownloadError::Checksum
|
||||
| ApplicationDownloadError::Lock
|
||||
| ApplicationDownloadError::IoError(_)
|
||||
);*/
|
||||
|
||||
if i == RETRY_COUNT - 1 || !retry {
|
||||
warn!("retry logic failed, not re-attempting.");
|
||||
tauri::async_runtime::spawn(async move {
|
||||
send!(sender, DownloadManagerSignal::Error(e));
|
||||
});
|
||||
let local_version_id = version_id.clone();
|
||||
s.spawn(move |_| {
|
||||
for i in 0..RETRY_COUNT {
|
||||
let base_path = unsafe_self.dropdata.base_path.clone();
|
||||
match download_game_chunk(
|
||||
&unsafe_self.metadata.id,
|
||||
&local_version_id,
|
||||
&chunk_id,
|
||||
&depot,
|
||||
&key,
|
||||
&chunk_data,
|
||||
file_list,
|
||||
base_path,
|
||||
&unsafe_self.control_flag,
|
||||
&download_progress_handle,
|
||||
&disk_progress_handle,
|
||||
) {
|
||||
Ok(true) => {
|
||||
local_sender.send(chunk_id.clone()).unwrap();
|
||||
drop(permit); // Take ownership
|
||||
return;
|
||||
}
|
||||
Ok(false) => return,
|
||||
Err(e) => {
|
||||
warn!("got error for chunk id {}: {e:?}", chunk_id);
|
||||
|
||||
let retry = true; /*matches!(
|
||||
&e,
|
||||
ApplicationDownloadError::Communication(_)
|
||||
| ApplicationDownloadError::Checksum
|
||||
| ApplicationDownloadError::Lock
|
||||
| ApplicationDownloadError::IoError(_)
|
||||
);*/
|
||||
|
||||
if i == RETRY_COUNT - 1 || !retry {
|
||||
warn!("retry logic failed, not re-attempting.");
|
||||
tauri::async_runtime::spawn(async move {
|
||||
send!(sender, DownloadManagerSignal::Error(e));
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
drop(sender);
|
||||
});
|
||||
});
|
||||
@@ -457,8 +526,12 @@ impl Downloadable for GameDownloadAgent {
|
||||
self.validate(app_handle)
|
||||
}
|
||||
|
||||
fn progress(&self) -> Arc<ProgressObject> {
|
||||
self.progress.clone()
|
||||
fn dl_progress(&self) -> &Arc<ProgressObject> {
|
||||
&self.download_progress
|
||||
}
|
||||
|
||||
fn disk_progress(&self) -> &Arc<ProgressObject> {
|
||||
&self.disk_progress
|
||||
}
|
||||
|
||||
fn control_flag(&self) -> DownloadThreadControl {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fs::{Permissions, set_permissions};
|
||||
use std::io::{Read, Seek as _, SeekFrom, Write as _};
|
||||
#[cfg(unix)]
|
||||
@@ -32,13 +33,18 @@ pub fn download_game_chunk(
|
||||
depot: &str,
|
||||
key: &[u8; 16],
|
||||
chunk_data: &ChunkData,
|
||||
file_list: &HashMap<String, String>,
|
||||
base_path: PathBuf,
|
||||
control_flag: &DownloadThreadControl,
|
||||
progress: ProgressHandle,
|
||||
// How much we're downloading
|
||||
download_progress: &ProgressHandle,
|
||||
// How much we're writing to disk
|
||||
disk_progress: &ProgressHandle,
|
||||
) -> Result<bool, ApplicationDownloadError> {
|
||||
// If we're paused
|
||||
if control_flag.get() == DownloadThreadControlFlag::Stop {
|
||||
progress.set(0);
|
||||
download_progress.set(0);
|
||||
disk_progress.set(0);
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
@@ -48,10 +54,7 @@ pub fn download_game_chunk(
|
||||
|
||||
let url = Url::parse(depot)
|
||||
.map_err(|v| ApplicationDownloadError::DownloadError(v.into()))?
|
||||
.join(&format!(
|
||||
"content/{}/{}/{}",
|
||||
game_id, version_id, chunk_id
|
||||
))
|
||||
.join(&format!("content/{}/{}/{}", game_id, version_id, chunk_id))
|
||||
.map_err(|v| ApplicationDownloadError::DownloadError(v.into()))?;
|
||||
|
||||
let response = DROP_CLIENT_SYNC
|
||||
@@ -77,7 +80,8 @@ pub fn download_game_chunk(
|
||||
}
|
||||
|
||||
if control_flag.get() == DownloadThreadControlFlag::Stop {
|
||||
progress.set(0);
|
||||
download_progress.set(0);
|
||||
disk_progress.set(0);
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
@@ -95,27 +99,39 @@ pub fn download_game_chunk(
|
||||
let mut cipher = Aes128Ctr64LE::new(key.into(), &chunk_data.iv.into());
|
||||
let mut read_buf = vec![0u8; READ_BUF_LEN];
|
||||
for file in &chunk_data.files {
|
||||
let should_write = file_list
|
||||
.get(&file.filename)
|
||||
.map(|v| v == version_id)
|
||||
.unwrap_or(false);
|
||||
let path = base_path.join(file.filename.clone());
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
let mut file_handle = std::fs::OpenOptions::new()
|
||||
.truncate(false)
|
||||
.write(true)
|
||||
.append(false)
|
||||
.create(true)
|
||||
.open(&path)?;
|
||||
file_handle.seek(SeekFrom::Start(file.start.try_into().unwrap()))?;
|
||||
let mut file_handle = if should_write {
|
||||
let mut file_handle = std::fs::OpenOptions::new()
|
||||
.truncate(false)
|
||||
.write(true)
|
||||
.append(false)
|
||||
.create(true)
|
||||
.open(&path)?;
|
||||
file_handle.seek(SeekFrom::Start(file.start.try_into().unwrap()))?;
|
||||
Some(file_handle)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut remaining = file.length;
|
||||
while remaining > 0 {
|
||||
let amount = stream_reader.read(&mut read_buf[0..remaining.min(READ_BUF_LEN)])?;
|
||||
progress.add(amount);
|
||||
download_progress.add(amount);
|
||||
remaining -= amount;
|
||||
|
||||
cipher.apply_keystream(&mut read_buf[0..amount]);
|
||||
hasher.update(&read_buf[0..amount]);
|
||||
file_handle.write_all(&read_buf[0..amount])?;
|
||||
//hasher.update(&read_buf[0..amount]);
|
||||
if let Some(file_handle) = &mut file_handle {
|
||||
file_handle.write_all(&read_buf[0..amount])?;
|
||||
disk_progress.add(amount);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
@@ -132,14 +148,14 @@ pub fn download_game_chunk(
|
||||
}
|
||||
|
||||
if control_flag.get() == DownloadThreadControlFlag::Stop {
|
||||
progress.set(0);
|
||||
download_progress.set(0);
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
let digest = hex::encode(hasher.finalize());
|
||||
if digest != chunk_data.checksum {
|
||||
return Err(ApplicationDownloadError::Checksum);
|
||||
//return Err(ApplicationDownloadError::Checksum);
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
// Drops go in buckets
|
||||
pub struct DownloadDrop {
|
||||
pub index: usize,
|
||||
pub filename: String,
|
||||
pub path: PathBuf,
|
||||
pub start: usize,
|
||||
pub length: usize,
|
||||
pub checksum: String,
|
||||
pub permissions: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct DownloadBucket {
|
||||
pub game_id: String,
|
||||
pub version: String,
|
||||
pub drops: Vec<DownloadDrop>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct DropValidateContext {
|
||||
pub index: usize,
|
||||
pub offset: usize,
|
||||
pub path: PathBuf,
|
||||
pub checksum: String,
|
||||
pub length: usize,
|
||||
}
|
||||
|
||||
impl From<DownloadBucket> for Vec<DropValidateContext> {
|
||||
fn from(value: DownloadBucket) -> Self {
|
||||
value
|
||||
.drops
|
||||
.into_iter()
|
||||
.map(|e| DropValidateContext {
|
||||
index: e.index,
|
||||
offset: e.start,
|
||||
path: e.path,
|
||||
checksum: e.checksum,
|
||||
length: e.length,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
@@ -2,5 +2,4 @@ pub mod download_agent;
|
||||
mod download_logic;
|
||||
pub mod drop_data;
|
||||
pub mod error;
|
||||
mod manifest;
|
||||
pub mod utils;
|
||||
@@ -5,10 +5,8 @@ use database::{
|
||||
};
|
||||
use log::{debug, error, warn};
|
||||
use remote::{
|
||||
auth::generate_authorization_header,
|
||||
error::RemoteAccessError,
|
||||
requests::generate_url,
|
||||
utils::DROP_CLIENT_ASYNC
|
||||
auth::generate_authorization_header, error::RemoteAccessError, requests::generate_url,
|
||||
utils::DROP_CLIENT_ASYNC,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs::remove_dir_all;
|
||||
@@ -160,29 +158,28 @@ pub fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle)
|
||||
spawn(move || {
|
||||
if let Err(e) = remove_dir_all(install_dir) {
|
||||
error!("{e}");
|
||||
} else {
|
||||
let mut db_handle = borrow_db_mut_checked();
|
||||
db_handle.applications.transient_statuses.remove(&meta);
|
||||
db_handle
|
||||
.applications
|
||||
.installed_game_version
|
||||
.remove(&meta.id);
|
||||
db_handle
|
||||
.applications
|
||||
.game_statuses
|
||||
.insert(meta.id.clone(), GameDownloadStatus::Remote {});
|
||||
let _ = db_handle.applications.transient_statuses.remove(&meta);
|
||||
|
||||
push_game_update(
|
||||
&app_handle,
|
||||
&meta.id,
|
||||
None,
|
||||
GameStatusManager::fetch_state(&meta.id, &db_handle),
|
||||
);
|
||||
|
||||
debug!("uninstalled game id {}", &meta.id);
|
||||
app_emit!(&app_handle, "update_library", ());
|
||||
}
|
||||
let mut db_handle = borrow_db_mut_checked();
|
||||
db_handle.applications.transient_statuses.remove(&meta);
|
||||
db_handle
|
||||
.applications
|
||||
.installed_game_version
|
||||
.remove(&meta.id);
|
||||
db_handle
|
||||
.applications
|
||||
.game_statuses
|
||||
.insert(meta.id.clone(), GameDownloadStatus::Remote {});
|
||||
let _ = db_handle.applications.transient_statuses.remove(&meta);
|
||||
|
||||
push_game_update(
|
||||
&app_handle,
|
||||
&meta.id,
|
||||
None,
|
||||
GameStatusManager::fetch_state(&meta.id, &db_handle),
|
||||
);
|
||||
|
||||
debug!("uninstalled game id {}", &meta.id);
|
||||
app_emit!(&app_handle, "update_library", ());
|
||||
});
|
||||
} else {
|
||||
warn!("invalid previous state for uninstall, failing silently.");
|
||||
|
||||
@@ -453,7 +453,7 @@ impl ProcessManager<'_> {
|
||||
#[cfg(target_os = "windows")]
|
||||
let mut command = Command::new("cmd");
|
||||
#[cfg(target_os = "windows")]
|
||||
command.raw_arg(format!("/C \"{}\"", &launch_string));
|
||||
command.raw_arg(format!("/C \"{}\"", &launch_parameters.0));
|
||||
|
||||
info!(
|
||||
"launching (in {}): {}",
|
||||
|
||||
@@ -14,6 +14,7 @@ droplet-rs = "0.7.3"
|
||||
gethostname = "1.0.2"
|
||||
hex = "0.4.3"
|
||||
http = "1.3.1"
|
||||
jsonwebtoken = { version = "10.3.0", features = ["rust_crypto"] }
|
||||
log = "0.4.28"
|
||||
md5 = "0.8.0"
|
||||
reqwest = { version = "0.12.28", default-features = false, features = [
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
use std::{collections::HashMap, env};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
env,
|
||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use chrono::Utc;
|
||||
use client::{app_status::AppStatus, user::User};
|
||||
use database::{DatabaseAuth, interface::borrow_db_checked};
|
||||
use droplet_rs::ssl::sign_nonce;
|
||||
use gethostname::gethostname;
|
||||
use jsonwebtoken::{Algorithm, EncodingKey, Header};
|
||||
use log::{error, warn};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
@@ -60,18 +65,36 @@ impl From<HandshakeResponse> for DatabaseAuth {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Claims {
|
||||
exp: usize,
|
||||
nbf: usize,
|
||||
}
|
||||
|
||||
pub fn generate_authorization_header() -> String {
|
||||
let certs = {
|
||||
let db = borrow_db_checked();
|
||||
db.auth.clone().expect("Authorisation not initialised")
|
||||
};
|
||||
|
||||
let nonce = Utc::now().timestamp_millis().to_string();
|
||||
let system_time: usize = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or(Duration::from_secs(0))
|
||||
.as_secs() as usize;
|
||||
|
||||
let signature =
|
||||
sign_nonce(certs.private, nonce.clone()).expect("Failed to generate authorisation header");
|
||||
let claims = Claims {
|
||||
nbf: system_time,
|
||||
exp: system_time + 10,
|
||||
};
|
||||
|
||||
format!("Nonce {} {} {}", certs.client_id, nonce, signature)
|
||||
let jwt = jsonwebtoken::encode(
|
||||
&Header::new(Algorithm::ES384),
|
||||
&claims,
|
||||
&EncodingKey::from_ec_pem(certs.private.as_bytes()).unwrap(),
|
||||
)
|
||||
.expect("failed to sign jwt");
|
||||
|
||||
format!("JWT {} {}", certs.client_id, jwt)
|
||||
}
|
||||
|
||||
pub async fn fetch_user() -> Result<User, RemoteAccessError> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use database::borrow_db_checked;
|
||||
use http::{
|
||||
HeaderMap, HeaderValue, Request, Response, StatusCode, Uri, header::USER_AGENT,
|
||||
HeaderMap, HeaderValue, Request, Response, StatusCode, Uri, header::{CONTENT_SECURITY_POLICY, USER_AGENT, X_FRAME_OPTIONS},
|
||||
};
|
||||
use log::{error, warn};
|
||||
use tauri::UriSchemeResponder;
|
||||
@@ -30,7 +30,7 @@ pub async fn handle_server_proto_wrapper(request: Request<Vec<u8>>, responder: U
|
||||
match handle_server_proto(request).await {
|
||||
Ok(r) => responder.respond(r),
|
||||
Err(e) => {
|
||||
warn!("Cache error: {e}");
|
||||
warn!("server proto error: {e}");
|
||||
responder.respond(
|
||||
Response::builder()
|
||||
.status(e)
|
||||
@@ -84,12 +84,13 @@ async fn handle_server_proto(request: Request<Vec<u8>>) -> Result<Response<Vec<u
|
||||
let response = match DROP_CLIENT_ASYNC
|
||||
.request(request.method().clone(), new_uri.to_string())
|
||||
.headers(headers)
|
||||
.body(request.body().clone()) // TODO: refactor this into a move
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(response) => response,
|
||||
Err(e) => {
|
||||
warn!("Could not send response. Got {e} when sending");
|
||||
warn!("Could not send response. Got {e:?} when sending");
|
||||
return Err(e.status().unwrap_or(StatusCode::BAD_REQUEST));
|
||||
}
|
||||
};
|
||||
@@ -102,6 +103,12 @@ async fn handle_server_proto(request: Request<Vec<u8>>) -> Result<Response<Vec<u
|
||||
{
|
||||
let client_response_headers = client_http_response.headers_mut().unwrap();
|
||||
for (header, header_value) in response.headers() {
|
||||
if header == CONTENT_SECURITY_POLICY {
|
||||
continue;
|
||||
}
|
||||
if header == X_FRAME_OPTIONS {
|
||||
continue;
|
||||
}
|
||||
client_response_headers.insert(header, header_value.clone());
|
||||
}
|
||||
};
|
||||
|
||||
+11
-6
@@ -2,8 +2,8 @@ use std::sync::nonpoison::Mutex;
|
||||
|
||||
use bitcode::{Decode, Encode};
|
||||
use database::{
|
||||
DownloadableMetadata, GameDownloadStatus, borrow_db_checked,
|
||||
borrow_db_mut_checked, platform::Platform,
|
||||
DownloadableMetadata, GameDownloadStatus, borrow_db_checked, borrow_db_mut_checked,
|
||||
platform::Platform,
|
||||
};
|
||||
use games::{
|
||||
collections::collection::Collection,
|
||||
@@ -190,7 +190,6 @@ pub async fn fetch_game_logic(
|
||||
let db_lock = borrow_db_checked();
|
||||
|
||||
let metadata_option = db_lock.applications.installed_game_version.get(&id);
|
||||
|
||||
|
||||
match metadata_option {
|
||||
None => None,
|
||||
@@ -258,7 +257,7 @@ struct VersionDownloadOptionRequiredContent {
|
||||
name: String,
|
||||
icon_object_id: String,
|
||||
short_description: String,
|
||||
size: usize,
|
||||
size: GameSize,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -268,9 +267,15 @@ pub struct VersionDownloadOption {
|
||||
display_name: Option<String>,
|
||||
version_path: String,
|
||||
platform: Platform,
|
||||
size: usize,
|
||||
size: GameSize,
|
||||
required_content: Vec<VersionDownloadOptionRequiredContent>,
|
||||
}
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GameSize {
|
||||
install_size: usize,
|
||||
download_size: usize,
|
||||
}
|
||||
|
||||
pub async fn fetch_game_version_options_logic(
|
||||
game_id: String,
|
||||
@@ -278,7 +283,7 @@ pub async fn fetch_game_version_options_logic(
|
||||
) -> Result<Vec<VersionDownloadOption>, RemoteAccessError> {
|
||||
let client = DROP_CLIENT_ASYNC.clone();
|
||||
|
||||
let response = generate_url(&["/api/v1/client/game/versions"], &[("id", &game_id)])?;
|
||||
let response = generate_url(&["/api/v1/client/game", &game_id, "versions"], &[])?;
|
||||
let response = client
|
||||
.get(response)
|
||||
.header("Authorization", generate_authorization_header())
|
||||
|
||||
@@ -73,7 +73,7 @@ async fn setup(handle: AppHandle) -> AppState {
|
||||
|
||||
let console = ConsoleAppender::builder()
|
||||
.encoder(Box::new(PatternEncoder::new(
|
||||
"{d} | {l} | {f}:{L} - {m}{n}",
|
||||
"{d} | {h({l})} | {f}:{L} - {m}{n}",
|
||||
)))
|
||||
.build();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user