[gnome-photos/wip/rishi/thumbnailer: 1/12] Add an out-of-process custom thumbnailer



commit 3aa7b9fd9b6ff2d54d4574c78eb501b3f1d63a46
Author: Debarshi Ray <debarshir gnome org>
Date:   Thu Feb 9 20:24:37 2017 +0100

    Add an out-of-process custom thumbnailer
    
    For a few reasons we cannot continue using GIO,
    GnomeDesktopThumbnailFactory and the desktop-wide thumbnail cache.
    
    First and foremost, we want the thumbnails to reflect the edited state
    of the items. Being a non-destructive editor, we never modify the
    original pixel source. It means the thumbnailer needs to thumbnail the
    item's source file and the graph of operations that have been applied
    on top of it. It is not reasonable to expect the generic desktop-wide
    thumbnailers to be able to handle our GEGL graph.
    
    Other non-destructive editors like DarkTable and Shotwell also have
    their own thumbnailers and caches separate from the desktop's. So,
    there is prior art in this direction and not an utterly crazy thing to
    do.
    
    As a positive side-effect, we are freed from GIO's reluctance to let an
    application look for a 'large' high resolution thumbnail without
    falling back to an inferior version.
    
    This thumbnailer runs in a separate process and communicates
    peer-to-peer over D-Bus to talk to the application. The same process
    can be used to generate multiple thumbnails and it lingers for a while
    before terminating. The cache is located at:
      ~/.cache/gnome-photos/thumbnails/<size>-<generation>
    
    Currently, <size> is set to 512 and <generation> is 0. The <generation>
    tag can be used to forcibly invalidate the thumbnail cache to handle
    thumbnailer bugs and updates.
    
    In future, we should look at sandboxing the thumbnailer to protect it
    from malicious image data.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=690255

 src/Makefile.am                 |   82 +++++
 src/photos-thumbnailer-dbus.xml |   38 ++
 src/photos-thumbnailer-main.c   |   69 ++++
 src/photos-thumbnailer.c        |  760 +++++++++++++++++++++++++++++++++++++++
 src/photos-thumbnailer.h        |   35 ++
 5 files changed, 984 insertions(+), 0 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 75a9264..4051b05 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,5 +1,7 @@
 bin_PROGRAMS = gnome-photos
 
+libexec_PROGRAMS = gnome-photos-thumbnailer
+
 gnome_photos_built_sources = \
        photos-about-data.c \
        photos-about-data.h \
@@ -267,8 +269,63 @@ gnome_photos_SOURCES = \
        photos-main.c \
        $(NULL)
 
+gnome_photos_thumbnailer_built_sources = \
+       photos-thumbnailer-dbus.c \
+       photos-thumbnailer-dbus.h \
+       photos-enums.c \
+       photos-enums.h \
+       $(NULL)
+
+nodist_gnome_photos_thumbnailer_SOURCES = \
+       $(gnome_photos_thumbnailer_built_sources) \
+       $(NULL)
+
+gnome_photos_thumbnailer_SOURCES = \
+       egg-counter.c \
+       egg-counter.h \
+       photos-debug.c \
+       photos-debug.h \
+       photos-error.c \
+       photos-error.h \
+       photos-gegl.c \
+       photos-gegl.h \
+       photos-glib.c \
+       photos-glib.h \
+       photos-jpeg-count.c \
+       photos-jpeg-count.h \
+       photos-operation-insta-common.h \
+       photos-operation-insta-curve.c \
+       photos-operation-insta-curve.h \
+       photos-operation-insta-filter.c \
+       photos-operation-insta-filter.h \
+       photos-operation-insta-hefe.c \
+       photos-operation-insta-hefe.h \
+       photos-operation-insta-hefe-curve.c \
+       photos-operation-insta-hefe-curve.h \
+       photos-operation-insta-hefe-vignette.c \
+       photos-operation-insta-hefe-vignette.h \
+       photos-operation-jpg-guess-sizes.c \
+       photos-operation-jpg-guess-sizes.h \
+       photos-operation-png-guess-sizes.c \
+       photos-operation-png-guess-sizes.h \
+       photos-operation-saturation.c \
+       photos-operation-saturation.h \
+       photos-pipeline.c \
+       photos-pipeline.h \
+       photos-pixbuf.c \
+       photos-pixbuf.h \
+       photos-png-count.c \
+       photos-png-count.h \
+       photos-quarks.c \
+       photos-quarks.h \
+       photos-thumbnailer.c \
+       photos-thumbnailer.h \
+       photos-thumbnailer-main.c \
+       $(NULL)
+
 BUILT_SOURCES = \
        $(gnome_photos_built_sources) \
