mirror of
https://github.com/Drop-OSS/drop-app.git
synced 2026-01-30 19:15:17 +01:00
Compare commits
10 Commits
v0.3.0-rc-
...
v0.3.1-mac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1bc7bf418 | ||
|
|
cb2295dd7c | ||
|
|
cc5339a389 | ||
|
|
6104bfda72 | ||
|
|
be688cb18f | ||
|
|
13cc69f10e | ||
|
|
574782f445 | ||
|
|
b5a8543194 | ||
|
|
d0e4aea5ce | ||
|
|
739e6166c5 |
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@@ -60,6 +60,9 @@ jobs:
|
||||
- uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
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__'
|
||||
|
||||
1
app.vue
1
app.vue
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<LoadingIndicator />
|
||||
<NuxtLayout class="select-none w-screen h-screen">
|
||||
<NuxtPage />
|
||||
<ModalStack />
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<MenuItems
|
||||
class="absolute bg-zinc-900 right-0 top-10 z-50 w-56 origin-top-right focus:outline-none shadow-md"
|
||||
>
|
||||
<PanelWidget class="flex-col gap-y-2">
|
||||
<div class="flex-col gap-y-2">
|
||||
<NuxtLink
|
||||
to="/id/me"
|
||||
class="transition inline-flex items-center w-full py-3 px-4 hover:bg-zinc-800"
|
||||
@@ -65,7 +65,7 @@
|
||||
</button>
|
||||
</MenuItem>
|
||||
</div>
|
||||
</PanelWidget>
|
||||
</div>
|
||||
</MenuItems>
|
||||
</transition>
|
||||
</Menu>
|
||||
|
||||
@@ -13,11 +13,7 @@
|
||||
<div class="max-w-lg">
|
||||
<slot />
|
||||
<div class="mt-10">
|
||||
<button
|
||||
@click="() => authWrapper_wrapper()"
|
||||
:disabled="loading"
|
||||
class="text-sm text-left font-semibold leading-7 text-blue-600"
|
||||
>
|
||||
<div>
|
||||
<div v-if="loading" role="status">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
@@ -37,10 +33,19 @@
|
||||
</svg>
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
<span v-else>
|
||||
Sign in with your browser <span aria-hidden="true">→</span>
|
||||
<span class="inline-flex gap-x-8 items-center" v-else>
|
||||
<button
|
||||
@click="() => authWrapper_wrapper()"
|
||||
:disabled="loading"
|
||||
class="px-3 py-1 inline-flex items-center gap-x-2 bg-zinc-700 rounded text-sm text-left font-semibold leading-7 text-white"
|
||||
>
|
||||
Sign in with your browser <ArrowTopRightOnSquareIcon class="size-4" />
|
||||
</button>
|
||||
<NuxtLink href="/auth/code" class="text-zinc-100 text-sm hover:text-zinc-300">
|
||||
Use a code →
|
||||
</NuxtLink>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-5" v-if="offerManual">
|
||||
<h1 class="text-zinc-100 font-semibold">Having trouble?</h1>
|
||||
@@ -121,6 +126,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { XCircleIcon } from "@heroicons/vue/16/solid";
|
||||
import { ArrowTopRightOnSquareIcon } from "@heroicons/vue/20/solid";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
7
components/LoadingIndicator.vue
Normal file
7
components/LoadingIndicator.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<template></template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const loading = useLoadingIndicator();
|
||||
|
||||
watch(loading.isLoading, console.log);
|
||||
</script>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="h-10 cursor-pointer flex flex-row items-center justify-between bg-zinc-950"
|
||||
class="h-16 cursor-pointer flex flex-row items-center justify-between bg-zinc-950"
|
||||
>
|
||||
<div class="px-5 py-3 grow" @mousedown="() => window.startDragging()">
|
||||
<Wordmark class="mt-1" />
|
||||
|
||||
Submodule drop-base updated: 26698e5b06...04125e89be
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "drop-app",
|
||||
"private": true,
|
||||
"version": "0.3.0-rc-8",
|
||||
"version": "0.3.1-mac",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "nuxt build",
|
||||
|
||||
37
pages/auth/code.vue
Normal file
37
pages/auth/code.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div class="min-h-full w-full flex items-center justify-center">
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="text-center">
|
||||
<h1 class="text-3xl font-semibold font-display leading-6 text-zinc-100">
|
||||
Device authorization
|
||||
</h1>
|
||||
<div class="mt-4">
|
||||
<p class="text-sm text-zinc-400 max-w-md mx-auto">
|
||||
Open Drop on another one of your devices, and use your account
|
||||
dropdown to "Authorize client", and enter the code below.
|
||||
</p>
|
||||
<div
|
||||
class="mt-8 flex items-center justify-center gap-x-5 text-8xl font-bold text-zinc-100"
|
||||
>
|
||||
<span v-for="letter in code.split('')">{{ letter }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-10 flex items-center justify-center gap-x-6">
|
||||
<NuxtLink href="/auth" class="text-sm font-semibold text-blue-600"
|
||||
><span aria-hidden="true">←</span> Use a different method
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
const code = await invoke<string>("auth_initiate_code");
|
||||
|
||||
definePageMeta({
|
||||
layout: "mini",
|
||||
});
|
||||
</script>
|
||||
@@ -76,7 +76,7 @@
|
||||
<TransitionGroup name="slide" tag="div" class="h-full">
|
||||
<img
|
||||
v-for="(url, index) in mediaUrls"
|
||||
:key="url"
|
||||
:key="index"
|
||||
:src="url"
|
||||
class="absolute inset-0 w-full h-full object-cover"
|
||||
v-show="index === currentImageIndex"
|
||||
@@ -157,8 +157,8 @@
|
||||
<template #default>
|
||||
<div class="sm:flex sm:items-start">
|
||||
<div class="mt-3 text-center sm:mt-0 sm:text-left">
|
||||
<h3 class="text-base font-semibold text-zinc-100"
|
||||
>Install {{ game.mName }}?
|
||||
<h3 class="text-base font-semibold text-zinc-100">
|
||||
Install {{ game.mName }}?
|
||||
</h3>
|
||||
<div class="mt-2">
|
||||
<p class="text-sm text-zinc-400">
|
||||
@@ -350,9 +350,7 @@
|
||||
<template #buttons>
|
||||
<LoadingButton
|
||||
@click="() => install()"
|
||||
:disabled="
|
||||
!(versionOptions && versionOptions.length > 0)
|
||||
"
|
||||
:disabled="!(versionOptions && versionOptions.length > 0)"
|
||||
:loading="installLoading"
|
||||
type="submit"
|
||||
class="ml-2 w-full sm:w-fit"
|
||||
@@ -370,7 +368,11 @@
|
||||
</template>
|
||||
</ModalTemplate>
|
||||
|
||||
<GameOptionsModal v-if="status.type === GameStatusEnum.Installed" v-model="configureModalOpen" :game-id="game.id" />
|
||||
<GameOptionsModal
|
||||
v-if="status.type === GameStatusEnum.Installed"
|
||||
v-model="configureModalOpen"
|
||||
:game-id="game.id"
|
||||
/>
|
||||
|
||||
<Transition
|
||||
enter="transition ease-out duration-300"
|
||||
@@ -420,7 +422,7 @@
|
||||
<img
|
||||
v-for="(url, index) in mediaUrls"
|
||||
v-show="currentImageIndex === index"
|
||||
:key="url"
|
||||
:key="index"
|
||||
:src="url"
|
||||
class="max-h-[90vh] max-w-[90vw] object-contain"
|
||||
:alt="`${game.mName} screenshot ${index + 1}`"
|
||||
@@ -441,10 +443,6 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
TransitionChild,
|
||||
TransitionRoot,
|
||||
Listbox,
|
||||
ListboxButton,
|
||||
ListboxLabel,
|
||||
@@ -482,7 +480,10 @@ const bannerUrl = await useObject(game.value.mBannerObjectId);
|
||||
|
||||
// Get all available images
|
||||
const mediaUrls = await Promise.all(
|
||||
game.value.mImageCarouselObjectIds.map((id) => useObject(id))
|
||||
game.value.mImageCarouselObjectIds.map(async (v) => {
|
||||
const src = await useObject(v);
|
||||
return src;
|
||||
})
|
||||
);
|
||||
|
||||
const htmlDescription = micromark(game.value.mDescription);
|
||||
@@ -496,7 +497,6 @@ const currentImageIndex = ref(0);
|
||||
|
||||
const configureModalOpen = ref(false);
|
||||
|
||||
|
||||
async function installFlow() {
|
||||
installFlowOpen.value = true;
|
||||
versionOptions.value = undefined;
|
||||
@@ -535,12 +535,11 @@ async function install() {
|
||||
}
|
||||
|
||||
async function resumeDownload() {
|
||||
try {
|
||||
await invoke("resume_download", { gameId: game.value.id })
|
||||
}
|
||||
catch(e) {
|
||||
console.error(e)
|
||||
}
|
||||
try {
|
||||
await invoke("resume_download", { gameId: game.value.id });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function launch() {
|
||||
|
||||
56
src-tauri/Cargo.lock
generated
56
src-tauri/Cargo.lock
generated
@@ -345,6 +345,22 @@ dependencies = [
|
||||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-tungstenite"
|
||||
version = "0.29.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef0f7efedeac57d9b26170f72965ecfd31473ca52ca7a64e925b0b6f5f079886"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tungstenite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atk"
|
||||
version = "0.18.2"
|
||||
@@ -1268,7 +1284,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "drop-app"
|
||||
version = "0.3.0-rc-8"
|
||||
version = "0.3.1-mac"
|
||||
dependencies = [
|
||||
"atomic-instant-full",
|
||||
"bitcode",
|
||||
@@ -1280,6 +1296,7 @@ dependencies = [
|
||||
"droplet-rs",
|
||||
"dynfmt",
|
||||
"filetime",
|
||||
"futures-lite",
|
||||
"gethostname",
|
||||
"hex 0.4.3",
|
||||
"http 1.3.1",
|
||||
@@ -1296,6 +1313,7 @@ dependencies = [
|
||||
"reqwest 0.12.16",
|
||||
"reqwest-middleware 0.4.2",
|
||||
"reqwest-middleware-cache",
|
||||
"reqwest-websocket",
|
||||
"rustbreak",
|
||||
"rustix 0.38.44",
|
||||
"schemars",
|
||||
@@ -4357,6 +4375,24 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest-websocket"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f91a811daaa8b54faeaec9d507a336897a3d243834a4965254a17d39da8b5c9"
|
||||
dependencies = [
|
||||
"async-tungstenite",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"reqwest 0.12.16",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"tungstenite",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rfd"
|
||||
version = "0.15.3"
|
||||
@@ -5831,6 +5867,7 @@ checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
@@ -6009,6 +6046,23 @@ version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"data-encoding",
|
||||
"http 1.3.1",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand 0.9.1",
|
||||
"sha1",
|
||||
"thiserror 2.0.12",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typeid"
|
||||
version = "1.0.3"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "drop-app"
|
||||
version = "0.3.0-rc-8"
|
||||
version = "0.3.1-mac"
|
||||
description = "The client application for the open-source, self-hosted game distribution platform Drop"
|
||||
authors = ["Drop OSS"]
|
||||
edition = "2024"
|
||||
@@ -68,6 +68,8 @@ known-folders = "1.2.0"
|
||||
native_model = { version = "0.6.1", features = ["rmp_serde_1_3"] }
|
||||
tauri-plugin-opener = "2.4.0"
|
||||
bitcode = "0.6.6"
|
||||
reqwest-websocket = "0.5.0"
|
||||
futures-lite = "2.6.0"
|
||||
# tailscale = { path = "./tailscale" }
|
||||
|
||||
[dependencies.dynfmt]
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
tauri_build::build();
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ pub fn cleanup_and_exit(app: &AppHandle, state: &tauri::State<'_, std::sync::Mut
|
||||
let download_manager = state.lock().unwrap().download_manager.clone();
|
||||
match download_manager.ensure_terminated() {
|
||||
Ok(res) => match res {
|
||||
Ok(_) => debug!("download manager terminated correctly"),
|
||||
Err(_) => error!("download manager failed to terminate correctly"),
|
||||
Ok(()) => debug!("download manager terminated correctly"),
|
||||
Err(()) => error!("download manager failed to terminate correctly"),
|
||||
},
|
||||
Err(e) => panic!("{e:?}"),
|
||||
}
|
||||
|
||||
@@ -67,20 +67,17 @@ impl DatabaseImpls for DatabaseInterface {
|
||||
|
||||
let exists = fs::exists(db_path.clone()).unwrap();
|
||||
|
||||
match exists {
|
||||
true => match PathDatabase::load_from_path(db_path.clone()) {
|
||||
Ok(db) => db,
|
||||
Err(e) => handle_invalid_database(e, db_path, games_base_dir, cache_dir),
|
||||
},
|
||||
false => {
|
||||
let default = Database::new(games_base_dir, None, cache_dir);
|
||||
debug!(
|
||||
"Creating database at path {}",
|
||||
db_path.as_os_str().to_str().unwrap()
|
||||
);
|
||||
PathDatabase::create_at_path(db_path, default)
|
||||
.expect("Database could not be created")
|
||||
}
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,19 +134,19 @@ impl<'a> Deref for DBRead<'a> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
impl<'a> DerefMut for DBWrite<'a> {
|
||||
impl DerefMut for DBWrite<'_> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
impl<'a> Drop for DBWrite<'a> {
|
||||
impl Drop for DBWrite<'_> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
ManuallyDrop::drop(&mut self.0);
|
||||
}
|
||||
|
||||
match DB.save() {
|
||||
Ok(_) => {}
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
error!("database failed to save with error {e}");
|
||||
panic!("database failed to save with error {e}")
|
||||
|
||||
@@ -27,7 +27,7 @@ pub mod data {
|
||||
use serde_with::serde_as;
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use super::*;
|
||||
use super::{Serialize, Deserialize, native_model};
|
||||
|
||||
fn default_template() -> String {
|
||||
"{}".to_owned()
|
||||
@@ -174,7 +174,7 @@ pub mod data {
|
||||
|
||||
use serde_with::serde_as;
|
||||
|
||||
use super::*;
|
||||
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)]
|
||||
@@ -283,7 +283,7 @@ pub mod data {
|
||||
mod v3 {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::*;
|
||||
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 {
|
||||
@@ -336,7 +336,7 @@ pub mod data {
|
||||
transient_statuses: HashMap::new(),
|
||||
},
|
||||
prev_database,
|
||||
base_url: "".to_owned(),
|
||||
base_url: String::new(),
|
||||
auth: None,
|
||||
settings: Settings::default(),
|
||||
cache_dir,
|
||||
|
||||
@@ -4,12 +4,12 @@ use crate::{database::models::data::DownloadableMetadata, AppState};
|
||||
|
||||
#[tauri::command]
|
||||
pub fn pause_downloads(state: tauri::State<'_, Mutex<AppState>>) {
|
||||
state.lock().unwrap().download_manager.pause_downloads()
|
||||
state.lock().unwrap().download_manager.pause_downloads();
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn resume_downloads(state: tauri::State<'_, Mutex<AppState>>) {
|
||||
state.lock().unwrap().download_manager.resume_downloads()
|
||||
state.lock().unwrap().download_manager.resume_downloads();
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -22,10 +22,10 @@ pub fn move_download_in_queue(
|
||||
.lock()
|
||||
.unwrap()
|
||||
.download_manager
|
||||
.rearrange(old_index, new_index)
|
||||
.rearrange(old_index, new_index);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn cancel_game(state: tauri::State<'_, Mutex<AppState>>, meta: DownloadableMetadata) {
|
||||
state.lock().unwrap().download_manager.cancel(meta)
|
||||
state.lock().unwrap().download_manager.cancel(meta);
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ impl DownloadManagerBuilder {
|
||||
DownloadManagerSignal::Cancel(meta) => {
|
||||
self.manage_cancel_signal(&meta);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
fn manage_queue_signal(&mut self, download_agent: DownloadAgent) {
|
||||
@@ -242,43 +242,47 @@ impl DownloadManagerBuilder {
|
||||
let app_handle = self.app_handle.clone();
|
||||
|
||||
*download_thread_lock = Some(spawn(move || {
|
||||
match download_agent.download(&app_handle) {
|
||||
// Ok(true) is for completed and exited properly
|
||||
Ok(true) => {
|
||||
debug!("download {:?} has completed", download_agent.metadata());
|
||||
match download_agent.validate() {
|
||||
Ok(true) => {
|
||||
download_agent.on_complete(&app_handle);
|
||||
sender
|
||||
.send(DownloadManagerSignal::Completed(download_agent.metadata()))
|
||||
.unwrap();
|
||||
}
|
||||
Ok(false) => {
|
||||
download_agent.on_incomplete(&app_handle);
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
"download {:?} has validation error {}",
|
||||
download_agent.metadata(),
|
||||
&e
|
||||
);
|
||||
download_agent.on_error(&app_handle, &e);
|
||||
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
||||
}
|
||||
loop {
|
||||
let download_result = match download_agent.download(&app_handle) {
|
||||
// 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);
|
||||
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Ok(false) is for incomplete but exited properly
|
||||
Ok(false) => {
|
||||
debug!("Donwload agent finished incomplete");
|
||||
};
|
||||
|
||||
// If the download gets cancel
|
||||
if !download_result {
|
||||
download_agent.on_incomplete(&app_handle);
|
||||
return;
|
||||
}
|
||||
Err(e) => {
|
||||
error!("download {:?} has error {}", download_agent.metadata(), &e);
|
||||
download_agent.on_error(&app_handle, &e);
|
||||
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
||||
|
||||
let validate_result = match download_agent.validate() {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"download {:?} has validation error {}",
|
||||
download_agent.metadata(),
|
||||
&e
|
||||
);
|
||||
download_agent.on_error(&app_handle, &e);
|
||||
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if validate_result {
|
||||
download_agent.on_complete(&app_handle);
|
||||
sender
|
||||
.send(DownloadManagerSignal::Completed(download_agent.metadata()))
|
||||
.unwrap();
|
||||
sender.send(DownloadManagerSignal::UpdateUIQueue).unwrap();
|
||||
return;
|
||||
}
|
||||
}
|
||||
sender.send(DownloadManagerSignal::UpdateUIQueue).unwrap();
|
||||
}));
|
||||
|
||||
self.set_status(DownloadManagerStatus::Downloading);
|
||||
|
||||
@@ -23,13 +23,13 @@ use super::{
|
||||
};
|
||||
|
||||
pub enum DownloadManagerSignal {
|
||||
/// Resumes (or starts) the DownloadManager
|
||||
/// Resumes (or starts) the `DownloadManager`
|
||||
Go,
|
||||
/// Pauses the DownloadManager
|
||||
/// Pauses the `DownloadManager`
|
||||
Stop,
|
||||
/// Called when a DownloadAgent has fully completed a download.
|
||||
/// Called when a `DownloadAgent` has fully completed a download.
|
||||
Completed(DownloadableMetadata),
|
||||
/// Generates and appends a DownloadAgent
|
||||
/// Generates and appends a `DownloadAgent`
|
||||
/// to the registry and queue
|
||||
Queue(DownloadAgent),
|
||||
/// Tells the Manager to stop the current
|
||||
@@ -70,14 +70,14 @@ pub enum DownloadStatus {
|
||||
Error,
|
||||
}
|
||||
|
||||
/// Accessible front-end for the DownloadManager
|
||||
/// Accessible front-end for the `DownloadManager`
|
||||
///
|
||||
/// The system works entirely through signals, both internally and externally,
|
||||
/// all of which are accessible through the DownloadManagerSignal type, but
|
||||
/// all of which are accessible through the `DownloadManagerSignal` type, but
|
||||
/// should not be used directly. Rather, signals are abstracted through this
|
||||
/// interface.
|
||||
///
|
||||
/// The actual download queue may be accessed through the .edit() function,
|
||||
/// The actual download queue may be accessed through the .`edit()` function,
|
||||
/// which provides raw access to the underlying queue.
|
||||
/// THIS EDITING IS BLOCKING!!!
|
||||
pub struct DownloadManager {
|
||||
@@ -139,7 +139,7 @@ impl DownloadManager {
|
||||
pub fn rearrange(&self, current_index: usize, new_index: usize) {
|
||||
if current_index == new_index {
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
let needs_pause = current_index == 0 || new_index == 0;
|
||||
if needs_pause {
|
||||
@@ -183,7 +183,7 @@ impl DownloadManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// Takes in the locked value from .edit() and attempts to
|
||||
/// Takes in the locked value from .`edit()` and attempts to
|
||||
/// get the index of whatever id is passed in
|
||||
fn get_index_from_id(
|
||||
queue: &mut MutexGuard<'_, VecDeque<DownloadableMetadata>>,
|
||||
|
||||
@@ -22,10 +22,7 @@ impl From<DownloadThreadControlFlag> for bool {
|
||||
/// false => Stop
|
||||
impl From<bool> for DownloadThreadControlFlag {
|
||||
fn from(value: bool) -> Self {
|
||||
match value {
|
||||
true => DownloadThreadControlFlag::Go,
|
||||
false => DownloadThreadControlFlag::Stop,
|
||||
}
|
||||
if value { DownloadThreadControlFlag::Go } else { DownloadThreadControlFlag::Stop }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ pub struct RollingProgressWindow<const S: usize> {
|
||||
impl<const S: usize> RollingProgressWindow<S> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
window: Arc::new([(); S].map(|_| AtomicUsize::new(0))),
|
||||
window: Arc::new([(); S].map(|()| AtomicUsize::new(0))),
|
||||
current: Arc::new(AtomicUsize::new(0)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ use serde_with::SerializeDisplay;
|
||||
|
||||
#[derive(SerializeDisplay)]
|
||||
pub enum ProcessError {
|
||||
SetupRequired,
|
||||
NotInstalled,
|
||||
AlreadyRunning,
|
||||
NotDownloaded,
|
||||
@@ -19,7 +18,6 @@ pub enum ProcessError {
|
||||
impl Display for ProcessError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let s = match self {
|
||||
ProcessError::SetupRequired => "Game not set up",
|
||||
ProcessError::NotInstalled => "Game not installed",
|
||||
ProcessError::AlreadyRunning => "Game already running",
|
||||
ProcessError::NotDownloaded => "Game not downloaded",
|
||||
|
||||
@@ -13,6 +13,7 @@ use super::drop_server_error::DropServerError;
|
||||
#[derive(Debug, SerializeDisplay)]
|
||||
pub enum RemoteAccessError {
|
||||
FetchError(Arc<reqwest::Error>),
|
||||
FetchErrorWS(Arc<reqwest_websocket::Error>),
|
||||
ParsingError(ParseError),
|
||||
InvalidEndpoint,
|
||||
HandshakeFailed(String),
|
||||
@@ -21,7 +22,7 @@ pub enum RemoteAccessError {
|
||||
UnparseableResponse(String),
|
||||
ManifestDownloadFailed(StatusCode, String),
|
||||
OutOfSync,
|
||||
Cache(cacache::Error),
|
||||
Cache(std::io::Error),
|
||||
}
|
||||
|
||||
impl Display for RemoteAccessError {
|
||||
@@ -29,7 +30,10 @@ impl Display for RemoteAccessError {
|
||||
match self {
|
||||
RemoteAccessError::FetchError(error) => {
|
||||
if error.is_connect() {
|
||||
return write!(f, "Failed to connect to Drop server. Check if you access Drop through a browser, and then try again.");
|
||||
return write!(
|
||||
f,
|
||||
"Failed to connect to Drop server. Check if you access Drop through a browser, and then try again."
|
||||
);
|
||||
}
|
||||
|
||||
write!(
|
||||
@@ -38,24 +42,44 @@ impl Display for RemoteAccessError {
|
||||
error,
|
||||
error
|
||||
.source()
|
||||
.map(|e| e.to_string())
|
||||
.map(std::string::ToString::to_string)
|
||||
.or_else(|| Some("Unknown error".to_string()))
|
||||
.unwrap()
|
||||
)
|
||||
},
|
||||
}
|
||||
RemoteAccessError::FetchErrorWS(error) => write!(
|
||||
f,
|
||||
"{}: {}",
|
||||
error,
|
||||
error
|
||||
.source()
|
||||
.map(|e| e.to_string())
|
||||
.or_else(|| Some("Unknown error".to_string()))
|
||||
.unwrap()
|
||||
),
|
||||
RemoteAccessError::ParsingError(parse_error) => {
|
||||
write!(f, "{parse_error}")
|
||||
}
|
||||
RemoteAccessError::InvalidEndpoint => write!(f, "invalid drop endpoint"),
|
||||
RemoteAccessError::HandshakeFailed(message) => write!(f, "failed to complete handshake: {message}"),
|
||||
RemoteAccessError::HandshakeFailed(message) => {
|
||||
write!(f, "failed to complete handshake: {message}")
|
||||
}
|
||||
RemoteAccessError::GameNotFound(id) => write!(f, "could not find game on server: {id}"),
|
||||
RemoteAccessError::InvalidResponse(error) => write!(f, "server returned an invalid response: {}, {}", error.status_code, error.status_message),
|
||||
RemoteAccessError::UnparseableResponse(error) => write!(f, "server returned an invalid response: {error}"),
|
||||
RemoteAccessError::ManifestDownloadFailed(status, response) => write!(
|
||||
RemoteAccessError::InvalidResponse(error) => write!(
|
||||
f,
|
||||
"failed to download game manifest: {status} {response}"
|
||||
"server returned an invalid response: {}, {}",
|
||||
error.status_code, error.status_message
|
||||
),
|
||||
RemoteAccessError::UnparseableResponse(error) => {
|
||||
write!(f, "server returned an invalid response: {error}")
|
||||
}
|
||||
RemoteAccessError::ManifestDownloadFailed(status, response) => {
|
||||
write!(f, "failed to download game manifest: {status} {response}")
|
||||
}
|
||||
RemoteAccessError::OutOfSync => write!(
|
||||
f,
|
||||
"server's and client's time are out of sync. Please ensure they are within at least 30 seconds of each other"
|
||||
),
|
||||
RemoteAccessError::OutOfSync => write!(f, "server's and client's time are out of sync. Please ensure they are within at least 30 seconds of each other"),
|
||||
RemoteAccessError::Cache(error) => write!(f, "Cache Error: {error}"),
|
||||
}
|
||||
}
|
||||
@@ -66,6 +90,11 @@ impl From<reqwest::Error> for RemoteAccessError {
|
||||
RemoteAccessError::FetchError(Arc::new(err))
|
||||
}
|
||||
}
|
||||
impl From<reqwest_websocket::Error> for RemoteAccessError {
|
||||
fn from(err: reqwest_websocket::Error) -> Self {
|
||||
RemoteAccessError::FetchErrorWS(Arc::new(err))
|
||||
}
|
||||
}
|
||||
impl From<ParseError> for RemoteAccessError {
|
||||
fn from(err: ParseError) -> Self {
|
||||
RemoteAccessError::ParsingError(err)
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
use reqwest::blocking::Client;
|
||||
use serde_json::json;
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
DB,
|
||||
database::db::DatabaseImpls,
|
||||
error::remote_access_error::RemoteAccessError,
|
||||
remote::{auth::generate_authorization_header, requests::make_request},
|
||||
DB,
|
||||
remote::{auth::generate_authorization_header, requests::make_request, utils::DROP_CLIENT_SYNC},
|
||||
};
|
||||
|
||||
use super::collection::{Collection, Collections};
|
||||
|
||||
#[tauri::command]
|
||||
pub fn fetch_collections() -> Result<Collections, RemoteAccessError> {
|
||||
let client = Client::new();
|
||||
let client = DROP_CLIENT_SYNC.clone();
|
||||
let response = make_request(&client, &["/api/v1/client/collection"], &[], |r| {
|
||||
r.header("Authorization", generate_authorization_header())
|
||||
})?
|
||||
@@ -24,7 +23,7 @@ pub fn fetch_collections() -> Result<Collections, RemoteAccessError> {
|
||||
|
||||
#[tauri::command]
|
||||
pub fn fetch_collection(collection_id: String) -> Result<Collection, RemoteAccessError> {
|
||||
let client = Client::new();
|
||||
let client = DROP_CLIENT_SYNC.clone();
|
||||
let response = make_request(
|
||||
&client,
|
||||
&["/api/v1/client/collection/", &collection_id],
|
||||
@@ -38,7 +37,7 @@ pub fn fetch_collection(collection_id: String) -> Result<Collection, RemoteAcces
|
||||
|
||||
#[tauri::command]
|
||||
pub fn create_collection(name: String) -> Result<Collection, RemoteAccessError> {
|
||||
let client = Client::new();
|
||||
let client = DROP_CLIENT_SYNC.clone();
|
||||
let base_url = DB.fetch_base_url();
|
||||
|
||||
let base_url = Url::parse(&format!("{base_url}api/v1/client/collection/"))?;
|
||||
@@ -57,7 +56,7 @@ pub fn add_game_to_collection(
|
||||
collection_id: String,
|
||||
game_id: String,
|
||||
) -> Result<(), RemoteAccessError> {
|
||||
let client = Client::new();
|
||||
let client = DROP_CLIENT_SYNC.clone();
|
||||
let url = Url::parse(&format!(
|
||||
"{}api/v1/client/collection/{}/entry/",
|
||||
DB.fetch_base_url(),
|
||||
@@ -74,7 +73,7 @@ pub fn add_game_to_collection(
|
||||
|
||||
#[tauri::command]
|
||||
pub fn delete_collection(collection_id: String) -> Result<bool, RemoteAccessError> {
|
||||
let client = Client::new();
|
||||
let client = DROP_CLIENT_SYNC.clone();
|
||||
let base_url = Url::parse(&format!(
|
||||
"{}api/v1/client/collection/{}",
|
||||
DB.fetch_base_url(),
|
||||
@@ -93,7 +92,7 @@ pub fn delete_game_in_collection(
|
||||
collection_id: String,
|
||||
game_id: String,
|
||||
) -> Result<(), RemoteAccessError> {
|
||||
let client = Client::new();
|
||||
let client = DROP_CLIENT_SYNC.clone();
|
||||
let base_url = Url::parse(&format!(
|
||||
"{}api/v1/client/collection/{}/entry",
|
||||
DB.fetch_base_url(),
|
||||
|
||||
@@ -15,10 +15,11 @@ use crate::games::downloads::manifest::{DropDownloadContext, DropManifest};
|
||||
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};
|
||||
use rayon::ThreadPoolBuilder;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::{create_dir_all, OpenOptions};
|
||||
use std::fs::{OpenOptions, create_dir_all};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::sync::{Arc, Mutex};
|
||||
@@ -26,7 +27,7 @@ use std::time::Instant;
|
||||
use tauri::{AppHandle, Emitter};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use rustix::fs::{fallocate, FallocateFlags};
|
||||
use rustix::fs::{FallocateFlags, fallocate};
|
||||
|
||||
use super::download_logic::download_game_chunk;
|
||||
use super::drop_data::DropData;
|
||||
@@ -115,7 +116,7 @@ impl GameDownloadAgent {
|
||||
);
|
||||
let res = self
|
||||
.run()
|
||||
.map_err(|_| ApplicationDownloadError::DownloadError);
|
||||
.map_err(|()| ApplicationDownloadError::DownloadError);
|
||||
|
||||
debug!(
|
||||
"{} took {}ms to download",
|
||||
@@ -135,7 +136,7 @@ impl GameDownloadAgent {
|
||||
|
||||
fn download_manifest(&self) -> Result<(), ApplicationDownloadError> {
|
||||
let header = generate_authorization_header();
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let client = DROP_CLIENT_SYNC.clone();
|
||||
let response = make_request(
|
||||
&client,
|
||||
&["/api/v1/client/game/manifest"],
|
||||
@@ -187,10 +188,7 @@ impl GameDownloadAgent {
|
||||
self.generate_contexts()?;
|
||||
}
|
||||
|
||||
self.context_map
|
||||
.lock()
|
||||
.unwrap()
|
||||
.extend(self.stored_manifest.get_contexts());
|
||||
*self.context_map.lock().unwrap() = self.stored_manifest.get_contexts();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -270,7 +268,7 @@ impl GameDownloadAgent {
|
||||
let contexts = self.contexts.lock().unwrap();
|
||||
debug!("{contexts:#?}");
|
||||
pool.scope(|scope| {
|
||||
let client = &reqwest::blocking::Client::new();
|
||||
let client = &DROP_CLIENT_SYNC.clone();
|
||||
let context_map = self.context_map.lock().unwrap();
|
||||
for (index, context) in contexts.iter().enumerate() {
|
||||
let client = client.clone();
|
||||
@@ -325,7 +323,7 @@ impl GameDownloadAgent {
|
||||
}
|
||||
});
|
||||
|
||||
let newly_completed = completed_contexts.to_owned();
|
||||
let newly_completed = completed_contexts.clone();
|
||||
|
||||
let completed_lock_len = {
|
||||
let mut context_map_lock = self.context_map.lock().unwrap();
|
||||
@@ -341,7 +339,7 @@ impl GameDownloadAgent {
|
||||
.map(|x| {
|
||||
(
|
||||
x.checksum.clone(),
|
||||
context_map_lock.get(&x.checksum).cloned().unwrap_or(false),
|
||||
context_map_lock.get(&x.checksum).copied().unwrap_or(false),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<(String, bool)>>();
|
||||
@@ -423,6 +421,7 @@ impl Downloadable for GameDownloadAgent {
|
||||
app_handle,
|
||||
)
|
||||
.unwrap();
|
||||
println!("Attempting to redownload");
|
||||
}
|
||||
|
||||
fn on_cancelled(&self, _app_handle: &tauri::AppHandle) {}
|
||||
|
||||
@@ -27,8 +27,9 @@ pub struct DropWriter<W: Write> {
|
||||
}
|
||||
impl DropWriter<File> {
|
||||
fn new(path: PathBuf) -> Self {
|
||||
let destination = OpenOptions::new().write(true).create(true).truncate(false).open(&path).unwrap();
|
||||
Self {
|
||||
destination: OpenOptions::new().write(true).open(path).unwrap(),
|
||||
destination,
|
||||
hasher: Context::new(),
|
||||
}
|
||||
}
|
||||
@@ -150,7 +151,7 @@ pub fn download_game_chunk(
|
||||
return Err(ApplicationDownloadError::Communication(
|
||||
RemoteAccessError::InvalidResponse(err),
|
||||
));
|
||||
};
|
||||
}
|
||||
return Err(ApplicationDownloadError::Communication(
|
||||
RemoteAccessError::UnparseableResponse(raw_res),
|
||||
));
|
||||
@@ -186,7 +187,7 @@ pub fn download_game_chunk(
|
||||
.map_err(|e| ApplicationDownloadError::IoError(e.kind()))?;
|
||||
if !completed {
|
||||
return Ok(false);
|
||||
};
|
||||
}
|
||||
|
||||
// If we complete the file, set the permissions (if on Linux)
|
||||
#[cfg(unix)]
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{Read, Write},
|
||||
path::PathBuf,
|
||||
collections::HashMap, fs::File, io::{Read, Write}, path::PathBuf
|
||||
};
|
||||
|
||||
use log::{debug, error, info, warn};
|
||||
@@ -12,7 +10,7 @@ pub type DropData = v1::DropData;
|
||||
static DROP_DATA_PATH: &str = ".dropdata";
|
||||
|
||||
pub mod v1 {
|
||||
use std::{path::PathBuf, sync::Mutex};
|
||||
use std::{collections::HashMap, path::PathBuf, sync::Mutex};
|
||||
|
||||
use native_model::native_model;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -22,7 +20,7 @@ pub mod v1 {
|
||||
pub struct DropData {
|
||||
pub game_id: String,
|
||||
pub game_version: String,
|
||||
pub contexts: Mutex<Vec<(String, bool)>>,
|
||||
pub contexts: Mutex<HashMap<String, bool>>,
|
||||
pub base_path: PathBuf,
|
||||
}
|
||||
|
||||
@@ -32,7 +30,7 @@ pub mod v1 {
|
||||
base_path,
|
||||
game_id,
|
||||
game_version,
|
||||
contexts: Mutex::new(Vec::new()),
|
||||
contexts: Mutex::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,12 +38,9 @@ pub mod v1 {
|
||||
|
||||
impl DropData {
|
||||
pub fn generate(game_id: String, game_version: String, base_path: PathBuf) -> Self {
|
||||
let mut file = match File::open(base_path.join(DROP_DATA_PATH)) {
|
||||
Ok(file) => file,
|
||||
Err(_) => {
|
||||
debug!("Generating new dropdata for game {game_id}");
|
||||
return DropData::new(game_id, game_version, base_path);
|
||||
}
|
||||
let mut file = if let Ok(file) = File::open(base_path.join(DROP_DATA_PATH)) { file } else {
|
||||
debug!("Generating new dropdata for game {game_id}");
|
||||
return DropData::new(game_id, game_version, base_path);
|
||||
};
|
||||
|
||||
let mut s = Vec::new();
|
||||
@@ -55,7 +50,7 @@ impl DropData {
|
||||
error!("{e}");
|
||||
return DropData::new(game_id, game_version, base_path);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
match native_model::rmp_serde_1_3::RmpSerde::decode(s) {
|
||||
Ok(manifest) => manifest,
|
||||
@@ -80,25 +75,28 @@ impl DropData {
|
||||
};
|
||||
|
||||
match file.write_all(&manifest_raw) {
|
||||
Ok(_) => {}
|
||||
Ok(()) => {}
|
||||
Err(e) => error!("{e}"),
|
||||
};
|
||||
}
|
||||
}
|
||||
pub fn set_contexts(&self, completed_contexts: &[(String, bool)]) {
|
||||
*self.contexts.lock().unwrap() = completed_contexts.to_owned();
|
||||
*self.contexts.lock().unwrap() = completed_contexts.iter().map(|s| (s.0.clone(), s.1)).collect();
|
||||
}
|
||||
pub fn set_context(&self, context: String, state: bool) {
|
||||
self.contexts.lock().unwrap().entry(context).insert_entry(state);
|
||||
}
|
||||
pub fn get_completed_contexts(&self) -> Vec<String> {
|
||||
self.contexts
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter_map(|x| if x.1 { Some(x.0.clone()) } else { None })
|
||||
.filter_map(|x| if *x.1 { Some(x.0.clone()) } else { None })
|
||||
.collect()
|
||||
}
|
||||
pub fn get_contexts(&self) -> Vec<(String, bool)> {
|
||||
pub fn get_contexts(&self) -> HashMap<String, bool> {
|
||||
info!(
|
||||
"Any contexts which are complete? {}",
|
||||
self.contexts.lock().unwrap().iter().any(|x| x.1)
|
||||
self.contexts.lock().unwrap().iter().any(|x| *x.1)
|
||||
);
|
||||
self.contexts.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{self, BufWriter, Read, Seek, SeekFrom, Write},
|
||||
sync::{mpsc::Sender, Arc},
|
||||
sync::{Arc, mpsc::Sender},
|
||||
};
|
||||
|
||||
use log::{debug, error, info};
|
||||
@@ -73,6 +73,7 @@ pub fn game_validate_logic(
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// If there are any contexts left which are false
|
||||
if !invalid_chunks.is_empty() {
|
||||
info!(
|
||||
@@ -80,6 +81,13 @@ pub fn game_validate_logic(
|
||||
dropdata.game_id.clone(),
|
||||
invalid_chunks
|
||||
);
|
||||
|
||||
for context in invalid_chunks.iter() {
|
||||
dropdata.set_context(context.1.clone(), false);
|
||||
}
|
||||
|
||||
dropdata.write();
|
||||
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
@@ -101,7 +109,9 @@ pub fn validate_game_chunk(
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let mut source = File::open(&ctx.path).unwrap();
|
||||
let Ok(mut source) = File::open(&ctx.path) else {
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
if ctx.offset != 0 {
|
||||
source
|
||||
@@ -115,7 +125,7 @@ pub fn validate_game_chunk(
|
||||
validate_copy(&mut source, &mut hasher, ctx.length, control_flag, progress).unwrap();
|
||||
if !completed {
|
||||
return Ok(false);
|
||||
};
|
||||
}
|
||||
|
||||
let res = hex::encode(hasher.compute().0);
|
||||
if res != ctx.checksum {
|
||||
|
||||
@@ -18,6 +18,7 @@ use crate::games::state::{GameStatusManager, GameStatusWithTransient};
|
||||
use crate::remote::auth::generate_authorization_header;
|
||||
use crate::remote::cache::{cache_object, get_cached_object, get_cached_object_db};
|
||||
use crate::remote::requests::make_request;
|
||||
use crate::remote::utils::DROP_CLIENT_SYNC;
|
||||
use crate::AppState;
|
||||
use bitcode::{Encode, Decode};
|
||||
|
||||
@@ -78,7 +79,7 @@ pub fn fetch_library_logic(
|
||||
) -> Result<Vec<Game>, RemoteAccessError> {
|
||||
let header = generate_authorization_header();
|
||||
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let client = DROP_CLIENT_SYNC.clone();
|
||||
let response = make_request(&client, &["/api/v1/client/user/library"], &[], |f| {
|
||||
f.header("Authorization", header)
|
||||
})?
|
||||
@@ -96,7 +97,7 @@ pub fn fetch_library_logic(
|
||||
|
||||
let mut db_handle = borrow_db_mut_checked();
|
||||
|
||||
for game in games.iter() {
|
||||
for game in &games {
|
||||
handle.games.insert(game.id.clone(), game.clone());
|
||||
if !db_handle.applications.game_statuses.contains_key(&game.id) {
|
||||
db_handle
|
||||
@@ -113,7 +114,7 @@ pub fn fetch_library_logic(
|
||||
}
|
||||
// We should always have a cache of the object
|
||||
// Pass db_handle because otherwise we get a gridlock
|
||||
let game = get_cached_object_db::<String, Game>(meta.id.clone(), &db_handle)?;
|
||||
let game = get_cached_object_db::<Game>(&meta.id.clone(), &db_handle)?;
|
||||
games.push(game);
|
||||
}
|
||||
|
||||
@@ -173,11 +174,11 @@ pub fn fetch_game_logic(
|
||||
version,
|
||||
};
|
||||
|
||||
cache_object(id, game)?;
|
||||
cache_object(&id, game)?;
|
||||
|
||||
return Ok(data);
|
||||
}
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let client = DROP_CLIENT_SYNC.clone();
|
||||
let response = make_request(&client, &["/api/v1/client/game/", &id], &[], |r| {
|
||||
r.header("Authorization", generate_authorization_header())
|
||||
})?
|
||||
@@ -212,7 +213,7 @@ pub fn fetch_game_logic(
|
||||
version,
|
||||
};
|
||||
|
||||
cache_object(id, &game)?;
|
||||
cache_object(&id, &game)?;
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
@@ -239,7 +240,7 @@ pub fn fetch_game_logic_offline(
|
||||
drop(handle);
|
||||
|
||||
let status = GameStatusManager::fetch_state(&id);
|
||||
let game = get_cached_object::<String, Game>(id)?;
|
||||
let game = get_cached_object::<Game>(&id)?;
|
||||
|
||||
Ok(FetchGameStruct {
|
||||
game,
|
||||
@@ -252,7 +253,7 @@ pub fn fetch_game_verion_options_logic(
|
||||
game_id: String,
|
||||
state: tauri::State<'_, Mutex<AppState>>,
|
||||
) -> Result<Vec<GameVersion>, RemoteAccessError> {
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let client = DROP_CLIENT_SYNC.clone();
|
||||
|
||||
let response = make_request(
|
||||
&client,
|
||||
@@ -329,37 +330,34 @@ pub fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle)
|
||||
drop(db_handle);
|
||||
|
||||
let app_handle = app_handle.clone();
|
||||
spawn(move || match remove_dir_all(install_dir) {
|
||||
Err(e) => {
|
||||
error!("{e}");
|
||||
}
|
||||
Ok(_) => {
|
||||
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
|
||||
.entry(meta.id.clone())
|
||||
.and_modify(|e| *e = GameDownloadStatus::Remote {});
|
||||
drop(db_handle);
|
||||
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
|
||||
.entry(meta.id.clone())
|
||||
.and_modify(|e| *e = GameDownloadStatus::Remote {});
|
||||
drop(db_handle);
|
||||
|
||||
debug!("uninstalled game id {}", &meta.id);
|
||||
app_handle.emit("update_library", ()).unwrap();
|
||||
debug!("uninstalled game id {}", &meta.id);
|
||||
app_handle.emit("update_library", ()).unwrap();
|
||||
|
||||
push_game_update(
|
||||
&app_handle,
|
||||
&meta.id,
|
||||
None,
|
||||
(Some(GameDownloadStatus::Remote {}), None),
|
||||
);
|
||||
}
|
||||
push_game_update(
|
||||
&app_handle,
|
||||
&meta.id,
|
||||
None,
|
||||
(Some(GameDownloadStatus::Remote {}), None),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
warn!("invalid previous state for uninstall, failing silently.")
|
||||
warn!("invalid previous state for uninstall, failing silently.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,7 +379,7 @@ pub fn on_game_incomplete(
|
||||
return Err(RemoteAccessError::GameNotFound(meta.id.clone()));
|
||||
}
|
||||
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let client = DROP_CLIENT_SYNC.clone();
|
||||
let response = make_request(
|
||||
&client,
|
||||
&["/api/v1/client/game/version"],
|
||||
@@ -443,7 +441,7 @@ pub fn on_game_complete(
|
||||
|
||||
let header = generate_authorization_header();
|
||||
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let client = DROP_CLIENT_SYNC.clone();
|
||||
let response = make_request(
|
||||
&client,
|
||||
&["/api/v1/client/game/version"],
|
||||
|
||||
@@ -12,7 +12,9 @@ mod process;
|
||||
mod remote;
|
||||
|
||||
use crate::process::commands::open_process_logs;
|
||||
use crate::remote::commands::auth_initiate_code;
|
||||
use crate::{database::db::DatabaseImpls, games::downloads::commands::resume_download};
|
||||
use bitcode::{Decode, Encode};
|
||||
use client::commands::fetch_state;
|
||||
use client::{
|
||||
autostart::{get_autostart_enabled, sync_autostart_on_startup, toggle_autostart},
|
||||
@@ -22,7 +24,7 @@ use database::commands::{
|
||||
add_download_dir, delete_download_dir, fetch_download_dir_stats, fetch_settings,
|
||||
fetch_system_data, update_settings,
|
||||
};
|
||||
use database::db::{borrow_db_checked, borrow_db_mut_checked, DatabaseInterface, DATA_ROOT_DIR};
|
||||
use database::db::{DATA_ROOT_DIR, DatabaseInterface, borrow_db_checked, borrow_db_mut_checked};
|
||||
use database::models::data::GameDownloadStatus;
|
||||
use download_manager::commands::{
|
||||
cancel_game, move_download_in_queue, pause_downloads, resume_downloads,
|
||||
@@ -37,13 +39,13 @@ use games::commands::{
|
||||
fetch_game, fetch_game_status, fetch_game_verion_options, fetch_library, uninstall_game,
|
||||
};
|
||||
use games::downloads::commands::download_game;
|
||||
use games::library::{update_game_configuration, Game};
|
||||
use log::{debug, info, warn, LevelFilter};
|
||||
use games::library::{Game, update_game_configuration};
|
||||
use log::{LevelFilter, debug, info, warn};
|
||||
use log4rs::Config;
|
||||
use log4rs::append::console::ConsoleAppender;
|
||||
use log4rs::append::file::FileAppender;
|
||||
use log4rs::config::{Appender, Root};
|
||||
use log4rs::encode::pattern::PatternEncoder;
|
||||
use log4rs::Config;
|
||||
use process::commands::{kill_game, launch_game};
|
||||
use process::process_manager::ProcessManager;
|
||||
use remote::auth::{self, recieve_handshake};
|
||||
@@ -51,7 +53,7 @@ use remote::commands::{
|
||||
auth_initiate, fetch_drop_object, gen_drop_url, manual_recieve_handshake, retry_connect,
|
||||
sign_out, use_remote,
|
||||
};
|
||||
use remote::fetch_object::{fetch_object, fetch_object_offline};
|
||||
use remote::fetch_object::fetch_object;
|
||||
use remote::server_proto::{handle_server_proto, handle_server_proto_offline};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs::File;
|
||||
@@ -71,7 +73,6 @@ use tauri::tray::TrayIconBuilder;
|
||||
use tauri::{AppHandle, Manager, RunEvent, WindowEvent};
|
||||
use tauri_plugin_deep_link::DeepLinkExt;
|
||||
use tauri_plugin_dialog::DialogExt;
|
||||
use bitcode::{Encode, Decode};
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Eq, PartialEq)]
|
||||
pub enum AppStatus {
|
||||
@@ -163,7 +164,7 @@ fn setup(handle: AppHandle) -> AppState<'static> {
|
||||
let mut missing_games = Vec::new();
|
||||
let statuses = db_handle.applications.game_statuses.clone();
|
||||
drop(db_handle);
|
||||
for (game_id, status) in statuses.into_iter() {
|
||||
for (game_id, status) in statuses {
|
||||
match status {
|
||||
GameDownloadStatus::Remote {} => {}
|
||||
GameDownloadStatus::PartiallyInstalled { .. } => {}
|
||||
@@ -267,6 +268,7 @@ pub fn run() {
|
||||
fetch_settings,
|
||||
// Auth
|
||||
auth_initiate,
|
||||
auth_initiate_code,
|
||||
retry_connect,
|
||||
manual_recieve_handshake,
|
||||
sign_out,
|
||||
@@ -344,7 +346,7 @@ pub fn run() {
|
||||
let binding = event.urls();
|
||||
let url = binding.first().unwrap();
|
||||
if url.host_str().unwrap() == "handshake" {
|
||||
recieve_handshake(handle.clone(), url.path().to_string())
|
||||
recieve_handshake(handle.clone(), url.path().to_string());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -401,15 +403,10 @@ pub fn run() {
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.register_asynchronous_uri_scheme_protocol("object", move |ctx, request, responder| {
|
||||
let state: tauri::State<'_, Mutex<AppState>> = ctx.app_handle().state();
|
||||
offline!(
|
||||
state,
|
||||
fetch_object,
|
||||
fetch_object_offline,
|
||||
request,
|
||||
responder
|
||||
);
|
||||
.register_asynchronous_uri_scheme_protocol("object", move |_ctx, request, responder| {
|
||||
tauri::async_runtime::spawn(async move {
|
||||
fetch_object(request, responder).await;
|
||||
});
|
||||
})
|
||||
.register_asynchronous_uri_scheme_protocol("server", move |ctx, request, responder| {
|
||||
let state: tauri::State<'_, Mutex<AppState>> = ctx.app_handle().state();
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
fn main() {
|
||||
drop_app_lib::run()
|
||||
drop_app_lib::run();
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@ pub fn launch_game(
|
||||
//};
|
||||
|
||||
match process_manager_lock.launch_process(id) {
|
||||
Ok(_) => {}
|
||||
Ok(()) => {}
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
}
|
||||
|
||||
drop(process_manager_lock);
|
||||
drop(state_lock);
|
||||
|
||||
@@ -181,9 +181,7 @@ impl ProcessManager<'_> {
|
||||
.cloned()
|
||||
{
|
||||
Some(GameDownloadStatus::Installed { version_name, .. }) => version_name,
|
||||
Some(GameDownloadStatus::SetupRequired { .. }) => {
|
||||
return Err(ProcessError::SetupRequired);
|
||||
}
|
||||
Some(GameDownloadStatus::SetupRequired { version_name, .. }) => version_name,
|
||||
_ => return Err(ProcessError::NotInstalled),
|
||||
};
|
||||
let meta = DownloadableMetadata {
|
||||
@@ -283,7 +281,7 @@ impl ProcessManager<'_> {
|
||||
let launch_string = game_launcher.create_launch_process(
|
||||
&meta,
|
||||
launch.to_string(),
|
||||
args.to_vec(),
|
||||
args.clone(),
|
||||
game_version,
|
||||
install_dir,
|
||||
);
|
||||
|
||||
@@ -12,9 +12,7 @@ use crate::{
|
||||
database::{
|
||||
db::{borrow_db_checked, borrow_db_mut_checked},
|
||||
models::data::DatabaseAuth,
|
||||
},
|
||||
error::{drop_server_error::DropServerError, remote_access_error::RemoteAccessError},
|
||||
AppState, AppStatus, User,
|
||||
}, error::{drop_server_error::DropServerError, remote_access_error::RemoteAccessError}, remote::utils::DROP_CLIENT_SYNC, AppState, AppStatus, User
|
||||
};
|
||||
|
||||
use super::{
|
||||
@@ -32,6 +30,7 @@ struct InitiateRequestBody {
|
||||
name: String,
|
||||
platform: String,
|
||||
capabilities: HashMap<String, CapabilityConfiguration>,
|
||||
mode: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -65,7 +64,7 @@ pub fn generate_authorization_header() -> String {
|
||||
pub fn fetch_user() -> Result<User, RemoteAccessError> {
|
||||
let header = generate_authorization_header();
|
||||
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let client = DROP_CLIENT_SYNC.clone();
|
||||
let response = make_request(&client, &["/api/v1/client/user"], &[], |f| {
|
||||
f.header("Authorization", header)
|
||||
})?
|
||||
@@ -81,11 +80,11 @@ pub fn fetch_user() -> Result<User, RemoteAccessError> {
|
||||
return Err(RemoteAccessError::InvalidResponse(err));
|
||||
}
|
||||
|
||||
response.json::<User>().map_err(|e| e.into())
|
||||
response.json::<User>().map_err(std::convert::Into::into)
|
||||
}
|
||||
|
||||
fn recieve_handshake_logic(app: &AppHandle, path: String) -> Result<(), RemoteAccessError> {
|
||||
let path_chunks: Vec<&str> = path.split("/").collect();
|
||||
let path_chunks: Vec<&str> = path.split('/').collect();
|
||||
if path_chunks.len() != 3 {
|
||||
app.emit("auth/failed", ()).unwrap();
|
||||
return Err(RemoteAccessError::HandshakeFailed(
|
||||
@@ -101,12 +100,12 @@ fn recieve_handshake_logic(app: &AppHandle, path: String) -> Result<(), RemoteAc
|
||||
let client_id = path_chunks.get(1).unwrap();
|
||||
let token = path_chunks.get(2).unwrap();
|
||||
let body = HandshakeRequestBody {
|
||||
client_id: client_id.to_string(),
|
||||
token: token.to_string(),
|
||||
client_id: (*client_id).to_string(),
|
||||
token: (*token).to_string(),
|
||||
};
|
||||
|
||||
let endpoint = base_url.join("/api/v1/client/auth/handshake")?;
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let client = DROP_CLIENT_SYNC.clone();
|
||||
let response = client.post(endpoint).json(&body).send()?;
|
||||
debug!("handshake responsded with {}", response.status().as_u16());
|
||||
if !response.status().is_success() {
|
||||
@@ -166,7 +165,7 @@ pub fn recieve_handshake(app: AppHandle, path: String) {
|
||||
app.emit("auth/finished", ()).unwrap();
|
||||
}
|
||||
|
||||
pub fn auth_initiate_logic() -> Result<(), RemoteAccessError> {
|
||||
pub fn auth_initiate_logic(mode: String) -> Result<String, RemoteAccessError> {
|
||||
let base_url = {
|
||||
let db_lock = borrow_db_checked();
|
||||
Url::parse(&db_lock.base_url.clone())?
|
||||
@@ -182,9 +181,10 @@ pub fn auth_initiate_logic() -> Result<(), RemoteAccessError> {
|
||||
("peerAPI".to_owned(), CapabilityConfiguration {}),
|
||||
("cloudSaves".to_owned(), CapabilityConfiguration {}),
|
||||
]),
|
||||
mode,
|
||||
};
|
||||
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let client = DROP_CLIENT_SYNC.clone();
|
||||
let response = client.post(endpoint.to_string()).json(&body).send()?;
|
||||
|
||||
if response.status() != 200 {
|
||||
@@ -194,13 +194,9 @@ pub fn auth_initiate_logic() -> Result<(), RemoteAccessError> {
|
||||
return Err(RemoteAccessError::HandshakeFailed(data.status_message));
|
||||
}
|
||||
|
||||
let redir_url = response.text()?;
|
||||
let complete_redir_url = base_url.join(&redir_url)?;
|
||||
let response = response.text()?;
|
||||
|
||||
debug!("opening web browser to continue authentication");
|
||||
webbrowser::open(complete_redir_url.as_ref()).unwrap();
|
||||
|
||||
Ok(())
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub fn setup() -> (AppStatus, Option<User>) {
|
||||
@@ -212,7 +208,7 @@ pub fn setup() -> (AppStatus, Option<User>) {
|
||||
let user_result = match fetch_user() {
|
||||
Ok(data) => data,
|
||||
Err(RemoteAccessError::FetchError(_)) => {
|
||||
let user = get_cached_object::<_, User>("user").unwrap();
|
||||
let user = get_cached_object::<User>("user").unwrap();
|
||||
return (AppStatus::Offline, Some(user));
|
||||
}
|
||||
Err(_) => return (AppStatus::SignedInNeedsReauth, None),
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use std::{
|
||||
fmt::Display,
|
||||
time::{Duration, SystemTime},
|
||||
fs::File,
|
||||
io::{self, Write},
|
||||
path::{Path, PathBuf},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -8,8 +10,8 @@ use crate::{
|
||||
error::remote_access_error::RemoteAccessError,
|
||||
};
|
||||
use bitcode::{Decode, DecodeOwned, Encode};
|
||||
use cacache::Integrity;
|
||||
use http::{Response, header::CONTENT_TYPE, response::Builder as ResponseBuilder};
|
||||
use log::debug;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! offline {
|
||||
@@ -23,47 +25,67 @@ macro_rules! offline {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cache_object<K: AsRef<str>, D: Encode>(
|
||||
key: K,
|
||||
data: &D,
|
||||
) -> Result<Integrity, RemoteAccessError> {
|
||||
fn get_sys_time_in_secs() -> u64 {
|
||||
match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
|
||||
Ok(n) => n.as_secs(),
|
||||
Err(_) => panic!("SystemTime before UNIX EPOCH!"),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_cache_path(base: &Path, key: &str) -> PathBuf {
|
||||
let key_hash = hex::encode(md5::compute(key.as_bytes()).0);
|
||||
base.join(key_hash)
|
||||
}
|
||||
|
||||
fn write_sync(base: &Path, key: &str, data: Vec<u8>) -> io::Result<()> {
|
||||
let cache_path = get_cache_path(base, key);
|
||||
let mut file = File::create(cache_path)?;
|
||||
file.write_all(&data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_sync(base: &Path, key: &str) -> io::Result<Vec<u8>> {
|
||||
let cache_path = get_cache_path(base, key);
|
||||
let file = std::fs::read(cache_path)?;
|
||||
Ok(file)
|
||||
}
|
||||
|
||||
pub fn cache_object<D: Encode>(key: &str, data: &D) -> Result<(), RemoteAccessError> {
|
||||
let bytes = bitcode::encode(data);
|
||||
cacache::write_sync(&borrow_db_checked().cache_dir, key, bytes)
|
||||
.map_err(RemoteAccessError::Cache)
|
||||
write_sync(&borrow_db_checked().cache_dir, key, bytes).map_err(RemoteAccessError::Cache)
|
||||
}
|
||||
pub fn get_cached_object<K: AsRef<str> + Display, D: Encode + DecodeOwned>(
|
||||
key: K,
|
||||
) -> Result<D, RemoteAccessError> {
|
||||
get_cached_object_db::<K, D>(key, &borrow_db_checked())
|
||||
pub fn get_cached_object<D: Encode + DecodeOwned>(key: &str) -> Result<D, RemoteAccessError> {
|
||||
get_cached_object_db::<D>(key, &borrow_db_checked())
|
||||
}
|
||||
pub fn get_cached_object_db<K: AsRef<str> + Display, D: DecodeOwned>(
|
||||
key: K,
|
||||
pub fn get_cached_object_db<D: DecodeOwned>(
|
||||
key: &str,
|
||||
db: &Database,
|
||||
) -> Result<D, RemoteAccessError> {
|
||||
let bytes = cacache::read_sync(&db.cache_dir, &key).map_err(RemoteAccessError::Cache)?;
|
||||
let data = bitcode::decode::<D>(&bytes).map_err(|_| {
|
||||
RemoteAccessError::Cache(cacache::Error::EntryNotFound(
|
||||
db.cache_dir.clone(),
|
||||
key.to_string(),
|
||||
))
|
||||
})?;
|
||||
let start = SystemTime::now();
|
||||
let bytes = read_sync(&db.cache_dir, key).map_err(RemoteAccessError::Cache)?;
|
||||
let read = start.elapsed().unwrap();
|
||||
let data =
|
||||
bitcode::decode::<D>(&bytes).map_err(|e| RemoteAccessError::Cache(io::Error::other(e)))?;
|
||||
let decode = start.elapsed().unwrap();
|
||||
debug!(
|
||||
"cache object took: r:{}, d:{}, b:{}",
|
||||
read.as_millis(),
|
||||
read.abs_diff(decode).as_millis(),
|
||||
bytes.len()
|
||||
);
|
||||
Ok(data)
|
||||
}
|
||||
#[derive(Encode, Decode)]
|
||||
pub struct ObjectCache {
|
||||
content_type: String,
|
||||
body: Vec<u8>,
|
||||
expiry: u128,
|
||||
expiry: u64,
|
||||
}
|
||||
|
||||
impl ObjectCache {
|
||||
pub fn has_expired(&self) -> bool {
|
||||
let duration = Duration::from_millis(self.expiry.try_into().unwrap());
|
||||
SystemTime::UNIX_EPOCH
|
||||
.checked_add(duration)
|
||||
.unwrap()
|
||||
.elapsed()
|
||||
.is_err()
|
||||
let current = get_sys_time_in_secs();
|
||||
self.expiry < current
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,12 +100,7 @@ impl From<Response<Vec<u8>>> for ObjectCache {
|
||||
.unwrap()
|
||||
.to_owned(),
|
||||
body: value.body().clone(),
|
||||
expiry: SystemTime::now()
|
||||
.checked_add(Duration::from_days(1))
|
||||
.unwrap()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis(),
|
||||
expiry: get_sys_time_in_secs() + 60 * 60 * 24,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
use std::sync::Mutex;
|
||||
|
||||
use log::debug;
|
||||
use reqwest::blocking::Client;
|
||||
use futures_lite::StreamExt;
|
||||
use log::{debug, warn};
|
||||
use reqwest_websocket::{Message, RequestBuilderExt};
|
||||
use serde::Deserialize;
|
||||
use tauri::{AppHandle, Emitter, Manager};
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
database::db::{borrow_db_checked, borrow_db_mut_checked},
|
||||
error::remote_access_error::RemoteAccessError,
|
||||
remote::{auth::generate_authorization_header, requests::make_request},
|
||||
AppState, AppStatus,
|
||||
database::db::{borrow_db_checked, borrow_db_mut_checked}, error::remote_access_error::RemoteAccessError, remote::{auth::generate_authorization_header, requests::make_request, utils::DROP_CLIENT_SYNC}, AppState, AppStatus
|
||||
};
|
||||
|
||||
use super::{
|
||||
@@ -42,7 +41,7 @@ pub fn gen_drop_url(path: String) -> Result<String, RemoteAccessError> {
|
||||
#[tauri::command]
|
||||
pub fn fetch_drop_object(path: String) -> Result<Vec<u8>, RemoteAccessError> {
|
||||
let _drop_url = gen_drop_url(path.clone())?;
|
||||
let req = make_request(&Client::new(), &[&path], &[], |r| {
|
||||
let req = make_request(&DROP_CLIENT_SYNC, &[&path], &[], |r| {
|
||||
r.header("Authorization", generate_authorization_header())
|
||||
})?
|
||||
.send();
|
||||
@@ -55,7 +54,7 @@ pub fn fetch_drop_object(path: String) -> Result<Vec<u8>, RemoteAccessError> {
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("{e}");
|
||||
get_cached_object::<&str, Vec<u8>>(&path)
|
||||
get_cached_object::<Vec<u8>>(&path)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,7 +90,78 @@ pub fn retry_connect(state: tauri::State<'_, Mutex<AppState>>) {
|
||||
|
||||
#[tauri::command]
|
||||
pub fn auth_initiate() -> Result<(), RemoteAccessError> {
|
||||
auth_initiate_logic()
|
||||
let base_url = {
|
||||
let db_lock = borrow_db_checked();
|
||||
Url::parse(&db_lock.base_url.clone())?
|
||||
};
|
||||
|
||||
let redir_url = auth_initiate_logic("callback".to_string())?;
|
||||
let complete_redir_url = base_url.join(&redir_url)?;
|
||||
|
||||
debug!("opening web browser to continue authentication");
|
||||
webbrowser::open(complete_redir_url.as_ref()).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CodeWebsocketResponse {
|
||||
#[serde(rename = "type")]
|
||||
response_type: String,
|
||||
value: String,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn auth_initiate_code(app: AppHandle) -> Result<String, RemoteAccessError> {
|
||||
let base_url = {
|
||||
let db_lock = borrow_db_checked();
|
||||
Url::parse(&db_lock.base_url.clone())?
|
||||
};
|
||||
|
||||
let code = auth_initiate_logic("code".to_string())?;
|
||||
let header_code = code.clone();
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let load = async || -> Result<(), RemoteAccessError> {
|
||||
let ws_url = base_url.join("/api/v1/client/auth/code/ws")?;
|
||||
let response = reqwest::Client::default()
|
||||
.get(ws_url)
|
||||
.header("Authorization", header_code)
|
||||
.upgrade()
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let mut websocket = response.into_websocket().await?;
|
||||
|
||||
while let Some(token) = websocket.try_next().await? {
|
||||
if let Message::Text(response) = token {
|
||||
let response = serde_json::from_str::<CodeWebsocketResponse>(&response)
|
||||
.map_err(|e| RemoteAccessError::UnparseableResponse(e.to_string()))?;
|
||||
match response.response_type.as_str() {
|
||||
"token" => {
|
||||
let recieve_app = app.clone();
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
manual_recieve_handshake(recieve_app, response.value);
|
||||
});
|
||||
return Ok(());
|
||||
}
|
||||
_ => return Err(RemoteAccessError::HandshakeFailed(response.value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(RemoteAccessError::HandshakeFailed(
|
||||
"Failed to connect to websocket".to_string(),
|
||||
))
|
||||
};
|
||||
|
||||
let result = load().await;
|
||||
if let Err(err) = result {
|
||||
warn!("{err}");
|
||||
app.emit("auth/failed", err.to_string()).unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
Ok(code)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
@@ -2,17 +2,18 @@ use http::{header::CONTENT_TYPE, response::Builder as ResponseBuilder};
|
||||
use log::warn;
|
||||
use tauri::UriSchemeResponder;
|
||||
|
||||
use crate::{database::db::DatabaseImpls, remote::utils::DROP_CLIENT_ASYNC, DB};
|
||||
|
||||
use super::{
|
||||
auth::generate_authorization_header,
|
||||
cache::{cache_object, get_cached_object, ObjectCache},
|
||||
requests::make_request,
|
||||
cache::{ObjectCache, cache_object, get_cached_object},
|
||||
};
|
||||
|
||||
pub fn fetch_object(request: http::Request<Vec<u8>>, responder: UriSchemeResponder) {
|
||||
pub async fn fetch_object(request: http::Request<Vec<u8>>, responder: UriSchemeResponder) {
|
||||
// Drop leading /
|
||||
let object_id = &request.uri().path()[1..];
|
||||
|
||||
let cache_result = get_cached_object::<&str, ObjectCache>(object_id);
|
||||
let cache_result = get_cached_object::<ObjectCache>(object_id);
|
||||
if let Ok(cache_result) = &cache_result
|
||||
&& !cache_result.has_expired()
|
||||
{
|
||||
@@ -21,17 +22,15 @@ pub fn fetch_object(request: http::Request<Vec<u8>>, responder: UriSchemeRespond
|
||||
}
|
||||
|
||||
let header = generate_authorization_header();
|
||||
let client: reqwest::blocking::Client = reqwest::blocking::Client::new();
|
||||
let response = make_request(&client, &["/api/v1/client/object/", object_id], &[], |f| {
|
||||
f.header("Authorization", header)
|
||||
})
|
||||
.unwrap()
|
||||
.send();
|
||||
let client = DROP_CLIENT_ASYNC.clone();
|
||||
let url = format!("{}api/v1/client/object/{object_id}", DB.fetch_base_url());
|
||||
let response = client.get(url).header("Authorization", header).send().await;
|
||||
|
||||
if response.is_err() {
|
||||
match cache_result {
|
||||
Ok(cache_result) => responder.respond(cache_result.into()),
|
||||
Err(e) => {
|
||||
warn!("{e}")
|
||||
warn!("{e}");
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -42,20 +41,11 @@ pub fn fetch_object(request: http::Request<Vec<u8>>, responder: UriSchemeRespond
|
||||
CONTENT_TYPE,
|
||||
response.headers().get("Content-Type").unwrap(),
|
||||
);
|
||||
let data = Vec::from(response.bytes().unwrap());
|
||||
let data = Vec::from(response.bytes().await.unwrap());
|
||||
let resp = resp_builder.body(data).unwrap();
|
||||
if cache_result.is_err() || cache_result.unwrap().has_expired() {
|
||||
cache_object::<&str, ObjectCache>(object_id, &resp.clone().into()).unwrap();
|
||||
cache_object::<ObjectCache>(object_id, &resp.clone().into()).unwrap();
|
||||
}
|
||||
|
||||
responder.respond(resp);
|
||||
}
|
||||
pub fn fetch_object_offline(request: http::Request<Vec<u8>>, responder: UriSchemeResponder) {
|
||||
let object_id = &request.uri().path()[1..];
|
||||
let data = get_cached_object::<&str, ObjectCache>(object_id);
|
||||
|
||||
match data {
|
||||
Ok(data) => responder.respond(data.into()),
|
||||
Err(e) => warn!("{e}"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use http::{uri::PathAndQuery, Request, Response, StatusCode, Uri};
|
||||
use reqwest::blocking::Client;
|
||||
use tauri::UriSchemeResponder;
|
||||
|
||||
use crate::database::db::borrow_db_checked;
|
||||
use crate::{database::db::borrow_db_checked, remote::utils::DROP_CLIENT_SYNC};
|
||||
|
||||
pub fn handle_server_proto_offline(_request: Request<Vec<u8>>, responder: UriSchemeResponder) {
|
||||
let four_oh_four = Response::builder()
|
||||
@@ -38,7 +37,7 @@ pub fn handle_server_proto(request: Request<Vec<u8>>, responder: UriSchemeRespon
|
||||
return;
|
||||
}
|
||||
|
||||
let client = Client::new();
|
||||
let client = DROP_CLIENT_SYNC.clone();
|
||||
let response = client
|
||||
.request(request.method().clone(), new_uri.to_string())
|
||||
.header("Authorization", format!("Bearer {web_token}"))
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
use std::sync::Mutex;
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
io::Read,
|
||||
sync::{LazyLock, Mutex},
|
||||
};
|
||||
|
||||
use log::{debug, warn};
|
||||
use log::{debug, info, warn};
|
||||
use reqwest::Certificate;
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
database::db::borrow_db_mut_checked, error::remote_access_error::RemoteAccessError, AppState,
|
||||
AppStatus,
|
||||
AppState, AppStatus,
|
||||
database::db::{DATA_ROOT_DIR, borrow_db_mut_checked},
|
||||
error::remote_access_error::RemoteAccessError,
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -15,6 +21,60 @@ struct DropHealthcheck {
|
||||
app_name: String,
|
||||
}
|
||||
|
||||
pub static DROP_CLIENT_SYNC: LazyLock<reqwest::blocking::Client> = LazyLock::new(get_client_sync);
|
||||
pub static DROP_CLIENT_ASYNC: LazyLock<reqwest::Client> = LazyLock::new(get_client_async);
|
||||
|
||||
fn fetch_certificates() -> Vec<Certificate> {
|
||||
let certificate_dir = DATA_ROOT_DIR.join("certificates");
|
||||
|
||||
let mut certs = Vec::new();
|
||||
match fs::read_dir(certificate_dir) {
|
||||
Ok(c) => {
|
||||
for entry in c {
|
||||
match entry {
|
||||
Ok(c) => {
|
||||
let mut buf = Vec::new();
|
||||
File::open(c.path()).unwrap().read_to_end(&mut buf).unwrap();
|
||||
|
||||
for cert in Certificate::from_pem_bundle(&buf).unwrap() {
|
||||
certs.push(cert);
|
||||
}
|
||||
info!(
|
||||
"added {} certificate(s) from {}",
|
||||
certs.len(),
|
||||
c.file_name().into_string().unwrap()
|
||||
);
|
||||
}
|
||||
Err(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("not loading certificates due to error: {e}");
|
||||
}
|
||||
};
|
||||
certs
|
||||
}
|
||||
|
||||
pub fn get_client_sync() -> reqwest::blocking::Client {
|
||||
let mut client = reqwest::blocking::ClientBuilder::new();
|
||||
|
||||
let certs = fetch_certificates();
|
||||
for cert in certs {
|
||||
client = client.add_root_certificate(cert);
|
||||
}
|
||||
client.build().unwrap()
|
||||
}
|
||||
pub fn get_client_async() -> reqwest::Client {
|
||||
let mut client = reqwest::ClientBuilder::new();
|
||||
|
||||
let certs = fetch_certificates();
|
||||
for cert in certs {
|
||||
client = client.add_root_certificate(cert);
|
||||
}
|
||||
client.build().unwrap()
|
||||
}
|
||||
|
||||
pub fn use_remote_logic(
|
||||
url: String,
|
||||
state: tauri::State<'_, Mutex<AppState<'_>>>,
|
||||
@@ -24,7 +84,8 @@ pub fn use_remote_logic(
|
||||
|
||||
// Test Drop url
|
||||
let test_endpoint = base_url.join("/api/v1")?;
|
||||
let response = reqwest::blocking::get(test_endpoint.to_string())?;
|
||||
let client = DROP_CLIENT_SYNC.clone();
|
||||
let response = client.get(test_endpoint.to_string()).send()?;
|
||||
|
||||
let result: DropHealthcheck = response.json()?;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2.0.0",
|
||||
"productName": "Drop Desktop Client",
|
||||
"version": "0.3.0-rc-8",
|
||||
"version": "0.3.1-mac",
|
||||
"identifier": "dev.drop.app",
|
||||
"build": {
|
||||
"beforeDevCommand": "yarn dev --port 1432",
|
||||
@@ -11,7 +11,7 @@
|
||||
},
|
||||
"app": {
|
||||
"security": {
|
||||
"csp": null,
|
||||
"csp": "",
|
||||
"assetProtocol": {
|
||||
"enable": true,
|
||||
"scope": {}
|
||||
|
||||
Reference in New Issue
Block a user