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