use std::fs::File;
use std::io::{Error, Result};
use std::os::unix::fs::FileExt;
use crate::Location;
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.len == 0 {
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, loc.offset as u64)?;
if bytes_read != buf.len() {
return Err(Error::other(format!(
"short read: expected {} bytes, got {} at offset {}",
buf.len(),
bytes_read,
loc.offset
)));
}
Ok(buf)
}
pub fn get_into(&self, loc: Location, out: &mut Vec<u8>) -> Result<()> {
if loc.len == 0 {
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..], loc.offset as u64);
match read_result {
Ok(n) if n == len => Ok(()),
Ok(n) => {
out.truncate(start);
Err(Error::other(format!(
"short read: expected {} bytes, got {} at offset {}",
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.len == 0 {
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..], loc.offset as u64) {
Ok(n) if n == len => {}
Ok(n) => {
unsafe { bytes.set_len(start) };
return Err(Error::other(format!(
"short read: expected {} bytes, got {} at offset {}",
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.len == 0 {
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..], loc.offset as u64);
match read_result {
Ok(n) if n == len => {}
Ok(n) => {
unsafe {
bytes.set_len(start);
}
return Err(Error::other(format!(
"short read: expected {} bytes, got {} at offset {}",
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(())
}
pub fn file_count(&self) -> usize {
self.files.len()
}
}