summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog26
-rw-r--r--ChangeLog.pre-2-1026
-rw-r--r--ChangeLog.pre-2-626
-rw-r--r--ChangeLog.pre-2-826
-rw-r--r--docs/reference/ChangeLog8
-rw-r--r--docs/reference/gtk/Makefile.am3
-rw-r--r--docs/reference/gtk/gtk-docs.sgml2
-rw-r--r--docs/reference/gtk/gtk-update-icon-cache.149
-rw-r--r--docs/reference/gtk/gtk-update-icon-cache.xml53
-rw-r--r--gtk/Makefile.am10
-rw-r--r--gtk/gtkdebug.h3
-rw-r--r--gtk/gtkiconcache.c280
-rw-r--r--gtk/gtkiconcache.h41
-rw-r--r--gtk/gtkicontheme.c192
-rw-r--r--gtk/gtkmain.c3
-rw-r--r--gtk/updateiconcache.c628
16 files changed, 1351 insertions, 25 deletions
diff --git a/ChangeLog b/ChangeLog
index c1a7c9d89a..e04e49c052 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,31 @@
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.
+
+2004-10-19 Matthias Clasen <mclasen@redhat.com>
+
* tests/testicontheme.c: Set the locale, tidy up output.
2004-10-18 Matthias Clasen <mclasen@redhat.com>
diff --git a/ChangeLog.pre-2-10 b/ChangeLog.pre-2-10
index c1a7c9d89a..e04e49c052 100644
--- a/ChangeLog.pre-2-10
+++ b/ChangeLog.pre-2-10
@@ -1,5 +1,31 @@
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.
+
+2004-10-19 Matthias Clasen <mclasen@redhat.com>
+
* tests/testicontheme.c: Set the locale, tidy up output.
2004-10-18 Matthias Clasen <mclasen@redhat.com>
diff --git a/ChangeLog.pre-2-6 b/ChangeLog.pre-2-6
index c1a7c9d89a..e04e49c052 100644
--- a/ChangeLog.pre-2-6
+++ b/ChangeLog.pre-2-6
@@ -1,5 +1,31 @@
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.
+
+2004-10-19 Matthias Clasen <mclasen@redhat.com>
+
* tests/testicontheme.c: Set the locale, tidy up output.
2004-10-18 Matthias Clasen <mclasen@redhat.com>
diff --git a/ChangeLog.pre-2-8 b/ChangeLog.pre-2-8
index c1a7c9d89a..e04e49c052 100644
--- a/ChangeLog.pre-2-8
+++ b/ChangeLog.pre-2-8
@@ -1,5 +1,31 @@
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.
+
+2004-10-19 Matthias Clasen <mclasen@redhat.com>
+
* tests/testicontheme.c: Set the locale, tidy up output.
2004-10-18 Matthias Clasen <mclasen@redhat.com>
diff --git a/docs/reference/ChangeLog b/docs/reference/ChangeLog
index f7f9929bfb..b5b11b1040 100644
--- a/docs/reference/ChangeLog
+++ b/docs/reference/ChangeLog
@@ -1,3 +1,11 @@
+2004-10-19 Matthias Clasen <mclasen@redhat.com>
+
+ * gtk/gtk-update-icon-cache.xml: A man page for gtk-update-icon-cache.
+
+ * gtk/gtk-docs.sgml: Add it here
+
+ * gtk/Makefile.am (man_MANS): ...and here.
+
2004-10-16 Matthias Clasen <mclasen@redhat.com>
* gtk/glossary.xml: Additions.
diff --git a/docs/reference/gtk/Makefile.am b/docs/reference/gtk/Makefile.am
index 52b66bf38a..57ab11c30c 100644
--- a/docs/reference/gtk/Makefile.am
+++ b/docs/reference/gtk/Makefile.am
@@ -104,6 +104,7 @@ content_files = \
windows.sgml \
x11.sgml \
gtk-query-immodules-2.0.xml \
+ gtk-update-icon-cache.xml \
visual_index.xml
# Images to copy into HTML directory
@@ -240,7 +241,7 @@ EXTRA_DIST += version.xml.in
########################################################################
-man_MANS = gtk-query-immodules-2.0.1
+man_MANS = gtk-query-immodules-2.0.1 gtk-update-icon-cache.1
if ENABLE_MAN
diff --git a/docs/reference/gtk/gtk-docs.sgml b/docs/reference/gtk/gtk-docs.sgml
index 2ced4af893..462afea8a5 100644
--- a/docs/reference/gtk/gtk-docs.sgml
+++ b/docs/reference/gtk/gtk-docs.sgml
@@ -193,6 +193,7 @@
<!ENTITY gtk-migrating-GtkComboBox SYSTEM "migrating-GtkComboBox.sgml">
<!ENTITY version SYSTEM "version.xml">
<!ENTITY gtk-query-immodules SYSTEM "gtk-query-immodules-2.0.xml">
+<!ENTITY gtk-update-icon-cache SYSTEM "gtk-update-icon-cache.xml">
<!ENTITY gtk-glossary SYSTEM "glossary.xml">
]>
<book id="index">
@@ -576,6 +577,7 @@ that is, GUI components such as <link linkend="GtkButton">GtkButton</link> or
<title>GTK+ Tools</title>
&gtk-query-immodules;
+ &gtk-update-icon-cache;
</part>
&gtk-glossary;
diff --git a/docs/reference/gtk/gtk-update-icon-cache.1 b/docs/reference/gtk/gtk-update-icon-cache.1
new file mode 100644
index 0000000000..162a97fafc
--- /dev/null
+++ b/docs/reference/gtk/gtk-update-icon-cache.1
@@ -0,0 +1,49 @@
+.\"Generated by db2man.xsl. Don't modify this, modify the source.
+.de Sh \" Subsection
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Ip \" List item
+.br
+.ie \\n(.$>=3 .ne \\$3
+.el .ne 3
+.IP "\\$1" \\$2
+..
+.TH "GTK-UPDATE-ICON-CA" 1 "" "" ""
+.SH NAME
+gtk-update-icon-cache \- Icon theme caching utility
+.SH "SYNOPSIS"
+.ad l
+.hy 0
+.HP 22
+\fBgtk\-update\-icon\-cache\fR [\-\-force] {iconpath}
+.ad
+.hy
+
+.SH "DESCRIPTION"
+
+.PP
+ \fBgtk\-update\-icon\-cache\fR creates mmap()able cache files for icon themes\&.
+
+.PP
+If expects to be given the path to a icon theme directory, e\&.g\&. \fI/usr/share/icons/hicolor\fR, and writes a \fIicon\-theme\&.cache\fR containing cached information about the icons in the directory tree below the given directory\&.
+
+.PP
+GTK+ can use the cache files created by \fBgtk\-update\-icon\-cache\fR to avoid a lot of system call and disk seek overhead when the application starts\&. Since the format of the cache files allows them to be mmap()ed shared between multiple applications, the overall memory consumption is reduced as well\&.
+
+.PP
+If called with the [\-\-force] argument, \fBgtk\-update\-icon\-cache\fR will overwrite an existing cache file even if it appears to be uptodate\&.
+
+.SH "BUGS"
+
+.PP
+None known yet\&.
+
diff --git a/docs/reference/gtk/gtk-update-icon-cache.xml b/docs/reference/gtk/gtk-update-icon-cache.xml
new file mode 100644
index 0000000000..e7baad89d5
--- /dev/null
+++ b/docs/reference/gtk/gtk-update-icon-cache.xml
@@ -0,0 +1,53 @@
+<refentry id="gtk-update-icon-cache">
+
+<refmeta>
+<refentrytitle>gtk-update-icon-cache</refentrytitle>
+<manvolnum>1</manvolnum>
+</refmeta>
+
+<refnamediv>
+<refname>gtk-update-icon-cache</refname>
+<refpurpose>Icon theme caching utility</refpurpose>
+</refnamediv>
+
+<refsynopsisdiv>
+<cmdsynopsis>
+<command>gtk-update-icon-cache</command>
+<arg choice="opt">--force</arg>
+<arg choice="req">iconpath</arg>
+</cmdsynopsis>
+</refsynopsisdiv>
+
+<refsect1><title>Description</title>
+<para>
+<command>gtk-update-icon-cache</command> creates mmap()able cache files for
+icon themes.
+</para>
+<para>
+If expects to be given the path to a icon theme directory, e.g.
+<filename>/usr/share/icons/hicolor</filename>, and writes a
+<filename>icon-theme.cache</filename> containing cached information
+about the icons in the directory tree below the given directory.
+</para>
+<para>
+GTK+ can use the cache files created by <command>gtk-update-icon-cache</command>
+to avoid a lot of system call and disk seek overhead when the application starts.
+Since the format of the cache files allows them to be mmap()ed shared between
+multiple applications, the overall memory consumption is reduced as well.
+</para>
+<para>
+If called with the --force argument,
+<command>gtk-update-icon-cache</command> will overwrite an existing cache
+file even if it appears to be uptodate.
+</para>
+</refsect1>
+
+<refsect1><title>Bugs</title>
+<para>
+None known yet.
+</para>
+</refsect1>
+
+</refentry>
+
+
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index 0a6987d690..8dcbc343f8 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -297,6 +297,7 @@ gtk_private_h_sources = \
gtkfilechooserutils.h \
gtkfilesystemunix.h \
gtkfilesystemmodel.h \
+ gtkiconcache.h \
gtkpathbar.h \
gtkrbtree.h \
gtksequence.h \
@@ -393,6 +394,7 @@ gtk_c_sources = \
gtkhsv.c \
gtkhsv.h \
gtkiconfactory.c \
+ gtkiconcache.c \
gtkicontheme.c \
gtkiconthemeparser.c \
gtkiconthemeparser.h \
@@ -701,13 +703,19 @@ LDADDS = \
#
# Installed tools
#
-bin_PROGRAMS = gtk-query-immodules-2.0
+bin_PROGRAMS = gtk-query-immodules-2.0 gtk-update-icon-cache
gtk_query_immodules_2_0_DEPENDENCIES = $(DEPS)
gtk_query_immodules_2_0_LDADD = $(LDADDS)
gtk_query_immodules_2_0_SOURCES = queryimmodules.c
+
+gtk_update_icon_cache_DEPENDENCIES = $(DEPS)
+gtk_update_icon_cache_LDADD = $(LDADDS)
+
+gtk_update_icon_cache_SOURCES = updateiconcache.c
+
.PHONY: files test test-debug
files:
diff --git a/gtk/gtkdebug.h b/gtk/gtkdebug.h
index ff1c0e74a2..2edfb41575 100644
--- a/gtk/gtkdebug.h
+++ b/gtk/gtkdebug.h
@@ -40,7 +40,8 @@ typedef enum {
GTK_DEBUG_KEYBINDINGS = 1 << 5,
GTK_DEBUG_MULTIHEAD = 1 << 6,
GTK_DEBUG_MODULES = 1 << 7,
- GTK_DEBUG_GEOMETRY = 1 << 8
+ GTK_DEBUG_GEOMETRY = 1 << 8,
+ GTK_DEBUG_ICONTHEME = 1 << 9
} GtkDebugFlag;
#ifdef G_ENABLE_DEBUG
diff --git a/gtk/gtkiconcache.c b/gtk/gtkiconcache.c
new file mode 100644
index 0000000000..766dfa0e23
--- /dev/null
+++ b/gtk/gtkiconcache.c
@@ -0,0 +1,280 @@
+/* gtkiconcache.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 <config.h>
+#include "gtkdebug.h"
+#include "gtkiconcache.h"
+
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <fcntl.h>
+#include <string.h>
+
+#define MAJOR_VERSION 1
+#define MINOR_VERSION 0
+
+#define GET_UINT16(cache, offset) (GUINT16_FROM_BE (*(guint16 *)((cache) + (offset))))
+#define GET_UINT32(cache, offset) (GUINT32_FROM_BE (*(guint32 *)((cache) + (offset))))
+
+struct _GtkIconCache {
+ gint ref_count;
+
+ gsize size;
+ gchar *buffer;
+};
+
+GtkIconCache *
+_gtk_icon_cache_ref (GtkIconCache *cache)
+{
+ cache->ref_count ++;
+
+ return cache;
+}
+
+void
+_gtk_icon_cache_unref (GtkIconCache *cache)
+{
+ cache->ref_count --;
+
+ if (cache->ref_count == 0)
+ {
+ GTK_NOTE (ICONTHEME,
+ g_print ("unmapping icon cache\n"));
+
+ munmap (cache->buffer, cache->size);
+ g_free (cache);
+ }
+}
+
+GtkIconCache *
+_gtk_icon_cache_new_for_path (const gchar *path)
+{
+ gchar *cache_filename;
+ gint fd;
+ struct stat st;
+ struct stat path_st;
+ gchar *buffer;
+ GtkIconCache *cache = NULL;
+
+ if (g_getenv ("GTK_NO_ICON_CACHE"))
+ return NULL;
+
+ /* Check if we have a cache file */
+ cache_filename = g_build_filename (path, "icon-theme.cache", NULL);
+
+ GTK_NOTE (ICONTHEME,
+ g_print ("look for cache in %s\n", path));
+
+ if (!g_file_test (cache_filename, G_FILE_TEST_IS_REGULAR))
+ {
+ g_free (cache_filename);
+ return NULL;
+ };
+
+ /* Open the file and mmap it */
+ fd = open (cache_filename, O_RDONLY);
+
+ g_free (cache_filename);
+
+ if (fd < 0)
+ return NULL;
+
+ if (fstat (fd, &st) < 0)
+ goto done;
+
+ if (stat (path, &path_st) < 0)
+ goto done;
+
+ /* Verify cache is uptodate */
+ if (st.st_mtime < path_st.st_mtime)
+ {
+ GTK_NOTE (ICONTHEME,
+ g_print ("cache outdated\n"));
+ goto done;
+ }
+
+ buffer = mmap (0, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
+
+ if (buffer == MAP_FAILED)
+ goto done;
+
+ /* Verify version */
+ if (GET_UINT16 (buffer, 0) != MAJOR_VERSION ||
+ GET_UINT16 (buffer, 2) != MINOR_VERSION)
+ {
+ munmap (buffer, st.st_size);
+ GTK_NOTE (ICONTHEME,
+ g_print ("wrong cache version\n"));
+ goto done;
+ }
+
+ GTK_NOTE (ICONTHEME,
+ g_print ("found cache for %s\n", path));
+
+ cache = g_new0 (GtkIconCache, 1);
+ cache->ref_count = 1;
+ cache->buffer = buffer;
+ cache->size = st.st_size;
+
+ done:
+ close (fd);
+
+ return cache;
+}
+
+static int
+get_directory_index (GtkIconCache *cache,
+ const gchar *directory)
+{
+ guint32 dir_list_offset;
+ int n_dirs;
+ int i;
+
+ dir_list_offset = GET_UINT32 (cache->buffer, 8);
+
+ n_dirs = GET_UINT32 (cache->buffer, dir_list_offset);
+
+ for (i = 0; i < n_dirs; i++)
+ {
+ guint32 name_offset = GET_UINT32 (cache->buffer, dir_list_offset + 4 + 4 * i);
+ gchar *name = cache->buffer + name_offset;
+ if (strcmp (name, directory) == 0)
+ return i;
+ }
+
+ return -1;
+}
+
+gboolean
+_gtk_icon_cache_has_directory (GtkIconCache *cache,
+ const gchar *directory)
+{
+ return get_directory_index (cache, directory) != -1;
+}
+
+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;
+}
+
+gint
+_gtk_icon_cache_get_icon_flags (GtkIconCache *cache,
+ const gchar *icon_name,
+ const gchar *directory)
+{
+ guint32 hash_offset;
+ guint32 n_buckets;
+ guint32 chain_offset;
+ int hash, directory_index;
+ guint32 image_list_offset, n_images;
+ gboolean found = FALSE;
+ int i;
+
+ hash_offset = GET_UINT32 (cache->buffer, 4);
+ n_buckets = GET_UINT32 (cache->buffer, hash_offset);
+
+ hash = icon_name_hash (icon_name) % n_buckets;
+
+ chain_offset = GET_UINT32 (cache->buffer, hash_offset + 4 + 4 * hash);
+ while (chain_offset != 0xffffffff)
+ {
+ guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4);
+ gchar *name = cache->buffer + name_offset;
+
+ if (strcmp (name, icon_name) == 0)
+ {
+ found = TRUE;
+ break;
+ }
+
+ chain_offset = GET_UINT32 (cache->buffer, chain_offset);
+ }
+
+ if (!found)
+ return 0;
+
+ /* We've found an icon list, now check if we have the right icon in it */
+ directory_index = get_directory_index (cache, directory);
+ image_list_offset = GET_UINT32 (cache->buffer, chain_offset + 8);
+ n_images = GET_UINT32 (cache->buffer, image_list_offset);
+
+ for (i = 0; i < n_images; i++)
+ {
+ if (GET_UINT16 (cache->buffer, image_list_offset + 4 + 8 * i) ==
+ directory_index)
+ return GET_UINT16 (cache->buffer, image_list_offset + 4 + 8 * i + 2);
+ }
+
+ return 0;
+}
+
+void
+_gtk_icon_cache_add_icons (GtkIconCache *cache,
+ const gchar *directory,
+ GHashTable *hash_table)
+{
+ int directory_index;
+ guint32 hash_offset, n_buckets;
+ guint32 chain_offset;
+ guint32 image_list_offset, n_images;
+ int i, j;
+
+ directory_index = get_directory_index (cache, directory);
+
+ if (directory_index == -1)
+ return;
+
+ hash_offset = GET_UINT32 (cache->buffer, 4);
+ n_buckets = GET_UINT32 (cache->buffer, hash_offset);
+
+ for (i = 0; i < n_buckets; i++)
+ {
+ chain_offset = GET_UINT32 (cache->buffer, hash_offset + 4 + 4 * i);
+ while (chain_offset != 0xffffffff)
+ {
+ guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4);
+ gchar *name = cache->buffer + name_offset;
+
+ image_list_offset = GET_UINT32 (cache->buffer, chain_offset + 8);
+ n_images = GET_UINT32 (cache->buffer, image_list_offset);
+
+ for (j = 0; j < n_images; j++)
+ {
+ if (GET_UINT16 (cache->buffer, image_list_offset + 4 + 8 * j) ==
+ directory_index)
+ g_hash_table_insert (hash_table, name, NULL);
+ }
+
+ chain_offset = GET_UINT32 (cache->buffer, chain_offset);
+ }
+ }
+
+}
diff --git a/gtk/gtkiconcache.h b/gtk/gtkiconcache.h
new file mode 100644
index 0000000000..ee9d48aeb3
--- /dev/null
+++ b/gtk/gtkiconcache.h
@@ -0,0 +1,41 @@
+/* gtkiconcache.h
+ * 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.
+ */
+#ifndef __GTK_ICON_CACHE_H__
+#define __GTK_ICON_CACHE_H__
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+typedef struct _GtkIconCache GtkIconCache;
+
+GtkIconCache *_gtk_icon_cache_new_for_path (const gchar *path);
+gboolean _gtk_icon_cache_has_directory (GtkIconCache *cache,
+ const gchar *directory);
+void _gtk_icon_cache_add_icons (GtkIconCache *cache,
+ const gchar *directory,
+ GHashTable *hash_table);
+
+gint _gtk_icon_cache_get_icon_flags (GtkIconCache *cache,
+ const gchar *icon_name,
+ const gchar *directory);
+
+GtkIconCache *_gtk_icon_cache_ref (GtkIconCache *cache);
+void _gtk_icon_cache_unref (GtkIconCache *cache);
+
+
+#endif /* __GTK_ICON_CACHE_H__ */
diff --git a/gtk/gtkicontheme.c b/gtk/gtkicontheme.c
index 6ac490e32d..b2053d620c 100644
--- a/gtk/gtkicontheme.c
+++ b/gtk/gtkicontheme.c
@@ -37,6 +37,7 @@
#include "gtkicontheme.h"
#include "gtkiconthemeparser.h"
+#include "gtkiconcache.h"
#include "gtkintl.h"
#include "gtksettings.h"
#include "gtkprivate.h"
@@ -61,7 +62,8 @@ typedef enum
ICON_SUFFIX_NONE = 0,
ICON_SUFFIX_XPM = 1 << 0,
ICON_SUFFIX_SVG = 1 << 1,
- ICON_SUFFIX_PNG = 1 << 2
+ ICON_SUFFIX_PNG = 1 << 2,
+ HAS_ICON_FILE = 1 << 3
} IconSuffix;
@@ -81,7 +83,8 @@ struct _GtkIconThemePrivate
*/
GList *themes;
GHashTable *unthemed_icons;
-
+ GList *unthemed_icons_caches;
+
/* Note: The keys of this hashtable are owned by the
* themedir and unthemed hashtables.
*/
@@ -132,6 +135,11 @@ typedef struct
char *comment;
char *example;
+ /* Icon caches, per theme directory, key is NULL if
+ * no cache exists for that directory
+ */
+ GHashTable *icon_caches;
+
/* In search order */
GList *dirs;
} IconTheme;
@@ -158,6 +166,9 @@ typedef struct
int threshold;
char *dir;
+ char *subdir;
+
+ GtkIconCache *cache;
GHashTable *icons;
GHashTable *icon_data;
@@ -204,6 +215,14 @@ static void do_theme_change (GtkIconTheme *icon_theme);
static void blow_themes (GtkIconTheme *icon_themes);
static void icon_data_free (GtkIconData *icon_data);
+static void load_icon_data (IconThemeDir *dir,
+ const char *path,
+ const char *name);
+
+static IconSuffix theme_dir_get_icon_suffix (IconThemeDir *dir,
+ const gchar *icon_name,
+ gboolean *has_icon_file);
+
static GtkIconInfo *icon_info_new (void);
static GtkIconInfo *icon_info_new_builtin (BuiltinIcon *icon);
@@ -499,8 +518,12 @@ pixbuf_supports_svg ()
{
GSList *formats = gdk_pixbuf_get_formats ();
GSList *tmp_list;
- gboolean found_svg = FALSE;
+ static gboolean found_svg = FALSE;
+ static gboolean value_known = FALSE;
+ if (value_known)
+ return found_svg;
+
for (tmp_list = formats; tmp_list && !found_svg; tmp_list = tmp_list->next)
{
gchar **mime_types = gdk_pixbuf_format_get_mime_types (tmp_list->data);
@@ -516,7 +539,8 @@ pixbuf_supports_svg ()
}
g_slist_free (formats);
-
+ value_known = TRUE;
+
return found_svg;
}
@@ -553,7 +577,8 @@ gtk_icon_theme_init (GtkIconTheme *icon_theme)
priv->themes_valid = FALSE;
priv->themes = NULL;
priv->unthemed_icons = NULL;
-
+ priv->unthemed_icons_caches = NULL;
+
priv->pixbuf_supports_svg = pixbuf_supports_svg ();
}
@@ -569,6 +594,8 @@ do_theme_change (GtkIconTheme *icon_theme)
{
GtkIconThemePrivate *priv = icon_theme->priv;
+ GTK_NOTE (ICONTHEME,
+ g_print ("change to icon theme \"%s\"\n", priv->current_theme));
blow_themes (icon_theme);
g_signal_emit (icon_theme, signal_changed, 0);
@@ -579,6 +606,16 @@ do_theme_change (GtkIconTheme *icon_theme)
}
}
+static void
+free_cache (gpointer data,
+ gpointer user_data)
+{
+ GtkIconCache *cache = (GtkIconCache *)data;
+
+ if (cache)
+ _gtk_icon_cache_unref (cache);
+}
+
static void
blow_themes (GtkIconTheme *icon_theme)
{
@@ -592,9 +629,13 @@ blow_themes (GtkIconTheme *icon_theme)
g_list_foreach (priv->dir_mtimes, (GFunc)free_dir_mtime, NULL);
g_list_free (priv->dir_mtimes);
g_hash_table_destroy (priv->unthemed_icons);
+ if (priv->unthemed_icons_caches)
+ g_list_foreach (priv->unthemed_icons_caches, free_cache, NULL);
+ g_list_free (priv->unthemed_icons_caches);
}
priv->themes = NULL;
priv->unthemed_icons = NULL;
+ priv->unthemed_icons_caches = NULL;
priv->dir_mtimes = NULL;
priv->all_icons = NULL;
priv->themes_valid = FALSE;
@@ -830,7 +871,7 @@ insert_theme (GtkIconTheme *icon_theme, const char *theme_name)
struct stat stat_buf;
priv = icon_theme->priv;
-
+
for (l = priv->themes; l != NULL; l = l->next)
{
theme = l->data;
@@ -910,6 +951,7 @@ insert_theme (GtkIconTheme *icon_theme, const char *theme_name)
dirs = g_strsplit (directories, ",", 0);
+ theme->icon_caches = NULL;
theme->dirs = NULL;
for (i = 0; dirs[i] != NULL; i++)
theme_subdir_load (icon_theme, theme, theme_file, dirs[i]);
@@ -973,13 +1015,25 @@ load_themes (GtkIconTheme *icon_theme)
/* Always look in the "default" icon theme */
insert_theme (icon_theme, DEFAULT_THEME_NAME);
priv->themes = g_list_reverse (priv->themes);
-
+
+
priv->unthemed_icons = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, (GDestroyNotify)free_unthemed_icon);
for (base = 0; base < icon_theme->priv->search_path_len; base++)
{
+ GtkIconCache *cache;
dir = icon_theme->priv->search_path[base];
+
+ cache = _gtk_icon_cache_new_for_path (dir);
+
+ if (cache != NULL)
+ {
+ priv->unthemed_icons_caches = g_list_prepend (priv->unthemed_icons_caches, cache);
+
+ continue;
+ }
+
gdir = g_dir_open (dir, 0, NULL);
if (gdir == NULL)
@@ -1036,7 +1090,7 @@ load_themes (GtkIconTheme *icon_theme)
unthemed_icon->svg_filename = abs_file;
else
unthemed_icon->no_svg_filename = abs_file;
-
+
g_hash_table_insert (priv->unthemed_icons,
base_name,
unthemed_icon);
@@ -1357,8 +1411,8 @@ gtk_icon_theme_get_icon_sizes (GtkIconTheme *icon_theme,
for (d = theme->dirs; d; d = d->next)
{
IconThemeDir *dir = d->data;
-
- suffix = GPOINTER_TO_UINT (g_hash_table_lookup (dir->icons, icon_name));
+
+ suffix = theme_dir_get_icon_suffix (dir, icon_name, NULL);
if (suffix != ICON_SUFFIX_NONE)
{
if (suffix == ICON_SUFFIX_SVG)
@@ -1566,16 +1620,25 @@ theme_destroy (IconTheme *theme)
g_list_foreach (theme->dirs, (GFunc)theme_dir_destroy, NULL);
g_list_free (theme->dirs);
+
+ if (theme->icon_caches)
+ g_hash_table_destroy (theme->icon_caches);
+
g_free (theme);
}
static void
theme_dir_destroy (IconThemeDir *dir)
{
- g_hash_table_destroy (dir->icons);
+ if (dir->cache)
+ _gtk_icon_cache_unref (dir->cache);
+ else
+ g_hash_table_destroy (dir->icons);
+
if (dir->icon_data)
g_hash_table_destroy (dir->icon_data);
g_free (dir->dir);
+ g_free (dir->subdir);
g_free (dir);
}
@@ -1663,6 +1726,31 @@ best_suffix (IconSuffix suffix,
return ICON_SUFFIX_NONE;
}
+
+static IconSuffix
+theme_dir_get_icon_suffix (IconThemeDir *dir,
+ const gchar *icon_name,
+ gboolean *has_icon_file)
+{
+ IconSuffix suffix;
+
+ if (dir->cache)
+ {
+ suffix = (IconSuffix)_gtk_icon_cache_get_icon_flags (dir->cache,
+ icon_name,
+ dir->subdir);
+
+ if (has_icon_file)
+ {
+ *has_icon_file = suffix & HAS_ICON_FILE;
+ }
+ }
+ else
+ suffix = GPOINTER_TO_UINT (g_hash_table_lookup (dir->icons, icon_name));
+
+ return suffix;
+}
+
static GtkIconInfo *
theme_lookup_icon (IconTheme *theme,
const char *icon_name,
@@ -1700,7 +1788,7 @@ theme_lookup_icon (IconTheme *theme,
{
dir = l->data;
- suffix = GPOINTER_TO_UINT (g_hash_table_lookup (dir->icons, icon_name));
+ suffix = theme_dir_get_icon_suffix (dir, icon_name, NULL);
if (suffix != ICON_SUFFIX_NONE &&
(allow_svg || suffix != ICON_SUFFIX_SVG))
@@ -1744,8 +1832,9 @@ theme_lookup_icon (IconTheme *theme,
if (min_dir)
{
GtkIconInfo *icon_info = icon_info_new ();
+ gboolean has_icon_file;
- suffix = GPOINTER_TO_UINT (g_hash_table_lookup (min_dir->icons, icon_name));
+ suffix = theme_dir_get_icon_suffix (min_dir, icon_name, &has_icon_file);
suffix = best_suffix (suffix, allow_svg);
g_assert (suffix != ICON_SUFFIX_NONE);
@@ -1753,6 +1842,24 @@ theme_lookup_icon (IconTheme *theme,
icon_info->filename = g_build_filename (min_dir->dir, file, NULL);
g_free (file);
+ if (min_dir->cache && has_icon_file)
+ {
+ gchar *icon_file_name, *icon_file_path;
+
+ icon_file_name = g_strconcat (icon_name, ".icon", NULL);
+ icon_file_path = g_build_filename (min_dir->dir, icon_file_name, NULL);
+
+ if (g_file_test (icon_file_path, G_FILE_TEST_IS_REGULAR))
+ {
+ if (min_dir->icon_data == NULL)
+ min_dir->icon_data = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, (GDestroyNotify)icon_data_free);
+ load_icon_data (min_dir, icon_file_path, icon_file_name);
+ }
+ g_free (icon_file_name);
+ g_free (icon_file_path);
+ }
+
if (min_dir->icon_data != NULL)
icon_info->data = g_hash_table_lookup (min_dir->icon_data, icon_name);
@@ -1779,10 +1886,21 @@ theme_list_icons (IconTheme *theme, GHashTable *icons,
if (context == dir->context ||
context == 0)
- g_hash_table_foreach (dir->icons,
- add_key_to_hash,
- icons);
-
+ {
+ if (dir->cache)
+ {
+ _gtk_icon_cache_add_icons (dir->cache,
+ dir->subdir,
+ icons);
+
+ }
+ else
+ {
+ g_hash_table_foreach (dir->icons,
+ add_key_to_hash,
+ icons);
+ }
+ }
l = l->next;
}
}
@@ -1889,7 +2007,9 @@ scan_directory (GtkIconThemePrivate *icon_theme,
char *base_name, *dot;
char *path;
IconSuffix suffix, hash_suffix;
-
+
+ GTK_NOTE (ICONTHEME,
+ g_print ("scanning directory %s\n", full_dir));
dir->icons = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, NULL);
@@ -1998,11 +2118,35 @@ theme_subdir_load (GtkIconTheme *icon_theme,
for (base = 0; base < icon_theme->priv->search_path_len; base++)
{
+ GtkIconCache *cache;
+ gchar *theme_path;
+
full_dir = g_build_filename (icon_theme->priv->search_path[base],
theme->name,
subdir,
NULL);
- if (g_file_test (full_dir, G_FILE_TEST_IS_DIR))
+
+ /* First, see if we have a cache for the directory */
+ if (!theme->icon_caches)
+ theme->icon_caches = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, (GDestroyNotify)free_cache);
+
+ theme_path = g_build_filename (icon_theme->priv->search_path[base],
+ theme->name,
+ NULL);
+
+ if (!g_hash_table_lookup_extended (theme->icon_caches, theme_path,
+ NULL, (gpointer)&cache))
+ {
+ /* This will return NULL if the cache doesn't exist or is outdated */
+ cache = _gtk_icon_cache_new_for_path (theme_path);
+
+ g_hash_table_insert (theme->icon_caches, g_strdup (theme_path), cache);
+ }
+
+ g_free (theme_path);
+
+ if (cache != NULL || g_file_test (full_dir, G_FILE_TEST_IS_DIR))
{
dir = g_new (IconThemeDir, 1);
dir->type = type;
@@ -2013,8 +2157,14 @@ theme_subdir_load (GtkIconTheme *icon_theme,
dir->threshold = threshold;
dir->dir = full_dir;
dir->icon_data = NULL;
-
- scan_directory (icon_theme->priv, dir, full_dir);
+ dir->subdir = g_strdup (subdir);
+ if (cache != NULL)
+ dir->cache = _gtk_icon_cache_ref (cache);
+ else
+ {
+ dir->cache = NULL;
+ scan_directory (icon_theme->priv, dir, full_dir);
+ }
theme->dirs = g_list_prepend (theme->dirs, dir);
}
diff --git a/gtk/gtkmain.c b/gtk/gtkmain.c
index a078645912..40acba8fd6 100644
--- a/gtk/gtkmain.c
+++ b/gtk/gtkmain.c
@@ -158,7 +158,8 @@ static const GDebugKey gtk_debug_keys[] = {
{"keybindings", GTK_DEBUG_KEYBINDINGS},
{"multihead", GTK_DEBUG_MULTIHEAD},
{"modules", GTK_DEBUG_MODULES},
- {"geometry", GTK_DEBUG_GEOMETRY}
+ {"geometry", GTK_DEBUG_GEOMETRY},
+ {"icontheme", GTK_DEBUG_ICONTHEME}
};
static const guint gtk_ndebug_keys = sizeof (gtk_debug_keys) / sizeof (GDebugKey);
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;
+}