Skip to main content

vizia_core/resource/
file_resource_loader.rs

1use crate::context::ResourceContext;
2
3#[cfg(feature = "tokio")]
4use super::ResourceLoadExecution;
5use super::{LoadingStatus, ResourceLoader, ResourceRequest};
6
7/// Built-in resource loader for local file paths and `file://` URLs.
8pub 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                // Mark as loading
51                cx.resource_manager.set_resource_status(req.path.clone(), LoadingStatus::Loading);
52
53                // Try to read the file synchronously
54                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                // Mark as error if loading failed
73                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                // Mark as loading
88                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                // Mark as loading before spawning task
177                cx.resource_manager.set_resource_status(req_path.clone(), LoadingStatus::Loading);
178
179                // Spawn an async task to read the file
180                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}