Skip to main content

tairitsu_packager/icons/
cache.rs

1use serde::{Deserialize, Serialize};
2use std::{
3    collections::HashMap,
4    fs,
5    io::Write,
6    path::{Path, PathBuf},
7};
8
9use sha2::{Digest, Sha256};
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct CacheManifest {
13    pub set_name: String,
14    pub version: String,
15    pub source_hash: String,
16    pub icon_count: usize,
17    pub icons: HashMap<String, IconData>,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct IconData {
22    pub path_d: String,
23    pub tags: Vec<String>,
24    pub aliases: Vec<String>,
25}
26
27impl CacheManifest {
28    pub fn load(path: &Path) -> Option<Self> {
29        let content = fs::read_to_string(path).ok()?;
30        serde_json::from_str(&content).ok()
31    }
32
33    pub fn save(&self, path: &Path) -> std::io::Result<()> {
34        if let Some(parent) = path.parent() {
35            fs::create_dir_all(parent)?;
36        }
37        let content = serde_json::to_string(self)?;
38        let mut f = fs::File::create(path)?;
39        f.write_all(content.as_bytes())
40    }
41
42    pub fn compute_hash(data: &[u8]) -> String {
43        let mut hasher = Sha256::new();
44        hasher.update(data);
45        format!("{:x}", hasher.finalize())
46    }
47
48    pub fn verify_hash(&self, current_data: &[u8]) -> bool {
49        let current_hash = Self::compute_hash(current_data);
50        self.source_hash == current_hash
51    }
52}
53
54pub struct IconCache {
55    root: PathBuf,
56    offline: bool,
57}
58
59impl IconCache {
60    pub fn new(root: PathBuf, offline: bool) -> Self {
61        Self { root, offline }
62    }
63
64    pub fn root(&self) -> &Path {
65        &self.root
66    }
67
68    pub fn is_offline(&self) -> bool {
69        self.offline
70    }
71
72    pub fn set_dir(&self, set_name: &str, version: &str) -> PathBuf {
73        self.root.join(set_name).join(version)
74    }
75
76    pub fn manifest_path(&self, set_name: &str, version: &str) -> PathBuf {
77        self.set_dir(set_name, version).join("manifest.json")
78    }
79
80    pub fn svg_data_path(&self, set_name: &str, version: &str) -> PathBuf {
81        self.set_dir(set_name, version).join("svg_data.dat")
82    }
83
84    pub fn font_path(&self, set_name: &str, version: &str) -> PathBuf {
85        self.set_dir(set_name, version).join("subset.woff2")
86    }
87
88    pub fn load_manifest(&self, set_name: &str, version: &str) -> Option<CacheManifest> {
89        let path = self.manifest_path(set_name, version);
90        if path.exists() {
91            CacheManifest::load(&path)
92        } else {
93            None
94        }
95    }
96
97    pub fn save_manifest(&self, manifest: &CacheManifest) -> std::io::Result<()> {
98        let path = self.manifest_path(&manifest.set_name, &manifest.version);
99        manifest.save(&path)
100    }
101
102    pub fn save_svg_data(
103        &self,
104        set_name: &str,
105        version: &str,
106        entries: &[(String, String)],
107    ) -> std::io::Result<PathBuf> {
108        let dir = self.set_dir(set_name, version);
109        fs::create_dir_all(&dir)?;
110        let path = dir.join("svg_data.dat");
111        let mut f = fs::File::create(&path)?;
112        for (name, path_d) in entries {
113            writeln!(f, "{}\t{}", name, path_d)?;
114        }
115        Ok(path)
116    }
117
118    pub fn load_svg_data(
119        &self,
120        set_name: &str,
121        version: &str,
122    ) -> std::io::Result<Vec<(String, String)>> {
123        let path = self.svg_data_path(set_name, version);
124        let content = fs::read_to_string(path)?;
125        let mut entries = Vec::new();
126        for line in content.lines() {
127            if let Some((name, path_d)) = line.split_once('\t') {
128                entries.push((name.to_string(), path_d.to_string()));
129            }
130        }
131        Ok(entries)
132    }
133
134    pub fn ensure_dir(&self, set_name: &str, version: &str) -> std::io::Result<()> {
135        let dir = self.set_dir(set_name, version);
136        fs::create_dir_all(dir)
137    }
138
139    pub fn has_cache(&self, set_name: &str, version: &str) -> bool {
140        self.manifest_path(set_name, version).exists()
141    }
142}
143
144pub fn resolve_cache_root(manifest_dir: Option<&Path>) -> PathBuf {
145    if let Ok(root) = std::env::var("HIKARI_ICONS_CACHE") {
146        return PathBuf::from(root);
147    }
148    if let Some(dir) = manifest_dir {
149        return dir.join("target").join("tairitsu-cache").join("icons");
150    }
151    if let Ok(target_dir) = std::env::var("CARGO_TARGET_DIR") {
152        return PathBuf::from(target_dir)
153            .join("tairitsu-cache")
154            .join("icons");
155    }
156    if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
157        let mut dir = PathBuf::from(&manifest_dir);
158        loop {
159            if dir.join("Cargo.toml").exists() {
160                if let Some(parent) = dir.parent() {
161                    if parent.join("Cargo.toml").exists() || parent.join("Cargo.lock").exists() {
162                        return parent.join("target").join("tairitsu-cache").join("icons");
163                    }
164                }
165                return dir.join("target").join("tairitsu-cache").join("icons");
166            }
167            if !dir.pop() {
168                break;
169            }
170        }
171        return PathBuf::from(manifest_dir)
172            .join("target")
173            .join("tairitsu-cache")
174            .join("icons");
175    }
176    let base = std::env::var("XDG_CACHE_HOME")
177        .map(PathBuf::from)
178        .unwrap_or_else(|_| {
179            let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
180            PathBuf::from(home).join(".cache")
181        });
182    base.join("tairitsu-cache").join("icons")
183}