1#![doc = include_str!("../README.md")]
2
3use std::io;
4use std::io::Read;
5use std::path::{Path, PathBuf};
6
7mod container;
8mod peekable;
9mod stream;
10
11pub use crate::container::{ArchiveKind, Container, ContainerKind, Items};
12pub use crate::stream::CompressionKind;
13pub use crate::stream::StreamKind;
14
15#[derive(Debug, strum::EnumIs)]
16pub enum FileKind {
17 File,
18 Directory,
19 Other,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
24pub enum SizeHint {
25 Exact(u64),
27 CompressedSize(u64),
29 #[default]
31 Unknown,
32}
33
34impl SizeHint {
35 pub fn exact(&self) -> Option<u64> {
37 match self {
38 SizeHint::Exact(n) => Some(*n),
39 _ => None,
40 }
41 }
42
43 pub fn compressed_size(&self) -> Option<u64> {
45 match self {
46 SizeHint::CompressedSize(n) => Some(*n),
47 _ => None,
48 }
49 }
50
51 pub fn any_known(&self) -> Option<u64> {
53 match self {
54 SizeHint::Exact(n) | SizeHint::CompressedSize(n) => Some(*n),
55 SizeHint::Unknown => None,
56 }
57 }
58
59 pub fn is_exact(&self) -> bool {
61 matches!(self, SizeHint::Exact(_))
62 }
63
64 pub fn is_unknown(&self) -> bool {
66 matches!(self, SizeHint::Unknown)
67 }
68}
69
70#[derive(Debug)]
71pub struct FileItem<T: Read> {
72 pub path: PathBuf,
73 pub reader: T,
74 pub kind: FileKind,
75 pub size_hint: SizeHint,
76}
77
78pub fn recursive_read<F>(path: &Path, mut reader: impl Read, callback: &mut F) -> io::Result<()>
79where
80 F: FnMut(FileItem<&mut dyn Read>) -> io::Result<()>,
81{
82 read_recursive_inner(
83 path,
84 FileKind::File,
85 SizeHint::Unknown,
86 &mut reader as &mut dyn Read,
87 callback,
88 )?;
89 Ok(())
90}
91
92fn handle_container<F>(path: &Path, mut archive: impl Container, callback: &mut F) -> io::Result<()>
93where
94 F: FnMut(FileItem<&mut dyn Read>) -> io::Result<()>,
95{
96 let mut items = archive.items()?;
97 while let Some(x) = items.next_item() {
98 let mut x = x?;
99 let reader = &mut x.reader as &mut dyn Read;
100 read_recursive_inner(
101 path.join(x.path).as_path(),
102 x.kind,
103 x.size_hint,
104 reader,
105 callback,
106 )?;
107 }
108 Ok(())
109}
110
111fn read_recursive_inner<F>(
112 path: &Path,
113 kind: FileKind,
114 size_hint: SizeHint,
115 reader: &mut dyn Read,
116 callback: &mut F,
117) -> io::Result<()>
118where
119 F: FnMut(FileItem<&mut dyn Read>) -> io::Result<()>,
120{
121 let container = ContainerKind::from_reader(reader)?;
122 match container {
123 ContainerKind::Stream(StreamKind::Raw(mut r)) => callback(FileItem {
124 path: path.to_path_buf(),
125 reader: &mut r as &mut dyn Read,
126 kind,
127 size_hint,
128 }),
129 ContainerKind::Stream(StreamKind::Compressed(mut c)) => {
130 let new_hint = match size_hint {
132 SizeHint::Exact(n) => SizeHint::CompressedSize(n),
133 other => other,
134 };
135 read_recursive_inner(path, kind, new_hint, &mut c as &mut dyn Read, callback)
136 }
137 ContainerKind::Archive(ArchiveKind::Tar(r)) => handle_container(path, r, callback),
138 ContainerKind::Archive(ArchiveKind::Zip(r)) => handle_container(path, r, callback),
139 }
140}
141
142pub fn iterate_archive<R, F>(mut reader: R, mut callback: F) -> io::Result<()>
151where
152 R: Read,
153 F: FnMut(FileItem<&mut dyn Read>) -> io::Result<()>,
154{
155 iterate_archive_inner(&mut reader as &mut dyn Read, &mut callback)
156}
157
158fn iterate_archive_inner<F>(reader: &mut dyn Read, callback: &mut F) -> io::Result<()>
159where
160 F: FnMut(FileItem<&mut dyn Read>) -> io::Result<()>,
161{
162 let container = ContainerKind::from_reader(reader)?;
163 match container {
164 ContainerKind::Stream(StreamKind::Raw(_)) => Err(io::Error::new(
166 io::ErrorKind::InvalidData,
167 "input is not an archive",
168 )),
169 ContainerKind::Stream(StreamKind::Compressed(mut c)) => {
171 iterate_archive_inner(&mut c as &mut dyn Read, callback)
172 }
173 ContainerKind::Archive(ArchiveKind::Tar(mut r)) => iterate_entries(&mut r, callback),
175 ContainerKind::Archive(ArchiveKind::Zip(mut r)) => iterate_entries(&mut r, callback),
176 }
177}
178
179fn iterate_entries<F>(archive: &mut impl Container, callback: &mut F) -> io::Result<()>
180where
181 F: FnMut(FileItem<&mut dyn Read>) -> io::Result<()>,
182{
183 let mut items = archive.items()?;
184 while let Some(item) = items.next_item() {
185 let mut item = item?;
186 callback(FileItem {
187 path: item.path,
188 reader: &mut item.reader as &mut dyn Read,
189 kind: item.kind,
190 size_hint: item.size_hint,
191 })?;
192 }
193 Ok(())
194}