1 /*
2  * connection-contact-info.c - ContactInfo implementation
3  * Copyright © 2011 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 "connection-contact-info.h"
21 
22 #include <telepathy-glib/interfaces.h>
23 /* Slightly sketchy; included for TpContactInfoFieldSpec. */
24 #include <telepathy-glib/connection.h>
25 #include <telepathy-glib/gtypes.h>
26 
27 #include "contact-manager.h"
28 
29 enum {
30     PROP_CONTACT_INFO_FLAGS,
31     PROP_SUPPORTED_FIELDS
32 };
33 
34 static gchar *i_heart_the_internet[] = { "type=internet", NULL };
35 
36 static GPtrArray *
get_supported_fields(void)37 get_supported_fields (void)
38 {
39   static TpContactInfoFieldSpec supported_fields[] = {
40       /* We omit 'nickname' because it shows up, unmodifiably, as the alias. */
41       { "n", NULL,
42         TP_CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT, 1 },
43       /* It's a little bit sketchy to expose 1st + ' ' + last as FN. But such
44        * are the limitations of the protocol.
45        */
46       { "fn", NULL,
47         TP_CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT, 1 },
48       { "email", i_heart_the_internet,
49         TP_CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT, 1 },
50       /* x-jabber is used for compatibility with Gabble */
51       { "x-jabber", NULL,
52         TP_CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT, 1 },
53       /* Heh, we could also include the contact's IP address(es) here. */
54       { NULL }
55   };
56   static gsize supported_fields_ptr_array = 0;
57 
58   if (g_once_init_enter (&supported_fields_ptr_array))
59     {
60       GPtrArray *fields = dbus_g_type_specialized_construct (
61           TP_ARRAY_TYPE_FIELD_SPECS);
62       TpContactInfoFieldSpec *spec;
63 
64       for (spec = supported_fields; spec->name != NULL; spec++)
65         g_ptr_array_add (fields,
66             tp_value_array_build (4,
67                 G_TYPE_STRING, spec->name,
68                 G_TYPE_STRV, spec->parameters,
69                 G_TYPE_UINT, spec->flags,
70                 G_TYPE_UINT, spec->max,
71                 G_TYPE_INVALID));
72 
73       g_once_init_leave (&supported_fields_ptr_array, (gsize) fields);
74     }
75 
76   return (GPtrArray *) supported_fields_ptr_array;
77 }
78 
79 static void
salut_conn_contact_info_get_property(GObject * object,GQuark iface,GQuark name,GValue * value,gpointer getter_data)80 salut_conn_contact_info_get_property (
81     GObject *object,
82     GQuark iface,
83     GQuark name,
84     GValue *value,
85     gpointer getter_data)
86 {
87   switch (GPOINTER_TO_UINT (getter_data))
88     {
89     case PROP_CONTACT_INFO_FLAGS:
90       g_value_set_uint (value, TP_CONTACT_INFO_FLAG_PUSH);
91       break;
92     case PROP_SUPPORTED_FIELDS:
93       g_value_set_boxed (value, get_supported_fields ());
94       break;
95     default:
96       g_assert_not_reached ();
97     }
98 }
99 
100 void
salut_conn_contact_info_class_init(SalutConnectionClass * klass)101 salut_conn_contact_info_class_init (
102     SalutConnectionClass *klass)
103 {
104   static TpDBusPropertiesMixinPropImpl props[] = {
105       { "ContactInfoFlags", GUINT_TO_POINTER (PROP_CONTACT_INFO_FLAGS), NULL },
106       { "SupportedFields", GUINT_TO_POINTER (PROP_SUPPORTED_FIELDS), NULL },
107       { NULL }
108   };
109 
110   tp_dbus_properties_mixin_implement_interface (
111       G_OBJECT_CLASS (klass),
112       TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_INFO,
113       salut_conn_contact_info_get_property,
114       NULL,
115       props);
116 }
117 
118 static void
add_singleton_field(GPtrArray * contact_info,const gchar * field_name,gchar ** parameters,const gchar * value)119 add_singleton_field (
120     GPtrArray *contact_info,
121     const gchar *field_name,
122     gchar **parameters,
123     const gchar *value)
124 {
125   const gchar *field_value[] = { value, NULL };
126 
127   g_ptr_array_add (contact_info,
128       tp_value_array_build (3,
129           G_TYPE_STRING, field_name,
130           G_TYPE_STRV, parameters,
131           G_TYPE_STRV, field_value,
132           G_TYPE_INVALID));
133 }
134 
135 static GPtrArray *
build_contact_info(const gchar * first,const gchar * last,const gchar * full_name,const gchar * email,const gchar * jid)136 build_contact_info (
137     const gchar *first,
138     const gchar *last,
139     const gchar *full_name,
140     const gchar *email,
141     const gchar *jid)
142 {
143   GPtrArray *contact_info = dbus_g_type_specialized_construct (
144       TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST);
145 
146   if (first != NULL || last != NULL)
147     {
148       const gchar *field_value[] = {
149           last != NULL ? last : "",
150           first != NULL ? first : "",
151           "",
152           "",
153           "",
154           NULL
155       };
156 
157       g_ptr_array_add (contact_info,
158           tp_value_array_build (3,
159               G_TYPE_STRING, "n",
160               G_TYPE_STRV, NULL,
161               G_TYPE_STRV, field_value,
162               G_TYPE_INVALID));
163 
164       g_warn_if_fail (full_name != NULL);
165       add_singleton_field (contact_info, "fn", NULL, full_name);
166     }
167 
168   if (email != NULL)
169     add_singleton_field (contact_info, "email", i_heart_the_internet, email);
170 
171   if (jid != NULL)
172     add_singleton_field (contact_info, "x-jabber", NULL, jid);
173 
174   return contact_info;
175 }
176 
177 static GPtrArray *
build_contact_info_for_contact(SalutContact * contact)178 build_contact_info_for_contact (
179     SalutContact *contact)
180 {
181   g_return_val_if_fail (contact != NULL, NULL);
182 
183   return build_contact_info (contact->first, contact->last, contact->full_name,
184       contact->email, contact->jid);
185 }
186 
187 static void
salut_conn_contact_info_fill_contact_attributes(GObject * obj,const GArray * contacts,GHashTable * attributes_hash)188 salut_conn_contact_info_fill_contact_attributes (
189     GObject *obj,
190     const GArray *contacts,
191     GHashTable *attributes_hash)
192 {
193   guint i;
194   SalutConnection *self = SALUT_CONNECTION (obj);
195   TpBaseConnection *base = TP_BASE_CONNECTION (self);
196   SalutContactManager *contact_manager;
197 
198   g_object_get (self, "contact-manager", &contact_manager, NULL);
199 
200   for (i = 0; i < contacts->len; i++)
201     {
202       TpHandle handle = g_array_index (contacts, TpHandle, i);
203       GPtrArray *contact_info = NULL;
204 
205       if (base->self_handle == handle)
206         {
207           /* TODO: dig contact info out of SalutSelf. There's overlap with
208            * connection parameters here … should they be DBus_Property
209            * parameters? Should we have a new flag which means “you set this on
210            * ContactInfo”? What?
211            */
212         }
213       else
214         {
215           SalutContact *contact = salut_contact_manager_get_contact (
216               contact_manager, handle);
217           if (contact != NULL)
218             {
219               contact_info = build_contact_info_for_contact (contact);
220               g_object_unref (contact);
221             }
222         }
223 
224       if (contact_info != NULL)
225         tp_contacts_mixin_set_contact_attribute (attributes_hash,
226             handle, TP_TOKEN_CONNECTION_INTERFACE_CONTACT_INFO_INFO,
227             tp_g_value_slice_new_take_boxed (
228                 TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST, contact_info));
229     }
230 
231   g_object_unref (contact_manager);
232 }
233 
salut_conn_contact_info_init(SalutConnection * self)234 void salut_conn_contact_info_init (
235     SalutConnection *self)
236 {
237   tp_contacts_mixin_add_contact_attributes_iface (
238       G_OBJECT (self),
239       TP_IFACE_CONNECTION_INTERFACE_CONTACT_INFO,
240       salut_conn_contact_info_fill_contact_attributes);
241 }
242 
243 void
salut_conn_contact_info_changed(SalutConnection * self,SalutContact * contact,TpHandle handle)244 salut_conn_contact_info_changed (
245     SalutConnection *self,
246     SalutContact *contact,
247     TpHandle handle)
248 {
249   GPtrArray *contact_info = build_contact_info_for_contact (contact);
250 
251   tp_svc_connection_interface_contact_info_emit_contact_info_changed (self,
252       handle, contact_info);
253   g_boxed_free (TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST, contact_info);
254 }
255 
256 static void
salut_conn_contact_info_get_contact_info(TpSvcConnectionInterfaceContactInfo * iface,const GArray * contacts,DBusGMethodInvocation * context)257 salut_conn_contact_info_get_contact_info (
258     TpSvcConnectionInterfaceContactInfo *iface,
259     const GArray *contacts,
260     DBusGMethodInvocation *context)
261 {
262   SalutConnection *self = SALUT_CONNECTION (iface);
263   TpBaseConnection *base = (TpBaseConnection *) self;
264   TpHandleRepoIface *contacts_repo =
265       tp_base_connection_get_handles (base, TP_HANDLE_TYPE_CONTACT);
266   SalutContactManager *contact_manager;
267   guint i;
268   GHashTable *ret;
269   GError *error = NULL;
270 
271   TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (TP_BASE_CONNECTION (iface),
272       context);
273 
274   if (!tp_handles_are_valid (contacts_repo, contacts, FALSE, &error))
275     {
276       dbus_g_method_return_error (context, error);
277       g_error_free (error);
278       return;
279     }
280 
281   g_object_get (self, "contact-manager", &contact_manager, NULL);
282   ret = dbus_g_type_specialized_construct (TP_HASH_TYPE_CONTACT_INFO_MAP);
283 
284   for (i = 0; i < contacts->len; i++)
285     {
286       TpHandle handle = g_array_index (contacts, TpHandle, i);
287       SalutContact *contact = salut_contact_manager_get_contact (
288           contact_manager, handle);
289 
290       if (contact != NULL)
291         {
292           g_hash_table_insert (ret, GUINT_TO_POINTER (handle),
293               build_contact_info_for_contact (contact));
294           g_object_unref (contact);
295         }
296     }
297 
298   tp_svc_connection_interface_contact_info_return_from_get_contact_info (
299       context, ret);
300   g_boxed_free (TP_HASH_TYPE_CONTACT_INFO_MAP, ret);
301   g_object_unref (contact_manager);
302 }
303 
304 static void
salut_conn_contact_info_request_contact_info(TpSvcConnectionInterfaceContactInfo * iface,guint handle,DBusGMethodInvocation * context)305 salut_conn_contact_info_request_contact_info (
306     TpSvcConnectionInterfaceContactInfo *iface,
307     guint handle,
308     DBusGMethodInvocation *context)
309 {
310   SalutConnection *self = SALUT_CONNECTION (iface);
311   TpBaseConnection *base = (TpBaseConnection *) self;
312   TpHandleRepoIface *contacts_repo =
313       tp_base_connection_get_handles (base, TP_HANDLE_TYPE_CONTACT);
314   GError *error = NULL;
315 
316   TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (TP_BASE_CONNECTION (iface),
317       context);
318 
319   if (!tp_handle_is_valid (contacts_repo, handle, &error))
320     {
321       dbus_g_method_return_error (context, error);
322       g_error_free (error);
323     }
324   else
325     {
326       SalutContactManager *contact_manager;
327       SalutContact *contact;
328 
329       g_object_get (self, "contact-manager", &contact_manager, NULL);
330       contact = salut_contact_manager_get_contact (contact_manager, handle);
331       g_object_unref (contact_manager);
332 
333       if (contact != NULL)
334         {
335           GPtrArray *contact_info = build_contact_info_for_contact (contact);
336 
337           tp_svc_connection_interface_contact_info_return_from_request_contact_info (
338               context, contact_info);
339           g_boxed_free (TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST, contact_info);
340         }
341       else
342         {
343           error = g_error_new (TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
344               "No information available for '%s'",
345               tp_handle_inspect (contacts_repo, handle));
346           dbus_g_method_return_error (context, error);
347           g_error_free (error);
348         }
349     }
350 }
351 
352 static void
salut_conn_contact_info_refresh_contact_info(TpSvcConnectionInterfaceContactInfo * iface,const GArray * contacts,DBusGMethodInvocation * context)353 salut_conn_contact_info_refresh_contact_info (
354     TpSvcConnectionInterfaceContactInfo *iface,
355     const GArray *contacts,
356     DBusGMethodInvocation *context)
357 {
358   /* This is a no-op on link-local XMPP: everything's always pushed to us. */
359   tp_svc_connection_interface_contact_info_return_from_refresh_contact_info (context);
360 }
361 
362 void
salut_conn_contact_info_iface_init(gpointer g_iface,gpointer iface_data)363 salut_conn_contact_info_iface_init (
364     gpointer g_iface,
365     gpointer iface_data)
366 {
367   TpSvcConnectionInterfaceContactInfoClass *klass = g_iface;
368 
369 #define IMPLEMENT(x) tp_svc_connection_interface_contact_info_implement_##x \
370     (klass, salut_conn_contact_info_##x)
371   IMPLEMENT (get_contact_info);
372   IMPLEMENT (request_contact_info);
373   IMPLEMENT (refresh_contact_info);
374 #undef IMPLEMENT
375 }
376