[gnome-photos/wip/rishi/thumbnailer: 1/12] Add an out-of-process custom thumbnailer
- From: Debarshi Ray <debarshir src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-photos/wip/rishi/thumbnailer: 1/12] Add an out-of-process custom thumbnailer
- Date: Tue, 14 Feb 2017 16:09:03 +0000 (UTC)
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]