tairitsu_packager/icons/
cache.rs1use 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}