dioxus_html/
file_data.rs

1use bytes::Bytes;
2use futures_util::Stream;
3use std::{path::PathBuf, pin::Pin, prelude::rust_2024::Future};
4
5#[derive(Clone)]
6pub struct FileData {
7    inner: std::sync::Arc<dyn NativeFileData>,
8}
9
10impl FileData {
11    pub fn new(inner: impl NativeFileData + 'static) -> Self {
12        Self {
13            inner: std::sync::Arc::new(inner),
14        }
15    }
16
17    pub fn content_type(&self) -> Option<String> {
18        self.inner.content_type()
19    }
20
21    pub fn name(&self) -> String {
22        self.inner.name()
23    }
24
25    pub fn size(&self) -> u64 {
26        self.inner.size()
27    }
28
29    pub fn last_modified(&self) -> u64 {
30        self.inner.last_modified()
31    }
32
33    pub async fn read_bytes(&self) -> Result<Bytes, dioxus_core::Error> {
34        self.inner.read_bytes().await
35    }
36
37    pub async fn read_string(&self) -> Result<String, dioxus_core::Error> {
38        self.inner.read_string().await
39    }
40
41    pub fn byte_stream(
42        &self,
43    ) -> Pin<Box<dyn Stream<Item = Result<Bytes, dioxus_core::Error>> + Send + 'static>> {
44        self.inner.byte_stream()
45    }
46
47    pub fn inner(&self) -> &dyn std::any::Any {
48        self.inner.inner()
49    }
50
51    pub fn path(&self) -> PathBuf {
52        self.inner.path()
53    }
54}
55
56impl PartialEq for FileData {
57    fn eq(&self, other: &Self) -> bool {
58        self.name() == other.name()
59            && self.size() == other.size()
60            && self.last_modified() == other.last_modified()
61            && self.path() == other.path()
62    }
63}
64
65pub trait NativeFileData: Send + Sync {
66    fn name(&self) -> String;
67    fn size(&self) -> u64;
68    fn last_modified(&self) -> u64;
69    fn path(&self) -> PathBuf;
70    fn content_type(&self) -> Option<String>;
71    fn read_bytes(
72        &self,
73    ) -> Pin<Box<dyn Future<Output = Result<Bytes, dioxus_core::Error>> + 'static>>;
74    fn byte_stream(
75        &self,
76    ) -> Pin<Box<dyn futures_util::Stream<Item = Result<Bytes, dioxus_core::Error>> + 'static + Send>>;
77    fn read_string(
78        &self,
79    ) -> Pin<Box<dyn Future<Output = Result<String, dioxus_core::Error>> + 'static>>;
80    fn inner(&self) -> &dyn std::any::Any;
81}
82
83impl std::fmt::Debug for FileData {
84    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85        f.debug_struct("FileData")
86            .field("name", &self.inner.name())
87            .field("size", &self.inner.size())
88            .field("last_modified", &self.inner.last_modified())
89            .finish()
90    }
91}
92
93pub trait HasFileData: std::any::Any {
94    fn files(&self) -> Vec<FileData>;
95}
96
97#[cfg(feature = "serialize")]
98pub use serialize::*;
99
100#[cfg(feature = "serialize")]
101mod serialize {
102    use super::*;
103
104    /// A serializable representation of file data
105    #[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]
106    pub struct SerializedFileData {
107        pub path: PathBuf,
108        pub size: u64,
109        pub last_modified: u64,
110        pub content_type: Option<String>,
111        pub contents: Option<bytes::Bytes>,
112    }
113
114    impl SerializedFileData {
115        /// Create a new empty serialized file data object
116        pub fn empty() -> Self {
117            Self {
118                path: PathBuf::new(),
119                size: 0,
120                last_modified: 0,
121                content_type: None,
122                contents: None,
123            }
124        }
125    }
126
127    impl NativeFileData for SerializedFileData {
128        fn name(&self) -> String {
129            self.path()
130                .file_name()
131                .unwrap()
132                .to_string_lossy()
133                .into_owned()
134        }
135
136        fn size(&self) -> u64 {
137            self.size
138        }
139
140        fn last_modified(&self) -> u64 {
141            self.last_modified
142        }
143
144        fn read_bytes(
145            &self,
146        ) -> Pin<Box<dyn Future<Output = Result<Bytes, dioxus_core::Error>> + 'static>> {
147            let contents = self.contents.clone();
148            let path = self.path.clone();
149
150            Box::pin(async move {
151                if let Some(contents) = contents {
152                    return Ok(contents);
153                }
154
155                #[cfg(not(target_arch = "wasm32"))]
156                if path.exists() {
157                    return Ok(std::fs::read(path).map(Bytes::from)?);
158                }
159
160                Err(dioxus_core::Error::msg("File contents not available"))
161            })
162        }
163
164        fn read_string(
165            &self,
166        ) -> Pin<Box<dyn Future<Output = Result<String, dioxus_core::Error>> + 'static>> {
167            let contents = self.contents.clone();
168            let path = self.path.clone();
169
170            Box::pin(async move {
171                if let Some(contents) = contents {
172                    return Ok(String::from_utf8(contents.to_vec())?);
173                }
174
175                #[cfg(not(target_arch = "wasm32"))]
176                if path.exists() {
177                    return Ok(std::fs::read_to_string(path)?);
178                }
179
180                Err(dioxus_core::Error::msg("File contents not available"))
181            })
182        }
183
184        fn byte_stream(
185            &self,
186        ) -> Pin<
187            Box<
188                dyn futures_util::Stream<Item = Result<Bytes, dioxus_core::Error>> + 'static + Send,
189            >,
190        > {
191            let contents = self.contents.clone();
192            let path = self.path.clone();
193
194            Box::pin(futures_util::stream::once(async move {
195                if let Some(contents) = contents {
196                    return Ok(contents);
197                }
198
199                #[cfg(not(target_arch = "wasm32"))]
200                if path.exists() {
201                    return Ok(std::fs::read(path).map(Bytes::from)?);
202                }
203
204                Err(dioxus_core::Error::msg("File contents not available"))
205            }))
206        }
207
208        fn inner(&self) -> &dyn std::any::Any {
209            self
210        }
211
212        fn path(&self) -> PathBuf {
213            self.path.clone()
214        }
215
216        fn content_type(&self) -> Option<String> {
217            self.content_type.clone()
218        }
219    }
220
221    impl<'de> serde::Deserialize<'de> for FileData {
222        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
223        where
224            D: serde::Deserializer<'de>,
225        {
226            let sfd = SerializedFileData::deserialize(deserializer)?;
227            Ok(FileData::new(sfd))
228        }
229    }
230}