browser_fs/
file.rs

1use std::io::{Error, ErrorKind, Result};
2use std::path::Path;
3
4use web_sys::{FileSystemFileHandle, FileSystemSyncAccessHandle};
5
6use crate::{Metadata, OpenOptions};
7
8// NOTE: `FileSystemFileHandle` can return a `File` that is also a `Blob`. From
9// that we could read the content of the file easily. This could be a solution
10// to the limitation of having a single access handle per file.
11
12/// An open file on the filesystem.
13///
14/// Depending on what options the file was opened with, this type can be used
15/// for reading and/or writing.
16///
17/// Files are automatically closed when they get dropped and any errors detected
18/// on closing are ignored. Use the [`sync_all()`][`File::sync_all()`] method
19/// before dropping a file if such errors need to be handled.
20///
21/// **NOTE:** If writing to a file, make sure to call
22/// [`flush()`][`futures_lite::io::AsyncWriteExt::flush()`],
23/// [`sync_data()`][`File::sync_data()`], or [`sync_all()`][`File::sync_all()`]
24/// before dropping the file or else some written data might get lost!
25///
26/// # Examples
27///
28/// Create a new file and write some bytes to it:
29///
30/// ```no_run
31/// use browser_fs::File;
32/// use futures_lite::io::AsyncWriteExt;
33///
34/// # futures_lite::future::block_on(async {
35/// let mut file = File::create("a.txt").await?;
36///
37/// file.write_all(b"Hello, world!").await?;
38/// file.flush().await?;
39/// # std::io::Result::Ok(()) });
40/// ```
41///
42/// Read the contents of a file into a vector of bytes:
43///
44/// ```no_run
45/// use browser_fs::File;
46/// use futures_lite::io::AsyncReadExt;
47///
48/// # futures_lite::future::block_on(async {
49/// let mut file = File::open("a.txt").await?;
50///
51/// let mut contents = Vec::new();
52/// file.read_to_end(&mut contents).await?;
53/// # std::io::Result::Ok(()) });
54/// ```
55#[derive(Debug)]
56pub struct File {
57    pub(crate) file: FileSystemFileHandle,
58    pub(crate) access: FileSystemSyncAccessHandle,
59    pub(crate) offset: u32,
60}
61
62impl File {
63    pub(crate) fn new(
64        file: FileSystemFileHandle,
65        access: FileSystemSyncAccessHandle,
66        offset: u32,
67    ) -> Self {
68        Self {
69            file,
70            access,
71            offset,
72        }
73    }
74
75    /// Opens a file in read-only mode.
76    ///
77    /// See the [`OpenOptions::open()`] function for more options.
78    ///
79    /// # Errors
80    ///
81    /// An error will be returned in the following situations:
82    ///
83    /// * `path` does not point to an existing file.
84    /// * The current process lacks permissions to read the file.
85    /// * Some other I/O error occurred.
86    /// * The file is already open.
87    ///
88    /// For more details, see the list of errors documented by
89    /// [`OpenOptions::open()`].
90    ///
91    /// # Examples
92    ///
93    /// ```no_run
94    /// use browser_fs::File;
95    ///
96    /// # futures_lite::future::block_on(async {
97    /// let file = File::open("a.txt").await?;
98    /// # std::io::Result::Ok(()) });
99    /// ```
100    pub async fn open<P: AsRef<Path>>(path: P) -> Result<File> {
101        OpenOptions::new().read(true).open(path).await
102    }
103
104    /// Opens a file in write-only mode.
105    ///
106    /// This method will create a file if it does not exist, and will append to
107    /// it if it does.
108    ///
109    /// See the [`OpenOptions::open`] function for more options.
110    ///
111    /// # Errors
112    ///
113    /// An error will be returned in the following situations:
114    ///
115    /// * The file's parent directory does not exist.
116    /// * The current process lacks permissions to write to the file.
117    /// * Some other I/O error occurred.
118    /// * The file is already open.
119    ///
120    /// For more details, see the list of errors documented by
121    /// [`OpenOptions::open()`].
122    ///
123    /// # Examples
124    ///
125    /// ```no_run
126    /// use browser_fs::File;
127    ///
128    /// # futures_lite::future::block_on(async {
129    /// let file = File::create("a.txt").await?;
130    /// # std::io::Result::Ok(()) });
131    /// ```
132    pub async fn create<P: AsRef<Path>>(path: P) -> Result<File> {
133        OpenOptions::new().create(true).write(true).open(path).await
134    }
135
136    /// Opens a file in write-only mode.
137    ///
138    /// This method will create a file if it does not exist, and will truncate
139    /// it if it does.
140    ///
141    /// See the [`OpenOptions::open`] function for more options.
142    ///
143    /// # Errors
144    ///
145    /// An error will be returned in the following situations:
146    ///
147    /// * The file's parent directory does not exist.
148    /// * The current process lacks permissions to write to the file.
149    /// * Some other I/O error occurred.
150    /// * The file is already open.
151    ///
152    /// For more details, see the list of errors documented by
153    /// [`OpenOptions::open()`].
154    ///
155    /// # Examples
156    ///
157    /// ```no_run
158    /// use browser_fs::File;
159    ///
160    /// # futures_lite::future::block_on(async {
161    /// let file = File::create_new("a.txt").await?;
162    /// # std::io::Result::Ok(()) });
163    /// ```
164    pub async fn create_new<P: AsRef<Path>>(path: P) -> Result<File> {
165        OpenOptions::new()
166            .create_new(true)
167            .write(true)
168            .open(path)
169            .await
170    }
171
172    /// Truncates the file.
173    ///
174    /// If `size` is less than the current file size, then the file will be
175    /// truncated. If it is greater than the current file size, then nothing
176    /// will happend.
177    ///
178    /// The file's cursor stays at the same position, even if the cursor ends up
179    /// being past the end of the file after this operation.
180    ///
181    /// # Examples
182    ///
183    /// ```no_run
184    /// use browser_fs::File;
185    ///
186    /// # futures_lite::future::block_on(async {
187    /// let mut file = File::create("a.txt").await?;
188    /// file.set_len(10).await?;
189    /// # std::io::Result::Ok(()) });
190    /// ```
191    pub async fn set_len(&self, size: u64) -> Result<()> {
192        self.access
193            .truncate_with_u32(size as u32)
194            .map_err(crate::from_js_error)?;
195        Ok(())
196    }
197
198    /// Synchronizes buffered contents and metadata to disk.
199    ///
200    /// This function will ensure that all in-memory data reaches the
201    /// filesystem.
202    ///
203    /// This can be used to handle errors that would otherwise only be caught by
204    /// closing the file. When a file is dropped, errors in synchronizing
205    /// this in-memory data are ignored.
206    ///
207    /// # Examples
208    ///
209    /// ```no_run
210    /// use browser_fs::File;
211    /// use futures_lite::io::AsyncWriteExt;
212    ///
213    /// # futures_lite::future::block_on(async {
214    /// let mut file = File::create("a.txt").await?;
215    ///
216    /// file.write_all(b"Hello, world!").await?;
217    /// file.sync_all().await?;
218    /// # std::io::Result::Ok(()) });
219    /// ```
220    pub async fn sync_all(&self) -> Result<()> {
221        self.sync_data().await
222    }
223
224    /// Synchronizes buffered contents to disk.
225    ///
226    /// This is similar to [`sync_all()`][`File::sync_data()`], except that file
227    /// metadata may not be synchronized.
228    ///
229    /// This is intended for use cases that must synchronize the contents of the
230    /// file, but don't need the file metadata synchronized to disk.
231    ///
232    /// Note that some platforms may simply implement this in terms of
233    /// [`sync_all()`][`File::sync_data()`].
234    ///
235    /// # Examples
236    ///
237    /// ```no_run
238    /// use browser_fs::File;
239    /// use futures_lite::io::AsyncWriteExt;
240    ///
241    /// # futures_lite::future::block_on(async {
242    /// let mut file = File::create("a.txt").await?;
243    ///
244    /// file.write_all(b"Hello, world!").await?;
245    /// file.sync_data().await?;
246    /// # std::io::Result::Ok(()) });
247    /// ```
248    pub async fn sync_data(&self) -> Result<()> {
249        self.access.flush().map_err(crate::from_js_error)?;
250        Ok(())
251    }
252
253    /// Queries metadata about the underlying file.
254    ///
255    /// # Examples
256    ///
257    /// ```no_run
258    /// # futures_lite::future::block_on(async {
259    /// let mut file = browser_fs::File::open("a.txt").await?;
260    /// let metadata = file.metadata().await?;
261    /// # std::io::Result::Ok(()) });
262    /// ```
263    pub async fn metadata(&self) -> Result<Metadata> {
264        Metadata::from_file_handle(&self.file).await
265    }
266}
267
268impl Drop for File {
269    fn drop(&mut self) {
270        self.access.close();
271    }
272}
273
274/// Removes a file.
275///
276/// # Errors
277///
278/// An error will be returned in the following situations:
279///
280/// * `path` does not point to an existing file.
281/// * The current process lacks permissions to remove the file.
282/// * Some other I/O error occurred.
283///
284/// # Examples
285///
286/// ```no_run
287/// # futures_lite::future::block_on(async {
288/// browser_fs::remove_file("a.txt").await?;
289/// # std::io::Result::Ok(()) });
290/// ```
291pub async fn remove_file<P: AsRef<Path>>(path: P) -> Result<()> {
292    let fpath = path.as_ref();
293
294    let dir = if let Some(parent) = fpath.parent() {
295        crate::get_directory(parent).await?
296    } else {
297        crate::root_directory().await?
298    };
299    let Some(fname) = fpath.file_name() else {
300        return Err(Error::new(ErrorKind::InvalidInput, "filename not found"));
301    };
302    let promise = dir.remove_entry(fname.to_string_lossy().as_ref());
303    crate::resolve_undefined(promise).await
304}
305
306/// Reads the entire contents of a file as raw bytes.
307///
308/// This is a convenience function for reading entire files. It pre-allocates a
309/// buffer based on the file size when available, so it is typically faster than
310/// manually opening a file and reading from it.
311///
312/// If you want to read the contents as a string, use [`read_to_string()`]
313/// instead.
314///
315/// # Errors
316///
317/// An error will be returned in the following situations:
318///
319/// * `path` does not point to an existing file.
320/// * The current process lacks permissions to read the file.
321/// * Some other I/O error occurred.
322///
323/// # Examples
324///
325/// ```no_run
326/// # futures_lite::future::block_on(async {
327/// let contents = browser_fs::read("a.txt").await?;
328/// # std::io::Result::Ok(()) });
329/// ```
330pub async fn read<P: AsRef<Path>>(path: P) -> Result<Vec<u8>> {
331    use futures_lite::AsyncReadExt;
332
333    let mut file = File::open(path).await?;
334    let mut buf = Vec::new();
335    file.read_to_end(&mut buf).await?;
336    Ok(buf)
337}
338
339/// Reads the entire contents of a file as a string.
340///
341/// This is a convenience function for reading entire files. It pre-allocates a
342/// string based on the file size when available, so it is typically faster than
343/// manually opening a file and reading from it.
344///
345/// If you want to read the contents as raw bytes, use [`read()`] instead.
346///
347/// # Errors
348///
349/// An error will be returned in the following situations:
350///
351/// * `path` does not point to an existing file.
352/// * The current process lacks permissions to read the file.
353/// * The contents of the file cannot be read as a UTF-8 string.
354/// * Some other I/O error occurred.
355///
356/// # Examples
357///
358/// ```no_run
359/// # futures_lite::future::block_on(async {
360/// let contents = browser_fs::read_to_string("a.txt").await?;
361/// # std::io::Result::Ok(()) });
362/// ```
363pub async fn read_to_string<P: AsRef<Path>>(path: P) -> Result<String> {
364    use futures_lite::AsyncReadExt;
365
366    let mut file = File::open(path).await?;
367    let mut buf = String::new();
368    file.read_to_string(&mut buf).await?;
369    Ok(buf)
370}
371
372/// Writes a slice of bytes as the new contents of a file.
373///
374/// This function will create a file if it does not exist, and will entirely
375/// replace its contents if it does.
376///
377/// # Errors
378///
379/// An error will be returned in the following situations:
380///
381/// * The file's parent directory does not exist.
382/// * The current process lacks permissions to write to the file.
383/// * Some other I/O error occurred.
384///
385/// # Examples
386///
387/// ```no_run
388/// # futures_lite::future::block_on(async {
389/// browser_fs::write("a.txt", b"Hello world!").await?;
390/// # std::io::Result::Ok(()) });
391/// ```
392pub async fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
393    use futures_lite::AsyncWriteExt;
394
395    let mut file = File::create_new(path).await?;
396    file.write_all(contents.as_ref()).await?;
397    file.flush().await?;
398    file.close().await?;
399    Ok(())
400}