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 Component::Prefix(_) => return Err(Error::from(ErrorKind::PermissionDenied)),
319 Component::CurDir | Component::RootDir => (),
320 Component::ParentDir => {
321 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#[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 pub async fn file_type(&self) -> Result<FileType> {
403 Ok(self.file_type)
404 }
405 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
447pub 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
463pub 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}