#![doc = include_str!("../README.md")]
use std::io;
use std::io::Read;
use std::path::{Path, PathBuf};
mod container;
mod peekable;
mod stream;
pub use crate::container::{ArchiveKind, Container, ContainerKind, Items};
pub use crate::stream::CompressionKind;
pub use crate::stream::StreamKind;
#[derive(Debug, strum::EnumIs)]
pub enum FileKind {
File,
Directory,
Other,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SizeHint {
Exact(u64),
CompressedSize(u64),
#[default]
Unknown,
}
impl SizeHint {
pub fn exact(&self) -> Option<u64> {
match self {
SizeHint::Exact(n) => Some(*n),
_ => None,
}
}
pub fn compressed_size(&self) -> Option<u64> {
match self {
SizeHint::CompressedSize(n) => Some(*n),
_ => None,
}
}
pub fn any_known(&self) -> Option<u64> {
match self {
SizeHint::Exact(n) | SizeHint::CompressedSize(n) => Some(*n),
SizeHint::Unknown => None,
}
}
pub fn is_exact(&self) -> bool {
matches!(self, SizeHint::Exact(_))
}
pub fn is_unknown(&self) -> bool {
matches!(self, SizeHint::Unknown)
}
}
#[derive(Debug)]
pub struct FileItem<T: Read> {
pub path: PathBuf,
pub reader: T,
pub kind: FileKind,
pub size_hint: SizeHint,
}
pub fn recursive_read<F>(path: &Path, mut reader: impl Read, callback: &mut F) -> io::Result<()>
where
F: FnMut(FileItem<&mut dyn Read>) -> io::Result<()>,
{
read_recursive_inner(
path,
FileKind::File,
SizeHint::Unknown,
&mut reader as &mut dyn Read,
callback,
)?;
Ok(())
}
fn handle_container<F>(path: &Path, mut archive: impl Container, callback: &mut F) -> io::Result<()>
where
F: FnMut(FileItem<&mut dyn Read>) -> io::Result<()>,
{
let mut items = archive.items()?;
while let Some(x) = items.next_item() {
let mut x = x?;
let reader = &mut x.reader as &mut dyn Read;
read_recursive_inner(
path.join(x.path).as_path(),
x.kind,
x.size_hint,
reader,
callback,
)?;
}
Ok(())
}
fn read_recursive_inner<F>(
path: &Path,
kind: FileKind,
size_hint: SizeHint,
reader: &mut dyn Read,
callback: &mut F,
) -> io::Result<()>
where
F: FnMut(FileItem<&mut dyn Read>) -> io::Result<()>,
{
let container = ContainerKind::from_reader(reader)?;
match container {
ContainerKind::Stream(StreamKind::Raw(mut r)) => callback(FileItem {
path: path.to_path_buf(),
reader: &mut r as &mut dyn Read,
kind,
size_hint,
}),
ContainerKind::Stream(StreamKind::Compressed(mut c)) => {
let new_hint = match size_hint {
SizeHint::Exact(n) => SizeHint::CompressedSize(n),
other => other,
};
read_recursive_inner(path, kind, new_hint, &mut c as &mut dyn Read, callback)
}
ContainerKind::Archive(ArchiveKind::Tar(r)) => handle_container(path, r, callback),
ContainerKind::Archive(ArchiveKind::Zip(r)) => handle_container(path, r, callback),
}
}
pub fn iterate_archive<R, F>(mut reader: R, mut callback: F) -> io::Result<()>
where
R: Read,
F: FnMut(FileItem<&mut dyn Read>) -> io::Result<()>,
{
iterate_archive_inner(&mut reader as &mut dyn Read, &mut callback)
}
fn iterate_archive_inner<F>(reader: &mut dyn Read, callback: &mut F) -> io::Result<()>
where
F: FnMut(FileItem<&mut dyn Read>) -> io::Result<()>,
{
let container = ContainerKind::from_reader(reader)?;
match container {
ContainerKind::Stream(StreamKind::Raw(_)) => Err(io::Error::new(
io::ErrorKind::InvalidData,
"input is not an archive",
)),
ContainerKind::Stream(StreamKind::Compressed(mut c)) => {
iterate_archive_inner(&mut c as &mut dyn Read, callback)
}
ContainerKind::Archive(ArchiveKind::Tar(mut r)) => iterate_entries(&mut r, callback),
ContainerKind::Archive(ArchiveKind::Zip(mut r)) => iterate_entries(&mut r, callback),
}
}
fn iterate_entries<F>(archive: &mut impl Container, callback: &mut F) -> io::Result<()>
where
F: FnMut(FileItem<&mut dyn Read>) -> io::Result<()>,
{
let mut items = archive.items()?;
while let Some(item) = items.next_item() {
let mut item = item?;
callback(FileItem {
path: item.path,
reader: &mut item.reader as &mut dyn Read,
kind: item.kind,
size_hint: item.size_hint,
})?;
}
Ok(())
}