1use crate::{
4 CompressionMethod,
5 compression::Compressor,
6 utils::{Counter, Crc32Writer},
7};
8use std::io;
9
10mod raw;
11
12#[derive(Debug, Default, Clone)]
14#[non_exhaustive]
15pub struct FileOptions {
16 pub compression_method: CompressionMethod,
18 pub level: Option<i32>,
20}
21
22#[derive(Default)]
54pub struct ArchiveWriter<W: io::Write> {
55 writer: W,
56 raw: raw::RawArchiveWriter,
57}
58
59impl ArchiveWriter<std::fs::File> {
60 pub fn create(path: impl AsRef<std::path::Path>) -> io::Result<Self> {
65 std::fs::File::create(path).map(Self::new)
66 }
67
68 pub fn create_new(path: impl AsRef<std::path::Path>) -> io::Result<Self> {
71 std::fs::File::create_new(path).map(Self::new)
72 }
73}
74
75impl<W: io::Write> ArchiveWriter<W> {
76 #[inline]
78 pub fn new(writer: W) -> Self {
79 ArchiveWriter {
80 writer,
81 raw: raw::RawArchiveWriter::default(),
82 }
83 }
84
85 pub fn add_file<R: io::Read>(
89 &mut self,
90 name: &str,
91 mut content: R,
92 options: &FileOptions,
93 ) -> io::Result<()> {
94 let mut w = Crc32Writer::new(Compressor::new(
95 Vec::new(),
96 options.compression_method,
97 options.level,
98 )?);
99 let uncompressed_size = io::copy(&mut content, &mut w)?;
100 let crc32 = w.result();
101 let compressed = w.into_inner().finish()?;
102
103 self.raw.write_file_raw(
104 &mut self.writer,
105 name,
106 &compressed,
107 raw::Metadata {
108 compression_method: options.compression_method,
109 uncompressed_size,
110 crc32,
111 typ: crate::FileType::File,
112 },
113 )?;
114
115 Ok(())
116 }
117
118 pub fn stream_file(
125 &mut self,
126 name: &str,
127 options: &FileOptions,
128 ) -> io::Result<FileStreamer<'_, W>> {
129 let writer = self.raw.start_stream_raw(&mut self.writer, name, options)?;
130
131 Ok(FileStreamer {
132 writer: Counter::new(Crc32Writer::new(Compressor::new(
133 writer,
134 options.compression_method,
135 options.level,
136 )?)),
137 })
138 }
139
140 pub fn add_directory(&mut self, name: &str) -> io::Result<()> {
142 if !name.ends_with('/') {
143 return Err(io::Error::new(
144 io::ErrorKind::InvalidInput,
145 "directory name must end with '/'",
146 ));
147 }
148
149 self.raw.write_file_raw(
150 &mut self.writer,
151 name,
152 &[],
153 raw::Metadata {
154 compression_method: CompressionMethod::STORE,
155 uncompressed_size: 0,
156 crc32: 0,
157 typ: crate::FileType::Directory,
158 },
159 )
160 }
161
162 pub fn add_symlink(&mut self, name: &str, target: &str) -> io::Result<()> {
164 self.raw.write_file_raw(
165 &mut self.writer,
166 name,
167 target.as_bytes(),
168 raw::Metadata {
169 compression_method: CompressionMethod::STORE,
170 uncompressed_size: target.len() as _,
171 crc32: crc32fast::hash(target.as_bytes()),
172 typ: crate::FileType::Symlink,
173 },
174 )
175 }
176
177 #[inline]
185 pub fn recover(&mut self) -> io::Result<()>
186 where
187 W: io::Seek,
188 {
189 self.raw.recover(&mut self.writer)
190 }
191
192 #[inline]
194 pub fn get_ref(&self) -> &W {
195 &self.writer
196 }
197
198 #[inline]
202 pub fn get_mut(&mut self) -> &mut W {
203 &mut self.writer
204 }
205
206 #[inline]
208 pub fn flush(&mut self) -> io::Result<()> {
209 self.writer.flush()
210 }
211
212 #[inline]
217 pub fn finish(mut self) -> io::Result<W> {
218 self.raw.finish(&mut self.writer)?;
219 Ok(self.writer)
220 }
221}
222
223pub struct FileStreamer<'a, W: io::Write> {
229 writer: Counter<Crc32Writer<Compressor<raw::RawFileStreamer<'a, &'a mut W>>>>,
230}
231
232impl<W: io::Write> io::Write for FileStreamer<'_, W> {
233 #[inline]
234 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
235 self.writer.write(buf)
236 }
237
238 #[inline]
239 fn flush(&mut self) -> io::Result<()> {
240 self.writer.flush()
241 }
242}
243
244impl<W: io::Write> FileStreamer<'_, W> {
245 pub fn finish(self) -> io::Result<()> {
247 let uncompressed_size = self.writer.amt;
248 let crc32 = self.writer.inner.result();
249
250 let raw_writer = self.writer.inner.into_inner().finish()?;
251
252 raw_writer.finish(uncompressed_size, crc32)
253 }
254}