gtk2/gtk/updateiconcache.c
Anders Carlsson 3fc42d7ab9 Update spec.
2005-03-15  Anders Carlsson  <andersca@imendio.com>

	* docs/iconcache.txt:
	Update spec.

	* gtk/gtkiconcache.c: (find_image_offset),
	(_gtk_icon_cache_get_icon_flags), (_gtk_icon_cache_add_icons),
	(_gtk_icon_cache_get_icon), (_gtk_icon_cache_get_icon_data):
	* gtk/gtkiconcache.h:
	Update to be able to fetch pixbuf data and icon metadata.

	* gtk/gtkicontheme.c: (theme_lookup_icon), (gtk_icon_info_free),
	(icon_info_ensure_scale_and_pixbuf):
	Use new cache functions.

	* gtk/updateiconcache.c: (foreach_remove_func), (load_icon_data),
	(maybe_cache_image_data), (scan_directory), (write_pixdata),
	(get_image_meta_data_size), (get_image_pixel_data_size),
	(get_image_data_size), (get_single_node_size), (get_bucket_size),
	(write_bucket), (main):
	Update to write pixbuf data as well as information from .icon
	files.
2005-03-15 13:18:25 +00:00

1039 lines
21 KiB
C

/* 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 <config.h>
#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>
#include <glib/gstdio.h>
#include <gdk-pixbuf/gdk-pixdata.h>
static gboolean force_update = FALSE;
static gboolean quiet = FALSE;
#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 CAN_CACHE_IMAGE_DATA(flags) (((flags) & HAS_SUFFIX_PNG) || ((flags) & HAS_SUFFIX_XPM))
#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 = g_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 = g_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;
gboolean has_pixdata;
GdkPixdata pixdata;
int has_embedded_rect;
int x0, y0, x1, y1;
int n_attach_points;
int *attach_points;
int n_display_names;
char **display_names;
} Image;
static gboolean
foreach_remove_func (gpointer key, gpointer value, gpointer user_data)
{
Image *image = (Image *)value;
GHashTable *files = user_data;
GList *list;
gboolean free_key = FALSE;
if (image->flags == HAS_ICON_FILE)
{
g_free (key);
g_free (image);
g_free (image->attach_points);
g_strfreev (image->display_names);
return TRUE;
}
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;
}
static void
load_icon_data (Image *image, const char *path)
{
GKeyFile *icon_file;
char **split;
gsize length;
char *str;
char *split_point;
int i;
gint *ivalues;
GError *error = NULL;
gchar **keys;
gsize n_keys;
icon_file = g_key_file_new ();
g_key_file_set_list_separator (icon_file, ',');
g_key_file_load_from_file (icon_file, path, G_KEY_FILE_KEEP_TRANSLATIONS, &error);
if (error)
{
g_error_free (error);
return;
}
ivalues = g_key_file_get_integer_list (icon_file,
"Icon Data", "EmbeddedTextRectangle",
&length, NULL);
if (ivalues)
{
if (length == 4)
{
image->has_embedded_rect = TRUE;
image->x0 = ivalues[0];
image->y0 = ivalues[1];
image->x1 = ivalues[2];
image->y1 = ivalues[3];
}
g_free (ivalues);
}
str = g_key_file_get_string (icon_file, "Icon Data", "AttachPoints", NULL);
if (str)
{
split = g_strsplit (str, "|", -1);
image->n_attach_points = g_strv_length (split);
image->attach_points = g_new (int, 2 * image->n_attach_points);
i = 0;
while (split[i] != NULL && i < image->n_attach_points)
{
split_point = strchr (split[i], ',');
if (split_point)
{
*split_point = 0;
split_point++;
image->attach_points[2 * i] = atoi (split[i]);
image->attach_points[2 * i + 1] = atoi (split_point);
}
i++;
}
g_strfreev (split);
g_free (str);
}
keys = g_key_file_get_keys (icon_file, "Icon Data", &n_keys, &error);
image->display_names = g_new0 (gchar *, 2 * n_keys + 1);
image->n_display_names = 0;
for (i = 0; i < n_keys; i++)
{
gchar *lang, *name;
if (g_str_has_prefix (keys[i], "DisplayName"))
{
gchar *open, *close = NULL;
open = strchr (keys[i], '[');
if (open)
close = strchr (open, ']');
if (open && close)
{
lang = g_strndup (open + 1, close - open - 1);
name = g_key_file_get_locale_string (icon_file,
"Icon Data", "DisplayName",
lang, NULL);
}
else
{
lang = g_strdup ("C");
name = g_key_file_get_string (icon_file,
"Icon Data", "DisplayName",
NULL);
}
image->display_names[2 * image->n_display_names] = lang;
image->display_names[2 * image->n_display_names + 1] = name;
image->n_display_names++;
}
}
g_strfreev (keys);
g_key_file_free (icon_file);
}
static void
maybe_cache_image_data (Image *image, const gchar *path)
{
if (CAN_CACHE_IMAGE_DATA(image->flags) && !image->has_pixdata)
{
GdkPixbuf *pixbuf;
pixbuf = gdk_pixbuf_new_from_file (path, NULL);
if (pixbuf)
{
image->has_pixdata = TRUE;
gdk_pixdata_from_pixbuf (&image->pixdata, pixbuf, FALSE);
}
}
}
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);
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;
maybe_cache_image_data (image, path);
}
else
{
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;
maybe_cache_image_data (image, path);
g_hash_table_insert (dir_hash, g_strdup (basename), image);
}
if (g_str_has_suffix (name, ".icon"))
load_icon_data (image, path);
g_free (basename);
}
g_free (path);
}
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;
}
gboolean
write_pixdata (FILE *cache, GdkPixdata *pixdata)
{
guint8 *s;
int len;
int i;
/* Type 0 is GdkPixdata */
if (!write_card32 (cache, 0))
return FALSE;
s = gdk_pixdata_serialize (pixdata, &len);
if (!write_card32 (cache, len))
{
g_free (s);
return FALSE;
}
i = fwrite (s, len, 1, cache);
g_free (s);
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_image_meta_data_size (Image *image)
{
gint i;
guint len = 0;
if (image->has_embedded_rect ||
image->attach_points > 0 ||
image->n_display_names > 0)
len += 12;
if (image->has_embedded_rect)
len += 8;
if (image->n_attach_points > 0)
len += 4 + image->n_attach_points * 4;
if (image->n_display_names > 0)
{
len += 4 + 8 * image->n_display_names;
for (i = 0; image->display_names[i]; i++)
len += ALIGN_VALUE (strlen (image->display_names[i]) + 1, 4);
}
return len;
}
guint
get_image_pixel_data_size (Image *image)
{
if (image->has_pixdata)
return image->pixdata.length + 8;
return 0;
}
guint
get_image_data_size (Image *image)
{
guint len;
len = 0;
len += get_image_pixel_data_size (image);
len += get_image_meta_data_size (image);
if (len > 0)
len += 8;
return len;
}
guint
get_single_node_size (HashNode *node, gboolean include_image_data)
{
int len = 0;
GList *list;
/* Node pointers */
len += 12;
/* Name */
len += ALIGN_VALUE (strlen (node->name) + 1, 4);
/* Image list */
len += 4 + g_list_length (node->image_list) * 8;
/* Image data */
if (include_image_data)
for (list = node->image_list; list; list = list->next)
{
Image *image = list->data;
len += get_image_data_size (image);
}
return len;
}
guint
get_bucket_size (HashNode *node)
{
int len = 0;
while (node)
{
len += get_single_node_size (node, TRUE);
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, TRUE);
int image_data_offset = *offset + get_single_node_size (node, FALSE);
int data_offset;
int tmp;
int i, j, 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 */
tmp = *offset + 12 + ALIGN_VALUE (strlen (node->name) + 1, 4);
if (!write_card32 (cache, tmp))
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;
/* Image data goes right after the image list */
tmp += 4 + len * 8;
list = node->image_list;
data_offset = image_data_offset;
for (i = 0; i < len; i++)
{
Image *image = list->data;
int image_data_size = get_image_data_size (image);
/* Directory index */
if (!write_card16 (cache, image->dir_index))
return FALSE;
/* Flags */
if (!write_card16 (cache, image->flags))
return FALSE;
/* Image data offset */
if (image_data_size > 0)
{
if (!write_card32 (cache, data_offset))
return FALSE;
data_offset += image_data_size;
}
else
{
if (!write_card32 (cache, 0))
return FALSE;
}
list = list->next;
}
/* Now write the image data */
list = node->image_list;
for (i = 0; i < len; i++, list = list->next)
{
Image *image = list->data;
int pixel_data_size = get_image_pixel_data_size (image);
int meta_data_size = get_image_meta_data_size (image);
if (meta_data_size + pixel_data_size == 0)
continue;
/* Pixel data */
if (pixel_data_size > 0)
{
if (!write_card32 (cache, image_data_offset + 8))
return FALSE;
}
else
{
if (!write_card32 (cache, 0))
return FALSE;
}
if (meta_data_size > 0)
{
if (!write_card32 (cache, image_data_offset + pixel_data_size + 8))
return FALSE;
}
else
{
if (!write_card32 (cache, 0))
return FALSE;
}
if (pixel_data_size > 0)
{
if (!write_pixdata (cache, &image->pixdata))
return FALSE;
}
if (meta_data_size > 0)
{
int ofs = image_data_offset + pixel_data_size + 20;
if (image->has_embedded_rect)
{
if (!write_card32 (cache, ofs))
return FALSE;
ofs += 8;
}
else
{
if (!write_card32 (cache, 0))
return FALSE;
}
if (image->n_attach_points > 0)
{
if (!write_card32 (cache, ofs))
return FALSE;
ofs += 4 + 4 * image->n_attach_points;
}
else
{
if (!write_card32 (cache, 0))
return FALSE;
}
if (image->n_display_names > 0)
{
if (!write_card32 (cache, ofs))
return FALSE;
}
else
{
if (!write_card32 (cache, 0))
return FALSE;
}
if (image->has_embedded_rect)
{
if (!write_card16 (cache, image->x0) ||
!write_card16 (cache, image->y0) ||
!write_card16 (cache, image->x1) ||
!write_card16 (cache, image->y1))
return FALSE;
}
if (image->n_attach_points > 0)
{
if (!write_card32 (cache, image->n_attach_points))
return FALSE;
for (j = 0; j < 2 * image->n_attach_points; j++)
{
if (!write_card16 (cache, image->attach_points[j]))
return FALSE;
}
}
if (image->n_display_names > 0)
{
if (!write_card32 (cache, image->n_display_names))
return FALSE;
ofs += 4 + 8 * image->n_display_names;
for (j = 0; j < 2 * image->n_display_names; j++)
{
if (!write_card32 (cache, ofs))
return FALSE;
ofs += ALIGN_VALUE (strlen (image->display_names[j]) + 1, 4);
}
for (j = 0; j < 2 * image->n_display_names; j++)
{
if (!write_string (cache, image->display_names[j]))
return FALSE;
}
}
}
image_data_offset += pixel_data_size + meta_data_size + 8;
}
*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 = g_fopen (tmp_cache_path, "wb");
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);
g_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)
{
g_unlink (tmp_cache_path);
exit (1);
}
cache_path = g_build_filename (path, CACHE_NAME, NULL);
if (g_rename (tmp_cache_path, cache_path) == -1)
{
g_unlink (tmp_cache_path);
exit (1);
}
/* Update time */
/* FIXME: What do do if an error occurs here? */
g_stat (path, &path_stat);
g_stat (cache_path, &cache_stat);
utime_buf.actime = path_stat.st_atime;
utime_buf.modtime = cache_stat.st_mtime;
utime (path, &utime_buf);
if (!quiet)
g_printerr ("Cache file created successfully.\n");
}
static GOptionEntry args[] = {
{ "force", 'f', 0, G_OPTION_ARG_NONE, &force_update, "Overwrite an existing cache, even if uptodate", NULL },
{ "quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet, "Turn off verbose output", NULL },
{ NULL }
};
int
main (int argc, char **argv)
{
gchar *path;
GOptionContext *context;
if (argc < 2)
return 0;
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];
#ifdef G_OS_WIN32
path = g_locale_to_utf8 (path, -1, NULL, NULL, NULL);
#endif
if (!force_update && is_cache_up_to_date (path))
return 0;
g_type_init ();
build_cache (path);
return 0;
}