1 /*
2  * conn.c - an example connection
3  *
4  * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
5  * Copyright © 2007-2009 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 "conn.h"
13 
14 #include <string.h>
15 
16 #include <dbus/dbus-glib.h>
17 
18 #include <telepathy-glib/telepathy-glib.h>
19 #include <telepathy-glib/handle-repo-dynamic.h>
20 #include <telepathy-glib/handle-repo-static.h>
21 
22 #include "contact-list-manager.h"
23 
24 static void init_aliasing (gpointer, gpointer);
25 
26 G_DEFINE_TYPE_WITH_CODE (ExampleContactListConnection,
27     example_contact_list_connection,
28     TP_TYPE_BASE_CONNECTION,
29     G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_ALIASING,
30       init_aliasing);
31     G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACTS,
32       tp_contacts_mixin_iface_init);
33     G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_PRESENCE,
34       tp_presence_mixin_iface_init);
35     G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
36       tp_presence_mixin_simple_presence_iface_init))
37 
38 enum
39 {
40   PROP_ACCOUNT = 1,
41   PROP_SIMULATION_DELAY,
42   N_PROPS
43 };
44 
45 struct _ExampleContactListConnectionPrivate
46 {
47   gchar *account;
48   guint simulation_delay;
49   ExampleContactListManager *list_manager;
50   gboolean away;
51 };
52 
53 static void
example_contact_list_connection_init(ExampleContactListConnection * self)54 example_contact_list_connection_init (ExampleContactListConnection *self)
55 {
56   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
57       EXAMPLE_TYPE_CONTACT_LIST_CONNECTION,
58       ExampleContactListConnectionPrivate);
59 }
60 
61 static void
get_property(GObject * object,guint property_id,GValue * value,GParamSpec * spec)62 get_property (GObject *object,
63               guint property_id,
64               GValue *value,
65               GParamSpec *spec)
66 {
67   ExampleContactListConnection *self =
68     EXAMPLE_CONTACT_LIST_CONNECTION (object);
69 
70   switch (property_id)
71     {
72     case PROP_ACCOUNT:
73       g_value_set_string (value, self->priv->account);
74       break;
75 
76     case PROP_SIMULATION_DELAY:
77       g_value_set_uint (value, self->priv->simulation_delay);
78       break;
79 
80     default:
81       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
82     }
83 }
84 
85 static void
set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * spec)86 set_property (GObject *object,
87               guint property_id,
88               const GValue *value,
89               GParamSpec *spec)
90 {
91   ExampleContactListConnection *self =
92     EXAMPLE_CONTACT_LIST_CONNECTION (object);
93 
94   switch (property_id)
95     {
96     case PROP_ACCOUNT:
97       g_free (self->priv->account);
98       self->priv->account = g_value_dup_string (value);
99       break;
100 
101     case PROP_SIMULATION_DELAY:
102       self->priv->simulation_delay = g_value_get_uint (value);
103       break;
104 
105     default:
106       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
107     }
108 }
109 
110 static void
finalize(GObject * object)111 finalize (GObject *object)
112 {
113   ExampleContactListConnection *self =
114     EXAMPLE_CONTACT_LIST_CONNECTION (object);
115 
116   tp_contacts_mixin_finalize (object);
117   g_free (self->priv->account);
118 
119   G_OBJECT_CLASS (example_contact_list_connection_parent_class)->finalize (
120       object);
121 }
122 
123 static gchar *
get_unique_connection_name(TpBaseConnection * conn)124 get_unique_connection_name (TpBaseConnection *conn)
125 {
126   ExampleContactListConnection *self = EXAMPLE_CONTACT_LIST_CONNECTION (conn);
127 
128   return g_strdup_printf ("%s@%p", self->priv->account, self);
129 }
130 
131 gchar *
example_contact_list_normalize_contact(TpHandleRepoIface * repo,const gchar * id,gpointer context,GError ** error)132 example_contact_list_normalize_contact (TpHandleRepoIface *repo,
133                                         const gchar *id,
134                                         gpointer context,
135                                         GError **error)
136 {
137   if (id[0] == '\0')
138     {
139       g_set_error (error, TP_ERROR, TP_ERROR_INVALID_HANDLE,
140           "Contact ID must not be empty");
141       return NULL;
142     }
143 
144   return g_utf8_normalize (id, -1, G_NORMALIZE_ALL_COMPOSE);
145 }
146 
147 static gchar *
example_contact_list_normalize_group(TpHandleRepoIface * repo,const gchar * id,gpointer context,GError ** error)148 example_contact_list_normalize_group (TpHandleRepoIface *repo,
149                                       const gchar *id,
150                                       gpointer context,
151                                       GError **error)
152 {
153   if (id[0] == '\0')
154     {
155       g_set_error (error, TP_ERROR, TP_ERROR_INVALID_HANDLE,
156           "Contact group name cannot be empty");
157       return NULL;
158     }
159 
160   return g_utf8_normalize (id, -1, G_NORMALIZE_ALL_COMPOSE);
161 }
162 
163 static void
create_handle_repos(TpBaseConnection * conn,TpHandleRepoIface * repos[NUM_TP_HANDLE_TYPES])164 create_handle_repos (TpBaseConnection *conn,
165                      TpHandleRepoIface *repos[NUM_TP_HANDLE_TYPES])
166 {
167   repos[TP_HANDLE_TYPE_CONTACT] = tp_dynamic_handle_repo_new
168       (TP_HANDLE_TYPE_CONTACT, example_contact_list_normalize_contact, NULL);
169 
170   repos[TP_HANDLE_TYPE_LIST] = tp_static_handle_repo_new
171       (TP_HANDLE_TYPE_LIST, example_contact_lists ());
172 
173   repos[TP_HANDLE_TYPE_GROUP] = tp_dynamic_handle_repo_new
174       (TP_HANDLE_TYPE_GROUP, example_contact_list_normalize_group, NULL);
175 }
176 
177 static void
alias_updated_cb(ExampleContactListManager * manager,TpHandle contact,ExampleContactListConnection * self)178 alias_updated_cb (ExampleContactListManager *manager,
179                   TpHandle contact,
180                   ExampleContactListConnection *self)
181 {
182   GPtrArray *aliases;
183   GValueArray *pair;
184 
185   pair = g_value_array_new (2);
186   g_value_array_append (pair, NULL);
187   g_value_array_append (pair, NULL);
188   g_value_init (pair->values + 0, G_TYPE_UINT);
189   g_value_init (pair->values + 1, G_TYPE_STRING);
190   g_value_set_uint (pair->values + 0, contact);
191   g_value_set_string (pair->values + 1,
192       example_contact_list_manager_get_alias (manager, contact));
193 
194   aliases = g_ptr_array_sized_new (1);
195   g_ptr_array_add (aliases, pair);
196 
197   tp_svc_connection_interface_aliasing_emit_aliases_changed (self, aliases);
198 
199   g_ptr_array_free (aliases, TRUE);
200   g_value_array_free (pair);
201 }
202 
203 static void
presence_updated_cb(ExampleContactListManager * manager,TpHandle contact,ExampleContactListConnection * self)204 presence_updated_cb (ExampleContactListManager *manager,
205                      TpHandle contact,
206                      ExampleContactListConnection *self)
207 {
208   TpBaseConnection *base = (TpBaseConnection *) self;
209   TpPresenceStatus *status;
210 
211   /* we ignore the presence indicated by the contact list for our own handle */
212   if (contact == base->self_handle)
213     return;
214 
215   status = tp_presence_status_new (
216       example_contact_list_manager_get_presence (manager, contact),
217       NULL);
218   tp_presence_mixin_emit_one_presence_update ((GObject *) self,
219       contact, status);
220   tp_presence_status_free (status);
221 }
222 
223 static GPtrArray *
create_channel_managers(TpBaseConnection * conn)224 create_channel_managers (TpBaseConnection *conn)
225 {
226   ExampleContactListConnection *self =
227     EXAMPLE_CONTACT_LIST_CONNECTION (conn);
228   GPtrArray *ret = g_ptr_array_sized_new (1);
229 
230   self->priv->list_manager =
231     EXAMPLE_CONTACT_LIST_MANAGER (g_object_new (
232           EXAMPLE_TYPE_CONTACT_LIST_MANAGER,
233           "connection", conn,
234           "simulation-delay", self->priv->simulation_delay,
235           NULL));
236 
237   g_signal_connect (self->priv->list_manager, "alias-updated",
238       G_CALLBACK (alias_updated_cb), self);
239   g_signal_connect (self->priv->list_manager, "presence-updated",
240       G_CALLBACK (presence_updated_cb), self);
241 
242   g_ptr_array_add (ret, self->priv->list_manager);
243 
244   return ret;
245 }
246 
247 static gboolean
start_connecting(TpBaseConnection * conn,GError ** error)248 start_connecting (TpBaseConnection *conn,
249                   GError **error)
250 {
251   ExampleContactListConnection *self = EXAMPLE_CONTACT_LIST_CONNECTION (conn);
252   TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn,
253       TP_HANDLE_TYPE_CONTACT);
254 
255   /* In a real connection manager we'd ask the underlying implementation to
256    * start connecting, then go to state CONNECTED when finished, but here
257    * we can do it immediately. */
258 
259   conn->self_handle = tp_handle_ensure (contact_repo, self->priv->account,
260       NULL, error);
261 
262   if (conn->self_handle == 0)
263     return FALSE;
264 
265   tp_base_connection_change_status (conn, TP_CONNECTION_STATUS_CONNECTED,
266       TP_CONNECTION_STATUS_REASON_REQUESTED);
267 
268   return TRUE;
269 }
270 
271 static void
shut_down(TpBaseConnection * conn)272 shut_down (TpBaseConnection *conn)
273 {
274   /* In a real connection manager we'd ask the underlying implementation to
275    * start shutting down, then call this function when finished, but here
276    * we can do it immediately. */
277   tp_base_connection_finish_shutdown (conn);
278 }
279 
280 static void
aliasing_fill_contact_attributes(GObject * object,const GArray * contacts,GHashTable * attributes)281 aliasing_fill_contact_attributes (GObject *object,
282                                   const GArray *contacts,
283                                   GHashTable *attributes)
284 {
285   ExampleContactListConnection *self =
286     EXAMPLE_CONTACT_LIST_CONNECTION (object);
287   guint i;
288 
289   for (i = 0; i < contacts->len; i++)
290     {
291       TpHandle contact = g_array_index (contacts, guint, i);
292 
293       tp_contacts_mixin_set_contact_attribute (attributes, contact,
294           TP_TOKEN_CONNECTION_INTERFACE_ALIASING_ALIAS,
295           tp_g_value_slice_new_string (
296             example_contact_list_manager_get_alias (self->priv->list_manager,
297               contact)));
298     }
299 }
300 
301 static void
constructed(GObject * object)302 constructed (GObject *object)
303 {
304   TpBaseConnection *base = TP_BASE_CONNECTION (object);
305   void (*chain_up) (GObject *) =
306     G_OBJECT_CLASS (example_contact_list_connection_parent_class)->constructed;
307 
308   if (chain_up != NULL)
309     chain_up (object);
310 
311   tp_contacts_mixin_init (object,
312       G_STRUCT_OFFSET (ExampleContactListConnection, contacts_mixin));
313   tp_base_connection_register_with_contacts_mixin (base);
314   tp_contacts_mixin_add_contact_attributes_iface (object,
315       TP_IFACE_CONNECTION_INTERFACE_ALIASING,
316       aliasing_fill_contact_attributes);
317 
318   tp_presence_mixin_init (object,
319       G_STRUCT_OFFSET (ExampleContactListConnection, presence_mixin));
320   tp_presence_mixin_simple_presence_register_with_contacts_mixin (object);
321 }
322 
323 static gboolean
status_available(GObject * object,guint index_)324 status_available (GObject *object,
325                   guint index_)
326 {
327   TpBaseConnection *base = TP_BASE_CONNECTION (object);
328 
329   if (base->status != TP_CONNECTION_STATUS_CONNECTED)
330     return FALSE;
331 
332   return TRUE;
333 }
334 
335 static GHashTable *
get_contact_statuses(GObject * object,const GArray * contacts,GError ** error)336 get_contact_statuses (GObject *object,
337                       const GArray *contacts,
338                       GError **error)
339 {
340   ExampleContactListConnection *self =
341     EXAMPLE_CONTACT_LIST_CONNECTION (object);
342   TpBaseConnection *base = TP_BASE_CONNECTION (object);
343   guint i;
344   GHashTable *result = g_hash_table_new_full (g_direct_hash, g_direct_equal,
345       NULL, (GDestroyNotify) tp_presence_status_free);
346 
347   for (i = 0; i < contacts->len; i++)
348     {
349       TpHandle contact = g_array_index (contacts, guint, i);
350       ExampleContactListPresence presence;
351       GHashTable *parameters;
352 
353       /* we get our own status from the connection, and everyone else's status
354        * from the contact lists */
355       if (contact == base->self_handle)
356         {
357           presence = (self->priv->away ? EXAMPLE_CONTACT_LIST_PRESENCE_AWAY
358               : EXAMPLE_CONTACT_LIST_PRESENCE_AVAILABLE);
359         }
360       else
361         {
362           presence = example_contact_list_manager_get_presence (
363               self->priv->list_manager, contact);
364         }
365 
366       parameters = g_hash_table_new_full (g_str_hash,
367           g_str_equal, NULL, (GDestroyNotify) tp_g_value_slice_free);
368       g_hash_table_insert (result, GUINT_TO_POINTER (contact),
369           tp_presence_status_new (presence, parameters));
370       g_hash_table_destroy (parameters);
371     }
372 
373   return result;
374 }
375 
376 static gboolean
set_own_status(GObject * object,const TpPresenceStatus * status,GError ** error)377 set_own_status (GObject *object,
378                 const TpPresenceStatus *status,
379                 GError **error)
380 {
381   ExampleContactListConnection *self =
382     EXAMPLE_CONTACT_LIST_CONNECTION (object);
383   TpBaseConnection *base = TP_BASE_CONNECTION (object);
384   GHashTable *presences;
385 
386   if (status->index == EXAMPLE_CONTACT_LIST_PRESENCE_AWAY)
387     {
388       if (self->priv->away)
389         return TRUE;
390 
391       self->priv->away = TRUE;
392     }
393   else
394     {
395       if (!self->priv->away)
396         return TRUE;
397 
398       self->priv->away = FALSE;
399     }
400 
401   presences = g_hash_table_new_full (g_direct_hash, g_direct_equal,
402       NULL, NULL);
403   g_hash_table_insert (presences, GUINT_TO_POINTER (base->self_handle),
404       (gpointer) status);
405   tp_presence_mixin_emit_presence_update (object, presences);
406   g_hash_table_destroy (presences);
407   return TRUE;
408 }
409 
410 static void
example_contact_list_connection_class_init(ExampleContactListConnectionClass * klass)411 example_contact_list_connection_class_init (
412     ExampleContactListConnectionClass *klass)
413 {
414   static const gchar *interfaces_always_present[] = {
415       TP_IFACE_CONNECTION_INTERFACE_ALIASING,
416       TP_IFACE_CONNECTION_INTERFACE_CONTACTS,
417       TP_IFACE_CONNECTION_INTERFACE_PRESENCE,
418       TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
419       TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
420       NULL };
421   TpBaseConnectionClass *base_class = (TpBaseConnectionClass *) klass;
422   GObjectClass *object_class = (GObjectClass *) klass;
423   GParamSpec *param_spec;
424 
425   object_class->get_property = get_property;
426   object_class->set_property = set_property;
427   object_class->constructed = constructed;
428   object_class->finalize = finalize;
429   g_type_class_add_private (klass,
430       sizeof (ExampleContactListConnectionPrivate));
431 
432   base_class->create_handle_repos = create_handle_repos;
433   base_class->get_unique_connection_name = get_unique_connection_name;
434   base_class->create_channel_managers = create_channel_managers;
435   base_class->start_connecting = start_connecting;
436   base_class->shut_down = shut_down;
437   base_class->interfaces_always_present = interfaces_always_present;
438 
439   param_spec = g_param_spec_string ("account", "Account name",
440       "The username of this user", NULL,
441       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
442       G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
443   g_object_class_install_property (object_class, PROP_ACCOUNT, param_spec);
444 
445   param_spec = g_param_spec_uint ("simulation-delay", "Simulation delay",
446       "Delay between simulated network events",
447       0, G_MAXUINT32, 1000,
448       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
449   g_object_class_install_property (object_class, PROP_SIMULATION_DELAY,
450       param_spec);
451 
452   tp_contacts_mixin_class_init (object_class,
453       G_STRUCT_OFFSET (ExampleContactListConnectionClass, contacts_mixin));
454   tp_presence_mixin_class_init (object_class,
455       G_STRUCT_OFFSET (ExampleContactListConnectionClass, presence_mixin),
456       status_available, get_contact_statuses, set_own_status,
457       example_contact_list_presence_statuses ());
458   tp_presence_mixin_simple_presence_init_dbus_properties (object_class);
459 }
460 
461 static void
get_alias_flags(TpSvcConnectionInterfaceAliasing * aliasing,DBusGMethodInvocation * context)462 get_alias_flags (TpSvcConnectionInterfaceAliasing *aliasing,
463                  DBusGMethodInvocation *context)
464 {
465   TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
466 
467   TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
468   tp_svc_connection_interface_aliasing_return_from_get_alias_flags (context,
469       TP_CONNECTION_ALIAS_FLAG_USER_SET);
470 }
471 
472 static void
get_aliases(TpSvcConnectionInterfaceAliasing * aliasing,const GArray * contacts,DBusGMethodInvocation * context)473 get_aliases (TpSvcConnectionInterfaceAliasing *aliasing,
474              const GArray *contacts,
475              DBusGMethodInvocation *context)
476 {
477   ExampleContactListConnection *self =
478     EXAMPLE_CONTACT_LIST_CONNECTION (aliasing);
479   TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
480   TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
481       TP_HANDLE_TYPE_CONTACT);
482   GHashTable *result;
483   GError *error = NULL;
484   guint i;
485 
486   TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
487 
488   if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
489     {
490       dbus_g_method_return_error (context, error);
491       g_error_free (error);
492       return;
493     }
494 
495   result = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
496 
497   for (i = 0; i < contacts->len; i++)
498     {
499       TpHandle contact = g_array_index (contacts, TpHandle, i);
500       const gchar *alias = example_contact_list_manager_get_alias (
501           self->priv->list_manager, contact);
502 
503       g_hash_table_insert (result, GUINT_TO_POINTER (contact),
504           (gchar *) alias);
505     }
506 
507   tp_svc_connection_interface_aliasing_return_from_get_aliases (context,
508       result);
509   g_hash_table_destroy (result);
510 }
511 
512 static void
request_aliases(TpSvcConnectionInterfaceAliasing * aliasing,const GArray * contacts,DBusGMethodInvocation * context)513 request_aliases (TpSvcConnectionInterfaceAliasing *aliasing,
514                  const GArray *contacts,
515                  DBusGMethodInvocation *context)
516 {
517   ExampleContactListConnection *self =
518     EXAMPLE_CONTACT_LIST_CONNECTION (aliasing);
519   TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
520   TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
521       TP_HANDLE_TYPE_CONTACT);
522   GPtrArray *result;
523   gchar **strings;
524   GError *error = NULL;
525   guint i;
526 
527   TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
528 
529   if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
530     {
531       dbus_g_method_return_error (context, error);
532       g_error_free (error);
533       return;
534     }
535 
536   result = g_ptr_array_sized_new (contacts->len + 1);
537 
538   for (i = 0; i < contacts->len; i++)
539     {
540       TpHandle contact = g_array_index (contacts, TpHandle, i);
541       const gchar *alias = example_contact_list_manager_get_alias (
542           self->priv->list_manager, contact);
543 
544       g_ptr_array_add (result, (gchar *) alias);
545     }
546 
547   g_ptr_array_add (result, NULL);
548   strings = (gchar **) g_ptr_array_free (result, FALSE);
549   tp_svc_connection_interface_aliasing_return_from_request_aliases (context,
550       (const gchar **) strings);
551   g_free (strings);
552 }
553 
554 static void
set_aliases(TpSvcConnectionInterfaceAliasing * aliasing,GHashTable * aliases,DBusGMethodInvocation * context)555 set_aliases (TpSvcConnectionInterfaceAliasing *aliasing,
556              GHashTable *aliases,
557              DBusGMethodInvocation *context)
558 {
559   ExampleContactListConnection *self =
560     EXAMPLE_CONTACT_LIST_CONNECTION (aliasing);
561   TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
562   TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
563       TP_HANDLE_TYPE_CONTACT);
564   GHashTableIter iter;
565   gpointer key, value;
566 
567   g_hash_table_iter_init (&iter, aliases);
568 
569   while (g_hash_table_iter_next (&iter, &key, &value))
570     {
571       GError *error = NULL;
572 
573       if (!tp_handle_is_valid (contact_repo, GPOINTER_TO_UINT (key),
574             &error))
575         {
576           dbus_g_method_return_error (context, error);
577           g_error_free (error);
578           return;
579         }
580     }
581 
582   g_hash_table_iter_init (&iter, aliases);
583 
584   while (g_hash_table_iter_next (&iter, &key, &value))
585     {
586       example_contact_list_manager_set_alias (self->priv->list_manager,
587           GPOINTER_TO_UINT (key), value);
588     }
589 
590   tp_svc_connection_interface_aliasing_return_from_set_aliases (context);
591 }
592 
593 static void
init_aliasing(gpointer iface,gpointer iface_data G_GNUC_UNUSED)594 init_aliasing (gpointer iface,
595                gpointer iface_data G_GNUC_UNUSED)
596 {
597   TpSvcConnectionInterfaceAliasingClass *klass = iface;
598 
599 #define IMPLEMENT(x) tp_svc_connection_interface_aliasing_implement_##x (\
600     klass, x)
601   IMPLEMENT(get_alias_flags);
602   IMPLEMENT(request_aliases);
603   IMPLEMENT(get_aliases);
604   IMPLEMENT(set_aliases);
605 #undef IMPLEMENT
606 }
607