Skip to main content

path_extra/tokio/
fs.rs

1#[cfg(unix)]
2use std::os::unix::{self, fs::PermissionsExt as _};
3use std::{fs::Permissions, io, path::Path};
4
5use tokio::fs::{File, OpenOptions};
6#[cfg(unix)]
7use tokio::task;
8
9#[trait_variant::make(Send)]
10pub trait FileExt {
11    async fn create_new_if_not_exists(path: impl AsRef<Path> + Send) -> io::Result<Option<Self>>
12    where
13        Self: Sized;
14
15    async fn create_if_not_exists(path: impl AsRef<Path> + Send) -> io::Result<Option<Self>>
16    where
17        Self: Sized;
18
19    async fn open_if_exists(path: impl AsRef<Path> + Send) -> io::Result<Option<Self>>
20    where
21        Self: Sized;
22
23    async fn with_permissions(&self, perm: Permissions) -> io::Result<&Self>;
24
25    async fn with_permissions_readonly(&self, readonly: bool) -> io::Result<&Self>;
26
27    #[cfg(unix)]
28    async fn with_permissions_mode(&self, mode: u32) -> io::Result<&Self>;
29    #[cfg(unix)]
30    async fn add_permissions_mode(&self, mode: u32) -> io::Result<&Self>;
31    #[cfg(unix)]
32    async fn remove_permissions_mode(&self, mode: u32) -> io::Result<&Self>;
33
34    #[cfg(unix)]
35    async fn chown(&self, uid: Option<u32>, gid: Option<u32>) -> io::Result<&Self>;
36}
37
38impl FileExt for File {
39    #[inline]
40    async fn create_new_if_not_exists(path: impl AsRef<Path> + Send) -> io::Result<Option<Self>> {
41        match Self::create_new(path).await {
42            Ok(file) => Ok(Some(file)),
43            Err(err) if err.kind() == io::ErrorKind::AlreadyExists => Ok(None),
44            Err(err) => Err(err),
45        }
46    }
47
48    #[inline]
49    async fn create_if_not_exists(path: impl AsRef<Path> + Send) -> io::Result<Option<Self>> {
50        let mut options = Self::options();
51
52        options.write(true).create_new(true);
53
54        options.open_if_not_exists(path).await
55    }
56
57    #[inline]
58    async fn open_if_exists(path: impl AsRef<Path> + Send) -> io::Result<Option<Self>> {
59        match Self::open(path).await {
60            Ok(file) => Ok(Some(file)),
61            Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
62            Err(err) => Err(err),
63        }
64    }
65
66    #[inline]
67    async fn with_permissions(&self, perm: Permissions) -> io::Result<&Self> {
68        self.set_permissions(perm).await?;
69
70        Ok(self)
71    }
72
73    #[inline]
74    async fn with_permissions_readonly(&self, readonly: bool) -> io::Result<&Self> {
75        let meta = self.metadata().await?;
76
77        let mut perm = meta.permissions();
78
79        perm.set_readonly(readonly);
80
81        self.with_permissions(perm).await
82    }
83
84    #[cfg(unix)]
85    #[inline]
86    async fn with_permissions_mode(&self, mode: u32) -> io::Result<&Self> {
87        let perm = Permissions::from_mode(mode);
88
89        self.with_permissions(perm).await
90    }
91
92    #[cfg(unix)]
93    #[inline]
94    async fn add_permissions_mode(&self, mode: u32) -> io::Result<&Self> {
95        let meta = self.metadata().await?;
96
97        let perm = meta.permissions();
98
99        self.with_permissions_mode(perm.mode() | mode).await
100    }
101
102    #[cfg(unix)]
103    #[inline]
104    async fn remove_permissions_mode(&self, mode: u32) -> io::Result<&Self> {
105        let meta = self.metadata().await?;
106
107        let perm = meta.permissions();
108
109        self.with_permissions_mode(perm.mode() & !mode).await
110    }
111
112    #[cfg(unix)]
113    #[inline]
114    async fn chown(&self, uid: Option<u32>, gid: Option<u32>) -> io::Result<&Self> {
115        let this = self.try_clone().await?;
116
117        task::spawn_blocking(move || unix::fs::fchown(this, uid, gid)).await??;
118
119        Ok(self)
120    }
121}
122
123#[trait_variant::make(Send)]
124pub trait OpenOptionsExt {
125    async fn open_if_not_exists(&self, path: impl AsRef<Path> + Send) -> io::Result<Option<File>>;
126    async fn open_if_exists(&self, path: impl AsRef<Path> + Send) -> io::Result<Option<File>>;
127}
128
129impl OpenOptionsExt for OpenOptions {
130    #[inline]
131    async fn open_if_not_exists(&self, path: impl AsRef<Path> + Send) -> io::Result<Option<File>> {
132        match self.open(path).await {
133            Ok(file) => Ok(Some(file)),
134            Err(err) if err.kind() == io::ErrorKind::AlreadyExists => Ok(None),
135            Err(err) => Err(err),
136        }
137    }
138
139    #[inline]
140    async fn open_if_exists(&self, path: impl AsRef<Path> + Send) -> io::Result<Option<File>> {
141        match self.open(path).await {
142            Ok(file) => Ok(Some(file)),
143            Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
144            Err(err) => Err(err),
145        }
146    }
147}