web_fs/
lib.rs

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