use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
use std::path::{Path, PathBuf};
use std::sync::Mutex;
use crate::error::{Error, Result};
pub struct FileSource {
path: PathBuf,
file: Mutex<File>,
len: u64,
}
impl std::fmt::Debug for FileSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FileSource")
.field("path", &self.path)
.field("len", &self.len)
.finish()
}
}
impl FileSource {
pub fn open(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
let file = File::open(path)?;
let len = file.metadata()?.len();
if len == 0 {
return Err(Error::InvalidFormat("empty input file"));
}
Ok(Self {
path: path.to_path_buf(),
file: Mutex::new(file),
len,
})
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn len(&self) -> u64 {
self.len
}
pub fn read_exact_at(&self, offset: u64, len: usize, context: &'static str) -> Result<Vec<u8>> {
let end = offset
.checked_add(len as u64)
.ok_or(Error::InvalidFormat("source range overflow"))?;
if end > self.len {
return Err(Error::truncated(
context,
len,
self.len.saturating_sub(offset) as usize,
));
}
let mut file = self
.file
.lock()
.map_err(|_| Error::InvalidFormat("source mutex poisoned"))?;
file.seek(SeekFrom::Start(offset))?;
let mut out = vec![0; len];
file.read_exact(&mut out)?;
Ok(out)
}
}