1use guicons_core::IconEntrySource;
2use guicons_net::{Lock, RemoteIcon};
3use std::fs;
4use std::path::Path;
5
6#[derive(Debug)]
7pub struct FetchSummary {
8 pub fetched: Vec<String>,
9 pub skipped: Vec<String>,
10 pub failed: Vec<(String, String)>,
11}
12
13impl FetchSummary {
14 pub fn is_success(&self) -> bool {
15 self.failed.is_empty()
16 }
17}
18
19pub fn fetch(manifest_path: &Path, force: bool) -> Result<FetchSummary, Vec<String>> {
25 let (manifest, errors) = guicons_core::load_icon_manifest(manifest_path);
26 if !errors.is_empty() {
27 return Err(errors.iter().map(|e| e.to_string()).collect());
28 }
29 let cache_dir = guicons_net::cache_dir(&manifest);
30 let lock_path = guicons_net::lock_path(&manifest);
31 let mut lock = Lock::load(&lock_path).map_err(|e| vec![e.to_string()])?;
32
33 let mut summary = FetchSummary {
34 fetched: Vec::new(),
35 skipped: Vec::new(),
36 failed: Vec::new(),
37 };
38
39 let icons: Vec<RemoteIcon<'_>> = manifest
40 .entries()
41 .iter()
42 .filter_map(|entry| match entry.source() {
43 IconEntrySource::Iconify(id) => Some(RemoteIcon::Iconify(id)),
44 IconEntrySource::Url(url) => Some(RemoteIcon::Url(url)),
45 IconEntrySource::File(_) | IconEntrySource::Glyph(_) => None,
46 })
47 .collect();
48
49 for &icon in &icons {
50 let label = icon.label().to_string();
51 match fetch_one(icon, &cache_dir, &lock, force) {
52 Ok((sha256, downloaded)) => {
53 lock.set(icon, sha256);
54 if downloaded {
55 summary.fetched.push(label);
56 } else {
57 summary.skipped.push(label);
58 }
59 }
60 Err(e) => summary.failed.push((label, e.to_string())),
61 }
62 }
63
64 lock.retain(&icons);
65 if !lock.is_empty() || lock_path.exists() {
66 lock.save(&lock_path).map_err(|e| vec![e.to_string()])?;
67 }
68 Ok(summary)
69}
70
71fn fetch_one(
72 icon: RemoteIcon<'_>,
73 cache_dir: &Path,
74 lock: &Lock,
75 force: bool,
76) -> Result<(String, bool), guicons_net::DownloadError> {
77 let cache_path = icon.cache_path(cache_dir)?;
78 if !force && let Ok(bytes) = fs::read(&cache_path) {
79 lock.verify(icon, &bytes)?;
80 return Ok((guicons_net::sha256_hex(&bytes), false));
81 }
82 let bytes = guicons_net::fetch(icon)?;
83 if !force {
84 lock.verify(icon, &bytes)?;
85 }
86 guicons_net::write_atomic(&cache_path, &bytes)?;
87 Ok((guicons_net::sha256_hex(&bytes), true))
88}