forked from AuroraMiddleware/gtk
512a33fee1
n_attach_points is the result of g_strv_length(): the index at which the string vector ends in NULL. So by definition, when i == n_attach_points, string[i] == NULL, and there is no need to check for the latter. The fact that we did appears to confuse static analysers, as the dereference and index check were inverted from what would normally be safe. We could reverse them, but we may as well just remove the unnecessary NULL check. https://bugzilla.gnome.org/show_bug.cgi?id=788458
1768 lines
39 KiB
C
1768 lines
39 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, 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;
|
|
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;
|
|
|
|
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);
|
|
|
|
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;
|
|
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 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);
|
|
|
|
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;
|
|
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;
|
|
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;
|
|
gint *ivalues;
|
|
GError *error = NULL;
|
|
gchar **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 (gchar *, 2 * n_keys + 1);
|
|
data->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);
|
|
}
|
|
|
|
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 (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);
|
|
|
|
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 gchar *path)
|
|
{
|
|
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);
|
|
|
|
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 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);
|
|
|
|
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 (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 *
|
|
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_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)))
|
|
{
|
|
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_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_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;
|
|
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;
|
|
|
|
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 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;
|
|
|
|
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;
|
|
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);
|
|
|
|
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 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
|
|
* 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++)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
* 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 gint
|
|
get_image_data_size (Image *image)
|
|
{
|
|
gint 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 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;
|
|
|
|
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 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);
|
|
}
|
|
|
|
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;
|
|
#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);
|
|
|
|
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_("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);
|
|
|
|
if (result)
|
|
{
|
|
fputs (result, stderr);
|
|
g_free (result);
|
|
}
|
|
|
|
fflush (stderr);
|
|
}
|
|
}
|
|
|
|
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
gchar *path;
|
|
GOptionContext *context;
|
|
|
|
if (argc < 2)
|
|
return 0;
|
|
|
|
g_set_printerr_handler (printerr_handler);
|
|
|
|
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);
|
|
|
|
path = argv[1];
|
|
#ifdef G_OS_WIN32
|
|
path = g_locale_to_utf8 (path, -1, NULL, NULL, NULL);
|
|
#endif
|
|
|
|
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);
|
|
}
|
|
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;
|
|
}
|