mirror of
https://github.com/Drop-OSS/drop-app.git
synced 2025-11-13 16:22:43 +10:00
feat(process): better process management, including running state
This commit is contained in:
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="inline-flex">
|
<div class="inline-flex divide-x divide-zinc-900">
|
||||||
<button type="button" @click="() => buttonActions[props.status.type]()" :class="[
|
<button type="button" @click="() => buttonActions[props.status.type]()" :class="[
|
||||||
styles[props.status.type],
|
styles[props.status.type],
|
||||||
showDropdown ? 'rounded-l-md' : 'rounded-md',
|
showDropdown ? 'rounded-l-md' : 'rounded-md',
|
||||||
@ -10,10 +10,11 @@
|
|||||||
</button>
|
</button>
|
||||||
<Menu v-if="showDropdown" as="div" class="relative inline-block text-left grow">
|
<Menu v-if="showDropdown" as="div" class="relative inline-block text-left grow">
|
||||||
<div class="h-full">
|
<div class="h-full">
|
||||||
<MenuButton
|
<MenuButton :class="[
|
||||||
class="inline-flex w-full h-full justify-center items-center rounded-r-md bg-zinc-800 px-1 py-2 text-sm font-semibold text-zinc-100 shadow-sm ring-1 ring-inset ring-zinc-600 hover:bg-zinc-800">
|
styles[props.status.type],
|
||||||
|
'inline-flex w-full h-full justify-center items-center rounded-r-md px-1 py-2 text-sm font-semibold shadow-sm'
|
||||||
<ChevronDownIcon class="size-5 text-gray-400" aria-hidden="true" />
|
]">
|
||||||
|
<ChevronDownIcon class="size-5" aria-hidden="true" />
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -53,26 +54,22 @@ import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue'
|
|||||||
const props = defineProps<{ status: GameStatus }>();
|
const props = defineProps<{ status: GameStatus }>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "install"): void;
|
(e: "install"): void;
|
||||||
(e: "play"): void;
|
(e: "launch"): void;
|
||||||
(e: "queue"): void;
|
(e: "queue"): void;
|
||||||
(e: "uninstall"): void;
|
(e: "uninstall"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const showDropdown = computed(() => props.status.type === GameStatusEnum.Installed);
|
const showDropdown = computed(() => props.status.type === GameStatusEnum.Installed || props.status.type === GameStatusEnum.SetupRequired);
|
||||||
|
|
||||||
const styles: { [key in GameStatusEnum]: string } = {
|
const styles: { [key in GameStatusEnum]: string } = {
|
||||||
[GameStatusEnum.Remote]:
|
[GameStatusEnum.Remote]: "bg-blue-600 text-white hover:bg-blue-500 focus-visible:outline-blue-600",
|
||||||
"bg-blue-600 text-white hover:bg-blue-500 focus-visible:outline-blue-600",
|
[GameStatusEnum.Queued]: "bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700",
|
||||||
[GameStatusEnum.Queued]:
|
[GameStatusEnum.Downloading]: "bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700",
|
||||||
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700",
|
[GameStatusEnum.SetupRequired]: "bg-yellow-600 text-white hover:bg-yellow-500 focus-visible:outline-yellow-600",
|
||||||
[GameStatusEnum.Downloading]:
|
[GameStatusEnum.Installed]: "bg-green-600 text-white hover:bg-green-500 focus-visible:outline-green-600",
|
||||||
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700",
|
[GameStatusEnum.Updating]: "bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700",
|
||||||
[GameStatusEnum.SetupRequired]:
|
[GameStatusEnum.Uninstalling]: "bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700",
|
||||||
"bg-yellow-600 text-white hover:bg-yellow-500 focus-visible:outline-yellow-600",
|
[GameStatusEnum.Running]: "bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700"
|
||||||
[GameStatusEnum.Installed]:
|
|
||||||
"bg-green-600 text-white hover:bg-green-500 focus-visible:outline-green-600",
|
|
||||||
[GameStatusEnum.Updating]: "",
|
|
||||||
[GameStatusEnum.Uninstalling]: "",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const buttonNames: { [key in GameStatusEnum]: string } = {
|
const buttonNames: { [key in GameStatusEnum]: string } = {
|
||||||
@ -83,6 +80,7 @@ const buttonNames: { [key in GameStatusEnum]: string } = {
|
|||||||
[GameStatusEnum.Installed]: "Play",
|
[GameStatusEnum.Installed]: "Play",
|
||||||
[GameStatusEnum.Updating]: "Updating",
|
[GameStatusEnum.Updating]: "Updating",
|
||||||
[GameStatusEnum.Uninstalling]: "Uninstalling",
|
[GameStatusEnum.Uninstalling]: "Uninstalling",
|
||||||
|
[GameStatusEnum.Running]: "Running"
|
||||||
};
|
};
|
||||||
|
|
||||||
const buttonIcons: { [key in GameStatusEnum]: Component } = {
|
const buttonIcons: { [key in GameStatusEnum]: Component } = {
|
||||||
@ -93,15 +91,17 @@ const buttonIcons: { [key in GameStatusEnum]: Component } = {
|
|||||||
[GameStatusEnum.Installed]: PlayIcon,
|
[GameStatusEnum.Installed]: PlayIcon,
|
||||||
[GameStatusEnum.Updating]: ArrowDownTrayIcon,
|
[GameStatusEnum.Updating]: ArrowDownTrayIcon,
|
||||||
[GameStatusEnum.Uninstalling]: TrashIcon,
|
[GameStatusEnum.Uninstalling]: TrashIcon,
|
||||||
|
[GameStatusEnum.Running]: PlayIcon
|
||||||
};
|
};
|
||||||
|
|
||||||
const buttonActions: { [key in GameStatusEnum]: () => void } = {
|
const buttonActions: { [key in GameStatusEnum]: () => void } = {
|
||||||
[GameStatusEnum.Remote]: () => emit("install"),
|
[GameStatusEnum.Remote]: () => emit("install"),
|
||||||
[GameStatusEnum.Queued]: () => emit("queue"),
|
[GameStatusEnum.Queued]: () => emit("queue"),
|
||||||
[GameStatusEnum.Downloading]: () => emit("queue"),
|
[GameStatusEnum.Downloading]: () => emit("queue"),
|
||||||
[GameStatusEnum.SetupRequired]: () => { },
|
[GameStatusEnum.SetupRequired]: () => emit("launch"),
|
||||||
[GameStatusEnum.Installed]: () => emit("play"),
|
[GameStatusEnum.Installed]: () => emit("launch"),
|
||||||
[GameStatusEnum.Updating]: () => emit("queue"),
|
[GameStatusEnum.Updating]: () => emit("queue"),
|
||||||
[GameStatusEnum.Uninstalling]: () => { },
|
[GameStatusEnum.Uninstalling]: () => { },
|
||||||
|
[GameStatusEnum.Running]: () => { }
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
<div class="h-full flex flex-row gap-x-4 items-stretch">
|
<div class="h-full flex flex-row gap-x-4 items-stretch">
|
||||||
<GameStatusButton
|
<GameStatusButton
|
||||||
@install="() => installFlow()"
|
@install="() => installFlow()"
|
||||||
@play="() => play()"
|
@launch="() => launch()"
|
||||||
@queue="() => queue()"
|
@queue="() => queue()"
|
||||||
@uninstall="() => uninstall()"
|
@uninstall="() => uninstall()"
|
||||||
:status="status"
|
:status="status"
|
||||||
@ -390,7 +390,7 @@ async function install() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function play() {
|
async function launch() {
|
||||||
try {
|
try {
|
||||||
await invoke("launch_game", { gameId: game.value.id });
|
await invoke("launch_game", { gameId: game.value.id });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@ -47,6 +47,7 @@ pub enum GameTransientStatus {
|
|||||||
Downloading { version_name: String },
|
Downloading { version_name: String },
|
||||||
Uninstalling {},
|
Uninstalling {},
|
||||||
Updating { version_name: String },
|
Updating { version_name: String },
|
||||||
|
Running {},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
|||||||
@ -221,11 +221,6 @@ impl GameDownloadAgent {
|
|||||||
|
|
||||||
*self.completed_contexts.lock().unwrap() = self.stored_manifest.get_completed_contexts();
|
*self.completed_contexts.lock().unwrap() = self.stored_manifest.get_completed_contexts();
|
||||||
|
|
||||||
info!(
|
|
||||||
"Completed contexts: {:?}",
|
|
||||||
*self.completed_contexts.lock().unwrap()
|
|
||||||
);
|
|
||||||
|
|
||||||
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));
|
||||||
|
|
||||||
@ -275,6 +270,8 @@ impl GameDownloadAgent {
|
|||||||
pool.scope(move |scope| {
|
pool.scope(move |scope| {
|
||||||
let completed_lock = self.completed_contexts.lock().unwrap();
|
let completed_lock = self.completed_contexts.lock().unwrap();
|
||||||
|
|
||||||
|
let count = self.contexts.len();
|
||||||
|
|
||||||
for (index, context) in self.contexts.iter().enumerate() {
|
for (index, context) in self.contexts.iter().enumerate() {
|
||||||
let progress = self.progress.get(index); // Clone arcs
|
let progress = self.progress.get(index); // Clone arcs
|
||||||
let progress_handle = ProgressHandle::new(progress, self.progress.clone());
|
let progress_handle = ProgressHandle::new(progress, self.progress.clone());
|
||||||
|
|||||||
@ -19,7 +19,7 @@ pub struct ProgressObject {
|
|||||||
sender: Sender<DownloadManagerSignal>,
|
sender: Sender<DownloadManagerSignal>,
|
||||||
|
|
||||||
points_towards_update: Arc<AtomicUsize>,
|
points_towards_update: Arc<AtomicUsize>,
|
||||||
points_to_push_update: Arc<Mutex<usize>>,
|
points_to_push_update: Arc<AtomicUsize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ProgressHandle {
|
pub struct ProgressHandle {
|
||||||
@ -58,7 +58,7 @@ impl ProgressObject {
|
|||||||
sender,
|
sender,
|
||||||
|
|
||||||
points_towards_update: Arc::new(AtomicUsize::new(0)),
|
points_towards_update: Arc::new(AtomicUsize::new(0)),
|
||||||
points_to_push_update: Arc::new(Mutex::new(points_to_push_update)),
|
points_to_push_update: Arc::new(AtomicUsize::new(points_to_push_update)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,9 +67,7 @@ impl ProgressObject {
|
|||||||
.points_towards_update
|
.points_towards_update
|
||||||
.fetch_add(amount_added, Ordering::Relaxed);
|
.fetch_add(amount_added, Ordering::Relaxed);
|
||||||
|
|
||||||
let to_update_handle = self.points_to_push_update.lock().unwrap();
|
let to_update = self.points_to_push_update.fetch_add(0, Ordering::Relaxed);
|
||||||
let to_update = *to_update_handle;
|
|
||||||
drop(to_update_handle);
|
|
||||||
|
|
||||||
if current_amount < to_update {
|
if current_amount < to_update {
|
||||||
return;
|
return;
|
||||||
@ -95,7 +93,8 @@ impl ProgressObject {
|
|||||||
}
|
}
|
||||||
pub fn set_max(&self, new_max: usize) {
|
pub fn set_max(&self, new_max: usize) {
|
||||||
*self.max.lock().unwrap() = new_max;
|
*self.max.lock().unwrap() = new_max;
|
||||||
*self.points_to_push_update.lock().unwrap() = new_max / PROGRESS_UPDATES;
|
self.points_to_push_update
|
||||||
|
.store(new_max / PROGRESS_UPDATES, Ordering::Relaxed);
|
||||||
info!("points to push update: {}", new_max / PROGRESS_UPDATES);
|
info!("points to push update: {}", new_max / PROGRESS_UPDATES);
|
||||||
}
|
}
|
||||||
pub fn set_size(&self, length: usize) {
|
pub fn set_size(&self, length: usize) {
|
||||||
|
|||||||
@ -22,7 +22,9 @@ use downloads::download_manager::DownloadManager;
|
|||||||
use downloads::download_manager_builder::DownloadManagerBuilder;
|
use downloads::download_manager_builder::DownloadManagerBuilder;
|
||||||
use http::Response;
|
use http::Response;
|
||||||
use http::{header::*, response::Builder as ResponseBuilder};
|
use http::{header::*, response::Builder as ResponseBuilder};
|
||||||
use library::{fetch_game, fetch_game_status, fetch_game_verion_options, fetch_library, uninstall_game, Game};
|
use library::{
|
||||||
|
fetch_game, fetch_game_status, fetch_game_verion_options, fetch_library, uninstall_game, Game,
|
||||||
|
};
|
||||||
use log::{debug, info, warn, LevelFilter};
|
use log::{debug, info, warn, LevelFilter};
|
||||||
use log4rs::append::console::ConsoleAppender;
|
use log4rs::append::console::ConsoleAppender;
|
||||||
use log4rs::append::file::FileAppender;
|
use log4rs::append::file::FileAppender;
|
||||||
@ -115,8 +117,8 @@ fn setup(handle: AppHandle) -> AppState<'static> {
|
|||||||
log4rs::init_config(config).unwrap();
|
log4rs::init_config(config).unwrap();
|
||||||
|
|
||||||
let games = HashMap::new();
|
let games = HashMap::new();
|
||||||
let download_manager = Arc::new(DownloadManagerBuilder::build(handle));
|
let download_manager = Arc::new(DownloadManagerBuilder::build(handle.clone()));
|
||||||
let process_manager = Arc::new(Mutex::new(ProcessManager::new()));
|
let process_manager = Arc::new(Mutex::new(ProcessManager::new(handle.clone())));
|
||||||
let compat_manager = Arc::new(Mutex::new(CompatibilityManager::new()));
|
let compat_manager = Arc::new(Mutex::new(CompatibilityManager::new()));
|
||||||
|
|
||||||
debug!("Checking if database is set up");
|
debug!("Checking if database is set up");
|
||||||
|
|||||||
@ -10,7 +10,7 @@ pub fn launch_game(
|
|||||||
let state_lock = state.lock().unwrap();
|
let state_lock = state.lock().unwrap();
|
||||||
let mut process_manager_lock = state_lock.process_manager.lock().unwrap();
|
let mut process_manager_lock = state_lock.process_manager.lock().unwrap();
|
||||||
|
|
||||||
process_manager_lock.launch_game(game_id)?;
|
process_manager_lock.launch_process(game_id)?;
|
||||||
|
|
||||||
drop(process_manager_lock);
|
drop(process_manager_lock);
|
||||||
drop(state_lock);
|
drop(state_lock);
|
||||||
|
|||||||
@ -2,27 +2,34 @@ use std::{
|
|||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fs::{File, OpenOptions},
|
fs::{File, OpenOptions},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::{Child, Command},
|
process::{Child, Command, ExitStatus},
|
||||||
sync::LazyLock,
|
sync::{Arc, LazyLock, Mutex},
|
||||||
|
thread::spawn,
|
||||||
};
|
};
|
||||||
|
|
||||||
use log::info;
|
use http::version;
|
||||||
|
use log::{info, warn};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tauri::{AppHandle, Emitter, Manager};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
db::{GameStatus, DATA_ROOT_DIR},
|
db::{GameStatus, GameTransientStatus, DATA_ROOT_DIR},
|
||||||
DB,
|
library::push_game_update,
|
||||||
|
process::process_manager,
|
||||||
|
state::GameStatusManager,
|
||||||
|
AppState, DB,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ProcessManager<'a> {
|
pub struct ProcessManager<'a> {
|
||||||
current_platform: Platform,
|
current_platform: Platform,
|
||||||
log_output_dir: PathBuf,
|
log_output_dir: PathBuf,
|
||||||
processes: HashMap<String, Child>,
|
processes: HashMap<String, Arc<Mutex<Child>>>,
|
||||||
|
app_handle: AppHandle,
|
||||||
game_launchers: HashMap<(Platform, Platform), &'a (dyn ProcessHandler + Sync + Send + 'static)>,
|
game_launchers: HashMap<(Platform, Platform), &'a (dyn ProcessHandler + Sync + Send + 'static)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProcessManager<'_> {
|
impl ProcessManager<'_> {
|
||||||
pub fn new() -> Self {
|
pub fn new(app_handle: AppHandle) -> Self {
|
||||||
let root_dir_lock = DATA_ROOT_DIR.lock().unwrap();
|
let root_dir_lock = DATA_ROOT_DIR.lock().unwrap();
|
||||||
let log_output_dir = root_dir_lock.join("logs");
|
let log_output_dir = root_dir_lock.join("logs");
|
||||||
drop(root_dir_lock);
|
drop(root_dir_lock);
|
||||||
@ -34,6 +41,7 @@ impl ProcessManager<'_> {
|
|||||||
Platform::Linux
|
Platform::Linux
|
||||||
},
|
},
|
||||||
|
|
||||||
|
app_handle,
|
||||||
processes: HashMap::new(),
|
processes: HashMap::new(),
|
||||||
log_output_dir,
|
log_output_dir,
|
||||||
game_launchers: HashMap::from([
|
game_launchers: HashMap::from([
|
||||||
@ -48,13 +56,13 @@ impl ProcessManager<'_> {
|
|||||||
),
|
),
|
||||||
(
|
(
|
||||||
(Platform::Linux, Platform::Windows),
|
(Platform::Linux, Platform::Windows),
|
||||||
&UMULauncher {} as &(dyn ProcessHandler + Sync + Send + 'static)
|
&UMULauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
|
||||||
)
|
),
|
||||||
]),
|
]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_command(&self, install_dir: &String, raw_command: String) -> (String, Vec<String>) {
|
fn process_command(&self, install_dir: &String, raw_command: String) -> (PathBuf, Vec<String>) {
|
||||||
let command_components = raw_command.split(" ").collect::<Vec<&str>>();
|
let command_components = raw_command.split(" ").collect::<Vec<&str>>();
|
||||||
let root = command_components[0].to_string();
|
let root = command_components[0].to_string();
|
||||||
|
|
||||||
@ -65,7 +73,51 @@ impl ProcessManager<'_> {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|v| v.to_string())
|
.map(|v| v.to_string())
|
||||||
.collect();
|
.collect();
|
||||||
(absolute_exe.to_str().unwrap().to_owned(), args)
|
(absolute_exe, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_process_finish(&mut self, game_id: String, result: Result<ExitStatus, std::io::Error>) {
|
||||||
|
if !self.processes.contains_key(&game_id) {
|
||||||
|
warn!("process on_finish was called, but game_id is no longer valid. finished with result: {:?}", result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("process for {} exited with {:?}", game_id, result);
|
||||||
|
|
||||||
|
self.processes.remove(&game_id);
|
||||||
|
|
||||||
|
let mut db_handle = DB.borrow_data_mut().unwrap();
|
||||||
|
db_handle.games.transient_statuses.remove(&game_id);
|
||||||
|
|
||||||
|
let current_state = db_handle.games.statuses.get(&game_id).cloned();
|
||||||
|
if let Some(saved_state) = current_state {
|
||||||
|
match saved_state {
|
||||||
|
GameStatus::SetupRequired {
|
||||||
|
version_name,
|
||||||
|
install_dir,
|
||||||
|
} => {
|
||||||
|
if let Some(exit_code) = result.ok() {
|
||||||
|
if exit_code.success() {
|
||||||
|
db_handle.games.statuses.insert(
|
||||||
|
game_id.clone(),
|
||||||
|
GameStatus::Installed {
|
||||||
|
version_name: version_name.to_string(),
|
||||||
|
install_dir: install_dir.to_string(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drop(db_handle);
|
||||||
|
|
||||||
|
let status = GameStatusManager::fetch_state(&game_id);
|
||||||
|
|
||||||
|
push_game_update(&self.app_handle, game_id.clone(), status);
|
||||||
|
|
||||||
|
// TODO better management
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn valid_platform(&self, platform: &Platform) -> Result<bool, String> {
|
pub fn valid_platform(&self, platform: &Platform) -> Result<bool, String> {
|
||||||
@ -75,26 +127,36 @@ impl ProcessManager<'_> {
|
|||||||
.contains_key(&(current.clone(), platform.clone())))
|
.contains_key(&(current.clone(), platform.clone())))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn launch_game(&mut self, game_id: String) -> Result<(), String> {
|
pub fn launch_process(&mut self, game_id: String) -> Result<(), String> {
|
||||||
if self.processes.contains_key(&game_id) {
|
if self.processes.contains_key(&game_id) {
|
||||||
return Err("Game or setup is already running.".to_owned());
|
return Err("Game or setup is already running.".to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
let db_lock = DB.borrow_data().unwrap();
|
let mut db_lock = DB.borrow_data_mut().unwrap();
|
||||||
let game_status = db_lock
|
let game_status = db_lock
|
||||||
.games
|
.games
|
||||||
.statuses
|
.statuses
|
||||||
.get(&game_id)
|
.get(&game_id)
|
||||||
.ok_or("Game not installed")?;
|
.ok_or("Game not installed")?;
|
||||||
|
|
||||||
let GameStatus::Installed {
|
let status_metadata: Option<(&String, &String)> = match game_status {
|
||||||
|
GameStatus::Installed {
|
||||||
version_name,
|
version_name,
|
||||||
install_dir,
|
install_dir,
|
||||||
} = game_status
|
} => Some((version_name, install_dir)),
|
||||||
else {
|
GameStatus::SetupRequired {
|
||||||
return Err("Game not installed.".to_owned());
|
version_name,
|
||||||
|
install_dir,
|
||||||
|
} => Some((version_name, install_dir)),
|
||||||
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if status_metadata.is_none() {
|
||||||
|
return Err("Game has not been downloaded.".to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
let (version_name, install_dir) = status_metadata.unwrap();
|
||||||
|
|
||||||
let game_version = db_lock
|
let game_version = db_lock
|
||||||
.games
|
.games
|
||||||
.versions
|
.versions
|
||||||
@ -103,10 +165,27 @@ impl ProcessManager<'_> {
|
|||||||
.get(version_name)
|
.get(version_name)
|
||||||
.ok_or("Invalid version name".to_owned())?;
|
.ok_or("Invalid version name".to_owned())?;
|
||||||
|
|
||||||
let (command, args) =
|
let raw_command: String = match game_status {
|
||||||
self.process_command(install_dir, game_version.launch_command.clone());
|
GameStatus::Installed {
|
||||||
|
version_name: _,
|
||||||
|
install_dir: _,
|
||||||
|
} => game_version.launch_command.clone(),
|
||||||
|
GameStatus::SetupRequired {
|
||||||
|
version_name: _,
|
||||||
|
install_dir: _,
|
||||||
|
} => game_version.setup_command.clone(),
|
||||||
|
_ => panic!("unreachable code"),
|
||||||
|
};
|
||||||
|
|
||||||
info!("launching process {} in {}", command, install_dir);
|
let (command, args) = self.process_command(install_dir, raw_command);
|
||||||
|
|
||||||
|
let target_current_dir = command.parent().unwrap().to_str().unwrap();
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"launching process {} in {}",
|
||||||
|
command.to_str().unwrap(),
|
||||||
|
target_current_dir
|
||||||
|
);
|
||||||
|
|
||||||
let current_time = chrono::offset::Local::now();
|
let current_time = chrono::offset::Local::now();
|
||||||
let mut log_file = OpenOptions::new()
|
let mut log_file = OpenOptions::new()
|
||||||
@ -132,8 +211,6 @@ impl ProcessManager<'_> {
|
|||||||
)))
|
)))
|
||||||
.map_err(|v| v.to_string())?;
|
.map_err(|v| v.to_string())?;
|
||||||
|
|
||||||
info!("opened log file for {}", command);
|
|
||||||
|
|
||||||
let current_platform = self.current_platform.clone();
|
let current_platform = self.current_platform.clone();
|
||||||
let target_platform = game_version.platform.clone();
|
let target_platform = game_version.platform.clone();
|
||||||
|
|
||||||
@ -143,17 +220,53 @@ impl ProcessManager<'_> {
|
|||||||
.ok_or("Invalid version for this platform.")
|
.ok_or("Invalid version for this platform.")
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let launch_process = game_launcher.launch_game(
|
let launch_process = game_launcher.launch_process(
|
||||||
&game_id,
|
&game_id,
|
||||||
version_name,
|
version_name,
|
||||||
command,
|
command.to_str().unwrap().to_owned(),
|
||||||
args,
|
args,
|
||||||
install_dir,
|
&target_current_dir.to_string(),
|
||||||
log_file,
|
log_file,
|
||||||
error_file,
|
error_file,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
self.processes.insert(game_id, launch_process);
|
let launch_process_handle = Arc::new(Mutex::new(launch_process));
|
||||||
|
|
||||||
|
db_lock
|
||||||
|
.games
|
||||||
|
.transient_statuses
|
||||||
|
.insert(game_id.clone(), GameTransientStatus::Running {});
|
||||||
|
|
||||||
|
push_game_update(
|
||||||
|
&self.app_handle,
|
||||||
|
game_id.clone(),
|
||||||
|
(None, Some(GameTransientStatus::Running {})),
|
||||||
|
);
|
||||||
|
|
||||||
|
let wait_thread_handle = launch_process_handle.clone();
|
||||||
|
let wait_thread_apphandle = self.app_handle.clone();
|
||||||
|
let wait_thread_game_id = game_id.clone();
|
||||||
|
|
||||||
|
spawn(move || {
|
||||||
|
let mut child_handle = wait_thread_handle.lock().unwrap();
|
||||||
|
let result: Result<ExitStatus, std::io::Error> = child_handle.wait();
|
||||||
|
|
||||||
|
let app_state = wait_thread_apphandle.state::<Mutex<AppState>>();
|
||||||
|
let app_state_handle = app_state.lock().unwrap();
|
||||||
|
|
||||||
|
let mut process_manager_handle = app_state_handle.process_manager.lock().unwrap();
|
||||||
|
process_manager_handle.on_process_finish(wait_thread_game_id, result);
|
||||||
|
|
||||||
|
// As everything goes out of scope, they should get dropped
|
||||||
|
// But just to explicit about it
|
||||||
|
drop(process_manager_handle);
|
||||||
|
drop(app_state_handle);
|
||||||
|
drop(child_handle);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.processes.insert(game_id, launch_process_handle);
|
||||||
|
|
||||||
|
info!("finished spawning process");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -166,13 +279,13 @@ pub enum Platform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait ProcessHandler: Send + 'static {
|
pub trait ProcessHandler: Send + 'static {
|
||||||
fn launch_game(
|
fn launch_process(
|
||||||
&self,
|
&self,
|
||||||
game_id: &String,
|
game_id: &String,
|
||||||
version_name: &String,
|
version_name: &String,
|
||||||
command: String,
|
command: String,
|
||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
install_dir: &String,
|
current_dir: &String,
|
||||||
log_file: File,
|
log_file: File,
|
||||||
error_file: File,
|
error_file: File,
|
||||||
) -> Result<Child, String>;
|
) -> Result<Child, String>;
|
||||||
@ -180,18 +293,18 @@ pub trait ProcessHandler: Send + 'static {
|
|||||||
|
|
||||||
struct NativeGameLauncher;
|
struct NativeGameLauncher;
|
||||||
impl ProcessHandler for NativeGameLauncher {
|
impl ProcessHandler for NativeGameLauncher {
|
||||||
fn launch_game(
|
fn launch_process(
|
||||||
&self,
|
&self,
|
||||||
game_id: &String,
|
game_id: &String,
|
||||||
version_name: &String,
|
version_name: &String,
|
||||||
command: String,
|
command: String,
|
||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
install_dir: &String,
|
current_dir: &String,
|
||||||
log_file: File,
|
log_file: File,
|
||||||
error_file: File,
|
error_file: File,
|
||||||
) -> Result<Child, String> {
|
) -> Result<Child, String> {
|
||||||
Command::new(command)
|
Command::new(command)
|
||||||
.current_dir(install_dir)
|
.current_dir(current_dir)
|
||||||
.stdout(log_file)
|
.stdout(log_file)
|
||||||
.stderr(error_file)
|
.stderr(error_file)
|
||||||
.args(args)
|
.args(args)
|
||||||
@ -202,13 +315,13 @@ impl ProcessHandler for NativeGameLauncher {
|
|||||||
|
|
||||||
struct UMULauncher;
|
struct UMULauncher;
|
||||||
impl ProcessHandler for UMULauncher {
|
impl ProcessHandler for UMULauncher {
|
||||||
fn launch_game(
|
fn launch_process(
|
||||||
&self,
|
&self,
|
||||||
game_id: &String,
|
game_id: &String,
|
||||||
version_name: &String,
|
version_name: &String,
|
||||||
command: String,
|
command: String,
|
||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
install_dir: &String,
|
current_dir: &String,
|
||||||
log_file: File,
|
log_file: File,
|
||||||
error_file: File,
|
error_file: File,
|
||||||
) -> Result<Child, String> {
|
) -> Result<Child, String> {
|
||||||
|
|||||||
1
types.ts
1
types.ts
@ -52,6 +52,7 @@ export enum GameStatusEnum {
|
|||||||
Updating = "Updating",
|
Updating = "Updating",
|
||||||
Uninstalling = "Uninstalling",
|
Uninstalling = "Uninstalling",
|
||||||
SetupRequired = "SetupRequired",
|
SetupRequired = "SetupRequired",
|
||||||
|
Running = "Running"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GameStatus = {
|
export type GameStatus = {
|
||||||
|
|||||||
Reference in New Issue
Block a user