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