summaryrefslogtreecommitdiff
path: root/gtk/updateiconcache.c
diff options
context:
space:
mode:
authorMatthias Clasen <mclasen@redhat.com>2004-10-19 18:45:41 +0000
committerMatthias Clasen <matthiasc@src.gnome.org>2004-10-19 18:45:41 +0000
commit6fc2b8118a77a4d3f6d237d573805302db5e54b7 (patch)
tree3705cce0a4866838f8230b0e369e1c080c27cfc2 /gtk/updateiconcache.c
parentb087f7655108019d797ca6096077443761915431 (diff)
downloadgtk+-6fc2b8118a77a4d3f6d237d573805302db5e54b7.tar.gz
Implement icon theme caching. (#154034, Martijn Vernooij, caching schema
2004-10-19 Matthias Clasen <mclasen@redhat.com> Implement icon theme caching. (#154034, Martijn Vernooij, caching schema proposed by Owen Taylor, initial implementation by Anders Carlsson) * gtk/gtkdebug.h: * gtk/gtkmain.c: Add a "icontheme" debug flag. * gtk/Makefile.am (gtk_c_sources): Add gtkiconcache.c (gtk_private_h_sources): Add gtkiconcache.h (bin_PROGRAMS): Add gtk-update-icon-cache * gtk/gtkicontheme.c: Use icon caches if they are available. Currently, GTK+ uses the cache to get information about the available sizes, image file formats and .icon files. The actual image data, and the .icon file contents are not cached yet. * gtk/updateiconcache.c: A cmdline utility for generating icon cache files. * gtk/gtkiconcache.h: * gtk/gtkiconcache.c: The glue code to mmap an icon cache file and manage the information it contains.
Diffstat (limited to 'gtk/updateiconcache.c')
-rw-r--r--gtk/updateiconcache.c628
1 files changed, 628 insertions, 0 deletions
diff --git a/gtk/updateiconcache.c b/gtk/updateiconcache.c
new file mode 100644
index 0000000000..e6957cff68
--- /dev/null
+++ b/gtk/updateiconcache.c
@@ -0,0 +1,628 @@
+/* updateiconcache.c
+ * Copyright (C) 2004 Anders Carlsson <andersca@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <utime.h>
+
+#include <glib.h>
+
+#define CACHE_NAME "icon-theme.cache"
+
+#define HAS_SUFFIX_XPM (1 << 0)
+#define HAS_SUFFIX_SVG (1 << 1)
+#define HAS_SUFFIX_PNG (1 << 2)
+#define HAS_ICON_FILE (1 << 3)
+
+#define MAJOR_VERSION 1
+#define MINOR_VERSION 0
+#define HASH_OFFSET 12
+
+#define ALIGN_VALUE(this, boundary) \
+ (( ((unsigned long)(this)) + (((unsigned long)(boundary)) -1)) & (~(((unsigned long)(boundary))-1)))
+
+gboolean
+is_cache_up_to_date (const gchar *path)
+{
+ struct stat path_stat, cache_stat;
+ gchar *cache_path;
+ int retval;
+
+ retval = stat (path, &path_stat);
+
+ if (retval < 0)
+ {
+ /* We can't stat the path,
+ * assume we have a updated cache */
+ return TRUE;
+ }
+
+ cache_path = g_build_filename (path, CACHE_NAME, NULL);
+ retval = stat (cache_path, &cache_stat);
+ g_free (cache_path);
+
+ if (retval < 0 && errno == ENOENT)
+ {
+ /* Cache file not found */
+ return FALSE;
+ }
+
+ /* Check mtime */
+ return cache_stat.st_mtime <= path_stat.st_mtime;
+}
+
+typedef struct
+{
+ int flags;
+ int dir_index;
+} Image;
+
+static gboolean
+foreach_remove_func (gpointer key, gpointer value, gpointer user_data)
+{
+ GHashTable *files = user_data;
+ GList *list;
+ gboolean free_key = FALSE;;
+
+ list = g_hash_table_lookup (files, key);
+ if (list)
+ free_key = TRUE;
+
+ list = g_list_prepend (list, value);
+ g_hash_table_insert (files, key, list);
+
+ if (free_key)
+ g_free (key);
+
+ return TRUE;
+}
+
+GList *
+scan_directory (const gchar *base_path,
+ const gchar *subdir,
+ GHashTable *files,
+ GList *directories,
+ gint depth)
+{
+ GHashTable *dir_hash;
+ GDir *dir;
+ const gchar *name;
+ gchar *dir_path;
+ gboolean dir_added = FALSE;
+ guint dir_index = 0xffff;
+
+ dir_path = g_build_filename (base_path, subdir, NULL);
+
+ /* FIXME: Use the gerror */
+ dir = g_dir_open (dir_path, 0, NULL);
+
+ if (!dir)
+ return directories;
+
+ dir_hash = g_hash_table_new (g_str_hash, g_str_equal);
+
+ while ((name = g_dir_read_name (dir)))
+ {
+ gchar *path;
+ gboolean retval;
+ int flags = 0;
+ Image *image;
+ gchar *basename, *dot;
+
+ path = g_build_filename (dir_path, name, NULL);
+ retval = g_file_test (path, G_FILE_TEST_IS_DIR);
+ if (retval)
+ {
+ gchar *subsubdir;
+
+ if (subdir)
+ subsubdir = g_build_filename (subdir, name, NULL);
+ else
+ subsubdir = g_strdup (name);
+ directories = scan_directory (base_path, subsubdir, files,
+ directories, depth + 1);
+ g_free (subsubdir);
+
+ continue;
+ }
+
+ retval = g_file_test (path, G_FILE_TEST_IS_REGULAR);
+ g_free (path);
+
+ if (retval)
+ {
+ if (g_str_has_suffix (name, ".png"))
+ flags |= HAS_SUFFIX_PNG;
+ else if (g_str_has_suffix (name, ".svg"))
+ flags |= HAS_SUFFIX_SVG;
+ else if (g_str_has_suffix (name, ".xpm"))
+ flags |= HAS_SUFFIX_XPM;
+ else if (g_str_has_suffix (name, ".icon"))
+ flags |= HAS_ICON_FILE;
+
+ if (flags == 0)
+ continue;
+
+ basename = g_strdup (name);
+ dot = strrchr (basename, '.');
+ *dot = '\0';
+
+ image = g_hash_table_lookup (dir_hash, basename);
+ if (image)
+ image->flags |= flags;
+ else if ((flags & HAS_ICON_FILE) != HAS_ICON_FILE)
+ {
+ if (!dir_added)
+ {
+ dir_added = TRUE;
+ if (subdir)
+ {
+ dir_index = g_list_length (directories);
+ directories = g_list_append (directories, g_strdup (subdir));
+ }
+ else
+ dir_index = 0xffff;
+ }
+
+ image = g_new0 (Image, 1);
+ image->flags = flags;
+ image->dir_index = dir_index;
+
+ g_hash_table_insert (dir_hash, g_strdup (basename), image);
+ }
+
+ g_free (basename);
+ }
+ }
+
+ g_dir_close (dir);
+
+ /* Move dir into the big file hash */
+ g_hash_table_foreach_remove (dir_hash, foreach_remove_func, files);
+
+ g_hash_table_destroy (dir_hash);
+
+ return directories;
+}
+
+typedef struct _HashNode HashNode;
+
+struct _HashNode
+{
+ HashNode *next;
+ gchar *name;
+ GList *image_list;
+};
+
+static guint
+icon_name_hash (gconstpointer key)
+{
+ const char *p = key;
+ guint h = *p;
+
+ if (h)
+ for (p += 1; *p != '\0'; p++)
+ h = (h << 5) - h + *p;
+
+ return h;
+}
+
+typedef struct {
+ gint size;
+ HashNode **nodes;
+} HashContext;
+
+static gboolean
+convert_to_hash (gpointer key, gpointer value, gpointer user_data)
+{
+ HashContext *context = user_data;
+ guint hash;
+ HashNode *node;
+
+ hash = icon_name_hash (key) % context->size;
+
+ node = g_new0 (HashNode, 1);
+ node->next = NULL;
+ node->name = key;
+ node->image_list = value;
+
+ if (context->nodes[hash] != NULL)
+ node->next = context->nodes[hash];
+
+ context->nodes[hash] = node;
+
+ return TRUE;
+}
+
+gboolean
+write_string (FILE *cache, const gchar *n)
+{
+ gchar *s;
+ int i, l;
+
+ l = ALIGN_VALUE (strlen (n) + 1, 4);
+
+ s = g_malloc0 (l);
+ strcpy (s, n);
+
+ i = fwrite (s, l, 1, cache);
+
+ return i == 1;
+
+}
+
+gboolean
+write_card16 (FILE *cache, guint16 n)
+{
+ int i;
+ gchar s[2];
+
+ *((guint16 *)s) = GUINT16_TO_BE (n);
+
+ i = fwrite (s, 2, 1, cache);
+
+ return i == 1;
+}
+
+gboolean
+write_card32 (FILE *cache, guint32 n)
+{
+ int i;
+ gchar s[4];
+
+ *((guint32 *)s) = GUINT32_TO_BE (n);
+
+ i = fwrite (s, 4, 1, cache);
+
+ return i == 1;
+}
+
+static gboolean
+write_header (FILE *cache, guint32 dir_list_offset)
+{
+ return (write_card16 (cache, MAJOR_VERSION) &&
+ write_card16 (cache, MINOR_VERSION) &&
+ write_card32 (cache, HASH_OFFSET) &&
+ write_card32 (cache, dir_list_offset));
+}
+
+
+guint
+get_single_node_size (HashNode *node)
+{
+ int len = 0;
+
+ /* Node pointers */
+ len += 12;
+
+ /* Name */
+ len += ALIGN_VALUE (strlen (node->name) + 1, 4);
+
+ /* Image list */
+ len += 4 + g_list_length (node->image_list) * 8;
+
+ return len;
+}
+
+guint
+get_bucket_size (HashNode *node)
+{
+ int len = 0;
+
+ while (node)
+ {
+ len += get_single_node_size (node);
+
+ node = node->next;
+ }
+
+ return len;
+}
+
+gboolean
+write_bucket (FILE *cache, HashNode *node, int *offset)
+{
+ while (node != NULL)
+ {
+ int next_offset = *offset + get_single_node_size (node);
+ int i, len;
+ GList *list;
+
+ /* Chain offset */
+ if (node->next != NULL)
+ {
+ if (!write_card32 (cache, next_offset))
+ return FALSE;
+ }
+ else
+ {
+ if (!write_card32 (cache, 0xffffffff))
+ return FALSE;
+ }
+
+ /* Icon name offset */
+ if (!write_card32 (cache, *offset + 12))
+ return FALSE;
+
+ /* Image list offset */
+ if (!write_card32 (cache, *offset + 12 + ALIGN_VALUE (strlen (node->name) + 1, 4)))
+ return FALSE;
+
+ /* Icon name */
+ if (!write_string (cache, node->name))
+ return FALSE;
+
+ /* Image list */
+ len = g_list_length (node->image_list);
+ if (!write_card32 (cache, len))
+ return FALSE;
+
+ list = node->image_list;
+ for (i = 0; i < len; i++)
+ {
+ Image *image = list->data;
+
+ /* Directory index */
+ if (!write_card16 (cache, image->dir_index))
+ return FALSE;
+
+ /* Flags */
+ if (!write_card16 (cache, image->flags))
+ return FALSE;
+
+ /* Image data offset */
+ if (!write_card32 (cache, 0))
+ return FALSE;
+
+ list = list->next;
+ }
+
+ *offset = next_offset;
+ node = node->next;
+ }
+
+ return TRUE;
+}
+
+gboolean
+write_hash_table (FILE *cache, HashContext *context, int *new_offset)
+{
+ int offset = HASH_OFFSET;
+ int node_offset;
+ int i;
+
+ if (!(write_card32 (cache, context->size)))
+ return FALSE;
+
+ /* Size int + size * 4 */
+ node_offset = offset + 4 + context->size * 4;
+
+ for (i = 0; i < context->size; i++)
+ {
+ if (context->nodes[i] != NULL)
+ {
+ if (!write_card32 (cache, node_offset))
+ return FALSE;
+
+ node_offset += get_bucket_size (context->nodes[i]);
+ }
+ else
+ {
+ if (!write_card32 (cache, 0xffffffff))
+ {
+ return FALSE;
+ }
+ }
+ }
+
+ *new_offset = node_offset;
+
+ /* Now write the buckets */
+ node_offset = offset + 4 + context->size * 4;
+
+ for (i = 0; i < context->size; i++)
+ {
+ if (!context->nodes[i])
+ continue;
+
+ if (!write_bucket (cache, context->nodes[i], &node_offset))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+write_dir_index (FILE *cache, int offset, GList *directories)
+{
+ int n_dirs;
+ GList *d;
+ char *dir;
+
+ n_dirs = g_list_length (directories);
+
+ if (!write_card32 (cache, n_dirs))
+ return FALSE;
+
+ offset += 4 + n_dirs * 4;
+
+ for (d = directories; d; d = d->next)
+ {
+ dir = d->data;
+ if (!write_card32 (cache, offset))
+ return FALSE;
+
+ offset += ALIGN_VALUE (strlen (dir) + 1, 4);
+ }
+
+ for (d = directories; d; d = d->next)
+ {
+ dir = d->data;
+
+ if (!write_string (cache, dir))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+write_file (FILE *cache, GHashTable *files, GList *directories)
+{
+ HashContext context;
+ int new_offset;
+
+ /* Convert the hash table into something looking a bit more
+ * like what we want to write to disk.
+ */
+ context.size = g_spaced_primes_closest (g_hash_table_size (files) / 3);
+ context.nodes = g_new0 (HashNode *, context.size);
+
+ g_hash_table_foreach_remove (files, convert_to_hash, &context);
+
+ /* Now write the file */
+ /* We write 0 as the directory list offset and go
+ * back and change it later */
+ if (!write_header (cache, 0))
+ {
+ g_printerr ("Failed to write header\n");
+ return FALSE;
+ }
+
+ if (!write_hash_table (cache, &context, &new_offset))
+ {
+ g_printerr ("Failed to write hash table\n");
+ return FALSE;
+ }
+
+ if (!write_dir_index (cache, new_offset, directories))
+ {
+ g_printerr ("Failed to write directory index\n");
+ return FALSE;
+ }
+
+ rewind (cache);
+
+ if (!write_header (cache, new_offset))
+ {
+ g_printerr ("Failed to rewrite header\n");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void
+build_cache (const gchar *path)
+{
+ gchar *cache_path, *tmp_cache_path;
+ GHashTable *files;
+ gboolean retval;
+ FILE *cache;
+ struct stat path_stat, cache_stat;
+ struct utimbuf utime_buf;
+ GList *directories = NULL;
+
+ tmp_cache_path = g_build_filename (path, "."CACHE_NAME, NULL);
+ cache = fopen (tmp_cache_path, "w");
+
+ if (!cache)
+ {
+ g_printerr ("Failed to write cache file: %s\n", g_strerror (errno));
+ exit (1);
+ }
+
+ files = g_hash_table_new (g_str_hash, g_str_equal);
+
+ directories = scan_directory (path, NULL, files, NULL, 0);
+
+ if (g_hash_table_size (files) == 0)
+ {
+ /* Empty table, just close and remove the file */
+
+ fclose (cache);
+ unlink (tmp_cache_path);
+ exit (0);
+ }
+
+ /* FIXME: Handle failure */
+ retval = write_file (cache, files, directories);
+ fclose (cache);
+
+ g_list_foreach (directories, (GFunc)g_free, NULL);
+ g_list_free (directories);
+
+ if (!retval)
+ {
+ unlink (tmp_cache_path);
+ exit (1);
+ }
+
+ cache_path = g_build_filename (path, CACHE_NAME, NULL);
+
+ if (rename (tmp_cache_path, cache_path) == -1)
+ {
+ unlink (tmp_cache_path);
+ exit (1);
+ }
+
+ /* Update time */
+ /* FIXME: What do do if an error occurs here? */
+ stat (path, &path_stat);
+ stat (cache_path, &cache_stat);
+
+ utime_buf.actime = path_stat.st_atime;
+ utime_buf.modtime = cache_stat.st_mtime;
+ utime (path, &utime_buf);
+
+ g_printerr ("Cache file created successfully.\n");
+}
+
+static gboolean force_update = FALSE;
+
+static GOptionEntry args[] = {
+ { "force", 0, 0, G_OPTION_ARG_NONE, &force_update, "Overwrite an existing cache, even if uptodate", NULL },
+ { NULL }
+};
+
+int
+main (int argc, char **argv)
+{
+ gchar *path;
+ GOptionContext *context;
+
+ context = g_option_context_new ("ICONPATH");
+ g_option_context_add_main_entries (context, args, NULL);
+
+ g_option_context_parse (context, &argc, &argv, NULL);
+
+ path = argv[1];
+
+ if (!force_update && is_cache_up_to_date (path))
+ return 0;
+
+ build_cache (path);
+
+ return 0;
+}