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