Compare commits

..

8 Commits

Author SHA1 Message Date
e7c3b50a79 chore: Make clippy happy
Signed-off-by: quexeky <git@quexeky.dev>
2025-09-04 12:27:09 +10:00
cba23eab14 chore: Bump version to include logging
(Albeit, logging occurs before we initialise the logger, but oh well)

Signed-off-by: quexeky <git@quexeky.dev>
2025-09-04 12:23:31 +10:00
02af1996ec fix: Use Drop-OSS/native_model
Signed-off-by: quexeky <git@quexeky.dev>
2025-09-04 12:18:51 +10:00
a2a597d3d8 fix: Fix native_model from requirements and add version requirements for models
Signed-off-by: quexeky <git@quexeky.dev>
2025-09-04 12:16:37 +10:00
7e70a17a43 Bump version to v0.3.3 2025-08-28 18:23:12 +10:00
8d61a68b8a Add placeholders to unfinished pages (#126)
* feat: add placeholders for community & news pages

* feat: add placeholder to interface in settings menu
2025-08-28 18:22:33 +10:00
44a1be6991 Fix for multi-version downloads (#125)
* fix: multi version downloads

* fix: remove debug utils

* fix: clippy
2025-08-28 18:05:05 +10:00
4f5fccf0c1 Add umu-run discovery (#122)
Signed-off-by: quexeky <git@quexeky.dev>
2025-08-28 18:05:05 +10:00
16 changed files with 191 additions and 120 deletions

View File

@ -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
View 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>

View File

@ -1,16 +0,0 @@
<template>
<div class="mx-auto flex flex-col items-center gap-y-4 max-w-2xl py-32 sm:py-48 lg:py-56">
<div>
<Wordmark />
</div>
<div class="text-center">
<h1 class="text-balance text-4xl font-bold font-display tracking-tight text-zinc-100 sm:text-6xl">
Under construction
</h1>
<p class="mt-6 text-lg leading-8 text-zinc-400">
Yes, we know. We're working on it <a class="text-white" target="_blank"
href="https://github.com/Drop-OSS/drop-app/issues/52">here.</a>
</p>
</div>
</div>
</template>

25
main/pages/news.vue Normal file
View 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>

View File

@ -1,16 +0,0 @@
<template>
<div class="mx-auto flex flex-col items-center gap-y-4 max-w-2xl py-32 sm:py-48 lg:py-56">
<div>
<Wordmark />
</div>
<div class="text-center">
<h1 class="text-balance text-4xl font-bold font-display tracking-tight text-zinc-100 sm:text-6xl">
Under construction
</h1>
<p class="mt-6 text-lg leading-8 text-zinc-400">
Yes, we know. We're working on it <a class="text-white" target="_blank"
href="https://github.com/Drop-OSS/drop-app/issues/52">here.</a>
</p>
</div>
</div>
</template>

View File

@ -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>

14
src-tauri/Cargo.lock generated
View File

@ -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",
@ -3118,13 +3118,13 @@ dependencies = [
[[package]] [[package]]
name = "native_model" name = "native_model"
version = "0.6.1" version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/Drop-OSS/native_model.git#a91b422cbd53116df1f20b2459fb3d8257458bfd"
checksum = "7050d759e3da6673361dddda4f4a743492279dd2c6484a21fbee0a8278620df0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bincode", "bincode",
"doc-comment", "doc-comment",
"log",
"native_model_macro", "native_model_macro",
"rmp-serde", "rmp-serde",
"serde", "serde",
@ -3134,10 +3134,10 @@ dependencies = [
[[package]] [[package]]
name = "native_model_macro" name = "native_model_macro"
version = "0.6.1" version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/Drop-OSS/native_model.git#a91b422cbd53116df1f20b2459fb3d8257458bfd"
checksum = "1577a0bebf5ed1754e240baf5d9b1845f51e598b20600aa894f55e11cd20cc6c"
dependencies = [ dependencies = [
"log",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.101", "syn 2.0.101",

View File

@ -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"
@ -65,7 +65,7 @@ whoami = "1.6.0"
filetime = "0.2.25" filetime = "0.2.25"
walkdir = "2.5.0" walkdir = "2.5.0"
known-folders = "1.2.0" known-folders = "1.2.0"
native_model = { version = "0.6.1", features = ["rmp_serde_1_3"] } native_model = { version = "0.6.4", features = ["rmp_serde_1_3"], git = "https://github.com/Drop-OSS/native_model.git"}
tauri-plugin-opener = "2.4.0" tauri-plugin-opener = "2.4.0"
bitcode = "0.6.6" bitcode = "0.6.6"
reqwest-websocket = "0.5.0" reqwest-websocket = "0.5.0"

View File

@ -8,7 +8,6 @@ use std::{
use chrono::Utc; use chrono::Utc;
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
use native_model::{Decode, Encode};
use rustbreak::{DeSerError, DeSerializer, PathDatabase, RustbreakError}; use rustbreak::{DeSerError, DeSerializer, PathDatabase, RustbreakError};
use serde::{Serialize, de::DeserializeOwned}; use serde::{Serialize, de::DeserializeOwned};
use url::Url; use url::Url;
@ -28,7 +27,7 @@ impl<T: native_model::Model + Serialize + DeserializeOwned> DeSerializer<T>
for DropDatabaseSerializer for DropDatabaseSerializer
{ {
fn serialize(&self, val: &T) -> rustbreak::error::DeSerResult<Vec<u8>> { fn serialize(&self, val: &T) -> rustbreak::error::DeSerResult<Vec<u8>> {
native_model::rmp_serde_1_3::RmpSerde::encode(val) native_model::encode(val)
.map_err(|e| DeSerError::Internal(e.to_string())) .map_err(|e| DeSerError::Internal(e.to_string()))
} }
@ -36,7 +35,7 @@ impl<T: native_model::Model + Serialize + DeserializeOwned> DeSerializer<T>
let mut buf = Vec::new(); let mut buf = Vec::new();
s.read_to_end(&mut buf) s.read_to_end(&mut buf)
.map_err(|e| rustbreak::error::DeSerError::Other(e.into()))?; .map_err(|e| rustbreak::error::DeSerError::Other(e.into()))?;
let val = native_model::rmp_serde_1_3::RmpSerde::decode(buf) let (val, _version) = native_model::decode(buf)
.map_err(|e| DeSerError::Internal(e.to_string()))?; .map_err(|e| DeSerError::Internal(e.to_string()))?;
Ok(val) Ok(val)
} }

View File

@ -1,10 +1,5 @@
/**
* NEXT BREAKING CHANGE
*
* UPDATE DATABASE TO USE RPMSERDENAMED
*
* WE CAN'T DELETE ANY FIELDS
*/
pub mod data { pub mod data {
use std::path::PathBuf; use std::path::PathBuf;
@ -12,6 +7,9 @@ pub mod data {
use native_model::native_model; use native_model::native_model;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
// NOTE: Within each version, you should NEVER use these types.
// Declare it using the actual version that it is from, i.e. v1::Settings rather than just Settings from here
pub type GameVersion = v1::GameVersion; pub type GameVersion = v1::GameVersion;
pub type Database = v3::Database; pub type Database = v3::Database;
pub type Settings = v1::Settings; pub type Settings = v1::Settings;
@ -22,7 +20,7 @@ pub mod data {
pub type DownloadableMetadata = v1::DownloadableMetadata; pub type DownloadableMetadata = v1::DownloadableMetadata;
pub type DownloadType = v1::DownloadType; pub type DownloadType = v1::DownloadType;
pub type DatabaseApplications = v2::DatabaseApplications; pub type DatabaseApplications = v2::DatabaseApplications;
pub type DatabaseCompatInfo = v2::DatabaseCompatInfo; // pub type DatabaseCompatInfo = v2::DatabaseCompatInfo;
use std::collections::HashMap; use std::collections::HashMap;
@ -180,16 +178,15 @@ pub mod data {
use serde_with::serde_as; use serde_with::serde_as;
use super::{ use super::{
ApplicationTransientStatus, DatabaseAuth, Deserialize, DownloadableMetadata, Deserialize, Serialize, native_model, v1,
GameVersion, Serialize, Settings, native_model, v1,
}; };
#[native_model(id = 1, version = 2, with = native_model::rmp_serde_1_3::RmpSerde)] #[native_model(id = 1, version = 2, with = native_model::rmp_serde_1_3::RmpSerde, from = v1::Database)]
#[derive(Serialize, Deserialize, Clone, Default)] #[derive(Serialize, Deserialize, Clone, Default)]
pub struct Database { pub struct Database {
#[serde(default)] #[serde(default)]
pub settings: Settings, pub settings: v1::Settings,
pub auth: Option<DatabaseAuth>, pub auth: Option<v1::DatabaseAuth>,
pub base_url: String, pub base_url: String,
pub applications: v1::DatabaseApplications, pub applications: v1::DatabaseApplications,
#[serde(skip)] #[serde(skip)]
@ -198,7 +195,7 @@ pub mod data {
pub compat_info: Option<DatabaseCompatInfo>, pub compat_info: Option<DatabaseCompatInfo>,
} }
#[native_model(id = 8, version = 2, with = native_model::rmp_serde_1_3::RmpSerde)] #[native_model(id = 9, version = 1, with = native_model::rmp_serde_1_3::RmpSerde)]
#[derive(Serialize, Deserialize, Clone, Default)] #[derive(Serialize, Deserialize, Clone, Default)]
pub struct DatabaseCompatInfo { pub struct DatabaseCompatInfo {
@ -221,7 +218,7 @@ pub mod data {
// Strings are version names for a particular game // Strings are version names for a particular game
#[derive(Serialize, Clone, Deserialize, Debug)] #[derive(Serialize, Clone, Deserialize, Debug)]
#[serde(tag = "type")] #[serde(tag = "type")]
#[native_model(id = 5, version = 2, with = native_model::rmp_serde_1_3::RmpSerde)] #[native_model(id = 5, version = 2, with = native_model::rmp_serde_1_3::RmpSerde, from = v1::GameDownloadStatus)]
pub enum GameDownloadStatus { pub enum GameDownloadStatus {
Remote {}, Remote {},
SetupRequired { SetupRequired {
@ -261,17 +258,17 @@ pub mod data {
#[serde_as] #[serde_as]
#[derive(Serialize, Clone, Deserialize, Default)] #[derive(Serialize, Clone, Deserialize, Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[native_model(id = 3, version = 2, with = native_model::rmp_serde_1_3::RmpSerde)] #[native_model(id = 3, version = 2, with = native_model::rmp_serde_1_3::RmpSerde, from=v1::DatabaseApplications)]
pub struct DatabaseApplications { pub struct DatabaseApplications {
pub install_dirs: Vec<PathBuf>, pub install_dirs: Vec<PathBuf>,
// Guaranteed to exist if the game also exists in the app state map // Guaranteed to exist if the game also exists in the app state map
pub game_statuses: HashMap<String, GameDownloadStatus>, pub game_statuses: HashMap<String, GameDownloadStatus>,
pub game_versions: HashMap<String, HashMap<String, GameVersion>>, pub game_versions: HashMap<String, HashMap<String, v1::GameVersion>>,
pub installed_game_version: HashMap<String, DownloadableMetadata>, pub installed_game_version: HashMap<String, v1::DownloadableMetadata>,
#[serde(skip)] #[serde(skip)]
pub transient_statuses: HashMap<DownloadableMetadata, ApplicationTransientStatus>, pub transient_statuses: HashMap<v1::DownloadableMetadata, v1::ApplicationTransientStatus>,
} }
impl From<v1::DatabaseApplications> for DatabaseApplications { impl From<v1::DatabaseApplications> for DatabaseApplications {
fn from(value: v1::DatabaseApplications) -> Self { fn from(value: v1::DatabaseApplications) -> Self {
@ -293,21 +290,21 @@ pub mod data {
use std::path::PathBuf; use std::path::PathBuf;
use super::{ use super::{
DatabaseApplications, DatabaseAuth, DatabaseCompatInfo, Deserialize, Serialize, Deserialize, Serialize,
Settings, native_model, v2, native_model, v2, v1,
}; };
#[native_model(id = 1, version = 3, with = native_model::rmp_serde_1_3::RmpSerde)] #[native_model(id = 1, version = 3, with = native_model::rmp_serde_1_3::RmpSerde, from = v2::Database)]
#[derive(Serialize, Deserialize, Clone, Default)] #[derive(Serialize, Deserialize, Clone, Default)]
pub struct Database { pub struct Database {
#[serde(default)] #[serde(default)]
pub settings: Settings, pub settings: v1::Settings,
pub auth: Option<DatabaseAuth>, pub auth: Option<v1::DatabaseAuth>,
pub base_url: String, pub base_url: String,
pub applications: DatabaseApplications, pub applications: v2::DatabaseApplications,
#[serde(skip)] #[serde(skip)]
pub prev_database: Option<PathBuf>, pub prev_database: Option<PathBuf>,
pub cache_dir: PathBuf, pub cache_dir: PathBuf,
pub compat_info: Option<DatabaseCompatInfo>, pub compat_info: Option<v2::DatabaseCompatInfo>,
} }
impl From<v2::Database> for Database { impl From<v2::Database> for Database {
@ -347,5 +344,6 @@ pub mod data {
compat_info: None, compat_info: None,
} }
} }
} }
} }

View File

@ -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."
),
} }
} }
} }

View File

@ -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,14 +359,28 @@ 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();
for version in versions {
let download_context = DROP_CLIENT_SYNC let download_context = DROP_CLIENT_SYNC
.post(generate_url(&["/api/v2/client/context"], &[]).unwrap()) .post(generate_url(&["/api/v2/client/context"], &[]).unwrap())
.json(&ManifestBody { .json(&ManifestBody {
game: self.id.clone(), game: self.id.clone(),
version: self.version.clone(), version: version.clone(),
}) })
.header("Authorization", generate_authorization_header()) .header("Authorization", generate_authorization_header())
.send()?; .send()?;
@ -364,11 +389,16 @@ impl GameDownloadAgent {
return Err(RemoteAccessError::InvalidResponse(download_context.json()?)); return Err(RemoteAccessError::InvalidResponse(download_context.json()?));
} }
let download_context = &download_context.json::<DownloadContext>()?; let download_context = download_context.json::<DownloadContext>()?;
info!(
"download context: ({}) {}",
&version, download_context.context
);
download_contexts.insert(version, download_context);
}
info!("download context: {}", download_context.context); let download_contexts = &download_contexts;
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 {

View File

@ -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,

View File

@ -64,7 +64,7 @@ use serde::{Deserialize, Serialize};
use std::fs::File; use std::fs::File;
use std::io::Write; use std::io::Write;
use std::panic::PanicHookInfo; use std::panic::PanicHookInfo;
use std::path::{Path, PathBuf}; use std::path::Path;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use std::time::SystemTime; use std::time::SystemTime;
@ -109,7 +109,7 @@ fn create_new_compat_info() -> Option<CompatInfo> {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
return None; return None;
let has_umu_installed = *UMU_LAUNCHER_EXECUTABLE == PathBuf::new(); let has_umu_installed = UMU_LAUNCHER_EXECUTABLE.is_some();
Some(CompatInfo { Some(CompatInfo {
umu_installed: has_umu_installed, umu_installed: has_umu_installed,
}) })

View File

@ -5,7 +5,7 @@ use std::{
sync::LazyLock, sync::LazyLock,
}; };
use log::debug; use log::{debug, info};
use crate::{ use crate::{
AppState, AppState,
@ -31,29 +31,29 @@ impl ProcessHandler for NativeGameLauncher {
} }
} }
pub static UMU_LAUNCHER_EXECUTABLE: LazyLock<PathBuf> = LazyLock::new(|| { pub static UMU_LAUNCHER_EXECUTABLE: LazyLock<Option<PathBuf>> = LazyLock::new(|| {
let x = get_umu_executable(); let x = get_umu_executable();
println!("{:?}", &x); info!("{:?}", &x);
x x
}); });
const UMU_BASE_LAUNCHER_EXECUTABLE: &str = "umu-run"; const UMU_BASE_LAUNCHER_EXECUTABLE: &str = "umu-run";
const UMU_INSTALL_DIRS: [&str; 4] = ["/app/share", "/use/local/share", "/usr/share", "/opt"]; const UMU_INSTALL_DIRS: [&str; 4] = ["/app/share", "/use/local/share", "/usr/share", "/opt"];
fn get_umu_executable() -> PathBuf { fn get_umu_executable() -> Option<PathBuf> {
if check_executable_exists(UMU_BASE_LAUNCHER_EXECUTABLE) { if check_executable_exists(UMU_BASE_LAUNCHER_EXECUTABLE) {
return PathBuf::from(UMU_BASE_LAUNCHER_EXECUTABLE); return Some(PathBuf::from(UMU_BASE_LAUNCHER_EXECUTABLE));
} }
for dir in UMU_INSTALL_DIRS { for dir in UMU_INSTALL_DIRS {
let p = PathBuf::from(dir).join(UMU_BASE_LAUNCHER_EXECUTABLE); let p = PathBuf::from(dir).join(UMU_BASE_LAUNCHER_EXECUTABLE);
if check_executable_exists(&p) { if check_executable_exists(&p) {
return p; return Some(p);
} }
} }
PathBuf::new() None
} }
fn check_executable_exists<P: AsRef<OsStr>>(exec: P) -> bool { fn check_executable_exists<P: AsRef<OsStr>>(exec: P) -> bool {
let has_umu_installed = Command::new(exec).stdout(Stdio::null()).spawn(); let has_umu_installed = Command::new(exec).stdout(Stdio::null()).output();
has_umu_installed.is_ok() has_umu_installed.is_ok()
} }
pub struct UMULauncher; pub struct UMULauncher;
@ -79,7 +79,7 @@ impl ProcessHandler for UMULauncher {
}; };
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(" ")
) )

View File

@ -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",