1extern crate memmap;
23#[macro_use]
24extern crate log;
25
26use memmap::Mmap;
27
28use std::io::{ErrorKind, Result, Error};
29use std::num::Wrapping;
30use std::fs::File;
31use std::path::Path;
32use std::collections::{HashMap, HashSet};
33use std::sync::Arc;
34
35#[derive(Debug, Clone)]
39pub struct GtkIconCache {
40 hash_offset: usize,
41 directory_list_offset: usize,
42
43 n_buckets: usize,
44
45 dir_names: HashMap<usize, String>,
46 file_mmap: Arc<Mmap>,
47}
48
49impl GtkIconCache {
50 pub fn with_file_path<T: AsRef<Path>>(path: T) -> Result<Self> {
56 let f = File::open(&path.as_ref())?;
58 let _last_modified = f.metadata().and_then(|x| x.modified()).ok();
59 let mmap = unsafe { Mmap::map(&f)? };
60
61 let r = Self {
62 hash_offset: 0,
63 directory_list_offset: 0,
64
65 n_buckets: 0,
66
67 dir_names: HashMap::new(),
68 file_mmap: Arc::new(mmap),
69 };
70
71 match r.load_cache() {
72 Some(cache) => Ok(cache),
73 _ => Err(Error::new(ErrorKind::Other, "cache load failed.")),
74 }
75 }
76
77 fn load_cache(mut self) -> Option<Self> {
78
79 let major_version = self.read_card16_from(0)?;
80 let minor_version = self.read_card16_from(2)?;
81
82 self.hash_offset = self.read_card32_from(4)?;
83 self.directory_list_offset = self.read_card32_from(8)?;
84 self.n_buckets = self.read_card32_from(self.hash_offset)?;
85
86 if major_version != 1usize && minor_version != 0usize {
87 return None;
88 }
89
90 let n_directorys = self.read_card32_from(self.directory_list_offset)?;
91
92 for i in 0..n_directorys {
94 let offset = self.read_card32_from(self.directory_list_offset + 4 + 4 * i)?;
95 if let Some(dir) = self.read_cstring_from(offset as usize) {
96 self.dir_names.insert(offset, dir);
97 }
98 }
99
100 trace!("{:#?}", self);
101
102 Some(self)
103 }
104
105 fn read_card16_from(&self, offset: usize) -> Option<usize> {
106 let m = &self.file_mmap;
107
108 if offset < self.file_mmap.len() - 2 {
109 Some((m[offset ] as usize) << 8 |
110 (m[offset + 1] as usize))
111 } else {
112 None
113 }
114 }
115
116 fn read_card32_from(&self, offset: usize) -> Option<usize> {
117 let m = &self.file_mmap;
118
119 if offset > 0 && offset < self.file_mmap.len() - 4 {
120 Some((m[offset ] as usize) << 24 |
121 (m[offset + 1] as usize) << 16 |
122 (m[offset + 2] as usize) << 8 |
123 (m[offset + 3] as usize))
124 } else {
125 None
126 }
127 }
128
129 fn read_cstring_from(&self, offset: usize) -> Option<String> {
130 let mut terminate = offset;
131
132 while self.file_mmap[terminate] != b'\0' { terminate += 1; }
133
134 if terminate == offset { return None; }
135
136 Some(String::from_utf8_lossy(&self.file_mmap[offset..terminate]).to_string())
137 }
138
139 pub fn lookup<T: AsRef<str>>(&self, name: T) -> Option<Vec<&String>> {
145 let icon_hash = icon_name_hash(name.as_ref());
146 let bucket_index = icon_hash % self.n_buckets;
147
148 let mut bucket_offset = self.read_card32_from(self.hash_offset + 4 + bucket_index * 4)?;
149 while let Some(bucket_name_offset) = self.read_card32_from(bucket_offset + 4) {
150 if let Some(cache) = self.read_cstring_from(bucket_name_offset) {
152 if cache == name.as_ref() {
153 let list_offset = self.read_card32_from(bucket_offset + 8)?;
154 let list_len = self.read_card32_from(list_offset)?;
155
156 let mut r = HashSet::with_capacity(list_len);
157 for i in 0..list_len {
159 if let Some(dir_index) = self.read_card16_from(list_offset + 4 + 8 * i) {
160 if let Some(offset) = self.read_card32_from(self.directory_list_offset + 4 + dir_index * 4) {
161 r.insert(offset);
162 }
163 }
164 }
165
166 let ref dir_names = self.dir_names;
167 return Some(r.iter().map(|x| dir_names.get(&x).unwrap()).collect())
168 }
169 }
170
171 bucket_offset = self.read_card32_from(bucket_offset)?;
173 }
174
175 None
177 }
178}
179
180fn icon_name_hash<T: AsRef<str>>(name: T) -> usize {
181
182 let name = name.as_ref().as_bytes();
183
184 name.iter()
185 .fold(Wrapping(0u32), |r, &c| (r << 5) - r + Wrapping(c as u32)).0
186 as usize
187}
188
189#[cfg(test)]
190mod test {
191
192 use GtkIconCache;
193 use icon_name_hash;
194
195 #[test]
196 fn test_icon_cache() {
197 let path = "test/caches/icon-theme.cache";
198 let icon_cache = GtkIconCache::with_file_path(path).unwrap();
199
200 let icon_name = "web-browser";
201 let icon_hash = icon_name_hash(icon_name);
202
203 assert_eq!(icon_hash, 2769241519);
204 assert_eq!(icon_cache.hash_offset, 12);
205
206 println!("=> {:?}", icon_cache.lookup(icon_name));
207 }
208
209 #[test]
210 fn test_cache_test1() {
211 let path = "test/caches/test1.cache";
212 let icon_cache = GtkIconCache::with_file_path(path).unwrap();
213
214 let dirs = icon_cache.lookup("test").unwrap();
215 assert!(dirs.contains(&&"apps/32".to_string()));
216 assert!(dirs.contains(&&"apps/48".to_string()));
217
218 let dirs = icon_cache.lookup("deepin-deb-installer").unwrap();
219 assert!(dirs.contains(&&"apps/16".to_string()));
220 assert!(dirs.contains(&&"apps/32".to_string()));
221 assert!(dirs.contains(&&"apps/48".to_string()));
222 assert!(dirs.contains(&&"apps/scalable".to_string()));
223 }
224
225 #[test]
226 fn test_icon_name_hash() {
227 assert_eq!(icon_name_hash("deepin-deb-installer"), 1927089920);
228 }
229}