gtk/tools/updateiconcache.c
Matthias Clasen ebe6d52252 Drop redundant glib version checks
We require GLib 2.72 now.
2022-11-17 22:49:56 -05:00

1765 lines
39 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <locale.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <errno.h>
#ifdef _MSC_VER
#include <io.h>
#include <sys/utime.h>
#else
#include <utime.h>
#endif
#include <glib.h>
#include <glib/gstdio.h>
#include <gdk-pixbuf/gdk-pixdata.h>
#include <glib/gi18n.h>
#include "gtkiconcachevalidatorprivate.h"
static gboolean force_update = FALSE;
static gboolean ignore_theme_index = FALSE;
static gboolean quiet = FALSE;
static gboolean index_only = TRUE;
static gboolean validate = FALSE;
static char *var_name = (char *) "-";
#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)))
#ifdef HAVE_FTW_H
#include <ftw.h>
static GStatBuf cache_dir_stat;
static gboolean cache_up_to_date;
static int check_dir_mtime (const char *dir,
const struct stat *sb,
int tf)
{
if (tf != FTW_NS && sb->st_mtime > cache_dir_stat.st_mtime)
{
cache_up_to_date = FALSE;
/* stop tree walk */
return 1;
}
return 0;
}
static gboolean
is_cache_up_to_date (const char *path)
{
char *cache_path;
int retval;
cache_path = g_build_filename (path, CACHE_NAME, NULL);
retval = g_stat (cache_path, &cache_dir_stat);
g_free (cache_path);
if (retval < 0)
{
/* Cache file not found */
return FALSE;
}
cache_up_to_date = TRUE;
ftw (path, check_dir_mtime, 20);
return cache_up_to_date;
}
#else /* !HAVE_FTW_H */
gboolean
is_cache_up_to_date (const char *path)
{
GStatBuf path_stat, cache_stat;
char *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)
{
/* Cache file not found */
return FALSE;
}
/* Check mtime */
return cache_stat.st_mtime >= path_stat.st_mtime;
}
#endif /* !HAVE_FTW_H */
static gboolean
has_theme_index (const char *path)
{
gboolean result;
char *index_path;
index_path = g_build_filename (path, "index.theme", NULL);
result = g_file_test (index_path, G_FILE_TEST_IS_REGULAR);
g_free (index_path);
return result;
}
typedef struct
{
GdkPixdata pixdata;
gboolean has_pixdata;
guint32 offset;
guint size;
} ImageData;
typedef struct
{
int has_embedded_rect;
int x0, y0, x1, y1;
int n_attach_points;
int *attach_points;
int n_display_names;
char **display_names;
guint32 offset;
int size;
} IconData;
static GHashTable *image_data_hash = NULL;
static GHashTable *icon_data_hash = NULL;
typedef struct
{
int flags;
int dir_index;
ImageData *image_data;
guint pixel_data_size;
IconData *icon_data;
guint icon_data_size;
} 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)
{
/* just a .icon file, throw away */
g_free (key);
g_free (image);
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 IconData *
load_icon_data (const char *path)
{
GKeyFile *icon_file;
char **split;
gsize length;
char *str;
char *split_point;
int i;
int *ivalues;
GError *error = NULL;
char **keys;
gsize n_keys;
IconData *data;
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);
g_key_file_free (icon_file);
return NULL;
}
data = g_new0 (IconData, 1);
ivalues = g_key_file_get_integer_list (icon_file,
"Icon Data", "EmbeddedTextRectangle",
&length, NULL);
if (ivalues)
{
if (length == 4)
{
data->has_embedded_rect = TRUE;
data->x0 = ivalues[0];
data->y0 = ivalues[1];
data->x1 = ivalues[2];
data->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);
data->n_attach_points = g_strv_length (split);
data->attach_points = g_new (int, 2 * data->n_attach_points);
for (i = 0; i < data->n_attach_points; ++i)
{
split_point = strchr (split[i], ',');
if (split_point)
{
*split_point = 0;
split_point++;
data->attach_points[2 * i] = atoi (split[i]);
data->attach_points[2 * i + 1] = atoi (split_point);
}
}
g_strfreev (split);
g_free (str);
}
keys = g_key_file_get_keys (icon_file, "Icon Data", &n_keys, &error);
data->display_names = g_new0 (char *, 2 * n_keys + 1);
data->n_display_names = 0;
for (i = 0; i < n_keys; i++)
{
char *lang, *name;
if (g_str_has_prefix (keys[i], "DisplayName"))
{
char *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);
}
data->display_names[2 * data->n_display_names] = lang;
data->display_names[2 * data->n_display_names + 1] = name;
data->n_display_names++;
}
}
g_strfreev (keys);
g_key_file_free (icon_file);
/* -1 means not computed yet, the real value depends
* on string pool state, and will be computed
* later
*/
data->size = -1;
return data;
}
/*
* This function was copied from gtkfilesystemunix.c, it should
* probably go to GLib
*/
static void
canonicalize_filename (char *filename)
{
char *p, *q;
gboolean last_was_slash = FALSE;
p = filename;
q = filename;
while (*p)
{
if (*p == G_DIR_SEPARATOR)
{
if (!last_was_slash)
*q++ = G_DIR_SEPARATOR;
last_was_slash = TRUE;
}
else
{
if (last_was_slash && *p == '.')
{
if (*(p + 1) == G_DIR_SEPARATOR ||
*(p + 1) == '\0')
{
if (*(p + 1) == '\0')
break;
p += 1;
}
else if (*(p + 1) == '.' &&
(*(p + 2) == G_DIR_SEPARATOR ||
*(p + 2) == '\0'))
{
if (q > filename + 1)
{
q--;
while (q > filename + 1 &&
*(q - 1) != G_DIR_SEPARATOR)
q--;
}
if (*(p + 2) == '\0')
break;
p += 2;
}
else
{
*q++ = *p;
last_was_slash = FALSE;
}
}
else
{
*q++ = *p;
last_was_slash = FALSE;
}
}
p++;
}
if (q > filename + 1 && *(q - 1) == G_DIR_SEPARATOR)
q--;
*q = '\0';
}
static char *
follow_links (const char *path)
{
char *target;
char *d, *s;
char *path2 = NULL;
path2 = g_strdup (path);
while (g_file_test (path2, G_FILE_TEST_IS_SYMLINK))
{
target = g_file_read_link (path2, NULL);
if (target)
{
if (g_path_is_absolute (target))
path2 = target;
else
{
d = g_path_get_dirname (path2);
s = g_build_filename (d, target, NULL);
g_free (d);
g_free (target);
g_free (path2);
path2 = s;
}
}
else
break;
}
if (strcmp (path, path2) == 0)
{
g_free (path2);
path2 = NULL;
}
return path2;
}
static void
maybe_cache_image_data (Image *image,
const char *path)
{
if (!index_only && !image->image_data &&
(g_str_has_suffix (path, ".png") || g_str_has_suffix (path, ".xpm")))
{
GdkPixbuf *pixbuf;
ImageData *idata;
char *path2;
idata = g_hash_table_lookup (image_data_hash, path);
path2 = follow_links (path);
if (path2)
{
ImageData *idata2;
canonicalize_filename (path2);
idata2 = g_hash_table_lookup (image_data_hash, path2);
if (idata && idata2 && idata != idata2)
g_error ("different idatas found for symlinked '%s' and '%s'\n",
path, path2);
if (idata && !idata2)
g_hash_table_insert (image_data_hash, g_strdup (path2), idata);
if (!idata && idata2)
{
g_hash_table_insert (image_data_hash, g_strdup (path), idata2);
idata = idata2;
}
}
if (!idata)
{
idata = g_new0 (ImageData, 1);
g_hash_table_insert (image_data_hash, g_strdup (path), idata);
if (path2)
g_hash_table_insert (image_data_hash, g_strdup (path2), idata);
}
if (!idata->has_pixdata)
{
pixbuf = gdk_pixbuf_new_from_file (path, NULL);
if (pixbuf)
{
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
gdk_pixdata_from_pixbuf (&idata->pixdata, pixbuf, FALSE);
G_GNUC_END_IGNORE_DEPRECATIONS;
idata->size = idata->pixdata.length + 8;
idata->has_pixdata = TRUE;
}
}
image->image_data = idata;
g_free (path2);
}
}
static void
maybe_cache_icon_data (Image *image,
const char *path)
{
if (g_str_has_suffix (path, ".icon"))
{
IconData *idata = NULL;
char *path2 = NULL;
idata = g_hash_table_lookup (icon_data_hash, path);
path2 = follow_links (path);
if (path2)
{
IconData *idata2;
canonicalize_filename (path2);
idata2 = g_hash_table_lookup (icon_data_hash, path2);
if (idata && idata2 && idata != idata2)
g_error ("different idatas found for symlinked '%s' and '%s'\n",
path, path2);
if (idata && !idata2)
g_hash_table_insert (icon_data_hash, g_strdup (path2), idata);
if (!idata && idata2)
{
g_hash_table_insert (icon_data_hash, g_strdup (path), idata2);
idata = idata2;
}
}
if (!idata)
{
idata = load_icon_data (path);
g_hash_table_insert (icon_data_hash, g_strdup (path), idata);
if (path2)
g_hash_table_insert (icon_data_hash, g_strdup (path2), idata);
}
image->icon_data = idata;
g_free (path2);
}
}
/*
* Finds all dir separators and replaces them with “/”.
* This makes sure that only /-separated paths are written in cache files,
* maintaining compatibility with theme index files that use slashes as
* directory separators on all platforms.
*/
static void
replace_backslashes_with_slashes (char *path)
{
size_t i;
if (path == NULL)
return;
for (i = 0; path[i]; i++)
if (G_IS_DIR_SEPARATOR (path[i]))
path[i] = '/';
}
static GList *
scan_directory (const char *base_path,
const char *subdir,
GHashTable *files,
GList *directories,
int depth)
{
GHashTable *dir_hash;
GDir *dir;
GList *list = NULL, *iterator = NULL;
const char *name;
char *dir_path;
gboolean dir_added = FALSE;
guint dir_index = 0xffff;
dir_path = g_build_path ("/", 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)))
{
list = g_list_prepend (list, g_strdup (name));
}
list = g_list_sort (list, (GCompareFunc) strcmp);
for (iterator = list; iterator; iterator = iterator->next)
{
name = iterator->data;
char *path;
gboolean retval;
int flags = 0;
Image *image;
char *basename, *dot;
path = g_build_filename (dir_path, name, NULL);
retval = g_file_test (path, G_FILE_TEST_IS_DIR);
if (retval)
{
char *subsubdir;
if (subdir)
subsubdir = g_build_path ("/", subdir, name, NULL);
else
subsubdir = g_strdup (name);
directories = scan_directory (base_path, subsubdir, files,
directories, depth + 1);
g_free (subsubdir);
continue;
}
/* ignore images in the toplevel directory */
if (subdir == NULL)
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)
{
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->dir_index = dir_index;
g_hash_table_insert (dir_hash, g_strdup (basename), image);
}
image->flags |= flags;
maybe_cache_image_data (image, path);
maybe_cache_icon_data (image, path);
g_free (basename);
}
g_free (path);
}
g_list_free_full (list, g_free);
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;
char *name;
GList *image_list;
int offset;
};
static guint
icon_name_hash (gconstpointer key)
{
const signed char *p = key;
guint32 h = *p;
if (h)
for (p += 1; *p != '\0'; p++)
h = (h << 5) - h + *p;
return h;
}
typedef struct {
int 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;
}
static GHashTable *string_pool = NULL;
static int
find_string (const char *n)
{
return GPOINTER_TO_INT (g_hash_table_lookup (string_pool, n));
}
static void
add_string (const char *n, int offset)
{
g_hash_table_insert (string_pool, (gpointer) n, GINT_TO_POINTER (offset));
}
static gboolean
write_string (FILE *cache, const char *n)
{
char *s;
int i, l;
l = ALIGN_VALUE (strlen (n) + 1, 4);
s = g_malloc0 (l);
strcpy (s, n);
i = fwrite (s, l, 1, cache);
g_free (s);
return i == 1;
}
static gboolean
write_card16 (FILE *cache, guint16 n)
{
int i;
n = GUINT16_TO_BE (n);
i = fwrite ((char *)&n, 2, 1, cache);
return i == 1;
}
static gboolean
write_card32 (FILE *cache, guint32 n)
{
int i;
n = GUINT32_TO_BE (n);
i = fwrite ((char *)&n, 4, 1, cache);
return i == 1;
}
static gboolean
write_image_data (FILE *cache, ImageData *image_data, int offset)
{
guint8 *s;
guint len;
int i;
GdkPixdata *pixdata = &image_data->pixdata;
/* Type 0 is GdkPixdata */
if (!write_card32 (cache, 0))
return FALSE;
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
s = gdk_pixdata_serialize (pixdata, &len);
G_GNUC_END_IGNORE_DEPRECATIONS;
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_icon_data (FILE *cache, IconData *icon_data, int offset)
{
int ofs = offset + 12;
int j;
int tmp, tmp2;
if (icon_data->has_embedded_rect)
{
if (!write_card32 (cache, ofs))
return FALSE;
ofs += 8;
}
else
{
if (!write_card32 (cache, 0))
return FALSE;
}
if (icon_data->n_attach_points > 0)
{
if (!write_card32 (cache, ofs))
return FALSE;
ofs += 4 + 4 * icon_data->n_attach_points;
}
else
{
if (!write_card32 (cache, 0))
return FALSE;
}
if (icon_data->n_display_names > 0)
{
if (!write_card32 (cache, ofs))
return FALSE;
}
else
{
if (!write_card32 (cache, 0))
return FALSE;
}
if (icon_data->has_embedded_rect)
{
if (!write_card16 (cache, icon_data->x0) ||
!write_card16 (cache, icon_data->y0) ||
!write_card16 (cache, icon_data->x1) ||
!write_card16 (cache, icon_data->y1))
return FALSE;
}
if (icon_data->n_attach_points > 0)
{
if (!write_card32 (cache, icon_data->n_attach_points))
return FALSE;
for (j = 0; j < 2 * icon_data->n_attach_points; j++)
{
if (!write_card16 (cache, icon_data->attach_points[j]))
return FALSE;
}
}
if (icon_data->n_display_names > 0)
{
if (!write_card32 (cache, icon_data->n_display_names))
return FALSE;
ofs += 4 + 8 * icon_data->n_display_names;
tmp = ofs;
for (j = 0; j < 2 * icon_data->n_display_names; j++)
{
tmp2 = find_string (icon_data->display_names[j]);
if (tmp2 == 0 || tmp2 == -1)
{
tmp2 = tmp;
tmp += ALIGN_VALUE (strlen (icon_data->display_names[j]) + 1, 4);
/* We're playing a little game with negative
* offsets here to handle duplicate strings in
* the array.
*/
add_string (icon_data->display_names[j], -tmp2);
}
else if (tmp2 < 0)
{
tmp2 = -tmp2;
}
if (!write_card32 (cache, tmp2))
return FALSE;
}
g_assert (ofs == ftell (cache));
for (j = 0; j < 2 * icon_data->n_display_names; j++)
{
tmp2 = find_string (icon_data->display_names[j]);
g_assert (tmp2 != 0 && tmp2 != -1);
if (tmp2 < 0)
{
tmp2 = -tmp2;
g_assert (tmp2 == ftell (cache));
add_string (icon_data->display_names[j], tmp2);
if (!write_string (cache, icon_data->display_names[j]))
return FALSE;
}
}
}
return TRUE;
}
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));
}
static int
get_image_meta_data_size (Image *image)
{
int i;
/* The complication with storing the size in both
* IconData and Image is necessary since we attribute
* the size of the IconData only to the first Image
* using it (at which time it is written out in the
* cache). Later Images just refer to the written out
* IconData via the offset.
*/
if (image->icon_data_size == 0)
{
if (image->icon_data && image->icon_data->size < 0)
{
IconData *data = image->icon_data;
data->size = 0;
if (data->has_embedded_rect ||
data->n_attach_points > 0 ||
data->n_display_names > 0)
data->size += 12;
if (data->has_embedded_rect)
data->size += 8;
if (data->n_attach_points > 0)
data->size += 4 + data->n_attach_points * 4;
if (data->n_display_names > 0)
{
data->size += 4 + 8 * data->n_display_names;
for (i = 0; data->display_names[i]; i++)
{
if (find_string (data->display_names[i]) == 0)
{
data->size += ALIGN_VALUE (strlen (data->display_names[i]) + 1, 4);
/* Adding the string to the pool with -1
* to indicate that it hasn't been written out
* to the cache yet. We still need it in the
* pool in case the same string occurs twice
* during a get_single_node_size() calculation.
*/
add_string (data->display_names[i], -1);
}
}
}
image->icon_data_size = data->size;
data->size = 0;
}
}
g_assert (image->icon_data_size % 4 == 0);
return image->icon_data_size;
}
static int
get_image_pixel_data_size (Image *image)
{
/* The complication with storing the size in both
* ImageData and Image is necessary since we attribute
* the size of the ImageData only to the first Image
* using it (at which time it is written out in the
* cache). Later Images just refer to the written out
* ImageData via the offset.
*/
if (image->pixel_data_size == 0)
{
if (image->image_data &&
image->image_data->has_pixdata)
{
image->pixel_data_size = image->image_data->size;
image->image_data->size = 0;
}
}
g_assert (image->pixel_data_size % 4 == 0);
return image->pixel_data_size;
}
static int
get_image_data_size (Image *image)
{
int len;
len = 0;
len += get_image_pixel_data_size (image);
len += get_image_meta_data_size (image);
/* Even if len is zero, we need to reserve space to
* write the ImageData, unless this is an .svg without
* .icon, in which case both image_data and icon_data
* are NULL.
*/
if (len > 0 || image->image_data || image->icon_data)
len += 8;
return len;
}
static void
get_single_node_size (HashNode *node, int *node_size, int *image_data_size)
{
GList *list;
/* Node pointers */
*node_size = 12;
/* Name */
if (find_string (node->name) == 0)
{
*node_size += ALIGN_VALUE (strlen (node->name) + 1, 4);
add_string (node->name, -1);
}
/* Image list */
*node_size += 4 + g_list_length (node->image_list) * 8;
/* Image data */
*image_data_size = 0;
for (list = node->image_list; list; list = list->next)
{
Image *image = list->data;
*image_data_size += get_image_data_size (image);
}
}
static gboolean
write_bucket (FILE *cache, HashNode *node, int *offset)
{
while (node != NULL)
{
int node_size, image_data_size;
int next_offset, image_data_offset;
int data_offset;
int name_offset;
int name_size;
int image_list_offset;
int i, len;
GList *list;
g_assert (*offset == ftell (cache));
node->offset = *offset;
get_single_node_size (node, &node_size, &image_data_size);
g_assert (node_size % 4 == 0);
g_assert (image_data_size % 4 == 0);
image_data_offset = *offset + node_size;
next_offset = *offset + node_size + image_data_size;
/* Chain offset */
if (node->next != NULL)
{
if (!write_card32 (cache, next_offset))
return FALSE;
}
else
{
if (!write_card32 (cache, 0xffffffff))
return FALSE;
}
name_size = 0;
name_offset = find_string (node->name);
if (name_offset <= 0)
{
name_offset = *offset + 12;
name_size = ALIGN_VALUE (strlen (node->name) + 1, 4);
add_string (node->name, name_offset);
}
if (!write_card32 (cache, name_offset))
return FALSE;
image_list_offset = *offset + 12 + name_size;
if (!write_card32 (cache, image_list_offset))
return FALSE;
/* Icon name */
if (name_size > 0)
{
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;
data_offset = image_data_offset;
for (i = 0; i < len; i++)
{
Image *image = list->data;
int image_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_size > 0)
{
if (!write_card32 (cache, data_offset))
return FALSE;
data_offset += image_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 (get_image_data_size (image) == 0)
continue;
/* Pixel data */
if (pixel_data_size > 0)
{
image->image_data->offset = image_data_offset + 8;
if (!write_card32 (cache, image->image_data->offset))
return FALSE;
}
else
{
if (!write_card32 (cache, (guint32) (image->image_data ? image->image_data->offset : 0)))
return FALSE;
}
if (meta_data_size > 0)
{
image->icon_data->offset = image_data_offset + pixel_data_size + 8;
if (!write_card32 (cache, image->icon_data->offset))
return FALSE;
}
else
{
if (!write_card32 (cache, image->icon_data ? image->icon_data->offset : 0))
return FALSE;
}
if (pixel_data_size > 0)
{
if (!write_image_data (cache, image->image_data, image->image_data->offset))
return FALSE;
}
if (meta_data_size > 0)
{
if (!write_icon_data (cache, image->icon_data, image->icon_data->offset))
return FALSE;
}
image_data_offset += pixel_data_size + meta_data_size + 8;
}
*offset = next_offset;
node = node->next;
}
return TRUE;
}
static 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;
offset += 4;
node_offset = offset + context->size * 4;
/* Just write zeros here, we will rewrite this later */
for (i = 0; i < context->size; i++)
{
if (!write_card32 (cache, 0))
return FALSE;
}
/* Now write the buckets */
for (i = 0; i < context->size; i++)
{
if (!context->nodes[i])
continue;
g_assert (node_offset % 4 == 0);
if (!write_bucket (cache, context->nodes[i], &node_offset))
return FALSE;
}
*new_offset = node_offset;
/* Now write out the bucket offsets */
fseek (cache, offset, SEEK_SET);
for (i = 0; i < context->size; i++)
{
if (context->nodes[i] != NULL)
node_offset = context->nodes[i]->offset;
else
node_offset = 0xffffffff;
if (!write_card32 (cache, node_offset))
return FALSE;
}
fseek (cache, 0, SEEK_END);
return TRUE;
}
static gboolean
write_dir_index (FILE *cache, int offset, GList *directories)
{
int n_dirs;
GList *d;
char *dir;
int tmp, tmp2;
n_dirs = g_list_length (directories);
if (!write_card32 (cache, n_dirs))
return FALSE;
offset += 4 + n_dirs * 4;
tmp = offset;
for (d = directories; d; d = d->next)
{
dir = d->data;
tmp2 = find_string (dir);
if (tmp2 == 0 || tmp2 == -1)
{
tmp2 = tmp;
tmp += ALIGN_VALUE (strlen (dir) + 1, 4);
/* We're playing a little game with negative
* offsets here to handle duplicate strings in
* the array, even though that should not
* really happen for the directory index.
*/
add_string (dir, -tmp2);
}
else if (tmp2 < 0)
{
tmp2 = -tmp2;
}
if (!write_card32 (cache, tmp2))
return FALSE;
}
g_assert (offset == ftell (cache));
for (d = directories; d; d = d->next)
{
dir = d->data;
tmp2 = find_string (dir);
g_assert (tmp2 != 0 && tmp2 != -1);
if (tmp2 < 0)
{
tmp2 = -tmp2;
g_assert (tmp2 == ftell (cache));
add_string (dir, tmp2);
if (!write_string (cache, dir))
return FALSE;
}
}
return TRUE;
}
static 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 folder index\n"));
return FALSE;
}
rewind (cache);
if (!write_header (cache, new_offset))
{
g_printerr (_("Failed to rewrite header\n"));
return FALSE;
}
return TRUE;
}
static gboolean
validate_file (const char *file)
{
GMappedFile *map;
CacheInfo info;
map = g_mapped_file_new (file, FALSE, NULL);
if (!map)
return FALSE;
info.cache = g_mapped_file_get_contents (map);
info.cache_size = g_mapped_file_get_length (map);
info.n_directories = 0;
info.flags = CHECK_OFFSETS|CHECK_STRINGS|CHECK_PIXBUFS;
if (!gtk_icon_cache_validate (&info))
{
g_mapped_file_unref (map);
return FALSE;
}
g_mapped_file_unref (map);
return TRUE;
}
/**
* safe_fclose:
* @f: A FILE* stream, must have underlying fd
*
* Unix defaults for data preservation after system crash
* are unspecified, and many systems will eat your data
* in this situation unless you explicitly fsync().
*
* Returns: %TRUE on success, %FALSE on failure, and will set errno()
*/
static gboolean
safe_fclose (FILE *f)
{
int fd = fileno (f);
g_assert (fd >= 0);
if (fflush (f) == EOF)
return FALSE;
#ifndef G_OS_WIN32
if (fsync (fd) < 0)
return FALSE;
#endif
if (fclose (f) == EOF)
return FALSE;
return TRUE;
}
static void
build_cache (const char *path)
{
char *cache_path, *tmp_cache_path;
#ifdef G_OS_WIN32
char *bak_cache_path = NULL;
#endif
GHashTable *files;
FILE *cache;
GStatBuf path_stat, cache_stat;
struct utimbuf utime_buf;
GList *directories = NULL;
int fd;
int retry_count = 0;
#ifndef G_OS_WIN32
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
#else
int mode = _S_IWRITE | _S_IREAD;
#endif
#ifndef _O_BINARY
#define _O_BINARY 0
#endif
tmp_cache_path = g_build_filename (path, "."CACHE_NAME, NULL);
cache_path = g_build_filename (path, CACHE_NAME, NULL);
opentmp:
if ((fd = g_open (tmp_cache_path, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC | _O_BINARY, mode)) == -1)
{
if (retry_count == 0)
{
retry_count++;
g_remove (tmp_cache_path);
goto opentmp;
}
g_printerr (_("Failed to open file %s : %s\n"), tmp_cache_path, g_strerror (errno));
exit (1);
}
cache = fdopen (fd, "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);
image_data_hash = g_hash_table_new (g_str_hash, g_str_equal);
icon_data_hash = g_hash_table_new (g_str_hash, g_str_equal);
string_pool = 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);
g_unlink (cache_path);
exit (0);
}
/* FIXME: Handle failure */
if (!write_file (cache, files, directories))
{
g_unlink (tmp_cache_path);
exit (1);
}
if (!safe_fclose (cache))
{
g_printerr (_("Failed to write cache file: %s\n"), g_strerror (errno));
g_unlink (tmp_cache_path);
exit (1);
}
cache = NULL;
g_list_free_full (directories, g_free);
if (!validate_file (tmp_cache_path))
{
g_printerr (_("The generated cache was invalid.\n"));
/*g_unlink (tmp_cache_path);*/
exit (1);
}
#ifdef G_OS_WIN32
if (g_file_test (cache_path, G_FILE_TEST_EXISTS))
{
bak_cache_path = g_strconcat (cache_path, ".bak", NULL);
g_unlink (bak_cache_path);
if (g_rename (cache_path, bak_cache_path) == -1)
{
int errsv = errno;
g_printerr (_("Could not rename %s to %s: %s, removing %s then.\n"),
cache_path, bak_cache_path,
g_strerror (errsv),
cache_path);
g_unlink (cache_path);
bak_cache_path = NULL;
}
}
#endif
if (g_rename (tmp_cache_path, cache_path) == -1)
{
int errsv = errno;
g_printerr (_("Could not rename %s to %s: %s\n"),
tmp_cache_path, cache_path,
g_strerror (errsv));
g_unlink (tmp_cache_path);
#ifdef G_OS_WIN32
if (bak_cache_path != NULL)
if (g_rename (bak_cache_path, cache_path) == -1)
{
errsv = errno;
g_printerr (_("Could not rename %s back to %s: %s.\n"),
bak_cache_path, cache_path,
g_strerror (errsv));
}
#endif
exit (1);
}
#ifdef G_OS_WIN32
if (bak_cache_path != NULL)
g_unlink (bak_cache_path);
#endif
/* Update time */
/* FIXME: What do do if an error occurs here? */
if (g_stat (path, &path_stat) < 0 ||
g_stat (cache_path, &cache_stat))
exit (1);
utime_buf.actime = path_stat.st_atime;
utime_buf.modtime = cache_stat.st_mtime;
g_utime (path, &utime_buf);
if (!quiet)
g_printerr (_("Cache file created successfully.\n"));
}
static void
write_csource (const char *path)
{
char *cache_path;
char *data;
gsize len;
int i;
cache_path = g_build_filename (path, CACHE_NAME, NULL);
if (!g_file_get_contents (cache_path, &data, &len, NULL))
exit (1);
g_printf ("#ifdef __SUNPRO_C\n");
g_printf ("#pragma align 4 (%s)\n", var_name);
g_printf ("#endif\n");
g_printf ("#ifdef __GNUC__\n");
g_printf ("static const guint8 %s[] __attribute__ ((__aligned__ (4))) = \n", var_name);
g_printf ("#else\n");
g_printf ("static const guint8 %s[] = \n", var_name);
g_printf ("#endif\n");
g_printf ("{\n");
for (i = 0; i < len - 1; i++)
{
if (i %12 == 0)
g_printf (" ");
g_printf ("0x%02x, ", (guint8)data[i]);
if (i % 12 == 11)
g_printf ("\n");
}
g_printf ("0x%02x\n};\n", (guint8)data[i]);
}
static GOptionEntry args[] = {
{ "force", 'f', 0, G_OPTION_ARG_NONE, &force_update, N_("Overwrite an existing cache, even if up to date"), NULL },
{ "ignore-theme-index", 't', 0, G_OPTION_ARG_NONE, &ignore_theme_index, N_("Dont check for the existence of index.theme"), NULL },
{ "index-only", 'i', 0, G_OPTION_ARG_NONE, &index_only, N_("Dont include image data in the cache"), NULL },
{ "include-image-data", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &index_only, N_("Include image data in the cache"), NULL },
{ "source", 'c', 0, G_OPTION_ARG_STRING, &var_name, N_("Output a C header file"), "NAME" },
{ "quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet, N_("Turn off verbose output"), NULL },
{ "validate", 'v', 0, G_OPTION_ARG_NONE, &validate, N_("Validate existing icon cache"), NULL },
{ NULL }
};
static void
printerr_handler (const char *string)
{
const char *charset;
fputs (g_get_prgname (), stderr);
fputs (": ", stderr);
if (g_get_charset (&charset))
fputs (string, stderr); /* charset is UTF-8 already */
else
{
char *result;
result = g_convert_with_fallback (string, -1, charset, "UTF-8", "?", NULL, NULL, NULL);
if (result)
{
fputs (result, stderr);
g_free (result);
}
fflush (stderr);
}
}
int
main (int argc, char **argv)
{
char *path;
GOptionContext *context;
if (argc < 2)
return 0;
g_set_printerr_handler (printerr_handler);
setlocale (LC_ALL, "");
bindtextdomain (GETTEXT_PACKAGE, GTK_LOCALEDIR);
#ifdef HAVE_BIND_TEXTDOMAIN_CODESET
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
#endif
context = g_option_context_new ("ICONPATH");
g_option_context_add_main_entries (context, args, GETTEXT_PACKAGE);
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 (validate)
{
char *file = g_build_filename (path, CACHE_NAME, NULL);
if (!g_file_test (file, G_FILE_TEST_IS_REGULAR))
{
if (!quiet)
g_printerr (_("File not found: %s\n"), file);
exit (1);
}
if (!validate_file (file))
{
if (!quiet)
g_printerr (_("Not a valid icon cache: %s\n"), file);
exit (1);
}
else
{
exit (0);
}
}
if (!ignore_theme_index && !has_theme_index (path))
{
if (path)
{
g_printerr (_("No theme index file.\n"));
}
else
{
g_printerr (_("No theme index file in “%s”.\n"
"If you really want to create an icon cache here, use --ignore-theme-index.\n"), path);
}
return 1;
}
if (!force_update && is_cache_up_to_date (path))
return 0;
replace_backslashes_with_slashes (path);
build_cache (path);
if (strcmp (var_name, "-") != 0)
write_csource (path);
return 0;
}