bp3d_os/fs/mod.rs
1// Copyright (c) 2023, BlockProject 3D
2//
3// All rights reserved.
4//
5// Redistribution and use in source and binary forms, with or without modification,
6// are permitted provided that the following conditions are met:
7//
8// * Redistributions of source code must retain the above copyright notice,
9// this list of conditions and the following disclaimer.
10// * Redistributions in binary form must reproduce the above copyright notice,
11// this list of conditions and the following disclaimer in the documentation
12// and/or other materials provided with the distribution.
13// * Neither the name of BlockProject 3D nor the names of its contributors
14// may be used to endorse or promote products derived from this software
15// without specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
21// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29//! This module provides cross-platform functions to hide, unhide files, manage file extensions and
30//! get the most compatible absolute path of a file.
31
32#[cfg(unix)]
33mod unix;
34
35#[cfg(windows)]
36mod windows;
37
38#[cfg(unix)]
39use unix as _impl;
40
41#[cfg(windows)]
42use windows as _impl;
43
44/// The result of hide and show.
45pub enum PathUpdate<T: AsRef<std::path::Path>> {
46 /// Indicates that the source path was changed.
47 Changed(std::path::PathBuf),
48
49 /// Indicates that the source path was returned as-is with no changes.
50 Unchanged(T),
51}
52
53impl<T: AsRef<std::path::Path>> AsRef<std::path::Path> for PathUpdate<T> {
54 fn as_ref(&self) -> &std::path::Path {
55 match self {
56 PathUpdate::Changed(v) => v.as_ref(),
57 PathUpdate::Unchanged(v) => v.as_ref(),
58 }
59 }
60}
61
62impl<T: AsRef<std::path::Path>> std::ops::Deref for PathUpdate<T> {
63 type Target = std::path::Path;
64
65 fn deref(&self) -> &std::path::Path {
66 self.as_ref()
67 }
68}
69
70/// Converts a path to an absolute path.
71///
72/// NOTE: Unlike [canonicalize](std::fs::canonicalize) the paths returned by this function may not
73/// always be normalized.
74///
75/// # Platform specific behavior
76///
77/// - On Unix, this function redirects to [canonicalize](std::fs::canonicalize).
78///
79/// - On Windows, contrary to [canonicalize](std::fs::canonicalize) which always normalizes the
80/// input path to UNC, this function will try it's best to avoid using UNC paths which aren't
81/// supported by all applications, including some built-in applications. Currently, the function
82/// calls the *GetFullPathNameW* API.
83///
84/// # Arguments
85///
86/// * `path`: the path to convert.
87///
88/// returns: Result<PathBuf, Error>
89///
90/// # Errors
91///
92/// Returns an [Error](std::io::Error) if the path couldn't be converted to an absolute path.
93pub fn get_absolute_path<T: AsRef<std::path::Path>>(
94 path: T,
95) -> std::io::Result<std::path::PathBuf> {
96 _impl::get_absolute_path(path)
97}
98
99/// Checks if a given path is hidden.
100///
101/// # Platform specific behavior
102///
103/// - On Unix, this function returns true when the given path has a '.' prefix.
104///
105/// - On Windows, this function return true when GetFileAttributesW succeeds and that the file
106/// attributes contains the attribute *FILE_ATTRIBUTE_HIDDEN*.
107///
108/// # Arguments
109///
110/// * `path`: the path to check.
111///
112/// returns: bool
113pub fn is_hidden<T: AsRef<std::path::Path>>(path: T) -> bool {
114 _impl::is_hidden(path)
115}
116
117/// Hides the given path in the current platform's file explorer.
118///
119/// # Platform specific behavior
120///
121/// - On Unix, this function prefixes the path with a '.' and returns [Changed](PathUpdate::Changed)
122/// if it does not already have one. If the path already has the prefix, the function returns
123/// [Unchanged](PathUpdate::Unchanged).
124///
125/// - On Windows, this function calls *GetFileAttributesW* and *SetFileAttributesW* with the
126/// *FILE_ATTRIBUTE_HIDDEN* attribute. Because windows uses file attributes to define if a
127/// file should be visible, the function always returns [Unchanged](PathUpdate::Unchanged).
128///
129/// # Arguments
130///
131/// * `path`: the path to convert.
132///
133/// returns: Result<(), Error>
134///
135/// # Errors
136///
137/// Returns an [Error](std::io::Error) if the path couldn't be hidden.
138pub fn hide<T: AsRef<std::path::Path>>(path: T) -> std::io::Result<PathUpdate<T>> {
139 _impl::hide(path)
140}
141
142/// Shows the given path in the current platform's file explorer.
143///
144/// # Platform specific behavior
145///
146/// - On Unix, this function removes the '.' prefix from the given path and returns
147/// [Changed](PathUpdate::Changed) if it does have it. If the path does not already has the
148/// prefix, the function returns [Unchanged](PathUpdate::Unchanged).
149///
150/// - On Windows, this function calls *GetFileAttributesW* and *SetFileAttributesW* and removes the
151/// *FILE_ATTRIBUTE_HIDDEN* attribute. Because windows uses file attributes to define if a file
152/// should be visible, the function always returns [Unchanged](PathUpdate::Unchanged).
153///
154/// # Arguments
155///
156/// * `path`: the path to convert.
157///
158/// returns: Result<(), Error>
159///
160/// # Errors
161///
162/// Returns an [Error](std::io::Error) if the path couldn't be un-hidden.
163pub fn show<T: AsRef<std::path::Path>>(path: T) -> std::io::Result<PathUpdate<T>> {
164 _impl::show(path)
165}
166
167/// Copy options.
168#[derive(Default)]
169pub struct CopyOptions<'a> {
170 overwrite: bool,
171 excludes: Vec<&'a std::ffi::OsStr>,
172}
173
174impl<'a> CopyOptions<'a> {
175 /// Creates a new default filled instance of [CopyOptions].
176 pub fn new() -> Self {
177 Self::default()
178 }
179
180 /// Sets whether overwriting existing files is accepted.
181 /// The default is to not allow overwriting files.
182 ///
183 /// # Arguments
184 ///
185 /// * `overwrite`: true to allow overwriting files, false otherwise.
186 ///
187 /// returns: &mut CopyOptions
188 pub fn overwrite(&mut self, overwrite: bool) -> &mut Self {
189 self.overwrite = overwrite;
190 self
191 }
192
193 /// Adds a file name or folder name to be excluded from the copy operation.
194 ///
195 /// # Arguments
196 ///
197 /// * `name`: the file or folder name to exclude.
198 ///
199 /// returns: &mut CopyOptions
200 pub fn exclude(&mut self, name: &'a std::ffi::OsStr) -> &mut Self {
201 self.excludes.push(name);
202 self
203 }
204}
205
206/// Copy a file or a folder.
207///
208/// # Usage
209///
210/// | src | dst | result |
211/// | ---- | ---- | ---------------------------------------------- |
212/// | file | file | copy src into dst using [copy](std::fs::copy). |
213/// | file | dir | copy src into dst/file_name. |
214/// | dir | file | error. |
215/// | dir | dir | deep copy of the content of src into dst. |
216///
217/// # Arguments
218///
219/// * `src`:
220/// * `dst`:
221///
222/// returns: Result<(), Error>
223pub fn copy<'a>(
224 src: &std::path::Path,
225 dst: &std::path::Path,
226 options: impl std::borrow::Borrow<CopyOptions<'a>>,
227) -> std::io::Result<()> {
228 let options = options.borrow();
229 if src
230 .file_name()
231 .map(|v| options.excludes.contains(&v))
232 .unwrap_or(false)
233 {
234 // No error but file is to be excluded so don't copy.
235 return Ok(());
236 }
237 if src.is_file() {
238 if dst.is_dir() {
239 let name = src.file_name().ok_or_else(|| {
240 std::io::Error::new(std::io::ErrorKind::InvalidInput, "invalid source file")
241 })?;
242 return copy(src, &dst.join(name), options);
243 } else {
244 if dst.is_file() && !options.overwrite {
245 return Err(std::io::Error::new(
246 std::io::ErrorKind::PermissionDenied,
247 "overwriting files is not allowed",
248 ));
249 }
250 return std::fs::copy(src, dst).map(|_| ());
251 }
252 }
253 if dst.is_file() {
254 return Err(std::io::Error::new(
255 std::io::ErrorKind::NotADirectory,
256 "a directory is needed to copy a directory",
257 ));
258 }
259 if !dst.exists() {
260 std::fs::create_dir(dst)?;
261 }
262 for v in std::fs::read_dir(src)? {
263 let entry = v?;
264 copy(&entry.path(), &dst.join(entry.file_name()), options)?;
265 }
266 Ok(())
267}