2 * Copyright (C) 2007-2009 Collabora Ltd.
3 * Copyright (C) 2007 Marco Barisione <marco@barisione.org>
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.
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.
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
19 * Authors: Marco Barisione <marco@barisione.org>
20 * Jonny Lamb <jonny.lamb@collabora.co.uk>
21 * Cosimo Cecchi <cosimo.cecchi@collabora.co.uk>
29 #include <arpa/inet.h>
30 #include <sys/types.h>
31 #include <sys/socket.h>
33 #include <netinet/in.h>
35 #include <glib/gi18n-lib.h>
38 #include <gio/gunixinputstream.h>
39 #include <gio/gunixoutputstream.h>
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>
46 #include "empathy-tp-file.h"
47 #include "empathy-time.h"
48 #include "empathy-utils.h"
50 #define DEBUG_FLAG EMPATHY_DEBUG_FT
51 #include "empathy-debug.h"
54 * SECTION:empathy-tp-file
55 * @title: EmpathyTpFile
56 * @short_description: Object which represents a Telepathy file channel
57 * @include: libempathy/empathy-tp-file.h
59 * #EmpathyTpFile is an object which represents a Telepathy file channel.
60 * Usually, clients do not need to deal with #EmpathyTpFile objects directly,
61 * and are supposed to use #EmpathyFTHandler and #EmpathyFTFactory for
62 * transferring files using libempathy.
65 /* EmpathyTpFile object */
67 struct _EmpathyTpFilePrivate {
68 GInputStream *in_stream;
69 GOutputStream *out_stream;
71 /* org.freedesktop.Telepathy.Channel.Type.FileTransfer D-Bus properties */
72 TpFileTransferState state;
73 TpFileTransferStateChangeReason state_change_reason;
74 TpSocketAddressType socket_address_type;
75 TpSocketAccessControl socket_access_control;
77 /* transfer properties */
79 GArray *socket_address;
83 /* GCancellable we're passed when offering/accepting the transfer */
84 GCancellable *cancellable;
86 /* callbacks for the operation */
87 EmpathyTpFileProgressCallback progress_callback;
88 gpointer progress_user_data;
89 EmpathyTpFileOperationCallback op_callback;
90 gpointer op_user_data;
96 G_DEFINE_TYPE (EmpathyTpFile, empathy_tp_file, TP_TYPE_FILE_TRANSFER_CHANNEL);
98 /* private functions */
101 tp_file_get_state_cb (TpProxy *proxy,
105 GObject *weak_object)
107 EmpathyTpFile *self = (EmpathyTpFile *) weak_object;
111 /* set a default value for the state */
112 self->priv->state = TP_FILE_TRANSFER_STATE_NONE;
116 self->priv->state = g_value_get_uint (value);
120 tp_file_get_available_socket_types_cb (TpProxy *proxy,
124 GObject *weak_object)
126 EmpathyTpFile *self = (EmpathyTpFile *) weak_object;
127 GHashTable *socket_types;
128 GArray *access_controls;
131 !G_VALUE_HOLDS (value, TP_HASH_TYPE_SUPPORTED_SOCKET_MAP))
133 /* set a default value */
134 self->priv->socket_address_type = TP_SOCKET_ADDRESS_TYPE_UNIX;
135 self->priv->socket_access_control = TP_SOCKET_ACCESS_CONTROL_LOCALHOST;
139 socket_types = g_value_get_boxed (value);
141 /* here UNIX is preferred to IPV4 */
142 if ((access_controls = g_hash_table_lookup (socket_types,
143 GUINT_TO_POINTER (TP_SOCKET_ADDRESS_TYPE_UNIX))) != NULL)
145 self->priv->socket_address_type = TP_SOCKET_ADDRESS_TYPE_UNIX;
146 self->priv->socket_access_control = TP_SOCKET_ACCESS_CONTROL_LOCALHOST;
150 if ((access_controls = g_hash_table_lookup (socket_types,
151 GUINT_TO_POINTER (TP_SOCKET_ADDRESS_TYPE_IPV4))) != NULL)
153 self->priv->socket_address_type = TP_SOCKET_ADDRESS_TYPE_IPV4;
155 /* TODO: we should prefer PORT over LOCALHOST when the CM will
159 self->priv->socket_access_control = TP_SOCKET_ACCESS_CONTROL_LOCALHOST;
163 DEBUG ("Socket address type: %u, access control %u",
164 self->priv->socket_address_type, self->priv->socket_access_control);
168 tp_file_invalidated_cb (TpProxy *proxy,
174 DEBUG ("Channel invalidated: %s", message);
176 if (self->priv->state != TP_FILE_TRANSFER_STATE_COMPLETED &&
177 self->priv->state != TP_FILE_TRANSFER_STATE_CANCELLED)
179 /* The channel is not in a finished state, an error occured */
180 self->priv->state = TP_FILE_TRANSFER_STATE_CANCELLED;
181 self->priv->state_change_reason =
182 TP_FILE_TRANSFER_STATE_CHANGE_REASON_LOCAL_ERROR;
187 ft_operation_close_clean (EmpathyTpFile *self)
189 if (self->priv->is_closed)
192 DEBUG ("FT operation close clean");
194 self->priv->is_closed = TRUE;
196 if (self->priv->op_callback != NULL)
197 self->priv->op_callback (self, NULL, self->priv->op_user_data);
201 ft_operation_close_with_error (EmpathyTpFile *self,
204 if (self->priv->is_closed)
207 DEBUG ("FT operation close with error %s", error->message);
209 self->priv->is_closed = TRUE;
211 /* close the channel if it's not cancelled already */
212 if (self->priv->state != TP_FILE_TRANSFER_STATE_CANCELLED)
213 empathy_tp_file_cancel (self);
215 if (self->priv->op_callback != NULL)
216 self->priv->op_callback (self, error, self->priv->op_user_data);
220 splice_stream_ready_cb (GObject *source,
224 EmpathyTpFile *self = user_data;
225 GError *error = NULL;
227 g_output_stream_splice_finish (G_OUTPUT_STREAM (source), res, &error);
229 DEBUG ("Splice stream ready cb, error %p", error);
231 if (error != NULL && !self->priv->is_closing)
233 ft_operation_close_with_error (self, error);
234 g_clear_error (&error);
240 tp_file_start_transfer (EmpathyTpFile *self)
242 gint fd, domain, res = 0;
243 GError *error = NULL;
244 struct sockaddr *my_addr = NULL;
247 if (self->priv->socket_address_type == TP_SOCKET_ADDRESS_TYPE_UNIX)
251 else if (self->priv->socket_address_type == TP_SOCKET_ADDRESS_TYPE_IPV4)
257 error = g_error_new_literal (EMPATHY_FT_ERROR_QUARK,
258 EMPATHY_FT_ERROR_NOT_SUPPORTED, _("Socket type not supported"));
260 DEBUG ("Socket not supported, closing channel");
262 ft_operation_close_with_error (self, error);
263 g_clear_error (&error);
268 fd = socket (domain, SOCK_STREAM, 0);
274 error = g_error_new_literal (EMPATHY_FT_ERROR_QUARK,
275 EMPATHY_FT_ERROR_SOCKET, g_strerror (code));
277 DEBUG ("Failed to create socket, closing channel");
279 ft_operation_close_with_error (self, error);
280 g_clear_error (&error);
285 if (self->priv->socket_address_type == TP_SOCKET_ADDRESS_TYPE_UNIX)
287 struct sockaddr_un addr;
289 memset (&addr, 0, sizeof (addr));
290 addr.sun_family = domain;
291 strncpy (addr.sun_path, self->priv->socket_address->data,
292 self->priv->socket_address->len);
294 my_addr = (struct sockaddr *) &addr;
295 my_size = sizeof (addr);
297 else if (self->priv->socket_address_type == TP_SOCKET_ADDRESS_TYPE_IPV4)
299 struct sockaddr_in addr;
301 memset (&addr, 0, sizeof (addr));
302 addr.sin_family = domain;
303 inet_pton (AF_INET, self->priv->socket_address->data, &addr.sin_addr);
304 addr.sin_port = htons (self->priv->port);
306 my_addr = (struct sockaddr *) &addr;
307 my_size = sizeof (addr);
310 res = connect (fd, my_addr, my_size);
316 error = g_error_new_literal (EMPATHY_FT_ERROR_QUARK,
317 EMPATHY_FT_ERROR_SOCKET, g_strerror (code));
319 DEBUG ("Failed to connect socket, closing channel");
321 ft_operation_close_with_error (self, error);
323 g_clear_error (&error);
328 DEBUG ("Start the transfer");
330 self->priv->start_time = empathy_time_get_current ();
332 /* notify we're starting a transfer */
333 if (self->priv->progress_callback != NULL)
334 self->priv->progress_callback (self, 0, self->priv->progress_user_data);
336 if (!tp_channel_get_requested ((TpChannel *) self))
338 GInputStream *socket_stream;
340 socket_stream = g_unix_input_stream_new (fd, TRUE);
342 g_output_stream_splice_async (self->priv->out_stream, socket_stream,
343 G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
344 G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
345 G_PRIORITY_DEFAULT, self->priv->cancellable,
346 splice_stream_ready_cb, self);
348 g_object_unref (socket_stream);
352 GOutputStream *socket_stream;
354 socket_stream = g_unix_output_stream_new (fd, TRUE);
356 g_output_stream_splice_async (socket_stream, self->priv->in_stream,
357 G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
358 G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
359 G_PRIORITY_DEFAULT, self->priv->cancellable,
360 splice_stream_ready_cb, self);
362 g_object_unref (socket_stream);
367 error_from_state_change_reason (TpFileTransferStateChangeReason reason)
370 GError *retval = NULL;
376 case TP_FILE_TRANSFER_STATE_CHANGE_REASON_NONE:
377 string = _("No reason was specified");
379 case TP_FILE_TRANSFER_STATE_CHANGE_REASON_REQUESTED:
380 string = _("The change in state was requested");
382 case TP_FILE_TRANSFER_STATE_CHANGE_REASON_LOCAL_STOPPED:
383 string = _("You canceled the file transfer");
385 case TP_FILE_TRANSFER_STATE_CHANGE_REASON_REMOTE_STOPPED:
386 string = _("The other participant canceled the file transfer");
388 case TP_FILE_TRANSFER_STATE_CHANGE_REASON_LOCAL_ERROR:
389 string = _("Error while trying to transfer the file");
391 case TP_FILE_TRANSFER_STATE_CHANGE_REASON_REMOTE_ERROR:
392 string = _("The other participant is unable to transfer the file");
395 string = _("Unknown reason");
399 retval = g_error_new_literal (EMPATHY_FT_ERROR_QUARK,
400 EMPATHY_FT_ERROR_TP_ERROR, string);
406 tp_file_state_changed_cb (TpChannel *proxy,
410 GObject *weak_object)
412 EmpathyTpFile *self = (EmpathyTpFile *) weak_object;
413 GError *error = NULL;
415 if (state == self->priv->state)
418 DEBUG ("File transfer state changed:\n"
419 "old state = %u, state = %u, reason = %u\n"
420 "\tincoming = %s, in_stream = %s, out_stream = %s",
421 self->priv->state, state, reason,
422 tp_channel_get_requested (proxy) ? "no" : "yes",
423 self->priv->in_stream ? "present" : "not present",
424 self->priv->out_stream ? "present" : "not present");
426 self->priv->state = state;
427 self->priv->state_change_reason = reason;
429 /* If the channel is open AND we have the socket path, we can start the
430 * transfer. The socket path could be NULL if we are not doing the actual
431 * data transfer but are just an observer for the channel.
433 if (state == TP_FILE_TRANSFER_STATE_OPEN &&
434 self->priv->socket_address != NULL)
435 tp_file_start_transfer (EMPATHY_TP_FILE (weak_object));
437 if (state == TP_FILE_TRANSFER_STATE_COMPLETED)
438 ft_operation_close_clean (EMPATHY_TP_FILE (weak_object));
440 if (state == TP_FILE_TRANSFER_STATE_CANCELLED)
442 error = error_from_state_change_reason (self->priv->state_change_reason);
443 ft_operation_close_with_error (EMPATHY_TP_FILE (weak_object), error);
444 g_clear_error (&error);
449 tp_file_transferred_bytes_changed_cb (TpFileTransferChannel *channel,
455 count = tp_file_transfer_channel_get_transferred_bytes (channel);
457 /* don't notify for 0 bytes count */
462 if (self->priv->progress_callback != NULL)
463 self->priv->progress_callback (self, count, self->priv->progress_user_data);
467 ft_operation_provide_or_accept_file_cb (TpChannel *proxy,
468 const GValue *address,
471 GObject *weak_object)
473 EmpathyTpFile *self = EMPATHY_TP_FILE (weak_object);
474 GError *myerr = NULL;
476 g_cancellable_set_error_if_cancelled (self->priv->cancellable, &myerr);
482 /* if we were both cancelled and failed when calling the method,
483 * report the method error.
485 g_clear_error (&myerr);
488 myerr = g_error_copy (error);
493 DEBUG ("Error: %s", myerr->message);
494 ft_operation_close_with_error (self, myerr);
495 g_clear_error (&myerr);
499 if (G_VALUE_TYPE (address) == DBUS_TYPE_G_UCHAR_ARRAY)
501 self->priv->socket_address = g_value_dup_boxed (address);
503 else if (G_VALUE_TYPE (address) == G_TYPE_STRING)
505 /* Old bugged version of telepathy-salut used to store the address
506 * as a 's' instead of an 'ay' */
509 path = g_value_get_string (address);
510 self->priv->socket_address = g_array_sized_new (TRUE, FALSE,
511 sizeof (gchar), strlen (path));
512 g_array_insert_vals (self->priv->socket_address, 0, path, strlen (path));
514 else if (G_VALUE_TYPE (address) == TP_STRUCT_TYPE_SOCKET_ADDRESS_IPV4)
516 GValueArray *val_array;
520 val_array = g_value_get_boxed (address);
523 v = g_value_array_get_nth (val_array, 0);
524 addr = g_value_get_string (v);
525 self->priv->socket_address = g_array_sized_new (TRUE, FALSE,
526 sizeof (gchar), strlen (addr));
527 g_array_insert_vals (self->priv->socket_address, 0, addr, strlen (addr));
530 v = g_value_array_get_nth (val_array, 1);
531 self->priv->port = g_value_get_uint (v);
534 DEBUG ("Got socket address: %s, port (not zero if IPV4): %d",
535 self->priv->socket_address->data, self->priv->port);
537 /* if the channel is already open, start the transfer now, otherwise,
538 * wait for the state change signal.
540 if (self->priv->state == TP_FILE_TRANSFER_STATE_OPEN)
541 tp_file_start_transfer (self);
545 initialize_empty_ac_variant (TpSocketAccessControl ac,
548 /* TODO: we will add more types here once we support PORT access control. */
549 if (ac == TP_SOCKET_ACCESS_CONTROL_LOCALHOST)
551 g_value_init (val, G_TYPE_STRING);
552 g_value_set_static_string (val, "");
557 file_read_async_cb (GObject *source,
561 GValue nothing = { 0 };
562 EmpathyTpFile *self = user_data;
563 GFileInputStream *in_stream;
564 GError *error = NULL;
566 in_stream = g_file_read_finish (G_FILE (source), res, &error);
568 if (error != NULL && !self->priv->is_closing)
570 ft_operation_close_with_error (self, error);
571 g_clear_error (&error);
575 self->priv->in_stream = G_INPUT_STREAM (in_stream);
577 /* we don't impose specific interface/port requirements even
578 * if we're not using UNIX sockets.
580 initialize_empty_ac_variant (self->priv->socket_access_control, ¬hing);
582 tp_cli_channel_type_file_transfer_call_provide_file ((TpChannel *) self, -1,
583 self->priv->socket_address_type, self->priv->socket_access_control,
584 ¬hing, ft_operation_provide_or_accept_file_cb,
585 NULL, NULL, G_OBJECT (self));
589 file_transfer_set_uri_cb (TpProxy *proxy,
592 GObject *weak_object)
594 GValue nothing = { 0 };
595 EmpathyTpFile *self = (EmpathyTpFile *) weak_object;
599 DEBUG ("Failed to set FileTransfer.URI: %s", error->message);
600 /* We don't return as that's not a big issue */
603 /* we don't impose specific interface/port requirements even
604 * if we're not using UNIX sockets.
606 initialize_empty_ac_variant (self->priv->socket_access_control, ¬hing);
608 tp_cli_channel_type_file_transfer_call_accept_file ((TpChannel *) self,
609 -1, self->priv->socket_address_type, self->priv->socket_access_control,
610 ¬hing, self->priv->offset,
611 ft_operation_provide_or_accept_file_cb, NULL, NULL, weak_object);
615 file_replace_async_cb (GObject *source,
619 EmpathyTpFile *self = user_data;
620 GError *error = NULL;
621 GFileOutputStream *out_stream;
622 GFile *file = G_FILE (source);
626 out_stream = g_file_replace_finish (file, res, &error);
630 ft_operation_close_with_error (self, error);
631 g_clear_error (&error);
636 self->priv->out_stream = G_OUTPUT_STREAM (out_stream);
638 /* Try setting FileTranfer.URI before accepting the file */
639 uri = g_file_get_uri (file);
640 value = tp_g_value_slice_new_take_string (uri);
642 tp_cli_dbus_properties_call_set ((TpChannel *) self, -1,
643 TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, "URI", value,
644 file_transfer_set_uri_cb, NULL, NULL, G_OBJECT (self));
646 tp_g_value_slice_free (value);
650 channel_closed_cb (TpChannel *proxy,
653 GObject *weak_object)
655 EmpathyTpFile *self = EMPATHY_TP_FILE (weak_object);
656 gboolean cancel = GPOINTER_TO_INT (user_data);
658 DEBUG ("Channel is closed, should cancel %s", cancel ? "True" : "False");
660 if (self->priv->cancellable != NULL &&
661 !g_cancellable_is_cancelled (self->priv->cancellable) && cancel)
662 g_cancellable_cancel (self->priv->cancellable);
666 close_channel_internal (EmpathyTpFile *self,
669 DEBUG ("Closing channel, should cancel %s", cancel ?
672 self->priv->is_closing = TRUE;
674 tp_cli_channel_call_close ((TpChannel *) self, -1,
675 channel_closed_cb, GINT_TO_POINTER (cancel), NULL, G_OBJECT (self));
678 /* GObject methods */
681 empathy_tp_file_init (EmpathyTpFile *self)
683 self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self),
684 EMPATHY_TYPE_TP_FILE, EmpathyTpFilePrivate);
688 do_dispose (GObject *object)
690 EmpathyTpFile *self = (EmpathyTpFile *) object;
692 tp_clear_object (&self->priv->in_stream);
693 tp_clear_object (&self->priv->out_stream);
694 tp_clear_object (&self->priv->cancellable);
696 G_OBJECT_CLASS (empathy_tp_file_parent_class)->dispose (object);
700 do_finalize (GObject *object)
702 EmpathyTpFile *self = (EmpathyTpFile *) object;
704 DEBUG ("%p", object);
706 if (self->priv->socket_address != NULL)
708 g_array_unref (self->priv->socket_address);
709 self->priv->socket_address = NULL;
712 G_OBJECT_CLASS (empathy_tp_file_parent_class)->finalize (object);
716 do_constructed (GObject *object)
718 EmpathyTpFile *self = (EmpathyTpFile *) object;
719 TpChannel *channel = (TpChannel *) self;
721 g_signal_connect (self, "invalidated",
722 G_CALLBACK (tp_file_invalidated_cb), self);
724 tp_cli_channel_type_file_transfer_connect_to_file_transfer_state_changed (
725 channel, tp_file_state_changed_cb, NULL, NULL, object, NULL);
727 g_signal_connect (self, "notify::transferred-bytes",
728 G_CALLBACK (tp_file_transferred_bytes_changed_cb), self);
730 tp_cli_dbus_properties_call_get (channel,
731 -1, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, "State", tp_file_get_state_cb,
734 tp_cli_dbus_properties_call_get (channel,
735 -1, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, "AvailableSocketTypes",
736 tp_file_get_available_socket_types_cb, NULL, NULL, object);
738 self->priv->state_change_reason =
739 TP_FILE_TRANSFER_STATE_CHANGE_REASON_NONE;
741 G_OBJECT_CLASS (empathy_tp_file_parent_class)->constructed (object);
745 empathy_tp_file_class_init (EmpathyTpFileClass *klass)
747 GObjectClass *object_class = G_OBJECT_CLASS (klass);
749 object_class->finalize = do_finalize;
750 object_class->dispose = do_dispose;
751 object_class->constructed = do_constructed;
753 g_type_class_add_private (object_class, sizeof (EmpathyTpFilePrivate));
759 empathy_tp_file_new (TpSimpleClientFactory *factory,
761 const gchar *object_path,
762 const GHashTable *immutable_properties,
765 TpProxy *conn_proxy = (TpProxy *) conn;
767 g_return_val_if_fail (TP_IS_CONNECTION (conn), NULL);
768 g_return_val_if_fail (immutable_properties != NULL, NULL);
770 return g_object_new (EMPATHY_TYPE_TP_FILE,
773 "dbus-daemon", conn_proxy->dbus_daemon,
774 "bus-name", conn_proxy->bus_name,
775 "object-path", object_path,
776 "channel-properties", immutable_properties,
781 * empathy_tp_file_accept:
782 * @self: an incoming #EmpathyTpFile
783 * @offset: the offset of @gfile where we should start writing
784 * @gfile: the destination #GFile for the transfer
785 * @cancellable: a #GCancellable
786 * @progress_callback: function to callback with progress information
787 * @progress_user_data: user_data to pass to @progress_callback
788 * @op_callback: function to callback when the transfer ends
789 * @op_user_data: user_data to pass to @op_callback
791 * Accepts an incoming file transfer, saving the result into @gfile.
792 * The callback @op_callback will be called both when the transfer is
793 * successful and in case of an error. Note that cancelling @cancellable,
794 * closes the socket of the file operation in progress, but doesn't
795 * guarantee that the transfer channel will be closed as well. Thus,
796 * empathy_tp_file_cancel() or empathy_tp_file_close() should be used to
797 * actually cancel an ongoing #EmpathyTpFile.
800 empathy_tp_file_accept (EmpathyTpFile *self,
803 GCancellable *cancellable,
804 EmpathyTpFileProgressCallback progress_callback,
805 gpointer progress_user_data,
806 EmpathyTpFileOperationCallback op_callback,
807 gpointer op_user_data)
809 g_return_if_fail (EMPATHY_IS_TP_FILE (self));
810 g_return_if_fail (G_IS_FILE (gfile));
811 g_return_if_fail (G_IS_CANCELLABLE (cancellable));
813 self->priv->cancellable = g_object_ref (cancellable);
814 self->priv->progress_callback = progress_callback;
815 self->priv->progress_user_data = progress_user_data;
816 self->priv->op_callback = op_callback;
817 self->priv->op_user_data = op_user_data;
818 self->priv->offset = offset;
820 g_file_replace_async (gfile, NULL, FALSE, G_FILE_CREATE_NONE,
821 G_PRIORITY_DEFAULT, cancellable, file_replace_async_cb, self);
826 * empathy_tp_file_offer:
827 * @self: an outgoing #EmpathyTpFile
828 * @gfile: the source #GFile for the transfer
829 * @cancellable: a #GCancellable
830 * @progress_callback: function to callback with progress information
831 * @progress_user_data: user_data to pass to @progress_callback
832 * @op_callback: function to callback when the transfer ends
833 * @op_user_data: user_data to pass to @op_callback
835 * Offers an outgoing file transfer, reading data from @gfile.
836 * The callback @op_callback will be called both when the transfer is
837 * successful and in case of an error. Note that cancelling @cancellable,
838 * closes the socket of the file operation in progress, but doesn't
839 * guarantee that the transfer channel will be closed as well. Thus,
840 * empathy_tp_file_cancel() or empathy_tp_file_close() should be used to
841 * actually cancel an ongoing #EmpathyTpFile.
844 empathy_tp_file_offer (EmpathyTpFile *self,
846 GCancellable *cancellable,
847 EmpathyTpFileProgressCallback progress_callback,
848 gpointer progress_user_data,
849 EmpathyTpFileOperationCallback op_callback,
850 gpointer op_user_data)
852 g_return_if_fail (EMPATHY_IS_TP_FILE (self));
853 g_return_if_fail (G_IS_FILE (gfile));
854 g_return_if_fail (G_IS_CANCELLABLE (cancellable));
856 self->priv->cancellable = g_object_ref (cancellable);
857 self->priv->progress_callback = progress_callback;
858 self->priv->progress_user_data = progress_user_data;
859 self->priv->op_callback = op_callback;
860 self->priv->op_user_data = op_user_data;
862 g_file_read_async (gfile, G_PRIORITY_DEFAULT, cancellable,
863 file_read_async_cb, self);
867 * empathy_tp_file_cancel:
868 * @self: an #EmpathyTpFile
870 * Cancels an ongoing #EmpathyTpFile, first closing the channel and then
871 * cancelling any I/O operation and closing the socket.
874 empathy_tp_file_cancel (EmpathyTpFile *self)
876 g_return_if_fail (EMPATHY_IS_TP_FILE (self));
878 close_channel_internal (self, TRUE);
882 * empathy_tp_file_close:
883 * @self: an #EmpathyTpFile
885 * Closes the channel for an ongoing #EmpathyTpFile. It's safe to call this
886 * method after the transfer has ended.
889 empathy_tp_file_close (EmpathyTpFile *self)
891 g_return_if_fail (EMPATHY_IS_TP_FILE (self));
893 close_channel_internal (self, FALSE);