use std::fs::File;
use std::io::{Read, Write};
use std::os::unix::io::{FromRawFd, RawFd};
use std::os::unix::net::UnixStream;
use std::sync::{Arc, Mutex};
use crate::error::{NdsError, Result};
use crate::pty_buffer::PtyBuffer;
pub struct PtyHandler {
master_fd: RawFd,
output_buffer: PtyBuffer,
last_prompt_position: Arc<Mutex<Option<usize>>>,
}
impl PtyHandler {
pub fn new(master_fd: RawFd, buffer_size: usize) -> Self {
PtyHandler {
master_fd,
output_buffer: PtyBuffer::new(buffer_size),
last_prompt_position: Arc::new(Mutex::new(None)),
}
}
pub fn read_pty_data(&mut self, buffer: &mut [u8]) -> Result<Option<Vec<u8>>> {
let master_file = unsafe { File::from_raw_fd(self.master_fd) };
let mut master_file_clone = master_file.try_clone()?;
std::mem::forget(master_file);
match master_file_clone.read(buffer) {
Ok(0) => Ok(None), Ok(n) => {
let data = buffer[..n].to_vec();
if Self::looks_like_prompt(&data) {
*self.last_prompt_position.lock().unwrap() = Some(n);
}
Ok(Some(data))
}
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
Ok(Some(Vec::new())) }
Err(e) => Err(NdsError::Io(e)),
}
}
pub fn write_to_pty(&mut self, data: &[u8]) -> Result<()> {
let mut master_file = unsafe { File::from_raw_fd(self.master_fd) };
master_file.write_all(data)?;
std::mem::forget(master_file); Ok(())
}
pub fn process_pty_output(
&mut self,
data: &[u8],
client: &mut Option<UnixStream>,
) -> Result<()> {
if let Some(ref mut stream) = client {
if let Err(e) = stream.write_all(data) {
if e.kind() == std::io::ErrorKind::BrokenPipe {
self.output_buffer.push(data);
*client = None;
}
return Err(NdsError::Io(e));
}
} else {
self.output_buffer.push(data);
}
Ok(())
}
pub fn send_buffered_data(&mut self, client: &mut UnixStream) -> Result<()> {
if !self.output_buffer.is_empty() {
let mut buffered_data = Vec::new();
self.output_buffer.drain_to(&mut buffered_data);
const CHUNK_SIZE: usize = 4096;
for chunk in buffered_data.chunks(CHUNK_SIZE) {
client.write_all(chunk)?;
client.flush()?;
std::thread::sleep(std::time::Duration::from_micros(100));
}
}
Ok(())
}
fn looks_like_prompt(data: &[u8]) -> bool {
if data.len() < 2 {
return false;
}
let last_chars = &data[data.len().saturating_sub(10)..];
last_chars.contains(&b'$')
|| last_chars.contains(&b'#')
|| last_chars.contains(&b'>')
|| last_chars.contains(&b'%')
}
pub fn refresh_terminal(&mut self) -> Result<()> {
self.write_to_pty(b"\x0c")?;
Ok(())
}
}