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}