X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fupdateiconcache.c;h=effea7c6c605611f497d77b8ec83f515001ab861;hb=56bcb1933f6de613e5d8689e23420d47b65425c3;hp=5aec9a2f00b4642f3b946e0234f99eb382cd6671;hpb=7bf87cf25aced575b657bc4084e2475222669741;p=~andy%2Fgtk diff --git a/gtk/updateiconcache.c b/gtk/updateiconcache.c index 5aec9a2f0..effea7c6c 100644 --- a/gtk/updateiconcache.c +++ b/gtk/updateiconcache.c @@ -12,12 +12,10 @@ * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * License along with this library. If not, see . */ -#include +#include "config.h" #include #include @@ -25,11 +23,13 @@ #include #include #include +#include #ifdef HAVE_UNISTD_H #include #endif #include #ifdef _MSC_VER +#include #include #else #include @@ -39,13 +39,20 @@ #include #include #include +#include "gtkiconcachevalidator.h" static gboolean force_update = FALSE; static gboolean ignore_theme_index = FALSE; static gboolean quiet = FALSE; static gboolean index_only = FALSE; +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) @@ -53,8 +60,6 @@ static gchar *var_name = "-"; #define HAS_SUFFIX_PNG (1 << 2) #define HAS_ICON_FILE (1 << 3) -#define CAN_CACHE_IMAGE_DATA(flags) (!index_only && (((flags) & HAS_SUFFIX_PNG) || ((flags) & HAS_SUFFIX_XPM))) - #define MAJOR_VERSION 1 #define MINOR_VERSION 0 #define HASH_OFFSET 12 @@ -62,16 +67,62 @@ static gchar *var_name = "-"; #define ALIGN_VALUE(this, boundary) \ (( ((unsigned long)(this)) + (((unsigned long)(boundary)) -1)) & (~(((unsigned long)(boundary))-1))) +#ifdef HAVE_FTW_H + +#include + +static GStatBuf cache_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_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) { - struct stat path_stat, cache_stat; gchar *cache_path; - int retval; + gint retval; + + 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; + } + + 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) + if (retval < 0) { /* We can't stat the path, * assume we have a updated cache */ @@ -92,6 +143,8 @@ is_cache_up_to_date (const gchar *path) return cache_stat.st_mtime >= path_stat.st_mtime; } +#endif /* !HAVE_FTW_H */ + static gboolean has_theme_index (const gchar *path) { @@ -110,13 +163,29 @@ has_theme_index (const gchar *path) typedef struct { - gboolean has_pixdata; GdkPixdata pixdata; + gboolean has_pixdata; guint32 offset; - guint pixel_data_size; + 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 { @@ -126,16 +195,11 @@ typedef struct ImageData *image_data; guint pixel_data_size; - int has_embedded_rect; - int x0, y0, x1, y1; - - int n_attach_points; - int *attach_points; - - int n_display_names; - char **display_names; + IconData *icon_data; + guint icon_data_size; } Image; + static gboolean foreach_remove_func (gpointer key, gpointer value, gpointer user_data) { @@ -146,9 +210,8 @@ foreach_remove_func (gpointer key, gpointer value, gpointer user_data) if (image->flags == HAS_ICON_FILE) { + /* just a .icon file, throw away */ g_free (key); - g_free (image->attach_points); - g_strfreev (image->display_names); g_free (image); return TRUE; @@ -167,8 +230,8 @@ foreach_remove_func (gpointer key, gpointer value, gpointer user_data) return TRUE; } -static void -load_icon_data (Image *image, const char *path) +static IconData * +load_icon_data (const char *path) { GKeyFile *icon_file; char **split; @@ -180,6 +243,7 @@ load_icon_data (Image *image, const char *path) GError *error = NULL; gchar **keys; gsize n_keys; + IconData *data; icon_file = g_key_file_new (); g_key_file_set_list_separator (icon_file, ','); @@ -187,9 +251,13 @@ load_icon_data (Image *image, const char *path) if (error) { g_error_free (error); - return; + 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); @@ -197,11 +265,11 @@ load_icon_data (Image *image, const char *path) { if (length == 4) { - image->has_embedded_rect = TRUE; - image->x0 = ivalues[0]; - image->y0 = ivalues[1]; - image->x1 = ivalues[2]; - image->y1 = ivalues[3]; + data->has_embedded_rect = TRUE; + data->x0 = ivalues[0]; + data->y0 = ivalues[1]; + data->x1 = ivalues[2]; + data->y1 = ivalues[3]; } g_free (ivalues); @@ -212,19 +280,19 @@ load_icon_data (Image *image, const char *path) { split = g_strsplit (str, "|", -1); - image->n_attach_points = g_strv_length (split); - image->attach_points = g_new (int, 2 * image->n_attach_points); + data->n_attach_points = g_strv_length (split); + data->attach_points = g_new (int, 2 * data->n_attach_points); i = 0; - while (split[i] != NULL && i < image->n_attach_points) + while (split[i] != NULL && i < data->n_attach_points) { split_point = strchr (split[i], ','); if (split_point) { *split_point = 0; split_point++; - image->attach_points[2 * i] = atoi (split[i]); - image->attach_points[2 * i + 1] = atoi (split_point); + data->attach_points[2 * i] = atoi (split[i]); + data->attach_points[2 * i + 1] = atoi (split_point); } i++; } @@ -234,8 +302,8 @@ load_icon_data (Image *image, const char *path) } keys = g_key_file_get_keys (icon_file, "Icon Data", &n_keys, &error); - image->display_names = g_new0 (gchar *, 2 * n_keys + 1); - image->n_display_names = 0; + data->display_names = g_new0 (gchar *, 2 * n_keys + 1); + data->n_display_names = 0; for (i = 0; i < n_keys; i++) { @@ -265,15 +333,23 @@ load_icon_data (Image *image, const char *path) NULL); } - image->display_names[2 * image->n_display_names] = lang; - image->display_names[2 * image->n_display_names + 1] = name; - image->n_display_names++; + 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; } /* @@ -392,14 +468,14 @@ static void maybe_cache_image_data (Image *image, const gchar *path) { - if (CAN_CACHE_IMAGE_DATA(image->flags) && !image->image_data) + 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) @@ -411,7 +487,7 @@ maybe_cache_image_data (Image *image, 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"), + g_error ("different idatas found for symlinked '%s' and '%s'\n", path, path2); if (idata && !idata2) @@ -439,18 +515,82 @@ maybe_cache_image_data (Image *image, if (pixbuf) { gdk_pixdata_from_pixbuf (&idata->pixdata, pixbuf, FALSE); - idata->pixel_data_size = idata->pixdata.length + 8; + 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) - g_free (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, @@ -465,7 +605,7 @@ scan_directory (const gchar *base_path, gboolean dir_added = FALSE; guint dir_index = 0xffff; - dir_path = g_build_filename (base_path, subdir, NULL); + dir_path = g_build_path ("/", base_path, subdir, NULL); /* FIXME: Use the gerror */ dir = g_dir_open (dir_path, 0, NULL); @@ -484,13 +624,14 @@ scan_directory (const gchar *base_path, gchar *basename, *dot; path = g_build_filename (dir_path, name, NULL); + retval = g_file_test (path, G_FILE_TEST_IS_DIR); if (retval) { gchar *subsubdir; if (subdir) - subsubdir = g_build_filename (subdir, name, NULL); + subsubdir = g_build_path ("/", subdir, name, NULL); else subsubdir = g_strdup (name); directories = scan_directory (base_path, subsubdir, files, @@ -500,6 +641,10 @@ scan_directory (const gchar *base_path, continue; } + /* ignore images in the toplevel directory */ + if (subdir == NULL) + continue; + retval = g_file_test (path, G_FILE_TEST_IS_REGULAR); if (retval) { @@ -520,12 +665,7 @@ scan_directory (const gchar *base_path, *dot = '\0'; image = g_hash_table_lookup (dir_hash, basename); - if (image) - { - image->flags |= flags; - maybe_cache_image_data (image, path); - } - else + if (!image) { if (!dir_added) { @@ -540,16 +680,15 @@ scan_directory (const gchar *base_path, } image = g_new0 (Image, 1); - image->flags = flags; image->dir_index = dir_index; - maybe_cache_image_data (image, path); - g_hash_table_insert (dir_hash, g_strdup (basename), image); } - if (g_str_has_suffix (name, ".icon")) - load_icon_data (image, path); - + image->flags |= flags; + + maybe_cache_image_data (image, path); + maybe_cache_icon_data (image, path); + g_free (basename); } @@ -573,6 +712,7 @@ struct _HashNode HashNode *next; gchar *name; GList *image_list; + gint offset; }; static guint @@ -615,6 +755,20 @@ convert_to_hash (gpointer key, gpointer value, gpointer user_data) 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) { @@ -628,6 +782,8 @@ write_string (FILE *cache, const gchar *n) i = fwrite (s, l, 1, cache); + g_free (s); + return i == 1; } @@ -658,11 +814,12 @@ write_card32 (FILE *cache, guint32 n) static gboolean -write_pixdata (FILE *cache, GdkPixdata *pixdata) +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)) @@ -683,6 +840,121 @@ write_pixdata (FILE *cache, GdkPixdata *pixdata) 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) { @@ -692,105 +964,140 @@ write_header (FILE *cache, guint32 dir_list_offset) write_card32 (cache, dir_list_offset)); } -static guint +static gint get_image_meta_data_size (Image *image) { gint i; - guint len = 0; - - if (image->has_embedded_rect || - image->attach_points > 0 || - image->n_display_names > 0) - len += 12; - if (image->has_embedded_rect) - len += 8; - - if (image->n_attach_points > 0) - len += 4 + image->n_attach_points * 4; - - if (image->n_display_names > 0) + /* 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) { - len += 4 + 8 * image->n_display_names; - - for (i = 0; image->display_names[i]; i++) - len += ALIGN_VALUE (strlen (image->display_names[i]) + 1, 4); + 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; + } } - return len; + g_assert (image->icon_data_size % 4 == 0); + + return image->icon_data_size; } -static guint +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->pixel_data_size; - image->image_data->pixel_data_size = 0; + 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 guint +static gint get_image_data_size (Image *image) { - guint len; + gint len; len = 0; len += get_image_pixel_data_size (image); len += get_image_meta_data_size (image); - - if (len > 0 || (image->image_data && image->image_data->has_pixdata)) + + /* 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 guint -get_single_node_size (HashNode *node, gboolean include_image_data) +static void +get_single_node_size (HashNode *node, int *node_size, int *image_data_size) { - int len = 0; GList *list; /* Node pointers */ - len += 12; + *node_size = 12; /* Name */ - len += ALIGN_VALUE (strlen (node->name) + 1, 4); + if (find_string (node->name) == 0) + { + *node_size += ALIGN_VALUE (strlen (node->name) + 1, 4); + add_string (node->name, -1); + } /* Image list */ - len += 4 + g_list_length (node->image_list) * 8; + *node_size += 4 + g_list_length (node->image_list) * 8; /* Image data */ - if (include_image_data) - for (list = node->image_list; list; list = list->next) - { - Image *image = list->data; - - len += get_image_data_size (image); - } - - return len; -} - -static guint -get_bucket_size (HashNode *node) -{ - int len = 0; - while (node) + *image_data_size = 0; + for (list = node->image_list; list; list = list->next) { - len += get_single_node_size (node, TRUE); + Image *image = list->data; - node = node->next; + *image_data_size += get_image_data_size (image); } - - return len; } static gboolean @@ -798,218 +1105,149 @@ write_bucket (FILE *cache, HashNode *node, int *offset) { while (node != NULL) { - int next_offset = *offset + get_single_node_size (node, TRUE); - int image_data_offset = *offset + get_single_node_size (node, FALSE); + int node_size, image_data_size; + int next_offset, image_data_offset; int data_offset; - int tmp; - int i, j, len; + 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; - } + { + if (!write_card32 (cache, next_offset)) + return FALSE; + } else - { - if (!write_card32 (cache, 0xffffffff)) - return FALSE; - } - - /* Icon name offset */ - if (!write_card32 (cache, *offset + 12)) - return FALSE; - - /* Image list offset */ - tmp = *offset + 12 + ALIGN_VALUE (strlen (node->name) + 1, 4); - if (!write_card32 (cache, tmp)) - return FALSE; - + { + 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 (!write_string (cache, node->name)) - return FALSE; - + 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; - - /* Image data goes right after the image list */ - tmp += 4 + len * 8; + return FALSE; list = node->image_list; data_offset = image_data_offset; for (i = 0; i < len; i++) - { - Image *image = list->data; - int image_data_size = get_image_data_size (image); - - /* Directory index */ - if (!write_card16 (cache, image->dir_index)) - return FALSE; - - /* Flags */ - if (!write_card16 (cache, image->flags)) - return FALSE; - - /* Image data offset */ - if (image_data_size > 0) - { - if (!write_card32 (cache, data_offset)) - return FALSE; - data_offset += image_data_size; - } - else - { - if (!write_card32 (cache, 0)) - return FALSE; - } - - list = list->next; - } + { + Image *image = list->data; + int image_data_size = get_image_data_size (image); + + /* Directory index */ + if (!write_card16 (cache, image->dir_index)) + return FALSE; + + /* Flags */ + if (!write_card16 (cache, image->flags)) + return FALSE; + + /* Image data offset */ + if (image_data_size > 0) + { + if (!write_card32 (cache, data_offset)) + return FALSE; + data_offset += image_data_size; + } + else + { + if (!write_card32 (cache, 0)) + return FALSE; + } + + list = list->next; + } /* Now write the image data */ list = node->image_list; for (i = 0; i < len; i++, list = list->next) - { - Image *image = list->data; - int pixel_data_size = get_image_pixel_data_size (image); - int meta_data_size = get_image_meta_data_size (image); - - if (get_image_data_size (image) == 0) - continue; - - /* Pixel data */ - if (pixel_data_size > 0) - { - if (!write_card32 (cache, image_data_offset + 8)) - return FALSE; - - image->image_data->offset = image_data_offset + 8; - } - else - { - gint offset; - - if (image->image_data) - offset = image->image_data->offset; - else - offset = 0; - - if (!write_card32 (cache, offset)) - return FALSE; - } - - if (meta_data_size > 0) - { - if (!write_card32 (cache, image_data_offset + pixel_data_size + 8)) - return FALSE; - } - else - { - if (!write_card32 (cache, 0)) - return FALSE; - } - - if (pixel_data_size > 0) - { - if (!write_pixdata (cache, &image->image_data->pixdata)) - return FALSE; - } - - if (meta_data_size > 0) - { - int ofs = image_data_offset + pixel_data_size + 20; - - if (image->has_embedded_rect) - { - if (!write_card32 (cache, ofs)) - return FALSE; - - ofs += 8; - } - else - { - if (!write_card32 (cache, 0)) - return FALSE; - } - - if (image->n_attach_points > 0) - { - if (!write_card32 (cache, ofs)) - return FALSE; - - ofs += 4 + 4 * image->n_attach_points; - } - else - { - if (!write_card32 (cache, 0)) - return FALSE; - } - - if (image->n_display_names > 0) - { - if (!write_card32 (cache, ofs)) - return FALSE; - } - else - { - if (!write_card32 (cache, 0)) - return FALSE; - } - - if (image->has_embedded_rect) - { - if (!write_card16 (cache, image->x0) || - !write_card16 (cache, image->y0) || - !write_card16 (cache, image->x1) || - !write_card16 (cache, image->y1)) - return FALSE; - } - - if (image->n_attach_points > 0) - { - if (!write_card32 (cache, image->n_attach_points)) - return FALSE; - - for (j = 0; j < 2 * image->n_attach_points; j++) - { - if (!write_card16 (cache, image->attach_points[j])) - return FALSE; - } - } - - if (image->n_display_names > 0) - { - if (!write_card32 (cache, image->n_display_names)) - return FALSE; - - ofs += 4 + 8 * image->n_display_names; - - for (j = 0; j < 2 * image->n_display_names; j++) - { - if (!write_card32 (cache, ofs)) - return FALSE; - - ofs += ALIGN_VALUE (strlen (image->display_names[j]) + 1, 4); - } - - for (j = 0; j < 2 * image->n_display_names; j++) - { - if (!write_string (cache, image->display_names[j])) - return FALSE; - } - } - } + { + Image *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; + } - image_data_offset += pixel_data_size + meta_data_size + 8; - } - *offset = next_offset; node = node->next; } - + return TRUE; } @@ -1023,41 +1261,44 @@ write_hash_table (FILE *cache, HashContext *context, int *new_offset) if (!(write_card32 (cache, context->size))) return FALSE; - /* Size int + size * 4 */ - node_offset = offset + 4 + context->size * 4; - + 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 (context->nodes[i] != NULL) - { - if (!write_card32 (cache, node_offset)) - return FALSE; - - node_offset += get_bucket_size (context->nodes[i]); - } - else - { - if (!write_card32 (cache, 0xffffffff)) - { - return FALSE; - } - } + if (!write_card32 (cache, 0)) + return FALSE; } - *new_offset = node_offset; - /* Now write the buckets */ - node_offset = offset + 4 + context->size * 4; - for (i = 0; i < context->size; i++) { if (!context->nodes[i]) continue; + 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; } @@ -1067,6 +1308,7 @@ 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); @@ -1075,21 +1317,48 @@ write_dir_index (FILE *cache, int offset, GList *directories) offset += 4 + n_dirs * 4; + tmp = offset; for (d = directories; d; d = d->next) { dir = d->data; - if (!write_card32 (cache, offset)) + + 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; - - offset += ALIGN_VALUE (strlen (dir) + 1, 4); } + g_assert (offset == ftell (cache)); for (d = directories; d; d = d->next) { dir = d->data; - if (!write_string (cache, dir)) - return FALSE; + 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; @@ -1141,6 +1410,58 @@ write_file (FILE *cache, GHashTable *files, GList *directories) 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) { @@ -1149,24 +1470,50 @@ build_cache (const gchar *path) gchar *bak_cache_path = NULL; #endif GHashTable *files; - gboolean retval; FILE *cache; - struct stat path_stat, cache_stat; + 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 = g_fopen (tmp_cache_path, "wb"); - + 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 (force_update && 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) @@ -1175,23 +1522,33 @@ build_cache (const gchar *path) fclose (cache); g_unlink (tmp_cache_path); + g_unlink (cache_path); exit (0); } /* FIXME: Handle failure */ - retval = write_file (cache, files, directories); - fclose (cache); + if (!write_file (cache, files, directories)) + { + g_unlink (tmp_cache_path); + exit (1); + } - g_list_foreach (directories, (GFunc)g_free, NULL); - g_list_free (directories); - - if (!retval) + 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; - cache_path = g_build_filename (path, CACHE_NAME, 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)) @@ -1200,9 +1557,11 @@ build_cache (const gchar *path) 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 (errno), + g_strerror (errsv), cache_path); g_unlink (cache_path); bak_cache_path = NULL; @@ -1212,16 +1571,22 @@ build_cache (const gchar *path) 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 (errno)); + 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) - g_printerr (_("Could not rename %s back to %s: %s.\n"), - bak_cache_path, cache_path, - g_strerror (errno)); + { + errsv = errno; + + g_printerr (_("Could not rename %s back to %s: %s.\n"), + bak_cache_path, cache_path, + g_strerror (errsv)); + } #endif exit (1); } @@ -1238,8 +1603,12 @@ build_cache (const gchar *path) 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")); } @@ -1285,9 +1654,36 @@ static GOptionEntry args[] = { { "index-only", 'i', 0, G_OPTION_ARG_NONE, &index_only, N_("Don't 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) { @@ -1296,11 +1692,17 @@ main (int argc, char **argv) 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); @@ -1312,17 +1714,47 @@ main (int argc, char **argv) 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)) { - g_printerr (_("No theme index file in '%s'.\n" + 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; - g_type_init (); + replace_backslashes_with_slashes (path); build_cache (path); if (strcmp (var_name, "-") != 0)