desktop: replicate tauri-plugin-shell logic (#13986)

This commit is contained in:
Brendan Allan
2026-02-18 02:40:52 +08:00
committed by GitHub
parent 7379903568
commit 4025b655a4

View File

@@ -6,13 +6,17 @@ use process_wrap::tokio::ProcessGroup;
use process_wrap::tokio::{JobObject, KillOnDrop}; use process_wrap::tokio::{JobObject, KillOnDrop};
#[cfg(unix)] #[cfg(unix)]
use std::os::unix::process::ExitStatusExt; use std::os::unix::process::ExitStatusExt;
use std::sync::Arc;
use std::{process::Stdio, time::Duration}; use std::{process::Stdio, time::Duration};
use tauri::{AppHandle, Manager, path::BaseDirectory}; use tauri::{AppHandle, Manager, path::BaseDirectory};
use tauri_plugin_store::StoreExt; use tauri_plugin_store::StoreExt;
use tauri_specta::Event; use tauri_specta::Event;
use tokio::io::{AsyncBufReadExt, BufReader}; use tokio::{
use tokio::process::Command; io::{AsyncBufRead, AsyncBufReadExt, BufReader},
use tokio::sync::{mpsc, oneshot}; process::Command,
sync::{mpsc, oneshot},
task::JoinHandle,
};
use tokio_stream::wrappers::ReceiverStream; use tokio_stream::wrappers::ReceiverStream;
use tracing::Instrument; use tracing::Instrument;
@@ -34,8 +38,8 @@ pub struct Config {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum CommandEvent { pub enum CommandEvent {
Stdout(Vec<u8>), Stdout(String),
Stderr(Vec<u8>), Stderr(String),
Error(String), Error(String),
Terminated(TerminatedPayload), Terminated(TerminatedPayload),
} }
@@ -64,10 +68,11 @@ pub async fn get_config(app: &AppHandle) -> Option<Config> {
events events
.fold(String::new(), async |mut config_str, event| { .fold(String::new(), async |mut config_str, event| {
if let CommandEvent::Stdout(stdout) = event if let CommandEvent::Stdout(s) = &event {
&& let Ok(s) = str::from_utf8(&stdout) config_str += s.as_str()
{ }
config_str += s if let CommandEvent::Stderr(s) = &event {
config_str += s.as_str()
} }
config_str config_str
@@ -317,9 +322,9 @@ pub fn spawn_command(
cmd cmd
}; };
cmd.stdin(Stdio::null());
cmd.stdout(Stdio::piped()); cmd.stdout(Stdio::piped());
cmd.stderr(Stdio::piped()); cmd.stderr(Stdio::piped());
cmd.stdin(Stdio::null());
#[cfg(windows)] #[cfg(windows)]
cmd.creation_flags(0x0800_0000); cmd.creation_flags(0x0800_0000);
@@ -337,32 +342,24 @@ pub fn spawn_command(
} }
let mut child = wrap.spawn()?; let mut child = wrap.spawn()?;
let stdout = child.stdout().take(); let guard = Arc::new(tokio::sync::RwLock::new(()));
let stderr = child.stderr().take();
let (tx, rx) = mpsc::channel(256); let (tx, rx) = mpsc::channel(256);
let (kill_tx, mut kill_rx) = mpsc::channel(1); let (kill_tx, mut kill_rx) = mpsc::channel(1);
if let Some(stdout) = stdout { let stdout = spawn_pipe_reader(
let tx = tx.clone(); tx.clone(),
tokio::spawn(async move { guard.clone(),
let mut lines = BufReader::new(stdout).lines(); BufReader::new(child.stdout().take().unwrap()),
while let Ok(Some(line)) = lines.next_line().await { CommandEvent::Stdout,
let _ = tx.send(CommandEvent::Stdout(line.into_bytes())).await; );
} let stderr = spawn_pipe_reader(
}); tx.clone(),
} guard.clone(),
BufReader::new(child.stderr().take().unwrap()),
CommandEvent::Stderr,
);
if let Some(stderr) = stderr { tokio::task::spawn(async move {
let tx = tx.clone();
tokio::spawn(async move {
let mut lines = BufReader::new(stderr).lines();
while let Ok(Some(line)) = lines.next_line().await {
let _ = tx.send(CommandEvent::Stderr(line.into_bytes())).await;
}
});
}
tokio::spawn(async move {
let mut kill_open = true; let mut kill_open = true;
let status = loop { let status = loop {
match child.try_wait() { match child.try_wait() {
@@ -394,6 +391,9 @@ pub fn spawn_command(
let _ = tx.send(CommandEvent::Error(err.to_string())).await; let _ = tx.send(CommandEvent::Error(err.to_string())).await;
} }
} }
stdout.abort();
stderr.abort();
}); });
let event_stream = ReceiverStream::new(rx); let event_stream = ReceiverStream::new(rx);
@@ -404,9 +404,7 @@ pub fn spawn_command(
fn signal_from_status(status: std::process::ExitStatus) -> Option<i32> { fn signal_from_status(status: std::process::ExitStatus) -> Option<i32> {
#[cfg(unix)] #[cfg(unix)]
{ return status.signal();
return status.signal();
}
#[cfg(not(unix))] #[cfg(not(unix))]
{ {
@@ -442,12 +440,10 @@ pub fn serve(
events events
.for_each(move |event| { .for_each(move |event| {
match event { match event {
CommandEvent::Stdout(line_bytes) => { CommandEvent::Stdout(line) => {
let line = String::from_utf8_lossy(&line_bytes);
tracing::info!("{line}"); tracing::info!("{line}");
} }
CommandEvent::Stderr(line_bytes) => { CommandEvent::Stderr(line) => {
let line = String::from_utf8_lossy(&line_bytes);
tracing::info!("{line}"); tracing::info!("{line}");
} }
CommandEvent::Error(err) => { CommandEvent::Error(err) => {
@@ -499,11 +495,7 @@ pub mod sqlite_migration {
} }
future::ready(match &event { future::ready(match &event {
CommandEvent::Stdout(stdout) => { CommandEvent::Stdout(s) | CommandEvent::Stderr(s) => {
let Ok(s) = str::from_utf8(stdout) else {
return future::ready(None);
};
if let Some(s) = s.strip_prefix("sqlite-migration:").map(|s| s.trim()) { if let Some(s) = s.strip_prefix("sqlite-migration:").map(|s| s.trim()) {
if let Ok(progress) = s.parse::<u8>() { if let Ok(progress) = s.parse::<u8>() {
let _ = SqliteMigrationProgress::InProgress(progress).emit(&app); let _ = SqliteMigrationProgress::InProgress(progress).emit(&app);
@@ -522,3 +514,41 @@ pub mod sqlite_migration {
}) })
} }
} }
fn spawn_pipe_reader<F: Fn(String) -> CommandEvent + Send + Copy + 'static>(
tx: mpsc::Sender<CommandEvent>,
guard: Arc<tokio::sync::RwLock<()>>,
pipe_reader: impl AsyncBufRead + Send + Unpin + 'static,
wrapper: F,
) -> JoinHandle<()> {
tokio::spawn(async move {
let _lock = guard.read().await;
let reader = BufReader::new(pipe_reader);
read_line(reader, tx, wrapper).await;
})
}
async fn read_line<F: Fn(String) -> CommandEvent + Send + Copy + 'static>(
reader: BufReader<impl AsyncBufRead + Unpin>,
tx: mpsc::Sender<CommandEvent>,
wrapper: F,
) {
let mut lines = reader.lines();
loop {
let line = lines.next_line().await;
match line {
Ok(s) => {
if let Some(s) = s {
let _ = tx.clone().send(wrapper(s)).await;
}
}
Err(e) => {
let tx_ = tx.clone();
let _ = tx_.send(CommandEvent::Error(e.to_string())).await;
break;
}
}
}
}