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