1use std::io::{Error, ErrorKind, Result};
42
43use js_sys::Promise;
44use wasm_bindgen::{JsCast, JsValue};
45use wasm_bindgen_futures::JsFuture;
46use web_sys::{
47 DomException, Exception, FileSystemDirectoryHandle, FileSystemFileHandle, StorageManager,
48 WorkerGlobalScope,
49};
50
51mod directory;
52mod external;
53mod file;
54mod metadata;
55mod open_options;
56mod read;
57mod seek;
58mod write;
59
60pub use directory::*;
61pub use file::*;
62pub use metadata::*;
63pub use open_options::*;
64
65fn from_js_error(value: JsValue) -> Error {
66 if value.is_instance_of::<DomException>() {
67 let handle = value.unchecked_into::<DomException>();
68 return match handle.name().as_str() {
69 "NotAllowedError" => Error::new(ErrorKind::PermissionDenied, handle.message()),
70 "NotFoundError" => Error::new(ErrorKind::NotFound, handle.message()),
71 "TypeMismatchError" => Error::new(ErrorKind::Other, handle.message()),
72 "InvalidModificationError" => {
73 Error::new(ErrorKind::DirectoryNotEmpty, handle.message())
74 }
75 _ => Error::other(handle.name()),
76 };
77 }
78 if value.is_instance_of::<Exception>() {
79 let handle = value.unchecked_into::<Exception>();
80 return Error::other(handle.name());
81 }
82 if let Some(err) = value.dyn_ref::<web_sys::js_sys::TypeError>() {
83 let message: String = err.message().into();
84 return Error::new(ErrorKind::InvalidInput, message);
85 }
86 Error::other("unknown error")
87}
88
89fn from_js<V: JsCast>(value: JsValue) -> Result<V> {
90 value.dyn_into::<V>().map_err(from_js_error)
91}
92
93async fn resolve<V: JsCast>(promise: Promise) -> Result<V> {
94 from_js(JsFuture::from(promise).await.map_err(from_js_error)?)
95}
96
97async fn resolve_undefined(promise: Promise) -> Result<()> {
98 JsFuture::from(promise).await.map_err(from_js_error)?;
99 Ok(())
100}
101
102async fn storage() -> Result<StorageManager> {
103 if js_sys::global().is_instance_of::<WorkerGlobalScope>() {
104 let global = js_sys::global().unchecked_into::<WorkerGlobalScope>();
105 return Ok(global.navigator().storage());
106 }
107 Err(Error::new(
108 ErrorKind::Unsupported,
109 "storage manage not accessible",
110 ))
111}
112
113async fn root_directory() -> Result<FileSystemDirectoryHandle> {
114 let storage = storage().await?;
115 let res = JsFuture::from(storage.get_directory())
116 .await
117 .map_err(|_| Error::new(ErrorKind::Unsupported, "unable to access root directory"))?;
118 res.dyn_into::<FileSystemDirectoryHandle>()
119 .map_err(|_| Error::new(ErrorKind::Unsupported, "unable to cast root directory"))
120}
121
122#[derive(Clone, Copy, Debug)]
123pub enum FileType {
124 File,
125 Directory,
126}
127
128impl FileType {
129 pub const fn is_dir(&self) -> bool {
130 matches!(self, Self::Directory)
131 }
132
133 pub const fn is_file(&self) -> bool {
134 matches!(self, Self::File)
135 }
136
137 pub const fn is_symlink(&self) -> bool {
138 false
139 }
140}
141
142#[derive(Debug)]
143enum Entry {
144 Directory(FileSystemDirectoryHandle),
145 File(FileSystemFileHandle),
146}
147
148impl Entry {
149 fn name(&self) -> String {
150 match self {
151 Self::Directory(inner) => inner.name(),
152 Self::File(inner) => inner.name(),
153 }
154 }
155
156 fn try_from_js_value(value: JsValue) -> Result<Self> {
157 if value.is_instance_of::<FileSystemFileHandle>() {
158 Ok(Self::File(value.unchecked_into()))
159 } else if value.is_instance_of::<FileSystemDirectoryHandle>() {
160 Ok(Self::Directory(value.unchecked_into()))
161 } else {
162 Err(Error::new(ErrorKind::InvalidData, "unable to caste handle"))
163 }
164 }
165
166 fn file_type(&self) -> FileType {
167 match self {
168 Self::Directory(_) => FileType::Directory,
169 Self::File(_) => FileType::File,
170 }
171 }
172}
173
174impl Entry {
175 async fn from_directory(parent: &FileSystemDirectoryHandle, name: &str) -> Result<Self> {
176 let dir_promise = parent.get_directory_handle(name);
177 let dir_res = crate::resolve::<FileSystemDirectoryHandle>(dir_promise).await;
178
179 let file_promise = parent.get_file_handle(name);
180 let file_res = crate::resolve::<FileSystemFileHandle>(file_promise).await;
181
182 dir_res.map(Self::Directory).or(file_res.map(Self::File))
183 }
184}