+       $(gnome_photos_thumbnailer_built_sources) \
        $(NULL)
 
 EXTRA_DIST = \
@@ -287,6 +344,7 @@ EXTRA_DIST = \
        photos-selection-toolbar.ui \
        photos-share-dialog.ui \
        photos-gom-miner.xml \
+       photos-thumbnailer-dbus.xml \
        photos-tracker-extract-priority.xml \
        photos-tracker-resources.xml \
        photos-dleyna-renderer-device.xml \
@@ -349,6 +407,22 @@ gnome_photos_LDADD = \
        $(top_builddir)/libgd/libgd.la \
        $(NULL)
 
+gnome_photos_thumbnailer_LDFLAGS = \
+       $(WARN_LDFLAGS) \
+       $(NULL)
+
+gnome_photos_thumbnailer_LDADD = \
+       $(BABL_LIBS) \
+       $(GEGL_LIBS) \
+       $(GDK_PIXBUF_LIBS) \
+       $(GIO_LIBS) \
+       $(GLIB_LIBS) \
+       $(JPEG_LIBS) \
+       $(PNG_LIBS) \
+       $(LIBM) \
+       $(LIBRT) \
+       $(NULL)
+
 CLEANFILES = \
        $(BUILT_SOURCES) \
        stamp-photos-about-data.h \
@@ -495,6 +569,14 @@ photos-shell-search-provider2.h photos-shell-search-provider2.c: org.gnome.Shell
                --interface-prefix org.gnome.Shell. \
                $<
 
+photos-thumbnailer-dbus.h photos-thumbnailer-dbus.c: photos-thumbnailer-dbus.xml
+       $(AM_V_GEN)gdbus-codegen \
+               --c-namespace Photos \
+               --generate-c-code photos-thumbnailer-dbus \
+               --interface-prefix org.gnome.Photos. \
+               --annotate "org.gnome.Photos.Thumbnailer" org.gtk.GDBus.C.Name ThumbnailerDBus \
+               $<
+
 photos-tracker-extract-priority.h photos-tracker-extract-priority.c: photos-tracker-extract-priority.xml
        $(AM_V_GEN)gdbus-codegen \
                --c-namespace Tracker \
