1 //! Contains Tar-specific building and unpacking functions
7 sync::mpsc::{self, Receiver},
12 use same_file::Handle;
19 logger::{info, warning},
20 Bytes, EscapedPathDisplay, FileVisibilityPolicy,
24 /// Unpacks the archive given by `archive` into the folder given by `into`.
25 /// Assumes that output_folder is empty
26 pub fn unpack_archive(reader: Box<dyn Read>, output_folder: &Path, quiet: bool) -> crate::Result<usize> {
27 assert!(output_folder.read_dir().expect("dir exists").count() == 0);
28 let mut archive = tar::Archive::new(reader);
30 let mut files_unpacked = 0;
31 for file in archive.entries()? {
34 file.unpack_in(output_folder)?;
36 // This is printed for every file in the archive and has little
37 // importance for most users, but would generate lots of
38 // spoken text for users using screen readers, braille displays
42 "{:?} extracted. ({})",
43 utils::strip_cur_dir(&output_folder.join(file.path()?)),
44 Bytes::new(file.size()),
54 /// List contents of `archive`, returning a vector of archive entries
56 mut archive: tar::Archive<impl Read + Send + 'static>,
57 ) -> impl Iterator<Item = crate::Result<FileInArchive>> {
58 struct Files(Receiver<crate::Result<FileInArchive>>);
59 impl Iterator for Files {
60 type Item = crate::Result<FileInArchive>;
62 fn next(&mut self) -> Option<Self::Item> {
67 let (tx, rx) = mpsc::channel();
68 thread::spawn(move || {
69 for file in archive.entries().expect("entries is only used once") {
70 let file_in_archive = (|| {
72 let path = file.path()?.into_owned();
73 let is_dir = file.header().entry_type().is_dir();
74 Ok(FileInArchive { path, is_dir })
76 tx.send(file_in_archive).unwrap();
83 /// Compresses the archives given by `input_filenames` into the file given previously to `writer`.
84 pub fn build_archive_from_paths<W>(
85 input_filenames: &[PathBuf],
88 file_visibility_policy: FileVisibilityPolicy,
94 let mut builder = tar::Builder::new(writer);
95 let output_handle = Handle::from_path(output_path);
97 for filename in input_filenames {
98 let previous_location = utils::cd_into_same_dir_as(filename)?;
101 // paths should be canonicalized by now, and the root directory rejected.
102 let filename = filename.file_name().unwrap();
104 for entry in file_visibility_policy.build_walker(filename) {
106 let path = entry.path();
108 // If the output_path is the same as the input file, warn the user and skip the input (in order to avoid compression recursion)
109 if let Ok(handle) = &output_handle {
110 if matches!(Handle::from_path(path), Ok(x) if &x == handle) {
112 "Cannot compress `{}` into itself, skipping",
113 output_path.display()
120 // This is printed for every file in `input_filenames` and has
121 // little importance for most users, but would generate lots of
122 // spoken text for users using screen readers, braille displays
125 info(format!("Compressing '{}'", EscapedPathDisplay::new(path)));
129 builder.append_dir(path, path)?;
131 let mut file = match fs::File::open(path) {
134 if e.kind() == std::io::ErrorKind::NotFound && path.is_symlink() {
135 // This path is for a broken symlink, ignore it
138 return Err(e.into());
141 builder.append_file(path, file.file_mut()).map_err(|err| {
142 FinalError::with_title("Could not create archive")
143 .detail("Unexpected error while trying to read file")
144 .detail(format!("Error: {err}."))
148 env::set_current_dir(previous_location)?;
151 Ok(builder.into_inner()?)