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