diff --git a/src/photos-thumbnailer-dbus.xml b/src/photos-thumbnailer-dbus.xml
new file mode 100644
index 0000000..6f9b30d
--- /dev/null
+++ b/src/photos-thumbnailer-dbus.xml
@@ -0,0 +1,38 @@
+<!DOCTYPE node PUBLIC
+"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd";>
+
+<!--
+ Photos - access, organize and share your photos on GNOME
+ Copyright © 2017 Red Hat, Inc.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+-->
+
+<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd";>
+  <interface name="org.gnome.Photos.Thumbnailer">
+    <method name="GenerateThumbnail">
+      <arg name="uri" type="s" direction="in" />
+      <arg name="mime_type" type="s" direction="in" />
+      <arg name="orientation" type="s" direction="in" />
+      <arg name="original_height" type="x" direction="in" />
+      <arg name="original_width" type="x" direction="in" />
+      <arg name="pipeline_uri" type="s" direction="in" />
+      <arg name="thumbnail_path" type="s" direction="in" />
+      <arg name="thumbnail_size" type="i" direction="in" />
+    </method>
+  </interface>
+</node>
diff --git a/src/photos-thumbnailer-main.c b/src/photos-thumbnailer-main.c
new file mode 100644
index 0000000..10126fe
--- /dev/null
+++ b/src/photos-thumbnailer-main.c
@@ -0,0 +1,69 @@
+/*
+ * Photos - access, organize and share your photos on GNOME
+ * Copyright © 2017 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+
+#include "config.h"
+
+#include <locale.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "egg-counter.h"
+#include "photos-debug.h"
+#include "photos-thumbnailer.h"
+
+
+static void
+photos_thumbnailer_main_counter_arena_foreach (EggCounter *counter, gpointer user_data)
+{
+  gint64 count;
+
+  count = egg_counter_get (counter);
+  photos_debug (PHOTOS_DEBUG_MEMORY, "%s.%s = %" G_GINT64_FORMAT, counter->category, counter->name, count);
+}
+
+
+gint
+main (gint argc, gchar *argv[])
+{
+  EggCounterArena *counter_arena;
+  GApplication *app;
+  gint exit_status = 0;
+
+  setlocale (LC_ALL, "");
+
+  photos_debug_init ();
+
+  bindtextdomain (GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
+  bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+  textdomain (GETTEXT_PACKAGE);
+
+  g_set_prgname (PACKAGE_TARNAME "-thumbnailer");
+
+  app = photos_thumbnailer_new ();
+  exit_status = g_application_run (app, argc, argv);
+  g_object_unref (app);
+
+  counter_arena = egg_counter_arena_get_default ();
+  egg_counter_arena_foreach (counter_arena, photos_thumbnailer_main_counter_arena_foreach, NULL);
+
+  return exit_status;
+}
diff --git a/src/photos-thumbnailer.c b/src/photos-thumbnailer.c
new file mode 100644
index 0000000..33767c8
--- /dev/null
+++ b/src/photos-thumbnailer.c
@@ -0,0 +1,760 @@
+/*
+ * Photos - access, organize and share your photos on GNOME
+ * Copyright © 2017 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <babl/babl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+#include <glib/gi18n.h>
+
+#include "photos-debug.h"
+#include "photos-error.h"
+#include "photos-gegl.h"
+#include "photos-pipeline.h"
+#include "photos-pixbuf.h"
+#include "photos-thumbnailer.h"
+#include "photos-thumbnailer-dbus.h"
+
+
+struct _PhotosThumbnailer
+{
+  GApplication parent_instance;
+  GDBusConnection *connection;
+  PhotosThumbnailerDBus *skeleton;
+  gchar *address;
+};
+
+
+G_DEFINE_TYPE (PhotosThumbnailer, photos_thumbnailer, G_TYPE_APPLICATION);
+
+
+typedef struct _PhotosThumbnailerGenerateData PhotosThumbnailerGenerateData;
+
+struct _PhotosThumbnailerGenerateData
+{
+  GFile *file;
+  GFileOutputStream *stream;
+  GQuark orientation;
+  GdkPixbuf *pixbuf_thumbnail;
+  GeglNode *graph;
+  PhotosPipeline *pipeline;
+  gchar *thumbnail_path;
+  gint thumbnail_size;
+  gint64 original_height;
+  gint64 original_width;
+};
+
+enum
+{
+  INACTIVITY_TIMEOUT = 12000 /* ms */
+};
+
+static const GOptionEntry COMMAND_LINE_OPTIONS[] =
+{
+  { "address", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, NULL, N_("D-Bus address to use"), NULL},
+  { NULL }
+};
+
+static const gchar *THUMBNAILER_PATH = "/org/gnome/Photos/Thumbnailer";
+
+
+static void
+photos_thumbnailer_generate_data_free (PhotosThumbnailerGenerateData *data)
+{
+  g_free (data->thumbnail_path);
+  g_clear_object (&data->file);
+  g_clear_object (&data->graph);
+  g_clear_object (&data->pipeline);
+  g_clear_object (&data->pixbuf_thumbnail);
+  g_clear_object (&data->stream);
+  g_slice_free (PhotosThumbnailerGenerateData, data);
+}
+
+
+static PhotosThumbnailerGenerateData *
+photos_thumbnailer_generate_data_new (GFile *file,
+                                      GQuark orientation,
+                                      gint64 original_height,
+                                      gint64 original_width,
+                                      const gchar *thumbnail_path,
+                                      gint thumbnail_size,
+                                      GeglNode *graph)
+{
+  PhotosThumbnailerGenerateData *data;
+
+  data = g_slice_new0 (PhotosThumbnailerGenerateData);
+  data->file = g_object_ref (file);
+  data->orientation = orientation;
+  data->original_height = original_height;
+  data->original_width = original_width;
+  data->thumbnail_path = g_strdup (thumbnail_path);
+  data->thumbnail_size = thumbnail_size;
+  data->graph = g_object_ref (graph);
+
+  return data;
+}
+
+
+static gboolean
+photos_thumbnailer_authorize_authenticated_peer (PhotosThumbnailer *self,
+                                                 GIOStream *iostream,
+                                                 GCredentials *credentials)
+{
+  GCredentials *own_credentials = NULL;
+  GError *error;
+  gboolean ret_val = FALSE;
+
+  if (credentials == NULL)
+    goto out;
+
+  own_credentials = g_credentials_new ();
+
+  error = NULL;
+  if (!g_credentials_is_same_user (credentials, own_credentials, &error))
+    {
+      g_warning ("Unable to authorize peer: %s", error->message);
+      g_error_free (error);
+      goto out;
+    }
+
+  ret_val = TRUE;
+
+ out:
+  g_clear_object (&own_credentials);
+  return ret_val;
+}
+
+
+static void
+photos_thumbnailer_generate_thumbnail_stream_close (GObject *source_object,
+                                                    GAsyncResult *res,
+                                                    gpointer user_data)
+{
+  GError *error;
+  GOutputStream *stream = G_OUTPUT_STREAM (source_object);
+  GTask *task = G_TASK (user_data);
+
+  error = NULL;
+  if (!g_output_stream_close_finish (stream, res, &error))
+    {
+      g_task_return_error (task, error);
+      goto out;
+    }
+
+  g_task_return_boolean (task, TRUE);
+
+ out:
+  g_object_unref (task);
+}
+
+
+static void
+photos_thumbnailer_generate_thumbnail_save_to_stream (GObject *source_object,
+                                                      GAsyncResult *res,
+                                                      gpointer user_data)
+{
+  GCancellable *cancellable;
+  GError *error;
+  GTask *task = G_TASK (user_data);
+  PhotosThumbnailerGenerateData *data;
+
+  cancellable = g_task_get_cancellable (task);
+  data = g_task_get_task_data (task);
+
+  error = NULL;
+  if (!gdk_pixbuf_save_to_stream_finish (res, &error))
+    {
+      g_task_return_error (task, error);
+      goto out;
+    }
+
+  g_output_stream_close_async (G_OUTPUT_STREAM (data->stream),
+                               G_PRIORITY_DEFAULT,
+                               cancellable,
+                               photos_thumbnailer_generate_thumbnail_stream_close,
+                               g_object_ref (task));
+
+ out:
+  g_object_unref (task);
+}
+
+
+static void
+photos_thumbnailer_generate_thumbnail_replace (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+  GCancellable *cancellable;
+  GError *error;
+  GFile *thumbnail_file = G_FILE (source_object);
+  GFileOutputStream *stream = NULL;
+  GTask *task = G_TASK (user_data);
+  PhotosThumbnailerGenerateData *data;
+  const gchar *prgname;
+  gchar *original_height_str = NULL;
+  gchar *original_width_str = NULL;
+  gchar *uri = NULL;
+
+  cancellable = g_task_get_cancellable (task);
+  data = g_task_get_task_data (task);
+
+  error = NULL;
+  stream = g_file_replace_finish (thumbnail_file, res, &error);
+  if (error != NULL)
+    {
+      g_task_return_error (task, error);
+      goto out;
+    }
+
+  g_assert_null (data->stream);
+  data->stream = g_object_ref (stream);
+
+  original_height_str = g_strdup_printf ("%" G_GINT64_FORMAT, data->original_height);
+  original_width_str = g_strdup_printf ("%" G_GINT64_FORMAT, data->original_width);
+  prgname = g_get_prgname ();
+  uri = g_file_get_uri (data->file);
+  gdk_pixbuf_save_to_stream_async (data->pixbuf_thumbnail,
+                                   G_OUTPUT_STREAM (stream),
+                                   "png",
+                                   cancellable,
+                                   photos_thumbnailer_generate_thumbnail_save_to_stream,
+                                   g_object_ref (task),
+                                   "tEXt::Software", prgname,
+                                   "tEXt::Thumb::URI", uri,
+                                   "tEXt::Thumb::Image::Height", original_height_str,
+                                   "tEXt::Thumb::Image::Width", original_width_str,
+                                   NULL);
+
+ out:
+  g_free (original_height_str);
+  g_free (original_width_str);
+  g_free (uri);
+  g_clear_object (&stream);
+  g_object_unref (task);
+}
+
+
+static void
+photos_thumbnailer_generate_thumbnail_process (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+  GCancellable *cancellable;
+  GError *error;
+  GFile *thumbnail_file = NULL;
+  GTask *task = G_TASK (user_data);
+  GeglProcessor *processor = GEGL_PROCESSOR (source_object);
+  PhotosThumbnailerGenerateData *data;
+  gchar *thumbnail_dir = NULL;
+  gdouble zoom = 0.0;
+  gint pixbuf_height;
+  gint pixbuf_width;
+  gint pixbuf_zoomed_height;
+  gint pixbuf_zoomed_width;
+
+  cancellable = g_task_get_cancellable (task);
+  data = g_task_get_task_data (task);
+
+  error = NULL;
+  if (!photos_gegl_processor_process_finish (processor, res, &error))
+    {
+      g_task_return_error (task, error);
+      goto out;
+    }
+
+  pixbuf_height = gdk_pixbuf_get_height (data->pixbuf_thumbnail);
+  pixbuf_width = gdk_pixbuf_get_width (data->pixbuf_thumbnail);
+
+  if (pixbuf_height > pixbuf_width && pixbuf_height > data->thumbnail_size)
+    {
+      zoom = (gdouble) pixbuf_height / (gdouble) data->thumbnail_size;
+      pixbuf_zoomed_height = data->thumbnail_size;
+      pixbuf_zoomed_width = (gint) (zoom * (gdouble) pixbuf_width + 0.5);
+    }
+  else if (pixbuf_height <= pixbuf_width && pixbuf_width > data->thumbnail_size)
+    {
+      zoom = (gdouble) pixbuf_width / (gdouble) data->thumbnail_size;
+      pixbuf_zoomed_height = (gint) (zoom * (gdouble) pixbuf_height + 0.5);
+      pixbuf_zoomed_width = data->thumbnail_size;
+    }
+
+  if (zoom > 0.0)
+    {
+      GdkPixbuf *pixbuf_scaled = NULL;
+
+      pixbuf_scaled = gdk_pixbuf_scale_simple (data->pixbuf_thumbnail,
+                                               pixbuf_zoomed_width,
+                                               pixbuf_zoomed_height,
+                                               GDK_INTERP_BILINEAR);
+
+      g_set_object (&data->pixbuf_thumbnail, pixbuf_scaled);
+      g_object_unref (pixbuf_scaled);
+    }
+
+  thumbnail_dir = g_path_get_dirname (data->thumbnail_path);
+  g_mkdir_with_parents (thumbnail_dir, 0700);
+
+  thumbnail_file = g_file_new_for_path (data->thumbnail_path);
+
+  photos_debug (PHOTOS_DEBUG_THUMBNAILER, "Saving thumbnail to %s", data->thumbnail_path);
+  g_file_replace_async (thumbnail_file,
+                        NULL,
+                        FALSE,
+                        G_FILE_CREATE_PRIVATE | G_FILE_CREATE_REPLACE_DESTINATION,
+                        G_PRIORITY_DEFAULT,
+                        cancellable,
+                        photos_thumbnailer_generate_thumbnail_replace,
+                        g_object_ref (task));
+
+ out:
+  g_free (thumbnail_dir);
+  g_clear_object (&thumbnail_file);
+  g_object_unref (task);
+}
+
+
+static void
+photos_thumbnailer_generate_thumbnail_pixbuf (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+  GCancellable *cancellable;
+  GError *error;
+  GTask *task = G_TASK (user_data);
+  GdkPixbuf *pixbuf = NULL;
+  GeglNode *orientation;
+  GeglNode *pipeline_node;
+  GeglNode *pixbuf_source;
+  GeglNode *save_pixbuf;
+  GeglProcessor *processor = NULL;
+  PhotosThumbnailerGenerateData *data;
+
+  cancellable = g_task_get_cancellable (task);
+  data = g_task_get_task_data (task);
+
+  error = NULL;
+  pixbuf = photos_pixbuf_new_from_file_at_size_finish (res, &error);
+  if (error != NULL)
+    {
+      g_task_return_error (task, error);
+      goto out;
+    }
+
+  g_assert_null (data->pixbuf_thumbnail);
+
+  pixbuf_source = gegl_node_new_child (data->graph, "operation", "gegl:pixbuf", "pixbuf", pixbuf, NULL);
+  orientation = photos_gegl_create_orientation_node (data->graph, data->orientation);
+  pipeline_node = photos_pipeline_get_graph (data->pipeline);
+  save_pixbuf = gegl_node_new_child (data->graph,
+                                     "operation", "gegl:save-pixbuf",
+                                     "pixbuf", &data->pixbuf_thumbnail,
+                                     NULL);
+
+  gegl_node_link_many (pixbuf_source, orientation, pipeline_node, save_pixbuf, NULL);
+
+  processor = gegl_node_new_processor (save_pixbuf, NULL);
+  photos_gegl_processor_process_async (processor,
+                                       cancellable,
+                                       photos_thumbnailer_generate_thumbnail_process,
+                                       g_object_ref (task));
+
+ out:
+  g_clear_object (&pixbuf);
+  g_clear_object (&processor);
+  g_object_unref (task);
+}
+
+
+static void
+photos_thumbnailer_generate_thumbnail_pipeline (GObject *source_object, GAsyncResult *res, gpointer 
user_data)
+{
+  GCancellable *cancellable;
+  GError *error;
+  GTask *task = G_TASK (user_data);
+  PhotosPipeline *pipeline = NULL;
+  PhotosThumbnailerGenerateData *data;
+  gchar *path = NULL;
+  gdouble height;
+  gdouble width;
+  gdouble x;
+  gdouble y;
+  gint load_height;
+  gint load_width;
+
+  cancellable = g_task_get_cancellable (task);
+  data = g_task_get_task_data (task);
+
+  error = NULL;
+  pipeline = photos_pipeline_new_finish (res, &error);
+  if (error != NULL)
+    {
+      g_task_return_error (task, error);
+      goto out;
+    }
+
+  g_assert_null (data->pipeline);
+  data->pipeline = g_object_ref (pipeline);
+
+  if (photos_pipeline_get (pipeline, "gegl:crop", "height", &height, "width", &width, "x", &x, "y", &y, 
NULL))
+    {
+      if (height < 0.0 || width < 0.0 || x < 0.0 || y < 0.0)
+        {
+          gchar *uri = NULL;
+
+          uri = g_file_get_uri (data->file);
+          g_warning ("Unable to crop the thumbnail for %s: Invalid parameters", uri);
+
+          photos_pipeline_remove (pipeline, "gegl:crop");
+          load_height = data->thumbnail_size;
+          load_width = data->thumbnail_size;
+
+          g_free (uri);
+        }
+      else
+        {
+          load_height = (gint64) data->original_height;
+          load_width = (gint64) data->original_width;
+        }
+    }
+  else
+    {
+      load_height = data->thumbnail_size;
+      load_width = data->thumbnail_size;
+    }
+
+  path = g_file_get_path (data->file);
+  if (!g_file_is_native (data->file))
+    {
+      gchar *uri = NULL;
+
+      uri = g_file_get_uri (data->file);
+      photos_debug (PHOTOS_DEBUG_NETWORK, "Downloading %s (%s)", uri, path);
+      g_free (uri);
+    }
+
+  photos_pixbuf_new_from_file_at_size_async (path,
+                                             load_width,
+                                             load_height,
+                                             cancellable,
+                                             photos_thumbnailer_generate_thumbnail_pixbuf,
+                                             g_object_ref (task));
+
+ out:
+  g_free (path);
+  g_clear_object (&pipeline);
+  g_object_unref (task);
+}
+
+
+static void
+photos_thumbnailer_generate_thumbnail_async (PhotosThumbnailer *self,
+                                             const gchar *uri,
+                                             const gchar *mime_type,
+                                             const gchar *orientation,
+                                             gint64 original_height,
+                                             gint64 original_width,
+                                             const gchar *pipeline_uri,
+                                             const gchar *thumbnail_path,
+                                             gint thumbnail_size,
+                                             GCancellable *cancellable,
+                                             GAsyncReadyCallback callback,
+                                             gpointer user_data)
+{
+  GFile *file = NULL;
+  GTask *task = NULL;
+  GQuark orientation_quark;
+  GeglNode *graph;
+  PhotosThumbnailerGenerateData *data;
+
+  g_return_if_fail (PHOTOS_IS_THUMBNAILER (self));
+  g_return_if_fail (uri != NULL && uri[0] != '\0');
+  g_return_if_fail (mime_type != NULL && mime_type[0] != '\0');
+  g_return_if_fail (orientation != NULL && orientation[0] != '\0');
+  g_return_if_fail (thumbnail_path != NULL && thumbnail_path[0] != '\0');
+  g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+  file = g_file_new_for_uri (uri);
+  orientation_quark = g_quark_from_string (orientation);
+  graph = gegl_node_new ();
+  data = photos_thumbnailer_generate_data_new (file,
+                                               orientation_quark,
+                                               original_height,
+                                               original_width,
+                                               thumbnail_path,
+                                               thumbnail_size,
+                                               graph);
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, photos_thumbnailer_generate_thumbnail_async);
+  g_task_set_task_data (task, data, (GDestroyNotify) photos_thumbnailer_generate_data_free);
+
+  photos_pipeline_new_async (graph,
+                             pipeline_uri,
+                             cancellable,
+                             photos_thumbnailer_generate_thumbnail_pipeline,
+                             g_object_ref (task));
+
+  g_object_unref (file);
+  g_object_unref (graph);
+  g_object_unref (task);
+}
+
+
+static gboolean
+photos_thumbnailer_generate_thumbnail_finish (PhotosThumbnailer *self, GAsyncResult *res, GError **error)
+{
+  GTask *task = G_TASK (res);
+
+  g_return_val_if_fail (g_task_is_valid (res, self), FALSE);
+  g_return_val_if_fail (g_task_get_source_tag (task) == photos_thumbnailer_generate_thumbnail_async, FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  return g_task_propagate_boolean (task, error);
+}
+
+
+static void
+photos_thumbnailer_handle_generate_thumbnail_generate_thumbnail (GObject *source_object,
+                                                                 GAsyncResult *res,
+                                                                 gpointer user_data)
+{
+  PhotosThumbnailer *self = PHOTOS_THUMBNAILER (source_object);
+  GDBusMethodInvocation *invocation = G_DBUS_METHOD_INVOCATION (user_data);
+  GError *error;
+
+  error = NULL;
+  if (!photos_thumbnailer_generate_thumbnail_finish (self, res, &error))
+    {
+      g_dbus_method_invocation_take_error (invocation, error);
+      goto out;
+    }
+
+  photos_thumbnailer_dbus_complete_generate_thumbnail (self->skeleton, invocation);
+
+ out:
+  photos_debug (PHOTOS_DEBUG_THUMBNAILER, "Completed GenerateThumbnail");
+  g_application_release (G_APPLICATION (self));
+  g_object_unref (invocation);
+}
+
+
+static gboolean
+photos_thumbnailer_handle_generate_thumbnail (PhotosThumbnailer *self,
+                                              GDBusMethodInvocation *invocation,
+                                              const gchar *uri,
+                                              const gchar *mime_type,
+                                              const gchar *orientation,
+                                              gint64 original_height,
+                                              gint64 original_width,
+                                              const gchar *pipeline_uri,
+                                              const gchar *thumbnail_path,
+                                              gint thumbnail_size)
+{
+  g_return_val_if_fail (PHOTOS_IS_THUMBNAILER (self), FALSE);
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), FALSE);
+  g_return_val_if_fail (uri != NULL && uri[0] != '\0', FALSE);
+  g_return_val_if_fail (mime_type != NULL && mime_type[0] != '\0', FALSE);
+  g_return_val_if_fail (orientation != NULL && orientation[0] != '\0', FALSE);
+  g_return_val_if_fail (pipeline_uri != NULL, FALSE);
+  g_return_val_if_fail (thumbnail_path != NULL && thumbnail_path[0] != '\0', FALSE);
+
+  photos_debug (PHOTOS_DEBUG_THUMBNAILER, "Handling GenerateThumbnail for %s", uri);
+
+  if (pipeline_uri[0] == '\0')
+    pipeline_uri = NULL;
+
+  g_application_hold (G_APPLICATION (self));
+  photos_thumbnailer_generate_thumbnail_async (self,
+                                               uri,
+                                               mime_type,
+                                               orientation,
+                                               original_height,
+                                               original_width,
+                                               pipeline_uri,
+                                               thumbnail_path,
+                                               thumbnail_size,
+                                               NULL,
+                                               
photos_thumbnailer_handle_generate_thumbnail_generate_thumbnail,
+                                               g_object_ref (invocation));
+
+  return TRUE;
+}
+
+
+static gboolean
+photos_thumbnailer_dbus_register (GApplication *application,
+                                  GDBusConnection *connection,
+                                  const gchar *object_path,
+                                  GError **error)
+{
+  PhotosThumbnailer *self = PHOTOS_THUMBNAILER (application);
+  GDBusAuthObserver *observer = NULL;
+  gboolean ret_val = FALSE;
+
+  if (!G_APPLICATION_CLASS (photos_thumbnailer_parent_class)->dbus_register (application,
+                                                                             connection,
+                                                                             object_path,
+                                                                             error))
+    goto out;
+
+  observer = g_dbus_auth_observer_new ();
+  g_signal_connect_swapped (observer,
+                            "authorize-authenticated-peer",
+                            G_CALLBACK (photos_thumbnailer_authorize_authenticated_peer),
+                            self);
+
+  self->connection = g_dbus_connection_new_for_address_sync (self->address,
+                                                             G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
+                                                             observer,
+                                                             NULL,
+                                                             error);
+  if (self->connection == NULL)
+    goto out;
+
+  if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self->skeleton),
+                                         self->connection,
+                                         THUMBNAILER_PATH,
+                                         error))
+    {
+      g_clear_object (&self->skeleton);
+      goto out;
+    }
+
+  ret_val = TRUE;
+
+ out:
+  g_clear_object (&observer);
+  return ret_val;
+}
+
+
+static void
+photos_thumbnailer_dbus_unregister (GApplication *application,
+                                    GDBusConnection *connection,
+                                    const gchar *object_path)
+{
+  PhotosThumbnailer *self = PHOTOS_THUMBNAILER (application);
+
+  if (self->skeleton != NULL)
+    {
+      g_dbus_interface_skeleton_unexport_from_connection (G_DBUS_INTERFACE_SKELETON (self->skeleton),
+                                                          self->connection);
+      g_clear_object (&self->skeleton);
+    }
+
+  G_APPLICATION_CLASS (photos_thumbnailer_parent_class)->dbus_unregister (application, connection, 
object_path);
+}
+
+
+static gint
+photos_thumbnailer_handle_local_options (GApplication *application, GVariantDict *options)
+{
+  PhotosThumbnailer *self = PHOTOS_THUMBNAILER (application);
+  gint ret_val = EXIT_FAILURE;
+
+  if (g_variant_dict_lookup (options, "address", "s", &self->address))
+    ret_val = -1;
+
+  return ret_val;
+}
+
+
+static void
+photos_thumbnailer_shutdown (GApplication *application)
+{
+  photos_debug (PHOTOS_DEBUG_THUMBNAILER, "Thumbnailer exiting");
+
+  G_APPLICATION_CLASS (photos_thumbnailer_parent_class)->shutdown (application);
+}
+
+
+static void
+photos_thumbnailer_startup (GApplication *application)
+{
+  G_APPLICATION_CLASS (photos_thumbnailer_parent_class)->startup (application);
+
+  gegl_init (NULL, NULL);
+  photos_debug (PHOTOS_DEBUG_THUMBNAILER, "Thumbnailer ready");
+}
+
+
+static void
+photos_thumbnailer_dispose (GObject *object)
+{
+  PhotosThumbnailer *self = PHOTOS_THUMBNAILER (object);
+
+  g_clear_object (&self->connection);
+  g_clear_object (&self->skeleton);
+
+  G_OBJECT_CLASS (photos_thumbnailer_parent_class)->dispose (object);
+}
+
+
+static void
+photos_thumbnailer_finalize (GObject *object)
+{
+  PhotosThumbnailer *self = PHOTOS_THUMBNAILER (object);
+
+  g_free (self->address);
+
+  if (g_application_get_is_registered (G_APPLICATION (self)))
+    gegl_exit ();
+
+  G_OBJECT_CLASS (photos_thumbnailer_parent_class)->finalize (object);
+}
+
+
+static void
+photos_thumbnailer_init (PhotosThumbnailer *self)
+{
+  photos_gegl_ensure_builtins ();
+
+  self->skeleton = photos_thumbnailer_dbus_skeleton_new ();
+  g_signal_connect_swapped (self->skeleton,
+                            "handle-generate-thumbnail",
+                            G_CALLBACK (photos_thumbnailer_handle_generate_thumbnail),
+                            self);
+
+  g_application_add_main_option_entries (G_APPLICATION (self), COMMAND_LINE_OPTIONS);
+}
+
+
+static void
+photos_thumbnailer_class_init (PhotosThumbnailerClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+  GApplicationClass *application_class = G_APPLICATION_CLASS (class);
+
+  object_class->dispose = photos_thumbnailer_dispose;
+  object_class->finalize = photos_thumbnailer_finalize;
+  application_class->dbus_register = photos_thumbnailer_dbus_register;
+  application_class->dbus_unregister = photos_thumbnailer_dbus_unregister;
+  application_class->handle_local_options = photos_thumbnailer_handle_local_options;
+  application_class->shutdown = photos_thumbnailer_shutdown;
+  application_class->startup = photos_thumbnailer_startup;
+}
+
+
+GApplication *
+photos_thumbnailer_new (void)
+{
+  return g_object_new (PHOTOS_TYPE_THUMBNAILER,
+                       "flags", G_APPLICATION_IS_SERVICE | G_APPLICATION_NON_UNIQUE,
+                       "inactivity-timeout", INACTIVITY_TIMEOUT,
+                       NULL);
+}
diff --git a/src/photos-thumbnailer.h b/src/photos-thumbnailer.h
new file mode 100644
index 0000000..f4d287a
--- /dev/null
+++ b/src/photos-thumbnailer.h
@@ -0,0 +1,35 @@
+/*
+ * Photos - access, organize and share your photos on GNOME
+ * Copyright © 2017 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef PHOTOS_THUMBNAILER_H
+#define PHOTOS_THUMBNAILER_H
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define PHOTOS_TYPE_THUMBNAILER (photos_thumbnailer_get_type ())
+G_DECLARE_FINAL_TYPE (PhotosThumbnailer, photos_thumbnailer, PHOTOS, THUMBNAILER, GApplication);
+
+GApplication          *photos_thumbnailer_new                    (void);
+
+G_END_DECLS
+
+#endif /* PHOTOS_THUMBNAILER_H */


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