]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tp-file.c
server-sasl-handler: stop using GET_PRIV
[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
791   tp_file = EMPATHY_TP_FILE (object);
792   priv = GET_PRIV (tp_file);
793
794   g_signal_connect (priv->channel, "invalidated",
795     G_CALLBACK (tp_file_invalidated_cb), tp_file);
796
797   priv->incoming = !tp_channel_get_requested (priv->channel);
798
799   tp_cli_channel_type_file_transfer_connect_to_file_transfer_state_changed (
800       priv->channel, tp_file_state_changed_cb, NULL, NULL, object, NULL);
801
802   tp_cli_channel_type_file_transfer_connect_to_transferred_bytes_changed (
803       priv->channel, tp_file_transferred_bytes_changed_cb,
804       NULL, NULL, object, NULL);
805
806   tp_cli_dbus_properties_call_get (priv->channel,
807       -1, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, "State", tp_file_get_state_cb,
808       NULL, NULL, object);
809
810   tp_cli_dbus_properties_call_get (priv->channel,
811       -1, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, "AvailableSocketTypes",
812       tp_file_get_available_socket_types_cb, NULL, NULL, object);
813
814   priv->state_change_reason =
815       TP_FILE_TRANSFER_STATE_CHANGE_REASON_NONE;
816 }
817
818 static void
819 empathy_tp_file_class_init (EmpathyTpFileClass *klass)
820 {
821   GObjectClass *object_class = G_OBJECT_CLASS (klass);
822
823   object_class->finalize = do_finalize;
824   object_class->dispose = do_dispose;
825   object_class->constructed = do_constructed;
826   object_class->get_property = do_get_property;
827   object_class->set_property = do_set_property;
828
829   /* Construct-only properties */
830
831   /**
832    * EmpathyTpFile:channel:
833    *
834    * The #TpChannel requested for the file transfer.
835    */
836   g_object_class_install_property (object_class,
837       PROP_CHANNEL,
838       g_param_spec_object ("channel",
839           "telepathy channel",
840           "The file transfer channel",
841           TP_TYPE_CHANNEL,
842           G_PARAM_READWRITE |
843           G_PARAM_CONSTRUCT_ONLY));
844
845   /**
846    * EmpathyTpFile:incoming:
847    *
848    * %TRUE if the transfer is incoming, %FALSE if it's outgoing.
849    */
850   g_object_class_install_property (object_class,
851       PROP_INCOMING,
852       g_param_spec_boolean ("incoming",
853           "direction of transfer",
854           "The direction of the file being transferred",
855           FALSE,
856           G_PARAM_READABLE));
857
858   g_type_class_add_private (object_class, sizeof (EmpathyTpFilePriv));
859 }
860
861 /* public methods */
862
863 /**
864  * empathy_tp_file_new:
865  * @channel: a #TpChannel
866  * @incoming: whether the file transfer is incoming or not
867  *
868  * Creates a new #EmpathyTpFile wrapping @channel.
869  * The returned #EmpathyTpFile should be unrefed
870  * with g_object_unref() when finished with.
871  *
872  * Return value: a new #EmpathyTpFile
873  */
874 EmpathyTpFile *
875 empathy_tp_file_new (TpChannel *channel)
876 {
877   EmpathyTpFile *tp_file;
878
879   g_return_val_if_fail (TP_IS_CHANNEL (channel), NULL);
880
881   tp_file = g_object_new (EMPATHY_TYPE_TP_FILE,
882       "channel", channel,
883       NULL);
884
885   return tp_file;
886 }
887
888 /**
889  * empathy_tp_file_accept:
890  * @tp_file: an incoming #EmpathyTpFile
891  * @offset: the offset of @gfile where we should start writing
892  * @gfile: the destination #GFile for the transfer
893  * @cancellable: a #GCancellable
894  * @progress_callback: function to callback with progress information
895  * @progress_user_data: user_data to pass to @progress_callback
896  * @op_callback: function to callback when the transfer ends
897  * @op_user_data: user_data to pass to @op_callback
898  *
899  * Accepts an incoming file transfer, saving the result into @gfile.
900  * The callback @op_callback will be called both when the transfer is
901  * successful and in case of an error. Note that cancelling @cancellable,
902  * closes the socket of the file operation in progress, but doesn't
903  * guarantee that the transfer channel will be closed as well. Thus,
904  * empathy_tp_file_cancel() or empathy_tp_file_close() should be used to
905  * actually cancel an ongoing #EmpathyTpFile.
906  */
907 void
908 empathy_tp_file_accept (EmpathyTpFile *tp_file,
909     guint64 offset,
910     GFile *gfile,
911     GCancellable *cancellable,
912     EmpathyTpFileProgressCallback progress_callback,
913     gpointer progress_user_data,
914     EmpathyTpFileOperationCallback op_callback,
915     gpointer op_user_data)
916 {
917   EmpathyTpFilePriv *priv = GET_PRIV (tp_file);
918
919   g_return_if_fail (EMPATHY_IS_TP_FILE (tp_file));
920   g_return_if_fail (G_IS_FILE (gfile));
921   g_return_if_fail (G_IS_CANCELLABLE (cancellable));
922
923   priv->cancellable = g_object_ref (cancellable);
924   priv->progress_callback = progress_callback;
925   priv->progress_user_data = progress_user_data;
926   priv->op_callback = op_callback;
927   priv->op_user_data = op_user_data;
928   priv->offset = offset;
929
930   g_file_replace_async (gfile, NULL, FALSE, G_FILE_CREATE_NONE,
931       G_PRIORITY_DEFAULT, cancellable, file_replace_async_cb, tp_file);
932 }
933
934
935 /**
936  * empathy_tp_file_offer:
937  * @tp_file: an outgoing #EmpathyTpFile
938  * @gfile: the source #GFile for the transfer
939  * @cancellable: a #GCancellable
940  * @progress_callback: function to callback with progress information
941  * @progress_user_data: user_data to pass to @progress_callback
942  * @op_callback: function to callback when the transfer ends
943  * @op_user_data: user_data to pass to @op_callback
944  *
945  * Offers an outgoing file transfer, reading data from @gfile.
946  * The callback @op_callback will be called both when the transfer is
947  * successful and in case of an error. Note that cancelling @cancellable,
948  * closes the socket of the file operation in progress, but doesn't
949  * guarantee that the transfer channel will be closed as well. Thus,
950  * empathy_tp_file_cancel() or empathy_tp_file_close() should be used to
951  * actually cancel an ongoing #EmpathyTpFile.
952  */
953 void
954 empathy_tp_file_offer (EmpathyTpFile *tp_file,
955     GFile *gfile,
956     GCancellable *cancellable,
957     EmpathyTpFileProgressCallback progress_callback,
958     gpointer progress_user_data,
959     EmpathyTpFileOperationCallback op_callback,
960     gpointer op_user_data)
961 {
962   EmpathyTpFilePriv *priv = GET_PRIV (tp_file);
963
964   g_return_if_fail (EMPATHY_IS_TP_FILE (tp_file));
965   g_return_if_fail (G_IS_FILE (gfile));
966   g_return_if_fail (G_IS_CANCELLABLE (cancellable));
967
968   priv->cancellable = g_object_ref (cancellable);
969   priv->progress_callback = progress_callback;
970   priv->progress_user_data = progress_user_data;
971   priv->op_callback = op_callback;
972   priv->op_user_data = op_user_data;
973
974   g_file_read_async (gfile, G_PRIORITY_DEFAULT, cancellable,
975       file_read_async_cb, tp_file);
976 }
977
978 /**
979  * empathy_tp_file_is_incoming:
980  * @tp_file: an #EmpathyTpFile
981  *
982  * Returns whether @tp_file is incoming.
983  *
984  * Return value: %TRUE if the @tp_file is incoming, otherwise %FALSE
985  */
986 gboolean
987 empathy_tp_file_is_incoming (EmpathyTpFile *tp_file)
988 {
989   EmpathyTpFilePriv *priv;
990
991   g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file), FALSE);
992
993   priv = GET_PRIV (tp_file);
994
995   return priv->incoming;
996 }
997
998 /**
999  * empathy_tp_file_cancel:
1000  * @tp_file: an #EmpathyTpFile
1001  *
1002  * Cancels an ongoing #EmpathyTpFile, first closing the channel and then
1003  * cancelling any I/O operation and closing the socket.
1004  */
1005 void
1006 empathy_tp_file_cancel (EmpathyTpFile *tp_file)
1007 {
1008   g_return_if_fail (EMPATHY_IS_TP_FILE (tp_file));
1009
1010   close_channel_internal (tp_file, TRUE);
1011 }
1012
1013 /**
1014  * empathy_tp_file_close:
1015  * @tp_file: an #EmpathyTpFile
1016  *
1017  * Closes the channel for an ongoing #EmpathyTpFile. It's safe to call this
1018  * method after the transfer has ended.
1019  */
1020 void
1021 empathy_tp_file_close (EmpathyTpFile *tp_file)
1022 {
1023   g_return_if_fail (EMPATHY_IS_TP_FILE (tp_file));
1024
1025   close_channel_internal (tp_file, FALSE);
1026 }