+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "iccery",
|
||||
"private": true,
|
||||
"version": "0.5.0",
|
||||
"version": "0.5.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"fetch-argyll": "node scripts/fetch-argyll.mjs",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "iccery"
|
||||
version = "0.5.0"
|
||||
version = "0.5.1"
|
||||
description = "Modern Printer Profiling UI frontend for ArgyllCMS"
|
||||
authors = ["Gordon"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -30,6 +30,13 @@ pub async fn kill_process(
|
||||
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> {
|
||||
if cfg!(windows) && !binary_name.to_lowercase().ends_with(".exe") {
|
||||
vec![format!("{}.exe", binary_name), binary_name.to_string()]
|
||||
|
||||
+25
-2
@@ -1,3 +1,5 @@
|
||||
use tauri::Manager;
|
||||
|
||||
mod commands;
|
||||
mod events;
|
||||
mod print;
|
||||
@@ -39,6 +41,7 @@ pub fn run() {
|
||||
commands::select_directory,
|
||||
commands::send_stdin,
|
||||
commands::kill_process,
|
||||
commands::kill_all_processes,
|
||||
commands::resolve_binary,
|
||||
commands::detect_instruments,
|
||||
commands::get_profile_path,
|
||||
@@ -66,6 +69,26 @@ pub fn run() {
|
||||
settings::export_preset_json,
|
||||
settings::import_preset_json,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
.build(tauri::generate_context!())
|
||||
.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())
|
||||
}
|
||||
|
||||
/// 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)]
|
||||
@@ -183,4 +215,33 @@ mod tests {
|
||||
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",
|
||||
"productName": "ICCery",
|
||||
"version": "0.5.0",
|
||||
"version": "0.5.1",
|
||||
"identifier": "com.gronod.iccery",
|
||||
"build": {
|
||||
"frontendDist": "../src"
|
||||
|
||||
Reference in New Issue
Block a user