From d0d5bfdfd2f6c0efbd9e5ddbb0e00e3a281d78c2 Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Sun, 27 Oct 2019 10:45:37 -0400 Subject: move ttyrec to a separate crate --- Cargo.lock | 12 +++ Cargo.toml | 1 + src/cmd/play.rs | 9 ++- src/cmd/record.rs | 22 ++++-- src/error.rs | 6 ++ src/main.rs | 1 - src/ttyrec.rs | 215 ------------------------------------------------------ 7 files changed, 38 insertions(+), 228 deletions(-) delete mode 100644 src/ttyrec.rs diff --git a/Cargo.lock b/Cargo.lock index 5fe861c..0f4d7a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1649,6 +1649,7 @@ dependencies = [ "tokio-pty-process-stream", "tokio-signal", "tokio-tls", + "ttyrec", "twoway", "url 2.1.0", "users", @@ -1993,6 +1994,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "ttyrec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "034c0e4a24b738a650ea69bcb829388a2e72baa5e7aa1e9aa85fcd03e9f5cac6" +dependencies = [ + "futures", + "snafu", + "tokio", +] + [[package]] name = "twoway" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index 1e93dc7..9f44de0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ tokio = { version = "0.1.22", features = ["codec", "fs", "io", "reactor", "rt-fu tokio-pty-process-stream = "0.1" tokio-signal = "0.2" tokio-tls = "0.2" +ttyrec = "0.1" twoway = "0.2" url = "2" users = "0.9" diff --git a/src/cmd/play.rs b/src/cmd/play.rs index d73dcb4..28d6985 100644 --- a/src/cmd/play.rs +++ b/src/cmd/play.rs @@ -50,7 +50,7 @@ enum FileState { fut: tokio::fs::file::OpenFuture, }, Open { - reader: crate::ttyrec::Reader, + reader: ttyrec::Reader, }, Eof, } @@ -106,7 +106,7 @@ impl PlaySession { filename: filename.to_string(), } })); - let reader = crate::ttyrec::Reader::new(file); + let reader = ttyrec::Reader::new(file); self.file = FileState::Open { reader }; Ok(component_future::Async::DidWork) } @@ -116,8 +116,9 @@ impl PlaySession { fn poll_read_file(&mut self) -> component_future::Poll<(), Error> { if let FileState::Open { reader } = &mut self.file { - if let Some(frame) = - component_future::try_ready!(reader.poll_read()) + if let Some(frame) = component_future::try_ready!(reader + .poll_read() + .context(crate::error::ReadTtyrec)) { self.to_write .insert_at(frame.data, self.base_time + frame.time); diff --git a/src/cmd/record.rs b/src/cmd/record.rs index 46e76e1..2d9116c 100644 --- a/src/cmd/record.rs +++ b/src/cmd/record.rs @@ -62,7 +62,7 @@ enum FileState { fut: tokio::fs::file::CreateFuture, }, Open { - writer: crate::ttyrec::Writer, + writer: ttyrec::Writer, }, } @@ -141,9 +141,11 @@ impl RecordSession { filename: filename.clone(), } })); - let mut writer = crate::ttyrec::Writer::new(file); + let mut writer = ttyrec::Writer::new(file); if !self.buffer.contents().is_empty() { - writer.frame(self.buffer.contents())?; + writer + .frame(self.buffer.contents()) + .context(crate::error::WriteTtyrec)?; } self.file = FileState::Open { writer }; Ok(component_future::Async::DidWork) @@ -176,7 +178,9 @@ impl RecordSession { tokio_pty_process_stream::Event::Output { data } => { self.record_bytes(&data); if let FileState::Open { writer } = &mut self.file { - writer.frame(&data)?; + writer + .frame(&data) + .context(crate::error::WriteTtyrec)?; } } } @@ -231,16 +235,18 @@ impl RecordSession { } }; - if writer.is_empty() { + if writer.needs_write() { + component_future::try_ready!(writer + .poll_write() + .context(crate::error::WriteTtyrec)); + Ok(component_future::Async::DidWork) + } else { // finish writing to the file before actually ending if self.done { Ok(component_future::Async::Ready(())) } else { Ok(component_future::Async::NothingToDo) } - } else { - component_future::try_ready!(writer.poll_write()); - Ok(component_future::Async::DidWork) } } } diff --git a/src/error.rs b/src/error.rs index ee160ce..5fa2f4d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -303,6 +303,9 @@ pub enum Error { #[snafu(display("failed to read from terminal: {}", source))] ReadTerminal { source: std::io::Error }, + #[snafu(display("failed to read ttyrec: {}", source))] + ReadTtyrec { source: ttyrec::Error }, + #[snafu(display("failed to resize pty: {}", source))] ResizePty { source: std::io::Error }, @@ -464,6 +467,9 @@ pub enum Error { #[snafu(display("failed to write to terminal: {}", source))] WriteTerminalSync { source: std::io::Error }, + + #[snafu(display("failed to write ttyrec: {}", source))] + WriteTtyrec { source: ttyrec::Error }, } pub type Result = std::result::Result; diff --git a/src/main.rs b/src/main.rs index bc57f00..e0db244 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,7 +28,6 @@ mod resize; mod server; mod session_list; mod term; -mod ttyrec; fn main() { dirs::Dirs::new().create_all().unwrap(); diff --git a/src/ttyrec.rs b/src/ttyrec.rs deleted file mode 100644 index 2ccefdc..0000000 --- a/src/ttyrec.rs +++ /dev/null @@ -1,215 +0,0 @@ -use crate::prelude::*; -use std::convert::TryFrom as _; - -pub struct Frame { - pub time: std::time::Duration, - pub data: Vec, -} - -impl std::convert::TryFrom for Vec { - type Error = Error; - - fn try_from(frame: Frame) -> Result { - let secs = u32::try_from(frame.time.as_secs()).map_err(|_| { - Error::FrameTooLong { - input: frame.time.as_secs(), - } - })?; - let micros = frame.time.subsec_micros(); - let len = u32::try_from(frame.data.len()).map_err(|_| { - Error::FrameTooBig { - input: frame.data.len(), - } - })?; - let mut bytes = vec![]; - bytes.extend(secs.to_le_bytes().iter()); - bytes.extend(micros.to_le_bytes().iter()); - bytes.extend(len.to_le_bytes().iter()); - bytes.extend(frame.data.iter()); - Ok(bytes) - } -} - -#[repr(packed)] -struct Header { - secs: u32, - micros: u32, - len: u32, -} - -impl Header { - fn time(&self) -> std::time::Duration { - std::time::Duration::from_micros(u64::from( - self.secs * 1_000_000 + self.micros, - )) - } - - fn len(&self) -> usize { - self.len as usize - } -} - -pub struct Parser { - reading: std::collections::VecDeque, - read_state: Option
, -} - -impl Parser { - pub fn new() -> Self { - Self { - reading: std::collections::VecDeque::new(), - read_state: None, - } - } - - pub fn add_bytes(&mut self, bytes: &[u8]) { - self.reading.extend(bytes.iter()); - } - - pub fn next_frame(&mut self) -> Option { - let header = if let Some(header) = &self.read_state { - header - } else { - if self.reading.len() < std::mem::size_of::
() { - return None; - } - - let secs1 = self.reading.pop_front().unwrap(); - let secs2 = self.reading.pop_front().unwrap(); - let secs3 = self.reading.pop_front().unwrap(); - let secs4 = self.reading.pop_front().unwrap(); - let secs = u32::from_le_bytes([secs1, secs2, secs3, secs4]); - - let usecs1 = self.reading.pop_front().unwrap(); - let usecs2 = self.reading.pop_front().unwrap(); - let usecs3 = self.reading.pop_front().unwrap(); - let usecs4 = self.reading.pop_front().unwrap(); - let micros = u32::from_le_bytes([usecs1, usecs2, usecs3, usecs4]); - - let len1 = self.reading.pop_front().unwrap(); - let len2 = self.reading.pop_front().unwrap(); - let len3 = self.reading.pop_front().unwrap(); - let len4 = self.reading.pop_front().unwrap(); - let len = u32::from_le_bytes([len1, len2, len3, len4]); - - let header = Header { secs, micros, len }; - self.read_state = Some(header); - self.read_state.as_ref().unwrap() - }; - - if self.reading.len() < header.len() { - return None; - } - - let mut data = vec![]; - for _ in 0..header.len() { - data.push(self.reading.pop_front().unwrap()); - } - - let time = header.time(); - - self.read_state = None; - Some(Frame { time, data }) - } -} - -pub struct Creator { - base_time: Option, -} - -impl Creator { - pub fn new() -> Self { - Self { base_time: None } - } - - pub fn frame(&mut self, data: &[u8]) -> Result> { - let cur_time = std::time::Instant::now(); - let base_time = if let Some(base_time) = &self.base_time { - base_time - } else { - self.base_time = Some(cur_time); - self.base_time.as_ref().unwrap() - }; - Vec::::try_from(Frame { - time: cur_time - *base_time, - data: data.to_vec(), - }) - } -} - -pub struct Reader { - reader: R, - parser: Parser, - read_buf: [u8; 4096], - done_reading: bool, -} - -impl Reader { - pub fn new(reader: R) -> Self { - Self { - reader, - parser: Parser::new(), - read_buf: [0; 4096], - done_reading: false, - } - } - - pub fn poll_read(&mut self) -> futures::Poll, Error> { - loop { - if let Some(frame) = self.parser.next_frame() { - return Ok(futures::Async::Ready(Some(frame))); - } else if self.done_reading { - return Ok(futures::Async::Ready(None)); - } - - let n = futures::try_ready!(self - .reader - .poll_read(&mut self.read_buf) - .context(crate::error::ReadFile)); - if n > 0 { - self.parser.add_bytes(&self.read_buf[..n]); - } else { - self.done_reading = true; - } - } - } -} - -pub struct Writer { - writer: W, - creator: Creator, - to_write: std::collections::VecDeque, -} - -impl Writer { - pub fn new(writer: W) -> Self { - Self { - writer, - creator: Creator::new(), - to_write: std::collections::VecDeque::new(), - } - } - - pub fn frame(&mut self, data: &[u8]) -> Result<()> { - let bytes = self.creator.frame(data)?; - self.to_write.extend(bytes.iter()); - Ok(()) - } - - pub fn poll_write(&mut self) -> futures::Poll<(), Error> { - let (a, b) = self.to_write.as_slices(); - let buf = if a.is_empty() { b } else { a }; - let n = futures::try_ready!(self - .writer - .poll_write(buf) - .context(crate::error::WriteFile)); - for _ in 0..n { - self.to_write.pop_front(); - } - Ok(futures::Async::Ready(())) - } - - pub fn is_empty(&self) -> bool { - self.to_write.is_empty() - } -} -- cgit v1.2.3-54-g00ecf