1 /*
2  *  objects for AddDispatchOperation calls
3  *
4  * Copyright © 2010 Collabora Ltd. <http://www.collabora.co.uk/>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library 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 GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  */
20 
21 /**
22  * SECTION:add-dispatch-operation-context
23  * @title: TpAddDispatchOperationContext
24  * @short_description: context of a Approver.AddDispatchOperation() call
25  *
26  * Object used to represent the context of a Approver.AddDispatchOperation()
27  * D-Bus call on a #TpBaseClient.
28  */
29 
30 /**
31  * TpAddDispatchOperationContext:
32  *
33  * Data structure representing the context of a Approver.AddDispatchOperation()
34  * call.
35  *
36  * Since: 0.11.5
37  */
38 
39 /**
40  * TpAddDispatchOperationContextClass:
41  *
42  * The class of a #TpAddDispatchOperationContext.
43  *
44  * Since: 0.11.5
45  */
46 
47 #include "config.h"
48 
49 #include "telepathy-glib/add-dispatch-operation-context-internal.h"
50 #include "telepathy-glib/add-dispatch-operation-context.h"
51 
52 #include <telepathy-glib/channel.h>
53 #include <telepathy-glib/dbus.h>
54 #include <telepathy-glib/gtypes.h>
55 
56 #define DEBUG_FLAG TP_DEBUG_CLIENT
57 #include "telepathy-glib/debug-internal.h"
58 #include "telepathy-glib/util-internal.h"
59 
60 struct _TpAddDispatchOperationContextClass {
61     /*<private>*/
62     GObjectClass parent_class;
63 };
64 
65 G_DEFINE_TYPE(TpAddDispatchOperationContext,
66     tp_add_dispatch_operation_context, G_TYPE_OBJECT)
67 
68 enum {
69     PROP_ACCOUNT = 1,
70     PROP_CONNECTION,
71     PROP_CHANNELS,
72     PROP_DISPATCH_OPERATION,
73     PROP_DBUS_CONTEXT,
74     N_PROPS
75 };
76 
77 struct _TpAddDispatchOperationContextPrivate
78 {
79   TpAddDispatchOperationContextState state;
80   GSimpleAsyncResult *result;
81   DBusGMethodInvocation *dbus_context;
82 
83   /* Number of calls we are waiting they return. Once they have all returned
84    * the context is considered as prepared */
85   guint num_pending;
86 };
87 
88 static void
tp_add_dispatch_operation_context_init(TpAddDispatchOperationContext * self)89 tp_add_dispatch_operation_context_init (TpAddDispatchOperationContext *self)
90 {
91   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
92       TP_TYPE_ADD_DISPATCH_OPERATION_CONTEXT,
93       TpAddDispatchOperationContextPrivate);
94 
95   self->priv->state = TP_ADD_DISPATCH_OPERATION_CONTEXT_STATE_NONE;
96 }
97 
98 static void
tp_add_dispatch_operation_context_dispose(GObject * object)99 tp_add_dispatch_operation_context_dispose (GObject *object)
100 {
101   TpAddDispatchOperationContext *self = TP_ADD_DISPATCH_OPERATION_CONTEXT (
102       object);
103   void (*dispose) (GObject *) =
104     G_OBJECT_CLASS (tp_add_dispatch_operation_context_parent_class)->dispose;
105 
106   if (self->priv->state == TP_ADD_DISPATCH_OPERATION_CONTEXT_STATE_NONE ||
107       self->priv->state == TP_ADD_DISPATCH_OPERATION_CONTEXT_STATE_DELAYED)
108     {
109       GError error = { TP_ERROR, TP_ERROR_NOT_IMPLEMENTED,
110           "Disposing the TpAddDispatchOperationContext" };
111 
112       WARNING ("Disposing a context in the %s state",
113           self->priv->state == TP_ADD_DISPATCH_OPERATION_CONTEXT_STATE_NONE ?
114           "none": "delayed");
115 
116       tp_add_dispatch_operation_context_fail (self, &error);
117     }
118 
119   if (self->account != NULL)
120     {
121       g_object_unref (self->account);
122       self->account = NULL;
123     }
124 
125   if (self->connection != NULL)
126     {
127       g_object_unref (self->connection);
128       self->connection = NULL;
129     }
130 
131   if (self->channels != NULL)
132     {
133       g_ptr_array_unref (self->channels);
134       self->channels = NULL;
135     }
136 
137   if (self->dispatch_operation != NULL)
138     {
139       g_object_unref (self->dispatch_operation);
140       self->dispatch_operation = NULL;
141     }
142 
143   if (self->priv->result != NULL)
144     {
145       g_object_unref (self->priv->result);
146       self->priv->result = NULL;
147     }
148 
149   if (dispose != NULL)
150     dispose (object);
151 }
152 
153 static void
tp_add_dispatch_operation_context_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)154 tp_add_dispatch_operation_context_get_property (GObject *object,
155     guint property_id,
156     GValue *value,
157     GParamSpec *pspec)
158 {
159   TpAddDispatchOperationContext *self = TP_ADD_DISPATCH_OPERATION_CONTEXT (
160       object);
161 
162   switch (property_id)
163     {
164       case PROP_ACCOUNT:
165         g_value_set_object (value, self->account);
166         break;
167 
168       case PROP_CONNECTION:
169         g_value_set_object (value, self->connection);
170         break;
171 
172       case PROP_CHANNELS:
173         g_value_set_boxed (value, self->channels);
174         break;
175 
176       case PROP_DISPATCH_OPERATION:
177         g_value_set_object (value, self->dispatch_operation);
178         break;
179 
180       default:
181         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
182         break;
183   }
184 }
185 
186 static void
tp_add_dispatch_operation_context_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)187 tp_add_dispatch_operation_context_set_property (GObject *object,
188     guint property_id,
189     const GValue *value,
190     GParamSpec *pspec)
191 {
192   TpAddDispatchOperationContext *self = TP_ADD_DISPATCH_OPERATION_CONTEXT (
193       object);
194 
195   switch (property_id)
196     {
197       case PROP_ACCOUNT:
198         self->account = g_value_dup_object (value);
199         break;
200 
201       case PROP_CONNECTION:
202         self->connection = g_value_dup_object (value);
203         break;
204 
205       case PROP_CHANNELS:
206         self->channels = g_value_dup_boxed (value);
207         break;
208 
209       case PROP_DISPATCH_OPERATION:
210         self->dispatch_operation = g_value_dup_object (value);
211         break;
212 
213       case PROP_DBUS_CONTEXT:
214         self->priv->dbus_context = g_value_get_pointer (value);
215         break;
216 
217       default:
218         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
219         break;
220   }
221 }
222 
223 static void
tp_add_dispatch_operation_context_constructed(GObject * object)224 tp_add_dispatch_operation_context_constructed (GObject *object)
225 {
226   TpAddDispatchOperationContext *self = TP_ADD_DISPATCH_OPERATION_CONTEXT (
227       object);
228   void (*chain_up) (GObject *) =
229     ((GObjectClass *)
230       tp_add_dispatch_operation_context_parent_class)->constructed;
231 
232   if (chain_up != NULL)
233     chain_up (object);
234 
235   g_assert (self->account != NULL);
236   g_assert (self->connection != NULL);
237   g_assert (self->channels != NULL);
238   g_assert (self->dispatch_operation != NULL);
239   g_assert (self->priv->dbus_context != NULL);
240 }
241 
242 static void
tp_add_dispatch_operation_context_class_init(TpAddDispatchOperationContextClass * cls)243 tp_add_dispatch_operation_context_class_init (
244     TpAddDispatchOperationContextClass *cls)
245 {
246   GObjectClass *object_class = G_OBJECT_CLASS (cls);
247   GParamSpec *param_spec;
248 
249   g_type_class_add_private (cls, sizeof (TpAddDispatchOperationContextPrivate));
250 
251   object_class->get_property = tp_add_dispatch_operation_context_get_property;
252   object_class->set_property = tp_add_dispatch_operation_context_set_property;
253   object_class->constructed = tp_add_dispatch_operation_context_constructed;
254   object_class->dispose = tp_add_dispatch_operation_context_dispose;
255 
256  /**
257    * TpAddDispatchOperationContext:account:
258    *
259    * A #TpAccount object representing the Account of the DispatchOperation
260    * that has been passed to AddDispatchOperation.
261    * Read-only except during construction.
262    *
263    * This property can't be %NULL.
264    *
265    * Since: 0.11.5
266    */
267   param_spec = g_param_spec_object ("account", "TpAccount",
268       "The TpAccount of the context",
269       TP_TYPE_ACCOUNT,
270       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
271   g_object_class_install_property (object_class, PROP_ACCOUNT,
272       param_spec);
273 
274   /**
275    * TpAddDispatchOperationContext:connection:
276    *
277    * A #TpConnection object representing the Connection of the DispatchOperation
278    * that has been passed to AddDispatchOperation.
279    * Read-only except during construction.
280    *
281    * This property can't be %NULL.
282    *
283    * Since: 0.11.5
284    */
285   param_spec = g_param_spec_object ("connection", "TpConnection",
286       "The TpConnection of the context",
287       TP_TYPE_CONNECTION,
288       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
289   g_object_class_install_property (object_class, PROP_CONNECTION,
290       param_spec);
291 
292   /**
293    * TpAddDispatchOperationContext:channels:
294    *
295    * A #GPtrArray containing #TpChannel objects representing the channels
296    * that have been passed to AddDispatchOperation.
297    * Read-only except during construction.
298    *
299    * This property can't be %NULL.
300    *
301    * Since: 0.11.5
302    */
303   param_spec = g_param_spec_boxed ("channels", "GPtrArray of TpChannel",
304       "The TpChannels that have been passed to AddDispatchOperation",
305       G_TYPE_PTR_ARRAY,
306       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
307   g_object_class_install_property (object_class, PROP_CHANNELS,
308       param_spec);
309 
310   /**
311    * TpAddDispatchOperationContext:dispatch-operation:
312    *
313    * A #TpChannelDispatchOperation object representing the
314    * ChannelDispatchOperation that has been passed to AddDispatchOperation.
315    * Read-only except during construction.
316    *
317    * This property can't be %NULL.
318    *
319    * Since: 0.11.5
320    */
321   param_spec = g_param_spec_object ("dispatch-operation",
322      "TpChannelDispatchOperation",
323      "The TpChannelDispatchOperation that has been passed to "
324      "AddDispatchOperation",
325      TP_TYPE_CHANNEL_DISPATCH_OPERATION,
326      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
327   g_object_class_install_property (object_class, PROP_DISPATCH_OPERATION,
328       param_spec);
329 
330   /**
331    * TpAddDispatchOperationContext:dbus-context: (skip)
332    *
333    * The #DBusGMethodInvocation representing the D-Bus context of the
334    * AddDispatchOperation call.
335    * Can only be written during construction.
336    *
337    * Since: 0.11.5
338    */
339   param_spec = g_param_spec_pointer ("dbus-context", "D-Bus context",
340       "The DBusGMethodInvocation associated with the AddDispatchOperation call",
341       G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
342   g_object_class_install_property (object_class, PROP_DBUS_CONTEXT,
343       param_spec);
344 }
345 
346 TpAddDispatchOperationContext *
_tp_add_dispatch_operation_context_new(TpAccount * account,TpConnection * connection,GPtrArray * channels,TpChannelDispatchOperation * dispatch_operation,DBusGMethodInvocation * dbus_context)347 _tp_add_dispatch_operation_context_new (
348     TpAccount *account,
349     TpConnection *connection,
350     GPtrArray *channels,
351     TpChannelDispatchOperation *dispatch_operation,
352     DBusGMethodInvocation *dbus_context)
353 {
354   return g_object_new (TP_TYPE_ADD_DISPATCH_OPERATION_CONTEXT,
355       "account", account,
356       "connection", connection,
357       "channels", channels,
358       "dispatch-operation", dispatch_operation,
359       "dbus-context", dbus_context,
360       NULL);
361 }
362 
363 /**
364  * tp_add_dispatch_operation_context_accept:
365  * @self: a #TpAddDispatchOperationContext
366  *
367  * Called by #TpBaseClientClassAddDispatchOperationImpl when it's done so
368  * the D-Bus method can return.
369  *
370  * Since: 0.11.5
371  */
372 void
tp_add_dispatch_operation_context_accept(TpAddDispatchOperationContext * self)373 tp_add_dispatch_operation_context_accept (TpAddDispatchOperationContext *self)
374 {
375   g_return_if_fail (self->priv->state ==
376       TP_ADD_DISPATCH_OPERATION_CONTEXT_STATE_NONE
377       || self->priv->state == TP_ADD_DISPATCH_OPERATION_CONTEXT_STATE_DELAYED);
378   g_return_if_fail (self->priv->dbus_context != NULL);
379 
380   self->priv->state = TP_ADD_DISPATCH_OPERATION_CONTEXT_STATE_DONE;
381   dbus_g_method_return (self->priv->dbus_context);
382 
383   self->priv->dbus_context = NULL;
384 }
385 
386 /**
387  * tp_add_dispatch_operation_context_fail:
388  * @self: a #TpAddDispatchOperationContext
389  * @error: the error to return from the method
390  *
391  * Called by #TpBaseClientClassAddDispatchOperationImpl to raise a D-Bus error.
392  *
393  * Since: 0.11.5
394  */
395 void
tp_add_dispatch_operation_context_fail(TpAddDispatchOperationContext * self,const GError * error)396 tp_add_dispatch_operation_context_fail (TpAddDispatchOperationContext *self,
397     const GError *error)
398 {
399   g_return_if_fail (self->priv->state ==
400       TP_ADD_DISPATCH_OPERATION_CONTEXT_STATE_NONE
401       || self->priv->state == TP_ADD_DISPATCH_OPERATION_CONTEXT_STATE_DELAYED);
402   g_return_if_fail (self->priv->dbus_context != NULL);
403 
404   self->priv->state = TP_ADD_DISPATCH_OPERATION_CONTEXT_STATE_FAILED;
405   dbus_g_method_return_error (self->priv->dbus_context, error);
406 
407   self->priv->dbus_context = NULL;
408 }
409 
410 /**
411  * tp_add_dispatch_operation_context_delay:
412  * @self: a #TpAddDispatchOperationContext
413  *
414  * Called by #TpBaseClientClassAddDispatchOperationImpl to indicate that it
415  * implements the method in an async way. The caller must take a reference
416  * to the #TpAddDispatchOperationContext before calling this function, and
417  * is responsible for calling either
418  * tp_add_dispatch_operation_context_accept() or
419  * tp_add_dispatch_operation_context_fail() later.
420  *
421  * Since: 0.11.5
422  */
423 void
tp_add_dispatch_operation_context_delay(TpAddDispatchOperationContext * self)424 tp_add_dispatch_operation_context_delay (TpAddDispatchOperationContext *self)
425 {
426   g_return_if_fail (self->priv->state ==
427       TP_ADD_DISPATCH_OPERATION_CONTEXT_STATE_NONE);
428 
429   self->priv->state = TP_ADD_DISPATCH_OPERATION_CONTEXT_STATE_DELAYED;
430 }
431 
432 TpAddDispatchOperationContextState
_tp_add_dispatch_operation_context_get_state(TpAddDispatchOperationContext * self)433 _tp_add_dispatch_operation_context_get_state (
434     TpAddDispatchOperationContext *self)
435 {
436   return self->priv->state;
437 }
438 
439 static gboolean
context_is_prepared(TpAddDispatchOperationContext * self)440 context_is_prepared (TpAddDispatchOperationContext *self)
441 {
442   return self->priv->num_pending == 0;
443 }
444 
445 static void
context_check_prepare(TpAddDispatchOperationContext * self)446 context_check_prepare (TpAddDispatchOperationContext *self)
447 {
448   if (!context_is_prepared (self))
449     return;
450 
451   /*  is prepared */
452   g_simple_async_result_complete (self->priv->result);
453 
454   g_object_unref (self->priv->result);
455   self->priv->result = NULL;
456 }
457 
458 static void
cdo_prepare_cb(GObject * source,GAsyncResult * result,gpointer user_data)459 cdo_prepare_cb (GObject *source,
460     GAsyncResult *result,
461     gpointer user_data)
462 {
463   TpAddDispatchOperationContext *self = user_data;
464   GError *error = NULL;
465 
466   if (self->priv->result == NULL)
467     goto out;
468 
469   if (!tp_proxy_prepare_finish (source, result, &error))
470     {
471       DEBUG ("Failed to prepare ChannelDispatchOperation: %s", error->message);
472 
473       g_error_free (error);
474     }
475 
476   self->priv->num_pending--;
477   context_check_prepare (self);
478 
479 out:
480   g_object_unref (self);
481 }
482 
483 static void
account_prepare_cb(GObject * source,GAsyncResult * result,gpointer user_data)484 account_prepare_cb (GObject *source,
485     GAsyncResult *result,
486     gpointer user_data)
487 {
488   TpAddDispatchOperationContext *self = user_data;
489   GError *error = NULL;
490 
491   if (self->priv->result == NULL)
492     goto out;
493 
494   if (!tp_proxy_prepare_finish (source, result, &error))
495     {
496       DEBUG ("Failed to prepare account: %s", error->message);
497       g_error_free (error);
498     }
499 
500   self->priv->num_pending--;
501   context_check_prepare (self);
502 
503 out:
504   g_object_unref (self);
505 }
506 
507 static void
conn_prepare_cb(GObject * source,GAsyncResult * result,gpointer user_data)508 conn_prepare_cb (GObject *source,
509     GAsyncResult *result,
510     gpointer user_data)
511 {
512   TpAddDispatchOperationContext *self = user_data;
513   GError *error = NULL;
514 
515   if (self->priv->result == NULL)
516     goto out;
517 
518   if (!tp_proxy_prepare_finish (source, result, &error))
519     {
520       DEBUG ("Failed to prepare connection: %s", error->message);
521       g_error_free (error);
522     }
523 
524   self->priv->num_pending--;
525   context_check_prepare (self);
526 
527 out:
528   g_object_unref (self);
529 }
530 
531 static void
adoc_channel_prepare_cb(GObject * source,GAsyncResult * result,gpointer user_data)532 adoc_channel_prepare_cb (GObject *source,
533     GAsyncResult *result,
534     gpointer user_data)
535 {
536   TpAddDispatchOperationContext *self = user_data;
537   GError *error = NULL;
538 
539   if (self->priv->result == NULL)
540     goto out;
541 
542   if (!tp_proxy_prepare_finish (source, result, &error))
543     {
544       DEBUG ("Failed to prepare channel: %s", error->message);
545 
546       g_error_free (error);
547     }
548 
549   self->priv->num_pending--;
550   context_check_prepare (self);
551 
552 out:
553   g_object_unref (self);
554 }
555 
556 static void
context_prepare(TpAddDispatchOperationContext * self,const GQuark * account_features,const GQuark * connection_features,const GQuark * channel_features)557 context_prepare (TpAddDispatchOperationContext *self,
558     const GQuark *account_features,
559     const GQuark *connection_features,
560     const GQuark *channel_features)
561 {
562   GQuark cdo_features[] = { TP_CHANNEL_DISPATCH_OPERATION_FEATURE_CORE, 0 };
563   guint i;
564 
565   self->priv->num_pending = 3;
566 
567   tp_proxy_prepare_async (self->account, account_features,
568       account_prepare_cb, g_object_ref (self));
569 
570   tp_proxy_prepare_async (self->connection, connection_features,
571       conn_prepare_cb, g_object_ref (self));
572 
573   tp_proxy_prepare_async (self->dispatch_operation, cdo_features,
574       cdo_prepare_cb, g_object_ref (self));
575 
576   for (i = 0; i < self->channels->len; i++)
577     {
578       TpChannel *channel = g_ptr_array_index (self->channels, i);
579 
580       self->priv->num_pending++;
581 
582       tp_proxy_prepare_async (channel, channel_features,
583           adoc_channel_prepare_cb, g_object_ref (self));
584     }
585 }
586 
587 void
_tp_add_dispatch_operation_context_prepare_async(TpAddDispatchOperationContext * self,const GQuark * account_features,const GQuark * connection_features,const GQuark * channel_features,GAsyncReadyCallback callback,gpointer user_data)588 _tp_add_dispatch_operation_context_prepare_async (
589     TpAddDispatchOperationContext *self,
590     const GQuark *account_features,
591     const GQuark *connection_features,
592     const GQuark *channel_features,
593     GAsyncReadyCallback callback,
594     gpointer user_data)
595 {
596   g_return_if_fail (TP_IS_ADD_DISPATCH_OPERATION_CONTEXT (self));
597   /* This is only used once, by TpBaseClient, so for simplicity, we only
598    * allow one asynchronous preparation */
599   g_return_if_fail (self->priv->result == NULL);
600 
601   self->priv->result = g_simple_async_result_new (G_OBJECT (self),
602       callback, user_data, _tp_add_dispatch_operation_context_prepare_async);
603 
604   context_prepare (self, account_features, connection_features,
605       channel_features);
606 }
607 
608 gboolean
_tp_add_dispatch_operation_context_prepare_finish(TpAddDispatchOperationContext * self,GAsyncResult * result,GError ** error)609 _tp_add_dispatch_operation_context_prepare_finish (
610     TpAddDispatchOperationContext *self,
611     GAsyncResult *result,
612     GError **error)
613 {
614   _tp_implement_finish_void (self,
615       _tp_add_dispatch_operation_context_prepare_async);
616 }
617