Implementation - Thumbnail Loader/Generator

A sample implementation of a simple thumbnail loader/generator, which implements only a part of the specification.

This first design/implementation was very complex. Since thumbnail generation/loading is only a minor part of the file managers work, a better design/implementation should be used.

The FilerThumbIcon part:

static FilerIconCacheEntry*
filer_thumb_icon_load (FilerIcon     *icon,
                       FilerIconSize  size)
{
  FilerIconCacheEntry *entry;
  FilerThumbIcon      *thumb_icon = FILER_THUMB_ICON (icon);
  filer_time_t        *timep;
  struct stat          file_stat;
  const gchar         *path = exo_uri_get_path (thumb_icon->priv->uri);
  GdkPixbuf           *pixbuf;
  gint                 real_size = filer_icon_sizes[size];
  gint                 width;
  gint                 height;
 
  /* query current file status */
  if (G_UNLIKELY (stat (path, &file_stat) < 0))
    return filer_icon_load (thumb_icon->priv->fallback, size);
 
  /* check for a cached variant of the thumbnail */
  entry = filer_icon_cache_lookup (icon->cache, size, FILER_ICON_THUMBNAIL, path);
  if (entry != NULL)
    {
      /* check modification time! */
      timep = g_object_get_qdata (G_OBJECT (entry->pixbuf),
                                  icon_mtime_quark);
      if (G_LIKELY (timep != NULL && *timep == file_stat.st_mtime))
        return entry;
 
      /* cached entry is no longer up-to-date, drop all icons
       * of that name (that includes combined icons) */
      filer_icon_cache_remove_by_name (icon->cache, path);
    }
 
  if (!thumb_icon->priv->loading
    && thumb_icon->priv->loaded != NULL)
    {
      width = gdk_pixbuf_get_width (thumb_icon->priv->loaded);
      height = gdk_pixbuf_get_height (thumb_icon->priv->loaded);
 
      if (width > real_size || height > real_size)
        pixbuf = exo_gdk_pixbuf_scale_ratio (thumb_icon->priv->loaded, real_size);
      else
        pixbuf = GDK_PIXBUF (g_object_ref (G_OBJECT (thumb_icon->priv->loaded)));
 
      timep = g_new (filer_time_t, 1);
      timep[0] = file_stat.st_mtime;
      g_object_set_qdata_full (G_OBJECT (pixbuf),
                               icon_mtime_quark,
                               timep, g_free);
 
      entry = filer_icon_cache_insert (icon->cache, size,
                                       FILER_ICON_THUMBNAIL,
                                       path, pixbuf);
      g_object_unref (G_OBJECT (pixbuf));
    }
  else
    {
      /* schedule to load this thumbnail */
      if (!thumb_icon->priv->loading)
        {
          filer_thumb_loader_schedule (thumb_icon->priv->loader, thumb_icon);
          thumb_icon->priv->loading = TRUE;
        }
 
      entry = filer_icon_load (thumb_icon->priv->fallback, size);
    }
 
  return entry;
}

The FilerThumbLoader part (with the calls to the D-Bus generator service):

static gboolean
filer_thumb_loader_idle (gpointer data)
{
  FilerThumbLoader *loader = FILER_THUMB_LOADER (data);
  FilerThumbIcon   *current;
  DBusConnection   *connection;
  DBusMessage      *message;
  struct stat       file_stat;
  GdkPixbuf        *pixbuf;
  ExoUri           *uri;
  gchar            *encoded_uri;
  gchar            *path;
  gchar             md5[37];
  guint             nicons;
 
  g_return_val_if_fail (FILER_IS_THUMB_LOADER (loader), FALSE);
 
  if (loader->priv->current != NULL)
    return FALSE;
 
  for (nicons = 0; nicons < 7; ++nicons)
    {
      current = g_queue_pop_head (loader->priv->thumbs);
      if (G_UNLIKELY (current == NULL))
        return FALSE;
 
      uri = filer_thumb_icon_get_uri (current);
      g_assert (exo_uri_is_local (uri));
 
      /* query current file status */
      if (G_UNLIKELY (stat (exo_uri_get_path (uri), &file_stat) < 0))
        {
          g_object_weak_unref (G_OBJECT (current), filer_thumb_loader_icon_destroyed, loader);
          continue;
        }
 
      /* generate md5 checksum */
      encoded_uri = exo_uri_encode (uri, 0);
      exo_md5_calculate_hash (encoded_uri, md5, 33);
      md5[32] = '.'; md5[33] = 'p'; md5[34] = 'n';
      md5[35] = 'g'; md5[36] = '\0';
 
      /* check if its useless to try to generate a thumbnail here */
      path = xfce_get_homefile (".thumbnails/fail/filer-1.0", md5, NULL);
      pixbuf = tryload (path, encoded_uri, &file_stat);
      g_free (path);
 
      if (pixbuf != NULL)
        {
          /* thumbnail generation will fail! */
          g_object_weak_unref (G_OBJECT (current), filer_thumb_loader_icon_destroyed, loader);
          g_object_unref (G_OBJECT (pixbuf));
          g_free (encoded_uri);
          continue;
        }
 
      /* check 128x128 thumbnails */
      path = xfce_get_homefile (".thumbnails/normal", md5, NULL);
      pixbuf = tryload (path, encoded_uri, &file_stat);
      g_free (path);
 
      /* check 256x256 thumbnails */
      if (G_UNLIKELY (pixbuf == NULL))
        {
          path = xfce_get_homefile (".thumbnails/large", md5, NULL);
          pixbuf = tryload (path, encoded_uri, &file_stat);
          g_free (path);
        }
 
      /* ... luckily, we found a thumbnail */
      if (G_LIKELY (pixbuf != NULL))
        {
          g_object_weak_unref (G_OBJECT (current), filer_thumb_loader_icon_destroyed, loader);
          filer_thumb_icon_set_from_pixbuf (current, pixbuf);
          g_object_unref (G_OBJECT (pixbuf));
          g_free (encoded_uri);
          continue;
        }
 
      /* ... no thumbnail, we've to create one */
      message = dbus_message_new_method_call ("org.xfce.Filer.ThumbnailGenerator",
                                              "/org/xfce/Filer/ThumbnailGenerator",
                                              "org.xfce.Filer.ThumbnailGenerator",
                                              "GenerateThumbnail");
      dbus_message_set_auto_activation (message, TRUE);
      dbus_message_append_args (message,
                                DBUS_TYPE_STRING, encoded_uri,
                                DBUS_TYPE_INT32, 128,
                                DBUS_TYPE_INVALID);
      g_free (encoded_uri);
 
      connection = exo_dbus_bus_connection ();
      if (!dbus_connection_send_with_reply (connection, message,
                                            &loader->priv->pending, INT_MAX))
        {
          g_object_weak_unref (G_OBJECT (current), filer_thumb_loader_icon_destroyed, loader);
          dbus_message_unref (message);
          continue;
        }
      else
        {
          dbus_pending_call_set_notify (loader->priv->pending,
                                        filer_thumb_loader_notify,
                                        loader, NULL);
          loader->priv->current = current;
        }
 
      dbus_message_unref (message);
      return FALSE;
    }
 
  return TRUE;
}

