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 #[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 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 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}