+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "iccery",
|
"name": "iccery",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"fetch-argyll": "node scripts/fetch-argyll.mjs",
|
"fetch-argyll": "node scripts/fetch-argyll.mjs",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "iccery"
|
name = "iccery"
|
||||||
version = "0.5.0"
|
version = "0.5.1"
|
||||||
description = "Modern Printer Profiling UI frontend for ArgyllCMS"
|
description = "Modern Printer Profiling UI frontend for ArgyllCMS"
|
||||||
authors = ["Gordon"]
|
authors = ["Gordon"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
@@ -30,6 +30,13 @@ pub async fn kill_process(
|
|||||||
state.kill(&id).await
|
state.kill(&id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn kill_all_processes(
|
||||||
|
state: State<'_, ProcessManager>,
|
||||||
|
) -> Result<usize, String> {
|
||||||
|
Ok(state.kill_all().await)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_binary_candidates(binary_name: &str) -> Vec<String> {
|
pub fn get_binary_candidates(binary_name: &str) -> Vec<String> {
|
||||||
if cfg!(windows) && !binary_name.to_lowercase().ends_with(".exe") {
|
if cfg!(windows) && !binary_name.to_lowercase().ends_with(".exe") {
|
||||||
vec![format!("{}.exe", binary_name), binary_name.to_string()]
|
vec![format!("{}.exe", binary_name), binary_name.to_string()]
|
||||||
|
|||||||
+25
-2
@@ -1,3 +1,5 @@
|
|||||||
|
use tauri::Manager;
|
||||||
|
|
||||||
mod commands;
|
mod commands;
|
||||||
mod events;
|
mod events;
|
||||||
mod print;
|
mod print;
|
||||||
@@ -39,6 +41,7 @@ pub fn run() {
|
|||||||
commands::select_directory,
|
commands::select_directory,
|
||||||
commands::send_stdin,
|
commands::send_stdin,
|
||||||
commands::kill_process,
|
commands::kill_process,
|
||||||
|
commands::kill_all_processes,
|
||||||
commands::resolve_binary,
|
commands::resolve_binary,
|
||||||
commands::detect_instruments,
|
commands::detect_instruments,
|
||||||
commands::get_profile_path,
|
commands::get_profile_path,
|
||||||
@@ -66,6 +69,26 @@ pub fn run() {
|
|||||||
settings::export_preset_json,
|
settings::export_preset_json,
|
||||||
settings::import_preset_json,
|
settings::import_preset_json,
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.build(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while building tauri application")
|
||||||
|
.run(|app_handle, event| {
|
||||||
|
match event {
|
||||||
|
tauri::RunEvent::Exit | tauri::RunEvent::ExitRequested { .. } => {
|
||||||
|
let pm = app_handle.state::<process_manager::ProcessManager>();
|
||||||
|
tauri::async_runtime::block_on(async {
|
||||||
|
pm.kill_all().await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
tauri::RunEvent::WindowEvent {
|
||||||
|
event: tauri::WindowEvent::CloseRequested { .. },
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let pm = app_handle.state::<process_manager::ProcessManager>();
|
||||||
|
tauri::async_runtime::block_on(async {
|
||||||
|
pm.kill_all().await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,6 +168,38 @@ impl ProcessManager {
|
|||||||
}
|
}
|
||||||
Err("Process not found".to_string())
|
Err("Process not found".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Kills all currently managed subprocesses, closes their stdins, and signals termination.
|
||||||
|
/// Returns the number of processes signaled.
|
||||||
|
pub async fn kill_all(&self) -> usize {
|
||||||
|
// 1. Close and drop all stdin streams
|
||||||
|
{
|
||||||
|
let mut stdins = self.stdins.lock().await;
|
||||||
|
stdins.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Extract and send kill signal to all active killer channels
|
||||||
|
let killers_to_signal = {
|
||||||
|
let mut killers = self.killers.lock().await;
|
||||||
|
let list: Vec<(String, tokio::sync::oneshot::Sender<()>)> = killers.drain().collect();
|
||||||
|
list
|
||||||
|
};
|
||||||
|
|
||||||
|
let count = killers_to_signal.len();
|
||||||
|
if count > 0 {
|
||||||
|
log::info!(target: "subprocess", "Terminating all managed subprocesses ({count} active)");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (id, killer) in killers_to_signal {
|
||||||
|
log::info!(target: "subprocess", "Sending kill signal to subprocess '{id}'");
|
||||||
|
let _ = killer.send(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give background tasks a brief moment to initiate child.start_kill()
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
|
||||||
|
|
||||||
|
count
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -183,4 +215,33 @@ mod tests {
|
|||||||
assert!(!stdins.contains_key("test_proc"));
|
assert!(!stdins.contains_key("test_proc"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_kill_all_cleans_maps() {
|
||||||
|
let pm = ProcessManager::new();
|
||||||
|
|
||||||
|
let (tx1, mut rx1) = tokio::sync::oneshot::channel::<()>();
|
||||||
|
let (tx2, mut rx2) = tokio::sync::oneshot::channel::<()>();
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut killers = pm.killers.lock().await;
|
||||||
|
killers.insert("proc_1".to_string(), tx1);
|
||||||
|
killers.insert("proc_2".to_string(), tx2);
|
||||||
|
}
|
||||||
|
|
||||||
|
let killed_count = pm.kill_all().await;
|
||||||
|
assert_eq!(killed_count, 2);
|
||||||
|
|
||||||
|
// Verify channels received signal
|
||||||
|
assert!(rx1.try_recv().is_ok());
|
||||||
|
assert!(rx2.try_recv().is_ok());
|
||||||
|
|
||||||
|
// Verify maps are empty
|
||||||
|
{
|
||||||
|
let stdins = pm.stdins.lock().await;
|
||||||
|
let killers = pm.killers.lock().await;
|
||||||
|
assert!(stdins.is_empty());
|
||||||
|
assert!(killers.is_empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "ICCery",
|
"productName": "ICCery",
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"identifier": "com.gronod.iccery",
|
"identifier": "com.gronod.iccery",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../src"
|
"frontendDist": "../src"
|
||||||
|
|||||||
Reference in New Issue
Block a user