1 /* ContactList channel manager
2  *
3  * Copyright © 2010 Collabora Ltd.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  */
19 
20 #include <config.h>
21 #include <telepathy-glib/base-contact-list.h>
22 #include <telepathy-glib/base-contact-list-internal.h>
23 
24 #include <dbus/dbus-glib-lowlevel.h>
25 
26 #include <telepathy-glib/contacts-mixin.h>
27 #include <telepathy-glib/dbus.h>
28 #include <telepathy-glib/handle-repo-dynamic.h>
29 #include <telepathy-glib/handle-repo-static.h>
30 #include <telepathy-glib/interfaces.h>
31 
32 #include <telepathy-glib/base-connection-internal.h>
33 #include <telepathy-glib/contact-list-channel-internal.h>
34 #include <telepathy-glib/handle-repo-internal.h>
35 
36 /**
37  * SECTION:base-contact-list
38  * @title: TpBaseContactList
39  * @short_description: channel manager for ContactList channels
40  *
41  * This class represents a connection's contact list (roster, buddy list etc.)
42  * inside a connection manager. It can be used to implement the ContactList
43  * D-Bus interface on the Connection.
44  *
45  * Connections that use #TpBaseContactList must also have the #TpContactsMixin.
46  *
47  * Connection managers should subclass #TpBaseContactList, implementing the
48  * virtual methods for core functionality in #TpBaseContactListClass.
49  * Then, in the connection manager's #TpBaseConnection subclass:
50  *
51  * <itemizedlist>
52  *  <listitem>
53  *   <para>in #G_DEFINE_TYPE_WITH_CODE, implement
54  *   #TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_LIST using
55  *   tp_base_contact_list_mixin_list_iface_init():</para>
56  * |[
57  * G_DEFINE_TYPE_WITH_CODE (MyConnection, my_connection,
58  *     TP_TYPE_BASE_CONNECTION,
59  *     // ...
60  *     G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_LIST,
61  *         tp_base_contact_list_mixin_list_iface_init);
62  *     // ...
63  *     )
64  * ]|
65  *  </listitem>
66  *  <listitem>
67  *   <para>in the <function>class_init</function> method, call
68  *    tp_base_contact_list_mixin_class_init() after
69  *    tp_contacts_mixin_class_init():</para>
70  * |[
71  * // ...
72  * tp_contacts_mixin_class_init (object_class,
73  *     G_STRUCT_OFFSET (MyConnectionClass, contacts_mixin));
74  * tp_base_contact_list_mixin_class_init (base_connection_class);
75  * // ...
76  * ]|
77  *   <para>and include %TP_IFACE_CONNECTION_INTERFACE_CONTACT_LIST in
78  *    the output of
79  *    #TpBaseConnectionClass.get_interfaces_always_present;</para>
80  *  </listitem>
81  *  <listitem>
82  *   <para>in the #TpBaseConnectionClass.create_channel_managers
83  *    implementation, create an instance of the #TpBaseContactList
84  *    subclass, and include it in the returned #GPtrArray;</para>
85  *  </listitem>
86  *  <listitem>
87  *   <para>in the <function>constructed</function> method, call
88  *    tp_base_contact_list_mixin_register_with_contacts_mixin() on the
89  *    <emphasis>connection</emphasis>.</para>
90  *  </listitem>
91  * </itemizedlist>
92  *
93  * To support user-defined contact groups too, additionally implement
94  * %TP_TYPE_CONTACT_GROUP_LIST in the #TpBaseContactList subclass, add the
95  * %TP_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS interface to the output of
96  * #TpBaseConnectionClass.get interfaces_always_present, and implement the
97  * %TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_GROUPS in the #TpBaseConnection
98  * subclass using tp_base_contact_list_mixin_groups_iface_init().
99  *
100  * Optionally, one or more of the #TP_TYPE_MUTABLE_CONTACT_LIST,
101  * #TP_TYPE_MUTABLE_CONTACT_GROUP_LIST, and #TP_TYPE_BLOCKABLE_CONTACT_LIST
102  * GObject interfaces may also be implemented, as appropriate to the protocol.
103  *
104  * In versions of the Telepathy D-Bus Interface Specification prior to
105  * 0.21.0, this functionality was provided as a collection of
106  * individual ContactList channels. As a result, this object also implements
107  * the #TpChannelManager interface, so that it can provide those channels.
108  * The channel objects are internal to this object, and not considered to be
109  * part of the API.
110  *
111  * Since: 0.13.0
112  */
113 
114 /**
115  * TpBaseContactList:
116  *
117  * A connection's contact list (roster, buddy list) inside a connection
118  * manager. Each #TpBaseConnection may have at most one #TpBaseContactList.
119  *
120  * This abstract base class provides the Telepathy "view" of the contact list:
121  * subclasses must provide access to the "model" by implementing its virtual
122  * methods in terms of the protocol's real contact list (e.g. the XMPP roster
123  * object in Wocky).
124  *
125  * The implementation must call tp_base_contact_list_set_list_received()
126  * exactly once, when the initial set of contacts has been received (or
127  * immediately, if that condition is not meaningful for the protocol).
128  *
129  * Since: 0.13.0
130  */
131 
132 /**
133  * TpBaseContactListClass:
134  * @parent_class: the parent class
135  * @dup_contacts: the implementation of tp_base_contact_list_dup_contacts();
136  *  every subclass must implement this itself
137  * @dup_states: the implementation of
138  *  tp_base_contact_list_dup_states(); every subclass must implement
139  *  this itself
140  * @get_contact_list_persists: the implementation of
141  *  tp_base_contact_list_get_contact_list_persists(); if a subclass does not
142  *  implement this itself, the default implementation always returns %TRUE,
143  *  which is correct for most protocols
144  * @download_async: the implementation of
145  *  tp_base_contact_list_download_async(); if a subclass does not implement
146  *  this itself, the default implementation will raise
147  *  TP_ERROR_NOT_IMPLEMENTED asynchronously. Since: 0.18.0
148  * @download_finish: the implementation of
149  *  tp_base_contact_list_download_finish(). Since: 0.18.0
150  *
151  * The class of a #TpBaseContactList.
152  *
153  * Additional functionality can be added by implementing #GInterface<!-- -->s.
154  * Most subclasses should implement %TP_TYPE_MUTABLE_CONTACT_LIST, which allows
155  * the contact list to be altered.
156  *
157  * Subclasses may implement %TP_TYPE_BLOCKABLE_CONTACT_LIST if contacts can
158  * be blocked from communicating with the user.
159  *
160  * Since: 0.13.0
161  */
162 
163 /**
164  * TpBaseContactListDupContactsFunc:
165  * @self: the contact list manager
166  *
167  * Signature of a virtual method to list contacts with a particular state;
168  * the required state is defined by the particular virtual method being
169  * implemented.
170  *
171  * The implementation is expected to have a cache of contacts on the contact
172  * list, which is updated based on protocol events.
173  *
174  * Returns: (transfer full): a set of contacts with the desired state
175  *
176  * Since: 0.13.0
177  */
178 
179 /**
180  * TpBaseContactListDupStatesFunc:
181  * @self: the contact list manager
182  * @contact: the contact
183  * @subscribe: (out) (allow-none): used to return the state of the user's
184  *  subscription to @contact's presence
185  * @publish: (out) (allow-none): used to return the state of @contact's
186  *  subscription to the user's presence
187  * @publish_request: (out) (allow-none) (transfer full): if @publish will be
188  *  set to %TP_SUBSCRIPTION_STATE_ASK, used to return the message that
189  *  @contact sent when they requested permission to see the user's presence;
190  *  otherwise, used to return an empty string
191  *
192  * Signature of a virtual method to get contacts' presences. It should return
193  * @subscribe = %TP_SUBSCRIPTION_STATE_NO, @publish = %TP_SUBSCRIPTION_STATE_NO
194  * and @publish_request = "", without error, for any contact not on the
195  * contact list.
196  *
197  * Since: 0.13.0
198  */
199 
200 /**
201  * TpBaseContactListAsyncFunc:
202  * @self: the contact list manager
203  * @callback: a callback to call on success, failure or disconnection
204  * @user_data: user data for the callback
205  *
206  * Signature of a virtual method that needs no additional information.
207  *
208  * Since: 0.18.0
209  */
210 
211 /**
212  * TpBaseContactListActOnContactsFunc:
213  * @self: the contact list manager
214  * @contacts: the contacts on which to act
215  * @callback: a callback to call on success, failure or disconnection
216  * @user_data: user data for the callback
217  *
218  * Signature of a virtual method that acts on a set of contacts and needs no
219  * additional information, such as removing contacts, approving or cancelling
220  * presence publication, cancelling presence subscription, or removing
221  * contacts.
222  *
223  * The virtual method should call tp_base_contact_list_contacts_changed()
224  * for any contacts it has changed, before returning.
225  *
226  * Since: 0.13.0
227  */
228 
229 /**
230  * TpBaseContactListRequestSubscriptionFunc:
231  * @self: the contact list manager
232  * @contacts: the contacts whose subscription is to be requested
233  * @message: an optional human-readable message from the user
234  * @callback: a callback to call on success, failure or disconnection
235  * @user_data: user data for the callback
236  *
237  * Signature of a virtual method to request permission to see some contacts'
238  * presence.
239  *
240  * The virtual method should call tp_base_contact_list_contacts_changed()
241  * for any contacts it has changed, before it calls @callback.
242  *
243  * Since: 0.13.0
244  */
245 
246 /**
247  * TpBaseContactListAsyncFinishFunc:
248  * @self: the contact list manager
249  * @result: the result of the asynchronous operation
250  * @error: used to raise an error if %FALSE is returned
251  *
252  * Signature of a virtual method to finish an async operation.
253  *
254  * Returns: %TRUE on success, or %FALSE if @error is set
255  *
256  * Since: 0.13.0
257  */
258 
259 #include "config.h"
260 
261 #include <telepathy-glib/base-connection.h>
262 
263 #include <telepathy-glib/handle-repo.h>
264 
265 #define DEBUG_FLAG TP_DEBUG_CONTACT_LISTS
266 #include "telepathy-glib/debug-internal.h"
267 #include "telepathy-glib/util-internal.h"
268 
269 struct _TpBaseContactListPrivate
270 {
271   TpBaseConnection *conn;
272   TpHandleRepoIface *contact_repo;
273 
274   TpContactListState state;
275   /* NULL unless state = FAILURE */
276   GError *failure /* initially NULL */;
277 
278   /* values referenced; 0'th remains NULL */
279   TpBaseContactListChannel *lists[TP_NUM_LIST_HANDLES];
280 
281   TpHandleRepoIface *group_repo;
282   /* handle borrowed from channel => referenced TpContactGroupChannel */
283   GHashTable *groups;
284 
285   /* borrowed TpExportableChannel => GSList of gpointer (request tokens) that
286    * will be satisfied by that channel when the contact list has been
287    * downloaded. The requests are in reverse chronological order; the list
288    * can also contain NULL.
289    *
290    * If a channel appears in the keys of this map, that means it hasn't been
291    * announced via NewChannels yet. */
292   GHashTable *channel_requests;
293 
294   /* DBusGMethodInvocation *s for calls to RequestBlockedContacts which are
295    * waiting for the contact list to (fail to) be downloaded.
296    */
297   GQueue blocked_contact_requests;
298 
299   gulong status_changed_id;
300 
301   /* TRUE if @conn implements TpSvcConnectionInterface$FOO - used to
302    * decide whether to emit signals on these new interfaces. Initialized in
303    * the constructor and cleared when we lose @conn. */
304   gboolean svc_contact_list;
305   gboolean svc_contact_groups;
306   gboolean svc_contact_blocking;
307 
308   /* TRUE if the contact list must be downloaded at connection. Default is
309    * TRUE. */
310   gboolean download_at_connection;
311 };
312 
313 struct _TpBaseContactListClassPrivate
314 {
315   char dummy;
316 };
317 
318 static void channel_manager_iface_init (TpChannelManagerIface *iface);
319 
320 G_DEFINE_ABSTRACT_TYPE_WITH_CODE (TpBaseContactList,
321     tp_base_contact_list,
322     G_TYPE_OBJECT,
323     G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER,
324       channel_manager_iface_init);
325     g_type_add_class_private (g_define_type_id, sizeof (
326         TpBaseContactListClassPrivate)))
327 
328 /**
329  * TP_TYPE_MUTABLE_CONTACT_LIST:
330  *
331  * Interface representing a #TpBaseContactList on which the contact list can
332  * potentially be changed.
333  *
334  * Since: 0.13.0
335  */
336 
337 /**
338  * TpMutableContactListInterface:
339  * @parent: the parent interface
340  * @request_subscription_async: the implementation of
341  *  tp_base_contact_list_request_subscription_async(); must always be provided
342  * @request_subscription_finish: the implementation of
343  *  tp_base_contact_list_request_subscription_finish(); the default
344  *  implementation may be used if @result is a #GSimpleAsyncResult
345  * @authorize_publication_async: the implementation of
346  *  tp_base_contact_list_authorize_publication_async(); must always be provided
347  * @authorize_publication_finish: the implementation of
348  *  tp_base_contact_list_authorize_publication_finish(); the default
349  *  implementation may be used if @result is a #GSimpleAsyncResult
350  * @remove_contacts_async: the implementation of
351  *  tp_base_contact_list_remove_contacts_async(); must always be provided
352  * @remove_contacts_finish: the implementation of
353  *  tp_base_contact_list_remove_contacts_finish(); the default
354  *  implementation may be used if @result is a #GSimpleAsyncResult
355  * @unsubscribe_async: the implementation of
356  *  tp_base_contact_list_unsubscribe_async(); must always be provided
357  * @unsubscribe_finish: the implementation of
358  *  tp_base_contact_list_unsubscribe_finish(); the default
359  *  implementation may be used if @result is a #GSimpleAsyncResult
360  * @unpublish_async: the implementation of
361  *  tp_base_contact_list_unpublish_async(); must always be provided
362  * @unpublish_finish: the implementation of
363  *  tp_base_contact_list_unpublish_finish(); the default
364  *  implementation may be used if @result is a #GSimpleAsyncResult
365  * @store_contacts_async: the implementation of
366  *  tp_base_contact_list_store_contacts_async(); if not reimplemented,
367  *  the default implementation is %NULL, which is interpreted as "do nothing"
368  * @store_contacts_finish: the implementation of
369  *  tp_base_contact_list_store_contacts_finish(); the default
370  *  implementation may be used if @result is a #GSimpleAsyncResult
371  * @can_change_contact_list: the implementation of
372  *  tp_base_contact_list_can_change_contact_list(); if not reimplemented,
373  *  the default implementation always returns %TRUE
374  * @get_request_uses_message: the implementation of
375  *  tp_base_contact_list_get_request_uses_message(); if not reimplemented,
376  *  the default implementation always returns %TRUE
377  *
378  * The interface vtable for a %TP_TYPE_MUTABLE_CONTACT_LIST.
379  *
380  * Since: 0.13.0
381  */
382 
383 G_DEFINE_INTERFACE (TpMutableContactList, tp_mutable_contact_list,
384     TP_TYPE_BASE_CONTACT_LIST)
385 
386 /**
387  * TP_TYPE_BLOCKABLE_CONTACT_LIST:
388  *
389  * Interface representing a #TpBaseContactList on which contacts can
390  * be blocked from communicating with the user.
391  *
392  * Since: 0.13.0
393  */
394 
395 /**
396  * TpBlockableContactListInterface:
397  * @parent: the parent interface
398  * @dup_blocked_contacts: the implementation of
399  *  tp_base_contact_list_dup_blocked_contacts(); must always be provided
400  * @block_contacts_async: the implementation of
401  *  tp_base_contact_list_block_contacts_async(); either this or
402  *  @block_contacts_with_abuse_async must always be provided
403  * @block_contacts_finish: the implementation of
404  *  tp_base_contact_list_block_contacts_finish(); the default
405  *  implementation may be used if @result is a #GSimpleAsyncResult
406  * @unblock_contacts_async: the implementation of
407  *  tp_base_contact_list_unblock_contacts_async(); must always be provided
408  * @unblock_contacts_finish: the implementation of
409  *  tp_base_contact_list_unblock_contacts_finish(); the default
410  *  implementation may be used if @result is a #GSimpleAsyncResult
411  * @can_block: the implementation of
412  *  tp_base_contact_list_can_block(); if not reimplemented,
413  *  the default implementation always returns %TRUE
414  * @block_contacts_with_abuse_async: the implementation of
415  *  tp_base_contact_list_block_contacts_async(); either this or
416  *  @block_contacts_async must always be provided. If the underlying protocol
417  *  does not support reporting contacts as abusive, implement
418  *  @block_contacts_async instead. Since: 0.15.1
419  *
420  * The interface vtable for a %TP_TYPE_BLOCKABLE_CONTACT_LIST.
421  *
422  * Since: 0.13.0
423  */
424 
425 G_DEFINE_INTERFACE (TpBlockableContactList, tp_blockable_contact_list,
426     TP_TYPE_BASE_CONTACT_LIST)
427 
428 /**
429  * TP_TYPE_CONTACT_GROUP_LIST:
430  *
431  * Interface representing a #TpBaseContactList on which contacts can
432  * be in user-defined groups, which cannot necessarily be edited
433  * (%TP_TYPE_MUTABLE_CONTACT_GROUP_LIST represents a list where these
434  * groups exist and can also be edited).
435  *
436  * Since: 0.13.0
437  */
438 
439 /**
440  * TpContactGroupListInterface:
441  * @parent: the parent interface
442  * @dup_groups: the implementation of
443  *  tp_base_contact_list_dup_groups(); must always be implemented
444  * @dup_group_members: the implementation of
445  *  tp_base_contact_list_dup_group_members(); must always be implemented
446  * @dup_contact_groups: the implementation of
447  *  tp_base_contact_list_dup_contact_groups(); must always be implemented
448  * @has_disjoint_groups: the implementation of
449  *  tp_base_contact_list_has_disjoint_groups(); if not reimplemented,
450  *  the default implementation always returns %FALSE
451  * @normalize_group: the implementation of
452  *  tp_base_contact_list_normalize_group(); if not reimplemented,
453  *  the default implementation is %NULL, which allows any UTF-8 string
454  *  as a group name (including the empty string) and assumes that any distinct
455  *  group names can coexist
456  *
457  * The interface vtable for a %TP_TYPE_CONTACT_GROUP_LIST.
458  *
459  * Since: 0.13.0
460  */
461 
462 G_DEFINE_INTERFACE (TpContactGroupList, tp_contact_group_list,
463     TP_TYPE_BASE_CONTACT_LIST)
464 
465 /**
466  * TP_TYPE_MUTABLE_CONTACT_GROUP_LIST:
467  *
468  * Interface representing a #TpBaseContactList on which user-defined contact
469  * groups can potentially be changed. %TP_TYPE_CONTACT_GROUP_LIST is a
470  * prerequisite for this interface.
471  *
472  * Since: 0.13.0
473  */
474 
475 /**
476  * TpMutableContactGroupListInterface:
477  * @parent: the parent interface
478  * @get_group_storage: the implementation of
479  *  tp_base_contact_list_get_group_storage(); the default implementation is
480  *  %NULL, which results in %TP_CONTACT_METADATA_STORAGE_TYPE_ANYONE being
481  *  advertised
482  * @set_contact_groups_async: the implementation of
483  *  tp_base_contact_list_set_contact_groups_async(); must always be implemented
484  * @set_contact_groups_finish: the implementation of
485  *  tp_base_contact_list_set_contact_groups_finish(); the default
486  *  implementation may be used if @result is a #GSimpleAsyncResult
487  * @set_group_members_async: the implementation of
488  *  tp_base_contact_list_set_group_members_async(); must always be implemented
489  * @set_group_members_finish: the implementation of
490  *  tp_base_contact_list_set_group_members_finish(); the default
491  *  implementation may be used if @result is a #GSimpleAsyncResult
492  * @add_to_group_async: the implementation of
493  *  tp_base_contact_list_add_to_group_async(); must always be implemented
494  * @add_to_group_finish: the implementation of
495  *  tp_base_contact_list_add_to_group_finish(); the default
496  *  implementation may be used if @result is a #GSimpleAsyncResult
497  * @remove_from_group_async: the implementation of
498  *  tp_base_contact_list_remove_from_group_async(); must always be implemented
499  * @remove_from_group_finish: the implementation of
500  *  tp_base_contact_list_remove_from_group_finish(); the default
501  *  implementation may be used if @result is a #GSimpleAsyncResult
502  * @remove_group_async: the implementation of
503  *  tp_base_contact_list_remove_group_async(); must always be implemented
504  * @remove_group_finish: the implementation of
505  *  tp_base_contact_list_remove_group_finish(); the default
506  *  implementation may be used if @result is a #GSimpleAsyncResult
507  * @rename_group_async: the implementation of
508  *  tp_base_contact_list_rename_group_async(); the default implementation
509  *  results in group renaming being emulated via a call to
510  *  @add_to_group_async and a call to @remove_group_async
511  * @rename_group_finish: the implementation of
512  *  tp_base_contact_list_rename_group_finish(); the default
513  *  implementation may be used if @result is a #GSimpleAsyncResult
514  *
515  * The interface vtable for a %TP_TYPE_MUTABLE_CONTACT_GROUP_LIST.
516  *
517  * Since: 0.13.0
518  */
519 
520 G_DEFINE_INTERFACE (TpMutableContactGroupList, tp_mutable_contact_group_list,
521     TP_TYPE_CONTACT_GROUP_LIST)
522 
523 enum {
524     PROP_CONNECTION = 1,
525     PROP_DOWNLOAD_AT_CONNECTION,
526     N_PROPS
527 };
528 
529 static void
530 tp_base_contact_list_contacts_changed_internal (TpBaseContactList *self,
531     TpHandleSet *changed, TpHandleSet *removed, gboolean is_initial_roster);
532 
533 static void
tp_base_contact_list_init(TpBaseContactList * self)534 tp_base_contact_list_init (TpBaseContactList *self)
535 {
536   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TP_TYPE_BASE_CONTACT_LIST,
537       TpBaseContactListPrivate);
538   self->priv->groups = g_hash_table_new_full (NULL, NULL, NULL,
539       g_object_unref);
540   self->priv->channel_requests = g_hash_table_new (NULL, NULL);
541   g_queue_init (&self->priv->blocked_contact_requests);
542 }
543 
544 static void
tp_base_contact_list_fail_channel_requests(TpBaseContactList * self,GQuark domain,gint code,const gchar * message)545 tp_base_contact_list_fail_channel_requests (TpBaseContactList *self,
546     GQuark domain,
547     gint code,
548     const gchar *message)
549 {
550   if (self->priv->channel_requests != NULL)
551     {
552       GHashTable *tmp = self->priv->channel_requests;
553       GHashTableIter iter;
554       gpointer value;
555 
556       self->priv->channel_requests = NULL;
557       g_hash_table_iter_init (&iter, tmp);
558 
559       while (g_hash_table_iter_next (&iter, NULL, &value))
560         {
561           GSList *requests = value;
562           GSList *slist;
563 
564           requests = g_slist_reverse (requests);
565 
566           for (slist = requests; slist != NULL; slist = slist->next)
567             {
568               tp_channel_manager_emit_request_failed (self,
569                   slist->data, domain, code, message);
570             }
571 
572           g_slist_free (requests);
573           g_hash_table_iter_steal (&iter);
574         }
575 
576       g_hash_table_unref (tmp);
577     }
578 }
579 
580 static void
tp_base_contact_list_fail_blocked_contact_requests(TpBaseContactList * self,const GError * error)581 tp_base_contact_list_fail_blocked_contact_requests (
582     TpBaseContactList *self,
583     const GError *error)
584 {
585   DBusGMethodInvocation *context;
586 
587   while ((context = g_queue_pop_head (&self->priv->blocked_contact_requests))
588           != NULL)
589     dbus_g_method_return_error (context, error);
590 }
591 
592 static void
tp_base_contact_list_free_contents(TpBaseContactList * self)593 tp_base_contact_list_free_contents (TpBaseContactList *self)
594 {
595   GError error = { TP_ERROR, TP_ERROR_DISCONNECTED,
596       "Disconnected before blocked contacts were retrieved" };
597   guint i;
598 
599   tp_base_contact_list_fail_channel_requests (self, TP_ERROR,
600       TP_ERROR_DISCONNECTED,
601       "Unable to complete channel request due to disconnection");
602   tp_base_contact_list_fail_blocked_contact_requests (self, &error);
603 
604   for (i = 0; i < TP_NUM_LIST_HANDLES; i++)
605     tp_clear_object (self->priv->lists + i);
606 
607   tp_clear_pointer (&self->priv->groups, g_hash_table_unref);
608   tp_clear_object (&self->priv->contact_repo);
609 
610   if (self->priv->group_repo != NULL)
611     {
612       /* the normalization data is a borrowed reference to @self, which must
613        * be released when @self is no longer usable */
614       _tp_dynamic_handle_repo_set_normalization_data (self->priv->group_repo,
615           NULL, NULL);
616       tp_clear_object (&self->priv->group_repo);
617     }
618 
619   if (self->priv->conn != NULL)
620     {
621       if (self->priv->status_changed_id != 0)
622         {
623           g_signal_handler_disconnect (self->priv->conn,
624               self->priv->status_changed_id);
625           self->priv->status_changed_id = 0;
626         }
627 
628       tp_clear_object (&self->priv->conn);
629       self->priv->svc_contact_list = FALSE;
630       self->priv->svc_contact_groups = FALSE;
631       self->priv->svc_contact_blocking = FALSE;
632     }
633 }
634 
635 static void
tp_base_contact_list_dispose(GObject * object)636 tp_base_contact_list_dispose (GObject *object)
637 {
638   TpBaseContactList *self = TP_BASE_CONTACT_LIST (object);
639   void (*dispose) (GObject *) =
640     G_OBJECT_CLASS (tp_base_contact_list_parent_class)->dispose;
641 
642   tp_base_contact_list_free_contents (self);
643   g_assert (self->priv->groups == NULL);
644   g_assert (self->priv->contact_repo == NULL);
645   g_assert (self->priv->group_repo == NULL);
646   g_assert (self->priv->lists[TP_LIST_HANDLE_SUBSCRIBE] == NULL);
647   g_assert (self->priv->channel_requests == NULL);
648 
649   if (dispose != NULL)
650     dispose (object);
651 }
652 
653 static void
tp_base_contact_list_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)654 tp_base_contact_list_get_property (GObject *object,
655     guint property_id,
656     GValue *value,
657     GParamSpec *pspec)
658 {
659   TpBaseContactList *self = TP_BASE_CONTACT_LIST (object);
660 
661   switch (property_id)
662     {
663     case PROP_CONNECTION:
664       g_value_set_object (value, self->priv->conn);
665       break;
666 
667     case PROP_DOWNLOAD_AT_CONNECTION:
668       g_value_set_boolean (value, self->priv->download_at_connection);
669       break;
670 
671     default:
672       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
673       break;
674     }
675 }
676 
677 static void
tp_base_contact_list_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)678 tp_base_contact_list_set_property (GObject *object,
679     guint property_id,
680     const GValue *value,
681     GParamSpec *pspec)
682 {
683   TpBaseContactList *self = TP_BASE_CONTACT_LIST (object);
684 
685   switch (property_id)
686     {
687     case PROP_CONNECTION:
688       g_assert (self->priv->conn == NULL);    /* construct-only */
689       self->priv->conn = g_value_dup_object (value);
690       break;
691 
692     case PROP_DOWNLOAD_AT_CONNECTION:
693       self->priv->download_at_connection = g_value_get_boolean (value);
694       break;
695 
696     default:
697       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
698       break;
699     }
700 }
701 
702 static gchar *
tp_base_contact_list_repo_normalize_group(TpHandleRepoIface * repo,const gchar * id,gpointer context,GError ** error)703 tp_base_contact_list_repo_normalize_group (TpHandleRepoIface *repo,
704     const gchar *id,
705     gpointer context,
706     GError **error)
707 {
708   TpBaseContactList *self =
709     _tp_dynamic_handle_repo_get_normalization_data (repo);
710   gchar *ret;
711 
712   if (id == NULL)
713     id = "";
714 
715   if (self == NULL)
716     {
717       /* already disconnected or something */
718       return g_strdup (id);
719     }
720 
721   ret = tp_base_contact_list_normalize_group (self, id);
722 
723   if (ret == NULL)
724     g_set_error (error, TP_ERROR, TP_ERROR_INVALID_HANDLE,
725         "Invalid group name '%s'", id);
726 
727   return ret;
728 }
729 
730 /* elements 0, 1... of this enum must be kept in sync with elements 1, 2...
731  * of the enum in the -internal header */
732 static const gchar * const tp_base_contact_list_contact_lists
733   [TP_NUM_LIST_HANDLES + 1] = {
734     "subscribe",
735     "publish",
736     "stored",
737     "deny",
738     NULL
739 };
740 
741 static void
status_changed_cb(TpBaseConnection * conn,guint status,guint reason,TpBaseContactList * self)742 status_changed_cb (TpBaseConnection *conn,
743     guint status,
744     guint reason,
745     TpBaseContactList *self)
746 {
747   if (status == TP_CONNECTION_STATUS_DISCONNECTED)
748     tp_base_contact_list_free_contents (self);
749 }
750 
751 static void
tp_base_contact_list_constructed(GObject * object)752 tp_base_contact_list_constructed (GObject *object)
753 {
754   TpBaseContactList *self = TP_BASE_CONTACT_LIST (object);
755   TpBaseContactListClass *cls = TP_BASE_CONTACT_LIST_GET_CLASS (self);
756   void (*chain_up) (GObject *) =
757     G_OBJECT_CLASS (tp_base_contact_list_parent_class)->constructed;
758   TpHandleRepoIface *list_repo;
759 
760   if (chain_up != NULL)
761     chain_up (object);
762 
763   g_assert (self->priv->conn != NULL);
764 
765   g_return_if_fail (cls->dup_contacts != NULL);
766   g_return_if_fail (cls->dup_states != NULL);
767   g_return_if_fail (cls->get_contact_list_persists != NULL);
768   g_return_if_fail (cls->download_async != NULL);
769 
770   self->priv->svc_contact_list =
771     TP_IS_SVC_CONNECTION_INTERFACE_CONTACT_LIST (self->priv->conn);
772   self->priv->svc_contact_groups =
773     TP_IS_SVC_CONNECTION_INTERFACE_CONTACT_GROUPS (self->priv->conn);
774   self->priv->svc_contact_blocking =
775     TP_IS_SVC_CONNECTION_INTERFACE_CONTACT_BLOCKING (self->priv->conn);
776 
777   if (TP_IS_MUTABLE_CONTACT_LIST (self))
778     {
779       TpMutableContactListInterface *iface =
780         TP_MUTABLE_CONTACT_LIST_GET_INTERFACE (self);
781 
782       g_return_if_fail (iface->can_change_contact_list != NULL);
783       g_return_if_fail (iface->get_request_uses_message != NULL);
784       g_return_if_fail (iface->request_subscription_async != NULL);
785       g_return_if_fail (iface->request_subscription_finish != NULL);
786       g_return_if_fail (iface->authorize_publication_async != NULL);
787       g_return_if_fail (iface->authorize_publication_finish != NULL);
788       /* iface->store_contacts_async == NULL is OK */
789       g_return_if_fail (iface->store_contacts_finish != NULL);
790       g_return_if_fail (iface->remove_contacts_async != NULL);
791       g_return_if_fail (iface->remove_contacts_finish != NULL);
792       g_return_if_fail (iface->unsubscribe_async != NULL);
793       g_return_if_fail (iface->unsubscribe_finish != NULL);
794       g_return_if_fail (iface->unpublish_async != NULL);
795       g_return_if_fail (iface->unpublish_finish != NULL);
796     }
797 
798   if (TP_IS_BLOCKABLE_CONTACT_LIST (self))
799     {
800       TpBlockableContactListInterface *iface =
801         TP_BLOCKABLE_CONTACT_LIST_GET_INTERFACE (self);
802 
803       g_return_if_fail (iface->can_block != NULL);
804       g_return_if_fail (iface->dup_blocked_contacts != NULL);
805       g_return_if_fail ((iface->block_contacts_async != NULL) ^
806           (iface->block_contacts_with_abuse_async != NULL));
807       g_return_if_fail (iface->block_contacts_finish != NULL);
808       g_return_if_fail (iface->unblock_contacts_async != NULL);
809       g_return_if_fail (iface->unblock_contacts_finish != NULL);
810     }
811 
812   self->priv->contact_repo = tp_base_connection_get_handles (self->priv->conn,
813       TP_HANDLE_TYPE_CONTACT);
814   g_object_ref (self->priv->contact_repo);
815 
816   list_repo = tp_static_handle_repo_new (TP_HANDLE_TYPE_LIST,
817       (const gchar **) tp_base_contact_list_contact_lists);
818 
819   if (TP_IS_CONTACT_GROUP_LIST (self))
820     {
821       TpContactGroupListInterface *iface =
822         TP_CONTACT_GROUP_LIST_GET_INTERFACE (self);
823 
824       g_return_if_fail (iface->has_disjoint_groups != NULL);
825       g_return_if_fail (iface->dup_groups != NULL);
826       g_return_if_fail (iface->dup_contact_groups != NULL);
827       g_return_if_fail (iface->dup_group_members != NULL);
828 
829       self->priv->group_repo = tp_dynamic_handle_repo_new (TP_HANDLE_TYPE_GROUP,
830           tp_base_contact_list_repo_normalize_group, NULL);
831 
832       /* borrowed ref so the handle repo can call our virtual method, released
833        * in tp_base_contact_list_free_contents */
834       _tp_dynamic_handle_repo_set_normalization_data (self->priv->group_repo,
835           self, NULL);
836 
837       _tp_base_connection_set_handle_repo (self->priv->conn,
838           TP_HANDLE_TYPE_GROUP, self->priv->group_repo);
839     }
840 
841   if (TP_IS_MUTABLE_CONTACT_GROUP_LIST (self))
842     {
843       TpMutableContactGroupListInterface *iface =
844         TP_MUTABLE_CONTACT_GROUP_LIST_GET_INTERFACE (self);
845 
846       g_return_if_fail (iface->set_contact_groups_async != NULL);
847       g_return_if_fail (iface->set_contact_groups_finish != NULL);
848       g_return_if_fail (iface->set_group_members_async != NULL);
849       g_return_if_fail (iface->set_group_members_finish != NULL);
850       g_return_if_fail (iface->add_to_group_async != NULL);
851       g_return_if_fail (iface->add_to_group_finish != NULL);
852       g_return_if_fail (iface->remove_from_group_async != NULL);
853       g_return_if_fail (iface->remove_from_group_finish != NULL);
854       g_return_if_fail (iface->remove_group_async != NULL);
855       g_return_if_fail (iface->remove_group_finish != NULL);
856     }
857 
858   _tp_base_connection_set_handle_repo (self->priv->conn, TP_HANDLE_TYPE_LIST,
859       list_repo);
860 
861   /* set_handle_repo doesn't steal a reference */
862   g_object_unref (list_repo);
863 
864   self->priv->status_changed_id = g_signal_connect (self->priv->conn,
865       "status-changed", (GCallback) status_changed_cb, self);
866 }
867 
868 static gboolean
tp_base_contact_list_simple_finish(TpBaseContactList * self,GAsyncResult * result,GError ** error)869 tp_base_contact_list_simple_finish (TpBaseContactList *self,
870     GAsyncResult *result,
871     GError **error)
872 {
873   GSimpleAsyncResult *simple = (GSimpleAsyncResult *) result;
874 
875   g_return_val_if_fail (g_simple_async_result_is_valid (
876       result, G_OBJECT (self), NULL), FALSE);
877 
878   return !g_simple_async_result_propagate_error (simple, error);
879 }
880 
881 static void
tp_base_contact_list_download_async_default(TpBaseContactList * self,GAsyncReadyCallback callback,gpointer user_data)882 tp_base_contact_list_download_async_default (TpBaseContactList *self,
883     GAsyncReadyCallback callback,
884     gpointer user_data)
885 {
886   g_simple_async_report_error_in_idle (G_OBJECT (self), callback,
887       user_data, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED,
888       "This CM does not implement Download");
889 }
890 
891 static void
tp_mutable_contact_list_default_init(TpMutableContactListInterface * iface)892 tp_mutable_contact_list_default_init (TpMutableContactListInterface *iface)
893 {
894   iface->request_subscription_finish = tp_base_contact_list_simple_finish;
895   iface->authorize_publication_finish = tp_base_contact_list_simple_finish;
896   iface->unsubscribe_finish = tp_base_contact_list_simple_finish;
897   iface->unpublish_finish = tp_base_contact_list_simple_finish;
898   iface->store_contacts_finish = tp_base_contact_list_simple_finish;
899   iface->remove_contacts_finish = tp_base_contact_list_simple_finish;
900 
901   iface->can_change_contact_list = tp_base_contact_list_true_func;
902   iface->get_request_uses_message = tp_base_contact_list_true_func;
903   /* there's no default for the other virtual methods */
904 }
905 
906 static void
tp_blockable_contact_list_default_init(TpBlockableContactListInterface * iface)907 tp_blockable_contact_list_default_init (TpBlockableContactListInterface *iface)
908 {
909   iface->block_contacts_finish = tp_base_contact_list_simple_finish;
910   iface->unblock_contacts_finish = tp_base_contact_list_simple_finish;
911 
912   iface->can_block = tp_base_contact_list_true_func;
913   /* there's no default for the other virtual methods */
914 }
915 
916 static void
tp_contact_group_list_default_init(TpContactGroupListInterface * iface)917 tp_contact_group_list_default_init (TpContactGroupListInterface *iface)
918 {
919   iface->has_disjoint_groups = tp_base_contact_list_false_func;
920   /* there's no default for the other virtual methods */
921 }
922 
923 static void tp_base_contact_list_emulate_rename_group (TpBaseContactList *,
924     const gchar *, const gchar *, GAsyncReadyCallback, gpointer);
925 
926 static void
tp_mutable_contact_group_list_default_init(TpMutableContactGroupListInterface * iface)927 tp_mutable_contact_group_list_default_init (
928     TpMutableContactGroupListInterface *iface)
929 {
930   iface->rename_group_async = tp_base_contact_list_emulate_rename_group;
931 
932   iface->add_to_group_finish = tp_base_contact_list_simple_finish;
933   iface->remove_from_group_finish = tp_base_contact_list_simple_finish;
934   iface->set_contact_groups_finish = tp_base_contact_list_simple_finish;
935   iface->remove_group_finish = tp_base_contact_list_simple_finish;
936   iface->rename_group_finish = tp_base_contact_list_simple_finish;
937   iface->set_group_members_finish = tp_base_contact_list_simple_finish;
938 }
939 
940 static void
tp_base_contact_list_class_init(TpBaseContactListClass * cls)941 tp_base_contact_list_class_init (TpBaseContactListClass *cls)
942 {
943   GObjectClass *object_class = G_OBJECT_CLASS (cls);
944 
945   g_type_class_add_private (cls, sizeof (TpBaseContactListPrivate));
946 
947   cls->priv = G_TYPE_CLASS_GET_PRIVATE (cls, TP_TYPE_BASE_CONTACT_LIST,
948       TpBaseContactListClassPrivate);
949 
950   /* defaults */
951   cls->get_contact_list_persists = tp_base_contact_list_true_func;
952   cls->download_async = tp_base_contact_list_download_async_default;
953   cls->download_finish = tp_base_contact_list_simple_finish;
954 
955   object_class->get_property = tp_base_contact_list_get_property;
956   object_class->set_property = tp_base_contact_list_set_property;
957   object_class->constructed = tp_base_contact_list_constructed;
958   object_class->dispose = tp_base_contact_list_dispose;
959 
960   /**
961    * TpBaseContactList:connection:
962    *
963    * The connection that owns this channel manager.
964    * Read-only except during construction.
965    *
966    * Since: 0.13.0
967    */
968   g_object_class_install_property (object_class, PROP_CONNECTION,
969       g_param_spec_object ("connection", "Connection",
970         "The connection that owns this channel manager",
971         TP_TYPE_BASE_CONNECTION,
972         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
973 
974   /**
975    * TpBaseContactList:download-at-connection:
976    *
977    * Whether the roster should be automatically downloaded at connection.
978    *
979    * This property doesn't change anything in TpBaseContactsList's behaviour.
980    * Implementations should check this property when they become connected
981    * and in their Download method, and behave accordingly.
982    *
983    * Since: 0.18.0
984    */
985   g_object_class_install_property (object_class, PROP_DOWNLOAD_AT_CONNECTION,
986       g_param_spec_boolean ("download-at-connection", "Download at connection",
987         "Whether the roster should be automatically downloaded at connection",
988         TRUE,
989         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
990 }
991 
992 static void
tp_base_contact_list_foreach_channel(TpChannelManager * manager,TpExportableChannelFunc func,gpointer user_data)993 tp_base_contact_list_foreach_channel (TpChannelManager *manager,
994     TpExportableChannelFunc func,
995     gpointer user_data)
996 {
997   TpBaseContactList *self = TP_BASE_CONTACT_LIST (manager);
998   GHashTableIter iter;
999   gpointer handle, channel;
1000   guint i;
1001 
1002   /* in both cases, we look in channel_requests to avoid including channels
1003    * that don't officially exist yet */
1004 
1005   for (i = 0; i < TP_NUM_LIST_HANDLES; i++)
1006     {
1007       if (self->priv->lists[i] != NULL &&
1008           !g_hash_table_lookup_extended (self->priv->channel_requests,
1009             self->priv->lists[i], NULL, NULL))
1010         func (TP_EXPORTABLE_CHANNEL (self->priv->lists[i]), user_data);
1011     }
1012 
1013   g_hash_table_iter_init (&iter, self->priv->groups);
1014 
1015   while (g_hash_table_iter_next (&iter, &handle, &channel))
1016     {
1017       if (!g_hash_table_lookup_extended (self->priv->channel_requests,
1018             channel, NULL, NULL))
1019         func (TP_EXPORTABLE_CHANNEL (channel), user_data);
1020     }
1021 }
1022 
1023 static const gchar * const fixed_properties[] = {
1024     TP_PROP_CHANNEL_CHANNEL_TYPE,
1025     TP_PROP_CHANNEL_TARGET_HANDLE_TYPE,
1026     NULL
1027 };
1028 
1029 static const gchar * const allowed_properties[] = {
1030     TP_PROP_CHANNEL_TARGET_HANDLE,
1031     TP_PROP_CHANNEL_TARGET_ID,
1032     NULL
1033 };
1034 
1035 static void
tp_base_contact_list_type_foreach_channel_class(GType type,TpChannelManagerTypeChannelClassFunc func,gpointer user_data)1036 tp_base_contact_list_type_foreach_channel_class (GType type,
1037     TpChannelManagerTypeChannelClassFunc func,
1038     gpointer user_data)
1039 {
1040   GHashTable *table = tp_asv_new (
1041       TP_PROP_CHANNEL_CHANNEL_TYPE,
1042           G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
1043       TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_LIST,
1044       NULL);
1045 
1046   func (type, table, allowed_properties, user_data);
1047 
1048   if (g_type_is_a (type, TP_TYPE_MUTABLE_CONTACT_GROUP_LIST))
1049     {
1050       g_hash_table_insert (table, TP_PROP_CHANNEL_TARGET_HANDLE_TYPE,
1051           tp_g_value_slice_new_uint (TP_HANDLE_TYPE_GROUP));
1052       func (type, table, allowed_properties, user_data);
1053     }
1054 
1055   g_hash_table_unref (table);
1056 }
1057 
1058 static void
tp_base_contact_list_associate_request(TpBaseContactList * self,gpointer chan,gpointer request_token)1059 tp_base_contact_list_associate_request (TpBaseContactList *self,
1060     gpointer chan,
1061     gpointer request_token)
1062 {
1063   GSList *requests = NULL;
1064 
1065   /* remember that it hasn't been announced yet, by putting it in
1066    * channel_requests */
1067   requests = g_hash_table_lookup (self->priv->channel_requests, chan);
1068   g_hash_table_steal (self->priv->channel_requests, chan);
1069   requests = g_slist_prepend (requests, request_token);
1070   g_hash_table_insert (self->priv->channel_requests, chan, requests);
1071 }
1072 
1073 static gpointer
tp_base_contact_list_new_channel(TpBaseContactList * self,TpHandleType handle_type,TpHandle handle,gpointer request_token)1074 tp_base_contact_list_new_channel (TpBaseContactList *self,
1075     TpHandleType handle_type,
1076     TpHandle handle,
1077     gpointer request_token)
1078 {
1079   gpointer chan;
1080   gchar *object_path;
1081   GType type;
1082 
1083   if (handle_type == TP_HANDLE_TYPE_LIST)
1084     {
1085       object_path = g_strdup_printf ("%s/ContactList/%s",
1086           tp_base_connection_get_object_path (self->priv->conn),
1087           tp_base_contact_list_contact_lists[handle - 1]);
1088       type = TP_TYPE_CONTACT_LIST_CHANNEL;
1089     }
1090   else
1091     {
1092       g_assert (handle_type == TP_HANDLE_TYPE_GROUP);
1093       object_path = g_strdup_printf ("%s/Group/%u",
1094           tp_base_connection_get_object_path (self->priv->conn), handle);
1095       type = TP_TYPE_CONTACT_GROUP_CHANNEL;
1096     }
1097 
1098   chan = g_object_new (type,
1099       "connection", self->priv->conn,
1100       "manager", self,
1101       "object-path", object_path,
1102       "handle-type", handle_type,
1103       "handle", handle,
1104       NULL);
1105 
1106   g_free (object_path);
1107 
1108   if (handle_type == TP_HANDLE_TYPE_LIST)
1109     {
1110       g_assert (self->priv->lists[handle] == NULL);
1111       self->priv->lists[handle] = chan;
1112     }
1113   else
1114     {
1115       g_assert (g_hash_table_lookup (self->priv->groups,
1116             GUINT_TO_POINTER (handle)) == NULL);
1117       g_hash_table_insert (self->priv->groups, GUINT_TO_POINTER (handle),
1118           chan);
1119     }
1120 
1121   tp_base_contact_list_associate_request (self, chan, request_token);
1122 
1123   return chan;
1124 }
1125 
1126 static void
tp_base_contact_list_announce_channel(TpBaseContactList * self,gpointer channel,const GError * error)1127 tp_base_contact_list_announce_channel (TpBaseContactList *self,
1128     gpointer channel,
1129     const GError *error)
1130 {
1131   GSList *requests = g_hash_table_lookup (self->priv->channel_requests,
1132       channel);
1133 
1134   /* this is all fine even if requests is NULL */
1135 
1136   g_hash_table_steal (self->priv->channel_requests, channel);
1137 
1138   /* get into chronological order */
1139   requests = g_slist_reverse (requests);
1140   /* our list of requests can include NULL, which isn't a valid request
1141    * token; get rid of it/them */
1142   requests = g_slist_remove_all (requests, NULL);
1143 
1144   if (error == NULL)
1145     {
1146       tp_channel_manager_emit_new_channel (self, channel, requests);
1147     }
1148   else
1149     {
1150       GSList *iter;
1151 
1152       for (iter = requests; iter != NULL; iter = iter->next)
1153         tp_channel_manager_emit_request_failed (self, iter->data,
1154             error->domain, error->code, error->message);
1155     }
1156 
1157   g_slist_free (requests);
1158 }
1159 
1160 static void
tp_base_contact_list_create_group_cb(GObject * source,GAsyncResult * result,gpointer channel)1161 tp_base_contact_list_create_group_cb (GObject *source,
1162     GAsyncResult *result,
1163     gpointer channel)
1164 {
1165   TpBaseContactList *self = TP_BASE_CONTACT_LIST (source);
1166   GError *error = NULL;
1167 
1168   if (tp_base_contact_list_add_to_group_finish (self, result, &error))
1169     {
1170       /* If all goes well, the channel should have been announced. */
1171       GSList *tokens = g_hash_table_lookup (self->priv->channel_requests,
1172           channel);
1173 
1174       if (tokens == NULL)
1175         return;
1176 
1177       g_set_error (&error, TP_ERROR, TP_ERROR_NOT_AVAILABLE,
1178           "%s did not create a group even though it claims to have done so",
1179           G_OBJECT_TYPE_NAME (self));
1180     }
1181 
1182   /**/
1183 
1184   g_clear_error (&error);
1185 }
1186 
1187 static gboolean
tp_base_contact_list_request_helper(TpChannelManager * manager,gpointer request_token,GHashTable * request_properties,gboolean is_create)1188 tp_base_contact_list_request_helper (TpChannelManager *manager,
1189     gpointer request_token,
1190     GHashTable *request_properties,
1191     gboolean is_create)
1192 {
1193   TpBaseContactList *self = (TpBaseContactList *) manager;
1194   TpHandleType handle_type;
1195   TpHandle handle;
1196   TpBaseContactListChannel *chan;
1197   GError *error = NULL;
1198 
1199   g_return_val_if_fail (TP_IS_BASE_CONTACT_LIST (self), FALSE);
1200 
1201   if (tp_strdiff (tp_asv_get_string (request_properties,
1202           TP_PROP_CHANNEL_CHANNEL_TYPE),
1203       TP_IFACE_CHANNEL_TYPE_CONTACT_LIST))
1204     {
1205       return FALSE;
1206     }
1207 
1208   handle_type = tp_asv_get_uint32 (request_properties,
1209       TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL);
1210 
1211   if (handle_type != TP_HANDLE_TYPE_LIST &&
1212       (handle_type != TP_HANDLE_TYPE_GROUP ||
1213        !TP_IS_CONTACT_GROUP_LIST (self)))
1214     {
1215       return FALSE;
1216     }
1217 
1218   handle = tp_asv_get_uint32 (request_properties,
1219       TP_PROP_CHANNEL_TARGET_HANDLE, NULL);
1220   g_assert (handle != 0);
1221 
1222   if (tp_channel_manager_asv_has_unknown_properties (request_properties,
1223         fixed_properties, allowed_properties, &error) ||
1224       tp_base_contact_list_get_connection (self, &error) == NULL)
1225     {
1226       goto error;
1227     }
1228 
1229   if (handle_type == TP_HANDLE_TYPE_LIST)
1230     {
1231       /* TpBaseConnection already checked the handle for validity */
1232       g_assert (handle > 0);
1233       g_assert (handle < TP_NUM_LIST_HANDLES);
1234 
1235       if (handle == TP_LIST_HANDLE_STORED &&
1236           !tp_base_contact_list_get_contact_list_persists (self))
1237         {
1238           g_set_error_literal (&error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED,
1239               "Subscriptions do not persist, so this connection lacks the "
1240               "'stored' channel");
1241           goto error;
1242         }
1243 
1244       if (handle == TP_LIST_HANDLE_DENY &&
1245           !tp_base_contact_list_can_block (self))
1246         {
1247           g_set_error_literal (&error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED,
1248               "This connection cannot put people on the 'deny' list");
1249           goto error;
1250         }
1251 
1252       chan = self->priv->lists[handle];
1253     }
1254   else
1255     {
1256       chan = g_hash_table_lookup (self->priv->groups,
1257           GUINT_TO_POINTER (handle));
1258     }
1259 
1260   if (chan == NULL)
1261     {
1262       if (handle_type == TP_HANDLE_TYPE_LIST)
1263         {
1264           /* make an object, don't announce it yet, and remember the
1265            * request token for when it's announced in set_list_received */
1266           tp_base_contact_list_new_channel (self, handle_type, handle,
1267               request_token);
1268         }
1269       else
1270         {
1271           if (TP_IS_MUTABLE_CONTACT_GROUP_LIST (self))
1272             {
1273               const gchar *name = tp_handle_inspect (self->priv->group_repo,
1274                   handle);
1275               gpointer channel;
1276               TpHandleSet *set = tp_handle_set_new (self->priv->contact_repo);
1277 
1278               /* make an object, don't announce it yet, and remember the
1279                * request token for when it's announced, if it's actually
1280                * created */
1281               channel = tp_base_contact_list_new_channel (self, handle_type,
1282                   handle, request_token);
1283 
1284               /* this will create the empty group, and announce the channel(s)
1285                * later if appropriate */
1286               tp_base_contact_list_add_to_group_async (self, name, set,
1287                   tp_base_contact_list_create_group_cb, channel);
1288               tp_handle_set_destroy (set);
1289             }
1290           else
1291             {
1292               g_set_error_literal (&error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED,
1293                   "This connection cannot create new groups");
1294               goto error;
1295             }
1296         }
1297     }
1298   else if (is_create)
1299     {
1300       g_set_error (&error, TP_ERROR, TP_ERROR_NOT_AVAILABLE,
1301           "A ContactList channel for type #%u, handle #%u already exists",
1302           handle_type, handle);
1303       goto error;
1304     }
1305   else if (g_hash_table_lookup_extended (self->priv->channel_requests, chan,
1306         NULL, NULL))
1307     {
1308       /* there are outstanding requests for the channel, and there's an object
1309        * to represent it, but it hasn't been announced; just append our
1310        * request */
1311       tp_base_contact_list_associate_request (self, chan, request_token);
1312     }
1313   else
1314     {
1315       tp_channel_manager_emit_request_already_satisfied (self,
1316           request_token, TP_EXPORTABLE_CHANNEL (chan));
1317     }
1318 
1319   return TRUE;
1320 
1321 error:
1322   tp_channel_manager_emit_request_failed (self, request_token,
1323       error->domain, error->code, error->message);
1324   g_error_free (error);
1325   return TRUE;
1326 }
1327 
1328 static gboolean
tp_base_contact_list_create_channel(TpChannelManager * manager,gpointer request_token,GHashTable * request_properties)1329 tp_base_contact_list_create_channel (TpChannelManager *manager,
1330     gpointer request_token,
1331     GHashTable *request_properties)
1332 {
1333   return tp_base_contact_list_request_helper (manager, request_token,
1334       request_properties, TRUE);
1335 }
1336 
1337 static gboolean
tp_base_contact_list_ensure_channel(TpChannelManager * manager,gpointer request_token,GHashTable * request_properties)1338 tp_base_contact_list_ensure_channel (TpChannelManager *manager,
1339     gpointer request_token,
1340     GHashTable *request_properties)
1341 {
1342   return tp_base_contact_list_request_helper (manager, request_token,
1343       request_properties, FALSE);
1344 }
1345 
1346 static void
channel_manager_iface_init(TpChannelManagerIface * iface)1347 channel_manager_iface_init (TpChannelManagerIface *iface)
1348 {
1349   iface->foreach_channel = tp_base_contact_list_foreach_channel;
1350   iface->type_foreach_channel_class =
1351       tp_base_contact_list_type_foreach_channel_class;
1352   iface->create_channel = tp_base_contact_list_create_channel;
1353   iface->ensure_channel = tp_base_contact_list_ensure_channel;
1354   /* In this channel manager, Request has the same semantics as Ensure */
1355   iface->request_channel = tp_base_contact_list_ensure_channel;
1356 }
1357 
1358 TpChannelGroupFlags
_tp_base_contact_list_get_group_flags(TpBaseContactList * self)1359 _tp_base_contact_list_get_group_flags (TpBaseContactList *self)
1360 {
1361   if (TP_IS_MUTABLE_CONTACT_GROUP_LIST (self))
1362     return TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_REMOVE;
1363 
1364   return 0;
1365 }
1366 
1367 TpChannelGroupFlags
_tp_base_contact_list_get_list_flags(TpBaseContactList * self,TpHandle list)1368 _tp_base_contact_list_get_list_flags (TpBaseContactList *self,
1369     TpHandle list)
1370 {
1371   if (!tp_base_contact_list_can_change_contact_list (self))
1372     return 0;
1373 
1374   switch (list)
1375     {
1376     case TP_LIST_HANDLE_PUBLISH:
1377       /* We always allow an attempt to stop publishing presence to people,
1378        * and an attempt to send people our presence (if only as a sort of
1379        * pre-authorization). */
1380       return TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_REMOVE;
1381 
1382     case TP_LIST_HANDLE_SUBSCRIBE:
1383       /* We can ask people to show us their presence, with a message.
1384        * We do our best to allow rescinding unreplied requests, and
1385        * unsubscribing, even if the underlying protocol does not. */
1386       return
1387         TP_CHANNEL_GROUP_FLAG_CAN_ADD |
1388         (tp_base_contact_list_get_request_uses_message (self)
1389           ? TP_CHANNEL_GROUP_FLAG_MESSAGE_ADD
1390           : 0) |
1391         TP_CHANNEL_GROUP_FLAG_CAN_REMOVE |
1392         TP_CHANNEL_GROUP_FLAG_CAN_RESCIND;
1393 
1394     case TP_LIST_HANDLE_STORED:
1395       /* We allow attempts to add people to the roster and remove them again,
1396        * even if the real protocol doesn't. */
1397       return TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_REMOVE;
1398 
1399     case TP_LIST_HANDLE_DENY:
1400       /* A deny list wouldn't be much good if we couldn't actually deny,
1401        * would it? */
1402       return TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_REMOVE;
1403 
1404     default:
1405       g_return_val_if_reached (0);
1406     }
1407 }
1408 
1409 static void
tp_base_contact_list_add_to_group_cb(GObject * source,GAsyncResult * result,gpointer user_data)1410 tp_base_contact_list_add_to_group_cb (GObject *source,
1411     GAsyncResult *result,
1412     gpointer user_data)
1413 {
1414   TpBaseContactList *self = TP_BASE_CONTACT_LIST (source);
1415   GError *error = NULL;
1416 
1417   g_return_if_fail (TP_IS_BASE_CONTACT_LIST (source));
1418 
1419   if (tp_base_contact_list_add_to_group_finish (self, result, &error))
1420     {
1421       dbus_g_method_return (user_data);
1422     }
1423   else
1424     {
1425       dbus_g_method_return_error (user_data, error);
1426       g_clear_error (&error);
1427     }
1428 }
1429 
1430 void
_tp_base_contact_list_add_to_group(TpBaseContactList * self,TpHandle group,const GArray * contacts_arr,const gchar * message G_GNUC_UNUSED,DBusGMethodInvocation * context)1431 _tp_base_contact_list_add_to_group (TpBaseContactList *self,
1432     TpHandle group,
1433     const GArray *contacts_arr,
1434     const gchar *message G_GNUC_UNUSED,
1435     DBusGMethodInvocation *context)
1436 {
1437   TpHandleSet *contacts;
1438   const gchar *group_name;
1439   GError *error = NULL;
1440 
1441   /* fail if not ready yet, failed, or disconnected, or if handles are bad */
1442   if (tp_base_contact_list_get_state (self, &error) !=
1443       TP_CONTACT_LIST_STATE_SUCCESS ||
1444       !tp_handles_are_valid (self->priv->contact_repo, contacts_arr, FALSE,
1445         &error))
1446     goto error;
1447 
1448   if (!TP_IS_MUTABLE_CONTACT_GROUP_LIST (self))
1449     {
1450       g_set_error (&error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED,
1451           "Cannot add contacts to a group");
1452       goto error;
1453     }
1454 
1455   contacts = tp_handle_set_new_from_array (self->priv->contact_repo,
1456       contacts_arr);
1457   group_name = tp_handle_inspect (self->priv->group_repo, group);
1458 
1459   tp_base_contact_list_add_to_group_async (self, group_name, contacts,
1460       tp_base_contact_list_add_to_group_cb, context);
1461 
1462   tp_handle_set_destroy (contacts);
1463   return;
1464 
1465 error:
1466   dbus_g_method_return_error (context, error);
1467   g_error_free (error);
1468 }
1469 
1470 static void
tp_base_contact_list_remove_from_group_cb(GObject * source,GAsyncResult * result,gpointer user_data)1471 tp_base_contact_list_remove_from_group_cb (GObject *source,
1472     GAsyncResult *result,
1473     gpointer user_data)
1474 {
1475   TpBaseContactList *self = TP_BASE_CONTACT_LIST (source);
1476   GError *error = NULL;
1477 
1478   g_return_if_fail (TP_IS_BASE_CONTACT_LIST (source));
1479 
1480   if (tp_base_contact_list_remove_from_group_finish (self, result, &error))
1481     {
1482       dbus_g_method_return (user_data);
1483     }
1484   else
1485     {
1486       dbus_g_method_return_error (user_data, error);
1487       g_clear_error (&error);
1488     }
1489 }
1490 
1491 void
_tp_base_contact_list_remove_from_group(TpBaseContactList * self,TpHandle group,const GArray * contacts_arr,const gchar * message G_GNUC_UNUSED,guint reason G_GNUC_UNUSED,DBusGMethodInvocation * context)1492 _tp_base_contact_list_remove_from_group (TpBaseContactList *self,
1493     TpHandle group,
1494     const GArray *contacts_arr,
1495     const gchar *message G_GNUC_UNUSED,
1496     guint reason G_GNUC_UNUSED,
1497     DBusGMethodInvocation *context)
1498 {
1499   TpHandleSet *contacts;
1500   const gchar *group_name;
1501   GError *error = NULL;
1502 
1503   /* fail if not ready yet, failed, or disconnected, or if handles are bad */
1504   if (tp_base_contact_list_get_state (self, &error) !=
1505       TP_CONTACT_LIST_STATE_SUCCESS ||
1506       !tp_handles_are_valid (self->priv->contact_repo, contacts_arr, FALSE,
1507         &error))
1508     goto error;
1509 
1510   if (!TP_IS_MUTABLE_CONTACT_GROUP_LIST (self))
1511     {
1512       g_set_error (&error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED,
1513           "Cannot remove contacts from a group");
1514       goto error;
1515     }
1516 
1517   contacts = tp_handle_set_new_from_array (self->priv->contact_repo,
1518       contacts_arr);
1519   group_name = tp_handle_inspect (self->priv->group_repo, group);
1520 
1521   tp_base_contact_list_remove_from_group_async (self, group_name, contacts,
1522       tp_base_contact_list_remove_from_group_cb, context);
1523 
1524   tp_handle_set_destroy (contacts);
1525   return;
1526 
1527 error:
1528   dbus_g_method_return_error (context, error);
1529   g_error_free (error);
1530 }
1531 
1532 static void
tp_base_contact_list_delete_group_by_handle_cb(GObject * source,GAsyncResult * result,gpointer user_data)1533 tp_base_contact_list_delete_group_by_handle_cb (GObject *source,
1534     GAsyncResult *result,
1535     gpointer user_data)
1536 {
1537   TpBaseContactList *self = TP_BASE_CONTACT_LIST (source);
1538   GError *error = NULL;
1539 
1540   g_return_if_fail (TP_IS_BASE_CONTACT_LIST (source));
1541 
1542   if (tp_base_contact_list_remove_group_finish (self, result, &error))
1543     {
1544       DEBUG ("Removing group '%s' succeeded", (gchar *) user_data);
1545     }
1546   else
1547     {
1548       DEBUG ("Removing group '%s' failed: %s #%d: %s", (gchar *) user_data,
1549           g_quark_to_string (error->domain), error->code, error->message);
1550       g_clear_error (&error);
1551     }
1552 
1553   g_free (user_data);
1554 }
1555 
1556 gboolean
_tp_base_contact_list_delete_group_by_handle(TpBaseContactList * self,TpHandle group,GError ** error)1557 _tp_base_contact_list_delete_group_by_handle (TpBaseContactList *self,
1558     TpHandle group,
1559     GError **error)
1560 {
1561   gchar *group_name;
1562 
1563   if (tp_base_contact_list_get_state (self, error) !=
1564       TP_CONTACT_LIST_STATE_SUCCESS)
1565     {
1566       g_set_error (error, TP_ERROR, TP_ERROR_DISCONNECTED, "Disconnected");
1567       return FALSE;
1568     }
1569 
1570   if (!TP_IS_MUTABLE_CONTACT_GROUP_LIST (self))
1571     {
1572       g_set_error (error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED,
1573           "Cannot remove a group");
1574       return FALSE;
1575     }
1576 
1577   group_name = g_strdup (tp_handle_inspect (self->priv->group_repo, group));
1578 
1579   tp_base_contact_list_remove_group_async (self, group_name,
1580       tp_base_contact_list_delete_group_by_handle_cb, group_name);
1581   return TRUE;
1582 }
1583 
1584 typedef struct {
1585     DBusGMethodInvocation *context;
1586     TpListHandle list;
1587 } ListContext;
1588 
1589 static ListContext *
list_context_new(DBusGMethodInvocation * context,TpListHandle list)1590 list_context_new (DBusGMethodInvocation *context,
1591     TpListHandle list)
1592 {
1593   ListContext *lc = g_slice_new0 (ListContext);
1594 
1595   lc->context = context;
1596   lc->list = list;
1597   return lc;
1598 }
1599 
1600 static void
list_context_finish_take_error(ListContext * lc,GError * error)1601 list_context_finish_take_error (ListContext *lc,
1602     GError *error)
1603 {
1604   if (error == NULL)
1605     {
1606       dbus_g_method_return (lc->context);
1607     }
1608   else
1609     {
1610       dbus_g_method_return_error (lc->context, error);
1611       g_error_free (error);
1612     }
1613 
1614   g_slice_free (ListContext, lc);
1615 }
1616 
1617 static void
tp_base_contact_list_add_to_list_cb(GObject * source,GAsyncResult * result,gpointer user_data)1618 tp_base_contact_list_add_to_list_cb (GObject *source,
1619     GAsyncResult *result,
1620     gpointer user_data)
1621 {
1622   TpBaseContactList *self = TP_BASE_CONTACT_LIST (source);
1623   ListContext *lc = user_data;
1624   GError *error = NULL;
1625   gboolean ok;
1626 
1627   g_return_if_fail (TP_IS_BASE_CONTACT_LIST (source));
1628 
1629   switch (lc->list)
1630     {
1631     case TP_LIST_HANDLE_SUBSCRIBE:
1632       ok = tp_base_contact_list_request_subscription_finish (self, result,
1633           &error);
1634       break;
1635 
1636     case TP_LIST_HANDLE_PUBLISH:
1637       ok = tp_base_contact_list_authorize_publication_finish (self, result,
1638           &error);
1639       break;
1640 
1641     case TP_LIST_HANDLE_STORED:
1642       ok = tp_base_contact_list_store_contacts_finish (self, result, &error);
1643       break;
1644 
1645     case TP_LIST_HANDLE_DENY:
1646       ok = tp_base_contact_list_block_contacts_finish (self, result, &error);
1647       break;
1648 
1649     default:
1650       g_return_if_reached ();
1651     }
1652 
1653   g_assert (ok == (error == NULL));
1654   list_context_finish_take_error (lc, error);
1655 }
1656 
1657 void
_tp_base_contact_list_add_to_list(TpBaseContactList * self,TpHandle list,const GArray * contacts_arr,const gchar * message,DBusGMethodInvocation * context)1658 _tp_base_contact_list_add_to_list (TpBaseContactList *self,
1659     TpHandle list,
1660     const GArray *contacts_arr,
1661     const gchar *message,
1662     DBusGMethodInvocation *context)
1663 {
1664   TpHandleSet *contacts;
1665   GError *error = NULL;
1666   ListContext *lc;
1667 
1668   /* fail if not ready yet, failed, or disconnected, or if handles are bad */
1669   if (tp_base_contact_list_get_state (self, &error) !=
1670       TP_CONTACT_LIST_STATE_SUCCESS ||
1671       !tp_handles_are_valid (self->priv->contact_repo, contacts_arr, FALSE,
1672         &error))
1673     goto error;
1674 
1675   if (!tp_base_contact_list_can_change_contact_list (self))
1676     {
1677       g_set_error (&error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED,
1678           "Cannot change subscriptions");
1679       goto error;
1680     }
1681 
1682   contacts = tp_handle_set_new_from_array (self->priv->contact_repo,
1683       contacts_arr);
1684   lc = list_context_new (context, list);
1685 
1686   switch (list)
1687     {
1688     case TP_LIST_HANDLE_SUBSCRIBE:
1689       tp_base_contact_list_request_subscription_async (self, contacts,
1690           message, tp_base_contact_list_add_to_list_cb, lc);
1691       break;
1692 
1693     case TP_LIST_HANDLE_PUBLISH:
1694       tp_base_contact_list_authorize_publication_async (self, contacts,
1695           tp_base_contact_list_add_to_list_cb, lc);
1696       break;
1697 
1698     case TP_LIST_HANDLE_STORED:
1699       tp_base_contact_list_store_contacts_async (self, contacts,
1700           tp_base_contact_list_add_to_list_cb, lc);
1701       break;
1702 
1703     case TP_LIST_HANDLE_DENY:
1704       tp_base_contact_list_block_contacts_async (self, contacts,
1705           tp_base_contact_list_add_to_list_cb, lc);
1706       break;
1707 
1708     default:
1709       g_assert_not_reached ();
1710     }
1711 
1712   tp_handle_set_destroy (contacts);
1713   return;
1714 
1715 error:
1716   dbus_g_method_return_error (context, error);
1717   g_error_free (error);
1718 }
1719 
1720 static void
tp_base_contact_list_remove_from_list_cb(GObject * source,GAsyncResult * result,gpointer user_data)1721 tp_base_contact_list_remove_from_list_cb (GObject *source,
1722     GAsyncResult *result,
1723     gpointer user_data)
1724 {
1725   TpBaseContactList *self = TP_BASE_CONTACT_LIST (source);
1726   ListContext *lc = user_data;
1727   GError *error = NULL;
1728   gboolean ok;
1729 
1730   g_return_if_fail (TP_IS_BASE_CONTACT_LIST (source));
1731 
1732   switch (lc->list)
1733     {
1734     case TP_LIST_HANDLE_SUBSCRIBE:
1735       ok = tp_base_contact_list_unsubscribe_finish (self, result, &error);
1736       break;
1737 
1738     case TP_LIST_HANDLE_PUBLISH:
1739       ok = tp_base_contact_list_unpublish_finish (self, result, &error);
1740       break;
1741 
1742     case TP_LIST_HANDLE_STORED:
1743       ok = tp_base_contact_list_remove_contacts_finish (self, result, &error);
1744       break;
1745 
1746     case TP_LIST_HANDLE_DENY:
1747       ok = tp_base_contact_list_unblock_contacts_finish (self, result, &error);
1748       break;
1749 
1750     default:
1751       g_return_if_reached ();
1752     }
1753 
1754   g_assert (ok == (error == NULL));
1755   list_context_finish_take_error (lc, error);
1756 }
1757 
1758 void
_tp_base_contact_list_remove_from_list(TpBaseContactList * self,TpHandle list,const GArray * contacts_arr,const gchar * message G_GNUC_UNUSED,guint reason G_GNUC_UNUSED,DBusGMethodInvocation * context)1759 _tp_base_contact_list_remove_from_list (TpBaseContactList *self,
1760     TpHandle list,
1761     const GArray *contacts_arr,
1762     const gchar *message G_GNUC_UNUSED,
1763     guint reason G_GNUC_UNUSED,
1764     DBusGMethodInvocation *context)
1765 {
1766   TpHandleSet *contacts;
1767   GError *error = NULL;
1768   ListContext *lc;
1769 
1770   /* fail if not ready yet, failed, or disconnected, or if handles are bad */
1771   if (tp_base_contact_list_get_state (self, &error) !=
1772       TP_CONTACT_LIST_STATE_SUCCESS ||
1773       !tp_handles_are_valid (self->priv->contact_repo, contacts_arr, FALSE,
1774         &error))
1775     goto error;
1776 
1777   if (!tp_base_contact_list_can_change_contact_list (self))
1778     {
1779       g_set_error (&error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED,
1780           "Cannot change subscriptions");
1781       goto error;
1782     }
1783 
1784   contacts = tp_handle_set_new_from_array (self->priv->contact_repo,
1785       contacts_arr);
1786   lc = list_context_new (context, list);
1787 
1788   switch (list)
1789     {
1790     case TP_LIST_HANDLE_SUBSCRIBE:
1791       tp_base_contact_list_unsubscribe_async (self, contacts,
1792           tp_base_contact_list_remove_from_list_cb, lc);
1793       break;
1794 
1795     case TP_LIST_HANDLE_PUBLISH:
1796       tp_base_contact_list_unpublish_async (self, contacts,
1797           tp_base_contact_list_remove_from_list_cb, lc);
1798       break;
1799 
1800     case TP_LIST_HANDLE_STORED:
1801       tp_base_contact_list_remove_contacts_async (self, contacts,
1802           tp_base_contact_list_remove_from_list_cb, lc);
1803       break;
1804 
1805     case TP_LIST_HANDLE_DENY:
1806       tp_base_contact_list_unblock_contacts_async (self, contacts,
1807           tp_base_contact_list_remove_from_list_cb, lc);
1808       break;
1809 
1810     default:
1811       g_assert_not_reached ();
1812     }
1813 
1814   tp_handle_set_destroy (contacts);
1815   return;
1816 
1817 error:
1818   dbus_g_method_return_error (context, error);
1819   g_error_free (error);
1820 }
1821 
1822 /**
1823  * tp_base_contact_list_set_list_pending:
1824  * @self: the contact list manager
1825  *
1826  * Record that receiving the initial contact list is in progress.
1827  *
1828  * Since: 0.13.0
1829  */
1830 void
tp_base_contact_list_set_list_pending(TpBaseContactList * self)1831 tp_base_contact_list_set_list_pending (TpBaseContactList *self)
1832 {
1833   g_return_if_fail (TP_IS_BASE_CONTACT_LIST (self));
1834   g_return_if_fail (self->priv->state == TP_CONTACT_LIST_STATE_NONE);
1835 
1836   if (self->priv->conn == NULL ||
1837       self->priv->state != TP_CONTACT_LIST_STATE_NONE)
1838     return;
1839 
1840   self->priv->state = TP_CONTACT_LIST_STATE_WAITING;
1841   tp_svc_connection_interface_contact_list_emit_contact_list_state_changed (
1842       self->priv->conn, self->priv->state);
1843 }
1844 
1845 /**
1846  * tp_base_contact_list_set_list_failed:
1847  * @self: the contact list manager
1848  * @domain: a #GError domain
1849  * @code: a #GError code
1850  * @message: a #GError message
1851  *
1852  * Record that receiving the initial contact list has failed.
1853  *
1854  * This method cannot be called after tp_base_contact_list_set_list_received()
1855  * is called.
1856  *
1857  * Since: 0.13.0
1858  */
1859 void
tp_base_contact_list_set_list_failed(TpBaseContactList * self,GQuark domain,gint code,const gchar * message)1860 tp_base_contact_list_set_list_failed (TpBaseContactList *self,
1861     GQuark domain,
1862     gint code,
1863     const gchar *message)
1864 {
1865   g_return_if_fail (TP_IS_BASE_CONTACT_LIST (self));
1866   g_return_if_fail (self->priv->state != TP_CONTACT_LIST_STATE_SUCCESS);
1867 
1868   if (self->priv->conn == NULL)
1869     return;
1870 
1871   self->priv->state = TP_CONTACT_LIST_STATE_FAILURE;
1872   g_clear_error (&self->priv->failure);
1873   self->priv->failure = g_error_new_literal (domain, code, message);
1874   tp_svc_connection_interface_contact_list_emit_contact_list_state_changed (
1875       self->priv->conn, self->priv->state);
1876 
1877   tp_base_contact_list_fail_channel_requests (self, domain, code, message);
1878   tp_base_contact_list_fail_blocked_contact_requests (self,
1879       self->priv->failure);
1880 }
1881 
1882 /**
1883  * tp_base_contact_list_set_list_received:
1884  * @self: the contact list manager
1885  *
1886  * Record that the initial contact list has been received. This allows the
1887  * contact list manager to reply to requests for the list of contacts that
1888  * were previously made, and reply to subsequent requests immediately.
1889  *
1890  * This method can be called at most once for a contact list manager.
1891  *
1892  * In protocols where there's no good definition of the point at which the
1893  * initial contact list has been received (such as link-local XMPP), this
1894  * method may be called immediately.
1895  *
1896  * The #TpBaseContactListDupContactsFunc and
1897  * #TpBaseContactListDupStatesFunc must already give correct
1898  * results when entering this method.
1899  *
1900  * If implemented, tp_base_contact_list_dup_blocked_contacts() must also
1901  * give correct results when entering this method.
1902  *
1903  * Since: 0.13.0
1904  */
1905 void
tp_base_contact_list_set_list_received(TpBaseContactList * self)1906 tp_base_contact_list_set_list_received (TpBaseContactList *self)
1907 {
1908   TpHandleSet *contacts;
1909   guint i;
1910 
1911   g_return_if_fail (TP_IS_BASE_CONTACT_LIST (self));
1912   g_return_if_fail (self->priv->state != TP_CONTACT_LIST_STATE_SUCCESS);
1913 
1914   if (self->priv->conn == NULL)
1915     return;
1916 
1917   self->priv->state = TP_CONTACT_LIST_STATE_SUCCESS;
1918   /* we emit the signal for this later */
1919 
1920   if (self->priv->lists[TP_LIST_HANDLE_SUBSCRIBE] == NULL)
1921     {
1922       tp_base_contact_list_new_channel (self,
1923           TP_HANDLE_TYPE_LIST, TP_LIST_HANDLE_SUBSCRIBE, NULL);
1924     }
1925 
1926   if (self->priv->lists[TP_LIST_HANDLE_PUBLISH] == NULL)
1927     {
1928       tp_base_contact_list_new_channel (self,
1929           TP_HANDLE_TYPE_LIST, TP_LIST_HANDLE_PUBLISH, NULL);
1930     }
1931 
1932   if (tp_base_contact_list_get_contact_list_persists (self) &&
1933       self->priv->lists[TP_LIST_HANDLE_STORED] == NULL)
1934     {
1935       tp_base_contact_list_new_channel (self,
1936           TP_HANDLE_TYPE_LIST, TP_LIST_HANDLE_STORED, NULL);
1937     }
1938 
1939   contacts = tp_base_contact_list_dup_contacts (self);
1940   g_return_if_fail (contacts != NULL);
1941 
1942   /* A quick sanity check to make sure that faulty implementations crash
1943    * during development :-) */
1944   tp_base_contact_list_dup_states (self,
1945       tp_base_connection_get_self_handle (self->priv->conn),
1946       NULL, NULL, NULL);
1947 
1948   if (DEBUGGING)
1949     {
1950       gchar *tmp = tp_intset_dump (tp_handle_set_peek (contacts));
1951 
1952       DEBUG ("Initial contacts: %s", tmp);
1953       g_free (tmp);
1954     }
1955 
1956   tp_base_contact_list_contacts_changed_internal (self, contacts, NULL, TRUE);
1957 
1958   if (tp_base_contact_list_can_block (self))
1959     {
1960       TpHandleSet *blocked;
1961 
1962       if (self->priv->lists[TP_LIST_HANDLE_DENY] == NULL)
1963         {
1964           tp_base_contact_list_new_channel (self,
1965               TP_HANDLE_TYPE_LIST, TP_LIST_HANDLE_DENY, NULL);
1966         }
1967 
1968       blocked = tp_base_contact_list_dup_blocked_contacts (self);
1969 
1970       if (DEBUGGING)
1971         {
1972           gchar *tmp = tp_intset_dump (tp_handle_set_peek (contacts));
1973 
1974           DEBUG ("Initially blocked contacts: %s", tmp);
1975           g_free (tmp);
1976         }
1977 
1978       tp_base_contact_list_contact_blocking_changed (self, blocked);
1979 
1980       if (self->priv->svc_contact_blocking &&
1981           self->priv->blocked_contact_requests.length > 0)
1982         {
1983           GHashTable *map = tp_handle_set_to_identifier_map (blocked);
1984           DBusGMethodInvocation *context;
1985 
1986           while ((context = g_queue_pop_head (
1987                       &self->priv->blocked_contact_requests)) != NULL)
1988             tp_svc_connection_interface_contact_blocking_return_from_request_blocked_contacts (context, map);
1989 
1990           g_hash_table_unref (map);
1991         }
1992 
1993       tp_handle_set_destroy (blocked);
1994     }
1995 
1996   for (i = 0; i < TP_NUM_LIST_HANDLES; i++)
1997     {
1998       if (self->priv->lists[i] != NULL)
1999         tp_base_contact_list_announce_channel (self, self->priv->lists[i],
2000             NULL);
2001     }
2002 
2003   /* The natural thing to do here would be to iterate over all contacts, and
2004    * for each contact, emit a signal adding them to their own groups. However,
2005    * that emits a signal per contact. Here we turn the data model inside out,
2006    * to emit one signal per group - that's probably fewer (and also means we
2007    * can put them in batches for legacy Group channels). */
2008   if (TP_IS_CONTACT_GROUP_LIST (self))
2009     {
2010       GStrv groups = tp_base_contact_list_dup_groups (self);
2011 
2012       tp_base_contact_list_groups_created (self,
2013           (const gchar * const *) groups, -1);
2014 
2015       for (i = 0; groups != NULL && groups[i] != NULL; i++)
2016         {
2017           TpHandleSet *members = tp_base_contact_list_dup_group_members (self,
2018               groups[i]);
2019 
2020           tp_base_contact_list_groups_changed (self, members,
2021               (const gchar * const *) groups + i, 1, NULL, 0);
2022           tp_handle_set_destroy (members);
2023         }
2024 
2025       g_strfreev (groups);
2026     }
2027 
2028   tp_handle_set_destroy (contacts);
2029 
2030   /* emit this last, so people can distinguish between the initial state
2031    * and subsequent changes */
2032   tp_svc_connection_interface_contact_list_emit_contact_list_state_changed (
2033       self->priv->conn, self->priv->state);
2034 }
2035 
2036 char
_tp_base_contact_list_presence_state_to_letter(TpSubscriptionState ps)2037 _tp_base_contact_list_presence_state_to_letter (TpSubscriptionState ps)
2038 {
2039   switch (ps)
2040     {
2041     case TP_SUBSCRIPTION_STATE_UNKNOWN:
2042       return '?';
2043 
2044     case TP_SUBSCRIPTION_STATE_YES:
2045       return 'Y';
2046 
2047     case TP_SUBSCRIPTION_STATE_NO:
2048       return 'N';
2049 
2050     case TP_SUBSCRIPTION_STATE_ASK:
2051       return 'A';
2052 
2053     case TP_SUBSCRIPTION_STATE_REMOVED_REMOTELY:
2054       return 'R';
2055 
2056     default:
2057       return '!';
2058     }
2059 }
2060 
2061 /**
2062  * tp_base_contact_list_contacts_changed:
2063  * @self: the contact list manager
2064  * @changed: (allow-none): a set of contacts added to the contact list or with
2065  *  a changed status
2066  * @removed: (allow-none): a set of contacts removed from the contact list
2067  *
2068  * Emit signals for a change to the contact list.
2069  *
2070  * The results of #TpBaseContactListDupContactsFunc and
2071  * #TpBaseContactListDupStatesFunc must already reflect
2072  * the contacts' new statuses when entering this method (in practice, this
2073  * means that implementations must update their own cache of contacts
2074  * before calling this method).
2075  *
2076  * Since: 0.13.0
2077  */
2078 void
tp_base_contact_list_contacts_changed(TpBaseContactList * self,TpHandleSet * changed,TpHandleSet * removed)2079 tp_base_contact_list_contacts_changed (TpBaseContactList *self,
2080     TpHandleSet *changed,
2081     TpHandleSet *removed)
2082 {
2083   tp_base_contact_list_contacts_changed_internal (self, changed, removed,
2084       FALSE);
2085 }
2086 
2087 static void
tp_base_contact_list_contacts_changed_internal(TpBaseContactList * self,TpHandleSet * changed,TpHandleSet * removed,gboolean is_initial_roster)2088 tp_base_contact_list_contacts_changed_internal (TpBaseContactList *self,
2089     TpHandleSet *changed,
2090     TpHandleSet *removed,
2091     gboolean is_initial_roster)
2092 {
2093   GHashTable *changes;
2094   GHashTable *change_ids;
2095   GArray *removals;
2096   GHashTable *removal_ids;
2097   TpIntsetFastIter iter;
2098   TpIntset *pub, *sub, *sub_rp, *unpub, *unsub, *store;
2099   GObject *sub_chan, *pub_chan, *stored_chan;
2100   TpHandle self_handle;
2101   TpHandle contact;
2102 
2103   g_return_if_fail (TP_IS_BASE_CONTACT_LIST (self));
2104 
2105   /* don't do anything if we're disconnecting, or if we haven't had the
2106    * initial contact list yet */
2107   if (tp_base_contact_list_get_state (self, NULL) !=
2108       TP_CONTACT_LIST_STATE_SUCCESS)
2109     return;
2110 
2111   self_handle = tp_base_connection_get_self_handle (self->priv->conn),
2112 
2113   sub_chan = (GObject *) self->priv->lists[TP_LIST_HANDLE_SUBSCRIBE];
2114   pub_chan = (GObject *) self->priv->lists[TP_LIST_HANDLE_PUBLISH];
2115   stored_chan = (GObject *) self->priv->lists[TP_LIST_HANDLE_STORED];
2116 
2117   g_return_if_fail (G_IS_OBJECT (sub_chan));
2118   g_return_if_fail (G_IS_OBJECT (pub_chan));
2119   /* stored_chan can legitimately be NULL, though */
2120 
2121   /* For some changes, we emit signals one by one, because the actor is
2122    * different every time. However, for these sets of changes, we do them all
2123    * at once, since they'll share an actor. */
2124   pub = tp_intset_new ();
2125   unpub = tp_intset_new ();
2126   unsub = tp_intset_new ();
2127   sub = tp_intset_new ();
2128   sub_rp = tp_intset_new ();
2129   store = tp_intset_new ();
2130 
2131   changes = g_hash_table_new_full (NULL, NULL, NULL,
2132       (GDestroyNotify) tp_value_array_free);
2133   change_ids = g_hash_table_new (NULL, NULL);
2134 
2135   if (changed != NULL)
2136     tp_intset_fast_iter_init (&iter, tp_handle_set_peek (changed));
2137 
2138   while (changed != NULL && tp_intset_fast_iter_next (&iter, &contact))
2139     {
2140       TpSubscriptionState subscribe = TP_SUBSCRIPTION_STATE_NO;
2141       TpSubscriptionState publish = TP_SUBSCRIPTION_STATE_NO;
2142       gchar *publish_request = NULL;
2143 
2144       tp_intset_add (store, contact);
2145 
2146       tp_base_contact_list_dup_states (self, contact,
2147           &subscribe, &publish, &publish_request);
2148 
2149       if (publish_request == NULL)
2150         publish_request = g_strdup ("");
2151 
2152       DEBUG ("Contact %s: subscribe=%c publish=%c '%s'",
2153           tp_handle_inspect (self->priv->contact_repo, contact),
2154           _tp_base_contact_list_presence_state_to_letter (subscribe),
2155           _tp_base_contact_list_presence_state_to_letter (publish),
2156           publish_request);
2157 
2158       switch (publish)
2159         {
2160         case TP_SUBSCRIPTION_STATE_NO:
2161         case TP_SUBSCRIPTION_STATE_UNKNOWN:
2162           tp_intset_add (unpub, contact);
2163           break;
2164 
2165         case TP_SUBSCRIPTION_STATE_ASK:
2166             {
2167               /* Emit any publication requests as we go along, since they can
2168                * each have a different message and actor */
2169               TpIntset *pub_lp = tp_intset_new_containing (contact);
2170 
2171               tp_group_mixin_change_members (pub_chan, publish_request,
2172                   NULL, NULL, pub_lp, NULL, contact,
2173                   TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
2174               tp_intset_destroy (pub_lp);
2175             }
2176           break;
2177 
2178         case TP_SUBSCRIPTION_STATE_REMOVED_REMOTELY:
2179             {
2180               /* Also emit publication request cancellations as we go along:
2181                * each one has a different actor */
2182               TpIntset *pub_cancelled = tp_intset_new_containing (
2183                   contact);
2184 
2185               tp_group_mixin_change_members (pub_chan, "",
2186                   NULL, pub_cancelled, NULL, NULL, contact,
2187                   TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
2188               tp_intset_destroy (pub_cancelled);
2189             }
2190           break;
2191 
2192         case TP_SUBSCRIPTION_STATE_YES:
2193           tp_intset_add (pub, contact);
2194           break;
2195 
2196         default:
2197           g_assert_not_reached ();
2198         }
2199 
2200       switch (subscribe)
2201         {
2202         case TP_SUBSCRIPTION_STATE_NO:
2203         case TP_SUBSCRIPTION_STATE_UNKNOWN:
2204           tp_intset_add (unsub, contact);
2205           break;
2206 
2207         case TP_SUBSCRIPTION_STATE_REMOVED_REMOTELY:
2208             {
2209               /* If our subscription request was rejected, the actor is the
2210                * other guy, and PERMISSION_DENIED seems a reasonable reason */
2211               TpIntset *sub_rejected = tp_intset_new_containing (contact);
2212 
2213               tp_group_mixin_change_members (sub_chan, "",
2214                   NULL, sub_rejected, NULL, NULL, contact,
2215                   TP_CHANNEL_GROUP_CHANGE_REASON_PERMISSION_DENIED);
2216               tp_intset_destroy (sub_rejected);
2217             }
2218           break;
2219 
2220         case TP_SUBSCRIPTION_STATE_ASK:
2221           tp_intset_add (sub_rp, contact);
2222           break;
2223 
2224         case TP_SUBSCRIPTION_STATE_YES:
2225           if (is_initial_roster)
2226             {
2227               tp_intset_add (sub, contact);
2228             }
2229           else
2230             {
2231               /* If our subscription request was accepted, the actor is the
2232                * other guy accepting */
2233               TpIntset *sub_approved = tp_intset_new_containing (contact);
2234 
2235               tp_group_mixin_change_members (sub_chan, "",
2236                   sub_approved, NULL, NULL, NULL, contact,
2237                   TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
2238               tp_intset_destroy (sub_approved);
2239             }
2240 
2241           break;
2242 
2243         default:
2244           g_assert_not_reached ();
2245         }
2246 
2247       g_hash_table_insert (changes, GUINT_TO_POINTER (contact),
2248           tp_value_array_build (3,
2249             G_TYPE_UINT, subscribe,
2250             G_TYPE_UINT, publish,
2251             G_TYPE_STRING, publish_request,
2252             G_TYPE_INVALID));
2253       g_free (publish_request);
2254 
2255       g_hash_table_insert (change_ids, GUINT_TO_POINTER (contact),
2256           (gchar *) tp_handle_inspect (self->priv->contact_repo, contact));
2257     }
2258 
2259   removal_ids = g_hash_table_new (NULL, NULL);
2260 
2261   if (removed != NULL)
2262     {
2263       guint i;
2264 
2265       tp_intset_union_update (unsub, tp_handle_set_peek (removed));
2266       tp_intset_union_update (unpub, tp_handle_set_peek (removed));
2267 
2268       removals = tp_handle_set_to_array (removed);
2269 
2270       for (i = 0; i < removals->len; i++)
2271         {
2272           TpHandle handle = g_array_index (removals, guint, i);
2273 
2274           g_hash_table_insert (removal_ids, GUINT_TO_POINTER (handle),
2275               (gchar *) tp_handle_inspect (self->priv->contact_repo, handle));
2276         }
2277     }
2278   else
2279     {
2280       removals = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), 0);
2281     }
2282 
2283   /* The actor is 0 for removals from subscribe and publish: we don't know
2284    * whether it was our idea, or caused by an unknown contact or by server
2285    * failure, since those are all represented as No. */
2286   tp_group_mixin_change_members (sub_chan, "",
2287       NULL, unsub, NULL, NULL, 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
2288   tp_group_mixin_change_members (pub_chan, "",
2289       NULL, unpub, NULL, NULL, 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
2290 
2291   /* pub is the set of contacts changing to publish=Yes (i.e. contacts we'll
2292    * allow to see our presence), which was presumably our idea. */
2293   tp_group_mixin_change_members (pub_chan, "",
2294       pub, NULL, NULL, NULL, self_handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
2295 
2296   /* sub is the set of contacts with subscribe=Yes while retrieving the
2297    * initial roster. We don't know if the contacts were already in the roster
2298    * or if they were added while we were offline, so the actor is 0.
2299    * Having all the initial contacts grouped together means we emit a single
2300    * MembersChanged and one MembersChangedDetailed for the whole roster. */
2301   tp_group_mixin_change_members (sub_chan, "", sub, NULL, NULL, NULL, 0,
2302       TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
2303 
2304   /* sub_rp is the set of contacts changing to subscribe=Ask, which was
2305    * presumably our idea. */
2306   tp_group_mixin_change_members (sub_chan, "", NULL, NULL, NULL, sub_rp,
2307       self_handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
2308 
2309   /* We use actor 0 for the stored list, since people can land on the stored
2310    * list for a variety of reasons (if someone has requested we publish to
2311    * them, they're temporarily claimed to be stored). */
2312   if (stored_chan != NULL)
2313     {
2314       tp_group_mixin_change_members (stored_chan, "",
2315           store,
2316           removed == NULL ? NULL : tp_handle_set_peek (removed),
2317           NULL, NULL,
2318           0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
2319     }
2320 
2321   if (g_hash_table_size (changes) > 0 || removals->len > 0)
2322     {
2323       DEBUG ("ContactsChanged([%u changed], [%u removed])",
2324           g_hash_table_size (changes), removals->len);
2325 
2326       if (self->priv->svc_contact_list)
2327         {
2328           tp_svc_connection_interface_contact_list_emit_contacts_changed_with_id (
2329               self->priv->conn, changes, change_ids, removal_ids);
2330           tp_svc_connection_interface_contact_list_emit_contacts_changed (
2331               self->priv->conn, changes, removals);
2332         }
2333     }
2334 
2335   tp_intset_destroy (pub);
2336   tp_intset_destroy (unpub);
2337   tp_intset_destroy (unsub);
2338   tp_intset_destroy (sub_rp);
2339   tp_intset_destroy (sub);
2340   tp_intset_destroy (store);
2341 
2342   g_hash_table_unref (changes);
2343   g_hash_table_unref (change_ids);
2344   g_hash_table_unref (removal_ids);
2345   g_array_unref (removals);
2346 }
2347 
2348 /**
2349  * tp_base_contact_list_one_contact_changed:
2350  * @self: the contact list manager
2351  * @changed: a contact handle
2352  *
2353  * Convenience wrapper around tp_base_contact_list_contacts_changed() for a
2354  * single handle in the 'changed' set and no 'removed' set.
2355  *
2356  * Since: 0.13.0
2357  */
2358 void
tp_base_contact_list_one_contact_changed(TpBaseContactList * self,TpHandle changed)2359 tp_base_contact_list_one_contact_changed (TpBaseContactList *self,
2360     TpHandle changed)
2361 {
2362   TpHandleSet *set;
2363 
2364   g_return_if_fail (TP_IS_BASE_CONTACT_LIST (self));
2365 
2366   /* if we're disconnecting, we might not have a handle repository any more:
2367    * tp_base_contact_list_contacts_changed does nothing in that situation */
2368   if (self->priv->contact_repo == NULL)
2369     return;
2370 
2371   set = tp_handle_set_new_containing (self->priv->contact_repo, changed);
2372   tp_base_contact_list_contacts_changed (self, set, NULL);
2373   tp_handle_set_destroy (set);
2374 }
2375 
2376 /**
2377  * tp_base_contact_list_one_contact_removed:
2378  * @self: the contact list manager
2379  * @removed: a contact handle
2380  *
2381  * Convenience wrapper around tp_base_contact_list_contacts_changed() for a
2382  * single handle in the 'removed' set and no 'changed' set.
2383  *
2384  * Since: 0.13.0
2385  */
2386 void
tp_base_contact_list_one_contact_removed(TpBaseContactList * self,TpHandle removed)2387 tp_base_contact_list_one_contact_removed (TpBaseContactList *self,
2388     TpHandle removed)
2389 {
2390   TpHandleSet *set;
2391 
2392   g_return_if_fail (TP_IS_BASE_CONTACT_LIST (self));
2393 
2394   /* if we're disconnecting, we might not have a handle repository any more:
2395    * tp_base_contact_list_contacts_changed does nothing in that situation */
2396   if (self->priv->contact_repo == NULL)
2397     return;
2398 
2399   set = tp_handle_set_new_containing (self->priv->contact_repo, removed);
2400   tp_base_contact_list_contacts_changed (self, NULL, set);
2401   tp_handle_set_destroy (set);
2402 }
2403 
2404 /**
2405  * tp_base_contact_list_contact_blocking_changed:
2406  * @self: the contact list manager
2407  * @changed: a set of contacts who were blocked or unblocked
2408  *
2409  * Emit signals for a change to the blocked contacts list.
2410  *
2411  * tp_base_contact_list_dup_blocked_contacts()
2412  * must already reflect the contacts' new statuses when entering this method
2413  * (in practice, this means that implementations must update their own cache
2414  * of contacts before calling this method).
2415  *
2416  * It is an error to call this method if tp_base_contact_list_can_block()
2417  * would return %FALSE.
2418  *
2419  * Since: 0.13.0
2420  */
2421 void
tp_base_contact_list_contact_blocking_changed(TpBaseContactList * self,TpHandleSet * changed)2422 tp_base_contact_list_contact_blocking_changed (TpBaseContactList *self,
2423     TpHandleSet *changed)
2424 {
2425   TpHandleSet *now_blocked;
2426   TpIntset *blocked, *unblocked;
2427   GHashTable *blocked_contacts, *unblocked_contacts;
2428   TpIntsetFastIter iter;
2429   GObject *deny_chan;
2430   TpHandle handle;
2431 
2432   g_return_if_fail (TP_IS_BASE_CONTACT_LIST (self));
2433   g_return_if_fail (changed != NULL);
2434 
2435   /* don't do anything if we're disconnecting, or if we haven't had the
2436    * initial contact list yet */
2437   if (tp_base_contact_list_get_state (self, NULL) !=
2438       TP_CONTACT_LIST_STATE_SUCCESS)
2439     return;
2440 
2441   g_return_if_fail (tp_base_contact_list_can_block (self));
2442 
2443   deny_chan = (GObject *) self->priv->lists[TP_LIST_HANDLE_DENY];
2444   g_return_if_fail (G_IS_OBJECT (deny_chan));
2445 
2446   now_blocked = tp_base_contact_list_dup_blocked_contacts (self);
2447 
2448   blocked = tp_intset_new ();
2449   unblocked = tp_intset_new ();
2450   blocked_contacts = g_hash_table_new (NULL, NULL);
2451   unblocked_contacts = g_hash_table_new (NULL, NULL);
2452 
2453   tp_intset_fast_iter_init (&iter, tp_handle_set_peek (changed));
2454 
2455   while (tp_intset_fast_iter_next (&iter, &handle))
2456     {
2457       const char *id = tp_handle_inspect (self->priv->contact_repo, handle);
2458 
2459       if (tp_handle_set_is_member (now_blocked, handle))
2460         {
2461           tp_intset_add (blocked, handle);
2462           g_hash_table_insert (blocked_contacts, GUINT_TO_POINTER (handle),
2463               (gpointer) id);
2464         }
2465       else
2466         {
2467           tp_intset_add (unblocked, handle);
2468           g_hash_table_insert (unblocked_contacts, GUINT_TO_POINTER (handle),
2469               (gpointer) id);
2470         }
2471 
2472       DEBUG ("Contact %s: blocked=%c", id,
2473           tp_handle_set_is_member (now_blocked, handle) ? 'Y' : 'N');
2474     }
2475 
2476   tp_group_mixin_change_members (deny_chan, "",
2477       blocked, unblocked, NULL, NULL,
2478       tp_base_connection_get_self_handle (self->priv->conn),
2479       TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
2480 
2481   if (self->priv->svc_contact_blocking &&
2482       (g_hash_table_size (blocked_contacts) > 0 ||
2483        g_hash_table_size (unblocked_contacts) > 0))
2484     tp_svc_connection_interface_contact_blocking_emit_blocked_contacts_changed (
2485         self->priv->conn, blocked_contacts, unblocked_contacts);
2486 
2487   tp_intset_destroy (blocked);
2488   tp_intset_destroy (unblocked);
2489   g_hash_table_unref (blocked_contacts);
2490   g_hash_table_unref (unblocked_contacts);
2491   tp_handle_set_destroy (now_blocked);
2492 }
2493 
2494 /**
2495  * tp_base_contact_list_dup_contacts:
2496  * @self: a contact list manager
2497  *
2498  * Return the contact list. It is incorrect to call this method before
2499  * tp_base_contact_list_set_list_received() has been called, or after the
2500  * connection has disconnected.
2501  *
2502  * This is a virtual method, implemented using
2503  * #TpBaseContactListClass.dup_contacts. Every subclass of #TpBaseContactList
2504  * must implement this method.
2505  *
2506  * If the contact list implements %TP_TYPE_BLOCKABLE_CONTACT_LIST, blocked
2507  * contacts should not appear in the result of this method unless they are
2508  * considered to be on the contact list for some other reason.
2509  *
2510  * Returns: (transfer full): a new #TpHandleSet of contact handles
2511  *
2512  * Since: 0.13.0
2513  */
2514 TpHandleSet *
tp_base_contact_list_dup_contacts(TpBaseContactList * self)2515 tp_base_contact_list_dup_contacts (TpBaseContactList *self)
2516 {
2517   TpBaseContactListClass *cls = TP_BASE_CONTACT_LIST_GET_CLASS (self);
2518 
2519   g_return_val_if_fail (cls != NULL, NULL);
2520   g_return_val_if_fail (cls->dup_contacts != NULL, NULL);
2521   g_return_val_if_fail (tp_base_contact_list_get_state (self, NULL) ==
2522       TP_CONTACT_LIST_STATE_SUCCESS, NULL);
2523 
2524   return cls->dup_contacts (self);
2525 }
2526 
2527 /**
2528  * tp_base_contact_list_request_subscription_async:
2529  * @self: a contact list manager
2530  * @contacts: the contacts whose subscription is to be requested
2531  * @message: an optional human-readable message from the user
2532  * @callback: a callback to call when the request for subscription succeeds
2533  *  or fails
2534  * @user_data: optional data to pass to @callback
2535  *
2536  * Request permission to see some contacts' presence.
2537  *
2538  * If the #TpBaseContactList subclass does not implement
2539  * %TP_TYPE_MUTABLE_CONTACT_LIST, it is an error to call this method.
2540  *
2541  * For implementations of %TP_TYPE_MUTABLE_CONTACT_LIST, this is a virtual
2542  * method which must be implemented, using
2543  * #TpMutableContactListInterface.request_subscription_async.
2544  * The implementation should call tp_base_contact_list_contacts_changed()
2545  * for any contacts it has changed, before it calls @callback.
2546  *
2547  * If @message will be ignored,
2548  * #TpMutableContactListInterface.get_request_uses_message should also be
2549  * reimplemented to return %FALSE.
2550  *
2551  * Since: 0.13.0
2552  */
2553 void
tp_base_contact_list_request_subscription_async(TpBaseContactList * self,TpHandleSet * contacts,const gchar * message,GAsyncReadyCallback callback,gpointer user_data)2554 tp_base_contact_list_request_subscription_async (TpBaseContactList *self,
2555     TpHandleSet *contacts,
2556     const gchar *message,
2557     GAsyncReadyCallback callback,
2558     gpointer user_data)
2559 {
2560   TpMutableContactListInterface *mutable_iface;
2561 
2562   mutable_iface = TP_MUTABLE_CONTACT_LIST_GET_INTERFACE (self);
2563   g_return_if_fail (mutable_iface != NULL);
2564   g_return_if_fail (mutable_iface->request_subscription_async != NULL);
2565 
2566   mutable_iface->request_subscription_async (self, contacts, message, callback,
2567       user_data);
2568 }
2569 
2570 /**
2571  * tp_base_contact_list_request_subscription_finish:
2572  * @self: a contact list manager
2573  * @result: the result passed to @callback by an implementation of
2574  *  tp_base_contact_list_request_subscription_async()
2575  * @error: used to raise an error if %FALSE is returned
2576  *
2577  * Interpret the result of an asynchronous call to
2578  * tp_base_contact_list_request_subscription_async().
2579  *
2580  * If the #TpBaseContactList subclass does not implement
2581  * %TP_TYPE_MUTABLE_CONTACT_LIST, it is an error to call this method.
2582  *
2583  * For implementations of %TP_TYPE_MUTABLE_CONTACT_LIST, this is a virtual
2584  * method which may be implemented using
2585  * #TpMutableContactListInterface.request_subscription_finish. If the @result
2586  * will be a #GSimpleAsyncResult, the default implementation may be used.
2587  *
2588  * Returns: %TRUE on success or %FALSE on error
2589  *
2590  * Since: 0.13.0
2591  */
2592 gboolean
tp_base_contact_list_request_subscription_finish(TpBaseContactList * self,GAsyncResult * result,GError ** error)2593 tp_base_contact_list_request_subscription_finish (TpBaseContactList *self,
2594     GAsyncResult *result,
2595     GError **error)
2596 {
2597   TpMutableContactListInterface *mutable_iface;
2598 
2599   mutable_iface = TP_MUTABLE_CONTACT_LIST_GET_INTERFACE (self);
2600   g_return_val_if_fail (mutable_iface != NULL, FALSE);
2601   g_return_val_if_fail (mutable_iface->request_subscription_finish != NULL,
2602       FALSE);
2603 
2604   return mutable_iface->request_subscription_finish (self, result, error);
2605 }
2606 
2607 /**
2608  * tp_base_contact_list_dup_states:
2609  * @self: a contact list manager
2610  * @contact: the contact
2611  * @subscribe: (out) (allow-none): used to return the state of the user's
2612  *  subscription to @contact's presence
2613  * @publish: (out) (allow-none): used to return the state of @contact's
2614  *  subscription to the user's presence
2615  * @publish_request: (out) (allow-none) (transfer full): if @publish will be
2616  *  set to %TP_SUBSCRIPTION_STATE_ASK, used to return the message that
2617  *  @contact sent when they requested permission to see the user's presence;
2618  *  otherwise, used to return an empty string
2619  *
2620  * Return the presence subscription state of @contact. It is incorrect to call
2621  * this method before tp_base_contact_list_set_list_received() has been
2622  * called, or after the connection has disconnected.
2623  *
2624  * This is a virtual method, implemented using
2625  * #TpBaseContactListClass.dup_states. Every subclass of #TpBaseContactList
2626  * must implement this method.
2627  *
2628  * Since: 0.13.0
2629  */
2630 void
tp_base_contact_list_dup_states(TpBaseContactList * self,TpHandle contact,TpSubscriptionState * subscribe,TpSubscriptionState * publish,gchar ** publish_request)2631 tp_base_contact_list_dup_states (TpBaseContactList *self,
2632     TpHandle contact,
2633     TpSubscriptionState *subscribe,
2634     TpSubscriptionState *publish,
2635     gchar **publish_request)
2636 {
2637   TpBaseContactListClass *cls = TP_BASE_CONTACT_LIST_GET_CLASS (self);
2638 
2639   g_return_if_fail (cls != NULL);
2640   g_return_if_fail (cls->dup_states != NULL);
2641   g_return_if_fail (tp_base_contact_list_get_state (self, NULL) ==
2642       TP_CONTACT_LIST_STATE_SUCCESS);
2643 
2644   cls->dup_states (self, contact, subscribe, publish, publish_request);
2645 
2646   if (publish_request != NULL && *publish_request == NULL)
2647     *publish_request = g_strdup ("");
2648 }
2649 
2650 /**
2651  * tp_base_contact_list_authorize_publication_async:
2652  * @self: a contact list manager
2653  * @contacts: the contacts to whom presence will be published
2654  * @callback: a callback to call when the authorization succeeds or fails
2655  * @user_data: optional data to pass to @callback
2656  *
2657  * Give permission for some contacts to see the local user's presence.
2658  *
2659  * If the #TpBaseContactList subclass does not implement
2660  * %TP_TYPE_MUTABLE_CONTACT_LIST, it is an error to call this method.
2661  *
2662  * For implementations of %TP_TYPE_MUTABLE_CONTACT_LIST, this is a virtual
2663  * method which must be implemented, using
2664  * #TpMutableContactListInterface.authorize_publication_async.
2665  * The implementation should call tp_base_contact_list_contacts_changed()
2666  * for any contacts it has changed, before it calls @callback.
2667  *
2668  * Since: 0.13.0
2669  */
2670 void
tp_base_contact_list_authorize_publication_async(TpBaseContactList * self,TpHandleSet * contacts,GAsyncReadyCallback callback,gpointer user_data)2671 tp_base_contact_list_authorize_publication_async (TpBaseContactList *self,
2672     TpHandleSet *contacts,
2673     GAsyncReadyCallback callback,
2674     gpointer user_data)
2675 {
2676   TpMutableContactListInterface *mutable_iface;
2677 
2678   mutable_iface = TP_MUTABLE_CONTACT_LIST_GET_INTERFACE (self);
2679   g_return_if_fail (mutable_iface != NULL);
2680   g_return_if_fail (mutable_iface->authorize_publication_async != NULL);
2681 
2682   mutable_iface->authorize_publication_async (self, contacts, callback,
2683       user_data);
2684 }
2685 
2686 /**
2687  * tp_base_contact_list_authorize_publication_finish:
2688  * @self: a contact list manager
2689  * @result: the result passed to @callback by an implementation of
2690  *  tp_base_contact_list_authorize_publication_async()
2691  * @error: used to raise an error if %FALSE is returned
2692  *
2693  * Interpret the result of an asynchronous call to
2694  * tp_base_contact_list_authorize_publication_async().
2695  *
2696  * If the #TpBaseContactList subclass does not implement
2697  * %TP_TYPE_MUTABLE_CONTACT_LIST, it is an error to call this method.
2698  *
2699  * For implementations of %TP_TYPE_MUTABLE_CONTACT_LIST, this is a virtual
2700  * method which may be implemented using
2701  * #TpMutableContactListInterface.authorize_publication_finish. If the @result
2702  * will be a #GSimpleAsyncResult, the default implementation may be used.
2703  *
2704  * Returns: %TRUE on success or %FALSE on error
2705  *
2706  * Since: 0.13.0
2707  */
2708 gboolean
tp_base_contact_list_authorize_publication_finish(TpBaseContactList * self,GAsyncResult * result,GError ** error)2709 tp_base_contact_list_authorize_publication_finish (TpBaseContactList *self,
2710     GAsyncResult *result,
2711     GError **error)
2712 {
2713   TpMutableContactListInterface *mutable_iface;
2714 
2715   mutable_iface = TP_MUTABLE_CONTACT_LIST_GET_INTERFACE (self);
2716   g_return_val_if_fail (mutable_iface != NULL, FALSE);
2717   g_return_val_if_fail (mutable_iface->authorize_publication_finish != NULL,
2718       FALSE);
2719 
2720   return mutable_iface->authorize_publication_finish (self, result, error);
2721 }
2722 
2723 /**
2724  * tp_base_contact_list_store_contacts_async:
2725  * @self: a contact list manager
2726  * @contacts: the contacts to be stored
2727  * @callback: a callback to call when the operation succeeds or fails
2728  * @user_data: optional data to pass to @callback
2729  *
2730  * Store @contacts on the contact list, without attempting to subscribe to
2731  * them or send presence to them. If this is not possible, do nothing.
2732  *
2733  * If the #TpBaseContactList subclass does not implement
2734  * %TP_TYPE_MUTABLE_CONTACT_LIST, it is an error to call this method.
2735  *
2736  * For implementations of %TP_TYPE_MUTABLE_CONTACT_LIST, this is a virtual
2737  * method, which may be implemented using
2738  * #TpMutableContactListInterface.store_contacts_async.
2739  * The implementation should call tp_base_contact_list_contacts_changed()
2740  * for any contacts it has changed, before calling @callback.
2741  *
2742  * If the implementation of
2743  * #TpMutableContactListInterface.store_contacts_async is %NULL (which is
2744  * the default), this method calls @callback to signal success, but does
2745  * nothing in the underlying protocol.
2746  *
2747  * Since: 0.13.0
2748  */
2749 void
tp_base_contact_list_store_contacts_async(TpBaseContactList * self,TpHandleSet * contacts,GAsyncReadyCallback callback,gpointer user_data)2750 tp_base_contact_list_store_contacts_async (TpBaseContactList *self,
2751     TpHandleSet *contacts,
2752     GAsyncReadyCallback callback,
2753     gpointer user_data)
2754 {
2755   TpMutableContactListInterface *mutable_iface;
2756 
2757   mutable_iface = TP_MUTABLE_CONTACT_LIST_GET_INTERFACE (self);
2758   g_return_if_fail (mutable_iface != NULL);
2759 
2760   if (mutable_iface->store_contacts_async == NULL)
2761     tp_simple_async_report_success_in_idle ((GObject *) self,
2762         callback, user_data, NULL);
2763   else
2764     mutable_iface->store_contacts_async (self, contacts, callback,
2765         user_data);
2766 }
2767 
2768 /**
2769  * tp_base_contact_list_store_contacts_finish:
2770  * @self: a contact list manager
2771  * @result: the result passed to @callback by an implementation of
2772  *  tp_base_contact_list_store_contacts_async()
2773  * @error: used to raise an error if %FALSE is returned
2774  *
2775  * Interpret the result of an asynchronous call to
2776  * tp_base_contact_list_store_contacts_async().
2777  *
2778  * If the #TpBaseContactList subclass does not implement
2779  * %TP_TYPE_MUTABLE_CONTACT_LIST, it is an error to call this method.
2780  *
2781  * For implementations of %TP_TYPE_MUTABLE_CONTACT_LIST, this is a virtual
2782  * method which may be implemented using
2783  * #TpMutableContactListInterface.store_contacts_finish. If the @result
2784  * will be a #GSimpleAsyncResult, the default implementation may be used.
2785  *
2786  * Returns: %TRUE on success or %FALSE on error
2787  *
2788  * Since: 0.13.0
2789  */
2790 gboolean
tp_base_contact_list_store_contacts_finish(TpBaseContactList * self,GAsyncResult * result,GError ** error)2791 tp_base_contact_list_store_contacts_finish (TpBaseContactList *self,
2792     GAsyncResult *result,
2793     GError **error)
2794 {
2795   TpMutableContactListInterface *mutable_iface;
2796 
2797   mutable_iface = TP_MUTABLE_CONTACT_LIST_GET_INTERFACE (self);
2798   g_return_val_if_fail (mutable_iface != NULL, FALSE);
2799   g_return_val_if_fail (mutable_iface->store_contacts_finish != NULL, FALSE);
2800 
2801   return mutable_iface->store_contacts_finish (self, result, error);
2802 }
2803 
2804 /**
2805  * tp_base_contact_list_remove_contacts_async:
2806  * @self: a contact list manager
2807  * @contacts: the contacts to be removed
2808  * @callback: a callback to call when the operation succeeds or fails
2809  * @user_data: optional data to pass to @callback
2810  *
2811  * Remove @contacts from the contact list entirely; this includes the
2812  * effect of both tp_base_contact_list_unsubscribe_async() and
2813  * tp_base_contact_list_unpublish_async(), and also reverses the effect of
2814  * tp_base_contact_list_store_contacts_async().
2815  *
2816  * If the #TpBaseContactList subclass does not implement
2817  * %TP_TYPE_MUTABLE_CONTACT_LIST, this method does nothing.
2818  *
2819  * For implementations of %TP_TYPE_MUTABLE_CONTACT_LIST, this is a virtual
2820  * method which must be implemented, using
2821  * #TpMutableContactListInterface.remove_contacts_async.
2822  * The implementation should call tp_base_contact_list_contacts_changed()
2823  * for any contacts it has changed, before calling @callback.
2824  *
2825  * Since: 0.13.0
2826  */
2827 void
tp_base_contact_list_remove_contacts_async(TpBaseContactList * self,TpHandleSet * contacts,GAsyncReadyCallback callback,gpointer user_data)2828 tp_base_contact_list_remove_contacts_async (TpBaseContactList *self,
2829     TpHandleSet *contacts,
2830     GAsyncReadyCallback callback,
2831     gpointer user_data)
2832 {
2833   TpMutableContactListInterface *mutable_iface;
2834 
2835   mutable_iface = TP_MUTABLE_CONTACT_LIST_GET_INTERFACE (self);
2836   g_return_if_fail (mutable_iface != NULL);
2837   g_return_if_fail (mutable_iface->remove_contacts_async != NULL);
2838 
2839   mutable_iface->remove_contacts_async (self, contacts, callback, user_data);
2840 }
2841 
2842 /**
2843  * tp_base_contact_list_remove_contacts_finish:
2844  * @self: a contact list manager
2845  * @result: the result passed to @callback by an implementation of
2846  *  tp_base_contact_list_remove_contacts_async()
2847  * @error: used to raise an error if %FALSE is returned
2848  *
2849  * Interpret the result of an asynchronous call to
2850  * tp_base_contact_list_remove_contacts_async().
2851  *
2852  * If the #TpBaseContactList subclass does not implement
2853  * %TP_TYPE_MUTABLE_CONTACT_LIST, it is an error to call this method.
2854  *
2855  * For implementations of %TP_TYPE_MUTABLE_CONTACT_LIST, this is a virtual
2856  * method which may be implemented using
2857  * #TpMutableContactListInterface.remove_contacts_finish. If the @result
2858  * will be a #GSimpleAsyncResult, the default implementation may be used.
2859  *
2860  * Returns: %TRUE on success or %FALSE on error
2861  *
2862  * Since: 0.13.0
2863  */
2864 gboolean
tp_base_contact_list_remove_contacts_finish(TpBaseContactList * self,GAsyncResult * result,GError ** error)2865 tp_base_contact_list_remove_contacts_finish (TpBaseContactList *self,
2866     GAsyncResult *result,
2867     GError **error)
2868 {
2869   TpMutableContactListInterface *mutable_iface;
2870 
2871   mutable_iface = TP_MUTABLE_CONTACT_LIST_GET_INTERFACE (self);
2872   g_return_val_if_fail (mutable_iface != NULL, FALSE);
2873   g_return_val_if_fail (mutable_iface->remove_contacts_finish != NULL, FALSE);
2874 
2875   return mutable_iface->remove_contacts_finish (self, result, error);
2876 }
2877 
2878 /**
2879  * tp_base_contact_list_unsubscribe_async:
2880  * @self: a contact list manager
2881  * @contacts: the contacts whose presence will no longer be received
2882  * @callback: a callback to call when the operation succeeds or fails
2883  * @user_data: optional data to pass to @callback
2884  *
2885  * Cancel a pending subscription request to @contacts, or attempt to stop
2886  * receiving their presence.
2887  *
2888  * If the #TpBaseContactList subclass does not implement
2889  * %TP_TYPE_MUTABLE_CONTACT_LIST, it is an error to call this method.
2890  *
2891  * For implementations of %TP_TYPE_MUTABLE_CONTACT_LIST, this is a virtual
2892  * method which must be implemented, using
2893  * #TpMutableContactListInterface.unsubscribe_async.
2894  * The implementation should call tp_base_contact_list_contacts_changed()
2895  * for any contacts it has changed, before calling @callback.
2896  *
2897  * Since: 0.13.0
2898  */
2899 void
tp_base_contact_list_unsubscribe_async(TpBaseContactList * self,TpHandleSet * contacts,GAsyncReadyCallback callback,gpointer user_data)2900 tp_base_contact_list_unsubscribe_async (TpBaseContactList *self,
2901     TpHandleSet *contacts,
2902     GAsyncReadyCallback callback,
2903     gpointer user_data)
2904 {
2905   TpMutableContactListInterface *mutable_iface;
2906 
2907   mutable_iface = TP_MUTABLE_CONTACT_LIST_GET_INTERFACE (self);
2908   g_return_if_fail (mutable_iface != NULL);
2909   g_return_if_fail (mutable_iface->unsubscribe_async != NULL);
2910 
2911   mutable_iface->unsubscribe_async (self, contacts, callback, user_data);
2912 }
2913 
2914 /**
2915  * tp_base_contact_list_unsubscribe_finish:
2916  * @self: a contact list manager
2917  * @result: the result passed to @callback by an implementation of
2918  *  tp_base_contact_list_unsubscribe_async()
2919  * @error: used to raise an error if %FALSE is returned
2920  *
2921  * Interpret the result of an asynchronous call to
2922  * tp_base_contact_list_unsubscribe_async().
2923  *
2924  * If the #TpBaseContactList subclass does not implement
2925  * %TP_TYPE_MUTABLE_CONTACT_LIST, it is an error to call this method.
2926  *
2927  * For implementations of %TP_TYPE_MUTABLE_CONTACT_LIST, this is a virtual
2928  * method which may be implemented using
2929  * #TpMutableContactListInterface.unsubscribe_finish. If the @result
2930  * will be a #GSimpleAsyncResult, the default implementation may be used.
2931  *
2932  * Returns: %TRUE on success or %FALSE on error
2933  *
2934  * Since: 0.13.0
2935  */
2936 gboolean
tp_base_contact_list_unsubscribe_finish(TpBaseContactList * self,GAsyncResult * result,GError ** error)2937 tp_base_contact_list_unsubscribe_finish (TpBaseContactList *self,
2938     GAsyncResult *result,
2939     GError **error)
2940 {
2941   TpMutableContactListInterface *mutable_iface;
2942 
2943   mutable_iface = TP_MUTABLE_CONTACT_LIST_GET_INTERFACE (self);
2944   g_return_val_if_fail (mutable_iface != NULL, FALSE);
2945   g_return_val_if_fail (mutable_iface->unsubscribe_finish != NULL, FALSE);
2946 
2947   return mutable_iface->unsubscribe_finish (self, result, error);
2948 }
2949 
2950 /**
2951  * tp_base_contact_list_unpublish_async:
2952  * @self: a contact list manager
2953  * @contacts: the contacts to whom presence will no longer be published
2954  * @callback: a callback to call when the operation succeeds or fails
2955  * @user_data: optional data to pass to @callback
2956  *
2957  * Reject a pending subscription request from @contacts, or attempt to stop
2958  * sending presence to them.
2959  *
2960  * If the #TpBaseContactList subclass does not implement
2961  * %TP_TYPE_MUTABLE_CONTACT_LIST, it is an error to call this method.
2962  *
2963  * For implementations of %TP_TYPE_MUTABLE_CONTACT_LIST, this is a virtual
2964  * method which must be implemented, using
2965  * #TpMutableContactListInterface.unpublish_async.
2966  * The implementation should call tp_base_contact_list_contacts_changed()
2967  * for any contacts it has changed, before calling @callback.
2968  *
2969  * Since: 0.13.0
2970  */
2971 void
tp_base_contact_list_unpublish_async(TpBaseContactList * self,TpHandleSet * contacts,GAsyncReadyCallback callback,gpointer user_data)2972 tp_base_contact_list_unpublish_async (TpBaseContactList *self,
2973     TpHandleSet *contacts,
2974     GAsyncReadyCallback callback,
2975     gpointer user_data)
2976 {
2977   TpMutableContactListInterface *mutable_iface;
2978 
2979   mutable_iface = TP_MUTABLE_CONTACT_LIST_GET_INTERFACE (self);
2980   g_return_if_fail (mutable_iface != NULL);
2981   g_return_if_fail (mutable_iface->unpublish_async != NULL);
2982 
2983   mutable_iface->unpublish_async (self, contacts, callback, user_data);
2984 }
2985 
2986 /**
2987  * tp_base_contact_list_unpublish_finish:
2988  * @self: a contact list manager
2989  * @result: the result passed to @callback by an implementation of
2990  *  tp_base_contact_list_unpublish_async()
2991  * @error: used to raise an error if %FALSE is returned
2992  *
2993  * Interpret the result of an asynchronous call to
2994  * tp_base_contact_list_unpublish_async().
2995  *
2996  * If the #TpBaseContactList subclass does not implement
2997  * %TP_TYPE_MUTABLE_CONTACT_LIST, it is an error to call this method.
2998  *
2999  * For implementations of %TP_TYPE_MUTABLE_CONTACT_LIST, this is a virtual
3000  * method which may be implemented using
3001  * #TpMutableContactListInterface.unpublish_finish. If the @result
3002  * will be a #GSimpleAsyncResult, the default implementation may be used.
3003  *
3004  * Returns: %TRUE on success or %FALSE on error
3005  *
3006  * Since: 0.13.0
3007  */
3008 gboolean
tp_base_contact_list_unpublish_finish(TpBaseContactList * self,GAsyncResult * result,GError ** error)3009 tp_base_contact_list_unpublish_finish (TpBaseContactList *self,
3010     GAsyncResult *result,
3011     GError **error)
3012 {
3013   TpMutableContactListInterface *mutable_iface;
3014 
3015   mutable_iface = TP_MUTABLE_CONTACT_LIST_GET_INTERFACE (self);
3016   g_return_val_if_fail (mutable_iface != NULL, FALSE);
3017   g_return_val_if_fail (mutable_iface->unpublish_finish != NULL, FALSE);
3018 
3019   return mutable_iface->unpublish_finish (self, result, error);
3020 }
3021 
3022 /**
3023  * TpBaseContactListBooleanFunc:
3024  * @self: a contact list manager
3025  *
3026  * Signature of a virtual method that returns a boolean result. These are used
3027  * for feature-discovery.
3028  *
3029  * For the simple cases of a constant result, use
3030  * tp_base_contact_list_true_func() or tp_base_contact_list_false_func().
3031  *
3032  * Returns: a boolean result
3033  *
3034  * Since: 0.13.0
3035  */
3036 
3037 /**
3038  * tp_base_contact_list_true_func:
3039  * @self: ignored
3040  *
3041  * An implementation of #TpBaseContactListBooleanFunc that returns %TRUE,
3042  * for use in simple cases.
3043  *
3044  * Returns: %TRUE
3045  *
3046  * Since: 0.13.0
3047  */
3048 gboolean
tp_base_contact_list_true_func(TpBaseContactList * self G_GNUC_UNUSED)3049 tp_base_contact_list_true_func (TpBaseContactList *self G_GNUC_UNUSED)
3050 {
3051   return TRUE;
3052 }
3053 
3054 /**
3055  * tp_base_contact_list_false_func:
3056  * @self: ignored
3057  *
3058  * An implementation of #TpBaseContactListBooleanFunc that returns %FALSE,
3059  * for use in simple cases.
3060  *
3061  * Returns: %FALSE
3062  *
3063  * Since: 0.13.0
3064  */
3065 gboolean
tp_base_contact_list_false_func(TpBaseContactList * self G_GNUC_UNUSED)3066 tp_base_contact_list_false_func (TpBaseContactList *self G_GNUC_UNUSED)
3067 {
3068   return FALSE;
3069 }
3070 
3071 /**
3072  * tp_base_contact_list_can_change_contact_list:
3073  * @self: a contact list manager
3074  *
3075  * Return whether the contact list can be changed.
3076  *
3077  * If the #TpBaseContactList subclass does not implement
3078  * %TP_TYPE_MUTABLE_CONTACT_LIST, this method always returns %FALSE.
3079  *
3080  * For implementations of %TP_TYPE_MUTABLE_CONTACT_LIST this is a virtual
3081  * method, implemented using
3082  * #TpMutableContactListInterface.can_change_contact_list.
3083  * The default implementation always returns %TRUE.
3084  *
3085  * In the rare case of a protocol where subscriptions can only sometimes be
3086  * changed and this is detected while connecting, the #TpBaseContactList
3087  * subclass should implement %TP_TYPE_MUTABLE_CONTACT_LIST.
3088  * #TpMutableContactListInterface.can_change_contact_list to its own
3089  * implementation, whose result must remain constant after the
3090  * #TpBaseConnection has moved to state %TP_CONNECTION_STATUS_CONNECTED.
3091  *
3092  * (For instance, this could be useful for XMPP, where subscriptions can
3093  * normally be altered, but on connections to Facebook Chat servers this is
3094  * not actually supported.)
3095  *
3096  * Returns: %TRUE if the contact list can be changed
3097  *
3098  * Since: 0.13.0
3099  */
3100 gboolean
tp_base_contact_list_can_change_contact_list(TpBaseContactList * self)3101 tp_base_contact_list_can_change_contact_list (TpBaseContactList *self)
3102 {
3103   TpMutableContactListInterface *iface;
3104 
3105   g_return_val_if_fail (TP_IS_BASE_CONTACT_LIST (self), FALSE);
3106 
3107   if (!TP_IS_MUTABLE_CONTACT_LIST (self))
3108     return FALSE;
3109 
3110   iface = TP_MUTABLE_CONTACT_LIST_GET_INTERFACE (self);
3111   g_return_val_if_fail (iface != NULL, FALSE);
3112   g_return_val_if_fail (iface->can_change_contact_list != NULL, FALSE);
3113 
3114   return iface->can_change_contact_list (self);
3115 }
3116 
3117 /**
3118  * tp_base_contact_list_get_contact_list_persists:
3119  * @self: a contact list manager
3120  *
3121  * Return whether subscriptions on this protocol persist between sessions
3122  * (i.e. are stored on the server).
3123  *
3124  * This is a virtual method, implemented using
3125  * #TpBaseContactListClass.get_contact_list_persists.
3126  *
3127  * The default implementation is tp_base_contact_list_true_func(), which is
3128  * correct for most protocols. Protocols where the contact list isn't stored
3129  * should use tp_base_contact_list_false_func() as their implementation.
3130  *
3131  * In the rare case of a protocol where subscriptions sometimes persist
3132  * and this is detected while connecting, the subclass can implement another
3133  * #TpBaseContactListBooleanFunc (whose result must remain constant
3134  * after the #TpBaseConnection has moved to state
3135  * %TP_CONNECTION_STATUS_CONNECTED), and use that as the implementation.
3136  *
3137  * Returns: %TRUE if subscriptions persist
3138  *
3139  * Since: 0.13.0
3140  */
3141 gboolean
tp_base_contact_list_get_contact_list_persists(TpBaseContactList * self)3142 tp_base_contact_list_get_contact_list_persists (TpBaseContactList *self)
3143 {
3144   TpBaseContactListClass *cls = TP_BASE_CONTACT_LIST_GET_CLASS (self);
3145 
3146   g_return_val_if_fail (cls != NULL, TRUE);
3147   g_return_val_if_fail (cls->get_contact_list_persists != NULL, TRUE);
3148 
3149   return cls->get_contact_list_persists (self);
3150 }
3151 
3152 /**
3153  * tp_base_contact_list_get_download_at_connection:
3154  * @self: a contact list manager
3155  *
3156  * This function returns the
3157  * #TpBaseContactList:download-at-connection property.
3158  *
3159  * Returns: the #TpBaseContactList:download-at-connection property
3160  *
3161  * Since: 0.18.0
3162  */
3163 gboolean
tp_base_contact_list_get_download_at_connection(TpBaseContactList * self)3164 tp_base_contact_list_get_download_at_connection (TpBaseContactList *self)
3165 {
3166   return self->priv->download_at_connection;
3167 }
3168 
3169 /**
3170  * tp_base_contact_list_download_async:
3171  * @self: a contact list manager
3172  * @callback: a callback to call when the operation succeeds or fails
3173  * @user_data: optional data to pass to @callback
3174  *
3175  * Download the contact list when it is not done automatically at
3176  * connection.
3177  *
3178  * If the #TpBaseContactList subclass does not override
3179  * download_async, the default implementation will raise
3180  * TP_ERROR_NOT_IMPLEMENTED asynchronously.
3181  *
3182  * Since: 0.18.0
3183  */
3184 void
tp_base_contact_list_download_async(TpBaseContactList * self,GAsyncReadyCallback callback,gpointer user_data)3185 tp_base_contact_list_download_async (TpBaseContactList *self,
3186     GAsyncReadyCallback callback,
3187     gpointer user_data)
3188 {
3189   TpBaseContactListClass *cls = TP_BASE_CONTACT_LIST_GET_CLASS (self);
3190 
3191   g_return_if_fail (cls != NULL);
3192   g_return_if_fail (cls->download_async != NULL);
3193 
3194   return cls->download_async (self, callback, user_data);
3195 }
3196 
3197 /**
3198  * tp_base_contact_list_download_finish:
3199  * @self: a contact list manager
3200  * @result: the result passed to @callback by an implementation of
3201  *  tp_base_contact_list_download_async()
3202  * @error: used to raise an error if %FALSE is returned
3203  *
3204  * Interpret the result of an asynchronous call to
3205  * tp_base_contact_list_download_async().
3206  *
3207  * This is a virtual method which may be implemented using
3208  * #TpBaseContactListClass.download_finish. If the @result
3209  * will be a #GSimpleAsyncResult, the default implementation may be used.
3210  *
3211  * Returns: %TRUE on success or %FALSE on error
3212  *
3213  * Since: 0.18.0
3214  */
3215 gboolean
tp_base_contact_list_download_finish(TpBaseContactList * self,GAsyncResult * result,GError ** error)3216 tp_base_contact_list_download_finish (TpBaseContactList *self,
3217     GAsyncResult *result,
3218     GError **error)
3219 {
3220   TpBaseContactListClass *cls = TP_BASE_CONTACT_LIST_GET_CLASS (self);
3221 
3222   g_return_val_if_fail (cls != NULL, FALSE);
3223   g_return_val_if_fail (cls->download_finish != NULL, FALSE);
3224 
3225   return cls->download_finish (self, result, error);
3226 }
3227 
3228 /**
3229  * tp_base_contact_list_get_request_uses_message:
3230  * @self: a contact list manager
3231  *
3232  * Return whether the tp_base_contact_list_request_subscription_async()
3233  * method's @message argument is actually used.
3234  *
3235  * If the #TpBaseContactList subclass does not implement
3236  * %TP_TYPE_MUTABLE_CONTACT_LIST, this method is meaningless, and always
3237  * returns %FALSE.
3238  *
3239  * For implementations of %TP_TYPE_MUTABLE_CONTACT_LIST, this is a virtual
3240  * method, implemented using
3241  * #TpMutableContactListInterface.get_request_uses_message.
3242  * The default implementation always returns %TRUE, which is correct for most
3243  * protocols; subclasses may reimplement this method with
3244  * tp_base_contact_list_false_func() or a custom implementation if desired.
3245  *
3246  * Returns: %TRUE if tp_base_contact_list_request_subscription_async() will not
3247  *  ignore its @message argument
3248  *
3249  * Since: 0.13.0
3250  */
3251 gboolean
tp_base_contact_list_get_request_uses_message(TpBaseContactList * self)3252 tp_base_contact_list_get_request_uses_message (TpBaseContactList *self)
3253 {
3254   TpMutableContactListInterface *iface;
3255 
3256   g_return_val_if_fail (TP_IS_BASE_CONTACT_LIST (self), FALSE);
3257 
3258   if (!TP_IS_MUTABLE_CONTACT_LIST (self))
3259     return FALSE;
3260 
3261   iface = TP_MUTABLE_CONTACT_LIST_GET_INTERFACE (self);
3262   g_return_val_if_fail (iface != NULL, FALSE);
3263   g_return_val_if_fail (iface->get_request_uses_message != NULL, FALSE);
3264 
3265   return iface->get_request_uses_message (self);
3266 }
3267 
3268 /**
3269  * TpBaseContactListBlockContactsWithAbuseFunc:
3270  * @self: the contact list manager
3271  * @contacts: the contacts to block
3272  * @report_abusive: whether to report the contacts as abusive to the server
3273  *  operator
3274  * @callback: a callback to call on success, failure or disconnection
3275  * @user_data: user data for the callback
3276  *
3277  * Signature of a virtual method that blocks a set of contacts, optionally
3278  * reporting them to the server operator as abusive.
3279  *
3280  * Since: 0.15.1
3281  */
3282 
3283 /**
3284  * tp_base_contact_list_can_block:
3285  * @self: a contact list manager
3286  *
3287  * Return whether this contact list has a list of blocked contacts. If it
3288  * does, that list is assumed to be modifiable.
3289  *
3290  * If the #TpBaseContactList subclass does not implement
3291  * %TP_TYPE_BLOCKABLE_CONTACT_LIST, this method always returns %FALSE.
3292  *
3293  * For implementations of %TP_TYPE_BLOCKABLE_CONTACT_LIST, this is a virtual
3294  * method, implemented using #TpBlockableContactListInterface.can_block.
3295  * The default implementation always returns %TRUE.
3296  *
3297  * In the case of a protocol where blocking may or may not work
3298  * and this is detected while connecting, the subclass can implement another
3299  * #TpBaseContactListBooleanFunc (whose result must remain constant
3300  * after the #TpBaseConnection has moved to state
3301  * %TP_CONNECTION_STATUS_CONNECTED), and use that as the implementation.
3302  *
3303  * (For instance, this could be useful for XMPP, where support for contact
3304  * blocking is server-dependent: telepathy-gabble 0.8.x implements it for
3305  * connections to Google Talk servers, but not for any other server.)
3306  *
3307  * Returns: %TRUE if communication from contacts can be blocked
3308  *
3309  * Since: 0.13.0
3310  */
3311 gboolean
tp_base_contact_list_can_block(TpBaseContactList * self)3312 tp_base_contact_list_can_block (TpBaseContactList *self)
3313 {
3314   TpBlockableContactListInterface *iface;
3315 
3316   g_return_val_if_fail (TP_IS_BASE_CONTACT_LIST (self), FALSE);
3317 
3318   if (!TP_IS_BLOCKABLE_CONTACT_LIST (self))
3319     return FALSE;
3320 
3321   iface = TP_BLOCKABLE_CONTACT_LIST_GET_INTERFACE (self);
3322   g_return_val_if_fail (iface != NULL, FALSE);
3323   g_return_val_if_fail (iface->can_block != NULL, FALSE);
3324 
3325   return iface->can_block (self);
3326 }
3327 
3328 /**
3329  * tp_base_contact_list_dup_blocked_contacts:
3330  * @self: a contact list manager
3331  *
3332  * Return the list of blocked contacts. It is incorrect to call this method
3333  * before tp_base_contact_list_set_list_received() has been called, after
3334  * the connection has disconnected, or on a #TpBaseContactList that does
3335  * not implement %TP_TYPE_BLOCKABLE_CONTACT_LIST.
3336  *
3337  * For implementations of %TP_TYPE_BLOCKABLE_CONTACT_LIST, this is a virtual
3338  * method, implemented using
3339  * #TpBlockableContactListInterface.dup_blocked_contacts.
3340  * It must always be implemented.
3341  *
3342  * Returns: (transfer full): a new #TpHandleSet of contact handles
3343  *
3344  * Since: 0.13.0
3345  */
3346 TpHandleSet *
tp_base_contact_list_dup_blocked_contacts(TpBaseContactList * self)3347 tp_base_contact_list_dup_blocked_contacts (TpBaseContactList *self)
3348 {
3349   TpBlockableContactListInterface *iface =
3350     TP_BLOCKABLE_CONTACT_LIST_GET_INTERFACE (self);
3351 
3352   g_return_val_if_fail (iface != NULL, NULL);
3353   g_return_val_if_fail (iface->dup_blocked_contacts != NULL, NULL);
3354   g_return_val_if_fail (tp_base_contact_list_get_state (self, NULL) ==
3355       TP_CONTACT_LIST_STATE_SUCCESS, NULL);
3356 
3357   return iface->dup_blocked_contacts (self);
3358 }
3359 
3360 /**
3361  * tp_base_contact_list_block_contacts_async:
3362  * @self: a contact list manager
3363  * @contacts: contacts whose communications should be blocked
3364  * @callback: a callback to call when the operation succeeds or fails
3365  * @user_data: optional data to pass to @callback
3366  *
3367  * Request that the given contacts are prevented from communicating with the
3368  * user, and that presence is not sent to them even if they have a valid
3369  * presence subscription, if possible. This is equivalent to calling
3370  * tp_base_contact_list_block_contacts_with_abuse_async(), passing #FALSE as
3371  * the report_abusive argument.
3372  *
3373  * If the #TpBaseContactList subclass does not implement
3374  * %TP_TYPE_BLOCKABLE_CONTACT_LIST, it is an error to call this method.
3375  *
3376  * For implementations of %TP_TYPE_BLOCKABLE_CONTACT_LIST, this is a virtual
3377  * method which must be implemented, using
3378  * #TpBlockableContactListInterface.block_contacts_async or
3379  * #TpBlockableContactListInterface.block_contacts_with_abuse_async.
3380  * The implementation should call
3381  * tp_base_contact_list_contact_blocking_changed()
3382  * for any contacts it has changed, before calling @callback.
3383  *
3384  * Since: 0.13.0
3385  */
3386 void
tp_base_contact_list_block_contacts_async(TpBaseContactList * self,TpHandleSet * contacts,GAsyncReadyCallback callback,gpointer user_data)3387 tp_base_contact_list_block_contacts_async (TpBaseContactList *self,
3388     TpHandleSet *contacts,
3389     GAsyncReadyCallback callback,
3390     gpointer user_data)
3391 {
3392   tp_base_contact_list_block_contacts_with_abuse_async (self, contacts, FALSE,
3393       callback, user_data);
3394 }
3395 
3396 /**
3397  * tp_base_contact_list_block_contacts_with_abuse_async:
3398  * @self: a contact list manager
3399  * @contacts: contacts whose communications should be blocked
3400  * @report_abusive: whether to report the contacts as abusive to the server
3401  *  operator
3402  * @callback: a callback to call when the operation succeeds or fails
3403  * @user_data: optional data to pass to @callback
3404  *
3405  * Request that the given contacts are prevented from communicating with the
3406  * user, and that presence is not sent to them even if they have a valid
3407  * presence subscription, if possible. If the #TpBaseContactList subclass
3408  * implements #TpBlockableContactListInterface.block_contacts_with_abuse_async
3409  * and @report_abusive is #TRUE, also report the given contacts as abusive to
3410  * the server operator.
3411  *
3412  * If the #TpBaseContactList subclass does not implement
3413  * %TP_TYPE_BLOCKABLE_CONTACT_LIST, it is an error to call this method.
3414  *
3415  * For implementations of %TP_TYPE_BLOCKABLE_CONTACT_LIST, this is a virtual
3416  * method which must be implemented, using
3417  * #TpBlockableContactListInterface.block_contacts_async or
3418  * #TpBlockableContactListInterface.block_contacts_with_abuse_async.
3419  * The implementation should call
3420  * tp_base_contact_list_contact_blocking_changed()
3421  * for any contacts it has changed, before calling @callback.
3422  *
3423  * Since: 0.15.1
3424  */
3425 void
tp_base_contact_list_block_contacts_with_abuse_async(TpBaseContactList * self,TpHandleSet * contacts,gboolean report_abusive,GAsyncReadyCallback callback,gpointer user_data)3426 tp_base_contact_list_block_contacts_with_abuse_async (TpBaseContactList *self,
3427     TpHandleSet *contacts,
3428     gboolean report_abusive,
3429     GAsyncReadyCallback callback,
3430     gpointer user_data)
3431 {
3432   TpBlockableContactListInterface *blockable_iface;
3433 
3434   blockable_iface = TP_BLOCKABLE_CONTACT_LIST_GET_INTERFACE (self);
3435   g_return_if_fail (blockable_iface != NULL);
3436 
3437   if (blockable_iface->block_contacts_async != NULL)
3438     blockable_iface->block_contacts_async (self, contacts, callback, user_data);
3439   else if (blockable_iface->block_contacts_with_abuse_async != NULL)
3440     blockable_iface->block_contacts_with_abuse_async (self, contacts,
3441         report_abusive, callback, user_data);
3442   else
3443     g_critical ("neither block_contacts_async nor "
3444         "block_contacts_with_abuse_async is implemented");
3445 }
3446 
3447 /**
3448  * tp_base_contact_list_block_contacts_finish:
3449  * @self: a contact list manager
3450  * @result: the result passed to @callback by an implementation of
3451  *  tp_base_contact_list_block_contacts_async()
3452  * @error: used to raise an error if %FALSE is returned
3453  *
3454  * Interpret the result of an asynchronous call to
3455  * tp_base_contact_list_block_contacts_async().
3456  *
3457  * If the #TpBaseContactList subclass does not implement
3458  * %TP_TYPE_BLOCKABLE_CONTACT_LIST, it is an error to call this method.
3459  *
3460  * For implementations of %TP_TYPE_BLOCKABLE_CONTACT_LIST, this is a virtual
3461  * method which may be implemented using
3462  * #TpBlockableContactListInterface.block_contacts_finish. If the @result
3463  * will be a #GSimpleAsyncResult, the default implementation may be used.
3464  *
3465  * Returns: %TRUE on success or %FALSE on error
3466  *
3467  * Since: 0.13.0
3468  */
3469 gboolean
tp_base_contact_list_block_contacts_finish(TpBaseContactList * self,GAsyncResult * result,GError ** error)3470 tp_base_contact_list_block_contacts_finish (TpBaseContactList *self,
3471     GAsyncResult *result,
3472     GError **error)
3473 {
3474   TpBlockableContactListInterface *blockable_iface;
3475 
3476   blockable_iface = TP_BLOCKABLE_CONTACT_LIST_GET_INTERFACE (self);
3477   g_return_val_if_fail (blockable_iface != NULL, FALSE);
3478   g_return_val_if_fail (blockable_iface->block_contacts_finish != NULL, FALSE);
3479 
3480   return blockable_iface->block_contacts_finish (self, result, error);
3481 }
3482 
3483 /**
3484  * tp_base_contact_list_block_contacts_with_abuse_finish:
3485  * @self: a contact list manager
3486  * @result: the result passed to @callback by an implementation of
3487  *  tp_base_contact_list_block_contacts_with_abuse_async()
3488  * @error: used to raise an error if %FALSE is returned
3489  *
3490  * Interpret the result of an asynchronous call to
3491  * tp_base_contact_list_block_contacts_with_abuse_async().
3492  *
3493  * If the #TpBaseContactList subclass does not implement
3494  * %TP_TYPE_BLOCKABLE_CONTACT_LIST, it is an error to call this method.
3495  *
3496  * For implementations of %TP_TYPE_BLOCKABLE_CONTACT_LIST, this is a virtual
3497  * method which may be implemented using
3498  * #TpBlockableContactListInterface.block_contacts_finish. If the @result
3499  * will be a #GSimpleAsyncResult, the default implementation may be used.
3500  *
3501  * Returns: %TRUE on success or %FALSE on error
3502  *
3503  * Since: 0.15.1
3504  */
3505 gboolean
tp_base_contact_list_block_contacts_with_abuse_finish(TpBaseContactList * self,GAsyncResult * result,GError ** error)3506 tp_base_contact_list_block_contacts_with_abuse_finish (TpBaseContactList *self,
3507     GAsyncResult *result,
3508     GError **error)
3509 {
3510   TpBlockableContactListInterface *blockable_iface;
3511 
3512   blockable_iface = TP_BLOCKABLE_CONTACT_LIST_GET_INTERFACE (self);
3513   g_return_val_if_fail (blockable_iface != NULL, FALSE);
3514   g_return_val_if_fail (blockable_iface->block_contacts_finish != NULL, FALSE);
3515 
3516   return blockable_iface->block_contacts_finish (self, result, error);
3517 }
3518 
3519 /**
3520  * tp_base_contact_list_unblock_contacts_async:
3521  * @self: a contact list manager
3522  * @contacts: contacts whose communications should no longer be blocked
3523  * @callback: a callback to call when the operation succeeds or fails
3524  * @user_data: optional data to pass to @callback
3525  *
3526  * Reverse the effects of tp_base_contact_list_block_contacts_async().
3527  *
3528  * If the #TpBaseContactList subclass does not implement
3529  * %TP_TYPE_BLOCKABLE_CONTACT_LIST, this method does nothing.
3530  *
3531  * For implementations of %TP_TYPE_BLOCKABLE_CONTACT_LIST, this is a virtual
3532  * method which must be implemented, using
3533  * #TpBlockableContactListInterface.unblock_contacts_async.
3534  * The implementation should call
3535  * tp_base_contact_list_contact_blocking_changed()
3536  * for any contacts it has changed, before calling @callback.
3537  *
3538  * Since: 0.13.0
3539  */
3540 void
tp_base_contact_list_unblock_contacts_async(TpBaseContactList * self,TpHandleSet * contacts,GAsyncReadyCallback callback,gpointer user_data)3541 tp_base_contact_list_unblock_contacts_async (TpBaseContactList *self,
3542     TpHandleSet *contacts,
3543     GAsyncReadyCallback callback,
3544     gpointer user_data)
3545 {
3546   TpBlockableContactListInterface *blockable_iface;
3547 
3548   blockable_iface = TP_BLOCKABLE_CONTACT_LIST_GET_INTERFACE (self);
3549   g_return_if_fail (blockable_iface != NULL);
3550   g_return_if_fail (blockable_iface->unblock_contacts_async != NULL);
3551 
3552   blockable_iface->unblock_contacts_async (self, contacts, callback, user_data);
3553 }
3554 
3555 /**
3556  * tp_base_contact_list_unblock_contacts_finish:
3557  * @self: a contact list manager
3558  * @result: the result passed to @callback by an implementation of
3559  *  tp_base_contact_list_unblock_contacts_async()
3560  * @error: used to raise an error if %FALSE is returned
3561  *
3562  * Interpret the result of an asynchronous call to
3563  * tp_base_contact_list_unblock_contacts_async().
3564  *
3565  * If the #TpBaseContactList subclass does not implement
3566  * %TP_TYPE_BLOCKABLE_CONTACT_LIST, it is an error to call this method.
3567  *
3568  * For implementations of %TP_TYPE_BLOCKABLE_CONTACT_LIST, this is a virtual
3569  * method which may be implemented using
3570  * #TpBlockableContactListInterface.unblock_contacts_finish. If the @result
3571  * will be a #GSimpleAsyncResult, the default implementation may be used.
3572  *
3573  * Returns: %TRUE on success or %FALSE on error
3574  *
3575  * Since: 0.13.0
3576  */
3577 gboolean
tp_base_contact_list_unblock_contacts_finish(TpBaseContactList * self,GAsyncResult * result,GError ** error)3578 tp_base_contact_list_unblock_contacts_finish (TpBaseContactList *self,
3579     GAsyncResult *result,
3580     GError **error)
3581 {
3582   TpBlockableContactListInterface *blockable_iface;
3583 
3584   blockable_iface = TP_BLOCKABLE_CONTACT_LIST_GET_INTERFACE (self);
3585   g_return_val_if_fail (blockable_iface != NULL, FALSE);
3586   g_return_val_if_fail (blockable_iface->unblock_contacts_finish != NULL,
3587       FALSE);
3588 
3589   return blockable_iface->unblock_contacts_finish (self, result, error);
3590 }
3591 
3592 /**
3593  * TpBaseContactListNormalizeFunc:
3594  * @self: a contact list manager
3595  * @s: a non-%NULL name to normalize
3596  *
3597  * Signature of a virtual method to normalize strings in a contact list
3598  * manager.
3599  *
3600  * Returns: a normalized form of @s, or %NULL on error
3601  *
3602  * Since: 0.13.0
3603  */
3604 
3605 /**
3606  * tp_base_contact_list_normalize_group:
3607  * @self: a contact list manager
3608  * @s: a non-%NULL group name to normalize
3609  *
3610  * Return a normalized form of the group name @s, or %NULL if a group of a
3611  * sufficiently similar name cannot be created.
3612  *
3613  * If the #TpBaseContactList subclass does not implement
3614  * %TP_TYPE_CONTACT_GROUP_LIST, this method is meaningless, and always
3615  * returns %NULL.
3616  *
3617  * For implementations of %TP_TYPE_CONTACT_GROUP_LIST, this is a virtual
3618  * method, implemented using #TpContactGroupListInterface.normalize_group.
3619  * If unimplemented, the default behaviour is to use the group's name as-is.
3620  *
3621  * Protocols where this default is not suitable (for instance, if group names
3622  * cannot be the empty string, or can only contain XML character data, or can
3623  * only contain a particular Unicode normal form like NFKC) should reimplement
3624  * this virtual method.
3625  *
3626  * Returns: a normalized form of @s, or %NULL on error
3627  *
3628  * Since: 0.13.0
3629  */
3630 gchar *
tp_base_contact_list_normalize_group(TpBaseContactList * self,const gchar * s)3631 tp_base_contact_list_normalize_group (TpBaseContactList *self,
3632     const gchar *s)
3633 {
3634   TpContactGroupListInterface *iface;
3635 
3636   g_return_val_if_fail (TP_IS_BASE_CONTACT_LIST (self), NULL);
3637   g_return_val_if_fail (s != NULL, NULL);
3638 
3639   if (!TP_IS_CONTACT_GROUP_LIST (self))
3640     return NULL;
3641 
3642   iface = TP_CONTACT_GROUP_LIST_GET_INTERFACE (self);
3643   g_return_val_if_fail (iface != NULL, FALSE);
3644 
3645   if (iface->normalize_group == NULL)
3646     return g_strdup (s);
3647 
3648   return iface->normalize_group (self, s);
3649 }
3650 
3651 /**
3652  * tp_base_contact_list_groups_created:
3653  * @self: a contact list manager
3654  * @created: (array length=n_created) (element-type utf8) (allow-none): zero
3655  *  or more groups that were created
3656  * @n_created: the number of groups created, or -1 if @created is
3657  *  %NULL-terminated
3658  *
3659  * Called by subclasses when new groups have been created. This will typically
3660  * be followed by a call to tp_base_contact_list_groups_changed() to add
3661  * some members to those groups.
3662  *
3663  * It is an error to call this method on a contact list that
3664  * does not implement %TP_TYPE_CONTACT_GROUP_LIST.
3665  *
3666  * Since: 0.13.0
3667  */
3668 void
tp_base_contact_list_groups_created(TpBaseContactList * self,const gchar * const * created,gssize n_created)3669 tp_base_contact_list_groups_created (TpBaseContactList *self,
3670     const gchar * const *created,
3671     gssize n_created)
3672 {
3673   GPtrArray *actually_created;
3674   gssize i;
3675 
3676   g_return_if_fail (TP_IS_BASE_CONTACT_LIST (self));
3677   g_return_if_fail (TP_IS_CONTACT_GROUP_LIST (self));
3678   g_return_if_fail (n_created >= -1);
3679   g_return_if_fail (n_created <= 0 || created != NULL);
3680 
3681   if (n_created == 0 || created == NULL)
3682     return;
3683 
3684   if (n_created < 0)
3685     {
3686       n_created = (gssize) g_strv_length ((GStrv) created);
3687 
3688       g_return_if_fail (n_created >= 0);
3689     }
3690   else
3691     {
3692       for (i = 0; i < n_created; i++)
3693         g_return_if_fail (created[i] != NULL);
3694     }
3695 
3696   if (self->priv->state != TP_CONTACT_LIST_STATE_SUCCESS)
3697     return;
3698 
3699   actually_created = g_ptr_array_sized_new (n_created + 1);
3700 
3701   for (i = 0; i < n_created; i++)
3702     {
3703       TpHandle handle = tp_handle_ensure (self->priv->group_repo, created[i],
3704           NULL, NULL);
3705 
3706       if (handle != 0)
3707         {
3708           gpointer c = g_hash_table_lookup (self->priv->groups,
3709               GUINT_TO_POINTER (handle));
3710 
3711           if (c == NULL)
3712             c = tp_base_contact_list_new_channel (self, TP_HANDLE_TYPE_GROUP,
3713                 handle, NULL);
3714 
3715           if (g_hash_table_lookup_extended (self->priv->channel_requests, c,
3716                 NULL, NULL))
3717             {
3718               /* the channel hasn't been announced yet: do so, and include
3719                * it in the GroupsCreated signal */
3720               g_ptr_array_add (actually_created, (gchar *) tp_handle_inspect (
3721                     self->priv->group_repo, handle));
3722 
3723               tp_base_contact_list_announce_channel (self, c, NULL);
3724             }
3725         }
3726     }
3727 
3728   if (actually_created->len > 0)
3729     {
3730       DEBUG ("GroupsCreated([%u including '%s'])", actually_created->len,
3731           (gchar *) g_ptr_array_index (actually_created, 0));
3732 
3733       if (self->priv->svc_contact_groups)
3734       {
3735         g_ptr_array_add (actually_created, NULL);
3736         tp_svc_connection_interface_contact_groups_emit_groups_created (
3737             self->priv->conn, (const gchar **) actually_created->pdata);
3738       }
3739     }
3740 
3741   g_ptr_array_unref (actually_created);
3742 }
3743 
3744 /**
3745  * tp_base_contact_list_groups_removed:
3746  * @self: a contact list manager
3747  * @removed: (array length=n_removed) (element-type utf8) (allow-none): zero
3748  *  or more groups that were removed
3749  * @n_removed: the number of groups removed, or -1 if @removed is
3750  *  %NULL-terminated
3751  *
3752  * Called by subclasses when groups have been removed.
3753  *
3754  * Calling tp_base_contact_list_dup_group_members() during this method should
3755  * return the groups' old members. If this is done correctly by a subclass,
3756  * then tp_base_contact_list_groups_changed() will automatically be emitted
3757  * for the old members, and the subclass does not need to do so.
3758  *
3759  * It is an error to call this method on a contact list that
3760  * does not implement %TP_TYPE_CONTACT_GROUP_LIST.
3761  *
3762  * Since: 0.13.0
3763  */
3764 void
tp_base_contact_list_groups_removed(TpBaseContactList * self,const gchar * const * removed,gssize n_removed)3765 tp_base_contact_list_groups_removed (TpBaseContactList *self,
3766     const gchar * const *removed,
3767     gssize n_removed)
3768 {
3769   GPtrArray *actually_removed;
3770   gssize i;
3771   TpHandleSet *old_members;
3772 
3773   g_return_if_fail (TP_IS_BASE_CONTACT_LIST (self));
3774   g_return_if_fail (TP_IS_CONTACT_GROUP_LIST (self));
3775   g_return_if_fail (removed != NULL);
3776   g_return_if_fail (n_removed >= -1);
3777   g_return_if_fail (n_removed <= 0 || removed != NULL);
3778 
3779   if (n_removed == 0 || removed == NULL)
3780     return;
3781 
3782   if (n_removed < 0)
3783     {
3784       n_removed = (gssize) g_strv_length ((GStrv) removed);
3785 
3786       g_return_if_fail (n_removed >= 0);
3787     }
3788   else
3789     {
3790       for (i = 0; i < n_removed; i++)
3791         g_return_if_fail (removed[i] != NULL);
3792     }
3793 
3794   if (self->priv->state != TP_CONTACT_LIST_STATE_SUCCESS)
3795     return;
3796 
3797   old_members = tp_handle_set_new (self->priv->contact_repo);
3798   actually_removed = g_ptr_array_new_full (n_removed + 1, g_free);
3799 
3800   for (i = 0; i < n_removed; i++)
3801     {
3802       TpHandle handle = tp_handle_lookup (self->priv->group_repo, removed[i],
3803           NULL, NULL);
3804 
3805       if (handle != 0)
3806         {
3807           gpointer c = g_hash_table_lookup (self->priv->groups,
3808               GUINT_TO_POINTER (handle));
3809 
3810           if (c != NULL)
3811             {
3812               gchar *name;
3813               TpHandleSet *group_members;
3814               TpHandle contact;
3815               TpIntsetFastIter iter;
3816 
3817               /* the handle might get unref'd by closing the channel, so copy
3818                * the string */
3819               name = g_strdup (tp_handle_inspect (self->priv->group_repo,
3820                     handle));
3821               g_ptr_array_add (actually_removed, name);
3822               group_members = tp_base_contact_list_dup_group_members (self,
3823                   name);
3824 
3825               tp_intset_fast_iter_init (&iter,
3826                   tp_handle_set_peek (group_members));
3827 
3828               while (tp_intset_fast_iter_next (&iter, &contact))
3829                 tp_handle_set_add (old_members, contact);
3830 
3831               /* Remove members if any: presumably the self-handle is the
3832                * actor. */
3833               tp_group_mixin_change_members (c, "",
3834                   NULL, tp_handle_set_peek (group_members), NULL, NULL,
3835                   tp_base_connection_get_self_handle (self->priv->conn),
3836                   TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
3837 
3838               tp_channel_manager_emit_channel_closed_for_object (self, c);
3839               _tp_base_contact_list_channel_close (c);
3840               g_hash_table_remove (self->priv->groups,
3841                   GUINT_TO_POINTER (handle));
3842 
3843               tp_handle_set_destroy (group_members);
3844             }
3845         }
3846     }
3847 
3848   if (actually_removed->len > 0)
3849     {
3850       GArray *members_arr = tp_handle_set_to_array (old_members);
3851 
3852       DEBUG ("GroupsRemoved([%u including '%s'])",
3853           actually_removed->len,
3854           (gchar *) g_ptr_array_index (actually_removed, 0));
3855 
3856       g_ptr_array_add (actually_removed, NULL);
3857 
3858       if (self->priv->svc_contact_groups)
3859         tp_svc_connection_interface_contact_groups_emit_groups_removed (
3860             self->priv->conn, (const gchar **) actually_removed->pdata);
3861 
3862       if (members_arr->len > 0)
3863         {
3864           /* we already added NULL to actually_removed, so subtract 1 from its
3865            * length */
3866           DEBUG ("GroupsChanged([%u contacts], [], [%u groups])",
3867               members_arr->len, actually_removed->len - 1);
3868 
3869           if (self->priv->svc_contact_groups)
3870             tp_svc_connection_interface_contact_groups_emit_groups_changed (
3871                 self->priv->conn, members_arr, NULL,
3872                 (const gchar **) actually_removed->pdata);
3873         }
3874 
3875       g_array_unref (members_arr);
3876     }
3877 
3878   tp_handle_set_destroy (old_members);
3879   g_ptr_array_unref (actually_removed);
3880 }
3881 
3882 /**
3883  * tp_base_contact_list_group_renamed:
3884  * @self: a contact list manager
3885  * @old_name: the group's old name
3886  * @new_name: the group's new name
3887  *
3888  * Called by subclasses when a group has been renamed.
3889  *
3890  * Calling tp_base_contact_list_dup_group_members() for @old_name during this
3891  * method should return the group's old members. If this is done correctly by
3892  * a subclass, then tp_base_contact_list_groups_changed() will automatically
3893  * be emitted for the members, and the subclass does not need to do so.
3894  *
3895  * It is an error to call this method on a contact list that
3896  * does not implement %TP_TYPE_CONTACT_GROUP_LIST.
3897  *
3898  * Since: 0.13.0
3899  */
3900 void
tp_base_contact_list_group_renamed(TpBaseContactList * self,const gchar * old_name,const gchar * new_name)3901 tp_base_contact_list_group_renamed (TpBaseContactList *self,
3902     const gchar *old_name,
3903     const gchar *new_name)
3904 {
3905   TpHandle old_handle, new_handle;
3906   gpointer old_chan, new_chan;
3907   const gchar *old_names[] = { old_name, NULL };
3908   const gchar *new_names[] = { new_name, NULL };
3909   const TpIntset *set;
3910   TpHandleSet *old_members;
3911 
3912   g_return_if_fail (TP_IS_BASE_CONTACT_LIST (self));
3913   g_return_if_fail (TP_IS_CONTACT_GROUP_LIST (self));
3914 
3915   if (self->priv->state != TP_CONTACT_LIST_STATE_SUCCESS)
3916     return;
3917 
3918   old_handle = tp_handle_ensure (self->priv->group_repo, old_name, NULL, NULL);
3919 
3920   if (old_handle == 0)
3921     return;
3922 
3923   old_chan = g_hash_table_lookup (self->priv->groups,
3924       GUINT_TO_POINTER (old_handle));
3925 
3926   new_handle = tp_handle_ensure (self->priv->group_repo, new_name, NULL, NULL);
3927 
3928   if (new_handle == 0)
3929     return;
3930 
3931   new_chan = g_hash_table_lookup (self->priv->groups,
3932       GUINT_TO_POINTER (new_handle));
3933 
3934   if (new_chan == NULL)
3935     {
3936       new_chan = tp_base_contact_list_new_channel (self, TP_HANDLE_TYPE_GROUP,
3937           new_handle, NULL);
3938     }
3939 
3940   if (g_hash_table_lookup_extended (self->priv->channel_requests, new_chan,
3941         NULL, NULL))
3942     {
3943       /* the channel hasn't been announced yet: do so */
3944       tp_base_contact_list_announce_channel (self, new_chan, NULL);
3945     }
3946 
3947   old_members = tp_base_contact_list_dup_group_members (self, old_name);
3948 
3949   /* move the members - presumably the self-handle is the actor */
3950   set = tp_handle_set_peek (old_members);
3951   tp_group_mixin_change_members (new_chan, "", set, NULL, NULL, NULL,
3952       tp_base_connection_get_self_handle (self->priv->conn),
3953       TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
3954 
3955   if (old_chan != NULL)
3956     {
3957       tp_group_mixin_change_members (old_chan, "", NULL, set, NULL, NULL,
3958           tp_base_connection_get_self_handle (self->priv->conn),
3959           TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
3960       tp_channel_manager_emit_channel_closed_for_object (self, old_chan);
3961       _tp_base_contact_list_channel_close (old_chan);
3962     }
3963 
3964   g_hash_table_remove (self->priv->groups, GUINT_TO_POINTER (old_handle));
3965 
3966   /* get normalized forms */
3967   old_names[0] = tp_handle_inspect (self->priv->group_repo, old_handle);
3968   new_names[0] = tp_handle_inspect (self->priv->group_repo, new_handle);
3969 
3970   DEBUG ("GroupRenamed('%s', '%s')", old_names[0], new_names[0]);
3971 
3972   if (self->priv->svc_contact_groups)
3973     {
3974       tp_svc_connection_interface_contact_groups_emit_group_renamed (
3975           self->priv->conn, old_names[0], new_names[0]);
3976 
3977       tp_svc_connection_interface_contact_groups_emit_groups_created (
3978           self->priv->conn, new_names);
3979 
3980       tp_svc_connection_interface_contact_groups_emit_groups_removed (
3981           self->priv->conn, old_names);
3982     }
3983 
3984   if (tp_intset_size (set) > 0)
3985     {
3986       DEBUG ("GroupsChanged([%u contacts], ['%s'], ['%s'])",
3987           tp_intset_size (set), new_names[0], old_names[0]);
3988 
3989       if (self->priv->svc_contact_groups)
3990         {
3991           GArray *arr = tp_intset_to_array (set);
3992 
3993           tp_svc_connection_interface_contact_groups_emit_groups_changed (
3994               self->priv->conn, arr, new_names, old_names);
3995           g_array_unref (arr);
3996         }
3997     }
3998 
3999   tp_handle_set_destroy (old_members);
4000 }
4001 
4002 /**
4003  * tp_base_contact_list_groups_changed:
4004  * @self: a contact list manager
4005  * @contacts: a set containing one or more contacts
4006  * @added: (array length=n_added) (element-type utf8) (allow-none): zero or
4007  *  more groups to which the @contacts were added, or %NULL (which has the
4008  *  same meaning as an empty list)
4009  * @n_added: the number of groups added, or -1 if @added is %NULL-terminated
4010  * @removed: (array zero-terminated=1) (element-type utf8) (allow-none): zero
4011  *  or more groups from which the @contacts were removed, or %NULL (which has
4012  *  the same meaning as an empty list)
4013  * @n_removed: the number of groups removed, or -1 if @removed is
4014  *  %NULL-terminated
4015  *
4016  * Called by subclasses when groups' membership has been changed.
4017  *
4018  * If any of the groups in @added are not already known to exist,
4019  * this method also signals that they were created, as if
4020  * tp_base_contact_list_groups_created() had been called first.
4021  *
4022  * It is an error to call this method on a contact list that
4023  * does not implement %TP_TYPE_CONTACT_GROUP_LIST.
4024  *
4025  * Since: 0.13.0
4026  */
4027 void
tp_base_contact_list_groups_changed(TpBaseContactList * self,TpHandleSet * contacts,const gchar * const * added,gssize n_added,const gchar * const * removed,gssize n_removed)4028 tp_base_contact_list_groups_changed (TpBaseContactList *self,
4029     TpHandleSet *contacts,
4030     const gchar * const *added,
4031     gssize n_added,
4032     const gchar * const *removed,
4033     gssize n_removed)
4034 {
4035   gssize i;
4036   GPtrArray *really_added, *really_removed;
4037 
4038   g_return_if_fail (TP_IS_BASE_CONTACT_LIST (self));
4039   g_return_if_fail (TP_IS_CONTACT_GROUP_LIST (self));
4040   g_return_if_fail (contacts != NULL);
4041   g_return_if_fail (n_added >= -1);
4042   g_return_if_fail (n_removed >= -1);
4043   g_return_if_fail (n_added <= 0 || added != NULL);
4044   g_return_if_fail (n_removed <= 0 || removed != NULL);
4045 
4046   if (tp_handle_set_is_empty (contacts))
4047     {
4048       DEBUG ("No contacts, doing nothing");
4049       return;
4050     }
4051 
4052   if (n_added < 0)
4053     {
4054       if (added == NULL)
4055         n_added = 0;
4056       else
4057         n_added = (gssize) g_strv_length ((GStrv) added);
4058 
4059       g_return_if_fail (n_added >= 0);
4060     }
4061   else
4062     {
4063       for (i = 0; i < n_added; i++)
4064         g_return_if_fail (added[i] != NULL);
4065     }
4066 
4067   if (n_removed < 0)
4068     {
4069       if (added == NULL)
4070         n_removed = 0;
4071       else
4072         n_removed = (gssize) g_strv_length ((GStrv) added);
4073 
4074       g_return_if_fail (n_removed >= 0);
4075     }
4076   else
4077     {
4078       for (i = 0; i < n_removed; i++)
4079         g_return_if_fail (removed[i] != NULL);
4080     }
4081 
4082   if (self->priv->state != TP_CONTACT_LIST_STATE_SUCCESS)
4083     return;
4084 
4085   DEBUG ("Changing up to %u contacts, adding %" G_GSSIZE_FORMAT
4086       " groups, removing %" G_GSSIZE_FORMAT,
4087       tp_handle_set_size (contacts), n_added, n_removed);
4088 
4089   tp_base_contact_list_groups_created (self, added, n_added);
4090 
4091   /* These two arrays are lists of the groups whose members really changed;
4092    * groups where the change was a no-op are skipped. */
4093   really_added = g_ptr_array_sized_new (n_added);
4094   really_removed = g_ptr_array_sized_new (n_removed);
4095 
4096   for (i = 0; i < n_added; i++)
4097     {
4098       TpHandle handle = tp_handle_lookup (self->priv->group_repo, added[i],
4099           NULL, NULL);
4100       gpointer c;
4101 
4102       /* it doesn't matter if handle is 0, we'll just get NULL */
4103       c = g_hash_table_lookup (self->priv->groups,
4104           GUINT_TO_POINTER (handle));
4105 
4106       if (c == NULL)
4107         {
4108           DEBUG ("No channel for group '%s', it must be invalid?", added[i]);
4109           continue;
4110         }
4111 
4112       DEBUG ("Adding %u contacts to group '%s'", tp_handle_set_size (contacts),
4113           added[i]);
4114 
4115       if (tp_group_mixin_change_members (c, "",
4116           tp_handle_set_peek (contacts), NULL, NULL, NULL,
4117           tp_base_connection_get_self_handle (self->priv->conn),
4118           TP_CHANNEL_GROUP_CHANGE_REASON_NONE))
4119         g_ptr_array_add (really_added, (gchar *) added[i]);
4120     }
4121 
4122   for (i = 0; i < n_removed; i++)
4123     {
4124       TpHandle handle = tp_handle_lookup (self->priv->group_repo, removed[i],
4125           NULL, NULL);
4126       gpointer c;
4127 
4128       /* it doesn't matter if handle is 0, we'll just get NULL */
4129       c = g_hash_table_lookup (self->priv->groups,
4130           GUINT_TO_POINTER (handle));
4131 
4132       if (c == NULL)
4133         {
4134           DEBUG ("Group '%s' doesn't exist", removed[i]);
4135           continue;
4136         }
4137 
4138       DEBUG ("Removing %u contacts from group '%s'",
4139           tp_handle_set_size (contacts), removed[i]);
4140 
4141       if (tp_group_mixin_change_members (c, "",
4142           NULL, tp_handle_set_peek (contacts), NULL, NULL,
4143           tp_base_connection_get_self_handle (self->priv->conn),
4144           TP_CHANNEL_GROUP_CHANGE_REASON_NONE))
4145         g_ptr_array_add (really_removed, (gchar *) removed[i]);
4146     }
4147 
4148   if (really_added->len > 0 || really_removed->len > 0)
4149     {
4150       DEBUG ("GroupsChanged([%u contacts], [%u groups], [%u groups])",
4151           tp_handle_set_size (contacts), really_added->len,
4152           really_removed->len);
4153 
4154       g_ptr_array_add (really_added, NULL);
4155       g_ptr_array_add (really_removed, NULL);
4156 
4157       if (self->priv->svc_contact_groups)
4158         {
4159           GArray *members_arr = tp_handle_set_to_array (contacts);
4160 
4161           tp_svc_connection_interface_contact_groups_emit_groups_changed (
4162               self->priv->conn, members_arr,
4163               (const gchar **) really_added->pdata,
4164               (const gchar **) really_removed->pdata);
4165           g_array_unref (members_arr);
4166         }
4167     }
4168 
4169   g_ptr_array_unref (really_added);
4170   g_ptr_array_unref (really_removed);
4171 }
4172 
4173 /**
4174  * tp_base_contact_list_one_contact_groups_changed:
4175  * @self: the contact list manager
4176  * @contact: a contact handle
4177  * @added: (array length=n_added) (element-type utf8) (allow-none): zero or
4178  *  more groups to which @contact was added, or %NULL
4179  * @n_added: the number of groups added, or -1 if @added is %NULL-terminated
4180  * @removed: (array zero-terminated=1) (element-type utf8) (allow-none): zero
4181  *  or more groups from which the @contact was removed, or %NULL
4182  * @n_removed: the number of groups removed, or -1 if @removed is
4183  *  %NULL-terminated
4184  *
4185  * Convenience wrapper around tp_base_contact_list_groups_changed() for a
4186  * single handle in the 'contacts' set.
4187  *
4188  * (There is no equivalent function for @added and @removed having trivial
4189  * contents, because you can already use <code>NULL, 0</code> for an empty
4190  * list or <code>&group_name, 1</code> for a single group.)
4191  *
4192  * Since: 0.13.0
4193  */
4194 void
tp_base_contact_list_one_contact_groups_changed(TpBaseContactList * self,TpHandle contact,const gchar * const * added,gssize n_added,const gchar * const * removed,gssize n_removed)4195 tp_base_contact_list_one_contact_groups_changed (TpBaseContactList *self,
4196     TpHandle contact,
4197     const gchar * const *added,
4198     gssize n_added,
4199     const gchar * const *removed,
4200     gssize n_removed)
4201 {
4202   TpHandleSet *set;
4203 
4204   g_return_if_fail (TP_IS_BASE_CONTACT_LIST (self));
4205 
4206   /* if we're disconnecting, we might not have a handle repository any more:
4207    * tp_base_contact_list_groups_changed does nothing in that situation */
4208   if (self->priv->contact_repo == NULL)
4209     return;
4210 
4211   set = tp_handle_set_new_containing (self->priv->contact_repo, contact);
4212   tp_base_contact_list_groups_changed (self, set, added, n_added, removed,
4213       n_removed);
4214   tp_handle_set_destroy (set);
4215 }
4216 
4217 /**
4218  * tp_base_contact_list_has_disjoint_groups:
4219  * @self: a contact list manager
4220  *
4221  * Return whether groups in this protocol are disjoint
4222  * (i.e. each contact can be in at most one group).
4223  * This is merely informational: subclasses are responsible for making
4224  * appropriate calls to tp_base_contact_list_groups_changed(), etc.
4225  *
4226  * If the #TpBaseContactList subclass does not implement
4227  * %TP_TYPE_CONTACT_GROUP_LIST, this method is meaningless, and always
4228  * returns %FALSE.
4229  *
4230  * For implementations of %TP_TYPE_CONTACT_GROUP_LIST, this is a virtual
4231  * method, implemented using #TpContactGroupListInterface.has_disjoint_groups.
4232  *
4233  * The default implementation is tp_base_contact_list_false_func();
4234  * subclasses where groups are disjoint should use
4235  * tp_base_contact_list_true_func() instead.
4236  * In the unlikely event that a protocol can have disjoint groups, or not,
4237  * determined at runtime, it can use a custom implementation.
4238  *
4239  * Returns: %TRUE if groups are disjoint
4240  *
4241  * Since: 0.13.0
4242  */
4243 gboolean
tp_base_contact_list_has_disjoint_groups(TpBaseContactList * self)4244 tp_base_contact_list_has_disjoint_groups (TpBaseContactList *self)
4245 {
4246   TpContactGroupListInterface *iface;
4247 
4248   g_return_val_if_fail (TP_IS_BASE_CONTACT_LIST (self), FALSE);
4249 
4250   if (!TP_IS_CONTACT_GROUP_LIST (self))
4251     return FALSE;
4252 
4253   iface = TP_CONTACT_GROUP_LIST_GET_INTERFACE (self);
4254   g_return_val_if_fail (iface != NULL, FALSE);
4255   g_return_val_if_fail (iface->has_disjoint_groups != NULL, FALSE);
4256 
4257   return iface->has_disjoint_groups (self);
4258 }
4259 
4260 /**
4261  * TpBaseContactListDupGroupsFunc:
4262  * @self: a contact list manager
4263  *
4264  * Signature of a virtual method that lists every group that exists on a
4265  * connection.
4266  *
4267  * Returns: (array zero-terminated=1) (element-type utf8) (transfer full): an
4268  *  array of groups
4269  *
4270  * Since: 0.13.0
4271  */
4272 
4273 /**
4274  * tp_base_contact_list_dup_groups:
4275  * @self: a contact list manager
4276  *
4277  * Return a list of all groups on this connection. It is incorrect to call
4278  * this method before tp_base_contact_list_set_list_received() has been
4279  * called, after the connection has disconnected, or on a #TpBaseContactList
4280  * that does not implement %TP_TYPE_CONTACT_GROUP_LIST.
4281  *
4282  * For implementations of %TP_TYPE_CONTACT_GROUP_LIST, this is a virtual
4283  * method, implemented using #TpContactGroupListInterface.dup_groups.
4284  * It must always be implemented.
4285  *
4286  * Returns: (array zero-terminated=1) (element-type utf8) (transfer full): an
4287  *  array of groups
4288  *
4289  * Since: 0.13.0
4290  */
4291 GStrv
tp_base_contact_list_dup_groups(TpBaseContactList * self)4292 tp_base_contact_list_dup_groups (TpBaseContactList *self)
4293 {
4294   TpContactGroupListInterface *iface =
4295     TP_CONTACT_GROUP_LIST_GET_INTERFACE (self);
4296 
4297   g_return_val_if_fail (iface != NULL, NULL);
4298   g_return_val_if_fail (iface->dup_groups != NULL, NULL);
4299   g_return_val_if_fail (tp_base_contact_list_get_state (self, NULL) ==
4300       TP_CONTACT_LIST_STATE_SUCCESS, NULL);
4301 
4302   return iface->dup_groups (self);
4303 }
4304 
4305 /**
4306  * TpBaseContactListDupContactGroupsFunc:
4307  * @self: a contact list manager
4308  * @contact: a non-zero contact handle
4309  *
4310  * Signature of a virtual method that lists the groups to which @contact
4311  * belongs.
4312  *
4313  * If @contact is not on the contact list, this method must return either
4314  * %NULL or an empty array, without error.
4315  *
4316  * Returns: (array zero-terminated=1) (element-type utf8) (transfer full): an
4317  *  array of groups
4318  *
4319  * Since: 0.13.0
4320  */
4321 
4322 /**
4323  * tp_base_contact_list_dup_contact_groups:
4324  * @self: a contact list manager
4325  * @contact: a contact handle
4326  *
4327  * Return a list of groups of which @contact is a member. It is incorrect to
4328  * call this method before tp_base_contact_list_set_list_received() has been
4329  * called, after the connection has disconnected, or on a #TpBaseContactList
4330  * that does not implement %TP_TYPE_CONTACT_GROUP_LIST.
4331  *
4332  * If @contact is not on the contact list, this method must return either
4333  * %NULL or an empty array.
4334  *
4335  * For implementations of %TP_TYPE_CONTACT_GROUP_LIST, this is a virtual
4336  * method, implemented using #TpContactGroupListInterface.dup_contact_groups.
4337  * It must always be implemented.
4338  *
4339  * Returns: (array zero-terminated=1) (element-type utf8) (transfer full): an
4340  *  array of groups
4341  *
4342  * Since: 0.13.0
4343  */
4344 GStrv
tp_base_contact_list_dup_contact_groups(TpBaseContactList * self,TpHandle contact)4345 tp_base_contact_list_dup_contact_groups (TpBaseContactList *self,
4346     TpHandle contact)
4347 {
4348   TpContactGroupListInterface *iface =
4349     TP_CONTACT_GROUP_LIST_GET_INTERFACE (self);
4350 
4351   g_return_val_if_fail (iface != NULL, NULL);
4352   g_return_val_if_fail (iface->dup_contact_groups != NULL, NULL);
4353   g_return_val_if_fail (tp_base_contact_list_get_state (self, NULL) ==
4354       TP_CONTACT_LIST_STATE_SUCCESS, NULL);
4355 
4356   return iface->dup_contact_groups (self, contact);
4357 }
4358 
4359 /**
4360  * TpBaseContactListDupGroupMembersFunc:
4361  * @self: a contact list manager
4362  * @group: a normalized group name
4363  *
4364  * Signature of a virtual method that lists the members of a group.
4365  *
4366  * Returns: (transfer full): a set of contact (%TP_HANDLE_TYPE_CONTACT) handles
4367  *
4368  * Since: 0.13.0
4369  */
4370 
4371 /**
4372  * tp_base_contact_list_dup_group_members:
4373  * @self: a contact list manager
4374  * @group: a normalized group name
4375  *
4376  * Return the set of members of @group. It is incorrect to
4377  * call this method before tp_base_contact_list_set_list_received() has been
4378  * called, after the connection has disconnected, or on a #TpBaseContactList
4379  * that does not implement %TP_TYPE_CONTACT_GROUP_LIST.
4380  *
4381  * If @group does not exist, this method must return either %NULL or an empty
4382  * set, without error.
4383  *
4384  * For implementations of %TP_TYPE_CONTACT_GROUP_LIST, this is a virtual
4385  * method, implemented using #TpContactGroupListInterface.dup_group_members.
4386  * It must always be implemented.
4387  *
4388  * Returns: a set of contact (%TP_HANDLE_TYPE_CONTACT) handles
4389  *
4390  * Since: 0.13.0
4391  */
4392 TpHandleSet *
tp_base_contact_list_dup_group_members(TpBaseContactList * self,const gchar * group)4393 tp_base_contact_list_dup_group_members (TpBaseContactList *self,
4394     const gchar *group)
4395 {
4396   TpContactGroupListInterface *iface =
4397     TP_CONTACT_GROUP_LIST_GET_INTERFACE (self);
4398 
4399   g_return_val_if_fail (iface != NULL, NULL);
4400   g_return_val_if_fail (iface->dup_group_members != NULL, NULL);
4401   g_return_val_if_fail (tp_base_contact_list_get_state (self, NULL) ==
4402       TP_CONTACT_LIST_STATE_SUCCESS, NULL);
4403 
4404   return iface->dup_group_members (self, group);
4405 }
4406 
4407 /**
4408  * TpBaseContactListGroupContactsFunc:
4409  * @self: a contact list manager
4410  * @group: a group
4411  * @contacts: a set of contact handles
4412  * @callback: a callback to call on success, failure or disconnection
4413  * @user_data: user data for the callback
4414  *
4415  * Signature of a virtual method that alters a group's members.
4416  *
4417  * Since: 0.13.0
4418  */
4419 
4420 /**
4421  * tp_base_contact_list_add_to_group_async:
4422  * @self: a contact list manager
4423  * @group: the normalized name of a group
4424  * @contacts: some contacts (may be an empty set)
4425  * @callback: a callback to call on success, failure or disconnection
4426  * @user_data: user data for the callback
4427  *
4428  * Add @contacts to @group, creating it if necessary.
4429  *
4430  * If @group does not exist, the implementation should create it, even if
4431  * @contacts is empty.
4432  *
4433  * If the #TpBaseContactList subclass does not implement
4434  * %TP_TYPE_MUTABLE_CONTACT_GROUP_LIST, it is an error to call this method.
4435  *
4436  * For implementations of %TP_TYPE_MUTABLE_CONTACT_GROUP_LIST, this is a
4437  * virtual method which must be implemented, using
4438  * #TpMutableContactGroupListInterface.add_to_group_async.
4439  * The implementation should call tp_base_contact_list_groups_changed()
4440  * for any changes it successfully made, before calling @callback.
4441  *
4442  * Since: 0.13.0
4443  */
4444 void
tp_base_contact_list_add_to_group_async(TpBaseContactList * self,const gchar * group,TpHandleSet * contacts,GAsyncReadyCallback callback,gpointer user_data)4445 tp_base_contact_list_add_to_group_async (TpBaseContactList *self,
4446     const gchar *group,
4447     TpHandleSet *contacts,
4448     GAsyncReadyCallback callback,
4449     gpointer user_data)
4450 {
4451   TpMutableContactGroupListInterface *iface;
4452 
4453   iface = TP_MUTABLE_CONTACT_GROUP_LIST_GET_INTERFACE (self);
4454   g_return_if_fail (iface != NULL);
4455   g_return_if_fail (iface->add_to_group_async != NULL);
4456 
4457   iface->add_to_group_async (self, group, contacts, callback, user_data);
4458 }
4459 
4460 /**
4461  * tp_base_contact_list_add_to_group_finish:
4462  * @self: a contact list manager
4463  * @result: the result passed to @callback by an implementation of
4464  *  tp_base_contact_list_add_to_group_async()
4465  * @error: used to raise an error if %FALSE is returned
4466  *
4467  * Interpret the result of an asynchronous call to
4468  * tp_base_contact_list_add_to_group_async().
4469  *
4470  * If the #TpBaseContactList subclass does not implement
4471  * %TP_TYPE_MUTABLE_CONTACT_GROUP_LIST, it is an error to call this method.
4472  *
4473  * For implementations of %TP_TYPE_MUTABLE_CONTACT_GROUP_LIST, this is a
4474  * virtual method which may be implemented using
4475  * #TpMutableContactGroupListInterface.add_to_group_finish. If the @result
4476  * will be a #GSimpleAsyncResult, the default implementation may be used.
4477  *
4478  * Returns: %TRUE on success or %FALSE on error
4479  *
4480  * Since: 0.13.0
4481  */
4482 gboolean
tp_base_contact_list_add_to_group_finish(TpBaseContactList * self,GAsyncResult * result,GError ** error)4483 tp_base_contact_list_add_to_group_finish (TpBaseContactList *self,
4484     GAsyncResult *result,
4485     GError **error)
4486 {
4487   TpMutableContactGroupListInterface *mutable_groups_iface;
4488 
4489   mutable_groups_iface = TP_MUTABLE_CONTACT_GROUP_LIST_GET_INTERFACE (self);
4490   g_return_val_if_fail (mutable_groups_iface != NULL, FALSE);
4491   g_return_val_if_fail (mutable_groups_iface->add_to_group_finish != NULL,
4492       FALSE);
4493 
4494   return mutable_groups_iface->add_to_group_finish (self, result, error);
4495 }
4496 
4497 /**
4498  * TpBaseContactListRenameGroupFunc:
4499  * @self: a contact list manager
4500  * @old_name: a group
4501  * @new_name: a new name for the group
4502  * @callback: a callback to call on success, failure or disconnection
4503  * @user_data: user data for the callback
4504  *
4505  * Signature of a method that renames groups.
4506  *
4507  * Since: 0.13.0
4508  */
4509 
4510 /**
4511  * tp_base_contact_list_rename_group_async:
4512  * @self: a contact list manager
4513  * @old_name: the normalized name of a group, which must exist
4514  * @new_name: a new normalized name for the group name
4515  * @callback: a callback to call on success, failure or disconnection
4516  * @user_data: user data for the callback
4517  *
4518  * Rename a group; if possible, do so as an atomic operation. If this
4519  * protocol can't do that, emulate renaming in terms of other operations.
4520  *
4521  * If the #TpBaseContactList subclass does not implement
4522  * %TP_TYPE_MUTABLE_CONTACT_GROUP_LIST, it is an error to call this method.
4523  *
4524  * For implementations of %TP_TYPE_MUTABLE_CONTACT_GROUP_LIST, this is a
4525  * virtual method which may be implemented, using
4526  * #TpMutableContactGroupListInterface.rename_group_async.
4527  *
4528  * If this virtual method is not implemented, the default is to implement
4529  * renaming a group as creating the new group, adding all the old group's
4530  * members to it, and removing the old group: this is appropriate for protocols
4531  * like XMPP, in which groups behave more like tags.
4532  *
4533  * The implementation should call tp_base_contact_list_group_renamed() before
4534  * calling @callback.
4535  *
4536  * Since: 0.13.0
4537  */
4538 void
tp_base_contact_list_rename_group_async(TpBaseContactList * self,const gchar * old_name,const gchar * new_name,GAsyncReadyCallback callback,gpointer user_data)4539 tp_base_contact_list_rename_group_async (TpBaseContactList *self,
4540     const gchar *old_name,
4541     const gchar *new_name,
4542     GAsyncReadyCallback callback,
4543     gpointer user_data)
4544 {
4545   TpMutableContactGroupListInterface *iface;
4546 
4547   g_return_if_fail (TP_IS_BASE_CONTACT_LIST (self));
4548   iface = TP_MUTABLE_CONTACT_GROUP_LIST_GET_INTERFACE (self);
4549   g_return_if_fail (iface != NULL);
4550   g_return_if_fail (iface->rename_group_async != NULL);
4551 
4552   iface->rename_group_async (self, old_name, new_name, callback,
4553       user_data);
4554 }
4555 
4556 static void
emulate_rename_group_remove_cb(GObject * source,GAsyncResult * result,gpointer user_data)4557 emulate_rename_group_remove_cb (GObject *source,
4558     GAsyncResult *result,
4559     gpointer user_data)
4560 {
4561   TpBaseContactList *self = TP_BASE_CONTACT_LIST (source);
4562   GSimpleAsyncResult *rename_result = user_data;
4563   GError *error = NULL;
4564 
4565   if (!tp_base_contact_list_remove_group_finish (self, result, &error))
4566     {
4567       g_simple_async_result_set_from_error (rename_result, error);
4568       g_clear_error (&error);
4569     }
4570 
4571   g_simple_async_result_complete (rename_result);
4572   g_object_unref (rename_result);
4573 }
4574 
4575 static void
emulate_rename_group_add_cb(GObject * source,GAsyncResult * result,gpointer user_data)4576 emulate_rename_group_add_cb (GObject *source,
4577     GAsyncResult *result,
4578     gpointer user_data)
4579 {
4580   TpBaseContactList *self = TP_BASE_CONTACT_LIST (source);
4581   GSimpleAsyncResult *rename_result = user_data;
4582   GError *error = NULL;
4583 
4584   if (!tp_base_contact_list_add_to_group_finish (self, result, &error))
4585     {
4586       g_simple_async_result_set_from_error (rename_result, error);
4587       g_clear_error (&error);
4588       g_simple_async_result_complete (rename_result);
4589       goto out;
4590     }
4591 
4592   tp_base_contact_list_remove_group_async (self,
4593       g_simple_async_result_get_op_res_gpointer (rename_result),
4594       emulate_rename_group_remove_cb, g_object_ref (rename_result));
4595 
4596 out:
4597   g_object_unref (rename_result);
4598 }
4599 
4600 static void
tp_base_contact_list_emulate_rename_group(TpBaseContactList * self,const gchar * old_name,const gchar * new_name,GAsyncReadyCallback callback,gpointer user_data)4601 tp_base_contact_list_emulate_rename_group (TpBaseContactList *self,
4602     const gchar *old_name,
4603     const gchar *new_name,
4604     GAsyncReadyCallback callback,
4605     gpointer user_data)
4606 {
4607   TpHandle old_handle;
4608   gpointer old_channel;
4609   GSimpleAsyncResult *result;
4610   TpHandleSet *old_members;
4611 
4612   old_handle = tp_handle_lookup (self->priv->group_repo, old_name, NULL,
4613       NULL);
4614   g_return_if_fail (old_handle != 0);
4615   old_channel = g_hash_table_lookup (self->priv->groups,
4616       GUINT_TO_POINTER (old_handle));
4617   g_return_if_fail (old_channel != NULL);
4618 
4619   result = g_simple_async_result_new ((GObject *) self, callback, user_data,
4620       tp_base_contact_list_emulate_rename_group);
4621 
4622   /* not really the operation result, just some extra data */
4623   g_simple_async_result_set_op_res_gpointer (result, g_strdup (old_name),
4624       g_free);
4625 
4626   old_members = tp_base_contact_list_dup_group_members (self, old_name);
4627   tp_base_contact_list_add_to_group_async (self, new_name, old_members,
4628       emulate_rename_group_add_cb, g_object_ref (result));
4629   g_object_unref (result);
4630   tp_handle_set_destroy (old_members);
4631 }
4632 
4633 /**
4634  * tp_base_contact_list_rename_group_finish:
4635  * @self: a contact list manager
4636  * @result: the result passed to @callback by an implementation of
4637  *  tp_base_contact_list_rename_group_async()
4638  * @error: used to raise an error if %FALSE is returned
4639  *
4640  * Interpret the result of an asynchronous call to
4641  * tp_base_contact_list_rename_group_async().
4642  *
4643  * If the #TpBaseContactList subclass does not implement
4644  * %TP_TYPE_MUTABLE_CONTACT_GROUP_LIST, it is an error to call this method.
4645  *
4646  * For implementations of %TP_TYPE_MUTABLE_CONTACT_GROUP_LIST, this is a
4647  * virtual method which may be implemented using
4648  * #TpMutableContactGroupListInterface.rename_group_finish. If the @result
4649  * will be a #GSimpleAsyncResult, the default implementation may be used.
4650  *
4651  * Returns: %TRUE on success or %FALSE on error
4652  *
4653  * Since: 0.13.0
4654  */
4655 gboolean
tp_base_contact_list_rename_group_finish(TpBaseContactList * self,GAsyncResult * result,GError ** error)4656 tp_base_contact_list_rename_group_finish (TpBaseContactList *self,
4657     GAsyncResult *result,
4658     GError **error)
4659 {
4660   TpMutableContactGroupListInterface *mutable_groups_iface;
4661 
4662   mutable_groups_iface = TP_MUTABLE_CONTACT_GROUP_LIST_GET_INTERFACE (self);
4663   g_return_val_if_fail (mutable_groups_iface != NULL, FALSE);
4664   g_return_val_if_fail (mutable_groups_iface->rename_group_finish != NULL,
4665       FALSE);
4666 
4667   return mutable_groups_iface->rename_group_finish (self, result, error);
4668 }
4669 
4670 /**
4671  * tp_base_contact_list_remove_from_group_async:
4672  * @self: a contact list manager
4673  * @group: the normalized name of a group
4674  * @contacts: some contacts
4675  * @callback: a callback to call on success, failure or disconnection
4676  * @user_data: user data for the callback
4677  *
4678  * Remove @contacts from @group.
4679  *
4680  * If the #TpBaseContactList subclass does not implement
4681  * %TP_TYPE_MUTABLE_CONTACT_GROUP_LIST, it is an error to call this method.
4682  *
4683  * For implementations of %TP_TYPE_MUTABLE_CONTACT_GROUP_LIST, this is a
4684  * virtual method which must be implemented, using
4685  * #TpMutableContactGroupListInterface.remove_from_group_async.
4686  * The implementation should call tp_base_contact_list_groups_changed()
4687  * for any changes it successfully made, before calling @callback.
4688  *
4689  * Since: 0.13.0
4690  */
tp_base_contact_list_remove_from_group_async(TpBaseContactList * self,const gchar * group,TpHandleSet * contacts,GAsyncReadyCallback callback,gpointer user_data)4691 void tp_base_contact_list_remove_from_group_async (TpBaseContactList *self,
4692     const gchar *group,
4693     TpHandleSet *contacts,
4694     GAsyncReadyCallback callback,
4695     gpointer user_data)
4696 {
4697   TpMutableContactGroupListInterface *iface;
4698 
4699   iface = TP_MUTABLE_CONTACT_GROUP_LIST_GET_INTERFACE (self);
4700   g_return_if_fail (iface != NULL);
4701   g_return_if_fail (iface->remove_from_group_async != NULL);
4702 
4703   iface->remove_from_group_async (self, group, contacts, callback, user_data);
4704 }
4705 
4706 /**
4707  * tp_base_contact_list_remove_from_group_finish:
4708  * @self: a contact list manager
4709  * @result: the result passed to @callback by an implementation of
4710  *  tp_base_contact_list_remove_from_group_async()
4711  * @error: used to raise an error if %FALSE is returned
4712  *
4713  * Interpret the result of an asynchronous call to
4714  * tp_base_contact_list_remove_from_group_async().
4715  *
4716  * If the #TpBaseContactList subclass does not implement
4717  * %TP_TYPE_MUTABLE_CONTACT_GROUP_LIST, it is an error to call this method.
4718  *
4719  * For implementations of %TP_TYPE_MUTABLE_CONTACT_GROUP_LIST, this is a
4720  * virtual method which may be implemented using
4721  * #TpMutableContactGroupListInterface.remove_from_group_finish. If the @result
4722  * will be a #GSimpleAsyncResult, the default implementation may be used.
4723  *
4724  * Returns: %TRUE on success or %FALSE on error
4725  *
4726  * Since: 0.13.0
4727  */
4728 gboolean
tp_base_contact_list_remove_from_group_finish(TpBaseContactList * self,GAsyncResult * result,GError ** error)4729 tp_base_contact_list_remove_from_group_finish (TpBaseContactList *self,
4730     GAsyncResult *result,
4731     GError **error)
4732 {
4733   TpMutableContactGroupListInterface *mutable_groups_iface;
4734 
4735   mutable_groups_iface = TP_MUTABLE_CONTACT_GROUP_LIST_GET_INTERFACE (self);
4736   g_return_val_if_fail (mutable_groups_iface != NULL, FALSE);
4737   g_return_val_if_fail (mutable_groups_iface->remove_from_group_finish != NULL,
4738       FALSE);
4739 
4740   return mutable_groups_iface->remove_from_group_finish (self, result, error);
4741 }
4742 
4743 /**
4744  * TpBaseContactListRemoveGroupFunc:
4745  * @self: a contact list manager
4746  * @group: the normalized name of a group
4747  * @callback: a callback to call on success, failure or disconnection
4748  * @user_data: user data for the callback
4749  *
4750  * Signature of a method that deletes groups.
4751  *
4752  * Since: 0.13.0
4753  */
4754 
4755 /**
4756  * tp_base_contact_list_remove_group_async:
4757  * @self: a contact list manager
4758  * @group: the normalized name of a group
4759  * @callback: a callback to call on success, failure or disconnection
4760  * @user_data: user data for the callback
4761  *
4762  * Remove a group entirely, removing any members in the process.
4763  *
4764  * If the #TpBaseContactList subclass does not implement
4765  * %TP_TYPE_MUTABLE_CONTACT_GROUP_LIST, it is an error to call this method.
4766  *
4767  * For implementations of %TP_TYPE_MUTABLE_CONTACT_GROUP_LIST, this is a
4768  * virtual method which must be implemented, using
4769  * #TpMutableContactGroupListInterface.remove_group_async.
4770  * The implementation should call tp_base_contact_list_groups_removed()
4771  * for any groups it successfully removed, before calling @callback.
4772  *
4773  * Since: 0.13.0
4774  */
4775 void
tp_base_contact_list_remove_group_async(TpBaseContactList * self,const gchar * group,GAsyncReadyCallback callback,gpointer user_data)4776 tp_base_contact_list_remove_group_async (TpBaseContactList *self,
4777     const gchar *group,
4778     GAsyncReadyCallback callback,
4779     gpointer user_data)
4780 {
4781   TpMutableContactGroupListInterface *mutable_group_iface;
4782 
4783   mutable_group_iface = TP_MUTABLE_CONTACT_GROUP_LIST_GET_INTERFACE (self);
4784   g_return_if_fail (mutable_group_iface != NULL);
4785   g_return_if_fail (mutable_group_iface->remove_group_async != NULL);
4786 
4787   mutable_group_iface->remove_group_async (self, group, callback, user_data);
4788 }
4789 
4790 /**
4791  * tp_base_contact_list_remove_group_finish:
4792  * @self: a contact list manager
4793  * @result: the result passed to @callback by an implementation of
4794  *  tp_base_contact_list_remove_group_async()
4795  * @error: used to raise an error if %FALSE is returned
4796  *
4797  * Interpret the result of an asynchronous call to
4798  * tp_base_contact_list_remove_group_async().
4799  *
4800  * If the #TpBaseContactList subclass does not implement
4801  * %TP_TYPE_MUTABLE_CONTACT_GROUP_LIST, it is an error to call this method.
4802  *
4803  * For implementations of %TP_TYPE_MUTABLE_CONTACT_GROUP_LIST, this is a
4804  * virtual method which may be implemented using
4805  * #TpMutableContactGroupListInterface.remove_group_finish. If the @result
4806  * will be a #GSimpleAsyncResult, the default implementation may be used.
4807  *
4808  * Returns: %TRUE on success or %FALSE on error
4809  *
4810  * Since: 0.13.0
4811  */
4812 gboolean
tp_base_contact_list_remove_group_finish(TpBaseContactList * self,GAsyncResult * result,GError ** error)4813 tp_base_contact_list_remove_group_finish (TpBaseContactList *self,
4814     GAsyncResult *result,
4815     GError **error)
4816 {
4817   TpMutableContactGroupListInterface *mutable_groups_iface;
4818 
4819   mutable_groups_iface = TP_MUTABLE_CONTACT_GROUP_LIST_GET_INTERFACE (self);
4820   g_return_val_if_fail (mutable_groups_iface != NULL, FALSE);
4821   g_return_val_if_fail (mutable_groups_iface->remove_group_finish != NULL,
4822       FALSE);
4823 
4824   return mutable_groups_iface->remove_group_finish (self, result, error);
4825 }
4826 
4827 static void
tp_base_contact_list_mixin_get_contact_list_attributes(TpSvcConnectionInterfaceContactList * svc,const gchar ** interfaces,gboolean hold,DBusGMethodInvocation * context)4828 tp_base_contact_list_mixin_get_contact_list_attributes (
4829     TpSvcConnectionInterfaceContactList *svc,
4830     const gchar **interfaces,
4831     gboolean hold,
4832     DBusGMethodInvocation *context)
4833 {
4834   TpBaseContactList *self = _tp_base_connection_find_channel_manager (
4835       (TpBaseConnection *) svc, TP_TYPE_BASE_CONTACT_LIST);
4836   TpContactsMixin *contacts_mixin = TP_CONTACTS_MIXIN (svc);
4837   GError *error = NULL;
4838 
4839   g_return_if_fail (TP_IS_BASE_CONTACT_LIST (self));
4840   g_return_if_fail (contacts_mixin != NULL);
4841 
4842   if (tp_base_contact_list_get_state (self, &error)
4843       != TP_CONTACT_LIST_STATE_SUCCESS)
4844     {
4845       dbus_g_method_return_error (context, error);
4846       g_clear_error (&error);
4847     }
4848   else
4849     {
4850       TpHandleSet *set;
4851       GArray *contacts;
4852       const gchar *assumed[] = { TP_IFACE_CONNECTION,
4853           TP_IFACE_CONNECTION_INTERFACE_CONTACT_LIST, NULL };
4854       gchar *sender = NULL;
4855       GHashTable *result;
4856 
4857       if (hold)
4858         sender = dbus_g_method_get_sender (context);
4859 
4860       set = tp_base_contact_list_dup_contacts (self);
4861       contacts = tp_handle_set_to_array (set);
4862       result = tp_contacts_mixin_get_contact_attributes (
4863           (GObject *) self->priv->conn, contacts, interfaces, assumed,
4864           sender);
4865       tp_svc_connection_interface_contact_list_return_from_get_contact_list_attributes (
4866           context, result);
4867 
4868       g_array_unref (contacts);
4869       tp_handle_set_destroy (set);
4870       g_free (sender);
4871       g_hash_table_unref (result);
4872     }
4873 }
4874 
4875 /**
4876  * TpBaseContactListSetContactGroupsFunc:
4877  * @self: a contact list manager
4878  * @contact: a contact handle
4879  * @normalized_names: (array length=n_names): the normalized names of some
4880  *  groups
4881  * @n_names: the number of groups
4882  * @callback: a callback to call on success, failure or disconnection
4883  * @user_data: user data for the callback
4884  *
4885  * Signature of an implementation of
4886  * tp_base_contact_list_set_contact_groups_async().
4887  *
4888  * Since: 0.13.0
4889  */
4890 
4891 /**
4892  * tp_base_contact_list_set_contact_groups_async:
4893  * @self: a contact list manager
4894  * @contact: a contact handle
4895  * @normalized_names: (array length=n_names): the normalized names of some
4896  *  groups
4897  * @n_names: the number of groups
4898  * @callback: a callback to call on success, failure or disconnection
4899  * @user_data: user data for the callback
4900  *
4901  * Add @contact to each group in @normalized_names, creating them if necessary,
4902  * and remove @contact from any other groups of which they are a member.
4903  *
4904  * If the #TpBaseContactList subclass does not implement
4905  * %TP_TYPE_MUTABLE_CONTACT_GROUP_LIST, it is an error to call this method.
4906  *
4907  * For implementations of %TP_TYPE_MUTABLE_CONTACT_GROUP_LIST, this is a
4908  * virtual method which must be implemented, using
4909  * #TpMutableContactGroupListInterface.set_contact_groups_async.
4910  * The implementation should call tp_base_contact_list_groups_changed()
4911  * for any changes it successfully made, before returning.
4912  *
4913  * Since: 0.13.0
4914  */
tp_base_contact_list_set_contact_groups_async(TpBaseContactList * self,TpHandle contact,const gchar * const * normalized_names,gsize n_names,GAsyncReadyCallback callback,gpointer user_data)4915 void tp_base_contact_list_set_contact_groups_async (TpBaseContactList *self,
4916     TpHandle contact,
4917     const gchar * const *normalized_names,
4918     gsize n_names,
4919     GAsyncReadyCallback callback,
4920     gpointer user_data)
4921 {
4922   TpMutableContactGroupListInterface *mutable_groups_iface;
4923 
4924   mutable_groups_iface = TP_MUTABLE_CONTACT_GROUP_LIST_GET_INTERFACE (self);
4925   g_return_if_fail (mutable_groups_iface != NULL);
4926   g_return_if_fail (mutable_groups_iface->set_contact_groups_async != NULL);
4927 
4928   mutable_groups_iface->set_contact_groups_async (self, contact,
4929       normalized_names, n_names, callback, user_data);
4930 }
4931 
4932 /**
4933  * tp_base_contact_list_set_contact_groups_finish:
4934  * @self: a contact list manager
4935  * @result: the result passed to @callback by an implementation of
4936  *  tp_base_contact_list_set_contact_groups_async()
4937  * @error: used to raise an error if %FALSE is returned
4938  *
4939  * Interpret the result of an asynchronous call to
4940  * tp_base_contact_list_set_contact_groups_async().
4941  *
4942  * If the #TpBaseContactList subclass does not implement
4943  * %TP_TYPE_MUTABLE_CONTACT_GROUP_LIST, it is an error to call this method.
4944  *
4945  * For implementations of %TP_TYPE_MUTABLE_CONTACT_GROUP_LIST, this is a
4946  * virtual method which may be implemented using
4947  * #TpMutableContactGroupListInterface.set_contact_groups_finish. If the
4948  * @result will be a #GSimpleAsyncResult, the default implementation may be
4949  * used.
4950  *
4951  * Returns: %TRUE on success or %FALSE on error
4952  *
4953  * Since: 0.13.0
4954  */
4955 gboolean
tp_base_contact_list_set_contact_groups_finish(TpBaseContactList * self,GAsyncResult * result,GError ** error)4956 tp_base_contact_list_set_contact_groups_finish (TpBaseContactList *self,
4957     GAsyncResult *result,
4958     GError **error)
4959 {
4960   TpMutableContactGroupListInterface *mutable_groups_iface;
4961 
4962   mutable_groups_iface = TP_MUTABLE_CONTACT_GROUP_LIST_GET_INTERFACE (self);
4963   g_return_val_if_fail (mutable_groups_iface != NULL, FALSE);
4964   g_return_val_if_fail (mutable_groups_iface->set_contact_groups_finish !=
4965       NULL, FALSE);
4966 
4967   return mutable_groups_iface->set_contact_groups_finish (self, result, error);
4968 }
4969 
4970 /**
4971  * tp_base_contact_list_set_group_members_async:
4972  * @self: a contact list manager
4973  * @normalized_group: the normalized name of a group
4974  * @contacts: the contacts who should be in the group
4975  * @callback: a callback to call on success, failure or disconnection
4976  * @user_data: user data for the callback
4977  *
4978  * Set the members of @normalized_group to be exactly @contacts (i.e.
4979  * add @contacts, and simultaneously remove all members not in @contacts).
4980  *
4981  * If @normalized_group does not exist, the implementation should create it,
4982  * even if @contacts is empty.
4983  *
4984  * If the #TpBaseContactList subclass does not implement
4985  * %TP_TYPE_MUTABLE_CONTACT_GROUP_LIST, it is an error to call this method.
4986  *
4987  * For implementations of %TP_TYPE_MUTABLE_CONTACT_GROUP_LIST, this is a
4988  * virtual method which must be implemented, using
4989  * #TpMutableContactGroupListInterface.set_group_members_async.
4990  * The implementation should call tp_base_contact_list_groups_changed()
4991  * for any changes it successfully made, before calling @callback.
4992  *
4993  * Since: 0.13.0
4994  */
4995 void
tp_base_contact_list_set_group_members_async(TpBaseContactList * self,const gchar * normalized_group,TpHandleSet * contacts,GAsyncReadyCallback callback,gpointer user_data)4996 tp_base_contact_list_set_group_members_async (TpBaseContactList *self,
4997     const gchar *normalized_group,
4998     TpHandleSet *contacts,
4999     GAsyncReadyCallback callback,
5000     gpointer user_data)
5001 {
5002   TpMutableContactGroupListInterface *mutable_groups_iface;
5003 
5004   mutable_groups_iface = TP_MUTABLE_CONTACT_GROUP_LIST_GET_INTERFACE (self);
5005   g_return_if_fail (mutable_groups_iface != NULL);
5006   g_return_if_fail (mutable_groups_iface->set_group_members_async != NULL);
5007 
5008   mutable_groups_iface->set_group_members_async (self, normalized_group,
5009       contacts, callback, user_data);
5010 }
5011 
5012 /**
5013  * tp_base_contact_list_set_group_members_finish:
5014  * @self: a contact list manager
5015  * @result: the result passed to @callback by an implementation of
5016  *  tp_base_contact_list_set_group_members_async()
5017  * @error: used to raise an error if %FALSE is returned
5018  *
5019  * Interpret the result of an asynchronous call to
5020  * tp_base_contact_list_set_group_members_async().
5021  *
5022  * If the #TpBaseContactList subclass does not implement
5023  * %TP_TYPE_MUTABLE_CONTACT_GROUP_LIST, it is an error to call this method.
5024  *
5025  * For implementations of %TP_TYPE_MUTABLE_CONTACT_GROUP_LIST, this is a virtual
5026  * method which may be implemented using
5027  * #TpMutableContactGroupListInterface.set_group_members_finish. If the @result
5028  * will be a #GSimpleAsyncResult, the default implementation may be used.
5029  *
5030  * Returns: %TRUE on success or %FALSE on error
5031  *
5032  * Since: 0.13.0
5033  */
5034 gboolean
tp_base_contact_list_set_group_members_finish(TpBaseContactList * self,GAsyncResult * result,GError ** error)5035 tp_base_contact_list_set_group_members_finish (TpBaseContactList *self,
5036     GAsyncResult *result,
5037     GError **error)
5038 {
5039   TpMutableContactGroupListInterface *mutable_groups_iface;
5040 
5041   mutable_groups_iface = TP_MUTABLE_CONTACT_GROUP_LIST_GET_INTERFACE (self);
5042   g_return_val_if_fail (mutable_groups_iface != NULL, FALSE);
5043   g_return_val_if_fail (mutable_groups_iface->set_group_members_finish !=
5044       NULL, FALSE);
5045 
5046   return mutable_groups_iface->set_group_members_finish (self, result, error);
5047 }
5048 
5049 static gboolean
tp_base_contact_list_check_change(TpBaseContactList * self,const GArray * contacts_or_null,GError ** error)5050 tp_base_contact_list_check_change (TpBaseContactList *self,
5051     const GArray *contacts_or_null,
5052     GError **error)
5053 {
5054   g_return_val_if_fail (TP_IS_BASE_CONTACT_LIST (self), FALSE);
5055 
5056   if (tp_base_contact_list_get_state (self, error) !=
5057       TP_CONTACT_LIST_STATE_SUCCESS)
5058     return FALSE;
5059 
5060   if (contacts_or_null != NULL &&
5061       !tp_handles_are_valid (self->priv->contact_repo, contacts_or_null, FALSE,
5062         error))
5063     return FALSE;
5064 
5065   return TRUE;
5066 }
5067 
5068 static gboolean
tp_base_contact_list_check_list_change(TpBaseContactList * self,const GArray * contacts_or_null,GError ** error)5069 tp_base_contact_list_check_list_change (TpBaseContactList *self,
5070     const GArray *contacts_or_null,
5071     GError **error)
5072 {
5073   if (!tp_base_contact_list_check_change (self, contacts_or_null, error))
5074     return FALSE;
5075 
5076   if (!tp_base_contact_list_can_change_contact_list (self))
5077     {
5078       g_set_error (error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED,
5079           "Cannot change subscriptions");
5080       return FALSE;
5081     }
5082 
5083   return TRUE;
5084 }
5085 
5086 static gboolean
tp_base_contact_list_check_group_change(TpBaseContactList * self,const GArray * contacts_or_null,GError ** error)5087 tp_base_contact_list_check_group_change (TpBaseContactList *self,
5088     const GArray *contacts_or_null,
5089     GError **error)
5090 {
5091   if (!tp_base_contact_list_check_change (self, contacts_or_null, error))
5092     return FALSE;
5093 
5094   if (tp_base_contact_list_get_group_storage (self) ==
5095       TP_CONTACT_METADATA_STORAGE_TYPE_NONE)
5096     {
5097       g_set_error (error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED,
5098           "Cannot change group memberships");
5099       return FALSE;
5100     }
5101 
5102   return TRUE;
5103 }
5104 
5105 /* Normally we'd use the return_from functions, but these methods all return
5106  * void, and life's too short. */
5107 static void
tp_base_contact_list_mixin_return_void(DBusGMethodInvocation * context,const GError * error)5108 tp_base_contact_list_mixin_return_void (DBusGMethodInvocation *context,
5109     const GError *error)
5110 {
5111   if (error == NULL)
5112     dbus_g_method_return (context);
5113   else
5114     dbus_g_method_return_error (context, error);
5115 }
5116 
5117 static void
tp_base_contact_list_mixin_request_subscription_cb(GObject * source,GAsyncResult * result,gpointer context)5118 tp_base_contact_list_mixin_request_subscription_cb (GObject *source,
5119     GAsyncResult *result,
5120     gpointer context)
5121 {
5122   TpBaseContactList *self = TP_BASE_CONTACT_LIST (source);
5123   GError *error = NULL;
5124 
5125   tp_base_contact_list_request_subscription_finish (self, result, &error);
5126   tp_base_contact_list_mixin_return_void (context, error);
5127   g_clear_error (&error);
5128 }
5129 
5130 static void
tp_base_contact_list_mixin_request_subscription(TpSvcConnectionInterfaceContactList * svc,const GArray * contacts,const gchar * message,DBusGMethodInvocation * context)5131 tp_base_contact_list_mixin_request_subscription (
5132     TpSvcConnectionInterfaceContactList *svc,
5133     const GArray *contacts,
5134     const gchar *message,
5135     DBusGMethodInvocation *context)
5136 {
5137   TpBaseContactList *self = _tp_base_connection_find_channel_manager (
5138       (TpBaseConnection *) svc, TP_TYPE_BASE_CONTACT_LIST);
5139   GError *error = NULL;
5140   TpHandleSet *contacts_set;
5141 
5142   if (!tp_base_contact_list_check_list_change (self, contacts, &error))
5143     goto error;
5144 
5145   contacts_set = tp_handle_set_new_from_array (self->priv->contact_repo,
5146       contacts);
5147   tp_base_contact_list_request_subscription_async (self, contacts_set, message,
5148       tp_base_contact_list_mixin_request_subscription_cb, context);
5149   tp_handle_set_destroy (contacts_set);
5150   return;
5151 
5152 error:
5153   tp_base_contact_list_mixin_return_void (context, error);
5154   g_clear_error (&error);
5155 }
5156 
5157 static void
tp_base_contact_list_mixin_authorize_publication_cb(GObject * source,GAsyncResult * result,gpointer context)5158 tp_base_contact_list_mixin_authorize_publication_cb (GObject *source,
5159     GAsyncResult *result,
5160     gpointer context)
5161 {
5162   TpBaseContactList *self = TP_BASE_CONTACT_LIST (source);
5163   GError *error = NULL;
5164 
5165   tp_base_contact_list_authorize_publication_finish (self, result, &error);
5166   tp_base_contact_list_mixin_return_void (context, error);
5167   g_clear_error (&error);
5168 }
5169 
5170 static void
tp_base_contact_list_mixin_authorize_publication(TpSvcConnectionInterfaceContactList * svc,const GArray * contacts,DBusGMethodInvocation * context)5171 tp_base_contact_list_mixin_authorize_publication (
5172     TpSvcConnectionInterfaceContactList *svc,
5173     const GArray *contacts,
5174     DBusGMethodInvocation *context)
5175 {
5176   TpBaseContactList *self = _tp_base_connection_find_channel_manager (
5177       (TpBaseConnection *) svc, TP_TYPE_BASE_CONTACT_LIST);
5178   GError *error = NULL;
5179   TpHandleSet *contacts_set;
5180 
5181   if (!tp_base_contact_list_check_list_change (self, contacts, &error))
5182     goto error;
5183 
5184   contacts_set = tp_handle_set_new_from_array (self->priv->contact_repo,
5185       contacts);
5186   tp_base_contact_list_authorize_publication_async (self, contacts_set,
5187       tp_base_contact_list_mixin_authorize_publication_cb, context);
5188   tp_handle_set_destroy (contacts_set);
5189   return;
5190 
5191 error:
5192   tp_base_contact_list_mixin_return_void (context, error);
5193   g_clear_error (&error);
5194 }
5195 
5196 static void
tp_base_contact_list_mixin_remove_contacts_cb(GObject * source,GAsyncResult * result,gpointer context)5197 tp_base_contact_list_mixin_remove_contacts_cb (GObject *source,
5198     GAsyncResult *result,
5199     gpointer context)
5200 {
5201   TpBaseContactList *self = TP_BASE_CONTACT_LIST (source);
5202   GError *error = NULL;
5203 
5204   tp_base_contact_list_remove_contacts_finish (self, result, &error);
5205   tp_base_contact_list_mixin_return_void (context, error);
5206   g_clear_error (&error);
5207 }
5208 
5209 static void
tp_base_contact_list_mixin_remove_contacts(TpSvcConnectionInterfaceContactList * svc,const GArray * contacts,DBusGMethodInvocation * context)5210 tp_base_contact_list_mixin_remove_contacts (
5211     TpSvcConnectionInterfaceContactList *svc,
5212     const GArray *contacts,
5213     DBusGMethodInvocation *context)
5214 {
5215   TpBaseContactList *self = _tp_base_connection_find_channel_manager (
5216       (TpBaseConnection *) svc, TP_TYPE_BASE_CONTACT_LIST);
5217   GError *error = NULL;
5218   TpHandleSet *contacts_set;
5219 
5220   if (!tp_base_contact_list_check_list_change (self, contacts, &error))
5221     goto error;
5222 
5223   contacts_set = tp_handle_set_new_from_array (self->priv->contact_repo,
5224       contacts);
5225   tp_base_contact_list_remove_contacts_async (self, contacts_set,
5226       tp_base_contact_list_mixin_remove_contacts_cb, context);
5227   tp_handle_set_destroy (contacts_set);
5228   return;
5229 
5230 error:
5231   tp_base_contact_list_mixin_return_void (context, error);
5232   g_clear_error (&error);
5233 }
5234 
5235 static void
tp_base_contact_list_mixin_unsubscribe_cb(GObject * source,GAsyncResult * result,gpointer context)5236 tp_base_contact_list_mixin_unsubscribe_cb (GObject *source,
5237     GAsyncResult *result,
5238     gpointer context)
5239 {
5240   TpBaseContactList *self = TP_BASE_CONTACT_LIST (source);
5241   GError *error = NULL;
5242 
5243   tp_base_contact_list_unsubscribe_finish (self, result, &error);
5244   tp_base_contact_list_mixin_return_void (context, error);
5245   g_clear_error (&error);
5246 }
5247 
5248 static void
tp_base_contact_list_mixin_unsubscribe(TpSvcConnectionInterfaceContactList * svc,const GArray * contacts,DBusGMethodInvocation * context)5249 tp_base_contact_list_mixin_unsubscribe (
5250     TpSvcConnectionInterfaceContactList *svc,
5251     const GArray *contacts,
5252     DBusGMethodInvocation *context)
5253 {
5254   TpBaseContactList *self = _tp_base_connection_find_channel_manager (
5255       (TpBaseConnection *) svc, TP_TYPE_BASE_CONTACT_LIST);
5256   GError *error = NULL;
5257   TpHandleSet *contacts_set;
5258 
5259   if (!tp_base_contact_list_check_list_change (self, contacts, &error))
5260     goto error;
5261 
5262   contacts_set = tp_handle_set_new_from_array (self->priv->contact_repo,
5263       contacts);
5264   tp_base_contact_list_unsubscribe_async (self, contacts_set,
5265       tp_base_contact_list_mixin_unsubscribe_cb, context);
5266   tp_handle_set_destroy (contacts_set);
5267   return;
5268 
5269 error:
5270   tp_base_contact_list_mixin_return_void (context, error);
5271   g_clear_error (&error);
5272 }
5273 
5274 static void
tp_base_contact_list_mixin_unpublish_cb(GObject * source,GAsyncResult * result,gpointer context)5275 tp_base_contact_list_mixin_unpublish_cb (GObject *source,
5276     GAsyncResult *result,
5277     gpointer context)
5278 {
5279   TpBaseContactList *self = TP_BASE_CONTACT_LIST (source);
5280   GError *error = NULL;
5281 
5282   tp_base_contact_list_unpublish_finish (self, result, &error);
5283   tp_base_contact_list_mixin_return_void (context, error);
5284   g_clear_error (&error);
5285 }
5286 
5287 static void
tp_base_contact_list_mixin_unpublish(TpSvcConnectionInterfaceContactList * svc,const GArray * contacts,DBusGMethodInvocation * context)5288 tp_base_contact_list_mixin_unpublish (
5289     TpSvcConnectionInterfaceContactList *svc,
5290     const GArray *contacts,
5291     DBusGMethodInvocation *context)
5292 {
5293   TpBaseContactList *self = _tp_base_connection_find_channel_manager (
5294       (TpBaseConnection *) svc, TP_TYPE_BASE_CONTACT_LIST);
5295   GError *error = NULL;
5296   TpHandleSet *contacts_set;
5297 
5298   if (!tp_base_contact_list_check_list_change (self, contacts, &error))
5299     goto error;
5300 
5301   contacts_set = tp_handle_set_new_from_array (self->priv->contact_repo,
5302       contacts);
5303   tp_base_contact_list_unpublish_async (self, contacts_set,
5304       tp_base_contact_list_mixin_unpublish_cb, context);
5305   tp_handle_set_destroy (contacts_set);
5306   return;
5307 
5308 error:
5309   tp_base_contact_list_mixin_return_void (context, error);
5310   g_clear_error (&error);
5311 }
5312 
5313 typedef enum {
5314     LP_CONTACT_LIST_STATE,
5315     LP_CONTACT_LIST_PERSISTS,
5316     LP_CAN_CHANGE_CONTACT_LIST,
5317     LP_REQUEST_USES_MESSAGE,
5318     LP_DOWNLOAD_AT_CONNECTION,
5319     NUM_LIST_PROPERTIES
5320 } ListProp;
5321 
5322 static TpDBusPropertiesMixinPropImpl known_list_props[] = {
5323     { "ContactListState", GINT_TO_POINTER (LP_CONTACT_LIST_STATE), },
5324     { "ContactListPersists", GINT_TO_POINTER (LP_CONTACT_LIST_PERSISTS), },
5325     { "CanChangeContactList", GINT_TO_POINTER (LP_CAN_CHANGE_CONTACT_LIST) },
5326     { "RequestUsesMessage", GINT_TO_POINTER (LP_REQUEST_USES_MESSAGE) },
5327     { "DownloadAtConnection", GINT_TO_POINTER (LP_DOWNLOAD_AT_CONNECTION) },
5328     { NULL }
5329 };
5330 
5331 static void
tp_base_contact_list_get_list_dbus_property(GObject * conn,GQuark interface G_GNUC_UNUSED,GQuark name G_GNUC_UNUSED,GValue * value,gpointer data)5332 tp_base_contact_list_get_list_dbus_property (GObject *conn,
5333     GQuark interface G_GNUC_UNUSED,
5334     GQuark name G_GNUC_UNUSED,
5335     GValue *value,
5336     gpointer data)
5337 {
5338   TpBaseContactList *self = _tp_base_connection_find_channel_manager (
5339       (TpBaseConnection *) conn, TP_TYPE_BASE_CONTACT_LIST);
5340   ListProp p = GPOINTER_TO_INT (data);
5341 
5342   g_return_if_fail (TP_IS_BASE_CONTACT_LIST (self));
5343   g_return_if_fail (self->priv->conn != NULL);
5344 
5345   switch (p)
5346     {
5347     case LP_CONTACT_LIST_STATE:
5348       g_return_if_fail (G_VALUE_HOLDS_UINT (value));
5349       g_value_set_uint (value, self->priv->state);
5350       break;
5351 
5352     case LP_CONTACT_LIST_PERSISTS:
5353       g_return_if_fail (G_VALUE_HOLDS_BOOLEAN (value));
5354       g_value_set_boolean (value,
5355           tp_base_contact_list_get_contact_list_persists (self));
5356       break;
5357 
5358     case LP_CAN_CHANGE_CONTACT_LIST:
5359       g_return_if_fail (G_VALUE_HOLDS_BOOLEAN (value));
5360       g_value_set_boolean (value,
5361           tp_base_contact_list_can_change_contact_list (self));
5362       break;
5363 
5364     case LP_REQUEST_USES_MESSAGE:
5365       g_return_if_fail (G_VALUE_HOLDS_BOOLEAN (value));
5366       g_value_set_boolean (value,
5367           tp_base_contact_list_get_request_uses_message (self));
5368       break;
5369 
5370     case LP_DOWNLOAD_AT_CONNECTION:
5371       g_return_if_fail (G_VALUE_HOLDS_BOOLEAN (value));
5372       g_value_set_boolean (value, self->priv->download_at_connection);
5373       break;
5374 
5375     default:
5376       g_return_if_reached ();
5377     }
5378 }
5379 
5380 static void
tp_base_contact_list_fill_list_contact_attributes(GObject * obj,const GArray * contacts,GHashTable * attributes_hash)5381 tp_base_contact_list_fill_list_contact_attributes (GObject *obj,
5382   const GArray *contacts,
5383   GHashTable *attributes_hash)
5384 {
5385   TpBaseContactList *self = _tp_base_connection_find_channel_manager (
5386       (TpBaseConnection *) obj, TP_TYPE_BASE_CONTACT_LIST);
5387   guint i;
5388 
5389   g_return_if_fail (TP_IS_BASE_CONTACT_LIST (self));
5390   g_return_if_fail (self->priv->conn != NULL);
5391 
5392   /* just omit the attributes if the contact list hasn't come in yet */
5393   if (self->priv->state != TP_CONTACT_LIST_STATE_SUCCESS)
5394     return;
5395 
5396   for (i = 0; i < contacts->len; i++)
5397     {
5398       TpSubscriptionState subscribe = TP_SUBSCRIPTION_STATE_NO;
5399       TpSubscriptionState publish = TP_SUBSCRIPTION_STATE_NO;
5400       gchar *publish_request = NULL;
5401       TpHandle handle;
5402 
5403       handle = g_array_index (contacts, TpHandle, i);
5404 
5405       tp_base_contact_list_dup_states (self, handle,
5406           &subscribe, &publish, &publish_request);
5407 
5408       tp_contacts_mixin_set_contact_attribute (attributes_hash,
5409           handle, TP_TOKEN_CONNECTION_INTERFACE_CONTACT_LIST_PUBLISH,
5410           tp_g_value_slice_new_uint (publish));
5411 
5412       tp_contacts_mixin_set_contact_attribute (attributes_hash,
5413           handle, TP_TOKEN_CONNECTION_INTERFACE_CONTACT_LIST_SUBSCRIBE,
5414           tp_g_value_slice_new_uint (subscribe));
5415 
5416       if (tp_str_empty (publish_request) ||
5417           publish != TP_SUBSCRIPTION_STATE_ASK)
5418         {
5419           g_free (publish_request);
5420         }
5421       else
5422         {
5423           tp_contacts_mixin_set_contact_attribute (attributes_hash,
5424               handle, TP_TOKEN_CONNECTION_INTERFACE_CONTACT_LIST_PUBLISH_REQUEST,
5425               tp_g_value_slice_new_take_string (publish_request));
5426         }
5427     }
5428 }
5429 
5430 static void
tp_base_contact_list_mixin_download_cb(GObject * source,GAsyncResult * result,gpointer context)5431 tp_base_contact_list_mixin_download_cb (GObject *source,
5432     GAsyncResult *result,
5433     gpointer context)
5434 {
5435   TpBaseContactList *self = TP_BASE_CONTACT_LIST (source);
5436   GError *error = NULL;
5437 
5438   tp_base_contact_list_download_finish (self, result, &error);
5439   tp_base_contact_list_mixin_return_void (context, error);
5440   g_clear_error (&error);
5441 }
5442 
5443 static void
tp_base_contact_list_mixin_download(TpSvcConnectionInterfaceContactList * svc,DBusGMethodInvocation * context)5444 tp_base_contact_list_mixin_download (
5445     TpSvcConnectionInterfaceContactList *svc,
5446     DBusGMethodInvocation *context)
5447 {
5448   TpBaseContactList *self = _tp_base_connection_find_channel_manager (
5449       (TpBaseConnection *) svc, TP_TYPE_BASE_CONTACT_LIST);
5450 
5451   tp_base_contact_list_download_async (self,
5452       tp_base_contact_list_mixin_download_cb, context);
5453 }
5454 
5455 /**
5456  * tp_base_contact_list_mixin_list_iface_init:
5457  * @klass: the service-side D-Bus interface
5458  *
5459  * Use the #TpBaseContactList like a mixin, to implement the ContactList
5460  * D-Bus interface.
5461  *
5462  * This function should be passed to G_IMPLEMENT_INTERFACE() for
5463  * #TpSvcConnectionInterfaceContactList.
5464  *
5465  * Since: 0.13.0
5466  */
5467 void
tp_base_contact_list_mixin_list_iface_init(TpSvcConnectionInterfaceContactListClass * klass)5468 tp_base_contact_list_mixin_list_iface_init (
5469     TpSvcConnectionInterfaceContactListClass *klass)
5470 {
5471 #define IMPLEMENT(x) tp_svc_connection_interface_contact_list_implement_##x (\
5472   klass, tp_base_contact_list_mixin_##x)
5473   IMPLEMENT (get_contact_list_attributes);
5474   IMPLEMENT (request_subscription);
5475   IMPLEMENT (authorize_publication);
5476   IMPLEMENT (remove_contacts);
5477   IMPLEMENT (unsubscribe);
5478   IMPLEMENT (unpublish);
5479   IMPLEMENT (download);
5480 #undef IMPLEMENT
5481 }
5482 
5483 /**
5484  * TpBaseContactListUIntFunc:
5485  * @self: a contact list manager
5486  *
5487  * Signature of a virtual method that returns an unsigned integer result.
5488  * These are used for feature-discovery.
5489  *
5490  * Returns: an unsigned integer result
5491  *
5492  * Since: 0.13.0
5493  */
5494 
5495 /**
5496  * tp_base_contact_list_get_group_storage:
5497  * @self: a contact list manager
5498  *
5499  * Return the extent to which user-defined groups can be set in this protocol.
5500  * If this is %TP_CONTACT_METADATA_STORAGE_TYPE_NONE, methods that would alter
5501  * the group list will not be called.
5502  *
5503  * If the #TpBaseContactList subclass does not implement
5504  * %TP_TYPE_MUTABLE_CONTACT_GROUP_LIST, this method is meaningless, and always
5505  * returns %TP_CONTACT_METADATA_STORAGE_TYPE_NONE.
5506  *
5507  * For implementations of %TP_TYPE_MUTABLE_CONTACT_GROUP_LIST, this is a
5508  * virtual method, implemented using
5509  * #TpMutableContactGroupListInterface.get_group_storage.
5510  *
5511  * The default implementation is %NULL, which is treated as equivalent to an
5512  * implementation that always returns %TP_CONTACT_METADATA_STORAGE_TYPE_ANYONE.
5513  * A custom implementation can also be used.
5514  *
5515  * Returns: a #TpContactMetadataStorageType
5516  *
5517  * Since: 0.13.0
5518  */
5519 TpContactMetadataStorageType
tp_base_contact_list_get_group_storage(TpBaseContactList * self)5520 tp_base_contact_list_get_group_storage (TpBaseContactList *self)
5521 {
5522   TpMutableContactGroupListInterface *iface;
5523 
5524   g_return_val_if_fail (TP_IS_BASE_CONTACT_LIST (self),
5525       TP_CONTACT_METADATA_STORAGE_TYPE_NONE);
5526 
5527   if (!TP_IS_MUTABLE_CONTACT_GROUP_LIST (self))
5528     return TP_CONTACT_METADATA_STORAGE_TYPE_NONE;
5529 
5530   iface = TP_MUTABLE_CONTACT_GROUP_LIST_GET_INTERFACE (self);
5531   g_return_val_if_fail (iface != NULL, TP_CONTACT_METADATA_STORAGE_TYPE_NONE);
5532 
5533   if (iface->get_group_storage == NULL)
5534     return TP_CONTACT_METADATA_STORAGE_TYPE_ANYONE;
5535 
5536   return iface->get_group_storage (self);
5537 }
5538 
5539 static void
tp_base_contact_list_mixin_set_contact_groups_cb(GObject * source,GAsyncResult * result,gpointer context)5540 tp_base_contact_list_mixin_set_contact_groups_cb (GObject *source,
5541     GAsyncResult *result,
5542     gpointer context)
5543 {
5544   TpBaseContactList *self = TP_BASE_CONTACT_LIST (source);
5545   GError *error = NULL;
5546 
5547   tp_base_contact_list_set_contact_groups_finish (self, result, &error);
5548   tp_base_contact_list_mixin_return_void (context, error);
5549   g_clear_error (&error);
5550 }
5551 
5552 static void
tp_base_contact_list_mixin_set_contact_groups(TpSvcConnectionInterfaceContactGroups * svc,guint contact,const gchar ** groups,DBusGMethodInvocation * context)5553 tp_base_contact_list_mixin_set_contact_groups (
5554     TpSvcConnectionInterfaceContactGroups *svc,
5555     guint contact,
5556     const gchar **groups,
5557     DBusGMethodInvocation *context)
5558 {
5559   TpBaseContactList *self = _tp_base_connection_find_channel_manager (
5560       (TpBaseConnection *) svc, TP_TYPE_BASE_CONTACT_LIST);
5561   const gchar *empty_strv[] = { NULL };
5562   GError *error = NULL;
5563   TpHandleSet *group_set = NULL;
5564   GPtrArray *normalized_groups = NULL;
5565   guint i;
5566 
5567   if (!tp_base_contact_list_check_group_change (self, NULL, &error))
5568     goto finally;
5569 
5570   if (groups == NULL)
5571     groups = empty_strv;
5572 
5573   group_set = tp_handle_set_new (self->priv->group_repo);
5574   normalized_groups = g_ptr_array_sized_new (g_strv_length ((GStrv) groups));
5575 
5576   for (i = 0; groups[i] != NULL; i++)
5577     {
5578       TpHandle group_handle = tp_handle_ensure (self->priv->group_repo,
5579           groups[i], NULL, NULL);
5580 
5581       if (group_handle != 0)
5582         {
5583           g_ptr_array_add (normalized_groups,
5584               (gchar *) tp_handle_inspect (self->priv->group_repo,
5585                 group_handle));
5586           tp_handle_set_add (group_set, group_handle);
5587         }
5588       else
5589         {
5590           DEBUG ("group '%s' not valid, ignoring it", groups[i]);
5591         }
5592     }
5593 
5594   tp_base_contact_list_set_contact_groups_async (self, contact,
5595       (const gchar * const *) normalized_groups->pdata,
5596       normalized_groups->len,
5597       tp_base_contact_list_mixin_set_contact_groups_cb, context);
5598   context = NULL;     /* ownership transferred to callback */
5599 
5600 finally:
5601   tp_clear_pointer (&group_set, tp_handle_set_destroy);
5602   tp_clear_pointer (&normalized_groups, g_ptr_array_unref);
5603 
5604   if (context != NULL)
5605     tp_base_contact_list_mixin_return_void (context, error);
5606 
5607   g_clear_error (&error);
5608 }
5609 
5610 static void
tp_base_contact_list_mixin_set_group_members_cb(GObject * source,GAsyncResult * result,gpointer context)5611 tp_base_contact_list_mixin_set_group_members_cb (GObject *source,
5612     GAsyncResult *result,
5613     gpointer context)
5614 {
5615   TpBaseContactList *self = TP_BASE_CONTACT_LIST (source);
5616   GError *error = NULL;
5617 
5618   tp_base_contact_list_set_group_members_finish (self, result, &error);
5619   tp_base_contact_list_mixin_return_void (context, error);
5620   g_clear_error (&error);
5621 }
5622 
5623 static void
tp_base_contact_list_mixin_set_group_members(TpSvcConnectionInterfaceContactGroups * svc,const gchar * group,const GArray * contacts,DBusGMethodInvocation * context)5624 tp_base_contact_list_mixin_set_group_members (
5625     TpSvcConnectionInterfaceContactGroups *svc,
5626     const gchar *group,
5627     const GArray *contacts,
5628     DBusGMethodInvocation *context)
5629 {
5630   TpBaseContactList *self = _tp_base_connection_find_channel_manager (
5631       (TpBaseConnection *) svc, TP_TYPE_BASE_CONTACT_LIST);
5632   TpHandleSet *contacts_set = NULL;
5633   GError *error = NULL;
5634 
5635   if (!tp_base_contact_list_check_group_change (self, NULL, &error))
5636     goto error;
5637 
5638   contacts_set = tp_handle_set_new_from_array (self->priv->contact_repo,
5639       contacts);
5640   tp_base_contact_list_set_group_members_async (self,
5641       group, contacts_set, tp_base_contact_list_mixin_set_group_members_cb,
5642       context);
5643   tp_handle_set_destroy (contacts_set);
5644   return;
5645 
5646 error:
5647   tp_base_contact_list_mixin_return_void (context, error);
5648   g_clear_error (&error);
5649 }
5650 
5651 static void
tp_base_contact_list_mixin_add_to_group_cb(GObject * source,GAsyncResult * result,gpointer context)5652 tp_base_contact_list_mixin_add_to_group_cb (GObject *source,
5653     GAsyncResult *result,
5654     gpointer context)
5655 {
5656   TpBaseContactList *self = TP_BASE_CONTACT_LIST (source);
5657   GError *error = NULL;
5658 
5659   tp_base_contact_list_add_to_group_finish (self, result, &error);
5660   tp_base_contact_list_mixin_return_void (context, error);
5661   g_clear_error (&error);
5662 }
5663 
5664 static void
tp_base_contact_list_mixin_add_to_group(TpSvcConnectionInterfaceContactGroups * svc,const gchar * group,const GArray * contacts,DBusGMethodInvocation * context)5665 tp_base_contact_list_mixin_add_to_group (
5666     TpSvcConnectionInterfaceContactGroups *svc,
5667     const gchar *group,
5668     const GArray *contacts,
5669     DBusGMethodInvocation *context)
5670 {
5671   TpBaseContactList *self = _tp_base_connection_find_channel_manager (
5672       (TpBaseConnection *) svc, TP_TYPE_BASE_CONTACT_LIST);
5673   GError *error = NULL;
5674   TpHandle group_handle = 0;
5675   TpHandleSet *contacts_set;
5676 
5677   if (!tp_base_contact_list_check_group_change (self, contacts, &error))
5678     goto sync_exit;
5679 
5680   /* get the handle so we can use the normalized name */
5681   group_handle = tp_handle_ensure (self->priv->group_repo, group, NULL,
5682       &error);
5683 
5684   /* if the group's name is syntactically invalid, just fail */
5685   if (group_handle == 0)
5686     goto sync_exit;
5687 
5688   contacts_set = tp_handle_set_new_from_array (self->priv->contact_repo,
5689       contacts);
5690   tp_base_contact_list_add_to_group_async (self,
5691       tp_handle_inspect (self->priv->group_repo, group_handle),
5692       contacts_set, tp_base_contact_list_mixin_add_to_group_cb, context);
5693   tp_handle_set_destroy (contacts_set);
5694   return;
5695 
5696 sync_exit:
5697   tp_base_contact_list_mixin_return_void (context, error);
5698   g_clear_error (&error);
5699 }
5700 
5701 static void
tp_base_contact_list_mixin_remove_from_group_cb(GObject * source,GAsyncResult * result,gpointer context)5702 tp_base_contact_list_mixin_remove_from_group_cb (GObject *source,
5703     GAsyncResult *result,
5704     gpointer context)
5705 {
5706   TpBaseContactList *self = TP_BASE_CONTACT_LIST (source);
5707   GError *error = NULL;
5708 
5709   tp_base_contact_list_remove_from_group_finish (self, result, &error);
5710   tp_base_contact_list_mixin_return_void (context, error);
5711   g_clear_error (&error);
5712 }
5713 
5714 static void
tp_base_contact_list_mixin_remove_from_group(TpSvcConnectionInterfaceContactGroups * svc,const gchar * group,const GArray * contacts,DBusGMethodInvocation * context)5715 tp_base_contact_list_mixin_remove_from_group (
5716     TpSvcConnectionInterfaceContactGroups *svc,
5717     const gchar *group,
5718     const GArray *contacts,
5719     DBusGMethodInvocation *context)
5720 {
5721   TpBaseContactList *self = _tp_base_connection_find_channel_manager (
5722       (TpBaseConnection *) svc, TP_TYPE_BASE_CONTACT_LIST);
5723   GError *error = NULL;
5724   TpHandle group_handle;
5725   TpHandleSet *contacts_set;
5726 
5727   if (!tp_base_contact_list_check_group_change (self, contacts, &error))
5728     goto sync_exit;
5729 
5730   /* get the handle so we can use the normalized name */
5731   group_handle = tp_handle_lookup (self->priv->group_repo, group, NULL, NULL);
5732 
5733   /* removing from a group that doesn't exist is a no-op */
5734   if (group_handle == 0)
5735     goto sync_exit;
5736 
5737   contacts_set = tp_handle_set_new_from_array (self->priv->contact_repo,
5738       contacts);
5739   tp_base_contact_list_remove_from_group_async (self,
5740       tp_handle_inspect (self->priv->group_repo, group_handle),
5741       contacts_set, tp_base_contact_list_mixin_remove_from_group_cb, context);
5742   tp_handle_set_destroy (contacts_set);
5743   return;
5744 
5745 sync_exit:
5746   tp_base_contact_list_mixin_return_void (context, error);
5747   g_clear_error (&error);
5748 }
5749 
5750 static void
tp_base_contact_list_mixin_remove_group_cb(GObject * source,GAsyncResult * result,gpointer context)5751 tp_base_contact_list_mixin_remove_group_cb (GObject *source,
5752     GAsyncResult *result,
5753     gpointer context)
5754 {
5755   TpBaseContactList *self = TP_BASE_CONTACT_LIST (source);
5756   GError *error = NULL;
5757 
5758   tp_base_contact_list_remove_group_finish (self, result, &error);
5759   tp_base_contact_list_mixin_return_void (context, error);
5760   g_clear_error (&error);
5761 }
5762 
5763 static void
tp_base_contact_list_mixin_remove_group(TpSvcConnectionInterfaceContactGroups * svc,const gchar * group,DBusGMethodInvocation * context)5764 tp_base_contact_list_mixin_remove_group (
5765     TpSvcConnectionInterfaceContactGroups *svc,
5766     const gchar *group,
5767     DBusGMethodInvocation *context)
5768 {
5769   TpBaseContactList *self = _tp_base_connection_find_channel_manager (
5770       (TpBaseConnection *) svc, TP_TYPE_BASE_CONTACT_LIST);
5771   GError *error = NULL;
5772   TpHandle group_handle;
5773 
5774   if (!tp_base_contact_list_check_group_change (self, NULL, &error))
5775     goto sync_exit;
5776 
5777   /* get the handle so we can use the normalized name */
5778   group_handle = tp_handle_lookup (self->priv->group_repo, group, NULL, NULL);
5779 
5780   /* removing from a group that doesn't exist is a no-op */
5781   if (group_handle == 0)
5782     goto sync_exit;
5783 
5784   tp_base_contact_list_remove_group_async (self, group,
5785       tp_base_contact_list_mixin_remove_group_cb, context);
5786   return;
5787 
5788 sync_exit:
5789   tp_base_contact_list_mixin_return_void (context, error);
5790   g_clear_error (&error);
5791 }
5792 
5793 static void
tp_base_contact_list_mixin_rename_group_cb(GObject * source,GAsyncResult * result,gpointer context)5794 tp_base_contact_list_mixin_rename_group_cb (GObject *source,
5795     GAsyncResult *result,
5796     gpointer context)
5797 {
5798   TpBaseContactList *self = TP_BASE_CONTACT_LIST (source);
5799   GError *error = NULL;
5800 
5801   tp_base_contact_list_rename_group_finish (self, result, &error);
5802   tp_base_contact_list_mixin_return_void (context, error);
5803   g_clear_error (&error);
5804 }
5805 
5806 static void
tp_base_contact_list_mixin_rename_group(TpSvcConnectionInterfaceContactGroups * svc,const gchar * before,const gchar * after,DBusGMethodInvocation * context)5807 tp_base_contact_list_mixin_rename_group (
5808     TpSvcConnectionInterfaceContactGroups *svc,
5809     const gchar *before,
5810     const gchar *after,
5811     DBusGMethodInvocation *context)
5812 {
5813   TpBaseContactList *self = _tp_base_connection_find_channel_manager (
5814       (TpBaseConnection *) svc, TP_TYPE_BASE_CONTACT_LIST);
5815   GError *error = NULL;
5816   TpHandle old_handle;
5817   gpointer old_channel;
5818   TpHandle new_handle = 0;
5819 
5820   if (!tp_base_contact_list_check_group_change (self, NULL, &error))
5821     goto sync_exit;
5822 
5823   old_handle = tp_handle_lookup (self->priv->group_repo, before, NULL, NULL);
5824   old_channel = g_hash_table_lookup (self->priv->groups,
5825       GUINT_TO_POINTER (old_handle));
5826 
5827   if (old_handle == 0 || old_channel == NULL)
5828     {
5829       g_set_error (&error, TP_ERROR, TP_ERROR_DOES_NOT_EXIST,
5830           "Group '%s' does not exist", before);
5831       goto sync_exit;
5832     }
5833 
5834   new_handle = tp_handle_ensure (self->priv->group_repo, after, NULL, &error);
5835 
5836   if (new_handle == 0)
5837     goto sync_exit;
5838 
5839   if (g_hash_table_lookup (self->priv->groups, GUINT_TO_POINTER (new_handle))
5840       != NULL)
5841     {
5842       g_set_error (&error, TP_ERROR, TP_ERROR_NOT_AVAILABLE,
5843           "Group '%s' already exists",
5844           tp_handle_inspect (self->priv->group_repo, new_handle));
5845       goto sync_exit;
5846     }
5847 
5848   tp_base_contact_list_rename_group_async (self,
5849       tp_handle_inspect (self->priv->group_repo, old_handle),
5850       tp_handle_inspect (self->priv->group_repo, new_handle),
5851       tp_base_contact_list_mixin_rename_group_cb, context);
5852   return;
5853 
5854 sync_exit:
5855   tp_base_contact_list_mixin_return_void (context, error);
5856   g_clear_error (&error);
5857 }
5858 
5859 typedef enum {
5860     GP_DISJOINT_GROUPS,
5861     GP_GROUP_STORAGE,
5862     GP_GROUPS,
5863     NUM_GROUP_PROPERTIES
5864 } GroupProp;
5865 
5866 static TpDBusPropertiesMixinPropImpl known_group_props[] = {
5867     { "DisjointGroups", GINT_TO_POINTER (GP_DISJOINT_GROUPS), },
5868     { "GroupStorage", GINT_TO_POINTER (GP_GROUP_STORAGE) },
5869     { "Groups", GINT_TO_POINTER (GP_GROUPS) },
5870     { NULL }
5871 };
5872 
5873 static void
tp_base_contact_list_get_group_dbus_property(GObject * conn,GQuark interface G_GNUC_UNUSED,GQuark name G_GNUC_UNUSED,GValue * value,gpointer data)5874 tp_base_contact_list_get_group_dbus_property (GObject *conn,
5875     GQuark interface G_GNUC_UNUSED,
5876     GQuark name G_GNUC_UNUSED,
5877     GValue *value,
5878     gpointer data)
5879 {
5880   TpBaseContactList *self = _tp_base_connection_find_channel_manager (
5881       (TpBaseConnection *) conn, TP_TYPE_BASE_CONTACT_LIST);
5882   GroupProp p = GPOINTER_TO_INT (data);
5883 
5884   g_return_if_fail (TP_IS_BASE_CONTACT_LIST (self));
5885   g_return_if_fail (TP_IS_CONTACT_GROUP_LIST (self));
5886   g_return_if_fail (self->priv->conn != NULL);
5887 
5888   switch (p)
5889     {
5890     case GP_DISJOINT_GROUPS:
5891       g_return_if_fail (G_VALUE_HOLDS_BOOLEAN (value));
5892       g_value_set_boolean (value,
5893           tp_base_contact_list_has_disjoint_groups (self));
5894       break;
5895 
5896     case GP_GROUP_STORAGE:
5897       g_return_if_fail (G_VALUE_HOLDS_UINT (value));
5898       g_value_set_uint (value, tp_base_contact_list_get_group_storage (self));
5899       break;
5900 
5901     case GP_GROUPS:
5902       g_return_if_fail (G_VALUE_HOLDS (value, G_TYPE_STRV));
5903 
5904       if (self->priv->state == TP_CONTACT_LIST_STATE_SUCCESS)
5905         g_value_take_boxed (value, tp_base_contact_list_dup_groups (self));
5906 
5907       break;
5908 
5909     default:
5910       g_return_if_reached ();
5911     }
5912 }
5913 
5914 static void
tp_base_contact_list_fill_groups_contact_attributes(GObject * obj,const GArray * contacts,GHashTable * attributes_hash)5915 tp_base_contact_list_fill_groups_contact_attributes (GObject *obj,
5916   const GArray *contacts,
5917   GHashTable *attributes_hash)
5918 {
5919   TpBaseContactList *self = _tp_base_connection_find_channel_manager (
5920       (TpBaseConnection *) obj, TP_TYPE_BASE_CONTACT_LIST);
5921   guint i;
5922 
5923   g_return_if_fail (TP_IS_BASE_CONTACT_LIST (self));
5924   g_return_if_fail (TP_IS_CONTACT_GROUP_LIST (self));
5925   g_return_if_fail (self->priv->conn != NULL);
5926 
5927   /* just omit the attributes if the contact list hasn't come in yet */
5928   if (self->priv->state != TP_CONTACT_LIST_STATE_SUCCESS)
5929     return;
5930 
5931   for (i = 0; i < contacts->len; i++)
5932     {
5933       TpHandle handle;
5934 
5935       handle = g_array_index (contacts, TpHandle, i);
5936 
5937       tp_contacts_mixin_set_contact_attribute (attributes_hash,
5938           handle, TP_TOKEN_CONNECTION_INTERFACE_CONTACT_GROUPS_GROUPS,
5939           tp_g_value_slice_new_take_boxed (G_TYPE_STRV,
5940             tp_base_contact_list_dup_contact_groups (self, handle)));
5941     }
5942 }
5943 
5944 static void
tp_base_contact_list_fill_blocking_contact_attributes(GObject * obj,const GArray * contacts,GHashTable * attributes_hash)5945 tp_base_contact_list_fill_blocking_contact_attributes (GObject *obj,
5946   const GArray *contacts,
5947   GHashTable *attributes_hash)
5948 {
5949   TpBaseContactList *self = _tp_base_connection_find_channel_manager (
5950       (TpBaseConnection *) obj, TP_TYPE_BASE_CONTACT_LIST);
5951   guint i;
5952   TpHandleSet *blocked;
5953 
5954   g_return_if_fail (TP_IS_BASE_CONTACT_LIST (self));
5955   g_return_if_fail (TP_IS_BLOCKABLE_CONTACT_LIST (self));
5956   g_return_if_fail (self->priv->conn != NULL);
5957 
5958   /* just omit the attributes if the contact list hasn't come in yet */
5959   if (self->priv->state != TP_CONTACT_LIST_STATE_SUCCESS)
5960     return;
5961 
5962   blocked = tp_base_contact_list_dup_blocked_contacts (self);
5963 
5964   for (i = 0; i < contacts->len; i++)
5965     {
5966       TpHandle handle;
5967       gboolean is_blocked;
5968 
5969       handle = g_array_index (contacts, TpHandle, i);
5970 
5971       is_blocked = tp_handle_set_is_member (blocked, handle);
5972 
5973       tp_contacts_mixin_set_contact_attribute (attributes_hash,
5974           handle, TP_TOKEN_CONNECTION_INTERFACE_CONTACT_BLOCKING_BLOCKED,
5975           tp_g_value_slice_new_boolean (is_blocked));
5976     }
5977 
5978   tp_handle_set_destroy (blocked);
5979 }
5980 
5981 /**
5982  * tp_base_contact_list_mixin_groups_iface_init:
5983  * @klass: the service-side D-Bus interface
5984  *
5985  * Use the #TpBaseContactList like a mixin, to implement the ContactGroups
5986  * D-Bus interface.
5987  *
5988  * This function should be passed to G_IMPLEMENT_INTERFACE() for
5989  * #TpSvcConnectionInterfaceContactGroups.
5990  *
5991  * Since: 0.13.0
5992  */
5993 void
tp_base_contact_list_mixin_groups_iface_init(TpSvcConnectionInterfaceContactGroupsClass * klass)5994 tp_base_contact_list_mixin_groups_iface_init (
5995     TpSvcConnectionInterfaceContactGroupsClass *klass)
5996 {
5997 #define IMPLEMENT(x) tp_svc_connection_interface_contact_groups_implement_##x (\
5998   klass, tp_base_contact_list_mixin_##x)
5999   IMPLEMENT (set_contact_groups);
6000   IMPLEMENT (set_group_members);
6001   IMPLEMENT (add_to_group);
6002   IMPLEMENT (remove_from_group);
6003   IMPLEMENT (remove_group);
6004   IMPLEMENT (rename_group);
6005 #undef IMPLEMENT
6006 }
6007 
6008 #define ERROR_IF_BLOCKING_NOT_SUPPORTED(self, context) \
6009   if (!self->priv->svc_contact_blocking) \
6010     { \
6011       GError e = { TP_ERROR, TP_ERROR_NOT_IMPLEMENTED, \
6012           "ContactBlocking is not supported on this connection" }; \
6013       dbus_g_method_return_error (context, &e); \
6014       return; \
6015     }
6016 
6017 static void
tp_base_contact_list_mixin_request_blocked_contacts(TpSvcConnectionInterfaceContactBlocking * svc,DBusGMethodInvocation * context)6018 tp_base_contact_list_mixin_request_blocked_contacts (
6019     TpSvcConnectionInterfaceContactBlocking *svc,
6020     DBusGMethodInvocation *context)
6021 {
6022   TpBaseContactList *self = _tp_base_connection_find_channel_manager (
6023       (TpBaseConnection *) svc, TP_TYPE_BASE_CONTACT_LIST);
6024 
6025   ERROR_IF_BLOCKING_NOT_SUPPORTED (self, context);
6026 
6027   switch (self->priv->state)
6028     {
6029     case TP_CONTACT_LIST_STATE_NONE:
6030     case TP_CONTACT_LIST_STATE_WAITING:
6031       g_queue_push_tail (&self->priv->blocked_contact_requests, context);
6032       break;
6033 
6034     case TP_CONTACT_LIST_STATE_FAILURE:
6035       g_warn_if_fail (self->priv->failure != NULL);
6036       dbus_g_method_return_error (context, self->priv->failure);
6037       break;
6038 
6039     case TP_CONTACT_LIST_STATE_SUCCESS:
6040       {
6041         TpHandleSet *blocked = tp_base_contact_list_dup_blocked_contacts (self);
6042         GHashTable *map = tp_handle_set_to_identifier_map (blocked);
6043 
6044         tp_svc_connection_interface_contact_blocking_return_from_request_blocked_contacts (context, map);
6045 
6046         g_hash_table_unref (map);
6047         tp_handle_set_destroy (blocked);
6048         break;
6049       }
6050 
6051     default:
6052       {
6053         GError broken = { TP_ERROR, TP_ERROR_CONFUSED,
6054             "My internal list of blocked contacts is inconsistent! "
6055             "I apologise for any inconvenience caused." };
6056         dbus_g_method_return_error (context, &broken);
6057         g_return_if_reached ();
6058       }
6059     }
6060 }
6061 
6062 static void
blocked_cb(GObject * source,GAsyncResult * result,gpointer user_data)6063 blocked_cb (
6064     GObject *source,
6065     GAsyncResult *result,
6066     gpointer user_data)
6067 {
6068   TpBaseContactList *self = TP_BASE_CONTACT_LIST (source);
6069   DBusGMethodInvocation *context = user_data;
6070   GError *error = NULL;
6071 
6072   if (tp_base_contact_list_block_contacts_with_abuse_finish (self, result,
6073           &error))
6074     {
6075       tp_svc_connection_interface_contact_blocking_return_from_block_contacts (
6076           context);
6077     }
6078   else
6079     {
6080       dbus_g_method_return_error (context, error);
6081       g_clear_error (&error);
6082     }
6083 }
6084 
6085 static void
tp_base_contact_list_mixin_block_contacts(TpSvcConnectionInterfaceContactBlocking * svc,const GArray * contacts_arr,gboolean report_abusive,DBusGMethodInvocation * context)6086 tp_base_contact_list_mixin_block_contacts (
6087     TpSvcConnectionInterfaceContactBlocking *svc,
6088     const GArray *contacts_arr,
6089     gboolean report_abusive,
6090     DBusGMethodInvocation *context)
6091 {
6092   TpBaseContactList *self = _tp_base_connection_find_channel_manager (
6093       (TpBaseConnection *) svc, TP_TYPE_BASE_CONTACT_LIST);
6094   TpHandleSet *contacts;
6095 
6096   ERROR_IF_BLOCKING_NOT_SUPPORTED (self, context);
6097 
6098   contacts = tp_handle_set_new_from_array (self->priv->contact_repo,
6099       contacts_arr);
6100   tp_base_contact_list_block_contacts_with_abuse_async (self, contacts,
6101       report_abusive, blocked_cb, context);
6102   tp_handle_set_destroy (contacts);
6103 }
6104 
6105 static void
unblocked_cb(GObject * source,GAsyncResult * result,gpointer user_data)6106 unblocked_cb (
6107     GObject *source,
6108     GAsyncResult *result,
6109     gpointer user_data)
6110 {
6111   TpBaseContactList *self = TP_BASE_CONTACT_LIST (source);
6112   DBusGMethodInvocation *context = user_data;
6113   GError *error = NULL;
6114 
6115   if (tp_base_contact_list_unblock_contacts_finish (self, result, &error))
6116     {
6117       tp_svc_connection_interface_contact_blocking_return_from_unblock_contacts (context);
6118     }
6119   else
6120     {
6121       dbus_g_method_return_error (context, error);
6122       g_clear_error (&error);
6123     }
6124 }
6125 
6126 static void
tp_base_contact_list_mixin_unblock_contacts(TpSvcConnectionInterfaceContactBlocking * svc,const GArray * contacts_arr,DBusGMethodInvocation * context)6127 tp_base_contact_list_mixin_unblock_contacts (
6128     TpSvcConnectionInterfaceContactBlocking *svc,
6129     const GArray *contacts_arr,
6130     DBusGMethodInvocation *context)
6131 {
6132   TpBaseContactList *self = _tp_base_connection_find_channel_manager (
6133       (TpBaseConnection *) svc, TP_TYPE_BASE_CONTACT_LIST);
6134   TpHandleSet *contacts;
6135 
6136   ERROR_IF_BLOCKING_NOT_SUPPORTED (self, context);
6137 
6138   contacts = tp_handle_set_new_from_array (self->priv->contact_repo,
6139       contacts_arr);
6140   tp_base_contact_list_unblock_contacts_async (self, contacts, unblocked_cb,
6141       context);
6142   tp_handle_set_destroy (contacts);
6143 }
6144 
6145 /**
6146  * tp_base_contact_list_mixin_blocking_iface_init:
6147  * @klass: the service-side D-Bus interface
6148  *
6149  * Use the #TpBaseContactList like a mixin, to implement the ContactBlocking
6150  * D-Bus interface.
6151  *
6152  * This function should be passed to G_IMPLEMENT_INTERFACE() for
6153  * #TpSvcConnectionInterfaceContactBlocking
6154  *
6155  * Since: 0.15.1
6156  */
6157 void
tp_base_contact_list_mixin_blocking_iface_init(TpSvcConnectionInterfaceContactBlockingClass * klass)6158 tp_base_contact_list_mixin_blocking_iface_init (
6159     TpSvcConnectionInterfaceContactBlockingClass *klass)
6160 {
6161 #define IMPLEMENT(x) tp_svc_connection_interface_contact_blocking_implement_##x (\
6162   klass, tp_base_contact_list_mixin_##x)
6163   IMPLEMENT (block_contacts);
6164   IMPLEMENT (unblock_contacts);
6165   IMPLEMENT (request_blocked_contacts);
6166 #undef IMPLEMENT
6167 }
6168 
6169 static TpDBusPropertiesMixinPropImpl known_blocking_props[] = {
6170     { "ContactBlockingCapabilities" },
6171     { NULL }
6172 };
6173 
6174 static void
tp_base_contact_list_get_blocking_dbus_property(GObject * conn,GQuark interface G_GNUC_UNUSED,GQuark name G_GNUC_UNUSED,GValue * value,gpointer data)6175 tp_base_contact_list_get_blocking_dbus_property (GObject *conn,
6176     GQuark interface G_GNUC_UNUSED,
6177     GQuark name G_GNUC_UNUSED,
6178     GValue *value,
6179     gpointer data)
6180 {
6181   TpBaseContactList *self = _tp_base_connection_find_channel_manager (
6182       (TpBaseConnection *) conn, TP_TYPE_BASE_CONTACT_LIST);
6183   TpBlockableContactListInterface *iface =
6184       TP_BLOCKABLE_CONTACT_LIST_GET_INTERFACE (self);
6185   static GQuark contact_blocking_capabilities_q = 0;
6186   guint flags = 0;
6187 
6188   g_return_if_fail (TP_IS_BASE_CONTACT_LIST (self));
6189   g_return_if_fail (TP_IS_BLOCKABLE_CONTACT_LIST (self));
6190   g_return_if_fail (self->priv->conn != NULL);
6191 
6192   if (G_UNLIKELY (contact_blocking_capabilities_q == 0))
6193     contact_blocking_capabilities_q =
6194         g_quark_from_static_string ("ContactBlockingCapabilities");
6195 
6196   g_return_if_fail (name == contact_blocking_capabilities_q);
6197 
6198   if (iface->block_contacts_with_abuse_async != NULL)
6199     flags |= TP_CONTACT_BLOCKING_CAPABILITY_CAN_REPORT_ABUSIVE;
6200 
6201   g_value_set_uint (value, flags);
6202 }
6203 
6204 /**
6205  * tp_base_contact_list_mixin_class_init:
6206  * @cls: A subclass of #TpBaseConnection that has a #TpContactsMixinClass,
6207  *  and implements #TpSvcConnectionInterfaceContactList using
6208  *  #TpBaseContactList
6209  *
6210  * Register the #TpBaseContactList to be used like a mixin in @cls.
6211  * Before this function is called, the #TpContactsMixin must be initialized
6212  * with tp_contacts_mixin_class_init().
6213  *
6214  * If the connection implements #TpSvcConnectionInterfaceContactGroups, this
6215  * function automatically sets up that interface as well as ContactList.
6216  * In this case, when the #TpBaseContactList is created later, it must
6217  * implement %TP_TYPE_CONTACT_GROUP_LIST.
6218  *
6219  * Since: 0.13.0
6220  */
6221 void
tp_base_contact_list_mixin_class_init(TpBaseConnectionClass * cls)6222 tp_base_contact_list_mixin_class_init (TpBaseConnectionClass *cls)
6223 {
6224   GType type = G_OBJECT_CLASS_TYPE (cls);
6225   GObjectClass *obj_cls = (GObjectClass *) cls;
6226 
6227   g_return_if_fail (TP_IS_BASE_CONNECTION_CLASS (cls));
6228   g_return_if_fail (TP_CONTACTS_MIXIN_CLASS (cls) != NULL);
6229   g_return_if_fail (g_type_is_a (type,
6230         TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_LIST));
6231 
6232   tp_dbus_properties_mixin_implement_interface (obj_cls,
6233       TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_LIST,
6234       tp_base_contact_list_get_list_dbus_property,
6235       NULL, known_list_props);
6236 
6237   if (g_type_is_a (type, TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_GROUPS))
6238     {
6239       tp_dbus_properties_mixin_implement_interface (obj_cls,
6240           TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_GROUPS,
6241           tp_base_contact_list_get_group_dbus_property,
6242           NULL, known_group_props);
6243     }
6244 
6245   if (g_type_is_a (type, TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_BLOCKING))
6246     {
6247       tp_dbus_properties_mixin_implement_interface (obj_cls,
6248           TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_BLOCKING,
6249           tp_base_contact_list_get_blocking_dbus_property,
6250           NULL, known_blocking_props);
6251     }
6252 }
6253 
6254 /**
6255  * tp_base_contact_list_mixin_register_with_contacts_mixin:
6256  * @conn: An instance of #TpBaseConnection that uses a #TpContactsMixin,
6257  *  and implements #TpSvcConnectionInterfaceContactList using
6258  *  #TpBaseContactList
6259  *
6260  * Register the ContactList interface with the Contacts interface to make it
6261  * inspectable. Before this function is called, the #TpContactsMixin must be
6262  * initialized with tp_contacts_mixin_init(), and @conn must have a
6263  * #TpBaseContactList in its list of channel managers (by creating it in
6264  * its #TpBaseConnectionClass.create_channel_managers implementation).
6265  *
6266  * If the connection implements #TpSvcConnectionInterfaceContactGroups
6267  * the #TpBaseContactList implements %TP_TYPE_CONTACT_GROUP_LIST,
6268  * this function automatically also registers the ContactGroups interface
6269  * with the contacts mixin.
6270  *
6271  * Since: 0.13.0
6272  */
6273 void
tp_base_contact_list_mixin_register_with_contacts_mixin(TpBaseConnection * conn)6274 tp_base_contact_list_mixin_register_with_contacts_mixin (
6275     TpBaseConnection *conn)
6276 {
6277   TpBaseContactList *self;
6278   GType type = G_OBJECT_TYPE (conn);
6279   GObject *object = (GObject *) conn;
6280 
6281   g_return_if_fail (TP_IS_BASE_CONNECTION (conn));
6282   self = _tp_base_connection_find_channel_manager (conn,
6283       TP_TYPE_BASE_CONTACT_LIST);
6284   g_return_if_fail (self != NULL);
6285   g_return_if_fail (g_type_is_a (type,
6286         TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_LIST));
6287 
6288   tp_contacts_mixin_add_contact_attributes_iface (object,
6289       TP_IFACE_CONNECTION_INTERFACE_CONTACT_LIST,
6290       tp_base_contact_list_fill_list_contact_attributes);
6291 
6292   if (g_type_is_a (type, TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_GROUPS)
6293       && TP_IS_CONTACT_GROUP_LIST (self))
6294     {
6295       tp_contacts_mixin_add_contact_attributes_iface (object,
6296           TP_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS,
6297           tp_base_contact_list_fill_groups_contact_attributes);
6298     }
6299 
6300   if (g_type_is_a (type, TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_BLOCKING)
6301       && TP_IS_BLOCKABLE_CONTACT_LIST (self))
6302     {
6303       tp_contacts_mixin_add_contact_attributes_iface (object,
6304           TP_IFACE_CONNECTION_INTERFACE_CONTACT_BLOCKING,
6305           tp_base_contact_list_fill_blocking_contact_attributes);
6306     }
6307 }
6308 
6309 /**
6310  * tp_base_contact_list_get_state:
6311  * @self: a contact list
6312  * @error: used to raise an error if something other than
6313  * %TP_CONTACT_LIST_STATE_SUCCESS is returned
6314  *
6315  * Return how much progress this object has made towards retrieving the
6316  * contact list.
6317  *
6318  * If this contact list's connection has disconnected, or retrieving the
6319  * contact list has failed, return %TP_CONTACT_LIST_STATE_FAILURE.
6320  *
6321  * Returns: the state of the contact list
6322  *
6323  * Since: 0.13.0
6324  */
6325 TpContactListState
tp_base_contact_list_get_state(TpBaseContactList * self,GError ** error)6326 tp_base_contact_list_get_state (TpBaseContactList *self,
6327     GError **error)
6328 {
6329   /* this checks TP_IS_BASE_CONTACT_LIST */
6330   if (tp_base_contact_list_get_connection (self, error) == NULL)
6331     return TP_CONTACT_LIST_STATE_FAILURE;
6332 
6333   if (self->priv->failure != NULL)
6334     {
6335       g_set_error_literal (error, self->priv->failure->domain,
6336           self->priv->failure->code, self->priv->failure->message);
6337       return TP_CONTACT_LIST_STATE_FAILURE;
6338     }
6339 
6340   /* on failure, self->priv->failure was meant to be set */
6341   g_return_val_if_fail (self->priv->state != TP_CONTACT_LIST_STATE_FAILURE,
6342       TP_CONTACT_LIST_STATE_FAILURE);
6343 
6344   if (self->priv->state != TP_CONTACT_LIST_STATE_SUCCESS)
6345     g_set_error (error, TP_ERROR, TP_ERROR_NOT_YET,
6346         "Contact list not downloaded yet");
6347 
6348   return self->priv->state;
6349 }
6350 
6351 /**
6352  * tp_base_contact_list_get_connection:
6353  * @self: a contact list
6354  * @error: used to raise an error if %NULL is returned
6355  *
6356  * Return the Connection this contact list uses. If this contact list's
6357  * connection has already disconnected, return %NULL instead.
6358  *
6359  * Returns: (transfer none): the connection, or %NULL
6360  *
6361  * Since: 0.13.0
6362  */
6363 TpBaseConnection *
tp_base_contact_list_get_connection(TpBaseContactList * self,GError ** error)6364 tp_base_contact_list_get_connection (TpBaseContactList *self,
6365     GError **error)
6366 {
6367   g_return_val_if_fail (TP_IS_BASE_CONTACT_LIST (self), NULL);
6368 
6369   if (self->priv->conn == NULL)
6370     {
6371       g_set_error_literal (error, TP_ERROR, TP_ERROR_DISCONNECTED,
6372           "Connection is no longer connected");
6373       return NULL;
6374     }
6375 
6376   return self->priv->conn;
6377 }
6378