Skip to main content

fs_err/tokio/
file.rs

1use crate::errors::{Error, ErrorKind};
2use std::fs::{Metadata, Permissions};
3use std::io;
4use std::io::{IoSlice, SeekFrom};
5use std::path::{Path, PathBuf};
6use std::pin::Pin;
7use std::task::{ready, Context, Poll};
8use tokio::fs;
9use tokio::fs::File as TokioFile;
10use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};
11
12use super::OpenOptions;
13
14#[allow(unused_imports)]
15use crate as fs_err; // for docs
16
17/// Wrapper around [`tokio::fs::File`] which adds more helpful
18/// information to all errors.
19#[derive(Debug)]
20#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
21pub struct File {
22    tokio: fs::File,
23    path: PathBuf,
24}
25
26impl File {
27    /// Attempts to open a file in read-only mode.
28    ///
29    /// Wrapper for [`tokio::fs::File::open`].
30    pub async fn open(path: impl Into<PathBuf>) -> io::Result<File> {
31        let path = path.into();
32        let f = TokioFile::open(&path)
33            .await
34            .map_err(|err| Error::build(err, ErrorKind::OpenFile, &path))?;
35        Ok(File::from_parts(f, path))
36    }
37
38    /// Opens a file in write-only mode.
39    ///
40    /// Wrapper for [`tokio::fs::File::create`].
41    pub async fn create(path: impl Into<PathBuf>) -> io::Result<File> {
42        let path = path.into();
43        match TokioFile::create(&path).await {
44            Ok(f) => Ok(File::from_parts(f, path)),
45            Err(err) => Err(Error::build(err, ErrorKind::CreateFile, &path)),
46        }
47    }
48
49    /// Opens a file in read-write mode.
50    ///
51    /// Wrapper for [`tokio::fs::File::create_new`].
52    pub async fn create_new(path: impl Into<PathBuf>) -> Result<Self, io::Error> {
53        let path = path.into();
54        match fs::File::create_new(&path).await {
55            Ok(file) => Ok(File::from_parts(file, path)),
56            Err(err) => Err(Error::build(err, ErrorKind::CreateFile, path)),
57        }
58    }
59
60    /// Returns a new `OpenOptions` object.
61    ///
62    /// Wrapper for [`tokio::fs::File::options`].
63    pub fn options() -> OpenOptions {
64        OpenOptions::new()
65    }
66
67    /// Converts a [`fs_err::File`] to a [`fs_err::tokio::File`].
68    ///
69    /// Wrapper for [`tokio::fs::File::from_std`].
70    pub fn from_std(std: crate::File) -> File {
71        let (std, path) = std.into_parts();
72        File::from_parts(TokioFile::from_std(std), path)
73    }
74
75    /// Attempts to sync all OS-internal metadata to disk.
76    ///
77    /// Wrapper for [`tokio::fs::File::sync_all`].
78    pub async fn sync_all(&self) -> io::Result<()> {
79        self.tokio
80            .sync_all()
81            .await
82            .map_err(|err| self.error(err, ErrorKind::SyncFile))
83    }
84
85    /// This function is similar to `sync_all`, except that it may not
86    /// synchronize file metadata to the filesystem.
87    ///
88    /// Wrapper for [`tokio::fs::File::sync_data`].
89    pub async fn sync_data(&self) -> io::Result<()> {
90        self.tokio
91            .sync_data()
92            .await
93            .map_err(|err| self.error(err, ErrorKind::SyncFile))
94    }
95
96    /// Truncates or extends the underlying file, updating the size of this file to become size.
97    ///
98    /// Wrapper for [`tokio::fs::File::set_len`].
99    pub async fn set_len(&self, size: u64) -> io::Result<()> {
100        self.tokio
101            .set_len(size)
102            .await
103            .map_err(|err| self.error(err, ErrorKind::SetLen))
104    }
105
106    /// Queries metadata about the underlying file.
107    ///
108    /// Wrapper for [`tokio::fs::File::metadata`].
109    pub async fn metadata(&self) -> io::Result<Metadata> {
110        self.tokio
111            .metadata()
112            .await
113            .map_err(|err| self.error(err, ErrorKind::Metadata))
114    }
115
116    /// Creates a new `File` instance that shares the same underlying file handle
117    /// as the existing `File` instance. Reads, writes, and seeks will affect both
118    /// `File` instances simultaneously.
119    ///
120    /// Wrapper for [`tokio::fs::File::try_clone`].
121    pub async fn try_clone(&self) -> io::Result<File> {
122        match self.tokio.try_clone().await {
123            Ok(file) => Ok(File::from_parts(file, self.path.clone())),
124            Err(err) => Err(self.error(err, ErrorKind::Clone)),
125        }
126    }
127
128    /// Destructures `File` into a [`fs_err::File`]. This function is async
129    /// to allow any in-flight operations to complete.
130    ///
131    /// Wrapper for [`tokio::fs::File::into_std`].
132    pub async fn into_std(self) -> crate::File {
133        crate::File::from_parts(self.tokio.into_std().await, self.path)
134    }
135
136    /// Tries to immediately destructure `File` into a [`fs_err::File`].
137    ///
138    /// Wrapper for [`tokio::fs::File::try_into_std`].
139    pub fn try_into_std(self) -> Result<crate::File, File> {
140        match self.tokio.try_into_std() {
141            Ok(f) => Ok(crate::File::from_parts(f, self.path)),
142            Err(f) => Err(File::from_parts(f, self.path)),
143        }
144    }
145
146    /// Changes the permissions on the underlying file.
147    ///
148    /// Wrapper for [`tokio::fs::File::set_permissions`].
149    pub async fn set_permissions(&self, perm: Permissions) -> io::Result<()> {
150        self.tokio
151            .set_permissions(perm)
152            .await
153            .map_err(|err| self.error(err, ErrorKind::SetPermissions))
154    }
155}
156
157/// Methods added by fs-err that are not available on [`tokio::fs::File`].
158impl File {
159    /// Creates a [`File`] from a raw file and its path.
160    pub fn from_parts<P>(file: TokioFile, path: P) -> Self
161    where
162        P: Into<PathBuf>,
163    {
164        File {
165            tokio: file,
166            path: path.into(),
167        }
168    }
169
170    /// Extract the raw file and its path from this [`File`].
171    pub fn into_parts(self) -> (TokioFile, PathBuf) {
172        (self.tokio, self.path)
173    }
174
175    /// Returns a reference to the underlying [`tokio::fs::File`].
176    pub fn file(&self) -> &TokioFile {
177        &self.tokio
178    }
179
180    /// Returns a mutable reference to the underlying [`tokio::fs::File`].
181    pub fn file_mut(&mut self) -> &mut TokioFile {
182        &mut self.tokio
183    }
184
185    /// Returns a reference to the path that this file was created with.
186    pub fn path(&self) -> &Path {
187        &self.path
188    }
189
190    /// Wrap the error in information specific to this [`File`] object.
191    fn error(&self, source: io::Error, kind: ErrorKind) -> io::Error {
192        Error::build(source, kind, &self.path)
193    }
194}
195
196impl From<crate::File> for File {
197    fn from(f: crate::File) -> Self {
198        let (f, path) = f.into_parts();
199        File::from_parts(f.into(), path)
200    }
201}
202
203impl From<File> for TokioFile {
204    fn from(f: File) -> Self {
205        f.into_parts().0
206    }
207}
208
209#[cfg(unix)]
210impl std::os::unix::io::AsRawFd for File {
211    fn as_raw_fd(&self) -> std::os::unix::io::RawFd {
212        self.tokio.as_raw_fd()
213    }
214}
215
216#[cfg(windows)]
217impl std::os::windows::io::AsRawHandle for File {
218    fn as_raw_handle(&self) -> std::os::windows::io::RawHandle {
219        self.tokio.as_raw_handle()
220    }
221}
222
223impl AsyncRead for File {
224    fn poll_read(
225        mut self: Pin<&mut Self>,
226        cx: &mut Context<'_>,
227        buf: &mut ReadBuf<'_>,
228    ) -> Poll<io::Result<()>> {
229        Poll::Ready(
230            ready!(Pin::new(&mut self.tokio).poll_read(cx, buf))
231                .map_err(|err| self.error(err, ErrorKind::Read)),
232        )
233    }
234}
235
236impl AsyncSeek for File {
237    fn start_seek(mut self: Pin<&mut Self>, position: SeekFrom) -> io::Result<()> {
238        Pin::new(&mut self.tokio)
239            .start_seek(position)
240            .map_err(|err| self.error(err, ErrorKind::Seek))
241    }
242
243    fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
244        Poll::Ready(
245            ready!(Pin::new(&mut self.tokio).poll_complete(cx))
246                .map_err(|err| self.error(err, ErrorKind::Seek)),
247        )
248    }
249}
250
251impl AsyncWrite for File {
252    fn poll_write(
253        mut self: Pin<&mut Self>,
254        cx: &mut Context<'_>,
255        buf: &[u8],
256    ) -> Poll<io::Result<usize>> {
257        Poll::Ready(
258            ready!(Pin::new(&mut self.tokio).poll_write(cx, buf))
259                .map_err(|err| self.error(err, ErrorKind::Write)),
260        )
261    }
262
263    fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
264        Poll::Ready(
265            ready!(Pin::new(&mut self.tokio).poll_flush(cx))
266                .map_err(|err| self.error(err, ErrorKind::Flush)),
267        )
268    }
269
270    fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
271        Poll::Ready(
272            ready!(Pin::new(&mut self.tokio).poll_shutdown(cx))
273                .map_err(|err| self.error(err, ErrorKind::Flush)),
274        )
275    }
276
277    fn poll_write_vectored(
278        mut self: Pin<&mut Self>,
279        cx: &mut Context<'_>,
280        bufs: &[IoSlice<'_>],
281    ) -> Poll<io::Result<usize>> {
282        Poll::Ready(
283            ready!(Pin::new(&mut self.tokio).poll_write_vectored(cx, bufs))
284                .map_err(|err| self.error(err, ErrorKind::Write)),
285        )
286    }
287
288    fn is_write_vectored(&self) -> bool {
289        self.tokio.is_write_vectored()
290    }
291}