tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

fs.rs (6696B)


      1 use std::{
      2    ffi::OsStr,
      3    fmt::{self, Display},
      4    fs,
      5    ops::Deref,
      6    path::{Path, PathBuf, StripPrefixError},
      7 };
      8 
      9 use miette::{ensure, Context, IntoDiagnostic};
     10 
     11 #[derive(Debug)]
     12 pub(crate) struct FileRoot {
     13    nickname: &'static str,
     14    path: PathBuf,
     15 }
     16 
     17 impl Eq for FileRoot {}
     18 
     19 impl PartialEq for FileRoot {
     20    fn eq(&self, other: &Self) -> bool {
     21        self.path == other.path
     22    }
     23 }
     24 
     25 impl Ord for FileRoot {
     26    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
     27        self.path.cmp(&other.path)
     28    }
     29 }
     30 
     31 impl PartialOrd for FileRoot {
     32    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
     33        Some(self.cmp(other))
     34    }
     35 }
     36 
     37 impl FileRoot {
     38    pub(crate) fn new<P>(nickname: &'static str, path: P) -> miette::Result<Self>
     39    where
     40        P: AsRef<Path>,
     41    {
     42        let path = path.as_ref();
     43        Ok(Self {
     44            nickname,
     45            path: dunce::canonicalize(path)
     46                .map_err(miette::Report::msg)
     47                .wrap_err_with(|| format!("failed to canonicalize {path:?}"))?,
     48        })
     49    }
     50 
     51    pub(crate) fn nickname(&self) -> &str {
     52        self.nickname
     53    }
     54 
     55    pub(crate) fn try_child<P>(&self, path: P) -> Result<Child<'_>, StripPrefixError>
     56    where
     57        P: AsRef<Path>,
     58    {
     59        let path = path.as_ref();
     60        if path.is_absolute() {
     61            path.strip_prefix(&self.path)?;
     62        }
     63        Ok(Child {
     64            root: self,
     65            path: self.path.join(path),
     66        })
     67    }
     68 
     69    #[track_caller]
     70    pub(crate) fn child<P>(&self, path: P) -> Child<'_>
     71    where
     72        P: AsRef<Path>,
     73    {
     74        self.try_child(path)
     75            .into_diagnostic()
     76            .wrap_err("invariant violation: `path` is absolute and not a child of this file root")
     77            .unwrap()
     78    }
     79 
     80    fn removed_dir<P>(&self, path: P) -> miette::Result<Child<'_>>
     81    where
     82        P: AsRef<Path>,
     83    {
     84        let path = path.as_ref();
     85        let child = self.child(path);
     86        if child.exists() {
     87            log::info!("removing old contents of {child}…",);
     88            log::trace!("removing directory {:?}", &*child);
     89            fs::remove_dir_all(&*child)
     90                .map_err(miette::Report::msg)
     91                .wrap_err_with(|| format!("failed to remove old contents of {child}"))?;
     92        }
     93        Ok(child)
     94    }
     95 
     96    pub(crate) fn regen_dir<P>(
     97        &self,
     98        path: P,
     99        gen: impl FnOnce(&Child<'_>) -> miette::Result<()>,
    100    ) -> miette::Result<Child<'_>>
    101    where
    102        P: AsRef<Path>,
    103    {
    104        let child = self.removed_dir(path)?;
    105        gen(&child)?;
    106        ensure!(
    107            child.is_dir(),
    108            "{} was not regenerated for an unknown reason",
    109            child,
    110        );
    111        Ok(child)
    112    }
    113 }
    114 
    115 impl Deref for FileRoot {
    116    type Target = Path;
    117 
    118    fn deref(&self) -> &Self::Target {
    119        &self.path
    120    }
    121 }
    122 
    123 impl AsRef<Path> for FileRoot {
    124    fn as_ref(&self) -> &Path {
    125        &self.path
    126    }
    127 }
    128 
    129 impl AsRef<OsStr> for FileRoot {
    130    fn as_ref(&self) -> &OsStr {
    131        self.path.as_os_str()
    132    }
    133 }
    134 
    135 impl Display for FileRoot {
    136    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    137        let Self { nickname, path } = self;
    138        write!(f, "`{}` (AKA `<{nickname}>`)", path.display())
    139    }
    140 }
    141 
    142 #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
    143 pub(crate) struct Child<'a> {
    144    root: &'a FileRoot,
    145    /// NOTE: This is always an absolute path that is a child of the `root`.
    146    path: PathBuf,
    147 }
    148 
    149 impl Child<'_> {
    150    pub(crate) fn relative_path(&self) -> &Path {
    151        let Self { root, path } = self;
    152        path.strip_prefix(root).unwrap()
    153    }
    154 
    155    pub(crate) fn try_child<P>(&self, path: P) -> Result<Self, StripPrefixError>
    156    where
    157        P: AsRef<Path>,
    158    {
    159        let child_path = path.as_ref();
    160        let Self { root, path } = self;
    161 
    162        if child_path.is_absolute() {
    163            child_path.strip_prefix(path)?;
    164        }
    165        Ok(Child {
    166            root,
    167            path: path.join(child_path),
    168        })
    169    }
    170 
    171    #[track_caller]
    172    pub(crate) fn child<P>(&self, path: P) -> Self
    173    where
    174        P: AsRef<Path>,
    175    {
    176        self.try_child(path)
    177            .into_diagnostic()
    178            .wrap_err("invariant violation: `path` is absolute and not a child of this child")
    179            .unwrap()
    180    }
    181 }
    182 
    183 impl Deref for Child<'_> {
    184    type Target = Path;
    185 
    186    fn deref(&self) -> &Self::Target {
    187        &self.path
    188    }
    189 }
    190 
    191 impl AsRef<Path> for Child<'_> {
    192    fn as_ref(&self) -> &Path {
    193        &self.path
    194    }
    195 }
    196 
    197 impl AsRef<OsStr> for Child<'_> {
    198    fn as_ref(&self) -> &OsStr {
    199        self.path.as_os_str()
    200    }
    201 }
    202 
    203 impl Display for Child<'_> {
    204    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    205        write!(
    206            f,
    207            "`<{}>{}{}`",
    208            self.root.nickname(),
    209            std::path::MAIN_SEPARATOR,
    210            self.relative_path().display()
    211        )
    212    }
    213 }
    214 
    215 pub(crate) fn read_to_string<P>(path: P) -> miette::Result<String>
    216 where
    217    P: AsRef<Path>,
    218 {
    219    fs::read_to_string(&path)
    220        .into_diagnostic()
    221        .wrap_err_with(|| {
    222            format!(
    223                "failed to read UTF-8 string from path {}",
    224                path.as_ref().display()
    225            )
    226        })
    227 }
    228 
    229 pub(crate) fn rename<P1, P2>(from: P1, to: P2) -> miette::Result<()>
    230 where
    231    P1: AsRef<Path>,
    232    P2: AsRef<Path>,
    233 {
    234    fs::rename(&from, &to).into_diagnostic().wrap_err_with(|| {
    235        format!(
    236            "failed to rename {} to {}",
    237            from.as_ref().display(),
    238            to.as_ref().display()
    239        )
    240    })
    241 }
    242 
    243 pub(crate) fn try_exists<P>(path: P) -> miette::Result<bool>
    244 where
    245    P: AsRef<Path>,
    246 {
    247    let path = path.as_ref();
    248    path.try_exists()
    249        .into_diagnostic()
    250        .wrap_err_with(|| format!("failed to check if path exists: {}", path.display()))
    251 }
    252 
    253 pub(crate) fn create_dir_all<P>(path: P) -> miette::Result<()>
    254 where
    255    P: AsRef<Path>,
    256 {
    257    fs::create_dir_all(&path)
    258        .into_diagnostic()
    259        .wrap_err_with(|| {
    260            format!(
    261                "failed to create directories leading up to {}",
    262                path.as_ref().display()
    263            )
    264        })
    265 }
    266 
    267 pub(crate) fn remove_file<P>(path: P) -> miette::Result<()>
    268 where
    269    P: AsRef<Path>,
    270 {
    271    fs::remove_file(&path)
    272        .into_diagnostic()
    273        .wrap_err_with(|| format!("failed to remove file at path {}", path.as_ref().display()))
    274 }
    275 
    276 pub(crate) fn write<P, C>(path: P, contents: C) -> miette::Result<()>
    277 where
    278    P: AsRef<Path>,
    279    C: AsRef<[u8]>,
    280 {
    281    fs::write(&path, &contents)
    282        .into_diagnostic()
    283        .wrap_err_with(|| format!("failed to write to path {}", path.as_ref().display()))
    284 }