use std::fs::File; use std::io::{self, Read, Seek, SeekFrom}; use std::path::Path; use std::sync::{Arc, Condvar, Mutex}; #[derive(Default)] struct State { available: u64, complete: bool, } #[derive(Default)] struct Shared { state: Mutex, changed: Condvar, } pub struct GrowingFileReader { file: File, pos: u64, shared: Arc, } pub struct GrowingFileWriter { shared: Arc, } pub fn growing_file(path: &Path) -> io::Result<(GrowingFileReader, GrowingFileWriter)> { let shared = Arc::new(Shared::default()); Ok(( GrowingFileReader { file: File::open(path)?, pos: 0, shared: Arc::clone(&shared), }, GrowingFileWriter { shared }, )) } impl GrowingFileWriter { pub fn add_available(&self, bytes: u64) { let mut state = self .shared .state .lock() .unwrap_or_else(std::sync::PoisonError::into_inner); state.available = state.available.saturating_add(bytes); self.shared.changed.notify_all(); } pub fn finish(&self) { let mut state = self .shared .state .lock() .unwrap_or_else(std::sync::PoisonError::into_inner); state.complete = true; self.shared.changed.notify_all(); } } impl Drop for GrowingFileWriter { fn drop(&mut self) { self.finish(); } } impl Read for GrowingFileReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { loop { let mut state = self .shared .state .lock() .unwrap_or_else(std::sync::PoisonError::into_inner); while self.pos >= state.available && !state.complete { state = self .shared .changed .wait(state) .unwrap_or_else(std::sync::PoisonError::into_inner); } if self.pos >= state.available { return Ok(0); } let n = usize::try_from((state.available - self.pos).min(buf.len() as u64)) .unwrap_or(buf.len()); drop(state); let read = self.file.read(&mut buf[..n])?; self.pos += read as u64; if read > 0 { return Ok(read); } } } } impl Seek for GrowingFileReader { fn seek(&mut self, _: SeekFrom) -> io::Result { Err(io::Error::new( io::ErrorKind::Unsupported, "federated stream is not seekable while downloading", )) } }