use std::fmt;
use std::io::{self, Read, Write};
use std::sync::mpsc::{self, Receiver, RecvTimeoutError};
use std::thread::{self, JoinHandle};
use std::time::Duration;
use super::command::Child;
use super::pty::{OwnedReadHalf, OwnedWriteHalf};
use crate::error::Result;
use crate::size::Size;
use crate::status::ExitStatus;
use crate::{PtyController, SessionOutput};
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 {
pub(super) const fn new(
child: Child,
output: OwnedReadHalf,
input: OwnedWriteHalf,
controller: PtyController,
) -> Self {
Self {
child,
output,
input,
controller,
}
}
#[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 fn wait(self) -> Result<ExitStatus> {
Ok(self.complete(false)?.status())
}
pub fn collect_output(self) -> Result<SessionOutput> {
self.complete(true)
}
fn complete(self, collect: bool) -> Result<SessionOutput> {
let (completed_tx, completed_rx) = mpsc::sync_channel(1);
let mut output = self.output;
let reader = thread::Builder::new()
.name("conpty-oxide-output".into())
.spawn(move || {
let result = if collect {
let mut bytes = Vec::new();
output.read_to_end(&mut bytes).map(|_| bytes)
} else {
io::copy(&mut output, &mut io::sink()).map(|_| Vec::new())
};
match completed_tx.send(()) {
Ok(()) | Err(_) => {},
}
result
})?;
BlockingCollector {
child: Some(self.child),
input: Some(self.input),
controller: Some(self.controller),
reader: Some(reader),
completed: completed_rx,
}
.finish()
}
#[must_use]
pub fn into_parts(self) -> SessionParts {
SessionParts {
child: self.child,
output: self.output,
input: self.input,
controller: self.controller,
}
}
}
struct BlockingCollector {
child: Option<Child>,
input: Option<OwnedWriteHalf>,
controller: Option<PtyController>,
reader: Option<JoinHandle<io::Result<Vec<u8>>>>,
completed: Receiver<()>,
}
impl BlockingCollector {
fn finish(mut self) -> Result<SessionOutput> {
let mut output = None;
let status = loop {
let child = self
.child
.as_mut()
.ok_or_else(|| io::Error::other("the collection child was already retired"))?;
match child.try_wait() {
Ok(Some(status)) => break Some(Ok(status)),
Ok(None) => {},
Err(err) => break Some(Err(err)),
}
match self.completed.recv_timeout(Duration::from_millis(10)) {
Ok(()) | Err(RecvTimeoutError::Disconnected) => {
output = Some(self.join_reader());
break match output.as_ref() {
Some(Ok(_bytes)) => Some(
self.child
.as_mut()
.ok_or_else(|| {
io::Error::other("the collection child was already retired")
})?
.wait(),
),
Some(Err(_reader_error)) => None,
None => {
return Err(io::Error::other(
"the output reader completed without a result",
)
.into());
},
};
},
Err(RecvTimeoutError::Timeout) => {},
}
};
let kill = self
.child
.as_mut()
.ok_or_else(|| io::Error::other("the collection child was already retired"))?
.kill();
drop(self.child.take());
drop(self.input.take());
drop(self.controller.take());
let bytes = output.unwrap_or_else(|| self.join_reader())?;
let status = status
.ok_or_else(|| io::Error::other("output collection ended without a root status"))??;
kill?;
Ok(SessionOutput::new(status, bytes))
}
fn join_reader(&mut self) -> io::Result<Vec<u8>> {
self.reader
.take()
.ok_or_else(|| io::Error::other("the output reader was already joined"))?
.join()
.map_err(|_panic_payload| io::Error::other("the output reader thread panicked"))?
}
}
impl Read for Session {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.output.read(buf)
}
}
impl Write for Session {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.input.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
#[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()
}
}