]> Pileus Git - ~andy/gtk/blob - gtk/gtkiconcache.c
fc44d036de527d5805305338e66a666799c228ea
[~andy/gtk] / gtk / gtkiconcache.c
1 /* gtkiconcache.c
2  * Copyright (C) 2004  Anders Carlsson <andersca@gnome.org>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #include <config.h>
21 #include "gtkdebug.h"
22 #include "gtkiconcache.h"
23 #include <glib/gstdio.h>
24
25 #ifdef HAVE_MMAP
26 #include <sys/mman.h>
27 #endif
28 #ifdef G_OS_WIN32
29 #include <windows.h>
30 #include <io.h>
31 #endif
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #ifdef HAVE_UNISTD_H
35 #include <unistd.h>
36 #endif
37 #include <fcntl.h>
38 #include <string.h>
39
40 #ifndef _O_BINARY
41 #define _O_BINARY 0
42 #endif
43
44 #define MAJOR_VERSION 1
45 #define MINOR_VERSION 0
46
47 #define GET_UINT16(cache, offset) (GUINT16_FROM_BE (*(guint16 *)((cache) + (offset))))
48 #define GET_UINT32(cache, offset) (GUINT32_FROM_BE (*(guint32 *)((cache) + (offset))))
49
50 struct _GtkIconCache {
51   gint ref_count;
52
53   gsize size;
54   gchar *buffer;
55 #ifdef G_OS_WIN32
56   HANDLE handle;
57 #endif
58 };
59
60 GtkIconCache *
61 _gtk_icon_cache_ref (GtkIconCache *cache)
62 {
63   cache->ref_count ++;
64   return cache;
65 }
66
67 void
68 _gtk_icon_cache_unref (GtkIconCache *cache)
69 {
70   cache->ref_count --;
71
72   if (cache->ref_count == 0)
73     {
74       GTK_NOTE (ICONTHEME, 
75                 g_print ("unmapping icon cache\n"));
76 #ifdef HAVE_MMAP
77       munmap (cache->buffer, cache->size);
78 #endif
79 #ifdef G_OS_WIN32
80       UnmapViewOfFile (cache->buffer);
81       CloseHandle (cache->handle);
82 #endif
83       g_free (cache);
84     }
85 }
86
87 GtkIconCache *
88 _gtk_icon_cache_new_for_path (const gchar *path)
89 {
90   GtkIconCache *cache = NULL;
91
92 #if defined(HAVE_MMAP) || defined(G_OS_WIN32)
93   gchar *cache_filename;
94   gint fd = -1;
95   struct stat st;
96   struct stat path_st;
97   gchar *buffer = NULL;
98 #ifdef G_OS_WIN32
99   HANDLE handle = NULL;
100 #endif
101
102   if (g_getenv ("GTK_NO_ICON_CACHE"))
103     return NULL;
104
105   /* Check if we have a cache file */
106   cache_filename = g_build_filename (path, "icon-theme.cache", NULL);
107
108   GTK_NOTE (ICONTHEME, 
109             g_print ("look for cache in %s\n", path));
110
111   if (!g_file_test (cache_filename, G_FILE_TEST_IS_REGULAR))
112     goto done;
113
114   if (g_stat (path, &path_st) < 0)
115     goto done;
116
117   /* Open the file and map it into memory */
118   fd = g_open (cache_filename, O_RDONLY|_O_BINARY, 0);
119
120   if (fd < 0)
121     {
122       g_free (cache_filename);
123       return NULL;
124     }
125   
126   if (fstat (fd, &st) < 0 || st.st_size < 4)
127     goto done;
128
129   /* Verify cache is uptodate */
130   if (st.st_mtime < path_st.st_mtime)
131     {
132       GTK_NOTE (ICONTHEME, 
133                 g_print ("cache outdated\n"));
134       goto done; 
135     }
136
137 #ifndef G_OS_WIN32
138   buffer = (gchar *) mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
139
140   if (buffer == MAP_FAILED)
141     goto done;
142 #else
143   handle = CreateFileMapping (_get_osfhandle (fd), NULL, PAGE_READONLY,
144                               0, 0, NULL);
145   if (handle == NULL)
146     goto done;
147
148   buffer = MapViewOfFile (handle, FILE_MAP_READ, 0, 0, 0);
149
150   if (buffer == NULL)
151     {
152       CloseHandle (handle);
153       goto done;
154     }
155 #endif
156
157   /* Verify version */
158   if (GET_UINT16 (buffer, 0) != MAJOR_VERSION ||
159       GET_UINT16 (buffer, 2) != MINOR_VERSION)
160     {
161 #ifndef G_OS_WIN32
162       munmap (buffer, st.st_size);
163 #else
164       UnmapViewOfFile (buffer);
165       CloseHandle (handle);
166 #endif
167       GTK_NOTE (ICONTHEME, 
168                 g_print ("wrong cache version\n"));
169       goto done;
170     }
171   
172   GTK_NOTE (ICONTHEME, 
173             g_print ("found cache for %s\n", path));
174
175   cache = g_new0 (GtkIconCache, 1);
176   cache->ref_count = 1;
177   cache->buffer = buffer;
178 #ifdef G_OS_WIN32
179   cache->handle = handle;
180 #endif
181   cache->size = st.st_size;
182  done:
183   g_free (cache_filename);  
184   if (fd != -1)
185     close (fd);
186
187 #endif  /* HAVE_MMAP || G_OS_WIN32 */
188
189   return cache;
190 }
191
192 static int
193 get_directory_index (GtkIconCache *cache,
194                      const gchar *directory)
195 {
196   guint32 dir_list_offset;
197   int n_dirs;
198   int i;
199   
200   dir_list_offset = GET_UINT32 (cache->buffer, 8);
201
202   n_dirs = GET_UINT32 (cache->buffer, dir_list_offset);
203
204   for (i = 0; i < n_dirs; i++)
205     {
206       guint32 name_offset = GET_UINT32 (cache->buffer, dir_list_offset + 4 + 4 * i);
207       gchar *name = cache->buffer + name_offset;
208       if (strcmp (name, directory) == 0)
209         return i;
210     }
211   
212   return -1;
213 }
214
215 gboolean
216 _gtk_icon_cache_has_directory (GtkIconCache *cache,
217                                const gchar *directory)
218 {
219   return get_directory_index (cache, directory) != -1;
220 }
221
222 static guint
223 icon_name_hash (gconstpointer key)
224 {
225   const char *p = key;
226   guint h = *p;
227
228   if (h)
229     for (p += 1; *p != '\0'; p++)
230       h = (h << 5) - h + *p;
231
232   return h;
233 }
234
235 gint
236 _gtk_icon_cache_get_icon_flags (GtkIconCache *cache,
237                                 const gchar  *icon_name,
238                                 const gchar  *directory)
239 {
240   guint32 hash_offset;
241   guint32 n_buckets;
242   guint32 chain_offset;
243   int hash, directory_index;
244   guint32 image_list_offset, n_images;
245   gboolean found = FALSE;
246   int i;
247   
248   hash_offset = GET_UINT32 (cache->buffer, 4);
249   n_buckets = GET_UINT32 (cache->buffer, hash_offset);
250
251   hash = icon_name_hash (icon_name) % n_buckets;
252
253   chain_offset = GET_UINT32 (cache->buffer, hash_offset + 4 + 4 * hash);
254   while (chain_offset != 0xffffffff)
255     {
256       guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4);
257       gchar *name = cache->buffer + name_offset;
258
259       if (strcmp (name, icon_name) == 0)
260         {
261           found = TRUE;
262           break;
263         }
264           
265       chain_offset = GET_UINT32 (cache->buffer, chain_offset);
266     }
267
268   if (!found)
269     return 0;
270
271   /* We've found an icon list, now check if we have the right icon in it */
272   directory_index = get_directory_index (cache, directory);
273   image_list_offset = GET_UINT32 (cache->buffer, chain_offset + 8);
274   n_images = GET_UINT32 (cache->buffer, image_list_offset);
275   
276   for (i = 0; i < n_images; i++)
277     {
278       if (GET_UINT16 (cache->buffer, image_list_offset + 4 + 8 * i) ==
279           directory_index)
280         return GET_UINT16 (cache->buffer, image_list_offset + 4 + 8 * i + 2);
281     }
282   
283   return 0;
284 }
285
286 void
287 _gtk_icon_cache_add_icons (GtkIconCache *cache,
288                            const gchar  *directory,
289                            GHashTable   *hash_table)
290 {
291   int directory_index;
292   guint32 hash_offset, n_buckets;
293   guint32 chain_offset;
294   guint32 image_list_offset, n_images;
295   int i, j;
296   
297   directory_index = get_directory_index (cache, directory);
298
299   if (directory_index == -1)
300     return;
301   
302   hash_offset = GET_UINT32 (cache->buffer, 4);
303   n_buckets = GET_UINT32 (cache->buffer, hash_offset);
304   
305   for (i = 0; i < n_buckets; i++)
306     {
307       chain_offset = GET_UINT32 (cache->buffer, hash_offset + 4 + 4 * i);
308       while (chain_offset != 0xffffffff)
309         {
310           guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4);
311           gchar *name = cache->buffer + name_offset;
312           
313           image_list_offset = GET_UINT32 (cache->buffer, chain_offset + 8);
314           n_images = GET_UINT32 (cache->buffer, image_list_offset);
315   
316           for (j = 0; j < n_images; j++)
317             {
318               if (GET_UINT16 (cache->buffer, image_list_offset + 4 + 8 * j) ==
319                   directory_index)
320                 g_hash_table_insert (hash_table, name, NULL);
321             }
322
323           chain_offset = GET_UINT32 (cache->buffer, chain_offset);
324         }
325     }  
326 }
327
328 gboolean
329 _gtk_icon_cache_has_icon (GtkIconCache *cache,
330                           const gchar  *icon_name)
331 {
332   guint32 hash_offset;
333   guint32 n_buckets;
334   guint32 chain_offset;
335   gint hash;
336   
337   hash_offset = GET_UINT32 (cache->buffer, 4);
338   n_buckets = GET_UINT32 (cache->buffer, hash_offset);
339
340   hash = icon_name_hash (icon_name) % n_buckets;
341
342   chain_offset = GET_UINT32 (cache->buffer, hash_offset + 4 + 4 * hash);
343   while (chain_offset != 0xffffffff)
344     {
345       guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4);
346       gchar *name = cache->buffer + name_offset;
347
348       if (strcmp (name, icon_name) == 0)
349         return TRUE;
350           
351       chain_offset = GET_UINT32 (cache->buffer, chain_offset);
352     }
353
354   return FALSE;
355 }
356                           
357