refactor: improve code formatting in `mod.rs` and `logger.rs`
[ouch.git] / src / archive / tar.rs
blobfda05eb7d8a6f2a5552c737ab9ac2dfebab38333
1 //! Contains Tar-specific building and unpacking functions
3 use std::{
4     env,
5     io::prelude::*,
6     path::{Path, PathBuf},
7     sync::mpsc::{self, Receiver},
8     thread,
9 };
11 use fs_err as fs;
12 use same_file::Handle;
14 use crate::{
15     error::FinalError,
16     list::FileInArchive,
17     utils::{
18         self,
19         logger::{info, warning},
20         Bytes, EscapedPathDisplay, FileVisibilityPolicy,
21     },
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()? {
32         let mut file = file?;
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
39         // and so on
40         if !quiet {
41             info(format!(
42                 "{:?} extracted. ({})",
43                 utils::strip_cur_dir(&output_folder.join(file.path()?)),
44                 Bytes::new(file.size()),
45             ));
47             files_unpacked += 1;
48         }
49     }
51     Ok(files_unpacked)
54 /// List contents of `archive`, returning a vector of archive entries
55 pub fn list_archive(
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> {
63             self.0.recv().ok()
64         }
65     }
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 = (|| {
71                 let file = file?;
72                 let path = file.path()?.into_owned();
73                 let is_dir = file.header().entry_type().is_dir();
74                 Ok(FileInArchive { path, is_dir })
75             })();
76             tx.send(file_in_archive).unwrap();
77         }
78     });
80     Files(rx)
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],
86     output_path: &Path,
87     writer: W,
88     file_visibility_policy: FileVisibilityPolicy,
89     quiet: bool,
90 ) -> crate::Result<W>
91 where
92     W: Write,
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)?;
100         // Unwrap safety:
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) {
105             let entry = entry?;
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) {
111                     warning(format!(
112                         "Cannot compress `{}` into itself, skipping",
113                         output_path.display()
114                     ));
116                     continue;
117                 }
118             }
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
123             // and so on
124             if !quiet {
125                 info(format!("Compressing '{}'", EscapedPathDisplay::new(path)));
126             }
128             if path.is_dir() {
129                 builder.append_dir(path, path)?;
130             } else {
131                 let mut file = match fs::File::open(path) {
132                     Ok(f) => f,
133                     Err(e) => {
134                         if e.kind() == std::io::ErrorKind::NotFound && path.is_symlink() {
135                             // This path is for a broken symlink, ignore it
136                             continue;
137                         }
138                         return Err(e.into());
139                     }
140                 };
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}."))
145                 })?;
146             }
147         }
148         env::set_current_dir(previous_location)?;
149     }
151     Ok(builder.into_inner()?)