1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2007-2008 Collabora Ltd.
4 * Copyright (C) 2007 Marco Barisione <marco@barisione.org>
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 * Authors: Marco Barisione <marco@barisione.org>
21 * Jonny Lamb <jonny.lamb@collabora.co.uk>
28 #include <sys/types.h>
29 #include <sys/socket.h>
32 #include <glib/gi18n-lib.h>
35 #include <gio/gunixinputstream.h>
36 #include <gio/gunixoutputstream.h>
38 #include <telepathy-glib/proxy-subclass.h>
39 #include <telepathy-glib/util.h>
41 #include "empathy-tp-file.h"
42 #include "empathy-tp-contact-factory.h"
43 #include "empathy-marshal.h"
44 #include "empathy-time.h"
45 #include "empathy-utils.h"
47 #include "extensions/extensions.h"
49 #define DEBUG_FLAG EMPATHY_DEBUG_FT
50 #include "empathy-debug.h"
53 * SECTION:empathy-tp-file
54 * @title: EmpathyTpFile
55 * @short_description: Object which represents a Telepathy file channel
56 * @include: libempathy/empathy-tp-file.h
58 * #EmpathyTpFile is an object which represents a Telepathy file channel.
63 * @parent: parent object
65 * Object which represents a Telepathy file channel.
69 * EMPATHY_TP_FILE_UNKNOWN_SIZE:
71 * Value used for the "size" or "estimated-size" properties when the size of
72 * the transferred file is unknown.
75 /* Functions to copy the content of a GInputStream to a GOutputStream */
78 #define BUFFER_SIZE 4096
79 #define STALLED_TIMEOUT 5
84 GCancellable *cancellable;
85 char *buff[N_BUFFERS]; /* the temporary buffers */
86 gsize count[N_BUFFERS]; /* how many bytes are used in the buffers */
87 gboolean is_full[N_BUFFERS]; /* whether the buffers contain data */
88 gint curr_read; /* index of the buffer used for reading */
89 gint curr_write; /* index of the buffer used for writing */
90 gboolean is_reading; /* we are reading */
91 gboolean is_writing; /* we are writing */
92 guint n_closed; /* number of streams that have been closed */
96 static void schedule_next (CopyData *copy);
99 copy_data_unref (CopyData *copy)
101 if (--copy->ref_count == 0)
105 /* Free the data only if both the input and output streams have
108 if (copy->n_closed < 2)
111 if (copy->in != NULL)
112 g_object_unref (copy->in);
114 if (copy->out != NULL)
115 g_object_unref (copy->out);
117 for (i = 0; i < N_BUFFERS; i++)
118 g_free (copy->buff[i]);
120 g_object_unref (copy->cancellable);
126 io_error (CopyData *copy,
129 g_cancellable_cancel (copy->cancellable);
132 g_warning ("I/O error");
133 else if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED)
134 ; /* Ignore cancellations */
136 g_warning ("I/O error: %d: %s\n", error->code, error->message);
138 if (copy->in != NULL)
139 g_input_stream_close (copy->in, NULL, NULL);
141 if (copy->out != NULL)
142 g_output_stream_close (copy->out, NULL, NULL);
144 copy_data_unref (copy);
148 close_done (GObject *source_object,
152 CopyData *copy = user_data;
154 g_object_unref (source_object);
155 copy_data_unref (copy);
159 write_done_cb (GObject *source_object,
163 CopyData *copy = user_data;
165 GError *error = NULL;
167 count_write = g_output_stream_write_finish (copy->out, res, &error);
169 if (count_write <= 0)
171 io_error (copy, error);
172 g_error_free (error);
176 copy->is_full[copy->curr_write] = FALSE;
177 copy->curr_write = (copy->curr_write + 1) % N_BUFFERS;
178 copy->is_writing = FALSE;
180 schedule_next (copy);
184 read_done_cb (GObject *source_object,
188 CopyData *copy = user_data;
190 GError *error = NULL;
192 count_read = g_input_stream_read_finish (copy->in, res, &error);
196 g_input_stream_close_async (copy->in, 0, copy->cancellable,
200 else if (count_read < 0)
202 io_error (copy, error);
203 g_error_free (error);
207 copy->count[copy->curr_read] = count_read;
208 copy->is_full[copy->curr_read] = TRUE;
209 copy->curr_read = (copy->curr_read + 1) % N_BUFFERS;
210 copy->is_reading = FALSE;
212 schedule_next (copy);
216 schedule_next (CopyData *copy)
218 if (copy->in != NULL &&
220 !copy->is_full[copy->curr_read])
222 /* We are not reading and the current buffer is empty, so
223 * start an async read. */
224 copy->is_reading = TRUE;
225 g_input_stream_read_async (copy->in,
226 copy->buff[copy->curr_read],
227 BUFFER_SIZE, 0, copy->cancellable,
231 if (!copy->is_writing &&
232 copy->is_full[copy->curr_write])
234 if (copy->count[copy->curr_write] == 0)
236 /* The last read on the buffer read 0 bytes, this
237 * means that we got an EOF, so we can close
238 * the output channel. */
239 g_output_stream_close_async (copy->out, 0,
246 /* We are not writing and the current buffer contains
247 * data, so start an async write. */
248 copy->is_writing = TRUE;
249 g_output_stream_write_async (copy->out,
250 copy->buff[copy->curr_write],
251 copy->count[copy->curr_write],
252 0, copy->cancellable,
253 write_done_cb, copy);
259 copy_stream (GInputStream *in,
261 GCancellable *cancellable)
266 g_return_if_fail (in != NULL);
267 g_return_if_fail (out != NULL);
269 copy = g_new0 (CopyData, 1);
270 copy->in = g_object_ref (in);
271 copy->out = g_object_ref (out);
274 if (cancellable != NULL)
275 copy->cancellable = g_object_ref (cancellable);
277 copy->cancellable = g_cancellable_new ();
279 for (i = 0; i < N_BUFFERS; i++)
280 copy->buff[i] = g_malloc (BUFFER_SIZE);
282 schedule_next (copy);
285 /* EmpathyTpFile object */
287 struct _EmpathyTpFilePriv {
288 EmpathyTpContactFactory *factory;
293 EmpathyContact *contact;
294 GInputStream *in_stream;
295 GOutputStream *out_stream;
297 /* org.freedesktop.Telepathy.Channel.Type.FileTransfer D-Bus properties */
298 TpFileTransferState state;
302 TpFileHashType content_hash_type;
305 guint64 transferred_bytes;
308 TpFileTransferStateChangeReason state_change_reason;
309 time_t last_update_time;
310 guint64 last_update_transferred_bytes;
314 GValue *socket_address;
315 GCancellable *cancellable;
327 PROP_TRANSFERRED_BYTES,
328 PROP_CONTENT_HASH_TYPE,
337 static guint signals[LAST_SIGNAL];
339 G_DEFINE_TYPE (EmpathyTpFile, empathy_tp_file, G_TYPE_OBJECT);
342 empathy_tp_file_init (EmpathyTpFile *tp_file)
344 EmpathyTpFilePriv *priv;
346 priv = G_TYPE_INSTANCE_GET_PRIVATE ((tp_file),
347 EMPATHY_TYPE_TP_FILE, EmpathyTpFilePriv);
349 tp_file->priv = priv;
353 tp_file_invalidated_cb (TpProxy *proxy,
357 EmpathyTpFile *tp_file)
359 DEBUG ("Channel invalidated: %s", message);
361 if (tp_file->priv->state != TP_FILE_TRANSFER_STATE_COMPLETED &&
362 tp_file->priv->state != TP_FILE_TRANSFER_STATE_CANCELLED)
364 /* The channel is not in a finished state, an error occured */
365 tp_file->priv->state = TP_FILE_TRANSFER_STATE_CANCELLED;
366 tp_file->priv->state_change_reason =
367 TP_FILE_TRANSFER_STATE_CHANGE_REASON_LOCAL_ERROR;
368 g_object_notify (G_OBJECT (tp_file), "state");
373 tp_file_finalize (GObject *object)
375 EmpathyTpFile *tp_file = EMPATHY_TP_FILE (object);
377 if (tp_file->priv->channel)
379 g_signal_handlers_disconnect_by_func (tp_file->priv->channel,
380 tp_file_invalidated_cb, object);
381 g_object_unref (tp_file->priv->channel);
382 tp_file->priv->channel = NULL;
385 if (tp_file->priv->factory)
387 g_object_unref (tp_file->priv->factory);
389 if (tp_file->priv->mc)
391 g_object_unref (tp_file->priv->mc);
394 g_free (tp_file->priv->filename);
395 if (tp_file->priv->socket_address != NULL)
396 tp_g_value_slice_free (tp_file->priv->socket_address);
397 g_free (tp_file->priv->description);
398 g_free (tp_file->priv->content_hash);
399 g_free (tp_file->priv->content_type);
401 if (tp_file->priv->in_stream)
402 g_object_unref (tp_file->priv->in_stream);
404 if (tp_file->priv->out_stream)
405 g_object_unref (tp_file->priv->out_stream);
407 if (tp_file->priv->contact)
408 g_object_unref (tp_file->priv->contact);
410 if (tp_file->priv->cancellable)
411 g_object_unref (tp_file->priv->cancellable);
413 if (tp_file->priv->stalled_id != 0)
414 g_source_remove (tp_file->priv->stalled_id);
416 G_OBJECT_CLASS (empathy_tp_file_parent_class)->finalize (object);
420 tp_file_stalled_cb (EmpathyTpFile *tp_file)
422 /* We didn't get transferred bytes update for a while, the transfer is
425 tp_file->priv->speed = 0;
426 tp_file->priv->remaining_time = -1;
427 g_signal_emit (tp_file, signals[REFRESH], 0);
433 tp_file_start_transfer (EmpathyTpFile *tp_file)
436 struct sockaddr_un addr;
439 fd = socket (PF_UNIX, SOCK_STREAM, 0);
442 DEBUG ("Failed to create socket, closing channel");
443 empathy_tp_file_cancel (tp_file);
447 array = g_value_get_boxed (tp_file->priv->socket_address);
449 memset (&addr, 0, sizeof (addr));
450 addr.sun_family = AF_UNIX;
451 strncpy (addr.sun_path, array->data, array->len);
453 if (connect (fd, (struct sockaddr*) &addr, sizeof (addr)) < 0)
455 DEBUG ("Failed to connect socket, closing channel");
456 empathy_tp_file_cancel (tp_file);
461 DEBUG ("Start the transfer");
463 tp_file->priv->last_update_time = empathy_time_get_current ();
464 tp_file->priv->last_update_transferred_bytes = tp_file->priv->transferred_bytes;
465 tp_file->priv->stalled_id = g_timeout_add_seconds (STALLED_TIMEOUT,
466 (GSourceFunc) tp_file_stalled_cb, tp_file);
468 tp_file->priv->cancellable = g_cancellable_new ();
469 if (tp_file->priv->incoming)
471 GInputStream *socket_stream;
473 socket_stream = g_unix_input_stream_new (fd, TRUE);
474 copy_stream (socket_stream, tp_file->priv->out_stream,
475 tp_file->priv->cancellable);
476 g_object_unref (socket_stream);
480 GOutputStream *socket_stream;
482 socket_stream = g_unix_output_stream_new (fd, TRUE);
483 copy_stream (tp_file->priv->in_stream, socket_stream,
484 tp_file->priv->cancellable);
485 g_object_unref (socket_stream);
490 tp_file_state_changed_cb (TpChannel *channel,
494 GObject *weak_object)
496 EmpathyTpFile *tp_file = EMPATHY_TP_FILE (weak_object);
498 if (state == tp_file->priv->state)
501 DEBUG ("File transfer state changed:\n"
502 "\tfilename = %s, old state = %u, state = %u, reason = %u\n"
503 "\tincoming = %s, in_stream = %s, out_stream = %s",
504 tp_file->priv->filename, tp_file->priv->state, state, reason,
505 tp_file->priv->incoming ? "yes" : "no",
506 tp_file->priv->in_stream ? "present" : "not present",
507 tp_file->priv->out_stream ? "present" : "not present");
509 /* If the channel is open AND we have the socket path, we can start the
510 * transfer. The socket path could be NULL if we are not doing the actual
511 * data transfer but are just an observer for the channel. */
512 if (state == TP_FILE_TRANSFER_STATE_OPEN &&
513 tp_file->priv->socket_address != NULL)
514 tp_file_start_transfer (tp_file);
516 tp_file->priv->state = state;
517 tp_file->priv->state_change_reason = reason;
519 g_object_notify (G_OBJECT (tp_file), "state");
523 tp_file_transferred_bytes_changed_cb (TpChannel *channel,
526 GObject *weak_object)
528 EmpathyTpFile *tp_file = EMPATHY_TP_FILE (weak_object);
529 time_t curr_time, elapsed_time;
530 guint64 transferred_bytes;
532 /* If we didn't progress since last update, return */
533 if (tp_file->priv->transferred_bytes == count)
536 /* Update the transferred bytes count */
537 tp_file->priv->transferred_bytes = count;
538 g_object_notify (G_OBJECT (tp_file), "transferred-bytes");
540 /* We got a progress, reset the stalled timeout */
541 if (tp_file->priv->stalled_id != 0)
542 g_source_remove (tp_file->priv->stalled_id);
543 tp_file->priv->stalled_id = g_timeout_add_seconds (STALLED_TIMEOUT,
544 (GSourceFunc) tp_file_stalled_cb, tp_file);
546 /* Calculate the transfer speed and remaining time estimation. We recalculate
547 * that each second to get more dynamic values that react faster to network
548 * changes. This is better than calculating the average from the begining of
549 * the transfer, I think. */
550 curr_time = empathy_time_get_current ();
551 elapsed_time = curr_time - tp_file->priv->last_update_time;
552 if (elapsed_time >= 1)
554 transferred_bytes = count - tp_file->priv->last_update_transferred_bytes;
555 tp_file->priv->speed = (gdouble) transferred_bytes / (gdouble) elapsed_time;
556 tp_file->priv->remaining_time = (tp_file->priv->size - count) /
557 tp_file->priv->speed;
558 tp_file->priv->last_update_transferred_bytes = count;
559 tp_file->priv->last_update_time = curr_time;
561 g_signal_emit (tp_file, signals[REFRESH], 0);
566 tp_file_check_if_ready (EmpathyTpFile *tp_file)
568 if (tp_file->priv->ready || tp_file->priv->contact == NULL ||
569 tp_file->priv->state == TP_FILE_TRANSFER_STATE_NONE)
572 tp_file->priv->ready = TRUE;
573 g_object_notify (G_OBJECT (tp_file), "ready");
577 tp_file_got_contact_cb (EmpathyTpContactFactory *factory,
578 EmpathyContact *contact,
581 GObject *weak_object)
583 EmpathyTpFile *tp_file = EMPATHY_TP_FILE (weak_object);
587 DEBUG ("Error: %s", error->message);
588 empathy_tp_file_cancel (tp_file);
592 tp_file->priv->contact = g_object_ref (contact);
593 tp_file_check_if_ready (tp_file);
597 tp_file_get_all_cb (TpProxy *proxy,
598 GHashTable *properties,
603 EmpathyTpFile *tp_file = EMPATHY_TP_FILE (file_obj);
607 DEBUG ("Error: %s", error->message);
608 tp_cli_channel_call_close (tp_file->priv->channel, -1, NULL, NULL, NULL,
613 tp_file->priv->size = g_value_get_uint64 (
614 g_hash_table_lookup (properties, "Size"));
615 g_object_notify (file_obj, "size");
617 tp_file->priv->state = g_value_get_uint (
618 g_hash_table_lookup (properties, "State"));
619 g_object_notify (file_obj, "state");
621 tp_file->priv->transferred_bytes = g_value_get_uint64 (
622 g_hash_table_lookup (properties, "TransferredBytes"));
623 g_object_notify (file_obj, "transferred-bytes");
625 tp_file->priv->filename = g_value_dup_string (
626 g_hash_table_lookup (properties, "Filename"));
627 g_object_notify (file_obj, "filename");
629 tp_file->priv->content_hash = g_value_dup_string (
630 g_hash_table_lookup (properties, "ContentHash"));
631 g_object_notify (file_obj, "content-hash");
633 tp_file->priv->content_hash_type = g_value_get_uint (
634 g_hash_table_lookup (properties, "ContentHashType"));
635 g_object_notify (file_obj, "content-hash-type");
637 tp_file->priv->content_type = g_value_dup_string (
638 g_hash_table_lookup (properties, "ContentType"));
639 g_object_notify (file_obj, "content-type");
641 tp_file->priv->description = g_value_dup_string (
642 g_hash_table_lookup (properties, "Description"));
644 tp_file_check_if_ready (tp_file);
648 tp_file_get_requested_cb (TpProxy *proxy,
649 const GValue *requested,
652 GObject *weak_object)
654 EmpathyTpFile *tp_file = EMPATHY_TP_FILE (weak_object);
658 DEBUG ("Error: %s", error->message);
659 tp_cli_channel_call_close (tp_file->priv->channel, -1, NULL, NULL, NULL,
664 tp_file->priv->incoming = !g_value_get_boolean (requested);
665 g_object_notify (G_OBJECT (tp_file), "incoming");
667 tp_file_check_if_ready (tp_file);
671 tp_file_constructor (GType type,
673 GObjectConstructParam *props)
676 EmpathyTpFile *tp_file;
678 TpConnection *connection;
680 file_obj = G_OBJECT_CLASS (empathy_tp_file_parent_class)->constructor (type,
683 tp_file = EMPATHY_TP_FILE (file_obj);
685 connection = tp_channel_borrow_connection (tp_file->priv->channel);
686 tp_file->priv->factory = empathy_tp_contact_factory_dup_singleton (connection);
687 tp_file->priv->mc = empathy_mission_control_dup_singleton ();
688 tp_file->priv->state_change_reason =
689 TP_FILE_TRANSFER_STATE_CHANGE_REASON_NONE;
691 g_signal_connect (tp_file->priv->channel, "invalidated",
692 G_CALLBACK (tp_file_invalidated_cb), tp_file);
694 tp_cli_channel_type_file_transfer_connect_to_file_transfer_state_changed (
695 tp_file->priv->channel, tp_file_state_changed_cb, NULL, NULL,
696 G_OBJECT (tp_file), NULL);
698 tp_cli_channel_type_file_transfer_connect_to_transferred_bytes_changed (
699 tp_file->priv->channel, tp_file_transferred_bytes_changed_cb,
700 NULL, NULL, G_OBJECT (tp_file), NULL);
702 tp_cli_dbus_properties_call_get (tp_file->priv->channel, -1,
703 TP_IFACE_CHANNEL, "Requested",
704 tp_file_get_requested_cb, NULL, NULL, file_obj);
706 tp_cli_dbus_properties_call_get_all (tp_file->priv->channel, -1,
707 TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER,
708 tp_file_get_all_cb, NULL, NULL, file_obj);
710 handle = tp_channel_get_handle (tp_file->priv->channel, NULL);
711 empathy_tp_contact_factory_get_from_handle (tp_file->priv->factory,
712 handle, tp_file_got_contact_cb, NULL, NULL, file_obj);
718 tp_file_get_property (GObject *object,
723 EmpathyTpFile *tp_file;
725 tp_file = EMPATHY_TP_FILE (object);
730 g_value_set_object (value, tp_file->priv->channel);
733 g_value_set_boolean (value, tp_file->priv->incoming);
736 g_value_set_uint (value, tp_file->priv->state);
738 case PROP_CONTENT_TYPE:
739 g_value_set_string (value, tp_file->priv->content_type);
742 g_value_set_string (value, tp_file->priv->filename);
745 g_value_set_uint64 (value, tp_file->priv->size);
747 case PROP_CONTENT_HASH_TYPE:
748 g_value_set_uint (value, tp_file->priv->content_hash_type);
750 case PROP_CONTENT_HASH:
751 g_value_set_string (value, tp_file->priv->content_hash);
753 case PROP_TRANSFERRED_BYTES:
754 g_value_set_uint64 (value, tp_file->priv->transferred_bytes);
757 g_value_set_boolean (value, tp_file->priv->ready);
760 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
766 tp_file_channel_set_dbus_property (gpointer proxy,
767 const gchar *property,
770 DEBUG ("Setting %s property", property);
771 tp_cli_dbus_properties_call_set (TP_PROXY (proxy), -1,
772 TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, property, value,
773 NULL, NULL, NULL, NULL);
777 tp_file_set_property (GObject *object,
782 EmpathyTpFile *tp_file = (EmpathyTpFile *) object;
786 tp_file->priv->channel = g_object_ref (g_value_get_object (value));
789 tp_file->priv->state = g_value_get_uint (value);
792 tp_file->priv->incoming = g_value_get_boolean (value);
795 g_free (tp_file->priv->filename);
796 tp_file->priv->filename = g_value_dup_string (value);
797 tp_file_channel_set_dbus_property (tp_file->priv->channel,
801 tp_file->priv->size = g_value_get_uint64 (value);
802 tp_file_channel_set_dbus_property (tp_file->priv->channel,
805 case PROP_CONTENT_TYPE:
806 tp_file_channel_set_dbus_property (tp_file->priv->channel,
807 "ContentType", value);
808 g_free (tp_file->priv->content_type);
809 tp_file->priv->content_type = g_value_dup_string (value);
811 case PROP_CONTENT_HASH:
812 tp_file_channel_set_dbus_property (tp_file->priv->channel,
813 "ContentHash", value);
814 g_free (tp_file->priv->content_hash);
815 tp_file->priv->content_hash = g_value_dup_string (value);
818 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
823 static GHashTable *ft_table = NULL;
826 tp_file_weak_notify_cb (gpointer channel,
829 g_hash_table_remove (ft_table, channel);
833 * empathy_tp_file_new:
834 * @channel: a #TpChannel
836 * Creates a new #EmpathyTpFile wrapping @channel, or return a new ref to an
837 * existing #EmpathyTpFile for that channel. The returned #EmpathyTpFile
838 * should be unrefed with g_object_unref() when finished with.
840 * Return value: a new #EmpathyTpFile
843 empathy_tp_file_new (TpChannel *channel)
845 EmpathyTpFile *tp_file;
847 g_return_val_if_fail (TP_IS_CHANNEL (channel), NULL);
849 if (ft_table != NULL)
851 tp_file = g_hash_table_lookup (ft_table, channel);
852 if (tp_file != NULL) {
853 return g_object_ref (tp_file);
857 ft_table = g_hash_table_new_full (empathy_proxy_hash,
858 empathy_proxy_equal, (GDestroyNotify) g_object_unref, NULL);
860 tp_file = g_object_new (EMPATHY_TYPE_TP_FILE,
864 g_hash_table_insert (ft_table, g_object_ref (channel), tp_file);
865 g_object_weak_ref (G_OBJECT (tp_file), tp_file_weak_notify_cb, channel);
871 * empathy_tp_file_get_channel
872 * @tp_file: an #EmpathyTpFile
874 * Returns the #TpChannel associated with @tp_file.
876 * Returns: the #TpChannel associated with @tp_file
879 empathy_tp_file_get_channel (EmpathyTpFile *tp_file)
881 g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file), NULL);
883 return tp_file->priv->channel;
887 tp_file_method_cb (TpChannel *channel,
888 const GValue *address,
891 GObject *weak_object)
893 EmpathyTpFile *tp_file = (EmpathyTpFile *) weak_object;
898 DEBUG ("Error: %s", error->message);
899 empathy_tp_file_cancel (tp_file);
903 if (G_VALUE_TYPE (address) == DBUS_TYPE_G_UCHAR_ARRAY)
905 tp_file->priv->socket_address = tp_g_value_slice_dup (address);
907 else if (G_VALUE_TYPE (address) == G_TYPE_STRING)
909 /* Old bugged version of telepathy-salut used to store the address
910 * as a 's' instead of an 'ay' */
913 path = g_value_get_string (address);
914 array = g_array_sized_new (TRUE, FALSE, sizeof (gchar), strlen (path));
915 g_array_insert_vals (array, 0, path, strlen (path));
917 tp_file->priv->socket_address = tp_g_value_slice_new (
918 DBUS_TYPE_G_UCHAR_ARRAY);
919 g_value_set_boxed (tp_file->priv->socket_address, array);
921 g_array_free (array, TRUE);
925 DEBUG ("Wrong address type: %s", G_VALUE_TYPE_NAME (address));
926 empathy_tp_file_cancel (tp_file);
930 array = g_value_get_boxed (tp_file->priv->socket_address);
931 DEBUG ("Got unix socket path: %s", array->data);
933 if (tp_file->priv->state == TP_FILE_TRANSFER_STATE_OPEN)
934 tp_file_start_transfer (tp_file);
938 * empathy_tp_file_accept:
939 * @tp_file: an #EmpathyTpFile
940 * @offset: position where to start the transfer
941 * @gfile: a #GFile where to write transfered data
942 * @error: a #GError set if there is an error when opening @gfile
944 * Accepts a file transfer that's in the "local pending" state (i.e.
945 * %TP_FILE_TRANSFER_STATE_LOCAL_PENDING).
948 empathy_tp_file_accept (EmpathyTpFile *tp_file,
953 GValue nothing = { 0 };
955 g_return_if_fail (EMPATHY_IS_TP_FILE (tp_file));
956 g_return_if_fail (G_IS_FILE (gfile));
958 tp_file->priv->out_stream = G_OUTPUT_STREAM (g_file_replace (gfile, NULL,
959 FALSE, 0, NULL, error));
963 g_free (tp_file->priv->filename);
964 tp_file->priv->filename = g_file_get_basename (gfile);
965 g_object_notify (G_OBJECT (tp_file), "filename");
967 DEBUG ("Accepting file: filename=%s", tp_file->priv->filename);
969 g_value_init (¬hing, G_TYPE_STRING);
970 g_value_set_static_string (¬hing, "");
972 tp_cli_channel_type_file_transfer_call_accept_file (tp_file->priv->channel,
973 -1, TP_SOCKET_ADDRESS_TYPE_UNIX, TP_SOCKET_ACCESS_CONTROL_LOCALHOST,
974 ¬hing, offset, tp_file_method_cb, NULL, NULL, G_OBJECT (tp_file));
978 * empathy_tp_file_offer:
979 * @tp_file: an #EmpathyTpFile
980 * @gfile: a #GFile where to read the data to transfer
981 * @error: a #GError set if there is an error when opening @gfile
983 * Offers a file transfer that's in the "not offered" state (i.e.
984 * %TP_FILE_TRANSFER_STATE_NOT_OFFERED).
987 empathy_tp_file_offer (EmpathyTpFile *tp_file, GFile *gfile, GError **error)
989 GValue nothing = { 0 };
991 g_return_if_fail (EMPATHY_IS_TP_FILE (tp_file));
993 tp_file->priv->in_stream = G_INPUT_STREAM (g_file_read (gfile, NULL, error));
997 g_value_init (¬hing, G_TYPE_STRING);
998 g_value_set_static_string (¬hing, "");
1000 tp_cli_channel_type_file_transfer_call_provide_file (tp_file->priv->channel,
1001 -1, TP_SOCKET_ADDRESS_TYPE_UNIX, TP_SOCKET_ACCESS_CONTROL_LOCALHOST,
1002 ¬hing, tp_file_method_cb, NULL, NULL, G_OBJECT (tp_file));
1006 * empathy_tp_file_get_contact:
1007 * @tp_file: an #EmpathyTpFile
1009 * Returns the #EmpathyContact that @tp_file is open with.
1011 * Return value: the #EmpathyContact that @tp_file is open with.
1014 empathy_tp_file_get_contact (EmpathyTpFile *tp_file)
1016 g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file), NULL);
1017 return tp_file->priv->contact;
1021 empathy_tp_file_get_filename (EmpathyTpFile *tp_file)
1023 g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file), NULL);
1024 return tp_file->priv->filename;
1028 * empathy_tp_file_is_incoming:
1029 * @tp_file: an #EmpathyTpFile
1031 * Returns whether @tp_file is incoming.
1033 * Return value: %TRUE if the @tp_file is incoming, otherwise %FALSE
1036 empathy_tp_file_is_incoming (EmpathyTpFile *tp_file)
1038 g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file), FALSE);
1039 return tp_file->priv->incoming;
1043 * empathy_tp_file_get_state:
1044 * @tp_file: an #EmpathyTpFile
1045 * @reason: return location for state change reason, or %NULL
1047 * Gets the current state of @tp_file. If @reason is not %NULL, then
1048 * it is set to the reason of the last state change.
1050 * Return value: a #TpFileTransferState
1053 empathy_tp_file_get_state (EmpathyTpFile *tp_file,
1054 TpFileTransferStateChangeReason *reason)
1056 g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file),
1057 TP_FILE_TRANSFER_STATE_NONE);
1060 *reason = tp_file->priv->state_change_reason;
1062 return tp_file->priv->state;
1066 * empathy_tp_file_get_size:
1067 * @tp_file: an #EmpathyTpFile
1069 * Gets the size of the file being transferred over @tp_file, in bytes.
1071 * Return value: the size of the file being transferred, in bytes
1074 empathy_tp_file_get_size (EmpathyTpFile *tp_file)
1076 g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file),
1077 EMPATHY_TP_FILE_UNKNOWN_SIZE);
1078 return tp_file->priv->size;
1082 * empathy_tp_file_get_transferred_bytes:
1083 * @tp_file: an #EmpathyTpFile
1085 * Gets the number of transferred bytes of @tp_file so far, in bytes.
1087 * Return value: number of transferred bytes of @tp_file, in bytes
1090 empathy_tp_file_get_transferred_bytes (EmpathyTpFile *tp_file)
1092 g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file), 0);
1093 return tp_file->priv->transferred_bytes;
1097 * empathy_tp_file_get_remaining_time:
1098 * @tp_file: a #EmpathyTpFile
1100 * Gets the estimated time remaining of @tp_file, in seconds.
1102 * Return value: the estimated time remaining of @tp_file, in seconds
1105 empathy_tp_file_get_remaining_time (EmpathyTpFile *tp_file)
1107 g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file), -1);
1109 if (tp_file->priv->size == EMPATHY_TP_FILE_UNKNOWN_SIZE)
1112 if (tp_file->priv->transferred_bytes == tp_file->priv->size)
1115 return tp_file->priv->remaining_time;
1119 * empathy_tp_file_get_speed:
1120 * @tp_file: an #EmpathyTpFile
1122 * Gets the current speed of the transfer @tp_file, in bytes per
1125 * Return value: the current speed of the transfer @tp_file, in
1129 empathy_tp_file_get_speed (EmpathyTpFile *tp_file)
1131 g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file), 0);
1133 if (tp_file->priv->transferred_bytes == tp_file->priv->size)
1136 return tp_file->priv->speed;
1140 empathy_tp_file_get_content_type (EmpathyTpFile *tp_file)
1142 g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file), NULL);
1143 return tp_file->priv->content_type;
1147 * empathy_tp_file_cancel:
1148 * @tp_file: an #EmpathyTpFile
1150 * Cancels the file transfer, @tp_file.
1153 empathy_tp_file_cancel (EmpathyTpFile *tp_file)
1155 g_return_if_fail (EMPATHY_IS_TP_FILE (tp_file));
1157 DEBUG ("Closing channel..");
1158 tp_cli_channel_call_close (tp_file->priv->channel, -1,
1159 NULL, NULL, NULL, NULL);
1161 if (tp_file->priv->cancellable != NULL)
1162 g_cancellable_cancel (tp_file->priv->cancellable);
1166 * empathy_tp_file_is_ready:
1167 * @tp_file: an #EmpathyTpFile
1169 * Returns whether the file channel @tp_file is ready for use.
1171 * @tp_file is classed as ready if its state is no longer
1172 * %TP_FILE_TRANSFER_STATE_NONE, or if details about the remote
1173 * contact have been fully received.
1175 * Return value: %TRUE if @tp_file is ready for use
1178 empathy_tp_file_is_ready (EmpathyTpFile *tp_file)
1180 g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file), FALSE);
1182 return tp_file->priv->ready;
1186 empathy_tp_file_class_init (EmpathyTpFileClass *klass)
1188 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1190 object_class->finalize = tp_file_finalize;
1191 object_class->constructor = tp_file_constructor;
1192 object_class->get_property = tp_file_get_property;
1193 object_class->set_property = tp_file_set_property;
1195 /* Construct-only properties */
1198 * EmpathyTpFile:channel:
1200 * The #TpChannel associated with the #EmpathyTpFile.
1202 g_object_class_install_property (object_class,
1204 g_param_spec_object ("channel",
1205 "telepathy channel",
1206 "The file transfer channel",
1209 G_PARAM_CONSTRUCT_ONLY));
1212 * EmpathyTpFile:state:
1214 * The #TpFileTransferState of the #EmpathyTpFile.
1216 g_object_class_install_property (object_class,
1218 g_param_spec_uint ("state",
1219 "state of the transfer",
1220 "The file transfer state",
1225 G_PARAM_CONSTRUCT));
1228 * EmpathyTpFile:incoming:
1230 * Whether the #EmpathyTpFile is incoming.
1232 g_object_class_install_property (object_class,
1234 g_param_spec_boolean ("incoming",
1236 "Whether the transfer is incoming",
1239 G_PARAM_CONSTRUCT));
1242 * EmpathyTpFile:ready:
1244 * Whether the #EmpathyTpFile is ready to use.
1246 g_object_class_install_property (object_class,
1248 g_param_spec_boolean ("ready",
1250 "Whether the object is ready",
1255 * EmpathyTpFile:filename:
1257 * The name of the file being transferred.
1259 g_object_class_install_property (object_class,
1261 g_param_spec_string ("filename",
1262 "name of the transfer",
1263 "The file transfer filename",
1265 G_PARAM_READWRITE));
1268 * EmpathyTpFile:size:
1270 * The size of the file being transferred.
1272 g_object_class_install_property (object_class,
1274 g_param_spec_uint64 ("size",
1276 "The file transfer size",
1280 G_PARAM_READWRITE));
1283 * EmpathyTpFile:content-type:
1285 * The content-type of the file being transferred.
1287 g_object_class_install_property (object_class,
1289 g_param_spec_string ("content-type",
1290 "file transfer content-type",
1291 "The file transfer content-type",
1293 G_PARAM_READWRITE));
1296 * EmpathyTpFile:content-hash-type:
1298 * The type of hash type stored in #EmpathyTpFile:content-hash,
1299 * from #TpFileHashType.
1301 g_object_class_install_property (object_class,
1302 PROP_CONTENT_HASH_TYPE,
1303 g_param_spec_uint ("content-hash-type",
1304 "file transfer hash type",
1305 "The type of the file transfer hash",
1309 G_PARAM_READWRITE));
1312 * EmpathyTpFile:content-hash:
1314 * A hash of the contents of the file being transferred.
1316 g_object_class_install_property (object_class,
1318 g_param_spec_string ("content-hash",
1319 "file transfer hash",
1320 "The hash of the transfer's contents",
1322 G_PARAM_READWRITE));
1325 * EmpathyTpFile:transferred-bytes:
1327 * The number of bytes transferred in the #EmpathyTpFile.
1329 g_object_class_install_property (object_class,
1330 PROP_TRANSFERRED_BYTES,
1331 g_param_spec_uint64 ("transferred-bytes",
1332 "bytes transferred",
1333 "The number of bytes transferred",
1337 G_PARAM_READWRITE));
1340 * EmpathyTpFile::refresh:
1341 * @tp_file: the #EmpathyTpFile
1343 * The progress of @tp_file has changed. This can either be an update
1344 * in the number of bytes transferred, or it can be to inform of the
1345 * transfer stalling.
1347 * This signal is designed for clients to provide more user feedback
1348 * when something to do with @tp_file changes. To avoid emitting this
1349 * signal too much, it is guaranteed that it will only ever be fired
1350 * at least every two seconds.
1352 signals[REFRESH] = g_signal_new ("refresh", G_TYPE_FROM_CLASS (klass),
1353 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
1354 g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
1356 g_type_class_add_private (object_class, sizeof (EmpathyTpFilePriv));