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>
34 static gboolean force_update = FALSE;
35 static gboolean quiet = FALSE;
37 #define CACHE_NAME "icon-theme.cache"
39 #define HAS_SUFFIX_XPM (1 << 0)
40 #define HAS_SUFFIX_SVG (1 << 1)
41 #define HAS_SUFFIX_PNG (1 << 2)
42 #define HAS_ICON_FILE (1 << 3)
44 #define MAJOR_VERSION 1
45 #define MINOR_VERSION 0
46 #define HASH_OFFSET 12
48 #define ALIGN_VALUE(this, boundary) \
49 (( ((unsigned long)(this)) + (((unsigned long)(boundary)) -1)) & (~(((unsigned long)(boundary))-1)))
52 is_cache_up_to_date (const gchar *path)
54 struct stat path_stat, cache_stat;
58 retval = g_stat (path, &path_stat);
62 /* We can't stat the path,
63 * assume we have a updated cache */
67 cache_path = g_build_filename (path, CACHE_NAME, NULL);
68 retval = g_stat (cache_path, &cache_stat);
71 if (retval < 0 && errno == ENOENT)
73 /* Cache file not found */
78 return cache_stat.st_mtime <= path_stat.st_mtime;
88 foreach_remove_func (gpointer key, gpointer value, gpointer user_data)
90 Image *image = (Image *)value;
91 GHashTable *files = user_data;
93 gboolean free_key = FALSE;
95 if (image->flags == HAS_ICON_FILE)
103 list = g_hash_table_lookup (files, key);
107 list = g_list_prepend (list, value);
108 g_hash_table_insert (files, key, list);
117 scan_directory (const gchar *base_path,
123 GHashTable *dir_hash;
127 gboolean dir_added = FALSE;
128 guint dir_index = 0xffff;
130 dir_path = g_build_filename (base_path, subdir, NULL);
132 /* FIXME: Use the gerror */
133 dir = g_dir_open (dir_path, 0, NULL);
138 dir_hash = g_hash_table_new (g_str_hash, g_str_equal);
140 while ((name = g_dir_read_name (dir)))
146 gchar *basename, *dot;
148 path = g_build_filename (dir_path, name, NULL);
149 retval = g_file_test (path, G_FILE_TEST_IS_DIR);
155 subsubdir = g_build_filename (subdir, name, NULL);
157 subsubdir = g_strdup (name);
158 directories = scan_directory (base_path, subsubdir, files,
159 directories, depth + 1);
165 retval = g_file_test (path, G_FILE_TEST_IS_REGULAR);
170 if (g_str_has_suffix (name, ".png"))
171 flags |= HAS_SUFFIX_PNG;
172 else if (g_str_has_suffix (name, ".svg"))
173 flags |= HAS_SUFFIX_SVG;
174 else if (g_str_has_suffix (name, ".xpm"))
175 flags |= HAS_SUFFIX_XPM;
176 else if (g_str_has_suffix (name, ".icon"))
177 flags |= HAS_ICON_FILE;
182 basename = g_strdup (name);
183 dot = strrchr (basename, '.');
186 image = g_hash_table_lookup (dir_hash, basename);
188 image->flags |= flags;
196 dir_index = g_list_length (directories);
197 directories = g_list_append (directories, g_strdup (subdir));
203 image = g_new0 (Image, 1);
204 image->flags = flags;
205 image->dir_index = dir_index;
207 g_hash_table_insert (dir_hash, g_strdup (basename), image);
216 /* Move dir into the big file hash */
217 g_hash_table_foreach_remove (dir_hash, foreach_remove_func, files);
219 g_hash_table_destroy (dir_hash);
224 typedef struct _HashNode HashNode;
234 icon_name_hash (gconstpointer key)
240 for (p += 1; *p != '\0'; p++)
241 h = (h << 5) - h + *p;
252 convert_to_hash (gpointer key, gpointer value, gpointer user_data)
254 HashContext *context = user_data;
258 hash = icon_name_hash (key) % context->size;
260 node = g_new0 (HashNode, 1);
263 node->image_list = value;
265 if (context->nodes[hash] != NULL)
266 node->next = context->nodes[hash];
268 context->nodes[hash] = node;
274 write_string (FILE *cache, const gchar *n)
279 l = ALIGN_VALUE (strlen (n) + 1, 4);
284 i = fwrite (s, l, 1, cache);
291 write_card16 (FILE *cache, guint16 n)
296 *((guint16 *)s) = GUINT16_TO_BE (n);
298 i = fwrite (s, 2, 1, cache);
304 write_card32 (FILE *cache, guint32 n)
309 *((guint32 *)s) = GUINT32_TO_BE (n);
311 i = fwrite (s, 4, 1, cache);
317 write_header (FILE *cache, guint32 dir_list_offset)
319 return (write_card16 (cache, MAJOR_VERSION) &&
320 write_card16 (cache, MINOR_VERSION) &&
321 write_card32 (cache, HASH_OFFSET) &&
322 write_card32 (cache, dir_list_offset));
327 get_single_node_size (HashNode *node)
335 len += ALIGN_VALUE (strlen (node->name) + 1, 4);
338 len += 4 + g_list_length (node->image_list) * 8;
344 get_bucket_size (HashNode *node)
350 len += get_single_node_size (node);
359 write_bucket (FILE *cache, HashNode *node, int *offset)
363 int next_offset = *offset + get_single_node_size (node);
368 if (node->next != NULL)
370 if (!write_card32 (cache, next_offset))
375 if (!write_card32 (cache, 0xffffffff))
379 /* Icon name offset */
380 if (!write_card32 (cache, *offset + 12))
383 /* Image list offset */
384 if (!write_card32 (cache, *offset + 12 + ALIGN_VALUE (strlen (node->name) + 1, 4)))
388 if (!write_string (cache, node->name))
392 len = g_list_length (node->image_list);
393 if (!write_card32 (cache, len))
396 list = node->image_list;
397 for (i = 0; i < len; i++)
399 Image *image = list->data;
401 /* Directory index */
402 if (!write_card16 (cache, image->dir_index))
406 if (!write_card16 (cache, image->flags))
409 /* Image data offset */
410 if (!write_card32 (cache, 0))
416 *offset = next_offset;
424 write_hash_table (FILE *cache, HashContext *context, int *new_offset)
426 int offset = HASH_OFFSET;
430 if (!(write_card32 (cache, context->size)))
433 /* Size int + size * 4 */
434 node_offset = offset + 4 + context->size * 4;
436 for (i = 0; i < context->size; i++)
438 if (context->nodes[i] != NULL)
440 if (!write_card32 (cache, node_offset))
443 node_offset += get_bucket_size (context->nodes[i]);
447 if (!write_card32 (cache, 0xffffffff))
454 *new_offset = node_offset;
456 /* Now write the buckets */
457 node_offset = offset + 4 + context->size * 4;
459 for (i = 0; i < context->size; i++)
461 if (!context->nodes[i])
464 if (!write_bucket (cache, context->nodes[i], &node_offset))
472 write_dir_index (FILE *cache, int offset, GList *directories)
478 n_dirs = g_list_length (directories);
480 if (!write_card32 (cache, n_dirs))
483 offset += 4 + n_dirs * 4;
485 for (d = directories; d; d = d->next)
488 if (!write_card32 (cache, offset))
491 offset += ALIGN_VALUE (strlen (dir) + 1, 4);
494 for (d = directories; d; d = d->next)
498 if (!write_string (cache, dir))
506 write_file (FILE *cache, GHashTable *files, GList *directories)
511 /* Convert the hash table into something looking a bit more
512 * like what we want to write to disk.
514 context.size = g_spaced_primes_closest (g_hash_table_size (files) / 3);
515 context.nodes = g_new0 (HashNode *, context.size);
517 g_hash_table_foreach_remove (files, convert_to_hash, &context);
519 /* Now write the file */
520 /* We write 0 as the directory list offset and go
521 * back and change it later */
522 if (!write_header (cache, 0))
524 g_printerr ("Failed to write header\n");
528 if (!write_hash_table (cache, &context, &new_offset))
530 g_printerr ("Failed to write hash table\n");
534 if (!write_dir_index (cache, new_offset, directories))
536 g_printerr ("Failed to write directory index\n");
542 if (!write_header (cache, new_offset))
544 g_printerr ("Failed to rewrite header\n");
552 build_cache (const gchar *path)
554 gchar *cache_path, *tmp_cache_path;
558 struct stat path_stat, cache_stat;
559 struct utimbuf utime_buf;
560 GList *directories = NULL;
562 tmp_cache_path = g_build_filename (path, "."CACHE_NAME, NULL);
563 cache = g_fopen (tmp_cache_path, "wb");
567 g_printerr ("Failed to write cache file: %s\n", g_strerror (errno));
571 files = g_hash_table_new (g_str_hash, g_str_equal);
573 directories = scan_directory (path, NULL, files, NULL, 0);
575 if (g_hash_table_size (files) == 0)
577 /* Empty table, just close and remove the file */
580 g_unlink (tmp_cache_path);
584 /* FIXME: Handle failure */
585 retval = write_file (cache, files, directories);
588 g_list_foreach (directories, (GFunc)g_free, NULL);
589 g_list_free (directories);
593 g_unlink (tmp_cache_path);
597 cache_path = g_build_filename (path, CACHE_NAME, NULL);
599 if (g_rename (tmp_cache_path, cache_path) == -1)
601 g_unlink (tmp_cache_path);
606 /* FIXME: What do do if an error occurs here? */
607 g_stat (path, &path_stat);
608 g_stat (cache_path, &cache_stat);
610 utime_buf.actime = path_stat.st_atime;
611 utime_buf.modtime = cache_stat.st_mtime;
612 utime (path, &utime_buf);
615 g_printerr ("Cache file created successfully.\n");
618 static GOptionEntry args[] = {
619 { "force", 'f', 0, G_OPTION_ARG_NONE, &force_update, "Overwrite an existing cache, even if uptodate", NULL },
620 { "quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet, "Turn off verbose output", NULL },
625 main (int argc, char **argv)
628 GOptionContext *context;
633 context = g_option_context_new ("ICONPATH");
634 g_option_context_add_main_entries (context, args, NULL);
636 g_option_context_parse (context, &argc, &argv, NULL);
640 path = g_locale_to_utf8 (path, -1, NULL, NULL, NULL);
643 if (!force_update && is_cache_up_to_date (path))