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