]> Pileus Git - ~andy/gtk/blob - gtk/updateiconcache.c
Some fixes from Morten Welinder (#172947):
[~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 <config.h>
21
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <unistd.h>
28 #include <errno.h>
29 #include <utime.h>
30
31 #include <glib.h>
32 #include <glib/gstdio.h>
33 #include <gdk-pixbuf/gdk-pixdata.h>
34
35 static gboolean force_update = FALSE;
36 static gboolean quiet = FALSE;
37
38 #define CACHE_NAME "icon-theme.cache"
39
40 #define HAS_SUFFIX_XPM (1 << 0)
41 #define HAS_SUFFIX_SVG (1 << 1)
42 #define HAS_SUFFIX_PNG (1 << 2)
43 #define HAS_ICON_FILE  (1 << 3)
44
45 #define CAN_CACHE_IMAGE_DATA(flags) (((flags) & HAS_SUFFIX_PNG) || ((flags) & HAS_SUFFIX_XPM))
46
47 #define MAJOR_VERSION 1
48 #define MINOR_VERSION 0
49 #define HASH_OFFSET 12
50
51 #define ALIGN_VALUE(this, boundary) \
52   (( ((unsigned long)(this)) + (((unsigned long)(boundary)) -1)) & (~(((unsigned long)(boundary))-1)))
53
54 gboolean
55 is_cache_up_to_date (const gchar *path)
56 {
57   struct stat path_stat, cache_stat;
58   gchar *cache_path;
59   int retval;
60   
61   retval = g_stat (path, &path_stat);
62
63   if (retval < 0)
64     {
65       /* We can't stat the path,
66        * assume we have a updated cache */
67       return TRUE;
68     }
69
70   cache_path = g_build_filename (path, CACHE_NAME, NULL);
71   retval = g_stat (cache_path, &cache_stat);
72   g_free (cache_path);
73   
74   if (retval < 0)
75     {
76       /* Cache file not found */
77       return FALSE;
78     }
79
80   /* Check mtime */
81   return cache_stat.st_mtime >= path_stat.st_mtime;
82 }
83
84 typedef struct
85 {
86   int flags;
87   int dir_index;
88
89   gboolean has_pixdata;
90   GdkPixdata pixdata;
91
92   int has_embedded_rect;
93   int x0, y0, x1, y1;
94   
95   int n_attach_points;
96   int *attach_points;
97   
98   int n_display_names;
99   char **display_names;
100 } Image;
101
102 static gboolean
103 foreach_remove_func (gpointer key, gpointer value, gpointer user_data)
104 {
105   Image *image = (Image *)value;
106   GHashTable *files = user_data;
107   GList *list;
108   gboolean free_key = FALSE;  
109
110   if (image->flags == HAS_ICON_FILE)
111     {
112       g_free (key);
113       g_free (image);
114       g_free (image->attach_points);
115       g_strfreev (image->display_names);
116
117       return TRUE;
118     }
119
120   list = g_hash_table_lookup (files, key);
121   if (list)
122     free_key = TRUE;
123   
124   list = g_list_prepend (list, value);
125   g_hash_table_insert (files, key, list);
126   
127   if (free_key)
128     g_free (key);
129   
130   return TRUE;
131 }
132
133 static void
134 load_icon_data (Image *image, const char *path)
135 {
136   GKeyFile *icon_file;
137   char **split;
138   gsize length;
139   char *str;
140   char *split_point;
141   int i;
142   gint *ivalues;
143   GError *error = NULL;
144   gchar **keys;
145   gsize n_keys;
146   
147   icon_file = g_key_file_new ();
148   g_key_file_set_list_separator (icon_file, ',');
149   g_key_file_load_from_file (icon_file, path, G_KEY_FILE_KEEP_TRANSLATIONS, &error);
150   if (error)
151     {
152       g_error_free (error);
153       return;
154     }
155
156   ivalues = g_key_file_get_integer_list (icon_file, 
157                                          "Icon Data", "EmbeddedTextRectangle",
158                                          &length, NULL);
159   if (ivalues)
160     {
161       if (length == 4)
162         {
163           image->has_embedded_rect = TRUE;
164           image->x0 = ivalues[0];
165           image->y0 = ivalues[1];
166           image->x1 = ivalues[2];
167           image->y1 = ivalues[3];
168         }
169       
170       g_free (ivalues);
171     }
172       
173   str = g_key_file_get_string (icon_file, "Icon Data", "AttachPoints", NULL);
174   if (str)
175     {
176       split = g_strsplit (str, "|", -1);
177       
178       image->n_attach_points = g_strv_length (split);
179       image->attach_points = g_new (int, 2 * image->n_attach_points);
180
181       i = 0;
182       while (split[i] != NULL && i < image->n_attach_points)
183         {
184           split_point = strchr (split[i], ',');
185           if (split_point)
186             {
187               *split_point = 0;
188               split_point++;
189               image->attach_points[2 * i] = atoi (split[i]);
190               image->attach_points[2 * i + 1] = atoi (split_point);
191             }
192           i++;
193         }
194       
195       g_strfreev (split);
196       g_free (str);
197     }
198       
199   keys = g_key_file_get_keys (icon_file, "Icon Data", &n_keys, &error);
200   image->display_names = g_new0 (gchar *, 2 * n_keys + 1); 
201   image->n_display_names = 0;
202   
203   for (i = 0; i < n_keys; i++)
204     {
205       gchar *lang, *name;
206       
207       if (g_str_has_prefix (keys[i], "DisplayName"))
208         {
209           gchar *open, *close = NULL;
210           
211           open = strchr (keys[i], '[');
212
213           if (open)
214             close = strchr (open, ']');
215
216           if (open && close)
217             {
218               lang = g_strndup (open + 1, close - open - 1);
219               name = g_key_file_get_locale_string (icon_file, 
220                                                    "Icon Data", "DisplayName",
221                                                    lang, NULL);
222             }
223           else
224             {
225               lang = g_strdup ("C");
226               name = g_key_file_get_string (icon_file, 
227                                             "Icon Data", "DisplayName",
228                                             NULL);
229             }
230           
231           image->display_names[2 * image->n_display_names] = lang;
232           image->display_names[2 * image->n_display_names + 1] = name;
233           image->n_display_names++;
234         }
235     }
236
237   g_strfreev (keys);
238   
239   g_key_file_free (icon_file);
240 }
241
242 static void
243 maybe_cache_image_data (Image *image, const gchar *path)
244 {
245   if (CAN_CACHE_IMAGE_DATA(image->flags) && !image->has_pixdata) 
246     {
247       GdkPixbuf *pixbuf;
248       
249       pixbuf = gdk_pixbuf_new_from_file (path, NULL);
250       
251       if (pixbuf) 
252         {
253           image->has_pixdata = TRUE;
254           gdk_pixdata_from_pixbuf (&image->pixdata, pixbuf, FALSE);
255         }
256     }
257 }
258
259 GList *
260 scan_directory (const gchar *base_path, 
261                 const gchar *subdir, 
262                 GHashTable  *files, 
263                 GList       *directories,
264                 gint         depth)
265 {
266   GHashTable *dir_hash;
267   GDir *dir;
268   const gchar *name;
269   gchar *dir_path;
270   gboolean dir_added = FALSE;
271   guint dir_index = 0xffff;
272   
273   dir_path = g_build_filename (base_path, subdir, NULL);
274
275   /* FIXME: Use the gerror */
276   dir = g_dir_open (dir_path, 0, NULL);
277   
278   if (!dir)
279     return directories;
280   
281   dir_hash = g_hash_table_new (g_str_hash, g_str_equal);
282
283   while ((name = g_dir_read_name (dir)))
284     {
285       gchar *path;
286       gboolean retval;
287       int flags = 0;
288       Image *image;
289       gchar *basename, *dot;
290
291       path = g_build_filename (dir_path, name, NULL);
292       retval = g_file_test (path, G_FILE_TEST_IS_DIR);
293       if (retval)
294         {
295           gchar *subsubdir;
296
297           if (subdir)
298             subsubdir = g_build_filename (subdir, name, NULL);
299           else
300             subsubdir = g_strdup (name);
301           directories = scan_directory (base_path, subsubdir, files, 
302                                         directories, depth + 1);
303           g_free (subsubdir);
304
305           continue;
306         }
307
308       retval = g_file_test (path, G_FILE_TEST_IS_REGULAR);
309
310       if (retval)
311         {
312           if (g_str_has_suffix (name, ".png"))
313             flags |= HAS_SUFFIX_PNG;
314           else if (g_str_has_suffix (name, ".svg"))
315             flags |= HAS_SUFFIX_SVG;
316           else if (g_str_has_suffix (name, ".xpm"))
317             flags |= HAS_SUFFIX_XPM;
318           else if (g_str_has_suffix (name, ".icon"))
319             flags |= HAS_ICON_FILE;
320           
321           if (flags == 0)
322             continue;
323           
324           basename = g_strdup (name);
325           dot = strrchr (basename, '.');
326           *dot = '\0';
327           
328           image = g_hash_table_lookup (dir_hash, basename);
329           if (image)
330             {
331               image->flags |= flags;
332               maybe_cache_image_data (image, path);
333             }
334           else
335             {
336               if (!dir_added) 
337                 {
338                   dir_added = TRUE;
339                   if (subdir)
340                     {
341                       dir_index = g_list_length (directories);
342                       directories = g_list_append (directories, g_strdup (subdir));
343                     }
344                   else
345                     dir_index = 0xffff;
346                 }
347                 
348               image = g_new0 (Image, 1);
349               image->flags = flags;
350               image->dir_index = dir_index;
351               maybe_cache_image_data (image, path);
352
353               g_hash_table_insert (dir_hash, g_strdup (basename), image);
354             }
355
356           if (g_str_has_suffix (name, ".icon"))
357             load_icon_data (image, path);
358
359           g_free (basename);
360         }
361
362       g_free (path);
363
364     }
365
366   g_dir_close (dir);
367
368   /* Move dir into the big file hash */
369   g_hash_table_foreach_remove (dir_hash, foreach_remove_func, files);
370   
371   g_hash_table_destroy (dir_hash);
372
373   return directories;
374 }
375
376 typedef struct _HashNode HashNode;
377
378 struct _HashNode
379 {
380   HashNode *next;
381   gchar *name;
382   GList *image_list;
383 };
384
385 static guint
386 icon_name_hash (gconstpointer key)
387 {
388   const signed char *p = key;
389   guint32 h = *p;
390
391   if (h)
392     for (p += 1; *p != '\0'; p++)
393       h = (h << 5) - h + *p;
394
395   return h;
396 }
397
398 typedef struct {
399   gint size;
400   HashNode **nodes;
401 } HashContext;
402
403 static gboolean
404 convert_to_hash (gpointer key, gpointer value, gpointer user_data)
405 {
406   HashContext *context = user_data;
407   guint hash;
408   HashNode *node;
409   
410   hash = icon_name_hash (key) % context->size;
411
412   node = g_new0 (HashNode, 1);
413   node->next = NULL;
414   node->name = key;
415   node->image_list = value;
416
417   if (context->nodes[hash] != NULL)
418     node->next = context->nodes[hash];
419
420   context->nodes[hash] = node;
421   
422   return TRUE;
423 }
424
425 gboolean
426 write_string (FILE *cache, const gchar *n)
427 {
428   gchar *s;
429   int i, l;
430   
431   l = ALIGN_VALUE (strlen (n) + 1, 4);
432   
433   s = g_malloc0 (l);
434   strcpy (s, n);
435
436   i = fwrite (s, l, 1, cache);
437
438   return i == 1;
439   
440 }
441
442 gboolean
443 write_card16 (FILE *cache, guint16 n)
444 {
445   int i;
446   gchar s[2];
447
448   *((guint16 *)s) = GUINT16_TO_BE (n);
449   
450   i = fwrite (s, 2, 1, cache);
451
452   return i == 1;
453 }
454
455 gboolean
456 write_card32 (FILE *cache, guint32 n)
457 {
458   int i;
459   gchar s[4];
460
461   *((guint32 *)s) = GUINT32_TO_BE (n);
462   
463   i = fwrite (s, 4, 1, cache);
464
465   return i == 1;
466 }
467
468
469 gboolean
470 write_pixdata (FILE *cache, GdkPixdata *pixdata)
471 {
472   guint8 *s;
473   int len;
474   int i;
475
476
477   /* Type 0 is GdkPixdata */
478   if (!write_card32 (cache, 0))
479     return FALSE;
480
481   s = gdk_pixdata_serialize (pixdata, &len);
482
483   if (!write_card32 (cache, len))
484     {
485       g_free (s);
486       return FALSE;
487     }
488
489   i = fwrite (s, len, 1, cache);
490   
491   g_free (s);
492
493   return i == 1;
494 }
495
496 static gboolean
497 write_header (FILE *cache, guint32 dir_list_offset)
498 {
499   return (write_card16 (cache, MAJOR_VERSION) &&
500           write_card16 (cache, MINOR_VERSION) &&
501           write_card32 (cache, HASH_OFFSET) &&
502           write_card32 (cache, dir_list_offset));
503 }
504
505 guint
506 get_image_meta_data_size (Image *image)
507 {
508   gint i;
509   guint len = 0;
510
511   if (image->has_embedded_rect ||
512       image->attach_points > 0 ||
513       image->n_display_names > 0)
514     len += 12;
515
516   if (image->has_embedded_rect)
517     len += 8;
518
519   if (image->n_attach_points > 0)
520     len += 4 + image->n_attach_points * 4;
521
522   if (image->n_display_names > 0)
523     {
524       len += 4 + 8 * image->n_display_names;
525
526       for (i = 0; image->display_names[i]; i++)
527         len += ALIGN_VALUE (strlen (image->display_names[i]) + 1, 4);
528     }
529
530   return len;
531 }
532
533 guint
534 get_image_pixel_data_size (Image *image)
535 {
536   if (image->has_pixdata)
537     return image->pixdata.length + 8;
538
539   return 0;
540 }
541
542 guint
543 get_image_data_size (Image *image)
544 {
545   guint len;
546   
547   len = 0;
548
549   len += get_image_pixel_data_size (image);
550   len += get_image_meta_data_size (image);
551   
552   if (len > 0)
553     len += 8;
554
555   return len;
556 }
557
558 guint
559 get_single_node_size (HashNode *node, gboolean include_image_data)
560 {
561   int len = 0;
562   GList *list;
563
564   /* Node pointers */
565   len += 12;
566
567   /* Name */
568   len += ALIGN_VALUE (strlen (node->name) + 1, 4);
569
570   /* Image list */
571   len += 4 + g_list_length (node->image_list) * 8;
572  
573   /* Image data */
574   if (include_image_data)
575     for (list = node->image_list; list; list = list->next)
576       {
577         Image *image = list->data;
578         
579         len += get_image_data_size (image);
580       }
581   
582   return len;
583 }
584
585 guint
586 get_bucket_size (HashNode *node)
587 {
588   int len = 0;
589
590   while (node)
591     {
592       len += get_single_node_size (node, TRUE);
593
594       node = node->next;
595     }
596
597   return len;
598 }
599
600 gboolean
601 write_bucket (FILE *cache, HashNode *node, int *offset)
602 {
603   while (node != NULL)
604     {
605       int next_offset = *offset + get_single_node_size (node, TRUE);
606       int image_data_offset = *offset + get_single_node_size (node, FALSE);
607       int data_offset;
608       int tmp;
609       int i, j, len;
610       GList *list;
611           
612       /* Chain offset */
613       if (node->next != NULL)
614         {
615           if (!write_card32 (cache, next_offset))
616             return FALSE;
617         }
618       else
619         {
620           if (!write_card32 (cache, 0xffffffff))
621             return FALSE;
622         }
623       
624       /* Icon name offset */
625       if (!write_card32 (cache, *offset + 12))
626         return FALSE;
627       
628       /* Image list offset */
629       tmp = *offset + 12 + ALIGN_VALUE (strlen (node->name) + 1, 4);
630       if (!write_card32 (cache, tmp))
631         return FALSE;
632       
633       /* Icon name */
634       if (!write_string (cache, node->name))
635         return FALSE;
636       
637       /* Image list */
638       len = g_list_length (node->image_list);
639       if (!write_card32 (cache, len))
640         return FALSE;
641       
642       /* Image data goes right after the image list */
643       tmp += 4 + len * 8;
644
645       list = node->image_list;
646       data_offset = image_data_offset;
647       for (i = 0; i < len; i++)
648         {
649           Image *image = list->data;
650           int image_data_size = get_image_data_size (image);
651           
652           /* Directory index */
653           if (!write_card16 (cache, image->dir_index))
654             return FALSE;
655           
656           /* Flags */
657           if (!write_card16 (cache, image->flags))
658             return FALSE;
659
660           /* Image data offset */
661           if (image_data_size > 0)
662             {
663               if (!write_card32 (cache, data_offset))
664                 return FALSE;
665               data_offset += image_data_size;
666             }
667           else
668             {
669               if (!write_card32 (cache, 0))
670                 return FALSE;
671             }
672
673           list = list->next;
674         }
675
676       /* Now write the image data */
677       list = node->image_list;
678       for (i = 0; i < len; i++, list = list->next)
679         {
680           Image *image = list->data;
681           int pixel_data_size = get_image_pixel_data_size (image);
682           int meta_data_size = get_image_meta_data_size (image);
683
684           if (meta_data_size + pixel_data_size == 0)
685             continue;
686
687           /* Pixel data */
688           if (pixel_data_size > 0) 
689             {
690               if (!write_card32 (cache, image_data_offset + 8))
691                 return FALSE;
692             }
693           else
694             {
695               if (!write_card32 (cache, 0))
696                 return FALSE;
697             }
698
699           if (meta_data_size > 0)
700             {
701               if (!write_card32 (cache, image_data_offset + pixel_data_size + 8))
702                 return FALSE;
703             }
704           else
705             {
706               if (!write_card32 (cache, 0))
707                 return FALSE;
708             }
709
710           if (pixel_data_size > 0)
711             {
712               if (!write_pixdata (cache, &image->pixdata))
713                 return FALSE;
714             }
715           
716           if (meta_data_size > 0)
717             {
718               int ofs = image_data_offset + pixel_data_size + 20;
719
720               if (image->has_embedded_rect)
721                 {
722                   if (!write_card32 (cache, ofs))
723                     return FALSE;
724               
725                   ofs += 8;
726                 }             
727               else
728                 {
729                   if (!write_card32 (cache, 0))
730                     return FALSE;
731                 }
732               
733               if (image->n_attach_points > 0)
734                 {
735                   if (!write_card32 (cache, ofs))
736                     return FALSE;
737
738                   ofs += 4 + 4 * image->n_attach_points;
739                 }
740               else
741                 {
742                   if (!write_card32 (cache, 0))
743                     return FALSE;
744                 }
745
746               if (image->n_display_names > 0)
747                 {
748                   if (!write_card32 (cache, ofs))
749                     return FALSE;
750                 }
751               else
752                 {
753                   if (!write_card32 (cache, 0))
754                     return FALSE;
755                 }
756
757               if (image->has_embedded_rect)
758                 {
759                   if (!write_card16 (cache, image->x0) ||
760                       !write_card16 (cache, image->y0) ||
761                       !write_card16 (cache, image->x1) ||
762                       !write_card16 (cache, image->y1))
763                     return FALSE;
764                 }
765
766               if (image->n_attach_points > 0)
767                 {
768                   if (!write_card32 (cache, image->n_attach_points))
769                     return FALSE;
770                   
771                   for (j = 0; j < 2 * image->n_attach_points; j++)
772                     {
773                       if (!write_card16 (cache, image->attach_points[j]))
774                         return FALSE;
775                     }             
776                 }
777
778               if (image->n_display_names > 0)
779                 {
780                   if (!write_card32 (cache, image->n_display_names))
781                     return FALSE;
782
783                   ofs += 4 + 8 * image->n_display_names;
784
785                   for (j = 0; j < 2 * image->n_display_names; j++)
786                     {
787                       if (!write_card32 (cache, ofs))
788                         return FALSE;
789
790                       ofs += ALIGN_VALUE (strlen (image->display_names[j]) + 1, 4);
791                     }
792
793                   for (j = 0; j < 2 * image->n_display_names; j++)
794                     {
795                       if (!write_string (cache, image->display_names[j]))
796                         return FALSE;
797                     }        
798                 }
799             }
800
801           image_data_offset += pixel_data_size + meta_data_size + 8;
802         }
803       
804       *offset = next_offset;
805       node = node->next;
806     }
807   
808   return TRUE;
809 }
810
811 gboolean
812 write_hash_table (FILE *cache, HashContext *context, int *new_offset)
813 {
814   int offset = HASH_OFFSET;
815   int node_offset;
816   int i;
817
818   if (!(write_card32 (cache, context->size)))
819     return FALSE;
820
821   /* Size int + size * 4 */
822   node_offset = offset + 4 + context->size * 4;
823   
824   for (i = 0; i < context->size; i++)
825     {
826       if (context->nodes[i] != NULL)
827         {
828           if (!write_card32 (cache, node_offset))
829             return FALSE;
830           
831           node_offset += get_bucket_size (context->nodes[i]);
832         }
833       else
834         {
835           if (!write_card32 (cache, 0xffffffff))
836             {
837               return FALSE;
838             }
839         }
840     }
841
842   *new_offset = node_offset;
843
844   /* Now write the buckets */
845   node_offset = offset + 4 + context->size * 4;
846   
847   for (i = 0; i < context->size; i++)
848     {
849       if (!context->nodes[i])
850         continue;
851
852       if (!write_bucket (cache, context->nodes[i], &node_offset))
853         return FALSE;
854     }
855
856   return TRUE;
857 }
858
859 gboolean
860 write_dir_index (FILE *cache, int offset, GList *directories)
861 {
862   int n_dirs;
863   GList *d;
864   char *dir;
865
866   n_dirs = g_list_length (directories);
867
868   if (!write_card32 (cache, n_dirs))
869     return FALSE;
870
871   offset += 4 + n_dirs * 4;
872
873   for (d = directories; d; d = d->next)
874     {
875       dir = d->data;
876       if (!write_card32 (cache, offset))
877         return FALSE;
878       
879       offset += ALIGN_VALUE (strlen (dir) + 1, 4);
880     }
881
882   for (d = directories; d; d = d->next)
883     {
884       dir = d->data;
885
886       if (!write_string (cache, dir))
887         return FALSE;
888     }
889   
890   return TRUE;
891 }
892
893 gboolean
894 write_file (FILE *cache, GHashTable *files, GList *directories)
895 {
896   HashContext context;
897   int new_offset;
898
899   /* Convert the hash table into something looking a bit more
900    * like what we want to write to disk.
901    */
902   context.size = g_spaced_primes_closest (g_hash_table_size (files) / 3);
903   context.nodes = g_new0 (HashNode *, context.size);
904   
905   g_hash_table_foreach_remove (files, convert_to_hash, &context);
906
907   /* Now write the file */
908   /* We write 0 as the directory list offset and go
909    * back and change it later */
910   if (!write_header (cache, 0))
911     {
912       g_printerr ("Failed to write header\n");
913       return FALSE;
914     }
915
916   if (!write_hash_table (cache, &context, &new_offset))
917     {
918       g_printerr ("Failed to write hash table\n");
919       return FALSE;
920     }
921
922   if (!write_dir_index (cache, new_offset, directories))
923     {
924       g_printerr ("Failed to write directory index\n");
925       return FALSE;
926     }
927   
928   rewind (cache);
929
930   if (!write_header (cache, new_offset))
931     {
932       g_printerr ("Failed to rewrite header\n");
933       return FALSE;
934     }
935     
936   return TRUE;
937 }
938
939 void
940 build_cache (const gchar *path)
941 {
942   gchar *cache_path, *tmp_cache_path;
943   GHashTable *files;
944   gboolean retval;
945   FILE *cache;
946   struct stat path_stat, cache_stat;
947   struct utimbuf utime_buf;
948   GList *directories = NULL;
949   
950   tmp_cache_path = g_build_filename (path, "."CACHE_NAME, NULL);
951   cache = g_fopen (tmp_cache_path, "wb");
952   
953   if (!cache)
954     {
955       g_printerr ("Failed to write cache file: %s\n", g_strerror (errno));
956       exit (1);
957     }
958
959   files = g_hash_table_new (g_str_hash, g_str_equal);
960   
961   directories = scan_directory (path, NULL, files, NULL, 0);
962
963   if (g_hash_table_size (files) == 0)
964     {
965       /* Empty table, just close and remove the file */
966
967       fclose (cache);
968       g_unlink (tmp_cache_path);
969       exit (0);
970     }
971     
972   /* FIXME: Handle failure */
973   retval = write_file (cache, files, directories);
974   fclose (cache);
975
976   g_list_foreach (directories, (GFunc)g_free, NULL);
977   g_list_free (directories);
978   
979   if (!retval)
980     {
981       g_unlink (tmp_cache_path);
982       exit (1);
983     }
984
985   cache_path = g_build_filename (path, CACHE_NAME, NULL);
986
987   if (g_rename (tmp_cache_path, cache_path) == -1)
988     {
989       g_unlink (tmp_cache_path);
990       exit (1);
991     }
992
993   /* Update time */
994   /* FIXME: What do do if an error occurs here? */
995   if (g_stat (path, &path_stat) < 0 ||
996       g_stat (cache_path, &cache_stat))
997     exit (1);
998
999   utime_buf.actime = path_stat.st_atime;
1000   utime_buf.modtime = cache_stat.st_mtime;
1001   utime (path, &utime_buf);
1002   
1003   if (!quiet)
1004     g_printerr ("Cache file created successfully.\n");
1005 }
1006
1007 static GOptionEntry args[] = {
1008   { "force", 'f', 0, G_OPTION_ARG_NONE, &force_update, "Overwrite an existing cache, even if uptodate", NULL },
1009   { "quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet, "Turn off verbose output", NULL },
1010   { NULL }
1011 };
1012
1013 int
1014 main (int argc, char **argv)
1015 {
1016   gchar *path;
1017   GOptionContext *context;
1018
1019   if (argc < 2)
1020     return 0;
1021
1022   context = g_option_context_new ("ICONPATH");
1023   g_option_context_add_main_entries (context, args, NULL);
1024
1025   g_option_context_parse (context, &argc, &argv, NULL);
1026   
1027   path = argv[1];
1028 #ifdef G_OS_WIN32
1029   path = g_locale_to_utf8 (path, -1, NULL, NULL, NULL);
1030 #endif
1031   
1032   if (!force_update && is_cache_up_to_date (path))
1033     return 0;
1034
1035   g_type_init ();
1036   build_cache (path);
1037   
1038   return 0;
1039 }