forked from didirus/AstralRinth
* initial * more fixes * logs * more fixes * working rescuer * minor log display fix * mac fixes * minor fix * libsselinux1 * linux error * actions test * more bugs. Modpack page! BIG changes * changed minimum 64 -> 8 * removed modpack page moved to modal * removed unnecessary css * mac compile * many revs * Merge colorful logs (#725) * make implementation not dumb * run prettier * null -> true * Add line numbers & make errors more robust. * improvments * changes; virtual scroll --------- Co-authored-by: qtchaos <72168435+qtchaos@users.noreply.github.com> * omorphia colors, comments fix * fixes; _JAVA_OPTIONS * revs * mac specific * more mac * some fixes * quick fix * add java reinstall option --------- Co-authored-by: qtchaos <72168435+qtchaos@users.noreply.github.com> Co-authored-by: Jai A <jaiagr+gpg@pm.me>
713 lines
24 KiB
Rust
713 lines
24 KiB
Rust
use super::{Profile, ProfilePathId};
|
|
use chrono::{DateTime, Utc};
|
|
use serde::Deserialize;
|
|
use serde::Serialize;
|
|
use std::{collections::HashMap, sync::Arc};
|
|
use sysinfo::PidExt;
|
|
use tokio::process::Child;
|
|
use tokio::process::Command;
|
|
use tokio::sync::RwLock;
|
|
|
|
use crate::event::emit::emit_process;
|
|
use crate::event::ProcessPayloadType;
|
|
use crate::util::fetch::read_json;
|
|
use crate::util::io::IOError;
|
|
use crate::{profile, ErrorKind};
|
|
use sysinfo::{ProcessExt, SystemExt};
|
|
|
|
use tokio::task::JoinHandle;
|
|
use uuid::Uuid;
|
|
|
|
const PROCESSES_JSON: &str = "processes.json";
|
|
|
|
// Child processes (instances of Minecraft)
|
|
// A wrapper over a Hashmap connecting PID -> MinecraftChild
|
|
pub struct Children(HashMap<Uuid, Arc<RwLock<MinecraftChild>>>);
|
|
|
|
#[derive(Debug)]
|
|
pub enum ChildType {
|
|
// A child process that is being managed by tokio
|
|
TokioChild(Child),
|
|
// A child process that was rescued from a cache (e.g. a process that was launched by theseus before the launcher was restarted)
|
|
// This may not have all the same functionality as a TokioChild
|
|
RescuedPID(u32),
|
|
}
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
pub struct ProcessCache {
|
|
pub pid: u32,
|
|
pub uuid: Uuid,
|
|
pub start_time: u64,
|
|
pub name: String,
|
|
pub exe: String,
|
|
pub profile_relative_path: ProfilePathId,
|
|
pub post_command: Option<String>,
|
|
}
|
|
impl ChildType {
|
|
pub async fn try_wait(&mut self) -> crate::Result<Option<i32>> {
|
|
match self {
|
|
ChildType::TokioChild(child) => Ok(child
|
|
.try_wait()
|
|
.map_err(IOError::from)?
|
|
.map(|x| x.code().unwrap_or(0))),
|
|
ChildType::RescuedPID(pid) => {
|
|
let mut system = sysinfo::System::new();
|
|
if !system.refresh_process(sysinfo::Pid::from_u32(*pid)) {
|
|
return Ok(Some(0));
|
|
}
|
|
let process = system.process(sysinfo::Pid::from_u32(*pid));
|
|
if let Some(process) = process {
|
|
if process.status() == sysinfo::ProcessStatus::Run {
|
|
Ok(None)
|
|
} else {
|
|
Ok(Some(0))
|
|
}
|
|
} else {
|
|
Ok(Some(0))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
pub async fn kill(&mut self) -> crate::Result<()> {
|
|
match self {
|
|
ChildType::TokioChild(child) => {
|
|
Ok(child.kill().await.map_err(IOError::from)?)
|
|
}
|
|
ChildType::RescuedPID(pid) => {
|
|
let mut system = sysinfo::System::new();
|
|
if system.refresh_process(sysinfo::Pid::from_u32(*pid)) {
|
|
let process = system.process(sysinfo::Pid::from_u32(*pid));
|
|
if let Some(process) = process {
|
|
process.kill();
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
pub fn id(&self) -> Option<u32> {
|
|
match self {
|
|
ChildType::TokioChild(child) => child.id(),
|
|
ChildType::RescuedPID(pid) => Some(*pid),
|
|
}
|
|
}
|
|
|
|
// Caches the process so that it can be restored if the launcher is restarted
|
|
// Stored in the caches/metadata/processes.json file
|
|
pub async fn cache_process(
|
|
&self,
|
|
uuid: uuid::Uuid,
|
|
profile_path_id: ProfilePathId,
|
|
post_command: Option<String>,
|
|
) -> crate::Result<()> {
|
|
let pid = match self {
|
|
ChildType::TokioChild(child) => child.id().unwrap_or(0),
|
|
ChildType::RescuedPID(pid) => *pid,
|
|
};
|
|
|
|
let state = crate::State::get().await?;
|
|
|
|
let mut system = sysinfo::System::new();
|
|
system.refresh_processes();
|
|
let process =
|
|
system.process(sysinfo::Pid::from_u32(pid)).ok_or_else(|| {
|
|
crate::ErrorKind::LauncherError(format!(
|
|
"Could not find process {}",
|
|
pid
|
|
))
|
|
})?;
|
|
let start_time = process.start_time();
|
|
let name = process.name().to_string();
|
|
let exe = process.exe().to_string_lossy().to_string();
|
|
|
|
let cached_process = ProcessCache {
|
|
pid,
|
|
start_time,
|
|
name,
|
|
exe,
|
|
post_command,
|
|
uuid,
|
|
profile_relative_path: profile_path_id,
|
|
};
|
|
|
|
let children_path = state
|
|
.directories
|
|
.caches_meta_dir()
|
|
.await
|
|
.join(PROCESSES_JSON);
|
|
let mut children_caches = if let Ok(children_json) =
|
|
read_json::<HashMap<uuid::Uuid, ProcessCache>>(
|
|
&children_path,
|
|
&state.io_semaphore,
|
|
)
|
|
.await
|
|
{
|
|
children_json
|
|
} else {
|
|
HashMap::new()
|
|
};
|
|
children_caches.insert(uuid, cached_process);
|
|
crate::util::fetch::write(
|
|
&children_path,
|
|
&serde_json::to_vec(&children_caches)?,
|
|
&state.io_semaphore,
|
|
)
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// Removes the process from the cache (ie: on process exit)
|
|
pub async fn remove_cache(&self, uuid: uuid::Uuid) -> crate::Result<()> {
|
|
let state = crate::State::get().await?;
|
|
let children_path = state
|
|
.directories
|
|
.caches_meta_dir()
|
|
.await
|
|
.join(PROCESSES_JSON);
|
|
let mut children_caches = if let Ok(children_json) =
|
|
read_json::<HashMap<uuid::Uuid, ProcessCache>>(
|
|
&children_path,
|
|
&state.io_semaphore,
|
|
)
|
|
.await
|
|
{
|
|
children_json
|
|
} else {
|
|
HashMap::new()
|
|
};
|
|
children_caches.remove(&uuid);
|
|
crate::util::fetch::write(
|
|
&children_path,
|
|
&serde_json::to_vec(&children_caches)?,
|
|
&state.io_semaphore,
|
|
)
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
// Minecraft Child, bundles together the PID, the actual Child, and the easily queryable stdout and stderr streams (if needed)
|
|
#[derive(Debug)]
|
|
pub struct MinecraftChild {
|
|
pub uuid: Uuid,
|
|
pub profile_relative_path: ProfilePathId,
|
|
pub manager: Option<JoinHandle<crate::Result<i32>>>, // None when future has completed and been handled
|
|
pub current_child: Arc<RwLock<ChildType>>,
|
|
pub last_updated_playtime: DateTime<Utc>, // The last time we updated the playtime for the associated profile
|
|
}
|
|
|
|
impl Children {
|
|
pub fn new() -> Self {
|
|
Children(HashMap::new())
|
|
}
|
|
|
|
// Loads cached processes from the caches/metadata/processes.json file, re-inserts them into the hashmap, and removes them from the file
|
|
// This will only be called once, on startup. Only processes who match a cached process (name, time started, pid, etc) will be re-inserted
|
|
pub async fn rescue_cache(&mut self) -> crate::Result<()> {
|
|
let state = crate::State::get().await?;
|
|
let children_path = state
|
|
.directories
|
|
.caches_meta_dir()
|
|
.await
|
|
.join(PROCESSES_JSON);
|
|
|
|
let mut children_caches = if let Ok(children_json) =
|
|
read_json::<HashMap<uuid::Uuid, ProcessCache>>(
|
|
&children_path,
|
|
&state.io_semaphore,
|
|
)
|
|
.await
|
|
{
|
|
// Overwrite the file with an empty hashmap- we will re-insert the cached processes
|
|
let empty = HashMap::<uuid::Uuid, ProcessCache>::new();
|
|
crate::util::fetch::write(
|
|
&children_path,
|
|
&serde_json::to_vec(&empty)?,
|
|
&state.io_semaphore,
|
|
)
|
|
.await?;
|
|
|
|
// Return the cached processes
|
|
children_json
|
|
} else {
|
|
HashMap::new()
|
|
};
|
|
|
|
for (_, cache) in children_caches.drain() {
|
|
let uuid = cache.uuid;
|
|
match self.insert_cached_process(cache).await {
|
|
Ok(child) => {
|
|
self.0.insert(uuid, child);
|
|
}
|
|
Err(e) => tracing::warn!(
|
|
"Failed to rescue cached process {}: {}",
|
|
uuid,
|
|
e
|
|
),
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
// Runs the command in process, 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
|
|
// Unlike a Hashmap's 'insert', this directly returns the reference to the MinecraftChild rather than any previously stored MinecraftChild that may exist
|
|
#[tracing::instrument(skip(
|
|
self,
|
|
uuid,
|
|
mc_command,
|
|
post_command,
|
|
censor_strings
|
|
))]
|
|
#[tracing::instrument(level = "trace", skip(self))]
|
|
#[theseus_macros::debug_pin]
|
|
pub async fn insert_new_process(
|
|
&mut self,
|
|
uuid: Uuid,
|
|
profile_relative_path: ProfilePathId,
|
|
mut mc_command: Command,
|
|
post_command: Option<String>, // Command to run after minecraft.
|
|
censor_strings: HashMap<String, String>,
|
|
) -> crate::Result<Arc<RwLock<MinecraftChild>>> {
|
|
// Takes the first element of the commands vector and spawns it
|
|
let child = mc_command.spawn().map_err(IOError::from)?;
|
|
let child = ChildType::TokioChild(child);
|
|
|
|
// Slots child into manager
|
|
let pid = child.id().ok_or_else(|| {
|
|
crate::ErrorKind::LauncherError(
|
|
"Process immediately failed, could not get PID".to_string(),
|
|
)
|
|
})?;
|
|
|
|
// Caches process so that it can be restored if the launcher is restarted
|
|
child
|
|
.cache_process(
|
|
uuid,
|
|
profile_relative_path.clone(),
|
|
post_command.clone(),
|
|
)
|
|
.await?;
|
|
let current_child = Arc::new(RwLock::new(child));
|
|
let manager = Some(tokio::spawn(Self::sequential_process_manager(
|
|
uuid,
|
|
post_command,
|
|
pid,
|
|
current_child.clone(),
|
|
profile_relative_path.clone(),
|
|
)));
|
|
|
|
emit_process(
|
|
uuid,
|
|
pid,
|
|
ProcessPayloadType::Launched,
|
|
"Launched Minecraft",
|
|
)
|
|
.await?;
|
|
|
|
let last_updated_playtime = Utc::now();
|
|
|
|
// Create MinecraftChild
|
|
let mchild = MinecraftChild {
|
|
uuid,
|
|
profile_relative_path,
|
|
current_child,
|
|
manager,
|
|
last_updated_playtime,
|
|
};
|
|
|
|
let mchild = Arc::new(RwLock::new(mchild));
|
|
self.0.insert(uuid, mchild.clone());
|
|
Ok(mchild)
|
|
}
|
|
|
|
// Rescues a cached process, inserts a child process to keep track of, and returns a reference to the container struct MinecraftChild
|
|
// Essentially 'reconnects' to a process that was launched by theseus before the launcher was restarted
|
|
// However, this may not have all the same functionality as a TokioChild, as we only have the PID and not the actual Child
|
|
// Only processes who match a cached process (name, time started, pid, etc) will be re-inserted. The function fails with an error if the process is notably different.
|
|
#[tracing::instrument(skip(self, cached_process,))]
|
|
#[tracing::instrument(level = "trace", skip(self))]
|
|
#[theseus_macros::debug_pin]
|
|
pub async fn insert_cached_process(
|
|
&mut self,
|
|
cached_process: ProcessCache,
|
|
) -> crate::Result<Arc<RwLock<MinecraftChild>>> {
|
|
let _state = crate::State::get().await?;
|
|
|
|
// Takes the first element of the commands vector and spawns it
|
|
// Checks processes, compares cached process to actual process
|
|
// Fails if notably different (meaning that the PID was reused, and we shouldn't reconnect to it)
|
|
{
|
|
let mut system = sysinfo::System::new();
|
|
system.refresh_processes();
|
|
let process = system
|
|
.process(sysinfo::Pid::from_u32(cached_process.pid))
|
|
.ok_or_else(|| {
|
|
crate::ErrorKind::LauncherError(format!(
|
|
"Could not find process {}",
|
|
cached_process.pid
|
|
))
|
|
})?;
|
|
|
|
if cached_process.start_time != process.start_time() {
|
|
return Err(ErrorKind::LauncherError(format!("Cached process {} has different start time than actual process {}", cached_process.pid, process.start_time())).into());
|
|
}
|
|
if cached_process.name != process.name() {
|
|
return Err(ErrorKind::LauncherError(format!("Cached process {} has different name than actual process {}", cached_process.pid, process.name())).into());
|
|
}
|
|
if cached_process.exe != process.exe().to_string_lossy() {
|
|
return Err(ErrorKind::LauncherError(format!("Cached process {} has different exe than actual process {}", cached_process.pid, process.exe().to_string_lossy())).into());
|
|
}
|
|
}
|
|
|
|
let child = ChildType::RescuedPID(cached_process.pid);
|
|
|
|
// Slots child into manager
|
|
let pid = child.id().ok_or_else(|| {
|
|
crate::ErrorKind::LauncherError(
|
|
"Process immediately failed, could not get PID".to_string(),
|
|
)
|
|
})?;
|
|
|
|
// Re-caches process so that it can be restored if the launcher is restarted
|
|
child
|
|
.cache_process(
|
|
cached_process.uuid,
|
|
cached_process.profile_relative_path.clone(),
|
|
cached_process.post_command.clone(),
|
|
)
|
|
.await?;
|
|
|
|
let current_child = Arc::new(RwLock::new(child));
|
|
let manager = Some(tokio::spawn(Self::sequential_process_manager(
|
|
cached_process.uuid,
|
|
cached_process.post_command,
|
|
pid,
|
|
current_child.clone(),
|
|
cached_process.profile_relative_path.clone(),
|
|
)));
|
|
|
|
emit_process(
|
|
cached_process.uuid,
|
|
pid,
|
|
ProcessPayloadType::Launched,
|
|
"Launched Minecraft",
|
|
)
|
|
.await?;
|
|
|
|
let last_updated_playtime = Utc::now();
|
|
|
|
// Create MinecraftChild
|
|
let mchild = MinecraftChild {
|
|
uuid: cached_process.uuid,
|
|
profile_relative_path: cached_process.profile_relative_path,
|
|
current_child,
|
|
manager,
|
|
last_updated_playtime,
|
|
};
|
|
|
|
let mchild = Arc::new(RwLock::new(mchild));
|
|
self.0.insert(cached_process.uuid, mchild.clone());
|
|
Ok(mchild)
|
|
}
|
|
|
|
// Spawns a new child process and inserts it into the hashmap
|
|
// Also, as the process ends, it spawns the follow-up process if it exists
|
|
// By convention, ExitStatus is last command's exit status, and we exit on the first non-zero exit status
|
|
#[tracing::instrument(skip(current_child))]
|
|
#[theseus_macros::debug_pin]
|
|
async fn sequential_process_manager(
|
|
uuid: Uuid,
|
|
post_command: Option<String>,
|
|
mut current_pid: u32,
|
|
current_child: Arc<RwLock<ChildType>>,
|
|
associated_profile: ProfilePathId,
|
|
) -> crate::Result<i32> {
|
|
let current_child = current_child.clone();
|
|
|
|
// Wait on current Minecraft Child
|
|
let mut mc_exit_status;
|
|
let mut last_updated_playtime = Utc::now();
|
|
loop {
|
|
if let Some(t) = current_child.write().await.try_wait().await? {
|
|
mc_exit_status = t;
|
|
break;
|
|
}
|
|
// sleep for 10ms
|
|
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
|
|
|
|
// Auto-update playtime every minute
|
|
let diff = Utc::now()
|
|
.signed_duration_since(last_updated_playtime)
|
|
.num_seconds();
|
|
if diff >= 60 {
|
|
if let Err(e) = profile::edit(&associated_profile, |prof| {
|
|
prof.metadata.recent_time_played += diff as u64;
|
|
async { Ok(()) }
|
|
})
|
|
.await
|
|
{
|
|
tracing::warn!(
|
|
"Failed to update playtime for profile {}: {}",
|
|
&associated_profile,
|
|
e
|
|
);
|
|
}
|
|
last_updated_playtime = Utc::now();
|
|
}
|
|
}
|
|
|
|
// Now fully complete- update playtime one last time
|
|
let diff = Utc::now()
|
|
.signed_duration_since(last_updated_playtime)
|
|
.num_seconds();
|
|
if let Err(e) = profile::edit(&associated_profile, |prof| {
|
|
prof.metadata.recent_time_played += diff as u64;
|
|
async { Ok(()) }
|
|
})
|
|
.await
|
|
{
|
|
tracing::warn!(
|
|
"Failed to update playtime for profile {}: {}",
|
|
&associated_profile,
|
|
e
|
|
);
|
|
}
|
|
|
|
// Publish play time update
|
|
// Allow failure, it will be stored locally and sent next time
|
|
// Sent in another thread as first call may take a couple seconds and hold up process ending
|
|
let associated_profile_clone = associated_profile.clone();
|
|
tokio::spawn(async move {
|
|
if let Err(e) =
|
|
profile::try_update_playtime(&associated_profile_clone.clone())
|
|
.await
|
|
{
|
|
tracing::warn!(
|
|
"Failed to update playtime for profile {}: {}",
|
|
&associated_profile_clone,
|
|
e
|
|
);
|
|
}
|
|
});
|
|
|
|
{
|
|
// Clear game played for Discord RPC
|
|
// May have other active processes, so we clear to the next running process
|
|
let state = crate::State::get().await?;
|
|
let _ = state.discord_rpc.clear_to_default(true).await;
|
|
}
|
|
|
|
// If in tauri, window should show itself again after process exists if it was hidden
|
|
#[cfg(feature = "tauri")]
|
|
{
|
|
let window = crate::EventState::get_main_window().await?;
|
|
if let Some(window) = window {
|
|
window.unminimize()?;
|
|
}
|
|
}
|
|
|
|
{
|
|
let current_child = current_child.write().await;
|
|
current_child.remove_cache(uuid).await?;
|
|
}
|
|
|
|
if !mc_exit_status == 0 {
|
|
emit_process(
|
|
uuid,
|
|
current_pid,
|
|
ProcessPayloadType::Finished,
|
|
"Exited process",
|
|
)
|
|
.await?;
|
|
|
|
return Ok(mc_exit_status); // Err for a non-zero exit is handled in helper
|
|
}
|
|
|
|
// If a post-command exist, switch to it and wait on it
|
|
// First, create the command by splitting arguments
|
|
let post_command = if let Some(hook) = post_command {
|
|
let mut cmd = hook.split(' ');
|
|
if let Some(command) = cmd.next() {
|
|
let mut command = Command::new(command);
|
|
command
|
|
.args(&cmd.collect::<Vec<&str>>())
|
|
.current_dir(associated_profile.get_full_path().await?);
|
|
Some(command)
|
|
} else {
|
|
None
|
|
}
|
|
} else {
|
|
None
|
|
};
|
|
|
|
if let Some(mut m_command) = post_command {
|
|
{
|
|
let mut current_child: tokio::sync::RwLockWriteGuard<
|
|
'_,
|
|
ChildType,
|
|
> = current_child.write().await;
|
|
let new_child = m_command.spawn().map_err(IOError::from)?;
|
|
current_pid = new_child.id().ok_or_else(|| {
|
|
crate::ErrorKind::LauncherError(
|
|
"Process immediately failed, could not get PID"
|
|
.to_string(),
|
|
)
|
|
})?;
|
|
*current_child = ChildType::TokioChild(new_child);
|
|
}
|
|
emit_process(
|
|
uuid,
|
|
current_pid,
|
|
ProcessPayloadType::Updated,
|
|
"Completed Minecraft, switching to post-commands",
|
|
)
|
|
.await?;
|
|
|
|
loop {
|
|
if let Some(t) = current_child.write().await.try_wait().await? {
|
|
mc_exit_status = t;
|
|
break;
|
|
}
|
|
// sleep for 10ms
|
|
tokio::time::sleep(tokio::time::Duration::from_millis(10))
|
|
.await;
|
|
}
|
|
}
|
|
|
|
emit_process(
|
|
uuid,
|
|
current_pid,
|
|
ProcessPayloadType::Finished,
|
|
"Exited process",
|
|
)
|
|
.await?;
|
|
|
|
Ok(mc_exit_status)
|
|
}
|
|
|
|
// Returns a ref to the child
|
|
pub fn get(&self, uuid: &Uuid) -> Option<Arc<RwLock<MinecraftChild>>> {
|
|
self.0.get(uuid).cloned()
|
|
}
|
|
|
|
// Gets all PID keys
|
|
pub fn keys(&self) -> Vec<Uuid> {
|
|
self.0.keys().cloned().collect()
|
|
}
|
|
|
|
// Get exit status of a child by PID
|
|
// Returns None if the child is still running
|
|
pub async fn exit_status(&self, uuid: &Uuid) -> crate::Result<Option<i32>> {
|
|
if let Some(child) = self.get(uuid) {
|
|
let child = child.write().await;
|
|
let status = child.current_child.write().await.try_wait().await?;
|
|
Ok(status)
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
// Gets all PID keys of running children
|
|
pub async fn running_keys(&self) -> crate::Result<Vec<Uuid>> {
|
|
let mut keys = Vec::new();
|
|
for key in self.keys() {
|
|
if let Some(child) = self.get(&key) {
|
|
let child = child.clone();
|
|
let child = child.write().await;
|
|
if child
|
|
.current_child
|
|
.write()
|
|
.await
|
|
.try_wait()
|
|
.await?
|
|
.is_none()
|
|
{
|
|
keys.push(key);
|
|
}
|
|
}
|
|
}
|
|
Ok(keys)
|
|
}
|
|
|
|
// Gets all PID keys of running children with a given profile path
|
|
pub async fn running_keys_with_profile(
|
|
&self,
|
|
profile_path: ProfilePathId,
|
|
) -> crate::Result<Vec<Uuid>> {
|
|
let running_keys = self.running_keys().await?;
|
|
let mut keys = Vec::new();
|
|
for key in running_keys {
|
|
if let Some(child) = self.get(&key) {
|
|
let child = child.clone();
|
|
let child = child.read().await;
|
|
if child.profile_relative_path == profile_path {
|
|
keys.push(key);
|
|
}
|
|
}
|
|
}
|
|
Ok(keys)
|
|
}
|
|
|
|
// Gets all profiles of running children
|
|
pub async fn running_profile_paths(
|
|
&self,
|
|
) -> crate::Result<Vec<ProfilePathId>> {
|
|
let mut profiles = Vec::new();
|
|
for key in self.keys() {
|
|
if let Some(child) = self.get(&key) {
|
|
let child = child.clone();
|
|
let child = child.write().await;
|
|
if child
|
|
.current_child
|
|
.write()
|
|
.await
|
|
.try_wait()
|
|
.await?
|
|
.is_none()
|
|
{
|
|
profiles.push(child.profile_relative_path.clone());
|
|
}
|
|
}
|
|
}
|
|
Ok(profiles)
|
|
}
|
|
|
|
// Gets all profiles of running children
|
|
// Returns clones because it would be serialized anyway
|
|
pub async fn running_profiles(&self) -> crate::Result<Vec<Profile>> {
|
|
let mut profiles = Vec::new();
|
|
for key in self.keys() {
|
|
if let Some(child) = self.get(&key) {
|
|
let child = child.clone();
|
|
let child = child.write().await;
|
|
if child
|
|
.current_child
|
|
.write()
|
|
.await
|
|
.try_wait()
|
|
.await?
|
|
.is_none()
|
|
{
|
|
if let Some(prof) = crate::api::profile::get(
|
|
&child.profile_relative_path.clone(),
|
|
None,
|
|
)
|
|
.await?
|
|
{
|
|
profiles.push(prof);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ok(profiles)
|
|
}
|
|
}
|
|
|
|
impl Default for Children {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|