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}