dfsm: Add all the missing API reference comments
[bendy-bus:bendy-bus.git] / dfsm / dfsm-dbus-output-sequence.c
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /*
3  * D-Bus Simulator
4  * Copyright (C) Philip Withnall 2012 <philip@tecnocode.co.uk>
5  * 
6  * D-Bus Simulator 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  * D-Bus Simulator 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 D-Bus Simulator.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 /**
21  * SECTION:dfsm-dbus-output-sequence
22  * @short_description: D-Bus output sequence
23  * @stability: Unstable
24  * @include: dfsm/dfsm-dbus-output-sequence.h
25  *
26  * D-Bus based implementation of #DfsmOutputSequence which allows for replies to method calls (successful or erroneous ones) and emits signals onto
27  * the bus. All actions are queued up when added to the output sequence, and are only propagated to the bus when dfsm_output_sequence_output() is
28  * called.
29  */
30
31 #include <string.h>
32 #include <glib.h>
33
34 #include "dfsm-dbus-output-sequence.h"
35 #include "dfsm-output-sequence.h"
36
37 typedef enum {
38         ENTRY_REPLY,
39         ENTRY_THROW,
40         ENTRY_EMIT,
41 } QueueEntryType;
42
43 typedef struct {
44         QueueEntryType entry_type;
45         union {
46                 struct {
47                         GVariant *parameters;
48                 } reply;
49                 struct {
50                         GError *error;
51                 } throw;
52                 struct {
53                         gchar *interface_name;
54                         gchar *signal_name;
55                         GVariant *parameters;
56                 } emit;
57         };
58 } QueueEntry;
59
60 static void
61 queue_entry_free (QueueEntry *entry)
62 {
63         switch (entry->entry_type) {
64                 case ENTRY_REPLY:
65                         g_variant_unref (entry->reply.parameters);
66                         break;
67                 case ENTRY_THROW:
68                         g_error_free (entry->throw.error);
69                         break;
70                 case ENTRY_EMIT:
71                         g_free (entry->emit.interface_name);
72                         g_free (entry->emit.signal_name);
73                         g_variant_unref (entry->emit.parameters);
74                         break;
75                 default:
76                         g_assert_not_reached ();
77         }
78
79         g_slice_free (QueueEntry, entry);
80 }
81
82 static void dfsm_dbus_output_sequence_iface_init (DfsmOutputSequenceInterface *iface);
83 static void dfsm_dbus_output_sequence_dispose (GObject *object);
84 static void dfsm_dbus_output_sequence_finalize (GObject *object);
85 static void dfsm_dbus_output_sequence_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
86 static void dfsm_dbus_output_sequence_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
87
88 static void dfsm_dbus_output_sequence_output (DfsmOutputSequence *sequence, GError **error);
89 static void dfsm_dbus_output_sequence_add_reply (DfsmOutputSequence *sequence, GVariant *parameters);
90 static void dfsm_dbus_output_sequence_add_throw (DfsmOutputSequence *sequence, GError *throw_error);
91 static void dfsm_dbus_output_sequence_add_emit (DfsmOutputSequence *sequence, const gchar *interface_name, const gchar *signal_name,
92                                                 GVariant *parameters);
93
94 struct _DfsmDBusOutputSequencePrivate {
95         GDBusConnection *connection;
96         gchar *object_path;
97         GDBusMethodInvocation *invocation;
98         GQueue/*<QueueEntry>*/ output_queue; /* head is the oldest entry (i.e. the one to get executed first) */
99 };
100
101 enum {
102         PROP_CONNECTION = 1,
103         PROP_OBJECT_PATH,
104         PROP_METHOD_INVOCATION,
105 };
106
107 G_DEFINE_TYPE_EXTENDED (DfsmDBusOutputSequence, dfsm_dbus_output_sequence, G_TYPE_OBJECT, 0,
108                         G_IMPLEMENT_INTERFACE (DFSM_TYPE_OUTPUT_SEQUENCE, dfsm_dbus_output_sequence_iface_init))
109
110 static void
111 dfsm_dbus_output_sequence_class_init (DfsmDBusOutputSequenceClass *klass)
112 {
113         GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
114
115         g_type_class_add_private (klass, sizeof (DfsmDBusOutputSequencePrivate));
116
117         gobject_class->get_property = dfsm_dbus_output_sequence_get_property;
118         gobject_class->set_property = dfsm_dbus_output_sequence_set_property;
119         gobject_class->dispose = dfsm_dbus_output_sequence_dispose;
120         gobject_class->finalize = dfsm_dbus_output_sequence_finalize;
121
122         g_object_class_install_property (gobject_class, PROP_CONNECTION,
123                                          g_param_spec_object ("connection",
124                                                               "Connection", "D-Bus connection.",
125                                                               G_TYPE_DBUS_CONNECTION,
126                                                               G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
127
128         g_object_class_install_property (gobject_class, PROP_OBJECT_PATH,
129                                          g_param_spec_string ("object-path",
130                                                               "Object Path", "Path of the D-Bus object this output sequence is for.",
131                                                               NULL,
132                                                               G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
133
134         g_object_class_install_property (gobject_class, PROP_METHOD_INVOCATION,
135                                          g_param_spec_object ("method-invocation",
136                                                               "Method Invocation",
137                                                               "Data about the D-Bus method invocation which triggered this output sequence.",
138                                                               G_TYPE_DBUS_METHOD_INVOCATION,
139                                                               G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
140 }
141
142 static void
143 dfsm_dbus_output_sequence_init (DfsmDBusOutputSequence *self)
144 {
145         self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, DFSM_TYPE_DBUS_OUTPUT_SEQUENCE, DfsmDBusOutputSequencePrivate);
146
147         /* Initialise the queue. */
148         g_queue_init (&self->priv->output_queue);
149 }
150
151 static void
152 dfsm_dbus_output_sequence_iface_init (DfsmOutputSequenceInterface *iface)
153 {
154         iface->output = dfsm_dbus_output_sequence_output;
155         iface->add_reply = dfsm_dbus_output_sequence_add_reply;
156         iface->add_throw = dfsm_dbus_output_sequence_add_throw;
157         iface->add_emit = dfsm_dbus_output_sequence_add_emit;
158 }
159
160 static void
161 dfsm_dbus_output_sequence_dispose (GObject *object)
162 {
163         DfsmDBusOutputSequencePrivate *priv = DFSM_DBUS_OUTPUT_SEQUENCE (object)->priv;
164
165         g_clear_object (&priv->invocation);
166         g_clear_object (&priv->connection);
167
168         /* Chain up to the parent class */
169         G_OBJECT_CLASS (dfsm_dbus_output_sequence_parent_class)->dispose (object);
170 }
171
172 static void
173 dfsm_dbus_output_sequence_finalize (GObject *object)
174 {
175         DfsmDBusOutputSequencePrivate *priv = DFSM_DBUS_OUTPUT_SEQUENCE (object)->priv;
176         QueueEntry *queue_entry;
177
178         /* Free any remaining entries in the queue. */
179         while ((queue_entry = g_queue_pop_head (&priv->output_queue)) != NULL) {
180                 queue_entry_free (queue_entry);
181         }
182
183         g_free (priv->object_path);
184
185         /* Chain up to the parent class */
186         G_OBJECT_CLASS (dfsm_dbus_output_sequence_parent_class)->finalize (object);
187 }
188
189 static void
190 dfsm_dbus_output_sequence_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
191 {
192         DfsmDBusOutputSequencePrivate *priv = DFSM_DBUS_OUTPUT_SEQUENCE (object)->priv;
193
194         switch (property_id) {
195                 case PROP_CONNECTION:
196                         g_value_set_object (value, priv->connection);
197                         break;
198                 case PROP_OBJECT_PATH:
199                         g_value_set_string (value, priv->object_path);
200                         break;
201                 case PROP_METHOD_INVOCATION:
202                         g_value_set_object (value, priv->invocation);
203                         break;
204                 default:
205                         /* We don't have any other property... */
206                         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
207                         break;
208         }
209 }
210
211 static void
212 dfsm_dbus_output_sequence_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
213 {
214         DfsmDBusOutputSequencePrivate *priv = DFSM_DBUS_OUTPUT_SEQUENCE (object)->priv;
215
216         switch (property_id) {
217                 case PROP_CONNECTION:
218                         /* Construct-only */
219                         priv->connection = g_value_dup_object (value);
220                         break;
221                 case PROP_OBJECT_PATH:
222                         /* Construct-only */
223                         priv->object_path = g_value_dup_string (value);
224                         break;
225                 case PROP_METHOD_INVOCATION:
226                         /* Construct-only */
227                         priv->invocation = g_value_dup_object (value);
228                         break;
229                 default:
230                         /* We don't have any other property... */
231                         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
232                         break;
233         }
234 }
235
236 static void
237 dfsm_dbus_output_sequence_output (DfsmOutputSequence *sequence, GError **error)
238 {
239         DfsmDBusOutputSequencePrivate *priv = DFSM_DBUS_OUTPUT_SEQUENCE (sequence)->priv;
240         QueueEntry *queue_entry;
241         GError *child_error = NULL;
242
243         /* Loop through the queue. */
244         while (child_error == NULL && (queue_entry = g_queue_pop_head (&priv->output_queue)) != NULL) {
245                 switch (queue_entry->entry_type) {
246                         case ENTRY_REPLY: {
247                                 gchar *reply_parameters_string;
248
249                                 /* Reply to the method call. */
250                                 g_assert (priv->invocation != NULL);
251                                 g_dbus_method_invocation_return_value (priv->invocation, queue_entry->reply.parameters);
252
253                                 /* Debug output. */
254                                 reply_parameters_string = g_variant_print (queue_entry->reply.parameters, FALSE);
255                                 g_debug ("Replying to D-Bus method call with out parameters: %s", reply_parameters_string);
256                                 g_free (reply_parameters_string);
257
258                                 break;
259                         }
260                         case ENTRY_THROW: {
261                                 /* Reply to the method call with an error. */
262                                 g_dbus_method_invocation_return_gerror (priv->invocation, queue_entry->throw.error);
263
264                                 /* Debug output. */
265                                 g_debug ("Throwing D-Bus error with domain ‘%s’ and code %i. Message: %s",
266                                          g_quark_to_string (queue_entry->throw.error->domain), queue_entry->throw.error->code,
267                                          queue_entry->throw.error->message);
268
269                                 break;
270                         }
271                         case ENTRY_EMIT: {
272                                 gchar *emit_parameters_string;
273
274                                 /* Emit a signal. */
275                                 g_dbus_connection_emit_signal (priv->connection, NULL, priv->object_path, queue_entry->emit.interface_name,
276                                                                queue_entry->emit.signal_name, queue_entry->emit.parameters, &child_error);
277
278                                 /* Debug output. */
279                                 emit_parameters_string = g_variant_print (queue_entry->emit.parameters, FALSE);
280                                 g_debug ("Emitting D-Bus signal ‘%s’ on interface ‘%s’ of object ‘%s’. Parameters: %s", queue_entry->emit.signal_name,
281                                          queue_entry->emit.interface_name, priv->object_path, emit_parameters_string);
282                                 g_free (emit_parameters_string);
283
284                                 /* Error? Skip the rest of the output. The remaining entries will be cleaned up when the OutputSequence is finalised.
285                                  * Note that we're only supposed to encounter errors here if the signal name is invalid (and similar such situations),
286                                  * so it's debatable that this code will ever be called. */
287                                 if (child_error != NULL) {
288                                         g_propagate_error (error, child_error);
289                                         break;
290                                 }
291
292                                 break;
293                         }
294                         default:
295                                 g_assert_not_reached ();
296                 }
297
298                 queue_entry_free (queue_entry);
299         }
300 }
301
302 static void
303 dfsm_dbus_output_sequence_add_reply (DfsmOutputSequence *sequence, GVariant *parameters)
304 {
305         DfsmDBusOutputSequencePrivate *priv = DFSM_DBUS_OUTPUT_SEQUENCE (sequence)->priv;
306         QueueEntry *queue_entry;
307
308         queue_entry = g_slice_new (QueueEntry);
309         queue_entry->entry_type = ENTRY_REPLY;
310         queue_entry->reply.parameters = g_variant_ref (parameters);
311
312         g_queue_push_tail (&priv->output_queue, queue_entry);
313 }
314
315 static void
316 dfsm_dbus_output_sequence_add_throw (DfsmOutputSequence *sequence, GError *throw_error)
317 {
318         DfsmDBusOutputSequencePrivate *priv = DFSM_DBUS_OUTPUT_SEQUENCE (sequence)->priv;
319         QueueEntry *queue_entry;
320
321         queue_entry = g_slice_new (QueueEntry);
322         queue_entry->entry_type = ENTRY_THROW;
323         queue_entry->throw.error = g_error_copy (throw_error);
324
325         g_queue_push_tail (&priv->output_queue, queue_entry);
326 }
327
328 static void
329 dfsm_dbus_output_sequence_add_emit (DfsmOutputSequence *sequence, const gchar *interface_name, const gchar *signal_name, GVariant *parameters)
330 {
331         DfsmDBusOutputSequencePrivate *priv = DFSM_DBUS_OUTPUT_SEQUENCE (sequence)->priv;
332         QueueEntry *queue_entry;
333
334         queue_entry = g_slice_new (QueueEntry);
335         queue_entry->entry_type = ENTRY_EMIT;
336         queue_entry->emit.interface_name = g_strdup (interface_name);
337         queue_entry->emit.signal_name = g_strdup (signal_name);
338         queue_entry->emit.parameters = g_variant_ref (parameters);
339
340         g_queue_push_tail (&priv->output_queue, queue_entry);
341 }
342
343 /**
344  * dfsm_dbus_output_sequence_new:
345  * @connection: a D-Bus connection to output the sequence over
346  * @object_path: D-Bus path of the object the output sequence will occur on
347  * @invocation: (allow-none): details of the triggering method invocation, or %NULL
348  *
349  * Create a new #DfsmDBusOutputSequence.
350  *
351  * Return value: (transfer full): a new #DfsmDBusOutputSequence
352  */
353 DfsmDBusOutputSequence *
354 dfsm_dbus_output_sequence_new (GDBusConnection *connection, const gchar *object_path, GDBusMethodInvocation *invocation)
355 {
356         g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
357         g_return_val_if_fail (object_path != NULL && *object_path != '\0', NULL);
358         g_return_val_if_fail (invocation == NULL || G_IS_DBUS_METHOD_INVOCATION (invocation), NULL);
359         g_return_val_if_fail (invocation == NULL || g_dbus_method_invocation_get_connection (invocation) == connection, NULL);
360         g_return_val_if_fail (invocation == NULL || strcmp (object_path, g_dbus_method_invocation_get_object_path (invocation)) == 0, NULL);
361
362         return g_object_new (DFSM_TYPE_DBUS_OUTPUT_SEQUENCE,
363                              "connection", connection,
364                              "object-path", object_path,
365                              "method-invocation", invocation,
366                              NULL);
367 }