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