[recipes/image-download: 9/10] Rework GrImage with new loading APIs



commit 03e99b1bb5f21738488e884196e899335e5d7a2a
Author: Matthias Clasen <mclasen redhat com>
Date:   Sun Mar 5 18:14:08 2017 -0500

    Rework GrImage with new loading APIs
    
    Add an async API to GrImage. The implementation uses libsoup
    to possibly load images from the network (the location where
    to load images from still has to be determined).
    
    The implementation is inspired by gnome-software's code for loading
    screenshots, but is generally simpler. We do support loading a small
    thumbnail first, and displaying it blurred, while the main image is
    downloading.
    
    Downloaded images are stored in XDG_CACHE_DIR/gnome-recipes.

 src/gr-image.c |  312 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 src/gr-image.h |   30 +++++-
 2 files changed, 335 insertions(+), 7 deletions(-)
---
diff --git a/src/gr-image.c b/src/gr-image.c
index dd61b2a..5310c72 100644
--- a/src/gr-image.c
+++ b/src/gr-image.c
@@ -20,12 +20,21 @@
 
 #include "config.h"
 
+#include <libsoup/soup.h>
+
 #include "gr-image.h"
+#include "gr-utils.h"
+
 
 struct _GrImage
 {
         GObject parent_instance;
         char *path;
+
+        SoupSession *session;
+        SoupMessage *thumbnail_message;
+        SoupMessage *image_message;
+        GList *pending;
 };
 
 G_DEFINE_TYPE (GrImage, gr_image, G_TYPE_OBJECT)
@@ -33,9 +42,20 @@ G_DEFINE_TYPE (GrImage, gr_image, G_TYPE_OBJECT)
 static void
 gr_image_finalize (GObject *object)
 {
-        GrImage *image = GR_IMAGE (object);
+        GrImage *ri = GR_IMAGE (object);
 
-        g_free (image->path);
+        if (ri->thumbnail_message)
+                soup_session_cancel_message (ri->session,
+                                             ri->thumbnail_message,
+                                             SOUP_STATUS_CANCELLED);
+        g_clear_object (&ri->thumbnail_message);
+        if (ri->image_message)
+                soup_session_cancel_message (ri->session,
+                                             ri->image_message,
+                                             SOUP_STATUS_CANCELLED);
+        g_clear_object (&ri->image_message);
+        g_clear_object (&ri->session);
+        g_free (ri->path);
 
         G_OBJECT_CLASS (gr_image_parent_class)->finalize (object);
 }
@@ -54,11 +74,13 @@ gr_image_init (GrImage *image)
 }
 
 GrImage *
