Some unit test cleanup and fixes
[qtcontacts-tracker:sirajs-contactsd-merge.git] / tests / lib / glib / contact-list-manager.c
1 /*
2  * Example channel manager for contact lists
3  *
4  * Copyright © 2007-2010 Collabora Ltd. <http://www.collabora.co.uk/>
5  * Copyright © 2007-2010 Nokia Corporation
6  *
7  * Copying and distribution of this file, with or without modification,
8  * are permitted in any medium without royalty provided the copyright
9  * notice and this notice are preserved.
10  */
11
12 #include "contact-list-manager.h"
13
14 #include <string.h>
15
16 #include <dbus/dbus-glib.h>
17
18 #include <telepathy-glib/telepathy-glib.h>
19
20 #include "contact-list.h"
21
22 /* elements 0, 1... of this array must be kept in sync with elements 1, 2...
23  * of the enum TestContactList in contact-list-manager.h */
24 static const gchar *_contact_lists[NUM_TEST_CONTACT_LISTS + 1] = {
25     "subscribe",
26     "publish",
27     "stored",
28     NULL
29 };
30
31 const gchar **
32 test_contact_lists (void)
33 {
34   return _contact_lists;
35 }
36
37 /* this array must be kept in sync with the enum
38  * TestContactListPresence in contact-list-manager.h */
39 static const TpPresenceStatusSpec _statuses[] = {
40       { "offline", TP_CONNECTION_PRESENCE_TYPE_OFFLINE, FALSE, NULL },
41       { "unknown", TP_CONNECTION_PRESENCE_TYPE_UNKNOWN, FALSE, NULL },
42       { "error", TP_CONNECTION_PRESENCE_TYPE_ERROR, FALSE, NULL },
43       { "away", TP_CONNECTION_PRESENCE_TYPE_AWAY, TRUE, NULL },
44       { "available", TP_CONNECTION_PRESENCE_TYPE_AVAILABLE, TRUE, NULL },
45       { NULL }
46 };
47
48 const TpPresenceStatusSpec *
49 test_contact_list_presence_statuses (void)
50 {
51   return _statuses;
52 }
53
54 typedef struct {
55     gchar *alias;
56     TestContactListAvatarData *avatar_data;
57
58     guint subscribe:1;
59     guint publish:1;
60     guint subscribe_requested:1;
61     guint publish_requested:1;
62
63     TpHandleSet *tags;
64
65 } ExampleContactDetails;
66
67 static ExampleContactDetails *
68 test_contact_details_new (void)
69 {
70   return g_slice_new0 (ExampleContactDetails);
71 }
72
73 static void
74 test_contact_details_destroy (gpointer p)
75 {
76   ExampleContactDetails *d = p;
77
78   if (d->tags != NULL)
79     tp_handle_set_destroy (d->tags);
80
81   g_free (d->alias);
82   test_contact_list_avatar_data_free (d->avatar_data);
83   g_slice_free (ExampleContactDetails, d);
84 }
85
86 static void channel_manager_iface_init (gpointer, gpointer);
87
88 G_DEFINE_TYPE_WITH_CODE (TestContactListManager,
89     test_contact_list_manager,
90     G_TYPE_OBJECT,
91     G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER,
92       channel_manager_iface_init))
93
94 enum
95 {
96   ALIAS_UPDATED,
97   AVATAR_UPDATED,
98   PRESENCE_UPDATED,
99   N_SIGNALS
100 };
101
102 static guint signals[N_SIGNALS] = { 0 };
103
104 enum
105 {
106   PROP_CONNECTION = 1,
107   PROP_SIMULATION_DELAY,
108   N_PROPS
109 };
110
111 struct _TestContactListManagerPrivate
112 {
113   TpBaseConnection *conn;
114   guint simulation_delay;
115   TpHandleRepoIface *contact_repo;
116   TpHandleRepoIface *group_repo;
117
118   TpHandleSet *contacts;
119   /* GUINT_TO_POINTER (handle borrowed from contacts)
120    *    => ExampleContactDetails */
121   GHashTable *contact_details;
122
123   TestContactList *lists[NUM_TEST_CONTACT_LISTS];
124
125   /* GUINT_TO_POINTER (handle borrowed from channel) => ExampleContactGroup */
126   GHashTable *groups;
127
128   /* borrowed TpExportableChannel => GSList of gpointer (request tokens) that
129    * will be satisfied by that channel when the contact list has been
130    * downloaded. The requests are in reverse chronological order */
131   GHashTable *queued_requests;
132
133   gulong status_changed_id;
134 };
135
136 static void
137 test_contact_list_manager_init (TestContactListManager *self)
138 {
139   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
140       TEST_TYPE_CONTACT_LIST_MANAGER, TestContactListManagerPrivate);
141
142   self->priv->contact_details = g_hash_table_new_full (g_direct_hash,
143       g_direct_equal, NULL, test_contact_details_destroy);
144   self->priv->groups = g_hash_table_new_full (g_direct_hash, g_direct_equal,
145       NULL, g_object_unref);
146   self->priv->queued_requests = g_hash_table_new_full (g_direct_hash,
147       g_direct_equal, NULL, NULL);
148
149   /* initialized properly in constructed() */
150   self->priv->contact_repo = NULL;
151   self->priv->group_repo = NULL;
152   self->priv->contacts = NULL;
153 }
154
155 static void
156 test_contact_list_manager_close_all (TestContactListManager *self)
157 {
158   guint i;
159
160   if (self->priv->queued_requests != NULL)
161     {
162       GHashTable *tmp = self->priv->queued_requests;
163       GHashTableIter iter;
164       gpointer key, value;
165
166       self->priv->queued_requests = NULL;
167       g_hash_table_iter_init (&iter, tmp);
168
169       while (g_hash_table_iter_next (&iter, &key, &value))
170         {
171           GSList *requests = value;
172           GSList *l;
173
174           requests = g_slist_reverse (requests);
175
176           for (l = requests; l != NULL; l = l->next)
177             {
178               tp_channel_manager_emit_request_failed (self,
179                   l->data, TP_ERRORS, TP_ERROR_DISCONNECTED,
180                   "Unable to complete channel request due to disconnection");
181             }
182
183           g_slist_free (requests);
184           g_hash_table_iter_steal (&iter);
185         }
186
187       g_hash_table_destroy (tmp);
188     }
189
190   if (self->priv->contacts != NULL)
191     {
192       tp_handle_set_destroy (self->priv->contacts);
193       self->priv->contacts = NULL;
194     }
195
196   if (self->priv->contact_details != NULL)
197     {
198       GHashTable *tmp = self->priv->contact_details;
199
200       self->priv->contact_details = NULL;
201       g_hash_table_destroy (tmp);
202     }
203
204   if (self->priv->groups != NULL)
205     {
206       GHashTable *tmp = self->priv->groups;
207
208       self->priv->groups = NULL;
209       g_hash_table_destroy (tmp);
210     }
211
212   for (i = 0; i < NUM_TEST_CONTACT_LISTS; i++)
213     {
214       if (self->priv->lists[i] != NULL)
215         {
216           TestContactList *list = self->priv->lists[i];
217
218           /* set self->priv->lists[i] to NULL here so list_closed_cb does
219            * not try to delete the list again */
220           self->priv->lists[i] = NULL;
221           g_object_unref (list);
222         }
223     }
224
225   if (self->priv->status_changed_id != 0)
226     {
227       g_signal_handler_disconnect (self->priv->conn,
228           self->priv->status_changed_id);
229       self->priv->status_changed_id = 0;
230     }
231 }
232
233 static void
234 dispose (GObject *object)
235 {
236   TestContactListManager *self = TEST_CONTACT_LIST_MANAGER (object);
237
238   test_contact_list_manager_close_all (self);
239   g_assert (self->priv->groups == NULL);
240   g_assert (self->priv->lists[0] == NULL);
241   g_assert (self->priv->queued_requests == NULL);
242
243   ((GObjectClass *) test_contact_list_manager_parent_class)->dispose (
244     object);
245 }
246
247 static void
248 get_property (GObject *object,
249               guint property_id,
250               GValue *value,
251               GParamSpec *pspec)
252 {
253   TestContactListManager *self = TEST_CONTACT_LIST_MANAGER (object);
254
255   switch (property_id)
256     {
257     case PROP_CONNECTION:
258       g_value_set_object (value, self->priv->conn);
259       break;
260
261     case PROP_SIMULATION_DELAY:
262       g_value_set_uint (value, self->priv->simulation_delay);
263       break;
264
265     default:
266       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
267     }
268 }
269
270 static void
271 set_property (GObject *object,
272               guint property_id,
273               const GValue *value,
274               GParamSpec *pspec)
275 {
276   TestContactListManager *self = TEST_CONTACT_LIST_MANAGER (object);
277
278   switch (property_id)
279     {
280     case PROP_CONNECTION:
281       /* We don't ref the connection, because it owns a reference to the
282        * manager, and it guarantees that the manager's lifetime is
283        * less than its lifetime */
284       self->priv->conn = g_value_get_object (value);
285       break;
286
287     case PROP_SIMULATION_DELAY:
288       self->priv->simulation_delay = g_value_get_uint (value);
289       break;
290
291     default:
292       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
293     }
294 }
295
296 static void
297 satisfy_queued_requests (TpExportableChannel *channel,
298                          gpointer user_data)
299 {
300   TestContactListManager *self = TEST_CONTACT_LIST_MANAGER (user_data);
301   GSList *requests = g_hash_table_lookup (self->priv->queued_requests,
302       channel);
303
304   /* this is all fine even if requests is NULL */
305   g_hash_table_steal (self->priv->queued_requests, channel);
306   requests = g_slist_reverse (requests);
307   tp_channel_manager_emit_new_channel (self, channel, requests);
308   g_slist_free (requests);
309 }
310
311 static ExampleContactDetails *
312 lookup_contact (TestContactListManager *self,
313                 TpHandle contact)
314 {
315   return g_hash_table_lookup (self->priv->contact_details,
316       GUINT_TO_POINTER (contact));
317 }
318
319 static ExampleContactDetails *
320 ensure_contact (TestContactListManager *self,
321                 TpHandle contact,
322                 gboolean *created)
323 {
324   ExampleContactDetails *ret = lookup_contact (self, contact);
325
326   if (ret == NULL)
327     {
328       tp_handle_set_add (self->priv->contacts, contact);
329
330       ret = test_contact_details_new ();
331       ret->alias = g_strdup (tp_handle_inspect (self->priv->contact_repo,
332             contact));
333       ret->avatar_data = NULL;
334
335       g_hash_table_insert (self->priv->contact_details,
336           GUINT_TO_POINTER (contact), ret);
337
338       if (created != NULL)
339         *created = TRUE;
340     }
341   else if (created != NULL)
342     {
343       *created = FALSE;
344     }
345
346   return ret;
347 }
348
349 static void
350 test_contact_list_manager_foreach_channel (TpChannelManager *manager,
351                                               TpExportableChannelFunc callback,
352                                               gpointer user_data)
353 {
354   TestContactListManager *self = TEST_CONTACT_LIST_MANAGER (manager);
355   GHashTableIter iter;
356   gpointer handle, channel;
357   guint i;
358
359   for (i = 0; i < NUM_TEST_CONTACT_LISTS; i++)
360     {
361       if (self->priv->lists[i] != NULL)
362         callback (TP_EXPORTABLE_CHANNEL (self->priv->lists[i]), user_data);
363     }
364
365   g_hash_table_iter_init (&iter, self->priv->groups);
366
367   while (g_hash_table_iter_next (&iter, &handle, &channel))
368     {
369       callback (TP_EXPORTABLE_CHANNEL (channel), user_data);
370     }
371 }
372
373 static ExampleContactGroup *ensure_group (TestContactListManager *self,
374     TpHandle handle);
375
376 static TestContactList *ensure_list (TestContactListManager *self,
377     TestContactListHandle handle);
378
379 static gboolean
380 receive_contact_lists (gpointer p)
381 {
382   TestContactListManager *self = p;
383 #if 0
384   TpHandle handle, cambridge, montreal, francophones;
385   ExampleContactDetails *d;
386   TpIntSet *set, *cam_set, *mtl_set, *fr_set;
387   TpIntSetFastIter iter;
388   TestContactList *subscribe, *publish, *stored;
389   ExampleContactGroup *cambridge_group, *montreal_group,
390       *francophones_group;
391
392   if (self->priv->groups == NULL)
393     {
394       /* connection already disconnected, so don't process the
395        * "data from the server" */
396       return FALSE;
397     }
398
399   /* In a real CM we'd have received a contact list from the server at this
400    * point. But this isn't a real CM, so we have to make one up... */
401
402   g_message ("Receiving roster from server");
403
404   subscribe = ensure_list (self, TEST_CONTACT_LIST_SUBSCRIBE);
405   publish = ensure_list (self, TEST_CONTACT_LIST_PUBLISH);
406   stored = ensure_list (self, TEST_CONTACT_LIST_STORED);
407
408   cambridge = tp_handle_ensure (self->priv->group_repo, "Cambridge", NULL,
409       NULL);
410   montreal = tp_handle_ensure (self->priv->group_repo, "Montreal", NULL,
411       NULL);
412   francophones = tp_handle_ensure (self->priv->group_repo, "Francophones",
413       NULL, NULL);
414
415   cambridge_group = ensure_group (self, cambridge);
416   montreal_group = ensure_group (self, montreal);
417   francophones_group = ensure_group (self, francophones);
418
419   /* Add various people who are already subscribing and publishing */
420
421   set = tp_intset_new ();
422   cam_set = tp_intset_new ();
423   mtl_set = tp_intset_new ();
424   fr_set = tp_intset_new ();
425
426   handle = tp_handle_ensure (self->priv->contact_repo, "sjoerd@example.com",
427       NULL, NULL);
428   tp_intset_add (set, handle);
429   tp_intset_add (cam_set, handle);
430   d = ensure_contact (self, handle, NULL);
431   g_free (d->alias);
432   d->alias = g_strdup ("Sjoerd");
433   test_contact_list_avatar_data_free (d->avatar_data);
434   d->avatar_data = NULL;
435   d->subscribe = TRUE;
436   d->publish = TRUE;
437   d->tags = tp_handle_set_new (self->priv->group_repo);
438   tp_handle_set_add (d->tags, cambridge);
439   tp_handle_unref (self->priv->contact_repo, handle);
440
441   handle = tp_handle_ensure (self->priv->contact_repo, "guillaume@example.com",
442       NULL, NULL);
443   tp_intset_add (set, handle);
444   tp_intset_add (cam_set, handle);
445   tp_intset_add (fr_set, handle);
446   d = ensure_contact (self, handle, NULL);
447   g_free (d->alias);
448   d->alias = g_strdup ("Guillaume");
449   test_contact_list_avatar_data_free (d->avatar_data);
450   d->avatar_data = NULL;
451   d->subscribe = TRUE;
452   d->publish = TRUE;
453   d->tags = tp_handle_set_new (self->priv->group_repo);
454   tp_handle_set_add (d->tags, cambridge);
455   tp_handle_set_add (d->tags, francophones);
456   tp_handle_unref (self->priv->contact_repo, handle);
457
458   handle = tp_handle_ensure (self->priv->contact_repo, "olivier@example.com",
459       NULL, NULL);
460   tp_intset_add (set, handle);
461   tp_intset_add (mtl_set, handle);
462   tp_intset_add (fr_set, handle);
463   d = ensure_contact (self, handle, NULL);
464   g_free (d->alias);
465   d->alias = g_strdup ("Olivier");
466   test_contact_list_avatar_data_free (d->avatar_data);
467   d->avatar_data = NULL;
468   d->subscribe = TRUE;
469   d->publish = TRUE;
470   d->tags = tp_handle_set_new (self->priv->group_repo);
471   tp_handle_set_add (d->tags, montreal);
472   tp_handle_set_add (d->tags, francophones);
473   tp_handle_unref (self->priv->contact_repo, handle);
474
475   handle = tp_handle_ensure (self->priv->contact_repo, "travis@example.com",
476       NULL, NULL);
477   tp_intset_add (set, handle);
478   d = ensure_contact (self, handle, NULL);
479   g_free (d->alias);
480   d->alias = g_strdup ("Travis");
481   test_contact_list_avatar_data_free (d->avatar_data);
482   d->avatar_data = NULL;
483   d->subscribe = TRUE;
484   d->publish = TRUE;
485   tp_handle_unref (self->priv->contact_repo, handle);
486
487   tp_group_mixin_change_members ((GObject *) subscribe, "",
488       set, NULL, NULL, NULL,
489       0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
490   tp_group_mixin_change_members ((GObject *) publish, "",
491       set, NULL, NULL, NULL,
492       0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
493   tp_group_mixin_change_members ((GObject *) stored, "",
494       set, NULL, NULL, NULL,
495       0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
496
497   tp_intset_fast_iter_init (&iter, set);
498
499   while (tp_intset_fast_iter_next (&iter, &handle))
500     {
501       g_signal_emit (self, signals[ALIAS_UPDATED], 0, handle);
502       g_signal_emit (self, signals[PRESENCE_UPDATED], 0, handle);
503     }
504
505   tp_intset_destroy (set);
506
507   /* Add a couple of people whose presence we've requested. They are
508    * remote-pending in subscribe */
509
510   set = tp_intset_new ();
511
512   handle = tp_handle_ensure (self->priv->contact_repo, "geraldine@example.com",
513       NULL, NULL);
514   tp_intset_add (set, handle);
515   tp_intset_add (cam_set, handle);
516   tp_intset_add (fr_set, handle);
517   d = ensure_contact (self, handle, NULL);
518   g_free (d->alias);
519   d->alias = g_strdup ("Géraldine");
520   test_contact_list_avatar_data_free (d->avatar_data);
521   d->avatar_data = NULL;
522   d->subscribe_requested = TRUE;
523   d->tags = tp_handle_set_new (self->priv->group_repo);
524   tp_handle_set_add (d->tags, cambridge);
525   tp_handle_set_add (d->tags, francophones);
526   tp_handle_unref (self->priv->contact_repo, handle);
527
528   handle = tp_handle_ensure (self->priv->contact_repo, "helen@example.com",
529       NULL, NULL);
530   tp_intset_add (set, handle);
531   tp_intset_add (cam_set, handle);
532   d = ensure_contact (self, handle, NULL);
533   g_free (d->alias);
534   d->alias = g_strdup ("Helen");
535   test_contact_list_avatar_data_free (d->avatar_data);
536   d->avatar_data = NULL;
537   d->subscribe_requested = TRUE;
538   d->tags = tp_handle_set_new (self->priv->group_repo);
539   tp_handle_set_add (d->tags, cambridge);
540   tp_handle_unref (self->priv->contact_repo, handle);
541
542   tp_group_mixin_change_members ((GObject *) subscribe, "",
543       NULL, NULL, NULL, set,
544       0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
545   tp_group_mixin_change_members ((GObject *) stored, "",
546       set, NULL, NULL, NULL,
547       0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
548
549   tp_intset_fast_iter_init (&iter, set);
550
551   while (tp_intset_fast_iter_next (&iter, &handle))
552     {
553       g_signal_emit (self, signals[ALIAS_UPDATED], 0, handle);
554       g_signal_emit (self, signals[PRESENCE_UPDATED], 0, handle);
555     }
556
557   tp_intset_destroy (set);
558
559   /* Receive a couple of authorization requests too. These people are
560    * local-pending in publish */
561
562   handle = tp_handle_ensure (self->priv->contact_repo, "wim@example.com",
563       NULL, NULL);
564   d = ensure_contact (self, handle, NULL);
565   g_free (d->alias);
566   d->alias = g_strdup ("Wim");
567   test_contact_list_avatar_data_free (d->avatar_data);
568   d->avatar_data = NULL;
569   d->publish_requested = TRUE;
570   tp_handle_unref (self->priv->contact_repo, handle);
571
572   set = tp_intset_new_containing (handle);
573   tp_group_mixin_change_members ((GObject *) publish,
574       "I'm more metal than you!",
575       NULL, NULL, set, NULL,
576       handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
577   tp_group_mixin_change_members ((GObject *) stored, "",
578       set, NULL, NULL, NULL,
579       handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
580   tp_intset_destroy (set);
581   g_signal_emit (self, signals[ALIAS_UPDATED], 0, handle);
582   g_signal_emit (self, signals[PRESENCE_UPDATED], 0, handle);
583
584   handle = tp_handle_ensure (self->priv->contact_repo, "christian@example.com",
585       NULL, NULL);
586   d = ensure_contact (self, handle, NULL);
587   g_free (d->alias);
588   d->alias = g_strdup ("Christian");
589   test_contact_list_avatar_data_free (d->avatar_data);
590   d->avatar_data = NULL;
591   d->publish_requested = TRUE;
592   tp_handle_unref (self->priv->contact_repo, handle);
593
594   set = tp_intset_new_containing (handle);
595   tp_group_mixin_change_members ((GObject *) publish,
596       "I have some fermented herring for you",
597       NULL, NULL, set, NULL,
598       handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
599   tp_group_mixin_change_members ((GObject *) stored, "",
600       set, NULL, NULL, NULL,
601       handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
602   tp_intset_destroy (set);
603   g_signal_emit (self, signals[ALIAS_UPDATED], 0, handle);
604   g_signal_emit (self, signals[PRESENCE_UPDATED], 0, handle);
605
606   tp_group_mixin_change_members ((GObject *) cambridge_group, "",
607       cam_set, NULL, NULL, NULL,
608       0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
609   tp_group_mixin_change_members ((GObject *) montreal_group, "",
610       mtl_set, NULL, NULL, NULL,
611       0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
612   tp_group_mixin_change_members ((GObject *) francophones_group, "",
613       fr_set, NULL, NULL, NULL,
614       0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
615
616   tp_intset_destroy (fr_set);
617   tp_intset_destroy (cam_set);
618   tp_intset_destroy (mtl_set);
619
620   tp_handle_unref (self->priv->group_repo, cambridge);
621   tp_handle_unref (self->priv->group_repo, montreal);
622   tp_handle_unref (self->priv->group_repo, francophones);
623 #endif
624
625   /* Now we've received the roster, we can satisfy all the queued requests */
626
627   test_contact_list_manager_foreach_channel ((TpChannelManager *) self,
628       satisfy_queued_requests, self);
629
630   g_assert (g_hash_table_size (self->priv->queued_requests) == 0);
631   g_hash_table_destroy (self->priv->queued_requests);
632   self->priv->queued_requests = NULL;
633
634   return FALSE;
635 }
636
637 static void
638 status_changed_cb (TpBaseConnection *conn,
639                    guint status,
640                    guint reason,
641                    TestContactListManager *self)
642 {
643   switch (status)
644     {
645     case TP_CONNECTION_STATUS_CONNECTED:
646         {
647           /* Do network I/O to get the contact list. This connection manager
648            * doesn't really have a server, so simulate a small network delay
649            * then invent a contact list */
650           g_timeout_add_full (G_PRIORITY_DEFAULT,
651               2 * self->priv->simulation_delay, receive_contact_lists,
652               g_object_ref (self), g_object_unref);
653         }
654       break;
655
656     case TP_CONNECTION_STATUS_DISCONNECTED:
657         {
658           test_contact_list_manager_close_all (self);
659         }
660       break;
661     }
662 }
663
664 static void
665 constructed (GObject *object)
666 {
667   TestContactListManager *self = TEST_CONTACT_LIST_MANAGER (object);
668   void (*chain_up) (GObject *) =
669       ((GObjectClass *) test_contact_list_manager_parent_class)->constructed;
670
671   if (chain_up != NULL)
672     {
673       chain_up (object);
674     }
675
676   self->priv->contact_repo = tp_base_connection_get_handles (self->priv->conn,
677       TP_HANDLE_TYPE_CONTACT);
678   self->priv->group_repo = tp_base_connection_get_handles (self->priv->conn,
679       TP_HANDLE_TYPE_GROUP);
680   self->priv->contacts = tp_handle_set_new (self->priv->contact_repo);
681
682   self->priv->status_changed_id = g_signal_connect (self->priv->conn,
683       "status-changed", (GCallback) status_changed_cb, self);
684 }
685
686 static void
687 test_contact_list_manager_class_init (TestContactListManagerClass *klass)
688 {
689   GParamSpec *param_spec;
690   GObjectClass *object_class = (GObjectClass *) klass;
691
692   object_class->constructed = constructed;
693   object_class->dispose = dispose;
694   object_class->get_property = get_property;
695   object_class->set_property = set_property;
696
697   param_spec = g_param_spec_object ("connection", "Connection object",
698       "The connection that owns this channel manager",
699       TP_TYPE_BASE_CONNECTION,
700       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
701       G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
702   g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
703
704   param_spec = g_param_spec_uint ("simulation-delay", "Simulation delay",
705       "Delay between simulated network events",
706       0, G_MAXUINT32, 1000,
707       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
708   g_object_class_install_property (object_class, PROP_SIMULATION_DELAY,
709       param_spec);
710
711   g_type_class_add_private (klass, sizeof (TestContactListManagerPrivate));
712
713   signals[ALIAS_UPDATED] = g_signal_new ("alias-updated",
714       G_TYPE_FROM_CLASS (klass),
715       G_SIGNAL_RUN_LAST,
716       0,
717       NULL, NULL,
718       g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT);
719
720   signals[AVATAR_UPDATED] = g_signal_new ("avatar-updated",
721       G_TYPE_FROM_CLASS (klass),
722       G_SIGNAL_RUN_LAST,
723       0,
724       NULL, NULL,
725       g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT);
726
727   signals[PRESENCE_UPDATED] = g_signal_new ("presence-updated",
728       G_TYPE_FROM_CLASS (klass),
729       G_SIGNAL_RUN_LAST,
730       0,
731       NULL, NULL,
732       g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT);
733 }
734
735 static void
736 list_closed_cb (TestContactList *chan,
737                 TestContactListManager *self)
738 {
739   TpHandle handle;
740
741   tp_channel_manager_emit_channel_closed_for_object (self,
742       TP_EXPORTABLE_CHANNEL (chan));
743
744   g_object_get (chan,
745       "handle", &handle,
746       NULL);
747
748   if (self->priv->lists[handle] == NULL)
749     return;
750
751   g_assert (chan == self->priv->lists[handle]);
752   g_object_unref (self->priv->lists[handle]);
753   self->priv->lists[handle] = NULL;
754 }
755
756 static void
757 group_closed_cb (ExampleContactGroup *chan,
758                  TestContactListManager *self)
759 {
760   tp_channel_manager_emit_channel_closed_for_object (self,
761       TP_EXPORTABLE_CHANNEL (chan));
762
763   if (self->priv->groups != NULL)
764     {
765       TpHandle handle;
766
767       g_object_get (chan,
768           "handle", &handle,
769           NULL);
770
771       g_hash_table_remove (self->priv->groups, GUINT_TO_POINTER (handle));
772     }
773 }
774
775 static TestContactListBase *
776 new_channel (TestContactListManager *self,
777              TpHandleType handle_type,
778              TpHandle handle,
779              gpointer request_token)
780 {
781   TestContactListBase *chan;
782   gchar *object_path;
783   GType type;
784   GSList *requests = NULL;
785
786   if (handle_type == TP_HANDLE_TYPE_LIST)
787     {
788       /* Some Telepathy clients wrongly assume that contact lists of type LIST
789        * have object paths ending with "/subscribe", "/publish" etc. -
790        * telepathy-spec has no such guarantee, so in this example we break
791        * those clients. Please read the spec when implementing it :-) */
792       object_path = g_strdup_printf ("%s/%sContactList",
793           self->priv->conn->object_path, _contact_lists[handle - 1]);
794       type = TEST_TYPE_CONTACT_LIST;
795     }
796   else
797     {
798       /* Using Group%u (with handle as the value of %u) would be OK here too,
799        * but we'll encode the group name into the object path to be kind
800        * to people reading debug logs. */
801       gchar *id = tp_escape_as_identifier (tp_handle_inspect (
802             self->priv->group_repo, handle));
803
804       g_assert (handle_type == TP_HANDLE_TYPE_GROUP);
805       object_path = g_strdup_printf ("%s/Group/%s",
806           self->priv->conn->object_path, id);
807       type = TEST_TYPE_CONTACT_GROUP;
808
809       g_free (id);
810     }
811
812   chan = g_object_new (type,
813       "connection", self->priv->conn,
814       "manager", self,
815       "object-path", object_path,
816       "handle-type", handle_type,
817       "handle", handle,
818       NULL);
819
820   g_free (object_path);
821
822   if (handle_type == TP_HANDLE_TYPE_LIST)
823     {
824       g_signal_connect (chan, "closed", (GCallback) list_closed_cb, self);
825       g_assert (self->priv->lists[handle] == NULL);
826       self->priv->lists[handle] = TEST_CONTACT_LIST (chan);
827     }
828   else
829     {
830       g_signal_connect (chan, "closed", (GCallback) group_closed_cb, self);
831
832       g_assert (g_hash_table_lookup (self->priv->groups,
833             GUINT_TO_POINTER (handle)) == NULL);
834       g_hash_table_insert (self->priv->groups, GUINT_TO_POINTER (handle),
835           TEST_CONTACT_GROUP (chan));
836     }
837
838   if (self->priv->queued_requests == NULL)
839     {
840       if (request_token != NULL)
841         requests = g_slist_prepend (requests, request_token);
842
843       tp_channel_manager_emit_new_channel (self, TP_EXPORTABLE_CHANNEL (chan),
844           requests);
845       g_slist_free (requests);
846     }
847   else if (request_token != NULL)
848     {
849       /* initial contact list not received yet, so we have to wait for it */
850       requests = g_hash_table_lookup (self->priv->queued_requests, chan);
851       g_hash_table_steal (self->priv->queued_requests, chan);
852       requests = g_slist_prepend (requests, request_token);
853       g_hash_table_insert (self->priv->queued_requests, chan, requests);
854     }
855
856   return chan;
857 }
858
859 static TestContactList *
860 ensure_list (TestContactListManager *self,
861              TestContactListHandle handle)
862 {
863   if (self->priv->lists[handle] == NULL)
864     {
865       new_channel (self, TP_HANDLE_TYPE_LIST, handle, NULL);
866       g_assert (self->priv->lists[handle] != NULL);
867     }
868
869   return self->priv->lists[handle];
870 }
871
872 static ExampleContactGroup *
873 ensure_group (TestContactListManager *self,
874               TpHandle handle)
875 {
876   ExampleContactGroup *group = g_hash_table_lookup (self->priv->groups,
877       GUINT_TO_POINTER (handle));
878
879   if (group == NULL)
880     {
881       group = TEST_CONTACT_GROUP (new_channel (self, TP_HANDLE_TYPE_GROUP,
882             handle, NULL));
883     }
884
885   return group;
886 }
887
888 static const gchar * const fixed_properties[] = {
889     TP_PROP_CHANNEL_CHANNEL_TYPE,
890     TP_PROP_CHANNEL_TARGET_HANDLE_TYPE,
891     NULL
892 };
893
894 static const gchar * const allowed_properties[] = {
895     TP_PROP_CHANNEL_TARGET_HANDLE,
896     TP_PROP_CHANNEL_TARGET_ID,
897     NULL
898 };
899
900 static void
901 test_contact_list_manager_foreach_channel_class (TpChannelManager *manager,
902     TpChannelManagerChannelClassFunc func,
903     gpointer user_data)
904 {
905     GHashTable *table = tp_asv_new (
906         TP_PROP_CHANNEL_CHANNEL_TYPE,
907             G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
908         TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_LIST,
909         NULL);
910
911     func (manager, table, allowed_properties, user_data);
912
913     g_hash_table_insert (table, TP_PROP_CHANNEL_TARGET_HANDLE_TYPE,
914         tp_g_value_slice_new_uint (TP_HANDLE_TYPE_GROUP));
915     func (manager, table, allowed_properties, user_data);
916
917     g_hash_table_destroy (table);
918 }
919
920 static gboolean
921 test_contact_list_manager_request (TestContactListManager *self,
922                                       gpointer request_token,
923                                       GHashTable *request_properties,
924                                       gboolean require_new)
925 {
926   TpHandleType handle_type;
927   TpHandle handle;
928   TestContactListBase *chan;
929   GError *error = NULL;
930
931   if (tp_strdiff (tp_asv_get_string (request_properties,
932           TP_PROP_CHANNEL_CHANNEL_TYPE),
933       TP_IFACE_CHANNEL_TYPE_CONTACT_LIST))
934     {
935       return FALSE;
936     }
937
938   handle_type = tp_asv_get_uint32 (request_properties,
939       TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL);
940
941   if (handle_type != TP_HANDLE_TYPE_LIST &&
942       handle_type != TP_HANDLE_TYPE_GROUP)
943     {
944       return FALSE;
945     }
946
947   handle = tp_asv_get_uint32 (request_properties,
948       TP_PROP_CHANNEL_TARGET_HANDLE, NULL);
949   g_assert (handle != 0);
950
951   if (tp_channel_manager_asv_has_unknown_properties (request_properties,
952         fixed_properties, allowed_properties, &error))
953     {
954       goto error;
955     }
956
957   if (handle_type == TP_HANDLE_TYPE_LIST)
958     {
959       /* telepathy-glib has already checked that the handle is valid */
960       g_assert (handle < NUM_TEST_CONTACT_LISTS);
961
962       chan = TEST_CONTACT_LIST_BASE (self->priv->lists[handle]);
963     }
964   else
965     {
966       chan = g_hash_table_lookup (self->priv->groups,
967           GUINT_TO_POINTER (handle));
968     }
969
970   if (chan == NULL)
971     {
972       new_channel (self, handle_type, handle, request_token);
973     }
974   else if (require_new)
975     {
976       g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
977           "A ContactList channel for type #%u, handle #%u already exists",
978           handle_type, handle);
979       goto error;
980     }
981   else
982     {
983       tp_channel_manager_emit_request_already_satisfied (self,
984           request_token, TP_EXPORTABLE_CHANNEL (chan));
985     }
986
987   return TRUE;
988
989 error:
990   tp_channel_manager_emit_request_failed (self, request_token,
991       error->domain, error->code, error->message);
992   g_error_free (error);
993   return TRUE;
994 }
995
996 static gboolean
997 test_contact_list_manager_create_channel (TpChannelManager *manager,
998                                              gpointer request_token,
999                                              GHashTable *request_properties)
1000 {
1001     return test_contact_list_manager_request (
1002         TEST_CONTACT_LIST_MANAGER (manager), request_token,
1003         request_properties, TRUE);
1004 }
1005
1006 static gboolean
1007 test_contact_list_manager_ensure_channel (TpChannelManager *manager,
1008                                              gpointer request_token,
1009                                              GHashTable *request_properties)
1010 {
1011     return test_contact_list_manager_request (
1012         TEST_CONTACT_LIST_MANAGER (manager), request_token,
1013         request_properties, FALSE);
1014 }
1015
1016 static void
1017 channel_manager_iface_init (gpointer g_iface,
1018                             gpointer data G_GNUC_UNUSED)
1019 {
1020   TpChannelManagerIface *iface = g_iface;
1021
1022   iface->foreach_channel = test_contact_list_manager_foreach_channel;
1023   iface->foreach_channel_class =
1024       test_contact_list_manager_foreach_channel_class;
1025   iface->create_channel = test_contact_list_manager_create_channel;
1026   iface->ensure_channel = test_contact_list_manager_ensure_channel;
1027   /* In this channel manager, Request has the same semantics as Ensure */
1028   iface->request_channel = test_contact_list_manager_ensure_channel;
1029 }
1030
1031 static void
1032 send_updated_roster (TestContactListManager *self,
1033                      TpHandle contact)
1034 {
1035   ExampleContactDetails *d = g_hash_table_lookup (self->priv->contact_details,
1036       GUINT_TO_POINTER (contact));
1037   const gchar *identifier = tp_handle_inspect (self->priv->contact_repo,
1038       contact);
1039
1040   /* In a real connection manager, we'd transmit these new details to the
1041    * server, rather than just printing messages. */
1042
1043   if (d == NULL)
1044     {
1045       g_message ("Deleting contact %s from server", identifier);
1046     }
1047   else
1048     {
1049       g_message ("Transmitting new state of contact %s to server", identifier);
1050       g_message ("\talias = %s", d->alias);
1051       g_message ("\tavatar_data = %s", d->avatar_data ? d->avatar_data->token : "");
1052       g_message ("\tcan see our presence = %s",
1053           d->publish ? "yes" :
1054           (d->publish_requested ? "no, but has requested it" : "no"));
1055       g_message ("\tsends us presence = %s",
1056           d->subscribe ? "yes" :
1057           (d->subscribe_requested ? "no, but we have requested it" : "no"));
1058
1059       if (d->tags == NULL || tp_handle_set_size (d->tags) == 0)
1060         {
1061           g_message ("\tnot in any groups");
1062         }
1063       else
1064         {
1065           TpIntSet *set = tp_handle_set_peek (d->tags);
1066           TpIntSetFastIter iter;
1067           TpHandle member;
1068
1069           tp_intset_fast_iter_init (&iter, set);
1070
1071           while (tp_intset_fast_iter_next (&iter, &member))
1072             {
1073               g_message ("\tin group: %s",
1074                   tp_handle_inspect (self->priv->group_repo, member));
1075             }
1076         }
1077     }
1078 }
1079
1080 gboolean
1081 test_contact_list_manager_add_to_group (TestContactListManager *self,
1082                                            GObject *channel,
1083                                            TpHandle group,
1084                                            TpHandle member,
1085                                            const gchar *message,
1086                                            GError **error)
1087 {
1088   gboolean updated;
1089   ExampleContactDetails *d = ensure_contact (self, member, &updated);
1090   TestContactList *stored = self->priv->lists[
1091     TEST_CONTACT_LIST_STORED];
1092
1093   if (d->tags == NULL)
1094     d->tags = tp_handle_set_new (self->priv->group_repo);
1095
1096   if (!tp_handle_set_is_member (d->tags, group))
1097     {
1098       tp_handle_set_add (d->tags, group);
1099       updated = TRUE;
1100     }
1101
1102   if (updated)
1103     {
1104       TpIntSet *added = tp_intset_new_containing (member);
1105
1106       send_updated_roster (self, member);
1107       tp_group_mixin_change_members (channel, "", added, NULL, NULL, NULL,
1108           self->priv->conn->self_handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
1109       tp_group_mixin_change_members ((GObject *) stored, "",
1110           added, NULL, NULL, NULL,
1111           self->priv->conn->self_handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
1112       tp_intset_destroy (added);
1113     }
1114
1115   return TRUE;
1116 }
1117
1118 gboolean
1119 test_contact_list_manager_remove_from_group (
1120     TestContactListManager *self,
1121     GObject *channel,
1122     TpHandle group,
1123     TpHandle member,
1124     const gchar *message,
1125     GError **error)
1126 {
1127   ExampleContactDetails *d = lookup_contact (self, member);
1128
1129   /* If not on the roster or not in any groups, we have nothing to do */
1130   if (d == NULL || d->tags == NULL)
1131     return TRUE;
1132
1133   if (tp_handle_set_remove (d->tags, group))
1134     {
1135       TpIntSet *removed = tp_intset_new_containing (member);
1136
1137       send_updated_roster (self, member);
1138       tp_group_mixin_change_members (channel, "", NULL, removed, NULL, NULL,
1139           self->priv->conn->self_handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
1140       tp_intset_destroy (removed);
1141     }
1142
1143   return TRUE;
1144 }
1145
1146 typedef struct {
1147     TestContactListManager *self;
1148     TpHandle contact;
1149 } SelfAndContact;
1150
1151 static SelfAndContact *
1152 self_and_contact_new (TestContactListManager *self,
1153                       TpHandle contact)
1154 {
1155   SelfAndContact *ret = g_slice_new0 (SelfAndContact);
1156
1157   ret->self = g_object_ref (self);
1158   ret->contact = contact;
1159   tp_handle_ref (self->priv->contact_repo, contact);
1160   return ret;
1161 }
1162
1163 static void
1164 self_and_contact_destroy (gpointer p)
1165 {
1166   SelfAndContact *s = p;
1167
1168   tp_handle_unref (s->self->priv->contact_repo, s->contact);
1169   g_object_unref (s->self);
1170   g_slice_free (SelfAndContact, s);
1171 }
1172
1173 static void
1174 receive_auth_request (TestContactListManager *self,
1175                       TpHandle contact)
1176 {
1177   ExampleContactDetails *d;
1178   TpIntSet *set;
1179   TestContactList *publish = self->priv->lists[
1180     TEST_CONTACT_LIST_PUBLISH];
1181   TestContactList *stored = self->priv->lists[
1182     TEST_CONTACT_LIST_STORED];
1183
1184   /* if shutting down, do nothing */
1185   if (publish == NULL)
1186     return;
1187
1188   /* A remote contact has asked to see our presence.
1189    *
1190    * In a real connection manager this would be the result of incoming
1191    * data from the server. */
1192
1193   g_message ("From server: %s has sent us a publish request",
1194       tp_handle_inspect (self->priv->contact_repo, contact));
1195
1196   d = ensure_contact (self, contact, NULL);
1197
1198   if (d->publish)
1199     return;
1200
1201   d->publish_requested = TRUE;
1202
1203   set = tp_intset_new_containing (contact);
1204   tp_group_mixin_change_members ((GObject *) publish,
1205       "May I see your presence, please?",
1206       NULL, NULL, set, NULL,
1207       contact, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
1208   tp_group_mixin_change_members ((GObject *) stored, "",
1209       set, NULL, NULL, NULL,
1210       contact, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
1211   tp_intset_destroy (set);
1212 }
1213
1214 static gboolean
1215 receive_authorized (gpointer p)
1216 {
1217   SelfAndContact *s = p;
1218   ExampleContactDetails *d;
1219   TpIntSet *set;
1220   TestContactList *subscribe = s->self->priv->lists[
1221     TEST_CONTACT_LIST_SUBSCRIBE];
1222   TestContactList *stored = s->self->priv->lists[
1223     TEST_CONTACT_LIST_STORED];
1224
1225   /* A remote contact has accepted our request to see their presence.
1226    *
1227    * In a real connection manager this would be the result of incoming
1228    * data from the server. */
1229
1230   g_message ("From server: %s has accepted our subscribe request",
1231       tp_handle_inspect (s->self->priv->contact_repo, s->contact));
1232
1233   d = ensure_contact (s->self, s->contact, NULL);
1234
1235   /* if we were already subscribed to them, then nothing really happened */
1236   if (d->subscribe)
1237     return FALSE;
1238
1239   d->subscribe_requested = FALSE;
1240   d->subscribe = TRUE;
1241
1242   set = tp_intset_new_containing (s->contact);
1243   tp_group_mixin_change_members ((GObject *) subscribe, "",
1244       set, NULL, NULL, NULL,
1245       s->contact, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
1246   tp_group_mixin_change_members ((GObject *) stored, "",
1247       set, NULL, NULL, NULL,
1248       s->contact, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
1249   tp_intset_destroy (set);
1250
1251   /* their presence changes to something other than UNKNOWN */
1252   g_signal_emit (s->self, signals[PRESENCE_UPDATED], 0, s->contact);
1253
1254   /* if we're not publishing to them, also pretend they have asked us to
1255    * do so */
1256   if (!d->publish)
1257     {
1258       receive_auth_request (s->self, s->contact);
1259     }
1260
1261   return FALSE;
1262 }
1263
1264 static gboolean
1265 receive_unauthorized (gpointer p)
1266 {
1267   SelfAndContact *s = p;
1268   ExampleContactDetails *d;
1269   TpIntSet *set;
1270   TestContactList *subscribe = s->self->priv->lists[
1271     TEST_CONTACT_LIST_SUBSCRIBE];
1272
1273   /* if shutting down, do nothing */
1274   if (subscribe == NULL)
1275     return FALSE;
1276
1277   /* A remote contact has rejected our request to see their presence.
1278    *
1279    * In a real connection manager this would be the result of incoming
1280    * data from the server. */
1281
1282   g_message ("From server: %s has rejected our subscribe request",
1283       tp_handle_inspect (s->self->priv->contact_repo, s->contact));
1284
1285   d = ensure_contact (s->self, s->contact, NULL);
1286
1287   if (!d->subscribe && !d->subscribe_requested)
1288     return FALSE;
1289
1290   d->subscribe_requested = FALSE;
1291   d->subscribe = FALSE;
1292
1293   set = tp_intset_new_containing (s->contact);
1294   tp_group_mixin_change_members ((GObject *) subscribe, "Say 'please'!",
1295       NULL, set, NULL, NULL,
1296       s->contact, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
1297   tp_intset_destroy (set);
1298
1299   /* their presence changes to UNKNOWN */
1300   g_signal_emit (s->self, signals[PRESENCE_UPDATED], 0, s->contact);
1301
1302   return FALSE;
1303 }
1304
1305 gboolean
1306 test_contact_list_manager_add_to_list (TestContactListManager *self,
1307                                           GObject *channel,
1308                                           TestContactListHandle list,
1309                                           TpHandle member,
1310                                           const gchar *message,
1311                                           GError **error)
1312 {
1313   TpIntSet *set;
1314   TestContactList *stored = ensure_list (self, TEST_CONTACT_LIST_STORED);
1315
1316   if (channel == NULL)
1317       channel = G_OBJECT (ensure_list (self, list));
1318
1319   switch (list)
1320     {
1321     case TEST_CONTACT_LIST_SUBSCRIBE:
1322       /* we would like to see member's presence */
1323         {
1324           gboolean created;
1325           ExampleContactDetails *d = ensure_contact (self, member, &created);
1326           gchar *message_lc;
1327
1328           /* if they already authorized us, it's a no-op */
1329           if (d->subscribe)
1330             return TRUE;
1331
1332           /* In a real connection manager we'd start a network request here */
1333           g_message ("Transmitting authorization request to %s: %s",
1334               tp_handle_inspect (self->priv->contact_repo, member),
1335               message);
1336
1337           if (created || !d->subscribe_requested)
1338             {
1339               d->subscribe_requested = TRUE;
1340               send_updated_roster (self, member);
1341             }
1342
1343           set = tp_intset_new_containing (member);
1344           tp_group_mixin_change_members (channel, message,
1345               NULL, NULL, NULL, set,
1346               self->priv->conn->self_handle,
1347               TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
1348           /* subscribing to someone implicitly puts them on Stored, too */
1349           tp_group_mixin_change_members ((GObject *) stored, "",
1350               set, NULL, NULL, NULL,
1351               self->priv->conn->self_handle,
1352               TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
1353           tp_intset_destroy (set);
1354
1355           /* Pretend that after a delay, the contact notices the request
1356            * and allows or rejects it. In this example connection manager,
1357            * empty requests are allowed, as are requests that contain "please"
1358            * case-insensitively. All other requests are denied. */
1359           message_lc = g_ascii_strdown (message, -1);
1360
1361           if (message[0] == '\0' || strstr (message_lc, "please") != NULL)
1362             {
1363               g_timeout_add_full (G_PRIORITY_DEFAULT,
1364                   self->priv->simulation_delay, receive_authorized,
1365                   self_and_contact_new (self, member),
1366                   self_and_contact_destroy);
1367             }
1368           else
1369             {
1370               g_timeout_add_full (G_PRIORITY_DEFAULT,
1371                   self->priv->simulation_delay,
1372                   receive_unauthorized,
1373                   self_and_contact_new (self, member),
1374                   self_and_contact_destroy);
1375             }
1376
1377           g_free (message_lc);
1378         }
1379       return TRUE;
1380
1381     case TEST_CONTACT_LIST_PUBLISH:
1382       /* We would like member to see our presence. This is meaningless,
1383        * unless they have asked for it. */
1384         {
1385           ExampleContactDetails *d = lookup_contact (self, member);
1386
1387           if (d == NULL || !d->publish_requested)
1388             {
1389               /* the group mixin won't actually allow this to be reached,
1390                * because of the flags we set */
1391               g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
1392                   "Can't unilaterally send presence to %s",
1393                   tp_handle_inspect (self->priv->contact_repo, member));
1394               return FALSE;
1395             }
1396
1397           if (!d->publish)
1398             {
1399               d->publish = TRUE;
1400               d->publish_requested = FALSE;
1401               send_updated_roster (self, member);
1402
1403               set = tp_intset_new_containing (member);
1404               tp_group_mixin_change_members (channel, "",
1405                   set, NULL, NULL, NULL,
1406                   self->priv->conn->self_handle,
1407                   TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
1408               tp_group_mixin_change_members ((GObject *) stored, "",
1409                   set, NULL, NULL, NULL,
1410                   self->priv->conn->self_handle,
1411                   TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
1412               tp_intset_destroy (set);
1413             }
1414         }
1415       return TRUE;
1416
1417     case TEST_CONTACT_LIST_STORED:
1418       /* we would like member to be on the roster */
1419         {
1420           gboolean created;
1421
1422           ensure_contact (self, member, &created);
1423
1424           if (created)
1425             send_updated_roster (self, member);
1426
1427           set = tp_intset_new_containing (member);
1428           tp_group_mixin_change_members (channel, "",
1429               set, NULL, NULL, NULL, self->priv->conn->self_handle,
1430               TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
1431           tp_intset_destroy (set);
1432         }
1433       return TRUE;
1434
1435     default:
1436       g_return_val_if_reached (FALSE);
1437     }
1438 }
1439
1440 static gboolean
1441 auth_request_cb (gpointer p)
1442 {
1443   SelfAndContact *s = p;
1444
1445   receive_auth_request (s->self, s->contact);
1446
1447   return FALSE;
1448 }
1449
1450 gboolean
1451 test_contact_list_manager_remove_from_list (TestContactListManager *self,
1452                                                GObject *channel,
1453                                                TestContactListHandle list,
1454                                                TpHandle member,
1455                                                const gchar *message,
1456                                                GError **error)
1457 {
1458   TpIntSet *set;
1459
1460   if (channel == NULL)
1461       channel = G_OBJECT (ensure_list (self, list));
1462
1463   switch (list)
1464     {
1465     case TEST_CONTACT_LIST_PUBLISH:
1466       /* we would like member not to see our presence any more, or we
1467        * would like to reject a request from them to see our presence */
1468         {
1469           ExampleContactDetails *d = lookup_contact (self, member);
1470
1471           if (d != NULL)
1472             {
1473               if (d->publish_requested)
1474                 {
1475                   g_message ("Rejecting authorization request from %s",
1476                       tp_handle_inspect (self->priv->contact_repo, member));
1477                   d->publish_requested = FALSE;
1478
1479                   set = tp_intset_new_containing (member);
1480                   tp_group_mixin_change_members (channel, "",
1481                       NULL, set, NULL, NULL,
1482                       self->priv->conn->self_handle,
1483                       TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
1484                   tp_intset_destroy (set);
1485                 }
1486               else if (d->publish)
1487                 {
1488                   g_message ("Removing authorization from %s",
1489                       tp_handle_inspect (self->priv->contact_repo, member));
1490                   d->publish = FALSE;
1491
1492                   set = tp_intset_new_containing (member);
1493                   tp_group_mixin_change_members (channel, "",
1494                       NULL, set, NULL, NULL,
1495                       self->priv->conn->self_handle,
1496                       TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
1497                   tp_intset_destroy (set);
1498
1499                   /* Pretend that after a delay, the contact notices the change
1500                    * and asks for our presence again */
1501                   g_timeout_add_full (G_PRIORITY_DEFAULT,
1502                       self->priv->simulation_delay, auth_request_cb,
1503                       self_and_contact_new (self, member),
1504                       self_and_contact_destroy);
1505                 }
1506               else
1507                 {
1508                   /* nothing to do, avoid "updating the roster" */
1509                   return TRUE;
1510                 }
1511
1512               send_updated_roster (self, member);
1513             }
1514         }
1515       return TRUE;
1516
1517     case TEST_CONTACT_LIST_SUBSCRIBE:
1518       /* we would like to avoid receiving member's presence any more,
1519        * or we would like to cancel an outstanding request for their
1520        * presence */
1521         {
1522           ExampleContactDetails *d = lookup_contact (self, member);
1523
1524           if (d != NULL)
1525             {
1526               if (d->subscribe_requested)
1527                 {
1528                   g_message ("Cancelling our authorization request to %s",
1529                       tp_handle_inspect (self->priv->contact_repo, member));
1530                   d->subscribe_requested = FALSE;
1531
1532                   set = tp_intset_new_containing (member);
1533                   tp_group_mixin_change_members (channel, "",
1534                       NULL, set, NULL, NULL,
1535                       self->priv->conn->self_handle,
1536                       TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
1537                   tp_intset_destroy (set);
1538                 }
1539               else if (d->subscribe)
1540                 {
1541                   g_message ("We no longer want presence from %s",
1542                       tp_handle_inspect (self->priv->contact_repo, member));
1543                   d->subscribe = FALSE;
1544
1545                   set = tp_intset_new_containing (member);
1546                   tp_group_mixin_change_members (channel, "",
1547                       NULL, set, NULL, NULL,
1548                       self->priv->conn->self_handle,
1549                       TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
1550                   tp_intset_destroy (set);
1551
1552                   /* since they're no longer on the subscribe list, we can't
1553                    * see their presence, so emit a signal changing it to
1554                    * UNKNOWN */
1555                   g_signal_emit (self, signals[PRESENCE_UPDATED], 0, member);
1556                 }
1557               else
1558                 {
1559                   /* nothing to do, avoid "updating the roster" */
1560                   return TRUE;
1561                 }
1562
1563               send_updated_roster (self, member);
1564             }
1565         }
1566       return TRUE;
1567
1568     case TEST_CONTACT_LIST_STORED:
1569       /* we would like to remove member from the roster altogether */
1570         {
1571           ExampleContactDetails *d = lookup_contact (self, member);
1572
1573           if (d != NULL)
1574             {
1575               g_hash_table_remove (self->priv->contact_details,
1576                   GUINT_TO_POINTER (member));
1577               send_updated_roster (self, member);
1578
1579               set = tp_intset_new_containing (member);
1580               tp_group_mixin_change_members (channel, "",
1581                   NULL, set, NULL, NULL,
1582                   self->priv->conn->self_handle,
1583                   TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
1584               tp_group_mixin_change_members (
1585                   (GObject *) self->priv->lists[TEST_CONTACT_LIST_SUBSCRIBE],
1586                   "", NULL, set, NULL, NULL,
1587                   self->priv->conn->self_handle,
1588                   TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
1589               tp_group_mixin_change_members (
1590                   (GObject *) self->priv->lists[TEST_CONTACT_LIST_PUBLISH],
1591                   "", NULL, set, NULL, NULL,
1592                   self->priv->conn->self_handle,
1593                   TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
1594               tp_intset_destroy (set);
1595
1596               tp_handle_set_remove (self->priv->contacts, member);
1597
1598               /* since they're no longer on the subscribe list, we can't
1599                * see their presence, so emit a signal changing it to
1600                * UNKNOWN */
1601               g_signal_emit (self, signals[PRESENCE_UPDATED], 0, member);
1602
1603             }
1604         }
1605       return TRUE;
1606
1607     default:
1608       g_return_val_if_reached (FALSE);
1609     }
1610 }
1611
1612 TestContactListPresence
1613 test_contact_list_manager_get_presence (TestContactListManager *self,
1614                                            TpHandle contact)
1615 {
1616   ExampleContactDetails *d = lookup_contact (self, contact);
1617   const gchar *id;
1618
1619   if (d == NULL || !d->subscribe)
1620     {
1621       /* we don't know the presence of people not on the subscribe list,
1622        * by definition */
1623       return TEST_CONTACT_LIST_PRESENCE_UNKNOWN;
1624     }
1625
1626   id = tp_handle_inspect (self->priv->contact_repo, contact);
1627
1628   /* In this example CM, we fake contacts' presence based on their name:
1629    * contacts in the first half of the alphabet are available, the rest
1630    * (including non-alphabetic and non-ASCII initial letters) are away. */
1631   if ((id[0] >= 'A' && id[0] <= 'M') || (id[0] >= 'a' && id[0] <= 'm'))
1632     {
1633       return TEST_CONTACT_LIST_PRESENCE_AVAILABLE;
1634     }
1635
1636   return TEST_CONTACT_LIST_PRESENCE_AWAY;
1637 }
1638
1639 const gchar *
1640 test_contact_list_manager_get_alias (TestContactListManager *self,
1641                                         TpHandle contact)
1642 {
1643   ExampleContactDetails *d = lookup_contact (self, contact);
1644
1645   if (d == NULL)
1646     {
1647       /* we don't have a user-defined alias for people not on the roster */
1648       return tp_handle_inspect (self->priv->contact_repo, contact);
1649     }
1650
1651   return d->alias;
1652 }
1653
1654 void
1655 test_contact_list_manager_set_alias (TestContactListManager *self,
1656                                         TpHandle contact,
1657                                         const gchar *alias)
1658 {
1659   gboolean created;
1660   ExampleContactDetails *d = ensure_contact (self, contact, &created);
1661   TestContactList *stored = self->priv->lists[
1662     TEST_CONTACT_LIST_STORED];
1663   gchar *old = d->alias;
1664   TpIntSet *set;
1665
1666   /* FIXME: if stored list hasn't been retrieved yet, queue the change for
1667    * later */
1668
1669   /* if shutting down, do nothing */
1670   if (stored == NULL)
1671     return;
1672
1673   d->alias = g_strdup (alias);
1674
1675   if (created || tp_strdiff (old, alias))
1676     send_updated_roster (self, contact);
1677
1678   g_free (old);
1679
1680   set = tp_intset_new_containing (contact);
1681   tp_group_mixin_change_members ((GObject *) stored, "",
1682       set, NULL, NULL, NULL, self->priv->conn->self_handle,
1683       TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
1684   tp_intset_destroy (set);
1685
1686   g_signal_emit (self, signals[ALIAS_UPDATED], 0, contact);
1687 }
1688
1689 const TestContactListAvatarData *
1690 test_contact_list_manager_get_avatar (TestContactListManager *self,
1691                                         TpHandle contact)
1692 {
1693   ExampleContactDetails *d = lookup_contact (self, contact);
1694
1695   if (d == NULL)
1696     {
1697       /* we don't have a user-defined avatar for people not on the roster */
1698       return NULL;
1699     }
1700
1701   return d->avatar_data;
1702 }
1703
1704 void
1705 test_contact_list_manager_set_avatar (TestContactListManager *self,
1706                                       TpHandle contact,
1707                                       const TestContactListAvatarData *avatar_data)
1708 {
1709   gboolean created;
1710   ExampleContactDetails *d = ensure_contact (self, contact, &created);
1711   TestContactList *stored = self->priv->lists[
1712     TEST_CONTACT_LIST_STORED];
1713   TestContactListAvatarData *old = d->avatar_data;
1714   TpIntSet *set;
1715   const gchar *old_token = old ? old->token : NULL;
1716   const gchar *new_token = avatar_data ? avatar_data->token : NULL;
1717
1718   /* FIXME: if stored list hasn't been retrieved yet, queue the change for
1719    * later */
1720
1721   /* if shutting down, do nothing */
1722   if (stored == NULL)
1723     return;
1724
1725   if (avatar_data) {
1726       d->avatar_data = test_contact_list_avatar_data_new (avatar_data->data,
1727               avatar_data->mime_type, avatar_data->token);
1728   } else {
1729       d->avatar_data = NULL;
1730   }
1731
1732   if (created || tp_strdiff (old_token, new_token))
1733     send_updated_roster (self, contact);
1734
1735   test_contact_list_avatar_data_free (old);
1736
1737   set = tp_intset_new_containing (contact);
1738   tp_group_mixin_change_members ((GObject *) stored, "",
1739       set, NULL, NULL, NULL, self->priv->conn->self_handle,
1740       TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
1741   tp_intset_destroy (set);
1742
1743   g_signal_emit (self, signals[AVATAR_UPDATED], 0, contact);
1744 }
1745
1746 TestContactListAvatarData *
1747 test_contact_list_avatar_data_new (GArray *data,
1748     const gchar *mime_type,
1749     const gchar *token)
1750 {
1751   TestContactListAvatarData *a;
1752
1753   a = g_slice_new (TestContactListAvatarData);
1754   a->data = data ? g_array_ref (data) : NULL;
1755   a->mime_type = g_strdup (mime_type);
1756   a->token = g_strdup (token);
1757
1758   return a;
1759 }
1760
1761 void
1762 test_contact_list_avatar_data_free (gpointer data)
1763 {
1764   TestContactListAvatarData *a = data;
1765
1766   if (a != NULL)
1767     {
1768       if (a->data != NULL)
1769         g_array_unref (a->data);
1770       g_free (a->mime_type);
1771       g_free (a->token);
1772       g_slice_free (TestContactListAvatarData, a);
1773     }
1774 }