vizia_core/resource/
file_resource_loader.rs1use crate::context::ResourceContext;
2
3#[cfg(feature = "tokio")]
4use super::ResourceLoadExecution;
5use super::{LoadingStatus, ResourceLoader, ResourceRequest};
6
7pub struct FileResourceLoader;
9
10fn is_http_url(path: &str) -> bool {
11 path.starts_with("http://") || path.starts_with("https://")
12}
13
14fn is_likely_svg(path: &str, data: &[u8]) -> bool {
15 if path.to_ascii_lowercase().ends_with(".svg") {
16 return true;
17 }
18
19 let trimmed = data
20 .strip_prefix(&[0xEF, 0xBB, 0xBF])
21 .unwrap_or(data)
22 .iter()
23 .copied()
24 .skip_while(|b| b.is_ascii_whitespace())
25 .collect::<Vec<u8>>();
26
27 trimmed.starts_with(b"<svg") || trimmed.starts_with(b"<?xml")
28}
29
30#[cfg(not(feature = "tokio"))]
31impl ResourceLoader for FileResourceLoader {
32 fn load(
33 &self,
34 request: ResourceRequest,
35 _options: super::ResourceLoadOptions,
36 cx: &mut ResourceContext,
37 ) -> bool {
38 match request {
39 ResourceRequest::Image(req) => {
40 if is_http_url(&req.path) {
41 return false;
42 }
43
44 let path = if req.path.starts_with("file://") {
45 req.path.strip_prefix("file://").unwrap_or(&req.path).to_string()
46 } else {
47 req.path.clone()
48 };
49
50 cx.resource_manager.set_resource_status(req.path.clone(), LoadingStatus::Loading);
52
53 if let Ok(data) = std::fs::read(&path) {
55 if let Some(image) =
56 skia_safe::Image::from_encoded(skia_safe::Data::new_copy(&data))
57 {
58 cx.load_image(req.name.clone(), image, req.policy);
59 cx.resource_manager
60 .set_resource_status(req.path.clone(), LoadingStatus::Loaded);
61 return true;
62 }
63 if is_likely_svg(&req.path, &data)
64 && cx.load_svg(req.name.clone(), &data, req.policy).is_some()
65 {
66 cx.resource_manager
67 .set_resource_status(req.path.clone(), LoadingStatus::Loaded);
68 return true;
69 }
70 }
71
72 cx.resource_manager.set_resource_status(req.path.clone(), LoadingStatus::Error);
74 true
75 }
76 ResourceRequest::Font(req) => {
77 if is_http_url(&req.path) {
78 return false;
79 }
80
81 let path = if req.path.starts_with("file://") {
82 req.path.strip_prefix("file://").unwrap_or(&req.path).to_string()
83 } else {
84 req.path.clone()
85 };
86
87 cx.resource_manager.set_resource_status(req.path.clone(), LoadingStatus::Loading);
89
90 if let Ok(data) = std::fs::read(&path) {
91 cx.load_font(req.path.clone(), &data);
92 return true;
93 }
94
95 cx.resource_manager.set_resource_status(req.path.clone(), LoadingStatus::Error);
96 true
97 }
98 ResourceRequest::Translation(req) => {
99 if is_http_url(&req.path) {
100 return false;
101 }
102
103 let path = if req.path.starts_with("file://") {
104 req.path.strip_prefix("file://").unwrap_or(&req.path).to_string()
105 } else {
106 req.path.clone()
107 };
108
109 cx.resource_manager.set_resource_status(req.path.clone(), LoadingStatus::Loading);
110
111 if let Ok(data) = std::fs::read(&path) {
112 if let Ok(ftl) = String::from_utf8(data) {
113 cx.load_translation(req.lang, req.path.clone(), &ftl);
114 return true;
115 }
116 }
117
118 cx.resource_manager.set_resource_status(req.path.clone(), LoadingStatus::Error);
119 true
120 }
121 }
122 }
123}
124
125#[cfg(feature = "tokio")]
126impl ResourceLoader for FileResourceLoader {
127 fn load(
128 &self,
129 request: ResourceRequest,
130 options: super::ResourceLoadOptions,
131 cx: &mut ResourceContext,
132 ) -> bool {
133 match request {
134 ResourceRequest::Image(req) => {
135 if is_http_url(&req.path) {
136 return false;
137 }
138
139 let path = if req.path.starts_with("file://") {
140 req.path.strip_prefix("file://").unwrap_or(&req.path).to_string()
141 } else {
142 req.path.clone()
143 };
144
145 let req_name = req.name.clone();
146 let req_path = req.path.clone();
147 let policy = req.policy;
148 let execution = options.execution;
149
150 if matches!(execution, ResourceLoadExecution::Sync) {
151 cx.resource_manager
152 .set_resource_status(req_path.clone(), LoadingStatus::Loading);
153
154 if let Ok(data) = std::fs::read(&path) {
155 if let Some(image) =
156 skia_safe::Image::from_encoded(skia_safe::Data::new_copy(&data))
157 {
158 cx.load_image(req_name, image, policy);
159 cx.resource_manager
160 .set_resource_status(req_path, LoadingStatus::Loaded);
161 return true;
162 }
163 if is_likely_svg(&req_path, &data) {
164 if cx.load_svg(req_name, &data, policy).is_some() {
165 cx.resource_manager
166 .set_resource_status(req_path, LoadingStatus::Loaded);
167 return true;
168 }
169 }
170 }
171
172 cx.resource_manager.set_resource_status(req_path, LoadingStatus::Error);
173 return true;
174 }
175
176 cx.resource_manager.set_resource_status(req_path.clone(), LoadingStatus::Loading);
178
179 cx.spawn_task(
181 crate::context::Task::new(move |_| {
182 let path = path.clone();
183 async move { tokio::fs::read(&path).await }
184 })
185 .on_result(move |result, proxy| {
186 use crate::context::TaskResult;
187 if let TaskResult::Completed(data) = result {
188 let loaded =
189 match proxy.load_image_encoded(req_name.clone(), &data, policy) {
190 Ok(true) => true,
191 Ok(false) => {
192 if is_likely_svg(&req_path, &data) {
193 proxy.load_svg(req_name.clone(), &data, policy).is_ok()
194 } else {
195 false
196 }
197 }
198 Err(_) => false,
199 };
200
201 let _ = proxy.update_resource_status(
202 req_path.clone(),
203 if loaded { LoadingStatus::Loaded } else { LoadingStatus::Error },
204 );
205 } else {
206 let _ = proxy
207 .update_resource_status(req_path.clone(), LoadingStatus::Error);
208 }
209 }),
210 );
211
212 true
213 }
214 ResourceRequest::Font(req) => {
215 if is_http_url(&req.path) {
216 return false;
217 }
218
219 let path = if req.path.starts_with("file://") {
220 req.path.strip_prefix("file://").unwrap_or(&req.path).to_string()
221 } else {
222 req.path.clone()
223 };
224
225 let req_path = req.path.clone();
226 let execution = options.execution;
227
228 if matches!(execution, ResourceLoadExecution::Sync) {
229 cx.resource_manager
230 .set_resource_status(req_path.clone(), LoadingStatus::Loading);
231
232 if let Ok(data) = std::fs::read(&path) {
233 cx.load_font(req_path, &data);
234 return true;
235 }
236
237 cx.resource_manager.set_resource_status(req_path, LoadingStatus::Error);
238 return true;
239 }
240
241 cx.resource_manager.set_resource_status(req_path.clone(), LoadingStatus::Loading);
242
243 cx.spawn_task(
244 crate::context::Task::new(move |_| {
245 let path = path.clone();
246 async move { tokio::fs::read(&path).await }
247 })
248 .on_result(move |result, proxy| {
249 use crate::context::TaskResult;
250 if let TaskResult::Completed(data) = result {
251 if proxy.load_font(req_path.clone(), &data).is_err() {
252 let _ = proxy
253 .update_resource_status(req_path.clone(), LoadingStatus::Error);
254 }
255 } else {
256 let _ = proxy
257 .update_resource_status(req_path.clone(), LoadingStatus::Error);
258 }
259 }),
260 );
261
262 true
263 }
264 ResourceRequest::Translation(req) => {
265 if is_http_url(&req.path) {
266 return false;
267 }
268
269 let path = if req.path.starts_with("file://") {
270 req.path.strip_prefix("file://").unwrap_or(&req.path).to_string()
271 } else {
272 req.path.clone()
273 };
274
275 let req_path = req.path.clone();
276 let lang = req.lang;
277 let execution = options.execution;
278
279 if matches!(execution, ResourceLoadExecution::Sync) {
280 cx.resource_manager
281 .set_resource_status(req_path.clone(), LoadingStatus::Loading);
282
283 if let Ok(data) = std::fs::read(&path) {
284 if let Ok(ftl) = String::from_utf8(data) {
285 cx.load_translation(lang, req_path, &ftl);
286 return true;
287 }
288 }
289
290 cx.resource_manager.set_resource_status(req_path, LoadingStatus::Error);
291 return true;
292 }
293
294 cx.resource_manager.set_resource_status(req_path.clone(), LoadingStatus::Loading);
295
296 cx.spawn_task(
297 crate::context::Task::new(move |_| {
298 let path = path.clone();
299 async move { tokio::fs::read(&path).await }
300 })
301 .on_result(move |result, proxy| {
302 use crate::context::TaskResult;
303 if let TaskResult::Completed(data) = result {
304 match String::from_utf8(data) {
305 Ok(ftl) => {
306 if proxy
307 .load_translation(lang.clone(), req_path.clone(), ftl)
308 .is_err()
309 {
310 let _ = proxy.update_resource_status(
311 req_path.clone(),
312 LoadingStatus::Error,
313 );
314 }
315 }
316 Err(_) => {
317 let _ = proxy.update_resource_status(
318 req_path.clone(),
319 LoadingStatus::Error,
320 );
321 }
322 }
323 } else {
324 let _ = proxy
325 .update_resource_status(req_path.clone(), LoadingStatus::Error);
326 }
327 }),
328 );
329
330 true
331 }
332 }
333 }
334}