use crate::core::error::{Error, Result};
use crate::storage::zero_copy::MemoryMappedView;
use std::fs::File;
use std::path::{Path, PathBuf};
pub struct MemoryMappedFile {
path: PathBuf,
byte_len: usize,
bytes: MemoryMappedView<u8>,
}
impl MemoryMappedFile {
pub fn new<P: AsRef<Path>>(path: P) -> Result<Self> {
let path = path.as_ref().to_path_buf();
let file = File::open(&path).map_err(|e| {
Error::IoError(format!(
"Failed to open {} for memory mapping: {}",
path.display(),
e
))
})?;
let byte_len = file
.metadata()
.map_err(|e| Error::IoError(format!("Failed to stat {}: {}", path.display(), e)))?
.len() as usize;
if byte_len == 0 {
return Err(Error::InvalidOperation(format!(
"Cannot memory-map empty file {}",
path.display()
)));
}
let bytes = MemoryMappedView::<u8>::from_file(file, byte_len)?;
Ok(Self {
path,
byte_len,
bytes,
})
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn len(&self) -> usize {
self.byte_len
}
pub fn is_empty(&self) -> bool {
self.byte_len == 0
}
pub fn as_bytes(&self) -> &[u8] {
self.bytes.as_slice()
}
pub unsafe fn view<T>(&self) -> Result<MemoryMappedView<T>> {
let element_size = std::mem::size_of::<T>();
if element_size == 0 {
return Err(Error::InvalidOperation(
"Cannot build a memory-mapped view of a zero-sized type".to_string(),
));
}
let file = File::open(&self.path).map_err(|e| {
Error::IoError(format!(
"Failed to reopen {} for typed mapping: {}",
self.path.display(),
e
))
})?;
MemoryMappedView::<T>::from_file(file, self.byte_len / element_size)
}
}
impl std::fmt::Debug for MemoryMappedFile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MemoryMappedFile")
.field("path", &self.path)
.field("byte_len", &self.byte_len)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
fn temp_file(name: &str, contents: &[u8]) -> PathBuf {
let mut path = std::env::temp_dir();
path.push(format!("pandrs_mmap_{}_{}.bin", name, std::process::id()));
let mut file = std::fs::File::create(&path).expect("create temp file");
file.write_all(contents).expect("write temp file");
file.sync_all().expect("sync temp file");
path
}
#[test]
fn maps_real_file_contents() {
let path = temp_file("contents", b"pandrs mapped bytes");
let mapped = MemoryMappedFile::new(&path).expect("map");
assert_eq!(mapped.len(), 19);
assert_eq!(mapped.as_bytes(), b"pandrs mapped bytes");
assert_eq!(mapped.path(), path.as_path());
drop(mapped);
let _ = std::fs::remove_file(&path);
}
#[test]
fn typed_view_reads_elements() {
let values: [u32; 4] = [1, 2, 3, 4];
let mut bytes = Vec::new();
for v in values {
bytes.extend_from_slice(&v.to_ne_bytes());
}
let path = temp_file("typed", &bytes);
let mapped = MemoryMappedFile::new(&path).expect("map");
let view = unsafe { mapped.view::<u32>() }.expect("typed view");
assert_eq!(view.as_slice(), &values[..]);
drop(view);
drop(mapped);
let _ = std::fs::remove_file(&path);
}
#[test]
fn missing_file_is_an_error() {
let mut path = std::env::temp_dir();
path.push("pandrs_mmap_definitely_absent.bin");
let _ = std::fs::remove_file(&path);
assert!(MemoryMappedFile::new(&path).is_err());
}
#[test]
fn empty_file_is_rejected() {
let path = temp_file("empty", b"");
assert!(MemoryMappedFile::new(&path).is_err());
let _ = std::fs::remove_file(&path);
}
}