The generator-side implementation (implemented as a D-Bus service in the original filer):

static GdkPixbuf*
filer_thumb_manager_load_thumbnail (const gchar *encoded_uri,
                                    gint         size)
{
  struct stat sb;
  GdkPixbuf *image;
  GdkPixbuf *scaled;
  GdkPixbuf *thumb = NULL;
  ExoUri    *uri;
  GError    *error = NULL;
  gchar     *save_path;
  gchar     *tmp_path;
  gchar     *smtime;
  gchar      md5[37];
  gint       width;
  gint       height;
 
  uri = exo_uri_new (encoded_uri, &error);
  if (G_UNLIKELY (uri == NULL))
    {
      g_warning ("Unable parse URI: %s", error->message);
      g_error_free (error);
      return NULL;
    }
 
  if (stat (exo_uri_get_path (uri), &sb) < 0)
    {
      g_warning ("Unable to stat '%s': %s", encoded_uri, g_strerror (errno));
      g_object_unref (uri);
      return NULL;
    }
 
  image = gdk_pixbuf_new_from_file (exo_uri_get_path (uri), &error);
  if (G_UNLIKELY (image == NULL))
    {
      g_warning ("Unable to load '%s': %s", encoded_uri, error->message);
      g_object_unref (uri);
      g_error_free (error);
      return NULL;
    }
 
  width = gdk_pixbuf_get_width (image);
  height = gdk_pixbuf_get_height (image);
 
  if (width > 128 || height > 128)
    {
      exo_md5_calculate_hash (encoded_uri, md5, 33);
      md5[32] = '.'; md5[33] = 'p'; md5[34] = 'n';
      md5[35] = 'g'; md5[36] = '\0';
 
      save_path = xfce_get_homefile (".thumbnails/normal", md5, NULL);
      tmp_path = g_path_get_dirname (save_path);
      xfce_mkdirhier (tmp_path, 0700, NULL);
      g_free (tmp_path);
 
      thumb = exo_gdk_pixbuf_scale_ratio (image, 128);
 
      tmp_path = g_strconcat (save_path, ".tmp", NULL);
      smtime = g_strdup_printf ("%lu", (unsigned long) sb.st_mtime);
      if (!gdk_pixbuf_save (thumb, tmp_path, "png", &error,
                            "tEXt::Thumb::URI", encoded_uri,
                            "tEXt::Thumb::MTime", smtime,
                            "tEXt::Software", PACKAGE_STRING,
                            NULL))
        {
          g_warning ("Failed to save thumbnail for '%s' to '%s': %s",
                     encoded_uri, tmp_path, error->message);
          g_error_free (error);
        }
      else if (rename (tmp_path, save_path) < 0)
        {
          g_warning ("Failed to rename '%s' to '%s': %s",
                     tmp_path, save_path, g_strerror (errno));
          unlink (tmp_path);
        }
      else
        {
          g_message ("Stored thumbnail for '%s' to '%s'", encoded_uri, save_path);
        }
      g_free (save_path);
      g_free (tmp_path);
      g_free (smtime);
    }
 
  if (thumb != NULL && size < 128)
    {
      scaled = exo_gdk_pixbuf_scale_ratio (thumb, size);
      g_object_unref (thumb);
      thumb = scaled;
    }
  else if (thumb == NULL)
    {
      thumb = exo_gdk_pixbuf_scale_ratio (image, size);
    }
  
  g_object_unref (image);
  g_object_unref (uri);
 
  return thumb;
}
 
  implementation/thumbnail-loader.txt · Last modified: 2005/02/08 23:46
 
Recent changes RSS feed Creative Commons License Driven by DokuWiki