dioxus_web/
file_engine.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
use std::any::Any;

use dioxus_html::FileEngine;
use futures_channel::oneshot;
use js_sys::Uint8Array;
use wasm_bindgen::{prelude::Closure, JsCast};
use web_sys::{File, FileList, FileReader};

/// A file engine for the web platform
pub struct WebFileEngine {
    file_reader: FileReader,
    file_list: FileList,
}

impl WebFileEngine {
    /// Create a new file engine from a file list
    pub fn new(file_list: FileList) -> Option<Self> {
        Some(Self {
            file_list,
            file_reader: FileReader::new().ok()?,
        })
    }

    fn len(&self) -> usize {
        self.file_list.length() as usize
    }

    fn get(&self, index: usize) -> Option<File> {
        self.file_list.item(index as u32)
    }

    fn find(&self, name: &str) -> Option<File> {
        (0..self.len())
            .filter_map(|i| self.get(i))
            .find(|f| f.name() == name)
    }
}

#[async_trait::async_trait(?Send)]
impl FileEngine for WebFileEngine {
    fn files(&self) -> Vec<String> {
        (0..self.len())
            .filter_map(|i| self.get(i).map(|f| f.name()))
            .collect()
    }

    async fn file_size(&self, file: &str) -> Option<u64> {
        let file = self.find(file)?;
        Some(file.size() as u64)
    }

    // read a file to bytes
    async fn read_file(&self, file: &str) -> Option<Vec<u8>> {
        let file = self.find(file)?;

        let file_reader = self.file_reader.clone();
        let (rx, tx) = oneshot::channel();
        let on_load: Closure<dyn FnMut()> = Closure::new({
            let mut rx = Some(rx);
            move || {
                let result = file_reader.result();
                let _ = rx
                    .take()
                    .expect("multiple files read without refreshing the channel")
                    .send(result);
            }
        });

        self.file_reader
            .set_onload(Some(on_load.as_ref().unchecked_ref()));
        on_load.forget();
        self.file_reader.read_as_array_buffer(&file).ok()?;

        if let Ok(Ok(js_val)) = tx.await {
            let as_u8_arr = Uint8Array::new(&js_val);
            let as_u8_vec = as_u8_arr.to_vec();

            Some(as_u8_vec)
        } else {
            None
        }
    }

    // read a file to string
    async fn read_file_to_string(&self, file: &str) -> Option<String> {
        let file = self.find(file)?;

        let file_reader = self.file_reader.clone();
        let (rx, tx) = oneshot::channel();
        let on_load: Closure<dyn FnMut()> = Closure::new({
            let mut rx = Some(rx);
            move || {
                let result = file_reader.result();
                let _ = rx
                    .take()
                    .expect("multiple files read without refreshing the channel")
                    .send(result);
            }
        });

        self.file_reader
            .set_onload(Some(on_load.as_ref().unchecked_ref()));
        on_load.forget();
        self.file_reader.read_as_text(&file).ok()?;

        if let Ok(Ok(js_val)) = tx.await {
            js_val.as_string()
        } else {
            None
        }
    }

    async fn get_native_file(&self, file: &str) -> Option<Box<dyn Any>> {
        let file = self.find(file)?;
        Some(Box::new(file))
    }
}

/// Helper trait for WebFileEngine
#[async_trait::async_trait(?Send)]
pub trait WebFileEngineExt {
    /// returns web_sys::File
    async fn get_web_file(&self, file: &str) -> Option<web_sys::File>;
}

#[async_trait::async_trait(?Send)]
impl WebFileEngineExt for std::sync::Arc<dyn FileEngine> {
    async fn get_web_file(&self, file: &str) -> Option<web_sys::File> {
        let native_file = self.get_native_file(file).await?;
        let ret = native_file.downcast::<web_sys::File>().ok()?;
        Some(*ret)
    }
}