browser_fs/
open_options.rs

1use std::io::{Error, ErrorKind};
2use std::path::Path;
3
4use wasm_bindgen::prelude::*;
5use web_sys::{FileSystemFileHandle, FileSystemGetFileOptions, FileSystemSyncAccessHandle};
6
7#[wasm_bindgen]
8#[derive(Clone, Copy, Debug, Default)]
9pub(crate) struct FileOptions {
10    pub append: bool,
11    pub create: bool,
12    pub create_new: bool,
13    pub read: bool,
14    pub truncate: bool,
15    pub write: bool,
16}
17
18/// A builder for opening files with configurable options.
19///
20/// Files can be opened in [`read`][`OpenOptions::read()`] and/or
21/// [`write`][`OpenOptions::write()`] mode.
22///
23/// The [`append`][`OpenOptions::append()`] option opens files in a special
24/// writing mode that moves the file cursor to the end of file before every
25/// write operation.
26///
27/// It is also possible to [`truncate`][`OpenOptions::truncate()`] the file
28/// right after opening, to [`create`][`OpenOptions::create()`] a file if it
29/// doesn't exist yet, or to always create a new file with
30/// [`create_new`][`OpenOptions::create_new()`].
31///
32/// # Examples
33///
34/// Open a file for reading:
35///
36/// ```no_run
37/// use browser_fs::OpenOptions;
38///
39/// # futures_lite::future::block_on(async {
40/// let file = OpenOptions::new().read(true).open("a.txt").await?;
41/// # std::io::Result::Ok(()) });
42/// ```
43///
44/// Open a file for both reading and writing, and create it if it doesn't exist
45/// yet:
46///
47/// ```no_run
48/// use browser_fs::OpenOptions;
49///
50/// # futures_lite::future::block_on(async {
51/// let file = OpenOptions::new()
52///     .read(true)
53///     .write(true)
54///     .create(true)
55///     .open("a.txt")
56///     .await?;
57/// # std::io::Result::Ok(()) });
58/// ```
59#[derive(Debug, Clone, Copy)]
60pub struct OpenOptions {
61    inner: FileOptions,
62}
63
64impl Default for OpenOptions {
65    fn default() -> Self {
66        Self::new()
67    }
68}
69
70impl OpenOptions {
71    /// Creates a blank set of options.
72    ///
73    /// All options are initially set to `false`.
74    ///
75    /// # Examples
76    ///
77    /// ```no_run
78    /// use browser_fs::OpenOptions;
79    ///
80    /// # futures_lite::future::block_on(async {
81    /// let file = OpenOptions::new().read(true).open("a.txt").await?;
82    /// # std::io::Result::Ok(()) });
83    /// ```
84    pub fn new() -> Self {
85        Self {
86            inner: FileOptions::default(),
87        }
88    }
89
90    /// Configures the option for append mode.
91    ///
92    /// When set to `true`, this option means the file will be writable after
93    /// opening and the file cursor will be moved to the end of file before
94    /// every write operaiton.
95    ///
96    /// # Examples
97    ///
98    /// ```no_run
99    /// use browser_fs::OpenOptions;
100    ///
101    /// # futures_lite::future::block_on(async {
102    /// let file = OpenOptions::new().append(true).open("a.txt").await?;
103    /// # std::io::Result::Ok(()) });
104    /// ```
105    pub fn append(mut self, append: bool) -> OpenOptions {
106        self.inner.append = append;
107        self
108    }
109
110    /// Configures the option for creating a new file if it doesn't exist.
111    ///
112    /// When set to `true`, this option means a new file will be created if it
113    /// doesn't exist.
114    ///
115    /// The file must be opened in [`write`][`OpenOptions::write()`] or
116    /// [`append`][`OpenOptions::append()`] mode for file creation to work.
117    ///
118    /// # Examples
119    ///
120    /// ```no_run
121    /// use browser_fs::OpenOptions;
122    ///
123    /// # futures_lite::future::block_on(async {
124    /// let file = OpenOptions::new()
125    ///     .write(true)
126    ///     .create(true)
127    ///     .open("a.txt")
128    ///     .await?;
129    /// # std::io::Result::Ok(()) });
130    /// ```
131    pub fn create(mut self, create: bool) -> OpenOptions {
132        self.inner.create = create;
133        self
134    }
135
136    /// Configures the option for creating a new file or failing if it already
137    /// exists.
138    ///
139    /// When set to `true`, this option means a new file will be created, or the
140    /// open operation will fail if the file already exists.
141    ///
142    /// The file must be opened in [`write`][`OpenOptions::write()`] or
143    /// [`append`][`OpenOptions::append()`] mode for file creation to work.
144    ///
145    /// # Examples
146    ///
147    /// ```no_run
148    /// use browser_fs::OpenOptions;
149    ///
150    /// # futures_lite::future::block_on(async {
151    /// let file = OpenOptions::new()
152    ///     .write(true)
153    ///     .create_new(true)
154    ///     .open("a.txt")
155    ///     .await?;
156    /// # std::io::Result::Ok(()) });
157    /// ```
158    pub fn create_new(mut self, create_new: bool) -> OpenOptions {
159        self.inner.create_new = create_new;
160        self
161    }
162
163    /// Configures the option for read mode.
164    ///
165    /// When set to `true`, this option means the file will be readable after
166    /// opening.
167    ///
168    /// # Examples
169    ///
170    /// ```no_run
171    /// use browser_fs::OpenOptions;
172    ///
173    /// # futures_lite::future::block_on(async {
174    /// let file = OpenOptions::new().read(true).open("a.txt").await?;
175    /// # std::io::Result::Ok(()) });
176    /// ```
177    pub fn read(mut self, read: bool) -> OpenOptions {
178        self.inner.read = read;
179        self
180    }
181
182    /// Configures the option for truncating the previous file.
183    ///
184    /// When set to `true`, the file will be truncated to the length of 0 bytes.
185    ///
186    /// The file must be opened in [`write`][`OpenOptions::write()`] or
187    /// [`append`][`OpenOptions::append()`] mode for truncation to work.
188    ///
189    /// # Examples
190    ///
191    /// ```no_run
192    /// use browser_fs::OpenOptions;
193    ///
194    /// # futures_lite::future::block_on(async {
195    /// let file = OpenOptions::new()
196    ///     .write(true)
197    ///     .truncate(true)
198    ///     .open("a.txt")
199    ///     .await?;
200    /// # std::io::Result::Ok(()) });
201    /// ```
202    pub fn truncate(mut self, truncate: bool) -> OpenOptions {
203        self.inner.truncate = truncate;
204        self
205    }
206
207    /// Configures the option for write mode.
208    ///
209    /// When set to `true`, this option means the file will be writable after
210    /// opening.
211    ///
212    /// If the file already exists, write calls on it will overwrite the
213    /// previous contents without truncating it.
214    ///
215    /// # Examples
216    ///
217    /// ```no_run
218    /// use browser_fs::OpenOptions;
219    ///
220    /// # futures_lite::future::block_on(async {
221    /// let file = OpenOptions::new().write(true).open("a.txt").await?;
222    /// # std::io::Result::Ok(()) });
223    /// ```
224    pub fn write(mut self, write: bool) -> OpenOptions {
225        self.inner.write = write;
226        self
227    }
228
229    /// Opens a file with the configured options.
230    ///
231    /// # Errors
232    ///
233    /// An error will be returned in the following situations:
234    ///
235    /// * The file does not exist and neither [`create`] nor [`create_new`] were
236    ///   set.
237    /// * The file's parent directory does not exist.
238    /// * The current process lacks permissions to open the file in the
239    ///   configured mode.
240    /// * The file already exists and [`create_new`] was set.
241    /// * Invalid combination of options was used, like [`truncate`] was set but
242    ///   [`write`] wasn't, or none of [`read`], [`write`], and [`append`] modes
243    ///   was set.
244    /// * An OS-level occurred, like too many files are open or the file name is
245    ///   too long.
246    /// * Some other I/O error occurred.
247    ///
248    /// [`read`]: `OpenOptions::read()`
249    /// [`write`]: `OpenOptions::write()`
250    /// [`append`]: `OpenOptions::append()`
251    /// [`truncate`]: `OpenOptions::truncate()`
252    /// [`create`]: `OpenOptions::create()`
253    /// [`create_new`]: `OpenOptions::create_new()`
254    ///
255    /// # Examples
256    ///
257    /// ```no_run
258    /// use browser_fs::OpenOptions;
259    ///
260    /// # futures_lite::future::block_on(async {
261    /// let file = OpenOptions::new().read(true).open("a.txt").await?;
262    /// # std::io::Result::Ok(()) });
263    /// ```
264    pub async fn open<P: AsRef<Path>>(&self, path: P) -> std::io::Result<crate::file::File> {
265        let fpath = path.as_ref();
266        let dir = if let Some(parent) = fpath.parent() {
267            crate::get_directory(parent).await?
268        } else {
269            crate::root_directory().await?
270        };
271        let Some(fname) = fpath.file_name() else {
272            return Err(Error::new(ErrorKind::InvalidInput, "filename not found"));
273        };
274        let opts = FileSystemGetFileOptions::new();
275        opts.set_create(self.inner.create || self.inner.create_new);
276        let promise = dir.get_file_handle_with_options(fname.to_string_lossy().as_ref(), &opts);
277        let file = crate::resolve::<FileSystemFileHandle>(promise).await?;
278        let promise = file.create_sync_access_handle();
279        let access = crate::resolve::<FileSystemSyncAccessHandle>(promise).await?;
280        let mut offset = 0;
281
282        if self.inner.write {
283            if self.inner.truncate || self.inner.create_new {
284                access.truncate_with_u32(0).map_err(crate::from_js_error)?;
285            } else {
286                offset = access.get_size().map_err(crate::from_js_error)? as u32;
287            }
288        }
289
290        Ok(crate::file::File::new(file, access, offset))
291    }
292}