#![allow(
clippy::disallowed_methods,
clippy::disallowed_types,
reason = "reader I/O: reads streamed resource files"
)]
use std::path::{Path, PathBuf};
use crate::value::Value;
use crate::{Error, Result, invalid};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StreamingInfo {
pub offset: u64,
pub size: u64,
pub path: String,
}
impl StreamingInfo {
pub fn of(v: &Value) -> Result<Option<StreamingInfo>> {
let num = |k: &str| {
v.get(k)
.and_then(|x| match x {
Value::UInt(u) => Some(*u),
Value::Int(i) => u64::try_from(*i).ok(),
_ => None,
})
.ok_or_else(|| invalid(format!("StreamingInfo: no {k}")))
};
let path = v
.get("path")
.and_then(Value::as_str)
.ok_or_else(|| invalid("StreamingInfo: no path"))?;
let (offset, size) = (num("offset")?, num("size")?);
if path.is_empty() || size == 0 {
return Ok(None);
}
Ok(Some(StreamingInfo {
offset,
size,
path: path.to_owned(),
}))
}
pub fn file_in(&self, dir: &Path) -> Result<PathBuf> {
let name = self.path.rsplit('/').next().unwrap_or(&self.path);
if name.is_empty() || name == "." || name == ".." || name.contains('\\') {
return Err(invalid(format!("streamed resource path {:?}", self.path)));
}
Ok(dir.join(name))
}
pub fn read(&self, dir: &Path) -> Result<Vec<u8>> {
use std::io::{Read, Seek, SeekFrom};
let path = self.file_in(dir)?;
let io = |source| Error::Io {
path: path.clone(),
source,
};
let mut f = std::fs::File::open(&path).map_err(io)?;
let len = f.metadata().map_err(io)?.len();
if self.offset.checked_add(self.size).is_none_or(|end| end > len) {
return Err(invalid(format!(
"{}: {}+{} past its end ({len} bytes)",
path.display(),
self.offset,
self.size
)));
}
f.seek(SeekFrom::Start(self.offset)).map_err(io)?;
let size = usize::try_from(self.size).map_err(|_| {
invalid(format!(
"{}: {} bytes do not fit memory",
path.display(),
self.size
))
})?;
let mut out = vec![0; size];
f.read_exact(&mut out).map_err(io)?;
Ok(out)
}
}
pub fn streaming_infos(v: &Value) -> Vec<(String, &Value)> {
fn walk<'a>(v: &'a Value, path: &str, out: &mut Vec<(String, &'a Value)>) {
match v {
Value::Struct(fields) => {
if v.get("offset").is_some() && v.get("size").is_some() && v.get("path").is_some() {
out.push((path.to_owned(), v));
return;
}
for (k, x) in fields {
walk(x, &format!("{path}.{k}"), out);
}
}
Value::Array(crate::value::Array::Values(items)) => {
for (i, x) in items.iter().enumerate() {
walk(x, &format!("{path}[{i}]"), out);
}
}
Value::Pair(p) => {
walk(&p.0, path, out);
walk(&p.1, path, out);
}
_ => {}
}
}
let mut out = Vec::new();
walk(v, "$", &mut out);
out
}