]> git.0d.be Git - empathy.git/blob - libempathy/empathy-utils.c
Merge branch 'gnome-3-8'
[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
35 #include "empathy-client-factory.h"
36 #include "empathy-presence-manager.h"
37 #include "extensions.h"
38
39 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
40 #include "empathy-debug.h"
41
42 /* Translation between presence types and string */
43 static struct {
44   const gchar *name;
45   TpConnectionPresenceType type;
46 } presence_types[] = {
47   { "available", TP_CONNECTION_PRESENCE_TYPE_AVAILABLE },
48   { "busy",      TP_CONNECTION_PRESENCE_TYPE_BUSY },
49   { "away",      TP_CONNECTION_PRESENCE_TYPE_AWAY },
50   { "ext_away",  TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY },
51   { "hidden",    TP_CONNECTION_PRESENCE_TYPE_HIDDEN },
52   { "offline",   TP_CONNECTION_PRESENCE_TYPE_OFFLINE },
53   { "unset",     TP_CONNECTION_PRESENCE_TYPE_UNSET },
54   { "unknown",   TP_CONNECTION_PRESENCE_TYPE_UNKNOWN },
55   { "error",     TP_CONNECTION_PRESENCE_TYPE_ERROR },
56   /* alternative names */
57   { "dnd",      TP_CONNECTION_PRESENCE_TYPE_BUSY },
58   { "brb",      TP_CONNECTION_PRESENCE_TYPE_AWAY },
59   { "xa",       TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY },
60   { NULL, },
61 };
62
63 static gboolean
64 properties_contains (gchar **list,
65                      gint length,
66                      const gchar *property);
67
68 static gboolean
69 check_writeable_property (TpConnection *connection,
70                           FolksIndividual *individual,
71                           gchar *property);
72
73 void
74 empathy_init (void)
75 {
76   static gboolean initialized = FALSE;
77   TpAccountManager *am;
78   EmpathyClientFactory *factory;
79
80   if (initialized)
81     return;
82
83   g_type_init ();
84
85   /* Setup gettext */
86   bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
87   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
88
89   /* Setup debug output for empathy and telepathy-glib */
90   if (g_getenv ("EMPATHY_TIMING") != NULL)
91     g_log_set_default_handler (tp_debug_timestamped_log_handler, NULL);
92
93   empathy_debug_set_flags (g_getenv ("EMPATHY_DEBUG"));
94   tp_debug_divert_messages (g_getenv ("EMPATHY_LOGFILE"));
95
96   emp_cli_init ();
97
98   initialized = TRUE;
99
100   factory = empathy_client_factory_dup ();
101   am = tp_account_manager_new_with_factory (TP_SIMPLE_CLIENT_FACTORY (factory));
102   tp_account_manager_set_default (am);
103
104   g_object_unref (factory);
105   g_object_unref (am);
106 }
107
108 gboolean
109 empathy_xml_validate_from_resource (xmlDoc *doc,
110     const gchar *dtd_resourcename)
111 {
112   GBytes *resourcecontents;
113   gconstpointer resourcedata;
114   gsize resourcesize;
115   xmlParserInputBufferPtr buffer;
116   xmlValidCtxt  cvp;
117   xmlDtd *dtd;
118   GError *error = NULL;
119   gboolean ret;
120
121   DEBUG ("Loading dtd resource %s", dtd_resourcename);
122
123   resourcecontents = g_resources_lookup_data (dtd_resourcename, G_RESOURCE_LOOKUP_FLAGS_NONE, &error);
124   if (error != NULL)
125     {
126       g_warning ("Unable to load dtd resource '%s': %s", dtd_resourcename, error->message);
127       g_error_free (error);
128       return FALSE;
129     }
130   resourcedata = g_bytes_get_data (resourcecontents, &resourcesize);
131   buffer = xmlParserInputBufferCreateStatic (resourcedata, resourcesize, XML_CHAR_ENCODING_UTF8);
132
133   memset (&cvp, 0, sizeof (cvp));
134   dtd = xmlIOParseDTD (NULL, buffer, XML_CHAR_ENCODING_UTF8);
135   ret = xmlValidateDtd (&cvp, doc, dtd);
136
137   xmlFreeDtd (dtd);
138   g_bytes_unref (resourcecontents);
139
140   return ret;
141 }
142
143 xmlNodePtr
144 empathy_xml_node_get_child (xmlNodePtr   node,
145     const gchar *child_name)
146 {
147   xmlNodePtr l;
148
149   g_return_val_if_fail (node != NULL, NULL);
150   g_return_val_if_fail (child_name != NULL, NULL);
151
152   for (l = node->children; l; l = l->next)
153     {
154       if (l->name && strcmp ((const gchar *) l->name, child_name) == 0)
155         return l;
156     }
157
158   return NULL;
159 }
160
161 xmlChar *
162 empathy_xml_node_get_child_content (xmlNodePtr   node,
163     const gchar *child_name)
164 {
165   xmlNodePtr l;
166
167   g_return_val_if_fail (node != NULL, NULL);
168   g_return_val_if_fail (child_name != NULL, NULL);
169
170   l = empathy_xml_node_get_child (node, child_name);
171   if (l != NULL)
172     return xmlNodeGetContent (l);
173
174   return NULL;
175 }
176
177 xmlNodePtr
178 empathy_xml_node_find_child_prop_value (xmlNodePtr   node,
179     const gchar *prop_name,
180     const gchar *prop_value)
181 {
182   xmlNodePtr l;
183   xmlNodePtr found = NULL;
184
185   g_return_val_if_fail (node != NULL, NULL);
186   g_return_val_if_fail (prop_name != NULL, NULL);
187   g_return_val_if_fail (prop_value != NULL, NULL);
188
189   for (l = node->children; l && !found; l = l->next)
190     {
191       xmlChar *prop;
192
193       if (!xmlHasProp (l, (const xmlChar *) prop_name))
194         continue;
195
196       prop = xmlGetProp (l, (const xmlChar *) prop_name);
197       if (prop && strcmp ((const gchar *) prop, prop_value) == 0)
198         found = l;
199
200       xmlFree (prop);
201     }
202
203   return found;
204 }
205
206 const gchar *
207 empathy_presence_get_default_message (TpConnectionPresenceType presence)
208 {
209   switch (presence)
210     {
211       case TP_CONNECTION_PRESENCE_TYPE_AVAILABLE:
212         return _("Available");
213       case TP_CONNECTION_PRESENCE_TYPE_BUSY:
214         return _("Busy");
215       case TP_CONNECTION_PRESENCE_TYPE_AWAY:
216       case TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY:
217         return _("Away");
218       case TP_CONNECTION_PRESENCE_TYPE_HIDDEN:
219         return _("Invisible");
220       case TP_CONNECTION_PRESENCE_TYPE_OFFLINE:
221         return _("Offline");
222       case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
223         /* translators: presence type is unknown */
224         return C_("presence", "Unknown");
225       case TP_CONNECTION_PRESENCE_TYPE_UNSET:
226       case TP_CONNECTION_PRESENCE_TYPE_ERROR:
227       default:
228         return NULL;
229     }
230
231   return NULL;
232 }
233
234 const gchar *
235 empathy_presence_to_str (TpConnectionPresenceType presence)
236 {
237   int i;
238
239   for (i = 0 ; presence_types[i].name != NULL; i++)
240     if (presence == presence_types[i].type)
241       return presence_types[i].name;
242
243   return NULL;
244 }
245
246 TpConnectionPresenceType
247 empathy_presence_from_str (const gchar *str)
248 {
249   int i;
250
251   for (i = 0 ; presence_types[i].name != NULL; i++)
252     if (!tp_strdiff (str, presence_types[i].name))
253       return presence_types[i].type;
254
255   return TP_CONNECTION_PRESENCE_TYPE_UNSET;
256 }
257
258 static const gchar *
259 empathy_status_reason_get_default_message (TpConnectionStatusReason reason)
260 {
261   switch (reason)
262     {
263       case TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED:
264         return _("No reason specified");
265       case TP_CONNECTION_STATUS_REASON_REQUESTED:
266         return _("Status is set to offline");
267       case TP_CONNECTION_STATUS_REASON_NETWORK_ERROR:
268         return _("Network error");
269       case TP_CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED:
270         return _("Authentication failed");
271       case TP_CONNECTION_STATUS_REASON_ENCRYPTION_ERROR:
272         return _("Encryption error");
273       case TP_CONNECTION_STATUS_REASON_NAME_IN_USE:
274         return _("Name in use");
275       case TP_CONNECTION_STATUS_REASON_CERT_NOT_PROVIDED:
276         return _("Certificate not provided");
277       case TP_CONNECTION_STATUS_REASON_CERT_UNTRUSTED:
278         return _("Certificate untrusted");
279       case TP_CONNECTION_STATUS_REASON_CERT_EXPIRED:
280         return _("Certificate expired");
281       case TP_CONNECTION_STATUS_REASON_CERT_NOT_ACTIVATED:
282         return _("Certificate not activated");
283       case TP_CONNECTION_STATUS_REASON_CERT_HOSTNAME_MISMATCH:
284         return _("Certificate hostname mismatch");
285       case TP_CONNECTION_STATUS_REASON_CERT_FINGERPRINT_MISMATCH:
286         return _("Certificate fingerprint mismatch");
287       case TP_CONNECTION_STATUS_REASON_CERT_SELF_SIGNED:
288         return _("Certificate self-signed");
289       case TP_CONNECTION_STATUS_REASON_CERT_OTHER_ERROR:
290         return _("Certificate error");
291       default:
292         return _("Unknown reason");
293     }
294 }
295
296 static GHashTable *
297 create_errors_to_message_hash (void)
298 {
299   GHashTable *errors;
300
301   errors = g_hash_table_new (g_str_hash, g_str_equal);
302   g_hash_table_insert (errors, TP_ERROR_STR_NETWORK_ERROR, _("Network error"));
303   g_hash_table_insert (errors, TP_ERROR_STR_AUTHENTICATION_FAILED,
304     _("Authentication failed"));
305   g_hash_table_insert (errors, TP_ERROR_STR_ENCRYPTION_ERROR,
306     _("Encryption error"));
307   g_hash_table_insert (errors, TP_ERROR_STR_CERT_NOT_PROVIDED,
308     _("Certificate not provided"));
309   g_hash_table_insert (errors, TP_ERROR_STR_CERT_UNTRUSTED,
310     _("Certificate untrusted"));
311   g_hash_table_insert (errors, TP_ERROR_STR_CERT_EXPIRED,
312     _("Certificate expired"));
313   g_hash_table_insert (errors, TP_ERROR_STR_CERT_NOT_ACTIVATED,
314     _("Certificate not activated"));
315   g_hash_table_insert (errors, TP_ERROR_STR_CERT_HOSTNAME_MISMATCH,
316     _("Certificate hostname mismatch"));
317   g_hash_table_insert (errors, TP_ERROR_STR_CERT_FINGERPRINT_MISMATCH,
318     _("Certificate fingerprint mismatch"));
319   g_hash_table_insert (errors, TP_ERROR_STR_CERT_SELF_SIGNED,
320     _("Certificate self-signed"));
321   g_hash_table_insert (errors, TP_ERROR_STR_CANCELLED,
322     _("Status is set to offline"));
323   g_hash_table_insert (errors, TP_ERROR_STR_ENCRYPTION_NOT_AVAILABLE,
324     _("Encryption is not available"));
325   g_hash_table_insert (errors, TP_ERROR_STR_CERT_INVALID,
326     _("Certificate is invalid"));
327   g_hash_table_insert (errors, TP_ERROR_STR_CONNECTION_REFUSED,
328     _("Connection has been refused"));
329   g_hash_table_insert (errors, TP_ERROR_STR_CONNECTION_FAILED,
330     _("Connection can't be established"));
331   g_hash_table_insert (errors, TP_ERROR_STR_CONNECTION_LOST,
332     _("Connection has been lost"));
333   g_hash_table_insert (errors, TP_ERROR_STR_ALREADY_CONNECTED,
334     _("This account is already connected to the server"));
335   g_hash_table_insert (errors, TP_ERROR_STR_CONNECTION_REPLACED,
336     _("Connection has been replaced by a new connection using the "
337     "same resource"));
338   g_hash_table_insert (errors, TP_ERROR_STR_REGISTRATION_EXISTS,
339     _("The account already exists on the server"));
340   g_hash_table_insert (errors, TP_ERROR_STR_SERVICE_BUSY,
341     _("Server is currently too busy to handle the connection"));
342   g_hash_table_insert (errors, TP_ERROR_STR_CERT_REVOKED,
343     _("Certificate has been revoked"));
344   g_hash_table_insert (errors, TP_ERROR_STR_CERT_INSECURE,
345     _("Certificate uses an insecure cipher algorithm or is "
346     "cryptographically weak"));
347   g_hash_table_insert (errors, TP_ERROR_STR_CERT_LIMIT_EXCEEDED,
348     _("The length of the server certificate, or the depth of the "
349     "server certificate chain, exceed the limits imposed by the "
350     "cryptography library"));
351   g_hash_table_insert (errors, TP_ERROR_STR_SOFTWARE_UPGRADE_REQUIRED,
352     _("Your software is too old"));
353   g_hash_table_insert (errors, DBUS_ERROR_NO_REPLY,
354     _("Internal error"));
355
356   return errors;
357 }
358
359 static const gchar *
360 empathy_dbus_error_name_get_default_message  (const gchar *error)
361 {
362   static GHashTable *errors_to_message = NULL;
363
364   if (error == NULL)
365     return NULL;
366
367   if (G_UNLIKELY (errors_to_message == NULL))
368     errors_to_message = create_errors_to_message_hash ();
369
370   return g_hash_table_lookup (errors_to_message, error);
371 }
372
373 const gchar *
374 empathy_account_get_error_message (TpAccount *account,
375     gboolean *user_requested)
376 {
377   const gchar *dbus_error;
378   const gchar *message;
379   const GHashTable *details = NULL;
380   TpConnectionStatusReason reason;
381
382   dbus_error = tp_account_get_detailed_error (account, &details);
383
384   if (user_requested != NULL)
385     {
386       if (tp_asv_get_boolean (details, "user-requested", NULL))
387         *user_requested = TRUE;
388       else
389         *user_requested = FALSE;
390     }
391
392   message = empathy_dbus_error_name_get_default_message (dbus_error);
393   if (message != NULL)
394     return message;
395
396   tp_account_get_connection_status (account, &reason);
397
398   DEBUG ("Don't understand error '%s'; fallback to the status reason (%u)",
399     dbus_error, reason);
400
401   return empathy_status_reason_get_default_message (reason);
402 }
403
404 gchar *
405 empathy_file_lookup (const gchar *filename, const gchar *subdir)
406 {
407   gchar *path;
408
409   if (subdir == NULL)
410     subdir = ".";
411
412   path = g_build_filename (g_getenv ("EMPATHY_SRCDIR"), subdir, filename, NULL);
413   if (!g_file_test (path, G_FILE_TEST_EXISTS))
414     {
415       g_free (path);
416       path = g_build_filename (DATADIR, "empathy", filename, NULL);
417     }
418
419   return path;
420 }
421
422 gboolean
423 empathy_check_available_state (void)
424 {
425   TpConnectionPresenceType presence;
426   EmpathyPresenceManager *presence_mgr;
427
428   presence_mgr = empathy_presence_manager_dup_singleton ();
429   presence = empathy_presence_manager_get_state (presence_mgr);
430   g_object_unref (presence_mgr);
431
432   if (presence != TP_CONNECTION_PRESENCE_TYPE_AVAILABLE &&
433     presence != TP_CONNECTION_PRESENCE_TYPE_UNSET)
434     return FALSE;
435
436   return TRUE;
437 }
438
439 gint
440 empathy_uint_compare (gconstpointer a,
441     gconstpointer b)
442 {
443   return *(guint *) a - *(guint *) b;
444 }
445
446 gchar *
447 empathy_protocol_icon_name (const gchar *protocol)
448 {
449   if (!tp_strdiff (protocol, "yahoojp"))
450     /* Yahoo Japan uses the same icon as Yahoo */
451     protocol = "yahoo";
452   else if (!tp_strdiff (protocol, "simple"))
453     /* SIMPLE uses the same icon as SIP */
454     protocol = "sip";
455   else if (!tp_strdiff (protocol, "sms"))
456     return g_strdup ("phone");
457
458   return g_strdup_printf ("im-%s", protocol);
459 }
460
461 GType
462 empathy_type_dbus_ao (void)
463 {
464   static GType t = 0;
465
466   if (G_UNLIKELY (t == 0))
467      t = dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH);
468
469   return t;
470 }
471
472 const char *
473 empathy_protocol_name_to_display_name (const gchar *proto_name)
474 {
475   int i;
476   static struct {
477     const gchar *proto;
478     const gchar *display;
479     gboolean translated;
480   } names[] = {
481     { "jabber", "Jabber", FALSE },
482     { "msn", "Windows Live (MSN)", FALSE, },
483     { "local-xmpp", N_("People Nearby"), TRUE },
484     { "irc", "IRC", FALSE },
485     { "icq", "ICQ", FALSE },
486     { "aim", "AIM", FALSE },
487     { "yahoo", "Yahoo!", FALSE },
488     { "yahoojp", N_("Yahoo! Japan"), TRUE },
489     { "groupwise", "GroupWise", FALSE },
490     { "sip", "SIP", FALSE },
491     { "gadugadu", "Gadu-Gadu", FALSE },
492     { "mxit", "Mxit", FALSE },
493     { "myspace", "Myspace", FALSE },
494     { "sametime", "Sametime", FALSE },
495     { "skype-dbus", "Skype (D-BUS)", FALSE },
496     { "skype-x11", "Skype (X11)", FALSE },
497     { "zephyr", "Zephyr", FALSE },
498     { NULL, NULL }
499   };
500
501   for (i = 0; names[i].proto != NULL; i++)
502     {
503       if (!tp_strdiff (proto_name, names[i].proto))
504         {
505           if (names[i].translated)
506             return gettext (names[i].display);
507           else
508             return names[i].display;
509         }
510     }
511
512   return proto_name;
513 }
514
515 const char *
516 empathy_service_name_to_display_name (const gchar *service_name)
517 {
518   int i;
519   static struct {
520     const gchar *service;
521     const gchar *display;
522     gboolean translated;
523   } names[] = {
524     { "google-talk", N_("Google Talk"), FALSE },
525     { "facebook", N_("Facebook Chat"), TRUE },
526     { NULL, NULL }
527   };
528
529   for (i = 0; names[i].service != NULL; i++)
530     {
531       if (!tp_strdiff (service_name, names[i].service))
532         {
533           if (names[i].translated)
534             return gettext (names[i].display);
535           else
536             return names[i].display;
537         }
538     }
539
540   return service_name;
541 }
542
543 gboolean
544 empathy_account_manager_get_accounts_connected (gboolean *connecting)
545 {
546   TpAccountManager *manager;
547   GList *accounts, *l;
548   gboolean out_connecting = FALSE;
549   gboolean out_connected = FALSE;
550
551   manager = tp_account_manager_dup ();
552
553   if (G_UNLIKELY (!tp_account_manager_is_prepared (manager,
554           TP_ACCOUNT_MANAGER_FEATURE_CORE)))
555     g_critical (G_STRLOC ": %s called before AccountManager ready", G_STRFUNC);
556
557   accounts = tp_account_manager_dup_valid_accounts (manager);
558
559   for (l = accounts; l != NULL; l = l->next)
560     {
561       TpConnectionStatus s = tp_account_get_connection_status (
562           TP_ACCOUNT (l->data), NULL);
563
564       if (s == TP_CONNECTION_STATUS_CONNECTING)
565         out_connecting = TRUE;
566       else if (s == TP_CONNECTION_STATUS_CONNECTED)
567         out_connected = TRUE;
568
569       if (out_connecting && out_connected)
570         break;
571     }
572
573   g_list_free_full (accounts, g_object_unref);
574   g_object_unref (manager);
575
576   if (connecting != NULL)
577     *connecting = out_connecting;
578
579   return out_connected;
580 }
581
582 /* Change the RequestedPresence of a newly created account to ensure that it
583  * is actually connected. */
584 void
585 empathy_connect_new_account (TpAccount *account,
586     TpAccountManager *account_manager)
587 {
588   TpConnectionPresenceType presence;
589   gchar *status, *message;
590
591   /* only force presence if presence was offline, unknown or unset */
592   presence = tp_account_get_requested_presence (account, NULL, NULL);
593   switch (presence)
594     {
595       case TP_CONNECTION_PRESENCE_TYPE_OFFLINE:
596       case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
597       case TP_CONNECTION_PRESENCE_TYPE_UNSET:
598         presence = tp_account_manager_get_most_available_presence (
599             account_manager, &status, &message);
600
601         if (presence == TP_CONNECTION_PRESENCE_TYPE_OFFLINE)
602           /* Global presence is offline; we force it so user doesn't have to
603            * manually change the presence to connect his new account. */
604           presence = TP_CONNECTION_PRESENCE_TYPE_AVAILABLE;
605
606         tp_account_request_presence_async (account, presence,
607             status, NULL, NULL, NULL);
608
609         g_free (status);
610         g_free (message);
611         break;
612
613        case TP_CONNECTION_PRESENCE_TYPE_AVAILABLE:
614        case TP_CONNECTION_PRESENCE_TYPE_AWAY:
615        case TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY:
616        case TP_CONNECTION_PRESENCE_TYPE_HIDDEN:
617        case TP_CONNECTION_PRESENCE_TYPE_BUSY:
618        case TP_CONNECTION_PRESENCE_TYPE_ERROR:
619        default:
620         /* do nothing if the presence is not offline */
621         break;
622     }
623 }
624
625 /* Translate Folks' general presence type to the Tp presence type */
626 TpConnectionPresenceType
627 empathy_folks_presence_type_to_tp (FolksPresenceType type)
628 {
629   return (TpConnectionPresenceType) type;
630 }
631
632 /* Returns TRUE if the given Individual contains a TpContact */
633 gboolean
634 empathy_folks_individual_contains_contact (FolksIndividual *individual)
635 {
636   GeeSet *personas;
637   GeeIterator *iter;
638   gboolean retval = FALSE;
639
640   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), FALSE);
641
642   personas = folks_individual_get_personas (individual);
643   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
644   while (!retval && gee_iterator_next (iter))
645     {
646       FolksPersona *persona = gee_iterator_get (iter);
647       TpContact *contact = NULL;
648
649       if (empathy_folks_persona_is_interesting (persona))
650         contact = tpf_persona_get_contact (TPF_PERSONA (persona));
651
652       g_clear_object (&persona);
653
654       if (contact != NULL)
655         retval = TRUE;
656     }
657   g_clear_object (&iter);
658
659   return retval;
660 }
661
662 /* TODO: this needs to be eliminated (and replaced in some cases with user
663  * prompts) when we break the assumption that FolksIndividuals are 1:1 with
664  * TpContacts */
665
666 /* Retrieve the EmpathyContact corresponding to the first TpContact contained
667  * within the given Individual. Note that this is a temporary convenience. See
668  * the TODO above. */
669 EmpathyContact *
670 empathy_contact_dup_from_folks_individual (FolksIndividual *individual)
671 {
672   GeeSet *personas;
673   GeeIterator *iter;
674   EmpathyContact *contact = NULL;
675
676   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
677
678   personas = folks_individual_get_personas (individual);
679   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
680   while (gee_iterator_next (iter) && (contact == NULL))
681     {
682       TpfPersona *persona = gee_iterator_get (iter);
683
684       if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (persona)))
685         {
686           TpContact *tp_contact;
687
688           tp_contact = tpf_persona_get_contact (persona);
689           if (tp_contact != NULL)
690             {
691               contact = empathy_contact_dup_from_tp_contact (tp_contact);
692               empathy_contact_set_persona (contact, FOLKS_PERSONA (persona));
693             }
694         }
695       g_clear_object (&persona);
696     }
697   g_clear_object (&iter);
698
699   if (contact == NULL)
700     {
701       DEBUG ("Can't create an EmpathyContact for Individual %s",
702           folks_individual_get_id (individual));
703     }
704
705   return contact;
706 }
707
708 TpChannelGroupChangeReason
709 tp_channel_group_change_reason_from_folks_groups_change_reason (
710     FolksGroupDetailsChangeReason reason)
711 {
712   return (TpChannelGroupChangeReason) reason;
713 }
714
715 TpfPersonaStore *
716 empathy_dup_persona_store_for_connection (TpConnection *connection)
717 {
718   FolksBackendStore *backend_store;
719   FolksBackend *backend;
720   TpfPersonaStore *result = NULL;
721
722   backend_store = folks_backend_store_dup ();
723   backend = folks_backend_store_dup_backend_by_name (backend_store,
724       "telepathy");
725   if (backend != NULL)
726     {
727       GeeMap *stores_map;
728       GeeMapIterator *iter;
729
730       stores_map = folks_backend_get_persona_stores (backend);
731       iter = gee_map_map_iterator (stores_map);
732       while (gee_map_iterator_next (iter))
733         {
734           TpfPersonaStore *persona_store = gee_map_iterator_get_value (iter);
735           TpAccount *account;
736           TpConnection *conn_cur;
737
738           account = tpf_persona_store_get_account (persona_store);
739           conn_cur = tp_account_get_connection (account);
740           if (conn_cur == connection)
741             result = persona_store;
742         }
743       g_clear_object (&iter);
744     }
745
746   g_object_unref (backend);
747   g_object_unref (backend_store);
748
749   return result;
750 }
751
752 gboolean
753 empathy_connection_can_add_personas (TpConnection *connection)
754 {
755   gboolean retval;
756   FolksPersonaStore *persona_store;
757
758   g_return_val_if_fail (TP_IS_CONNECTION (connection), FALSE);
759
760   if (tp_connection_get_status (connection, NULL) !=
761           TP_CONNECTION_STATUS_CONNECTED)
762       return FALSE;
763
764   persona_store = FOLKS_PERSONA_STORE (
765       empathy_dup_persona_store_for_connection (connection));
766
767   retval = (folks_persona_store_get_can_add_personas (persona_store) ==
768       FOLKS_MAYBE_BOOL_TRUE);
769
770   g_clear_object (&persona_store);
771
772   return retval;
773 }
774
775 gboolean
776 empathy_connection_can_alias_personas (TpConnection *connection,
777                                        FolksIndividual *individual)
778 {
779   gboolean retval;
780
781   g_return_val_if_fail (TP_IS_CONNECTION (connection), FALSE);
782
783   if (tp_connection_get_status (connection, NULL) !=
784           TP_CONNECTION_STATUS_CONNECTED)
785       return FALSE;
786
787   retval = check_writeable_property (connection, individual, "alias");
788
789   return retval;
790 }
791
792 gboolean
793 empathy_connection_can_group_personas (TpConnection *connection,
794                                        FolksIndividual *individual)
795 {
796   gboolean retval;
797
798   g_return_val_if_fail (TP_IS_CONNECTION (connection), FALSE);
799
800   if (tp_connection_get_status (connection, NULL) !=
801           TP_CONNECTION_STATUS_CONNECTED)
802       return FALSE;
803
804   retval = check_writeable_property (connection, individual, "groups");
805
806   return retval;
807 }
808
809 gboolean
810 empathy_folks_persona_is_interesting (FolksPersona *persona)
811 {
812   /* We're not interested in non-Telepathy personas */
813   if (!TPF_IS_PERSONA (persona))
814     return FALSE;
815
816   /* We're not interested in user personas which haven't been added to the
817    * contact list (see bgo#637151). */
818   if (folks_persona_get_is_user (persona) &&
819       !tpf_persona_get_is_in_contact_list (TPF_PERSONA (persona)))
820     {
821       return FALSE;
822     }
823
824   return TRUE;
825 }
826
827 gchar *
828 empathy_get_x509_certificate_hostname (gnutls_x509_crt_t cert)
829 {
830   gchar dns_name[256];
831   gsize dns_name_size;
832   gint idx;
833   gint res = 0;
834
835   /* this snippet is taken from GnuTLS.
836    * see gnutls/lib/x509/rfc2818_hostname.c
837    */
838   for (idx = 0; res >= 0; idx++)
839     {
840       dns_name_size = sizeof (dns_name);
841       res = gnutls_x509_crt_get_subject_alt_name (cert, idx,
842           dns_name, &dns_name_size, NULL);
843
844       if (res == GNUTLS_SAN_DNSNAME || res == GNUTLS_SAN_IPADDRESS)
845         return g_strndup (dns_name, dns_name_size);
846     }
847
848   dns_name_size = sizeof (dns_name);
849   res = gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_COMMON_NAME,
850       0, 0, dns_name, &dns_name_size);
851
852   if (res >= 0)
853     return g_strndup (dns_name, dns_name_size);
854
855   return NULL;
856 }
857
858 gchar *
859 empathy_format_currency (gint amount,
860     guint scale,
861     const gchar *currency)
862 {
863 #define MINUS "\342\210\222"
864 #define EURO "\342\202\254"
865 #define YEN "\302\245"
866 #define POUND "\302\243"
867
868   /* localised representations of currency */
869   /* FIXME: check these, especially negatives and decimals */
870   static const struct {
871     const char *currency;
872     const char *positive;
873     const char *negative;
874     const char *decimal;
875   } currencies[] = {
876     /* sym   positive    negative          decimal */
877     { "EUR", EURO "%s",  MINUS EURO "%s",  "." },
878     { "USD", "$%s",      MINUS "$%s",      "." },
879     { "JPY", YEN "%s"    MINUS YEN "%s",   "." },
880     { "GBP", POUND "%s", MINUS POUND "%s", "." },
881     { "PLN", "%s zl",    MINUS "%s zl",    "." },
882     { "BRL", "R$%s",     MINUS "R$%s",     "." },
883     { "SEK", "%s kr",    MINUS "%s kr",    "." },
884     { "DKK", "kr %s",    "kr " MINUS "%s", "." },
885     { "HKD", "$%s",      MINUS "$%s",      "." },
886     { "CHF", "%s Fr.",   MINUS "%s Fr.",   "." },
887     { "NOK", "kr %s",    "kr" MINUS "%s",  "," },
888     { "CAD", "$%s",      MINUS "$%s",      "." },
889     { "TWD", "$%s",      MINUS "$%s",      "." },
890     { "AUD", "$%s",      MINUS "$%s",      "." },
891   };
892
893   const char *positive = "%s";
894   const char *negative = MINUS "%s";
895   const char *decimal = ".";
896   char *fmt_amount, *money;
897   guint i;
898
899   /* get the localised currency format */
900   for (i = 0; i < G_N_ELEMENTS (currencies); i++)
901     {
902       if (!tp_strdiff (currency, currencies[i].currency))
903         {
904           positive = currencies[i].positive;
905           negative = currencies[i].negative;
906           decimal = currencies[i].decimal;
907           break;
908         }
909     }
910
911   /* format the amount using the scale */
912   if (scale == 0)
913     {
914       /* no decimal point required */
915       fmt_amount = g_strdup_printf ("%d", amount);
916     }
917   else
918     {
919       /* don't use floating point arithmatic, it's noisy;
920        * we take the absolute values, because we want the minus
921        * sign to appear before the $ */
922       int divisor = pow (10, scale);
923       int dollars = abs (amount / divisor);
924       int cents = abs (amount % divisor);
925
926       fmt_amount = g_strdup_printf ("%d%s%0*d",
927         dollars, decimal, scale, cents);
928     }
929
930   money = g_strdup_printf (amount < 0 ? negative : positive, fmt_amount);
931   g_free (fmt_amount);
932
933   return money;
934 }
935
936 gboolean
937 empathy_account_has_uri_scheme_tel (TpAccount *account)
938 {
939   const gchar * const * uri_schemes;
940   guint i;
941
942   uri_schemes = tp_account_get_uri_schemes (account);
943   if (uri_schemes == NULL)
944     return FALSE;
945
946   for (i = 0; uri_schemes[i] != NULL; i++)
947     {
948       if (!tp_strdiff (uri_schemes[i], "tel"))
949         return TRUE;
950     }
951
952   return FALSE;
953 }
954
955 /* Return the TpContact on @conn associated with @individual, if any */
956 TpContact *
957 empathy_get_tp_contact_for_individual (FolksIndividual *individual,
958     TpConnection *conn)
959 {
960   TpContact *contact = NULL;
961   GeeSet *personas;
962   GeeIterator *iter;
963
964   personas = folks_individual_get_personas (individual);
965   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
966   while (contact == NULL && gee_iterator_next (iter))
967     {
968       TpfPersona *persona = gee_iterator_get (iter);
969       TpConnection *contact_conn;
970       TpContact *contact_cur = NULL;
971
972       if (TPF_IS_PERSONA (persona))
973         {
974           contact_cur = tpf_persona_get_contact (persona);
975           if (contact_cur != NULL)
976             {
977               contact_conn = tp_contact_get_connection (contact_cur);
978
979               if (!tp_strdiff (tp_proxy_get_object_path (contact_conn),
980                     tp_proxy_get_object_path (conn)))
981                 contact = contact_cur;
982             }
983         }
984
985       g_clear_object (&persona);
986     }
987   g_clear_object (&iter);
988
989   return contact;
990 }
991
992 static gboolean
993 properties_contains (gchar **list,
994                      gint length,
995                      const gchar *property)
996 {
997   gint i;
998
999   for (i = 0; i < length; i++)
1000     {
1001       if (!tp_strdiff (list[i], property))
1002         return TRUE;
1003     }
1004
1005   return FALSE;
1006 }
1007
1008 static gboolean
1009 check_writeable_property (TpConnection *connection,
1010                           FolksIndividual *individual,
1011                           gchar *property)
1012 {
1013   gchar **properties;
1014   gint prop_len;
1015   gboolean retval = FALSE;
1016   GeeSet *personas;
1017   GeeIterator *iter;
1018   FolksPersonaStore *persona_store;
1019
1020   persona_store = FOLKS_PERSONA_STORE (
1021       empathy_dup_persona_store_for_connection (connection));
1022
1023   properties =
1024     folks_persona_store_get_always_writeable_properties (persona_store,
1025                                                          &prop_len);
1026   retval = properties_contains (properties, prop_len, property);
1027   if (retval == TRUE)
1028     goto out;
1029
1030   /* Lets see if the Individual contains a Persona with the given property */
1031   personas = folks_individual_get_personas (individual);
1032   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1033   while (!retval && gee_iterator_next (iter))
1034     {
1035       FolksPersona *persona = gee_iterator_get (iter);
1036
1037       properties =
1038         folks_persona_get_writeable_properties (persona, &prop_len);
1039       retval = properties_contains (properties, prop_len, property);
1040
1041       g_clear_object (&persona);
1042
1043       if (retval == TRUE)
1044         break;
1045     }
1046   g_clear_object (&iter);
1047
1048 out:
1049   g_clear_object (&persona_store);
1050   return retval;
1051 }
1052
1053 /* Calculate whether the Individual can do audio or video calls.
1054  * FIXME: We can remove this once libfolks has grown capabilities support
1055  * again: bgo#626179. */
1056 void
1057 empathy_individual_can_audio_video_call (FolksIndividual *individual,
1058     gboolean *can_audio_call,
1059     gboolean *can_video_call,
1060     EmpathyContact **out_contact)
1061 {
1062   GeeSet *personas;
1063   GeeIterator *iter;
1064   gboolean can_audio = FALSE, can_video = FALSE;
1065
1066   personas = folks_individual_get_personas (individual);
1067   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1068   while (gee_iterator_next (iter))
1069     {
1070       FolksPersona *persona = gee_iterator_get (iter);
1071       TpContact *tp_contact;
1072
1073       if (!empathy_folks_persona_is_interesting (persona))
1074         goto while_finish;
1075
1076       tp_contact = tpf_persona_get_contact (TPF_PERSONA (persona));
1077       if (tp_contact != NULL)
1078         {
1079           EmpathyContact *contact;
1080
1081           contact = empathy_contact_dup_from_tp_contact (tp_contact);
1082           empathy_contact_set_persona (contact, persona);
1083
1084           can_audio = can_audio || empathy_contact_get_capabilities (contact) &
1085               EMPATHY_CAPABILITIES_AUDIO;
1086           can_video = can_video || empathy_contact_get_capabilities (contact) &
1087               EMPATHY_CAPABILITIES_VIDEO;
1088
1089           if (out_contact != NULL)
1090             *out_contact = g_object_ref (contact);
1091
1092           g_object_unref (contact);
1093         }
1094
1095 while_finish:
1096       g_clear_object (&persona);
1097
1098       if (can_audio && can_video)
1099         break;
1100     }
1101
1102   g_clear_object (&iter);
1103
1104   if (can_audio_call != NULL)
1105     *can_audio_call = can_audio;
1106
1107   if (can_video_call != NULL)
1108     *can_video_call = can_video;
1109 }
1110
1111 gboolean
1112 empathy_client_types_contains_mobile_device (const GStrv types) {
1113   int i;
1114
1115   if (types == NULL)
1116     return FALSE;
1117
1118   for (i = 0; types[i] != NULL; i++)
1119     if (!tp_strdiff (types[i], "phone") || !tp_strdiff (types[i], "handheld"))
1120         return TRUE;
1121
1122   return FALSE;
1123 }
1124
1125 static FolksIndividual *
1126 create_individual_from_persona (FolksPersona *persona)
1127 {
1128   GeeSet *personas;
1129   FolksIndividual *individual;
1130
1131   personas = GEE_SET (
1132       gee_hash_set_new (FOLKS_TYPE_PERSONA, g_object_ref, g_object_unref,
1133       NULL, NULL, NULL, NULL, NULL, NULL));
1134
1135   gee_collection_add (GEE_COLLECTION (personas), persona);
1136
1137   individual = folks_individual_new (personas);
1138
1139   g_clear_object (&personas);
1140
1141   return individual;
1142 }
1143
1144 FolksIndividual *
1145 empathy_create_individual_from_tp_contact (TpContact *contact)
1146 {
1147   TpfPersona *persona;
1148   FolksIndividual *individual;
1149
1150   persona = tpf_persona_dup_for_contact (contact);
1151   if (persona == NULL)
1152     {
1153       DEBUG ("Failed to get a persona for %s",
1154           tp_contact_get_identifier (contact));
1155       return NULL;
1156     }
1157
1158   individual = create_individual_from_persona (FOLKS_PERSONA (persona));
1159
1160   g_object_unref (persona);
1161   return individual;
1162 }
1163
1164 /* Look for a FolksIndividual containing @contact as one of his persona
1165  * and create one if needed */
1166 FolksIndividual *
1167 empathy_ensure_individual_from_tp_contact (TpContact *contact)
1168 {
1169   TpfPersona *persona;
1170   FolksIndividual *individual;
1171
1172   persona = tpf_persona_dup_for_contact (contact);
1173   if (persona == NULL)
1174     {
1175       DEBUG ("Failed to get a persona for %s",
1176           tp_contact_get_identifier (contact));
1177       return NULL;
1178     }
1179
1180   individual = folks_persona_get_individual (FOLKS_PERSONA (persona));
1181
1182   if (individual != NULL)
1183     g_object_ref (individual);
1184   else
1185     individual = create_individual_from_persona (FOLKS_PERSONA (persona));
1186
1187   g_object_unref (persona);
1188   return individual;
1189 }
1190
1191 const gchar * const *
1192 empathy_individual_get_client_types (FolksIndividual *individual)
1193 {
1194   GeeSet *personas;
1195   GeeIterator *iter;
1196   const gchar * const *types = NULL;
1197   FolksPresenceType presence_type = FOLKS_PRESENCE_TYPE_UNSET;
1198
1199   personas = folks_individual_get_personas (individual);
1200   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1201   while (gee_iterator_next (iter))
1202     {
1203       FolksPresenceDetails *presence;
1204       FolksPersona *persona = gee_iterator_get (iter);
1205
1206       /* We only want personas which have presence and a TpContact */
1207       if (!empathy_folks_persona_is_interesting (persona))
1208         goto while_finish;
1209
1210       presence = FOLKS_PRESENCE_DETAILS (persona);
1211
1212       if (folks_presence_details_typecmp (
1213               folks_presence_details_get_presence_type (presence),
1214               presence_type) > 0)
1215         {
1216           TpContact *tp_contact;
1217
1218           presence_type = folks_presence_details_get_presence_type (presence);
1219
1220           tp_contact = tpf_persona_get_contact (TPF_PERSONA (persona));
1221           if (tp_contact != NULL)
1222             types = tp_contact_get_client_types (tp_contact);
1223         }
1224
1225 while_finish:
1226       g_clear_object (&persona);
1227     }
1228   g_clear_object (&iter);
1229
1230   return types;
1231 }
1232
1233 GVariant *
1234 empathy_asv_to_vardict (const GHashTable *asv)
1235 {
1236   return empathy_boxed_to_variant (TP_HASH_TYPE_STRING_VARIANT_MAP, "a{sv}",
1237       (gpointer) asv);
1238 }
1239
1240 GVariant *
1241 empathy_boxed_to_variant (GType gtype,
1242     const gchar *variant_type,
1243     gpointer boxed)
1244 {
1245   GValue v = G_VALUE_INIT;
1246   GVariant *ret;
1247
1248   g_return_val_if_fail (boxed != NULL, NULL);
1249
1250   g_value_init (&v, gtype);
1251   g_value_set_boxed (&v, boxed);
1252
1253   ret = dbus_g_value_build_g_variant (&v);
1254   g_return_val_if_fail (!tp_strdiff (g_variant_get_type_string (ret),
1255         variant_type), NULL);
1256
1257   g_value_unset (&v);
1258
1259   return g_variant_ref_sink (ret);
1260 }