gtk2/gtk/updateiconcache.c

1776 lines
39 KiB
C
Raw Normal View History

/* 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
2012-02-27 13:01:10 +00:00
* 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 "gtkiconcachevalidator.h"
static gboolean force_update = FALSE;
static gboolean ignore_theme_index = FALSE;
static gboolean quiet = FALSE;
updateiconcache: Don't include image data by default anymore Since large images are in the icon cache, and apps don't tend to use that many icons anymore, simply don't include image data and instead make apps load files from disk. Additionally, since they're stored in GdkPixbuf data, that means that we have to first convert them either to a cairo_surface_t, which requires converting pixel data to be premulitplied, or an OpenGL texture, which requires a whole GPU upload anyway. So, even with the icon cache, the goal of icons through zero-copy, mmap()'d data from disk just isn't doable with the icon cache format we have. The icon cache on my disk is nearing 100MB, since we include a bunch of high-resolution application icons, that I doubt would be used by apps at all. Removing this inefficient pixel data makes memory usage for all applications go down, with no speed loss. The icon cache also, however, has an index of what icons are in each folder, which prevents a readdir() and allows GTK+ to know what icon is where without having to do a bunch of stat(); calls. Keeping this data is good for GTK+, so we should still keep the index. It doesn't make sense to remove any code for mapping pixel data from the icon cache. There's a plan in the works to have a symbolic icon cache that does pixel math on 16x16 icons to prevent slow SVG rendering. 16x16 pixels are fairly small, and such images are flat colors, which should compress easily, so the icon cache would be worthwhile here. So let's keep the code around in preparation for that case. https://bugzilla.gnome.org/show_bug.cgi?id=721895
2014-01-10 17:14:41 +00:00
static gboolean index_only = TRUE;
static gboolean validate = FALSE;
static gchar *var_name = "-";
/* Quite ugly - if we just add the c file to the
* list of sources in Makefile.am, libtool complains.
*/
#include "gtkiconcachevalidator.c"
#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;
2014-12-12 21:50:19 +00:00
static int check_dir_mtime (const char *dir,
const GStatBuf *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 gchar *path)
{
gchar *cache_path;
gint retval;
cache_path = g_build_filename (path, CACHE_NAME, NULL);
retval = g_stat (cache_path, &cache_dir_stat);
g_free (cache_path);
2014-12-12 21:50:19 +00:00
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 gchar *path)
{
GStatBuf path_stat, cache_stat;
gchar *cache_path;
2014-12-12 21:50:19 +00:00
int retval;
retval = g_stat (path, &path_stat);
2014-12-12 21:50:19 +00:00
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);
2014-12-12 21:50:19 +00:00
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 gchar *path)
{
gboolean result;
gchar *index_path;
index_path = g_build_filename (path, "index.theme", NULL);
result = g_file_test (index_path, G_FILE_TEST_IS_REGULAR);
2014-12-12 21:50:19 +00:00
g_free (index_path);
return result;
}
2014-12-12 21:50:19 +00:00
typedef struct
{
GdkPixdata pixdata;
gboolean has_pixdata;
guint32 offset;
guint size;
} ImageData;
2014-12-12 21:50:19 +00:00
typedef struct
{
int has_embedded_rect;
int x0, y0, x1, y1;
2014-12-12 21:50:19 +00:00
int n_attach_points;
int *attach_points;
2014-12-12 21:50:19 +00:00
int n_display_names;
char **display_names;
guint32 offset;
gint 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;
2014-12-12 21:50:19 +00:00
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;
2014-12-12 21:50:19 +00:00
list = g_list_prepend (list, value);
g_hash_table_insert (files, key, list);
2014-12-12 21:50:19 +00:00
if (free_key)
g_free (key);
2014-12-12 21:50:19 +00:00
return TRUE;
}
static IconData *
load_icon_data (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;
IconData *data;
2014-12-12 21:50:19 +00:00
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);
2014-12-12 21:50:19 +00:00
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];
}
2014-12-12 21:50:19 +00:00
g_free (ivalues);
}
2014-12-12 21:50:19 +00:00
str = g_key_file_get_string (icon_file, "Icon Data", "AttachPoints", NULL);
if (str)
{
split = g_strsplit (str, "|", -1);
2014-12-12 21:50:19 +00:00
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);
}
}
2014-12-12 21:50:19 +00:00
g_strfreev (split);
g_free (str);
}
2014-12-12 21:50:19 +00:00
keys = g_key_file_get_keys (icon_file, "Icon Data", &n_keys, &error);
2014-12-12 21:50:19 +00:00
data->display_names = g_new0 (gchar *, 2 * n_keys + 1);
data->n_display_names = 0;
2014-12-12 21:50:19 +00:00
for (i = 0; i < n_keys; i++)
{
gchar *lang, *name;
2014-12-12 21:50:19 +00:00
if (g_str_has_prefix (keys[i], "DisplayName"))
{
gchar *open, *close = NULL;
2014-12-12 21:50:19 +00:00
open = strchr (keys[i], '[');
if (open)
close = strchr (open, ']');
if (open && close)
{
lang = g_strndup (open + 1, close - open - 1);
2014-12-12 21:50:19 +00:00
name = g_key_file_get_locale_string (icon_file,
"Icon Data", "DisplayName",
lang, NULL);
}
else
{
lang = g_strdup ("C");
2014-12-12 21:50:19 +00:00
name = g_key_file_get_string (icon_file,
"Icon Data", "DisplayName",
NULL);
}
2014-12-12 21:50:19 +00:00
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);
2014-12-12 21:50:19 +00:00
g_key_file_free (icon_file);
/* -1 means not computed yet, the real value depends
2014-12-12 21:50:19 +00:00
* 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 (gchar *filename)
{
gchar *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 gchar *
follow_links (const gchar *path)
{
gchar *target;
gchar *d, *s;
gchar *path2 = NULL;
path2 = g_strdup (path);
while (g_file_test (path2, G_FILE_TEST_IS_SYMLINK))
{
target = g_file_read_link (path2, NULL);
2014-12-12 21:50:19 +00:00
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
2014-12-12 21:50:19 +00:00
maybe_cache_image_data (Image *image,
const gchar *path)
{
2014-12-12 21:50:19 +00:00
if (!index_only && !image->image_data &&
(g_str_has_suffix (path, ".png") || g_str_has_suffix (path, ".xpm")))
{
GdkPixbuf *pixbuf;
ImageData *idata;
gchar *path2;
idata = g_hash_table_lookup (image_data_hash, path);
path2 = follow_links (path);
if (path2)
{
ImageData *idata2;
canonicalize_filename (path2);
2014-12-12 21:50:19 +00:00
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;
}
}
2014-12-12 21:50:19 +00:00
if (!idata)
{
idata = g_new0 (ImageData, 1);
g_hash_table_insert (image_data_hash, g_strdup (path), idata);
if (path2)
2014-12-12 21:50:19 +00:00
g_hash_table_insert (image_data_hash, g_strdup (path2), idata);
}
if (!idata->has_pixdata)
{
pixbuf = gdk_pixbuf_new_from_file (path, NULL);
2014-12-12 21:50:19 +00:00
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 gchar *path)
{
if (g_str_has_suffix (path, ".icon"))
{
IconData *idata = NULL;
gchar *path2 = NULL;
idata = g_hash_table_lookup (icon_data_hash, path);
path2 = follow_links (path);
if (path2)
{
IconData *idata2;
canonicalize_filename (path2);
2014-12-12 21:50:19 +00:00
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;
}
}
2014-12-12 21:50:19 +00:00
if (!idata)
{
idata = load_icon_data (path);
g_hash_table_insert (icon_data_hash, g_strdup (path), idata);
if (path2)
2014-12-12 21:50:19 +00:00
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 (gchar *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 *
2014-12-12 21:50:19 +00:00
scan_directory (const gchar *base_path,
const gchar *subdir,
GHashTable *files,
GList *directories,
gint depth)
{
GHashTable *dir_hash;
GDir *dir;
GList *list = NULL, *iterator = NULL;
const gchar *name;
gchar *dir_path;
gboolean dir_added = FALSE;
guint dir_index = 0xffff;
2014-12-12 21:50:19 +00:00
dir_path = g_build_path ("/", base_path, subdir, NULL);
/* FIXME: Use the gerror */
dir = g_dir_open (dir_path, 0, NULL);
2014-12-12 21:50:19 +00:00
if (!dir)
return directories;
2014-12-12 21:50:19 +00:00
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)
{
gchar *path;
gboolean retval;
int flags = 0;
Image *image;
gchar *basename, *dot;
name = iterator->data;
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_path ("/", subdir, name, NULL);
else
subsubdir = g_strdup (name);
2014-12-12 21:50:19 +00:00
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;
2014-12-12 21:50:19 +00:00
if (flags == 0)
continue;
2014-12-12 21:50:19 +00:00
basename = g_strdup (name);
dot = strrchr (basename, '.');
*dot = '\0';
2014-12-12 21:50:19 +00:00
image = g_hash_table_lookup (dir_hash, basename);
if (!image)
{
2014-12-12 21:50:19 +00:00
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;
}
2014-12-12 21:50:19 +00:00
image = g_new0 (Image, 1);
image->dir_index = dir_index;
g_hash_table_insert (dir_hash, g_strdup (basename), image);
}
image->flags |= flags;
2014-12-12 21:50:19 +00:00
maybe_cache_image_data (image, path);
maybe_cache_icon_data (image, path);
2014-12-12 21:50:19 +00:00
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);
2014-12-12 21:50:19 +00:00
g_hash_table_destroy (dir_hash);
return directories;
}
typedef struct _HashNode HashNode;
struct _HashNode
{
HashNode *next;
gchar *name;
GList *image_list;
gint 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 {
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;
2014-12-12 21:50:19 +00:00
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;
2014-12-12 21:50:19 +00:00
return TRUE;
}
static GHashTable *string_pool = NULL;
2014-12-12 21:50:19 +00:00
static int
find_string (const gchar *n)
{
return GPOINTER_TO_INT (g_hash_table_lookup (string_pool, n));
}
static void
add_string (const gchar *n, int offset)
{
g_hash_table_insert (string_pool, (gpointer) n, GINT_TO_POINTER (offset));
}
static gboolean
write_string (FILE *cache, const gchar *n)
{
gchar *s;
int i, l;
2014-12-12 21:50:19 +00:00
l = ALIGN_VALUE (strlen (n) + 1, 4);
2014-12-12 21:50:19 +00:00
s = g_malloc0 (l);
strcpy (s, n);
i = fwrite (s, l, 1, cache);
g_free (s);
return i == 1;
2014-12-12 21:50:19 +00:00
}
static gboolean
write_card16 (FILE *cache, guint16 n)
{
int i;
n = GUINT16_TO_BE (n);
2014-12-12 21:50:19 +00:00
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);
2014-12-12 21:50:19 +00:00
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;
gint 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);
2014-12-12 21:50:19 +00:00
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;
2014-12-12 21:50:19 +00:00
ofs += 8;
2014-12-12 21:50:19 +00:00
}
else
{
if (!write_card32 (cache, 0))
return FALSE;
}
2014-12-12 21:50:19 +00:00
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;
2014-12-12 21:50:19 +00:00
for (j = 0; j < 2 * icon_data->n_attach_points; j++)
{
if (!write_card16 (cache, icon_data->attach_points[j]))
return FALSE;
2014-12-12 21:50:19 +00:00
}
}
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
2014-12-12 21:50:19 +00:00
* 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;
}
}
2014-12-12 21:50:19 +00:00
}
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 gint
get_image_meta_data_size (Image *image)
{
gint 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
2014-12-12 21:50:19 +00:00
* 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++)
2014-12-12 21:50:19 +00:00
{
int poolv;
if ((poolv = 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);
}
}
2014-12-12 21:50:19 +00:00
}
image->icon_data_size = data->size;
data->size = 0;
}
}
g_assert (image->icon_data_size % 4 == 0);
return image->icon_data_size;
}
static gint
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
2014-12-12 21:50:19 +00:00
* 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)
{
2014-12-12 21:50:19 +00:00
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 gint
get_image_data_size (Image *image)
{
gint len;
2014-12-12 21:50:19 +00:00
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
2014-12-12 21:50:19 +00:00
* 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;
2014-12-12 21:50:19 +00:00
/* 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;
2014-12-12 21:50:19 +00:00
/* 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;
2014-12-12 21:50:19 +00:00
tmp2 = find_string (dir);
2014-12-12 21:50:19 +00:00
if (tmp2 == 0 || tmp2 == -1)
{
tmp2 = tmp;
tmp += ALIGN_VALUE (strlen (dir) + 1, 4);
/* We're playing a little game with negative
2014-12-12 21:50:19 +00:00
* 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));
2014-12-12 21:50:19 +00:00
add_string (dir, tmp2);
if (!write_string (cache, dir))
return FALSE;
}
}
2014-12-12 21:50:19 +00:00
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);
2014-12-12 21:50:19 +00:00
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;
}
2014-12-12 21:50:19 +00:00
rewind (cache);
if (!write_header (cache, new_offset))
{
g_printerr (_("Failed to rewrite header\n"));
return FALSE;
}
2014-12-12 21:50:19 +00:00
return TRUE;
}
static gboolean
validate_file (const gchar *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;
2014-12-12 21:50:19 +00:00
if (!_gtk_icon_cache_validate (&info))
{
g_mapped_file_unref (map);
return FALSE;
}
2014-12-12 21:50:19 +00:00
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;
2010-11-24 12:36:18 +00:00
#ifndef G_OS_WIN32
if (fsync (fd) < 0)
return FALSE;
2010-11-24 12:36:18 +00:00
#endif
if (fclose (f) == EOF)
return FALSE;
return TRUE;
}
static void
build_cache (const gchar *path)
{
gchar *cache_path, *tmp_cache_path;
#ifdef G_OS_WIN32
gchar *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);
}
2014-12-12 21:50:19 +00:00
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);
2014-12-12 21:50:19 +00:00
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);
}
2014-12-12 21:50:19 +00:00
/* 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;
#if GLIB_CHECK_VERSION (2, 17, 1)
g_utime (path, &utime_buf);
#else
utime (path, &utime_buf);
#endif
if (!quiet)
g_printerr (_("Cache file created successfully.\n"));
}
static void
write_csource (const gchar *path)
{
gchar *cache_path;
gchar *data;
gsize len;
gint i;
cache_path = g_build_filename (path, CACHE_NAME, NULL);
if (!g_file_get_contents (cache_path, &data, &len, NULL))
exit (1);
2014-12-12 21:50:19 +00:00
g_printf ("#ifdef __SUNPRO_C\n");
2014-12-12 21:50:19 +00:00
g_printf ("#pragma align 4 (%s)\n", var_name);
g_printf ("#endif\n");
2014-12-12 21:50:19 +00:00
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");
}
2014-12-12 21:50:19 +00:00
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_("Don't check for the existence of index.theme"), NULL },
{ "index-only", 'i', 0, G_OPTION_ARG_NONE, &index_only, N_("Don't 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 gchar *string)
{
const gchar *charset;
fputs (g_get_prgname (), stderr);
fputs (": ", stderr);
if (g_get_charset (&charset))
fputs (string, stderr); /* charset is UTF-8 already */
else
{
gchar *result;
result = g_convert_with_fallback (string, -1, charset, "UTF-8", "?", NULL, NULL, NULL);
2014-12-12 21:50:19 +00:00
if (result)
{
fputs (result, stderr);
g_free (result);
}
2014-12-12 21:50:19 +00:00
fflush (stderr);
}
}
int
main (int argc, char **argv)
{
gchar *path;
GOptionContext *context;
if (argc < 2)
return 0;
g_set_printerr_handler (printerr_handler);
2014-12-12 21:50:19 +00:00
setlocale (LC_ALL, "");
#ifdef ENABLE_NLS
bindtextdomain (GETTEXT_PACKAGE, GTK_LOCALEDIR);
#ifdef HAVE_BIND_TEXTDOMAIN_CODESET
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
#endif
#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);
2014-12-12 21:50:19 +00:00
path = argv[1];
#ifdef G_OS_WIN32
path = g_locale_to_utf8 (path, -1, NULL, NULL, NULL);
#endif
2014-12-12 21:50:19 +00:00
if (validate)
{
gchar *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);
}
2014-12-12 21:50:19 +00:00
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;
}
2014-12-12 21:50:19 +00:00
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;
}