1 /*
2  * presence-mixin.c - Source for TpPresenceMixin
3  * Copyright (C) 2005-2008 Collabora Ltd.
4  * Copyright (C) 2005-2007 Nokia Corporation
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:presence-mixin
23  * @title: TpPresenceMixin
24  * @short_description: a mixin implementation of the Presence connection
25  *  interface
26  * @see_also: #TpSvcConnectionInterfacePresence
27  *
28  * This mixin can be added to a #TpBaseConnection subclass to implement the
29  * SimplePresence and/or Presence interfaces. Implementing both interfaces
30  * (as described below) is recommended. In particular, you must implement the
31  * old-style Presence interface if compatibility with telepathy-glib
32  * versions older than 0.11.13 is required.
33  *
34  * To use the presence mixin, include a #TpPresenceMixinClass somewhere in your
35  * class structure and a #TpPresenceMixin somewhere in your instance structure,
36  * and call tp_presence_mixin_class_init() from your class_init function,
37  * tp_presence_mixin_init() from your init function or constructor, and
38  * tp_presence_mixin_finalize() from your dispose or finalize function.
39  *
40  * <section>
41  * <title>Implementing SimplePresence</title>
42  * <para>
43  *   Since 0.7.13 this mixin supports the entire SimplePresence interface.
44  *   You can implement #TpSvcConnectionInterfaceSimplePresence as follows:
45  *   <itemizedlist>
46  *     <listitem>
47  *       <para>use the #TpContactsMixin and
48  *        <link linkend="telepathy-glib-dbus-properties-mixin">TpDBusPropertiesMixin</link>;</para>
49  *     </listitem>
50  *     <listitem>
51  *       <para>pass tp_presence_mixin_simple_presence_iface_init() as an
52  *         argument to G_IMPLEMENT_INTERFACE(), like so:
53  *       </para>
54  *       |[
55  *       G_DEFINE_TYPE_WITH_CODE (MyConnection, my_connection,
56  *           TP_TYPE_BASE_CONNECTION,
57  *           // ...
58  *           G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
59  *               tp_presence_mixin_simple_presence_iface_init);
60  *           // ...
61  *           )
62  *       ]|
63  *     </listitem>
64  *     <listitem>
65  *       <para>
66  *         call tp_presence_mixin_simple_presence_init_dbus_properties() in the
67  *         #GTypeInfo class_init function;
68  *       </para>
69  *     </listitem>
70  *     <listitem>
71  *       <para>
72  *         call tp_presence_mixin_simple_presence_register_with_contacts_mixin()
73  *         in the #GObjectClass constructed function.
74  *       </para>
75  *     </listitem>
76  *   </itemizedlist>
77  * </para>
78  * </section> <!-- Simple Presence -->
79  * <section>
80  * <title>Implementing old-style Presence</title>
81  * <para>
82  *   This mixin also supports a large subset of the deprecated Presence
83  *   interface. It does not support protocols where it is possible to set
84  *   multiple statuses on yourself at once (all presence statuses will have the
85  *   exclusive flag set), or last-activity-time information.
86  * </para>
87  * <para>
88  *   To use the presence mixin as the implementation of
89  *   #TpSvcConnectionInterfacePresence, use tp_presence_mixin_iface_init() as
90  *   the function you pass to G_IMPLEMENT_INTERFACE(), as in the following
91  *   example.  The presence mixin implements all of the D-Bus methods in the
92  *   Presence interface.
93  * </para>
94  * |[
95  * G_DEFINE_TYPE_WITH_CODE (MyConnection, my_connection,
96  *     TP_TYPE_BASE_CONNECTION,
97  *     // ...
98  *     G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_PRESENCE,
99  *         tp_presence_mixin_iface_init);
100  *     // ...
101  *     )
102  * ]|
103  * <para>
104  *   In telepathy-glib versions older than 0.11.13, every connection
105  *   that used the #TpPresenceMixin was required to implement
106  *   #TpSvcConnectionInterfacePresence; failing to do so would lead to an
107  *   assertion failure. Since 0.11.13, this is no longer required.
108  * </para>
109  * </section> <!-- complex Presence -->
110  *
111  * Since: 0.5.13
112  */
113 
114 /**
115  * TpPresenceStatusOptionalArgumentSpec:
116  * @name: Name of the argument as passed over D-Bus
117  * @dtype: D-Bus type signature of the argument
118  *
119  * Structure specifying a supported optional argument for a presence status.
120  *
121  * In addition to the fields documented here, there are two gpointer fields
122  * which must currently be %NULL. A meaning may be defined for these in a
123  * future version of telepathy-glib.
124  */
125 
126 /**
127  * TpPresenceStatusSpec:
128  * @name: String identifier of the presence status
129  * @presence_type: A type value, as specified by #TpConnectionPresenceType
130  * @self: Indicates if this status may be set on yourself
131  * @optional_arguments: An array of #TpPresenceStatusOptionalArgumentSpec
132  *  structures representing the optional arguments for this status, terminated
133  *  by a NULL name. If there are no optional arguments for a status, this can
134  *  be NULL. In modern Telepathy connection managers, the only optional
135  *  argument should be a string (type "s") named "message" on statuses
136  *  that have an optional human-readable message. All other optional arguments
137  *  are deprecated.
138  *
139  * Structure specifying a supported presence status.
140  *
141  * In addition to the fields documented here, there are two gpointer fields
142  * which must currently be %NULL. A meaning may be defined for these in a
143  * future version of telepathy-glib.
144  */
145 
146 /**
147  * TpPresenceStatus:
148  * @index: Index of the presence status in the provided supported presence
149  *  statuses array
150  * @optional_arguments: A GHashTable mapping of string identifiers to GValues
151  *  of the optional status arguments, if any. If there are no optional
152  *  arguments, this pointer may be NULL.
153  *
154  * Structure representing a presence status.
155  *
156  * In addition to the fields documented here, there are two gpointer fields
157  * which must currently be %NULL. A meaning may be defined for these in a
158  * future version of telepathy-glib.
159  *
160  * In modern Telepathy connection managers, the only optional
161  * argument should be a %G_TYPE_STRING named "message", on statuses
162  * that have an optional human-readable message. All other optional arguments
163  * are deprecated.
164  */
165 
166 /**
167  * TpPresenceMixinStatusAvailableFunc:
168  * @obj: An instance of a #TpBaseConnection subclass implementing the presence
169  *  interface with this mixin
170  * @which: An index into the array of #TpPresenceStatusSpec provided to
171  *  tp_presence_mixin_class_init()
172  *
173  * Signature of a callback to be used to determine if a given presence
174  * status can be set on the connection. Most users of this mixin do not need to
175  * supply an implementation of this callback: the value of
176  * #TpPresenceStatusSpec.self is enough to determine whether this is a
177  * user-settable presence, so %NULL should be passed to
178  * tp_presence_mixin_class_init() for this callback.
179  *
180  * One place where this callback may be needed is on XMPP: not all server
181  * implementation support the user becoming invisible. So an XMPP
182  * implementation would implement this function, so that—once connected—the
183  * hidden status is only available if the server supports it. Before the
184  * connection is connected, this callback should return %TRUE for every status
185  * that might possibly be supported: this allows the user to at least try to
186  * sign in as invisible.
187  *
188  * Returns: %TRUE if the status can be set on this connection; %FALSE if not.
189  */
190 
191 /**
192  * TpPresenceMixinGetContactStatusesFunc:
193  * @obj: An object with this mixin.
194  * @contacts: An array of #TpHandle for the contacts to get presence status for
195  * @error: Used to return a Telepathy D-Bus error if %NULL is returned
196  *
197  * Signature of the callback used to get the stored presence status of
198  * contacts. The returned hash table should have contact handles mapped to
199  * their respective presence statuses in #TpPresenceStatus structs.
200  *
201  * The returned hash table will be freed with g_hash_table_unref. The
202  * callback is responsible for ensuring that this does any cleanup that
203  * may be necessary.
204  *
205  * Returns: (transfer full): The contact presence on success, %NULL with
206  *  error set on error
207  */
208 
209 /**
210  * TpPresenceMixinSetOwnStatusFunc:
211  * @obj: An object with this mixin.
212  * @status: The status to set, or NULL for whatever the protocol defines as a
213  *  "default" status
214  * @error: Used to return a Telepathy D-Bus error if %FALSE is returned
215  *
216  * Signature of the callback used to commit changes to the user's own presence
217  * status in SetStatuses. It is also used in ClearStatus and RemoveStatus to
218  * reset the user's own status back to the "default" one with a %NULL status
219  * argument.
220  *
221  * The optional_arguments hash table in @status, if not NULL, will have been
222  * filtered so it only contains recognised parameters, so the callback
223  * need not (and cannot) check for unrecognised parameters. However, the
224  * types of the parameters are not currently checked, so the callback is
225  * responsible for doing so.
226  *
227  * The callback is responsible for emitting PresenceUpdate, if appropriate,
228  * by calling tp_presence_mixin_emit_presence_update().
229  *
230  * Returns: %TRUE if the operation was successful, %FALSE if not.
231  */
232 
233 /**
234  * TpPresenceMixinGetMaximumStatusMessageLengthFunc:
235  * @obj: An object with this mixin.
236  *
237  * Signature of a callback used to determine the maximum length of status
238  * messages. If this callback is provided and returns non-zero, the
239  * #TpPresenceMixinSetOwnStatusFunc implementation is responsible for
240  * truncating the message to fit this limit, if necessary.
241  *
242  * Returns: the maximum number of UTF-8 characters which may appear in a status
243  * message, or 0 if there is no limit.
244  * Since: 0.14.5
245  */
246 
247 /**
248  * TpPresenceMixinClass:
249  * @status_available: The status-available function that was passed to
250  *  tp_presence_mixin_class_init()
251  * @get_contact_statuses: The get-contact-statuses function that was passed to
252  *  tp_presence_mixin_class_init()
253  * @set_own_status: The set-own-status function that was passed to
254  *  tp_presence_mixin_class_init()
255  * @statuses: The presence statuses array that was passed to
256  *  tp_presence_mixin_class_init()
257  * @get_maximum_status_message_length: The callback used to discover the
258  *  the limit for status messages length, if any. Since: 0.14.5
259  *
260  * Structure to be included in the class structure of objects that
261  * use this mixin. Initialize it with tp_presence_mixin_class_init().
262  *
263  * If the protocol imposes a limit on the length of status messages, one should
264  * implement @get_maximum_status_message_length. If this callback is not
265  * implemented, it is assumed that there is no limit. The callback function
266  * should be set after calling tp_presence_mixin_class_init(), like so:
267  *
268  * |[
269  * TpPresenceMixinClass *mixin_class;
270  *
271  * tp_presence_mixin_class_init ((GObjectClass *) klass,
272  *     G_STRUCT_OFFSET (SomeObjectClass, presence_mixin));
273  * mixin_class = TP_PRESENCE_MIXIN_CLASS (klass);
274  * mixin_class->get_maximum_status_message_length =
275  *     some_object_get_maximum_status_message_length;
276  * ]|
277  *
278  * All other fields should be considered read-only.
279  */
280 
281 /**
282  * TpPresenceMixin:
283  *
284  * Structure to be included in the instance structure of objects that
285  * use this mixin. Initialize it with tp_presence_mixin_init().
286  *
287  * There are no public fields.
288  */
289 
290 #include "config.h"
291 
292 #include <telepathy-glib/presence-mixin.h>
293 
294 #include <dbus/dbus-glib.h>
295 #include <string.h>
296 
297 #include <telepathy-glib/base-connection.h>
298 #include <telepathy-glib/enums.h>
299 #include <telepathy-glib/errors.h>
300 #include <telepathy-glib/gtypes.h>
301 #include <telepathy-glib/interfaces.h>
302 #include <telepathy-glib/contacts-mixin.h>
303 
304 #define DEBUG_FLAG TP_DEBUG_PRESENCE
305 
306 #include "debug-internal.h"
307 
308 
309 static GHashTable *construct_simple_presence_hash (
310   const TpPresenceStatusSpec *supported_statuses,
311   GHashTable *contact_statuses);
312 
313 /*
314  * deep_copy_hashtable
315  *
316  * Make a deep copy of a GHashTable.
317  */
318 static GHashTable *
deep_copy_hashtable(GHashTable * hash_table)319 deep_copy_hashtable (GHashTable *hash_table)
320 {
321   GValue value = {0, };
322 
323   if (!hash_table)
324     return NULL;
325 
326   g_value_init (&value, TP_HASH_TYPE_STRING_VARIANT_MAP);
327   g_value_take_boxed (&value, hash_table);
328   return g_value_dup_boxed (&value);
329 }
330 
331 
332 /**
333  * tp_presence_status_new: (skip)
334  * @which: Index of the presence status in the provided supported presence
335  *  statuses array
336  * @optional_arguments: Optional arguments for the presence statuses. Can be
337  *  NULL if there are no optional arguments. The presence status object makes a
338  *  copy of the hashtable, so you should free the original.
339  *
340  * Construct a presence status structure. You should free the returned
341  * structure with #tp_presence_status_free.
342  *
343  * In modern Telepathy connection managers, the only optional
344  * argument should be a %G_TYPE_STRING named "message", on statuses
345  * that have an optional human-readable message. All other optional arguments
346  * are deprecated.
347  *
348  * Returns: A pointer to the newly allocated presence status structure.
349  */
350 TpPresenceStatus *
tp_presence_status_new(guint which,GHashTable * optional_arguments)351 tp_presence_status_new (guint which,
352                         GHashTable *optional_arguments)
353 {
354   TpPresenceStatus *status = g_slice_new (TpPresenceStatus);
355 
356   status->index = which;
357   status->optional_arguments = deep_copy_hashtable (optional_arguments);
358 
359   return status;
360 }
361 
362 
363 /**
364  * tp_presence_status_free: (skip)
365  * @status: A pointer to the presence status structure to free.
366  *
367  * Deallocate all resources associated with a presence status structure.
368  */
369 void
tp_presence_status_free(TpPresenceStatus * status)370 tp_presence_status_free (TpPresenceStatus *status)
371 {
372   if (!status)
373     return;
374 
375   if (status->optional_arguments)
376     g_hash_table_unref (status->optional_arguments);
377 
378   g_slice_free (TpPresenceStatus, status);
379 }
380 
381 
382 /**
383  * tp_presence_mixin_class_get_offset_quark: (skip)
384  *
385  * <!--no documentation beyond Returns: needed-->
386  *
387  * Returns: the quark used for storing mixin offset on a GObjectClass
388  */
389 GQuark
tp_presence_mixin_class_get_offset_quark()390 tp_presence_mixin_class_get_offset_quark ()
391 {
392   static GQuark offset_quark = 0;
393   if (!offset_quark)
394     offset_quark = g_quark_from_static_string ("TpPresenceMixinClassOffsetQuark");
395   return offset_quark;
396 }
397 
398 /**
399  * tp_presence_mixin_get_offset_quark: (skip)
400  *
401  * <!--no documentation beyond Returns: needed-->
402  *
403  * Returns: the quark used for storing mixin offset on a GObject
404  */
405 GQuark
tp_presence_mixin_get_offset_quark()406 tp_presence_mixin_get_offset_quark ()
407 {
408   static GQuark offset_quark = 0;
409   if (!offset_quark)
410     offset_quark = g_quark_from_static_string ("TpPresenceMixinOffsetQuark");
411   return offset_quark;
412 }
413 
414 /**
415  * tp_presence_mixin_class_init: (skip)
416  * @obj_cls: The class of the implementation that uses this mixin
417  * @offset: The byte offset of the TpPresenceMixinClass within the class
418  * structure
419  * @status_available: A callback to be used to determine if a given presence
420  *  status can be set on a particular connection. Should usually be %NULL, to
421  *  consider all statuses with #TpPresenceStatusSpec.self set to %TRUE to be
422  *  settable.
423  * @get_contact_statuses: A callback to be used get the current presence status
424  *  for contacts. This is used in implementations of various D-Bus methods and
425  *  hence must be provided.
426  * @set_own_status: A callback to be used to commit changes to the user's own
427  *  presence status to the server. This is used in implementations of various
428  *  D-Bus methods and hence must be provided.
429  * @statuses: An array of #TpPresenceStatusSpec structures representing all
430  *  presence statuses supported by the protocol, terminated by a NULL name.
431  *
432  * Initialize the presence mixin. Should be called from the implementation's
433  * class_init function like so:
434  *
435  * <informalexample><programlisting>
436  * tp_presence_mixin_class_init ((GObjectClass *) klass,
437  *                               G_STRUCT_OFFSET (SomeObjectClass,
438  *                                                presence_mixin));
439  * </programlisting></informalexample>
440  */
441 
442 void
tp_presence_mixin_class_init(GObjectClass * obj_cls,glong offset,TpPresenceMixinStatusAvailableFunc status_available,TpPresenceMixinGetContactStatusesFunc get_contact_statuses,TpPresenceMixinSetOwnStatusFunc set_own_status,const TpPresenceStatusSpec * statuses)443 tp_presence_mixin_class_init (GObjectClass *obj_cls,
444                               glong offset,
445                               TpPresenceMixinStatusAvailableFunc status_available,
446                               TpPresenceMixinGetContactStatusesFunc get_contact_statuses,
447                               TpPresenceMixinSetOwnStatusFunc set_own_status,
448                               const TpPresenceStatusSpec *statuses)
449 {
450   TpPresenceMixinClass *mixin_cls;
451   guint i;
452 
453   DEBUG ("called.");
454 
455   g_assert (get_contact_statuses != NULL);
456   g_assert (set_own_status != NULL);
457   g_assert (statuses != NULL);
458 
459   g_assert (G_IS_OBJECT_CLASS (obj_cls));
460 
461   g_type_set_qdata (G_OBJECT_CLASS_TYPE (obj_cls),
462       TP_PRESENCE_MIXIN_CLASS_OFFSET_QUARK,
463       GINT_TO_POINTER (offset));
464 
465   mixin_cls = TP_PRESENCE_MIXIN_CLASS (obj_cls);
466 
467   mixin_cls->status_available = status_available;
468   mixin_cls->get_contact_statuses = get_contact_statuses;
469   mixin_cls->set_own_status = set_own_status;
470   mixin_cls->statuses = statuses;
471   mixin_cls->get_maximum_status_message_length = NULL;
472 
473   for (i = 0; statuses[i].name != NULL; i++)
474     {
475       if (statuses[i].self)
476         {
477           switch (statuses[i].presence_type)
478             {
479             case TP_CONNECTION_PRESENCE_TYPE_OFFLINE:
480             case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
481             case TP_CONNECTION_PRESENCE_TYPE_ERROR:
482               WARNING ("Status \"%s\" of type %u should not be available "
483                   "to set on yourself", statuses[i].name,
484                   statuses[i].presence_type);
485               break;
486 
487             default:
488               break;
489             }
490         }
491     }
492 }
493 
494 /**
495  * tp_presence_mixin_init: (skip)
496  * @obj: An instance of the implementation that uses this mixin
497  * @offset: The byte offset of the TpPresenceMixin within the object structure
498  *
499  * Initialize the presence mixin. Should be called from the implementation's
500  * instance init function like so:
501  *
502  * <informalexample><programlisting>
503  * tp_presence_mixin_init ((GObject *) self,
504  *                         G_STRUCT_OFFSET (SomeObject, presence_mixin));
505  * </programlisting></informalexample>
506  */
507 void
tp_presence_mixin_init(GObject * obj,glong offset)508 tp_presence_mixin_init (GObject *obj,
509                         glong offset)
510 {
511   DEBUG ("called.");
512 
513   g_assert (G_IS_OBJECT (obj));
514 
515   g_type_set_qdata (G_OBJECT_TYPE (obj),
516                     TP_PRESENCE_MIXIN_OFFSET_QUARK,
517                     GINT_TO_POINTER (offset));
518 }
519 
520 /**
521  * tp_presence_mixin_finalize: (skip)
522  * @obj: An object with this mixin.
523  *
524  * Free resources held by the presence mixin.
525  */
526 void
tp_presence_mixin_finalize(GObject * obj)527 tp_presence_mixin_finalize (GObject *obj)
528 {
529   DEBUG ("%p", obj);
530 
531   /* free any data held directly by the object here */
532 }
533 
534 static void
construct_presence_hash_foreach(GHashTable * presence_hash,const TpPresenceStatusSpec * supported_statuses,TpHandle handle,TpPresenceStatus * status)535 construct_presence_hash_foreach (
536     GHashTable *presence_hash,
537     const TpPresenceStatusSpec *supported_statuses,
538     TpHandle handle,
539     TpPresenceStatus *status)
540 {
541   GHashTable *parameters;
542   GHashTable *contact_status;
543   GValueArray *vals;
544 
545   contact_status = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
546       (GDestroyNotify) g_hash_table_unref);
547 
548   parameters = deep_copy_hashtable (status->optional_arguments);
549 
550   if (!parameters)
551     parameters = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
552 
553   g_hash_table_insert (contact_status,
554       (gpointer) supported_statuses[status->index].name, parameters);
555 
556   vals = tp_value_array_build (2,
557       G_TYPE_UINT, 0,
558       TP_HASH_TYPE_MULTIPLE_STATUS_MAP, contact_status,
559       G_TYPE_INVALID);
560   g_hash_table_unref (contact_status);
561 
562   g_hash_table_insert (presence_hash, GUINT_TO_POINTER (handle), vals);
563 }
564 
565 
566 static GHashTable *
construct_presence_hash(const TpPresenceStatusSpec * supported_statuses,GHashTable * contact_statuses)567 construct_presence_hash (const TpPresenceStatusSpec *supported_statuses,
568                          GHashTable *contact_statuses)
569 {
570   GHashTable *presence_hash = g_hash_table_new_full (NULL, NULL, NULL,
571       (GDestroyNotify) tp_value_array_free);
572   GHashTableIter iter;
573   gpointer key, value;
574 
575   DEBUG ("called.");
576 
577   g_hash_table_iter_init (&iter, contact_statuses);
578   while (g_hash_table_iter_next (&iter, &key, &value))
579     construct_presence_hash_foreach (presence_hash, supported_statuses,
580         GPOINTER_TO_UINT (key), value);
581 
582   return presence_hash;
583 }
584 
585 
586 /**
587  * tp_presence_mixin_emit_presence_update: (skip)
588  * @obj: A connection object with this mixin
589  * @contact_presences: A mapping of contact handles to #TpPresenceStatus
590  *  structures with the presence data to emit
591  *
592  * Emit the PresenceUpdate signal for multiple contacts. For emitting
593  * PresenceUpdate for a single contact, there is a convenience wrapper called
594  * #tp_presence_mixin_emit_one_presence_update.
595  */
596 void
tp_presence_mixin_emit_presence_update(GObject * obj,GHashTable * contact_statuses)597 tp_presence_mixin_emit_presence_update (GObject *obj,
598                                         GHashTable *contact_statuses)
599 {
600   TpPresenceMixinClass *mixin_cls =
601     TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj));
602   GHashTable *presence_hash;
603 
604   DEBUG ("called.");
605 
606   if (g_type_interface_peek (G_OBJECT_GET_CLASS (obj),
607       TP_TYPE_SVC_CONNECTION_INTERFACE_PRESENCE) != NULL)
608     {
609       presence_hash = construct_presence_hash (mixin_cls->statuses,
610           contact_statuses);
611       tp_svc_connection_interface_presence_emit_presence_update (obj,
612           presence_hash);
613 
614       g_hash_table_unref (presence_hash);
615     }
616 
617   if (g_type_interface_peek (G_OBJECT_GET_CLASS (obj),
618       TP_TYPE_SVC_CONNECTION_INTERFACE_SIMPLE_PRESENCE) != NULL)
619     {
620       presence_hash = construct_simple_presence_hash (mixin_cls->statuses,
621         contact_statuses);
622       tp_svc_connection_interface_simple_presence_emit_presences_changed (obj,
623         presence_hash);
624 
625       g_hash_table_unref (presence_hash);
626     }
627 }
628 
629 
630 /**
631  * tp_presence_mixin_emit_one_presence_update: (skip)
632  * @obj: A connection object with this mixin
633  * @handle: The handle of the contact to emit the signal for
634  * @status: The new status to emit
635  *
636  * Emit the PresenceUpdate signal for a single contact. This method is just a
637  * convenience wrapper around #tp_presence_mixin_emit_presence_update.
638  */
639 void
tp_presence_mixin_emit_one_presence_update(GObject * obj,TpHandle handle,const TpPresenceStatus * status)640 tp_presence_mixin_emit_one_presence_update (GObject *obj,
641                                             TpHandle handle,
642                                             const TpPresenceStatus *status)
643 {
644   GHashTable *contact_statuses;
645 
646   DEBUG ("called.");
647 
648   contact_statuses = g_hash_table_new (NULL, NULL);
649   g_hash_table_insert (contact_statuses, GUINT_TO_POINTER (handle),
650       (gpointer) status);
651   tp_presence_mixin_emit_presence_update (obj, contact_statuses);
652 
653   g_hash_table_unref (contact_statuses);
654 }
655 
656 
657 /*
658  * tp_presence_mixin_add_status:
659  *
660  * Implements D-Bus method AddStatus
661  * on interface org.freedesktop.Telepathy.Connection.Interface.Presence
662  */
663 static void
tp_presence_mixin_add_status(TpSvcConnectionInterfacePresence * iface,const gchar * status,GHashTable * parms,DBusGMethodInvocation * context)664 tp_presence_mixin_add_status (TpSvcConnectionInterfacePresence *iface,
665                               const gchar *status,
666                               GHashTable *parms,
667                               DBusGMethodInvocation *context)
668 {
669   TpBaseConnection *conn = TP_BASE_CONNECTION (iface);
670   GError error = { TP_ERROR, TP_ERROR_NOT_IMPLEMENTED,
671     "Only one status is possible at a time with this protocol!" };
672 
673   DEBUG ("called.");
674 
675   TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context);
676 
677   dbus_g_method_return_error (context, &error);
678 }
679 
680 
681 /*
682  * tp_presence_mixin_clear_status:
683  *
684  * Implements D-Bus method ClearStatus
685  * on interface org.freedesktop.Telepathy.Connection.Interface.Presence
686  */
687 static void
tp_presence_mixin_clear_status(TpSvcConnectionInterfacePresence * iface,DBusGMethodInvocation * context)688 tp_presence_mixin_clear_status (TpSvcConnectionInterfacePresence *iface,
689                                 DBusGMethodInvocation *context)
690 {
691   GObject *obj = (GObject *) iface;
692   TpBaseConnection *conn = TP_BASE_CONNECTION (iface);
693   TpPresenceMixinClass *mixin_cls =
694     TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj));
695   GError *error = NULL;
696 
697   DEBUG ("called.");
698 
699   TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context);
700 
701   if (!mixin_cls->set_own_status (obj, NULL, &error))
702     {
703       dbus_g_method_return_error (context, error);
704       g_error_free (error);
705       return;
706     }
707 
708   tp_svc_connection_interface_presence_return_from_clear_status (context);
709 }
710 
711 
712 /*
713  * tp_presence_mixin_get_presence:
714  *
715  * Implements D-Bus method GetPresence
716  * on interface org.freedesktop.Telepathy.Connection.Interface.Presence
717  */
718 static void
tp_presence_mixin_get_presence(TpSvcConnectionInterfacePresence * iface,const GArray * contacts,DBusGMethodInvocation * context)719 tp_presence_mixin_get_presence (TpSvcConnectionInterfacePresence *iface,
720                                 const GArray *contacts,
721                                 DBusGMethodInvocation *context)
722 {
723   GObject *obj = (GObject *) iface;
724   TpBaseConnection *conn = TP_BASE_CONNECTION (obj);
725   TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn,
726       TP_HANDLE_TYPE_CONTACT);
727   TpPresenceMixinClass *mixin_cls =
728     TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj));
729   GHashTable *contact_statuses;
730   GHashTable *presence_hash;
731   GError *error = NULL;
732 
733   DEBUG ("called.");
734 
735   TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context);
736 
737   if (contacts->len == 0)
738     {
739       presence_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
740       tp_svc_connection_interface_presence_return_from_get_presence (context,
741           presence_hash);
742       g_hash_table_unref (presence_hash);
743       return;
744     }
745 
746   if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
747     {
748       dbus_g_method_return_error (context, error);
749       g_error_free (error);
750       return;
751     }
752 
753   contact_statuses = mixin_cls->get_contact_statuses (obj, contacts, &error);
754 
755   if (!contact_statuses)
756     {
757       dbus_g_method_return_error (context, error);
758       g_error_free (error);
759       return;
760     }
761 
762   presence_hash = construct_presence_hash (mixin_cls->statuses,
763       contact_statuses);
764   tp_svc_connection_interface_presence_return_from_get_presence (context,
765       presence_hash);
766   g_hash_table_unref (presence_hash);
767   g_hash_table_unref (contact_statuses);
768 }
769 
770 
771 static GHashTable *
get_statuses_arguments(const TpPresenceStatusOptionalArgumentSpec * specs)772 get_statuses_arguments (const TpPresenceStatusOptionalArgumentSpec *specs)
773 {
774   GHashTable *arguments = g_hash_table_new (g_str_hash, g_str_equal);
775   int i;
776 
777   for (i=0; specs != NULL && specs[i].name != NULL; i++)
778     g_hash_table_insert (arguments, (gchar *) specs[i].name,
779         (gchar *) specs[i].dtype);
780 
781   return arguments;
782 }
783 
784 static gboolean
check_status_available(GObject * object,TpPresenceMixinClass * mixin_cls,guint i,GError ** error,gboolean for_self)785 check_status_available (GObject *object,
786                         TpPresenceMixinClass *mixin_cls,
787                         guint i,
788                         GError **error,
789                         gboolean for_self)
790 {
791   if (for_self)
792     {
793       if (!mixin_cls->statuses[i].self)
794         {
795           g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
796               "cannot set status '%s' on yourself",
797               mixin_cls->statuses[i].name);
798           return FALSE;
799         }
800 
801       /* never allow OFFLINE, UNKNOWN or ERROR - if the CM says they're
802        * OK to set on yourself, then it's wrong */
803       switch (mixin_cls->statuses[i].presence_type)
804         {
805         case TP_CONNECTION_PRESENCE_TYPE_OFFLINE:
806         case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
807         case TP_CONNECTION_PRESENCE_TYPE_ERROR:
808           g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
809               "cannot set offline/unknown/error status '%s' on yourself",
810               mixin_cls->statuses[i].name);
811           return FALSE;
812 
813         default:
814           break;
815         }
816     }
817 
818   if (mixin_cls->status_available
819       && !mixin_cls->status_available (object, i))
820     {
821       DEBUG ("requested status %s is not available",
822           mixin_cls->statuses[i].name);
823       g_set_error (error, TP_ERROR, TP_ERROR_NOT_AVAILABLE,
824           "requested status '%s' is not available on this connection",
825           mixin_cls->statuses[i].name);
826       return FALSE;
827     }
828 
829   return TRUE;
830 }
831 
832 /*
833  * tp_presence_mixin_get_statuses:
834  *
835  * Implements D-Bus method GetStatuses
836  * on interface org.freedesktop.Telepathy.Connection.Interface.Presence
837  */
838 static void
tp_presence_mixin_get_statuses(TpSvcConnectionInterfacePresence * iface,DBusGMethodInvocation * context)839 tp_presence_mixin_get_statuses (TpSvcConnectionInterfacePresence *iface,
840                                 DBusGMethodInvocation *context)
841 {
842   TpBaseConnection *conn = TP_BASE_CONNECTION (iface);
843   GObject *obj = (GObject *) conn;
844   TpPresenceMixinClass *mixin_cls =
845     TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj));
846   GHashTable *ret;
847   GValueArray *status;
848   int i;
849 
850   DEBUG ("called.");
851 
852   TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context);
853 
854   ret = g_hash_table_new_full (g_str_hash, g_str_equal,
855                                NULL, (GDestroyNotify) tp_value_array_free);
856 
857   for (i=0; mixin_cls->statuses[i].name != NULL; i++)
858     {
859       GHashTable *args;
860 
861       /* the spec says we include statuses here even if they're not available
862        * to set on yourself */
863       if (!check_status_available (obj, mixin_cls, i, NULL, FALSE))
864         continue;
865 
866       args = get_statuses_arguments (mixin_cls->statuses[i].optional_arguments);
867       status = tp_value_array_build (4,
868           G_TYPE_UINT, (guint) mixin_cls->statuses[i].presence_type,
869           G_TYPE_BOOLEAN, mixin_cls->statuses[i].self,
870           G_TYPE_BOOLEAN, TRUE, /* exclusive */
871           DBUS_TYPE_G_STRING_STRING_HASHTABLE, args,
872           G_TYPE_INVALID);
873       g_hash_table_unref (args);
874 
875       g_hash_table_insert (ret, (gchar *) mixin_cls->statuses[i].name,
876           status);
877     }
878 
879   tp_svc_connection_interface_presence_return_from_get_statuses (context, ret);
880   g_hash_table_unref (ret);
881 }
882 
883 
884 /*
885  * tp_presence_mixin_set_last_activity_time:
886  *
887  * Implements D-Bus method SetLastActivityTime
888  * on interface org.freedesktop.Telepathy.Connection.Interface.Presence
889  */
890 static void
tp_presence_mixin_set_last_activity_time(TpSvcConnectionInterfacePresence * iface,guint timestamp,DBusGMethodInvocation * context)891 tp_presence_mixin_set_last_activity_time (TpSvcConnectionInterfacePresence *iface,
892                                           guint timestamp,
893                                           DBusGMethodInvocation *context)
894 {
895   TpBaseConnection *conn = TP_BASE_CONNECTION (iface);
896 
897   TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context);
898 
899   tp_svc_connection_interface_presence_return_from_set_last_activity_time (
900       context);
901 }
902 
903 
904 /*
905  * tp_presence_mixin_remove_status:
906  *
907  * Implements D-Bus method RemoveStatus
908  * on interface org.freedesktop.Telepathy.Connection.Interface.Presence
909  */
910 static void
tp_presence_mixin_remove_status(TpSvcConnectionInterfacePresence * iface,const gchar * status,DBusGMethodInvocation * context)911 tp_presence_mixin_remove_status (TpSvcConnectionInterfacePresence *iface,
912                                  const gchar *status,
913                                  DBusGMethodInvocation *context)
914 {
915   GObject *obj = (GObject *) iface;
916   TpBaseConnection *conn = TP_BASE_CONNECTION (iface);
917   TpPresenceMixinClass *mixin_cls =
918     TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj));
919   GArray *self_contacts;
920   GError *error = NULL;
921   GHashTable *self_contact_statuses;
922   TpPresenceStatus *self_status;
923   TpHandle self_handle;
924 
925   DEBUG ("called.");
926 
927   TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context);
928 
929   self_contacts = g_array_sized_new (TRUE, TRUE, sizeof (TpHandle), 1);
930   self_handle = tp_base_connection_get_self_handle (conn);
931   g_array_append_val (self_contacts, self_handle);
932   self_contact_statuses = mixin_cls->get_contact_statuses (obj, self_contacts,
933       &error);
934 
935   if (!self_contact_statuses)
936     {
937       dbus_g_method_return_error (context, error);
938       g_error_free (error);
939       g_array_unref (self_contacts);
940       return;
941     }
942 
943   self_status = (TpPresenceStatus *) g_hash_table_lookup (self_contact_statuses,
944       GUINT_TO_POINTER (tp_base_connection_get_self_handle (conn)));
945 
946   if (!self_status)
947     {
948       DEBUG ("Got no self status, assuming we already have default status");
949       g_array_unref (self_contacts);
950       g_hash_table_unref (self_contact_statuses);
951       tp_svc_connection_interface_presence_return_from_remove_status (context);
952       return;
953     }
954 
955   if (!tp_strdiff (status, mixin_cls->statuses[self_status->index].name))
956     {
957       if (mixin_cls->set_own_status (obj, NULL, &error))
958         {
959           tp_svc_connection_interface_presence_return_from_remove_status (context);
960         }
961       else
962         {
963           dbus_g_method_return_error (context, error);
964           g_error_free (error);
965         }
966     }
967   else
968     {
969       GError nonexistent = { TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
970           "Attempting to remove non-existent presence." };
971       dbus_g_method_return_error (context, &nonexistent);
972     }
973 
974   g_array_unref (self_contacts);
975   g_hash_table_unref (self_contact_statuses);
976 }
977 
978 
979 /*
980  * tp_presence_mixin_request_presence:
981  *
982  * Implements D-Bus method RequestPresence
983  * on interface org.freedesktop.Telepathy.Connection.Interface.Presence
984  */
985 static void
tp_presence_mixin_request_presence(TpSvcConnectionInterfacePresence * iface,const GArray * contacts,DBusGMethodInvocation * context)986 tp_presence_mixin_request_presence (TpSvcConnectionInterfacePresence *iface,
987                                     const GArray *contacts,
988                                     DBusGMethodInvocation *context)
989 {
990   GObject *obj = (GObject *) iface;
991   TpPresenceMixinClass *mixin_cls =
992     TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj));
993   TpBaseConnection *conn = TP_BASE_CONNECTION (iface);
994   TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn,
995       TP_HANDLE_TYPE_CONTACT);
996   GHashTable *contact_statuses;
997   GError *error = NULL;
998 
999   DEBUG ("called.");
1000 
1001   TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context);
1002 
1003   if (contacts->len == 0)
1004     {
1005       tp_svc_connection_interface_presence_return_from_request_presence (context);
1006       return;
1007     }
1008 
1009   if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
1010     {
1011       dbus_g_method_return_error (context, error);
1012       g_error_free (error);
1013       return;
1014     }
1015 
1016   contact_statuses = mixin_cls->get_contact_statuses (obj, contacts, &error);
1017 
1018   if (!contact_statuses)
1019     {
1020       dbus_g_method_return_error (context, error);
1021       g_error_free (error);
1022       return;
1023     }
1024 
1025   tp_presence_mixin_emit_presence_update (obj, contact_statuses);
1026   tp_svc_connection_interface_presence_return_from_request_presence (context);
1027 
1028   g_hash_table_unref (contact_statuses);
1029 }
1030 
1031 static int
check_for_status(GObject * object,const gchar * status,GError ** error)1032 check_for_status (GObject *object, const gchar *status, GError **error)
1033 {
1034   TpPresenceMixinClass *mixin_cls =
1035     TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (object));
1036   int i;
1037 
1038   for (i = 0; mixin_cls->statuses[i].name != NULL; i++)
1039     {
1040       if (!tp_strdiff (mixin_cls->statuses[i].name, status))
1041         break;
1042     }
1043 
1044   if (mixin_cls->statuses[i].name != NULL)
1045     {
1046       DEBUG ("Found status \"%s\", checking if it's available...",
1047           (const gchar *) status);
1048 
1049       if (!check_status_available (object, mixin_cls, i, error, TRUE))
1050         return -1;
1051     }
1052   else
1053     {
1054       DEBUG ("got unknown status identifier %s", status);
1055       g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1056           "unknown status identifier: %s", status);
1057       return -1;
1058     }
1059 
1060   return i;
1061 }
1062 
1063 static gboolean
set_status(GObject * obj,const gchar * status_name,GHashTable * provided_arguments,GError ** error)1064 set_status (
1065     GObject *obj,
1066     const gchar *status_name,
1067     GHashTable *provided_arguments,
1068     GError **error)
1069 {
1070   TpPresenceMixinClass *mixin_cls =
1071     TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj));
1072   TpPresenceStatus status_to_set = { 0, };
1073   int status;
1074   GHashTable *optional_arguments = NULL;
1075   gboolean ret = TRUE;
1076 
1077   DEBUG ("called.");
1078 
1079   /* This function will actually only be invoked once for one SetStatus request,
1080    * since we check that the hash table has size 1 in
1081    * tp_presence_mixin_set_status(). Therefore there are no problems with
1082    * sharing the foreach data like this.
1083    */
1084   status = check_for_status (obj, status_name, error);
1085 
1086   if (status == -1)
1087     return FALSE;
1088 
1089   DEBUG ("The status is available.");
1090 
1091   if (provided_arguments != NULL)
1092     {
1093       int j;
1094       const TpPresenceStatusOptionalArgumentSpec *specs =
1095         mixin_cls->statuses[status].optional_arguments;
1096 
1097       for (j=0; specs != NULL && specs[j].name != NULL; j++)
1098         {
1099           GValue *provided_value =
1100             g_hash_table_lookup (provided_arguments, specs[j].name);
1101           GValue *new_value;
1102 
1103           if (!provided_value)
1104             continue;
1105           new_value = tp_g_value_slice_dup (provided_value);
1106 
1107           if (!optional_arguments)
1108             optional_arguments =
1109               g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
1110                   (GDestroyNotify) tp_g_value_slice_free);
1111 
1112           if (DEBUGGING)
1113             {
1114               gchar *value_contents = g_strdup_value_contents (new_value);
1115               DEBUG ("Got optional argument (\"%s\", %s)", specs[j].name,
1116                   value_contents);
1117               g_free (value_contents);
1118             }
1119 
1120           g_hash_table_insert (optional_arguments,
1121               (gpointer) specs[j].name, new_value);
1122         }
1123     }
1124 
1125   status_to_set.index = status;
1126   status_to_set.optional_arguments = optional_arguments;
1127 
1128   DEBUG ("About to try setting status \"%s\"",
1129       mixin_cls->statuses[status].name);
1130 
1131   ret = mixin_cls->set_own_status (obj, &status_to_set, error);
1132 
1133   if (optional_arguments)
1134     g_hash_table_unref (optional_arguments);
1135 
1136   return ret;
1137 }
1138 
1139 
1140 /*
1141  * tp_presence_mixin_set_status:
1142  *
1143  * Implements D-Bus method SetStatus
1144  * on interface org.freedesktop.Telepathy.Connection.Interface.Presence
1145  */
1146 static void
tp_presence_mixin_set_status(TpSvcConnectionInterfacePresence * iface,GHashTable * statuses,DBusGMethodInvocation * context)1147 tp_presence_mixin_set_status (TpSvcConnectionInterfacePresence *iface,
1148                               GHashTable *statuses,
1149                               DBusGMethodInvocation *context)
1150 {
1151   GObject *obj = (GObject *) iface;
1152   TpBaseConnection *conn = TP_BASE_CONNECTION (iface);
1153   GHashTableIter iter;
1154   gpointer key, value;
1155   GError *error = NULL;
1156 
1157   DEBUG ("called.");
1158 
1159   TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context);
1160 
1161   g_hash_table_iter_init (&iter, statuses);
1162   if (!g_hash_table_iter_next (&iter, &key, &value) ||
1163       g_hash_table_iter_next (&iter, NULL, NULL))
1164     {
1165       GError invalid = { TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1166           "Only one status may be set at a time in this protocol" };
1167       DEBUG ("got more than one status");
1168       dbus_g_method_return_error (context, &invalid);
1169       return;
1170     }
1171 
1172   if (set_status (obj, key, value, &error))
1173     {
1174       tp_svc_connection_interface_presence_return_from_set_status (context);
1175     }
1176   else
1177     {
1178       DEBUG ("failed: %s", error->message);
1179       dbus_g_method_return_error (context, error);
1180       g_error_free (error);
1181     }
1182 }
1183 
1184 
1185 /**
1186  * tp_presence_mixin_iface_init: (skip)
1187  * @g_iface: A pointer to the #TpSvcConnectionInterfacePresenceClass in an
1188  *  object class
1189  * @iface_data: Ignored
1190  *
1191  * Fill in the vtable entries needed to implement the presence interface using
1192  * this mixin. This function should usually be called via G_IMPLEMENT_INTERFACE.
1193  */
1194 void
tp_presence_mixin_iface_init(gpointer g_iface,gpointer iface_data)1195 tp_presence_mixin_iface_init (gpointer g_iface, gpointer iface_data)
1196 {
1197   TpSvcConnectionInterfacePresenceClass *klass = g_iface;
1198 
1199 #define IMPLEMENT(x) tp_svc_connection_interface_presence_implement_##x (klass,\
1200     tp_presence_mixin_##x)
1201   IMPLEMENT(add_status);
1202   IMPLEMENT(clear_status);
1203   IMPLEMENT(get_presence);
1204   IMPLEMENT(get_statuses);
1205   IMPLEMENT(remove_status);
1206   IMPLEMENT(request_presence);
1207   IMPLEMENT(set_last_activity_time);
1208   IMPLEMENT(set_status);
1209 #undef IMPLEMENT
1210 }
1211 
1212 enum {
1213   MIXIN_DP_SIMPLE_STATUSES,
1214   MIXIN_DP_SIMPLE_MAX_STATUS_MESSAGE_LENGTH,
1215   NUM_MIXIN_SIMPLE_DBUS_PROPERTIES
1216 };
1217 
1218 static TpDBusPropertiesMixinPropImpl known_simple_presence_props[] = {
1219   { "Statuses", NULL, NULL },
1220   { "MaximumStatusMessageLength", NULL, NULL },
1221   { NULL }
1222 };
1223 
1224 static void
tp_presence_mixin_get_simple_presence_dbus_property(GObject * object,GQuark interface,GQuark name,GValue * value,gpointer unused G_GNUC_UNUSED)1225 tp_presence_mixin_get_simple_presence_dbus_property (GObject *object,
1226                                                      GQuark interface,
1227                                                      GQuark name,
1228                                                      GValue *value,
1229                                                      gpointer unused
1230                                                        G_GNUC_UNUSED)
1231 {
1232   TpPresenceMixinClass *mixin_cls =
1233       TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (object));
1234   static GQuark q[NUM_MIXIN_SIMPLE_DBUS_PROPERTIES] = { 0, };
1235 
1236   DEBUG ("called.");
1237 
1238   if (G_UNLIKELY (q[0] == 0))
1239     {
1240       q[MIXIN_DP_SIMPLE_STATUSES] = g_quark_from_static_string ("Statuses");
1241       q[MIXIN_DP_SIMPLE_MAX_STATUS_MESSAGE_LENGTH] =
1242           g_quark_from_static_string ("MaximumStatusMessageLength");
1243     }
1244 
1245   g_return_if_fail (object != NULL);
1246 
1247   if (name == q[MIXIN_DP_SIMPLE_STATUSES])
1248     {
1249       GHashTable *ret;
1250       GValueArray *status;
1251       int i;
1252 
1253       g_return_if_fail (G_VALUE_HOLDS_BOXED (value));
1254 
1255       ret = g_hash_table_new_full (g_str_hash, g_str_equal,
1256                                NULL, (GDestroyNotify) tp_value_array_free);
1257 
1258       for (i=0; mixin_cls->statuses[i].name != NULL; i++)
1259         {
1260           gboolean message;
1261 
1262           /* we include statuses here even if they're not available
1263            * to set on yourself */
1264           if (!check_status_available (object, mixin_cls, i, NULL, FALSE))
1265             continue;
1266 
1267           message = tp_presence_status_spec_has_message (
1268               &mixin_cls->statuses[i]);
1269 
1270           status = tp_value_array_build (3,
1271              G_TYPE_UINT, (guint) mixin_cls->statuses[i].presence_type,
1272              G_TYPE_BOOLEAN, mixin_cls->statuses[i].self,
1273              G_TYPE_BOOLEAN, message,
1274              G_TYPE_INVALID);
1275 
1276          g_hash_table_insert (ret, (gchar *) mixin_cls->statuses[i].name,
1277              status);
1278        }
1279        g_value_take_boxed (value, ret);
1280     }
1281   else if (name == q[MIXIN_DP_SIMPLE_MAX_STATUS_MESSAGE_LENGTH])
1282     {
1283       guint max_status_message_length = 0;
1284 
1285       g_assert (G_VALUE_HOLDS (value, G_TYPE_UINT));
1286 
1287       if (mixin_cls->get_maximum_status_message_length != NULL)
1288         max_status_message_length =
1289             mixin_cls->get_maximum_status_message_length (object);
1290 
1291       g_value_set_uint (value, max_status_message_length);
1292     }
1293   else
1294     {
1295       g_return_if_reached ();
1296     }
1297 
1298 }
1299 
1300 /**
1301  * tp_presence_mixin_simple_presence_init_dbus_properties: (skip)
1302  * @cls: The class of an object with this mixin
1303  *
1304  * Set up #TpDBusPropertiesMixinClass to use this mixin's implementation of
1305  * the SimplePresence interface's properties.
1306  *
1307  * This automatically sets up a list of the supported properties for the
1308  * SimplePresence interface.
1309  *
1310  * Since: 0.7.13
1311  */
1312 void
tp_presence_mixin_simple_presence_init_dbus_properties(GObjectClass * cls)1313 tp_presence_mixin_simple_presence_init_dbus_properties (GObjectClass *cls)
1314 {
1315 
1316   tp_dbus_properties_mixin_implement_interface (cls,
1317       TP_IFACE_QUARK_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
1318       tp_presence_mixin_get_simple_presence_dbus_property,
1319       NULL, known_simple_presence_props);
1320 }
1321 
1322 /*
1323  * tp_presence_mixin_simple_presence_set_presence:
1324  *
1325  * Implements D-Bus method SetPresence
1326  * on interface org.freedesktop.Telepathy.Connection.Interface.SimplePresence
1327  */
1328 static void
tp_presence_mixin_simple_presence_set_presence(TpSvcConnectionInterfaceSimplePresence * iface,const gchar * status,const gchar * message,DBusGMethodInvocation * context)1329 tp_presence_mixin_simple_presence_set_presence (
1330     TpSvcConnectionInterfaceSimplePresence *iface,
1331     const gchar *status,
1332     const gchar *message,
1333     DBusGMethodInvocation *context)
1334 {
1335   GObject *obj = (GObject *) iface;
1336   TpPresenceMixinClass *mixin_cls =
1337     TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj));
1338   TpPresenceStatus status_to_set = { 0, };
1339   int s;
1340   GError *error = NULL;
1341   GHashTable *optional_arguments = NULL;
1342 
1343   DEBUG ("called.");
1344 
1345   s = check_for_status (obj, status, &error);
1346   if (s == -1)
1347     goto out;
1348 
1349   status_to_set.index = s;
1350 
1351   if (*message != '\0')
1352     {
1353       optional_arguments = g_hash_table_new_full (g_str_hash, g_str_equal,
1354           NULL, (GDestroyNotify) tp_g_value_slice_free);
1355       g_hash_table_insert (optional_arguments, "message",
1356           tp_g_value_slice_new_string (message));
1357       status_to_set.optional_arguments = optional_arguments;
1358     }
1359 
1360   mixin_cls->set_own_status (obj, &status_to_set, &error);
1361 
1362 out:
1363   if (error == NULL)
1364     {
1365       tp_svc_connection_interface_simple_presence_return_from_set_presence (
1366           context);
1367     }
1368   else
1369     {
1370       dbus_g_method_return_error (context, error);
1371       g_error_free (error);
1372     }
1373 
1374   if (optional_arguments != NULL)
1375     g_hash_table_unref (optional_arguments);
1376 }
1377 
1378 static GValueArray *
construct_simple_presence_value_array(TpPresenceStatus * status,const TpPresenceStatusSpec * supported_statuses)1379 construct_simple_presence_value_array (TpPresenceStatus *status,
1380     const TpPresenceStatusSpec *supported_statuses)
1381 {
1382   TpConnectionPresenceType status_type;
1383   const gchar *status_name;
1384   const gchar *message = NULL;
1385   GValueArray *presence;
1386 
1387   status_name = supported_statuses[status->index].name;
1388   status_type = supported_statuses[status->index].presence_type;
1389 
1390   if (status->optional_arguments != NULL)
1391     {
1392       GValue *val;
1393       val = g_hash_table_lookup (status->optional_arguments, "message");
1394       if (val != NULL)
1395         message = g_value_get_string (val);
1396     }
1397 
1398   if (message == NULL)
1399     message = "";
1400 
1401   presence = tp_value_array_build (3,
1402       G_TYPE_UINT, status_type,
1403       G_TYPE_STRING, status_name,
1404       G_TYPE_STRING, message,
1405       G_TYPE_INVALID);
1406 
1407   return presence;
1408 }
1409 
1410 static void
construct_simple_presence_hash_foreach(GHashTable * presence_hash,const TpPresenceStatusSpec * supported_statuses,TpHandle handle,TpPresenceStatus * status)1411 construct_simple_presence_hash_foreach (
1412     GHashTable *presence_hash,
1413     const TpPresenceStatusSpec *supported_statuses,
1414     TpHandle handle,
1415     TpPresenceStatus *status)
1416 {
1417   GValueArray *presence;
1418 
1419   presence = construct_simple_presence_value_array (status, supported_statuses);
1420   g_hash_table_insert (presence_hash, GUINT_TO_POINTER (handle), presence);
1421 }
1422 
1423 static GHashTable *
construct_simple_presence_hash(const TpPresenceStatusSpec * supported_statuses,GHashTable * contact_statuses)1424 construct_simple_presence_hash (const TpPresenceStatusSpec *supported_statuses,
1425                          GHashTable *contact_statuses)
1426 {
1427   GHashTable *presence_hash = g_hash_table_new_full (NULL, NULL, NULL,
1428       (GDestroyNotify) tp_value_array_free);
1429   GHashTableIter iter;
1430   gpointer key, value;
1431 
1432   DEBUG ("called.");
1433 
1434   g_hash_table_iter_init (&iter, contact_statuses);
1435   while (g_hash_table_iter_next (&iter, &key, &value))
1436     construct_simple_presence_hash_foreach (presence_hash, supported_statuses,
1437         GPOINTER_TO_UINT (key), value);
1438 
1439   return presence_hash;
1440 }
1441 
1442 /*
1443  * tp_presence_mixin_get_simple_presence:
1444  *
1445  * Implements D-Bus method GetPresence
1446  * on interface org.freedesktop.Telepathy.Connection.Interface.SimplePresence
1447  */
1448 static void
tp_presence_mixin_simple_presence_get_presences(TpSvcConnectionInterfaceSimplePresence * iface,const GArray * contacts,DBusGMethodInvocation * context)1449 tp_presence_mixin_simple_presence_get_presences (
1450     TpSvcConnectionInterfaceSimplePresence *iface,
1451     const GArray *contacts,
1452     DBusGMethodInvocation *context)
1453 {
1454   GObject *obj = (GObject *) iface;
1455   TpBaseConnection *conn = TP_BASE_CONNECTION (obj);
1456   TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn,
1457       TP_HANDLE_TYPE_CONTACT);
1458   TpPresenceMixinClass *mixin_cls =
1459     TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj));
1460   GHashTable *contact_statuses;
1461   GHashTable *presence_hash;
1462   GError *error = NULL;
1463 
1464   DEBUG ("called.");
1465 
1466   TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context);
1467 
1468   if (contacts->len == 0)
1469     {
1470       presence_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
1471       tp_svc_connection_interface_simple_presence_return_from_get_presences (
1472         context, presence_hash);
1473       g_hash_table_unref (presence_hash);
1474       return;
1475     }
1476 
1477   if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
1478     {
1479       dbus_g_method_return_error (context, error);
1480       g_error_free (error);
1481       return;
1482     }
1483 
1484   contact_statuses = mixin_cls->get_contact_statuses (obj, contacts, &error);
1485 
1486   if (!contact_statuses)
1487     {
1488       dbus_g_method_return_error (context, error);
1489       g_error_free (error);
1490       return;
1491     }
1492 
1493   presence_hash = construct_simple_presence_hash (mixin_cls->statuses,
1494       contact_statuses);
1495   tp_svc_connection_interface_simple_presence_return_from_get_presences (
1496       context, presence_hash);
1497   g_hash_table_unref (presence_hash);
1498   g_hash_table_unref (contact_statuses);
1499 }
1500 
1501 /**
1502  * tp_presence_mixin_simple_presence_iface_init: (skip)
1503  * @g_iface: A pointer to the #TpSvcConnectionInterfaceSimplePresenceClass in
1504  * an object class
1505  * @iface_data: Ignored
1506  *
1507  * Fill in the vtable entries needed to implement the simple presence interface
1508  * using this mixin. This function should usually be called via
1509  * G_IMPLEMENT_INTERFACE.
1510  *
1511  * Since: 0.7.13
1512  */
1513 void
tp_presence_mixin_simple_presence_iface_init(gpointer g_iface,gpointer iface_data)1514 tp_presence_mixin_simple_presence_iface_init (gpointer g_iface,
1515                                               gpointer iface_data)
1516 {
1517   TpSvcConnectionInterfaceSimplePresenceClass *klass = g_iface;
1518 
1519 #define IMPLEMENT(x) tp_svc_connection_interface_simple_presence_implement_##x\
1520  (klass, tp_presence_mixin_simple_presence_##x)
1521   IMPLEMENT(set_presence);
1522   IMPLEMENT(get_presences);
1523 #undef IMPLEMENT
1524 }
1525 
1526 static void
tp_presence_mixin_simple_presence_fill_contact_attributes(GObject * obj,const GArray * contacts,GHashTable * attributes_hash)1527 tp_presence_mixin_simple_presence_fill_contact_attributes (GObject *obj,
1528   const GArray *contacts, GHashTable *attributes_hash)
1529 {
1530   TpPresenceMixinClass *mixin_cls =
1531     TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj));
1532   GHashTable *contact_statuses;
1533   GError *error = NULL;
1534 
1535   contact_statuses = mixin_cls->get_contact_statuses (obj, contacts, &error);
1536 
1537   if (contact_statuses == NULL)
1538     {
1539       DEBUG ("get_contact_statuses failed: %s", error->message);
1540       g_error_free (error);
1541     }
1542   else
1543     {
1544       GHashTableIter iter;
1545       gpointer key, value;
1546       G_GNUC_BEGIN_IGNORE_DEPRECATIONS
1547       GType type = G_TYPE_VALUE_ARRAY;
1548       G_GNUC_END_IGNORE_DEPRECATIONS
1549 
1550       g_hash_table_iter_init (&iter, contact_statuses);
1551       while (g_hash_table_iter_next (&iter, &key, &value))
1552         {
1553           TpHandle handle = GPOINTER_TO_UINT (key);
1554           TpPresenceStatus *status = value;
1555           GValueArray *presence = construct_simple_presence_value_array (
1556               status, mixin_cls->statuses);
1557 
1558           tp_contacts_mixin_set_contact_attribute (attributes_hash, handle,
1559               TP_TOKEN_CONNECTION_INTERFACE_SIMPLE_PRESENCE_PRESENCE,
1560               tp_g_value_slice_new_take_boxed (type, presence));
1561         }
1562 
1563       g_hash_table_unref (contact_statuses);
1564     }
1565 }
1566 
1567 /**
1568  * tp_presence_mixin_simple_presence_register_with_contacts_mixin: (skip)
1569  * @obj: An instance that of the implementation that uses both the Contacts
1570  * mixin and this mixin
1571  *
1572  * Register the SimplePresence interface with the Contacts interface to make it
1573  * inspectable. The Contacts mixin should be initialized before this function
1574  * is called
1575  */
1576 void
tp_presence_mixin_simple_presence_register_with_contacts_mixin(GObject * obj)1577 tp_presence_mixin_simple_presence_register_with_contacts_mixin (GObject *obj)
1578 {
1579   tp_contacts_mixin_add_contact_attributes_iface (obj,
1580       TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
1581       tp_presence_mixin_simple_presence_fill_contact_attributes);
1582 }
1583 
1584 /* For now, self->priv is just self if heap-allocated, NULL if not. */
1585 static gboolean
_tp_presence_status_spec_is_heap_allocated(const TpPresenceStatusSpec * self)1586 _tp_presence_status_spec_is_heap_allocated (const TpPresenceStatusSpec *self)
1587 {
1588   return (self->priv == (TpPresenceStatusSpecPrivate *) self);
1589 }
1590 
1591 /**
1592  * tp_presence_status_spec_get_presence_type:
1593  * @self: a presence status specification
1594  *
1595  * Return the category into which this presence type falls. For instance,
1596  * for XMPP's "" (do not disturb) status, this would return
1597  * %TP_CONNECTION_PRESENCE_TYPE_BUSY.
1598  *
1599  * Returns: a #TpConnectionPresenceType
1600  * Since: 0.23.1
1601  */
1602 TpConnectionPresenceType
tp_presence_status_spec_get_presence_type(const TpPresenceStatusSpec * self)1603 tp_presence_status_spec_get_presence_type (const TpPresenceStatusSpec *self)
1604 {
1605   g_return_val_if_fail (self != NULL, TP_CONNECTION_PRESENCE_TYPE_UNSET);
1606 
1607   return self->presence_type;
1608 }
1609 
1610 /**
1611  * tp_presence_status_spec_get_name:
1612  * @self: a presence status specification
1613  *
1614  * <!-- -->
1615  *
1616  * Returns: (transfer none): the name of this presence status,
1617  *  such as "available" or "out-to-lunch".
1618  * Since: 0.23.1
1619  */
1620 const gchar *
tp_presence_status_spec_get_name(const TpPresenceStatusSpec * self)1621 tp_presence_status_spec_get_name (const TpPresenceStatusSpec *self)
1622 {
1623   g_return_val_if_fail (self != NULL, NULL);
1624 
1625   return self->name;
1626 }
1627 
1628 /**
1629  * tp_presence_status_spec_can_set_on_self:
1630  * @self: a presence status specification
1631  *
1632  * <!-- -->
1633  *
1634  * Returns: %TRUE if the user can set this presence status on themselves (most
1635  *  statuses), or %FALSE if they cannot directly set it on
1636  *  themselves (typically used for %TP_CONNECTION_PRESENCE_TYPE_OFFLINE
1637  *  and %TP_CONNECTION_PRESENCE_TYPE_ERROR)
1638  * Since: 0.23.1
1639  */
1640 gboolean
tp_presence_status_spec_can_set_on_self(const TpPresenceStatusSpec * self)1641 tp_presence_status_spec_can_set_on_self (const TpPresenceStatusSpec *self)
1642 {
1643   g_return_val_if_fail (self != NULL, FALSE);
1644 
1645   return self->self;
1646 }
1647 
1648 /**
1649  * tp_presence_status_spec_has_message:
1650  * @self: a presence status specification
1651  *
1652  * <!-- -->
1653  *
1654  * Returns: %TRUE if this presence status is accompanied by an optional
1655  *  human-readable message
1656  * Since: 0.23.1
1657  */
1658 gboolean
tp_presence_status_spec_has_message(const TpPresenceStatusSpec * self)1659 tp_presence_status_spec_has_message (const TpPresenceStatusSpec *self)
1660 {
1661   const TpPresenceStatusOptionalArgumentSpec *arg;
1662 
1663   g_return_val_if_fail (self != NULL, FALSE);
1664 
1665   if (self->optional_arguments == NULL)
1666     return FALSE;
1667 
1668   for (arg = self->optional_arguments; arg->name != NULL; arg++)
1669     {
1670       if (!tp_strdiff (arg->name, "message") && !tp_strdiff (arg->dtype, "s"))
1671         return TRUE;
1672     }
1673 
1674   return FALSE;
1675 }
1676 
1677 /**
1678  * tp_presence_status_spec_new:
1679  * @name: the name of the new presence status
1680  * @type: the category into which this presence status falls
1681  * @can_set_on_self: %TRUE if the user can set this presence status
1682  *  on themselves
1683  * @has_message: %TRUE if this presence status is accompanied by an
1684  *  optional human-readable message
1685  *
1686  * <!-- -->
1687  *
1688  * Returns: (transfer full): a new #TpPresenceStatusSpec
1689  * Since: 0.23.1
1690  */
1691 TpPresenceStatusSpec *
tp_presence_status_spec_new(const gchar * name,TpConnectionPresenceType type,gboolean can_set_on_self,gboolean has_message)1692 tp_presence_status_spec_new (const gchar *name,
1693     TpConnectionPresenceType type,
1694     gboolean can_set_on_self,
1695     gboolean has_message)
1696 {
1697   TpPresenceStatusSpec *ret;
1698   static const TpPresenceStatusOptionalArgumentSpec yes_it_has_a_message[] = {
1699         { "message", "s" },
1700         { NULL }
1701   };
1702 
1703   g_return_val_if_fail (!tp_str_empty (name), NULL);
1704   g_return_val_if_fail (type >= 0 && type < TP_NUM_CONNECTION_PRESENCE_TYPES,
1705       NULL);
1706 
1707   ret = g_slice_new0 (TpPresenceStatusSpec);
1708 
1709   ret->name = g_strdup (name);
1710   ret->presence_type = type;
1711   ret->self = can_set_on_self;
1712 
1713   if (has_message)
1714     ret->optional_arguments = yes_it_has_a_message;
1715   else
1716     ret->optional_arguments = NULL;
1717 
1718   /* dummy marker for "this is on the heap" rather than a real struct */
1719   ret->priv = (TpPresenceStatusSpecPrivate *) ret;
1720 
1721   return ret;
1722 }
1723 
1724 /**
1725  * tp_presence_status_spec_copy:
1726  * @self: a presence status specification
1727  *
1728  * Copy a presence status specification.
1729  *
1730  * If @self has optional arguments other than a string named "message",
1731  * they are not copied. Optional arguments with other names or types
1732  * are deprecated.
1733  *
1734  * Returns: (transfer full): a new #TpPresenceStatusSpec resembling @self
1735  * Since: 0.23.1
1736  */
1737 TpPresenceStatusSpec *
tp_presence_status_spec_copy(const TpPresenceStatusSpec * self)1738 tp_presence_status_spec_copy (const TpPresenceStatusSpec *self)
1739 {
1740   g_return_val_if_fail (self != NULL, NULL);
1741 
1742   return tp_presence_status_spec_new (self->name, self->presence_type,
1743       self->self, tp_presence_status_spec_has_message (self));
1744 }
1745 
1746 /**
1747  * tp_presence_status_spec_free:
1748  * @self: (transfer full): a presence status specification
1749  *
1750  * Free a presence status specification produced by
1751  * tp_presence_status_spec_new() or tp_presence_status_spec_copy().
1752  *
1753  * Since: 0.23.1
1754  */
1755 void
tp_presence_status_spec_free(TpPresenceStatusSpec * self)1756 tp_presence_status_spec_free (TpPresenceStatusSpec *self)
1757 {
1758   g_return_if_fail (_tp_presence_status_spec_is_heap_allocated (self));
1759 
1760   /* This struct was designed to always be on the stack, so freeing this
1761    * needs a non-const-correct cast */
1762   g_free ((gchar *) self->name);
1763 
1764   g_slice_free (TpPresenceStatusSpec, self);
1765 }
1766 
1767 G_DEFINE_BOXED_TYPE (TpPresenceStatusSpec, tp_presence_status_spec,
1768     tp_presence_status_spec_copy, tp_presence_status_spec_free)
1769