refactor: improve code formatting in `mod.rs` and `logger.rs`
[ouch.git] / src / utils / fs.rs
blobd33411ad90bb56aeb96912a5453e7c88e3c2f9b1
1 //! Filesystem utility functions.
3 use std::{
4     env,
5     io::Read,
6     path::{Path, PathBuf},
7 };
9 use fs_err as fs;
11 use super::user_wants_to_overwrite;
12 use crate::{
13     extension::Extension,
14     utils::{logger::info_accessible, EscapedPathDisplay},
15     QuestionPolicy,
18 pub fn is_path_stdin(path: &Path) -> bool {
19     path.as_os_str() == "-"
22 /// Remove `path` asking the user to overwrite if necessary.
23 ///
24 /// * `Ok(true)` means the path is clear,
25 /// * `Ok(false)` means the user doesn't want to overwrite
26 /// * `Err(_)` is an error
27 pub fn clear_path(path: &Path, question_policy: QuestionPolicy) -> crate::Result<bool> {
28     if path.exists() && !user_wants_to_overwrite(path, question_policy)? {
29         return Ok(false);
30     }
32     remove_file_or_dir(path)?;
34     Ok(true)
37 pub fn remove_file_or_dir(path: &Path) -> crate::Result<()> {
38     if path.is_dir() {
39         fs::remove_dir_all(path)?;
40     } else if path.is_file() {
41         fs::remove_file(path)?;
42     }
43     Ok(())
46 /// Creates a directory at the path, if there is nothing there.
47 pub fn create_dir_if_non_existent(path: &Path) -> crate::Result<()> {
48     if !path.exists() {
49         fs::create_dir_all(path)?;
50         // creating a directory is an important change to the file system we
51         // should always inform the user about
52         info_accessible(format!("Directory {} created", EscapedPathDisplay::new(path)));
53     }
54     Ok(())
57 /// Returns current directory, but before change the process' directory to the
58 /// one that contains the file pointed to by `filename`.
59 pub fn cd_into_same_dir_as(filename: &Path) -> crate::Result<PathBuf> {
60     let previous_location = env::current_dir()?;
62     let parent = filename.parent().ok_or(crate::Error::CompressingRootFolder)?;
63     env::set_current_dir(parent)?;
65     Ok(previous_location)
68 /// Try to detect the file extension by looking for known magic strings
69 /// Source: <https://en.wikipedia.org/wiki/List_of_file_signatures>
70 pub fn try_infer_extension(path: &Path) -> Option<Extension> {
71     fn is_zip(buf: &[u8]) -> bool {
72         buf.len() >= 3
73             && buf[..=1] == [0x50, 0x4B]
74             && (buf[2..=3] == [0x3, 0x4] || buf[2..=3] == [0x5, 0x6] || buf[2..=3] == [0x7, 0x8])
75     }
76     fn is_tar(buf: &[u8]) -> bool {
77         buf.len() > 261 && buf[257..=261] == [0x75, 0x73, 0x74, 0x61, 0x72]
78     }
79     fn is_gz(buf: &[u8]) -> bool {
80         buf.starts_with(&[0x1F, 0x8B, 0x8])
81     }
82     fn is_bz2(buf: &[u8]) -> bool {
83         buf.starts_with(&[0x42, 0x5A, 0x68])
84     }
85     fn is_bz3(buf: &[u8]) -> bool {
86         buf.starts_with(bzip3::MAGIC_NUMBER)
87     }
88     fn is_xz(buf: &[u8]) -> bool {
89         buf.starts_with(&[0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00])
90     }
91     fn is_lz4(buf: &[u8]) -> bool {
92         buf.starts_with(&[0x04, 0x22, 0x4D, 0x18])
93     }
94     fn is_sz(buf: &[u8]) -> bool {
95         buf.starts_with(&[0xFF, 0x06, 0x00, 0x00, 0x73, 0x4E, 0x61, 0x50, 0x70, 0x59])
96     }
97     fn is_zst(buf: &[u8]) -> bool {
98         buf.starts_with(&[0x28, 0xB5, 0x2F, 0xFD])
99     }
100     fn is_rar(buf: &[u8]) -> bool {
101         // ref https://www.rarlab.com/technote.htm#rarsign
102         // RAR 5.0 8 bytes length signature: 0x52 0x61 0x72 0x21 0x1A 0x07 0x01 0x00
103         // RAR 4.x 7 bytes length signature: 0x52 0x61 0x72 0x21 0x1A 0x07 0x00
104         buf.len() >= 7
105             && buf.starts_with(&[0x52, 0x61, 0x72, 0x21, 0x1A, 0x07])
106             && (buf[6] == 0x00 || (buf.len() >= 8 && buf[6..=7] == [0x01, 0x00]))
107     }
108     fn is_sevenz(buf: &[u8]) -> bool {
109         buf.starts_with(&[0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C])
110     }
112     let buf = {
113         let mut buf = [0; 270];
115         // Error cause will be ignored, so use std::fs instead of fs_err
116         let result = std::fs::File::open(path).map(|mut file| file.read(&mut buf));
118         // In case of file open or read failure, could not infer a extension
119         if result.is_err() {
120             return None;
121         }
122         buf
123     };
125     use crate::extension::CompressionFormat::*;
126     if is_zip(&buf) {
127         Some(Extension::new(&[Zip], "zip"))
128     } else if is_tar(&buf) {
129         Some(Extension::new(&[Tar], "tar"))
130     } else if is_gz(&buf) {
131         Some(Extension::new(&[Gzip], "gz"))
132     } else if is_bz2(&buf) {
133         Some(Extension::new(&[Bzip], "bz2"))
134     } else if is_bz3(&buf) {
135         Some(Extension::new(&[Bzip3], "bz3"))
136     } else if is_xz(&buf) {
137         Some(Extension::new(&[Lzma], "xz"))
138     } else if is_lz4(&buf) {
139         Some(Extension::new(&[Lz4], "lz4"))
140     } else if is_sz(&buf) {
141         Some(Extension::new(&[Snappy], "sz"))
142     } else if is_zst(&buf) {
143         Some(Extension::new(&[Zstd], "zst"))
144     } else if is_rar(&buf) {
145         Some(Extension::new(&[Rar], "rar"))
146     } else if is_sevenz(&buf) {
147         Some(Extension::new(&[SevenZip], "7z"))
148     } else {
149         None
150     }