use std::fmt;
use std::future::Future;
use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
use ::tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use crate::error::Result;
use crate::size::Size;
use crate::status::ExitStatus;
use crate::{PtyController, SessionOutput};
use super::{Child, OwnedReadHalf, OwnedWriteHalf};
pub struct Session {
pub(super) child: Child,
pub(super) output: OwnedReadHalf,
pub(super) input: OwnedWriteHalf,
pub(super) controller: PtyController,
}
impl fmt::Debug for Session {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Session")
.field("child", &self.child)
.field("controller", &self.controller)
.finish_non_exhaustive()
}
}
impl Session {
#[must_use]
pub const fn id(&self) -> u32 {
self.child.id()
}
pub fn try_wait(&mut self) -> Result<Option<ExitStatus>> {
self.child.try_wait()
}
pub fn kill(&mut self) -> Result<()> {
self.child.kill()
}
pub fn resize(&self, size: Size) -> Result<()> {
self.controller.resize(size)
}
#[must_use]
pub fn size(&self) -> Size {
self.controller.size()
}
pub fn clear(&self) -> Result<()> {
self.controller.clear()
}
#[must_use]
pub fn supports_clear(&self) -> bool {
self.controller.supports_clear()
}
pub async fn wait(self) -> Result<ExitStatus> {
Ok(self.complete(false).await?.status())
}
pub async fn collect_output(self) -> Result<SessionOutput> {
self.complete(true).await
}
async fn complete(mut self, collect: bool) -> Result<SessionOutput> {
let mut bytes = Vec::new();
let (status, output_finished) =
collect_until_root(&mut self.child, &mut self.output, &mut bytes, collect).await?;
let kill = self.child.kill();
let Self {
child,
mut output,
mut input,
controller,
} = self;
drop(child);
let input_result = std::future::poll_fn(|cx| Pin::new(&mut input).poll_shutdown(cx)).await;
drop(input);
drop(controller);
let output_result = if output_finished {
Ok(())
} else {
drain_to_end(&mut output, &mut bytes, collect).await
};
output_result?;
input_result?;
kill?;
Ok(SessionOutput::new(status, bytes))
}
#[must_use]
pub fn into_parts(self) -> SessionParts {
SessionParts {
child: self.child,
output: self.output,
input: self.input,
controller: self.controller,
}
}
}
enum CollectionEvent {
Root(Result<ExitStatus>),
Output(io::Result<usize>),
}
async fn collect_until_root(
child: &mut Child,
output: &mut OwnedReadHalf,
bytes: &mut Vec<u8>,
collect: bool,
) -> Result<(ExitStatus, bool)> {
let mut chunk = [0_u8; 4096];
let mut output_finished = false;
let mut wait = std::pin::pin!(child.wait());
let status = loop {
let event = std::future::poll_fn(|cx| {
if let Poll::Ready(status) = wait.as_mut().poll(cx) {
return Poll::Ready(CollectionEvent::Root(status));
}
if output_finished {
return Poll::Pending;
}
let mut read_buf = ReadBuf::new(&mut chunk);
match Pin::new(&mut *output).poll_read(cx, &mut read_buf) {
Poll::Ready(Ok(())) => {
Poll::Ready(CollectionEvent::Output(Ok(read_buf.filled().len())))
},
Poll::Ready(Err(err)) => Poll::Ready(CollectionEvent::Output(Err(err))),
Poll::Pending => Poll::Pending,
}
})
.await;
match event {
CollectionEvent::Root(status) => break status?,
CollectionEvent::Output(Ok(0)) => output_finished = true,
CollectionEvent::Output(Ok(read)) if collect => {
bytes.extend_from_slice(&chunk[..read]);
},
CollectionEvent::Output(Ok(_read)) => {},
CollectionEvent::Output(Err(err)) => return Err(err.into()),
}
};
Ok((status, output_finished))
}
async fn drain_to_end(
output: &mut OwnedReadHalf,
bytes: &mut Vec<u8>,
collect: bool,
) -> io::Result<()> {
let mut chunk = [0_u8; 4096];
loop {
let read = std::future::poll_fn(|cx| {
let mut read_buf = ReadBuf::new(&mut chunk);
match Pin::new(&mut *output).poll_read(cx, &mut read_buf) {
Poll::Ready(Ok(())) => Poll::Ready(Ok(read_buf.filled().len())),
Poll::Ready(Err(err)) => Poll::Ready(Err(err)),
Poll::Pending => Poll::Pending,
}
})
.await?;
if read == 0 {
return Ok(());
}
if collect {
bytes.extend_from_slice(&chunk[..read]);
}
}
}
impl AsyncRead for Session {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
Pin::new(&mut self.get_mut().output).poll_read(cx, buf)
}
}
impl AsyncWrite for Session {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
Pin::new(&mut self.get_mut().input).poll_write(cx, buf)
}
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(Ok(()))
}
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Pin::new(&mut self.get_mut().input).poll_shutdown(cx)
}
}
#[non_exhaustive]
pub struct SessionParts {
pub child: Child,
pub output: OwnedReadHalf,
pub input: OwnedWriteHalf,
pub controller: PtyController,
}
impl fmt::Debug for SessionParts {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SessionParts")
.field("child", &self.child)
.field("controller", &self.controller)
.finish_non_exhaustive()
}
}