use std::collections::BTreeMap;
use crate::v2::{
read::{
volume_header::{DirectoryMetadata, FileMetadata, HeaderEntry},
VolumeHeaderError,
},
Span,
};
use shared_buffer::OwnedBuffer;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DirEntry<'volume> {
Dir(Directory<'volume>),
File(FileEntry),
}
impl<'volume> DirEntry<'volume> {
fn from_metadata(
metadata: HeaderEntry<'volume>,
data_offset: usize,
data_section: OwnedBuffer,
) -> Result<Self, DirEntryError> {
match metadata {
HeaderEntry::Directory(dir) => {
Ok(Directory::new(dir, data_offset, data_section).into())
}
HeaderEntry::File(meta) => {
let file = FileEntry::from_metadata(meta, data_offset, data_section)?;
Ok(file.into())
}
}
}
pub fn into_file(self) -> Option<FileEntry> {
match self {
DirEntry::File(f) => Some(f),
_ => None,
}
}
pub fn into_dir(self) -> Option<Directory<'volume>> {
match self {
DirEntry::Dir(d) => Some(d),
_ => None,
}
}
}
impl<'volume> From<Directory<'volume>> for DirEntry<'volume> {
fn from(v: Directory<'volume>) -> Self {
Self::Dir(v)
}
}
impl From<FileEntry> for DirEntry<'_> {
fn from(f: FileEntry) -> Self {
Self::File(f)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Directory<'volume> {
metadata: DirectoryMetadata<'volume>,
data_offset: usize,
data_section: OwnedBuffer,
}
impl<'volume> Directory<'volume> {
pub(crate) fn new(
metadata: DirectoryMetadata<'volume>,
data_offset: usize,
data_section: OwnedBuffer,
) -> Self {
Directory {
metadata,
data_offset,
data_section,
}
}
pub fn entries(
&self,
) -> impl Iterator<Item = Result<(&'volume str, DirEntry<'volume>), DirEntryError>> + '_ {
self.metadata.clone().entries().map(|result| {
result
.map_err(DirEntryError::from)
.and_then(|(name, entry)| {
let entry = DirEntry::from_metadata(
entry,
self.data_offset,
self.data_section.clone(),
)?;
Ok((name, entry))
})
})
}
pub fn find(&self, name: &str) -> Result<Option<DirEntry<'volume>>, DirEntryError> {
for result in self.entries() {
let (entry_name, entry) = result?;
if name == entry_name {
return Ok(Some(entry));
}
}
Ok(None)
}
pub fn is_empty(&self) -> bool {
self.entries().filter_map(|result| result.ok()).count() == 0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FileEntry {
data: OwnedBuffer,
offset: usize,
checksum: [u8; 32],
}
impl FileEntry {
pub(crate) fn from_metadata(
meta: FileMetadata,
data_offset: usize,
data_section: OwnedBuffer,
) -> Result<FileEntry, DirEntryError> {
let FileMetadata {
start_offset,
end_offset,
checksum,
} = meta;
if start_offset > data_section.len() || end_offset > data_section.len() {
return Err(DirEntryError::FileOutOfBounds {
start_offset,
end_offset,
data_section_length: data_section.len(),
});
}
let data = data_section.slice(start_offset..end_offset);
Ok(FileEntry {
data,
offset: data_offset + start_offset,
checksum,
})
}
pub fn bytes(&self) -> &OwnedBuffer {
&self.data
}
pub fn len(&self) -> usize {
self.bytes().len()
}
pub fn is_empty(&self) -> bool {
self.bytes().is_empty()
}
pub fn span(&self) -> Span {
Span::new(self.offset, self.len())
}
pub fn checksum(&self) -> [u8; 32] {
self.checksum
}
}
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
#[non_exhaustive]
pub enum DirEntryError {
#[error("The volume header was corrupted")]
Header(#[from] VolumeHeaderError),
#[error("File data is out of bounds ({start_offset}..{end_offset} is outside of 0..{data_section_length})")]
FileOutOfBounds {
start_offset: usize,
end_offset: usize,
data_section_length: usize,
},
}
impl TryFrom<DirEntry<'_>> for crate::v2::write::DirEntry<'_> {
type Error = DirEntryError;
fn try_from(value: DirEntry<'_>) -> Result<Self, Self::Error> {
match value {
DirEntry::Dir(d) => d.try_into().map(crate::v2::write::DirEntry::Dir),
DirEntry::File(f) => Ok(crate::v2::write::DirEntry::File(f.into())),
}
}
}
impl TryFrom<Directory<'_>> for crate::v2::write::Directory<'_> {
type Error = DirEntryError;
fn try_from(dir: Directory<'_>) -> Result<Self, Self::Error> {
let mut children = BTreeMap::new();
for result in dir.entries() {
let (name, entry) = result?;
let name = name
.parse()
.expect("All names coming from the directory parser should be valid path segments");
children.insert(name, entry.try_into()?);
}
Ok(crate::v2::write::Directory { children })
}
}
impl From<FileEntry> for crate::v2::write::FileEntry<'_> {
fn from(value: FileEntry) -> Self {
crate::v2::write::FileEntry::Owned(value.bytes().clone().into_bytes())
}
}