use std::borrow::Cow;
use std::fs::File;
use std::io;
use std::ops::Deref;
use std::path::Path;
use std::sync::Arc;
use memmap2::Mmap;
use crate::cell::StableDeref;
#[derive(Debug)]
enum ByteViewBacking<'a> {
Buf(Cow<'a, [u8]>),
Mmap(Mmap),
}
impl Deref for ByteViewBacking<'_> {
type Target = [u8];
fn deref(&self) -> &Self::Target {
match *self {
ByteViewBacking::Buf(ref buf) => buf,
ByteViewBacking::Mmap(ref mmap) => mmap,
}
}
}
#[derive(Clone, Debug)]
pub struct ByteView<'a> {
backing: Arc<ByteViewBacking<'a>>,
}
impl<'a> ByteView<'a> {
fn with_backing(backing: ByteViewBacking<'a>) -> Self {
ByteView {
backing: Arc::new(backing),
}
}
pub fn from_cow(cow: Cow<'a, [u8]>) -> Self {
ByteView::with_backing(ByteViewBacking::Buf(cow))
}
pub fn from_slice(buffer: &'a [u8]) -> Self {
ByteView::from_cow(Cow::Borrowed(buffer))
}
pub fn from_vec(buffer: Vec<u8>) -> Self {
ByteView::from_cow(Cow::Owned(buffer))
}
pub fn map_file(file: File) -> Result<Self, io::Error> {
Self::map_file_ref(&file)
}
pub fn map_file_ref(file: &File) -> Result<Self, io::Error> {
let backing = match unsafe { Mmap::map(file) } {
Ok(mmap) => ByteViewBacking::Mmap(mmap),
Err(err) => {
if err.kind() == io::ErrorKind::InvalidInput
|| (cfg!(windows) && err.raw_os_error() == Some(1006))
{
ByteViewBacking::Buf(Cow::Borrowed(b""))
} else {
return Err(err);
}
}
};
Ok(ByteView::with_backing(backing))
}
pub fn read<R: io::Read>(mut reader: R) -> Result<Self, io::Error> {
let mut buffer = vec![];
reader.read_to_end(&mut buffer)?;
Ok(ByteView::from_vec(buffer))
}
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, io::Error> {
let file = File::open(path)?;
Self::map_file(file)
}
#[inline(always)]
pub fn as_slice(&self) -> &[u8] {
self.backing.deref()
}
}
impl AsRef<[u8]> for ByteView<'_> {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
self.as_slice()
}
}
impl Deref for ByteView<'_> {
type Target = [u8];
#[inline(always)]
fn deref(&self) -> &Self::Target {
self.as_slice()
}
}
unsafe impl StableDeref for ByteView<'_> {}
#[cfg(test)]
mod tests {
use super::*;
use std::io::{Read, Seek, Write};
use similar_asserts::assert_eq;
use tempfile::NamedTempFile;
#[test]
fn test_open_empty_file() -> Result<(), std::io::Error> {
let tmp = NamedTempFile::new()?;
let view = ByteView::open(tmp.path())?;
assert_eq!(&*view, b"");
Ok(())
}
#[test]
fn test_open_file() -> Result<(), std::io::Error> {
let mut tmp = NamedTempFile::new()?;
tmp.write_all(b"1234")?;
let view = ByteView::open(tmp.path())?;
assert_eq!(&*view, b"1234");
Ok(())
}
#[test]
fn test_mmap_fd_reuse() -> Result<(), std::io::Error> {
let mut tmp = NamedTempFile::new()?;
tmp.write_all(b"1234")?;
let view = ByteView::map_file_ref(tmp.as_file())?;
let _path = tmp.path().to_path_buf();
let mut file = tmp.into_file();
#[cfg(not(windows))]
{
assert!(!_path.exists());
}
let mut buf = Vec::new();
file.rewind()?;
file.read_to_end(&mut buf)?;
assert_eq!(buf, b"1234");
drop(file);
assert_eq!(&*view, b"1234");
Ok(())
}
}