Skip to main content

simple_unc/
simple_unc.rs

1#![cfg_attr(not(target_os = "windows"), allow(unused))]
2#[cfg(windows)]
3use crate::{Drives, PathExt};
4use std::{
5    borrow::Cow,
6    fs, io,
7    path::{Path, PathBuf},
8};
9
10#[derive(Default)]
11pub struct SimpleUnc {
12    /// Map to the network share drive when possible.
13    /// ```
14    /// # use simple_unc::SimpleUnc;
15    /// let path = "file.txt";
16    /// let unc = SimpleUnc { map_to_drive: true, ..Default::default() };
17    /// let canonicalized = unc.canonicalize(path);
18    /// ```
19    /// If the `file.txt` is in a network drive,
20    /// the result should be `Z:\dir\file.txt`
21    /// instead of `\\server\share\dir\file.txt`.
22    pub map_to_drive: bool,
23
24    /// Skip the [`dunce`] crate simplification.
25    ///
26    /// [`dunce`]: https://crates.io/crates/dunce
27    pub skip_dunce: bool,
28}
29
30impl SimpleUnc {
31    pub fn canonicalize(&self, path: impl AsRef<Path>) -> io::Result<PathBuf> {
32        let canonicalized = fs::canonicalize(path)?;
33        #[cfg(windows)]
34        if let Some(simplified) = self.simplify(&canonicalized)? {
35            return Ok(simplified.into_owned());
36        }
37        Ok(canonicalized)
38    }
39
40    pub fn simplify<'a>(&self, path: &'a Path) -> io::Result<Option<Cow<'a, Path>>> {
41        #[cfg(windows)]
42        return self._simplify(path).map_err(io_error_from_anyhow);
43        #[cfg(not(windows))]
44        Ok(None)
45    }
46
47    #[cfg(windows)]
48    fn _simplify<'a>(&self, path: &'a Path) -> anyhow::Result<Option<Cow<'a, Path>>> {
49        // Try mapped network share drives.
50        if let Some(drive_path) = Drives::drive_path(path)? {
51            if self.map_to_drive {
52                return Ok(Some(Cow::Owned(drive_path.to_path_buf())));
53            }
54            if let Some(unc) = path.unc_from_win32_file_namespace() {
55                return Ok(Some(Cow::Owned(unc)));
56            }
57        }
58
59        if !self.skip_dunce {
60            // Try `dunce::simplified`.
61            let simplified = dunce::simplified(path);
62            if !std::ptr::eq(path, simplified) {
63                return Ok(Some(Cow::Borrowed(simplified)));
64            }
65        }
66        Ok(None)
67    }
68
69    /// Refreshes the cached information.
70    pub fn refresh() -> io::Result<()> {
71        #[cfg(windows)]
72        Drives::refresh().map_err(io_error_from_anyhow)?;
73        Ok(())
74    }
75}
76
77fn io_error_from_anyhow(error: anyhow::Error) -> io::Error {
78    match error.downcast::<io::Error>() {
79        Ok(io_error) => io_error,
80        Err(other_error) => io::Error::other(other_error),
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn simplify_none() {
90        let unc = SimpleUnc::default();
91        assert_eq!(unc.simplify(Path::new(r"C:\foo")).unwrap(), None);
92    }
93
94    #[cfg(windows)]
95    #[test]
96    fn simplify_dunce() {
97        let unc = SimpleUnc::default();
98        assert_eq!(
99            unc.simplify(Path::new(r"\\?\C:\foo")).unwrap(),
100            Some(Cow::Borrowed(Path::new(r"C:\foo")))
101        );
102    }
103
104    #[cfg(windows)]
105    #[test]
106    fn simplify_dunce_skip() {
107        let unc = SimpleUnc {
108            skip_dunce: true,
109            ..Default::default()
110        };
111        assert_eq!(unc.simplify(Path::new(r"\\?\C:\foo")).unwrap(), None);
112    }
113}