1use std::fmt::Debug;
2use futures::Stream;
3use futures::StreamExt;
4use js_sys::{ArrayBuffer, AsyncIterator, Uint8Array};
5use wasm_bindgen::{JsCast, JsValue};
6use wasm_bindgen_futures::{JsFuture, stream::JsStream};
7
8pub struct GetFileHandleOptions {
9 pub create: bool,
10}
11
12impl Default for GetFileHandleOptions {
13 fn default() -> Self {
14 Self {
15 create: false,
16 }
17 }
18}
19
20pub struct GetDirectoryHandleOptions {
21 pub create: bool,
22}
23
24impl Default for GetDirectoryHandleOptions {
25 fn default() -> Self {
26 Self {
27 create: false,
28 }
29 }
30}
31
32pub struct CreateWritableOptions {
33 pub keep_existing_data: bool,
34}
35
36impl Default for CreateWritableOptions {
37 fn default() -> Self {
38 Self {
39 keep_existing_data: false,
40 }
41 }
42}
43
44pub struct FileSystemRemoveOptions {
45 pub recursive: bool,
46}
47
48impl Default for FileSystemRemoveOptions {
49 fn default() -> Self {
50 Self {
51 recursive: false,
52 }
53 }
54}
55
56#[derive(Debug, Clone)]
57pub enum DirectoryEntry {
58 File(FileHandle),
59 Directory(DirectoryHandle),
60}
61
62pub async fn storage_directory() -> std::io::Result<DirectoryHandle> {
64 use wasm_bindgen_futures::JsFuture;
65 use web_sys::FileSystemDirectoryHandle;
66
67 let window = web_sys::window().ok_or(std::io::Error::new(std::io::ErrorKind::Other, "No window object"))?;
68 let navigator = window.navigator();
69
70 let root_directory_handle =
71 FileSystemDirectoryHandle::from(map_io_result(JsFuture::from(navigator.storage().get_directory()).await)?);
72
73 Ok(DirectoryHandle::from(root_directory_handle))
74}
75
76#[derive(Debug, Clone)]
77pub struct DirectoryHandle(web_sys::FileSystemDirectoryHandle);
78
79#[derive(Debug, Clone)]
80pub struct FileHandle(web_sys::FileSystemFileHandle);
81
82#[derive(Debug, Clone)]
83pub struct WritableFileStream(web_sys::FileSystemWritableFileStream);
84
85#[derive(Debug, Clone)]
86pub struct Blob(web_sys::Blob);
87
88#[derive(Debug, Clone)]
89pub struct File(web_sys::File);
90
91#[derive(Debug, Clone)]
92pub struct FileList(web_sys::FileList);
93
94pub struct FileListIter {
95 list: FileList,
96 index: usize,
97}
98
99impl From<web_sys::FileSystemDirectoryHandle> for DirectoryHandle {
100 fn from(handle: web_sys::FileSystemDirectoryHandle) -> Self {
101 Self(handle)
102 }
103}
104
105impl From<web_sys::FileSystemFileHandle> for FileHandle {
106 fn from(handle: web_sys::FileSystemFileHandle) -> Self {
107 Self(handle)
108 }
109}
110
111impl From<web_sys::FileSystemWritableFileStream> for WritableFileStream {
112 fn from(handle: web_sys::FileSystemWritableFileStream) -> Self {
113 Self(handle)
114 }
115}
116
117impl From<web_sys::Blob> for Blob {
118 fn from(handle: web_sys::Blob) -> Self {
119 Self(handle)
120 }
121}
122
123impl From<web_sys::File> for File {
124 fn from(handle: web_sys::File) -> Self {
125 Self(handle)
126 }
127}
128
129impl From<web_sys::FileList> for FileList {
130 fn from(handle: web_sys::FileList) -> Self {
131 Self(handle)
132 }
133}
134
135impl DirectoryHandle {
136 pub fn name(&self) -> String {
138 self.0.name()
139 }
140
141 pub async fn eq(&self, other: &Self) -> bool {
143 map_io_result(JsFuture::from(self.0.is_same_entry(&other.0.clone().dyn_into().unwrap())).await).unwrap().is_truthy()
144 }
145
146 pub async fn get_file_handle(&self, name: &str) -> std::io::Result<FileHandle> {
147 self.get_file_handle_with_options(name, &Default::default()).await
148 }
149
150 pub async fn get_file_handle_with_options(
151 &self,
152 name: &str,
153 options: &crate::GetFileHandleOptions,
154 ) -> std::io::Result<FileHandle> {
155 let fs_options = web_sys::FileSystemGetFileOptions::new();
156 fs_options.set_create(options.create);
157 let file_system_file_handle = web_sys::FileSystemFileHandle::from(
158 map_io_result(JsFuture::from(self.0.get_file_handle_with_options(name, &fs_options)).await)?,
159 );
160 Ok(FileHandle(file_system_file_handle))
161 }
162
163 pub async fn get_directory_handle(&self, name: &str) -> std::io::Result<Self> {
164 self.get_directory_handle_with_options(name, &Default::default()).await
165 }
166
167 pub async fn get_directory_handle_with_options(
168 &self,
169 name: &str,
170 options: &crate::GetDirectoryHandleOptions,
171 ) -> std::io::Result<Self> {
172 let fs_options = web_sys::FileSystemGetDirectoryOptions::new();
173 fs_options.set_create(options.create);
174 let file_system_directory_handle = web_sys::FileSystemDirectoryHandle::from(
175 map_io_result(JsFuture::from(self.0.get_directory_handle_with_options(name, &fs_options)).await)?,
176 );
177 Ok(DirectoryHandle(file_system_directory_handle))
178 }
179
180 pub async fn remove_entry(&mut self, name: &str) -> std::io::Result<()> {
181 map_io_result(JsFuture::from(self.0.remove_entry(name)).await)?;
182 Ok(())
183 }
184
185 pub async fn remove_entry_with_options(
186 &mut self,
187 name: &str,
188 options: &FileSystemRemoveOptions,
189 ) -> std::io::Result<()> {
190 let fs_options = web_sys::FileSystemRemoveOptions::new();
191 fs_options.set_recursive(options.recursive);
192 map_io_result(JsFuture::from(self.0.remove_entry_with_options(name, &fs_options)).await)?;
193 Ok(())
194 }
195
196 pub async fn entries(
197 &self,
198 ) -> std::io::Result<impl Stream<Item = std::io::Result<(String, DirectoryEntry)>>>
199 {
200 let entries_iterator = self.0.entries();
201 let async_iterator = AsyncIterator::from(entries_iterator);
202 let js_stream: JsStream = JsStream::from(async_iterator);
203
204 let stream = js_stream.map(|item| {
205 map_io_result(match item {
206 Ok(js_array) => {
207 let array = js_sys::Array::from(&js_array);
209 let filename = array
210 .get(0)
211 .as_string()
212 .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "Invalid filename"))?;
213 let handle = array.get(1);
214
215 let entry = if handle.has_type::<web_sys::FileSystemFileHandle>() {
217 DirectoryEntry::File(FileHandle(web_sys::FileSystemFileHandle::from(handle)))
218 } else if handle.has_type::<web_sys::FileSystemDirectoryHandle>() {
219 DirectoryEntry::Directory(DirectoryHandle(web_sys::FileSystemDirectoryHandle::from(
220 handle,
221 )))
222 } else {
223 return Err(std::io::Error::new(std::io::ErrorKind::Other, "Unknown handle type"));
224 };
225
226 Ok((filename, entry))
227 }
228 Err(e) => Err(e),
229 })
230 });
231
232 Ok(stream)
233 }
234}
235
236impl FileHandle {
237 pub fn name(&self) -> String {
239 self.0.name()
240 }
241
242 pub async fn eq(&self, other: &Self) -> bool {
244 map_io_result(JsFuture::from(self.0.is_same_entry(&other.0.clone().dyn_into().unwrap())).await).unwrap().is_truthy()
245 }
246
247 pub async fn create_writable(&mut self) -> std::io::Result<WritableFileStream> {
248 self.create_writable_with_options(&Default::default()).await
249 }
250
251 pub async fn create_writable_with_options(
252 &mut self,
253 options: &crate::CreateWritableOptions,
254 ) -> std::io::Result<WritableFileStream> {
255 let fs_options = web_sys::FileSystemCreateWritableOptions::new();
256 fs_options.set_keep_existing_data(options.keep_existing_data);
257 let file_system_writable_file_stream = web_sys::FileSystemWritableFileStream::unchecked_from_js(
258 map_io_result(JsFuture::from(self.0.create_writable_with_options(&fs_options)).await)?,
259 );
260 Ok(WritableFileStream(file_system_writable_file_stream))
261 }
262
263 pub async fn read(&self) -> std::io::Result<Vec<u8>> {
264 self.get_blob().await?.binary().await
265 }
266
267 pub async fn size(&self) -> std::io::Result<usize> {
268 let size = self.get_blob().await?.size();
269 Ok(size)
270 }
271}
272
273impl FileHandle {
274 pub async fn get_blob(&self) -> std::io::Result<Blob> {
275 let file: web_sys::Blob = map_io_result(JsFuture::from(self.0.get_file()).await)?.into();
276 Ok(Blob(file))
277 }
278
279 pub async fn get_file(&self) -> std::io::Result<File> {
280 let file: web_sys::File = map_io_result(JsFuture::from(self.0.get_file()).await)?.into();
281 Ok(File(file))
282 }
283}
284
285impl WritableFileStream {
286 pub async fn write(&mut self, data: Vec<u8>) -> std::io::Result<()> {
287 let uint8_array = js_sys::Uint8Array::from(data.as_slice());
295 let array = js_sys::Array::new();
296 array.push(&uint8_array);
297 let blob = map_io_result(web_sys::Blob::new_with_u8_array_sequence(&array))?;
298
299 map_io_result(JsFuture::from(map_io_result(self.0.write_with_blob(&blob))?).await)?;
300 Ok(())
301 }
302
303 pub async fn close(&mut self) -> std::io::Result<()> {
304 map_io_result(JsFuture::from(self.0.close()).await)?;
305 Ok(())
306 }
307
308 pub async fn seek(&mut self, offset: usize) -> std::io::Result<()> {
309 map_io_result(JsFuture::from(map_io_result(self.0.seek_with_u32(offset as u32))?).await)?;
310 Ok(())
311 }
312}
313
314impl Blob {
315 pub fn size(&self) -> usize {
316 self.0.size() as usize
317 }
318
319 pub async fn binary(&self) -> std::io::Result<Vec<u8>> {
320 let buffer = ArrayBuffer::unchecked_from_js(map_io_result(JsFuture::from(self.0.array_buffer()).await)?);
321 let uint8_array = Uint8Array::new(&buffer);
322 let mut vec = vec![0; self.size()];
323 uint8_array.copy_to(&mut vec);
324 Ok(vec)
325 }
326
327 #[allow(dead_code)]
328 pub async fn text(&self) -> std::io::Result<String> {
329 map_io_result(JsFuture::from(self.0.text()).await)?
330 .as_string()
331 .ok_or(std::io::Error::new(std::io::ErrorKind::Other, "Unknown error"))
332 }
333}
334
335impl File {
336 pub fn last_modified(&self) -> std::time::SystemTime {
338 std::time::SystemTime::UNIX_EPOCH.checked_add(
339 std::time::Duration::from_millis(unsafe { self.0.last_modified().to_int_unchecked() })
340 ).unwrap()
341 }
342
343 pub fn name(&self) -> String {
345 self.0.name()
346 }
347
348 pub fn as_blob(&self) -> Blob {
350 Blob::from(self.0.clone().dyn_into::<web_sys::Blob>().unwrap())
351 }
352}
353
354impl FileList {
355 pub fn len(&self) -> usize {
356 self.0.length() as usize
357 }
358
359 pub fn iter(&self) -> FileListIter {
360 self.clone().into_iter()
361 }
362
363 pub fn get(&self, index: usize) -> Option<File> {
364 self.0.item(index as u32).map(|f| File::from(f))
365 }
366}
367
368impl std::iter::IntoIterator for FileList {
369 type Item = File;
370 type IntoIter = FileListIter;
371
372 fn into_iter(self) -> Self::IntoIter {
373 FileListIter {
374 list: self,
375 index: 0,
376 }
377 }
378}
379
380impl std::iter::Iterator for FileListIter {
381 type Item = File;
382
383 fn next(&mut self) -> Option<Self::Item> {
384 let Some(item) = self.list.0.item(self.index as u32) else {
385 return None;
386 };
387 self.index += 1;
388 Some(File::from(item))
389 }
390}
391
392fn map_io_result<T>(result: Result<T, JsValue>) -> std::io::Result<T> {
393 result.map_err(|e| {
394 use wasm_bindgen::JsCast;
395 let Ok(e) = e.dyn_into::<js_sys::Error>() else {
396 return std::io::Error::new(std::io::ErrorKind::Other, "Unknown error");
397 };
398 std::io::Error::new(std::io::ErrorKind::Other, e.to_string().as_string().unwrap())
399 })
400}