mirror of
https://github.com/Drop-OSS/drop-app.git
synced 2025-11-23 21:21:18 +10:00
refactor: into rust workspaces
This commit is contained in:
19
src-tauri/drop-process/Cargo.toml
Normal file
19
src-tauri/drop-process/Cargo.toml
Normal file
@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "drop-process"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4.42"
|
||||
drop-database = { path = "../drop-database" }
|
||||
drop-errors = { path = "../drop-errors" }
|
||||
drop-native-library = { path = "../drop-native-library" }
|
||||
dynfmt = "0.1.5"
|
||||
log = "0.4.28"
|
||||
page_size = "0.6.0"
|
||||
serde = "1.0.220"
|
||||
shared_child = "1.1.1"
|
||||
sysinfo = "0.37.0"
|
||||
tauri = "2.8.5"
|
||||
tauri-plugin-opener = "2.5.0"
|
||||
|
||||
33
src-tauri/drop-process/src/format.rs
Normal file
33
src-tauri/drop-process/src/format.rs
Normal file
@ -0,0 +1,33 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use dynfmt::{Argument, FormatArgs};
|
||||
|
||||
pub struct DropFormatArgs {
|
||||
positional: Vec<String>,
|
||||
map: HashMap<&'static str, String>,
|
||||
}
|
||||
|
||||
impl DropFormatArgs {
|
||||
pub fn new(launch_string: String, working_dir: &String, executable_name: &String, absolute_executable_name: String) -> Self {
|
||||
let mut positional = Vec::new();
|
||||
let mut map: HashMap<&'static str, String> = HashMap::new();
|
||||
|
||||
positional.push(launch_string);
|
||||
|
||||
map.insert("dir", working_dir.to_string());
|
||||
map.insert("exe", executable_name.to_string());
|
||||
map.insert("abs_exe", absolute_executable_name);
|
||||
|
||||
Self { positional, map }
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatArgs for DropFormatArgs {
|
||||
fn get_index(&self, index: usize) -> Result<Option<dynfmt::Argument<'_>>, ()> {
|
||||
Ok(self.positional.get(index).map(|arg| arg as Argument<'_>))
|
||||
}
|
||||
|
||||
fn get_key(&self, key: &str) -> Result<Option<dynfmt::Argument<'_>>, ()> {
|
||||
Ok(self.map.get(key).map(|arg| arg as Argument<'_>))
|
||||
}
|
||||
}
|
||||
4
src-tauri/drop-process/src/lib.rs
Normal file
4
src-tauri/drop-process/src/lib.rs
Normal file
@ -0,0 +1,4 @@
|
||||
mod format;
|
||||
mod process_handlers;
|
||||
pub mod process_manager;
|
||||
pub mod utils;
|
||||
135
src-tauri/drop-process/src/process_handlers.rs
Normal file
135
src-tauri/drop-process/src/process_handlers.rs
Normal file
@ -0,0 +1,135 @@
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
path::PathBuf,
|
||||
process::{Command, Stdio},
|
||||
sync::LazyLock,
|
||||
};
|
||||
|
||||
use drop_database::{models::data::{Database, DownloadableMetadata, GameVersion}, process::Platform};
|
||||
use log::{debug, info};
|
||||
|
||||
use crate::process_manager::ProcessHandler;
|
||||
|
||||
|
||||
pub struct NativeGameLauncher;
|
||||
impl ProcessHandler for NativeGameLauncher {
|
||||
fn create_launch_process(
|
||||
&self,
|
||||
_meta: &DownloadableMetadata,
|
||||
launch_command: String,
|
||||
args: Vec<String>,
|
||||
_game_version: &GameVersion,
|
||||
_current_dir: &str,
|
||||
) -> String {
|
||||
format!("\"{}\" {}", launch_command, args.join(" "))
|
||||
}
|
||||
|
||||
fn valid_for_platform(&self, _db: &Database, _target: &Platform) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
impl ProcessHandler for UMULauncher {
|
||||
fn create_launch_process(
|
||||
&self,
|
||||
_meta: &DownloadableMetadata,
|
||||
launch_command: String,
|
||||
args: Vec<String>,
|
||||
game_version: &GameVersion,
|
||||
_current_dir: &str,
|
||||
) -> String {
|
||||
debug!("Game override: \"{:?}\"", &game_version.umu_id_override);
|
||||
let game_id = match &game_version.umu_id_override {
|
||||
Some(game_override) => {
|
||||
if game_override.is_empty() {
|
||||
game_version.game_id.clone()
|
||||
} else {
|
||||
game_override.clone()
|
||||
}
|
||||
}
|
||||
None => game_version.game_id.clone(),
|
||||
};
|
||||
format!(
|
||||
"GAMEID={game_id} {umu:?} \"{launch}\" {args}",
|
||||
umu = UMU_LAUNCHER_EXECUTABLE.as_ref().unwrap(),
|
||||
launch = launch_command,
|
||||
args = args.join(" ")
|
||||
)
|
||||
}
|
||||
|
||||
fn valid_for_platform(&self, _db: &Database, _target: &Platform) -> bool {
|
||||
UMU_LAUNCHER_EXECUTABLE.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AsahiMuvmLauncher;
|
||||
impl ProcessHandler for AsahiMuvmLauncher {
|
||||
fn create_launch_process(
|
||||
&self,
|
||||
meta: &DownloadableMetadata,
|
||||
launch_command: String,
|
||||
args: Vec<String>,
|
||||
game_version: &GameVersion,
|
||||
current_dir: &str,
|
||||
) -> String {
|
||||
let umu_launcher = UMULauncher {};
|
||||
let umu_string = umu_launcher.create_launch_process(
|
||||
meta,
|
||||
launch_command,
|
||||
args,
|
||||
game_version,
|
||||
current_dir,
|
||||
);
|
||||
let mut args_cmd = umu_string
|
||||
.split("umu-run")
|
||||
.collect::<Vec<&str>>()
|
||||
.into_iter();
|
||||
let args = args_cmd.next().unwrap().trim();
|
||||
let cmd = format!("umu-run{}", args_cmd.next().unwrap());
|
||||
|
||||
format!("{args} muvm -- {cmd}")
|
||||
}
|
||||
|
||||
#[allow(unreachable_code)]
|
||||
#[allow(unused_variables)]
|
||||
fn valid_for_platform(&self, _db: &Database, _target: &Platform) -> bool {
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
return false;
|
||||
|
||||
#[cfg(not(target_arch = "aarch64"))]
|
||||
return false;
|
||||
|
||||
let page_size = page_size::get();
|
||||
if page_size != 16384 {
|
||||
return false;
|
||||
}
|
||||
|
||||
UMU_LAUNCHER_EXECUTABLE.is_some()
|
||||
}
|
||||
}
|
||||
410
src-tauri/drop-process/src/process_manager.rs
Normal file
410
src-tauri/drop-process/src/process_manager.rs
Normal file
@ -0,0 +1,410 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs::{OpenOptions, create_dir_all},
|
||||
io::{self},
|
||||
path::PathBuf,
|
||||
process::{Command, ExitStatus},
|
||||
str::FromStr,
|
||||
sync::{Arc, Mutex},
|
||||
thread::spawn,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
use drop_database::{borrow_db_checked, borrow_db_mut_checked, db::DATA_ROOT_DIR, models::data::{ApplicationTransientStatus, Database, DownloadType, DownloadableMetadata, GameDownloadStatus, GameVersion}, process::Platform, DB};
|
||||
use drop_errors::process_error::ProcessError;
|
||||
use drop_native_library::{library::push_game_update, state::GameStatusManager};
|
||||
use dynfmt::Format;
|
||||
use dynfmt::SimpleCurlyFormat;
|
||||
use log::{debug, info, warn};
|
||||
use shared_child::SharedChild;
|
||||
use tauri::{AppHandle, Emitter};
|
||||
use tauri_plugin_opener::OpenerExt;
|
||||
|
||||
use crate::{format::DropFormatArgs, process_handlers::{AsahiMuvmLauncher, NativeGameLauncher, UMULauncher}};
|
||||
|
||||
pub struct RunningProcess {
|
||||
handle: Arc<SharedChild>,
|
||||
start: SystemTime,
|
||||
manually_killed: bool,
|
||||
}
|
||||
|
||||
pub struct ProcessManager<'a> {
|
||||
current_platform: Platform,
|
||||
log_output_dir: PathBuf,
|
||||
processes: HashMap<String, RunningProcess>,
|
||||
app_handle: AppHandle,
|
||||
game_launchers: Vec<(
|
||||
(Platform, Platform),
|
||||
&'a (dyn ProcessHandler + Sync + Send + 'static),
|
||||
)>,
|
||||
}
|
||||
|
||||
impl ProcessManager<'_> {
|
||||
pub fn new(app_handle: AppHandle) -> Self {
|
||||
let log_output_dir = DATA_ROOT_DIR.join("logs");
|
||||
|
||||
ProcessManager {
|
||||
#[cfg(target_os = "windows")]
|
||||
current_platform: Platform::Windows,
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
current_platform: Platform::MacOs,
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
current_platform: Platform::Linux,
|
||||
|
||||
app_handle,
|
||||
processes: HashMap::new(),
|
||||
log_output_dir,
|
||||
game_launchers: vec![
|
||||
// Current platform to target platform
|
||||
(
|
||||
(Platform::Windows, Platform::Windows),
|
||||
&NativeGameLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
|
||||
),
|
||||
(
|
||||
(Platform::Linux, Platform::Linux),
|
||||
&NativeGameLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
|
||||
),
|
||||
(
|
||||
(Platform::MacOs, Platform::MacOs),
|
||||
&NativeGameLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
|
||||
),
|
||||
(
|
||||
(Platform::Linux, Platform::Windows),
|
||||
&AsahiMuvmLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
|
||||
),
|
||||
(
|
||||
(Platform::Linux, Platform::Windows),
|
||||
&UMULauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
|
||||
),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kill_game(&mut self, game_id: String) -> Result<(), io::Error> {
|
||||
match self.processes.get_mut(&game_id) {
|
||||
Some(process) => {
|
||||
process.manually_killed = true;
|
||||
process.handle.kill()?;
|
||||
process.handle.wait()?;
|
||||
Ok(())
|
||||
}
|
||||
None => Err(io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
"Game ID not running",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_log_dir(&self, game_id: String) -> PathBuf {
|
||||
self.log_output_dir.join(game_id)
|
||||
}
|
||||
|
||||
pub fn open_process_logs(&mut self, game_id: String) -> Result<(), ProcessError> {
|
||||
let dir = self.get_log_dir(game_id);
|
||||
self.app_handle
|
||||
.opener()
|
||||
.open_path(dir.to_str().unwrap(), None::<&str>)
|
||||
.map_err(ProcessError::OpenerError)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
debug!("process for {:?} exited with {:?}", &game_id, result);
|
||||
|
||||
let process = self.processes.remove(&game_id).unwrap();
|
||||
|
||||
let mut db_handle = borrow_db_mut_checked();
|
||||
let meta = db_handle
|
||||
.applications
|
||||
.installed_game_version
|
||||
.get(&game_id)
|
||||
.cloned()
|
||||
.unwrap();
|
||||
db_handle.applications.transient_statuses.remove(&meta);
|
||||
|
||||
let current_state = db_handle.applications.game_statuses.get(&game_id).cloned();
|
||||
if let Some(GameDownloadStatus::SetupRequired {
|
||||
version_name,
|
||||
install_dir,
|
||||
}) = current_state
|
||||
&& let Ok(exit_code) = result
|
||||
&& exit_code.success()
|
||||
{
|
||||
db_handle.applications.game_statuses.insert(
|
||||
game_id.clone(),
|
||||
GameDownloadStatus::Installed {
|
||||
version_name: version_name.to_string(),
|
||||
install_dir: install_dir.to_string(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let elapsed = process.start.elapsed().unwrap_or(Duration::ZERO);
|
||||
// If we started and ended really quickly, something might've gone wrong
|
||||
// Or if the status isn't 0
|
||||
// Or if it's an error
|
||||
if !process.manually_killed
|
||||
&& (elapsed.as_secs() <= 2 || result.is_err() || !result.unwrap().success())
|
||||
{
|
||||
warn!("drop detected that the game {game_id} may have failed to launch properly");
|
||||
let _ = self.app_handle.emit("launch_external_error", &game_id);
|
||||
}
|
||||
|
||||
// This is too many unwraps for me to be comfortable
|
||||
let version_data = db_handle
|
||||
.applications
|
||||
.game_versions
|
||||
.get(&game_id)
|
||||
.unwrap()
|
||||
.get(&meta.version.unwrap())
|
||||
.unwrap();
|
||||
|
||||
let status = GameStatusManager::fetch_state(&game_id, &db_handle);
|
||||
|
||||
push_game_update(
|
||||
&self.app_handle,
|
||||
&game_id,
|
||||
Some(version_data.clone()),
|
||||
status,
|
||||
);
|
||||
}
|
||||
|
||||
fn fetch_process_handler(
|
||||
&self,
|
||||
db_lock: &Database,
|
||||
target_platform: &Platform,
|
||||
) -> Result<&(dyn ProcessHandler + Send + Sync), ProcessError> {
|
||||
Ok(self
|
||||
.game_launchers
|
||||
.iter()
|
||||
.find(|e| {
|
||||
let (e_current, e_target) = e.0;
|
||||
e_current == self.current_platform
|
||||
&& e_target == *target_platform
|
||||
&& e.1.valid_for_platform(db_lock, target_platform)
|
||||
})
|
||||
.ok_or(ProcessError::InvalidPlatform)?
|
||||
.1)
|
||||
}
|
||||
|
||||
pub fn valid_platform(&self, platform: &Platform,) -> Result<bool, String> {
|
||||
let db_lock = borrow_db_checked();
|
||||
let process_handler = self.fetch_process_handler(&db_lock, platform);
|
||||
Ok(process_handler.is_ok())
|
||||
}
|
||||
|
||||
pub fn launch_process(
|
||||
&mut self,
|
||||
game_id: String,
|
||||
process_manager_lock: &'static Mutex<ProcessManager<'static>>,
|
||||
) -> Result<(), ProcessError> {
|
||||
if self.processes.contains_key(&game_id) {
|
||||
return Err(ProcessError::AlreadyRunning);
|
||||
}
|
||||
|
||||
let version = match DB
|
||||
.borrow_data()
|
||||
.unwrap()
|
||||
.applications
|
||||
.game_statuses
|
||||
.get(&game_id)
|
||||
.cloned()
|
||||
{
|
||||
Some(GameDownloadStatus::Installed { version_name, .. }) => version_name,
|
||||
Some(GameDownloadStatus::SetupRequired { version_name, .. }) => version_name,
|
||||
_ => return Err(ProcessError::NotInstalled),
|
||||
};
|
||||
let meta = DownloadableMetadata {
|
||||
id: game_id.clone(),
|
||||
version: Some(version.clone()),
|
||||
download_type: DownloadType::Game,
|
||||
};
|
||||
|
||||
let mut db_lock = borrow_db_mut_checked();
|
||||
|
||||
let game_status = db_lock
|
||||
.applications
|
||||
.game_statuses
|
||||
.get(&game_id)
|
||||
.ok_or(ProcessError::NotInstalled)?;
|
||||
|
||||
let (version_name, install_dir) = match game_status {
|
||||
GameDownloadStatus::Installed {
|
||||
version_name,
|
||||
install_dir,
|
||||
} => (version_name, install_dir),
|
||||
GameDownloadStatus::SetupRequired {
|
||||
version_name,
|
||||
install_dir,
|
||||
} => (version_name, install_dir),
|
||||
_ => return Err(ProcessError::NotInstalled),
|
||||
};
|
||||
|
||||
debug!(
|
||||
"Launching process {:?} with version {:?}",
|
||||
&game_id,
|
||||
db_lock.applications.game_versions.get(&game_id).unwrap()
|
||||
);
|
||||
|
||||
let game_version = db_lock
|
||||
.applications
|
||||
.game_versions
|
||||
.get(&game_id)
|
||||
.ok_or(ProcessError::InvalidID)?
|
||||
.get(version_name)
|
||||
.ok_or(ProcessError::InvalidVersion)?;
|
||||
|
||||
// TODO: refactor this path with open_process_logs
|
||||
let game_log_folder = &self.get_log_dir(game_id);
|
||||
create_dir_all(game_log_folder).map_err(ProcessError::IOError)?;
|
||||
|
||||
let current_time = chrono::offset::Local::now();
|
||||
let log_file = OpenOptions::new()
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.read(true)
|
||||
.create(true)
|
||||
.open(game_log_folder.join(format!("{}-{}.log", &version, current_time.timestamp())))
|
||||
.map_err(ProcessError::IOError)?;
|
||||
|
||||
let error_file = OpenOptions::new()
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.read(true)
|
||||
.create(true)
|
||||
.open(game_log_folder.join(format!(
|
||||
"{}-{}-error.log",
|
||||
&version,
|
||||
current_time.timestamp()
|
||||
)))
|
||||
.map_err(ProcessError::IOError)?;
|
||||
|
||||
let target_platform = game_version.platform;
|
||||
|
||||
let process_handler = self.fetch_process_handler(&db_lock, &target_platform)?;
|
||||
|
||||
let (launch, args) = match game_status {
|
||||
GameDownloadStatus::Installed {
|
||||
version_name: _,
|
||||
install_dir: _,
|
||||
} => (&game_version.launch_command, &game_version.launch_args),
|
||||
GameDownloadStatus::SetupRequired {
|
||||
version_name: _,
|
||||
install_dir: _,
|
||||
} => (&game_version.setup_command, &game_version.setup_args),
|
||||
GameDownloadStatus::PartiallyInstalled {
|
||||
version_name: _,
|
||||
install_dir: _,
|
||||
} => unreachable!("Game registered as 'Partially Installed'"),
|
||||
GameDownloadStatus::Remote {} => unreachable!("Game registered as 'Remote'"),
|
||||
};
|
||||
|
||||
let launch = PathBuf::from_str(install_dir).unwrap().join(launch);
|
||||
let launch = launch.to_str().unwrap();
|
||||
|
||||
let launch_string = process_handler.create_launch_process(
|
||||
&meta,
|
||||
launch.to_string(),
|
||||
args.clone(),
|
||||
game_version,
|
||||
install_dir,
|
||||
);
|
||||
|
||||
let format_args = DropFormatArgs::new(
|
||||
launch_string,
|
||||
install_dir,
|
||||
&game_version.launch_command,
|
||||
launch.to_string(),
|
||||
);
|
||||
|
||||
let launch_string = SimpleCurlyFormat
|
||||
.format(&game_version.launch_command_template, format_args)
|
||||
.map_err(|e| ProcessError::FormatError(e.to_string()))?
|
||||
.to_string();
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use std::os::windows::process::CommandExt;
|
||||
#[cfg(target_os = "windows")]
|
||||
let mut command = Command::new("cmd");
|
||||
#[cfg(target_os = "windows")]
|
||||
command.raw_arg(format!("/C \"{}\"", &launch_string));
|
||||
|
||||
info!("launching (in {install_dir}): {launch_string}",);
|
||||
|
||||
#[cfg(unix)]
|
||||
let mut command: Command = Command::new("sh");
|
||||
#[cfg(unix)]
|
||||
command.args(vec!["-c", &launch_string]);
|
||||
|
||||
debug!("final launch string:\n\n{launch_string}\n");
|
||||
|
||||
command
|
||||
.stderr(error_file)
|
||||
.stdout(log_file)
|
||||
.env_remove("RUST_LOG")
|
||||
.current_dir(install_dir);
|
||||
|
||||
let child = command.spawn().map_err(ProcessError::IOError)?;
|
||||
|
||||
let launch_process_handle =
|
||||
Arc::new(SharedChild::new(child).map_err(ProcessError::IOError)?);
|
||||
|
||||
db_lock
|
||||
.applications
|
||||
.transient_statuses
|
||||
.insert(meta.clone(), ApplicationTransientStatus::Running {});
|
||||
|
||||
push_game_update(
|
||||
&self.app_handle,
|
||||
&meta.id,
|
||||
None,
|
||||
(None, Some(ApplicationTransientStatus::Running {})),
|
||||
);
|
||||
|
||||
let wait_thread_handle = launch_process_handle.clone();
|
||||
let wait_thread_game_id = meta.clone();
|
||||
|
||||
spawn(move || {
|
||||
let result: Result<ExitStatus, std::io::Error> = launch_process_handle.wait();
|
||||
|
||||
let mut process_manager_handle = process_manager_lock.lock().unwrap();
|
||||
process_manager_handle.on_process_finish(wait_thread_game_id.id, result);
|
||||
|
||||
// As everything goes out of scope, they should get dropped
|
||||
// But just to explicit about it
|
||||
drop(process_manager_handle);
|
||||
});
|
||||
|
||||
self.processes.insert(
|
||||
meta.id,
|
||||
RunningProcess {
|
||||
handle: wait_thread_handle,
|
||||
start: SystemTime::now(),
|
||||
manually_killed: false,
|
||||
},
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ProcessHandler: Send + 'static {
|
||||
fn create_launch_process(
|
||||
&self,
|
||||
meta: &DownloadableMetadata,
|
||||
launch_command: String,
|
||||
args: Vec<String>,
|
||||
game_version: &GameVersion,
|
||||
current_dir: &str,
|
||||
) -> String;
|
||||
|
||||
fn valid_for_platform(&self, db: &Database, target: &Platform) -> bool;
|
||||
}
|
||||
25
src-tauri/drop-process/src/utils.rs
Normal file
25
src-tauri/drop-process/src/utils.rs
Normal file
@ -0,0 +1,25 @@
|
||||
use std::{io, path::PathBuf, sync::Arc};
|
||||
|
||||
use drop_errors::application_download_error::ApplicationDownloadError;
|
||||
use sysinfo::{Disk, DiskRefreshKind, Disks};
|
||||
|
||||
pub fn get_disk_available(mount_point: PathBuf) -> Result<u64, ApplicationDownloadError> {
|
||||
let disks = Disks::new_with_refreshed_list_specifics(DiskRefreshKind::nothing().with_storage());
|
||||
|
||||
let mut disk_iter = disks.into_iter().collect::<Vec<&Disk>>();
|
||||
disk_iter.sort_by(|a, b| {
|
||||
b.mount_point()
|
||||
.to_string_lossy()
|
||||
.len()
|
||||
.cmp(&a.mount_point().to_string_lossy().len())
|
||||
});
|
||||
|
||||
for disk in disk_iter {
|
||||
if mount_point.starts_with(disk.mount_point()) {
|
||||
return Ok(disk.available_space());
|
||||
}
|
||||
}
|
||||
Err(ApplicationDownloadError::IoError(Arc::new(io::Error::other(
|
||||
"could not find disk of path",
|
||||
))))
|
||||
}
|
||||
Reference in New Issue
Block a user