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