2 * Copyright (C) 2004 Anders Carlsson <andersca@gnome.org>
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.
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.
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.
25 #include <sys/types.h>
32 #include <glib/gstdio.h>
33 #include <gdk-pixbuf/gdk-pixdata.h>
35 static gboolean force_update = FALSE;
36 static gboolean quiet = FALSE;
38 #define CACHE_NAME "icon-theme.cache"
40 #define HAS_SUFFIX_XPM (1 << 0)
41 #define HAS_SUFFIX_SVG (1 << 1)
42 #define HAS_SUFFIX_PNG (1 << 2)
43 #define HAS_ICON_FILE (1 << 3)
45 #define CAN_CACHE_IMAGE_DATA(flags) (((flags) & HAS_SUFFIX_PNG) || ((flags) & HAS_SUFFIX_XPM))
47 #define MAJOR_VERSION 1
48 #define MINOR_VERSION 0
49 #define HASH_OFFSET 12
51 #define ALIGN_VALUE(this, boundary) \
52 (( ((unsigned long)(this)) + (((unsigned long)(boundary)) -1)) & (~(((unsigned long)(boundary))-1)))
55 is_cache_up_to_date (const gchar *path)
57 struct stat path_stat, cache_stat;
61 retval = g_stat (path, &path_stat);
65 /* We can't stat the path,
66 * assume we have a updated cache */
70 cache_path = g_build_filename (path, CACHE_NAME, NULL);
71 retval = g_stat (cache_path, &cache_stat);
76 /* Cache file not found */
81 return cache_stat.st_mtime >= path_stat.st_mtime;
92 int has_embedded_rect;
103 foreach_remove_func (gpointer key, gpointer value, gpointer user_data)
105 Image *image = (Image *)value;
106 GHashTable *files = user_data;
108 gboolean free_key = FALSE;
110 if (image->flags == HAS_ICON_FILE)
114 g_free (image->attach_points);
115 g_strfreev (image->display_names);
120 list = g_hash_table_lookup (files, key);
124 list = g_list_prepend (list, value);
125 g_hash_table_insert (files, key, list);
134 load_icon_data (Image *image, const char *path)
143 GError *error = NULL;
147 icon_file = g_key_file_new ();
148 g_key_file_set_list_separator (icon_file, ',');
149 g_key_file_load_from_file (icon_file, path, G_KEY_FILE_KEEP_TRANSLATIONS, &error);
152 g_error_free (error);
156 ivalues = g_key_file_get_integer_list (icon_file,
157 "Icon Data", "EmbeddedTextRectangle",
163 image->has_embedded_rect = TRUE;
164 image->x0 = ivalues[0];
165 image->y0 = ivalues[1];
166 image->x1 = ivalues[2];
167 image->y1 = ivalues[3];
173 str = g_key_file_get_string (icon_file, "Icon Data", "AttachPoints", NULL);
176 split = g_strsplit (str, "|", -1);
178 image->n_attach_points = g_strv_length (split);
179 image->attach_points = g_new (int, 2 * image->n_attach_points);
182 while (split[i] != NULL && i < image->n_attach_points)
184 split_point = strchr (split[i], ',');
189 image->attach_points[2 * i] = atoi (split[i]);
190 image->attach_points[2 * i + 1] = atoi (split_point);
199 keys = g_key_file_get_keys (icon_file, "Icon Data", &n_keys, &error);
200 image->display_names = g_new0 (gchar *, 2 * n_keys + 1);
201 image->n_display_names = 0;
203 for (i = 0; i < n_keys; i++)
207 if (g_str_has_prefix (keys[i], "DisplayName"))
209 gchar *open, *close = NULL;
211 open = strchr (keys[i], '[');
214 close = strchr (open, ']');
218 lang = g_strndup (open + 1, close - open - 1);
219 name = g_key_file_get_locale_string (icon_file,
220 "Icon Data", "DisplayName",
225 lang = g_strdup ("C");
226 name = g_key_file_get_string (icon_file,
227 "Icon Data", "DisplayName",
231 image->display_names[2 * image->n_display_names] = lang;
232 image->display_names[2 * image->n_display_names + 1] = name;
233 image->n_display_names++;
239 g_key_file_free (icon_file);
243 maybe_cache_image_data (Image *image, const gchar *path)
245 if (CAN_CACHE_IMAGE_DATA(image->flags) && !image->has_pixdata)
249 pixbuf = gdk_pixbuf_new_from_file (path, NULL);
253 image->has_pixdata = TRUE;
254 gdk_pixdata_from_pixbuf (&image->pixdata, pixbuf, FALSE);
260 scan_directory (const gchar *base_path,
266 GHashTable *dir_hash;
270 gboolean dir_added = FALSE;
271 guint dir_index = 0xffff;
273 dir_path = g_build_filename (base_path, subdir, NULL);
275 /* FIXME: Use the gerror */
276 dir = g_dir_open (dir_path, 0, NULL);
281 dir_hash = g_hash_table_new (g_str_hash, g_str_equal);
283 while ((name = g_dir_read_name (dir)))
289 gchar *basename, *dot;
291 path = g_build_filename (dir_path, name, NULL);
292 retval = g_file_test (path, G_FILE_TEST_IS_DIR);
298 subsubdir = g_build_filename (subdir, name, NULL);
300 subsubdir = g_strdup (name);
301 directories = scan_directory (base_path, subsubdir, files,
302 directories, depth + 1);
308 retval = g_file_test (path, G_FILE_TEST_IS_REGULAR);
312 if (g_str_has_suffix (name, ".png"))
313 flags |= HAS_SUFFIX_PNG;
314 else if (g_str_has_suffix (name, ".svg"))
315 flags |= HAS_SUFFIX_SVG;
316 else if (g_str_has_suffix (name, ".xpm"))
317 flags |= HAS_SUFFIX_XPM;
318 else if (g_str_has_suffix (name, ".icon"))
319 flags |= HAS_ICON_FILE;
324 basename = g_strdup (name);
325 dot = strrchr (basename, '.');
328 image = g_hash_table_lookup (dir_hash, basename);
331 image->flags |= flags;
332 maybe_cache_image_data (image, path);
341 dir_index = g_list_length (directories);
342 directories = g_list_append (directories, g_strdup (subdir));
348 image = g_new0 (Image, 1);
349 image->flags = flags;
350 image->dir_index = dir_index;
351 maybe_cache_image_data (image, path);
353 g_hash_table_insert (dir_hash, g_strdup (basename), image);
356 if (g_str_has_suffix (name, ".icon"))
357 load_icon_data (image, path);
368 /* Move dir into the big file hash */
369 g_hash_table_foreach_remove (dir_hash, foreach_remove_func, files);
371 g_hash_table_destroy (dir_hash);
376 typedef struct _HashNode HashNode;
386 icon_name_hash (gconstpointer key)
388 const signed char *p = key;
392 for (p += 1; *p != '\0'; p++)
393 h = (h << 5) - h + *p;
404 convert_to_hash (gpointer key, gpointer value, gpointer user_data)
406 HashContext *context = user_data;
410 hash = icon_name_hash (key) % context->size;
412 node = g_new0 (HashNode, 1);
415 node->image_list = value;
417 if (context->nodes[hash] != NULL)
418 node->next = context->nodes[hash];
420 context->nodes[hash] = node;
426 write_string (FILE *cache, const gchar *n)
431 l = ALIGN_VALUE (strlen (n) + 1, 4);
436 i = fwrite (s, l, 1, cache);
443 write_card16 (FILE *cache, guint16 n)
448 *((guint16 *)s) = GUINT16_TO_BE (n);
450 i = fwrite (s, 2, 1, cache);
456 write_card32 (FILE *cache, guint32 n)
461 *((guint32 *)s) = GUINT32_TO_BE (n);
463 i = fwrite (s, 4, 1, cache);
470 write_pixdata (FILE *cache, GdkPixdata *pixdata)
477 /* Type 0 is GdkPixdata */
478 if (!write_card32 (cache, 0))
481 s = gdk_pixdata_serialize (pixdata, &len);
483 if (!write_card32 (cache, len))
489 i = fwrite (s, len, 1, cache);
497 write_header (FILE *cache, guint32 dir_list_offset)
499 return (write_card16 (cache, MAJOR_VERSION) &&
500 write_card16 (cache, MINOR_VERSION) &&
501 write_card32 (cache, HASH_OFFSET) &&
502 write_card32 (cache, dir_list_offset));
506 get_image_meta_data_size (Image *image)
511 if (image->has_embedded_rect ||
512 image->attach_points > 0 ||
513 image->n_display_names > 0)
516 if (image->has_embedded_rect)
519 if (image->n_attach_points > 0)
520 len += 4 + image->n_attach_points * 4;
522 if (image->n_display_names > 0)
524 len += 4 + 8 * image->n_display_names;
526 for (i = 0; image->display_names[i]; i++)
527 len += ALIGN_VALUE (strlen (image->display_names[i]) + 1, 4);
534 get_image_pixel_data_size (Image *image)
536 if (image->has_pixdata)
537 return image->pixdata.length + 8;
543 get_image_data_size (Image *image)
549 len += get_image_pixel_data_size (image);
550 len += get_image_meta_data_size (image);
559 get_single_node_size (HashNode *node, gboolean include_image_data)
568 len += ALIGN_VALUE (strlen (node->name) + 1, 4);
571 len += 4 + g_list_length (node->image_list) * 8;
574 if (include_image_data)
575 for (list = node->image_list; list; list = list->next)
577 Image *image = list->data;
579 len += get_image_data_size (image);
586 get_bucket_size (HashNode *node)
592 len += get_single_node_size (node, TRUE);
601 write_bucket (FILE *cache, HashNode *node, int *offset)
605 int next_offset = *offset + get_single_node_size (node, TRUE);
606 int image_data_offset = *offset + get_single_node_size (node, FALSE);
613 if (node->next != NULL)
615 if (!write_card32 (cache, next_offset))
620 if (!write_card32 (cache, 0xffffffff))
624 /* Icon name offset */
625 if (!write_card32 (cache, *offset + 12))
628 /* Image list offset */
629 tmp = *offset + 12 + ALIGN_VALUE (strlen (node->name) + 1, 4);
630 if (!write_card32 (cache, tmp))
634 if (!write_string (cache, node->name))
638 len = g_list_length (node->image_list);
639 if (!write_card32 (cache, len))
642 /* Image data goes right after the image list */
645 list = node->image_list;
646 data_offset = image_data_offset;
647 for (i = 0; i < len; i++)
649 Image *image = list->data;
650 int image_data_size = get_image_data_size (image);
652 /* Directory index */
653 if (!write_card16 (cache, image->dir_index))
657 if (!write_card16 (cache, image->flags))
660 /* Image data offset */
661 if (image_data_size > 0)
663 if (!write_card32 (cache, data_offset))
665 data_offset += image_data_size;
669 if (!write_card32 (cache, 0))
676 /* Now write the image data */
677 list = node->image_list;
678 for (i = 0; i < len; i++, list = list->next)
680 Image *image = list->data;
681 int pixel_data_size = get_image_pixel_data_size (image);
682 int meta_data_size = get_image_meta_data_size (image);
684 if (meta_data_size + pixel_data_size == 0)
688 if (pixel_data_size > 0)
690 if (!write_card32 (cache, image_data_offset + 8))
695 if (!write_card32 (cache, 0))
699 if (meta_data_size > 0)
701 if (!write_card32 (cache, image_data_offset + pixel_data_size + 8))
706 if (!write_card32 (cache, 0))
710 if (pixel_data_size > 0)
712 if (!write_pixdata (cache, &image->pixdata))
716 if (meta_data_size > 0)
718 int ofs = image_data_offset + pixel_data_size + 20;
720 if (image->has_embedded_rect)
722 if (!write_card32 (cache, ofs))
729 if (!write_card32 (cache, 0))
733 if (image->n_attach_points > 0)
735 if (!write_card32 (cache, ofs))
738 ofs += 4 + 4 * image->n_attach_points;
742 if (!write_card32 (cache, 0))
746 if (image->n_display_names > 0)
748 if (!write_card32 (cache, ofs))
753 if (!write_card32 (cache, 0))
757 if (image->has_embedded_rect)
759 if (!write_card16 (cache, image->x0) ||
760 !write_card16 (cache, image->y0) ||
761 !write_card16 (cache, image->x1) ||
762 !write_card16 (cache, image->y1))
766 if (image->n_attach_points > 0)
768 if (!write_card32 (cache, image->n_attach_points))
771 for (j = 0; j < 2 * image->n_attach_points; j++)
773 if (!write_card16 (cache, image->attach_points[j]))
778 if (image->n_display_names > 0)
780 if (!write_card32 (cache, image->n_display_names))
783 ofs += 4 + 8 * image->n_display_names;
785 for (j = 0; j < 2 * image->n_display_names; j++)
787 if (!write_card32 (cache, ofs))
790 ofs += ALIGN_VALUE (strlen (image->display_names[j]) + 1, 4);
793 for (j = 0; j < 2 * image->n_display_names; j++)
795 if (!write_string (cache, image->display_names[j]))
801 image_data_offset += pixel_data_size + meta_data_size + 8;
804 *offset = next_offset;
812 write_hash_table (FILE *cache, HashContext *context, int *new_offset)
814 int offset = HASH_OFFSET;
818 if (!(write_card32 (cache, context->size)))
821 /* Size int + size * 4 */
822 node_offset = offset + 4 + context->size * 4;
824 for (i = 0; i < context->size; i++)
826 if (context->nodes[i] != NULL)
828 if (!write_card32 (cache, node_offset))
831 node_offset += get_bucket_size (context->nodes[i]);
835 if (!write_card32 (cache, 0xffffffff))
842 *new_offset = node_offset;
844 /* Now write the buckets */
845 node_offset = offset + 4 + context->size * 4;
847 for (i = 0; i < context->size; i++)
849 if (!context->nodes[i])
852 if (!write_bucket (cache, context->nodes[i], &node_offset))
860 write_dir_index (FILE *cache, int offset, GList *directories)
866 n_dirs = g_list_length (directories);
868 if (!write_card32 (cache, n_dirs))
871 offset += 4 + n_dirs * 4;
873 for (d = directories; d; d = d->next)
876 if (!write_card32 (cache, offset))
879 offset += ALIGN_VALUE (strlen (dir) + 1, 4);
882 for (d = directories; d; d = d->next)
886 if (!write_string (cache, dir))
894 write_file (FILE *cache, GHashTable *files, GList *directories)
899 /* Convert the hash table into something looking a bit more
900 * like what we want to write to disk.
902 context.size = g_spaced_primes_closest (g_hash_table_size (files) / 3);
903 context.nodes = g_new0 (HashNode *, context.size);
905 g_hash_table_foreach_remove (files, convert_to_hash, &context);
907 /* Now write the file */
908 /* We write 0 as the directory list offset and go
909 * back and change it later */
910 if (!write_header (cache, 0))
912 g_printerr ("Failed to write header\n");
916 if (!write_hash_table (cache, &context, &new_offset))
918 g_printerr ("Failed to write hash table\n");
922 if (!write_dir_index (cache, new_offset, directories))
924 g_printerr ("Failed to write directory index\n");
930 if (!write_header (cache, new_offset))
932 g_printerr ("Failed to rewrite header\n");
940 build_cache (const gchar *path)
942 gchar *cache_path, *tmp_cache_path;
946 struct stat path_stat, cache_stat;
947 struct utimbuf utime_buf;
948 GList *directories = NULL;
950 tmp_cache_path = g_build_filename (path, "."CACHE_NAME, NULL);
951 cache = g_fopen (tmp_cache_path, "wb");
955 g_printerr ("Failed to write cache file: %s\n", g_strerror (errno));
959 files = g_hash_table_new (g_str_hash, g_str_equal);
961 directories = scan_directory (path, NULL, files, NULL, 0);
963 if (g_hash_table_size (files) == 0)
965 /* Empty table, just close and remove the file */
968 g_unlink (tmp_cache_path);
972 /* FIXME: Handle failure */
973 retval = write_file (cache, files, directories);
976 g_list_foreach (directories, (GFunc)g_free, NULL);
977 g_list_free (directories);
981 g_unlink (tmp_cache_path);
985 cache_path = g_build_filename (path, CACHE_NAME, NULL);
987 if (g_rename (tmp_cache_path, cache_path) == -1)
989 g_unlink (tmp_cache_path);
994 /* FIXME: What do do if an error occurs here? */
995 if (g_stat (path, &path_stat) < 0 ||
996 g_stat (cache_path, &cache_stat))
999 utime_buf.actime = path_stat.st_atime;
1000 utime_buf.modtime = cache_stat.st_mtime;
1001 utime (path, &utime_buf);
1004 g_printerr ("Cache file created successfully.\n");
1007 static GOptionEntry args[] = {
1008 { "force", 'f', 0, G_OPTION_ARG_NONE, &force_update, "Overwrite an existing cache, even if uptodate", NULL },
1009 { "quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet, "Turn off verbose output", NULL },
1014 main (int argc, char **argv)
1017 GOptionContext *context;
1022 context = g_option_context_new ("ICONPATH");
1023 g_option_context_add_main_entries (context, args, NULL);
1025 g_option_context_parse (context, &argc, &argv, NULL);
1029 path = g_locale_to_utf8 (path, -1, NULL, NULL, NULL);
1032 if (!force_update && is_cache_up_to_date (path))