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