You've already forked AstralRinth
forked from didirus/AstralRinth
Event handling (#75)
* working on amcros * fleshed out draft * added feature support * finished loading * Fixed issue with multiple data types in macro * Working, and added more loading uses * added window scopes * clippy, fmt * working other variants * fmt; clippy * prettier * refactored emissions to use increment * fixed deadlock * doc changes * clippy, prettier * uuid change * restructured events to util * loading restructure * merge fixes * comments mistake * better cfg tauri feature structuring * added extra fields to some loading enum variants * removed Option<> * added pack + version labels * doc change
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -3537,6 +3537,7 @@ dependencies = [
|
|||||||
"futures",
|
"futures",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
|
"paste",
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -3545,6 +3546,7 @@ dependencies = [
|
|||||||
"sha2 0.9.9",
|
"sha2 0.9.9",
|
||||||
"sled",
|
"sled",
|
||||||
"sys-info",
|
"sys-info",
|
||||||
|
"tauri",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ thiserror = "1.0"
|
|||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-error = "0.2"
|
tracing-error = "0.2"
|
||||||
|
|
||||||
|
tauri = { version = "1.2", optional = true}
|
||||||
|
paste = { version = "1.0", optional = true}
|
||||||
|
|
||||||
async-tungstenite = { version = "0.20.0", features = ["tokio-runtime", "tokio-native-tls"] }
|
async-tungstenite = { version = "0.20.0", features = ["tokio-runtime", "tokio-native-tls"] }
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
@@ -45,3 +47,4 @@ dunce = "1.0.3"
|
|||||||
winreg = "0.11.0"
|
winreg = "0.11.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
tauri = ["dep:tauri", "dep:paste"]
|
||||||
@@ -45,7 +45,7 @@ pub async fn get_optimal_jre_key(profile: &Profile) -> crate::Result<String> {
|
|||||||
.minecraft
|
.minecraft
|
||||||
.versions
|
.versions
|
||||||
.iter()
|
.iter()
|
||||||
.find(|it| it.id == profile.metadata.game_version.as_ref())
|
.find(|it| it.id == profile.metadata.game_version)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
crate::ErrorKind::LauncherError(format!(
|
crate::ErrorKind::LauncherError(format!(
|
||||||
"Invalid or unknown Minecraft version: {}",
|
"Invalid or unknown Minecraft version: {}",
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
use crate::config::MODRINTH_API_URL;
|
use crate::config::MODRINTH_API_URL;
|
||||||
use crate::data::ModLoader;
|
use crate::data::ModLoader;
|
||||||
|
use crate::event::emit::{init_loading, loading_try_for_each_concurrent};
|
||||||
|
use crate::event::LoadingBarType;
|
||||||
use crate::state::{ModrinthProject, ModrinthVersion, SideType};
|
use crate::state::{ModrinthProject, ModrinthVersion, SideType};
|
||||||
use crate::util::fetch::{
|
use crate::util::fetch::{
|
||||||
fetch, fetch_json, fetch_mirrors, write, write_cached_icon,
|
fetch, fetch_json, fetch_mirrors, write, write_cached_icon,
|
||||||
};
|
};
|
||||||
use crate::State;
|
use crate::State;
|
||||||
use async_zip::tokio::read::seek::ZipFileReader;
|
use async_zip::tokio::read::seek::ZipFileReader;
|
||||||
use futures::TryStreamExt;
|
|
||||||
use reqwest::Method;
|
use reqwest::Method;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -133,19 +134,20 @@ pub async fn install_pack_from_version_id(
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
install_pack(file, icon, Some(version.project_id)).await
|
install_pack(file, icon, 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).await
|
install_pack(bytes::Bytes::from(file), None, None, None).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn install_pack(
|
async fn install_pack(
|
||||||
file: bytes::Bytes,
|
file: bytes::Bytes,
|
||||||
icon: Option<PathBuf>,
|
icon: Option<PathBuf>,
|
||||||
project_id: Option<String>,
|
project_id: Option<String>,
|
||||||
|
version_id: Option<String>,
|
||||||
) -> crate::Result<PathBuf> {
|
) -> crate::Result<PathBuf> {
|
||||||
let state = &State::get().await?;
|
let state = &State::get().await?;
|
||||||
|
|
||||||
@@ -215,22 +217,39 @@ async fn install_pack(
|
|||||||
.into());
|
.into());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let pack_name = pack.name.clone();
|
||||||
let profile = crate::api::profile_create::profile_create(
|
let profile = crate::api::profile_create::profile_create(
|
||||||
pack.name,
|
pack.name,
|
||||||
game_version.clone(),
|
game_version.clone(),
|
||||||
mod_loader.unwrap_or(ModLoader::Vanilla),
|
mod_loader.unwrap_or(ModLoader::Vanilla),
|
||||||
loader_version,
|
loader_version,
|
||||||
icon,
|
icon,
|
||||||
project_id,
|
project_id.clone(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let loading_bar = init_loading(
|
||||||
|
LoadingBarType::PackDownload {
|
||||||
|
pack_name ,
|
||||||
|
pack_id: project_id,
|
||||||
|
pack_version: version_id,
|
||||||
|
},
|
||||||
|
100.0,
|
||||||
|
"Downloading modpack...",
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let num_files = pack.files.len();
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
futures::stream::iter(pack.files.into_iter())
|
loading_try_for_each_concurrent(
|
||||||
.map(Ok::<PackFile, crate::Error>)
|
futures::stream::iter(pack.files.into_iter())
|
||||||
.try_for_each_concurrent(None, |project| {
|
.map(Ok::<PackFile, crate::Error>),
|
||||||
|
None,
|
||||||
|
Some(&loading_bar),
|
||||||
|
100.0,
|
||||||
|
num_files,
|
||||||
|
None,
|
||||||
|
|project| {
|
||||||
let profile = profile.clone();
|
let profile = profile.clone();
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
//TODO: Future update: prompt user for optional files in a modpack
|
//TODO: Future update: prompt user for optional files in a modpack
|
||||||
if let Some(env) = project.env {
|
if let Some(env) = project.env {
|
||||||
@@ -266,11 +285,11 @@ async fn install_pack(
|
|||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
})
|
},
|
||||||
.await?;
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let extract_overrides = |overrides: String| async {
|
let extract_overrides = |overrides: String| async {
|
||||||
let reader = Cursor::new(&file);
|
let reader = Cursor::new(&file);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
//! Theseus profile management interface
|
//! Theseus profile management interface
|
||||||
use crate::{
|
use crate::{
|
||||||
auth::{self, refresh},
|
auth::{self, refresh},
|
||||||
|
event::{emit::emit_profile, ProfilePayloadType},
|
||||||
launcher::download,
|
launcher::download,
|
||||||
state::MinecraftChild,
|
state::MinecraftChild,
|
||||||
};
|
};
|
||||||
@@ -20,6 +21,17 @@ use tokio::{fs, process::Command, sync::RwLock};
|
|||||||
pub async fn remove(path: &Path) -> crate::Result<()> {
|
pub async fn remove(path: &Path) -> crate::Result<()> {
|
||||||
let state = State::get().await?;
|
let state = State::get().await?;
|
||||||
let mut profiles = state.profiles.write().await;
|
let mut profiles = state.profiles.write().await;
|
||||||
|
|
||||||
|
if let Some(profile) = profiles.0.get(path) {
|
||||||
|
emit_profile(
|
||||||
|
profile.uuid,
|
||||||
|
profile.path.clone(),
|
||||||
|
&profile.metadata.name,
|
||||||
|
ProfilePayloadType::Removed,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
profiles.remove(path).await?;
|
profiles.remove(path).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -46,7 +58,17 @@ where
|
|||||||
let mut profiles = state.profiles.write().await;
|
let mut profiles = state.profiles.write().await;
|
||||||
|
|
||||||
match profiles.0.get_mut(path) {
|
match profiles.0.get_mut(path) {
|
||||||
Some(ref mut profile) => action(profile).await,
|
Some(ref mut profile) => {
|
||||||
|
emit_profile(
|
||||||
|
profile.uuid,
|
||||||
|
profile.path.clone(),
|
||||||
|
&profile.metadata.name,
|
||||||
|
ProfilePayloadType::Edited,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
action(profile).await
|
||||||
|
}
|
||||||
None => Err(crate::ErrorKind::UnmanagedProfileError(
|
None => Err(crate::ErrorKind::UnmanagedProfileError(
|
||||||
path.display().to_string(),
|
path.display().to_string(),
|
||||||
)
|
)
|
||||||
@@ -219,7 +241,7 @@ pub async fn run_credentials(
|
|||||||
.minecraft
|
.minecraft
|
||||||
.versions
|
.versions
|
||||||
.iter()
|
.iter()
|
||||||
.find(|it| it.id == profile.metadata.game_version.as_ref())
|
.find(|it| it.id == profile.metadata.game_version)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
crate::ErrorKind::LauncherError(format!(
|
crate::ErrorKind::LauncherError(format!(
|
||||||
"Invalid or unknown Minecraft version: {}",
|
"Invalid or unknown Minecraft version: {}",
|
||||||
@@ -325,6 +347,7 @@ pub async fn run_credentials(
|
|||||||
&memory,
|
&memory,
|
||||||
&resolution,
|
&resolution,
|
||||||
credentials,
|
credentials,
|
||||||
|
&profile,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -335,8 +358,9 @@ pub async fn run_credentials(
|
|||||||
"Process failed to stay open.".to_string(),
|
"Process failed to stay open.".to_string(),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
let mchild_arc =
|
let mchild_arc = state_children
|
||||||
state_children.insert_process(pid, path.to_path_buf(), mc_process);
|
.insert_process(pid, path.to_path_buf(), mc_process)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(mchild_arc)
|
Ok(mchild_arc)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
//! Theseus profile management interface
|
//! Theseus profile management interface
|
||||||
use crate::{jre, prelude::ModLoader};
|
use crate::{
|
||||||
|
event::{emit::emit_profile, ProfilePayloadType},
|
||||||
|
jre,
|
||||||
|
prelude::ModLoader,
|
||||||
|
};
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
state::{JavaSettings, Profile},
|
state::{JavaSettings, Profile},
|
||||||
State,
|
State,
|
||||||
@@ -135,7 +139,8 @@ pub async fn profile_create(
|
|||||||
|
|
||||||
// Fully canonicalize now that its created for storing purposes
|
// Fully canonicalize now that its created for storing purposes
|
||||||
let path = canonicalize(&path)?;
|
let path = canonicalize(&path)?;
|
||||||
let mut profile = Profile::new(name, game_version, path.clone()).await?;
|
let mut profile =
|
||||||
|
Profile::new(uuid, name, game_version, path.clone()).await?;
|
||||||
if let Some(ref icon) = icon {
|
if let Some(ref icon) = icon {
|
||||||
let bytes = tokio::fs::read(icon).await?;
|
let bytes = tokio::fs::read(icon).await?;
|
||||||
profile
|
profile
|
||||||
@@ -167,9 +172,16 @@ pub async fn profile_create(
|
|||||||
println!("Could not detect optimal JRE: {optimal_version_key}, falling back to system default.");
|
println!("Could not detect optimal JRE: {optimal_version_key}, falling back to system default.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emit_profile(
|
||||||
|
uuid,
|
||||||
|
path.clone(),
|
||||||
|
&profile.metadata.name,
|
||||||
|
ProfilePayloadType::Created,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
{
|
{
|
||||||
let mut profiles = state.profiles.write().await;
|
let mut profiles = state.profiles.write().await;
|
||||||
profiles.insert(profile)?;
|
profiles.insert(profile).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
State::sync().await?;
|
State::sync().await?;
|
||||||
|
|||||||
@@ -85,6 +85,9 @@ pub enum ErrorKind {
|
|||||||
#[error("Error parsing date: {0}")]
|
#[error("Error parsing date: {0}")]
|
||||||
ChronoParseError(#[from] chrono::ParseError),
|
ChronoParseError(#[from] chrono::ParseError),
|
||||||
|
|
||||||
|
#[error("Event error: {0}")]
|
||||||
|
EventError(#[from] crate::event::EventError),
|
||||||
|
|
||||||
#[error("Zip error: {0}")]
|
#[error("Zip error: {0}")]
|
||||||
ZipError(#[from] async_zip::error::ZipError),
|
ZipError(#[from] async_zip::error::ZipError),
|
||||||
|
|
||||||
|
|||||||
327
theseus/src/event/emit.rs
Normal file
327
theseus/src/event/emit.rs
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
use crate::event::{
|
||||||
|
EventError, LoadingBar, LoadingBarId, LoadingBarType, ProcessPayloadType,
|
||||||
|
ProfilePayloadType,
|
||||||
|
};
|
||||||
|
use futures::prelude::*;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[cfg(feature = "tauri")]
|
||||||
|
use crate::event::{
|
||||||
|
LoadingPayload, ProcessPayload, ProfilePayload, WarningPayload,
|
||||||
|
};
|
||||||
|
#[cfg(feature = "tauri")]
|
||||||
|
use tauri::Manager;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Events are a way we can communciate with the Tauri frontend from the Rust backend.
|
||||||
|
We include a feature flag for Tauri, so that we can compile this code without Tauri.
|
||||||
|
|
||||||
|
To use events, we need to do the following:
|
||||||
|
1) Make sure we are using the tauri feature flag
|
||||||
|
2) Initialize the EventState with EventState::init() *before* initializing the theseus State
|
||||||
|
3) Call emit_x functions to send events to the frontend
|
||||||
|
For emit_loading() specifically, we need to inialize the loading bar with init_loading() first and pass the received loader in
|
||||||
|
|
||||||
|
For example:
|
||||||
|
pub async fn loading_function() -> crate::Result<()> {
|
||||||
|
loading_function()).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn loading_function() -> crate::Result<()> {
|
||||||
|
let loading_bar = init_loading(LoadingBarType::StateInit, 100.0, "Loading something long...").await;
|
||||||
|
for i in 0..100 {
|
||||||
|
emit_loading(&loading_bar, 1.0, None).await?;
|
||||||
|
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Initialize a loading bar for use in emit_loading
|
||||||
|
// This will generate a LoadingBarId, which is used to refer to the loading bar uniquely.
|
||||||
|
// total is the total amount of work to be done- all emissions will be considered a fraction of this value (should be 1 or 100 for simplicity)
|
||||||
|
// default_message is the message to display on the loading bar if no message is passed to emit_loading
|
||||||
|
pub async fn init_loading(
|
||||||
|
bar_type: LoadingBarType,
|
||||||
|
total: f64,
|
||||||
|
default_message: &str,
|
||||||
|
) -> crate::Result<LoadingBarId> {
|
||||||
|
let event_state = crate::EventState::get().await?;
|
||||||
|
let key = LoadingBarId::new(bar_type);
|
||||||
|
|
||||||
|
event_state.loading_bars.write().await.insert(
|
||||||
|
key.clone(),
|
||||||
|
LoadingBar {
|
||||||
|
loading_bar_id: key.clone(),
|
||||||
|
message: default_message.to_string(),
|
||||||
|
total,
|
||||||
|
current: 0.0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// attempt an initial loading_emit event to the frontend
|
||||||
|
emit_loading(&key, 0.0, None).await?;
|
||||||
|
Ok(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// emit_loading emits a loading event to the frontend
|
||||||
|
// key refers to the loading bar to update
|
||||||
|
// increment refers to by what relative increment to the loading struct's total to update
|
||||||
|
// message is the message to display on the loading bar- if None, use the loading bar's default one
|
||||||
|
// By convention, fraction is the fraction of the progress bar that is filled
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
pub async fn emit_loading(
|
||||||
|
key: &LoadingBarId,
|
||||||
|
increment_frac: f64,
|
||||||
|
message: Option<&str>,
|
||||||
|
) -> crate::Result<()> {
|
||||||
|
let event_state = crate::EventState::get().await?;
|
||||||
|
|
||||||
|
let mut loading_bar = event_state.loading_bars.write().await;
|
||||||
|
let loading_bar = match loading_bar.get_mut(key) {
|
||||||
|
Some(f) => f,
|
||||||
|
None => {
|
||||||
|
return Err(EventError::NoLoadingBar(key.clone()).into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tick up loading bar
|
||||||
|
loading_bar.current += increment_frac;
|
||||||
|
let display_frac = loading_bar.current / loading_bar.total;
|
||||||
|
let display_frac = if display_frac > 1.0 {
|
||||||
|
None // by convention, when its done, we submit None
|
||||||
|
// any further updates will be ignored (also sending None)
|
||||||
|
} else {
|
||||||
|
Some(display_frac)
|
||||||
|
};
|
||||||
|
// Emit event to tauri
|
||||||
|
#[cfg(feature = "tauri")]
|
||||||
|
event_state
|
||||||
|
.app
|
||||||
|
.emit_all(
|
||||||
|
"loading",
|
||||||
|
LoadingPayload {
|
||||||
|
fraction: display_frac,
|
||||||
|
message: message.unwrap_or(&loading_bar.message).to_string(),
|
||||||
|
event: key.key.clone(),
|
||||||
|
loader_uuid: key.uuid,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.map_err(EventError::from)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// emit_warning(message)
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
pub async fn emit_warning(message: &str) -> crate::Result<()> {
|
||||||
|
#[cfg(feature = "tauri")]
|
||||||
|
{
|
||||||
|
let event_state = crate::EventState::get().await?;
|
||||||
|
event_state
|
||||||
|
.app
|
||||||
|
.emit_all(
|
||||||
|
"warning",
|
||||||
|
WarningPayload {
|
||||||
|
message: message.to_string(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.map_err(EventError::from)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// emit_process(pid, event, message)
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
pub async fn emit_process(
|
||||||
|
uuid: uuid::Uuid,
|
||||||
|
pid: u32,
|
||||||
|
event: ProcessPayloadType,
|
||||||
|
message: &str,
|
||||||
|
) -> crate::Result<()> {
|
||||||
|
#[cfg(feature = "tauri")]
|
||||||
|
{
|
||||||
|
let event_state = crate::EventState::get().await?;
|
||||||
|
event_state
|
||||||
|
.app
|
||||||
|
.emit_all(
|
||||||
|
"process",
|
||||||
|
ProcessPayload {
|
||||||
|
uuid,
|
||||||
|
pid,
|
||||||
|
event,
|
||||||
|
message: message.to_string(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.map_err(EventError::from)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// emit_profile(path, event)
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
pub async fn emit_profile(
|
||||||
|
uuid: uuid::Uuid,
|
||||||
|
path: PathBuf,
|
||||||
|
name: &str,
|
||||||
|
event: ProfilePayloadType,
|
||||||
|
) -> crate::Result<()> {
|
||||||
|
#[cfg(feature = "tauri")]
|
||||||
|
{
|
||||||
|
let event_state = crate::EventState::get().await?;
|
||||||
|
event_state
|
||||||
|
.app
|
||||||
|
.emit_all(
|
||||||
|
"profile",
|
||||||
|
ProfilePayload {
|
||||||
|
uuid,
|
||||||
|
path,
|
||||||
|
name: name.to_string(),
|
||||||
|
event,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.map_err(EventError::from)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// loading_join! macro
|
||||||
|
// loading_join!(key: Option<&LoadingBarId>, total: f64, message: Option<&str>; task1, task2, task3...)
|
||||||
|
// This will submit a loading event with the given message for each task as they complete
|
||||||
|
// task1, task2, task3 are async tasks that yuo want to to join on await on
|
||||||
|
// Key is the key to use for which loading bar to submit these results to- a LoadingBarId. If None, it does nothing
|
||||||
|
// Total is the total amount of progress that the loading bar should take up by all futures in this (will be split evenly amongst them).
|
||||||
|
// If message is Some(t) you will overwrite this loading bar's message with a custom one
|
||||||
|
// For example, if you want the tasks to range as 0.1, 0.2, 0.3 (of the progress bar), you would do:
|
||||||
|
// loading_join!(loading_bar, 0.1; task1, task2, task3)
|
||||||
|
// This will await on each of the tasks, and as each completes, it will emit a loading event for 0.033, 0.066, 0.099, etc
|
||||||
|
// This should function as a drop-in replacement for tokio::try_join_all! in most cases- except the function *itself* calls ? rather than needing it.
|
||||||
|
#[cfg(feature = "tauri")]
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! loading_join {
|
||||||
|
($key:expr, $total:expr, $message:expr; $($future:expr $(,)?)+) => {{
|
||||||
|
let mut num_futures = 0;
|
||||||
|
$(
|
||||||
|
{
|
||||||
|
let _ = &$future; // useless to allow matching to $future
|
||||||
|
num_futures += 1;
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
let increment = $total / num_futures as f64;
|
||||||
|
|
||||||
|
// Create tokio::pinned values
|
||||||
|
$(
|
||||||
|
paste::paste! {
|
||||||
|
tokio::pin! {
|
||||||
|
let [<unique_name_ $future>] = $future;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
$(
|
||||||
|
paste::paste! {
|
||||||
|
let mut [<result_ $future>] = None;
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
|
||||||
|
// Resolve each future and call respective loading as each resolves in any order
|
||||||
|
for _ in 0..num_futures {
|
||||||
|
paste::paste! {
|
||||||
|
tokio::select! {
|
||||||
|
$(
|
||||||
|
v = &mut [<unique_name_ $future>], if ![<result_$future>].is_some() => {
|
||||||
|
if let Some(key) = $key {
|
||||||
|
$crate::event::emit::emit_loading(key, increment, $message).await?;
|
||||||
|
}
|
||||||
|
[<result_ $future>] = Some(v);
|
||||||
|
},
|
||||||
|
)*
|
||||||
|
else => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract values out of option, then out of error, returning if any errors happened
|
||||||
|
$(
|
||||||
|
paste::paste! {
|
||||||
|
let [<result_ $future>] = [<result_ $future>].take().unwrap()?; // unwrap here acceptable as numbers of futures and resolved values is guaranteed to be the same
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
|
||||||
|
paste::paste!{
|
||||||
|
($(
|
||||||
|
[<result_ $future>], // unwrap here acceptable as numbers of futures and resolved values is guaranteed to be the same
|
||||||
|
)+)
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "tauri"))]
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! loading_join {
|
||||||
|
($start:expr, $end:expr, $message:expr; $($future:expr $(,)?)+) => {{
|
||||||
|
tokio::try_join!($($future),+)?
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
// A drop in replacement to try_for_each_concurrent that emits loading events as it goes
|
||||||
|
// Key is the key to use for which loading bar- a LoadingBarId. If None, does nothing
|
||||||
|
// Total is the total amount of progress that the loading bar should take up by all futures in this (will be split evenly amongst them).
|
||||||
|
// If message is Some(t) you will overwrite this loading bar's message with a custom one
|
||||||
|
// num_futs is the number of futures that will be run, which is needed as we allow Iterator to be passed in, which doesn't have a size
|
||||||
|
#[cfg(feature = "tauri")]
|
||||||
|
pub async fn loading_try_for_each_concurrent<I, F, Fut, T>(
|
||||||
|
stream: I,
|
||||||
|
limit: Option<usize>,
|
||||||
|
key: Option<&LoadingBarId>,
|
||||||
|
total: f64,
|
||||||
|
num_futs: usize, // num is in here as we allow Iterator to be passed in, which doesn't have a size
|
||||||
|
message: Option<&str>,
|
||||||
|
f: F,
|
||||||
|
) -> crate::Result<()>
|
||||||
|
where
|
||||||
|
I: futures::TryStreamExt<Error = crate::Error> + TryStream<Ok = T>,
|
||||||
|
F: FnMut(T) -> Fut + Send,
|
||||||
|
Fut: Future<Output = crate::Result<()>> + Send,
|
||||||
|
T: Send,
|
||||||
|
{
|
||||||
|
let mut f = f;
|
||||||
|
stream
|
||||||
|
.try_for_each_concurrent(limit, |item| {
|
||||||
|
let f = f(item);
|
||||||
|
async move {
|
||||||
|
f.await?;
|
||||||
|
if let Some(key) = key {
|
||||||
|
emit_loading(key, total / (num_futs as f64), message)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "tauri"))]
|
||||||
|
pub async fn loading_try_for_each_concurrent<I, F, Fut, T>(
|
||||||
|
stream: I,
|
||||||
|
limit: Option<usize>,
|
||||||
|
_key: Option<&LoadingBarId>,
|
||||||
|
_total: f64,
|
||||||
|
_num_futs: usize, // num is in here as we allow Iterator to be passed in, which doesn't have a size
|
||||||
|
_message: Option<&str>,
|
||||||
|
f: F,
|
||||||
|
) -> crate::Result<()>
|
||||||
|
where
|
||||||
|
I: futures::TryStreamExt<Error = crate::Error> + TryStream<Ok = T>,
|
||||||
|
F: FnMut(T) -> Fut + Send,
|
||||||
|
Fut: Future<Output = crate::Result<()>> + Send,
|
||||||
|
T: Send,
|
||||||
|
{
|
||||||
|
let mut f = f;
|
||||||
|
stream
|
||||||
|
.try_for_each_concurrent(limit, |item| {
|
||||||
|
let f = f(item);
|
||||||
|
async move {
|
||||||
|
f.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
157
theseus/src/event/mod.rs
Normal file
157
theseus/src/event/mod.rs
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
//! Theseus state management system
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{collections::HashMap, fmt, path::PathBuf, sync::Arc};
|
||||||
|
use tokio::sync::OnceCell;
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
pub mod emit;
|
||||||
|
|
||||||
|
// Global event state
|
||||||
|
// Stores the Tauri app handle and other event-related state variables
|
||||||
|
static EVENT_STATE: OnceCell<Arc<EventState>> = OnceCell::const_new();
|
||||||
|
pub struct EventState {
|
||||||
|
/// Tauri app
|
||||||
|
#[cfg(feature = "tauri")]
|
||||||
|
pub app: tauri::AppHandle,
|
||||||
|
pub loading_bars: RwLock<HashMap<LoadingBarId, LoadingBar>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventState {
|
||||||
|
#[cfg(feature = "tauri")]
|
||||||
|
pub async fn init(app: tauri::AppHandle) -> crate::Result<Arc<Self>> {
|
||||||
|
EVENT_STATE
|
||||||
|
.get_or_try_init(|| async {
|
||||||
|
Ok(Arc::new(Self {
|
||||||
|
app,
|
||||||
|
loading_bars: RwLock::new(HashMap::new()),
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map(Arc::clone)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "tauri"))]
|
||||||
|
pub async fn init() -> crate::Result<Arc<Self>> {
|
||||||
|
EVENT_STATE
|
||||||
|
.get_or_try_init(|| async {
|
||||||
|
Ok(Arc::new(Self {
|
||||||
|
loading_bars: RwLock::new(HashMap::new()),
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map(Arc::clone)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "tauri")]
|
||||||
|
pub async fn get() -> crate::Result<Arc<Self>> {
|
||||||
|
Ok(EVENT_STATE.get().ok_or(EventError::NotInitialized)?.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialization requires no app handle in non-tauri mode, so we can just use the same function
|
||||||
|
#[cfg(not(feature = "tauri"))]
|
||||||
|
pub async fn get() -> crate::Result<Arc<Self>> {
|
||||||
|
Self::init().await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct LoadingBar {
|
||||||
|
pub loading_bar_id: LoadingBarId,
|
||||||
|
pub message: String,
|
||||||
|
pub total: f64,
|
||||||
|
pub current: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loading Bar Id lets us uniquely identify loading bars stored in the state
|
||||||
|
// the uuid lets us identify loading bars across threads
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
|
pub struct LoadingBarId {
|
||||||
|
pub key: LoadingBarType,
|
||||||
|
pub uuid: Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LoadingBarId {
|
||||||
|
pub fn new(key: LoadingBarType) -> Self {
|
||||||
|
Self {
|
||||||
|
key,
|
||||||
|
uuid: Uuid::new_v4(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for LoadingBarId {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{:?}-{}", self.key, self.uuid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
|
pub enum LoadingBarType {
|
||||||
|
StateInit,
|
||||||
|
PackDownload {
|
||||||
|
pack_name: String,
|
||||||
|
pack_id: Option<String>,
|
||||||
|
pack_version: Option<String>,
|
||||||
|
},
|
||||||
|
MinecraftDownload {
|
||||||
|
profile_uuid: Uuid,
|
||||||
|
profile_name: String,
|
||||||
|
},
|
||||||
|
ProfileSync,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Clone)]
|
||||||
|
pub struct LoadingPayload {
|
||||||
|
pub event: LoadingBarType,
|
||||||
|
pub loader_uuid: Uuid,
|
||||||
|
pub fraction: Option<f64>, // by convention, if optional, it means the loading is done
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Clone)]
|
||||||
|
pub struct WarningPayload {
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Clone)]
|
||||||
|
pub struct ProcessPayload {
|
||||||
|
pub uuid: Uuid, // processes in state are going to be identified by UUIDs, as they might change to different processes
|
||||||
|
pub pid: u32,
|
||||||
|
pub event: ProcessPayloadType,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
#[derive(Serialize, Clone)]
|
||||||
|
pub enum ProcessPayloadType {
|
||||||
|
Launched,
|
||||||
|
// Finishing, // TODO: process restructing incoming, currently this is never emitted
|
||||||
|
// Finished, // TODO: process restructing incoming, currently this is never emitted
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Clone)]
|
||||||
|
pub struct ProfilePayload {
|
||||||
|
pub uuid: Uuid,
|
||||||
|
pub path: PathBuf,
|
||||||
|
pub name: String,
|
||||||
|
pub event: ProfilePayloadType,
|
||||||
|
}
|
||||||
|
#[derive(Serialize, Clone)]
|
||||||
|
pub enum ProfilePayloadType {
|
||||||
|
Created,
|
||||||
|
Added, // also triggered when Created
|
||||||
|
Edited,
|
||||||
|
Removed,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum EventError {
|
||||||
|
#[error("Event state was not properly initialized")]
|
||||||
|
NotInitialized,
|
||||||
|
|
||||||
|
#[error("Non-existent loading bar of key: {0}")]
|
||||||
|
NoLoadingBar(LoadingBarId),
|
||||||
|
|
||||||
|
#[cfg(feature = "tauri")]
|
||||||
|
#[error("Tauri error: {0}")]
|
||||||
|
TauriError(#[from] tauri::Error),
|
||||||
|
}
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
//! Downloader for Minecraft data
|
//! Downloader for Minecraft data
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
event::{
|
||||||
|
emit::{emit_loading, init_loading, loading_try_for_each_concurrent},
|
||||||
|
LoadingBarId, LoadingBarType,
|
||||||
|
},
|
||||||
|
process::Profile,
|
||||||
state::State,
|
state::State,
|
||||||
util::{fetch::*, platform::OsExt},
|
util::{fetch::*, platform::OsExt},
|
||||||
};
|
};
|
||||||
@@ -19,14 +24,25 @@ use tokio::{fs, sync::OnceCell};
|
|||||||
pub async fn download_minecraft(
|
pub async fn download_minecraft(
|
||||||
st: &State,
|
st: &State,
|
||||||
version: &GameVersionInfo,
|
version: &GameVersionInfo,
|
||||||
|
profile: &Profile,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
log::info!("Downloading Minecraft version {}", version.id);
|
log::info!("Downloading Minecraft version {}", version.id);
|
||||||
let assets_index = download_assets_index(st, version).await?;
|
let assets_index = download_assets_index(st, version).await?;
|
||||||
|
|
||||||
|
let loading_bar = init_loading(
|
||||||
|
LoadingBarType::MinecraftDownload {
|
||||||
|
// If we are downloading minecraft for a profile, provide its name and uuid
|
||||||
|
profile_name: profile.metadata.name.clone(),
|
||||||
|
profile_uuid: profile.uuid,
|
||||||
|
},
|
||||||
|
100.0,
|
||||||
|
"Downloading Minecraft...",
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
tokio::try_join! {
|
tokio::try_join! {
|
||||||
download_client(st, version),
|
download_client(st, version, Some(&loading_bar)),
|
||||||
download_assets(st, version.assets == "legacy", &assets_index),
|
download_assets(st, version.assets == "legacy", &assets_index, Some(&loading_bar)),
|
||||||
download_libraries(st, version.libraries.as_slice(), &version.id)
|
download_libraries(st, version.libraries.as_slice(), &version.id, Some(&loading_bar))
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
log::info!("Done downloading Minecraft!");
|
log::info!("Done downloading Minecraft!");
|
||||||
@@ -74,6 +90,7 @@ pub async fn download_version_info(
|
|||||||
pub async fn download_client(
|
pub async fn download_client(
|
||||||
st: &State,
|
st: &State,
|
||||||
version_info: &GameVersionInfo,
|
version_info: &GameVersionInfo,
|
||||||
|
loading_bar: Option<&LoadingBarId>,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
let version = &version_info.id;
|
let version = &version_info.id;
|
||||||
log::debug!("Locating client for version {version}");
|
log::debug!("Locating client for version {version}");
|
||||||
@@ -101,6 +118,9 @@ pub async fn download_client(
|
|||||||
write(&path, &bytes, &st.io_semaphore).await?;
|
write(&path, &bytes, &st.io_semaphore).await?;
|
||||||
log::info!("Fetched client version {version}");
|
log::info!("Fetched client version {version}");
|
||||||
}
|
}
|
||||||
|
if let Some(loading_bar) = loading_bar {
|
||||||
|
emit_loading(loading_bar, 20.0, None).await?;
|
||||||
|
}
|
||||||
|
|
||||||
log::debug!("Client loaded for version {version}!");
|
log::debug!("Client loaded for version {version}!");
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -138,11 +158,20 @@ pub async fn download_assets(
|
|||||||
st: &State,
|
st: &State,
|
||||||
with_legacy: bool,
|
with_legacy: bool,
|
||||||
index: &AssetsIndex,
|
index: &AssetsIndex,
|
||||||
|
loading_bar: Option<&LoadingBarId>,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
log::debug!("Loading assets");
|
log::debug!("Loading assets");
|
||||||
stream::iter(index.objects.iter())
|
let num_futs = index.objects.len();
|
||||||
.map(Ok::<(&String, &Asset), crate::Error>)
|
let assets = stream::iter(index.objects.iter())
|
||||||
.try_for_each_concurrent(None, |(name, asset)| async move {
|
.map(Ok::<(&String, &Asset), crate::Error>);
|
||||||
|
|
||||||
|
loading_try_for_each_concurrent(assets,
|
||||||
|
None,
|
||||||
|
loading_bar,
|
||||||
|
50.0,
|
||||||
|
num_futs,
|
||||||
|
None,
|
||||||
|
|(name, asset)| async move {
|
||||||
let hash = &asset.hash;
|
let hash = &asset.hash;
|
||||||
let resource_path = st.directories.object_dir(hash);
|
let resource_path = st.directories.object_dir(hash);
|
||||||
let url = format!(
|
let url = format!(
|
||||||
@@ -190,6 +219,7 @@ pub async fn download_libraries(
|
|||||||
st: &State,
|
st: &State,
|
||||||
libraries: &[Library],
|
libraries: &[Library],
|
||||||
version: &str,
|
version: &str,
|
||||||
|
loading_bar: Option<&LoadingBarId>,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
log::debug!("Loading libraries");
|
log::debug!("Loading libraries");
|
||||||
|
|
||||||
@@ -197,10 +227,10 @@ pub async fn download_libraries(
|
|||||||
fs::create_dir_all(st.directories.libraries_dir()),
|
fs::create_dir_all(st.directories.libraries_dir()),
|
||||||
fs::create_dir_all(st.directories.version_natives_dir(version))
|
fs::create_dir_all(st.directories.version_natives_dir(version))
|
||||||
}?;
|
}?;
|
||||||
|
let num_files = libraries.len();
|
||||||
|
loading_try_for_each_concurrent(
|
||||||
stream::iter(libraries.iter())
|
stream::iter(libraries.iter())
|
||||||
.map(Ok::<&Library, crate::Error>)
|
.map(Ok::<&Library, crate::Error>), None, loading_bar,50.0,num_files, None,|library| async move {
|
||||||
.try_for_each_concurrent(None, |library| async move {
|
|
||||||
if let Some(rules) = &library.rules {
|
if let Some(rules) = &library.rules {
|
||||||
if !rules.iter().all(super::parse_rule) {
|
if !rules.iter().all(super::parse_rule) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
use crate::{process, state as st};
|
use crate::{process, state as st};
|
||||||
use daedalus as d;
|
use daedalus as d;
|
||||||
use dunce::canonicalize;
|
use dunce::canonicalize;
|
||||||
|
use st::Profile;
|
||||||
use std::{path::Path, process::Stdio};
|
use std::{path::Path, process::Stdio};
|
||||||
use tokio::process::{Child, Command};
|
use tokio::process::{Child, Command};
|
||||||
|
|
||||||
@@ -57,6 +58,7 @@ pub async fn launch_minecraft(
|
|||||||
memory: &st::MemorySettings,
|
memory: &st::MemorySettings,
|
||||||
resolution: &st::WindowSize,
|
resolution: &st::WindowSize,
|
||||||
credentials: &auth::Credentials,
|
credentials: &auth::Credentials,
|
||||||
|
profile: &Profile, // optional ref to Profile for event tracking
|
||||||
) -> crate::Result<Child> {
|
) -> crate::Result<Child> {
|
||||||
let state = st::State::get().await?;
|
let state = st::State::get().await?;
|
||||||
let instance_path = &canonicalize(instance_path)?;
|
let instance_path = &canonicalize(instance_path)?;
|
||||||
@@ -88,7 +90,7 @@ pub async fn launch_minecraft(
|
|||||||
.version_dir(&version_jar)
|
.version_dir(&version_jar)
|
||||||
.join(format!("{version_jar}.jar"));
|
.join(format!("{version_jar}.jar"));
|
||||||
|
|
||||||
download::download_minecraft(&state, &version_info).await?;
|
download::download_minecraft(&state, &version_info, profile).await?;
|
||||||
st::State::sync().await?;
|
st::State::sync().await?;
|
||||||
|
|
||||||
if let Some(processors) = &version_info.processors {
|
if let Some(processors) = &version_info.processors {
|
||||||
|
|||||||
@@ -13,9 +13,11 @@ mod util;
|
|||||||
mod api;
|
mod api;
|
||||||
mod config;
|
mod config;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod event;
|
||||||
mod launcher;
|
mod launcher;
|
||||||
mod state;
|
mod state;
|
||||||
|
|
||||||
pub use api::*;
|
pub use api::*;
|
||||||
pub use error::*;
|
pub use error::*;
|
||||||
|
pub use event::EventState;
|
||||||
pub use state::State;
|
pub use state::State;
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ use tokio::io::{AsyncBufReadExt, BufReader};
|
|||||||
use tokio::process::{ChildStderr, ChildStdout};
|
use tokio::process::{ChildStderr, ChildStdout};
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
use crate::event::emit::emit_process;
|
||||||
|
use crate::event::ProcessPayloadType;
|
||||||
|
|
||||||
use super::Profile;
|
use super::Profile;
|
||||||
|
|
||||||
// Child processes (instances of Minecraft)
|
// Child processes (instances of Minecraft)
|
||||||
@@ -13,6 +16,7 @@ pub struct Children(HashMap<u32, Arc<RwLock<MinecraftChild>>>);
|
|||||||
// Minecraft Child, bundles together the PID, the actual Child, and the easily queryable stdout and stderr streams
|
// Minecraft Child, bundles together the PID, the actual Child, and the easily queryable stdout and stderr streams
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MinecraftChild {
|
pub struct MinecraftChild {
|
||||||
|
pub uuid: uuid::Uuid,
|
||||||
pub pid: u32,
|
pub pid: u32,
|
||||||
pub profile_path: PathBuf, //todo: make UUID when profiles are recognized by UUID
|
pub profile_path: PathBuf, //todo: make UUID when profiles are recognized by UUID
|
||||||
pub child: tokio::process::Child,
|
pub child: tokio::process::Child,
|
||||||
@@ -28,12 +32,14 @@ impl Children {
|
|||||||
// Inserts a child process to keep track of, and returns a reference to the container struct MinecraftChild
|
// Inserts a child process to keep track of, and returns a reference to the container struct MinecraftChild
|
||||||
// The threads for stdout and stderr are spawned here
|
// The threads for stdout and stderr are spawned here
|
||||||
// Unlike a Hashmap's 'insert', this directly returns the reference to the Child rather than any previously stored Child that may exist
|
// Unlike a Hashmap's 'insert', this directly returns the reference to the Child rather than any previously stored Child that may exist
|
||||||
pub fn insert_process(
|
pub async fn insert_process(
|
||||||
&mut self,
|
&mut self,
|
||||||
pid: u32,
|
pid: u32,
|
||||||
profile_path: PathBuf,
|
profile_path: PathBuf,
|
||||||
mut child: tokio::process::Child,
|
mut child: tokio::process::Child,
|
||||||
) -> Arc<RwLock<MinecraftChild>> {
|
) -> crate::Result<Arc<RwLock<MinecraftChild>>> {
|
||||||
|
let uuid = uuid::Uuid::new_v4();
|
||||||
|
|
||||||
// Create std watcher threads for stdout and stderr
|
// Create std watcher threads for stdout and stderr
|
||||||
let stdout = SharedOutput::new();
|
let stdout = SharedOutput::new();
|
||||||
if let Some(child_stdout) = child.stdout.take() {
|
if let Some(child_stdout) = child.stdout.take() {
|
||||||
@@ -54,8 +60,17 @@ impl Children {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emit_process(
|
||||||
|
uuid,
|
||||||
|
pid,
|
||||||
|
ProcessPayloadType::Launched,
|
||||||
|
"Launched Minecraft",
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
// Create MinecraftChild
|
// Create MinecraftChild
|
||||||
let mchild = MinecraftChild {
|
let mchild = MinecraftChild {
|
||||||
|
uuid,
|
||||||
pid,
|
pid,
|
||||||
profile_path,
|
profile_path,
|
||||||
child,
|
child,
|
||||||
@@ -64,7 +79,7 @@ impl Children {
|
|||||||
};
|
};
|
||||||
let mchild = Arc::new(RwLock::new(mchild));
|
let mchild = Arc::new(RwLock::new(mchild));
|
||||||
self.0.insert(pid, mchild.clone());
|
self.0.insert(pid, mchild.clone());
|
||||||
mchild
|
Ok(mchild)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a ref to the child
|
// Returns a ref to the child
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
//! Theseus state management system
|
//! Theseus state management system
|
||||||
use crate::config::sled_config;
|
use crate::config::sled_config;
|
||||||
|
use crate::event::emit::emit_loading;
|
||||||
|
|
||||||
|
use crate::event::emit::init_loading;
|
||||||
|
use crate::event::LoadingBarType;
|
||||||
use crate::jre;
|
use crate::jre;
|
||||||
|
use crate::loading_join;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::{OnceCell, RwLock, Semaphore};
|
use tokio::sync::{OnceCell, RwLock, Semaphore};
|
||||||
|
|
||||||
@@ -68,6 +74,8 @@ impl State {
|
|||||||
LAUNCHER_STATE
|
LAUNCHER_STATE
|
||||||
.get_or_try_init(|| {
|
.get_or_try_init(|| {
|
||||||
async {
|
async {
|
||||||
|
|
||||||
|
let loading_bar = init_loading(LoadingBarType::StateInit, 100.0, "Initializing launcher...").await?;
|
||||||
// Directories
|
// Directories
|
||||||
let directories = DirectoryInfo::init().await?;
|
let directories = DirectoryInfo::init().await?;
|
||||||
|
|
||||||
@@ -77,6 +85,8 @@ impl State {
|
|||||||
.path(directories.database_file())
|
.path(directories.database_file())
|
||||||
.open()?;
|
.open()?;
|
||||||
|
|
||||||
|
emit_loading(&loading_bar, 10.0, None).await?;
|
||||||
|
|
||||||
// Settings
|
// Settings
|
||||||
let mut settings =
|
let mut settings =
|
||||||
Settings::init(&directories.settings_file()).await?;
|
Settings::init(&directories.settings_file()).await?;
|
||||||
@@ -87,11 +97,17 @@ impl State {
|
|||||||
let io_semaphore =
|
let io_semaphore =
|
||||||
RwLock::new(Semaphore::new(io_semaphore_max));
|
RwLock::new(Semaphore::new(io_semaphore_max));
|
||||||
|
|
||||||
|
let metadata_fut = Metadata::init(&database);
|
||||||
|
let profiles_fut =
|
||||||
|
Profiles::init(&directories, &io_semaphore);
|
||||||
|
|
||||||
// Launcher data
|
// Launcher data
|
||||||
let (metadata, profiles) = tokio::try_join! {
|
let (metadata, profiles) = loading_join! {
|
||||||
Metadata::init(&database),
|
Some(&loading_bar), 20.0, Some("Initializing metadata and profiles...");
|
||||||
Profiles::init(&directories, &io_semaphore),
|
metadata_fut, profiles_fut
|
||||||
}?;
|
};
|
||||||
|
|
||||||
|
emit_loading(&loading_bar, 10.0, None).await?;
|
||||||
let users = Users::init(&database)?;
|
let users = Users::init(&database)?;
|
||||||
|
|
||||||
let children = Children::new();
|
let children = Children::new();
|
||||||
@@ -101,13 +117,16 @@ impl State {
|
|||||||
// On launcher initialization, attempt a tag fetch after tags init
|
// On launcher initialization, attempt a tag fetch after tags init
|
||||||
let mut tags = Tags::init(&database)?;
|
let mut tags = Tags::init(&database)?;
|
||||||
if let Err(tag_fetch_err) =
|
if let Err(tag_fetch_err) =
|
||||||
tags.fetch_update(&io_semaphore).await
|
tags.fetch_update(&io_semaphore,Some(&loading_bar)).await
|
||||||
{
|
{
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
"Failed to fetch tags on launcher init: {}",
|
"Failed to fetch tags on launcher init: {}",
|
||||||
tag_fetch_err
|
tag_fetch_err
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
emit_loading(&loading_bar, 10.0, None).await?;
|
||||||
|
|
||||||
// On launcher initialization, if global java variables are unset, try to find and set them
|
// On launcher initialization, if global java variables are unset, try to find and set them
|
||||||
// (they are required for the game to launch)
|
// (they are required for the game to launch)
|
||||||
if settings.java_globals.count() == 0 {
|
if settings.java_globals.count() == 0 {
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
use super::settings::{Hooks, MemorySettings, WindowSize};
|
use super::settings::{Hooks, MemorySettings, WindowSize};
|
||||||
use crate::config::MODRINTH_API_URL;
|
use crate::config::MODRINTH_API_URL;
|
||||||
use crate::data::DirectoryInfo;
|
use crate::data::DirectoryInfo;
|
||||||
|
use crate::event::emit::{
|
||||||
|
emit_profile, init_loading, loading_try_for_each_concurrent,
|
||||||
|
};
|
||||||
|
use crate::event::{LoadingBarType, ProfilePayloadType};
|
||||||
use crate::state::projects::Project;
|
use crate::state::projects::Project;
|
||||||
use crate::state::{ModrinthVersion, ProjectType};
|
use crate::state::{ModrinthVersion, ProjectType};
|
||||||
use crate::util::fetch::{fetch, fetch_json, write, write_cached_icon};
|
use crate::util::fetch::{fetch, fetch_json, write, write_cached_icon};
|
||||||
@@ -17,6 +21,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
use tokio::sync::Semaphore;
|
use tokio::sync::Semaphore;
|
||||||
use tokio::{fs, sync::RwLock};
|
use tokio::{fs, sync::RwLock};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
const PROFILE_JSON_PATH: &str = "profile.json";
|
const PROFILE_JSON_PATH: &str = "profile.json";
|
||||||
|
|
||||||
@@ -28,6 +33,7 @@ pub const CURRENT_FORMAT_VERSION: u32 = 1;
|
|||||||
// Represent a Minecraft instance.
|
// Represent a Minecraft instance.
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct Profile {
|
pub struct Profile {
|
||||||
|
pub uuid: Uuid, // todo: will be used in restructure to refer to profiles
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
pub metadata: ProfileMetadata,
|
pub metadata: ProfileMetadata,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
@@ -90,6 +96,7 @@ pub struct JavaSettings {
|
|||||||
impl Profile {
|
impl Profile {
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub async fn new(
|
pub async fn new(
|
||||||
|
uuid: Uuid,
|
||||||
name: String,
|
name: String,
|
||||||
version: String,
|
version: String,
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
@@ -102,6 +109,7 @@ impl Profile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
uuid,
|
||||||
path: canonicalize(path)?,
|
path: canonicalize(path)?,
|
||||||
metadata: ProfileMetadata {
|
metadata: ProfileMetadata {
|
||||||
name,
|
name,
|
||||||
@@ -368,7 +376,14 @@ impl Profiles {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
pub fn insert(&mut self, profile: Profile) -> crate::Result<&Self> {
|
pub async fn insert(&mut self, profile: Profile) -> crate::Result<&Self> {
|
||||||
|
emit_profile(
|
||||||
|
profile.uuid,
|
||||||
|
profile.path.clone(),
|
||||||
|
&profile.metadata.name,
|
||||||
|
ProfilePayloadType::Added,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
self.0.insert(
|
self.0.insert(
|
||||||
canonicalize(&profile.path)?
|
canonicalize(&profile.path)?
|
||||||
.to_str()
|
.to_str()
|
||||||
@@ -387,6 +402,7 @@ impl Profiles {
|
|||||||
path: &'a Path,
|
path: &'a Path,
|
||||||
) -> crate::Result<&Self> {
|
) -> crate::Result<&Self> {
|
||||||
self.insert(Self::read_profile_from_dir(&canonicalize(path)?).await?)
|
self.insert(Self::read_profile_from_dir(&canonicalize(path)?).await?)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
@@ -404,9 +420,21 @@ impl Profiles {
|
|||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn sync(&self) -> crate::Result<&Self> {
|
pub async fn sync(&self) -> crate::Result<&Self> {
|
||||||
stream::iter(self.0.iter())
|
let loading_bar = init_loading(
|
||||||
.map(Ok::<_, crate::Error>)
|
LoadingBarType::ProfileSync,
|
||||||
.try_for_each_concurrent(None, |(path, profile)| async move {
|
100.0,
|
||||||
|
"Syncing profiles...",
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let num_futs = self.0.len();
|
||||||
|
loading_try_for_each_concurrent(
|
||||||
|
stream::iter(self.0.iter()).map(Ok::<_, crate::Error>),
|
||||||
|
None,
|
||||||
|
Some(&loading_bar),
|
||||||
|
100.0,
|
||||||
|
num_futs,
|
||||||
|
None,
|
||||||
|
|(path, profile)| async move {
|
||||||
let json = serde_json::to_vec(&profile)?;
|
let json = serde_json::to_vec(&profile)?;
|
||||||
|
|
||||||
let json_path = Path::new(&path.to_string_lossy().to_string())
|
let json_path = Path::new(&path.to_string_lossy().to_string())
|
||||||
@@ -414,8 +442,9 @@ impl Profiles {
|
|||||||
|
|
||||||
fs::write(json_path, json).await?;
|
fs::write(json_path, json).await?;
|
||||||
Ok::<_, crate::Error>(())
|
Ok::<_, crate::Error>(())
|
||||||
})
|
},
|
||||||
.await?;
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ use serde::{Deserialize, Serialize};
|
|||||||
use tokio::sync::{RwLock, Semaphore};
|
use tokio::sync::{RwLock, Semaphore};
|
||||||
|
|
||||||
use crate::config::{BINCODE_CONFIG, MODRINTH_API_URL};
|
use crate::config::{BINCODE_CONFIG, MODRINTH_API_URL};
|
||||||
|
use crate::event::LoadingBarId;
|
||||||
|
use crate::loading_join;
|
||||||
use crate::util::fetch::fetch_json;
|
use crate::util::fetch::fetch_json;
|
||||||
|
|
||||||
const CATEGORIES_DB_TREE: &[u8] = b"categories";
|
const CATEGORIES_DB_TREE: &[u8] = b"categories";
|
||||||
@@ -139,6 +141,7 @@ impl Tags {
|
|||||||
pub async fn fetch_update(
|
pub async fn fetch_update(
|
||||||
&mut self,
|
&mut self,
|
||||||
semaphore: &RwLock<Semaphore>,
|
semaphore: &RwLock<Semaphore>,
|
||||||
|
loading_bar: Option<&LoadingBarId>,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
let categories = format!("{MODRINTH_API_URL}tag/category");
|
let categories = format!("{MODRINTH_API_URL}tag/category");
|
||||||
let loaders = format!("{MODRINTH_API_URL}tag/loader");
|
let loaders = format!("{MODRINTH_API_URL}tag/loader");
|
||||||
@@ -147,6 +150,50 @@ impl Tags {
|
|||||||
let donation_platforms =
|
let donation_platforms =
|
||||||
format!("{MODRINTH_API_URL}tag/donation_platform");
|
format!("{MODRINTH_API_URL}tag/donation_platform");
|
||||||
let report_types = format!("{MODRINTH_API_URL}tag/report_type");
|
let report_types = format!("{MODRINTH_API_URL}tag/report_type");
|
||||||
|
|
||||||
|
let categories_fut = fetch_json::<Vec<Category>>(
|
||||||
|
Method::GET,
|
||||||
|
&categories,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
semaphore,
|
||||||
|
);
|
||||||
|
let loaders_fut = fetch_json::<Vec<Loader>>(
|
||||||
|
Method::GET,
|
||||||
|
&loaders,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
semaphore,
|
||||||
|
);
|
||||||
|
let game_versions_fut = fetch_json::<Vec<GameVersion>>(
|
||||||
|
Method::GET,
|
||||||
|
&game_versions,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
semaphore,
|
||||||
|
);
|
||||||
|
let licenses_fut = fetch_json::<Vec<License>>(
|
||||||
|
Method::GET,
|
||||||
|
&licenses,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
semaphore,
|
||||||
|
);
|
||||||
|
let donation_platforms_fut = fetch_json::<Vec<DonationPlatform>>(
|
||||||
|
Method::GET,
|
||||||
|
&donation_platforms,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
semaphore,
|
||||||
|
);
|
||||||
|
let report_types_fut = fetch_json::<Vec<String>>(
|
||||||
|
Method::GET,
|
||||||
|
&report_types,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
semaphore,
|
||||||
|
);
|
||||||
|
|
||||||
let (
|
let (
|
||||||
categories,
|
categories,
|
||||||
loaders,
|
loaders,
|
||||||
@@ -154,50 +201,14 @@ impl Tags {
|
|||||||
licenses,
|
licenses,
|
||||||
donation_platforms,
|
donation_platforms,
|
||||||
report_types,
|
report_types,
|
||||||
) = tokio::try_join!(
|
) = loading_join!(loading_bar, 0.5, None;
|
||||||
fetch_json::<Vec<Category>>(
|
categories_fut,
|
||||||
Method::GET,
|
loaders_fut,
|
||||||
&categories,
|
game_versions_fut,
|
||||||
None,
|
licenses_fut,
|
||||||
None,
|
donation_platforms_fut,
|
||||||
semaphore
|
report_types_fut
|
||||||
),
|
);
|
||||||
fetch_json::<Vec<Loader>>(
|
|
||||||
Method::GET,
|
|
||||||
&loaders,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
semaphore
|
|
||||||
),
|
|
||||||
fetch_json::<Vec<GameVersion>>(
|
|
||||||
Method::GET,
|
|
||||||
&game_versions,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
semaphore
|
|
||||||
),
|
|
||||||
fetch_json::<Vec<License>>(
|
|
||||||
Method::GET,
|
|
||||||
&licenses,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
semaphore
|
|
||||||
),
|
|
||||||
fetch_json::<Vec<DonationPlatform>>(
|
|
||||||
Method::GET,
|
|
||||||
&donation_platforms,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
semaphore
|
|
||||||
),
|
|
||||||
fetch_json::<Vec<String>>(
|
|
||||||
Method::GET,
|
|
||||||
&report_types,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
semaphore
|
|
||||||
),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Store the tags in the database
|
// Store the tags in the database
|
||||||
self.0.categories.insert(
|
self.0.categories.insert(
|
||||||
|
|||||||
2689
theseus_gui/package-lock.json
generated
Normal file
2689
theseus_gui/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,7 @@ tauri-build = { version = "1.2", features = [] }
|
|||||||
regex = "1.5"
|
regex = "1.5"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
theseus = { path = "../../theseus" }
|
theseus = { path = "../../theseus", features = ["tauri"] }
|
||||||
|
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use theseus::prelude::*;
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn profile_create_empty() -> Result<PathBuf> {
|
pub async fn profile_create_empty() -> Result<PathBuf> {
|
||||||
let res = profile_create::profile_create_empty().await?;
|
let res = profile_create::profile_create_empty().await?;
|
||||||
State::sync().await?;
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ mod api;
|
|||||||
|
|
||||||
// Should be called in launcher initialization
|
// Should be called in launcher initialization
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn initialize_state() -> api::Result<()> {
|
async fn initialize_state(app: tauri::AppHandle) -> api::Result<()> {
|
||||||
|
theseus::EventState::init(app).await?;
|
||||||
State::get().await?;
|
State::get().await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
80
theseus_gui/src/helpers/events.js
Normal file
80
theseus_gui/src/helpers/events.js
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
Event listeners for interacting with the Rust api
|
||||||
|
These are all async functions that return a promise that resolves to the payload object (whatever Rust is trying to deliver)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
callback is a function that takes a single argument, which is the payload object (whatever Rust is trying to deliver)
|
||||||
|
|
||||||
|
You can call these to await any kind of emitted signal from Rust, and then do something with the payload object
|
||||||
|
An example place to put this is at the start of main.js before the state is initialized- that way
|
||||||
|
you can listen for any emitted signal from Rust and do something with it as the state is being initialized
|
||||||
|
|
||||||
|
Example:
|
||||||
|
import { loading_listener } from '@/helpers/events'
|
||||||
|
await loading_listener((event) => {
|
||||||
|
// event.event is the event name (useful if you want to use a single callback fn for multiple event types)
|
||||||
|
// event.payload is the payload object
|
||||||
|
console.log(event)
|
||||||
|
})
|
||||||
|
|
||||||
|
Putting that in a script will print any emitted signal from rust
|
||||||
|
*/
|
||||||
|
import { listen } from '@tauri-apps/api/event'
|
||||||
|
|
||||||
|
/// Payload for the 'loading' event
|
||||||
|
/*
|
||||||
|
LoadingPayload {
|
||||||
|
event: "StateInit", "PackDownload", etc
|
||||||
|
- Certain states have additional fields:
|
||||||
|
- PackDownload: {
|
||||||
|
pack_name: name of the pack
|
||||||
|
pack_id, optional, the id of the modpack
|
||||||
|
pack_version, optional, the version of the modpack
|
||||||
|
- MinecraftDownload: {
|
||||||
|
profile_name: name of the profile
|
||||||
|
profile_uuid: unique identification of the profile
|
||||||
|
loader_uuid: unique identification of the loading bar
|
||||||
|
fraction: number, (as a fraction of 1, how much we'vel oaded so far). If null, by convention, loading is finished
|
||||||
|
message: message to display to the user
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
export async function loading_listener(callback) {
|
||||||
|
return await listen('loading', (event) => callback(event.payload))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Payload for the 'process' event
|
||||||
|
/*
|
||||||
|
ProcessPayload {
|
||||||
|
uuid: unique identification of the process in the state (currently identified by PID, but that will change)
|
||||||
|
pid: process ID
|
||||||
|
event: event type ("Launched", "Finished")
|
||||||
|
message: message to display to the user
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
export async function process_listener(callback) {
|
||||||
|
return await listen('process', (event) => callback(event.payload))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Payload for the 'profile' event
|
||||||
|
/*
|
||||||
|
ProfilePayload {
|
||||||
|
uuid: unique identification of the process in the state (currently identified by path, but that will change)
|
||||||
|
name: name of the profile
|
||||||
|
path: path to profile
|
||||||
|
event: event type ("Created", "Added", "Edited", "Removed")
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
export async function profile_listener(callback) {
|
||||||
|
return await listen('profile', (event) => callback(event.payload))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Payload for the 'warning' event
|
||||||
|
/*
|
||||||
|
WarningPayload {
|
||||||
|
message: message to display to the user
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
export async function warning_listener(callback) {
|
||||||
|
return await listen('warning', (event) => callback(event.payload))
|
||||||
|
}
|
||||||
@@ -118,23 +118,16 @@ async fn main() -> theseus::Result<()> {
|
|||||||
println!("Minecraft PID: {}", pid);
|
println!("Minecraft PID: {}", pid);
|
||||||
|
|
||||||
// Wait 5 seconds
|
// Wait 5 seconds
|
||||||
println!("Waiting 20 seconds to gather logs...");
|
println!("Waiting 5 seconds to gather logs...");
|
||||||
sleep(Duration::from_secs(20)).await;
|
sleep(Duration::from_secs(5)).await;
|
||||||
let stdout = process::get_stdout_by_pid(pid).await?;
|
let _stdout = process::get_stdout_by_pid(pid).await?;
|
||||||
println!("Logs after 5sec <<< {stdout} >>> end stdout");
|
let _stderr = process::get_stderr_by_pid(pid).await?;
|
||||||
|
// println!("Logs after 5sec <<< {stdout} >>> end stdout");
|
||||||
|
|
||||||
println!(
|
|
||||||
"All running process PIDs {:?}",
|
|
||||||
process::get_all_running_pids().await?
|
|
||||||
);
|
|
||||||
println!(
|
println!(
|
||||||
"All running process paths {:?}",
|
"All running process paths {:?}",
|
||||||
process::get_all_running_profile_paths().await?
|
process::get_all_running_profile_paths().await?
|
||||||
);
|
);
|
||||||
println!(
|
|
||||||
"All running process profiles {:?}",
|
|
||||||
process::get_all_running_profiles().await?
|
|
||||||
);
|
|
||||||
|
|
||||||
// hold the lock to the process until it ends
|
// hold the lock to the process until it ends
|
||||||
println!("Waiting for process to end...");
|
println!("Waiting for process to end...");
|
||||||
|
|||||||
Reference in New Issue
Block a user