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