1 /*
2  * Photos - access, organize and share your photos on GNOME
3  * Copyright © 2017 – 2019 Red Hat, Inc.
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 
20 #include "config.h"
21 
22 #include <locale.h>
23 #include <stdlib.h>
24 
25 #include <gdk-pixbuf/gdk-pixbuf.h>
26 #include <gegl.h>
27 #include <glib/gi18n.h>
28 
29 #include "photos-debug.h"
30 #include "photos-error.h"
31 #include "photos-gegl.h"
32 #include "photos-pipeline.h"
33 #include "photos-pixbuf.h"
34 #include "photos-thumbnailer.h"
35 #include "photos-thumbnailer-dbus.h"
36 
37 
38 struct _PhotosThumbnailer
39 {
40   GApplication parent_instance;
41   GDBusConnection *connection;
42   GHashTable *cancellables;
43   PhotosThumbnailerDBus *skeleton;
44   gchar *address;
45 };
46 
47 
48 G_DEFINE_TYPE (PhotosThumbnailer, photos_thumbnailer, G_TYPE_APPLICATION);
49 
50 
51 typedef struct _PhotosThumbnailerGenerateData PhotosThumbnailerGenerateData;
52 
53 struct _PhotosThumbnailerGenerateData
54 {
55   GFile *file;
56   GFileOutputStream *stream;
57   GQuark orientation;
58   GdkPixbuf *pixbuf_thumbnail;
59   GeglNode *graph;
60   PhotosPipeline *pipeline;
61   gchar *thumbnail_path;
62   gint thumbnail_size;
63   gint64 original_height;
64   gint64 original_width;
65 };
66 
67 enum
68 {
69   INACTIVITY_TIMEOUT = 12000 /* ms */
70 };
71 
72 static const GOptionEntry COMMAND_LINE_OPTIONS[] =
73 {
74   { "address", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, NULL, N_("D-Bus address to use"), NULL},
75   { NULL }
76 };
77 
78 static const gchar *THUMBNAILER_PATH = "/org/gnome/Photos/Thumbnailer";
79 
80 
81 static void
photos_thumbnailer_generate_data_free(PhotosThumbnailerGenerateData * data)82 photos_thumbnailer_generate_data_free (PhotosThumbnailerGenerateData *data)
83 {
84   g_free (data->thumbnail_path);
85   g_clear_object (&data->file);
86   g_clear_object (&data->graph);
87   g_clear_object (&data->pipeline);
88   g_clear_object (&data->pixbuf_thumbnail);
89   g_clear_object (&data->stream);
90   g_slice_free (PhotosThumbnailerGenerateData, data);
91 }
92 
93 
94 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)95 photos_thumbnailer_generate_data_new (GFile *file,
96                                       GQuark orientation,
97                                       gint64 original_height,
98                                       gint64 original_width,
99                                       const gchar *thumbnail_path,
100                                       gint thumbnail_size,
101                                       GeglNode *graph)
102 {
103   PhotosThumbnailerGenerateData *data;
104 
105   data = g_slice_new0 (PhotosThumbnailerGenerateData);
106   data->file = g_object_ref (file);
107   data->orientation = orientation;
108   data->original_height = original_height;
109   data->original_width = original_width;
110   data->thumbnail_path = g_strdup (thumbnail_path);
111   data->thumbnail_size = thumbnail_size;
112   data->graph = g_object_ref (graph);
113 
114   return data;
115 }
116 
117 
118 static gboolean
photos_thumbnailer_authorize_authenticated_peer(PhotosThumbnailer * self,GIOStream * iostream,GCredentials * credentials)119 photos_thumbnailer_authorize_authenticated_peer (PhotosThumbnailer *self,
120                                                  GIOStream *iostream,
121                                                  GCredentials *credentials)
122 {
123   g_autoptr (GCredentials) own_credentials = NULL;
124   gboolean ret_val = FALSE;
125 
126   if (credentials == NULL)
127     goto out;
128 
129   own_credentials = g_credentials_new ();
130 
131   {
132     g_autoptr (GError) error = NULL;
133 
134     if (!g_credentials_is_same_user (credentials, own_credentials, &error))
135       {
136         g_warning ("Unable to authorize peer: %s", error->message);
137         goto out;
138       }
139   }
140 
141   ret_val = TRUE;
142 
143  out:
144   return ret_val;
145 }
146 
147 
148 static void
photos_thumbnailer_generate_thumbnail_stream_close(GObject * source_object,GAsyncResult * res,gpointer user_data)149 photos_thumbnailer_generate_thumbnail_stream_close (GObject *source_object,
150                                                     GAsyncResult *res,
151                                                     gpointer user_data)
152 {
153   GOutputStream *stream = G_OUTPUT_STREAM (source_object);
154   g_autoptr (GTask) task = G_TASK (user_data);
155 
156   {
157     g_autoptr (GError) error = NULL;
158 
159     if (!g_output_stream_close_finish (stream, res, &error))
160       {
161         g_task_return_error (task, g_steal_pointer (&error));
162         goto out;
163       }
164   }
165 
166   g_task_return_boolean (task, TRUE);
167 
168  out:
169   return;
170 }
171 
172 
173 static void
photos_thumbnailer_generate_thumbnail_save_to_stream(GObject * source_object,GAsyncResult * res,gpointer user_data)174 photos_thumbnailer_generate_thumbnail_save_to_stream (GObject *source_object,
175                                                       GAsyncResult *res,
176                                                       gpointer user_data)
177 {
178   GCancellable *cancellable;
179   g_autoptr (GTask) task = G_TASK (user_data);
180   PhotosThumbnailerGenerateData *data;
181 
182   cancellable = g_task_get_cancellable (task);
183   data = g_task_get_task_data (task);
184 
185   {
186     g_autoptr (GError) error = NULL;
187 
188     if (!gdk_pixbuf_save_to_stream_finish (res, &error))
189       {
190         g_task_return_error (task, g_steal_pointer (&error));
191         goto out;
192       }
193   }
194 
195   g_output_stream_close_async (G_OUTPUT_STREAM (data->stream),
196                                G_PRIORITY_DEFAULT,
197                                cancellable,
198                                photos_thumbnailer_generate_thumbnail_stream_close,
199                                g_object_ref (task));
200 
201  out:
202   return;
203 }
204 
205 
206 static void
photos_thumbnailer_generate_thumbnail_replace(GObject * source_object,GAsyncResult * res,gpointer user_data)207 photos_thumbnailer_generate_thumbnail_replace (GObject *source_object, GAsyncResult *res, gpointer user_data)
208 {
209   GCancellable *cancellable;
210   GFile *thumbnail_file = G_FILE (source_object);
211   g_autoptr (GFileOutputStream) stream = NULL;
212   g_autoptr (GTask) task = G_TASK (user_data);
213   PhotosThumbnailerGenerateData *data;
214   const gchar *prgname;
215   g_autofree gchar *original_height_str = NULL;
216   g_autofree gchar *original_width_str = NULL;
217   g_autofree gchar *uri = NULL;
218 
219   cancellable = g_task_get_cancellable (task);
220   data = g_task_get_task_data (task);
221 
222   {
223     g_autoptr (GError) error = NULL;
224 
225     stream = g_file_replace_finish (thumbnail_file, res, &error);
226     if (error != NULL)
227       {
228         g_task_return_error (task, g_steal_pointer (&error));
229         goto out;
230       }
231   }
232 
233   g_assert_null (data->stream);
234   data->stream = g_object_ref (stream);
235 
236   original_height_str = g_strdup_printf ("%" G_GINT64_FORMAT, data->original_height);
237   original_width_str = g_strdup_printf ("%" G_GINT64_FORMAT, data->original_width);
238   prgname = g_get_prgname ();
239   uri = g_file_get_uri (data->file);
240   gdk_pixbuf_save_to_stream_async (data->pixbuf_thumbnail,
241                                    G_OUTPUT_STREAM (stream),
242                                    "png",
243                                    cancellable,
244                                    photos_thumbnailer_generate_thumbnail_save_to_stream,
245                                    g_object_ref (task),
246                                    "tEXt::Software", prgname,
247                                    "tEXt::Thumb::URI", uri,
248                                    "tEXt::Thumb::Image::Height", original_height_str,
249                                    "tEXt::Thumb::Image::Width", original_width_str,
250                                    NULL);
251 
252  out:
253   return;
254 }
255 
256 
257 static void
photos_thumbnailer_generate_thumbnail_process(GObject * source_object,GAsyncResult * res,gpointer user_data)258 photos_thumbnailer_generate_thumbnail_process (GObject *source_object, GAsyncResult *res, gpointer user_data)
259 {
260   GCancellable *cancellable;
261   g_autoptr (GFile) thumbnail_file = NULL;
262   g_autoptr (GTask) task = G_TASK (user_data);
263   GeglNode *pipeline_node;
264   GeglProcessor *processor = GEGL_PROCESSOR (source_object);
265   PhotosThumbnailerGenerateData *data;
266   g_autofree gchar *thumbnail_dir = NULL;
267   gdouble zoom = 0.0;
268   gint pixbuf_height;
269   gint pixbuf_width;
270   gint pixbuf_zoomed_height;
271   gint pixbuf_zoomed_width;
272 
273   cancellable = g_task_get_cancellable (task);
274   data = g_task_get_task_data (task);
275 
276   {
277     g_autoptr (GError) error = NULL;
278 
279     if (!photos_gegl_processor_process_finish (processor, res, &error))
280       {
281         g_task_return_error (task, g_steal_pointer (&error));
282         goto out;
283       }
284   }
285 
286   g_assert_null (data->pixbuf_thumbnail);
287 
288   pipeline_node = photos_pipeline_get_graph (data->pipeline);
289   data->pixbuf_thumbnail = photos_gegl_create_pixbuf_from_node (pipeline_node);
290   pixbuf_height = gdk_pixbuf_get_height (data->pixbuf_thumbnail);
291   pixbuf_width = gdk_pixbuf_get_width (data->pixbuf_thumbnail);
292 
293   if (pixbuf_height > pixbuf_width && pixbuf_height > data->thumbnail_size)
294     {
295       zoom = (gdouble) data->thumbnail_size / (gdouble) pixbuf_height;
296       pixbuf_zoomed_height = data->thumbnail_size;
297       pixbuf_zoomed_width = (gint) (zoom * (gdouble) pixbuf_width + 0.5);
298     }
299   else if (pixbuf_height <= pixbuf_width && pixbuf_width > data->thumbnail_size)
300     {
301       zoom = (gdouble) data->thumbnail_size / (gdouble) pixbuf_width;
302       pixbuf_zoomed_height = (gint) (zoom * (gdouble) pixbuf_height + 0.5);
303       pixbuf_zoomed_width = data->thumbnail_size;
304     }
305 
306   if (zoom > 0.0)
307     {
308       g_autoptr (GdkPixbuf) pixbuf_scaled = NULL;
309 
310       photos_debug (PHOTOS_DEBUG_THUMBNAILER,
311                     "Scaling thumbnail to %d×%d",
312                     pixbuf_zoomed_width,
313                     pixbuf_zoomed_height);
314 
315       pixbuf_scaled = gdk_pixbuf_scale_simple (data->pixbuf_thumbnail,
316                                                pixbuf_zoomed_width,
317                                                pixbuf_zoomed_height,
318                                                GDK_INTERP_BILINEAR);
319 
320       g_set_object (&data->pixbuf_thumbnail, pixbuf_scaled);
321     }
322 
323   thumbnail_dir = g_path_get_dirname (data->thumbnail_path);
324   g_mkdir_with_parents (thumbnail_dir, 0700);
325 
326   thumbnail_file = g_file_new_for_path (data->thumbnail_path);
327 
328   photos_debug (PHOTOS_DEBUG_THUMBNAILER, "Saving thumbnail to %s", data->thumbnail_path);
329   g_file_replace_async (thumbnail_file,
330                         NULL,
331                         FALSE,
332                         G_FILE_CREATE_PRIVATE | G_FILE_CREATE_REPLACE_DESTINATION,
333                         G_PRIORITY_DEFAULT,
334                         cancellable,
335                         photos_thumbnailer_generate_thumbnail_replace,
336                         g_object_ref (task));
337 
338  out:
339   return;
340 }
341 
342 
343 static void
photos_thumbnailer_generate_thumbnail_pixbuf(GObject * source_object,GAsyncResult * res,gpointer user_data)344 photos_thumbnailer_generate_thumbnail_pixbuf (GObject *source_object, GAsyncResult *res, gpointer user_data)
345 {
346   GCancellable *cancellable;
347   g_autoptr (GTask) task = G_TASK (user_data);
348   g_autoptr (GdkPixbuf) pixbuf = NULL;
349   g_autoptr (GeglBuffer) buffer = NULL;
350   g_autoptr (GeglBuffer) buffer_oriented = NULL;
351   GeglNode *buffer_source;
352   GeglNode *pipeline_node;
353   g_autoptr (GeglProcessor) processor = NULL;
354   PhotosThumbnailerGenerateData *data;
355 
356   cancellable = g_task_get_cancellable (task);
357   data = g_task_get_task_data (task);
358 
359   {
360     g_autoptr (GError) error = NULL;
361 
362     pixbuf = photos_pixbuf_new_from_file_at_size_finish (res, &error);
363     if (error != NULL)
364       {
365         g_task_return_error (task, g_steal_pointer (&error));
366         goto out;
367       }
368   }
369 
370   buffer = photos_gegl_buffer_new_from_pixbuf (pixbuf);
371   buffer_oriented = photos_gegl_buffer_apply_orientation (buffer, data->orientation);
372 
373   buffer_source = gegl_node_new_child (data->graph, "operation", "gegl:buffer-source", "buffer", buffer_oriented, NULL);
374   pipeline_node = photos_pipeline_get_graph (data->pipeline);
375   gegl_node_link (buffer_source, pipeline_node);
376 
377   processor = gegl_node_new_processor (pipeline_node, NULL);
378   photos_gegl_processor_process_async (processor,
379                                        cancellable,
380                                        photos_thumbnailer_generate_thumbnail_process,
381                                        g_object_ref (task));
382 
383  out:
384   return;
385 }
386 
387 
388 static void
photos_thumbnailer_generate_thumbnail_pipeline(GObject * source_object,GAsyncResult * res,gpointer user_data)389 photos_thumbnailer_generate_thumbnail_pipeline (GObject *source_object, GAsyncResult *res, gpointer user_data)
390 {
391   GCancellable *cancellable;
392   g_autoptr (GTask) task = G_TASK (user_data);
393   g_autoptr (PhotosPipeline) pipeline = NULL;
394   PhotosThumbnailerGenerateData *data;
395   gboolean has_crop;
396   g_autofree gchar *path = NULL;
397   g_autofree gchar *uri = NULL;
398   gdouble height;
399   gdouble width;
400   gdouble x;
401   gdouble y;
402   gint load_height;
403   gint load_width;
404 
405   cancellable = g_task_get_cancellable (task);
406   data = g_task_get_task_data (task);
407 
408   {
409     g_autoptr (GError) error = NULL;
410 
411     pipeline = photos_pipeline_new_finish (res, &error);
412     if (error != NULL)
413       {
414         g_task_return_error (task, g_steal_pointer (&error));
415         goto out;
416       }
417   }
418 
419   g_assert_null (data->pipeline);
420   data->pipeline = g_object_ref (pipeline);
421 
422   uri = g_file_get_uri (data->file);
423 
424   has_crop = photos_pipeline_get (pipeline,
425                                   "gegl:crop",
426                                   "height", &height,
427                                   "width", &width,
428                                   "x", &x,
429                                   "y", &y,
430                                   NULL);
431   if (has_crop)
432     {
433       if (height < 0.0 || width < 0.0 || x < 0.0 || y < 0.0)
434         {
435           g_warning ("Unable to crop the thumbnail for %s: Invalid parameters", uri);
436 
437           photos_pipeline_remove (pipeline, "gegl:crop");
438           has_crop = FALSE;
439         }
440     }
441 
442   if (has_crop
443       || (0 < data->original_height
444           && data->original_height < data->thumbnail_size
445           && 0 < data->original_width
446           && data->original_width < data->thumbnail_size))
447     {
448       load_height = (gint) data->original_height;
449       load_width = (gint) data->original_width;
450     }
451   else
452     {
453       load_height = data->thumbnail_size;
454       load_width = data->thumbnail_size;
455     }
456 
457   path = g_file_get_path (data->file);
458   if (!g_file_is_native (data->file))
459     photos_debug (PHOTOS_DEBUG_NETWORK, "Downloading %s (%s)", uri, path);
460 
461   photos_debug (PHOTOS_DEBUG_THUMBNAILER, "Loading %s at %d×%d", uri, load_width, load_height);
462   photos_pixbuf_new_from_file_at_size_async (path,
463                                              load_width,
464                                              load_height,
465                                              cancellable,
466                                              photos_thumbnailer_generate_thumbnail_pixbuf,
467                                              g_object_ref (task));
468 
469  out:
470   return;
471 }
472 
473 
474 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 * const * pipeline_uris,const gchar * thumbnail_path,gint thumbnail_size,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)475 photos_thumbnailer_generate_thumbnail_async (PhotosThumbnailer *self,
476                                              const gchar *uri,
477                                              const gchar *mime_type,
478                                              const gchar *orientation,
479                                              gint64 original_height,
480                                              gint64 original_width,
481                                              const gchar *const *pipeline_uris,
482                                              const gchar *thumbnail_path,
483                                              gint thumbnail_size,
484                                              GCancellable *cancellable,
485                                              GAsyncReadyCallback callback,
486                                              gpointer user_data)
487 {
488   g_autoptr (GFile) file = NULL;
489   g_autoptr (GTask) task = NULL;
490   GQuark orientation_quark;
491   g_autoptr (GeglNode) graph = NULL;
492   PhotosThumbnailerGenerateData *data;
493 
494   g_return_if_fail (PHOTOS_IS_THUMBNAILER (self));
495   g_return_if_fail (uri != NULL && uri[0] != '\0');
496   g_return_if_fail (mime_type != NULL && mime_type[0] != '\0');
497   g_return_if_fail (orientation != NULL && orientation[0] != '\0');
498   g_return_if_fail (thumbnail_path != NULL && thumbnail_path[0] != '\0');
499   g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
500 
501   file = g_file_new_for_uri (uri);
502   orientation_quark = g_quark_from_string (orientation);
503   graph = gegl_node_new ();
504   data = photos_thumbnailer_generate_data_new (file,
505                                                orientation_quark,
506                                                original_height,
507                                                original_width,
508                                                thumbnail_path,
509                                                thumbnail_size,
510                                                graph);
511 
512   task = g_task_new (self, cancellable, callback, user_data);
513   g_task_set_source_tag (task, photos_thumbnailer_generate_thumbnail_async);
514   g_task_set_task_data (task, data, (GDestroyNotify) photos_thumbnailer_generate_data_free);
515 
516   photos_pipeline_new_async (graph,
517                              pipeline_uris,
518                              cancellable,
519                              photos_thumbnailer_generate_thumbnail_pipeline,
520                              g_object_ref (task));
521 }
522 
523 
524 static gboolean
photos_thumbnailer_generate_thumbnail_finish(PhotosThumbnailer * self,GAsyncResult * res,GError ** error)525 photos_thumbnailer_generate_thumbnail_finish (PhotosThumbnailer *self, GAsyncResult *res, GError **error)
526 {
527   GTask *task = G_TASK (res);
528 
529   g_return_val_if_fail (g_task_is_valid (res, self), FALSE);
530   g_return_val_if_fail (g_task_get_source_tag (task) == photos_thumbnailer_generate_thumbnail_async, FALSE);
531   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
532 
533   return g_task_propagate_boolean (task, error);
534 }
535 
536 
537 static gboolean
photos_thumbnailer_handle_cancel(PhotosThumbnailer * self,GDBusMethodInvocation * invocation,guint serial)538 photos_thumbnailer_handle_cancel (PhotosThumbnailer *self,
539                                   GDBusMethodInvocation *invocation,
540                                   guint serial)
541 {
542   GCancellable *cancellable;
543   GDBusConnection *connection;
544   GDBusMethodInvocation *invocation_ongoing;
545   GHashTableIter iter;
546 
547   g_return_val_if_fail (PHOTOS_IS_THUMBNAILER (self), FALSE);
548   g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), FALSE);
549 
550   photos_debug (PHOTOS_DEBUG_THUMBNAILER, "Handling Cancel for %u", serial);
551   g_application_hold (G_APPLICATION (self));
552 
553   connection = g_dbus_method_invocation_get_connection (invocation);
554 
555   g_hash_table_iter_init (&iter, self->cancellables);
556   while (g_hash_table_iter_next (&iter, (gpointer *) &invocation_ongoing, (gpointer *) &cancellable))
557     {
558       GDBusConnection *connection_ongoing;
559       GDBusMessage *message_ongoing;
560       guint32 serial_ongoing;
561 
562       connection_ongoing = g_dbus_method_invocation_get_connection (invocation_ongoing);
563       message_ongoing = g_dbus_method_invocation_get_message (invocation_ongoing);
564       serial_ongoing = g_dbus_message_get_serial (message_ongoing);
565 
566       if (connection == connection_ongoing && (guint32) serial == serial_ongoing)
567         {
568           g_cancellable_cancel (cancellable);
569           photos_thumbnailer_dbus_complete_cancel (self->skeleton, invocation);
570           goto out;
571         }
572     }
573 
574   g_dbus_method_invocation_return_error_literal (invocation, PHOTOS_ERROR, 0, "Invalid serial");
575 
576  out:
577   photos_debug (PHOTOS_DEBUG_THUMBNAILER, "Completed Cancel");
578   g_application_release (G_APPLICATION (self));
579   return TRUE;
580 }
581 
582 
583 static void
photos_thumbnailer_handle_generate_thumbnail_generate_thumbnail(GObject * source_object,GAsyncResult * res,gpointer user_data)584 photos_thumbnailer_handle_generate_thumbnail_generate_thumbnail (GObject *source_object,
585                                                                  GAsyncResult *res,
586                                                                  gpointer user_data)
587 {
588   PhotosThumbnailer *self = PHOTOS_THUMBNAILER (source_object);
589   g_autoptr (GDBusMethodInvocation) invocation = G_DBUS_METHOD_INVOCATION (user_data);
590 
591   {
592     g_autoptr (GError) error = NULL;
593 
594     if (!photos_thumbnailer_generate_thumbnail_finish (self, res, &error))
595       {
596         g_dbus_method_invocation_take_error (invocation, g_steal_pointer (&error));
597         goto out;
598       }
599   }
600 
601   photos_thumbnailer_dbus_complete_generate_thumbnail (self->skeleton, invocation);
602 
603  out:
604   photos_debug (PHOTOS_DEBUG_THUMBNAILER, "Completed GenerateThumbnail");
605   g_application_release (G_APPLICATION (self));
606   g_hash_table_remove (self->cancellables, invocation);
607 }
608 
609 
610 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,GVariant * pipeline_uris_variant,const gchar * thumbnail_path,gint thumbnail_size)611 photos_thumbnailer_handle_generate_thumbnail (PhotosThumbnailer *self,
612                                               GDBusMethodInvocation *invocation,
613                                               const gchar *uri,
614                                               const gchar *mime_type,
615                                               const gchar *orientation,
616                                               gint64 original_height,
617                                               gint64 original_width,
618                                               GVariant *pipeline_uris_variant,
619                                               const gchar *thumbnail_path,
620                                               gint thumbnail_size)
621 {
622   g_autoptr (GCancellable) cancellable = NULL;
623   g_auto (GStrv) pipeline_uris = NULL;
624 
625   g_return_val_if_fail (PHOTOS_IS_THUMBNAILER (self), FALSE);
626   g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), FALSE);
627   g_return_val_if_fail (uri != NULL && uri[0] != '\0', FALSE);
628   g_return_val_if_fail (mime_type != NULL && mime_type[0] != '\0', FALSE);
629   g_return_val_if_fail (orientation != NULL && orientation[0] != '\0', FALSE);
630   g_return_val_if_fail (g_variant_is_of_type (pipeline_uris_variant, G_VARIANT_TYPE ("as")), FALSE);
631   g_return_val_if_fail (thumbnail_path != NULL && thumbnail_path[0] != '\0', FALSE);
632 
633   photos_debug (PHOTOS_DEBUG_THUMBNAILER, "Handling GenerateThumbnail for %s", uri);
634 
635   pipeline_uris = g_variant_dup_strv (pipeline_uris_variant, NULL);
636 
637   cancellable = g_cancellable_new ();
638   g_hash_table_insert (self->cancellables, g_object_ref (invocation), g_object_ref (cancellable));
639 
640   g_application_hold (G_APPLICATION (self));
641   photos_thumbnailer_generate_thumbnail_async (self,
642                                                uri,
643                                                mime_type,
644                                                orientation,
645                                                original_height,
646                                                original_width,
647                                                (const gchar *const *) pipeline_uris,
648                                                thumbnail_path,
649                                                thumbnail_size,
650                                                cancellable,
651                                                photos_thumbnailer_handle_generate_thumbnail_generate_thumbnail,
652                                                g_object_ref (invocation));
653 
654   return TRUE;
655 }
656 
657 
658 static gboolean
photos_thumbnailer_dbus_register(GApplication * application,GDBusConnection * connection,const gchar * object_path,GError ** error)659 photos_thumbnailer_dbus_register (GApplication *application,
660                                   GDBusConnection *connection,
661                                   const gchar *object_path,
662                                   GError **error)
663 {
664   PhotosThumbnailer *self = PHOTOS_THUMBNAILER (application);
665   g_autoptr (GDBusAuthObserver) observer = NULL;
666   gboolean ret_val = FALSE;
667 
668   g_return_val_if_fail (self->skeleton == NULL, FALSE);
669 
670   if (!G_APPLICATION_CLASS (photos_thumbnailer_parent_class)->dbus_register (application,
671                                                                              connection,
672                                                                              object_path,
673                                                                              error))
674     goto out;
675 
676   observer = g_dbus_auth_observer_new ();
677   g_signal_connect_swapped (observer,
678                             "authorize-authenticated-peer",
679                             G_CALLBACK (photos_thumbnailer_authorize_authenticated_peer),
680                             self);
681 
682   self->connection = g_dbus_connection_new_for_address_sync (self->address,
683                                                              G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
684                                                              observer,
685                                                              NULL,
686                                                              error);
687   if (self->connection == NULL)
688     goto out;
689 
690   self->skeleton = photos_thumbnailer_dbus_skeleton_new ();
691   g_signal_connect_swapped (self->skeleton,
692                             "handle-cancel",
693                             G_CALLBACK (photos_thumbnailer_handle_cancel),
694                             self);
695   g_signal_connect_swapped (self->skeleton,
696                             "handle-generate-thumbnail",
697                             G_CALLBACK (photos_thumbnailer_handle_generate_thumbnail),
698                             self);
699 
700   if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self->skeleton),
701                                          self->connection,
702                                          THUMBNAILER_PATH,
703                                          error))
704     {
705       g_clear_object (&self->skeleton);
706       goto out;
707     }
708 
709   ret_val = TRUE;
710 
711  out:
712   return ret_val;
713 }
714 
715 
716 static void
photos_thumbnailer_dbus_unregister(GApplication * application,GDBusConnection * connection,const gchar * object_path)717 photos_thumbnailer_dbus_unregister (GApplication *application,
718                                     GDBusConnection *connection,
719                                     const gchar *object_path)
720 {
721   PhotosThumbnailer *self = PHOTOS_THUMBNAILER (application);
722 
723   if (self->skeleton != NULL)
724     {
725       g_dbus_interface_skeleton_unexport_from_connection (G_DBUS_INTERFACE_SKELETON (self->skeleton),
726                                                           self->connection);
727       g_clear_object (&self->skeleton);
728     }
729 
730   G_APPLICATION_CLASS (photos_thumbnailer_parent_class)->dbus_unregister (application, connection, object_path);
731 }
732 
733 
734 static gint
photos_thumbnailer_handle_local_options(GApplication * application,GVariantDict * options)735 photos_thumbnailer_handle_local_options (GApplication *application, GVariantDict *options)
736 {
737   PhotosThumbnailer *self = PHOTOS_THUMBNAILER (application);
738   gint ret_val = EXIT_FAILURE;
739 
740   if (g_variant_dict_lookup (options, "address", "s", &self->address))
741     ret_val = -1;
742 
743   return ret_val;
744 }
745 
746 
747 static void
photos_thumbnailer_shutdown(GApplication * application)748 photos_thumbnailer_shutdown (GApplication *application)
749 {
750   photos_debug (PHOTOS_DEBUG_THUMBNAILER, "Thumbnailer exiting");
751 
752   G_APPLICATION_CLASS (photos_thumbnailer_parent_class)->shutdown (application);
753 }
754 
755 
756 static void
photos_thumbnailer_startup(GApplication * application)757 photos_thumbnailer_startup (GApplication *application)
758 {
759   G_APPLICATION_CLASS (photos_thumbnailer_parent_class)->startup (application);
760 
761   photos_gegl_init ();
762   photos_debug (PHOTOS_DEBUG_THUMBNAILER, "Thumbnailer ready");
763 }
764 
765 
766 static void
photos_thumbnailer_dispose(GObject * object)767 photos_thumbnailer_dispose (GObject *object)
768 {
769   PhotosThumbnailer *self = PHOTOS_THUMBNAILER (object);
770 
771   g_assert_null (self->skeleton);
772 
773   g_clear_object (&self->connection);
774   g_clear_pointer (&self->cancellables, g_hash_table_unref);
775 
776   G_OBJECT_CLASS (photos_thumbnailer_parent_class)->dispose (object);
777 }
778 
779 
780 static void
photos_thumbnailer_finalize(GObject * object)781 photos_thumbnailer_finalize (GObject *object)
782 {
783   PhotosThumbnailer *self = PHOTOS_THUMBNAILER (object);
784 
785   g_free (self->address);
786 
787   if (g_application_get_is_registered (G_APPLICATION (self)))
788     gegl_exit ();
789 
790   G_OBJECT_CLASS (photos_thumbnailer_parent_class)->finalize (object);
791 }
792 
793 
794 static void
photos_thumbnailer_init(PhotosThumbnailer * self)795 photos_thumbnailer_init (PhotosThumbnailer *self)
796 {
797   setlocale (LC_ALL, "");
798 
799   bindtextdomain (GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
800   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
801   textdomain (GETTEXT_PACKAGE);
802 
803   photos_gegl_ensure_builtins ();
804 
805   self->cancellables = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, g_object_unref);
806   g_application_add_main_option_entries (G_APPLICATION (self), COMMAND_LINE_OPTIONS);
807 }
808 
809 
810 static void
photos_thumbnailer_class_init(PhotosThumbnailerClass * class)811 photos_thumbnailer_class_init (PhotosThumbnailerClass *class)
812 {
813   GObjectClass *object_class = G_OBJECT_CLASS (class);
814   GApplicationClass *application_class = G_APPLICATION_CLASS (class);
815 
816   object_class->dispose = photos_thumbnailer_dispose;
817   object_class->finalize = photos_thumbnailer_finalize;
818   application_class->dbus_register = photos_thumbnailer_dbus_register;
819   application_class->dbus_unregister = photos_thumbnailer_dbus_unregister;
820   application_class->handle_local_options = photos_thumbnailer_handle_local_options;
821   application_class->shutdown = photos_thumbnailer_shutdown;
822   application_class->startup = photos_thumbnailer_startup;
823 }
824 
825 
826 GApplication *
photos_thumbnailer_new(void)827 photos_thumbnailer_new (void)
828 {
829   return g_object_new (PHOTOS_TYPE_THUMBNAILER,
830                        "flags", G_APPLICATION_IS_SERVICE | G_APPLICATION_NON_UNIQUE,
831                        "inactivity-timeout", INACTIVITY_TIMEOUT,
832                        NULL);
833 }
834