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