use std::collections::VecDeque;
use std::io::{self, Read, Seek, SeekFrom};
use std::sync::{Arc, Condvar, Mutex};
use std::thread::{self, JoinHandle};
use std::time::Duration;
use oxideav_container::ReadSeek;
const BLOCK: usize = 256 * 1024;
struct RingState {
buf: VecDeque<u8>,
ring_start: u64,
capacity: usize,
total_len: Option<u64>,
eof: bool,
err: Option<io::Error>,
target_pos: Option<u64>,
stop: bool,
}
struct Shared {
state: Mutex<RingState>,
not_full: Condvar,
not_empty: Condvar,
}
pub struct BufferedSource {
shared: Arc<Shared>,
pos: u64,
worker: Option<JoinHandle<()>>,
}
impl BufferedSource {
pub fn new(mut inner: Box<dyn ReadSeek>, capacity: usize) -> io::Result<Self> {
let capacity = capacity.max(4 * BLOCK);
let pos = inner.stream_position()?;
let end = inner.seek(SeekFrom::End(0))?;
let total_len = Some(end);
inner.seek(SeekFrom::Start(pos))?;
let state = RingState {
buf: VecDeque::with_capacity(capacity),
ring_start: pos,
capacity,
total_len,
eof: total_len == Some(pos),
err: None,
target_pos: None,
stop: false,
};
let shared = Arc::new(Shared {
state: Mutex::new(state),
not_full: Condvar::new(),
not_empty: Condvar::new(),
});
let worker_shared = Arc::clone(&shared);
let worker = thread::spawn(move || worker_loop(worker_shared, inner));
Ok(Self {
shared,
pos,
worker: Some(worker),
})
}
pub fn len(&self) -> Option<u64> {
self.shared.state.lock().unwrap().total_len
}
pub fn is_empty(&self) -> bool {
matches!(self.len(), Some(0))
}
}
fn worker_loop(shared: Arc<Shared>, mut inner: Box<dyn ReadSeek>) {
let mut scratch = vec![0u8; BLOCK];
loop {
let to_read: usize;
{
let mut st = shared.state.lock().unwrap();
loop {
if st.stop {
return;
}
if let Some(target) = st.target_pos.take() {
st.buf.clear();
st.ring_start = target;
st.eof = matches!(st.total_len, Some(end) if target >= end);
st.err = None;
shared.not_empty.notify_all();
drop(st);
if let Err(e) = inner.seek(SeekFrom::Start(target)) {
let mut st = shared.state.lock().unwrap();
st.err = Some(e);
shared.not_empty.notify_all();
return;
}
st = shared.state.lock().unwrap();
continue;
}
if st.eof {
st = shared.not_full.wait(st).unwrap();
continue;
}
let free = st.capacity - st.buf.len();
if free == 0 {
st = shared.not_full.wait(st).unwrap();
continue;
}
to_read = free.min(BLOCK);
break;
}
}
let read_result = inner.read(&mut scratch[..to_read]);
let mut st = shared.state.lock().unwrap();
if st.target_pos.is_some() || st.stop {
continue;
}
match read_result {
Ok(0) => {
st.eof = true;
shared.not_empty.notify_all();
}
Ok(n) => {
st.buf.extend(scratch[..n].iter().copied());
shared.not_empty.notify_all();
}
Err(e) => {
st.err = Some(e);
shared.not_empty.notify_all();
return;
}
}
}
}
impl Read for BufferedSource {
fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
if out.is_empty() {
return Ok(0);
}
let mut st = self.shared.state.lock().unwrap();
loop {
if let Some(e) = st.err.take() {
return Err(e);
}
let rel = self.pos.saturating_sub(st.ring_start) as usize;
if self.pos < st.ring_start {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"BufferedSource: reader behind ring start",
));
}
if rel < st.buf.len() {
let avail = st.buf.len() - rel;
let n = avail.min(out.len());
for (i, byte) in st.buf.iter().skip(rel).take(n).enumerate() {
out[i] = *byte;
}
self.pos += n as u64;
let drop_n = rel + n;
let rear = st.capacity / 8;
if drop_n > rear {
let to_drop = drop_n - rear;
st.buf.drain(..to_drop);
st.ring_start += to_drop as u64;
self.shared.not_full.notify_one();
}
return Ok(n);
}
if st.eof {
return Ok(0);
}
let (new_st, wait_result) = self
.shared
.not_empty
.wait_timeout(st, Duration::from_secs(30))
.unwrap();
st = new_st;
if wait_result.timed_out() && st.err.is_none() && !st.eof {
return Err(io::Error::new(
io::ErrorKind::TimedOut,
"BufferedSource: prefetch timeout (30s)",
));
}
}
}
}
impl Seek for BufferedSource {
fn seek(&mut self, from: SeekFrom) -> io::Result<u64> {
let mut st = self.shared.state.lock().unwrap();
let total = st.total_len;
let new_pos: u64 = match from {
SeekFrom::Start(n) => n,
SeekFrom::Current(d) => add_signed(self.pos, d)?,
SeekFrom::End(d) => {
let end = total.ok_or_else(|| {
io::Error::new(io::ErrorKind::Unsupported, "stream length unknown")
})?;
add_signed(end, d)?
}
};
let ring_end = st.ring_start + st.buf.len() as u64;
if new_pos >= st.ring_start && new_pos <= ring_end {
self.pos = new_pos;
return Ok(new_pos);
}
st.target_pos = Some(new_pos);
st.buf.clear();
st.ring_start = new_pos;
st.eof = matches!(total, Some(end) if new_pos >= end);
st.err = None;
self.pos = new_pos;
self.shared.not_full.notify_all();
self.shared.not_empty.notify_all();
Ok(new_pos)
}
}
fn add_signed(base: u64, delta: i64) -> io::Result<u64> {
if delta >= 0 {
base.checked_add(delta as u64)
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "seek overflow"))
} else {
let mag = delta.unsigned_abs();
base.checked_sub(mag)
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "seek before start"))
}
}
impl Drop for BufferedSource {
fn drop(&mut self) {
{
let mut st = self.shared.state.lock().unwrap();
st.stop = true;
}
self.shared.not_full.notify_all();
self.shared.not_empty.notify_all();
if let Some(h) = self.worker.take() {
let _ = h.join();
}
}
}