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