Navbar wireup (#98)

* Navbar wireup

* Fix height issue

* Fix syncing

* working branch

* Added root directories to breadcrumbs

* fix jre detect

---------

Co-authored-by: Jai A <jaiagr+gpg@pm.me>
This commit is contained in:
Adrian O.V
2023-04-26 23:19:37 -04:00
committed by GitHub
parent f0b8a708a3
commit bda63d5d64
29 changed files with 631 additions and 299 deletions

4
Cargo.lock generated
View File

@@ -689,9 +689,9 @@ dependencies = [
[[package]] [[package]]
name = "daedalus" name = "daedalus"
version = "0.1.18" version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83c9c34a2d4904bcaa4cfa5f62b38c915c106fdc92a6a66276ae2bd5ba1b2527" checksum = "ab1ff8f873475996ff3d755659e5e0fbe5a2d02d6fc84ff2b625874a8c446973"
dependencies = [ dependencies = [
"bincode", "bincode",
"bytes", "bytes",

View File

@@ -19,7 +19,7 @@ zip = "0.5"
async_zip = { version = "0.0.13", features = ["full"] } async_zip = { version = "0.0.13", features = ["full"] }
chrono = { version = "0.4.19", features = ["serde"] } chrono = { version = "0.4.19", features = ["serde"] }
daedalus = { version = "0.1.18" } daedalus = { version = "0.1.20" }
dirs = "4.0" dirs = "4.0"
log = "0.4.14" log = "0.4.14"

View File

@@ -69,8 +69,8 @@ pub async fn get_optimal_jre_key(profile: &Profile) -> crate::Result<String> {
.map(|it| it.major_version) .map(|it| it.major_version)
.unwrap_or(0) .unwrap_or(0)
{ {
0..=16 => JAVA_8_KEY.to_string(), 0..=15 => JAVA_8_KEY.to_string(),
17 => JAVA_17_KEY.to_string(), 16..=17 => JAVA_17_KEY.to_string(),
_ => JAVA_18PLUS_KEY.to_string(), _ => JAVA_18PLUS_KEY.to_string(),
}; };
Ok(optimal_key) Ok(optimal_key)

View File

@@ -136,18 +136,26 @@ pub async fn install_pack_from_version_id(
None None
}; };
install_pack(file, icon, Some(version.project_id), Some(version.id)).await install_pack(
file,
icon,
Some(project.title),
Some(version.project_id),
Some(version.id),
)
.await
} }
pub async fn install_pack_from_file(path: PathBuf) -> crate::Result<PathBuf> { pub async fn install_pack_from_file(path: PathBuf) -> crate::Result<PathBuf> {
let file = fs::read(path).await?; let file = fs::read(path).await?;
install_pack(bytes::Bytes::from(file), None, None, None).await install_pack(bytes::Bytes::from(file), None, None, None, None).await
} }
async fn install_pack( async fn install_pack(
file: bytes::Bytes, file: bytes::Bytes,
icon: Option<PathBuf>, icon: Option<PathBuf>,
override_title: Option<String>,
project_id: Option<String>, project_id: Option<String>,
version_id: Option<String>, version_id: Option<String>,
) -> crate::Result<PathBuf> { ) -> crate::Result<PathBuf> {
@@ -192,7 +200,7 @@ async fn install_pack(
let mut game_version = None; let mut game_version = None;
let mut mod_loader = None; let mut mod_loader = None;
let mut loader_version = None; let mut loader_version = None;
for (key, value) in pack.dependencies { for (key, value) in &pack.dependencies {
match key { match key {
PackDependency::Forge => { PackDependency::Forge => {
mod_loader = Some(ModLoader::Forge); mod_loader = Some(ModLoader::Forge);
@@ -219,13 +227,11 @@ async fn install_pack(
.into()); .into());
}; };
let pack_name = pack.name.clone(); let profile_raw = crate::api::profile_create::profile_create(
override_title.unwrap_or_else(|| pack.name.clone()),
let profile = crate::api::profile_create::profile_create(
pack.name,
game_version.clone(), game_version.clone(),
mod_loader.unwrap_or(ModLoader::Vanilla), mod_loader.unwrap_or(ModLoader::Vanilla),
loader_version, loader_version.cloned(),
icon, icon,
Some(LinkedData { Some(LinkedData {
project_id: project_id.clone(), project_id: project_id.clone(),
@@ -234,141 +240,167 @@ async fn install_pack(
Some(true), Some(true),
) )
.await?; .await?;
let profile = profile_raw.clone();
let loading_bar = init_loading( let result = async {
LoadingBarType::PackDownload { let loading_bar = init_loading(
pack_name, LoadingBarType::PackDownload {
pack_id: project_id, pack_name: pack.name.clone(),
pack_version: version_id, pack_id: project_id,
}, pack_version: version_id,
100.0, },
"Downloading modpack...", 100.0,
) "Downloading modpack...",
.await?; )
let num_files = pack.files.len();
use futures::StreamExt;
loading_try_for_each_concurrent(
futures::stream::iter(pack.files.into_iter())
.map(Ok::<PackFile, crate::Error>),
None,
Some(&loading_bar),
80.0,
num_files,
None,
|project| {
let profile = profile.clone();
async move {
//TODO: Future update: prompt user for optional files in a modpack
if let Some(env) = project.env {
if env
.get(&EnvType::Client)
.map(|x| x == &SideType::Unsupported)
.unwrap_or(false)
{
return Ok(());
}
}
let file = fetch_mirrors(
&project
.downloads
.iter()
.map(|x| &**x)
.collect::<Vec<&str>>(),
project.hashes.get(&PackFileHash::Sha1).map(|x| &**x),
&state.io_semaphore,
)
.await?;
let path =
std::path::Path::new(&project.path).components().next();
if let Some(path) = path {
match path {
Component::CurDir | Component::Normal(_) => {
let path = profile.join(project.path);
write(&path, &file, &state.io_semaphore)
.await?;
}
_ => {}
};
}
Ok(())
}
},
)
.await?;
let extract_overrides = |overrides: String| async {
let reader = Cursor::new(&file);
let mut overrides_zip =
ZipFileReader::new(reader).await.map_err(|_| {
crate::Error::from(crate::ErrorKind::InputError(
"Failed extract overrides Zip".to_string(),
))
})?;
let profile = profile.clone();
async move {
for index in 0..overrides_zip.file().entries().len() {
let file = overrides_zip
.file()
.entries()
.get(index)
.unwrap()
.entry()
.clone();
let file_path = PathBuf::from(file.filename());
if file.filename().starts_with(&overrides)
&& !file.filename().ends_with('/')
{
// Reads the file into the 'content' variable
let mut content = Vec::new();
let mut reader = overrides_zip.entry(index).await?;
reader.read_to_end_checked(&mut content, &file).await?;
let mut new_path = PathBuf::new();
let components = file_path.components().skip(1);
for component in components {
new_path.push(component);
}
if new_path.file_name().is_some() {
write(
&profile.join(new_path),
&content,
&state.io_semaphore,
)
.await?;
}
}
}
Ok::<(), crate::Error>(())
}
.await
};
emit_loading(&loading_bar, 0.05, Some("Extracting overrides")).await?;
extract_overrides("overrides".to_string()).await?;
extract_overrides("client_overrides".to_string()).await?;
emit_loading(&loading_bar, 0.1, Some("Done extacting overrides"))
.await?; .await?;
super::profile::sync(&profile).await?; let num_files = pack.files.len();
use futures::StreamExt;
loading_try_for_each_concurrent(
futures::stream::iter(pack.files.into_iter())
.map(Ok::<PackFile, crate::Error>),
None,
Some(&loading_bar),
80.0,
num_files,
None,
|project| {
let profile = profile.clone();
async move {
//TODO: Future update: prompt user for optional files in a modpack
if let Some(env) = project.env {
if env
.get(&EnvType::Client)
.map(|x| x == &SideType::Unsupported)
.unwrap_or(false)
{
return Ok(());
}
}
if let Some(profile) = crate::api::profile::get(&profile).await? { let file = fetch_mirrors(
crate::launcher::install_minecraft(&profile, Some(loading_bar)) &project
.downloads
.iter()
.map(|x| &**x)
.collect::<Vec<&str>>(),
project
.hashes
.get(&PackFileHash::Sha1)
.map(|x| &**x),
&state.io_semaphore,
)
.await?;
let path = std::path::Path::new(&project.path)
.components()
.next();
if let Some(path) = path {
match path {
Component::CurDir | Component::Normal(_) => {
let path = profile.join(project.path);
write(&path, &file, &state.io_semaphore)
.await?;
}
_ => {}
};
}
Ok(())
}
},
)
.await?;
let extract_overrides = |overrides: String| async {
let reader = Cursor::new(&file);
let mut overrides_zip =
ZipFileReader::new(reader).await.map_err(|_| {
crate::Error::from(crate::ErrorKind::InputError(
"Failed extract overrides Zip".to_string(),
))
})?;
let profile = profile.clone();
async move {
for index in 0..overrides_zip.file().entries().len() {
let file = overrides_zip
.file()
.entries()
.get(index)
.unwrap()
.entry()
.clone();
let file_path = PathBuf::from(file.filename());
if file.filename().starts_with(&overrides)
&& !file.filename().ends_with('/')
{
// Reads the file into the 'content' variable
let mut content = Vec::new();
let mut reader = overrides_zip.entry(index).await?;
reader
.read_to_end_checked(&mut content, &file)
.await?;
let mut new_path = PathBuf::new();
let components = file_path.components().skip(1);
for component in components {
new_path.push(component);
}
if new_path.file_name().is_some() {
write(
&profile.join(new_path),
&content,
&state.io_semaphore,
)
.await?;
}
}
}
Ok::<(), crate::Error>(())
}
.await
};
emit_loading(&loading_bar, 0.05, Some("Extracting overrides"))
.await?; .await?;
} else { extract_overrides("overrides".to_string()).await?;
extract_overrides("client_overrides".to_string()).await?;
emit_loading(&loading_bar, 0.1, Some("Done extacting overrides")) emit_loading(&loading_bar, 0.1, Some("Done extacting overrides"))
.await?; .await?;
}
Ok(profile) if let Some(profile) = crate::api::profile::get(&profile).await? {
tokio::try_join!(
super::profile::sync(&profile.path),
crate::launcher::install_minecraft(
&profile,
Some(loading_bar)
),
)?;
} else {
emit_loading(
&loading_bar,
0.1,
Some("Done extacting overrides"),
)
.await?;
}
Ok::<PathBuf, crate::Error>(profile)
}
.await;
match result {
Ok(profile) => Ok(profile),
Err(err) => {
let _ = crate::api::profile::remove(&profile_raw).await;
Err(err)
}
}
} else { } else {
Err(crate::Error::from(crate::ErrorKind::InputError( Err(crate::Error::from(crate::ErrorKind::InputError(
"No pack manifest found in mrpack".to_string(), "No pack manifest found in mrpack".to_string(),

View File

@@ -136,6 +136,7 @@ pub fn get_jvm_arguments(
parsed_arguments.push(arg); parsed_arguments.push(arg);
} }
} }
parsed_arguments.push("-Dorg.lwjgl.util.Debug=true".to_string());
Ok(parsed_arguments) Ok(parsed_arguments)
} }

View File

@@ -264,7 +264,7 @@ pub async fn download_libraries(
library library
.natives .natives
.as_ref()? .as_ref()?
.get(&Os::native())?, .get(&Os::native_arch())?,
library library
.downloads .downloads
.as_ref()? .as_ref()?

View File

@@ -4,6 +4,7 @@ use crate::event::LoadingBarType;
use crate::{ use crate::{
process, process,
state::{self as st, MinecraftChild}, state::{self as st, MinecraftChild},
State,
}; };
use daedalus as d; use daedalus as d;
use dunce::canonicalize; use dunce::canonicalize;
@@ -55,7 +56,7 @@ pub async fn install_minecraft(
profile: &Profile, profile: &Profile,
existing_loading_bar: Option<Uuid>, existing_loading_bar: Option<Uuid>,
) -> crate::Result<()> { ) -> crate::Result<()> {
let state = st::State::get().await?; let state = State::get().await?;
let instance_path = &canonicalize(&profile.path)?; let instance_path = &canonicalize(&profile.path)?;
let metadata = state.metadata.read().await; let metadata = state.metadata.read().await;
@@ -98,7 +99,6 @@ pub async fn install_minecraft(
.await?; .await?;
download::download_minecraft(&state, &version_info, loading_bar).await?; download::download_minecraft(&state, &version_info, loading_bar).await?;
st::State::sync().await?;
let client_path = state let client_path = state
.directories .directories
@@ -201,7 +201,7 @@ pub async fn install_minecraft(
async { Ok(()) } async { Ok(()) }
}) })
.await?; .await?;
crate::api::profile::sync(&profile.path).await?; State::sync().await?;
Ok(()) Ok(())
} }
@@ -327,7 +327,7 @@ pub async fn launch_minecraft(
let mut state_children = state.children.write().await; let mut state_children = state.children.write().await;
state_children state_children
.insert_process( .insert_process(
uuid::Uuid::new_v4(), Uuid::new_v4(),
instance_path.to_path_buf(), instance_path.to_path_buf(),
command, command,
post_exit_hook, post_exit_hook,
@@ -337,9 +337,11 @@ pub async fn launch_minecraft(
fn clear_cargo_env_vals(command: &mut Command) -> &mut Command { fn clear_cargo_env_vals(command: &mut Command) -> &mut Command {
for (key, _) in std::env::vars() { for (key, _) in std::env::vars() {
if key.starts_with("CARGO") { command.env_remove(key);
command.env_remove(key);
} // if key.starts_with("CARGO") {
// command.env_remove(key);
// }
} }
command command
} }

View File

@@ -1,6 +1,7 @@
//! Theseus metadata //! Theseus metadata
use crate::data::DirectoryInfo; use crate::data::DirectoryInfo;
use crate::util::fetch::{read_json, write}; use crate::util::fetch::{read_json, write};
use crate::State;
use daedalus::{ use daedalus::{
minecraft::{fetch_version_manifest, VersionManifest as MinecraftManifest}, minecraft::{fetch_version_manifest, VersionManifest as MinecraftManifest},
modded::{ modded::{
@@ -96,7 +97,7 @@ impl Metadata {
pub async fn update() { pub async fn update() {
let res = async { let res = async {
let metadata_fetch = Metadata::fetch().await?; let metadata_fetch = Metadata::fetch().await?;
let state = crate::State::get().await?; let state = State::get().await?;
let metadata_path = let metadata_path =
state.directories.caches_meta_dir().join("metadata.json"); state.directories.caches_meta_dir().join("metadata.json");

View File

@@ -164,7 +164,8 @@ impl Profile {
let paths = self.get_profile_project_paths()?; let paths = self.get_profile_project_paths()?;
let projects = crate::state::infer_data_from_files( let projects = crate::state::infer_data_from_files(
&[(self.clone(), paths)], self.clone(),
paths,
state.directories.caches_dir(), state.directories.caches_dir(),
&state.io_semaphore, &state.io_semaphore,
) )
@@ -386,31 +387,28 @@ impl Profiles {
} }
} }
if !files.is_empty() { future::try_join_all(files.into_iter().map(
let inferred = super::projects::infer_data_from_files( |(profile, files)| async {
&files, let profile_path = profile.path.clone();
state.directories.caches_dir(), let inferred = super::projects::infer_data_from_files(
&state.io_semaphore, profile,
) files,
.await?; state.directories.caches_dir(),
let mut wipe_profiles = Vec::new(); &state.io_semaphore,
for (key, value) in inferred { )
if let Some((profile, _)) = .await?;
files.iter().find(|(_, files)| files.contains(&key))
let mut new_profiles = state.profiles.write().await;
if let Some(profile) = new_profiles.0.get_mut(&profile_path)
{ {
let mut new_profiles = state.profiles.write().await; profile.projects = inferred;
if let Some(profile) =
new_profiles.0.get_mut(&profile.path)
{
if !wipe_profiles.contains(&profile.path) {
profile.projects = HashMap::new();
wipe_profiles.push(profile.path.clone());
}
profile.projects.insert(key, value);
}
} }
} drop(new_profiles);
}
Ok::<(), crate::Error>(())
},
))
.await?;
Ok::<(), crate::Error>(()) Ok::<(), crate::Error>(())
} }

View File

@@ -1,7 +1,6 @@
//! Project management + inference //! Project management + inference
use crate::config::MODRINTH_API_URL; use crate::config::MODRINTH_API_URL;
use crate::data::ModLoader;
use crate::state::Profile; use crate::state::Profile;
use crate::util::fetch::{fetch_json, write_cached_icon}; use crate::util::fetch::{fetch_json, write_cached_icon};
use async_zip::tokio::read::fs::ZipFileReader; use async_zip::tokio::read::fs::ZipFileReader;
@@ -250,36 +249,51 @@ async fn read_icon_from_file(
} }
pub async fn infer_data_from_files( pub async fn infer_data_from_files(
paths: &[(Profile, Vec<PathBuf>)], profile: Profile,
paths: Vec<PathBuf>,
cache_dir: PathBuf, cache_dir: PathBuf,
io_semaphore: &RwLock<Semaphore>, io_semaphore: &RwLock<Semaphore>,
) -> crate::Result<HashMap<PathBuf, Project>> { ) -> crate::Result<HashMap<PathBuf, Project>> {
let mut file_path_hashes = HashMap::new(); let mut file_path_hashes = HashMap::new();
// TODO: Make this concurrent and use progressive hashing to avoid loading each JAR in memory // TODO: Make this concurrent and use progressive hashing to avoid loading each JAR in memory
for set in paths { for path in paths {
for path in &set.1 { let mut file = tokio::fs::File::open(path.clone()).await?;
let mut file = tokio::fs::File::open(path.clone()).await?;
let mut buffer = Vec::new(); let mut buffer = Vec::new();
file.read_to_end(&mut buffer).await?; file.read_to_end(&mut buffer).await?;
let hash = format!("{:x}", sha2::Sha512::digest(&buffer)); let hash = format!("{:x}", sha2::Sha512::digest(&buffer));
file_path_hashes.insert(hash, path.clone()); file_path_hashes.insert(hash, path.clone());
}
} }
let files: HashMap<String, ModrinthVersion> = fetch_json( let files_url = format!("{}version_files", MODRINTH_API_URL);
Method::POST, let updates_url = format!("{}version_files/update", MODRINTH_API_URL);
&format!("{}version_files", MODRINTH_API_URL), let (files, update_versions) = tokio::try_join!(
None, fetch_json::<HashMap<String, ModrinthVersion>>(
Some(json!({ Method::POST,
"hashes": file_path_hashes.keys().collect::<Vec<_>>(), &files_url,
"algorithm": "sha512", None,
})), Some(json!({
io_semaphore, "hashes": file_path_hashes.keys().collect::<Vec<_>>(),
) "algorithm": "sha512",
.await?; })),
io_semaphore,
),
fetch_json::<HashMap<String, ModrinthVersion>>(
Method::POST,
&updates_url,
None,
Some(json!({
"hashes": file_path_hashes.keys().collect::<Vec<_>>(),
"algorithm": "sha512",
"loaders": [profile.metadata.loader],
"game_versions": [profile.metadata.game_version]
})),
io_semaphore,
)
)?;
let projects: Vec<ModrinthProject> = fetch_json( let projects: Vec<ModrinthProject> = fetch_json(
Method::GET, Method::GET,
&format!( &format!(
@@ -297,6 +311,7 @@ pub async fn infer_data_from_files(
io_semaphore, io_semaphore,
) )
.await?; .await?;
let teams: Vec<ModrinthTeamMember> = fetch_json::< let teams: Vec<ModrinthTeamMember> = fetch_json::<
Vec<Vec<ModrinthTeamMember>>, Vec<Vec<ModrinthTeamMember>>,
>( >(
@@ -317,26 +332,6 @@ pub async fn infer_data_from_files(
.flatten() .flatten()
.collect(); .collect();
let mut update_versions: Vec<ModrinthVersion> = fetch_json(
Method::GET,
&format!(
"{}versions?ids={}",
MODRINTH_API_URL,
serde_json::to_string(
&projects
.iter()
.flat_map(|x| x.versions.clone())
.collect::<Vec<String>>()
)?
),
None,
None,
io_semaphore,
)
.await?;
update_versions.sort_by(|a, b| b.date_published.cmp(&a.date_published));
let mut return_projects = HashMap::new(); let mut return_projects = HashMap::new();
let mut further_analyze_projects: Vec<(String, PathBuf)> = Vec::new(); let mut further_analyze_projects: Vec<(String, PathBuf)> = Vec::new();
@@ -345,8 +340,6 @@ pub async fn infer_data_from_files(
if let Some(project) = if let Some(project) =
projects.iter().find(|x| version.project_id == x.id) projects.iter().find(|x| version.project_id == x.id)
{ {
let profile = paths.iter().find(|x| x.1.contains(&path));
let file_name = path let file_name = path
.file_name() .file_name()
.unwrap_or_default() .unwrap_or_default()
@@ -356,7 +349,6 @@ pub async fn infer_data_from_files(
return_projects.insert( return_projects.insert(
path, path,
Project { Project {
sha512: hash,
disabled: false, disabled: false,
metadata: ProjectMetadata::Modrinth { metadata: ProjectMetadata::Modrinth {
project: Box::new(project.clone()), project: Box::new(project.clone()),
@@ -366,48 +358,25 @@ pub async fn infer_data_from_files(
.filter(|x| x.team_id == project.team) .filter(|x| x.team_id == project.team)
.cloned() .cloned()
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
update_version: if let Some((profile, _)) = &profile update_version: if let Some(val) =
update_versions.get(&hash)
{ {
update_versions Some(Box::new(val.clone()))
.iter()
.find(|x| {
x.project_id == project.id
&& x.game_versions.contains(
&profile.metadata.game_version,
)
&& if profile.metadata.loader
== ModLoader::Vanilla
{
true
} else {
x.loaders.contains(
&profile
.metadata
.loader
.as_api_str()
.to_string(),
)
}
})
.cloned()
.map(Box::new)
} else { } else {
None None
}, },
incompatible: if let Some((profile, _)) = &profile {
!version.loaders.contains( incompatible: !version.loaders.contains(
&profile &profile
.metadata .metadata
.loader .loader
.as_api_str() .as_api_str()
.to_string(), .to_string(),
) || version ) || version
.game_versions .game_versions
.contains(&profile.metadata.game_version) .contains(&profile.metadata.game_version),
} else {
false
},
}, },
sha512: hash,
file_name, file_name,
}, },
); );

View File

@@ -6,10 +6,63 @@ use regex::Regex;
pub trait OsExt { pub trait OsExt {
/// Get the OS of the current system /// Get the OS of the current system
fn native() -> Self; fn native() -> Self;
/// Gets the OS + Arch of the current system
fn native_arch() -> Self;
} }
impl OsExt for Os { impl OsExt for Os {
fn native_arch() -> Self {
if std::env::consts::OS == "windows" {
if std::env::consts::ARCH == "aarch64" {
Os::WindowsArm64
} else {
Os::Windows
}
} else if std::env::consts::OS == "linux" {
if std::env::consts::ARCH == "aarch64" {
Os::LinuxArm64
} else if std::env::consts::ARCH == "arm" {
Os::LinuxArm32
} else {
Os::Linux
}
} else if std::env::consts::OS == "macos" {
if std::env::consts::ARCH == "aarch64" {
Os::OsxArm64
} else {
Os::Osx
}
} else {
Os::Unknown
}
}
fn native() -> Self { fn native() -> Self {
if std::env::consts::OS == "windows" {
if std::env::consts::ARCH == "aarch64" {
Os::WindowsArm64
} else {
Os::Windows
}
} else if std::env::consts::OS == "linux" {
if std::env::consts::ARCH == "aarch64" {
Os::LinuxArm64
} else if std::env::consts::ARCH == "arm" {
Os::LinuxArm32
} else {
Os::Linux
}
} else if std::env::consts::OS == "macos" {
if std::env::consts::ARCH == "aarch64" {
Os::OsxArm64
} else {
Os::Osx
}
} else {
Os::Unknown
};
match std::env::consts::OS { match std::env::consts::OS {
"windows" => Self::Windows, "windows" => Self::Windows,
"macos" => Self::Osx, "macos" => Self::Osx,
@@ -35,7 +88,7 @@ pub fn os_rule(rule: &OsRule) -> bool {
} }
if let Some(name) = &rule.name { if let Some(name) = &rule.name {
rule_match &= &Os::native() == name; rule_match &= &Os::native() == name || &Os::native_arch() == name;
} }
if let Some(version) = &rule.version { if let Some(version) = &rule.version {
@@ -49,8 +102,13 @@ pub fn os_rule(rule: &OsRule) -> bool {
} }
pub fn classpath_separator() -> &'static str { pub fn classpath_separator() -> &'static str {
match Os::native() { match Os::native_arch() {
Os::Osx | Os::Linux | Os::Unknown => ":", Os::Osx
Os::Windows => ";", | Os::OsxArm64
| Os::Linux
| Os::LinuxArm32
| Os::LinuxArm64
| Os::Unknown => ":",
Os::Windows | Os::WindowsArm64 => ";",
} }
} }

View File

@@ -1,20 +1,14 @@
<script setup> <script setup>
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { RouterView, RouterLink } from 'vue-router' import { RouterView, RouterLink } from 'vue-router'
import { import { HomeIcon, SearchIcon, LibraryIcon, PlusIcon, SettingsIcon } from 'omorphia'
ChevronLeftIcon,
ChevronRightIcon,
HomeIcon,
SearchIcon,
LibraryIcon,
PlusIcon,
SettingsIcon,
} from 'omorphia'
import { useTheming } from '@/store/state' import { useTheming } from '@/store/state'
import AccountsCard from '@/components/ui/AccountsCard.vue' import AccountsCard from '@/components/ui/AccountsCard.vue'
import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue' import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue'
import { list } from '@/helpers/profile' import { list } from '@/helpers/profile'
import { get } from '@/helpers/settings' import { get } from '@/helpers/settings'
import Breadcrumbs from '@/components/ui/Breadcrumbs.vue'
import RunningAppBar from '@/components/ui/RunningAppBar.vue'
const themeStore = useTheming() const themeStore = useTheming()
@@ -65,12 +59,12 @@ list().then(
<div class="view"> <div class="view">
<div class="appbar"> <div class="appbar">
<section class="navigation-controls"> <section class="navigation-controls">
<ChevronLeftIcon @click="$router.back()" /> <Breadcrumbs />
<ChevronRightIcon @click="$router.forward()" />
<p>{{ $route.name }}</p>
</section> </section>
<section class="mod-stats"> <section class="mod-stats">
<p>{{ installedMods }} mods installed</p> <Suspense>
<RunningAppBar />
</Suspense>
</section> </section>
</div> </div>
<div class="router-view"> <div class="router-view">
@@ -90,7 +84,7 @@ list().then(
overflow: hidden; overflow: hidden;
.view { .view {
width: 100%; width: calc(100% - 5rem);
.appbar { .appbar {
display: flex; display: flex;
@@ -98,7 +92,8 @@ list().then(
align-items: center; align-items: center;
background: var(--color-super-raised-bg); background: var(--color-super-raised-bg);
text-align: center; text-align: center;
padding: 0.5rem 1rem; padding: 0 0 0 1rem;
height: 3.25rem;
z-index: 11; z-index: 11;
.navigation-controls { .navigation-controls {
@@ -132,6 +127,7 @@ list().then(
} }
.mod-stats { .mod-stats {
height: 100%;
display: inherit; display: inherit;
align-items: inherit; align-items: inherit;
justify-content: flex-end; justify-content: flex-end;
@@ -140,7 +136,7 @@ list().then(
.router-view { .router-view {
width: 100%; width: 100%;
height: calc(100% - 2rem); height: calc(100% - 3.125rem);
overflow: auto; overflow: auto;
overflow-x: hidden; overflow-x: hidden;
} }

View File

@@ -2,3 +2,5 @@ export { default as PlayIcon } from './play.svg'
export { default as OpenFolderIcon } from './folder-open.svg' export { default as OpenFolderIcon } from './folder-open.svg'
export { default as BrowseIcon } from './folder-search.svg' export { default as BrowseIcon } from './folder-search.svg'
export { default as LoginIcon } from './log-in.svg' export { default as LoginIcon } from './log-in.svg'
export { default as StopIcon } from './stop-circle.svg'
export { default as TerminalIcon } from './terminal-square.svg'

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-stop-circle"><circle cx="12" cy="12" r="10"></circle><rect width="6" height="6" x="9" y="9"></rect></svg>

After

Width:  |  Height:  |  Size: 307 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-terminal-square"><path d="m7 11 2-2-2-2"></path><path d="M11 13h4"></path><rect width="18" height="18" x="3" y="3" rx="2" ry="2"></rect></svg>

After

Width:  |  Height:  |  Size: 344 B

View File

@@ -0,0 +1,64 @@
<template>
<div class="breadcrumbs">
<div v-for="breadcrumb in breadcrumbs" :key="breadcrumb.name" class="breadcrumbs__item">
<router-link
v-if="breadcrumb.link"
:to="breadcrumb.link.replace('{id}', encodeURIComponent($route.params.id))"
>{{
breadcrumb.name.charAt(0) === '?'
? breadcrumbData.getName(breadcrumb.name.slice(1))
: breadcrumb.name
}}
</router-link>
<span v-else class="selected">{{
breadcrumb.name.charAt(0) === '?'
? breadcrumbData.getName(breadcrumb.name.slice(1))
: breadcrumb.name
}}</span>
<ChevronRightIcon v-if="breadcrumb.link" class="chevron" />
</div>
</div>
</template>
<script setup>
import { ChevronRightIcon } from 'omorphia'
import { useBreadcrumbs } from '@/store/breadcrumbs'
import { useRoute } from 'vue-router'
import { computed } from 'vue'
const route = useRoute()
const breadcrumbData = useBreadcrumbs()
const breadcrumbs = computed(() => {
const additionalContext =
route.meta.useContext === true
? breadcrumbData.context
: route.meta.useRootContext === true
? breadcrumbData.rootContext
: null
return additionalContext ? [additionalContext, ...route.meta.breadcrumb] : route.meta.breadcrumb
})
</script>
<style scoped lang="scss">
.breadcrumbs {
display: flex;
flex-direction: row;
.breadcrumbs__item {
display: flex;
flex-direction: row;
vertical-align: center;
margin: auto 0;
.chevron,
a {
margin: auto 0;
}
}
}
.selected {
color: var(--color-contrast);
}
</style>

View File

@@ -0,0 +1,103 @@
<template>
<div v-if="currentProcesses[0]" class="status">
<span class="circle running" />
<span class="running-text">
{{ currentProcesses[0].metadata.name }}
</span>
<Button icon-only class="icon-button stop" @click="stop()">
<StopIcon />
</Button>
<Button icon-only class="icon-button" @click="goToTerminal()">
<TerminalIcon />
</Button>
</div>
<div v-else class="status">
<span class="circle stopped" />
<span class="running-text"> No running profiles </span>
</div>
</template>
<script setup>
import { Button } from 'omorphia'
import { StopIcon, TerminalIcon } from '@/assets/icons'
import { ref } from 'vue'
import {
get_all_running_profiles as getRunningProfiles,
kill_by_uuid as killProfile,
get_uuids_by_profile_path as getProfileProcesses,
} from '@/helpers/process'
import { process_listener } from '@/helpers/events'
import { useRouter } from 'vue-router'
const router = useRouter()
const currentProcesses = ref(await getRunningProfiles())
await process_listener(async () => {
await refresh()
})
const refresh = async () => {
currentProcesses.value = await getRunningProfiles()
}
const stop = async () => {
try {
const processes = await getProfileProcesses(currentProcesses.value[0].path)
await killProfile(processes[0])
} catch (e) {
console.error(e)
}
await refresh()
}
const goToTerminal = () => {
router.push(`/instance/${encodeURIComponent(currentProcesses.value[0].path)}/logs`)
}
</script>
<style scoped lang="scss">
.status {
height: 100%;
display: flex;
flex-direction: row;
align-items: center;
gap: 0.5rem;
background-color: var(--color-raised-bg);
padding: 0 1rem;
margin: 0;
}
.running-text {
white-space: nowrap;
overflow: hidden;
}
.circle {
width: 0.5rem;
height: 0.5rem;
border-radius: 50%;
display: inline-block;
margin-right: 0.25rem;
&.running {
background-color: var(--color-brand);
}
&.stopped {
background-color: var(--color-base);
}
}
.icon-button {
background-color: rgba(0, 0, 0, 0);
box-shadow: none;
width: 1.25rem !important;
height: 1.25rem !important;
&.stop {
--text-color: var(--color-red) !important;
}
}
</style>

View File

@@ -31,8 +31,8 @@ export async function get_all_running_uuids() {
/// Gets all running process IDs with a given profile path /// Gets all running process IDs with a given profile path
/// Returns [u32] /// Returns [u32]
export async function get_uuids_by_profile_path(profile_path) { export async function get_uuids_by_profile_path(profilePath) {
return await invoke('process_get_uuids_by_profile_path', { profile_path }) return await invoke('process_get_uuids_by_profile_path', { profilePath })
} }
/// Gets all running process IDs with a given profile path /// Gets all running process IDs with a given profile path
@@ -43,8 +43,8 @@ export async function get_all_running_profile_paths(profile_path) {
/// Gets all running process IDs with a given profile path /// Gets all running process IDs with a given profile path
/// Returns [u32] /// Returns [u32]
export async function get_all_running_profiles(profile_path) { export async function get_all_running_profiles() {
return await invoke('process_get_all_running_profiles', { profile_path }) return await invoke('process_get_all_running_profiles')
} }
/// Gets process stderr by UUID /// Gets process stderr by UUID

View File

@@ -17,9 +17,14 @@ import {
} from 'omorphia' } from 'omorphia'
import Multiselect from 'vue-multiselect' import Multiselect from 'vue-multiselect'
import { useSearch } from '@/store/state' import { useSearch } from '@/store/state'
import { useBreadcrumbs } from '@/store/breadcrumbs'
import { get_categories, get_loaders, get_game_versions } from '@/helpers/tags' import { get_categories, get_loaders, get_game_versions } from '@/helpers/tags'
import { useRoute } from 'vue-router'
const searchStore = useSearch() const searchStore = useSearch()
const breadcrumbs = useBreadcrumbs()
const route = useRoute()
breadcrumbs.setContext({ name: 'Browse', link: route.path })
const showSnapshots = ref(false) const showSnapshots = ref(false)
const loading = ref(true) const loading = ref(true)

View File

@@ -2,9 +2,16 @@
import RowDisplay from '@/components/RowDisplay.vue' import RowDisplay from '@/components/RowDisplay.vue'
import { shallowRef } from 'vue' import { shallowRef } from 'vue'
import { list } from '@/helpers/profile.js' import { list } from '@/helpers/profile.js'
import { useRoute } from 'vue-router'
import { useBreadcrumbs } from '@/store/breadcrumbs'
const route = useRoute()
const breadcrumbs = useBreadcrumbs()
const profiles = await list() const profiles = await list()
const recentInstances = shallowRef(Object.values(profiles)) const recentInstances = shallowRef(Object.values(profiles))
breadcrumbs.setRootContext({ name: 'Home', link: route.path })
</script> </script>
<template> <template>

View File

@@ -2,9 +2,16 @@
import GridDisplay from '@/components/GridDisplay.vue' import GridDisplay from '@/components/GridDisplay.vue'
import { shallowRef } from 'vue' import { shallowRef } from 'vue'
import { list } from '@/helpers/profile.js' import { list } from '@/helpers/profile.js'
import { useRoute } from 'vue-router'
import { useBreadcrumbs } from '@/store/breadcrumbs'
const route = useRoute()
const breadcrumbs = useBreadcrumbs()
const profiles = await list() const profiles = await list()
const instances = shallowRef(Object.values(profiles)) const instances = shallowRef(Object.values(profiles))
breadcrumbs.setRootContext({ name: 'Library', link: route.path })
</script> </script>
<template> <template>

View File

@@ -5,9 +5,9 @@
<Avatar size="lg" :src="convertFileSrc(instance.metadata.icon)" /> <Avatar size="lg" :src="convertFileSrc(instance.metadata.icon)" />
<div class="instance-info"> <div class="instance-info">
<h2 class="name">{{ instance.metadata.name }}</h2> <h2 class="name">{{ instance.metadata.name }}</h2>
<span class="metadata" <span class="metadata">
>{{ instance.metadata.loader }} {{ instance.metadata.game_version }}</span {{ instance.metadata.loader }} {{ instance.metadata.game_version }}
> </span>
</div> </div>
<span class="button-group"> <span class="button-group">
<Button color="primary" class="instance-button" @click="run($route.params.id)"> <Button color="primary" class="instance-button" @click="run($route.params.id)">
@@ -47,9 +47,17 @@ import { get, run } from '@/helpers/profile'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { shallowRef } from 'vue' import { shallowRef } from 'vue'
import { convertFileSrc } from '@tauri-apps/api/tauri' import { convertFileSrc } from '@tauri-apps/api/tauri'
import { useBreadcrumbs } from '@/store/breadcrumbs'
const breadcrumbs = useBreadcrumbs()
const route = useRoute() const route = useRoute()
const instance = shallowRef(await get(route.params.id)) const instance = shallowRef(await get(route.params.id))
breadcrumbs.setName('Instance', instance.value.metadata.name)
breadcrumbs.setContext({
name: instance.value.metadata.name,
link: route.path,
})
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@@ -77,7 +85,7 @@ Button {
flex-direction: column; flex-direction: column;
padding: 1rem; padding: 1rem;
background: var(--color-raised-bg); background: var(--color-raised-bg);
min-height: calc(100% - 2rem); min-height: calc(100% - 3.25rem);
overflow: hidden; overflow: hidden;
} }

View File

@@ -36,7 +36,7 @@
</div> </div>
<div v-for="mod in search" :key="mod.file_name" class="table-row"> <div v-for="mod in search" :key="mod.file_name" class="table-row">
<div class="table-cell table-text"> <div class="table-cell table-text">
<Button v-if="true" icon-only> <Button v-if="mod.outdated" icon-only>
<UpdatedIcon /> <UpdatedIcon />
</Button> </Button>
<Button v-else disabled icon-only> <Button v-else disabled icon-only>
@@ -106,6 +106,7 @@ for (const project of Object.values(props.instance.projects)) {
file_name: project.file_name, file_name: project.file_name,
icon: project.metadata.project.icon_url, icon: project.metadata.project.icon_url,
disabled: project.disabled, disabled: project.disabled,
outdated: project.metadata.update_version,
}) })
} else if (project.metadata.type === 'inferred') { } else if (project.metadata.type === 'inferred') {
projects.value.push({ projects.value.push({
@@ -115,6 +116,7 @@ for (const project of Object.values(props.instance.projects)) {
file_name: project.file_name, file_name: project.file_name,
icon: project.metadata.icon ? convertFileSrc(project.metadata.icon) : null, icon: project.metadata.icon ? convertFileSrc(project.metadata.icon) : null,
disabled: project.disabled, disabled: project.disabled,
outdated: false,
}) })
} else { } else {
projects.value.push({ projects.value.push({
@@ -124,6 +126,7 @@ for (const project of Object.values(props.instance.projects)) {
file_name: project.file_name, file_name: project.file_name,
icon: null, icon: null,
disabled: project.disabled, disabled: project.disabled,
outdated: false,
}) })
} }
} }

View File

@@ -218,9 +218,11 @@ import { ofetch } from 'ofetch'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { ref, shallowRef, watch } from 'vue' import { ref, shallowRef, watch } from 'vue'
import InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue' import InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue'
import { useBreadcrumbs } from '@/store/breadcrumbs'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const breadcrumbs = useBreadcrumbs()
const confirmModal = ref(null) const confirmModal = ref(null)
const loaders = ref(await get_loaders()) const loaders = ref(await get_loaders())
@@ -232,6 +234,8 @@ const [data, versions, members, dependencies] = await Promise.all([
ofetch(`https://api.modrinth.com/v2/project/${route.params.id}/dependencies`).then(shallowRef), ofetch(`https://api.modrinth.com/v2/project/${route.params.id}/dependencies`).then(shallowRef),
]) ])
breadcrumbs.setName('Project', data.value.title)
watch( watch(
() => route.params.id, () => route.params.id,
() => { () => {
@@ -246,7 +250,9 @@ async function install(version) {
const packs = Object.values(await list()) const packs = Object.values(await list())
if ( if (
packs.length === 0 || packs.length === 0 ||
!packs.map((value) => value.metadata).find((pack) => pack.linked_project_id === data.value.id) !packs
.map((value) => value.metadata)
.find((pack) => pack.linked_data?.project_id === data.value.id)
) { ) {
let id = await pack_install(version) let id = await pack_install(version)
await router.push({ path: `/instance/${encodeURIComponent(id)}` }) await router.push({ path: `/instance/${encodeURIComponent(id)}` })

View File

@@ -183,6 +183,9 @@ import {
import { releaseColor } from '@/helpers/utils' import { releaseColor } from '@/helpers/utils'
import { ref, defineProps } from 'vue' import { ref, defineProps } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { useBreadcrumbs } from '@/store/breadcrumbs'
const breadcrumbs = useBreadcrumbs()
const route = useRoute() const route = useRoute()
@@ -210,7 +213,10 @@ const props = defineProps({
}) })
const version = ref(props.versions.find((version) => version.id === route.params.version)) const version = ref(props.versions.find((version) => version.id === route.params.version))
breadcrumbs.setName('Version', version.value.name)
const author = ref(props.members.find((member) => member.user.id === version.value.author_id)) const author = ref(props.members.find((member) => member.user.id === version.value.author_id))
const displayDependencies = ref( const displayDependencies = ref(
version.value.dependencies.map((dependency) => { version.value.dependencies.map((dependency) => {
const version = props.dependencies.versions.find((obj) => obj.id === dependency.version_id) const version = props.dependencies.versions.find((obj) => obj.id === dependency.version_id)

View File

@@ -13,31 +13,33 @@ export default new createRouter({
path: '/', path: '/',
name: 'Home', name: 'Home',
component: Pages.Index, component: Pages.Index,
meta: {
breadcrumb: [{ name: 'Home' }],
},
}, },
{ {
path: '/browse', path: '/browse',
name: 'Browse', name: 'Browse',
component: Pages.Browse, component: Pages.Browse,
meta: {
breadcrumb: [{ name: 'Browse' }],
},
}, },
{ {
path: '/library', path: '/library',
name: 'Library', name: 'Library',
component: Pages.Library, component: Pages.Library,
}, meta: {
{ breadcrumb: [{ name: 'Library' }],
path: '/add-instance', },
name: 'Add Instance',
component: Pages.AddInstance,
},
{
path: '/project',
name: 'Project',
component: Pages.Project,
}, },
{ {
path: '/settings', path: '/settings',
name: 'Settings', name: 'Settings',
component: Pages.Settings, component: Pages.Settings,
meta: {
breadcrumb: [{ name: 'Settings' }],
},
}, },
{ {
path: '/project/:id', path: '/project/:id',
@@ -49,22 +51,42 @@ export default new createRouter({
path: '', path: '',
name: 'Description', name: 'Description',
component: Project.Description, component: Project.Description,
meta: {
useContext: true,
breadcrumb: [{ name: '?Project' }],
},
}, },
{ {
path: 'versions', path: 'versions',
name: 'Versions', name: 'Versions',
component: Project.Versions, component: Project.Versions,
meta: {
useContext: true,
breadcrumb: [{ name: '?Project', link: '/project/{id}/' }, { name: 'Versions' }],
},
}, },
{ {
path: 'version/:version', path: 'version/:version',
name: 'Version', name: 'Version',
component: Project.Version, component: Project.Version,
props: true, props: true,
meta: {
useContext: true,
breadcrumb: [
{ name: '?Project', link: '/project/{id}/' },
{ name: 'Versions', link: '/project/{id}/versions' },
{ name: '?Version' },
],
},
}, },
{ {
path: 'gallery', path: 'gallery',
name: 'Gallery', name: 'Gallery',
component: Project.Gallery, component: Project.Gallery,
meta: {
useContext: true,
breadcrumb: [{ name: '?Project', link: '/project/{id}/' }, { name: 'Gallery' }],
},
}, },
], ],
}, },
@@ -78,16 +100,28 @@ export default new createRouter({
path: '', path: '',
name: 'Mods', name: 'Mods',
component: Instance.Mods, component: Instance.Mods,
meta: {
useRootContext: true,
breadcrumb: [{ name: '?Instance' }],
},
}, },
{ {
path: 'options', path: 'options',
name: 'Options', name: 'Options',
component: Instance.Options, component: Instance.Options,
meta: {
useRootContext: true,
breadcrumb: [{ name: '?Instance', link: '/instance/{id}/' }, { name: 'Options' }],
},
}, },
{ {
path: 'logs', path: 'logs',
name: 'Logs', name: 'Logs',
component: Instance.Logs, component: Instance.Logs,
meta: {
useRootContext: true,
breadcrumb: [{ name: '?Instance', link: '/instance/{id}/' }, { name: 'Logs' }],
},
}, },
], ],
}, },

View File

@@ -0,0 +1,23 @@
import { defineStore } from 'pinia'
export const useBreadcrumbs = defineStore('breadcrumbsStore', {
state: () => ({
names: new Map(),
context: null,
rootContext: null,
}),
actions: {
getName(route) {
return this.names.get(route) ?? route
},
setName(route, title) {
this.names.set(route, title)
},
setContext(context) {
this.context = context
},
setRootContext(context) {
this.rootContext = context
},
},
})

View File

@@ -1,4 +1,5 @@
import { useSearch } from './search' import { useSearch } from './search'
import { useTheming } from './theme' import { useTheming } from './theme'
import { useBreadcrumbs } from './breadcrumbs'
export { useSearch, useTheming } export { useSearch, useTheming, useBreadcrumbs }

View File

@@ -4,6 +4,7 @@
)] )]
use dunce::canonicalize; use dunce::canonicalize;
use theseus::jre::autodetect_java_globals;
use theseus::prelude::*; use theseus::prelude::*;
use theseus::profile_create::profile_create; use theseus::profile_create::profile_create;
use tokio::time::{sleep, Duration}; use tokio::time::{sleep, Duration};
@@ -32,8 +33,9 @@ async fn main() -> theseus::Result<()> {
// Initialize state // Initialize state
let st = State::get().await?; let st = State::get().await?;
State::update(); //State::update();
st.settings.write().await.java_globals = autodetect_java_globals().await?;
st.settings.write().await.max_concurrent_downloads = 5; st.settings.write().await.max_concurrent_downloads = 5;
st.settings.write().await.hooks.post_exit = st.settings.write().await.hooks.post_exit =
Some("echo This is after Minecraft runs- global setting!".to_string()); Some("echo This is after Minecraft runs- global setting!".to_string());
@@ -53,7 +55,7 @@ async fn main() -> theseus::Result<()> {
let name = "Example".to_string(); let name = "Example".to_string();
let game_version = "1.19.2".to_string(); let game_version = "1.19.2".to_string();
let modloader = ModLoader::Fabric; let modloader = ModLoader::Vanilla;
let loader_version = "stable".to_string(); let loader_version = "stable".to_string();
let profile_path = profile_create( let profile_path = profile_create(
@@ -88,7 +90,7 @@ async fn main() -> theseus::Result<()> {
// //
// profile::remove_project(&profile_path, &mod_menu_path).await?; // profile::remove_project(&profile_path, &mod_menu_path).await?;
// let profile_path = // let profile_path =
// pack::install_pack_from_version_id("KxUUUFh5".to_string()) // pack::install_pack_from_version_id("zroFQG1k".to_string())
// .await // .await
// .unwrap(); // .unwrap();
@@ -126,7 +128,9 @@ async fn main() -> theseus::Result<()> {
println!("Waiting 20 seconds to gather logs..."); println!("Waiting 20 seconds to gather logs...");
sleep(Duration::from_secs(20)).await; sleep(Duration::from_secs(20)).await;
let stdout = process::get_stdout_by_uuid(&uuid).await?; let stdout = process::get_stdout_by_uuid(&uuid).await?;
let stderr = process::get_stderr_by_uuid(&uuid).await?;
println!("Logs after 5sec <<< {stdout} >>> end stdout"); println!("Logs after 5sec <<< {stdout} >>> end stdout");
println!("Logs after 5sec <<< {stderr} >>> end stderr");
println!( println!(
"All running process UUID {:?}", "All running process UUID {:?}",