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