-gr_image_new (const char *path)
+gr_image_new (SoupSession *session,
+              const char  *path)
 {
         GrImage *image;
 
         image = g_object_new (GR_TYPE_IMAGE, NULL);
+        image->session = g_object_ref (session);
         gr_image_set_path (image, path);
 
         return image;
@@ -83,3 +105,287 @@ gr_image_array_new (void)
 {
         return g_ptr_array_new_with_free_func (g_object_unref);
 }
+
+static GdkPixbuf *
+load_pixbuf (const char *path,
+             int         width,
+             int         height,
+             gboolean    fit)
+{
+        GdkPixbuf *pixbuf;
+
+        if (fit)
+                pixbuf = load_pixbuf_fit_size (path, width, height, FALSE);
+        else
+                pixbuf = load_pixbuf_fill_size (path, width, height);
+
+        return pixbuf;
+}
+
+typedef struct {
+        GtkImage *image;
+        int width;
+        int height;
+        gboolean fit;
+        GCancellable *cancellable;
+        GrImageCallback callback;
+        gpointer data;
+} TaskData;
+
+static void
+task_data_free (gpointer data)
+{
+        TaskData *td = data;
+
+        g_clear_object (&td->cancellable);
+
+        g_free (td);
+}
+
+static char *
+get_image_url (const char *path)
+{
+        g_autofree char *basename = NULL;
+
+        basename = g_path_get_basename (path);
+        return g_strconcat ("http://mclasen.fedorapeople.org/recipes/images/";, basename, NULL);
+}
+
+static char *
+get_thumbnail_url (const char *path)
+{
+        g_autofree char *basename = NULL;
+
+        basename = g_path_get_basename (path);
+        return g_strconcat ("http://mclasen.fedorapeople.org/recipes/thumbnails/";, basename, NULL);
+}
+
+static char *
+get_image_cache_path (const char *path)
+{
+        char *filename;
+        g_autofree char *cache_dir = NULL;
+        g_autofree char *basename = NULL;
+
+        basename = g_path_get_basename (path);
+        filename = g_build_filename (g_get_user_cache_dir (), PACKAGE_NAME, "images", basename, NULL);
+        cache_dir = g_path_get_dirname (filename);
+        g_mkdir_with_parents (cache_dir, 0755);
+
+        return filename;
+}
+
+static char *
+get_thumbnail_cache_path (const char *path)
+{
+        char *filename;
+        g_autofree char *cache_dir = NULL;
+        g_autofree char *basename = NULL;
+
+        basename = g_path_get_basename (path);
+        filename = g_build_filename (g_get_user_cache_dir (), PACKAGE_NAME, "thumbnails", basename, NULL);
+        cache_dir = g_path_get_dirname (filename);
+        g_mkdir_with_parents (cache_dir, 0755);
+
+        return filename;
+}
+
+static void
+set_image (SoupSession *session,
+           SoupMessage *msg,
+           gpointer     data)
+{
+        GrImage *ri = data;
+        g_autofree char *cache_path = NULL;
+        GdkPixbuf *pixbuf;
+        GList *l;
+
+        l = ri->pending;
+        while (l) {
+                GList *next = l->next;
+                TaskData *td = l->data;
+
+                if (g_cancellable_is_cancelled (td->cancellable)) {
+                        ri->pending = g_list_remove (ri->pending, td);
+                        task_data_free (td);
+                }
+                l = next;
+        }
+
+        if (msg->status_code == SOUP_STATUS_CANCELLED || ri->session == NULL) {
+                g_debug ("Message cancelled");
+                goto error;
+        }
+
+        if (msg->status_code != SOUP_STATUS_OK) {
+                g_debug ("Status not ok: %d", msg->status_code);
+                goto out;
+        }
+
+        if (msg == ri->thumbnail_message) {
+                if (ri->image_message == NULL) // already got the image, ignore the thumbnail
+                        goto out;
+                cache_path = get_thumbnail_cache_path (ri->path);
+        }
+        else {
+                cache_path = get_image_cache_path (ri->path);
+        }
+
+        g_debug ("Saving image to %s", cache_path);
+        if (!g_file_set_contents (cache_path, msg->response_body->data, msg->response_body->length, NULL)) {
+                g_debug ("Saving image to %s failed", cache_path);
+                goto out;
+        }
+
+        g_debug ("Loading image for %s", ri->path);
+
+        for (l = ri->pending; l; l = l->next) {
+                TaskData *td = l->data;
+
+                if (msg == ri->thumbnail_message) {
+                        g_autoptr(GdkPixbuf) tmp = NULL;
+
+                        tmp = load_pixbuf (cache_path, 120, 120 * td->height / td->width, td->fit);
+
+                        pixbuf = gdk_pixbuf_scale_simple (tmp, td->width, td->height, GDK_INTERP_BILINEAR);
+                        pixbuf_blur (pixbuf, 5, 3);
+                }
+                else {
+                        pixbuf = load_pixbuf (cache_path, td->width, td->height, td->fit);
+                }
+                if (pixbuf)
+                        td->callback (ri, pixbuf, td->data);
+                else {
+                        g_message ("Failed to load %s", ri->path);
+                        break;
+                }
+        }
+
+out:
+        if (msg == ri->thumbnail_message)
+                g_clear_object (&ri->thumbnail_message);
+        else
+                g_clear_object (&ri->image_message);
+
+        if (ri->thumbnail_message || ri->image_message)
+                return;
+
+error:
+        g_list_free_full (ri->pending, task_data_free);
+        ri->pending = NULL;
+}
+
+void
+gr_image_load (GrImage         *ri,
+               int              width,
+               int              height,
+               gboolean         fit,
+               GCancellable    *cancellable,
+               GrImageCallback  callback,
+               gpointer         data)
+{
+        TaskData *td;
+        g_autofree char *cache_path = NULL;
+        g_autofree char *thumbnail_cache_path = NULL;
+        g_autoptr(GdkPixbuf) pixbuf = NULL;
+
+        if (ri->path[0] == '/') {
+                pixbuf = load_pixbuf (ri->path, width, height, fit);
+                if (pixbuf) {
+                        g_debug ("Use local image for %s", ri->path);
+                        callback (ri, pixbuf, data);
+                        return;
+                }
+        }
+
+        cache_path = get_image_cache_path (ri->path);
+        pixbuf = load_pixbuf (cache_path, width, height, fit);
+
+        if (pixbuf) {
+                g_debug ("Use cached image for %s", ri->path);
+                callback (ri, pixbuf, data);
+                return;
+        }
+
+        td = g_new0 (TaskData, 1);
+        td->width = width;
+        td->height = height;
+        td->fit = fit;
+        td->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+        td->callback = callback;
+        td->data = data;
+
+        ri->pending = g_list_prepend (ri->pending, td);
+
+        thumbnail_cache_path = get_thumbnail_cache_path (ri->path);
+        pixbuf = load_pixbuf (thumbnail_cache_path, 120, 120 * height / width, fit);
+        if (pixbuf) {
+                g_autoptr(GdkPixbuf) blurred = NULL;
+                g_debug ("Use cached thumbnail for %s", ri->path);
+
+                blurred = gdk_pixbuf_scale_simple (pixbuf, width, height, GDK_INTERP_BILINEAR);
+                pixbuf_blur (blurred, 5, 3);
+                callback (ri, blurred, data);
+        }
+        else if (width > 240 && ri->thumbnail_message == NULL) {
+                g_autofree char *url = NULL;
+                g_autoptr(SoupURI) base_uri = NULL;
+
+                url = get_thumbnail_url (ri->path);
+                base_uri = soup_uri_new (url);
+                ri->thumbnail_message = soup_message_new_from_uri (SOUP_METHOD_GET, base_uri);
+                g_debug ("Load thumbnail for %s from %s", ri->path, url);
+                soup_session_queue_message (ri->session, g_object_ref (ri->thumbnail_message), set_image, 
ri);
+        }
+
+        if (ri->image_message == NULL) {
+                g_autofree char *url = NULL;
+                g_autoptr(SoupURI) base_uri = NULL;
+
+                url = get_image_url (ri->path);
+                base_uri = soup_uri_new (url);
+                ri->image_message = soup_message_new_from_uri (SOUP_METHOD_GET, base_uri);
+                g_debug ("Load image for %s from %s", ri->path, url);
+                soup_session_queue_message (ri->session, g_object_ref (ri->image_message), set_image, ri);
+        }
+}
+
+void
+gr_image_set_pixbuf (GrImage   *ri,
+                     GdkPixbuf *pixbuf,
+                     gpointer   data)
+{
+        gtk_image_set_from_pixbuf (GTK_IMAGE (data), pixbuf);
+}
+
+GdkPixbuf  *
+gr_image_load_sync (GrImage   *ri,
+                    int        width,
+                    int        height,
+                    gboolean   fit)
+{
+        GdkPixbuf *pixbuf;
+
+        if (ri->path[0] == '/') {
+                pixbuf = load_pixbuf (ri->path, width, height, fit);
+        }
+        else {
+                g_autofree char *cache_path = NULL;
+
+                cache_path = get_image_cache_path (ri->path);
+                pixbuf = load_pixbuf (cache_path, width, height, fit);
+        }
+
+        if (!pixbuf) {
+                g_autoptr(GtkIconInfo) info = NULL;
+
+                info = gtk_icon_theme_lookup_icon (gtk_icon_theme_get_default (),
+                                                   "org.gnome.Recipes",
+                                                    256,
+                                                    GTK_ICON_LOOKUP_FORCE_SIZE);
+                pixbuf = load_pixbuf (gtk_icon_info_get_filename (info), width, height, fit);
+
+        }
+
+        return pixbuf;
+}
diff --git a/src/gr-image.h b/src/gr-image.h
index f438bef..eec924b 100644
--- a/src/gr-image.h
+++ b/src/gr-image.h
@@ -21,6 +21,7 @@
 #pragma once
 
 #include <gtk/gtk.h>
+#include <libsoup/soup.h>
 
 G_BEGIN_DECLS
 
@@ -28,10 +29,31 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (GrImage, gr_image, GR, IMAGE, GObject)
 
-GrImage    *gr_image_new      (const char *path);
-void        gr_image_set_path (GrImage    *image,
-                               const char *path);
-const char *gr_image_get_path (GrImage    *image);
+GrImage    *gr_image_new         (SoupSession       *session,
+                                  const char        *path);
+void        gr_image_set_path    (GrImage           *image,
+                                  const char        *path);
+const char *gr_image_get_path    (GrImage           *image);
+GdkPixbuf  *gr_image_load_sync   (GrImage           *image,
+                                  int                 width,
+                                  int                 height,
+                                  gboolean            fit);
+
+typedef void (*GrImageCallback) (GrImage   *ri,
+                                 GdkPixbuf *pixbuf,
+                                 gpointer   data);
+
+void        gr_image_load        (GrImage            *ri,
+                                  int                 width,
+                                  int                 height,
+                                  gboolean            fit,
+                                  GCancellable       *cancellable,
+                                  GrImageCallback     callback,
+                                  gpointer            data);
+
+void        gr_image_set_pixbuf  (GrImage   *ri,
+                                  GdkPixbuf *pixbuf,
+                                  gpointer   data);
 
 GPtrArray *gr_image_array_new (void);
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]