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