Skip to main content

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        /// Create serialized file metadata without eagerly reading the file contents.
134        pub(crate) fn from_file_data(file_data: &FileData) -> Self {
135            Self {
136                path: file_data.path(),
137                size: file_data.size(),
138                last_modified: file_data.last_modified(),
139                content_type: file_data.content_type(),
140                contents: None,
141            }
142        }
143    }
144
145    impl NativeFileData for SerializedFileData {
146        fn name(&self) -> String {
147            self.path()
148                .file_name()
149                .unwrap()
150                .to_string_lossy()
151                .into_owned()
152        }
153
154        fn size(&self) -> u64 {
155            self.size
156        }
157
158        fn last_modified(&self) -> u64 {
159            self.last_modified
160        }
161
162        fn read_bytes(
163            &self,
164        ) -> Pin<Box<dyn Future<Output = Result<Bytes, dioxus_core::CapturedError>> + 'static>>
165        {
166            let contents = self.contents.clone();
167            let path = self.path.clone();
168
169            Box::pin(async move {
170                if let Some(contents) = contents {
171                    return Ok(contents);
172                }
173
174                #[cfg(not(target_arch = "wasm32"))]
175                if path.exists() {
176                    return Ok(std::fs::read(path).map(Bytes::from)?);
177                }
178
179                Err(dioxus_core::CapturedError::msg(
180                    "File contents not available",
181                ))
182            })
183        }
184
185        fn read_string(
186            &self,
187        ) -> Pin<Box<dyn Future<Output = Result<String, dioxus_core::CapturedError>> + 'static>>
188        {
189            let contents = self.contents.clone();
190            let path = self.path.clone();
191
192            Box::pin(async move {
193                if let Some(contents) = contents {
194                    return Ok(String::from_utf8(contents.to_vec())?);
195                }
196
197                #[cfg(not(target_arch = "wasm32"))]
198                if path.exists() {
199                    return Ok(std::fs::read_to_string(path)?);
200                }
201
202                Err(dioxus_core::CapturedError::msg(
203                    "File contents not available",
204                ))
205            })
206        }
207
208        fn byte_stream(
209            &self,
210        ) -> Pin<
211            Box<
212                dyn futures_util::Stream<Item = Result<Bytes, dioxus_core::CapturedError>>
213                    + 'static
214                    + Send,
215            >,
216        > {
217            let contents = self.contents.clone();
218            let path = self.path.clone();
219
220            Box::pin(futures_util::stream::once(async move {
221                if let Some(contents) = contents {
222                    return Ok(contents);
223                }
224
225                #[cfg(not(target_arch = "wasm32"))]
226                if path.exists() {
227                    return Ok(std::fs::read(path).map(Bytes::from)?);
228                }
229
230                Err(dioxus_core::CapturedError::msg(
231                    "File contents not available",
232                ))
233            }))
234        }
235
236        fn inner(&self) -> &dyn std::any::Any {
237            self
238        }
239
240        fn path(&self) -> PathBuf {
241            self.path.clone()
242        }
243
244        fn content_type(&self) -> Option<String> {
245            self.content_type.clone()
246        }
247    }
248
249    impl<'de> serde::Deserialize<'de> for FileData {
250        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
251        where
252            D: serde::Deserializer<'de>,
253        {
254            let sfd = SerializedFileData::deserialize(deserializer)?;
255            Ok(FileData::new(sfd))
256        }
257    }
258}