diff options
author | Jesse Luehrs <doy@tozt.net> | 2021-12-03 21:28:44 -0500 |
---|---|---|
committer | Jesse Luehrs <doy@tozt.net> | 2021-12-04 01:58:12 -0500 |
commit | 9d07ac10cf7ec1278dd90ae8c4fe73cbd80c3fd5 (patch) | |
tree | f13aa19d200087b392e6481da97fdeb30dfd3bae | |
parent | 8a98d4fee2172d5ac53e74bcc95cd39aa68492a3 (diff) | |
download | ttyrec-9d07ac10cf7ec1278dd90ae8c4fe73cbd80c3fd5.tar.gz ttyrec-9d07ac10cf7ec1278dd90ae8c4fe73cbd80c3fd5.zip |
reintroduce readers and writers with a new api
-rw-r--r-- | Cargo.toml | 5 | ||||
-rw-r--r-- | src/blocking/mod.rs | 4 | ||||
-rw-r--r-- | src/blocking/reader.rs | 46 | ||||
-rw-r--r-- | src/blocking/writer.rs | 43 | ||||
-rw-r--r-- | src/error.rs | 12 | ||||
-rw-r--r-- | src/lib.rs | 11 | ||||
-rw-r--r-- | src/reader.rs | 49 | ||||
-rw-r--r-- | src/writer.rs | 46 |
8 files changed, 215 insertions, 1 deletions
@@ -12,3 +12,8 @@ keywords = ["ttyrec"] categories = ["parser-implementations"] [dependencies] +futures = { version = "0.3.18", optional = true } + +[features] +default = ["async"] +async = ["futures"] diff --git a/src/blocking/mod.rs b/src/blocking/mod.rs new file mode 100644 index 0000000..1ffaccd --- /dev/null +++ b/src/blocking/mod.rs @@ -0,0 +1,4 @@ +pub mod reader; +pub use reader::Reader; +pub mod writer; +pub use writer::Writer; diff --git a/src/blocking/reader.rs b/src/blocking/reader.rs new file mode 100644 index 0000000..86c348d --- /dev/null +++ b/src/blocking/reader.rs @@ -0,0 +1,46 @@ +/// Reads ttyrec frames from a `std::io::Read` instance. +pub struct Reader<T: std::io::Read> { + input: T, + parser: crate::Parser, + buf: [u8; 4096], +} + +impl<T: std::io::Read> Reader<T> { + /// Creates a new `Reader` from a `std::io::Read` instance. + pub fn new(input: T) -> Self { + Self { + input, + parser: crate::Parser::new(), + buf: [0; 4096], + } + } + + /// Returns the next parsed frame from the input stream. + /// + /// # Errors + /// * `crate::Error::EOF`: The input stream has been closed. + /// * `crate::Error::Read`: There was an error reading from the input + /// stream. + pub fn read_frame(&mut self) -> crate::Result<crate::Frame> { + loop { + if let Some(frame) = self.parser.next_frame() { + return Ok(frame); + } + let bytes = self + .input + .read(&mut self.buf) + .map_err(|source| crate::Error::Read { source })?; + if bytes == 0 { + return Err(crate::Error::EOF); + } + self.parser.add_bytes(&self.buf[..bytes]); + } + } + + /// How much the timestamps in this file should be offset by. + /// + /// See `Parser::offset`. + pub fn offset(&self) -> Option<std::time::Duration> { + self.parser.offset() + } +} diff --git a/src/blocking/writer.rs b/src/blocking/writer.rs new file mode 100644 index 0000000..5dade25 --- /dev/null +++ b/src/blocking/writer.rs @@ -0,0 +1,43 @@ +/// Writes ttyrec frames to a `std::io::Write` instance. +pub struct Writer<T: std::io::Write> { + output: T, + creator: crate::Creator, +} + +impl<T: std::io::Write> Writer<T> { + /// Creates a new `Writer` from a `std::io::Write` instance. + pub fn new(output: T) -> Self { + Self { + output, + creator: crate::Creator::new(), + } + } + + /// Writes a new frame to the output stream, using the current time and + /// given data. + /// + /// # Errors + /// * `crate::Error::Write`: There was an error writing to the input + /// stream. + pub fn frame(&mut self, data: &[u8]) -> crate::Result<()> { + self.frame_at(std::time::Instant::now(), data) + } + + /// Writes a new frame to the output stream, using the given time and + /// data. + /// + /// # Errors + /// * `crate::Error::Write`: There was an error writing to the input + /// stream. + pub fn frame_at( + &mut self, + cur_time: std::time::Instant, + data: &[u8], + ) -> crate::Result<()> { + let frame = self.creator.frame_at(cur_time, data); + let bytes: Vec<u8> = frame.try_into()?; + self.output + .write_all(&bytes) + .map_err(|source| crate::Error::Write { source }) + } +} diff --git a/src/error.rs b/src/error.rs index 9ca35a8..6978990 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,9 @@ /// Errors potentially returned by this crate. #[derive(Debug)] pub enum Error { + /// eof + EOF, + /// failed to create ttyrec frame: got N bytes of data but ttyrec frames /// can be at most M bytes FrameTooBig { input: usize }, @@ -8,13 +11,22 @@ pub enum Error { /// failed to create ttyrec frame: got N seconds but ttyrec frames can be /// at most M seconds FrameTooLong { input: u64 }, + + /// failed to read from input + Read { source: std::io::Error }, + + /// failed to write to output + Write { source: std::io::Error }, } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + Self::EOF => write!(f, "eof"), Self::FrameTooBig { input } => write!(f, "failed to create ttyrec frame: got {} bytes of data, but ttyrec frames can be at most {} bytes", input, u32::max_value()), Self::FrameTooLong { input } => write!(f, "failed to create ttyrec frame: got {} seconds, but ttyrecs can be at most {} seconds", input, u32::max_value()), + Self::Read { source } => write!(f, "failed to read from input: {}", source), + Self::Write { source } => write!(f, "failed to write to output: {}", source), } } } @@ -19,8 +19,17 @@ mod creator; pub use creator::Creator; mod error; -pub use error::Error; +pub use error::{Error, Result}; mod frame; pub use frame::Frame; mod parser; pub use parser::Parser; +pub mod blocking; +#[cfg(feature = "async")] +mod reader; +#[cfg(feature = "async")] +pub use reader::Reader; +#[cfg(feature = "async")] +mod writer; +#[cfg(feature = "async")] +pub use writer::Writer; diff --git a/src/reader.rs b/src/reader.rs new file mode 100644 index 0000000..6641bb9 --- /dev/null +++ b/src/reader.rs @@ -0,0 +1,49 @@ +use futures::io::AsyncReadExt as _; + +/// Reads ttyrec frames from a `futures::io::AsyncRead` instance. +pub struct Reader<T: futures::io::AsyncRead> { + input: T, + parser: crate::Parser, + buf: [u8; 4096], +} + +impl<T: futures::io::AsyncRead + std::marker::Unpin + Send> Reader<T> { + /// Creates a new `Reader` from a `futures::io::AsyncRead` instance. + pub fn new(input: T) -> Self { + Self { + input, + parser: crate::Parser::new(), + buf: [0; 4096], + } + } + + /// Returns the next parsed frame from the input stream. + /// + /// # Errors + /// * `crate::Error::EOF`: The input stream has been closed. + /// * `crate::Error::Read`: There was an error reading from the input + /// stream. + pub async fn read_frame(&mut self) -> crate::Result<crate::Frame> { + loop { + if let Some(frame) = self.parser.next_frame() { + return Ok(frame); + } + let bytes = self + .input + .read(&mut self.buf) + .await + .map_err(|source| crate::Error::Read { source })?; + if bytes == 0 { + return Err(crate::Error::EOF); + } + self.parser.add_bytes(&self.buf[..bytes]); + } + } + + /// How much the timestamps in this file should be offset by. + /// + /// See `Parser::offset`. + pub fn offset(&self) -> Option<std::time::Duration> { + self.parser.offset() + } +} diff --git a/src/writer.rs b/src/writer.rs new file mode 100644 index 0000000..6975ad1 --- /dev/null +++ b/src/writer.rs @@ -0,0 +1,46 @@ +use futures::io::AsyncWriteExt as _; + +/// Writes ttyrec frames to a `futures::io::AsyncWrite` instance. +pub struct Writer<T: futures::io::AsyncWrite> { + output: T, + creator: crate::Creator, +} + +impl<T: futures::io::AsyncWrite + std::marker::Unpin + Send> Writer<T> { + /// Creates a new `Writer` from a `futures::io::AsyncWrite` instance. + pub fn new(output: T) -> Self { + Self { + output, + creator: crate::Creator::new(), + } + } + + /// Writes a new frame to the output stream, using the current time and + /// given data. + /// + /// # Errors + /// * `crate::Error::Write`: There was an error writing to the input + /// stream. + pub async fn frame(&mut self, data: &[u8]) -> crate::Result<()> { + self.frame_at(std::time::Instant::now(), data).await + } + + /// Writes a new frame to the output stream, using the given time and + /// data. + /// + /// # Errors + /// * `crate::Error::Write`: There was an error writing to the input + /// stream. + pub async fn frame_at( + &mut self, + cur_time: std::time::Instant, + data: &[u8], + ) -> crate::Result<()> { + let frame = self.creator.frame_at(cur_time, data); + let bytes: Vec<u8> = frame.try_into()?; + self.output + .write_all(&bytes) + .await + .map_err(|source| crate::Error::Write { source }) + } +} |