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