use nix::libc::c_int;
use nix::poll::PollFlags;
use std::io::Read;
use std::io::Result;
use std::io::Seek;
use std::io::SeekFrom;
use std::os::fd::AsFd;
use std::time::Duration;
use super::utils;
pub struct TimeoutReader<H>
where
H: Read + AsFd,
{
timeout: Option<c_int>,
handle: H,
}
impl<H> Read for TimeoutReader<H>
where
H: Read + AsFd,
{
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
utils::wait_until_ready(self.timeout, &self.handle, PollFlags::POLLIN)?;
self.handle.read(buf)
}
}
impl<H> Seek for TimeoutReader<H>
where
H: Read + AsFd + Seek,
{
fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
self.handle.seek(pos)
}
}
impl<H> AsFd for TimeoutReader<H>
where
H: Read + AsFd,
{
fn as_fd(&self) -> std::os::unix::prelude::BorrowedFd<'_> {
self.handle.as_fd()
}
}
impl<H> Clone for TimeoutReader<H>
where
H: Read + AsFd + Clone,
{
fn clone(&self) -> TimeoutReader<H> {
TimeoutReader {
handle: self.handle.clone(),
..*self
}
}
}
impl<H> TimeoutReader<H>
where
H: Read + AsFd,
{
pub fn new<T: Into<Option<Duration>>>(handle: H, timeout: T) -> TimeoutReader<H> {
TimeoutReader {
timeout: timeout.into().map(utils::duration_to_ms),
handle,
}
}
}
pub trait TimeoutReadExt<H>
where
H: Read + AsFd,
{
fn with_timeout<T: Into<Option<Duration>>>(self, timeout: T) -> TimeoutReader<H>;
}
impl<H> TimeoutReadExt<H> for H
where
H: Read + AsFd,
{
fn with_timeout<T: Into<Option<Duration>>>(self, timeout: T) -> TimeoutReader<H> {
TimeoutReader::new(self, timeout)
}
}
#[cfg(test)]
mod tests {
use std::env;
use std::fs::File;
use std::io::Read;
use std::os::fd::AsRawFd;
use std::path::PathBuf;
use std::time::Duration;
use super::*;
lazy_static! {
static ref CRATE_ROOT: PathBuf = {
env::current_exe()
.unwrap()
.parent()
.unwrap()
.parent()
.unwrap()
.parent()
.unwrap()
.parent()
.unwrap()
.to_path_buf()
};
}
#[test]
fn read_regular_file_with_timeout() {
let original_contents = include_str!("../test_data/regular_file.txt");
let mut regular_file = CRATE_ROOT.clone();
regular_file.push("test_data");
regular_file.push("regular_file.txt");
let fp = File::open(regular_file).unwrap();
let fp_fd = fp.as_raw_fd();
let mut fp = TimeoutReader::new(fp, Duration::new(5, 0));
let mut read_contents = String::new();
fp.read_to_string(&mut read_contents).unwrap();
assert_eq!(original_contents, read_contents);
assert_eq!(fp_fd, fp.as_fd().as_raw_fd());
}
#[test]
fn read_regular_file_no_timeout() {
let original_contents = include_str!("../test_data/regular_file.txt");
let mut regular_file = CRATE_ROOT.clone();
regular_file.push("test_data");
regular_file.push("regular_file.txt");
let fp = File::open(regular_file).unwrap();
let mut fp = TimeoutReader::new(fp, None);
let mut read_contents = String::new();
fp.read_to_string(&mut read_contents).unwrap();
assert_eq!(original_contents, read_contents);
}
#[test]
fn read_regular_file_with_timeout_extension_trait() {
let original_contents = include_str!("../test_data/regular_file.txt");
let mut regular_file = CRATE_ROOT.clone();
regular_file.push("test_data");
regular_file.push("regular_file.txt");
let fp = File::open(regular_file).unwrap();
let mut fp = fp.with_timeout(Duration::new(5, 0));
let mut read_contents = String::new();
fp.read_to_string(&mut read_contents).unwrap();
assert_eq!(original_contents, read_contents);
}
}