]> git.0d.be Git - empathy.git/blob - libempathy/empathy-utils.c
don't pass a GError when first trying to start gnome-contacts
[empathy.git] / libempathy / empathy-utils.c
1 /*
2  * Copyright (C) 2003-2007 Imendio AB
3  * Copyright (C) 2007-2011 Collabora Ltd.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program 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  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA  02110-1301  USA
19  *
20  * Authors: Richard Hult <richard@imendio.com>
21  *          Martyn Russell <martyn@imendio.com>
22  *          Xavier Claessens <xclaesse@gmail.com>
23  *
24  * Some snippets are taken from GnuTLS 2.8.6, which is distributed under the
25  * same GNU Lesser General Public License 2.1 (or later) version. See
26  * empathy_get_x509_certified_hostname ().
27  */
28
29 #include "config.h"
30 #include "empathy-utils.h"
31
32 #include <glib/gi18n-lib.h>
33 #include <dbus/dbus-protocol.h>
34 #include <math.h>
35 #include <telepathy-glib/telepathy-glib-dbus.h>
36
37 #include "empathy-client-factory.h"
38 #include "extensions.h"
39
40 #include <math.h>
41
42 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
43 #include "empathy-debug.h"
44
45 /* Translation between presence types and string */
46 static struct {
47   const gchar *name;
48   TpConnectionPresenceType type;
49 } presence_types[] = {
50   { "available", TP_CONNECTION_PRESENCE_TYPE_AVAILABLE },
51   { "busy",      TP_CONNECTION_PRESENCE_TYPE_BUSY },
52   { "away",      TP_CONNECTION_PRESENCE_TYPE_AWAY },
53   { "ext_away",  TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY },
54   { "hidden",    TP_CONNECTION_PRESENCE_TYPE_HIDDEN },
55   { "offline",   TP_CONNECTION_PRESENCE_TYPE_OFFLINE },
56   { "unset",     TP_CONNECTION_PRESENCE_TYPE_UNSET },
57   { "unknown",   TP_CONNECTION_PRESENCE_TYPE_UNKNOWN },
58   { "error",     TP_CONNECTION_PRESENCE_TYPE_ERROR },
59   /* alternative names */
60   { "dnd",      TP_CONNECTION_PRESENCE_TYPE_BUSY },
61   { "brb",      TP_CONNECTION_PRESENCE_TYPE_AWAY },
62   { "xa",       TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY },
63   { NULL, },
64 };
65
66 static gboolean
67 properties_contains (gchar **list,
68                      gint length,
69                      const gchar *property);
70
71 static gboolean
72 check_writeable_property (TpConnection *connection,
73                           FolksIndividual *individual,
74                           gchar *property);
75
76 void
77 empathy_init (void)
78 {
79   static gboolean initialized = FALSE;
80   TpAccountManager *am;
81   EmpathyClientFactory *factory;
82
83   if (initialized)
84     return;
85
86   g_type_init ();
87
88   /* Setup gettext */
89   bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
90   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
91
92   /* Setup debug output for empathy and telepathy-glib */
93   if (g_getenv ("EMPATHY_TIMING") != NULL)
94     g_log_set_default_handler (tp_debug_timestamped_log_handler, NULL);
95
96   empathy_debug_set_flags (g_getenv ("EMPATHY_DEBUG"));
97   tp_debug_divert_messages (g_getenv ("EMPATHY_LOGFILE"));
98
99   emp_cli_init ();
100
101   initialized = TRUE;
102
103   factory = empathy_client_factory_dup ();
104   am = tp_account_manager_new_with_factory (TP_SIMPLE_CLIENT_FACTORY (factory));
105   tp_account_manager_set_default (am);
106
107   g_object_unref (factory);
108   g_object_unref (am);
109 }
110
111 xmlNodePtr
112 empathy_xml_node_get_child (xmlNodePtr   node,
113     const gchar *child_name)
114 {
115   xmlNodePtr l;
116
117   g_return_val_if_fail (node != NULL, NULL);
118   g_return_val_if_fail (child_name != NULL, NULL);
119
120   for (l = node->children; l; l = l->next)
121     {
122       if (l->name && strcmp ((const gchar *) l->name, child_name) == 0)
123         return l;
124     }
125
126   return NULL;
127 }
128
129 xmlChar *
130 empathy_xml_node_get_child_content (xmlNodePtr   node,
131     const gchar *child_name)
132 {
133   xmlNodePtr l;
134
135   g_return_val_if_fail (node != NULL, NULL);
136   g_return_val_if_fail (child_name != NULL, NULL);
137
138   l = empathy_xml_node_get_child (node, child_name);
139   if (l != NULL)
140     return xmlNodeGetContent (l);
141
142   return NULL;
143 }
144
145 xmlNodePtr
146 empathy_xml_node_find_child_prop_value (xmlNodePtr   node,
147     const gchar *prop_name,
148     const gchar *prop_value)
149 {
150   xmlNodePtr l;
151   xmlNodePtr found = NULL;
152
153   g_return_val_if_fail (node != NULL, NULL);
154   g_return_val_if_fail (prop_name != NULL, NULL);
155   g_return_val_if_fail (prop_value != NULL, NULL);
156
157   for (l = node->children; l && !found; l = l->next)
158     {
159       xmlChar *prop;
160
161       if (!xmlHasProp (l, (const xmlChar *) prop_name))
162         continue;
163
164       prop = xmlGetProp (l, (const xmlChar *) prop_name);
165       if (prop && strcmp ((const gchar *) prop, prop_value) == 0)
166         found = l;
167
168       xmlFree (prop);
169     }
170
171   return found;
172 }
173
174 const gchar *
175 empathy_presence_get_default_message (TpConnectionPresenceType presence)
176 {
177   switch (presence)
178     {
179       case TP_CONNECTION_PRESENCE_TYPE_AVAILABLE:
180         return _("Available");
181       case TP_CONNECTION_PRESENCE_TYPE_BUSY:
182         return _("Busy");
183       case TP_CONNECTION_PRESENCE_TYPE_AWAY:
184       case TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY:
185         return _("Away");
186       case TP_CONNECTION_PRESENCE_TYPE_HIDDEN:
187         return _("Invisible");
188       case TP_CONNECTION_PRESENCE_TYPE_OFFLINE:
189         return _("Offline");
190       case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
191         /* translators: presence type is unknown */
192         return C_("presence", "Unknown");
193       case TP_CONNECTION_PRESENCE_TYPE_UNSET:
194       case TP_CONNECTION_PRESENCE_TYPE_ERROR:
195       default:
196         return NULL;
197     }
198
199   return NULL;
200 }
201
202 const gchar *
203 empathy_presence_to_str (TpConnectionPresenceType presence)
204 {
205   int i;
206
207   for (i = 0 ; presence_types[i].name != NULL; i++)
208     if (presence == presence_types[i].type)
209       return presence_types[i].name;
210
211   return NULL;
212 }
213
214 TpConnectionPresenceType
215 empathy_presence_from_str (const gchar *str)
216 {
217   int i;
218
219   for (i = 0 ; presence_types[i].name != NULL; i++)
220     if (!tp_strdiff (str, presence_types[i].name))
221       return presence_types[i].type;
222
223   return TP_CONNECTION_PRESENCE_TYPE_UNSET;
224 }
225
226 static const gchar *
227 empathy_status_reason_get_default_message (TpConnectionStatusReason reason)
228 {
229   switch (reason)
230     {
231       case TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED:
232         return _("No reason specified");
233       case TP_CONNECTION_STATUS_REASON_REQUESTED:
234         return _("Status is set to offline");
235       case TP_CONNECTION_STATUS_REASON_NETWORK_ERROR:
236         return _("Network error");
237       case TP_CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED:
238         return _("Authentication failed");
239       case TP_CONNECTION_STATUS_REASON_ENCRYPTION_ERROR:
240         return _("Encryption error");
241       case TP_CONNECTION_STATUS_REASON_NAME_IN_USE:
242         return _("Name in use");
243       case TP_CONNECTION_STATUS_REASON_CERT_NOT_PROVIDED:
244         return _("Certificate not provided");
245       case TP_CONNECTION_STATUS_REASON_CERT_UNTRUSTED:
246         return _("Certificate untrusted");
247       case TP_CONNECTION_STATUS_REASON_CERT_EXPIRED:
248         return _("Certificate expired");
249       case TP_CONNECTION_STATUS_REASON_CERT_NOT_ACTIVATED:
250         return _("Certificate not activated");
251       case TP_CONNECTION_STATUS_REASON_CERT_HOSTNAME_MISMATCH:
252         return _("Certificate hostname mismatch");
253       case TP_CONNECTION_STATUS_REASON_CERT_FINGERPRINT_MISMATCH:
254         return _("Certificate fingerprint mismatch");
255       case TP_CONNECTION_STATUS_REASON_CERT_SELF_SIGNED:
256         return _("Certificate self-signed");
257       case TP_CONNECTION_STATUS_REASON_CERT_OTHER_ERROR:
258         return _("Certificate error");
259       default:
260         return _("Unknown reason");
261     }
262 }
263
264 static GHashTable *
265 create_errors_to_message_hash (void)
266 {
267   GHashTable *errors;
268
269   errors = g_hash_table_new (g_str_hash, g_str_equal);
270   g_hash_table_insert (errors, TP_ERROR_STR_NETWORK_ERROR, _("Network error"));
271   g_hash_table_insert (errors, TP_ERROR_STR_AUTHENTICATION_FAILED,
272     _("Authentication failed"));
273   g_hash_table_insert (errors, TP_ERROR_STR_ENCRYPTION_ERROR,
274     _("Encryption error"));
275   g_hash_table_insert (errors, TP_ERROR_STR_CERT_NOT_PROVIDED,
276     _("Certificate not provided"));
277   g_hash_table_insert (errors, TP_ERROR_STR_CERT_UNTRUSTED,
278     _("Certificate untrusted"));
279   g_hash_table_insert (errors, TP_ERROR_STR_CERT_EXPIRED,
280     _("Certificate expired"));
281   g_hash_table_insert (errors, TP_ERROR_STR_CERT_NOT_ACTIVATED,
282     _("Certificate not activated"));
283   g_hash_table_insert (errors, TP_ERROR_STR_CERT_HOSTNAME_MISMATCH,
284     _("Certificate hostname mismatch"));
285   g_hash_table_insert (errors, TP_ERROR_STR_CERT_FINGERPRINT_MISMATCH,
286     _("Certificate fingerprint mismatch"));
287   g_hash_table_insert (errors, TP_ERROR_STR_CERT_SELF_SIGNED,
288     _("Certificate self-signed"));
289   g_hash_table_insert (errors, TP_ERROR_STR_CANCELLED,
290     _("Status is set to offline"));
291   g_hash_table_insert (errors, TP_ERROR_STR_ENCRYPTION_NOT_AVAILABLE,
292     _("Encryption is not available"));
293   g_hash_table_insert (errors, TP_ERROR_STR_CERT_INVALID,
294     _("Certificate is invalid"));
295   g_hash_table_insert (errors, TP_ERROR_STR_CONNECTION_REFUSED,
296     _("Connection has been refused"));
297   g_hash_table_insert (errors, TP_ERROR_STR_CONNECTION_FAILED,
298     _("Connection can't be established"));
299   g_hash_table_insert (errors, TP_ERROR_STR_CONNECTION_LOST,
300     _("Connection has been lost"));
301   g_hash_table_insert (errors, TP_ERROR_STR_ALREADY_CONNECTED,
302     _("This account is already connected to the server"));
303   g_hash_table_insert (errors, TP_ERROR_STR_CONNECTION_REPLACED,
304     _("Connection has been replaced by a new connection using the "
305     "same resource"));
306   g_hash_table_insert (errors, TP_ERROR_STR_REGISTRATION_EXISTS,
307     _("The account already exists on the server"));
308   g_hash_table_insert (errors, TP_ERROR_STR_SERVICE_BUSY,
309     _("Server is currently too busy to handle the connection"));
310   g_hash_table_insert (errors, TP_ERROR_STR_CERT_REVOKED,
311     _("Certificate has been revoked"));
312   g_hash_table_insert (errors, TP_ERROR_STR_CERT_INSECURE,
313     _("Certificate uses an insecure cipher algorithm or is "
314     "cryptographically weak"));
315   g_hash_table_insert (errors, TP_ERROR_STR_CERT_LIMIT_EXCEEDED,
316     _("The length of the server certificate, or the depth of the "
317     "server certificate chain, exceed the limits imposed by the "
318     "cryptography library"));
319   g_hash_table_insert (errors, TP_ERROR_STR_SOFTWARE_UPGRADE_REQUIRED,
320     _("Your software is too old"));
321   g_hash_table_insert (errors, DBUS_ERROR_NO_REPLY,
322     _("Internal error"));
323
324   return errors;
325 }
326
327 static const gchar *
328 empathy_dbus_error_name_get_default_message  (const gchar *error)
329 {
330   static GHashTable *errors_to_message = NULL;
331
332   if (error == NULL)
333     return NULL;
334
335   if (G_UNLIKELY (errors_to_message == NULL))
336     errors_to_message = create_errors_to_message_hash ();
337
338   return g_hash_table_lookup (errors_to_message, error);
339 }
340
341 const gchar *
342 empathy_account_get_error_message (TpAccount *account,
343     gboolean *user_requested)
344 {
345   const gchar *dbus_error;
346   const gchar *message;
347   const GHashTable *details = NULL;
348   TpConnectionStatusReason reason;
349
350   dbus_error = tp_account_get_detailed_error (account, &details);
351
352   if (user_requested != NULL)
353     {
354       if (tp_asv_get_boolean (details, "user-requested", NULL))
355         *user_requested = TRUE;
356       else
357         *user_requested = FALSE;
358     }
359
360   message = empathy_dbus_error_name_get_default_message (dbus_error);
361   if (message != NULL)
362     return message;
363
364   tp_account_get_connection_status (account, &reason);
365
366   DEBUG ("Don't understand error '%s'; fallback to the status reason (%u)",
367     dbus_error, reason);
368
369   return empathy_status_reason_get_default_message (reason);
370 }
371
372 gchar *
373 empathy_file_lookup (const gchar *filename, const gchar *subdir)
374 {
375   gchar *path;
376
377   if (subdir == NULL)
378     subdir = ".";
379
380   path = g_build_filename (g_getenv ("EMPATHY_SRCDIR"), subdir, filename, NULL);
381   if (!g_file_test (path, G_FILE_TEST_EXISTS))
382     {
383       g_free (path);
384       path = g_build_filename (DATADIR, "empathy", filename, NULL);
385     }
386
387   return path;
388 }
389
390 gint
391 empathy_uint_compare (gconstpointer a,
392     gconstpointer b)
393 {
394   return *(guint *) a - *(guint *) b;
395 }
396
397 GType
398 empathy_type_dbus_ao (void)
399 {
400   static GType t = 0;
401
402   if (G_UNLIKELY (t == 0))
403      t = dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH);
404
405   return t;
406 }
407
408 gboolean
409 empathy_account_manager_get_accounts_connected (gboolean *connecting)
410 {
411   TpAccountManager *manager;
412   GList *accounts, *l;
413   gboolean out_connecting = FALSE;
414   gboolean out_connected = FALSE;
415
416   manager = tp_account_manager_dup ();
417
418   if (G_UNLIKELY (!tp_proxy_is_prepared (manager,
419           TP_ACCOUNT_MANAGER_FEATURE_CORE)))
420     g_critical (G_STRLOC ": %s called before AccountManager ready", G_STRFUNC);
421
422   accounts = tp_account_manager_dup_valid_accounts (manager);
423
424   for (l = accounts; l != NULL; l = l->next)
425     {
426       TpConnectionStatus s = tp_account_get_connection_status (
427           TP_ACCOUNT (l->data), NULL);
428
429       if (s == TP_CONNECTION_STATUS_CONNECTING)
430         out_connecting = TRUE;
431       else if (s == TP_CONNECTION_STATUS_CONNECTED)
432         out_connected = TRUE;
433
434       if (out_connecting && out_connected)
435         break;
436     }
437
438   g_list_free_full (accounts, g_object_unref);
439   g_object_unref (manager);
440
441   if (connecting != NULL)
442     *connecting = out_connecting;
443
444   return out_connected;
445 }
446
447 /* Translate Folks' general presence type to the Tp presence type */
448 TpConnectionPresenceType
449 empathy_folks_presence_type_to_tp (FolksPresenceType type)
450 {
451   return (TpConnectionPresenceType) type;
452 }
453
454 /* Returns TRUE if the given Individual contains a TpContact */
455 gboolean
456 empathy_folks_individual_contains_contact (FolksIndividual *individual)
457 {
458   GeeSet *personas;
459   GeeIterator *iter;
460   gboolean retval = FALSE;
461
462   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), FALSE);
463
464   personas = folks_individual_get_personas (individual);
465   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
466   while (!retval && gee_iterator_next (iter))
467     {
468       FolksPersona *persona = gee_iterator_get (iter);
469       TpContact *contact = NULL;
470
471       if (empathy_folks_persona_is_interesting (persona))
472         contact = tpf_persona_get_contact (TPF_PERSONA (persona));
473
474       g_clear_object (&persona);
475
476       if (contact != NULL)
477         retval = TRUE;
478     }
479   g_clear_object (&iter);
480
481   return retval;
482 }
483
484 /* TODO: this needs to be eliminated (and replaced in some cases with user
485  * prompts) when we break the assumption that FolksIndividuals are 1:1 with
486  * TpContacts */
487
488 /* Retrieve the EmpathyContact corresponding to the first TpContact contained
489  * within the given Individual. Note that this is a temporary convenience. See
490  * the TODO above. */
491 EmpathyContact *
492 empathy_contact_dup_from_folks_individual (FolksIndividual *individual)
493 {
494   GeeSet *personas;
495   GeeIterator *iter;
496   EmpathyContact *contact = NULL;
497
498   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
499
500   personas = folks_individual_get_personas (individual);
501   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
502   while (gee_iterator_next (iter) && (contact == NULL))
503     {
504       TpfPersona *persona = gee_iterator_get (iter);
505
506       if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (persona)))
507         {
508           TpContact *tp_contact;
509
510           tp_contact = tpf_persona_get_contact (persona);
511           if (tp_contact != NULL)
512             {
513               contact = empathy_contact_dup_from_tp_contact (tp_contact);
514               empathy_contact_set_persona (contact, FOLKS_PERSONA (persona));
515             }
516         }
517       g_clear_object (&persona);
518     }
519   g_clear_object (&iter);
520
521   if (contact == NULL)
522     {
523       DEBUG ("Can't create an EmpathyContact for Individual %s",
524           folks_individual_get_id (individual));
525     }
526
527   return contact;
528 }
529
530 TpChannelGroupChangeReason
531 tp_channel_group_change_reason_from_folks_groups_change_reason (
532     FolksGroupDetailsChangeReason reason)
533 {
534   return (TpChannelGroupChangeReason) reason;
535 }
536
537 TpfPersonaStore *
538 empathy_dup_persona_store_for_connection (TpConnection *connection)
539 {
540   FolksBackendStore *backend_store;
541   FolksBackend *backend;
542   TpfPersonaStore *result = NULL;
543
544   backend_store = folks_backend_store_dup ();
545   backend = folks_backend_store_dup_backend_by_name (backend_store,
546       "telepathy");
547   if (backend != NULL)
548     {
549       GeeMap *stores_map;
550       GeeMapIterator *iter;
551
552       stores_map = folks_backend_get_persona_stores (backend);
553       iter = gee_map_map_iterator (stores_map);
554       while (gee_map_iterator_next (iter))
555         {
556           TpfPersonaStore *persona_store = gee_map_iterator_get_value (iter);
557           TpAccount *account;
558           TpConnection *conn_cur;
559
560           account = tpf_persona_store_get_account (persona_store);
561           conn_cur = tp_account_get_connection (account);
562           if (conn_cur == connection)
563             result = g_object_ref (persona_store);
564
565           g_clear_object (&persona_store);
566         }
567       g_clear_object (&iter);
568     }
569
570   g_object_unref (backend);
571   g_object_unref (backend_store);
572
573   return result;
574 }
575
576 gboolean
577 empathy_connection_can_add_personas (TpConnection *connection)
578 {
579   gboolean retval;
580   FolksPersonaStore *persona_store;
581
582   g_return_val_if_fail (TP_IS_CONNECTION (connection), FALSE);
583
584   if (tp_connection_get_status (connection, NULL) !=
585           TP_CONNECTION_STATUS_CONNECTED)
586       return FALSE;
587
588   persona_store = FOLKS_PERSONA_STORE (
589       empathy_dup_persona_store_for_connection (connection));
590
591   retval = (folks_persona_store_get_can_add_personas (persona_store) ==
592       FOLKS_MAYBE_BOOL_TRUE);
593
594   g_clear_object (&persona_store);
595
596   return retval;
597 }
598
599 gboolean
600 empathy_connection_can_alias_personas (TpConnection *connection,
601                                        FolksIndividual *individual)
602 {
603   gboolean retval;
604
605   g_return_val_if_fail (TP_IS_CONNECTION (connection), FALSE);
606
607   if (tp_connection_get_status (connection, NULL) !=
608           TP_CONNECTION_STATUS_CONNECTED)
609       return FALSE;
610
611   retval = check_writeable_property (connection, individual, "alias");
612
613   return retval;
614 }
615
616 gboolean
617 empathy_connection_can_group_personas (TpConnection *connection,
618                                        FolksIndividual *individual)
619 {
620   gboolean retval;
621
622   g_return_val_if_fail (TP_IS_CONNECTION (connection), FALSE);
623
624   if (tp_connection_get_status (connection, NULL) !=
625           TP_CONNECTION_STATUS_CONNECTED)
626       return FALSE;
627
628   retval = check_writeable_property (connection, individual, "groups");
629
630   return retval;
631 }
632
633 gboolean
634 empathy_folks_persona_is_interesting (FolksPersona *persona)
635 {
636   /* We're not interested in non-Telepathy personas */
637   if (!TPF_IS_PERSONA (persona))
638     return FALSE;
639
640   /* We're not interested in user personas which haven't been added to the
641    * contact list (see bgo#637151). */
642   if (folks_persona_get_is_user (persona) &&
643       !tpf_persona_get_is_in_contact_list (TPF_PERSONA (persona)))
644     {
645       return FALSE;
646     }
647
648   return TRUE;
649 }
650
651 gchar *
652 empathy_get_x509_certificate_hostname (gnutls_x509_crt_t cert)
653 {
654   gchar dns_name[256];
655   gsize dns_name_size;
656   gint idx;
657   gint res = 0;
658
659   /* this snippet is taken from GnuTLS.
660    * see gnutls/lib/x509/rfc2818_hostname.c
661    */
662   for (idx = 0; res >= 0; idx++)
663     {
664       dns_name_size = sizeof (dns_name);
665       res = gnutls_x509_crt_get_subject_alt_name (cert, idx,
666           dns_name, &dns_name_size, NULL);
667
668       if (res == GNUTLS_SAN_DNSNAME || res == GNUTLS_SAN_IPADDRESS)
669         return g_strndup (dns_name, dns_name_size);
670     }
671
672   dns_name_size = sizeof (dns_name);
673   res = gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_COMMON_NAME,
674       0, 0, dns_name, &dns_name_size);
675
676   if (res >= 0)
677     return g_strndup (dns_name, dns_name_size);
678
679   return NULL;
680 }
681
682 gchar *
683 empathy_format_currency (gint amount,
684     guint scale,
685     const gchar *currency)
686 {
687 #define MINUS "\342\210\222"
688 #define EURO "\342\202\254"
689 #define YEN "\302\245"
690 #define POUND "\302\243"
691
692   /* localised representations of currency */
693   /* FIXME: check these, especially negatives and decimals */
694   static const struct {
695     const char *currency;
696     const char *positive;
697     const char *negative;
698     const char *decimal;
699   } currencies[] = {
700     /* sym   positive    negative          decimal */
701     { "EUR", EURO "%s",  MINUS EURO "%s",  "." },
702     { "USD", "$%s",      MINUS "$%s",      "." },
703     { "JPY", YEN "%s"    MINUS YEN "%s",   "." },
704     { "GBP", POUND "%s", MINUS POUND "%s", "." },
705     { "PLN", "%s zl",    MINUS "%s zl",    "." },
706     { "BRL", "R$%s",     MINUS "R$%s",     "." },
707     { "SEK", "%s kr",    MINUS "%s kr",    "." },
708     { "DKK", "kr %s",    "kr " MINUS "%s", "." },
709     { "HKD", "$%s",      MINUS "$%s",      "." },
710     { "CHF", "%s Fr.",   MINUS "%s Fr.",   "." },
711     { "NOK", "kr %s",    "kr" MINUS "%s",  "," },
712     { "CAD", "$%s",      MINUS "$%s",      "." },
713     { "TWD", "$%s",      MINUS "$%s",      "." },
714     { "AUD", "$%s",      MINUS "$%s",      "." },
715   };
716
717   const char *positive = "%s";
718   const char *negative = MINUS "%s";
719   const char *decimal = ".";
720   char *fmt_amount, *money;
721   guint i;
722
723   /* get the localised currency format */
724   for (i = 0; i < G_N_ELEMENTS (currencies); i++)
725     {
726       if (!tp_strdiff (currency, currencies[i].currency))
727         {
728           positive = currencies[i].positive;
729           negative = currencies[i].negative;
730           decimal = currencies[i].decimal;
731           break;
732         }
733     }
734
735   /* format the amount using the scale */
736   if (scale == 0)
737     {
738       /* no decimal point required */
739       fmt_amount = g_strdup_printf ("%d", amount);
740     }
741   else
742     {
743       /* don't use floating point arithmatic, it's noisy;
744        * we take the absolute values, because we want the minus
745        * sign to appear before the $ */
746       int divisor = pow (10, scale);
747       int dollars = abs (amount / divisor);
748       int cents = abs (amount % divisor);
749
750       fmt_amount = g_strdup_printf ("%d%s%0*d",
751         dollars, decimal, scale, cents);
752     }
753
754   money = g_strdup_printf (amount < 0 ? negative : positive, fmt_amount);
755   g_free (fmt_amount);
756
757   return money;
758 }
759
760 /* Return the TpContact on @conn associated with @individual, if any */
761 TpContact *
762 empathy_get_tp_contact_for_individual (FolksIndividual *individual,
763     TpConnection *conn)
764 {
765   TpContact *contact = NULL;
766   GeeSet *personas;
767   GeeIterator *iter;
768
769   personas = folks_individual_get_personas (individual);
770   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
771   while (contact == NULL && gee_iterator_next (iter))
772     {
773       TpfPersona *persona = gee_iterator_get (iter);
774       TpConnection *contact_conn;
775       TpContact *contact_cur = NULL;
776
777       if (TPF_IS_PERSONA (persona))
778         {
779           contact_cur = tpf_persona_get_contact (persona);
780           if (contact_cur != NULL)
781             {
782               contact_conn = tp_contact_get_connection (contact_cur);
783
784               if (!tp_strdiff (tp_proxy_get_object_path (contact_conn),
785                     tp_proxy_get_object_path (conn)))
786                 contact = contact_cur;
787             }
788         }
789
790       g_clear_object (&persona);
791     }
792   g_clear_object (&iter);
793
794   return contact;
795 }
796
797 static gboolean
798 properties_contains (gchar **list,
799                      gint length,
800                      const gchar *property)
801 {
802   gint i;
803
804   for (i = 0; i < length; i++)
805     {
806       if (!tp_strdiff (list[i], property))
807         return TRUE;
808     }
809
810   return FALSE;
811 }
812
813 static gboolean
814 check_writeable_property (TpConnection *connection,
815                           FolksIndividual *individual,
816                           gchar *property)
817 {
818   gchar **properties;
819   gint prop_len;
820   gboolean retval = FALSE;
821   GeeSet *personas;
822   GeeIterator *iter;
823   FolksPersonaStore *persona_store;
824
825   persona_store = FOLKS_PERSONA_STORE (
826       empathy_dup_persona_store_for_connection (connection));
827
828   properties =
829     folks_persona_store_get_always_writeable_properties (persona_store,
830                                                          &prop_len);
831   retval = properties_contains (properties, prop_len, property);
832   if (retval == TRUE)
833     goto out;
834
835   /* Lets see if the Individual contains a Persona with the given property */
836   personas = folks_individual_get_personas (individual);
837   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
838   while (!retval && gee_iterator_next (iter))
839     {
840       FolksPersona *persona = gee_iterator_get (iter);
841
842       properties =
843         folks_persona_get_writeable_properties (persona, &prop_len);
844       retval = properties_contains (properties, prop_len, property);
845
846       g_clear_object (&persona);
847
848       if (retval == TRUE)
849         break;
850     }
851   g_clear_object (&iter);
852
853 out:
854   g_clear_object (&persona_store);
855   return retval;
856 }
857
858 /* Calculate whether the Individual can do audio or video calls.
859  * FIXME: We can remove this once libfolks has grown capabilities support
860  * again: bgo#626179. */
861 void
862 empathy_individual_can_audio_video_call (FolksIndividual *individual,
863     gboolean *can_audio_call,
864     gboolean *can_video_call,
865     EmpathyContact **out_contact)
866 {
867   GeeSet *personas;
868   GeeIterator *iter;
869   gboolean can_audio = FALSE, can_video = FALSE;
870
871   personas = folks_individual_get_personas (individual);
872   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
873   while (gee_iterator_next (iter))
874     {
875       FolksPersona *persona = gee_iterator_get (iter);
876       TpContact *tp_contact;
877
878       if (!empathy_folks_persona_is_interesting (persona))
879         goto while_finish;
880
881       tp_contact = tpf_persona_get_contact (TPF_PERSONA (persona));
882       if (tp_contact != NULL)
883         {
884           EmpathyContact *contact;
885
886           contact = empathy_contact_dup_from_tp_contact (tp_contact);
887           empathy_contact_set_persona (contact, persona);
888
889           can_audio = can_audio || empathy_contact_get_capabilities (contact) &
890               EMPATHY_CAPABILITIES_AUDIO;
891           can_video = can_video || empathy_contact_get_capabilities (contact) &
892               EMPATHY_CAPABILITIES_VIDEO;
893
894           if (out_contact != NULL)
895             *out_contact = g_object_ref (contact);
896
897           g_object_unref (contact);
898         }
899
900 while_finish:
901       g_clear_object (&persona);
902
903       if (can_audio && can_video)
904         break;
905     }
906
907   g_clear_object (&iter);
908
909   if (can_audio_call != NULL)
910     *can_audio_call = can_audio;
911
912   if (can_video_call != NULL)
913     *can_video_call = can_video;
914 }
915
916 gboolean
917 empathy_client_types_contains_mobile_device (const GStrv types) {
918   int i;
919
920   if (types == NULL)
921     return FALSE;
922
923   for (i = 0; types[i] != NULL; i++)
924     if (!tp_strdiff (types[i], "phone") || !tp_strdiff (types[i], "handheld"))
925         return TRUE;
926
927   return FALSE;
928 }
929
930 static FolksIndividual *
931 create_individual_from_persona (FolksPersona *persona)
932 {
933   GeeSet *personas;
934   FolksIndividual *individual;
935
936   personas = GEE_SET (
937       gee_hash_set_new (FOLKS_TYPE_PERSONA, g_object_ref, g_object_unref,
938       NULL, NULL, NULL, NULL, NULL, NULL));
939
940   gee_collection_add (GEE_COLLECTION (personas), persona);
941
942   individual = folks_individual_new (personas);
943
944   g_clear_object (&personas);
945
946   return individual;
947 }
948
949 /* Look for a FolksIndividual containing @contact as one of his persona
950  * and create one if needed */
951 FolksIndividual *
952 empathy_ensure_individual_from_tp_contact (TpContact *contact)
953 {
954   TpfPersona *persona;
955   FolksIndividual *individual;
956
957   persona = tpf_persona_dup_for_contact (contact);
958   if (persona == NULL)
959     {
960       DEBUG ("Failed to get a persona for %s",
961           tp_contact_get_identifier (contact));
962       return NULL;
963     }
964
965   individual = folks_persona_get_individual (FOLKS_PERSONA (persona));
966
967   if (individual != NULL)
968     g_object_ref (individual);
969   else
970     individual = create_individual_from_persona (FOLKS_PERSONA (persona));
971
972   g_object_unref (persona);
973   return individual;
974 }
975
976 const gchar * const *
977 empathy_individual_get_client_types (FolksIndividual *individual)
978 {
979   GeeSet *personas;
980   GeeIterator *iter;
981   const gchar * const *types = NULL;
982   FolksPresenceType presence_type = FOLKS_PRESENCE_TYPE_UNSET;
983
984   personas = folks_individual_get_personas (individual);
985   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
986   while (gee_iterator_next (iter))
987     {
988       FolksPresenceDetails *presence;
989       FolksPersona *persona = gee_iterator_get (iter);
990
991       /* We only want personas which have presence and a TpContact */
992       if (!empathy_folks_persona_is_interesting (persona))
993         goto while_finish;
994
995       presence = FOLKS_PRESENCE_DETAILS (persona);
996
997       if (folks_presence_details_typecmp (
998               folks_presence_details_get_presence_type (presence),
999               presence_type) > 0)
1000         {
1001           TpContact *tp_contact;
1002
1003           presence_type = folks_presence_details_get_presence_type (presence);
1004
1005           tp_contact = tpf_persona_get_contact (TPF_PERSONA (persona));
1006           if (tp_contact != NULL)
1007             types = tp_contact_get_client_types (tp_contact);
1008         }
1009
1010 while_finish:
1011       g_clear_object (&persona);
1012     }
1013   g_clear_object (&iter);
1014
1015   return types;
1016 }
1017
1018 GVariant *
1019 empathy_asv_to_vardict (const GHashTable *asv)
1020 {
1021   return empathy_boxed_to_variant (TP_HASH_TYPE_STRING_VARIANT_MAP, "a{sv}",
1022       (gpointer) asv);
1023 }
1024
1025 GVariant *
1026 empathy_boxed_to_variant (GType gtype,
1027     const gchar *variant_type,
1028     gpointer boxed)
1029 {
1030   GValue v = G_VALUE_INIT;
1031   GVariant *ret;
1032
1033   g_return_val_if_fail (boxed != NULL, NULL);
1034
1035   g_value_init (&v, gtype);
1036   g_value_set_boxed (&v, boxed);
1037
1038   ret = dbus_g_value_build_g_variant (&v);
1039   g_return_val_if_fail (!tp_strdiff (g_variant_get_type_string (ret),
1040         variant_type), NULL);
1041
1042   g_value_unset (&v);
1043
1044   return g_variant_ref_sink (ret);
1045 }