desktop: replicate tauri-plugin-shell logic (#13986)
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user