| /* 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_("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 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; |
| } |