1 /*
2 * Photos - access, organize and share your photos on GNOME
3 * Copyright © 2015 – 2019 Red Hat, Inc.
4 * Copyright © 2016 Umang Jain
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20
21 #include "config.h"
22
23 #include <string.h>
24
25 #include <dazzle.h>
26 #include <glib.h>
27
28 #include "photos-debug.h"
29 #include "photos-gegl.h"
30 #include "photos-operation-insta-common.h"
31 #include "photos-pipeline.h"
32
33
34 struct _PhotosPipeline
35 {
36 GObject parent_instance;
37 GHashTable *hash;
38 GStrv uris;
39 GeglNode *graph;
40 gchar *snapshot;
41 };
42
43 enum
44 {
45 PROP_0,
46 PROP_PARENT,
47 PROP_URIS
48 };
49
50 static void photos_pipeline_async_initable_iface_init (GAsyncInitableIface *iface);
51
52
53 G_DEFINE_TYPE_EXTENDED (PhotosPipeline, photos_pipeline, G_TYPE_OBJECT, 0,
54 G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, photos_pipeline_async_initable_iface_init));
55 DZL_DEFINE_COUNTER (instances, "PhotosPipeline", "Instances", "Number of PhotosPipeline instances")
56
57
58 static const gchar *OPERATIONS[] =
59 {
60 "gegl:crop",
61 "gegl:noise-reduction",
62 "gegl:shadows-highlights",
63 "photos:saturation",
64 "photos:insta-filter"
65 };
66
67
68 static void
photos_pipeline_link_nodes(GeglNode * input,GeglNode * output,GSList * nodes)69 photos_pipeline_link_nodes (GeglNode *input, GeglNode *output, GSList *nodes)
70 {
71 GSList *l;
72 GeglNode *node;
73
74 if (nodes == NULL)
75 {
76 gegl_node_link (input, output);
77 return;
78 }
79
80 node = GEGL_NODE (nodes->data);
81 gegl_node_link (input, node);
82
83 for (l = nodes; l != NULL && l->next != NULL; l = l->next)
84 {
85 GeglNode *sink = GEGL_NODE (l->next->data);
86 GeglNode *source = GEGL_NODE (l->data);
87 gegl_node_link (source, sink);
88 }
89
90 node = GEGL_NODE (l->data);
91 gegl_node_link (node, output);
92 }
93
94
95 static void
photos_pipeline_reset(PhotosPipeline * self)96 photos_pipeline_reset (PhotosPipeline *self)
97 {
98 GSList *nodes = NULL;
99 GeglNode *input;
100 GeglNode *last;
101 GeglNode *output;
102 guint i;
103
104 input = gegl_node_get_input_proxy (self->graph, "input");
105 output = gegl_node_get_output_proxy (self->graph, "output");
106 last = gegl_node_get_producer (output, "input", NULL);
107 g_return_if_fail (last == input);
108
109 for (i = 0; i < G_N_ELEMENTS (OPERATIONS); i++)
110 {
111 GeglNode *node;
112
113 node = gegl_node_new_child (self->graph, "operation", OPERATIONS[i], NULL);
114 gegl_node_set_passthrough (node, TRUE);
115 g_hash_table_insert (self->hash, g_strdup (OPERATIONS[i]), g_object_ref (node));
116 nodes = g_slist_prepend (nodes, g_object_ref (node));
117 }
118
119 nodes = g_slist_reverse (nodes);
120 photos_pipeline_link_nodes (input, output, nodes);
121
122 g_slist_free_full (nodes, g_object_unref);
123 }
124
125
126 static gboolean
photos_pipeline_create_graph_from_xml(PhotosPipeline * self,const gchar * contents)127 photos_pipeline_create_graph_from_xml (PhotosPipeline *self, const gchar *contents)
128 {
129 g_autoptr (GeglNode) graph = NULL;
130 GeglNode *input;
131 GeglNode *output;
132 g_autoptr (GSList) children = NULL;
133 GSList *l;
134 gboolean ret_val = FALSE;
135
136 /* HACK: This graph is busted. eg., the input and output proxies
137 * point to the same GeglNode. I can't imagine this to be
138 * anything else other than a GEGL bug.
139 *
140 * Therefore, we are going to re-construct a proper graph
141 * ourselves.
142 */
143 graph = gegl_node_new_from_xml (contents, "/");
144 if (graph == NULL)
145 goto out;
146
147 g_hash_table_remove_all (self->hash);
148 photos_gegl_remove_children_from_node (self->graph);
149
150 input = gegl_node_get_input_proxy (self->graph, "input");
151 output = gegl_node_get_output_proxy (self->graph, "output");
152
153 children = gegl_node_get_children (graph);
154 for (l = children; l != NULL; l = l->next)
155 {
156 GeglNode *node = GEGL_NODE (l->data);
157 const gchar *operation;
158 const gchar *operation_compat;
159
160 g_object_ref (node);
161 gegl_node_remove_child (graph, node);
162 gegl_node_add_child (self->graph, node);
163 g_object_unref (node);
164
165 operation = gegl_node_get_operation (node);
166 g_hash_table_insert (self->hash, g_strdup (operation), g_object_ref (node));
167
168 operation_compat = gegl_operation_get_key (operation, "compat-name");
169 if (operation_compat != NULL)
170 g_hash_table_insert (self->hash, g_strdup (operation_compat), g_object_ref (node));
171 }
172
173 photos_pipeline_link_nodes (input, output, children);
174
175 ret_val = TRUE;
176
177 out:
178 return ret_val;
179 }
180
181
182 static void
photos_pipeline_save_delete(GObject * source_object,GAsyncResult * res,gpointer user_data)183 photos_pipeline_save_delete (GObject *source_object, GAsyncResult *res, gpointer user_data)
184 {
185 g_autoptr (GTask) task = G_TASK (user_data);
186 PhotosPipeline *self;
187 GCancellable *cancellable;
188 GFile *file = G_FILE (source_object);
189 g_autoptr (GFile) file_next = NULL;
190 guint i;
191
192 self = PHOTOS_PIPELINE (g_task_get_source_object (task));
193 cancellable = g_task_get_cancellable (task);
194 i = GPOINTER_TO_UINT (g_task_get_task_data (task));
195
196 {
197 g_autoptr (GError) error = NULL;
198
199 if (!g_file_delete_finish (file, res, &error))
200 {
201 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
202 {
203 g_task_return_error (task, g_steal_pointer (&error));
204 goto out;
205 }
206 }
207 }
208
209 i++;
210 if (self->uris[i] == NULL)
211 {
212 g_task_return_boolean (task, TRUE);
213 goto out;
214 }
215
216 g_task_set_task_data (task, GUINT_TO_POINTER (i), NULL);
217
218 file_next = g_file_new_for_uri (self->uris[i]);
219 g_file_delete_async (file_next,
220 G_PRIORITY_DEFAULT,
221 cancellable,
222 photos_pipeline_save_delete,
223 g_object_ref (task));
224
225 out:
226 return;
227 }
228
229
230 static void
photos_pipeline_save_replace_contents(GObject * source_object,GAsyncResult * res,gpointer user_data)231 photos_pipeline_save_replace_contents (GObject *source_object, GAsyncResult *res, gpointer user_data)
232 {
233 g_autoptr (GTask) task = G_TASK (user_data);
234 GFile *file = G_FILE (source_object);
235
236 {
237 g_autoptr (GError) error = NULL;
238
239 if (!g_file_replace_contents_finish (file, res, NULL, &error))
240 {
241 g_task_return_error (task, g_steal_pointer (&error));
242 goto out;
243 }
244 }
245
246 g_task_return_boolean (task, TRUE);
247
248 out:
249 return;
250 }
251
252
253 static void
photos_pipeline_constructed(GObject * object)254 photos_pipeline_constructed (GObject *object)
255 {
256 PhotosPipeline *self = PHOTOS_PIPELINE (object);
257 GeglNode *input;
258 GeglNode *output;
259
260 G_OBJECT_CLASS (photos_pipeline_parent_class)->constructed (object);
261
262 input = gegl_node_get_input_proxy (self->graph, "input");
263 output = gegl_node_get_output_proxy (self->graph, "output");
264 gegl_node_link (input, output);
265 }
266
267
268 static void
photos_pipeline_dispose(GObject * object)269 photos_pipeline_dispose (GObject *object)
270 {
271 PhotosPipeline *self = PHOTOS_PIPELINE (object);
272
273 /* We must drop all references to the child nodes before destroying
274 * the graph. The other option would be to ensure that the
275 * GeglProcessor is destroyed before its Pipeline, but since that is
276 * harder to enforce, let's do this instead.
277 *
278 * See: https://bugzilla.gnome.org/show_bug.cgi?id=759995
279 */
280 g_clear_pointer (&self->hash, g_hash_table_unref);
281
282 g_clear_object (&self->graph);
283
284 G_OBJECT_CLASS (photos_pipeline_parent_class)->dispose (object);
285 }
286
287
288 static void
photos_pipeline_finalize(GObject * object)289 photos_pipeline_finalize (GObject *object)
290 {
291 PhotosPipeline *self = PHOTOS_PIPELINE (object);
292
293 g_strfreev (self->uris);
294 g_free (self->snapshot);
295
296 G_OBJECT_CLASS (photos_pipeline_parent_class)->finalize (object);
297
298 DZL_COUNTER_DEC (instances);
299 }
300
301
302 static void
photos_pipeline_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)303 photos_pipeline_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
304 {
305 PhotosPipeline *self = PHOTOS_PIPELINE (object);
306
307 switch (prop_id)
308 {
309 case PROP_PARENT:
310 {
311 GeglNode *parent;
312
313 parent = GEGL_NODE (g_value_get_object (value));
314 photos_pipeline_set_parent (self, parent);
315 break;
316 }
317
318 case PROP_URIS:
319 self->uris = (GStrv) g_value_dup_boxed (value);
320 break;
321
322 default:
323 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
324 break;
325 }
326 }
327
328
329 static void
photos_pipeline_init(PhotosPipeline * self)330 photos_pipeline_init (PhotosPipeline *self)
331 {
332 DZL_COUNTER_INC (instances);
333
334 self->hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
335 self->graph = gegl_node_new ();
336 }
337
338
339 static void
photos_pipeline_class_init(PhotosPipelineClass * class)340 photos_pipeline_class_init (PhotosPipelineClass *class)
341 {
342 GObjectClass *object_class = G_OBJECT_CLASS (class);
343
344 object_class->constructed = photos_pipeline_constructed;
345 object_class->dispose = photos_pipeline_dispose;
346 object_class->finalize = photos_pipeline_finalize;
347 object_class->set_property = photos_pipeline_set_property;
348
349 g_object_class_install_property (object_class,
350 PROP_PARENT,
351 g_param_spec_object ("parent",
352 "GeglNode object",
353 "A GeglNode representing the parent graph",
354 GEGL_TYPE_NODE,
355 G_PARAM_CONSTRUCT | G_PARAM_WRITABLE));
356
357 g_object_class_install_property (object_class,
358 PROP_URIS,
359 g_param_spec_boxed ("uris",
360 "URIs",
361 "Possible locations from which to load this pipeline, and"
362 "afterwards it will be saved to the first non-NULL URI.",
363 G_TYPE_STRV,
364 G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
365 }
366
367
368 static void
photos_pipeline_async_initable_init_load_contents(GObject * source_object,GAsyncResult * res,gpointer user_data)369 photos_pipeline_async_initable_init_load_contents (GObject *source_object, GAsyncResult *res, gpointer user_data)
370 {
371 g_autoptr (GTask) task = G_TASK (user_data);
372 PhotosPipeline *self;
373 GCancellable *cancellable;
374 GFile *file = G_FILE (source_object);
375 g_autofree gchar *contents = NULL;
376 const gchar *uri;
377
378 self = PHOTOS_PIPELINE (g_task_get_source_object (task));
379 cancellable = g_task_get_cancellable (task);
380 uri = (const gchar *) g_task_get_task_data (task);
381
382 {
383 g_autoptr (GError) error = NULL;
384
385 if (!g_file_load_contents_finish (file, res, &contents, NULL, NULL, &error))
386 {
387 if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
388 {
389 g_autoptr (GFile) file_next = NULL;
390 guint i;
391
392 for (i = 0; self->uris[i] != NULL; i++)
393 {
394 if (g_strcmp0 (self->uris[i], uri) == 0)
395 break;
396 }
397
398 g_assert_nonnull (self->uris[i]);
399
400 i++;
401 if (self->uris[i] == NULL)
402 goto carry_on;
403
404 g_task_set_task_data (task, g_strdup (self->uris[i]), g_free);
405
406 file_next = g_file_new_for_uri (self->uris[i]);
407 g_file_load_contents_async (file_next,
408 cancellable,
409 photos_pipeline_async_initable_init_load_contents,
410 g_object_ref (task));
411
412 goto out;
413 }
414 else
415 {
416 g_task_return_error (task, g_steal_pointer (&error));
417 goto out;
418 }
419 }
420 }
421
422 if (!(photos_pipeline_create_graph_from_xml (self, contents)))
423 g_warning ("Unable to deserialize from %s", uri);
424
425 carry_on:
426 g_task_return_boolean (task, TRUE);
427
428 out:
429 return;
430 }
431
432
433 static void
photos_pipeline_async_initable_init_async(GAsyncInitable * initable,gint io_priority,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)434 photos_pipeline_async_initable_init_async (GAsyncInitable *initable,
435 gint io_priority,
436 GCancellable *cancellable,
437 GAsyncReadyCallback callback,
438 gpointer user_data)
439 {
440 PhotosPipeline *self = PHOTOS_PIPELINE (initable);
441 g_autoptr (GFile) file = NULL;
442 g_autoptr (GTask) task = NULL;
443
444 task = g_task_new (self, cancellable, callback, user_data);
445 g_task_set_source_tag (task, photos_pipeline_async_initable_init_async);
446
447 if (self->uris == NULL || self->uris[0] == NULL || self->uris[0][0] == '\0')
448 {
449 g_task_return_boolean (task, TRUE);
450 goto out;
451 }
452
453 g_task_set_task_data (task, g_strdup (self->uris[0]), g_free);
454
455 file = g_file_new_for_uri (self->uris[0]);
456 g_file_load_contents_async (file,
457 cancellable,
458 photos_pipeline_async_initable_init_load_contents,
459 g_object_ref (task));
460
461 out:
462 return;
463 }
464
465
466 static gboolean
photos_pipeline_async_initable_init_finish(GAsyncInitable * initable,GAsyncResult * res,GError ** error)467 photos_pipeline_async_initable_init_finish (GAsyncInitable *initable, GAsyncResult *res, GError **error)
468 {
469 PhotosPipeline *self = PHOTOS_PIPELINE (initable);
470 GTask *task;
471
472 g_return_val_if_fail (g_task_is_valid (res, self), FALSE);
473 task = G_TASK (res);
474
475 g_return_val_if_fail (g_task_get_source_tag (task) == photos_pipeline_async_initable_init_async, FALSE);
476 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
477
478 return g_task_propagate_boolean (task, error);
479 }
480
481
482 static void
photos_pipeline_async_initable_iface_init(GAsyncInitableIface * iface)483 photos_pipeline_async_initable_iface_init (GAsyncInitableIface *iface)
484 {
485 iface->init_async = photos_pipeline_async_initable_init_async;
486 iface->init_finish = photos_pipeline_async_initable_init_finish;
487 }
488
489
490 void
photos_pipeline_new_async(GeglNode * parent,const gchar * const * uris,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)491 photos_pipeline_new_async (GeglNode *parent,
492 const gchar *const *uris,
493 GCancellable *cancellable,
494 GAsyncReadyCallback callback,
495 gpointer user_data)
496 {
497 g_return_if_fail (parent == NULL || GEGL_IS_NODE (parent));
498 g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
499
500 g_async_initable_new_async (PHOTOS_TYPE_PIPELINE,
501 G_PRIORITY_DEFAULT,
502 cancellable,
503 callback,
504 user_data,
505 "parent", parent,
506 "uris", uris,
507 NULL);
508 }
509
510
511 PhotosPipeline *
photos_pipeline_new_finish(GAsyncResult * res,GError ** error)512 photos_pipeline_new_finish (GAsyncResult *res, GError **error)
513 {
514 GObject *ret_val;
515 g_autoptr (GObject) source_object = NULL;
516
517 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
518
519 source_object = g_async_result_get_source_object (res);
520 ret_val = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), res, error);
521 return PHOTOS_PIPELINE (ret_val);
522 }
523
524
525 void
photos_pipeline_add_valist(PhotosPipeline * self,const gchar * operation,const gchar * first_property_name,va_list ap)526 photos_pipeline_add_valist (PhotosPipeline *self,
527 const gchar *operation,
528 const gchar *first_property_name,
529 va_list ap)
530 {
531 GeglNode *input;
532 GeglNode *last;
533 GeglNode *node;
534 GeglNode *output;
535 g_autofree gchar *xml = NULL;
536
537 g_return_if_fail (PHOTOS_IS_PIPELINE (self));
538 g_return_if_fail (operation != NULL && operation[0] != '\0');
539
540 input = gegl_node_get_input_proxy (self->graph, "input");
541 output = gegl_node_get_output_proxy (self->graph, "output");
542 last = gegl_node_get_producer (output, "input", NULL);
543 if (last == input)
544 photos_pipeline_reset (self);
545
546 node = GEGL_NODE (g_hash_table_lookup (self->hash, operation));
547 if (node == NULL)
548 {
549 last = gegl_node_get_producer (output, "input", NULL);
550 node = gegl_node_new_child (self->graph, "operation", operation, NULL);
551 gegl_node_disconnect (output, "input");
552 gegl_node_link_many (last, node, output, NULL);
553 g_hash_table_insert (self->hash, g_strdup (operation), g_object_ref (node));
554 }
555 else
556 {
557 gegl_node_set_passthrough (node, FALSE);
558 }
559
560 gegl_node_set_valist (node, first_property_name, ap);
561
562 xml = gegl_node_to_xml_full (self->graph, self->graph, "/");
563 photos_debug (PHOTOS_DEBUG_GEGL, "Pipeline: %s", xml);
564 }
565
566
567 gboolean
photos_pipeline_get(PhotosPipeline * self,const gchar * operation,const gchar * first_property_name,...)568 photos_pipeline_get (PhotosPipeline *self, const gchar *operation, const gchar *first_property_name, ...)
569 {
570 gboolean ret_val;
571 va_list ap;
572
573 g_return_val_if_fail (PHOTOS_IS_PIPELINE (self), FALSE);
574 g_return_val_if_fail (operation != NULL && operation[0] != '\0', FALSE);
575
576 va_start (ap, first_property_name);
577 ret_val = photos_pipeline_get_valist (self, operation, first_property_name, ap);
578 va_end (ap);
579
580 return ret_val;
581 }
582
583
584 gboolean
photos_pipeline_get_valist(PhotosPipeline * self,const gchar * operation,const gchar * first_property_name,va_list ap)585 photos_pipeline_get_valist (PhotosPipeline *self,
586 const gchar *operation,
587 const gchar *first_property_name,
588 va_list ap)
589 {
590 GeglNode *node;
591 gboolean ret_val = FALSE;
592
593 g_return_val_if_fail (PHOTOS_IS_PIPELINE (self), FALSE);
594 g_return_val_if_fail (operation != NULL && operation[0] != '\0', FALSE);
595
596 node = GEGL_NODE (g_hash_table_lookup (self->hash, operation));
597 if (node == NULL)
598 goto out;
599
600 if (gegl_node_get_passthrough (node))
601 goto out;
602
603 gegl_node_get_valist (node, first_property_name, ap);
604 ret_val = TRUE;
605
606 out:
607 return ret_val;
608 }
609
610
611 GeglNode *
photos_pipeline_get_graph(PhotosPipeline * self)612 photos_pipeline_get_graph (PhotosPipeline *self)
613 {
614 g_return_val_if_fail (PHOTOS_IS_PIPELINE (self), NULL);
615 return self->graph;
616 }
617
618
619 GeglNode *
photos_pipeline_get_output(PhotosPipeline * self)620 photos_pipeline_get_output (PhotosPipeline *self)
621 {
622 GeglNode *output;
623
624 g_return_val_if_fail (PHOTOS_IS_PIPELINE (self), NULL);
625
626 output = gegl_node_get_output_proxy (self->graph, "output");
627 return output;
628 }
629
630
631 gboolean
photos_pipeline_is_edited(PhotosPipeline * self)632 photos_pipeline_is_edited (PhotosPipeline *self)
633 {
634 g_autoptr (GSList) children = NULL;
635 GSList *l;
636 guint n_operations = 0;
637
638 g_return_val_if_fail (PHOTOS_IS_PIPELINE (self), FALSE);
639
640 children = gegl_node_get_children (self->graph);
641 if (children == NULL)
642 goto out;
643
644 for (l = children; l != NULL && n_operations == 0; l = l->next)
645 {
646 GeglNode *node = GEGL_NODE (l->data);
647 const char *operation;
648
649 if (gegl_node_get_passthrough (node))
650 continue;
651
652 operation = gegl_node_get_operation (node);
653
654 if (g_strcmp0 (operation, "gegl:nop") == 0)
655 {
656 continue;
657 }
658 else if (g_strcmp0 (operation, "photos:magic-filter") == 0)
659 {
660 gint preset;
661
662 gegl_node_get (node, "preset", &preset, NULL);
663 if (preset == PHOTOS_OPERATION_INSTA_PRESET_NONE)
664 continue;
665 }
666
667 n_operations++;
668 }
669
670 out:
671 return n_operations > 0;
672 }
673
674
675 GeglProcessor *
photos_pipeline_new_processor(PhotosPipeline * self)676 photos_pipeline_new_processor (PhotosPipeline *self)
677 {
678 GeglProcessor *processor;
679
680 g_return_val_if_fail (PHOTOS_IS_PIPELINE (self), NULL);
681
682 processor = gegl_node_new_processor (self->graph, NULL);
683 return processor;
684 }
685
686
687 void
photos_pipeline_save_async(PhotosPipeline * self,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)688 photos_pipeline_save_async (PhotosPipeline *self,
689 GCancellable *cancellable,
690 GAsyncReadyCallback callback,
691 gpointer user_data)
692 {
693 g_autoptr (GFile) file = NULL;
694 g_autoptr (GTask) task = NULL;
695
696 g_return_if_fail (PHOTOS_IS_PIPELINE (self));
697 g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
698
699 task = g_task_new (self, cancellable, callback, user_data);
700 g_task_set_source_tag (task, photos_pipeline_save_async);
701
702 file = g_file_new_for_uri (self->uris[0]);
703
704 if (photos_pipeline_is_edited (self))
705 {
706 gchar *xml = NULL;
707 gsize len;
708
709 xml = gegl_node_to_xml_full (self->graph, self->graph, "/");
710 g_return_if_fail (xml != NULL);
711
712 /* We need to keep 'xml' alive until g_file_replace_contents_async
713 * returns.
714 */
715 g_task_set_task_data (task, xml, g_free);
716
717 len = strlen (xml);
718 g_file_replace_contents_async (file,
719 xml,
720 len,
721 NULL,
722 FALSE,
723 G_FILE_CREATE_REPLACE_DESTINATION,
724 cancellable,
725 photos_pipeline_save_replace_contents,
726 g_object_ref (task));
727 }
728 else
729 {
730 g_task_set_task_data (task, GUINT_TO_POINTER (0), NULL);
731 g_file_delete_async (file,
732 G_PRIORITY_DEFAULT,
733 cancellable,
734 photos_pipeline_save_delete,
735 g_object_ref (task));
736 }
737 }
738
739
740 gboolean
photos_pipeline_save_finish(PhotosPipeline * self,GAsyncResult * res,GError ** error)741 photos_pipeline_save_finish (PhotosPipeline *self, GAsyncResult *res, GError **error)
742 {
743 GTask *task;
744
745 g_return_val_if_fail (g_task_is_valid (res, self), FALSE);
746 task = G_TASK (res);
747
748 g_return_val_if_fail (g_task_get_source_tag (task) == photos_pipeline_save_async, FALSE);
749 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
750
751 return g_task_propagate_boolean (task, error);
752 }
753
754
755 gboolean
photos_pipeline_remove(PhotosPipeline * self,const gchar * operation)756 photos_pipeline_remove (PhotosPipeline *self, const gchar *operation)
757 {
758 GeglNode *node;
759 gboolean ret_val = FALSE;
760 g_autofree gchar *xml = NULL;
761
762 g_return_val_if_fail (PHOTOS_IS_PIPELINE (self), FALSE);
763 g_return_val_if_fail (operation != NULL && operation[0] != '\0', FALSE);
764
765 node = GEGL_NODE (g_hash_table_lookup (self->hash, operation));
766 if (node == NULL)
767 goto out;
768
769 if (gegl_node_get_passthrough (node))
770 goto out;
771
772 gegl_node_set_passthrough (node, TRUE);
773
774 xml = gegl_node_to_xml_full (self->graph, self->graph, "/");
775 photos_debug (PHOTOS_DEBUG_GEGL, "Pipeline: %s", xml);
776
777 ret_val = TRUE;
778
779 out:
780 return ret_val;
781 }
782
783
784 void
photos_pipeline_revert(PhotosPipeline * self)785 photos_pipeline_revert (PhotosPipeline *self)
786 {
787 g_autofree gchar *xml = NULL;
788
789 g_return_if_fail (PHOTOS_IS_PIPELINE (self));
790 g_return_if_fail (self->snapshot != NULL);
791
792 if (!photos_pipeline_create_graph_from_xml (self, self->snapshot))
793 g_warning ("Unable to revert to: %s", self->snapshot);
794
795 g_clear_pointer (&self->snapshot, g_free);
796
797 xml = gegl_node_to_xml_full (self->graph, self->graph, "/");
798 photos_debug (PHOTOS_DEBUG_GEGL, "Pipeline: %s", xml);
799 }
800
801
802 void
photos_pipeline_revert_to_original(PhotosPipeline * self)803 photos_pipeline_revert_to_original (PhotosPipeline *self)
804 {
805 const gchar *empty_xml = "<?xml version='1.0' encoding='UTF-8'?><gegl></gegl>";
806 g_autofree gchar *xml = NULL;
807
808 g_return_if_fail (PHOTOS_IS_PIPELINE (self));
809
810 if (!photos_pipeline_create_graph_from_xml (self, empty_xml))
811 g_warning ("Unable to revert to original");
812
813 g_clear_pointer (&self->snapshot, g_free);
814
815 xml = gegl_node_to_xml_full (self->graph, self->graph, "/");
816 photos_debug (PHOTOS_DEBUG_GEGL, "Pipeline: %s", xml);
817 }
818
819
820 void
photos_pipeline_set_parent(PhotosPipeline * self,GeglNode * parent)821 photos_pipeline_set_parent (PhotosPipeline *self, GeglNode *parent)
822 {
823 GeglNode *old_parent;
824
825 g_return_if_fail (PHOTOS_IS_PIPELINE (self));
826 g_return_if_fail (parent == NULL || GEGL_IS_NODE (parent));
827
828 old_parent = gegl_node_get_parent (self->graph);
829 if (parent == old_parent)
830 return;
831
832 if (old_parent != NULL)
833 gegl_node_remove_child (old_parent, self->graph);
834
835 if (parent != NULL)
836 gegl_node_add_child (parent, self->graph);
837 }
838
839
840 void
photos_pipeline_snapshot(PhotosPipeline * self)841 photos_pipeline_snapshot (PhotosPipeline *self)
842 {
843 g_return_if_fail (PHOTOS_IS_PIPELINE (self));
844
845 g_free (self->snapshot);
846 self->snapshot = gegl_node_to_xml_full (self->graph, self->graph, "/");
847 photos_debug (PHOTOS_DEBUG_GEGL, "Snapshot: %s", self->snapshot);
848 }
849