use std::sync::Arc;
use std::time::Duration;
use super::{IdleDetectorCore, NativePtyProcess, PtyError};
use crate::blocking_island::dispatch;
#[derive(Clone, Debug, PartialEq)]
pub struct IdleWaitOutcome {
pub reached: bool,
pub reason: String,
pub idle_seconds: f64,
pub returncode: Option<i32>,
}
async fn run_blocking<T, F>(operation: F) -> Result<T, PtyError>
where
T: Send + 'static,
F: FnOnce() -> Result<T, PtyError> + Send + 'static,
{
infallible(operation).await?
}
async fn infallible<T, F>(operation: F) -> Result<T, PtyError>
where
T: Send + 'static,
F: FnOnce() -> T + Send + 'static,
{
dispatch(operation)
.await
.map_err(|error| PtyError::Other(format!("PTY blocking operation failed: {error}")))
}
#[derive(Clone)]
pub struct AsyncPtyProcess {
process: Arc<NativePtyProcess>,
}
impl AsyncPtyProcess {
pub fn new(
argv: Vec<String>,
cwd: Option<String>,
env: Option<Vec<(String, String)>>,
rows: u16,
cols: u16,
nice: Option<i32>,
) -> Result<Self, PtyError> {
Ok(Self {
process: Arc::new(NativePtyProcess::new(argv, cwd, env, rows, cols, nice)?),
})
}
pub async fn start(&self) -> Result<(), PtyError> {
let process = Arc::clone(&self.process);
run_blocking(move || process.start_impl()).await
}
pub async fn read_chunk(&self, timeout: Option<Duration>) -> Result<Option<Vec<u8>>, PtyError> {
let process = Arc::clone(&self.process);
run_blocking(move || process.read_chunk_impl(timeout.map(|value| value.as_secs_f64())))
.await
}
pub async fn write(&self, bytes: Vec<u8>, submit: bool) -> Result<(), PtyError> {
let process = Arc::clone(&self.process);
run_blocking(move || process.write_impl(&bytes, submit)).await
}
pub async fn resize(&self, rows: u16, cols: u16) -> Result<(), PtyError> {
let process = Arc::clone(&self.process);
run_blocking(move || process.resize_impl(rows, cols)).await
}
pub async fn wait(&self, timeout: Option<Duration>) -> Result<i32, PtyError> {
let process = Arc::clone(&self.process);
run_blocking(move || process.wait_impl(timeout.map(|value| value.as_secs_f64()))).await
}
pub async fn terminate(&self) -> Result<(), PtyError> {
let process = Arc::clone(&self.process);
run_blocking(move || process.terminate_impl()).await
}
pub async fn kill(&self) -> Result<(), PtyError> {
let process = Arc::clone(&self.process);
run_blocking(move || process.kill_impl()).await
}
pub async fn close(&self) -> Result<(), PtyError> {
let process = Arc::clone(&self.process);
run_blocking(move || process.close_impl()).await
}
pub async fn pid(&self) -> Result<Option<u32>, PtyError> {
let process = Arc::clone(&self.process);
run_blocking(move || process.pid()).await
}
pub async fn send_interrupt(&self) -> Result<(), PtyError> {
let process = Arc::clone(&self.process);
run_blocking(move || process.send_interrupt_impl()).await
}
pub async fn terminate_tree(&self) -> Result<(), PtyError> {
let process = Arc::clone(&self.process);
run_blocking(move || process.terminate_tree_impl()).await
}
pub async fn kill_tree(&self) -> Result<(), PtyError> {
let process = Arc::clone(&self.process);
run_blocking(move || process.kill_tree_impl()).await
}
pub async fn respond_to_queries(&self, data: Vec<u8>) -> Result<(), PtyError> {
let process = Arc::clone(&self.process);
run_blocking(move || process.respond_to_queries_impl(&data)).await
}
pub async fn wait_and_drain(
&self,
timeout: Option<Duration>,
drain_timeout: Duration,
) -> Result<i32, PtyError> {
let process = Arc::clone(&self.process);
let timeout = timeout.map(|value| value.as_secs_f64());
let drain_timeout = drain_timeout.as_secs_f64();
run_blocking(move || process.wait_and_drain_impl(timeout, drain_timeout)).await
}
pub async fn wait_for_reader_closed(
&self,
timeout: Option<Duration>,
) -> Result<bool, PtyError> {
let process = Arc::clone(&self.process);
let timeout = timeout.map(|value| value.as_secs_f64());
infallible(move || process.wait_for_reader_closed_impl(timeout)).await
}
pub async fn attach_idle_detector(
&self,
detector: Arc<IdleDetectorCore>,
) -> Result<(), PtyError> {
let process = Arc::clone(&self.process);
infallible(move || process.attach_idle_detector(&detector)).await
}
pub async fn detach_idle_detector(&self) -> Result<(), PtyError> {
let process = Arc::clone(&self.process);
infallible(move || process.detach_idle_detector()).await
}
pub async fn wait_for_idle(
&self,
detector: Arc<IdleDetectorCore>,
timeout: Option<Duration>,
) -> Result<IdleWaitOutcome, PtyError> {
let timeout = timeout.map(|value| value.as_secs_f64());
infallible(move || {
let (reached, reason, idle_seconds, returncode) = detector.wait(timeout);
IdleWaitOutcome {
reached,
reason,
idle_seconds,
returncode,
}
})
.await
}
pub async fn start_terminal_input_relay(&self) -> Result<(), PtyError> {
let process = Arc::clone(&self.process);
run_blocking(move || process.start_terminal_input_relay_impl()).await
}
pub async fn stop_terminal_input_relay(&self) -> Result<(), PtyError> {
let process = Arc::clone(&self.process);
infallible(move || process.stop_terminal_input_relay_impl()).await
}
pub fn request_terminal_input_relay_stop(&self) {
self.process.request_terminal_input_relay_stop();
}
pub fn terminal_input_relay_active(&self) -> bool {
self.process.terminal_input_relay_active()
}
pub fn set_echo(&self, enabled: bool) {
self.process.set_echo(enabled);
}
pub fn echo_enabled(&self) -> bool {
self.process.echo_enabled()
}
pub fn close_nonblocking(&self) {
self.process.close_nonblocking();
}
pub fn mark_reader_closed(&self) {
self.process.mark_reader_closed();
}
pub fn store_returncode(&self, code: i32) {
self.process.store_returncode(code);
}
pub fn record_input_metrics(&self, data: &[u8], submit: bool) {
self.process.record_input_metrics(data, submit);
}
pub fn pty_input_bytes_total(&self) -> usize {
self.process.pty_input_bytes_total()
}
pub fn pty_newline_events_total(&self) -> usize {
self.process.pty_newline_events_total()
}
pub fn pty_submit_events_total(&self) -> usize {
self.process.pty_submit_events_total()
}
pub fn pty_output_bytes_total(&self) -> usize {
self.process.pty_output_bytes_total()
}
pub fn pty_control_churn_bytes_total(&self) -> usize {
self.process.pty_control_churn_bytes_total()
}
}
#[cfg(test)]
mod tests {
use super::AsyncPtyProcess;
use std::time::Duration;
#[tokio::test]
async fn async_pty_dispatches_start_read_and_close_through_island() {
let argv = crate::pty::platform_shell_argv("echo async-pty");
let process =
AsyncPtyProcess::new(argv, None, None, 24, 80, None).expect("async PTY configuration");
process.start().await.expect("async PTY start");
let _ = process.read_chunk(Some(Duration::from_secs(1))).await;
assert!(process.pid().await.expect("async PTY pid").is_some());
process.close().await.expect("async PTY close");
}
}