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.
This commit is contained in:
Matthias Clasen 2004-10-19 18:45:41 +00:00 committed by Matthias Clasen
parent b087f76551
commit 6fc2b8118a
16 changed files with 1351 additions and 25 deletions

View File

@ -1,3 +1,29 @@
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.

View File

@ -1,3 +1,29 @@
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.

View File

@ -1,3 +1,29 @@
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.

View File

@ -1,3 +1,29 @@
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.

View File

@ -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.

View File

@ -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

View File

@ -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;

View File

@ -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\&.

View File

@ -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>

View File

@ -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:

View File

@ -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

280
gtk/gtkiconcache.c Normal file
View File

@ -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);
}
}
}

41
gtk/gtkiconcache.h Normal file
View File

@ -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__ */

View File

@ -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,6 +83,7 @@ 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,7 +518,11 @@ 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)
{
@ -516,6 +539,7 @@ pixbuf_supports_svg ()
}
g_slist_free (formats);
value_known = TRUE;
return found_svg;
}
@ -553,6 +577,7 @@ 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;
@ -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]);
@ -974,12 +1016,24 @@ load_themes (GtkIconTheme *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)
@ -1358,7 +1412,7 @@ gtk_icon_theme_get_icon_sizes (GtkIconTheme *icon_theme,
{
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;
}
}
@ -1890,6 +2008,8 @@ scan_directory (GtkIconThemePrivate *icon_theme,
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);
}

View File

@ -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);

628
gtk/updateiconcache.c Normal file
View File

@ -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;
}