use std::fs::File;
use std::io::{Error, Result};
use std::os::unix::fs::FileExt;
use crate::Location;
fn make_short_read_error(expected: usize, got: usize, offset: u32) -> Error {
Error::other(format!(
"short read: expected {expected} bytes, got {got} at offset {offset}"
))
}
#[derive(Debug)]
pub struct FileArena {
files: Vec<File>,
}
impl FileArena {
pub fn new(files: Vec<File>) -> Result<Self> {
if files.is_empty() {
return Err(Error::other("FileArena::new: files vector is empty"));
}
Ok(Self { files })
}
pub fn get(&self, loc: Location) -> Result<Vec<u8>> {
if loc.is_empty() {
return Ok(Vec::new());
}
let mut buf = vec![0u8; loc.len() as usize];
let bytes_read =
self.files[loc.file_index() as usize].read_at(&mut buf, u64::from(loc.offset()))?;
if bytes_read != buf.len() {
return Err(make_short_read_error(buf.len(), bytes_read, loc.offset()));
}
Ok(buf)
}
pub fn get_into(&self, loc: Location, out: &mut Vec<u8>) -> Result<()> {
if loc.is_empty() {
return Ok(());
}
let len = loc.len() as usize;
out.reserve(len);
let start = out.len();
unsafe {
out.set_len(start + len);
}
let read_result = self.files[loc.file_index() as usize]
.read_at(&mut out[start..], u64::from(loc.offset()));
match read_result {
Ok(n) if n == len => Ok(()),
Ok(n) => {
out.truncate(start);
Err(make_short_read_error(len, n, loc.offset()))
}
Err(e) => {
out.truncate(start);
Err(e)
}
}
}
pub fn get_str_into(&self, loc: Location, out: &mut String) -> Result<()> {
if loc.is_empty() {
return Ok(());
}
let len = loc.len() as usize;
let start = out.len();
let bytes = unsafe { out.as_mut_vec() };
bytes.reserve(len);
unsafe {
std::ptr::write_bytes(bytes.as_mut_ptr().add(start), 0, len);
bytes.set_len(start + len);
}
match self.files[loc.file_index() as usize]
.read_at(&mut bytes[start..], u64::from(loc.offset()))
{
Ok(n) if n == len => {}
Ok(n) => {
unsafe { bytes.set_len(start) };
return Err(make_short_read_error(len, n, loc.offset()));
}
Err(e) => {
unsafe { bytes.set_len(start) };
return Err(e);
}
}
if let Err(e) = std::str::from_utf8(&bytes[start..]) {
unsafe { bytes.set_len(start) };
return Err(Error::other(format!(
"stored bytes are not valid UTF-8: {e}"
)));
}
Ok(())
}
pub unsafe fn get_str_into_unchecked(&self, loc: Location, out: &mut String) -> Result<()> {
if loc.is_empty() {
return Ok(());
}
let len = loc.len() as usize;
let start = out.len();
let bytes = unsafe { out.as_mut_vec() };
bytes.reserve(len);
unsafe {
let ptr = bytes.as_mut_ptr().add(start);
std::ptr::write_bytes(ptr, 0, len);
bytes.set_len(start + len);
}
let read_result = self.files[loc.file_index() as usize]
.read_at(&mut bytes[start..], u64::from(loc.offset()));
match read_result {
Ok(n) if n == len => {}
Ok(n) => {
unsafe {
bytes.set_len(start);
}
return Err(make_short_read_error(len, n, loc.offset()));
}
Err(e) => {
unsafe {
bytes.set_len(start);
}
return Err(e);
}
}
debug_assert!(
std::str::from_utf8(&out.as_bytes()[start..]).is_ok(),
"farena::get_str_into_unchecked: bytes at {loc:?} are not valid UTF-8"
);
Ok(())
}
#[must_use]
pub fn file_count(&self) -> usize {
self.files.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
const _: () = {
const fn assert_sync<T: Sync>() {}
const fn assert_send<T: Send>() {}
assert_sync::<FileArena>();
assert_send::<FileArena>();
};
#[test]
fn short_read_error_format() {
let err = make_short_read_error(100, 50, 1234);
assert_eq!(
err.to_string(),
"short read: expected 100 bytes, got 50 at offset 1234"
);
}
}