1use crate::archive::{ArchivePacker, ArchiveUnpacker};
2use crate::archive_error::ArchiveError;
3pub use crate::tar_error::TarError;
4use crate::tree_differ::TreeDiffer;
5use binstall_tar::{Archive as TarArchive, Builder as TarBuilder};
6use starbase_utils::fs;
7use std::io::{Write, prelude::*};
8use std::path::{Path, PathBuf};
9use tracing::{instrument, trace};
10
11pub struct TarPacker {
13 archive: TarBuilder<Box<dyn Write>>,
14}
15
16impl TarPacker {
17 pub fn create(writer: Box<dyn Write>) -> Result<Self, ArchiveError> {
19 Ok(TarPacker {
20 archive: TarBuilder::new(writer),
21 })
22 }
23
24 pub fn new(output_file: &Path) -> Result<Self, ArchiveError> {
26 TarPacker::create(Box::new(fs::create_file(output_file)?))
27 }
28
29 #[cfg(feature = "tar-gz")]
31 pub fn new_gz(output_file: &Path) -> Result<Self, ArchiveError> {
32 Self::new_gz_with_level(output_file, 4)
33 }
34
35 #[cfg(feature = "tar-gz")]
37 pub fn new_gz_with_level(output_file: &Path, level: u32) -> Result<Self, ArchiveError> {
38 TarPacker::create(Box::new(flate2::write::GzEncoder::new(
39 fs::create_file(output_file)?,
40 flate2::Compression::new(level),
41 )))
42 }
43
44 #[cfg(feature = "tar-xz")]
46 pub fn new_xz(output_file: &Path) -> Result<Self, ArchiveError> {
47 Self::new_xz_with_level(output_file, 4)
48 }
49
50 #[cfg(feature = "tar-xz")]
52 pub fn new_xz_with_level(output_file: &Path, level: u32) -> Result<Self, ArchiveError> {
53 TarPacker::create(Box::new(liblzma::write::XzEncoder::new(
54 fs::create_file(output_file)?,
55 level,
56 )))
57 }
58
59 #[cfg(feature = "tar-zstd")]
61 pub fn new_zstd(output_file: &Path) -> Result<Self, ArchiveError> {
62 Self::new_zstd_with_level(output_file, 3) }
64
65 #[cfg(feature = "tar-zstd")]
67 pub fn new_zstd_with_level(output_file: &Path, level: u32) -> Result<Self, ArchiveError> {
68 let encoder = zstd::stream::Encoder::new(fs::create_file(output_file)?, level as i32)
69 .map_err(|error| TarError::ZstdDictionary {
70 error: Box::new(error),
71 })?;
72
73 TarPacker::create(Box::new(encoder.auto_finish()))
74 }
75
76 #[cfg(feature = "tar-bz2")]
78 pub fn new_bz2(output_file: &Path) -> Result<Self, ArchiveError> {
79 Self::new_bz2_with_level(output_file, 6) }
81
82 #[cfg(feature = "tar-bz2")]
84 pub fn new_bz2_with_level(output_file: &Path, level: u32) -> Result<Self, ArchiveError> {
85 TarPacker::create(Box::new(bzip2::write::BzEncoder::new(
86 fs::create_file(output_file)?,
87 bzip2::Compression::new(level),
88 )))
89 }
90}
91
92impl ArchivePacker for TarPacker {
93 fn add_file(&mut self, name: &str, file: &Path) -> Result<(), ArchiveError> {
94 trace!(source = name, input = ?file, "Packing file");
95
96 self.archive
97 .append_file(name, &mut fs::open_file(file)?)
98 .map_err(|error| TarError::AddFailure {
99 source: file.to_path_buf(),
100 error: Box::new(error),
101 })?;
102
103 Ok(())
104 }
105
106 fn add_dir(&mut self, name: &str, dir: &Path) -> Result<(), ArchiveError> {
107 trace!(source = name, input = ?dir, "Packing directory");
108
109 self.archive
110 .append_dir_all(name, dir)
111 .map_err(|error| TarError::AddFailure {
112 source: dir.to_path_buf(),
113 error: Box::new(error),
114 })?;
115
116 Ok(())
117 }
118
119 #[instrument(name = "pack_tar", skip_all)]
120 fn pack(&mut self) -> Result<(), ArchiveError> {
121 trace!("Creating tarball");
122
123 self.archive
124 .finish()
125 .map_err(|error| TarError::PackFailure {
126 error: Box::new(error),
127 })?;
128
129 Ok(())
130 }
131}
132
133pub struct TarUnpacker {
135 archive: TarArchive<Box<dyn Read>>,
136 output_dir: PathBuf,
137}
138
139impl TarUnpacker {
140 pub fn create(output_dir: &Path, reader: Box<dyn Read>) -> Result<Self, ArchiveError> {
142 fs::create_dir_all(output_dir)?;
143
144 Ok(TarUnpacker {
145 archive: TarArchive::new(reader),
146 output_dir: output_dir.to_path_buf(),
147 })
148 }
149
150 pub fn new(output_dir: &Path, input_file: &Path) -> Result<Self, ArchiveError> {
152 TarUnpacker::create(output_dir, Box::new(fs::open_file(input_file)?))
153 }
154
155 #[cfg(feature = "tar-gz")]
157 pub fn new_gz(output_dir: &Path, input_file: &Path) -> Result<Self, ArchiveError> {
158 TarUnpacker::create(
159 output_dir,
160 Box::new(flate2::read::GzDecoder::new(fs::open_file(input_file)?)),
161 )
162 }
163
164 #[cfg(feature = "tar-xz")]
166 pub fn new_xz(output_dir: &Path, input_file: &Path) -> Result<Self, ArchiveError> {
167 TarUnpacker::create(
168 output_dir,
169 Box::new(liblzma::read::XzDecoder::new(fs::open_file(input_file)?)),
170 )
171 }
172
173 #[cfg(feature = "tar-zstd")]
175 pub fn new_zstd(output_dir: &Path, input_file: &Path) -> Result<Self, ArchiveError> {
176 let decoder = zstd::stream::Decoder::new(fs::open_file(input_file)?).map_err(|error| {
177 TarError::ZstdDictionary {
178 error: Box::new(error),
179 }
180 })?;
181
182 TarUnpacker::create(output_dir, Box::new(decoder))
183 }
184
185 #[cfg(feature = "tar-bz2")]
187 pub fn new_bz2(output_dir: &Path, input_file: &Path) -> Result<Self, ArchiveError> {
188 TarUnpacker::create(
189 output_dir,
190 Box::new(bzip2::read::BzDecoder::new(fs::open_file(input_file)?)),
191 )
192 }
193}
194
195impl ArchiveUnpacker for TarUnpacker {
196 #[instrument(name = "unpack_tar", skip_all)]
197 fn unpack(&mut self, prefix: &str, differ: &mut TreeDiffer) -> Result<PathBuf, ArchiveError> {
198 self.archive.set_overwrite(true);
199
200 trace!(output_dir = ?self.output_dir, "Opening tarball");
201
202 let mut count = 0;
203
204 for entry in self
205 .archive
206 .entries()
207 .map_err(|error| TarError::UnpackFailure {
208 error: Box::new(error),
209 })?
210 {
211 let mut entry = entry.map_err(|error| TarError::UnpackFailure {
212 error: Box::new(error),
213 })?;
214 let mut path: PathBuf = entry.path().unwrap().into_owned();
215
216 if !prefix.is_empty()
218 && let Ok(suffix) = path.strip_prefix(prefix)
219 {
220 path = suffix.to_owned();
221 }
222
223 let output_path = self.output_dir.join(&path);
225
226 if let Some(parent_dir) = output_path.parent() {
227 fs::create_dir_all(parent_dir)?;
228 }
229
230 entry
235 .unpack(&output_path)
236 .map_err(|error| TarError::ExtractFailure {
237 source: output_path.clone(),
238 error: Box::new(error),
239 })?;
240 differ.untrack_file(&output_path);
243 count += 1;
244 }
245
246 trace!("Unpacked {} files", count);
247
248 Ok(self.output_dir.clone())
249 }
250}