use std::io::{BufRead, BufReader, Read}; use std::sync::mpsc; use std::time::{Duration, Instant}; use std::{io, thread}; pub struct ThreadedReader { #[allow(dead_code)] handle: thread::JoinHandle>, rx: mpsc::Receiver, } impl ThreadedReader { pub fn new(reader: R) -> Self where R: Read + Send + 'static, { let (tx, rx) = mpsc::channel(); let handle = thread::spawn(move || { let mut reader = BufReader::new(reader); let mut line = String::new(); loop { match reader.read_line(&mut line) { Ok(0) => break Ok(()), Ok(_) => { // Consume the line and create an empty line to // be filled in next time let line2 = line; line = String::new(); if let Err(line) = tx.send(line2) { return Err(io::Error::new( io::ErrorKind::Other, format!( "Failed to pass along line because channel closed! Line: '{}'", line.0 ), )); } } Err(x) => return Err(x), } } }); Self { handle, rx } } /// Tries to read the next line if available pub fn try_read_line(&mut self) -> Option { self.rx.try_recv().ok() } /// Reads the next line, waiting for at minimum "timeout" pub fn try_read_line_timeout(&mut self, timeout: Duration) -> Option { let start_time = Instant::now(); let mut checked_at_least_once = false; while !checked_at_least_once || start_time.elapsed() < timeout { if let Some(line) = self.try_read_line() { return Some(line); } checked_at_least_once = true; } None } /// Reads the next line, waiting for at minimum "timeout" before panicking pub fn read_line_timeout(&mut self, timeout: Duration) -> String { let start_time = Instant::now(); let mut checked_at_least_once = false; while !checked_at_least_once || start_time.elapsed() < timeout { if let Some(line) = self.try_read_line() { return line; } checked_at_least_once = true; } panic!("Reached timeout of {:?}", timeout); } /// Reads the next line, waiting for at minimum default timeout before panicking #[allow(dead_code)] pub fn read_line_default_timeout(&mut self) -> String { self.read_line_timeout(Self::default_timeout()) } /// Creates a new duration representing a default timeout for the threaded reader pub fn default_timeout() -> Duration { Duration::from_millis(250) } /// Waits for reader to shut down, returning the result #[allow(dead_code)] pub fn wait(self) -> io::Result<()> { match self.handle.join() { Ok(x) => x, Err(x) => std::panic::resume_unwind(x), } } }