]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tp-file.c
Use values from TpFileTransferState
[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 <sys/types.h>
30 #include <sys/socket.h>
31 #include <sys/un.h>
32
33 #include <glib/gi18n-lib.h>
34
35 #include <gio/gio.h>
36 #include <gio/gunixinputstream.h>
37 #include <gio/gunixoutputstream.h>
38
39 #include <telepathy-glib/proxy-subclass.h>
40 #include <telepathy-glib/util.h>
41
42 #include "empathy-tp-file.h"
43 #include "empathy-marshal.h"
44 #include "empathy-time.h"
45 #include "empathy-utils.h"
46
47 #include "extensions/extensions.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  */
60
61 /**
62  * EmpathyTpFile:
63  * @parent: parent object
64  *
65  * Object which represents a Telepathy file channel.
66  */
67
68 /**
69  * EMPATHY_TP_FILE_UNKNOWN_SIZE:
70  *
71  * Value used for the "size" or "estimated-size" properties when the size of
72  * the transferred file is unknown.
73  */
74
75 /* EmpathyTpFile object */
76
77 typedef struct {
78   TpChannel *channel;
79   gboolean ready;
80
81   GInputStream *in_stream;
82   GOutputStream *out_stream;
83
84   /* org.freedesktop.Telepathy.Channel.Type.FileTransfer D-Bus properties */
85   EmpFileTransferState state;
86   EmpFileTransferStateChangeReason state_change_reason;
87
88   /* transfer properties */
89   gboolean incoming;
90   time_t start_time;
91   GArray *unix_socket_path;
92   guint64 offset;
93
94   /* GCancellable we're passed when offering/accepting the transfer */
95   GCancellable *cancellable;
96
97   /* callbacks for the operation */
98   EmpathyTpFileProgressCallback progress_callback;
99   gpointer progress_user_data;
100   EmpathyTpFileOperationCallback op_callback;
101   gpointer op_user_data;
102
103   gboolean dispose_run;
104 } EmpathyTpFilePriv;
105
106 enum {
107   PROP_0,
108   PROP_CHANNEL,
109   PROP_INCOMING,
110   PROP_STATE
111 };
112
113 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyTpFile)
114
115 G_DEFINE_TYPE (EmpathyTpFile, empathy_tp_file, G_TYPE_OBJECT);
116
117 /* private functions */
118
119 static void
120 tp_file_get_state_cb (TpProxy *proxy,
121                       const GValue *value,
122                       const GError *error,
123                       gpointer user_data,
124                       GObject *weak_object)
125 {
126   EmpathyTpFilePriv *priv = GET_PRIV (weak_object);
127
128   if (error)
129     {
130       /* set a default value for the state */
131       priv->state = TP_FILE_TRANSFER_STATE_NONE;
132       return;
133     }
134
135   priv->state = g_value_get_uint (value);
136 }
137
138 static void
139 tp_file_invalidated_cb (TpProxy       *proxy,
140                         guint          domain,
141                         gint           code,
142                         gchar         *message,
143                         EmpathyTpFile *tp_file)
144 {
145   EmpathyTpFilePriv *priv = GET_PRIV (tp_file);
146
147   DEBUG ("Channel invalidated: %s", message);
148
149   if (priv->state != TP_FILE_TRANSFER_STATE_COMPLETED &&
150       priv->state != TP_FILE_TRANSFER_STATE_CANCELLED)
151     {
152       /* The channel is not in a finished state, an error occured */
153       priv->state = TP_FILE_TRANSFER_STATE_CANCELLED;
154       priv->state_change_reason =
155           TP_FILE_TRANSFER_STATE_CHANGE_REASON_LOCAL_ERROR;
156       g_object_notify (G_OBJECT (tp_file), "state");
157     }
158 }
159
160 static void
161 ft_operation_close_clean (EmpathyTpFile *tp_file)
162 {
163   EmpathyTpFilePriv *priv = GET_PRIV (tp_file);
164
165   DEBUG ("Splice close clean");
166
167   if (priv->op_callback)
168     priv->op_callback (tp_file, NULL, priv->op_user_data);
169 }
170
171 static void
172 ft_operation_close_with_error (EmpathyTpFile *tp_file,
173                                GError *error)
174 {
175   EmpathyTpFilePriv *priv = GET_PRIV (tp_file);
176
177   DEBUG ("Splice close with error %s", error->message);
178
179   if (priv->op_callback)
180     priv->op_callback (tp_file, error, priv->op_user_data);
181 }
182
183 static void
184 splice_stream_ready_cb (GObject *source,
185                         GAsyncResult *res,
186                         gpointer user_data)
187 {
188   EmpathyTpFile *tp_file;
189   GError *error = NULL;
190
191   tp_file = user_data;
192
193   g_output_stream_splice_finish (G_OUTPUT_STREAM (source), res, &error);
194
195   if (error != NULL)
196     {
197       ft_operation_close_with_error (tp_file, error);
198       g_clear_error (&error);
199       return;
200     }
201 }
202
203 static void
204 tp_file_start_transfer (EmpathyTpFile *tp_file)
205 {
206   gint fd;
207   struct sockaddr_un addr;
208   GError *error = NULL;
209   EmpathyTpFilePriv *priv = GET_PRIV (tp_file);
210
211   fd = socket (PF_UNIX, SOCK_STREAM, 0);
212   if (fd < 0)
213     {
214       int code = errno;
215
216       error = g_error_new_literal (EMPATHY_FT_ERROR_QUARK,
217           EMPATHY_FT_ERROR_SOCKET, g_strerror (code));
218
219       DEBUG ("Failed to create socket, closing channel");
220
221       ft_operation_close_with_error (tp_file, error);
222       g_clear_error (&error);
223
224       return;
225     }
226
227   memset (&addr, 0, sizeof (addr));
228   addr.sun_family = AF_UNIX;
229   strncpy (addr.sun_path, priv->unix_socket_path->data,
230       priv->unix_socket_path->len);
231
232   if (connect (fd, (struct sockaddr*) &addr, sizeof (addr)) < 0)
233     {
234       int code = errno;
235
236       error = g_error_new_literal (EMPATHY_FT_ERROR_QUARK,
237           EMPATHY_FT_ERROR_SOCKET, g_strerror (code));
238
239       DEBUG ("Failed to connect socket, closing channel");
240
241       ft_operation_close_with_error (tp_file, error);
242       close (fd);
243       g_clear_error (&error);
244
245       return;
246     }
247
248   DEBUG ("Start the transfer");
249
250   priv->start_time = empathy_time_get_current ();
251
252   /* notify we're starting a transfer */
253   if (priv->progress_callback)
254     priv->progress_callback (tp_file, 0, priv->progress_user_data);
255
256   if (priv->incoming)
257     {
258       GInputStream *socket_stream;
259
260       socket_stream = g_unix_input_stream_new (fd, TRUE);
261
262       g_output_stream_splice_async (priv->out_stream, socket_stream,
263           G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
264           G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
265           G_PRIORITY_DEFAULT, priv->cancellable,
266           splice_stream_ready_cb, tp_file);
267
268       g_object_unref (socket_stream);
269     }
270   else
271     {
272       GOutputStream *socket_stream;
273
274       socket_stream = g_unix_output_stream_new (fd, TRUE);
275
276       g_output_stream_splice_async (socket_stream, priv->in_stream,
277           G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
278           G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
279           G_PRIORITY_DEFAULT, priv->cancellable,
280           splice_stream_ready_cb, tp_file);
281
282       g_object_unref (socket_stream);
283     }
284 }
285
286 static void
287 tp_file_state_changed_cb (TpChannel *proxy,
288                           guint state,
289                           guint reason,
290                           gpointer user_data,
291                           GObject *weak_object)
292 {
293   EmpathyTpFilePriv *priv = GET_PRIV (weak_object);
294
295   if (state == priv->state)
296     return;
297
298   DEBUG ("File transfer state changed:\n"
299       "old state = %u, state = %u, reason = %u\n"
300       "\tincoming = %s, in_stream = %s, out_stream = %s",
301       priv->state, state, reason,
302       priv->incoming ? "yes" : "no",
303       priv->in_stream ? "present" : "not present",
304       priv->out_stream ? "present" : "not present");
305
306   priv->state = state;
307   priv->state_change_reason = reason;
308
309   /* If the channel is open AND we have the socket path, we can start the
310    * transfer. The socket path could be NULL if we are not doing the actual
311    * data transfer but are just an observer for the channel.
312    */
313   if (state == TP_FILE_TRANSFER_STATE_OPEN &&
314       priv->unix_socket_path != NULL)
315     tp_file_start_transfer (EMPATHY_TP_FILE (weak_object));
316
317   if (state == TP_FILE_TRANSFER_STATE_COMPLETED)
318     ft_operation_close_clean (EMPATHY_TP_FILE (weak_object));
319
320   g_object_notify (weak_object, "state");
321 }
322
323 static void
324 tp_file_transferred_bytes_changed_cb (TpChannel *proxy,
325                                       guint64 count,
326                                       gpointer user_data,
327                                       GObject *weak_object)
328 {
329   EmpathyTpFilePriv *priv = GET_PRIV (weak_object);
330
331   /* notify clients */
332   if (priv->progress_callback)
333     priv->progress_callback (EMPATHY_TP_FILE (weak_object),
334         count, priv->progress_user_data);
335 }
336
337 static void
338 ft_operation_provide_or_accept_file_cb (TpChannel *proxy,
339                                         const GValue *address,
340                                         const GError *error,
341                                         gpointer user_data,
342                                         GObject *weak_object)
343 {
344   EmpathyTpFile *tp_file = EMPATHY_TP_FILE (weak_object);
345   GError *myerr = NULL;
346   EmpathyTpFilePriv *priv = GET_PRIV (tp_file);
347
348   g_cancellable_set_error_if_cancelled (priv->cancellable, &myerr);
349
350   if (error)
351     {
352       if (myerr)
353         {
354           /* if we were both cancelled and failed when calling the method,
355           * report the method error.
356           */
357           g_clear_error (&myerr);
358           myerr = g_error_copy (error);
359         }
360     }
361
362   if (myerr)
363     {
364       DEBUG ("Error: %s", error->message);
365       ft_operation_close_with_error (tp_file, myerr);
366       g_clear_error (&myerr);
367       return;
368     }
369
370   if (G_VALUE_TYPE (address) == DBUS_TYPE_G_UCHAR_ARRAY)
371     {
372       priv->unix_socket_path = g_value_dup_boxed (address);
373     }
374   else if (G_VALUE_TYPE (address) == G_TYPE_STRING)
375     {
376       /* Old bugged version of telepathy-salut used to store the address
377        * as a 's' instead of an 'ay' */
378       const gchar *path;
379
380       path = g_value_get_string (address);
381       priv->unix_socket_path = g_array_sized_new (TRUE, FALSE, sizeof (gchar),
382                                                   strlen (path));
383       g_array_insert_vals (priv->unix_socket_path, 0, path, strlen (path));
384     }
385
386   DEBUG ("Got unix socket path: %s", priv->unix_socket_path->data);
387
388   /* if the channel is already open, start the transfer now, otherwise,
389    * wait for the state change signal.
390    */
391   if (priv->state == TP_FILE_TRANSFER_STATE_OPEN)
392     tp_file_start_transfer (tp_file);
393 }
394
395 static void
396 file_read_async_cb (GObject *source,
397                     GAsyncResult *res,
398                     gpointer user_data)
399 {
400   GValue nothing = { 0 };
401   EmpathyTpFile *tp_file = user_data;
402   EmpathyTpFilePriv *priv;
403   GFileInputStream *in_stream;
404   GError *error = NULL;
405
406   priv = GET_PRIV (tp_file);
407
408   in_stream = g_file_read_finish (G_FILE (source), res, &error);
409
410   if (error != NULL)
411     {
412       ft_operation_close_with_error (tp_file, error);
413       g_clear_error (&error);
414       return;
415     }
416
417   priv->in_stream = G_INPUT_STREAM (in_stream);
418
419   g_value_init (&nothing, G_TYPE_STRING);
420   g_value_set_static_string (&nothing, "");
421
422   tp_cli_channel_type_file_transfer_call_provide_file (
423       priv->channel, -1,
424       TP_SOCKET_ADDRESS_TYPE_UNIX, TP_SOCKET_ACCESS_CONTROL_LOCALHOST,
425       &nothing, ft_operation_provide_or_accept_file_cb, NULL, NULL, G_OBJECT (tp_file));
426 }
427
428 static void
429 file_replace_async_cb (GObject *source,
430                        GAsyncResult *res,
431                        gpointer user_data)
432 {
433   GValue nothing = { 0 };
434   EmpathyTpFile *tp_file = user_data;
435   EmpathyTpFilePriv *priv;
436   GError *error = NULL;
437   GFileOutputStream *out_stream;
438
439   priv = GET_PRIV (tp_file);
440
441   out_stream = g_file_replace_finish (G_FILE (source), res, &error);
442
443   if (error != NULL)
444     {
445       ft_operation_close_with_error (tp_file, error);
446       g_clear_error (&error);
447
448       return;
449     }
450
451   priv->out_stream = G_OUTPUT_STREAM (out_stream);
452
453   g_value_init (&nothing, G_TYPE_STRING);
454   g_value_set_static_string (&nothing, "");
455
456   tp_cli_channel_type_file_transfer_call_accept_file (priv->channel,
457       -1, TP_SOCKET_ADDRESS_TYPE_UNIX, TP_SOCKET_ACCESS_CONTROL_LOCALHOST,
458       &nothing, priv->offset,
459       ft_operation_provide_or_accept_file_cb, NULL, NULL, G_OBJECT (tp_file));
460 }
461
462 /* GObject methods */
463
464 static void
465 empathy_tp_file_init (EmpathyTpFile *tp_file)
466 {
467   EmpathyTpFilePriv *priv;
468
469   priv = G_TYPE_INSTANCE_GET_PRIVATE ((tp_file),
470       EMPATHY_TYPE_TP_FILE, EmpathyTpFilePriv);
471
472   tp_file->priv = priv;
473 }
474
475 static void
476 do_dispose (GObject *object)
477 {
478   EmpathyTpFilePriv *priv = GET_PRIV (object);
479
480   if (priv->dispose_run)
481     return;
482
483   priv->dispose_run = TRUE;
484
485   if (priv->channel)
486     {
487       g_signal_handlers_disconnect_by_func (priv->channel,
488           tp_file_invalidated_cb, object);
489       g_object_unref (priv->channel);
490       priv->channel = NULL;
491     }
492
493   if (priv->in_stream)
494     g_object_unref (priv->in_stream);
495
496   if (priv->out_stream)
497     g_object_unref (priv->out_stream);
498
499   if (priv->cancellable)
500     g_object_unref (priv->cancellable);
501
502   G_OBJECT_CLASS (empathy_tp_file_parent_class)->dispose (object);
503 }
504
505 static void
506 do_finalize (GObject *object)
507 {
508   EmpathyTpFilePriv *priv = GET_PRIV (object);
509
510   g_free (priv->unix_socket_path);
511
512   G_OBJECT_CLASS (empathy_tp_file_parent_class)->finalize (object);
513 }
514
515 static void
516 do_get_property (GObject *object,
517                  guint param_id,
518                  GValue *value,
519                  GParamSpec *pspec)
520 {
521   EmpathyTpFilePriv *priv = GET_PRIV (object);
522
523   switch (param_id)
524     {
525       case PROP_CHANNEL:
526         g_value_set_object (value, priv->channel);
527         break;
528       case PROP_INCOMING:
529         g_value_set_boolean (value, priv->incoming);
530         break;
531       case PROP_STATE:
532         g_value_set_uint (value, priv->state);
533         break;
534       default:
535         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
536         break;
537     };
538 }
539
540 static void
541 do_set_property (GObject *object,
542                  guint param_id,
543                  const GValue *value,
544                  GParamSpec *pspec)
545 {
546   EmpathyTpFilePriv *priv = GET_PRIV (object);
547   switch (param_id)
548     {
549       case PROP_CHANNEL:
550         priv->channel = g_object_ref (g_value_get_object (value));
551         break;
552       case PROP_INCOMING:
553         priv->incoming = g_value_get_boolean (value);
554         break;
555       case PROP_STATE:
556         priv->state = g_value_get_uint (value);
557         break;
558       default:
559         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
560         break;
561     };
562 }
563
564 static GObject *
565 do_constructor (GType type,
566                 guint n_props,
567                 GObjectConstructParam *props)
568 {
569   GObject *file_obj;
570   EmpathyTpFile *tp_file;
571   EmpathyTpFilePriv *priv;
572
573   file_obj = G_OBJECT_CLASS (empathy_tp_file_parent_class)->constructor (type,
574       n_props, props);
575   
576   tp_file = EMPATHY_TP_FILE (file_obj);
577   priv = GET_PRIV (tp_file);
578
579   g_signal_connect (priv->channel, "invalidated",
580     G_CALLBACK (tp_file_invalidated_cb), tp_file);
581
582   tp_cli_channel_type_file_transfer_connect_to_file_transfer_state_changed (
583       priv->channel, tp_file_state_changed_cb, NULL, NULL,
584       G_OBJECT (tp_file), NULL);
585
586   tp_cli_channel_type_file_transfer_connect_to_transferred_bytes_changed (
587       priv->channel, tp_file_transferred_bytes_changed_cb,
588       NULL, NULL, G_OBJECT (tp_file), NULL);
589
590   tp_cli_dbus_properties_call_get (priv->channel,
591       -1, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, "State", tp_file_get_state_cb,
592       NULL, NULL, file_obj);
593
594   priv->state_change_reason =
595       TP_FILE_TRANSFER_STATE_CHANGE_REASON_NONE;
596
597   return file_obj;
598 }
599
600 static void
601 empathy_tp_file_class_init (EmpathyTpFileClass *klass)
602 {
603   GObjectClass *object_class = G_OBJECT_CLASS (klass);
604
605   object_class->finalize = do_finalize;
606   object_class->dispose = do_dispose;
607   object_class->constructor = do_constructor;
608   object_class->get_property = do_get_property;
609   object_class->set_property = do_set_property;
610
611   /* Construct-only properties */
612   g_object_class_install_property (object_class,
613       PROP_CHANNEL,
614       g_param_spec_object ("channel",
615           "telepathy channel",
616           "The file transfer channel",
617           TP_TYPE_CHANNEL,
618           G_PARAM_READWRITE |
619           G_PARAM_CONSTRUCT_ONLY));
620
621   g_object_class_install_property (object_class,
622       PROP_INCOMING,
623       g_param_spec_boolean ("incoming",
624           "direction of transfer",
625           "The direction of the file being transferred",
626           FALSE,
627           G_PARAM_READWRITE |
628           G_PARAM_CONSTRUCT_ONLY));
629
630   g_object_class_install_property (object_class,
631       PROP_STATE,
632       g_param_spec_uint ("state",
633           "state of the transfer",
634           "The file transfer state",
635           0,
636           G_MAXUINT,
637           G_MAXUINT,
638           G_PARAM_READWRITE |
639           G_PARAM_CONSTRUCT));
640
641   g_type_class_add_private (object_class, sizeof (EmpathyTpFilePriv));
642 }
643
644 /* public methods */
645
646 /**
647  * empathy_tp_file_new:
648  * @channel: a #TpChannel
649  *
650  * Creates a new #EmpathyTpFile wrapping @channel, or return a new ref to an
651  * existing #EmpathyTpFile for that channel. The returned #EmpathyTpFile
652  * should be unrefed with g_object_unref() when finished with.
653  *
654  * Return value: a new #EmpathyTpFile
655  */
656 EmpathyTpFile *
657 empathy_tp_file_new (TpChannel *channel, gboolean incoming)
658 {
659   EmpathyTpFile *tp_file;
660
661   g_return_val_if_fail (TP_IS_CHANNEL (channel), NULL);
662
663   tp_file = g_object_new (EMPATHY_TYPE_TP_FILE,
664       "channel", channel, "incoming", incoming,
665       NULL);
666
667   return tp_file;
668 }
669
670 void
671 empathy_tp_file_accept (EmpathyTpFile *tp_file,
672                         guint64 offset,
673                         GFile *gfile,
674                         GCancellable *cancellable,
675                         EmpathyTpFileProgressCallback progress_callback,
676                         gpointer progress_user_data,
677                         EmpathyTpFileOperationCallback op_callback,
678                         gpointer op_user_data)
679 {
680   EmpathyTpFilePriv *priv = GET_PRIV (tp_file);
681
682   g_return_if_fail (EMPATHY_IS_TP_FILE (tp_file));
683   g_return_if_fail (G_IS_FILE (gfile));
684   g_return_if_fail (G_IS_CANCELLABLE (cancellable));
685
686   priv->cancellable = g_object_ref (cancellable);
687   priv->progress_callback = progress_callback;
688   priv->progress_user_data = progress_user_data;
689   priv->op_callback = op_callback;
690   priv->op_user_data = op_user_data;
691   priv->offset = offset;
692
693   g_file_replace_async (gfile, NULL, FALSE, G_FILE_CREATE_NONE,
694       G_PRIORITY_DEFAULT, cancellable, file_replace_async_cb, tp_file);
695 }
696
697 void
698 empathy_tp_file_offer (EmpathyTpFile *tp_file,
699                        GFile *gfile,
700                        GCancellable *cancellable,
701                        EmpathyTpFileProgressCallback progress_callback,
702                        gpointer progress_user_data,
703                        EmpathyTpFileOperationCallback op_callback,
704                        gpointer op_user_data)
705 {
706   EmpathyTpFilePriv *priv = GET_PRIV (tp_file);
707
708   g_return_if_fail (EMPATHY_IS_TP_FILE (tp_file));
709   g_return_if_fail (G_IS_FILE (gfile));
710   g_return_if_fail (G_IS_CANCELLABLE (cancellable));
711
712   priv->cancellable = g_object_ref (cancellable);
713   priv->progress_callback = progress_callback;
714   priv->progress_user_data = progress_user_data;
715   priv->op_callback = op_callback;
716   priv->op_user_data = op_user_data;
717
718   g_file_read_async (gfile, G_PRIORITY_DEFAULT, cancellable,
719       file_read_async_cb, tp_file);
720 }
721
722 /**
723  * empathy_tp_file_is_incoming:
724  * @tp_file: an #EmpathyTpFile
725  *
726  * Returns whether @tp_file is incoming.
727  *
728  * Return value: %TRUE if the @tp_file is incoming, otherwise %FALSE
729  */
730 gboolean
731 empathy_tp_file_is_incoming (EmpathyTpFile *tp_file)
732 {
733   EmpathyTpFilePriv *priv;
734
735   g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file), FALSE);
736   
737   priv = GET_PRIV (tp_file);
738
739   return priv->incoming;
740 }
741
742 /**
743  * empathy_tp_file_get_state:
744  * @tp_file: an #EmpathyTpFile
745  * @reason: return location for state change reason, or %NULL
746  *
747  * Gets the current state of @tp_file. If @reason is not %NULL, then
748  * it is set to the reason of the last state change.
749  *
750  * Return value: a #TpFileTransferState
751  */
752 TpFileTransferState
753 empathy_tp_file_get_state (EmpathyTpFile *tp_file,
754                            TpFileTransferStateChangeReason *reason)
755 {
756   EmpathyTpFilePriv *priv = GET_PRIV (tp_file);
757
758   g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file),
759       TP_FILE_TRANSFER_STATE_NONE);
760
761   if (reason != NULL)
762     *reason = priv->state_change_reason;
763
764   return priv->state;
765 }
766
767 /**
768  * empathy_tp_file_cancel:
769  * @tp_file: an #EmpathyTpFile
770  *
771  * Cancels the file transfer, @tp_file.
772  */
773 void
774 empathy_tp_file_cancel (EmpathyTpFile *tp_file)
775 {
776   EmpathyTpFilePriv *priv;
777
778   g_return_if_fail (EMPATHY_IS_TP_FILE (tp_file));
779   
780   priv = GET_PRIV (tp_file);
781
782   DEBUG ("Closing channel..");
783   tp_cli_channel_call_close (priv->channel, -1,
784     NULL, NULL, NULL, NULL);
785
786   if (priv->cancellable != NULL)
787     g_cancellable_cancel (priv->cancellable);
788 }
789
790 /**
791  * empathy_tp_file_is_ready:
792  * @tp_file: an #EmpathyTpFile
793  *
794  * Returns whether the file channel @tp_file is ready for use.
795  *
796  * @tp_file is classed as ready if its state is no longer
797  * %TP_FILE_TRANSFER_STATE_NONE, or if details about the remote
798  * contact have been fully received.
799  *
800  * Return value: %TRUE if @tp_file is ready for use
801  */
802 gboolean
803 empathy_tp_file_is_ready (EmpathyTpFile *tp_file)
804 {
805   g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file), FALSE);
806
807   return tp_file->priv->ready;
808 }
809