 096b49013a
			
		
	
	096b49013a
	
	
	
		
			
			This is a followup to 0fd185fa6d. There is no
good reason to only try again if --force is passed. Do it
always.
See
https://bugzilla.redhat.com/show_bug.cgi?id=1194957
		
	
		
			
				
	
	
		
			1766 lines
		
	
	
		
			39 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1766 lines
		
	
	
		
			39 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* updateiconcache.c
 | |
|  * Copyright (C) 2004  Anders Carlsson <andersca@gnome.org>
 | |
|  *
 | |
|  * This library is free software; you can redistribute it and/or
 | |
|  * modify it under the terms of the GNU Library General Public
 | |
|  * License as published by the Free Software Foundation; either
 | |
|  * version 2 of the License, or (at your option) any later version.
 | |
|  *
 | |
|  * This library is distributed in the hope that it will be useful,
 | |
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | |
|  * Library General Public License for more details.
 | |
|  *
 | |
|  * You should have received a copy of the GNU Library General Public
 | |
|  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 | |
|  */
 | |
| 
 | |
| #include "config.h"
 | |
| 
 | |
| #include <locale.h>
 | |
| #include <stdlib.h>
 | |
| #include <stdio.h>
 | |
| #include <string.h>
 | |
| #include <sys/types.h>
 | |
| #include <sys/stat.h>
 | |
| #include <fcntl.h>
 | |
| #ifdef HAVE_UNISTD_H
 | |
| #include <unistd.h>
 | |
| #endif
 | |
| #include <errno.h>
 | |
| #ifdef _MSC_VER
 | |
| #include <io.h>
 | |
| #include <sys/utime.h>
 | |
| #else
 | |
| #include <utime.h>
 | |
| #endif
 | |
| 
 | |
| #include <glib.h>
 | |
| #include <glib/gstdio.h>
 | |
| #include <gdk-pixbuf/gdk-pixdata.h>
 | |
| #include <glib/gi18n.h>
 | |
| #include "gtkiconcachevalidator.h"
 | |
| 
 | |
| static gboolean force_update = FALSE;
 | |
| static gboolean ignore_theme_index = FALSE;
 | |
| static gboolean quiet = FALSE;
 | |
| static gboolean index_only = TRUE;
 | |
| static gboolean validate = FALSE;
 | |
| static gchar *var_name = "-";
 | |
| 
 | |
| /* Quite ugly - if we just add the c file to the
 | |
|  * list of sources in Makefile.am, libtool complains.
 | |
|  */
 | |
| #include "gtkiconcachevalidator.c"
 | |
| 
 | |
| #define CACHE_NAME "icon-theme.cache"
 | |
| 
 | |
| #define HAS_SUFFIX_XPM (1 << 0)
 | |
| #define HAS_SUFFIX_SVG (1 << 1)
 | |
| #define HAS_SUFFIX_PNG (1 << 2)
 | |
| #define HAS_ICON_FILE  (1 << 3)
 | |
| 
 | |
| #define MAJOR_VERSION 1
 | |
| #define MINOR_VERSION 0
 | |
| #define HASH_OFFSET 12
 | |
| 
 | |
| #define ALIGN_VALUE(this, boundary) \
 | |
|   (( ((unsigned long)(this)) + (((unsigned long)(boundary)) -1)) & (~(((unsigned long)(boundary))-1)))
 | |
| 
 | |
| #ifdef HAVE_FTW_H
 | |
| 
 | |
| #include <ftw.h>
 | |
| 
 | |
| static GStatBuf cache_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)
 | |
| {
 | |
|   GStatBuf path_stat, cache_stat;
 | |
|   gchar *cache_path;
 | |
|   int retval;
 | |
| 
 | |
|   retval = g_stat (path, &path_stat);
 | |
| 
 | |
|   if (retval < 0)
 | |
|     {
 | |
|       /* We can't stat the path,
 | |
|        * assume we have a updated cache */
 | |
|       return TRUE;
 | |
|     }
 | |
| 
 | |
|   cache_path = g_build_filename (path, CACHE_NAME, NULL);
 | |
|   retval = g_stat (cache_path, &cache_stat);
 | |
|   g_free (cache_path);
 | |
| 
 | |
|   if (retval < 0)
 | |
|     {
 | |
|       /* Cache file not found */
 | |
|       return FALSE;
 | |
|     }
 | |
| 
 | |
|   /* Check mtime */
 | |
|   return cache_stat.st_mtime >= path_stat.st_mtime;
 | |
| }
 | |
| 
 | |
| #endif  /* !HAVE_FTW_H */
 | |
| 
 | |
| static gboolean
 | |
| has_theme_index (const gchar *path)
 | |
