web_fs/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use wasm_bindgen_futures::{JsFuture, stream::JsStream};
4
5mod c_static_str;
6pub(crate) use c_static_str::*;
7mod open_options;
8use arena::Arena;
9use js_sys::{ArrayBuffer, Object, Reflect};
10pub use open_options::{OpenFileFuture, OpenOptions};
11use read::ReadResult;
12use util::{Task, get_value, get_value_as_f64, js_value_to_error, set_value};
13mod arena;
14mod file;
15mod read;
16mod seek;
17mod write;
18pub use file::{File, TruncateFuture};
19mod metadata;
20mod util;
21pub use metadata::*;
22
23use std::{
24    cell::RefCell,
25    ffi::OsString,
26    io::{Error, ErrorKind, Result},
27    path::{Component, Path, PathBuf},
28    rc::Rc,
29};
30
31use futures_lite::{AsyncReadExt, AsyncWriteExt, Stream, StreamExt};
32use wasm_bindgen::prelude::*;
33use web_sys::{
34    FileSystemDirectoryHandle, FileSystemFileHandle, FileSystemGetDirectoryOptions,
35    FileSystemGetFileOptions, FileSystemRemoveOptions, MessageEvent, Worker, WorkerGlobalScope,
36    window,
37};
38
39const GETTING_JS_FIELD_ERROR: &str = "Getting js field error, this is an error of the crate.";
40const ARENA_REMOVE_ERROR: &str = "Removing from arena error, this is an error of the crate.";
41const DYN_INTO_ERROR: &str = "Converting js type failed, this is an error of the crate.";
42const POST_ERROR: &str = "Posting message to worker failed, this is an error of the crate";
43
44struct FsInner {
45    opening_tasks: Arena<Rc<RefCell<Task<Result<File>>>>>,
46    reading_tasks: Arena<Rc<RefCell<Task<Result<ReadResult>>>>>,
47    writing_tasks: Arena<Rc<RefCell<Task<Result<usize>>>>>,
48    flushing_tasks: Arena<Rc<RefCell<Task<Result<()>>>>>,
49    closing_tasks: Arena<Rc<RefCell<Task<Result<()>>>>>,
50    truncating_tasks: Arena<Rc<RefCell<Task<Result<()>>>>>,
51}
52struct Fs {
53    inner: Rc<RefCell<FsInner>>,
54    _closure: Closure<dyn FnMut(MessageEvent)>,
55    worker: Worker,
56}
57impl Fs {
58    fn new() -> Self {
59        let worker = Worker::new(&wasm_bindgen::link_to!(module = "/src/worker.js"))
60            .expect("Creating web worker failed. This crate relies on web worker to work.");
61
62        let inner = FsInner {
63            opening_tasks: Arena::new(),
64            reading_tasks: Arena::new(),
65            writing_tasks: Arena::new(),
66            flushing_tasks: Arena::new(),
67            closing_tasks: Arena::new(),
68            truncating_tasks: Arena::new(),
69        };
70        let inner = Rc::new(RefCell::new(inner));
71        let inner_clone = inner.clone();
72        #[repr(u32)]
73        enum InMsgType {
74            Open = 0,
75            Read,
76            Write,
77            Flush,
78            Close,
79            Truncate,
80        }
81        let on_message: Closure<dyn FnMut(MessageEvent)> =
82            Closure::new(move |msg: MessageEvent| {
83                let received = msg.data();
84                let error = get_value(&received, &ERROR);
85                let error = if !error.is_undefined() {
86                    Some(error.as_string()).expect(
87                        "Converting js error to string failed, this is an error of the crate.",
88                    )
89                } else {
90                    None
91                };
92
93                let open_msg = Reflect::get_u32(&received, InMsgType::Open as u32)
94                    .expect(GETTING_JS_FIELD_ERROR);
95                if !open_msg.is_undefined() {
96                    let index = get_value_as_f64(&open_msg, &INDEX) as usize;
97                    let task = inner_clone
98                        .borrow_mut()
99                        .opening_tasks
100                        .remove(index)
101                        .expect(ARENA_REMOVE_ERROR);
102                    let mut state = task.borrow_mut();
103                    if let Some(error) = error {
104                        state.result = Some(Err(Error::other(error)));
105                    } else {
106                        let fd = get_value_as_f64(&open_msg, &FD) as usize;
107                        let size = get_value_as_f64(&open_msg, &SIZE) as u64;
108                        state.result = Some(Ok(File::new(fd, size)));
109                    }
110                    if let Some(waker) = state.waker.take() {
111                        waker.wake();
112                    }
113                    return;
114                }
115                let read_msg = Reflect::get_u32(&received, InMsgType::Read as u32)
116                    .expect(GETTING_JS_FIELD_ERROR);
117                if !read_msg.is_undefined() {
118                    let index = get_value_as_f64(&read_msg, &INDEX) as usize;
119                    let task = inner_clone
120                        .borrow_mut()
121                        .reading_tasks
122                        .remove(index)
123                        .expect(ARENA_REMOVE_ERROR);
124                    let mut state = task.borrow_mut();
125                    if let Some(error) = error {
126                        state.result = Some(Err(Error::other(error)));
127                    } else {
128                        let size = get_value_as_f64(&read_msg, &SIZE) as usize;
129                        let array_buffer = get_value(&read_msg, &BUF)
130                            .dyn_into::<ArrayBuffer>()
131                            .expect(DYN_INTO_ERROR);
132                        state.result = Some(Ok(ReadResult {
133                            buf: array_buffer,
134                            size,
135                        }));
136                    }
137                    if let Some(waker) = state.waker.take() {
138                        waker.wake();
139                    }
140                    return;
141                }
142                let write_msg = Reflect::get_u32(&received, InMsgType::Write as u32)
143                    .expect(GETTING_JS_FIELD_ERROR);
144                if !write_msg.is_undefined() {
145                    let index = get_value_as_f64(&write_msg, &INDEX) as usize;
146                    let task = inner_clone
147                        .borrow_mut()
148                        .writing_tasks
149                        .remove(index)
150                        .expect(ARENA_REMOVE_ERROR);
151                    let mut state = task.borrow_mut();
152
153                    if let Some(error) = error {
154                        state.result = Some(Err(Error::other(error)));
155                    } else {
156                        let size = get_value_as_f64(&write_msg, &SIZE) as usize;
157                        state.result = Some(Ok(size));
158                    }
159
160                    if let Some(waker) = state.waker.take() {
161                        waker.wake();
162                    }
163                    return;
164                }
165                let flush_msg = Reflect::get_u32(&received, InMsgType::Flush as u32)
166                    .expect(GETTING_JS_FIELD_ERROR);
167                if !flush_msg.is_undefined() {
168                    let index = get_value_as_f64(&flush_msg, &INDEX) as usize;
169                    let task = inner_clone
170                        .borrow_mut()
171                        .flushing_tasks
172                        .remove(index)
173                        .expect(ARENA_REMOVE_ERROR);
174                    let mut state = task.borrow_mut();
175
176                    if let Some(error) = error {
177                        state.result = Some(Err(Error::other(error)));
178                    } else {
179                        state.result = Some(Ok(()))
180                    }
181
182                    if let Some(waker) = state.waker.take() {
183                        waker.wake();
184                    }
185                    return;
186                }
187                let close_msg = Reflect::get_u32(&received, InMsgType::Close as u32)
188                    .expect(GETTING_JS_FIELD_ERROR);
189                if !close_msg.is_undefined() {
190                    let index = get_value_as_f64(&close_msg, &INDEX) as usize;
191                    let task = inner_clone
192                        .borrow_mut()
193                        .closing_tasks
194                        .remove(index)
195                        .expect(ARENA_REMOVE_ERROR);
196                    let mut state = task.borrow_mut();
197
198                    if let Some(error) = error {
199                        state.result = Some(Err(Error::other(error)));
200                    } else {
201                        state.result = Some(Ok(()))
202                    }
203
204                    if let Some(waker) = state.waker.take() {
205                        waker.wake();
206                    }
207                    return;
208                }
209                let truncate_msg = Reflect::get_u32(&received, InMsgType::Truncate as u32)
210                    .expect(GETTING_JS_FIELD_ERROR);
211                if !truncate_msg.is_undefined() {
212                    let index = get_value_as_f64(&truncate_msg, &INDEX) as usize;
213                    let task = inner_clone
214                        .borrow_mut()
215                        .truncating_tasks
216                        .remove(index)
217                        .expect(ARENA_REMOVE_ERROR);
218                    let mut state = task.borrow_mut();
219
220                    if let Some(error) = error {
221                        state.result = Some(Err(Error::other(error)));
222                    } else {
223                        state.result = Some(Ok(()))
224                    }
225
226                    if let Some(waker) = state.waker.take() {
227                        waker.wake();
228                    }
229                    return;
230                }
231            });
232        worker.set_onmessage(Some(on_message.as_ref().unchecked_ref()));
233        Self {
234            inner,
235            _closure: on_message,
236            worker,
237        }
238    }
239    fn drop_file(&self, fd: usize) {
240        let msg = Object::new();
241        let drop = Object::new();
242        set_value(&drop, &FD, &JsValue::from(fd));
243        set_value(&msg, &DROP, &drop);
244
245        self.worker.post_message(&msg).expect(POST_ERROR);
246    }
247}
248thread_local! {
249    static FS: RefCell<Fs> = RefCell::new(Fs::new());
250}
251
252async fn get_root() -> Result<FileSystemDirectoryHandle> {
253    let storage = if let Some(window) = window() {
254        let navigator = window.navigator();
255        navigator.storage()
256    } else if js_sys::global().is_instance_of::<WorkerGlobalScope>() {
257        let global = js_sys::global().unchecked_into::<WorkerGlobalScope>();
258        global.navigator().storage()
259    } else {
260        return Err(std::io::Error::new(
261            std::io::ErrorKind::Unsupported,
262            "unable to access browser storage",
263        ));
264    };
265    JsFuture::from(storage.get_directory())
266        .await
267        .map_err(|_| {
268            std::io::Error::new(
269                std::io::ErrorKind::Unsupported,
270                "unable to get root directory",
271            )
272        })?
273        .dyn_into::<FileSystemDirectoryHandle>()
274        .map_err(|_| std::io::Error::new(std::io::ErrorKind::Unsupported, DYN_INTO_ERROR))
275}
276
277async fn child_dir(
278    parent: &FileSystemDirectoryHandle,
279    name: &str,
280    create: bool,
281) -> Result<FileSystemDirectoryHandle> {
282    let options = FileSystemGetDirectoryOptions::new();
283    options.set_create(create);
284    let result = JsFuture::from(parent.get_directory_handle_with_options(name, &options))
285        .await
286        .map_err(|e| js_value_to_error(e))?
287        .dyn_into::<FileSystemDirectoryHandle>()
288        .expect(DYN_INTO_ERROR);
289    Ok(result)
290}
291
292async fn child_file(
293    parent: &FileSystemDirectoryHandle,
294    name: &str,
295    create: bool,
296) -> Result<FileSystemFileHandle> {
297    let options = FileSystemGetFileOptions::new();
298    options.set_create(create);
299    let result = JsFuture::from(parent.get_file_handle_with_options(name, &options))
300        .await
301        .map_err(|e| js_value_to_error(e))?
302        .dyn_into::<FileSystemFileHandle>()
303        .expect(DYN_INTO_ERROR);
304    Ok(result)
305}
306
307async fn get_parent_dir<P: AsRef<Path>>(
308    path: P,
309    create: bool,
310) -> Result<FileSystemDirectoryHandle> {
311    let path = path.as_ref();
312    let root = get_root().await?;
313    let mut parents_stack = vec![root];
314    if let Some(path) = path.parent() {
315        for component in path.components() {
316            match component {
317                // Browser can't access system root, so this is PermissionDenied.
318                Component::Prefix(_) => return Err(Error::from(ErrorKind::PermissionDenied)),
319                Component::CurDir | Component::RootDir => (),
320                Component::ParentDir => {
321                    // Accessing the parent of the root is also not allowed.
322                    if parents_stack.len() == 1 {
323                        return Err(Error::from(ErrorKind::PermissionDenied));
324                    } else {
325                        parents_stack.pop();
326                    }
327                }
328                Component::Normal(name) => {
329                    let name = name.to_string_lossy();
330                    parents_stack.push(
331                        child_dir(parents_stack.last().as_ref().unwrap(), &name, create).await?,
332                    );
333                }
334            }
335        }
336    }
337    Ok(parents_stack.pop().unwrap())
338}
339
340async fn get_dir<P: AsRef<Path>>(
341    path: P,
342    create: bool,
343    create_parents: bool,
344) -> Result<FileSystemDirectoryHandle> {
345    let parent_dir = get_parent_dir(&path, create_parents).await?;
346    if let Some(name) = path.as_ref().file_name() {
347        let name = name.to_string_lossy();
348        child_dir(&parent_dir, &name, create).await
349    } else {
350        Ok(parent_dir)
351    }
352}
353
354async fn get_file<P: AsRef<Path>>(path: P, create: bool) -> Result<FileSystemFileHandle> {
355    let parent_dir = get_parent_dir(&path, false).await?;
356    if let Some(name) = path.as_ref().file_name() {
357        let name = name.to_string_lossy();
358        child_file(&parent_dir, &name, create).await
359    } else {
360        Err(Error::from(ErrorKind::AlreadyExists))
361    }
362}
363
364pub async fn create_dir<P: AsRef<Path>>(path: P) -> Result<()> {
365    get_dir(path, true, false).await?;
366    Ok(())
367}
368pub async fn create_dir_all<P: AsRef<Path>>(path: P) -> Result<()> {
369    get_dir(path, true, true).await?;
370    Ok(())
371}
372
373/// Symlink is not supported.
374#[derive(Debug, Clone, Copy, PartialEq, Eq)]
375pub enum FileType {
376    File,
377    Dir,
378}
379impl FileType {
380    pub fn is_dir(&self) -> bool {
381        *self == Self::Dir
382    }
383    pub fn is_file(&self) -> bool {
384        *self == Self::File
385    }
386    pub fn is_symlink(&self) -> bool {
387        false
388    }
389}
390
391#[derive(Debug)]
392pub struct DirEntry {
393    name: OsString,
394    file_type: FileType,
395    path: PathBuf,
396}
397impl DirEntry {
398    pub fn file_name(&self) -> OsString {
399        self.name.clone()
400    }
401    /// Symlink is not supported. This does not actually require to async. It is async to be compatible with async-fs.
402    pub async fn file_type(&self) -> Result<FileType> {
403        Ok(self.file_type)
404    }
405    /// Currently not supported.
406    pub async fn metadata(&self) -> Result<std::fs::Metadata> {
407        Err(Error::other("Metadata is not supported currently"))
408    }
409    pub fn path(&self) -> PathBuf {
410        self.path.clone()
411    }
412}
413
414pub async fn read_dir<P: AsRef<Path>>(path: P) -> Result<impl Stream<Item = Result<DirEntry>>> {
415    let dir = get_dir(&path, false, false).await?;
416    let stream = JsStream::from(dir.entries());
417    let read_dir = stream.map(move |v| {
418        let entry = v.map_err(|e| js_value_to_error(e))?;
419        const RESOLVE_ENTRY_ERROR: &str =
420            "Getting the key and value of the dir entry failed, this is an error of the crate.";
421        let key = Reflect::get_u32(&entry, 0)
422            .expect(RESOLVE_ENTRY_ERROR)
423            .as_string()
424            .expect("This is supposed to be a string, else this is an error of the crate.");
425        let value = Reflect::get_u32(&entry, 1).expect(RESOLVE_ENTRY_ERROR);
426
427        let mut path = path.as_ref().to_path_buf();
428        path.push(&key);
429        let name = OsString::from(key);
430        if let Some(_) = value.dyn_ref::<FileSystemFileHandle>() {
431            Ok(DirEntry {
432                name,
433                file_type: FileType::File,
434                path,
435            })
436        } else {
437            Ok(DirEntry {
438                name,
439                file_type: FileType::Dir,
440                path,
441            })
442        }
443    });
444    Ok(read_dir)
445}
446
447/// Currently `remove_dir()` and `remove_file()` work the same.
448pub async fn remove_dir<P: AsRef<Path>>(path: P) -> Result<()> {
449    let parent_dir = get_parent_dir(&path, false).await?;
450    let name = path
451        .as_ref()
452        .file_name()
453        .ok_or(Error::from(ErrorKind::NotFound))?
454        .to_string_lossy();
455
456    JsFuture::from(parent_dir.remove_entry(&name))
457        .await
458        .map_err(|e| js_value_to_error(e))?;
459
460    Ok(())
461}
462
463/// Currently `remove_dir()` and `remove_file()` work the same.
464pub async fn remove_file<P: AsRef<Path>>(path: P) -> Result<()> {
465    remove_dir(path).await
466}
467
468pub async fn remove_dir_all<P: AsRef<Path>>(path: P) -> Result<()> {
469    let parent_dir = get_parent_dir(&path, false).await?;
470    let name = path
471        .as_ref()
472        .file_name()
473        .ok_or(Error::from(ErrorKind::NotFound))?
474        .to_string_lossy();
475
476    let options = FileSystemRemoveOptions::new();
477    options.set_recursive(true);
478
479    JsFuture::from(parent_dir.remove_entry_with_options(&name, &options))
480        .await
481        .map_err(|e| js_value_to_error(e))?;
482
483    Ok(())
484}
485
486pub async fn read<P: AsRef<Path>>(path: P) -> Result<Vec<u8>> {
487    let mut file = File::open(path).await?;
488    let mut buf = Vec::new();
489    file.read_to_end(&mut buf).await?;
490    Ok(buf)
491}
492
493pub async fn read_to_string<P: AsRef<Path>>(path: P) -> Result<String> {
494    let mut file = File::open(path).await?;
495    let mut buf = String::new();
496    file.read_to_string(&mut buf).await?;
497    Ok(buf)
498}
499
500pub async fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
501    let mut file = File::create_new(path).await?;
502    file.write_all(contents.as_ref()).await.unwrap();
503    Ok(())
504}
505
506pub async fn copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<u64> {
507    let mut src = File::open(from).await?;
508    let mut dst = File::create_new(to).await?;
509    let buf_size = src.size.min(1 << 6) as usize;
510    let mut buf = vec![0; buf_size];
511    loop {
512        let read_size = src.read(&mut buf).await?;
513        if read_size == 0 {
514            break;
515        }
516        dst.write_all(&buf[0..read_size]).await?;
517        buf[0..read_size].fill(0);
518    }
519    Ok(src.size)
520}