]> Pileus Git - ~andy/gtk/blob - gtk/updateiconcache.c
d44d93e5e0b37ba51dcf18be47987a78961609af
[~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 <locale.h>
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #ifdef HAVE_UNISTD_H
29 #include <unistd.h>
30 #endif
31 #include <errno.h>
32 #ifdef _MSC_VER
33 #include <sys/utime.h>
34 #else
35 #include <utime.h>
36 #endif
37
38 #include <glib.h>
39 #include <glib/gstdio.h>
40 #include <gdk-pixbuf/gdk-pixdata.h>
41 #include <glib/gi18n.h>
42 #include "gtkiconcachevalidator.h"
43
44 static gboolean force_update = FALSE;
45 static gboolean ignore_theme_index = FALSE;
46 static gboolean quiet = FALSE;
47 static gboolean index_only = FALSE;
48 static gboolean validate = FALSE;
49 static gchar *var_name = "-";
50
51 /* Quite ugly - if we just add the c file to the
52  * list of sources in Makefile.am, libtool complains.
53  */
54 #include "gtkiconcachevalidator.c"
55
56 #define CACHE_NAME "icon-theme.cache"
57
58 #define HAS_SUFFIX_XPM (1 << 0)
59 #define HAS_SUFFIX_SVG (1 << 1)
60 #define HAS_SUFFIX_PNG (1 << 2)
61 #define HAS_ICON_FILE  (1 << 3)
62
63 #define MAJOR_VERSION 1
64 #define MINOR_VERSION 0
65 #define HASH_OFFSET 12
66
67 #define ALIGN_VALUE(this, boundary) \
68   (( ((unsigned long)(this)) + (((unsigned long)(boundary)) -1)) & (~(((unsigned long)(boundary))-1)))
69
70 #ifdef HAVE_FTW_H
71
72 #include <ftw.h>
73
74 static struct stat cache_stat;
75 static gboolean cache_up_to_date;
76
77 static int check_dir_mtime (const char        *dir, 
78                             const struct stat *sb,
79                             int                tf)
80 {
81   if (tf != FTW_NS && sb->st_mtime > cache_stat.st_mtime)
82     {
83       cache_up_to_date = FALSE;
84       /* stop tree walk */
85       return 1;
86     }
87
88   return 0;
89 }
90
91  gboolean
92  is_cache_up_to_date (const gchar *path)
93  {
94   gchar *cache_path;
95   gint retval;
96
97   cache_path = g_build_filename (path, CACHE_NAME, NULL);
98   retval = g_stat (cache_path, &cache_stat);
99   g_free (cache_path);
100   
101   if (retval < 0)
102     {
103       /* Cache file not found */
104       return FALSE;
105     }
106
107   cache_up_to_date = TRUE;
108
109   ftw (path, check_dir_mtime, 20);
110
111   return cache_up_to_date;
112 }
113
114 #else  /* !HAVE_FTW_H */
115
116 gboolean
117 is_cache_up_to_date (const gchar *path)
118 {
119   struct stat path_stat, cache_stat;
120   gchar *cache_path;
121   int retval; 
122   
123   retval = g_stat (path, &path_stat);
124
125   if (retval < 0) 
126     {
127       /* We can't stat the path,
128        * assume we have a updated cache */
129       return TRUE;
130     }
131
132   cache_path = g_build_filename (path, CACHE_NAME, NULL);
133   retval = g_stat (cache_path, &cache_stat);
134   g_free (cache_path);
135   
136   if (retval < 0)
137     {
138       /* Cache file not found */
139       return FALSE;
140     }
141
142   /* Check mtime */
143   return cache_stat.st_mtime >= path_stat.st_mtime;
144 }
145
146 #endif  /* !HAVE_FTW_H */
147
148 static gboolean
149 has_theme_index (const gchar *path)
150 {
151   gboolean result;
152   gchar *index_path;
153
154   index_path = g_build_filename (path, "index.theme", NULL);
155
156   result = g_file_test (index_path, G_FILE_TEST_IS_REGULAR);
157   
158   g_free (index_path);
159
160   return result;
161 }
162
163
164 typedef struct 
165 {
166   GdkPixdata pixdata;
167   gboolean has_pixdata;
168   guint32 offset;
169   guint size;
170 } ImageData;
171
172 typedef struct 
173 {
174   int has_embedded_rect;
175   int x0, y0, x1, y1;
176   
177   int n_attach_points;
178   int *attach_points;
179   
180   int n_display_names;
181   char **display_names;
182
183   guint32 offset;
184   gint size;
185 } IconData;
186
187 static GHashTable *image_data_hash = NULL;
188 static GHashTable *icon_data_hash = NULL;
189
190 typedef struct
191 {
192   int flags;
193   int dir_index;
194
195   ImageData *image_data;
196   guint pixel_data_size;
197
198   IconData *icon_data;
199   guint icon_data_size;
200 } Image;
201
202 static void
203 free_icon_data (IconData *icon_data)
204 {
205   g_free (icon_data->attach_points);
206   g_strfreev (icon_data->display_names);
207   g_free (icon_data);
208 }
209
210 static gboolean
211 foreach_remove_func (gpointer key, gpointer value, gpointer user_data)
212 {
213   Image *image = (Image *)value;
214   GHashTable *files = user_data;
215   GList *list;
216   gboolean free_key = FALSE;  
217
218   if (image->flags == HAS_ICON_FILE)
219     {
220       /* just a .icon file, throw away */
221       g_free (key);
222       free_icon_data (image->icon_data);
223       g_free (image);
224
225       return TRUE;
226     }
227
228   list = g_hash_table_lookup (files, key);
229   if (list)
230     free_key = TRUE;
231   
232   list = g_list_prepend (list, value);
233   g_hash_table_insert (files, key, list);
234   
235   if (free_key)
236     g_free (key);
237   
238   return TRUE;
239 }
240
241 static IconData *
242 load_icon_data (const char *path)
243 {
244   GKeyFile *icon_file;
245   char **split;
246   gsize length;
247   char *str;
248   char *split_point;
249   int i;
250   gint *ivalues;
251   GError *error = NULL;
252   gchar **keys;
253   gsize n_keys;
254   IconData *data;
255   
256   icon_file = g_key_file_new ();
257   g_key_file_set_list_separator (icon_file, ',');
258   g_key_file_load_from_file (icon_file, path, G_KEY_FILE_KEEP_TRANSLATIONS, &error);
259   if (error)
260     {
261       g_error_free (error);
262       g_key_file_free (icon_file);
263
264       return NULL;
265     }
266
267   data = g_new0 (IconData, 1);
268
269   ivalues = g_key_file_get_integer_list (icon_file, 
270                                          "Icon Data", "EmbeddedTextRectangle",
271                                          &length, NULL);
272   if (ivalues)
273     {
274       if (length == 4)
275         {
276           data->has_embedded_rect = TRUE;
277           data->x0 = ivalues[0];
278           data->y0 = ivalues[1];
279           data->x1 = ivalues[2];
280           data->y1 = ivalues[3];
281         }
282       
283       g_free (ivalues);
284     }
285       
286   str = g_key_file_get_string (icon_file, "Icon Data", "AttachPoints", NULL);
287   if (str)
288     {
289       split = g_strsplit (str, "|", -1);
290       
291       data->n_attach_points = g_strv_length (split);
292       data->attach_points = g_new (int, 2 * data->n_attach_points);
293
294       i = 0;
295       while (split[i] != NULL && i < data->n_attach_points)
296         {
297           split_point = strchr (split[i], ',');
298           if (split_point)
299             {
300               *split_point = 0;
301               split_point++;
302               data->attach_points[2 * i] = atoi (split[i]);
303               data->attach_points[2 * i + 1] = atoi (split_point);
304             }
305           i++;
306         }
307       
308       g_strfreev (split);
309       g_free (str);
310     }
311       
312   keys = g_key_file_get_keys (icon_file, "Icon Data", &n_keys, &error);
313   data->display_names = g_new0 (gchar *, 2 * n_keys + 1); 
314   data->n_display_names = 0;
315   
316   for (i = 0; i < n_keys; i++)
317     {
318       gchar *lang, *name;
319       
320       if (g_str_has_prefix (keys[i], "DisplayName"))
321         {
322           gchar *open, *close = NULL;
323           
324           open = strchr (keys[i], '[');
325
326           if (open)
327             close = strchr (open, ']');
328
329           if (open && close)
330             {
331               lang = g_strndup (open + 1, close - open - 1);
332               name = g_key_file_get_locale_string (icon_file, 
333                                                    "Icon Data", "DisplayName",
334                                                    lang, NULL);
335             }
336           else
337             {
338               lang = g_strdup ("C");
339               name = g_key_file_get_string (icon_file, 
340                                             "Icon Data", "DisplayName",
341                                             NULL);
342             }
343           
344           data->display_names[2 * data->n_display_names] = lang;
345           data->display_names[2 * data->n_display_names + 1] = name;
346           data->n_display_names++;
347         }
348     }
349
350   g_strfreev (keys);
351   
352   g_key_file_free (icon_file);
353
354   /* -1 means not computed yet, the real value depends
355    * on string pool state, and will be computed 
356    * later 
357    */
358   data->size = -1;
359
360   return data;
361 }
362
363 /*
364  * This function was copied from gtkfilesystemunix.c, it should
365  * probably go to GLib
366  */
367 static void
368 canonicalize_filename (gchar *filename)
369 {
370   gchar *p, *q;
371   gboolean last_was_slash = FALSE;
372
373   p = filename;
374   q = filename;
375
376   while (*p)
377     {
378       if (*p == G_DIR_SEPARATOR)
379         {
380           if (!last_was_slash)
381             *q++ = G_DIR_SEPARATOR;
382
383           last_was_slash = TRUE;
384         }
385       else
386         {
387           if (last_was_slash && *p == '.')
388             {
389               if (*(p + 1) == G_DIR_SEPARATOR ||
390                   *(p + 1) == '\0')
391                 {
392                   if (*(p + 1) == '\0')
393                     break;
394
395                   p += 1;
396                 }
397               else if (*(p + 1) == '.' &&
398                        (*(p + 2) == G_DIR_SEPARATOR ||
399                         *(p + 2) == '\0'))
400                 {
401                   if (q > filename + 1)
402                     {
403                       q--;
404                       while (q > filename + 1 &&
405                              *(q - 1) != G_DIR_SEPARATOR)
406                         q--;
407                     }
408
409                   if (*(p + 2) == '\0')
410                     break;
411
412                   p += 2;
413                 }
414               else
415                 {
416                   *q++ = *p;
417                   last_was_slash = FALSE;
418                 }
419             }
420           else
421             {
422               *q++ = *p;
423               last_was_slash = FALSE;
424             }
425         }
426
427       p++;
428     }
429
430   if (q > filename + 1 && *(q - 1) == G_DIR_SEPARATOR)
431     q--;
432
433   *q = '\0';
434 }
435
436 static gchar *
437 follow_links (const gchar *path)
438 {
439   gchar *target;
440   gchar *d, *s;
441   gchar *path2 = NULL;
442
443   path2 = g_strdup (path);
444   while (g_file_test (path2, G_FILE_TEST_IS_SYMLINK))
445     {
446       target = g_file_read_link (path2, NULL);
447       
448       if (target)
449         {
450           if (g_path_is_absolute (target))
451             path2 = target;
452           else
453             {
454               d = g_path_get_dirname (path2);
455               s = g_build_filename (d, target, NULL);
456               g_free (d);
457               g_free (target);
458               g_free (path2);
459               path2 = s;
460             }
461         }
462       else
463         break;
464     }
465
466   if (strcmp (path, path2) == 0)
467     {
468       g_free (path2);
469       path2 = NULL;
470     }
471
472   return path2;
473 }
474
475 static void
476 maybe_cache_image_data (Image       *image, 
477                         const gchar *path)
478 {
479   if (!index_only && !image->image_data && 
480       (g_str_has_suffix (path, ".png") || g_str_has_suffix (path, ".xpm")))
481     {
482       GdkPixbuf *pixbuf;
483       ImageData *idata;
484       gchar *path2;
485
486       idata = g_hash_table_lookup (image_data_hash, path);
487       path2 = follow_links (path);
488
489       if (path2)
490         {
491           ImageData *idata2;
492
493           canonicalize_filename (path2);
494   
495           idata2 = g_hash_table_lookup (image_data_hash, path2);
496
497           if (idata && idata2 && idata != idata2)
498             g_error (_("different idatas found for symlinked '%s' and '%s'\n"),
499                      path, path2);
500
501           if (idata && !idata2)
502             g_hash_table_insert (image_data_hash, g_strdup (path2), idata);
503
504           if (!idata && idata2)
505             {
506               g_hash_table_insert (image_data_hash, g_strdup (path), idata2);
507               idata = idata2;
508             }
509         }
510       
511       if (!idata)
512         {
513           idata = g_new0 (ImageData, 1);
514           g_hash_table_insert (image_data_hash, g_strdup (path), idata);
515           if (path2)
516             g_hash_table_insert (image_data_hash, g_strdup (path2), idata);  
517         }
518
519       if (!idata->has_pixdata)
520         {
521           pixbuf = gdk_pixbuf_new_from_file (path, NULL);
522           
523           if (pixbuf) 
524             {
525               gdk_pixdata_from_pixbuf (&idata->pixdata, pixbuf, FALSE);
526               idata->size = idata->pixdata.length + 8;
527               idata->has_pixdata = TRUE;
528             }
529         }
530
531       image->image_data = idata;
532
533       g_free (path2);
534     }
535 }
536
537 static void
538 maybe_cache_icon_data (Image       *image,
539                        const gchar *path)
540 {
541   if (g_str_has_suffix (path, ".icon"))
542     {
543       IconData *idata = NULL;
544       gchar *path2 = NULL;
545
546       idata = g_hash_table_lookup (icon_data_hash, path);
547       path2 = follow_links (path);
548
549       if (path2)
550         {
551           IconData *idata2;
552
553           canonicalize_filename (path2);
554   
555           idata2 = g_hash_table_lookup (icon_data_hash, path2);
556
557           if (idata && idata2 && idata != idata2)
558             g_error (_("different idatas found for symlinked '%s' and '%s'\n"),
559                      path, path2);
560
561           if (idata && !idata2)
562             g_hash_table_insert (icon_data_hash, g_strdup (path2), idata);
563
564           if (!idata && idata2)
565             {
566               g_hash_table_insert (icon_data_hash, g_strdup (path), idata2);
567               idata = idata2;
568             }
569         }
570       
571       if (!idata)
572         {
573           idata = load_icon_data (path);
574           g_hash_table_insert (icon_data_hash, g_strdup (path), idata);
575           if (path2)
576             g_hash_table_insert (icon_data_hash, g_strdup (path2), idata);  
577         }
578
579       image->icon_data = idata;
580
581       g_free (path2);
582     }
583 }
584
585 static GList *
586 scan_directory (const gchar *base_path, 
587                 const gchar *subdir, 
588                 GHashTable  *files, 
589                 GList       *directories,
590                 gint         depth)
591 {
592   GHashTable *dir_hash;
593   GDir *dir;
594   const gchar *name;
595   gchar *dir_path;
596   gboolean dir_added = FALSE;
597   guint dir_index = 0xffff;
598   
599   dir_path = g_build_filename (base_path, subdir, NULL);
600
601   /* FIXME: Use the gerror */
602   dir = g_dir_open (dir_path, 0, NULL);
603   
604   if (!dir)
605     return directories;
606   
607   dir_hash = g_hash_table_new (g_str_hash, g_str_equal);
608
609   while ((name = g_dir_read_name (dir)))
610     {
611       gchar *path;
612       gboolean retval;
613       int flags = 0;
614       Image *image;
615       gchar *basename, *dot;
616
617       path = g_build_filename (dir_path, name, NULL);
618       retval = g_file_test (path, G_FILE_TEST_IS_DIR);
619       if (retval)
620         {
621           gchar *subsubdir;
622
623           if (subdir)
624             subsubdir = g_build_filename (subdir, name, NULL);
625           else
626             subsubdir = g_strdup (name);
627           directories = scan_directory (base_path, subsubdir, files, 
628                                         directories, depth + 1);
629           g_free (subsubdir);
630
631           continue;
632         }
633
634       retval = g_file_test (path, G_FILE_TEST_IS_REGULAR);
635       if (retval)
636         {
637           if (g_str_has_suffix (name, ".png"))
638             flags |= HAS_SUFFIX_PNG;
639           else if (g_str_has_suffix (name, ".svg"))
640             flags |= HAS_SUFFIX_SVG;
641           else if (g_str_has_suffix (name, ".xpm"))
642             flags |= HAS_SUFFIX_XPM;
643           else if (g_str_has_suffix (name, ".icon"))
644             flags |= HAS_ICON_FILE;
645           
646           if (flags == 0)
647             continue;
648           
649           basename = g_strdup (name);
650           dot = strrchr (basename, '.');
651           *dot = '\0';
652           
653           image = g_hash_table_lookup (dir_hash, basename);
654           if (!image)
655             {
656               if (!dir_added) 
657                 {
658                   dir_added = TRUE;
659                   if (subdir)
660                     {
661                       dir_index = g_list_length (directories);
662                       directories = g_list_append (directories, g_strdup (subdir));
663                     }
664                   else
665                     dir_index = 0xffff;
666                 }
667                 
668               image = g_new0 (Image, 1);
669               image->dir_index = dir_index;
670               g_hash_table_insert (dir_hash, g_strdup (basename), image);
671             }
672
673           image->flags |= flags;
674       
675           maybe_cache_image_data (image, path);
676           maybe_cache_icon_data (image, path);
677        
678           g_free (basename);
679         }
680
681       g_free (path);
682     }
683
684   g_dir_close (dir);
685
686   /* Move dir into the big file hash */
687   g_hash_table_foreach_remove (dir_hash, foreach_remove_func, files);
688   
689   g_hash_table_destroy (dir_hash);
690
691   return directories;
692 }
693
694 typedef struct _HashNode HashNode;
695
696 struct _HashNode
697 {
698   HashNode *next;
699   gchar *name;
700   GList *image_list;
701   gint offset;
702 };
703
704 static guint
705 icon_name_hash (gconstpointer key)
706 {
707   const signed char *p = key;
708   guint32 h = *p;
709
710   if (h)
711     for (p += 1; *p != '\0'; p++)
712       h = (h << 5) - h + *p;
713
714   return h;
715 }
716
717 typedef struct {
718   gint size;
719   HashNode **nodes;
720 } HashContext;
721
722 static gboolean
723 convert_to_hash (gpointer key, gpointer value, gpointer user_data)
724 {
725   HashContext *context = user_data;
726   guint hash;
727   HashNode *node;
728   
729   hash = icon_name_hash (key) % context->size;
730
731   node = g_new0 (HashNode, 1);
732   node->next = NULL;
733   node->name = key;
734   node->image_list = value;
735
736   if (context->nodes[hash] != NULL)
737     node->next = context->nodes[hash];
738
739   context->nodes[hash] = node;
740   
741   return TRUE;
742 }
743
744 static GHashTable *string_pool = NULL;
745  
746 static int
747 find_string (const gchar *n)
748 {
749   return GPOINTER_TO_INT (g_hash_table_lookup (string_pool, n));
750 }
751
752 static void
753 add_string (const gchar *n, int offset)
754 {
755   g_hash_table_insert (string_pool, n, GINT_TO_POINTER (offset));
756 }
757
758 static gboolean
759 write_string (FILE *cache, const gchar *n)
760 {
761   gchar *s;
762   int i, l;
763   
764   l = ALIGN_VALUE (strlen (n) + 1, 4);
765   
766   s = g_malloc0 (l);
767   strcpy (s, n);
768
769   i = fwrite (s, l, 1, cache);
770
771   g_free (s);
772
773   return i == 1;
774   
775 }
776
777 static gboolean
778 write_card16 (FILE *cache, guint16 n)
779 {
780   int i;
781
782   n = GUINT16_TO_BE (n);
783   
784   i = fwrite ((char *)&n, 2, 1, cache);
785
786   return i == 1;
787 }
788
789 static gboolean
790 write_card32 (FILE *cache, guint32 n)
791 {
792   int i;
793
794   n = GUINT32_TO_BE (n);
795   
796   i = fwrite ((char *)&n, 4, 1, cache);
797
798   return i == 1;
799 }
800
801
802 static gboolean
803 write_image_data (FILE *cache, ImageData *image_data, int offset)
804 {
805   guint8 *s;
806   guint len;
807   gint i;
808   GdkPixdata *pixdata = &image_data->pixdata;
809
810   /* Type 0 is GdkPixdata */
811   if (!write_card32 (cache, 0))
812     return FALSE;
813
814   s = gdk_pixdata_serialize (pixdata, &len);
815
816   if (!write_card32 (cache, len))
817     {
818       g_free (s);
819       return FALSE;
820     }
821
822   i = fwrite (s, len, 1, cache);
823   
824   g_free (s);
825
826   return i == 1;
827 }
828
829 static gboolean
830 write_icon_data (FILE *cache, IconData *icon_data, int offset)
831 {
832   int ofs = offset + 12;
833   int j;
834   int tmp, tmp2;
835
836   if (icon_data->has_embedded_rect)
837     {
838       if (!write_card32 (cache, ofs))
839         return FALSE;
840               
841        ofs += 8;
842     }         
843   else
844     {
845       if (!write_card32 (cache, 0))
846         return FALSE;
847     }
848               
849   if (icon_data->n_attach_points > 0)
850     {
851       if (!write_card32 (cache, ofs))
852         return FALSE;
853
854       ofs += 4 + 4 * icon_data->n_attach_points;
855     }
856   else
857     {
858       if (!write_card32 (cache, 0))
859         return FALSE;
860     }
861
862   if (icon_data->n_display_names > 0)
863     {
864       if (!write_card32 (cache, ofs))
865         return FALSE;
866     }
867   else
868     {
869       if (!write_card32 (cache, 0))
870         return FALSE;
871     }
872
873   if (icon_data->has_embedded_rect)
874     {
875       if (!write_card16 (cache, icon_data->x0) ||
876           !write_card16 (cache, icon_data->y0) ||
877           !write_card16 (cache, icon_data->x1) ||
878           !write_card16 (cache, icon_data->y1))
879         return FALSE;
880     }
881
882   if (icon_data->n_attach_points > 0)
883     {
884       if (!write_card32 (cache, icon_data->n_attach_points))
885         return FALSE;
886                   
887       for (j = 0; j < 2 * icon_data->n_attach_points; j++)
888         {
889           if (!write_card16 (cache, icon_data->attach_points[j]))
890             return FALSE;
891         }                 
892     }
893
894   if (icon_data->n_display_names > 0)
895     {
896       if (!write_card32 (cache, icon_data->n_display_names))
897         return FALSE;
898
899       ofs += 4 + 8 * icon_data->n_display_names;
900
901       tmp = ofs;
902       for (j = 0; j < 2 * icon_data->n_display_names; j++)
903         {
904           tmp2 = find_string (icon_data->display_names[j]);
905           if (tmp2 == 0 || tmp2 == -1)
906             {
907               tmp2 = tmp;
908               tmp += ALIGN_VALUE (strlen (icon_data->display_names[j]) + 1, 4);
909               /* We're playing a little game with negative
910                * offsets here to handle duplicate strings in 
911                * the array.
912                */
913               add_string (icon_data->display_names[j], -tmp2);
914             }
915           else if (tmp2 < 0)
916             {
917               tmp2 = -tmp2;
918             }
919
920           if (!write_card32 (cache, tmp2))
921             return FALSE;
922
923         }
924
925       g_assert (ofs == ftell (cache));
926       for (j = 0; j < 2 * icon_data->n_display_names; j++)
927         {
928           tmp2 = find_string (icon_data->display_names[j]);
929           g_assert (tmp2 != 0 && tmp2 != -1);
930           if (tmp2 < 0)
931             {
932               tmp2 = -tmp2;
933               g_assert (tmp2 == ftell (cache));
934               add_string (icon_data->display_names[j], tmp2);
935               if (!write_string (cache, icon_data->display_names[j]))
936                 return FALSE;
937             }
938         }
939     }        
940
941   return TRUE;
942 }
943
944 static gboolean
945 write_header (FILE *cache, guint32 dir_list_offset)
946 {
947   return (write_card16 (cache, MAJOR_VERSION) &&
948           write_card16 (cache, MINOR_VERSION) &&
949           write_card32 (cache, HASH_OFFSET) &&
950           write_card32 (cache, dir_list_offset));
951 }
952
953 static gint
954 get_image_meta_data_size (Image *image)
955 {
956   gint i;
957
958   /* The complication with storing the size in both
959    * IconData and Image is necessary since we attribute
960    * the size of the IconData only to the first Image
961    * using it (at which time it is written out in the 
962    * cache). Later Images just refer to the written out
963    * IconData via the offset.
964    */
965   if (image->icon_data_size == 0)
966     {
967       if (image->icon_data && image->icon_data->size < 0)
968         {
969           IconData *data = image->icon_data;
970
971           data->size = 0;
972
973           if (data->has_embedded_rect ||
974               data->n_attach_points > 0 ||
975               data->n_display_names > 0)
976             data->size += 12;
977
978           if (data->has_embedded_rect)
979             data->size += 8;
980
981           if (data->n_attach_points > 0)
982             data->size += 4 + data->n_attach_points * 4;
983
984           if (data->n_display_names > 0)
985             {
986               data->size += 4 + 8 * data->n_display_names;
987
988               for (i = 0; data->display_names[i]; i++)
989                 { 
990                   int poolv;
991                   if ((poolv = find_string (data->display_names[i])) == 0)
992                     {
993                       data->size += ALIGN_VALUE (strlen (data->display_names[i]) + 1, 4);
994                       /* Adding the string to the pool with -1
995                        * to indicate that it hasn't been written out
996                        * to the cache yet. We still need it in the
997                        * pool in case the same string occurs twice
998                        * during a get_single_node_size() calculation.
999                        */
1000                       add_string (data->display_names[i], -1);
1001                     }
1002                 }
1003            } 
1004
1005           image->icon_data_size = data->size;
1006           data->size = 0;
1007         }
1008     }
1009
1010   g_assert (image->icon_data_size % 4 == 0);
1011
1012   return image->icon_data_size;
1013 }
1014
1015 static gint
1016 get_image_pixel_data_size (Image *image)
1017 {
1018   /* The complication with storing the size in both
1019    * ImageData and Image is necessary since we attribute
1020    * the size of the ImageData only to the first Image
1021    * using it (at which time it is written out in the 
1022    * cache). Later Images just refer to the written out
1023    * ImageData via the offset.
1024    */
1025   if (image->pixel_data_size == 0)
1026     {
1027       if (image->image_data && 
1028           image->image_data->has_pixdata)
1029         {
1030           image->pixel_data_size = image->image_data->size;
1031           image->image_data->size = 0;
1032         }
1033     }
1034
1035   g_assert (image->pixel_data_size % 4 == 0);
1036
1037   return image->pixel_data_size;
1038 }
1039
1040 static gint
1041 get_image_data_size (Image *image)
1042 {
1043   gint len;
1044   
1045   len = 0;
1046
1047   len += get_image_pixel_data_size (image);
1048   len += get_image_meta_data_size (image);
1049
1050   /* Even if len is zero, we need to reserve space to
1051    * write the ImageData, unless this is an .svg without 
1052    * .icon, in which case both image_data and icon_data
1053    * are NULL.
1054    */
1055   if (len > 0 || image->image_data || image->icon_data)
1056     len += 8;
1057
1058   return len;
1059 }
1060
1061 static void
1062 get_single_node_size (HashNode *node, int *node_size, int *image_data_size)
1063 {
1064   GList *list;
1065
1066   /* Node pointers */
1067   *node_size = 12;
1068
1069   /* Name */
1070   if (find_string (node->name) == 0)
1071     {
1072       *node_size += ALIGN_VALUE (strlen (node->name) + 1, 4);
1073       add_string (node->name, -1);
1074     }
1075
1076   /* Image list */
1077   *node_size += 4 + g_list_length (node->image_list) * 8;
1078  
1079   /* Image data */
1080   *image_data_size = 0;
1081   for (list = node->image_list; list; list = list->next)
1082     {
1083       Image *image = list->data;
1084
1085       *image_data_size += get_image_data_size (image);
1086     }
1087 }
1088
1089 static gboolean
1090 write_bucket (FILE *cache, HashNode *node, int *offset)
1091 {
1092   while (node != NULL)
1093     {
1094       int node_size, image_data_size;
1095       int next_offset, image_data_offset;
1096       int data_offset;
1097       int name_offset;
1098       int name_size;
1099       int image_list_offset;
1100       int tmp;
1101       int i, len;
1102       GList *list;
1103
1104       g_assert (*offset == ftell (cache));
1105
1106       node->offset = *offset;
1107           
1108       get_single_node_size (node, &node_size, &image_data_size);
1109       g_assert (node_size % 4 == 0);
1110       g_assert (image_data_size % 4 == 0);
1111       image_data_offset = *offset + node_size;
1112       next_offset = *offset + node_size + image_data_size;
1113       /* Chain offset */
1114       if (node->next != NULL)
1115         {
1116           if (!write_card32 (cache, next_offset))
1117             return FALSE;
1118         }
1119       else
1120         {
1121           if (!write_card32 (cache, 0xffffffff))
1122             return FALSE;
1123         }
1124       
1125       name_size = 0;
1126       name_offset = find_string (node->name);
1127       if (name_offset <= 0)
1128         {
1129           name_offset = *offset + 12;
1130           name_size = ALIGN_VALUE (strlen (node->name) + 1, 4);
1131           add_string (node->name, name_offset);
1132         }
1133       if (!write_card32 (cache, name_offset))
1134         return FALSE;
1135       
1136       image_list_offset = *offset + 12 + name_size;
1137       if (!write_card32 (cache, image_list_offset))
1138         return FALSE;
1139       
1140       /* Icon name */
1141       if (name_size > 0)
1142         {
1143           if (!write_string (cache, node->name))
1144             return FALSE;
1145         }
1146
1147       /* Image list */
1148       len = g_list_length (node->image_list);
1149       if (!write_card32 (cache, len))
1150         return FALSE;
1151       
1152       /* Image data goes right after the image list */
1153       tmp = image_list_offset + 4 + len * 8;
1154
1155       list = node->image_list;
1156       data_offset = image_data_offset;
1157       for (i = 0; i < len; i++)
1158         {
1159           Image *image = list->data;
1160           int image_data_size = get_image_data_size (image);
1161
1162           /* Directory index */
1163           if (!write_card16 (cache, image->dir_index))
1164             return FALSE;
1165           
1166           /* Flags */
1167           if (!write_card16 (cache, image->flags))
1168             return FALSE;
1169
1170           /* Image data offset */
1171           if (image_data_size > 0)
1172             {
1173               if (!write_card32 (cache, data_offset))
1174                 return FALSE;
1175               data_offset += image_data_size;
1176             }
1177           else 
1178             {
1179               if (!write_card32 (cache, 0))
1180                 return FALSE;
1181             }
1182
1183           list = list->next;
1184         }
1185
1186       /* Now write the image data */
1187       list = node->image_list;
1188       for (i = 0; i < len; i++, list = list->next)
1189         {
1190           Image *image = list->data;
1191           int pixel_data_size = get_image_pixel_data_size (image);
1192           int meta_data_size = get_image_meta_data_size (image);
1193
1194           if (get_image_data_size (image) == 0)
1195             continue;
1196
1197           /* Pixel data */
1198           if (pixel_data_size > 0) 
1199             {
1200               image->image_data->offset = image_data_offset + 8;
1201               if (!write_card32 (cache, image->image_data->offset))
1202                 return FALSE;
1203             }
1204           else
1205             {
1206               if (!write_card32 (cache, (guint32) image->image_data ? image->image_data->offset : 0))
1207                 return FALSE;
1208             }
1209
1210           if (meta_data_size > 0)
1211             {
1212               image->icon_data->offset = image_data_offset + pixel_data_size + 8;
1213               if (!write_card32 (cache, image->icon_data->offset))
1214                 return FALSE;
1215             }
1216           else
1217             {
1218               if (!write_card32 (cache, image->icon_data ? image->icon_data->offset : 0))
1219                 return FALSE;
1220             }
1221
1222           if (pixel_data_size > 0)
1223             {
1224               if (!write_image_data (cache, image->image_data, image->image_data->offset))
1225                 return FALSE;
1226             }
1227           
1228           if (meta_data_size > 0)
1229             {
1230               if (!write_icon_data (cache, image->icon_data, image->icon_data->offset))
1231                 return FALSE;
1232             }
1233
1234           image_data_offset += pixel_data_size + meta_data_size + 8;
1235         }
1236       
1237       *offset = next_offset;
1238       node = node->next;
1239     }
1240   
1241   return TRUE;
1242 }
1243
1244 static gboolean
1245 write_hash_table (FILE *cache, HashContext *context, int *new_offset)
1246 {
1247   int offset = HASH_OFFSET;
1248   int node_offset;
1249   int i;
1250
1251   if (!(write_card32 (cache, context->size)))
1252     return FALSE;
1253
1254   offset += 4;
1255   node_offset = offset + context->size * 4;
1256   /* Just write zeros here, we will rewrite this later */  
1257   for (i = 0; i < context->size; i++)
1258     {
1259       if (!write_card32 (cache, 0))
1260         return FALSE;
1261     }
1262
1263   /* Now write the buckets */
1264   for (i = 0; i < context->size; i++)
1265     {
1266       if (!context->nodes[i])
1267         continue;
1268
1269       g_assert (node_offset % 4 == 0);
1270       if (!write_bucket (cache, context->nodes[i], &node_offset))
1271         return FALSE;
1272     }
1273
1274   *new_offset = node_offset;
1275
1276   /* Now write out the bucket offsets */
1277
1278   fseek (cache, offset, SEEK_SET);
1279
1280   for (i = 0; i < context->size; i++)
1281     {
1282       if (context->nodes[i] != NULL)
1283         node_offset = context->nodes[i]->offset;
1284       else
1285         node_offset = 0xffffffff;
1286       if (!write_card32 (cache, node_offset))
1287         return FALSE;
1288     }
1289
1290   fseek (cache, 0, SEEK_END);
1291
1292   return TRUE;
1293 }
1294
1295 static gboolean
1296 write_dir_index (FILE *cache, int offset, GList *directories)
1297 {
1298   int n_dirs;
1299   GList *d;
1300   char *dir;
1301   int tmp, tmp2;
1302
1303   n_dirs = g_list_length (directories);
1304
1305   if (!write_card32 (cache, n_dirs))
1306     return FALSE;
1307
1308   offset += 4 + n_dirs * 4;
1309
1310   tmp = offset;
1311   for (d = directories; d; d = d->next)
1312     {
1313       dir = d->data;
1314   
1315       tmp2 = find_string (dir);
1316     
1317       if (tmp2 == 0 || tmp2 == -1)
1318         {
1319           tmp2 = tmp;
1320           tmp += ALIGN_VALUE (strlen (dir) + 1, 4);
1321           /* We're playing a little game with negative
1322            * offsets here to handle duplicate strings in 
1323            * the array, even though that should not 
1324            * really happen for the directory index.
1325            */
1326           add_string (dir, -tmp2);
1327         }
1328       else if (tmp2 < 0)
1329         {
1330           tmp2 = -tmp2;
1331         }
1332
1333       if (!write_card32 (cache, tmp2))
1334         return FALSE;
1335     }
1336
1337   g_assert (offset == ftell (cache));
1338   for (d = directories; d; d = d->next)
1339     {
1340       dir = d->data;
1341
1342       tmp2 = find_string (dir);
1343       g_assert (tmp2 != 0 && tmp2 != -1);
1344       if (tmp2 < 0)
1345         {
1346           tmp2 = -tmp2;
1347           g_assert (tmp2 == ftell (cache));
1348           add_string (dir, tmp2); 
1349           if (!write_string (cache, dir))
1350             return FALSE;
1351         }
1352     }
1353   
1354   return TRUE;
1355 }
1356
1357 static gboolean
1358 write_file (FILE *cache, GHashTable *files, GList *directories)
1359 {
1360   HashContext context;
1361   int new_offset;
1362
1363   /* Convert the hash table into something looking a bit more
1364    * like what we want to write to disk.
1365    */
1366   context.size = g_spaced_primes_closest (g_hash_table_size (files) / 3);
1367   context.nodes = g_new0 (HashNode *, context.size);
1368   
1369   g_hash_table_foreach_remove (files, convert_to_hash, &context);
1370
1371   /* Now write the file */
1372   /* We write 0 as the directory list offset and go
1373    * back and change it later */
1374   if (!write_header (cache, 0))
1375     {
1376       g_printerr (_("Failed to write header\n"));
1377       return FALSE;
1378     }
1379
1380   if (!write_hash_table (cache, &context, &new_offset))
1381     {
1382       g_printerr (_("Failed to write hash table\n"));
1383       return FALSE;
1384     }
1385
1386   if (!write_dir_index (cache, new_offset, directories))
1387     {
1388       g_printerr (_("Failed to write folder index\n"));
1389       return FALSE;
1390     }
1391   
1392   rewind (cache);
1393
1394   if (!write_header (cache, new_offset))
1395     {
1396       g_printerr (_("Failed to rewrite header\n"));
1397       return FALSE;
1398     }
1399     
1400   return TRUE;
1401 }
1402
1403 static gboolean
1404 validate_file (const gchar *file)
1405 {
1406   GMappedFile *map;
1407   CacheInfo info;
1408
1409   map = g_mapped_file_new (file, FALSE, NULL);
1410   if (!map)
1411     return FALSE;
1412
1413   info.cache = g_mapped_file_get_contents (map);
1414   info.cache_size = g_mapped_file_get_length (map);
1415   info.n_directories = 0;
1416   info.flags = CHECK_OFFSETS|CHECK_STRINGS|CHECK_PIXBUFS;
1417
1418   if (!_gtk_icon_cache_validate (&info)) 
1419     {
1420       g_mapped_file_free (map);
1421       return FALSE;
1422     }
1423   
1424   g_mapped_file_free (map);
1425
1426   return TRUE;
1427 }
1428
1429 static void
1430 build_cache (const gchar *path)
1431 {
1432   gchar *cache_path, *tmp_cache_path;
1433 #ifdef G_OS_WIN32
1434   gchar *bak_cache_path = NULL;
1435 #endif
1436   GHashTable *files;
1437   gboolean retval;
1438   FILE *cache;
1439   struct stat path_stat, cache_stat;
1440   struct utimbuf utime_buf;
1441   GList *directories = NULL;
1442   
1443   tmp_cache_path = g_build_filename (path, "."CACHE_NAME, NULL);
1444   cache = g_fopen (tmp_cache_path, "wb");
1445   
1446   if (!cache)
1447     {
1448       g_printerr (_("Failed to write cache file: %s\n"), g_strerror (errno));
1449       exit (1);
1450     }
1451
1452   files = g_hash_table_new (g_str_hash, g_str_equal);
1453   image_data_hash = g_hash_table_new (g_str_hash, g_str_equal);
1454   icon_data_hash = g_hash_table_new (g_str_hash, g_str_equal);
1455   string_pool = g_hash_table_new (g_str_hash, g_str_equal);
1456  
1457   directories = scan_directory (path, NULL, files, NULL, 0);
1458
1459   if (g_hash_table_size (files) == 0)
1460     {
1461       /* Empty table, just close and remove the file */
1462
1463       fclose (cache);
1464       g_unlink (tmp_cache_path);
1465       exit (0);
1466     }
1467     
1468   /* FIXME: Handle failure */
1469   retval = write_file (cache, files, directories);
1470   fclose (cache);
1471
1472   g_list_foreach (directories, (GFunc)g_free, NULL);
1473   g_list_free (directories);
1474   
1475   if (!retval)
1476     {
1477       g_unlink (tmp_cache_path);
1478       exit (1);
1479     }
1480
1481   if (!validate_file (tmp_cache_path))
1482     {
1483       g_printerr (_("The generated cache was invalid.\n"));
1484       g_unlink (tmp_cache_path);
1485       exit (1);
1486     }
1487
1488   cache_path = g_build_filename (path, CACHE_NAME, NULL);
1489
1490 #ifdef G_OS_WIN32
1491   if (g_file_test (cache_path, G_FILE_TEST_EXISTS))
1492     {
1493       bak_cache_path = g_strconcat (cache_path, ".bak", NULL);
1494       g_unlink (bak_cache_path);
1495       if (g_rename (cache_path, bak_cache_path) == -1)
1496         {
1497           g_printerr (_("Could not rename %s to %s: %s, removing %s then.\n"),
1498                       cache_path, bak_cache_path,
1499                       g_strerror (errno),
1500                       cache_path);
1501           g_unlink (cache_path);
1502           bak_cache_path = NULL;
1503         }
1504     }
1505 #endif
1506
1507   if (g_rename (tmp_cache_path, cache_path) == -1)
1508     {
1509       g_printerr (_("Could not rename %s to %s: %s\n"),
1510                   tmp_cache_path, cache_path,
1511                   g_strerror (errno));
1512       g_unlink (tmp_cache_path);
1513 #ifdef G_OS_WIN32
1514       if (bak_cache_path != NULL)
1515         if (g_rename (bak_cache_path, cache_path) == -1)
1516           g_printerr (_("Could not rename %s back to %s: %s.\n"),
1517                       bak_cache_path, cache_path,
1518                       g_strerror (errno));
1519 #endif
1520       exit (1);
1521     }
1522 #ifdef G_OS_WIN32
1523   if (bak_cache_path != NULL)
1524     g_unlink (bak_cache_path);
1525 #endif
1526
1527   /* Update time */
1528   /* FIXME: What do do if an error occurs here? */
1529   if (g_stat (path, &path_stat) < 0 ||
1530       g_stat (cache_path, &cache_stat))
1531     exit (1);
1532
1533   utime_buf.actime = path_stat.st_atime;
1534   utime_buf.modtime = cache_stat.st_mtime;
1535   utime (path, &utime_buf);
1536   
1537   if (!quiet)
1538     g_printerr (_("Cache file created successfully.\n"));
1539 }
1540
1541 static void
1542 write_csource (const gchar *path)
1543 {
1544   gchar *cache_path;
1545   gchar *data;
1546   gsize len;
1547   gint i;
1548
1549   cache_path = g_build_filename (path, CACHE_NAME, NULL);
1550   if (!g_file_get_contents (cache_path, &data, &len, NULL))
1551     exit (1);
1552   
1553   g_printf ("#ifdef __SUNPRO_C\n");
1554   g_printf ("#pragma align 4 (%s)\n", var_name);   
1555   g_printf ("#endif\n");
1556   
1557   g_printf ("#ifdef __GNUC__\n");
1558   g_printf ("static const guint8 %s[] __attribute__ ((__aligned__ (4))) = \n", var_name);
1559   g_printf ("#else\n");
1560   g_printf ("static const guint8 %s[] = \n", var_name);
1561   g_printf ("#endif\n");
1562
1563   g_printf ("{\n");
1564   for (i = 0; i < len - 1; i++)
1565     {
1566       if (i %12 == 0)
1567         g_printf ("  ");
1568       g_printf ("0x%02x, ", (guint8)data[i]);
1569       if (i % 12 == 11)
1570         g_printf ("\n");
1571     }
1572   
1573   g_printf ("0x%02x\n};\n", (guint8)data[i]);
1574 }
1575
1576 static GOptionEntry args[] = {
1577   { "force", 'f', 0, G_OPTION_ARG_NONE, &force_update, N_("Overwrite an existing cache, even if up to date"), NULL },
1578   { "ignore-theme-index", 't', 0, G_OPTION_ARG_NONE, &ignore_theme_index, N_("Don't check for the existence of index.theme"), NULL },
1579   { "index-only", 'i', 0, G_OPTION_ARG_NONE, &index_only, N_("Don't include image data in the cache"), NULL },
1580   { "source", 'c', 0, G_OPTION_ARG_STRING, &var_name, N_("Output a C header file"), "NAME" },
1581   { "quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet, N_("Turn off verbose output"), NULL },
1582   { "validate", 'v', 0, G_OPTION_ARG_NONE, &validate, N_("Validate existing icon cache"), NULL },
1583   { NULL }
1584 };
1585
1586 int
1587 main (int argc, char **argv)
1588 {
1589   gchar *path;
1590   GOptionContext *context;
1591
1592   if (argc < 2)
1593     return 0;
1594   
1595   setlocale (LC_ALL, "");
1596
1597   bindtextdomain (GETTEXT_PACKAGE, GTK_LOCALEDIR);
1598   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
1599
1600   context = g_option_context_new ("ICONPATH");
1601   g_option_context_add_main_entries (context, args, GETTEXT_PACKAGE);
1602
1603   g_option_context_parse (context, &argc, &argv, NULL);
1604   
1605   path = argv[1];
1606 #ifdef G_OS_WIN32
1607   path = g_locale_to_utf8 (path, -1, NULL, NULL, NULL);
1608 #endif
1609   
1610   if (validate)
1611     {
1612        gchar *file = g_build_filename (path, CACHE_NAME, NULL);
1613
1614        if (!g_file_test (file, G_FILE_TEST_IS_REGULAR))
1615          {
1616             if (!quiet)
1617               g_printerr (_("File not found: %s\n"), file);
1618             exit (1);
1619          }
1620        if (!validate_file (file))
1621          {
1622            if (!quiet)
1623              g_printerr (_("Not a valid icon cache: %s\n"), file);
1624            exit (1);
1625          }
1626        else 
1627          {
1628            exit (0);
1629          }
1630     }
1631
1632   if (!ignore_theme_index && !has_theme_index (path))
1633     {
1634       g_printerr (_("No theme index file in '%s'.\n"
1635                     "If you really want to create an icon cache here, use --ignore-theme-index.\n"), path);
1636       return 1;
1637     }
1638   
1639   if (!force_update && is_cache_up_to_date (path))
1640     return 0;
1641
1642   g_type_init ();
1643   build_cache (path);
1644
1645   if (strcmp (var_name, "-") != 0)
1646     write_csource (path);
1647
1648   return 0;
1649 }