mirror of
https://github.com/Drop-OSS/drop-app.git
synced 2025-11-14 08:41:21 +10:00
Compare commits
7 Commits
v0.3.2
...
ea6fa551a2
| Author | SHA1 | Date | |
|---|---|---|---|
| ea6fa551a2 | |||
| be4fc2d37a | |||
| 7e70a17a43 | |||
| 8d61a68b8a | |||
| 44a1be6991 | |||
| 4f5fccf0c1 | |||
| 5eef2bf60f |
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@ -69,9 +69,9 @@ jobs:
|
|||||||
security set-keychain-settings -t 3600 -u build.keychain
|
security set-keychain-settings -t 3600 -u build.keychain
|
||||||
|
|
||||||
curl https://droposs.org/drop.crt --output drop.pem
|
curl https://droposs.org/drop.crt --output drop.pem
|
||||||
sudo security authorizationdb write com.apple.trust-settings.admin allow
|
sudo security authorizationdb write com.apple.trust-settings.user allow
|
||||||
sudo security add-trusted-cert -d -r trustRoot -k build.keychain -p codeSign -u -1 drop.pem
|
security add-trusted-cert -r trustRoot -k build.keychain -p codeSign -u -1 drop.pem
|
||||||
sudo security authorizationdb remove com.apple.trust-settings.admin
|
sudo security authorizationdb remove com.apple.trust-settings.user
|
||||||
|
|
||||||
security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
|
security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
|
||||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain
|
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "view",
|
"name": "view",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nuxt generate",
|
"build": "nuxt generate",
|
||||||
|
|||||||
25
main/pages/community.vue
Normal file
25
main/pages/community.vue
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<template>
|
||||||
|
<div class="grow w-full h-full flex items-center justify-center">
|
||||||
|
<div class="flex flex-col items-center">
|
||||||
|
<WrenchScrewdriverIcon
|
||||||
|
class="h-12 w-12 text-blue-600"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<div class="mt-3 text-center sm:mt-5">
|
||||||
|
<h1 class="text-3xl font-semibold font-display leading-6 text-zinc-100">
|
||||||
|
Under construction
|
||||||
|
</h1>
|
||||||
|
<div class="mt-4">
|
||||||
|
<p class="text-sm text-zinc-400 max-w-lg">
|
||||||
|
This page hasn't been implemented yet.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
WrenchScrewdriverIcon,
|
||||||
|
} from "@heroicons/vue/20/solid";
|
||||||
|
</script>
|
||||||
25
main/pages/news.vue
Normal file
25
main/pages/news.vue
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<template>
|
||||||
|
<div class="grow w-full h-full flex items-center justify-center">
|
||||||
|
<div class="flex flex-col items-center">
|
||||||
|
<WrenchScrewdriverIcon
|
||||||
|
class="h-12 w-12 text-blue-600"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<div class="mt-3 text-center sm:mt-5">
|
||||||
|
<h1 class="text-3xl font-semibold font-display leading-6 text-zinc-100">
|
||||||
|
Under construction
|
||||||
|
</h1>
|
||||||
|
<div class="mt-4">
|
||||||
|
<p class="text-sm text-zinc-400 max-w-lg">
|
||||||
|
This page hasn't been implemented yet.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
WrenchScrewdriverIcon,
|
||||||
|
} from "@heroicons/vue/20/solid";
|
||||||
|
</script>
|
||||||
@ -1,7 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<div class="grow w-full h-full flex items-center justify-center">
|
||||||
|
<div class="flex flex-col items-center">
|
||||||
|
<WrenchScrewdriverIcon
|
||||||
|
class="h-12 w-12 text-blue-600"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<div class="mt-3 text-center sm:mt-5">
|
||||||
|
<h1 class="text-3xl font-semibold font-display leading-6 text-zinc-100">
|
||||||
|
Under construction
|
||||||
|
</h1>
|
||||||
|
<div class="mt-4">
|
||||||
|
<p class="text-sm text-zinc-400 max-w-lg">
|
||||||
|
This page hasn't been implemented yet.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { WrenchScrewdriverIcon } from "@heroicons/vue/20/solid";
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
2
src-tauri/Cargo.lock
generated
2
src-tauri/Cargo.lock
generated
@ -1284,7 +1284,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "drop-app"
|
name = "drop-app"
|
||||||
version = "0.3.2"
|
version = "0.3.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atomic-instant-full",
|
"atomic-instant-full",
|
||||||
"bitcode",
|
"bitcode",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "drop-app"
|
name = "drop-app"
|
||||||
version = "0.3.2"
|
version = "0.3.3"
|
||||||
description = "The client application for the open-source, self-hosted game distribution platform Drop"
|
description = "The client application for the open-source, self-hosted game distribution platform Drop"
|
||||||
authors = ["Drop OSS"]
|
authors = ["Drop OSS"]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|||||||
@ -23,6 +23,7 @@ pub enum RemoteAccessError {
|
|||||||
ManifestDownloadFailed(StatusCode, String),
|
ManifestDownloadFailed(StatusCode, String),
|
||||||
OutOfSync,
|
OutOfSync,
|
||||||
Cache(std::io::Error),
|
Cache(std::io::Error),
|
||||||
|
CorruptedState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for RemoteAccessError {
|
impl Display for RemoteAccessError {
|
||||||
@ -81,6 +82,10 @@ impl Display for RemoteAccessError {
|
|||||||
"server's and client's time are out of sync. Please ensure they are within at least 30 seconds of each other"
|
"server's and client's time are out of sync. Please ensure they are within at least 30 seconds of each other"
|
||||||
),
|
),
|
||||||
RemoteAccessError::Cache(error) => write!(f, "Cache Error: {error}"),
|
RemoteAccessError::Cache(error) => write!(f, "Cache Error: {error}"),
|
||||||
|
RemoteAccessError::CorruptedState => write!(
|
||||||
|
f,
|
||||||
|
"Drop encountered a corrupted internal state. Please report this to the developers, with details of reproduction."
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,8 +22,8 @@ use crate::remote::requests::generate_url;
|
|||||||
use crate::remote::utils::{DROP_CLIENT_ASYNC, DROP_CLIENT_SYNC};
|
use crate::remote::utils::{DROP_CLIENT_ASYNC, DROP_CLIENT_SYNC};
|
||||||
use log::{debug, error, info, warn};
|
use log::{debug, error, info, warn};
|
||||||
use rayon::ThreadPoolBuilder;
|
use rayon::ThreadPoolBuilder;
|
||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::fs::{OpenOptions, create_dir_all};
|
use std::fs::{create_dir_all, OpenOptions};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::mpsc::Sender;
|
use std::sync::mpsc::Sender;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
@ -242,12 +242,8 @@ impl GameDownloadAgent {
|
|||||||
|
|
||||||
let mut buckets = Vec::new();
|
let mut buckets = Vec::new();
|
||||||
|
|
||||||
let mut current_bucket = DownloadBucket {
|
let mut current_buckets = HashMap::<String, DownloadBucket>::new();
|
||||||
game_id: game_id.clone(),
|
let mut current_bucket_sizes = HashMap::<String, usize>::new();
|
||||||
version: self.version.clone(),
|
|
||||||
drops: Vec::new(),
|
|
||||||
};
|
|
||||||
let mut current_bucket_size = 0;
|
|
||||||
|
|
||||||
for (raw_path, chunk) in manifest {
|
for (raw_path, chunk) in manifest {
|
||||||
let path = base_path.join(Path::new(&raw_path));
|
let path = base_path.join(Path::new(&raw_path));
|
||||||
@ -282,28 +278,41 @@ impl GameDownloadAgent {
|
|||||||
|
|
||||||
buckets.push(DownloadBucket {
|
buckets.push(DownloadBucket {
|
||||||
game_id: game_id.clone(),
|
game_id: game_id.clone(),
|
||||||
version: self.version.clone(),
|
version: chunk.version_name.clone(),
|
||||||
drops: vec![drop],
|
drops: vec![drop],
|
||||||
});
|
});
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if current_bucket_size + *length >= TARGET_BUCKET_SIZE
|
let current_bucket_size = current_bucket_sizes
|
||||||
|
.entry(chunk.version_name.clone())
|
||||||
|
.or_insert_with(|| 0);
|
||||||
|
let c_version_name = chunk.version_name.clone();
|
||||||
|
let c_game_id = game_id.clone();
|
||||||
|
let current_bucket = current_buckets
|
||||||
|
.entry(chunk.version_name.clone())
|
||||||
|
.or_insert_with(|| DownloadBucket {
|
||||||
|
game_id: c_game_id,
|
||||||
|
version: c_version_name,
|
||||||
|
drops: vec![],
|
||||||
|
});
|
||||||
|
|
||||||
|
if *current_bucket_size + length >= TARGET_BUCKET_SIZE
|
||||||
&& !current_bucket.drops.is_empty()
|
&& !current_bucket.drops.is_empty()
|
||||||
{
|
{
|
||||||
// Move current bucket into list and make a new one
|
// Move current bucket into list and make a new one
|
||||||
buckets.push(current_bucket);
|
buckets.push(current_bucket.clone());
|
||||||
current_bucket = DownloadBucket {
|
*current_bucket = DownloadBucket {
|
||||||
game_id: game_id.clone(),
|
game_id: game_id.clone(),
|
||||||
version: self.version.clone(),
|
version: chunk.version_name.clone(),
|
||||||
drops: Vec::new(),
|
drops: vec![],
|
||||||
};
|
};
|
||||||
current_bucket_size = 0;
|
*current_bucket_size = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
current_bucket.drops.push(drop);
|
current_bucket.drops.push(drop);
|
||||||
current_bucket_size += *length;
|
*current_bucket_size += *length;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
@ -312,8 +321,10 @@ impl GameDownloadAgent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !current_bucket.drops.is_empty() {
|
for (_, bucket) in current_buckets.into_iter() {
|
||||||
buckets.push(current_bucket);
|
if !bucket.drops.is_empty() {
|
||||||
|
buckets.push(bucket);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("buckets: {}", buckets.len());
|
info!("buckets: {}", buckets.len());
|
||||||
@ -348,27 +359,46 @@ impl GameDownloadAgent {
|
|||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let buckets = self.buckets.lock().unwrap();
|
||||||
|
|
||||||
|
let mut download_contexts = HashMap::<String, DownloadContext>::new();
|
||||||
|
|
||||||
|
let versions = buckets
|
||||||
|
.iter()
|
||||||
|
.map(|e| &e.version)
|
||||||
|
.collect::<HashSet<_>>()
|
||||||
|
.into_iter().cloned()
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
info!("downloading across these versions: {versions:?}");
|
||||||
|
|
||||||
let completed_contexts = Arc::new(boxcar::Vec::new());
|
let completed_contexts = Arc::new(boxcar::Vec::new());
|
||||||
let completed_indexes_loop_arc = completed_contexts.clone();
|
let completed_indexes_loop_arc = completed_contexts.clone();
|
||||||
|
|
||||||
let download_context = DROP_CLIENT_SYNC
|
for version in versions {
|
||||||
.post(generate_url(&["/api/v2/client/context"], &[]).unwrap())
|
let download_context = DROP_CLIENT_SYNC
|
||||||
.json(&ManifestBody {
|
.post(generate_url(&["/api/v2/client/context"], &[]).unwrap())
|
||||||
game: self.id.clone(),
|
.json(&ManifestBody {
|
||||||
version: self.version.clone(),
|
game: self.id.clone(),
|
||||||
})
|
version: version.clone(),
|
||||||
.header("Authorization", generate_authorization_header())
|
})
|
||||||
.send()?;
|
.header("Authorization", generate_authorization_header())
|
||||||
|
.send()?;
|
||||||
|
|
||||||
if download_context.status() != 200 {
|
if download_context.status() != 200 {
|
||||||
return Err(RemoteAccessError::InvalidResponse(download_context.json()?));
|
return Err(RemoteAccessError::InvalidResponse(download_context.json()?));
|
||||||
|
}
|
||||||
|
|
||||||
|
let download_context = download_context.json::<DownloadContext>()?;
|
||||||
|
info!(
|
||||||
|
"download context: ({}) {}",
|
||||||
|
&version, download_context.context
|
||||||
|
);
|
||||||
|
download_contexts.insert(version, download_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
let download_context = &download_context.json::<DownloadContext>()?;
|
let download_contexts = &download_contexts;
|
||||||
|
|
||||||
info!("download context: {}", download_context.context);
|
|
||||||
|
|
||||||
let buckets = self.buckets.lock().unwrap();
|
|
||||||
pool.scope(|scope| {
|
pool.scope(|scope| {
|
||||||
let context_map = self.context_map.lock().unwrap();
|
let context_map = self.context_map.lock().unwrap();
|
||||||
for (index, bucket) in buckets.iter().enumerate() {
|
for (index, bucket) in buckets.iter().enumerate() {
|
||||||
@ -400,6 +430,11 @@ impl GameDownloadAgent {
|
|||||||
|
|
||||||
let sender = self.sender.clone();
|
let sender = self.sender.clone();
|
||||||
|
|
||||||
|
let download_context = download_contexts
|
||||||
|
.get(&bucket.version)
|
||||||
|
.ok_or(RemoteAccessError::CorruptedState)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
scope.spawn(move |_| {
|
scope.spawn(move |_| {
|
||||||
// 3 attempts
|
// 3 attempts
|
||||||
for i in 0..RETRY_COUNT {
|
for i in 0..RETRY_COUNT {
|
||||||
|
|||||||
@ -172,7 +172,7 @@ pub fn download_game_bucket(
|
|||||||
let raw_res = response.text().map_err(|e| {
|
let raw_res = response.text().map_err(|e| {
|
||||||
ApplicationDownloadError::Communication(RemoteAccessError::FetchError(e.into()))
|
ApplicationDownloadError::Communication(RemoteAccessError::FetchError(e.into()))
|
||||||
})?;
|
})?;
|
||||||
info!("{}", raw_res);
|
info!("{raw_res}");
|
||||||
if let Ok(err) = serde_json::from_str::<DropServerError>(&raw_res) {
|
if let Ok(err) = serde_json::from_str::<DropServerError>(&raw_res) {
|
||||||
return Err(ApplicationDownloadError::Communication(
|
return Err(ApplicationDownloadError::Communication(
|
||||||
RemoteAccessError::InvalidResponse(err),
|
RemoteAccessError::InvalidResponse(err),
|
||||||
@ -196,8 +196,7 @@ pub fn download_game_bucket(
|
|||||||
let length = raw_length.parse::<usize>().unwrap_or(0);
|
let length = raw_length.parse::<usize>().unwrap_or(0);
|
||||||
let Some(drop) = bucket.drops.get(i) else {
|
let Some(drop) = bucket.drops.get(i) else {
|
||||||
warn!(
|
warn!(
|
||||||
"invalid number of Content-Lengths recieved: {}, {}",
|
"invalid number of Content-Lengths recieved: {i}, {lengths}"
|
||||||
i, lengths
|
|
||||||
);
|
);
|
||||||
return Err(ApplicationDownloadError::DownloadError);
|
return Err(ApplicationDownloadError::DownloadError);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
// Drops go in buckets
|
// Drops go in buckets
|
||||||
pub struct DownloadDrop {
|
pub struct DownloadDrop {
|
||||||
pub index: usize,
|
pub index: usize,
|
||||||
@ -14,7 +14,7 @@ pub struct DownloadDrop {
|
|||||||
pub permissions: u32,
|
pub permissions: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct DownloadBucket {
|
pub struct DownloadBucket {
|
||||||
pub game_id: String,
|
pub game_id: String,
|
||||||
pub version: String,
|
pub version: String,
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
#![feature(duration_millis_float)]
|
#![feature(duration_millis_float)]
|
||||||
#![feature(iterator_try_collect)]
|
#![feature(iterator_try_collect)]
|
||||||
#![deny(clippy::all)]
|
#![deny(clippy::all)]
|
||||||
|
#![deny(clippy::unwrap_used)]
|
||||||
|
|
||||||
mod database;
|
mod database;
|
||||||
mod games;
|
mod games;
|
||||||
@ -13,6 +14,7 @@ mod download_manager;
|
|||||||
mod error;
|
mod error;
|
||||||
mod process;
|
mod process;
|
||||||
mod remote;
|
mod remote;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
use crate::database::scan::scan_install_dirs;
|
use crate::database::scan::scan_install_dirs;
|
||||||
use crate::process::commands::open_process_logs;
|
use crate::process::commands::open_process_logs;
|
||||||
@ -45,7 +47,7 @@ use games::commands::{
|
|||||||
};
|
};
|
||||||
use games::downloads::commands::download_game;
|
use games::downloads::commands::download_game;
|
||||||
use games::library::{Game, update_game_configuration};
|
use games::library::{Game, update_game_configuration};
|
||||||
use log::{LevelFilter, debug, info, warn};
|
use log::{LevelFilter, debug, info, warn, error};
|
||||||
use log4rs::Config;
|
use log4rs::Config;
|
||||||
use log4rs::append::console::ConsoleAppender;
|
use log4rs::append::console::ConsoleAppender;
|
||||||
use log4rs::append::file::FileAppender;
|
use log4rs::append::file::FileAppender;
|
||||||
@ -65,7 +67,6 @@ use std::fs::File;
|
|||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::panic::PanicHookInfo;
|
use std::panic::PanicHookInfo;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::{Command, Stdio};
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
@ -110,13 +111,7 @@ fn create_new_compat_info() -> Option<CompatInfo> {
|
|||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
return None;
|
return None;
|
||||||
|
|
||||||
let has_umu_installed = Command::new(UMU_LAUNCHER_EXECUTABLE)
|
let has_umu_installed = UMU_LAUNCHER_EXECUTABLE.is_some();
|
||||||
.stdout(Stdio::null())
|
|
||||||
.spawn();
|
|
||||||
if let Err(umu_error) = &has_umu_installed {
|
|
||||||
warn!("disabling windows support with error: {umu_error}");
|
|
||||||
}
|
|
||||||
let has_umu_installed = has_umu_installed.is_ok();
|
|
||||||
Some(CompatInfo {
|
Some(CompatInfo {
|
||||||
umu_installed: has_umu_installed,
|
umu_installed: has_umu_installed,
|
||||||
})
|
})
|
||||||
@ -144,7 +139,7 @@ async fn setup(handle: AppHandle) -> AppState<'static> {
|
|||||||
)))
|
)))
|
||||||
.append(false)
|
.append(false)
|
||||||
.build(DATA_ROOT_DIR.join("./drop.log"))
|
.build(DATA_ROOT_DIR.join("./drop.log"))
|
||||||
.unwrap();
|
.expect("Failed to setup logfile");
|
||||||
|
|
||||||
let console = ConsoleAppender::builder()
|
let console = ConsoleAppender::builder()
|
||||||
.encoder(Box::new(PatternEncoder::new(
|
.encoder(Box::new(PatternEncoder::new(
|
||||||
@ -164,9 +159,9 @@ async fn setup(handle: AppHandle) -> AppState<'static> {
|
|||||||
.appenders(vec!["logfile", "console"])
|
.appenders(vec!["logfile", "console"])
|
||||||
.build(LevelFilter::from_str(&log_level).expect("Invalid log level")),
|
.build(LevelFilter::from_str(&log_level).expect("Invalid log level")),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.expect("Failed to build config");
|
||||||
|
|
||||||
log4rs::init_config(config).unwrap();
|
log4rs::init_config(config).expect("Failed to initialise log4rs");
|
||||||
|
|
||||||
let games = HashMap::new();
|
let games = HashMap::new();
|
||||||
let download_manager = Arc::new(DownloadManagerBuilder::build(handle.clone()));
|
let download_manager = Arc::new(DownloadManagerBuilder::build(handle.clone()));
|
||||||
@ -377,42 +372,57 @@ pub fn run() {
|
|||||||
.shadow(false)
|
.shadow(false)
|
||||||
.data_directory(DATA_ROOT_DIR.join(".webview"))
|
.data_directory(DATA_ROOT_DIR.join(".webview"))
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.expect("Failed to build main window");
|
||||||
|
|
||||||
app.deep_link().on_open_url(move |event| {
|
app.deep_link().on_open_url(move |event| {
|
||||||
debug!("handling drop:// url");
|
debug!("handling drop:// url");
|
||||||
let binding = event.urls();
|
let binding = event.urls();
|
||||||
let url = binding.first().unwrap();
|
let url = match binding.first() {
|
||||||
if url.host_str().unwrap() == "handshake" {
|
Some(url) => url,
|
||||||
tauri::async_runtime::spawn(recieve_handshake(
|
None => {
|
||||||
handle.clone(),
|
warn!("No value recieved from deep link. Is this a drop server?");
|
||||||
url.path().to_string(),
|
return;
|
||||||
));
|
}
|
||||||
|
};
|
||||||
|
if let Some("handshake") = url.host_str() {
|
||||||
|
tauri::async_runtime::spawn(recieve_handshake(
|
||||||
|
handle.clone(),
|
||||||
|
url.path().to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
let open_menu_item = MenuItem::with_id(app, "open", "Open", true, None::<&str>).expect("Failed to generate open menu item");
|
||||||
|
|
||||||
|
let sep = PredefinedMenuItem::separator(app).expect("Failed to generate menu separator item");
|
||||||
|
|
||||||
|
let quit_menu_item = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>).expect("Failed to generate quit menu item");
|
||||||
|
|
||||||
let menu = Menu::with_items(
|
let menu = Menu::with_items(
|
||||||
app,
|
app,
|
||||||
&[
|
&[
|
||||||
&MenuItem::with_id(app, "open", "Open", true, None::<&str>).unwrap(),
|
&open_menu_item,
|
||||||
&PredefinedMenuItem::separator(app).unwrap(),
|
&sep,
|
||||||
/*
|
/*
|
||||||
&MenuItem::with_id(app, "show_library", "Library", true, None::<&str>)?,
|
&MenuItem::with_id(app, "show_library", "Library", true, None::<&str>)?,
|
||||||
&MenuItem::with_id(app, "show_settings", "Settings", true, None::<&str>)?,
|
&MenuItem::with_id(app, "show_settings", "Settings", true, None::<&str>)?,
|
||||||
&PredefinedMenuItem::separator(app)?,
|
&PredefinedMenuItem::separator(app)?,
|
||||||
*/
|
*/
|
||||||
&MenuItem::with_id(app, "quit", "Quit", true, None::<&str>).unwrap(),
|
&quit_menu_item,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.unwrap();
|
.expect("Failed to generate menu");
|
||||||
|
|
||||||
run_on_tray(|| {
|
run_on_tray(|| {
|
||||||
TrayIconBuilder::new()
|
TrayIconBuilder::new()
|
||||||
.icon(app.default_window_icon().unwrap().clone())
|
.icon(app.default_window_icon().expect("Failed to get default window icon").clone())
|
||||||
.menu(&menu)
|
.menu(&menu)
|
||||||
.on_menu_event(|app, event| match event.id.as_ref() {
|
.on_menu_event(|app, event| match event.id.as_ref() {
|
||||||
"open" => {
|
"open" => {
|
||||||
app.webview_windows().get("main").unwrap().show().unwrap();
|
app.webview_windows()
|
||||||
|
.get("main")
|
||||||
|
.expect("Failed to get webview")
|
||||||
|
.show()
|
||||||
|
.expect("Failed to show window");
|
||||||
}
|
}
|
||||||
"quit" => {
|
"quit" => {
|
||||||
cleanup_and_exit(app, &app.state());
|
cleanup_and_exit(app, &app.state());
|
||||||
@ -429,15 +439,19 @@ pub fn run() {
|
|||||||
{
|
{
|
||||||
let mut db_handle = borrow_db_mut_checked();
|
let mut db_handle = borrow_db_mut_checked();
|
||||||
if let Some(original) = db_handle.prev_database.take() {
|
if let Some(original) = db_handle.prev_database.take() {
|
||||||
|
let canonicalised = match original.canonicalize() {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(_) => original,
|
||||||
|
};
|
||||||
warn!(
|
warn!(
|
||||||
"Database corrupted. Original file at {}",
|
"Database corrupted. Original file at {}",
|
||||||
original.canonicalize().unwrap().to_string_lossy()
|
canonicalised.display()
|
||||||
);
|
);
|
||||||
app.dialog()
|
app.dialog()
|
||||||
.message(
|
.message(format!(
|
||||||
"Database corrupted. A copy has been saved at: ".to_string()
|
"Database corrupted. A copy has been saved at: {}",
|
||||||
+ original.to_str().unwrap(),
|
canonicalised.display()
|
||||||
)
|
))
|
||||||
.title("Database corrupted")
|
.title("Database corrupted")
|
||||||
.show(|_| {});
|
.show(|_| {});
|
||||||
}
|
}
|
||||||
@ -470,7 +484,7 @@ pub fn run() {
|
|||||||
.on_window_event(|window, event| {
|
.on_window_event(|window, event| {
|
||||||
if let WindowEvent::CloseRequested { api, .. } = event {
|
if let WindowEvent::CloseRequested { api, .. } = event {
|
||||||
run_on_tray(|| {
|
run_on_tray(|| {
|
||||||
window.hide().unwrap();
|
window.hide().expect("Failed to close window in tray");
|
||||||
api.prevent_close();
|
api.prevent_close();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,11 @@
|
|||||||
use log::debug;
|
use std::{
|
||||||
|
ffi::OsStr,
|
||||||
|
path::PathBuf,
|
||||||
|
process::{Command, Stdio},
|
||||||
|
sync::LazyLock,
|
||||||
|
};
|
||||||
|
|
||||||
|
use log::{debug, info};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AppState,
|
AppState,
|
||||||
@ -24,7 +31,31 @@ impl ProcessHandler for NativeGameLauncher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const UMU_LAUNCHER_EXECUTABLE: &str = "umu-run";
|
pub static UMU_LAUNCHER_EXECUTABLE: LazyLock<Option<PathBuf>> = LazyLock::new(|| {
|
||||||
|
let x = get_umu_executable();
|
||||||
|
info!("{:?}", &x);
|
||||||
|
x
|
||||||
|
});
|
||||||
|
const UMU_BASE_LAUNCHER_EXECUTABLE: &str = "umu-run";
|
||||||
|
const UMU_INSTALL_DIRS: [&str; 4] = ["/app/share", "/use/local/share", "/usr/share", "/opt"];
|
||||||
|
|
||||||
|
fn get_umu_executable() -> Option<PathBuf> {
|
||||||
|
if check_executable_exists(UMU_BASE_LAUNCHER_EXECUTABLE) {
|
||||||
|
return Some(PathBuf::from(UMU_BASE_LAUNCHER_EXECUTABLE));
|
||||||
|
}
|
||||||
|
|
||||||
|
for dir in UMU_INSTALL_DIRS {
|
||||||
|
let p = PathBuf::from(dir).join(UMU_BASE_LAUNCHER_EXECUTABLE);
|
||||||
|
if check_executable_exists(&p) {
|
||||||
|
return Some(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
fn check_executable_exists<P: AsRef<OsStr>>(exec: P) -> bool {
|
||||||
|
let has_umu_installed = Command::new(exec).stdout(Stdio::null()).output();
|
||||||
|
has_umu_installed.is_ok()
|
||||||
|
}
|
||||||
pub struct UMULauncher;
|
pub struct UMULauncher;
|
||||||
impl ProcessHandler for UMULauncher {
|
impl ProcessHandler for UMULauncher {
|
||||||
fn create_launch_process(
|
fn create_launch_process(
|
||||||
@ -47,8 +78,8 @@ impl ProcessHandler for UMULauncher {
|
|||||||
None => game_version.game_id.clone(),
|
None => game_version.game_id.clone(),
|
||||||
};
|
};
|
||||||
format!(
|
format!(
|
||||||
"GAMEID={game_id} {umu} \"{launch}\" {args}",
|
"GAMEID={game_id} {umu:?} \"{launch}\" {args}",
|
||||||
umu = UMU_LAUNCHER_EXECUTABLE,
|
umu = UMU_LAUNCHER_EXECUTABLE.as_ref().unwrap(),
|
||||||
launch = launch_command,
|
launch = launch_command,
|
||||||
args = args.join(" ")
|
args = args.join(" ")
|
||||||
)
|
)
|
||||||
@ -80,7 +111,10 @@ impl ProcessHandler for AsahiMuvmLauncher {
|
|||||||
game_version,
|
game_version,
|
||||||
current_dir,
|
current_dir,
|
||||||
);
|
);
|
||||||
let mut args_cmd = umu_string.split("umu-run").collect::<Vec<&str>>().into_iter();
|
let mut args_cmd = umu_string
|
||||||
|
.split("umu-run")
|
||||||
|
.collect::<Vec<&str>>()
|
||||||
|
.into_iter();
|
||||||
let args = args_cmd.next().unwrap().trim();
|
let args = args_cmd.next().unwrap().trim();
|
||||||
let cmd = format!("umu-run{}", args_cmd.next().unwrap());
|
let cmd = format!("umu-run{}", args_cmd.next().unwrap());
|
||||||
|
|
||||||
|
|||||||
@ -347,11 +347,10 @@ impl ProcessManager<'_> {
|
|||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
use std::os::windows::process::CommandExt;
|
use std::os::windows::process::CommandExt;
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
let mut command = Command::new("start");
|
let mut command = Command::new("cmd");
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
command.raw_arg(format!("/min cmd /C \"{}\"", &launch_string));
|
command.raw_arg(format!("/C \"{}\"", &launch_string));
|
||||||
|
|
||||||
info!("launching (in {install_dir}): {launch_string}",);
|
info!("launching (in {install_dir}): {launch_string}",);
|
||||||
|
|
||||||
|
|||||||
@ -130,7 +130,7 @@ pub fn auth_initiate_code(app: AppHandle) -> Result<String, RemoteAccessError> {
|
|||||||
let code = auth_initiate_logic("code".to_string())?;
|
let code = auth_initiate_logic("code".to_string())?;
|
||||||
let header_code = code.clone();
|
let header_code = code.clone();
|
||||||
|
|
||||||
println!("using code: {} to sign in", code);
|
println!("using code: {code} to sign in");
|
||||||
|
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
let load = async || -> Result<(), RemoteAccessError> {
|
let load = async || -> Result<(), RemoteAccessError> {
|
||||||
|
|||||||
@ -14,6 +14,7 @@ use crate::{
|
|||||||
AppState, AppStatus,
|
AppState, AppStatus,
|
||||||
database::db::{DATA_ROOT_DIR, borrow_db_mut_checked},
|
database::db::{DATA_ROOT_DIR, borrow_db_mut_checked},
|
||||||
error::remote_access_error::RemoteAccessError,
|
error::remote_access_error::RemoteAccessError,
|
||||||
|
state_lock,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@ -37,16 +38,40 @@ fn fetch_certificates() -> Vec<Certificate> {
|
|||||||
match entry {
|
match entry {
|
||||||
Ok(c) => {
|
Ok(c) => {
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
File::open(c.path()).unwrap().read_to_end(&mut buf).unwrap();
|
match File::open(c.path()) {
|
||||||
|
Ok(f) => f,
|
||||||
for cert in Certificate::from_pem_bundle(&buf).unwrap() {
|
Err(e) => {
|
||||||
certs.push(cert);
|
warn!(
|
||||||
|
"Failed to open file at {} with error {}",
|
||||||
|
c.path().display(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.read_to_end(&mut buf)
|
||||||
|
.expect(&format!(
|
||||||
|
"Failed to read to end of certificate file {}",
|
||||||
|
c.path().display()
|
||||||
|
));
|
||||||
|
|
||||||
|
match Certificate::from_pem_bundle(&buf) {
|
||||||
|
Ok(certificates) => {
|
||||||
|
for cert in certificates {
|
||||||
|
certs.push(cert);
|
||||||
|
}
|
||||||
|
info!(
|
||||||
|
"added {} certificate(s) from {}",
|
||||||
|
certs.len(),
|
||||||
|
c.file_name().display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(e) => warn!(
|
||||||
|
"Invalid certificate file {} with error {}",
|
||||||
|
c.path().display(),
|
||||||
|
e
|
||||||
|
),
|
||||||
}
|
}
|
||||||
info!(
|
|
||||||
"added {} certificate(s) from {}",
|
|
||||||
certs.len(),
|
|
||||||
c.file_name().into_string().unwrap()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Err(_) => todo!(),
|
Err(_) => todo!(),
|
||||||
}
|
}
|
||||||
@ -65,7 +90,7 @@ pub fn get_client_sync() -> reqwest::blocking::Client {
|
|||||||
for cert in DROP_CERT_BUNDLE.iter() {
|
for cert in DROP_CERT_BUNDLE.iter() {
|
||||||
client = client.add_root_certificate(cert.clone());
|
client = client.add_root_certificate(cert.clone());
|
||||||
}
|
}
|
||||||
client.use_rustls_tls().build().unwrap()
|
client.use_rustls_tls().build().expect("Failed to build synchronous client")
|
||||||
}
|
}
|
||||||
pub fn get_client_async() -> reqwest::Client {
|
pub fn get_client_async() -> reqwest::Client {
|
||||||
let mut client = reqwest::ClientBuilder::new();
|
let mut client = reqwest::ClientBuilder::new();
|
||||||
@ -73,7 +98,7 @@ pub fn get_client_async() -> reqwest::Client {
|
|||||||
for cert in DROP_CERT_BUNDLE.iter() {
|
for cert in DROP_CERT_BUNDLE.iter() {
|
||||||
client = client.add_root_certificate(cert.clone());
|
client = client.add_root_certificate(cert.clone());
|
||||||
}
|
}
|
||||||
client.use_rustls_tls().build().unwrap()
|
client.use_rustls_tls().build().expect("Failed to build asynchronous client")
|
||||||
}
|
}
|
||||||
pub fn get_client_ws() -> reqwest::Client {
|
pub fn get_client_ws() -> reqwest::Client {
|
||||||
let mut client = reqwest::ClientBuilder::new();
|
let mut client = reqwest::ClientBuilder::new();
|
||||||
@ -81,7 +106,11 @@ pub fn get_client_ws() -> reqwest::Client {
|
|||||||
for cert in DROP_CERT_BUNDLE.iter() {
|
for cert in DROP_CERT_BUNDLE.iter() {
|
||||||
client = client.add_root_certificate(cert.clone());
|
client = client.add_root_certificate(cert.clone());
|
||||||
}
|
}
|
||||||
client.use_rustls_tls().http1_only().build().unwrap()
|
client
|
||||||
|
.use_rustls_tls()
|
||||||
|
.http1_only()
|
||||||
|
.build()
|
||||||
|
.expect("Failed to build websocket client")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn use_remote_logic(
|
pub async fn use_remote_logic(
|
||||||
@ -107,7 +136,7 @@ pub async fn use_remote_logic(
|
|||||||
return Err(RemoteAccessError::InvalidEndpoint);
|
return Err(RemoteAccessError::InvalidEndpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut app_state = state.lock().unwrap();
|
let mut app_state = state_lock!(state);
|
||||||
app_state.status = AppStatus::SignedOut;
|
app_state.status = AppStatus::SignedOut;
|
||||||
drop(app_state);
|
drop(app_state);
|
||||||
|
|
||||||
|
|||||||
1
src-tauri/src/utils/mod.rs
Normal file
1
src-tauri/src/utils/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod state_lock;
|
||||||
6
src-tauri/src/utils/state_lock.rs
Normal file
6
src-tauri/src/utils/state_lock.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#[macro_export]
|
||||||
|
macro_rules! state_lock {
|
||||||
|
($state:expr) => {
|
||||||
|
$state.lock().expect("Failed to lock onto state")
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2.0.0",
|
"$schema": "https://schema.tauri.app/config/2.0.0",
|
||||||
"productName": "Drop Desktop Client",
|
"productName": "Drop Desktop Client",
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"identifier": "dev.drop.client",
|
"identifier": "dev.drop.client",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "yarn --cwd main dev --port 1432",
|
"beforeDevCommand": "yarn --cwd main dev --port 1432",
|
||||||
|
|||||||
Reference in New Issue
Block a user