use std::{fmt::Debug, sync::Arc};
use shared_buffer::OwnedBuffer;
use crate::{PathSegment, PathSegmentError, PathSegments, ToPathSegments};
#[derive(Debug, Clone)]
pub struct Volume {
imp: Arc<dyn AbstractVolume + Send + Sync + 'static>,
}
impl Volume {
pub(crate) fn new(volume: impl AbstractVolume + Send + Sync + 'static) -> Self {
Volume {
imp: Arc::new(volume),
}
}
pub fn metadata(&self, path: impl ToPathSegments) -> Option<Metadata> {
let path = path.to_path_segments().ok()?;
self.imp.metadata(&path)
}
pub fn read_dir(&self, path: impl ToPathSegments) -> Option<Vec<(PathSegment, Metadata)>> {
let path = path.to_path_segments().ok()?;
self.imp.read_dir(&path)
}
pub fn read_file(&self, path: impl ToPathSegments) -> Option<OwnedBuffer> {
let path = path.to_path_segments().ok()?;
self.imp.read_file(&path)
}
}
#[derive(Debug, Copy, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum Metadata {
Dir,
File {
length: usize,
},
}
impl Metadata {
pub fn is_dir(self) -> bool {
matches!(self, Metadata::Dir)
}
pub fn is_file(self) -> bool {
matches!(self, Metadata::File { .. })
}
}
pub(crate) trait AbstractVolume: Debug {
fn metadata(&self, path: &PathSegments) -> Option<Metadata>;
fn read_dir(&self, path: &PathSegments) -> Option<Vec<(PathSegment, Metadata)>>;
fn read_file(&self, path: &PathSegments) -> Option<OwnedBuffer>;
}
impl AbstractVolume for crate::v2::read::VolumeSection {
fn metadata(&self, path: &PathSegments) -> Option<Metadata> {
let entry = self.header().find(path).ok().flatten()?;
Some(v2_metadata(&entry))
}
fn read_dir(&self, path: &PathSegments) -> Option<Vec<(PathSegment, Metadata)>> {
let meta = self
.header()
.find(path)
.ok()
.flatten()
.and_then(|entry| entry.into_dir())?;
let mut entries = Vec::new();
for (name, entry) in meta.entries().flatten() {
let segment: PathSegment = name.parse().unwrap();
let meta = v2_metadata(&entry);
entries.push((segment, meta));
}
Some(entries)
}
fn read_file(&self, path: &PathSegments) -> Option<OwnedBuffer> {
self.lookup_file(path).map(Into::into).ok()
}
}
fn v2_metadata(header_entry: &crate::v2::read::volume_header::HeaderEntry<'_>) -> Metadata {
match header_entry {
crate::v2::read::volume_header::HeaderEntry::Directory(_) => Metadata::Dir,
crate::v2::read::volume_header::HeaderEntry::File(
crate::v2::read::volume_header::FileMetadata {
start_offset,
end_offset,
..
},
) => Metadata::File {
length: end_offset - start_offset,
},
}
}
#[derive(Debug)]
pub(crate) struct VolumeV1 {
pub(crate) volume: crate::v1::Volume<'static>,
pub(crate) buffer: OwnedBuffer,
}
impl VolumeV1 {
fn get_shared(&self, needle: &[u8]) -> OwnedBuffer {
if needle.is_empty() {
return OwnedBuffer::new();
}
let range = crate::utils::subslice_offsets(&self.buffer, needle);
self.buffer.slice(range)
}
}
impl AbstractVolume for VolumeV1 {
fn metadata(&self, path: &PathSegments) -> Option<Metadata> {
let path = path.to_string();
if let Ok(bytes) = self.volume.get_file(&path) {
return Some(Metadata::File {
length: bytes.len(),
});
}
if self.volume.read_dir(&path).is_ok() {
return Some(Metadata::Dir);
}
None
}
fn read_dir(&self, path: &PathSegments) -> Option<Vec<(PathSegment, Metadata)>> {
let path = path.to_string();
let mut entries = Vec::new();
for entry in self.volume.read_dir(&path).ok()? {
let name: PathSegment = match entry.text.parse() {
Ok(p) => p,
Err(_) => continue,
};
let meta = v1_metadata(&entry);
entries.push((name, meta));
}
Some(entries)
}
fn read_file(&self, path: &PathSegments) -> Option<OwnedBuffer> {
let path = path.to_string();
let bytes = self.volume.get_file(&path).ok()?;
let owned_bytes = self.get_shared(bytes);
Some(owned_bytes)
}
}
fn v1_metadata(entry: &crate::v1::FsEntry<'_>) -> Metadata {
match entry.fs_type {
crate::v1::FsEntryType::File => Metadata::File {
length: entry.get_len().try_into().unwrap(),
},
crate::v1::FsEntryType::Dir => Metadata::Dir,
}
}
impl AbstractVolume for crate::wasmer_package::Volume {
fn metadata(&self, path: &PathSegments) -> Option<Metadata> {
self.metadata(path)
}
fn read_dir(&self, path: &PathSegments) -> Option<Vec<(PathSegment, Metadata)>> {
self.read_dir(path)
}
fn read_file(&self, path: &PathSegments) -> Option<OwnedBuffer> {
self.read_file(path)
}
}
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum VolumeError {
#[error("The item wasn't found")]
NotFound,
#[error("Invalid path")]
Path(#[from] PathSegmentError),
#[error("Not a directory")]
NotADirectory,
#[error("Not a file")]
NotAFile,
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use super::*;
use crate::{metadata::Manifest, v1::DirOrFile, v2::write::Writer};
fn v2_volume(volume: crate::v2::write::Directory<'static>) -> crate::v2::read::VolumeSection {
let manifest = Manifest::default();
let mut writer = Writer::default()
.write_manifest(&manifest)
.unwrap()
.write_atoms(BTreeMap::new())
.unwrap();
writer.write_volume("volume", volume).unwrap();
let serialized = writer.finish(crate::v2::SignatureAlgorithm::None).unwrap();
let reader = crate::v2::read::OwnedReader::parse(serialized).unwrap();
reader.get_volume("volume").unwrap()
}
#[test]
fn v2() {
let dir = dir_map! {
"path" => dir_map! {
"to" => dir_map! {
"file.txt" => b"Hello, World!",
}
},
"another.txt" => b"Another",
};
let volume = v2_volume(dir);
let volume = Volume::new(volume);
assert!(volume.read_file("").is_none());
assert_eq!(volume.read_file("/another.txt").unwrap(), b"Another");
assert_eq!(
volume.metadata("/another.txt").unwrap(),
Metadata::File { length: 7 }
);
assert_eq!(
volume.read_file("/path/to/file.txt").unwrap(),
b"Hello, World!",
);
assert_eq!(
volume.read_dir("/").unwrap(),
vec![
(
PathSegment::parse("another.txt").unwrap(),
Metadata::File { length: 7 },
),
(PathSegment::parse("path").unwrap(), Metadata::Dir),
],
);
assert_eq!(
volume.read_dir("/path").unwrap(),
vec![(PathSegment::parse("to").unwrap(), Metadata::Dir)],
);
assert_eq!(
volume.read_dir("/path/to/").unwrap(),
vec![(
PathSegment::parse("file.txt").unwrap(),
Metadata::File { length: 13 }
)],
);
}
fn owned_volume_v1(entries: BTreeMap<DirOrFile, Vec<u8>>) -> VolumeV1 {
let bytes = crate::v1::Volume::serialize_files(entries);
let borrowed_volume = crate::v1::Volume::parse(&bytes).unwrap();
VolumeV1 {
volume: unsafe { std::mem::transmute(borrowed_volume) },
buffer: bytes.into(),
}
}
#[test]
fn v1_owned() {
let mut dir = BTreeMap::new();
dir.insert(DirOrFile::Dir("/path".into()), Vec::new());
dir.insert(DirOrFile::Dir("/path/to".into()), Vec::new());
dir.insert(
DirOrFile::File("/path/to/file.txt".into()),
b"Hello, World!".to_vec(),
);
dir.insert(DirOrFile::Dir("/path/to".into()), Vec::new());
dir.insert(DirOrFile::File("/another.txt".into()), b"Another".to_vec());
let volume = owned_volume_v1(dir);
let volume = Volume::new(volume);
assert!(volume.read_file("").is_none());
assert_eq!(volume.read_file("/another.txt").unwrap(), b"Another");
assert_eq!(
volume.metadata("/another.txt").unwrap(),
Metadata::File { length: 7 }
);
assert_eq!(
volume.read_file("/path/to/file.txt").unwrap(),
b"Hello, World!",
);
assert_eq!(volume.metadata("/path/to").unwrap(), Metadata::Dir,);
assert_eq!(
volume.read_dir("/").unwrap(),
vec![
(
PathSegment::parse("another.txt").unwrap(),
Metadata::File { length: 7 }
),
(PathSegment::parse("path").unwrap(), Metadata::Dir),
],
);
assert_eq!(
volume.read_dir("/path").unwrap(),
vec![(PathSegment::parse("to").unwrap(), Metadata::Dir)],
);
assert_eq!(
volume.read_dir("/path/to/").unwrap(),
vec![(
PathSegment::parse("file.txt").unwrap(),
Metadata::File { length: 13 }
)],
);
}
}