]> Pileus Git - ~andy/gtk/blobdiff - gtk/updateiconcache.c
Do without GtkSelectionWindow
[~andy/gtk] / gtk / updateiconcache.c
index 076c10219d94d0a15bf94ed2d78abbe9781d7e71..effea7c6c605611f497d77b8ec83f515001ab861 100644 (file)
  * 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 <http://www.gnu.org/licenses/>.
  */
 
-#include <config.h>
+#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 = 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"
 
@@ -48,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 <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)
 {
-  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 */
@@ -68,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)
 {
@@ -94,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);
 
@@ -113,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, 
@@ -127,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);
@@ -146,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, 
@@ -162,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"))
@@ -184,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) 
                {
@@ -201,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);
@@ -228,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++)
@@ -270,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;
@@ -283,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)
 {
@@ -322,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;
+       }
+    }
 
-guint
-get_single_node_size (HashNode *node)
+  g_assert (image->icon_data_size % 4 == 0);
+
+  return image->icon_data_size;
+}
+
+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;
@@ -430,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);
 
@@ -482,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;
@@ -521,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;
     }
   
@@ -541,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)
@@ -578,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)
 {
@@ -630,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);
   
@@ -640,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;
 }