X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fupdateiconcache.c;h=effea7c6c605611f497d77b8ec83f515001ab861;hb=d484721b5ca9e82d6422cca8a3a40f001208f87b;hp=cdf6afa5bcb4a17ac44e21ee0cf5c7504147fbf3;hpb=673f0a86b96eeacfc12ed4e1cd5d87e9a7a54983;p=~andy%2Fgtk diff --git a/gtk/updateiconcache.c b/gtk/updateiconcache.c index cdf6afa5b..effea7c6c 100644 --- a/gtk/updateiconcache.c +++ b/gtk/updateiconcache.c @@ -12,25 +12,46 @@ * 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 "config.h" + +#include #include #include #include #include #include +#include +#ifdef HAVE_UNISTD_H #include +#endif #include +#ifdef _MSC_VER +#include +#include +#else #include +#endif #include #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" @@ -46,16 +67,62 @@ static gboolean quiet = FALSE; #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) +{ + gchar *cache_path; + 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) { - struct stat path_stat, cache_stat; + GStatBuf path_stat, cache_stat; gchar *cache_path; - int retval; + 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 */ @@ -66,22 +133,73 @@ is_cache_up_to_date (const gchar *path) retval = g_stat (cache_path, &cache_stat); g_free (cache_path); - if (retval < 0 && errno == ENOENT) + if (retval < 0) { /* Cache file not found */ return FALSE; } /* Check mtime */ - return cache_stat.st_mtime <= path_stat.st_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) { @@ -92,6 +210,7 @@ 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); @@ -111,7 +230,368 @@ foreach_remove_func (gpointer key, gpointer value, gpointer user_data) return TRUE; } -GList * +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); + + i = 0; + while (split[i] != NULL && i < data->n_attach_points) + { + 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); + } + i++; + } + + 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) + { + gdk_pixdata_from_pixbuf (&idata->pixdata, pixbuf, FALSE); + 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, @@ -125,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); @@ -144,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, @@ -160,9 +641,11 @@ 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); - g_free (path); - if (retval) { if (g_str_has_suffix (name, ".png")) @@ -182,9 +665,7 @@ scan_directory (const gchar *base_path, *dot = '\0'; image = g_hash_table_lookup (dir_hash, basename); - if (image) - image->flags |= flags; - else + if (!image) { if (!dir_added) { @@ -199,14 +680,19 @@ scan_directory (const gchar *base_path, } image = g_new0 (Image, 1); - image->flags = flags; 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); @@ -226,13 +712,14 @@ struct _HashNode HashNode *next; gchar *name; GList *image_list; + gint offset; }; static guint icon_name_hash (gconstpointer key) { - const char *p = key; - guint h = *p; + const signed char *p = key; + guint32 h = *p; if (h) for (p += 1; *p != '\0'; p++) @@ -268,7 +755,21 @@ convert_to_hash (gpointer key, gpointer value, gpointer user_data) return TRUE; } -gboolean +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; @@ -281,36 +782,179 @@ write_string (FILE *cache, const gchar *n) i = fwrite (s, l, 1, cache); + g_free (s); + return i == 1; } -gboolean +static gboolean write_card16 (FILE *cache, guint16 n) { int i; - gchar s[2]; - *((guint16 *)s) = GUINT16_TO_BE (n); + n = GUINT16_TO_BE (n); - i = fwrite (s, 2, 1, cache); + i = fwrite ((char *)&n, 2, 1, cache); return i == 1; } -gboolean +static gboolean write_card32 (FILE *cache, guint32 n) { int i; - gchar s[4]; - *((guint32 *)s) = GUINT32_TO_BE (n); + 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; + + s = gdk_pixdata_serialize (pixdata, &len); + + if (!write_card32 (cache, len)) + { + g_free (s); + return FALSE; + } + + i = fwrite (s, len, 1, cache); - i = fwrite (s, 4, 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) { @@ -320,105 +964,294 @@ write_header (FILE *cache, guint32 dir_list_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; +} -guint -get_single_node_size (HashNode *node) +static gint +get_image_pixel_data_size (Image *image) { - int len = 0; + /* 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; + } + } - /* Node pointers */ - len += 12; + g_assert (image->pixel_data_size % 4 == 0); - /* Name */ - len += ALIGN_VALUE (strlen (node->name) + 1, 4); + return image->pixel_data_size; +} - /* Image list */ - len += 4 + g_list_length (node->image_list) * 8; +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; } -guint -get_bucket_size (HashNode *node) +static void +get_single_node_size (HashNode *node, int *node_size, int *image_data_size) { - int len = 0; + GList *list; - while (node) - { - len += get_single_node_size (node); + /* Node pointers */ + *node_size = 12; - node = node->next; + /* Name */ + if (find_string (node->name) == 0) + { + *node_size += ALIGN_VALUE (strlen (node->name) + 1, 4); + add_string (node->name, -1); } - return len; + /* 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); + } } -gboolean +static gboolean write_bucket (FILE *cache, HashNode *node, int *offset) { while (node != NULL) { - int next_offset = *offset + get_single_node_size (node); + 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; - } + { + 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 */ - if (!write_card32 (cache, *offset + 12 + ALIGN_VALUE (strlen (node->name) + 1, 4))) - 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; - + return FALSE; + list = node->image_list; + data_offset = image_data_offset; for (i = 0; i < len; i++) - { - Image *image = list->data; - - /* Directory index */ - if (!write_card16 (cache, image->dir_index)) - return FALSE; - - /* Flags */ - if (!write_card16 (cache, image->flags)) - return FALSE; - - /* Image data offset */ - 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) + { + 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; } -gboolean +static gboolean write_hash_table (FILE *cache, HashContext *context, int *new_offset) { int offset = HASH_OFFSET; @@ -428,50 +1261,54 @@ 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; } -gboolean +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); @@ -480,27 +1317,54 @@ 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; } -gboolean +static gboolean write_file (FILE *cache, GHashTable *files, GList *directories) { HashContext context; @@ -519,19 +1383,19 @@ write_file (FILE *cache, GHashTable *files, GList *directories) * back and change it later */ if (!write_header (cache, 0)) { - g_printerr ("Failed to write header\n"); + 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"); + g_printerr (_("Failed to write hash table\n")); return FALSE; } if (!write_dir_index (cache, new_offset, directories)) { - g_printerr ("Failed to write directory index\n"); + g_printerr (_("Failed to write folder index\n")); return FALSE; } @@ -539,35 +1403,117 @@ write_file (FILE *cache, GHashTable *files, GList *directories) if (!write_header (cache, new_offset)) { - g_printerr ("Failed to rewrite header\n"); + g_printerr (_("Failed to rewrite header\n")); return FALSE; } return TRUE; } -void +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; - 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"); - - if (!cache) + 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) { - g_printerr ("Failed to write cache file: %s\n", g_strerror (errno)); + 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); } - files = g_hash_table_new (g_str_hash, g_str_equal); + 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) @@ -576,49 +1522,168 @@ 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)) + { + 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? */ - g_stat (path, &path_stat); - g_stat (cache_path, &cache_stat); + 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"); + 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, "Overwrite an existing cache, even if uptodate", NULL }, - { "quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet, "Turn off verbose output", NULL }, + { "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 }, + { "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) { @@ -628,8 +1693,19 @@ 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, NULL); + g_option_context_add_main_entries (context, args, GETTEXT_PACKAGE); g_option_context_parse (context, &argc, &argv, NULL); @@ -638,10 +1714,51 @@ 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)) + { + 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; }