| {
 | |
|   gboolean result;
 | |
|   gchar *index_path;
 | |
| 
 | |
|   index_path = g_build_filename (path, "index.theme", NULL);
 | |
| 
 | |
|   result = g_file_test (index_path, G_FILE_TEST_IS_REGULAR);
 | |
| 
 | |
|   g_free (index_path);
 | |
| 
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| 
 | |
| typedef struct
 | |
| {
 | |
|   GdkPixdata pixdata;
 | |
|   gboolean has_pixdata;
 | |
|   guint32 offset;
 | |
|   guint size;
 | |
| } ImageData;
 | |
| 
 | |
| typedef struct
 | |
| {
 | |
|   int has_embedded_rect;
 | |
|   int x0, y0, x1, y1;
 | |
| 
 | |
|   int n_attach_points;
 | |
|   int *attach_points;
 | |
| 
 | |
|   int n_display_names;
 | |
|   char **display_names;
 | |
| 
 | |
|   guint32 offset;
 | |
|   gint size;
 | |
| } IconData;
 | |
| 
 | |
| static GHashTable *image_data_hash = NULL;
 | |
| static GHashTable *icon_data_hash = NULL;
 | |
| 
 | |
| typedef struct
 | |
| {
 | |
|   int flags;
 | |
|   int dir_index;
 | |
| 
 | |
|   ImageData *image_data;
 | |
|   guint pixel_data_size;
 | |
| 
 | |
|   IconData *icon_data;
 | |
|   guint icon_data_size;
 | |
| } Image;
 | |
| 
 | |
| 
 | |
| static gboolean
 | |
| foreach_remove_func (gpointer key, gpointer value, gpointer user_data)
 | |
| {
 | |
|   Image *image = (Image *)value;
 | |
|   GHashTable *files = user_data;
 | |
|   GList *list;
 | |
|   gboolean free_key = FALSE;
 | |
| 
 | |
|   if (image->flags == HAS_ICON_FILE)
 | |
|     {
 | |
|       /* just a .icon file, throw away */
 | |
|       g_free (key);
 | |
|       g_free (image);
 | |
| 
 | |
|       return TRUE;
 | |
|     }
 | |
| 
 | |
|   list = g_hash_table_lookup (files, key);
 | |
|   if (list)
 | |
|     free_key = TRUE;
 | |
| 
 | |
|   list = g_list_prepend (list, value);
 | |
|   g_hash_table_insert (files, key, list);
 | |
| 
 | |
|   if (free_key)
 | |
|     g_free (key);
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static IconData *
 | |
| load_icon_data (const char *path)
 | |
| {
 | |
|   GKeyFile *icon_file;
 | |
|   char **split;
 | |
|   gsize length;
 | |
|   char *str;
 | |
|   char *split_point;
 | |
|   int i;
 | |
|   gint *ivalues;
 | |
|   GError *error = NULL;
 | |
|   gchar **keys;
 | |
|   gsize n_keys;
 | |
|   IconData *data;
 | |
| 
 | |
|   icon_file = g_key_file_new ();
 | |
|   g_key_file_set_list_separator (icon_file, ',');
 | |
|   g_key_file_load_from_file (icon_file, path, G_KEY_FILE_KEEP_TRANSLATIONS, &error);
 | |
|   if (error)
 | |
|     {
 | |
|       g_error_free (error);
 | |
|       g_key_file_free (icon_file);
 | |
| 
 | |
|       return NULL;
 | |
|     }
 | |
| 
 | |
|   data = g_new0 (IconData, 1);
 | |
| 
 | |
|   ivalues = g_key_file_get_integer_list (icon_file,
 | |
| 					 "Icon Data", "EmbeddedTextRectangle",
 | |
| 					 &length, NULL);
 | |
|   if (ivalues)
 | |
|     {
 | |
|       if (length == 4)
 | |
| 	{
 | |
| 	  data->has_embedded_rect = TRUE;
 | |
| 	  data->x0 = ivalues[0];
 | |
| 	  data->y0 = ivalues[1];
 | |
| 	  data->x1 = ivalues[2];
 | |
| 	  data->y1 = ivalues[3];
 | |
| 	}
 | |
| 
 | |
|       g_free (ivalues);
 | |
|     }
 | |
| 
 | |
|   str = g_key_file_get_string (icon_file, "Icon Data", "AttachPoints", NULL);
 | |
|   if (str)
 | |
|     {
 | |
|       split = g_strsplit (str, "|", -1);
 | |
| 
 | |
|       data->n_attach_points = g_strv_length (split);
 | |
|       data->attach_points = g_new (int, 2 * data->n_attach_points);
 | |
| 
 | |
|       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,
 | |
| 		GList       *directories,
 | |
| 		gint         depth)
 | |
| {
 | |
|   GHashTable *dir_hash;
 | |
|   GDir *dir;
 | |
|   const gchar *name;
 | |
|   gchar *dir_path;
 | |
|   gboolean dir_added = FALSE;
 | |
|   guint dir_index = 0xffff;
 | |
| 
 | |
|   dir_path = g_build_path ("/", base_path, subdir, NULL);
 | |
| 
 | |
|   /* FIXME: Use the gerror */
 | |
|   dir = g_dir_open (dir_path, 0, NULL);
 | |
| 
 | |
|   if (!dir)
 | |
|     return directories;
 | |
| 
 | |
|   dir_hash = g_hash_table_new (g_str_hash, g_str_equal);
 | |
| 
 | |
|   while ((name = g_dir_read_name (dir)))
 | |
|     {
 | |
|       gchar *path;
 | |
|       gboolean retval;
 | |
|       int flags = 0;
 | |
|       Image *image;
 | |
|       gchar *basename, *dot;
 | |
| 
 | |
|       path = g_build_filename (dir_path, name, NULL);
 | |
| 
 | |
|       retval = g_file_test (path, G_FILE_TEST_IS_DIR);
 | |
|       if (retval)
 | |
| 	{
 | |
| 	  gchar *subsubdir;
 | |
| 
 | |
| 	  if (subdir)
 | |
| 	    subsubdir = g_build_path ("/", subdir, name, NULL);
 | |
| 	  else
 | |
| 	    subsubdir = g_strdup (name);
 | |
| 	  directories = scan_directory (base_path, subsubdir, files,
 | |
| 					directories, depth + 1);
 | |
| 	  g_free (subsubdir);
 | |
| 
 | |
| 	  continue;
 | |
| 	}
 | |
| 
 | |
|       /* ignore images in the toplevel directory */
 | |
|       if (subdir == NULL)
 | |
|         continue;
 | |
| 
 | |
|       retval = g_file_test (path, G_FILE_TEST_IS_REGULAR);
 | |
|       if (retval)
 | |
| 	{
 | |
| 	  if (g_str_has_suffix (name, ".png"))
 | |
| 	    flags |= HAS_SUFFIX_PNG;
 | |
| 	  else if (g_str_has_suffix (name, ".svg"))
 | |
| 	    flags |= HAS_SUFFIX_SVG;
 | |
| 	  else if (g_str_has_suffix (name, ".xpm"))
 | |
| 	    flags |= HAS_SUFFIX_XPM;
 | |
| 	  else if (g_str_has_suffix (name, ".icon"))
 | |
| 	    flags |= HAS_ICON_FILE;
 | |
| 
 | |
| 	  if (flags == 0)
 | |
| 	    continue;
 | |
| 
 | |
| 	  basename = g_strdup (name);
 | |
| 	  dot = strrchr (basename, '.');
 | |
| 	  *dot = '\0';
 | |
| 
 | |
| 	  image = g_hash_table_lookup (dir_hash, basename);
 | |
| 	  if (!image)
 | |
| 	    {
 | |
| 	      if (!dir_added)
 | |
| 		{
 | |
| 		  dir_added = TRUE;
 | |
| 		  if (subdir)
 | |
| 		    {
 | |
| 		      dir_index = g_list_length (directories);
 | |
| 		      directories = g_list_append (directories, g_strdup (subdir));
 | |
| 		    }
 | |
| 		  else
 | |
| 		    dir_index = 0xffff;
 | |
| 		}
 | |
| 
 | |
| 	      image = g_new0 (Image, 1);
 | |
| 	      image->dir_index = dir_index;
 | |
| 	      g_hash_table_insert (dir_hash, g_strdup (basename), image);
 | |
| 	    }
 | |
| 
 | |
| 	  image->flags |= flags;
 | |
| 
 | |
| 	  maybe_cache_image_data (image, path);
 | |
|           maybe_cache_icon_data (image, path);
 | |
| 
 | |
| 	  g_free (basename);
 | |
| 	}
 | |
| 
 | |
|       g_free (path);
 | |
|     }
 | |
| 
 | |
|   g_dir_close (dir);
 | |
| 
 | |
|   /* Move dir into the big file hash */
 | |
|   g_hash_table_foreach_remove (dir_hash, foreach_remove_func, files);
 | |
| 
 | |
|   g_hash_table_destroy (dir_hash);
 | |
| 
 | |
|   return directories;
 | |
| }
 | |
| 
 | |
| typedef struct _HashNode HashNode;
 | |
| 
 | |
| struct _HashNode
 | |
| {
 | |
|   HashNode *next;
 | |
|   gchar *name;
 | |
|   GList *image_list;
 | |
|   gint offset;
 | |
| };
 | |
| 
 | |
| static guint
 | |
| icon_name_hash (gconstpointer key)
 | |
| {
 | |
|   const signed char *p = key;
 | |
|   guint32 h = *p;
 | |
| 
 | |
|   if (h)
 | |
|     for (p += 1; *p != '\0'; p++)
 | |
|       h = (h << 5) - h + *p;
 | |
| 
 | |
|   return h;
 | |
| }
 | |
| 
 | |
| typedef struct {
 | |
|   gint size;
 | |
|   HashNode **nodes;
 | |
| } HashContext;
 | |
| 
 | |
| static gboolean
 | |
| convert_to_hash (gpointer key, gpointer value, gpointer user_data)
 | |
| {
 | |
|   HashContext *context = user_data;
 | |
|   guint hash;
 | |
|   HashNode *node;
 | |
| 
 | |
|   hash = icon_name_hash (key) % context->size;
 | |
| 
 | |
|   node = g_new0 (HashNode, 1);
 | |
|   node->next = NULL;
 | |
|   node->name = key;
 | |
|   node->image_list = value;
 | |
| 
 | |
|   if (context->nodes[hash] != NULL)
 | |
|     node->next = context->nodes[hash];
 | |
| 
 | |
|   context->nodes[hash] = node;
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static GHashTable *string_pool = NULL;
 | |
| 
 | |
| static int
 | |
| find_string (const gchar *n)
 | |
| {
 | |
|   return GPOINTER_TO_INT (g_hash_table_lookup (string_pool, n));
 | |
| }
 | |
| 
 | |
| static void
 | |
| add_string (const gchar *n, int offset)
 | |
| {
 | |
|   g_hash_table_insert (string_pool, (gpointer) n, GINT_TO_POINTER (offset));
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| write_string (FILE *cache, const gchar *n)
 | |
| {
 | |
|   gchar *s;
 | |
|   int i, l;
 | |
| 
 | |
|   l = ALIGN_VALUE (strlen (n) + 1, 4);
 | |
| 
 | |
|   s = g_malloc0 (l);
 | |
|   strcpy (s, n);
 | |
| 
 | |
|   i = fwrite (s, l, 1, cache);
 | |
| 
 | |
|   g_free (s);
 | |
| 
 | |
|   return i == 1;
 | |
| 
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| write_card16 (FILE *cache, guint16 n)
 | |
| {
 | |
|   int i;
 | |
| 
 | |
|   n = GUINT16_TO_BE (n);
 | |
| 
 | |
|   i = fwrite ((char *)&n, 2, 1, cache);
 | |
| 
 | |
|   return i == 1;
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| write_card32 (FILE *cache, guint32 n)
 | |
| {
 | |
|   int i;
 | |
| 
 | |
|   n = GUINT32_TO_BE (n);
 | |
| 
 | |
|   i = fwrite ((char *)&n, 4, 1, cache);
 | |
| 
 | |
|   return i == 1;
 | |
| }
 | |
| 
 | |
| 
 | |
| static gboolean
 | |
| write_image_data (FILE *cache, ImageData *image_data, int offset)
 | |
| {
 | |
|   guint8 *s;
 | |
|   guint len;
 | |
|   gint i;
 | |
|   GdkPixdata *pixdata = &image_data->pixdata;
 | |
| 
 | |
|   /* Type 0 is GdkPixdata */
 | |
|   if (!write_card32 (cache, 0))
 | |
|     return FALSE;
 | |
| 
 | |
|   s = gdk_pixdata_serialize (pixdata, &len);
 | |
| 
 | |
|   if (!write_card32 (cache, len))
 | |
|     {
 | |
|       g_free (s);
 | |
|       return FALSE;
 | |
|     }
 | |
| 
 | |
|   i = fwrite (s, len, 1, cache);
 | |
| 
 | |
|   g_free (s);
 | |
| 
 | |
|   return i == 1;
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| write_icon_data (FILE *cache, IconData *icon_data, int offset)
 | |
| {
 | |
|   int ofs = offset + 12;
 | |
|   int j;
 | |
|   int tmp, tmp2;
 | |
| 
 | |
|   if (icon_data->has_embedded_rect)
 | |
|     {
 | |
|       if (!write_card32 (cache, ofs))
 | |
|         return FALSE;
 | |
| 
 | |
|        ofs += 8;
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       if (!write_card32 (cache, 0))
 | |
|         return FALSE;
 | |
|     }
 | |
| 
 | |
|   if (icon_data->n_attach_points > 0)
 | |
|     {
 | |
|       if (!write_card32 (cache, ofs))
 | |
|         return FALSE;
 | |
| 
 | |
|       ofs += 4 + 4 * icon_data->n_attach_points;
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       if (!write_card32 (cache, 0))
 | |
|         return FALSE;
 | |
|     }
 | |
| 
 | |
|   if (icon_data->n_display_names > 0)
 | |
|     {
 | |
|       if (!write_card32 (cache, ofs))
 | |
| 	return FALSE;
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       if (!write_card32 (cache, 0))
 | |
|         return FALSE;
 | |
|     }
 | |
| 
 | |
|   if (icon_data->has_embedded_rect)
 | |
|     {
 | |
|       if (!write_card16 (cache, icon_data->x0) ||
 | |
|           !write_card16 (cache, icon_data->y0) ||
 | |
| 	  !write_card16 (cache, icon_data->x1) ||
 | |
| 	  !write_card16 (cache, icon_data->y1))
 | |
|         return FALSE;
 | |
|     }
 | |
| 
 | |
|   if (icon_data->n_attach_points > 0)
 | |
|     {
 | |
|       if (!write_card32 (cache, icon_data->n_attach_points))
 | |
|         return FALSE;
 | |
| 
 | |
|       for (j = 0; j < 2 * icon_data->n_attach_points; j++)
 | |
|         {
 | |
|           if (!write_card16 (cache, icon_data->attach_points[j]))
 | |
|             return FALSE;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|   if (icon_data->n_display_names > 0)
 | |
|     {
 | |
|       if (!write_card32 (cache, icon_data->n_display_names))
 | |
|         return FALSE;
 | |
| 
 | |
|       ofs += 4 + 8 * icon_data->n_display_names;
 | |
| 
 | |
|       tmp = ofs;
 | |
|       for (j = 0; j < 2 * icon_data->n_display_names; j++)
 | |
|         {
 | |
|           tmp2 = find_string (icon_data->display_names[j]);
 | |
|           if (tmp2 == 0 || tmp2 == -1)
 | |
|             {
 | |
|               tmp2 = tmp;
 | |
|               tmp += ALIGN_VALUE (strlen (icon_data->display_names[j]) + 1, 4);
 | |
|               /* We're playing a little game with negative
 | |
|                * offsets here to handle duplicate strings in
 | |
|                * the array.
 | |
|                */
 | |
|               add_string (icon_data->display_names[j], -tmp2);
 | |
|             }
 | |
|           else if (tmp2 < 0)
 | |
|             {
 | |
|               tmp2 = -tmp2;
 | |
|             }
 | |
| 
 | |
|           if (!write_card32 (cache, tmp2))
 | |
|             return FALSE;
 | |
| 
 | |
|         }
 | |
| 
 | |
|       g_assert (ofs == ftell (cache));
 | |
|       for (j = 0; j < 2 * icon_data->n_display_names; j++)
 | |
|         {
 | |
|           tmp2 = find_string (icon_data->display_names[j]);
 | |
|           g_assert (tmp2 != 0 && tmp2 != -1);
 | |
|           if (tmp2 < 0)
 | |
|             {
 | |
|               tmp2 = -tmp2;
 | |
|               g_assert (tmp2 == ftell (cache));
 | |
|               add_string (icon_data->display_names[j], tmp2);
 | |
|               if (!write_string (cache, icon_data->display_names[j]))
 | |
|                 return FALSE;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| write_header (FILE *cache, guint32 dir_list_offset)
 | |
| {
 | |
|   return (write_card16 (cache, MAJOR_VERSION) &&
 | |
| 	  write_card16 (cache, MINOR_VERSION) &&
 | |
| 	  write_card32 (cache, HASH_OFFSET) &&
 | |
| 	  write_card32 (cache, dir_list_offset));
 | |
| }
 | |
| 
 | |
| static gint
 | |
| get_image_meta_data_size (Image *image)
 | |
| {
 | |
|   gint i;
 | |
| 
 | |
|   /* The complication with storing the size in both
 | |
|    * IconData and Image is necessary since we attribute
 | |
|    * the size of the IconData only to the first Image
 | |
|    * using it (at which time it is written out in the
 | |
|    * cache). Later Images just refer to the written out
 | |
|    * IconData via the offset.
 | |
|    */
 | |
|   if (image->icon_data_size == 0)
 | |
|     {
 | |
|       if (image->icon_data && image->icon_data->size < 0)
 | |
| 	{
 | |
|           IconData *data = image->icon_data;
 | |
| 
 | |
|           data->size = 0;
 | |
| 
 | |
|           if (data->has_embedded_rect ||
 | |
|               data->n_attach_points > 0 ||
 | |
|               data->n_display_names > 0)
 | |
|             data->size += 12;
 | |
| 
 | |
|           if (data->has_embedded_rect)
 | |
|             data->size += 8;
 | |
| 
 | |
|           if (data->n_attach_points > 0)
 | |
|             data->size += 4 + data->n_attach_points * 4;
 | |
| 
 | |
|           if (data->n_display_names > 0)
 | |
|             {
 | |
|               data->size += 4 + 8 * data->n_display_names;
 | |
| 
 | |
|               for (i = 0; data->display_names[i]; i++)
 | |
|                 {
 | |
|                   int poolv;
 | |
|                   if ((poolv = find_string (data->display_names[i])) == 0)
 | |
|                     {
 | |
|                       data->size += ALIGN_VALUE (strlen (data->display_names[i]) + 1, 4);
 | |
|                       /* Adding the string to the pool with -1
 | |
|                        * to indicate that it hasn't been written out
 | |
|                        * to the cache yet. We still need it in the
 | |
|                        * pool in case the same string occurs twice
 | |
|                        * during a get_single_node_size() calculation.
 | |
|                        */
 | |
|                       add_string (data->display_names[i], -1);
 | |
|                     }
 | |
|                 }
 | |
|            }
 | |
| 
 | |
| 	  image->icon_data_size = data->size;
 | |
| 	  data->size = 0;
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|   g_assert (image->icon_data_size % 4 == 0);
 | |
| 
 | |
|   return image->icon_data_size;
 | |
| }
 | |
| 
 | |
| static gint
 | |
| get_image_pixel_data_size (Image *image)
 | |
| {
 | |
|   /* The complication with storing the size in both
 | |
|    * ImageData and Image is necessary since we attribute
 | |
|    * the size of the ImageData only to the first Image
 | |
|    * using it (at which time it is written out in the
 | |
|    * cache). Later Images just refer to the written out
 | |
|    * ImageData via the offset.
 | |
|    */
 | |
|   if (image->pixel_data_size == 0)
 | |
|     {
 | |
|       if (image->image_data &&
 | |
| 	  image->image_data->has_pixdata)
 | |
| 	{
 | |
| 	  image->pixel_data_size = image->image_data->size;
 | |
| 	  image->image_data->size = 0;
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|   g_assert (image->pixel_data_size % 4 == 0);
 | |
| 
 | |
|   return image->pixel_data_size;
 | |
| }
 | |
| 
 | |
| static gint
 | |
| get_image_data_size (Image *image)
 | |
| {
 | |
|   gint len;
 | |
| 
 | |
|   len = 0;
 | |
| 
 | |
|   len += get_image_pixel_data_size (image);
 | |
|   len += get_image_meta_data_size (image);
 | |
| 
 | |
|   /* Even if len is zero, we need to reserve space to
 | |
|    * write the ImageData, unless this is an .svg without
 | |
|    * .icon, in which case both image_data and icon_data
 | |
|    * are NULL.
 | |
|    */
 | |
|   if (len > 0 || image->image_data || image->icon_data)
 | |
|     len += 8;
 | |
| 
 | |
|   return len;
 | |
| }
 | |
| 
 | |
| static void
 | |
| get_single_node_size (HashNode *node, int *node_size, int *image_data_size)
 | |
| {
 | |
|   GList *list;
 | |
| 
 | |
|   /* Node pointers */
 | |
|   *node_size = 12;
 | |
| 
 | |
|   /* Name */
 | |
|   if (find_string (node->name) == 0)
 | |
|     {
 | |
|       *node_size += ALIGN_VALUE (strlen (node->name) + 1, 4);
 | |
|       add_string (node->name, -1);
 | |
|     }
 | |
| 
 | |
|   /* Image list */
 | |
|   *node_size += 4 + g_list_length (node->image_list) * 8;
 | |
| 
 | |
|   /* Image data */
 | |
|   *image_data_size = 0;
 | |
|   for (list = node->image_list; list; list = list->next)
 | |
|     {
 | |
|       Image *image = list->data;
 | |
| 
 | |
|       *image_data_size += get_image_data_size (image);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| write_bucket (FILE *cache, HashNode *node, int *offset)
 | |
| {
 | |
|   while (node != NULL)
 | |
|     {
 | |
|       int node_size, image_data_size;
 | |
|       int next_offset, image_data_offset;
 | |
|       int data_offset;
 | |
|       int name_offset;
 | |
|       int name_size;
 | |
|       int image_list_offset;
 | |
|       int i, len;
 | |
|       GList *list;
 | |
| 
 | |
|       g_assert (*offset == ftell (cache));
 | |
| 
 | |
|       node->offset = *offset;
 | |
| 
 | |
|       get_single_node_size (node, &node_size, &image_data_size);
 | |
|       g_assert (node_size % 4 == 0);
 | |
|       g_assert (image_data_size % 4 == 0);
 | |
|       image_data_offset = *offset + node_size;
 | |
|       next_offset = *offset + node_size + image_data_size;
 | |
|       /* Chain offset */
 | |
|       if (node->next != NULL)
 | |
|         {
 | |
|           if (!write_card32 (cache, next_offset))
 | |
|             return FALSE;
 | |
|         }
 | |
|       else
 | |
|         {
 | |
|           if (!write_card32 (cache, 0xffffffff))
 | |
|             return FALSE;
 | |
|         }
 | |
| 
 | |
|       name_size = 0;
 | |
|       name_offset = find_string (node->name);
 | |
|       if (name_offset <= 0)
 | |
|         {
 | |
|           name_offset = *offset + 12;
 | |
|           name_size = ALIGN_VALUE (strlen (node->name) + 1, 4);
 | |
|           add_string (node->name, name_offset);
 | |
|         }
 | |
|       if (!write_card32 (cache, name_offset))
 | |
|         return FALSE;
 | |
| 
 | |
|       image_list_offset = *offset + 12 + name_size;
 | |
|       if (!write_card32 (cache, image_list_offset))
 | |
|         return FALSE;
 | |
| 
 | |
|       /* Icon name */
 | |
|       if (name_size > 0)
 | |
|         {
 | |
|           if (!write_string (cache, node->name))
 | |
|             return FALSE;
 | |
|         }
 | |
| 
 | |
|       /* Image list */
 | |
|       len = g_list_length (node->image_list);
 | |
|       if (!write_card32 (cache, len))
 | |
|         return FALSE;
 | |
| 
 | |
|       list = node->image_list;
 | |
|       data_offset = image_data_offset;
 | |
|       for (i = 0; i < len; i++)
 | |
|         {
 | |
|           Image *image = list->data;
 | |
|           int image_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;
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| write_hash_table (FILE *cache, HashContext *context, int *new_offset)
 | |
| {
 | |
|   int offset = HASH_OFFSET;
 | |
|   int node_offset;
 | |
|   int i;
 | |
| 
 | |
|   if (!(write_card32 (cache, context->size)))
 | |
|     return FALSE;
 | |
| 
 | |
|   offset += 4;
 | |
|   node_offset = offset + context->size * 4;
 | |
|   /* Just write zeros here, we will rewrite this later */
 | |
|   for (i = 0; i < context->size; i++)
 | |
|     {
 | |
|       if (!write_card32 (cache, 0))
 | |
| 	return FALSE;
 | |
|     }
 | |
| 
 | |
|   /* Now write the buckets */
 | |
|   for (i = 0; i < context->size; i++)
 | |
|     {
 | |
|       if (!context->nodes[i])
 | |
| 	continue;
 | |
| 
 | |
|       g_assert (node_offset % 4 == 0);
 | |
|       if (!write_bucket (cache, context->nodes[i], &node_offset))
 | |
| 	return FALSE;
 | |
|     }
 | |
| 
 | |
|   *new_offset = node_offset;
 | |
| 
 | |
|   /* Now write out the bucket offsets */
 | |
| 
 | |
|   fseek (cache, offset, SEEK_SET);
 | |
| 
 | |
|   for (i = 0; i < context->size; i++)
 | |
|     {
 | |
|       if (context->nodes[i] != NULL)
 | |
|         node_offset = context->nodes[i]->offset;
 | |
|       else
 | |
| 	node_offset = 0xffffffff;
 | |
|       if (!write_card32 (cache, node_offset))
 | |
|         return FALSE;
 | |
|     }
 | |
| 
 | |
|   fseek (cache, 0, SEEK_END);
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| write_dir_index (FILE *cache, int offset, GList *directories)
 | |
| {
 | |
|   int n_dirs;
 | |
|   GList *d;
 | |
|   char *dir;
 | |
|   int tmp, tmp2;
 | |
| 
 | |
|   n_dirs = g_list_length (directories);
 | |
| 
 | |
|   if (!write_card32 (cache, n_dirs))
 | |
|     return FALSE;
 | |
| 
 | |
|   offset += 4 + n_dirs * 4;
 | |
| 
 | |
|   tmp = offset;
 | |
|   for (d = directories; d; d = d->next)
 | |
|     {
 | |
|       dir = d->data;
 | |
| 
 | |
|       tmp2 = find_string (dir);
 | |
| 
 | |
|       if (tmp2 == 0 || tmp2 == -1)
 | |
|         {
 | |
|           tmp2 = tmp;
 | |
|           tmp += ALIGN_VALUE (strlen (dir) + 1, 4);
 | |
|           /* We're playing a little game with negative
 | |
|            * offsets here to handle duplicate strings in
 | |
|            * the array, even though that should not
 | |
|            * really happen for the directory index.
 | |
|            */
 | |
|           add_string (dir, -tmp2);
 | |
|         }
 | |
|       else if (tmp2 < 0)
 | |
|         {
 | |
|           tmp2 = -tmp2;
 | |
|         }
 | |
| 
 | |
|       if (!write_card32 (cache, tmp2))
 | |
| 	return FALSE;
 | |
|     }
 | |
| 
 | |
|   g_assert (offset == ftell (cache));
 | |
|   for (d = directories; d; d = d->next)
 | |
|     {
 | |
|       dir = d->data;
 | |
| 
 | |
|       tmp2 = find_string (dir);
 | |
|       g_assert (tmp2 != 0 && tmp2 != -1);
 | |
|       if (tmp2 < 0)
 | |
|         {
 | |
|           tmp2 = -tmp2;
 | |
|           g_assert (tmp2 == ftell (cache));
 | |
|           add_string (dir, tmp2);
 | |
|           if (!write_string (cache, dir))
 | |
| 	    return FALSE;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| write_file (FILE *cache, GHashTable *files, GList *directories)
 | |
| {
 | |
|   HashContext context;
 | |
|   int new_offset;
 | |
| 
 | |
|   /* Convert the hash table into something looking a bit more
 | |
|    * like what we want to write to disk.
 | |
|    */
 | |
|   context.size = g_spaced_primes_closest (g_hash_table_size (files) / 3);
 | |
|   context.nodes = g_new0 (HashNode *, context.size);
 | |
| 
 | |
|   g_hash_table_foreach_remove (files, convert_to_hash, &context);
 | |
| 
 | |
|   /* Now write the file */
 | |
|   /* We write 0 as the directory list offset and go
 | |
|    * back and change it later */
 | |
|   if (!write_header (cache, 0))
 | |
|     {
 | |
|       g_printerr (_("Failed to write header\n"));
 | |
|       return FALSE;
 | |
|     }
 | |
| 
 | |
|   if (!write_hash_table (cache, &context, &new_offset))
 | |
|     {
 | |
|       g_printerr (_("Failed to write hash table\n"));
 | |
|       return FALSE;
 | |
|     }
 | |
| 
 | |
|   if (!write_dir_index (cache, new_offset, directories))
 | |
|     {
 | |
|       g_printerr (_("Failed to write folder index\n"));
 | |
|       return FALSE;
 | |
|     }
 | |
| 
 | |
|   rewind (cache);
 | |
| 
 | |
|   if (!write_header (cache, new_offset))
 | |
|     {
 | |
|       g_printerr (_("Failed to rewrite header\n"));
 | |
|       return FALSE;
 | |
|     }
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| validate_file (const gchar *file)
 | |
| {
 | |
|   GMappedFile *map;
 | |
|   CacheInfo info;
 | |
| 
 | |
|   map = g_mapped_file_new (file, FALSE, NULL);
 | |
|   if (!map)
 | |
|     return FALSE;
 | |
| 
 | |
|   info.cache = g_mapped_file_get_contents (map);
 | |
|   info.cache_size = g_mapped_file_get_length (map);
 | |
|   info.n_directories = 0;
 | |
|   info.flags = CHECK_OFFSETS|CHECK_STRINGS|CHECK_PIXBUFS;
 | |
| 
 | |
|   if (!_gtk_icon_cache_validate (&info))
 | |
|     {
 | |
|       g_mapped_file_unref (map);
 | |
|       return FALSE;
 | |
|     }
 | |
| 
 | |
|   g_mapped_file_unref (map);
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * safe_fclose:
 | |
|  * @f: A FILE* stream, must have underlying fd
 | |
|  *
 | |
|  * Unix defaults for data preservation after system crash
 | |
|  * are unspecified, and many systems will eat your data
 | |
|  * in this situation unless you explicitly fsync().
 | |
|  *
 | |
|  * Returns: %TRUE on success, %FALSE on failure, and will set errno()
 | |
|  */
 | |
| static gboolean
 | |
| safe_fclose (FILE *f)
 | |
| {
 | |
|   int fd = fileno (f);
 | |
|   g_assert (fd >= 0);
 | |
|   if (fflush (f) == EOF)
 | |
|     return FALSE;
 | |
| #ifndef G_OS_WIN32
 | |
|   if (fsync (fd) < 0)
 | |
|     return FALSE;
 | |
| #endif
 | |
|   if (fclose (f) == EOF)
 | |
|     return FALSE;
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static void
 | |
| build_cache (const gchar *path)
 | |
| {
 | |
|   gchar *cache_path, *tmp_cache_path;
 | |
| #ifdef G_OS_WIN32
 | |
|   gchar *bak_cache_path = NULL;
 | |
| #endif
 | |
|   GHashTable *files;
 | |
|   FILE *cache;
 | |
|   GStatBuf path_stat, cache_stat;
 | |
|   struct utimbuf utime_buf;
 | |
|   GList *directories = NULL;
 | |
|   int fd;
 | |
|   int retry_count = 0;
 | |
| #ifndef G_OS_WIN32
 | |
|   mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
 | |
| #else
 | |
|   int mode = _S_IWRITE | _S_IREAD;
 | |
| #endif
 | |
| #ifndef _O_BINARY
 | |
| #define _O_BINARY 0
 | |
| #endif
 | |
| 
 | |
|   tmp_cache_path = g_build_filename (path, "."CACHE_NAME, NULL);
 | |
|   cache_path = g_build_filename (path, CACHE_NAME, NULL);
 | |
| 
 | |
| opentmp:
 | |
|   if ((fd = g_open (tmp_cache_path, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC | _O_BINARY, mode)) == -1)
 | |
|     {
 | |
|       if (retry_count == 0)
 | |
|         {
 | |
|           retry_count++;
 | |
|           g_remove (tmp_cache_path);
 | |
|           goto opentmp;
 | |
|         }
 | |
|       g_printerr (_("Failed to open file %s : %s\n"), tmp_cache_path, g_strerror (errno));
 | |
|       exit (1);
 | |
|     }
 | |
| 
 | |
|   cache = fdopen (fd, "wb");
 | |
| 
 | |
|   if (!cache)
 | |
|     {
 | |
|       g_printerr (_("Failed to write cache file: %s\n"), g_strerror (errno));
 | |
|       exit (1);
 | |
|     }
 | |
| 
 | |
|   files = g_hash_table_new (g_str_hash, g_str_equal);
 | |
|   image_data_hash = g_hash_table_new (g_str_hash, g_str_equal);
 | |
|   icon_data_hash = g_hash_table_new (g_str_hash, g_str_equal);
 | |
|   string_pool = g_hash_table_new (g_str_hash, g_str_equal);
 | |
| 
 | |
|   directories = scan_directory (path, NULL, files, NULL, 0);
 | |
| 
 | |
|   if (g_hash_table_size (files) == 0)
 | |
|     {
 | |
|       /* Empty table, just close and remove the file */
 | |
| 
 | |
|       fclose (cache);
 | |
|       g_unlink (tmp_cache_path);
 | |
|       g_unlink (cache_path);
 | |
|       exit (0);
 | |
|     }
 | |
| 
 | |
|   /* FIXME: Handle failure */
 | |
|   if (!write_file (cache, files, directories))
 | |
|     {
 | |
|       g_unlink (tmp_cache_path);
 | |
|       exit (1);
 | |
|     }
 | |
| 
 | |
|   if (!safe_fclose (cache))
 | |
|     {
 | |
|       g_printerr (_("Failed to write cache file: %s\n"), g_strerror (errno));
 | |
|       g_unlink (tmp_cache_path);
 | |
|       exit (1);
 | |
|     }
 | |
|   cache = NULL;
 | |
| 
 | |
|   g_list_free_full (directories, g_free);
 | |
| 
 | |
|   if (!validate_file (tmp_cache_path))
 | |
|     {
 | |
|       g_printerr (_("The generated cache was invalid.\n"));
 | |
|       /*g_unlink (tmp_cache_path);*/
 | |
|       exit (1);
 | |
|     }
 | |
| 
 | |
| #ifdef G_OS_WIN32
 | |
|   if (g_file_test (cache_path, G_FILE_TEST_EXISTS))
 | |
|     {
 | |
|       bak_cache_path = g_strconcat (cache_path, ".bak", NULL);
 | |
|       g_unlink (bak_cache_path);
 | |
|       if (g_rename (cache_path, bak_cache_path) == -1)
 | |
| 	{
 | |
|           int errsv = errno;
 | |
| 
 | |
| 	  g_printerr (_("Could not rename %s to %s: %s, removing %s then.\n"),
 | |
| 		      cache_path, bak_cache_path,
 | |
| 		      g_strerror (errsv),
 | |
| 		      cache_path);
 | |
| 	  g_unlink (cache_path);
 | |
| 	  bak_cache_path = NULL;
 | |
| 	}
 | |
|     }
 | |
| #endif
 | |
| 
 | |
|   if (g_rename (tmp_cache_path, cache_path) == -1)
 | |
|     {
 | |
|       int errsv = errno;
 | |
| 
 | |
|       g_printerr (_("Could not rename %s to %s: %s\n"),
 | |
| 		  tmp_cache_path, cache_path,
 | |
| 		  g_strerror (errsv));
 | |
|       g_unlink (tmp_cache_path);
 | |
| #ifdef G_OS_WIN32
 | |
|       if (bak_cache_path != NULL)
 | |
| 	if (g_rename (bak_cache_path, cache_path) == -1)
 | |
|           {
 | |
|             errsv = errno;
 | |
| 
 | |
|             g_printerr (_("Could not rename %s back to %s: %s.\n"),
 | |
|                         bak_cache_path, cache_path,
 | |
|                         g_strerror (errsv));
 | |
|           }
 | |
| #endif
 | |
|       exit (1);
 | |
|     }
 | |
| #ifdef G_OS_WIN32
 | |
|   if (bak_cache_path != NULL)
 | |
|     g_unlink (bak_cache_path);
 | |
| #endif
 | |
| 
 | |
|   /* Update time */
 | |
|   /* FIXME: What do do if an error occurs here? */
 | |
|   if (g_stat (path, &path_stat) < 0 ||
 | |
|       g_stat (cache_path, &cache_stat))
 | |
|     exit (1);
 | |
| 
 | |
|   utime_buf.actime = path_stat.st_atime;
 | |
|   utime_buf.modtime = cache_stat.st_mtime;
 | |
| #if GLIB_CHECK_VERSION (2, 17, 1)
 | |
|   g_utime (path, &utime_buf);
 | |
| #else
 | |
|   utime (path, &utime_buf);
 | |
| #endif
 | |
| 
 | |
|   if (!quiet)
 | |
|     g_printerr (_("Cache file created successfully.\n"));
 | |
| }
 | |
| 
 | |
| static void
 | |
| write_csource (const gchar *path)
 | |
| {
 | |
|   gchar *cache_path;
 | |
|   gchar *data;
 | |
|   gsize len;
 | |
|   gint i;
 | |
| 
 | |
|   cache_path = g_build_filename (path, CACHE_NAME, NULL);
 | |
|   if (!g_file_get_contents (cache_path, &data, &len, NULL))
 | |
|     exit (1);
 | |
| 
 | |
|   g_printf ("#ifdef __SUNPRO_C\n");
 | |
|   g_printf ("#pragma align 4 (%s)\n", var_name);
 | |
|   g_printf ("#endif\n");
 | |
| 
 | |
|   g_printf ("#ifdef __GNUC__\n");
 | |
|   g_printf ("static const guint8 %s[] __attribute__ ((__aligned__ (4))) = \n", var_name);
 | |
|   g_printf ("#else\n");
 | |
|   g_printf ("static const guint8 %s[] = \n", var_name);
 | |
|   g_printf ("#endif\n");
 | |
| 
 | |
|   g_printf ("{\n");
 | |
|   for (i = 0; i < len - 1; i++)
 | |
|     {
 | |
|       if (i %12 == 0)
 | |
| 	g_printf ("  ");
 | |
|       g_printf ("0x%02x, ", (guint8)data[i]);
 | |
|       if (i % 12 == 11)
 | |
|         g_printf ("\n");
 | |
|     }
 | |
| 
 | |
|   g_printf ("0x%02x\n};\n", (guint8)data[i]);
 | |
| }
 | |
| 
 | |
| static GOptionEntry args[] = {
 | |
|   { "force", 'f', 0, G_OPTION_ARG_NONE, &force_update, N_("Overwrite an existing cache, even if up to date"), NULL },
 | |
|   { "ignore-theme-index", 't', 0, G_OPTION_ARG_NONE, &ignore_theme_index, N_("Don't check for the existence of index.theme"), NULL },
 | |
|   { "index-only", 'i', 0, G_OPTION_ARG_NONE, &index_only, N_("Don't include image data in the cache"), NULL },
 | |
|   { "include-image-data", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &index_only, N_("Include image data in the cache"), NULL },
 | |
|   { "source", 'c', 0, G_OPTION_ARG_STRING, &var_name, N_("Output a C header file"), "NAME" },
 | |
|   { "quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet, N_("Turn off verbose output"), NULL },
 | |
|   { "validate", 'v', 0, G_OPTION_ARG_NONE, &validate, N_("Validate existing icon cache"), NULL },
 | |
|   { NULL }
 | |
| };
 | |
| 
 | |
| static void
 | |
| printerr_handler (const gchar *string)
 | |
| {
 | |
|   const gchar *charset;
 | |
| 
 | |
|   fputs (g_get_prgname (), stderr);
 | |
|   fputs (": ", stderr);
 | |
|   if (g_get_charset (&charset))
 | |
|     fputs (string, stderr); /* charset is UTF-8 already */
 | |
|   else
 | |
|     {
 | |
|       gchar *result;
 | |
| 
 | |
|       result = g_convert_with_fallback (string, -1, charset, "UTF-8", "?", NULL, NULL, NULL);
 | |
| 
 | |
|       if (result)
 | |
|         {
 | |
|           fputs (result, stderr);
 | |
|           g_free (result);
 | |
|         }
 | |
| 
 | |
|       fflush (stderr);
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| int
 | |
| main (int argc, char **argv)
 | |
| {
 | |
|   gchar *path;
 | |
|   GOptionContext *context;
 | |
| 
 | |
|   if (argc < 2)
 | |
|     return 0;
 | |
| 
 | |
|   g_set_printerr_handler (printerr_handler);
 | |
| 
 | |
|   setlocale (LC_ALL, "");
 | |
| 
 | |
| #ifdef ENABLE_NLS
 | |
|   bindtextdomain (GETTEXT_PACKAGE, GTK_LOCALEDIR);
 | |
| #ifdef HAVE_BIND_TEXTDOMAIN_CODESET
 | |
|   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
 | |
| #endif
 | |
| #endif
 | |
| 
 | |
|   context = g_option_context_new ("ICONPATH");
 | |
|   g_option_context_add_main_entries (context, args, GETTEXT_PACKAGE);
 | |
| 
 | |
|   g_option_context_parse (context, &argc, &argv, NULL);
 | |
| 
 | |
|   path = argv[1];
 | |
| #ifdef G_OS_WIN32
 | |
|   path = g_locale_to_utf8 (path, -1, NULL, NULL, NULL);
 | |
| #endif
 | |
| 
 | |
|   if (validate)
 | |
|     {
 | |
|        gchar *file = g_build_filename (path, CACHE_NAME, NULL);
 | |
| 
 | |
|        if (!g_file_test (file, G_FILE_TEST_IS_REGULAR))
 | |
|          {
 | |
|             if (!quiet)
 | |
|               g_printerr (_("File not found: %s\n"), file);
 | |
|             exit (1);
 | |
|          }
 | |
|        if (!validate_file (file))
 | |
|          {
 | |
|            if (!quiet)
 | |
|              g_printerr (_("Not a valid icon cache: %s\n"), file);
 | |
|            exit (1);
 | |
|          }
 | |
|        else
 | |
|          {
 | |
|            exit (0);
 | |
|          }
 | |
|     }
 | |
| 
 | |
|   if (!ignore_theme_index && !has_theme_index (path))
 | |
|     {
 | |
|       if (path)
 | |
| 	{
 | |
| 	  g_printerr (_("No theme index file.\n"));
 | |
| 	}
 | |
|       else
 | |
| 	{
 | |
| 	  g_printerr (_("No theme index file in '%s'.\n"
 | |
| 		    "If you really want to create an icon cache here, use --ignore-theme-index.\n"), path);
 | |
| 	}
 | |
| 
 | |
|       return 1;
 | |
|     }
 | |
| 
 | |
|   if (!force_update && is_cache_up_to_date (path))
 | |
|     return 0;
 | |
| 
 | |
|   replace_backslashes_with_slashes (path);
 | |
|   build_cache (path);
 | |
| 
 | |
|   if (strcmp (var_name, "-") != 0)
 | |
|     write_csource (path);
 | |
| 
 | |
|   return 0;
 | |
| }
 |