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