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