]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tp-file.c
Emit an error for socket types we don't handle
[empathy.git] / libempathy / empathy-tp-file.c
1 /*
2  * Copyright (C) 2007-2009 Collabora Ltd.
3  * Copyright (C) 2007 Marco Barisione <marco@barisione.org>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library 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  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  *
19  * Authors: Marco Barisione <marco@barisione.org>
20  *          Jonny Lamb <jonny.lamb@collabora.co.uk>
21  *          Cosimo Cecchi <cosimo.cecchi@collabora.co.uk>
22  */
23
24 #include <config.h>
25
26 #include <string.h>
27 #include <unistd.h>
28 #include <errno.h>
29 #include <arpa/inet.h>
30 #include <sys/types.h>
31 #include <sys/socket.h>
32 #include <sys/un.h>
33
34 #include <glib/gi18n-lib.h>
35
36 #include <gio/gio.h>
37 #include <gio/gunixinputstream.h>
38 #include <gio/gunixoutputstream.h>
39
40 #include <telepathy-glib/gtypes.h>
41 #include <telepathy-glib/proxy-subclass.h>
42 #include <telepathy-glib/util.h>
43
44 #include "empathy-tp-file.h"
45 #include "empathy-marshal.h"
46 #include "empathy-time.h"
47 #include "empathy-utils.h"
48
49 #define DEBUG_FLAG EMPATHY_DEBUG_FT
50 #include "empathy-debug.h"
51
52 /**
53  * SECTION:empathy-tp-file
54  * @title: EmpathyTpFile
55  * @short_description: Object which represents a Telepathy file channel
56  * @include: libempathy/empathy-tp-file.h
57  *
58  * #EmpathyTpFile is an object which represents a Telepathy file channel.
59  * Usually, clients do not need to deal with #EmpathyTpFile objects directly,
60  * and are supposed to use #EmpathyFTHandler and #EmpathyFTFactory for
61  * transferring files using libempathy.
62  */
63
64 /* EmpathyTpFile object */
65
66 typedef struct {
67   TpChannel *channel;
68   gboolean ready;
69
70   GInputStream *in_stream;
71   GOutputStream *out_stream;
72
73   /* org.freedesktop.Telepathy.Channel.Type.FileTransfer D-Bus properties */
74   TpFileTransferState state;
75   TpFileTransferStateChangeReason state_change_reason;
76   TpSocketAddressType socket_address_type;
77   TpSocketAccessControl socket_access_control;
78
79   /* transfer properties */
80   gboolean incoming;
81   time_t start_time;
82   GArray *socket_address;
83   guint port;
84   guint64 offset;
85
86   /* GCancellable we're passed when offering/accepting the transfer */
87   GCancellable *cancellable;
88
89   /* callbacks for the operation */
90   EmpathyTpFileProgressCallback progress_callback;
91   gpointer progress_user_data;
92   EmpathyTpFileOperationCallback op_callback;
93   gpointer op_user_data;
94
95   gboolean is_closed;
96
97   gboolean dispose_run;
98 } EmpathyTpFilePriv;
99
100 enum {
101   PROP_0,
102   PROP_CHANNEL,
103   PROP_INCOMING
104 };
105
106 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyTpFile)
107
108 G_DEFINE_TYPE (EmpathyTpFile, empathy_tp_file, G_TYPE_OBJECT);
109
110 /* private functions */
111
112 static void
113 tp_file_get_state_cb (TpProxy *proxy,
114     const GValue *value,
115     const GError *error,
116     gpointer user_data,
117     GObject *weak_object)
118 {
119   EmpathyTpFilePriv *priv = GET_PRIV (weak_object);
120
121   if (error != NULL)
122     {
123       /* set a default value for the state */
124       priv->state = TP_FILE_TRANSFER_STATE_NONE;
125       return;
126     }
127
128   priv->state = g_value_get_uint (value);
129 }
130
131 static void
132 tp_file_get_available_socket_types_cb (TpProxy *proxy,
133     const GValue *value,
134     const GError *error,
135     gpointer user_data,
136     GObject *weak_object)
137 {
138   EmpathyTpFilePriv *priv = GET_PRIV (weak_object);
139   GHashTable *socket_types;
140   GArray *access_controls;
141
142   if (error != NULL ||
143       !G_VALUE_HOLDS (value, TP_HASH_TYPE_SUPPORTED_SOCKET_MAP))
144     {
145       /* set a default value */
146       priv->socket_address_type = TP_SOCKET_ADDRESS_TYPE_UNIX;
147       priv->socket_access_control = TP_SOCKET_ACCESS_CONTROL_LOCALHOST;
148       goto out;
149     }
150
151   socket_types = g_value_get_boxed (value);
152
153   /* here UNIX is preferred to IPV4 */
154   if ((access_controls = g_hash_table_lookup (socket_types,
155       GUINT_TO_POINTER (TP_SOCKET_ADDRESS_TYPE_UNIX))) != NULL)
156     {
157       priv->socket_address_type = TP_SOCKET_ADDRESS_TYPE_UNIX;
158       priv->socket_access_control = TP_SOCKET_ACCESS_CONTROL_LOCALHOST;
159       goto out;
160     }
161
162   if ((access_controls = g_hash_table_lookup (socket_types,
163       GUINT_TO_POINTER (TP_SOCKET_ADDRESS_TYPE_IPV4))) != NULL)
164     {
165       priv->socket_address_type = TP_SOCKET_ADDRESS_TYPE_IPV4;
166       g_array_sort (access_controls, empathy_uint_compare);
167
168       /* here port is preferred over localhost */
169       if ((g_array_index (access_controls, guint, 0) ==
170           TP_SOCKET_ACCESS_CONTROL_LOCALHOST) &&
171           (g_array_index (access_controls, guint, 1) ==
172           TP_SOCKET_ACCESS_CONTROL_PORT))
173         priv->socket_access_control = TP_SOCKET_ACCESS_CONTROL_PORT;
174       else
175         priv->socket_access_control =
176             g_array_index (access_controls, guint, 0);
177     }
178
179 out:
180   DEBUG ("Socket address type: %u, access control %u",
181       priv->socket_address_type, priv->socket_access_control);  
182 }
183
184 static void
185 tp_file_invalidated_cb (TpProxy       *proxy,
186     guint          domain,
187     gint           code,
188     gchar         *message,
189     EmpathyTpFile *tp_file)
190 {
191   EmpathyTpFilePriv *priv = GET_PRIV (tp_file);
192
193   DEBUG ("Channel invalidated: %s", message);
194
195   if (priv->state != TP_FILE_TRANSFER_STATE_COMPLETED &&
196       priv->state != TP_FILE_TRANSFER_STATE_CANCELLED)
197     {
198       /* The channel is not in a finished state, an error occured */
199       priv->state = TP_FILE_TRANSFER_STATE_CANCELLED;
200       priv->state_change_reason =
201           TP_FILE_TRANSFER_STATE_CHANGE_REASON_LOCAL_ERROR;
202     }
203 }
204
205 static void
206 ft_operation_close_clean (EmpathyTpFile *tp_file)
207 {
208   EmpathyTpFilePriv *priv = GET_PRIV (tp_file);
209
210   if (priv->is_closed)
211     return;
212
213   DEBUG ("FT operation close clean");
214
215   priv->is_closed = TRUE;
216
217   if (priv->op_callback != NULL)
218     priv->op_callback (tp_file, NULL, priv->op_user_data);
219 }
220
221 static void
222 ft_operation_close_with_error (EmpathyTpFile *tp_file,
223     GError *error)
224 {
225   EmpathyTpFilePriv *priv = GET_PRIV (tp_file);
226
227   if (priv->is_closed)
228     return;
229
230   DEBUG ("FT operation close with error %s", error->message);
231
232   priv->is_closed = TRUE;
233
234   /* close the channel if it's not cancelled already */
235   if (priv->state != TP_FILE_TRANSFER_STATE_CANCELLED)
236     empathy_tp_file_cancel (tp_file);
237
238   if (priv->op_callback != NULL)
239     priv->op_callback (tp_file, error, priv->op_user_data);
240 }
241
242 static void
243 splice_stream_ready_cb (GObject *source,
244     GAsyncResult *res,
245     gpointer user_data)
246 {
247   EmpathyTpFile *tp_file;
248   GError *error = NULL;
249
250   tp_file = user_data;
251
252   g_output_stream_splice_finish (G_OUTPUT_STREAM (source), res, &error);
253
254   DEBUG ("Splice stream ready cb, error %p", error);
255
256   if (error != NULL)
257     {
258       ft_operation_close_with_error (tp_file, error);
259       g_clear_error (&error);
260       return;
261     }
262 }
263
264 static void
265 tp_file_start_transfer (EmpathyTpFile *tp_file)
266 {
267   gint fd, domain, res = 0;
268   GError *error = NULL;
269   struct sockaddr *my_addr = NULL;
270   size_t my_size = 0;
271   EmpathyTpFilePriv *priv = GET_PRIV (tp_file);
272
273   if (priv->socket_address_type == TP_SOCKET_ADDRESS_TYPE_UNIX)
274     {
275       domain = AF_UNIX;
276     }
277   else if (priv->socket_address_type == TP_SOCKET_ADDRESS_TYPE_IPV4)
278     {
279       domain = AF_INET;
280     }
281   else
282     {
283       error = g_error_new_literal (EMPATHY_FT_ERROR_QUARK,
284           EMPATHY_FT_ERROR_NOT_SUPPORTED, _("Socket type not supported"));
285
286       DEBUG ("Socket not supported, closing channel");
287
288       ft_operation_close_with_error (tp_file, error);
289       g_clear_error (&error);
290
291       return;
292     }
293
294   fd = socket (domain, SOCK_STREAM, 0);
295
296   if (fd < 0)
297     {
298       int code = errno;
299
300       error = g_error_new_literal (EMPATHY_FT_ERROR_QUARK,
301           EMPATHY_FT_ERROR_SOCKET, g_strerror (code));
302
303       DEBUG ("Failed to create socket, closing channel");
304
305       ft_operation_close_with_error (tp_file, error);
306       g_clear_error (&error);
307
308       return;
309     }
310
311   if (priv->socket_address_type == TP_SOCKET_ADDRESS_TYPE_UNIX)
312     {
313       struct sockaddr_un addr;
314
315       memset (&addr, 0, sizeof (addr));
316       addr.sun_family = domain;
317       strncpy (addr.sun_path, priv->socket_address->data,
318           priv->socket_address->len);
319
320       my_addr = (struct sockaddr *) &addr;
321       my_size = sizeof (addr);
322     }
323   else if (priv->socket_address_type == TP_SOCKET_ADDRESS_TYPE_IPV4)
324     {
325       struct sockaddr_in addr;
326
327       memset (&addr, 0, sizeof (addr));
328       addr.sin_family = domain;
329       inet_pton (AF_INET, priv->socket_address->data, &addr.sin_addr);
330       addr.sin_port = htons (priv->port);
331
332       my_addr = (struct sockaddr *) &addr;
333       my_size = sizeof (addr);
334     }
335
336   res = connect (fd, my_addr, my_size);
337
338   if (res < 0)
339     {
340       int code = errno;
341
342       error = g_error_new_literal (EMPATHY_FT_ERROR_QUARK,
343           EMPATHY_FT_ERROR_SOCKET, g_strerror (code));
344
345       DEBUG ("Failed to connect socket, closing channel");
346
347       ft_operation_close_with_error (tp_file, error);
348       close (fd);
349       g_clear_error (&error);
350
351       return;
352     }
353
354   DEBUG ("Start the transfer");
355
356   priv->start_time = empathy_time_get_current ();
357
358   /* notify we're starting a transfer */
359   if (priv->progress_callback != NULL)
360     priv->progress_callback (tp_file, 0, priv->progress_user_data);
361
362   if (priv->incoming)
363     {
364       GInputStream *socket_stream;
365
366       socket_stream = g_unix_input_stream_new (fd, TRUE);
367
368       g_output_stream_splice_async (priv->out_stream, socket_stream,
369           G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
370           G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
371           G_PRIORITY_DEFAULT, priv->cancellable,
372           splice_stream_ready_cb, tp_file);
373
374       g_object_unref (socket_stream);
375     }
376   else
377     {
378       GOutputStream *socket_stream;
379
380       socket_stream = g_unix_output_stream_new (fd, TRUE);
381
382       g_output_stream_splice_async (socket_stream, priv->in_stream,
383           G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
384           G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
385           G_PRIORITY_DEFAULT, priv->cancellable,
386           splice_stream_ready_cb, tp_file);
387
388       g_object_unref (socket_stream);
389     }
390 }
391
392 static GError *
393 error_from_state_change_reason (TpFileTransferStateChangeReason reason)
394 {
395   const char *string;
396   GError *retval = NULL;
397
398   string = NULL;
399
400   switch (reason)
401     {
402       case TP_FILE_TRANSFER_STATE_CHANGE_REASON_NONE:
403         string = _("No reason was specified");
404         break;
405       case TP_FILE_TRANSFER_STATE_CHANGE_REASON_REQUESTED:
406         string = _("The change in state was requested");
407         break;
408       case TP_FILE_TRANSFER_STATE_CHANGE_REASON_LOCAL_STOPPED:
409         string = _("You canceled the file transfer");
410         break;
411       case TP_FILE_TRANSFER_STATE_CHANGE_REASON_REMOTE_STOPPED:
412         string = _("The other participant canceled the file transfer");
413         break;
414       case TP_FILE_TRANSFER_STATE_CHANGE_REASON_LOCAL_ERROR:
415         string = _("Error while trying to transfer the file");
416         break;
417       case TP_FILE_TRANSFER_STATE_CHANGE_REASON_REMOTE_ERROR:
418         string = _("The other participant is unable to transfer the file");
419         break;
420       default:
421         string = _("Unknown reason");
422         break;
423     }
424
425   retval = g_error_new_literal (EMPATHY_FT_ERROR_QUARK,
426       EMPATHY_FT_ERROR_TP_ERROR, string);
427
428   return retval;
429 }
430
431 static void
432 tp_file_state_changed_cb (TpChannel *proxy,
433     guint state,
434     guint reason,
435     gpointer user_data,
436     GObject *weak_object)
437 {
438   EmpathyTpFilePriv *priv = GET_PRIV (weak_object);
439   GError *error = NULL;
440
441   if (state == priv->state)
442     return;
443
444   DEBUG ("File transfer state changed:\n"
445       "old state = %u, state = %u, reason = %u\n"
446       "\tincoming = %s, in_stream = %s, out_stream = %s",
447       priv->state, state, reason,
448       priv->incoming ? "yes" : "no",
449       priv->in_stream ? "present" : "not present",
450       priv->out_stream ? "present" : "not present");
451
452   priv->state = state;
453   priv->state_change_reason = reason;
454
455   /* If the channel is open AND we have the socket path, we can start the
456    * transfer. The socket path could be NULL if we are not doing the actual
457    * data transfer but are just an observer for the channel.
458    */
459   if (state == TP_FILE_TRANSFER_STATE_OPEN &&
460       priv->socket_address != NULL)
461     tp_file_start_transfer (EMPATHY_TP_FILE (weak_object));
462
463   if (state == TP_FILE_TRANSFER_STATE_COMPLETED)
464     ft_operation_close_clean (EMPATHY_TP_FILE (weak_object));
465
466   if (state == TP_FILE_TRANSFER_STATE_CANCELLED)
467     {
468       error = error_from_state_change_reason (priv->state_change_reason);
469       ft_operation_close_with_error (EMPATHY_TP_FILE (weak_object), error);
470       g_clear_error (&error);
471     }
472 }
473
474 static void
475 tp_file_transferred_bytes_changed_cb (TpChannel *proxy,
476     guint64 count,
477     gpointer user_data,
478     GObject *weak_object)
479 {
480   EmpathyTpFilePriv *priv = GET_PRIV (weak_object);
481
482   /* don't notify for 0 bytes count */
483   if (count == 0)
484     return;
485
486   /* notify clients */
487   if (priv->progress_callback != NULL)
488     priv->progress_callback (EMPATHY_TP_FILE (weak_object),
489         count, priv->progress_user_data);
490 }
491
492 static void
493 ft_operation_provide_or_accept_file_cb (TpChannel *proxy,
494     const GValue *address,
495     const GError *error,
496     gpointer user_data,
497     GObject *weak_object)
498 {
499   EmpathyTpFile *tp_file = EMPATHY_TP_FILE (weak_object);
500   GError *myerr = NULL;
501   EmpathyTpFilePriv *priv = GET_PRIV (tp_file);
502
503   g_cancellable_set_error_if_cancelled (priv->cancellable, &myerr);
504
505   if (error != NULL)
506     {
507       if (myerr != NULL)
508         {
509           /* if we were both cancelled and failed when calling the method,
510           * report the method error.
511           */
512           g_clear_error (&myerr);
513           myerr = g_error_copy (error);
514         }
515     }
516
517   if (myerr != NULL)
518     {
519       DEBUG ("Error: %s", error->message);
520       ft_operation_close_with_error (tp_file, myerr);
521       g_clear_error (&myerr);
522       return;
523     }
524
525   if (G_VALUE_TYPE (address) == DBUS_TYPE_G_UCHAR_ARRAY)
526     {
527       priv->socket_address = g_value_dup_boxed (address);
528     }
529   else if (G_VALUE_TYPE (address) == G_TYPE_STRING)
530     {
531       /* Old bugged version of telepathy-salut used to store the address
532        * as a 's' instead of an 'ay' */
533       const gchar *path;
534
535       path = g_value_get_string (address);
536       priv->socket_address = g_array_sized_new (TRUE, FALSE, sizeof (gchar),
537           strlen (path));
538       g_array_insert_vals (priv->socket_address, 0, path, strlen (path));
539     }
540   else if (G_VALUE_TYPE (address) == TP_STRUCT_TYPE_SOCKET_ADDRESS_IPV4)
541     {
542       GValueArray *val_array;
543       GValue *v;
544       const char *addr;
545
546       val_array = g_value_get_boxed (address);
547
548       /* IPV4 address */
549       v = g_value_array_get_nth (val_array, 0);
550       addr = g_value_get_string (v);
551       priv->socket_address = g_array_sized_new (TRUE, FALSE, sizeof (gchar),
552           strlen (addr));
553       g_array_insert_vals (priv->socket_address, 0, addr, strlen (addr));
554
555       /* port number */
556       v = g_value_array_get_nth (val_array, 1);
557       priv->port = g_value_get_uint (v);
558     }
559
560   DEBUG ("Got socket address: %s, port (not zero if IPV4): %d",
561       priv->socket_address->data, priv->port);
562
563   /* if the channel is already open, start the transfer now, otherwise,
564    * wait for the state change signal.
565    */
566   if (priv->state == TP_FILE_TRANSFER_STATE_OPEN)
567     tp_file_start_transfer (tp_file);
568 }
569
570 static void
571 initialize_empty_ac_variant (TpSocketAccessControl ac,
572     GValue *val)
573 {
574   if (ac == TP_SOCKET_ACCESS_CONTROL_LOCALHOST)
575     {
576       g_value_init (val, G_TYPE_STRING);
577       g_value_set_static_string (val, "");
578     }
579   else if (ac == TP_SOCKET_ACCESS_CONTROL_PORT)
580     {
581       g_value_init (val, TP_STRUCT_TYPE_SOCKET_ADDRESS_IPV4);
582     }
583 }
584
585 static void
586 file_read_async_cb (GObject *source,
587     GAsyncResult *res,
588     gpointer user_data)
589 {
590   GValue nothing = { 0 };
591   EmpathyTpFile *tp_file = user_data;
592   EmpathyTpFilePriv *priv;
593   GFileInputStream *in_stream;
594   GError *error = NULL;
595
596   priv = GET_PRIV (tp_file);
597
598   in_stream = g_file_read_finish (G_FILE (source), res, &error);
599
600   if (error != NULL)
601     {
602       ft_operation_close_with_error (tp_file, error);
603       g_clear_error (&error);
604       return;
605     }
606
607   priv->in_stream = G_INPUT_STREAM (in_stream);
608
609   /* we don't impose specific interface/port requirements even
610    * if we're not using UNIX sockets.
611    */
612   initialize_empty_ac_variant (priv->socket_access_control, &nothing);
613
614   tp_cli_channel_type_file_transfer_call_provide_file (
615       priv->channel, -1,
616       priv->socket_address_type, priv->socket_access_control,
617       &nothing, ft_operation_provide_or_accept_file_cb,
618       NULL, NULL, G_OBJECT (tp_file));
619 }
620
621 static void
622 file_replace_async_cb (GObject *source,
623     GAsyncResult *res,
624     gpointer user_data)
625 {
626   GValue nothing = { 0 };
627   EmpathyTpFile *tp_file = user_data;
628   EmpathyTpFilePriv *priv;
629   GError *error = NULL;
630   GFileOutputStream *out_stream;
631
632   priv = GET_PRIV (tp_file);
633
634   out_stream = g_file_replace_finish (G_FILE (source), res, &error);
635
636   if (error != NULL)
637     {
638       ft_operation_close_with_error (tp_file, error);
639       g_clear_error (&error);
640
641       return;
642     }
643
644   priv->out_stream = G_OUTPUT_STREAM (out_stream);
645
646   /* we don't impose specific interface/port requirements even
647    * if we're not using UNIX sockets.
648    */
649   initialize_empty_ac_variant (priv->socket_access_control, &nothing);
650
651   tp_cli_channel_type_file_transfer_call_accept_file (priv->channel,
652       -1, priv->socket_address_type, priv->socket_access_control,
653       &nothing, priv->offset,
654       ft_operation_provide_or_accept_file_cb, NULL, NULL, G_OBJECT (tp_file));
655 }
656
657 static void
658 channel_closed_cb (TpChannel *proxy,
659     const GError *error,
660     gpointer user_data,
661     GObject *weak_object)
662 {
663   EmpathyTpFile *tp_file = EMPATHY_TP_FILE (weak_object);
664   EmpathyTpFilePriv *priv = GET_PRIV (tp_file);
665   gboolean cancel = GPOINTER_TO_INT (user_data);
666
667   DEBUG ("Channel is closed, should cancel %s", cancel ? "True" : "False");
668
669   if (priv->cancellable != NULL &&
670       !g_cancellable_is_cancelled (priv->cancellable) && cancel)
671     g_cancellable_cancel (priv->cancellable);
672 }
673
674 static void
675 close_channel_internal (EmpathyTpFile *tp_file,
676     gboolean cancel)
677 {
678   EmpathyTpFilePriv *priv = GET_PRIV (tp_file);
679
680   DEBUG ("Closing channel, should cancel %s", cancel ?
681          "True" : "False");
682
683   tp_cli_channel_call_close (priv->channel, -1,
684     channel_closed_cb, GINT_TO_POINTER (cancel), NULL, G_OBJECT (tp_file));
685 }
686
687 /* GObject methods */
688
689 static void
690 empathy_tp_file_init (EmpathyTpFile *tp_file)
691 {
692   EmpathyTpFilePriv *priv;
693
694   priv = G_TYPE_INSTANCE_GET_PRIVATE ((tp_file),
695       EMPATHY_TYPE_TP_FILE, EmpathyTpFilePriv);
696
697   tp_file->priv = priv;
698 }
699
700 static void
701 do_dispose (GObject *object)
702 {
703   EmpathyTpFilePriv *priv = GET_PRIV (object);
704
705   if (priv->dispose_run)
706     return;
707
708   priv->dispose_run = TRUE;
709
710   if (priv->channel != NULL)
711     {
712       g_signal_handlers_disconnect_by_func (priv->channel,
713           tp_file_invalidated_cb, object);
714       g_object_unref (priv->channel);
715       priv->channel = NULL;
716     }
717
718   if (priv->in_stream != NULL)
719     g_object_unref (priv->in_stream);
720
721   if (priv->out_stream != NULL)
722     g_object_unref (priv->out_stream);
723
724   if (priv->cancellable != NULL)
725     g_object_unref (priv->cancellable);
726
727   G_OBJECT_CLASS (empathy_tp_file_parent_class)->dispose (object);
728 }
729
730 static void
731 do_finalize (GObject *object)
732 {
733   EmpathyTpFilePriv *priv = GET_PRIV (object);
734
735   DEBUG ("%p", object);
736
737   if (priv->socket_address != NULL)
738     {
739       g_array_free (priv->socket_address, TRUE);
740       priv->socket_address = NULL;
741     }
742
743   G_OBJECT_CLASS (empathy_tp_file_parent_class)->finalize (object);
744 }
745
746 static void
747 do_get_property (GObject *object,
748     guint param_id,
749     GValue *value,
750     GParamSpec *pspec)
751 {
752   EmpathyTpFilePriv *priv = GET_PRIV (object);
753
754   switch (param_id)
755     {
756       case PROP_CHANNEL:
757         g_value_set_object (value, priv->channel);
758         break;
759       case PROP_INCOMING:
760         g_value_set_boolean (value, priv->incoming);
761         break;
762       default:
763         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
764         break;
765     };
766 }
767
768 static void
769 do_set_property (GObject *object,
770     guint param_id,
771     const GValue *value,
772     GParamSpec *pspec)
773 {
774   EmpathyTpFilePriv *priv = GET_PRIV (object);
775   switch (param_id)
776     {
777       case PROP_CHANNEL:
778         priv->channel = g_object_ref (g_value_get_object (value));
779         break;
780       case PROP_INCOMING:
781         priv->incoming = g_value_get_boolean (value);
782         break;
783       default:
784         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
785         break;
786     };
787 }
788
789 static void
790 do_constructed (GObject *object)
791 {
792   EmpathyTpFile *tp_file;
793   EmpathyTpFilePriv *priv;
794
795   tp_file = EMPATHY_TP_FILE (object);
796   priv = GET_PRIV (tp_file);
797
798   g_signal_connect (priv->channel, "invalidated",
799     G_CALLBACK (tp_file_invalidated_cb), tp_file);
800
801   tp_cli_channel_type_file_transfer_connect_to_file_transfer_state_changed (
802       priv->channel, tp_file_state_changed_cb, NULL, NULL, object, NULL);
803
804   tp_cli_channel_type_file_transfer_connect_to_transferred_bytes_changed (
805       priv->channel, tp_file_transferred_bytes_changed_cb,
806       NULL, NULL, object, NULL);
807
808   tp_cli_dbus_properties_call_get (priv->channel,
809       -1, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, "State", tp_file_get_state_cb,
810       NULL, NULL, object);
811
812   tp_cli_dbus_properties_call_get (priv->channel,
813       -1, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, "AvailableSocketTypes",
814       tp_file_get_available_socket_types_cb, NULL, NULL, object);
815
816   priv->state_change_reason =
817       TP_FILE_TRANSFER_STATE_CHANGE_REASON_NONE;
818 }
819
820 static void
821 empathy_tp_file_class_init (EmpathyTpFileClass *klass)
822 {
823   GObjectClass *object_class = G_OBJECT_CLASS (klass);
824
825   object_class->finalize = do_finalize;
826   object_class->dispose = do_dispose;
827   object_class->constructed = do_constructed;
828   object_class->get_property = do_get_property;
829   object_class->set_property = do_set_property;
830
831   /* Construct-only properties */
832
833   /**
834    * EmpathyTpFile:channel:
835    *
836    * The #TpChannel requested for the file transfer.
837    */
838   g_object_class_install_property (object_class,
839       PROP_CHANNEL,
840       g_param_spec_object ("channel",
841           "telepathy channel",
842           "The file transfer channel",
843           TP_TYPE_CHANNEL,
844           G_PARAM_READWRITE |
845           G_PARAM_CONSTRUCT_ONLY));
846
847   /**
848    * EmpathyTpFile:incoming:
849    *
850    * %TRUE if the transfer is incoming, %FALSE if it's outgoing.
851    */
852   g_object_class_install_property (object_class,
853       PROP_INCOMING,
854       g_param_spec_boolean ("incoming",
855           "direction of transfer",
856           "The direction of the file being transferred",
857           FALSE,
858           G_PARAM_READWRITE |
859           G_PARAM_CONSTRUCT_ONLY));
860
861   g_type_class_add_private (object_class, sizeof (EmpathyTpFilePriv));
862 }
863
864 /* public methods */
865
866 /**
867  * empathy_tp_file_new:
868  * @channel: a #TpChannel
869  * @incoming: whether the file transfer is incoming or not
870  *
871  * Creates a new #EmpathyTpFile wrapping @channel, with the direction
872  * specified by @incoming. The returned #EmpathyTpFile should be unrefed
873  * with g_object_unref() when finished with.
874  *
875  * Return value: a new #EmpathyTpFile
876  */
877 EmpathyTpFile *
878 empathy_tp_file_new (TpChannel *channel,
879     gboolean incoming)
880 {
881   EmpathyTpFile *tp_file;
882
883   g_return_val_if_fail (TP_IS_CHANNEL (channel), NULL);
884
885   tp_file = g_object_new (EMPATHY_TYPE_TP_FILE,
886       "channel", channel, "incoming", incoming,
887       NULL);
888
889   return tp_file;
890 }
891
892 /**
893  * empathy_tp_file_accept:
894  * @tp_file: an incoming #EmpathyTpFile
895  * @offset: the offset of @gfile where we should start writing
896  * @gfile: the destination #GFile for the transfer
897  * @cancellable: a #GCancellable
898  * @progress_callback: function to callback with progress information
899  * @progress_user_data: user_data to pass to @progress_callback
900  * @op_callback: function to callback when the transfer ends
901  * @op_user_data: user_data to pass to @op_callback
902  *
903  * Accepts an incoming file transfer, saving the result into @gfile.
904  * The callback @op_callback will be called both when the transfer is
905  * successful and in case of an error. Note that cancelling @cancellable,
906  * closes the socket of the file operation in progress, but doesn't
907  * guarantee that the transfer channel will be closed as well. Thus,
908  * empathy_tp_file_cancel() or empathy_tp_file_close() should be used to
909  * actually cancel an ongoing #EmpathyTpFile.
910  */
911 void
912 empathy_tp_file_accept (EmpathyTpFile *tp_file,
913     guint64 offset,
914     GFile *gfile,
915     GCancellable *cancellable,
916     EmpathyTpFileProgressCallback progress_callback,
917     gpointer progress_user_data,
918     EmpathyTpFileOperationCallback op_callback,
919     gpointer op_user_data)
920 {
921   EmpathyTpFilePriv *priv = GET_PRIV (tp_file);
922
923   g_return_if_fail (EMPATHY_IS_TP_FILE (tp_file));
924   g_return_if_fail (G_IS_FILE (gfile));
925   g_return_if_fail (G_IS_CANCELLABLE (cancellable));
926
927   priv->cancellable = g_object_ref (cancellable);
928   priv->progress_callback = progress_callback;
929   priv->progress_user_data = progress_user_data;
930   priv->op_callback = op_callback;
931   priv->op_user_data = op_user_data;
932   priv->offset = offset;
933
934   g_file_replace_async (gfile, NULL, FALSE, G_FILE_CREATE_NONE,
935       G_PRIORITY_DEFAULT, cancellable, file_replace_async_cb, tp_file);
936 }
937
938
939 /**
940  * empathy_tp_file_offer:
941  * @tp_file: an outgoing #EmpathyTpFile
942  * @gfile: the source #GFile for the transfer
943  * @cancellable: a #GCancellable
944  * @progress_callback: function to callback with progress information
945  * @progress_user_data: user_data to pass to @progress_callback
946  * @op_callback: function to callback when the transfer ends
947  * @op_user_data: user_data to pass to @op_callback
948  *
949  * Offers an outgoing file transfer, reading data from @gfile.
950  * The callback @op_callback will be called both when the transfer is
951  * successful and in case of an error. Note that cancelling @cancellable,
952  * closes the socket of the file operation in progress, but doesn't
953  * guarantee that the transfer channel will be closed as well. Thus,
954  * empathy_tp_file_cancel() or empathy_tp_file_close() should be used to
955  * actually cancel an ongoing #EmpathyTpFile.
956  */
957 void
958 empathy_tp_file_offer (EmpathyTpFile *tp_file,
959     GFile *gfile,
960     GCancellable *cancellable,
961     EmpathyTpFileProgressCallback progress_callback,
962     gpointer progress_user_data,
963     EmpathyTpFileOperationCallback op_callback,
964     gpointer op_user_data)
965 {
966   EmpathyTpFilePriv *priv = GET_PRIV (tp_file);
967
968   g_return_if_fail (EMPATHY_IS_TP_FILE (tp_file));
969   g_return_if_fail (G_IS_FILE (gfile));
970   g_return_if_fail (G_IS_CANCELLABLE (cancellable));
971
972   priv->cancellable = g_object_ref (cancellable);
973   priv->progress_callback = progress_callback;
974   priv->progress_user_data = progress_user_data;
975   priv->op_callback = op_callback;
976   priv->op_user_data = op_user_data;
977
978   g_file_read_async (gfile, G_PRIORITY_DEFAULT, cancellable,
979       file_read_async_cb, tp_file);
980 }
981
982 /**
983  * empathy_tp_file_is_incoming:
984  * @tp_file: an #EmpathyTpFile
985  *
986  * Returns whether @tp_file is incoming.
987  *
988  * Return value: %TRUE if the @tp_file is incoming, otherwise %FALSE
989  */
990 gboolean
991 empathy_tp_file_is_incoming (EmpathyTpFile *tp_file)
992 {
993   EmpathyTpFilePriv *priv;
994
995   g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file), FALSE);
996
997   priv = GET_PRIV (tp_file);
998
999   return priv->incoming;
1000 }
1001
1002 /**
1003  * empathy_tp_file_cancel:
1004  * @tp_file: an #EmpathyTpFile
1005  *
1006  * Cancels an ongoing #EmpathyTpFile, first closing the channel and then
1007  * cancelling any I/O operation and closing the socket.
1008  */
1009 void
1010 empathy_tp_file_cancel (EmpathyTpFile *tp_file)
1011 {
1012   g_return_if_fail (EMPATHY_IS_TP_FILE (tp_file));
1013
1014   close_channel_internal (tp_file, TRUE);
1015 }
1016
1017 /**
1018  * empathy_tp_file_close:
1019  * @tp_file: an #EmpathyTpFile
1020  *
1021  * Closes the channel for an ongoing #EmpathyTpFile. It's safe to call this
1022  * method after the transfer has ended.
1023  */
1024 void
1025 empathy_tp_file_close (EmpathyTpFile *tp_file)
1026 {
1027   g_return_if_fail (EMPATHY_IS_TP_FILE (tp_file));
1028
1029   close_channel_internal (tp_file, FALSE);
1030 }