Merge pull request #261 from ouch-org/refac/optimize-current-dir-call
[ouch.git] / src / archive / tar.rs
blobacf0908c4c9b9c1f5a68a97a59f2c7b7726ab68b
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 tar;
14 use crate::{
15     error::FinalError,
16     info,
17     list::FileInArchive,
18     utils::{self, Bytes, FileVisibilityPolicy},
21 /// Unpacks the archive given by `archive` into the folder given by `into`.
22 /// Assumes that output_folder is empty
23 pub fn unpack_archive(
24     reader: Box<dyn Read>,
25     output_folder: &Path,
26     mut display_handle: impl Write,
27 ) -> crate::Result<Vec<PathBuf>> {
28     assert!(output_folder.read_dir().expect("dir exists").count() == 0);
29     let mut archive = tar::Archive::new(reader);
31     let mut files_unpacked = vec![];
32     for file in archive.entries()? {
33         let mut file = file?;
35         let file_path = output_folder.join(file.path()?);
36         file.unpack_in(output_folder)?;
38         // This is printed for every file in the archive and has little
39         // importance for most users, but would generate lots of
40         // spoken text for users using screen readers, braille displays
41         // and so on
43         info!(@display_handle, inaccessible, "{:?} extracted. ({})", utils::strip_cur_dir(&output_folder.join(file.path()?)), Bytes::new(file.size()));
45         files_unpacked.push(file_path);
46     }
48     Ok(files_unpacked)
51 /// List contents of `archive`, returning a vector of archive entries
52 pub fn list_archive(
53     mut archive: tar::Archive<impl Read + Send + 'static>,
54 ) -> impl Iterator<Item = crate::Result<FileInArchive>> {
55     struct Files(Receiver<crate::Result<FileInArchive>>);
56     impl Iterator for Files {
57         type Item = crate::Result<FileInArchive>;
59         fn next(&mut self) -> Option<Self::Item> {
60             self.0.recv().ok()
61         }
62     }
64     let (tx, rx) = mpsc::channel();
65     thread::spawn(move || {
66         for file in archive.entries().expect("entries is only used once") {
67             let file_in_archive = (|| {
68                 let file = file?;
69                 let path = file.path()?.into_owned();
70                 let is_dir = file.header().entry_type().is_dir();
71                 Ok(FileInArchive { path, is_dir })
72             })();
73             tx.send(file_in_archive).unwrap();
74         }
75     });
77     Files(rx)
80 /// Compresses the archives given by `input_filenames` into the file given previously to `writer`.
81 pub fn build_archive_from_paths<W, D>(
82     input_filenames: &[PathBuf],
83     writer: W,
84     file_visibility_policy: FileVisibilityPolicy,
85     mut display_handle: D,
86 ) -> crate::Result<W>
87 where
88     W: Write,
89     D: Write,
91     let mut builder = tar::Builder::new(writer);
93     for filename in input_filenames {
94         let previous_location = utils::cd_into_same_dir_as(filename)?;
96         // Safe unwrap, input shall be treated before
97         let filename = filename.file_name().unwrap();
99         for entry in file_visibility_policy.build_walker(filename) {
100             let entry = entry?;
101             let path = entry.path();
103             // This is printed for every file in `input_filenames` and has
104             // little importance for most users, but would generate lots of
105             // spoken text for users using screen readers, braille displays
106             // and so on
107             info!(@display_handle, inaccessible, "Compressing '{}'.", utils::to_utf(path));
109             if path.is_dir() {
110                 builder.append_dir(path, path)?;
111             } else {
112                 let mut file = match fs::File::open(path) {
113                     Ok(f) => f,
114                     Err(e) => {
115                         if e.kind() == std::io::ErrorKind::NotFound && utils::is_symlink(path) {
116                             // This path is for a broken symlink
117                             // We just ignore it
118                             continue;
119                         }
120                         return Err(e.into());
121                     }
122                 };
123                 builder.append_file(path, file.file_mut()).map_err(|err| {
124                     FinalError::with_title("Could not create archive")
125                         .detail("Unexpected error while trying to read file")
126                         .detail(format!("Error: {}.", err))
127                 })?;
128             }
129         }
130         env::set_current_dir(previous_location)?;
131     }
133     Ok(builder.into_inner()?)