]> Pileus Git - ~andy/gtk/blob - gtk/updateiconcache.c
5231a064b61cea39751fbc565e37ea07f388e60f
[~andy/gtk] / gtk / updateiconcache.c
1 /* updateiconcache.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 <stdlib.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <unistd.h>
26 #include <errno.h>
27 #include <utime.h>
28
29 #include <glib.h>
30 #include <glib/gstdio.h>
31
32 #define CACHE_NAME "icon-theme.cache"
33
34 #define HAS_SUFFIX_XPM (1 << 0)
35 #define HAS_SUFFIX_SVG (1 << 1)
36 #define HAS_SUFFIX_PNG (1 << 2)
37 #define HAS_ICON_FILE  (1 << 3)
38
39 #define MAJOR_VERSION 1
40 #define MINOR_VERSION 0
41 #define HASH_OFFSET 12
42
43 #define ALIGN_VALUE(this, boundary) \
44   (( ((unsigned long)(this)) + (((unsigned long)(boundary)) -1)) & (~(((unsigned long)(boundary))-1)))
45
46 gboolean
47 is_cache_up_to_date (const gchar *path)
48 {
49   struct stat path_stat, cache_stat;
50   gchar *cache_path;
51   int retval;
52   
53   retval = g_stat (path, &path_stat);
54
55   if (retval < 0)
56     {
57       /* We can't stat the path,
58        * assume we have a updated cache */
59       return TRUE;
60     }
61
62   cache_path = g_build_filename (path, CACHE_NAME, NULL);
63   retval = g_stat (cache_path, &cache_stat);
64   g_free (cache_path);
65   
66   if (retval < 0 && errno == ENOENT)
67     {
68       /* Cache file not found */
69       return FALSE;
70     }
71
72   /* Check mtime */
73   return cache_stat.st_mtime <= path_stat.st_mtime;
74 }
75
76 typedef struct
77 {
78   int flags;
79   int dir_index;
80 } Image;
81
82 static gboolean
83 foreach_remove_func (gpointer key, gpointer value, gpointer user_data)
84 {
85   Image *image = (Image *)value;
86   GHashTable *files = user_data;
87   GList *list;
88   gboolean free_key = FALSE;  
89
90   if (image->flags == HAS_ICON_FILE)
91     {
92       g_free (key);
93       g_free (image);
94
95       return TRUE;
96     }
97
98   list = g_hash_table_lookup (files, key);
99   if (list)
100     free_key = TRUE;
101   
102   list = g_list_prepend (list, value);
103   g_hash_table_insert (files, key, list);
104   
105   if (free_key)
106     g_free (key);
107   
108   return TRUE;
109 }
110
111 GList *
112 scan_directory (const gchar *base_path, 
113                 const gchar *subdir, 
114                 GHashTable  *files, 
115                 GList       *directories,
116                 gint         depth)
117 {
118   GHashTable *dir_hash;
119   GDir *dir;
120   const gchar *name;
121   gchar *dir_path;
122   gboolean dir_added = FALSE;
123   guint dir_index = 0xffff;
124   
125   dir_path = g_build_filename (base_path, subdir, NULL);
126
127   /* FIXME: Use the gerror */
128   dir = g_dir_open (dir_path, 0, NULL);
129   
130   if (!dir)
131     return directories;
132   
133   dir_hash = g_hash_table_new (g_str_hash, g_str_equal);
134
135   while ((name = g_dir_read_name (dir)))
136     {
137       gchar *path;
138       gboolean retval;
139       int flags = 0;
140       Image *image;
141       gchar *basename, *dot;
142
143       path = g_build_filename (dir_path, name, NULL);
144       retval = g_file_test (path, G_FILE_TEST_IS_DIR);
145       if (retval)
146         {
147           gchar *subsubdir;
148
149           if (subdir)
150             subsubdir = g_build_filename (subdir, name, NULL);
151           else
152             subsubdir = g_strdup (name);
153           directories = scan_directory (base_path, subsubdir, files, 
154                                         directories, depth + 1);
155           g_free (subsubdir);
156
157           continue;
158         }
159
160       retval = g_file_test (path, G_FILE_TEST_IS_REGULAR);
161       g_free (path);
162       
163       if (retval)
164         {
165           if (g_str_has_suffix (name, ".png"))
166             flags |= HAS_SUFFIX_PNG;
167           else if (g_str_has_suffix (name, ".svg"))
168             flags |= HAS_SUFFIX_SVG;
169           else if (g_str_has_suffix (name, ".xpm"))
170             flags |= HAS_SUFFIX_XPM;
171           else if (g_str_has_suffix (name, ".icon"))
172             flags |= HAS_ICON_FILE;
173           
174           if (flags == 0)
175             continue;
176           
177           basename = g_strdup (name);
178           dot = strrchr (basename, '.');
179           *dot = '\0';
180           
181           image = g_hash_table_lookup (dir_hash, basename);
182           if (image)
183             image->flags |= flags;
184           else
185             {
186               if (!dir_added) 
187                 {
188                   dir_added = TRUE;
189                   if (subdir)
190                     {
191                       dir_index = g_list_length (directories);
192                       directories = g_list_append (directories, g_strdup (subdir));
193                     }
194                   else
195                     dir_index = 0xffff;
196                 }
197                 
198               image = g_new0 (Image, 1);
199               image->flags = flags;
200               image->dir_index = dir_index;
201               
202               g_hash_table_insert (dir_hash, g_strdup (basename), image);
203             }
204
205           g_free (basename);
206         }
207     }
208
209   g_dir_close (dir);
210
211   /* Move dir into the big file hash */
212   g_hash_table_foreach_remove (dir_hash, foreach_remove_func, files);
213   
214   g_hash_table_destroy (dir_hash);
215
216   return directories;
217 }
218
219 typedef struct _HashNode HashNode;
220
221 struct _HashNode
222 {
223   HashNode *next;
224   gchar *name;
225   GList *image_list;
226 };
227
228 static guint
229 icon_name_hash (gconstpointer key)
230 {
231   const char *p = key;
232   guint h = *p;
233
234   if (h)
235     for (p += 1; *p != '\0'; p++)
236       h = (h << 5) - h + *p;
237
238   return h;
239 }
240
241 typedef struct {
242   gint size;
243   HashNode **nodes;
244 } HashContext;
245
246 static gboolean
247 convert_to_hash (gpointer key, gpointer value, gpointer user_data)
248 {
249   HashContext *context = user_data;
250   guint hash;
251   HashNode *node;
252   
253   hash = icon_name_hash (key) % context->size;
254
255   node = g_new0 (HashNode, 1);
256   node->next = NULL;
257   node->name = key;
258   node->image_list = value;
259
260   if (context->nodes[hash] != NULL)
261     node->next = context->nodes[hash];
262
263   context->nodes[hash] = node;
264   
265   return TRUE;
266 }
267
268 gboolean
269 write_string (FILE *cache, const gchar *n)
270 {
271   gchar *s;
272   int i, l;
273   
274   l = ALIGN_VALUE (strlen (n) + 1, 4);
275   
276   s = g_malloc0 (l);
277   strcpy (s, n);
278
279   i = fwrite (s, l, 1, cache);
280
281   return i == 1;
282   
283 }
284
285 gboolean
286 write_card16 (FILE *cache, guint16 n)
287 {
288   int i;
289   gchar s[2];
290
291   *((guint16 *)s) = GUINT16_TO_BE (n);
292   
293   i = fwrite (s, 2, 1, cache);
294
295   return i == 1;
296 }
297
298 gboolean
299 write_card32 (FILE *cache, guint32 n)
300 {
301   int i;
302   gchar s[4];
303
304   *((guint32 *)s) = GUINT32_TO_BE (n);
305   
306   i = fwrite (s, 4, 1, cache);
307
308   return i == 1;
309 }
310
311 static gboolean
312 write_header (FILE *cache, guint32 dir_list_offset)
313 {
314   return (write_card16 (cache, MAJOR_VERSION) &&
315           write_card16 (cache, MINOR_VERSION) &&
316           write_card32 (cache, HASH_OFFSET) &&
317           write_card32 (cache, dir_list_offset));
318 }
319
320
321 guint
322 get_single_node_size (HashNode *node)
323 {
324   int len = 0;
325
326   /* Node pointers */
327   len += 12;
328
329   /* Name */
330   len += ALIGN_VALUE (strlen (node->name) + 1, 4);
331
332   /* Image list */
333   len += 4 + g_list_length (node->image_list) * 8;
334
335   return len;
336 }
337
338 guint
339 get_bucket_size (HashNode *node)
340 {
341   int len = 0;
342
343   while (node)
344     {
345       len += get_single_node_size (node);
346
347       node = node->next;
348     }
349
350   return len;
351 }
352
353 gboolean
354 write_bucket (FILE *cache, HashNode *node, int *offset)
355 {
356   while (node != NULL)
357     {
358       int next_offset = *offset + get_single_node_size (node);
359       int i, len;
360       GList *list;
361           
362       /* Chain offset */
363       if (node->next != NULL)
364         {
365           if (!write_card32 (cache, next_offset))
366             return FALSE;
367         }
368       else
369         {
370           if (!write_card32 (cache, 0xffffffff))
371             return FALSE;
372         }
373       
374       /* Icon name offset */
375       if (!write_card32 (cache, *offset + 12))
376         return FALSE;
377       
378       /* Image list offset */
379       if (!write_card32 (cache, *offset + 12 + ALIGN_VALUE (strlen (node->name) + 1, 4)))
380         return FALSE;
381       
382       /* Icon name */
383       if (!write_string (cache, node->name))
384         return FALSE;
385       
386       /* Image list */
387       len = g_list_length (node->image_list);
388       if (!write_card32 (cache, len))
389         return FALSE;
390       
391       list = node->image_list;
392       for (i = 0; i < len; i++)
393         {
394           Image *image = list->data;
395           
396           /* Directory index */
397           if (!write_card16 (cache, image->dir_index))
398             return FALSE;
399           
400           /* Flags */
401           if (!write_card16 (cache, image->flags))
402             return FALSE;
403           
404           /* Image data offset */
405           if (!write_card32 (cache, 0))
406             return FALSE;
407           
408           list = list->next;
409         }
410
411       *offset = next_offset;
412       node = node->next;
413     }
414   
415   return TRUE;
416 }
417
418 gboolean
419 write_hash_table (FILE *cache, HashContext *context, int *new_offset)
420 {
421   int offset = HASH_OFFSET;
422   int node_offset;
423   int i;
424
425   if (!(write_card32 (cache, context->size)))
426     return FALSE;
427
428   /* Size int + size * 4 */
429   node_offset = offset + 4 + context->size * 4;
430   
431   for (i = 0; i < context->size; i++)
432     {
433       if (context->nodes[i] != NULL)
434         {
435           if (!write_card32 (cache, node_offset))
436             return FALSE;
437           
438           node_offset += get_bucket_size (context->nodes[i]);
439         }
440       else
441         {
442           if (!write_card32 (cache, 0xffffffff))
443             {
444               return FALSE;
445             }
446         }
447     }
448
449   *new_offset = node_offset;
450
451   /* Now write the buckets */
452   node_offset = offset + 4 + context->size * 4;
453   
454   for (i = 0; i < context->size; i++)
455     {
456       if (!context->nodes[i])
457         continue;
458
459       if (!write_bucket (cache, context->nodes[i], &node_offset))
460         return FALSE;
461     }
462
463   return TRUE;
464 }
465
466 gboolean
467 write_dir_index (FILE *cache, int offset, GList *directories)
468 {
469   int n_dirs;
470   GList *d;
471   char *dir;
472
473   n_dirs = g_list_length (directories);
474
475   if (!write_card32 (cache, n_dirs))
476     return FALSE;
477
478   offset += 4 + n_dirs * 4;
479
480   for (d = directories; d; d = d->next)
481     {
482       dir = d->data;
483       if (!write_card32 (cache, offset))
484         return FALSE;
485       
486       offset += ALIGN_VALUE (strlen (dir) + 1, 4);
487     }
488
489   for (d = directories; d; d = d->next)
490     {
491       dir = d->data;
492
493       if (!write_string (cache, dir))
494         return FALSE;
495     }
496   
497   return TRUE;
498 }
499
500 gboolean
501 write_file (FILE *cache, GHashTable *files, GList *directories)
502 {
503   HashContext context;
504   int new_offset;
505
506   /* Convert the hash table into something looking a bit more
507    * like what we want to write to disk.
508    */
509   context.size = g_spaced_primes_closest (g_hash_table_size (files) / 3);
510   context.nodes = g_new0 (HashNode *, context.size);
511   
512   g_hash_table_foreach_remove (files, convert_to_hash, &context);
513
514   /* Now write the file */
515   /* We write 0 as the directory list offset and go
516    * back and change it later */
517   if (!write_header (cache, 0))
518     {
519       g_printerr ("Failed to write header\n");
520       return FALSE;
521     }
522
523   if (!write_hash_table (cache, &context, &new_offset))
524     {
525       g_printerr ("Failed to write hash table\n");
526       return FALSE;
527     }
528
529   if (!write_dir_index (cache, new_offset, directories))
530     {
531       g_printerr ("Failed to write directory index\n");
532       return FALSE;
533     }
534   
535   rewind (cache);
536
537   if (!write_header (cache, new_offset))
538     {
539       g_printerr ("Failed to rewrite header\n");
540       return FALSE;
541     }
542     
543   return TRUE;
544 }
545
546 void
547 build_cache (const gchar *path)
548 {
549   gchar *cache_path, *tmp_cache_path;
550   GHashTable *files;
551   gboolean retval;
552   FILE *cache;
553   struct stat path_stat, cache_stat;
554   struct utimbuf utime_buf;
555   GList *directories = NULL;
556   
557   tmp_cache_path = g_build_filename (path, "."CACHE_NAME, NULL);
558   cache = g_fopen (tmp_cache_path, "wb");
559   
560   if (!cache)
561     {
562       g_printerr ("Failed to write cache file: %s\n", g_strerror (errno));
563       exit (1);
564     }
565
566   files = g_hash_table_new (g_str_hash, g_str_equal);
567   
568   directories = scan_directory (path, NULL, files, NULL, 0);
569
570   if (g_hash_table_size (files) == 0)
571     {
572       /* Empty table, just close and remove the file */
573
574       fclose (cache);
575       g_unlink (tmp_cache_path);
576       exit (0);
577     }
578     
579   /* FIXME: Handle failure */
580   retval = write_file (cache, files, directories);
581   fclose (cache);
582
583   g_list_foreach (directories, (GFunc)g_free, NULL);
584   g_list_free (directories);
585   
586   if (!retval)
587     {
588       g_unlink (tmp_cache_path);
589       exit (1);
590     }
591
592   cache_path = g_build_filename (path, CACHE_NAME, NULL);
593
594   if (g_rename (tmp_cache_path, cache_path) == -1)
595     {
596       g_unlink (tmp_cache_path);
597       exit (1);
598     }
599
600   /* Update time */
601   /* FIXME: What do do if an error occurs here? */
602   g_stat (path, &path_stat);
603   g_stat (cache_path, &cache_stat);
604
605   utime_buf.actime = path_stat.st_atime;
606   utime_buf.modtime = cache_stat.st_mtime;
607   utime (path, &utime_buf);
608   
609   g_printerr ("Cache file created successfully.\n");
610 }
611
612 static gboolean force_update = FALSE;
613
614 static GOptionEntry args[] = {
615   { "force", 0, 0, G_OPTION_ARG_NONE, &force_update, "Overwrite an existing cache, even if uptodate", NULL },
616   { NULL }
617 };
618
619 int
620 main (int argc, char **argv)
621 {
622   gchar *path;
623   GOptionContext *context;
624
625   if (argc < 2)
626     return 0;
627
628   context = g_option_context_new ("ICONPATH");
629   g_option_context_add_main_entries (context, args, NULL);
630
631   g_option_context_parse (context, &argc, &argv, NULL);
632   
633   path = argv[1];
634 #ifdef G_OS_WIN32
635   path = g_locale_to_utf8 (path, -1, NULL, NULL, NULL);
636 #endif
637   
638   if (!force_update && is_cache_up_to_date (path))
639     return 0;
640
641   build_cache (path);
642   
643   return 0;
644 }