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; }