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