]> git.0d.be Git - empathy.git/blob - libempathy/empathy-utils.c
Fixed channel property setting typo so ContentType is actually set. (Jonny Lamb)
[empathy.git] / libempathy / empathy-utils.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2003-2007 Imendio AB
4  * Copyright (C) 2007-2008 Collabora Ltd.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  *
21  * Authors: Richard Hult <richard@imendio.com>
22  *          Martyn Russell <martyn@imendio.com>
23  *          Xavier Claessens <xclaesse@gmail.com>
24  *          Jonny Lamb <jonny.lamb@collabora.co.uk>
25  */
26
27 #include "config.h"
28
29 #include <string.h>
30 #include <time.h>
31 #include <sys/types.h>
32 #include <regex.h>
33
34 #include <gio/gio.h>
35 #include <glib/gi18n.h>
36
37 #include <libxml/uri.h>
38 #include <telepathy-glib/connection.h>
39 #include <telepathy-glib/channel.h>
40 #include <telepathy-glib/dbus.h>
41
42 #include <extensions/extensions.h>
43
44 #include "empathy-utils.h"
45 #include "empathy-contact-factory.h"
46 #include "empathy-contact-manager.h"
47
48 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
49 #include "empathy-debug.h"
50
51 static void regex_init (void);
52
53 gchar *
54 empathy_substring (const gchar *str,
55                   gint         start,
56                   gint         end)
57 {
58         return g_strndup (str + start, end - start);
59 }
60
61 /*
62  * Regular Expression code to match urls.
63  */
64 #define APTCHARS  "-A-Za-z0-9,-."
65 #define USERCHARS "-A-Za-z0-9"
66 #define PASSCHARS "-A-Za-z0-9,?;.:/!%$^*&~\"#'"
67 #define HOSTCHARS "-A-Za-z0-9_"
68 #define PATHCHARS "-A-Za-z0-9_$.+!*(),;:@&=?/~#%"
69 #define SCHEME    "(news:|telnet:|nntp:|file:/|https?:|ftps?:|webcal:)"
70 #define USER      "[" USERCHARS "]+(:["PASSCHARS "]+)?"
71 #define URLPATH   "/[" PATHCHARS "]*[^]'.}>) \t\r\n,\\\"]"
72
73 static regex_t dingus[EMPATHY_REGEX_ALL];
74
75 static void
76 regex_init (void)
77 {
78         static gboolean  inited = FALSE;
79         const gchar     *expression;
80         gint             i;
81
82         if (inited) {
83                 return;
84         }
85
86         for (i = 0; i < EMPATHY_REGEX_ALL; i++) {
87                 switch (i) {
88                 case EMPATHY_REGEX_AS_IS:
89                         expression =
90                                 SCHEME "//(" USER "@)?[" HOSTCHARS ".]+"
91                                 "(:[0-9]+)?(" URLPATH ")?";
92                         break;
93                 case EMPATHY_REGEX_BROWSER:
94                         expression =
95                                 "(www|ftp)[" HOSTCHARS "]*\\.[" HOSTCHARS ".]+"
96                                 "(:[0-9]+)?(" URLPATH ")?";
97                         break;
98                 case EMPATHY_REGEX_APT:
99                         expression =
100                                 "apt://[" APTCHARS "]*";
101                         break;
102                 case EMPATHY_REGEX_EMAIL:
103                         expression =
104                                 "(mailto:)?[a-z0-9][a-z0-9._-]*@[a-z0-9]"
105                                 "[a-z0-9-]*(\\.[a-z0-9][a-z0-9-]*)+";
106                         break;
107                 case EMPATHY_REGEX_OTHER:
108                         expression =
109                                 "news:[-A-Z\\^_a-z{|}~!\"#$%&'()*+,./0-9;:=?`]+"
110                                 "@[" HOSTCHARS ".]+(:[0-9]+)?";
111                         break;
112                 default:
113                         /* Silence the compiler. */
114                         expression = NULL;
115                         continue;
116                 }
117
118                 memset (&dingus[i], 0, sizeof (regex_t));
119                 regcomp (&dingus[i], expression, REG_EXTENDED | REG_ICASE);
120         }
121
122         inited = TRUE;
123 }
124
125 gint
126 empathy_regex_match (EmpathyRegExType  type,
127                     const gchar     *msg,
128                     GArray          *start,
129                     GArray          *end)
130 {
131         regmatch_t matches[1];
132         gint       ret = 0;
133         gint       num_matches = 0;
134         gint       offset = 0;
135         gint       i;
136
137         g_return_val_if_fail (type >= 0 || type <= EMPATHY_REGEX_ALL, 0);
138
139         regex_init ();
140
141         while (!ret && type != EMPATHY_REGEX_ALL) {
142                 ret = regexec (&dingus[type], msg + offset, 1, matches, 0);
143                 if (ret == 0) {
144                         gint s;
145
146                         num_matches++;
147
148                         s = matches[0].rm_so + offset;
149                         offset = matches[0].rm_eo + offset;
150
151                         g_array_append_val (start, s);
152                         g_array_append_val (end, offset);
153                 }
154         }
155
156         if (type != EMPATHY_REGEX_ALL) {
157                 DEBUG ("Found %d matches for regex type:%d", num_matches, type);
158                 return num_matches;
159         }
160
161         /* If EMPATHY_REGEX_ALL then we run ALL regex's on the string. */
162         for (i = 0; i < EMPATHY_REGEX_ALL; i++, ret = 0) {
163                 while (!ret) {
164                         ret = regexec (&dingus[i], msg + offset, 1, matches, 0);
165                         if (ret == 0) {
166                                 gint s;
167
168                                 num_matches++;
169
170                                 s = matches[0].rm_so + offset;
171                                 offset = matches[0].rm_eo + offset;
172
173                                 g_array_append_val (start, s);
174                                 g_array_append_val (end, offset);
175                         }
176                 }
177         }
178
179         DEBUG ("Found %d matches for ALL regex types", num_matches);
180
181         return num_matches;
182 }
183
184 gint
185 empathy_strcasecmp (const gchar *s1,
186                    const gchar *s2)
187 {
188         return empathy_strncasecmp (s1, s2, -1);
189 }
190
191 gint
192 empathy_strncasecmp (const gchar *s1,
193                     const gchar *s2,
194                     gsize        n)
195 {
196         gchar *u1, *u2;
197         gint   ret_val;
198
199         u1 = g_utf8_casefold (s1, n);
200         u2 = g_utf8_casefold (s2, n);
201
202         ret_val = g_utf8_collate (u1, u2);
203         g_free (u1);
204         g_free (u2);
205
206         return ret_val;
207 }
208
209 gboolean
210 empathy_xml_validate (xmlDoc      *doc,
211                      const gchar *dtd_filename)
212 {
213         gchar        *path, *escaped;
214         xmlValidCtxt  cvp;
215         xmlDtd       *dtd;
216         gboolean      ret;
217
218         path = g_build_filename (g_getenv ("EMPATHY_SRCDIR"), "libempathy",
219                                  dtd_filename, NULL);
220         if (!g_file_test (path, G_FILE_TEST_EXISTS)) {
221                 g_free (path);
222                 path = g_build_filename (DATADIR, "empathy", dtd_filename, NULL);
223         }
224         DEBUG ("Loading dtd file %s", path);
225
226         /* The list of valid chars is taken from libxml. */
227         escaped = xmlURIEscapeStr (path, ":@&=+$,/?;");
228         g_free (path);
229
230         memset (&cvp, 0, sizeof (cvp));
231         dtd = xmlParseDTD (NULL, escaped);
232         ret = xmlValidateDtd (&cvp, doc, dtd);
233
234         xmlFree (escaped);
235         xmlFreeDtd (dtd);
236
237         return ret;
238 }
239
240 xmlNodePtr
241 empathy_xml_node_get_child (xmlNodePtr   node, 
242                            const gchar *child_name)
243 {
244         xmlNodePtr l;
245
246         g_return_val_if_fail (node != NULL, NULL);
247         g_return_val_if_fail (child_name != NULL, NULL);
248
249         for (l = node->children; l; l = l->next) {
250                 if (l->name && strcmp (l->name, child_name) == 0) {
251                         return l;
252                 }
253         }
254
255         return NULL;
256 }
257
258 xmlChar *
259 empathy_xml_node_get_child_content (xmlNodePtr   node, 
260                                    const gchar *child_name)
261 {
262         xmlNodePtr l;
263
264         g_return_val_if_fail (node != NULL, NULL);
265         g_return_val_if_fail (child_name != NULL, NULL);
266
267         l = empathy_xml_node_get_child (node, child_name);
268         if (l) {
269                 return xmlNodeGetContent (l);
270         }
271                 
272         return NULL;
273 }
274
275 xmlNodePtr
276 empathy_xml_node_find_child_prop_value (xmlNodePtr   node, 
277                                        const gchar *prop_name,
278                                        const gchar *prop_value)
279 {
280         xmlNodePtr l;
281         xmlNodePtr found = NULL;
282
283         g_return_val_if_fail (node != NULL, NULL);
284         g_return_val_if_fail (prop_name != NULL, NULL);
285         g_return_val_if_fail (prop_value != NULL, NULL);
286
287         for (l = node->children; l && !found; l = l->next) {
288                 xmlChar *prop;
289
290                 if (!xmlHasProp (l, prop_name)) {
291                         continue;
292                 }
293
294                 prop = xmlGetProp (l, prop_name);
295                 if (prop && strcmp (prop, prop_value) == 0) {
296                         found = l;
297                 }
298                 
299                 xmlFree (prop);
300         }
301                 
302         return found;
303 }
304
305 guint
306 empathy_account_hash (gconstpointer key)
307 {
308         g_return_val_if_fail (MC_IS_ACCOUNT (key), 0);
309
310         return g_str_hash (mc_account_get_unique_name (MC_ACCOUNT (key)));
311 }
312
313 gboolean
314 empathy_account_equal (gconstpointer a,
315                        gconstpointer b)
316 {
317         const gchar *name_a;
318         const gchar *name_b;
319
320         g_return_val_if_fail (MC_IS_ACCOUNT (a), FALSE);
321         g_return_val_if_fail (MC_IS_ACCOUNT (b), FALSE);
322
323         name_a = mc_account_get_unique_name (MC_ACCOUNT (a));
324         name_b = mc_account_get_unique_name (MC_ACCOUNT (b));
325
326         return g_str_equal (name_a, name_b);
327 }
328
329 MissionControl *
330 empathy_mission_control_new (void)
331 {
332         static MissionControl *mc = NULL;
333
334         if (!mc) {
335                 mc = mission_control_new (tp_get_bus ());
336                 g_object_add_weak_pointer (G_OBJECT (mc), (gpointer) &mc);
337         } else {
338                 g_object_ref (mc);
339         }
340
341         return mc;
342 }
343
344 const gchar *
345 empathy_presence_get_default_message (McPresence presence)
346 {
347         switch (presence) {
348         case MC_PRESENCE_AVAILABLE:
349                 return _("Available");
350         case MC_PRESENCE_DO_NOT_DISTURB:
351                 return _("Busy");
352         case MC_PRESENCE_AWAY:
353         case MC_PRESENCE_EXTENDED_AWAY:
354                 return _("Away");
355         case MC_PRESENCE_HIDDEN:
356                 return _("Hidden");
357         case MC_PRESENCE_OFFLINE:
358         case MC_PRESENCE_UNSET:
359                 return _("Offline");
360         default:
361                 g_assert_not_reached ();
362         }
363
364         return NULL;
365 }
366
367 const gchar *
368 empathy_presence_to_str (McPresence presence)
369 {
370         switch (presence) {
371         case MC_PRESENCE_AVAILABLE:
372                 return "available";
373         case MC_PRESENCE_DO_NOT_DISTURB:
374                 return "busy";
375         case MC_PRESENCE_AWAY:
376                 return "away";
377         case MC_PRESENCE_EXTENDED_AWAY:
378                 return "ext_away";
379         case MC_PRESENCE_HIDDEN:
380                 return "hidden";
381         case MC_PRESENCE_OFFLINE:
382                 return "offline";
383         case MC_PRESENCE_UNSET:
384                 return "unset";
385         default:
386                 g_assert_not_reached ();
387         }
388
389         return NULL;
390 }
391
392 McPresence
393 empathy_presence_from_str (const gchar *str)
394 {
395         if (strcmp (str, "available") == 0) {
396                 return MC_PRESENCE_AVAILABLE;
397         } else if ((strcmp (str, "dnd") == 0) || (strcmp (str, "busy") == 0)) {
398                 return MC_PRESENCE_DO_NOT_DISTURB;
399         } else if ((strcmp (str, "away") == 0) || (strcmp (str, "brb") == 0)) {
400                 return MC_PRESENCE_AWAY;
401         } else if ((strcmp (str, "xa") == 0) || (strcmp (str, "ext_away") == 0)) {
402                 return MC_PRESENCE_EXTENDED_AWAY;
403         } else if (strcmp (str, "hidden") == 0) {
404                 return MC_PRESENCE_HIDDEN;
405         } else if (strcmp (str, "offline") == 0) {
406                 return MC_PRESENCE_OFFLINE;
407         } else if (strcmp (str, "unset") == 0) {
408                 return MC_PRESENCE_UNSET;
409         }
410
411         return MC_PRESENCE_UNSET;
412 }
413
414 gchar *
415 empathy_file_lookup (const gchar *filename, const gchar *subdir)
416 {
417         gchar *path;
418
419         if (!subdir) {
420                 subdir = ".";
421         }
422
423         path = g_build_filename (g_getenv ("EMPATHY_SRCDIR"), subdir, filename, NULL);
424         if (!g_file_test (path, G_FILE_TEST_EXISTS)) {
425                 g_free (path);
426                 path = g_build_filename (DATADIR, "empathy", filename, NULL);
427         }
428
429         return path;
430 }
431
432 typedef struct {
433         EmpathyRunUntilReadyFunc  func;
434         gpointer                  user_data;
435         GObject                  *object;
436         GMainLoop                *loop;
437 } RunUntilReadyData;
438
439 static void
440 run_until_ready_cb (RunUntilReadyData *data)
441 {
442         if (!data->func || data->func (data->object, data->user_data)) {
443                 DEBUG ("Object %p is ready", data->object);
444                 g_main_loop_quit (data->loop);
445         }
446 }
447
448 static gboolean
449 object_is_ready (GObject *object,
450                  gpointer user_data)
451 {
452         gboolean ready;
453
454         g_object_get (object, "ready", &ready, NULL);
455
456         return ready;
457 }
458
459 void
460 empathy_run_until_ready_full (gpointer                  object,
461                               const gchar              *signal,
462                               EmpathyRunUntilReadyFunc  func,
463                               gpointer                  user_data,
464                               GMainLoop               **loop)
465 {
466         RunUntilReadyData  data;
467         gulong             signal_id;
468
469         g_return_if_fail (G_IS_OBJECT (object));
470         g_return_if_fail (signal != NULL);
471
472         if (func && func (object, user_data)) {
473                 return;
474         }
475
476         DEBUG ("Starting run until ready for object %p", object);
477
478         data.func = func;
479         data.user_data = user_data;
480         data.object = object;
481         data.loop = g_main_loop_new (NULL, FALSE);
482
483         signal_id = g_signal_connect_swapped (object, signal,
484                                               G_CALLBACK (run_until_ready_cb),
485                                               &data);
486         if (loop != NULL) {
487                 *loop = data.loop;
488         }
489
490         g_main_loop_run (data.loop);
491
492         if (loop != NULL) {
493                 *loop = NULL;
494         }
495
496         g_signal_handler_disconnect (object, signal_id);
497         g_main_loop_unref (data.loop);
498 }
499
500 void
501 empathy_run_until_ready (gpointer object)
502 {
503         empathy_run_until_ready_full (object, "notify::ready", object_is_ready,
504                                       NULL, NULL);
505 }
506
507 McAccount *
508 empathy_channel_get_account (TpChannel *channel)
509 {
510         TpConnection   *connection;
511         McAccount      *account;
512         MissionControl *mc;
513
514         g_object_get (channel, "connection", &connection, NULL);
515         mc = empathy_mission_control_new ();
516         account = mission_control_get_account_for_tpconnection (mc, connection, NULL);
517         g_object_unref (connection);
518         g_object_unref (mc);
519
520         return account;
521 }
522
523 typedef void (*AccountStatusChangedFunc) (MissionControl           *mc,
524                                           TpConnectionStatus        status,
525                                           McPresence                presence,
526                                           TpConnectionStatusReason  reason,
527                                           const gchar              *unique_name,
528                                           gpointer                  user_data);
529
530 typedef struct {
531         AccountStatusChangedFunc handler;
532         gpointer                 user_data;
533         GClosureNotify           free_func;
534         MissionControl          *mc;
535 } AccountStatusChangedData;
536
537 typedef struct {
538         TpConnectionStatus        status;
539         McPresence                presence;
540         TpConnectionStatusReason  reason;
541         gchar                    *unique_name;
542         AccountStatusChangedData *data;
543 } InvocationData;
544
545 static void
546 account_status_changed_data_free (gpointer ptr,
547                                   GClosure *closure)
548 {
549         AccountStatusChangedData *data = ptr;
550
551         if (data->free_func) {
552                 data->free_func (data->user_data, closure);
553         }
554         g_object_unref (data->mc);
555         g_slice_free (AccountStatusChangedData, data);
556 }
557
558 static gboolean
559 account_status_changed_invoke_callback (gpointer data)
560 {
561         InvocationData *invocation_data = data;
562
563         invocation_data->data->handler (invocation_data->data->mc,
564                                         invocation_data->status,
565                                         invocation_data->presence,
566                                         invocation_data->reason,
567                                         invocation_data->unique_name,
568                                         invocation_data->data->user_data);
569
570         g_free (invocation_data->unique_name);
571         g_slice_free (InvocationData, invocation_data);
572
573         return FALSE;
574 }
575
576 static void
577 account_status_changed_cb (MissionControl           *mc,
578                            TpConnectionStatus        status,
579                            McPresence                presence,
580                            TpConnectionStatusReason  reason,
581                            const gchar              *unique_name,
582                            AccountStatusChangedData *data)
583 {
584         InvocationData *invocation_data;
585
586         invocation_data = g_slice_new (InvocationData);
587         invocation_data->status = status;
588         invocation_data->presence = presence;
589         invocation_data->reason = reason;
590         invocation_data->unique_name = g_strdup (unique_name);
591         invocation_data->data = data;
592
593         g_idle_add_full (G_PRIORITY_HIGH,
594                          account_status_changed_invoke_callback,
595                          invocation_data, NULL);
596 }
597
598 gpointer
599 empathy_connect_to_account_status_changed (MissionControl *mc,
600                                            GCallback       handler,
601                                            gpointer        user_data,
602                                            GClosureNotify  free_func)
603 {
604         AccountStatusChangedData *data;
605
606         g_return_val_if_fail (IS_MISSIONCONTROL (mc), NULL);
607         g_return_val_if_fail (handler != NULL, NULL);
608         
609         data = g_slice_new (AccountStatusChangedData);
610         data->handler = (AccountStatusChangedFunc) handler;
611         data->user_data = user_data;
612         data->free_func = free_func;
613         data->mc = g_object_ref (mc);
614
615         dbus_g_proxy_connect_signal (DBUS_G_PROXY (mc), "AccountStatusChanged",
616                                      G_CALLBACK (account_status_changed_cb),
617                                      data, account_status_changed_data_free);
618
619         return data;
620 }
621
622 void
623 empathy_disconnect_account_status_changed (gpointer token)
624 {
625         AccountStatusChangedData *data = token;
626
627         dbus_g_proxy_disconnect_signal (DBUS_G_PROXY (data->mc),
628                                         "AccountStatusChanged",
629                                         G_CALLBACK (account_status_changed_cb),
630                                         data);
631 }
632
633 guint
634 empathy_proxy_hash (gconstpointer key)
635 {
636         TpProxy      *proxy = TP_PROXY (key);
637         TpProxyClass *proxy_class = TP_PROXY_GET_CLASS (key);
638
639         g_return_val_if_fail (TP_IS_PROXY (proxy), 0);
640         g_return_val_if_fail (proxy_class->must_have_unique_name, 0);
641
642         return g_str_hash (proxy->object_path) ^ g_str_hash (proxy->bus_name);
643 }
644
645 gboolean
646 empathy_proxy_equal (gconstpointer a,
647                      gconstpointer b)
648 {
649         TpProxy *proxy_a = TP_PROXY (a);
650         TpProxy *proxy_b = TP_PROXY (b);
651         TpProxyClass *proxy_a_class = TP_PROXY_GET_CLASS (a);
652         TpProxyClass *proxy_b_class = TP_PROXY_GET_CLASS (b);
653
654         g_return_val_if_fail (TP_IS_PROXY (proxy_a), FALSE);
655         g_return_val_if_fail (TP_IS_PROXY (proxy_b), FALSE);
656         g_return_val_if_fail (proxy_a_class->must_have_unique_name, 0);
657         g_return_val_if_fail (proxy_b_class->must_have_unique_name, 0);
658
659         return g_str_equal (proxy_a->object_path, proxy_b->object_path) &&
660                g_str_equal (proxy_a->bus_name, proxy_b->bus_name);
661 }
662
663 typedef struct {
664         gint timeout_ms;
665         gchar *channel_type;
666         guint handle_type;
667         empathy_connection_callback_for_request_channel callback;
668         gpointer user_data;
669         GDestroyNotify destroy;
670         TpHandle handle;
671         gboolean suppress_handler;
672         guint ref_count;
673 } ConnectionRequestChannelData;
674
675 static void
676 connection_request_channel_data_unref (gpointer user_data)
677 {
678         ConnectionRequestChannelData *data = (ConnectionRequestChannelData*) user_data;
679
680         if (--data->ref_count == 0) {
681                 g_free (data->channel_type);
682                 if (data->destroy) {
683                         data->destroy (data->user_data);
684                 }
685                 g_slice_free (ConnectionRequestChannelData, data);
686         }
687 }
688
689 static void
690 connection_request_channel_cb (TpConnection *connection,
691                                const gchar *object_path,
692                                const GError *error,
693                                gpointer user_data,
694                                GObject *weak_object)
695 {
696         ConnectionRequestChannelData *data = (ConnectionRequestChannelData*) user_data;
697         TpChannel *channel;
698
699         if (!data->callback) {
700                 return;
701         }
702
703         if (error) {
704                 data->callback (connection, NULL, error, data->user_data, weak_object);
705                 return;
706         }
707
708         channel = tp_channel_new (connection, object_path,
709                                   data->channel_type,
710                                   data->handle_type,
711                                   data->handle, NULL);
712
713         data->callback (connection, channel, NULL, data->user_data, weak_object);
714         g_object_unref (channel);
715 }
716
717 static void
718 connection_request_handles_cb (TpConnection *connection,
719                                const GArray *handles,
720                                const GError *error,
721                                gpointer user_data,
722                                GObject *weak_object)
723 {
724         ConnectionRequestChannelData *data = (ConnectionRequestChannelData*) user_data;
725
726         if (error) {
727                 if (data->callback) {
728                         data->callback (connection, NULL, error, data->user_data, weak_object);
729                 }
730                 return;
731         }
732
733         data->handle = g_array_index (handles, guint, 0);
734         data->ref_count++;
735         tp_cli_connection_call_request_channel (connection, data->timeout_ms,
736                                                 data->channel_type,
737                                                 data->handle_type,
738                                                 data->handle,
739                                                 data->suppress_handler,
740                                                 connection_request_channel_cb,
741                                                 data,
742                                                 (GDestroyNotify) connection_request_channel_data_unref,
743                                                 weak_object);
744 }
745
746 void
747 empathy_connection_request_channel (TpConnection *connection,
748                                     gint timeout_ms,
749                                     const gchar *channel_type,
750                                     guint handle_type,
751                                     const gchar *name,
752                                     gboolean suppress_handler,
753                                     empathy_connection_callback_for_request_channel callback,
754                                     gpointer user_data,
755                                     GDestroyNotify destroy,
756                                     GObject *weak_object)
757 {
758         const gchar *names[] = {name, NULL};
759         ConnectionRequestChannelData *data;
760
761         data = g_slice_new (ConnectionRequestChannelData);
762         data->timeout_ms = timeout_ms;
763         data->channel_type = g_strdup (channel_type);
764         data->handle_type = handle_type;
765         data->callback = callback;
766         data->user_data = user_data;
767         data->destroy = destroy;
768         data->handle = 0;
769         data->suppress_handler = suppress_handler;
770         data->ref_count = 1;
771         tp_cli_connection_call_request_handles (connection,
772                                                 timeout_ms,
773                                                 handle_type,
774                                                 names,
775                                                 connection_request_handles_cb,
776                                                 data,
777                                                 (GDestroyNotify) connection_request_channel_data_unref,
778                                                 weak_object);
779 }
780
781 EmpathyTpFile *
782 empathy_send_file (EmpathyContact *contact,
783                    GFile          *gfile)
784 {
785         GFileInfo      *info;
786         guint64         size;
787         GInputStream   *in_stream = NULL;
788         MissionControl *mc;
789         McAccount      *account;
790         TpConnection   *connection;
791         guint           handle;
792         gchar          *object_path;
793         TpChannel      *channel;
794         EmpathyTpFile  *tp_file;
795         GError         *error = NULL;
796         GValue          value = { 0 };
797         gchar          *filename;
798
799         g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
800         g_return_val_if_fail (G_IS_FILE (gfile), NULL);
801
802         info = g_file_query_info (gfile,
803                                   G_FILE_ATTRIBUTE_STANDARD_SIZE ","
804                                   G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
805                                   0, NULL, NULL);
806         size = info ? g_file_info_get_size (info) : EMPATHY_TP_FILE_UNKNOWN_SIZE;
807         filename = g_file_get_basename (gfile);
808         in_stream = G_INPUT_STREAM (g_file_read (gfile, NULL, NULL));
809         mc = empathy_mission_control_new ();
810         account = empathy_contact_get_account (contact);
811         connection = mission_control_get_tpconnection (mc, account, NULL);
812         tp_connection_run_until_ready (connection, FALSE, NULL, NULL);
813         handle = empathy_contact_get_handle (contact);
814
815         DEBUG ("Sending %s from a stream to %s (size %llu, content-type %s)",
816                filename, empathy_contact_get_name (contact), size,
817                g_file_info_get_content_type (info));
818
819         if (!tp_cli_connection_run_request_channel (connection, -1,
820                                                     EMP_IFACE_CHANNEL_TYPE_FILE,
821                                                     TP_HANDLE_TYPE_CONTACT,
822                                                     handle,
823                                                     FALSE,
824                                                     &object_path,
825                                                     &error,
826                                                     NULL)) {
827                 DEBUG ("Couldn't request channel: %s",
828                        error ? error->message : "No error given");
829                 g_clear_error (&error);
830                 g_object_unref (mc);
831                 g_object_unref (connection);
832                 return NULL;
833         }
834
835         channel = tp_channel_new (connection,
836                                   object_path,
837                                   EMP_IFACE_CHANNEL_TYPE_FILE,
838                                   TP_HANDLE_TYPE_CONTACT,
839                                   handle,
840                                   NULL);
841
842         /* FIXME: this should go in CreateChannel in the new requests API */
843
844         g_value_init (&value, G_TYPE_STRING);
845         g_value_set_string (&value, g_filename_display_basename (filename));
846         tp_cli_dbus_properties_call_set (TP_PROXY (channel), -1,
847                 EMP_IFACE_CHANNEL_TYPE_FILE, "Filename",
848                 &value, NULL, NULL, NULL, NULL);
849         g_value_reset (&value);
850
851         g_value_set_string (&value, g_file_info_get_content_type (info));
852         tp_cli_dbus_properties_call_set (TP_PROXY (channel), -1,
853                 EMP_IFACE_CHANNEL_TYPE_FILE, "ContentType",
854                 &value, NULL, NULL, NULL, NULL);
855
856         g_value_unset (&value);
857
858         g_value_init (&value, G_TYPE_UINT64);
859         g_value_set_uint64 (&value, size);
860         tp_cli_dbus_properties_call_set (TP_PROXY (channel), -1,
861                 EMP_IFACE_CHANNEL_TYPE_FILE, "Size",
862                 &value, NULL, NULL, NULL, NULL);
863         g_value_unset (&value);
864
865         tp_file = empathy_tp_file_new (account, channel);
866
867         if (tp_file) {
868                 empathy_tp_file_set_input_stream (tp_file, in_stream);
869         }
870
871         empathy_tp_file_offer (tp_file);
872
873         g_object_unref (mc);
874         g_object_unref (connection);
875         g_object_unref (channel);
876         g_free (object_path);
877
878         return tp_file;
879 }
880