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