Skip to main content

guicons_cli/
fetch.rs

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
19/// Populates the manifest's cache directory ([`guicons_net::cache_dir`]) for
20/// every `iconify`/`url` entry and records their sha256 in `icons.lock`.
21///
22/// A download or cached copy that differs from the lock fails that icon;
23/// `force` re-downloads everything and re-locks it to what came back.
24pub 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}