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 <sys/utime.h>
38 #include <glib/gstdio.h>
39 #include <gdk-pixbuf/gdk-pixdata.h>
41 static gboolean force_update = FALSE;
42 static gboolean quiet = FALSE;
44 #define CACHE_NAME "icon-theme.cache"
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)
51 #define CAN_CACHE_IMAGE_DATA(flags) (((flags) & HAS_SUFFIX_PNG) || ((flags) & HAS_SUFFIX_XPM))
53 #define MAJOR_VERSION 1
54 #define MINOR_VERSION 0
55 #define HASH_OFFSET 12
57 #define ALIGN_VALUE(this, boundary) \
58 (( ((unsigned long)(this)) + (((unsigned long)(boundary)) -1)) & (~(((unsigned long)(boundary))-1)))
61 is_cache_up_to_date (const gchar *path)
63 struct stat path_stat, cache_stat;
67 retval = g_stat (path, &path_stat);
71 /* We can't stat the path,
72 * assume we have a updated cache */
76 cache_path = g_build_filename (path, CACHE_NAME, NULL);
77 retval = g_stat (cache_path, &cache_stat);
82 /* Cache file not found */
87 return cache_stat.st_mtime >= path_stat.st_mtime;
95 guint pixel_data_size;
98 static GHashTable *image_data_hash = NULL;
105 ImageData *image_data;
106 guint pixel_data_size;
108 int has_embedded_rect;
115 char **display_names;
119 foreach_remove_func (gpointer key, gpointer value, gpointer user_data)
121 Image *image = (Image *)value;
122 GHashTable *files = user_data;
124 gboolean free_key = FALSE;
126 if (image->flags == HAS_ICON_FILE)
130 g_free (image->attach_points);
131 g_strfreev (image->display_names);
136 list = g_hash_table_lookup (files, key);
140 list = g_list_prepend (list, value);
141 g_hash_table_insert (files, key, list);
150 load_icon_data (Image *image, const char *path)
159 GError *error = NULL;
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);
168 g_error_free (error);
172 ivalues = g_key_file_get_integer_list (icon_file,
173 "Icon Data", "EmbeddedTextRectangle",
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];
189 str = g_key_file_get_string (icon_file, "Icon Data", "AttachPoints", NULL);
192 split = g_strsplit (str, "|", -1);
194 image->n_attach_points = g_strv_length (split);
195 image->attach_points = g_new (int, 2 * image->n_attach_points);
198 while (split[i] != NULL && i < image->n_attach_points)
200 split_point = strchr (split[i], ',');
205 image->attach_points[2 * i] = atoi (split[i]);
206 image->attach_points[2 * i + 1] = atoi (split_point);
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;
219 for (i = 0; i < n_keys; i++)
223 if (g_str_has_prefix (keys[i], "DisplayName"))
225 gchar *open, *close = NULL;
227 open = strchr (keys[i], '[');
230 close = strchr (open, ']');
234 lang = g_strndup (open + 1, close - open - 1);
235 name = g_key_file_get_locale_string (icon_file,
236 "Icon Data", "DisplayName",
241 lang = g_strdup ("C");
242 name = g_key_file_get_string (icon_file,
243 "Icon Data", "DisplayName",
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++;
255 g_key_file_free (icon_file);
259 * This function was copied from gtkfilesystemunix.c, it should
260 * probably go to GLib
263 canonicalize_filename (gchar *filename)
266 gboolean last_was_slash = FALSE;
273 if (*p == G_DIR_SEPARATOR)
276 *q++ = G_DIR_SEPARATOR;
278 last_was_slash = TRUE;
282 if (last_was_slash && *p == '.')
284 if (*(p + 1) == G_DIR_SEPARATOR ||
287 if (*(p + 1) == '\0')
292 else if (*(p + 1) == '.' &&
293 (*(p + 2) == G_DIR_SEPARATOR ||
296 if (q > filename + 1)
299 while (q > filename + 1 &&
300 *(q - 1) != G_DIR_SEPARATOR)
304 if (*(p + 2) == '\0')
312 last_was_slash = FALSE;
318 last_was_slash = FALSE;
325 if (q > filename + 1 && *(q - 1) == G_DIR_SEPARATOR)
332 follow_links (const gchar *path)
338 path2 = g_strdup (path);
339 while (g_file_test (path2, G_FILE_TEST_IS_SYMLINK))
341 target = g_file_read_link (path2, NULL);
345 if (g_path_is_absolute (target))
349 d = g_path_get_dirname (path2);
350 s = g_build_filename (d, target, NULL);
361 if (strcmp (path, path2) == 0)
371 maybe_cache_image_data (Image *image,
374 if (CAN_CACHE_IMAGE_DATA(image->flags) && !image->image_data)
380 idata = g_hash_table_lookup (image_data_hash, path);
382 path2 = follow_links (path);
388 canonicalize_filename (path2);
390 idata2 = g_hash_table_lookup (image_data_hash, path2);
392 if (idata && idata2 && idata != idata2)
393 g_error ("different idatas found for symlinked '%s' and '%s'\n",
396 if (idata && !idata2)
397 g_hash_table_insert (image_data_hash, g_strdup (path2), idata);
399 if (!idata && idata2)
401 g_hash_table_insert (image_data_hash, g_strdup (path), idata2);
408 idata = g_new0 (ImageData, 1);
409 g_hash_table_insert (image_data_hash, g_strdup (path), idata);
411 g_hash_table_insert (image_data_hash, g_strdup (path2), idata);
414 if (!idata->has_pixdata)
416 pixbuf = gdk_pixbuf_new_from_file (path, NULL);
420 gdk_pixdata_from_pixbuf (&idata->pixdata, pixbuf, FALSE);
421 idata->pixel_data_size = idata->pixdata.length + 8;
422 idata->has_pixdata = TRUE;
426 image->image_data = idata;
434 scan_directory (const gchar *base_path,
440 GHashTable *dir_hash;
444 gboolean dir_added = FALSE;
445 guint dir_index = 0xffff;
447 dir_path = g_build_filename (base_path, subdir, NULL);
449 /* FIXME: Use the gerror */
450 dir = g_dir_open (dir_path, 0, NULL);
455 dir_hash = g_hash_table_new (g_str_hash, g_str_equal);
457 while ((name = g_dir_read_name (dir)))
463 gchar *basename, *dot;
465 path = g_build_filename (dir_path, name, NULL);
466 retval = g_file_test (path, G_FILE_TEST_IS_DIR);
472 subsubdir = g_build_filename (subdir, name, NULL);
474 subsubdir = g_strdup (name);
475 directories = scan_directory (base_path, subsubdir, files,
476 directories, depth + 1);
482 retval = g_file_test (path, G_FILE_TEST_IS_REGULAR);
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;
497 basename = g_strdup (name);
498 dot = strrchr (basename, '.');
501 image = g_hash_table_lookup (dir_hash, basename);
504 image->flags |= flags;
505 maybe_cache_image_data (image, path);
514 dir_index = g_list_length (directories);
515 directories = g_list_append (directories, g_strdup (subdir));
521 image = g_new0 (Image, 1);
522 image->flags = flags;
523 image->dir_index = dir_index;
524 maybe_cache_image_data (image, path);
526 g_hash_table_insert (dir_hash, g_strdup (basename), image);
529 if (g_str_has_suffix (name, ".icon"))
530 load_icon_data (image, path);
540 /* Move dir into the big file hash */
541 g_hash_table_foreach_remove (dir_hash, foreach_remove_func, files);
543 g_hash_table_destroy (dir_hash);
548 typedef struct _HashNode HashNode;
558 icon_name_hash (gconstpointer key)
560 const signed char *p = key;
564 for (p += 1; *p != '\0'; p++)
565 h = (h << 5) - h + *p;
576 convert_to_hash (gpointer key, gpointer value, gpointer user_data)
578 HashContext *context = user_data;
582 hash = icon_name_hash (key) % context->size;
584 node = g_new0 (HashNode, 1);
587 node->image_list = value;
589 if (context->nodes[hash] != NULL)
590 node->next = context->nodes[hash];
592 context->nodes[hash] = node;
598 write_string (FILE *cache, const gchar *n)
603 l = ALIGN_VALUE (strlen (n) + 1, 4);
608 i = fwrite (s, l, 1, cache);
615 write_card16 (FILE *cache, guint16 n)
619 n = GUINT16_TO_BE (n);
621 i = fwrite ((char *)&n, 2, 1, cache);
627 write_card32 (FILE *cache, guint32 n)
631 n = GUINT32_TO_BE (n);
633 i = fwrite ((char *)&n, 4, 1, cache);
640 write_pixdata (FILE *cache, GdkPixdata *pixdata)
646 /* Type 0 is GdkPixdata */
647 if (!write_card32 (cache, 0))
650 s = gdk_pixdata_serialize (pixdata, &len);
652 if (!write_card32 (cache, len))
658 i = fwrite (s, len, 1, cache);
666 write_header (FILE *cache, guint32 dir_list_offset)
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));
675 get_image_meta_data_size (Image *image)
680 if (image->has_embedded_rect ||
681 image->attach_points > 0 ||
682 image->n_display_names > 0)
685 if (image->has_embedded_rect)
688 if (image->n_attach_points > 0)
689 len += 4 + image->n_attach_points * 4;
691 if (image->n_display_names > 0)
693 len += 4 + 8 * image->n_display_names;
695 for (i = 0; image->display_names[i]; i++)
696 len += ALIGN_VALUE (strlen (image->display_names[i]) + 1, 4);
703 get_image_pixel_data_size (Image *image)
705 if (image->pixel_data_size == 0)
707 if (image->image_data &&
708 image->image_data->has_pixdata)
710 image->pixel_data_size = image->image_data->pixel_data_size;
711 image->image_data->pixel_data_size = 0;
715 return image->pixel_data_size;
719 get_image_data_size (Image *image)
725 len += get_image_pixel_data_size (image);
726 len += get_image_meta_data_size (image);
728 if (len > 0 || (image->image_data && image->image_data->has_pixdata))
735 get_single_node_size (HashNode *node, gboolean include_image_data)
744 len += ALIGN_VALUE (strlen (node->name) + 1, 4);
747 len += 4 + g_list_length (node->image_list) * 8;
750 if (include_image_data)
751 for (list = node->image_list; list; list = list->next)
753 Image *image = list->data;
755 len += get_image_data_size (image);
762 get_bucket_size (HashNode *node)
767 len += get_single_node_size (node, TRUE);
776 write_bucket (FILE *cache, HashNode *node, int *offset)
780 int next_offset = *offset + get_single_node_size (node, TRUE);
781 int image_data_offset = *offset + get_single_node_size (node, FALSE);
788 if (node->next != NULL)
790 if (!write_card32 (cache, next_offset))
795 if (!write_card32 (cache, 0xffffffff))
799 /* Icon name offset */
800 if (!write_card32 (cache, *offset + 12))
803 /* Image list offset */
804 tmp = *offset + 12 + ALIGN_VALUE (strlen (node->name) + 1, 4);
805 if (!write_card32 (cache, tmp))
809 if (!write_string (cache, node->name))
813 len = g_list_length (node->image_list);
814 if (!write_card32 (cache, len))
817 /* Image data goes right after the image list */
820 list = node->image_list;
821 data_offset = image_data_offset;
822 for (i = 0; i < len; i++)
824 Image *image = list->data;
825 int image_data_size = get_image_data_size (image);
827 /* Directory index */
828 if (!write_card16 (cache, image->dir_index))
832 if (!write_card16 (cache, image->flags))
835 /* Image data offset */
836 if (image_data_size > 0)
838 if (!write_card32 (cache, data_offset))
840 data_offset += image_data_size;
844 if (!write_card32 (cache, 0))
851 /* Now write the image data */
852 list = node->image_list;
853 for (i = 0; i < len; i++, list = list->next)
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);
859 if (get_image_data_size (image) == 0)
863 if (pixel_data_size > 0)
865 if (!write_card32 (cache, image_data_offset + 8))
868 image->image_data->offset = image_data_offset + 8;
874 if (image->image_data)
875 offset = image->image_data->offset;
879 if (!write_card32 (cache, offset))
883 if (meta_data_size > 0)
885 if (!write_card32 (cache, image_data_offset + pixel_data_size + 8))
890 if (!write_card32 (cache, 0))
894 if (pixel_data_size > 0)
896 if (!write_pixdata (cache, &image->image_data->pixdata))
900 if (meta_data_size > 0)
902 int ofs = image_data_offset + pixel_data_size + 20;
904 if (image->has_embedded_rect)
906 if (!write_card32 (cache, ofs))
913 if (!write_card32 (cache, 0))
917 if (image->n_attach_points > 0)
919 if (!write_card32 (cache, ofs))
922 ofs += 4 + 4 * image->n_attach_points;
926 if (!write_card32 (cache, 0))
930 if (image->n_display_names > 0)
932 if (!write_card32 (cache, ofs))
937 if (!write_card32 (cache, 0))
941 if (image->has_embedded_rect)
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))
950 if (image->n_attach_points > 0)
952 if (!write_card32 (cache, image->n_attach_points))
955 for (j = 0; j < 2 * image->n_attach_points; j++)
957 if (!write_card16 (cache, image->attach_points[j]))
962 if (image->n_display_names > 0)
964 if (!write_card32 (cache, image->n_display_names))
967 ofs += 4 + 8 * image->n_display_names;
969 for (j = 0; j < 2 * image->n_display_names; j++)
971 if (!write_card32 (cache, ofs))
974 ofs += ALIGN_VALUE (strlen (image->display_names[j]) + 1, 4);
977 for (j = 0; j < 2 * image->n_display_names; j++)
979 if (!write_string (cache, image->display_names[j]))
985 image_data_offset += pixel_data_size + meta_data_size + 8;
988 *offset = next_offset;
996 write_hash_table (FILE *cache, HashContext *context, int *new_offset)
998 int offset = HASH_OFFSET;
1002 if (!(write_card32 (cache, context->size)))
1005 /* Size int + size * 4 */
1006 node_offset = offset + 4 + context->size * 4;
1008 for (i = 0; i < context->size; i++)
1010 if (context->nodes[i] != NULL)
1012 if (!write_card32 (cache, node_offset))
1015 node_offset += get_bucket_size (context->nodes[i]);
1019 if (!write_card32 (cache, 0xffffffff))
1026 *new_offset = node_offset;
1028 /* Now write the buckets */
1029 node_offset = offset + 4 + context->size * 4;
1031 for (i = 0; i < context->size; i++)
1033 if (!context->nodes[i])
1036 if (!write_bucket (cache, context->nodes[i], &node_offset))
1044 write_dir_index (FILE *cache, int offset, GList *directories)
1050 n_dirs = g_list_length (directories);
1052 if (!write_card32 (cache, n_dirs))
1055 offset += 4 + n_dirs * 4;
1057 for (d = directories; d; d = d->next)
1060 if (!write_card32 (cache, offset))
1063 offset += ALIGN_VALUE (strlen (dir) + 1, 4);
1066 for (d = directories; d; d = d->next)
1070 if (!write_string (cache, dir))
1078 write_file (FILE *cache, GHashTable *files, GList *directories)
1080 HashContext context;
1083 /* Convert the hash table into something looking a bit more
1084 * like what we want to write to disk.
1086 context.size = g_spaced_primes_closest (g_hash_table_size (files) / 3);
1087 context.nodes = g_new0 (HashNode *, context.size);
1089 g_hash_table_foreach_remove (files, convert_to_hash, &context);
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))
1096 g_printerr ("Failed to write header\n");
1100 if (!write_hash_table (cache, &context, &new_offset))
1102 g_printerr ("Failed to write hash table\n");
1106 if (!write_dir_index (cache, new_offset, directories))
1108 g_printerr ("Failed to write directory index\n");
1114 if (!write_header (cache, new_offset))
1116 g_printerr ("Failed to rewrite header\n");
1124 build_cache (const gchar *path)
1126 gchar *cache_path, *tmp_cache_path;
1130 struct stat path_stat, cache_stat;
1131 struct utimbuf utime_buf;
1132 GList *directories = NULL;
1134 tmp_cache_path = g_build_filename (path, "."CACHE_NAME, NULL);
1135 cache = g_fopen (tmp_cache_path, "wb");
1139 g_printerr ("Failed to write cache file: %s\n", g_strerror (errno));
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);
1146 directories = scan_directory (path, NULL, files, NULL, 0);
1148 if (g_hash_table_size (files) == 0)
1150 /* Empty table, just close and remove the file */
1153 g_unlink (tmp_cache_path);
1157 /* FIXME: Handle failure */
1158 retval = write_file (cache, files, directories);
1161 g_list_foreach (directories, (GFunc)g_free, NULL);
1162 g_list_free (directories);
1166 g_unlink (tmp_cache_path);
1170 cache_path = g_build_filename (path, CACHE_NAME, NULL);
1172 if (g_rename (tmp_cache_path, cache_path) == -1)
1174 g_unlink (tmp_cache_path);
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))
1184 utime_buf.actime = path_stat.st_atime;
1185 utime_buf.modtime = cache_stat.st_mtime;
1186 utime (path, &utime_buf);
1189 g_printerr ("Cache file created successfully.\n");
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 },
1199 main (int argc, char **argv)
1202 GOptionContext *context;
1207 context = g_option_context_new ("ICONPATH");
1208 g_option_context_add_main_entries (context, args, NULL);
1210 g_option_context_parse (context, &argc, &argv, NULL);
1214 path = g_locale_to_utf8 (path, -1, NULL, NULL, NULL);
1217 if (!force_update && is_cache_up_to_date (path))