use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom};
use std::sync::{Arc, Mutex};
pub trait ReadSeekSend: Read + Seek + Send {}
impl<T: Read + Seek + Send> ReadSeekSend for T {}
fn pread(file: &File, buf: &mut [u8], offset: u64) -> io::Result<usize> {
#[cfg(unix)]
use std::os::unix::fs::FileExt;
#[cfg(windows)]
use std::os::windows::fs::FileExt;
let mut total = 0usize;
while total < buf.len() {
#[cfg(unix)]
let res = file.read_at(&mut buf[total..], offset + total as u64);
#[cfg(windows)]
let res = file.seek_read(&mut buf[total..], offset + total as u64);
#[cfg(not(any(unix, windows)))]
let res: io::Result<usize> = Err(io::Error::new(
io::ErrorKind::Unsupported,
"positioned reads unsupported on this platform",
));
match res {
Ok(0) => break,
Ok(n) => total += n,
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
}
Ok(total)
}
pub enum Backing {
File(File),
Sub {
file: Arc<File>,
base: u64,
len: u64,
},
Mem(Arc<[u8]>),
Reader {
inner: Mutex<Box<dyn ReadSeekSend>>,
len: u64,
},
}
impl Backing {
#[must_use]
pub fn sub(file: Arc<File>, base: u64, len: u64) -> Self {
Backing::Sub { file, base, len }
}
#[must_use]
pub fn from_bytes(bytes: impl Into<Arc<[u8]>>) -> Self {
Backing::Mem(bytes.into())
}
#[must_use]
pub fn len(&self) -> u64 {
match self {
Backing::File(f) => f.metadata().map_or(0, |m| m.len()),
Backing::Mem(b) => b.len() as u64,
Backing::Sub { len, .. } | Backing::Reader { len, .. } => *len,
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
match self {
Backing::File(f) => pread(f, buf, offset),
Backing::Sub { file, base, len } => {
let avail = len.saturating_sub(offset);
if avail == 0 {
return Ok(0);
}
let want = (buf.len() as u64).min(avail) as usize;
pread(file, &mut buf[..want], base + offset)
}
Backing::Mem(bytes) => {
let off = offset.min(bytes.len() as u64) as usize;
let src = &bytes[off..];
let n = src.len().min(buf.len());
buf[..n].copy_from_slice(&src[..n]);
Ok(n)
}
Backing::Reader { inner, len } => {
let avail = len.saturating_sub(offset);
if avail == 0 {
return Ok(0);
}
let want = (buf.len() as u64).min(avail) as usize;
let mut guard = inner
.lock()
.map_err(|_| io::Error::other("backing reader mutex poisoned"))?;
guard.seek(SeekFrom::Start(offset))?;
let dst = &mut buf[..want];
let mut total = 0;
while total < dst.len() {
match guard.read(&mut dst[total..])? {
0 => break, n => total += n,
}
}
Ok(total)
}
}
}
pub fn read_exact_at(&self, offset: u64, len: usize) -> io::Result<Vec<u8>> {
let mut buf = vec![0u8; len];
let n = self.read_at(&mut buf, offset)?;
buf.truncate(n);
Ok(buf)
}
}
impl std::fmt::Debug for Backing {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Backing::File(_) => f.debug_struct("File").field("len", &self.len()).finish(),
Backing::Sub { base, len, .. } => f
.debug_struct("Sub")
.field("base", base)
.field("len", len)
.finish(),
Backing::Mem(b) => f.debug_struct("Mem").field("len", &b.len()).finish(),
Backing::Reader { len, .. } => f.debug_struct("Reader").field("len", len).finish(),
}
}
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use super::{Backing, ReadSeekSend};
fn cursor_backing(data: Vec<u8>) -> Backing {
let len = data.len() as u64;
let inner = std::sync::Mutex::new(Box::new(Cursor::new(data)) as Box<dyn ReadSeekSend>);
Backing::Reader { inner, len }
}
#[test]
fn reader_backing_len_matches_construction() {
let b = cursor_backing(vec![1u8, 2, 3, 4, 5]);
assert_eq!(b.len(), 5);
assert!(!b.is_empty());
}
#[test]
fn reader_backing_read_at_fills_buf() {
let b = cursor_backing(vec![10, 20, 30, 40, 50]);
let mut buf = [0u8; 3];
let n = b.read_at(&mut buf, 1).unwrap();
assert_eq!(n, 3);
assert_eq!(buf, [20, 30, 40]);
}
#[test]
fn reader_backing_read_at_clamps_at_eof() {
let b = cursor_backing(vec![10, 20, 30]);
let mut buf = [0u8; 10];
let n = b.read_at(&mut buf, 1).unwrap();
assert_eq!(n, 2, "read past end should be clamped");
assert_eq!(&buf[..2], &[20, 30]);
}
#[test]
fn reader_backing_read_at_past_eof_returns_zero() {
let b = cursor_backing(vec![1, 2, 3]);
let mut buf = [0xFFu8; 4];
let n = b.read_at(&mut buf, 100).unwrap();
assert_eq!(n, 0);
}
#[test]
fn reader_backing_debug_does_not_panic() {
let b = cursor_backing(vec![0u8; 8]);
let s = format!("{b:?}");
assert!(s.contains("Reader"), "Debug output should name the variant");
}
#[test]
fn reader_backing_read_exact_at_works() {
let b = cursor_backing(vec![0, 1, 2, 3, 4, 5, 6, 7]);
let v = b.read_exact_at(2, 4).unwrap();
assert_eq!(v, vec![2, 3, 4, 5]);
}
#[test]
fn empty_reader_backing_is_empty() {
let b = cursor_backing(vec![]);
assert!(b.is_empty());
assert_eq!(b.len(), 0);
}
}