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