Compare commits

..

2 Commits

Author SHA1 Message Date
96df57ac54 chore: Add "Under Construction" page for community and news
Signed-off-by: quexeky <git@quexeky.dev>
2025-08-19 12:17:41 +10:00
8069616f2b feat: add umu executable finding (#122)
Signed-off-by: quexeky <git@quexeky.dev>
2025-08-19 10:52:24 +10:00
14 changed files with 84 additions and 158 deletions

View File

@ -1,7 +1,7 @@
{ {
"name": "view", "name": "view",
"private": true, "private": true,
"version": "0.3.3", "version": "0.3.2",
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "nuxt generate", "build": "nuxt generate",

View File

@ -1,25 +0,0 @@
<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

@ -0,0 +1,16 @@
<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,25 +0,0 @@
<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>

16
main/pages/news/index.vue Normal file
View File

@ -0,0 +1,16 @@
<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,23 +1,7 @@
<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
View File

@ -1284,7 +1284,7 @@ dependencies = [
[[package]] [[package]]
name = "drop-app" name = "drop-app"
version = "0.3.3" version = "0.3.2"
dependencies = [ dependencies = [
"atomic-instant-full", "atomic-instant-full",
"bitcode", "bitcode",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "drop-app" name = "drop-app"
version = "0.3.3" version = "0.3.2"
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"

View File

@ -23,7 +23,6 @@ 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 {
@ -82,10 +81,6 @@ 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, HashSet}; 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::path::{Path, PathBuf};
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -242,8 +242,12 @@ impl GameDownloadAgent {
let mut buckets = Vec::new(); let mut buckets = Vec::new();
let mut current_buckets = HashMap::<String, DownloadBucket>::new(); let mut current_bucket = DownloadBucket {
let mut current_bucket_sizes = HashMap::<String, usize>::new(); game_id: game_id.clone(),
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));
@ -278,41 +282,28 @@ impl GameDownloadAgent {
buckets.push(DownloadBucket { buckets.push(DownloadBucket {
game_id: game_id.clone(), game_id: game_id.clone(),
version: chunk.version_name.clone(), version: self.version.clone(),
drops: vec![drop], drops: vec![drop],
}); });
continue; continue;
} }
let current_bucket_size = current_bucket_sizes if current_bucket_size + *length >= TARGET_BUCKET_SIZE
.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.clone()); buckets.push(current_bucket);
*current_bucket = DownloadBucket { current_bucket = DownloadBucket {
game_id: game_id.clone(), game_id: game_id.clone(),
version: chunk.version_name.clone(), version: self.version.clone(),
drops: vec![], drops: Vec::new(),
}; };
*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")]
@ -321,10 +312,8 @@ impl GameDownloadAgent {
} }
} }
for (_, bucket) in current_buckets.into_iter() { if !current_bucket.drops.is_empty() {
if !bucket.drops.is_empty() { buckets.push(current_bucket);
buckets.push(bucket);
}
} }
info!("buckets: {}", buckets.len()); info!("buckets: {}", buckets.len());
@ -359,46 +348,27 @@ 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()?;
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_contexts = &download_contexts; let download_context = &download_context.json::<DownloadContext>()?;
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() {
@ -430,11 +400,6 @@ 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, Serialize)] #[derive(Debug, Clone)]
// 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, Serialize)] #[derive(Debug, Clone)]
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; use std::path::{Path, PathBuf};
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.is_some(); let has_umu_installed = *UMU_LAUNCHER_EXECUTABLE == PathBuf::new();
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, info}; use log::debug;
use crate::{ use crate::{
AppState, AppState,
@ -31,29 +31,29 @@ impl ProcessHandler for NativeGameLauncher {
} }
} }
pub static UMU_LAUNCHER_EXECUTABLE: LazyLock<Option<PathBuf>> = LazyLock::new(|| { pub static UMU_LAUNCHER_EXECUTABLE: LazyLock<PathBuf> = LazyLock::new(|| {
let x = get_umu_executable(); let x = get_umu_executable();
info!("{:?}", &x); println!("{:?}", &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() -> Option<PathBuf> { fn get_umu_executable() -> PathBuf {
if check_executable_exists(UMU_BASE_LAUNCHER_EXECUTABLE) { if check_executable_exists(UMU_BASE_LAUNCHER_EXECUTABLE) {
return Some(PathBuf::from(UMU_BASE_LAUNCHER_EXECUTABLE)); return 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 Some(p); return p;
} }
} }
None PathBuf::new()
} }
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()).output(); let has_umu_installed = Command::new(exec).stdout(Stdio::null()).spawn();
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.as_ref().unwrap(), umu = &*UMU_LAUNCHER_EXECUTABLE,
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.3", "version": "0.3.2",
"identifier": "dev.drop.client", "identifier": "dev.drop.client",
"build": { "build": {
"beforeDevCommand": "yarn --cwd main dev --port 1432", "beforeDevCommand": "yarn --cwd main dev --port 1432",