| use std::{ | |
| fs::File, | |
| io::{ErrorKind, Read, Result, Seek, SeekFrom}, | |
| path::PathBuf, | |
| time::{Duration, Instant}, | |
| }; | |
| const MIN_DURTION_BETWEEN_READS: Duration = Duration::from_secs(1); | |
| struct Follow<T> { | |
| src: T, | |
| offset: u64, | |
| last_reached_end_at: Option<Instant>, | |
| } | |
| impl<T: Followable> Read for Follow<T> { | |
| fn read(&mut self, buf: &mut [u8]) -> Result<usize> { | |
| loop { | |
| let now = Instant::now(); | |
| if let Some(last_reached_end_at) = self.last_reached_end_at { | |
| let delta = now.duration_since(last_reached_end_at); | |
| if delta < MIN_DURTION_BETWEEN_READS { | |
| std::thread::sleep(MIN_DURTION_BETWEEN_READS - delta); | |
| continue; | |
| } | |
| } | |
| let mut f = self.src.open()?; | |
| let size = f.seek(SeekFrom::End(0))?; | |
| if size < self.offset { | |
| // The src was truncated | |
| return Err(ErrorKind::UnexpectedEof.into()); | |
| } | |
| if size == self.offset { | |
| self.last_reached_end_at = Some(now); | |
| continue; | |
| } | |
| f.seek(SeekFrom::Start(self.offset))?; | |
| let bytes_read = f.read(buf)?; | |
| self.offset += bytes_read as u64; | |
| if bytes_read < buf.len() { | |
| self.last_reached_end_at = Some(now); | |
| } | |
| return Ok(bytes_read); | |
| } | |
| } | |
| } | |
| pub trait Followable { | |
| type Output: Seek + Read; | |
| fn open(&self) -> Result<Self::Output>; | |
| } | |
| impl Followable for PathBuf { | |
| type Output = File; | |
| fn open(&self) -> Result<Self::Output> { | |
| File::open(self) | |
| } | |
| } | |
| /// Read the src and do not stop when end of file is reached, but rather to wait for additional data to be appended. Similar to `tail -f`. | |
| pub fn follow<F: Followable>(src: F) -> impl Read { | |
| Follow { | |
| src, | |
| offset: 0, | |
| last_reached_end_at: None, | |
| } | |
| } |