update credits
[LibreOffice.git] / tubes / source / file-transfer-helper.c
blob6450343bbb0911945b31c888905a8c3af198a9e8
1 /*
2 * empathy-ft-handler.c - Source for EmpathyFTHandler
3 * Copyright © 2009, 2012 Collabora Ltd.
4 * Copyright © 2009 Frédéric Péters
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 * Author: Cosimo Cecchi <cosimo.cecchi@collabora.co.uk>
23 /* empathy-ft-handler.c */
25 #include <glib.h>
26 #include <glib/gi18n.h>
27 #include <telepathy-glib/account-channel-request.h>
28 #include <telepathy-glib/util.h>
29 #include <telepathy-glib/dbus.h>
30 #include <telepathy-glib/interfaces.h>
32 #include <tubes/file-transfer-helper.h>
34 #define DEBUG(...)
36 /**
37 * SECTION:empathy-ft-handler
38 * @title: EmpathyFTHandler
39 * @short_description: an object representing a File Transfer
40 * @include: libempathy/empathy-ft-handler
42 * #EmpathyFTHandler is the object which represents a File Transfer with all
43 * its properties.
44 * The creation of an #EmpathyFTHandler is done with
45 * empathy_ft_handler_new_outgoing() or empathy_ft_handler_new_incoming(),
46 * even though clients should not need to call them directly, as
47 * #EmpathyFTFactory does it for them. Remember that for the file transfer
48 * to work with an incoming handler,
49 * empathy_ft_handler_incoming_set_destination() should be called after
50 * empathy_ft_handler_new_incoming(). #EmpathyFTFactory does this
51 * automatically.
52 * It's important to note that, as the creation of the handlers is async, once
53 * an handler is created, it already has all the interesting properties set,
54 * like filename, total bytes, content type and so on, making it useful
55 * to be displayed in an UI.
56 * The transfer API works like a state machine; it has three signals,
57 * ::transfer-started, ::transfer-progress, ::transfer-done, which will be
58 * emitted in the relevant phases.
59 * In addition, if the handler is created with checksumming enabled,
60 * other three signals (::hashing-started, ::hashing-progress, ::hashing-done)
61 * will be emitted before or after the transfer, depending on the direction
62 * (respectively outgoing and incoming) of the handler.
63 * At any time between the call to empathy_ft_handler_start_transfer() and
64 * the last signal, a ::transfer-error can be emitted, indicating that an
65 * error has happened in the operation. The message of the error is localized
66 * to use in an UI.
69 G_DEFINE_TYPE (EmpathyFTHandler, empathy_ft_handler, G_TYPE_OBJECT)
71 #define BUFFER_SIZE 4096
73 enum {
74 PROP_CHANNEL = 1,
75 PROP_G_FILE,
76 PROP_ACCOUNT,
77 PROP_CONTACT,
78 PROP_CONTENT_TYPE,
79 PROP_DESCRIPTION,
80 PROP_FILENAME,
81 PROP_MODIFICATION_TIME,
82 PROP_TOTAL_BYTES,
83 PROP_TRANSFERRED_BYTES,
84 PROP_USER_ACTION_TIME
87 enum {
88 HASHING_STARTED,
89 HASHING_PROGRESS,
90 HASHING_DONE,
91 TRANSFER_STARTED,
92 TRANSFER_PROGRESS,
93 TRANSFER_DONE,
94 TRANSFER_ERROR,
95 LAST_SIGNAL
98 typedef struct {
99 GInputStream *stream;
100 GError *error /* comment to make the style checker happy */;
101 guchar *buffer;
102 GChecksum *checksum;
103 gssize total_read;
104 guint64 total_bytes;
105 EmpathyFTHandler *handler;
106 } HashingData;
108 typedef struct {
109 EmpathyFTHandlerReadyCallback callback;
110 gpointer user_data;
111 EmpathyFTHandler *handler;
112 } CallbacksData;
114 /* private data */
115 struct _EmpathyFTHandlerPriv {
116 gboolean dispose_run;
118 GFile *gfile;
119 TpFileTransferChannel *channel;
120 GCancellable *cancellable;
121 gboolean use_hash;
123 /* request for the new transfer */
124 GHashTable *request;
126 /* transfer properties */
127 TpAccount *account;
128 TpContact *contact;
129 gchar *content_type;
130 gchar *filename;
131 gchar *description;
132 guint64 total_bytes;
133 guint64 transferred_bytes;
134 guint64 mtime;
135 gchar *content_hash;
136 TpFileHashType content_hash_type;
137 gchar *service_name;
139 gint64 user_action_time;
141 /* time and speed */
142 gdouble speed;
143 guint remaining_time;
144 gint64 last_update_time;
146 gboolean is_completed;
149 static guint signals[LAST_SIGNAL] = { 0 };
151 static gboolean do_hash_job_incoming (GIOSchedulerJob *job,
152 GCancellable *cancellable, gpointer user_data);
154 /* GObject implementations */
155 static void
156 do_get_property (GObject *object,
157 guint property_id,
158 GValue *value,
159 GParamSpec *pspec)
161 EmpathyFTHandler *self = EMPATHY_FT_HANDLER (object);
162 EmpathyFTHandlerPriv *priv = self->priv;
164 switch (property_id)
166 case PROP_ACCOUNT:
167 g_value_set_object (value, priv->account);
168 break;
169 case PROP_CONTACT:
170 g_value_set_object (value, priv->contact);
171 break;
172 case PROP_CONTENT_TYPE:
173 g_value_set_string (value, priv->content_type);
174 break;
175 case PROP_DESCRIPTION:
176 g_value_set_string (value, priv->description);
177 break;
178 case PROP_FILENAME:
179 g_value_set_string (value, priv->filename);
180 break;
181 case PROP_MODIFICATION_TIME:
182 g_value_set_uint64 (value, priv->mtime);
183 break;
184 case PROP_TOTAL_BYTES:
185 g_value_set_uint64 (value, priv->total_bytes);
186 break;
187 case PROP_TRANSFERRED_BYTES:
188 g_value_set_uint64 (value, priv->transferred_bytes);
189 break;
190 case PROP_G_FILE:
191 g_value_set_object (value, priv->gfile);
192 break;
193 case PROP_CHANNEL:
194 g_value_set_object (value, priv->channel);
195 break;
196 case PROP_USER_ACTION_TIME:
197 g_value_set_int64 (value, priv->user_action_time);
198 break;
199 default:
200 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
204 static void
205 do_set_property (GObject *object,
206 guint property_id,
207 const GValue *value,
208 GParamSpec *pspec)
210 EmpathyFTHandler *self = EMPATHY_FT_HANDLER (object);
211 EmpathyFTHandlerPriv *priv = self->priv;
213 switch (property_id)
215 case PROP_ACCOUNT:
216 priv->account = g_value_dup_object (value);
217 break;
218 case PROP_CONTACT:
219 priv->contact = g_value_dup_object (value);
220 break;
221 case PROP_CONTENT_TYPE:
222 priv->content_type = g_value_dup_string (value);
223 break;
224 case PROP_DESCRIPTION:
225 priv->description = g_value_dup_string (value);
226 break;
227 case PROP_FILENAME:
228 priv->filename = g_value_dup_string (value);
229 break;
230 case PROP_MODIFICATION_TIME:
231 priv->mtime = g_value_get_uint64 (value);
232 break;
233 case PROP_TOTAL_BYTES:
234 priv->total_bytes = g_value_get_uint64 (value);
235 break;
236 case PROP_TRANSFERRED_BYTES:
237 priv->transferred_bytes = g_value_get_uint64 (value);
238 break;
239 case PROP_G_FILE:
240 priv->gfile = g_value_dup_object (value);
241 break;
242 case PROP_CHANNEL:
243 priv->channel = g_value_dup_object (value);
244 break;
245 case PROP_USER_ACTION_TIME:
246 priv->user_action_time = g_value_get_int64 (value);
247 break;
248 default:
249 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
253 static void
254 do_dispose (GObject *object)
256 EmpathyFTHandler *self = EMPATHY_FT_HANDLER (object);
257 EmpathyFTHandlerPriv *priv = self->priv;
259 if (priv->dispose_run)
260 return;
262 priv->dispose_run = TRUE;
264 if (priv->account != NULL) {
265 g_object_unref (priv->account);
266 priv->account = NULL;
269 if (priv->contact != NULL) {
270 g_object_unref (priv->contact);
271 priv->contact = NULL;
274 if (priv->gfile != NULL) {
275 g_object_unref (priv->gfile);
276 priv->gfile = NULL;
279 if (priv->channel != NULL) {
280 tp_channel_close_async (TP_CHANNEL (priv->channel), NULL, NULL);
281 g_object_unref (priv->channel);
282 priv->channel = NULL;
285 if (priv->cancellable != NULL) {
286 g_object_unref (priv->cancellable);
287 priv->cancellable = NULL;
290 if (priv->request != NULL)
292 g_hash_table_unref (priv->request);
293 priv->request = NULL;
296 G_OBJECT_CLASS (empathy_ft_handler_parent_class)->dispose (object);
299 static void
300 do_finalize (GObject *object)
302 EmpathyFTHandler *self = EMPATHY_FT_HANDLER (object);
303 EmpathyFTHandlerPriv *priv = self->priv;
305 DEBUG ("%p", object);
307 g_free (priv->content_type);
308 priv->content_type = NULL;
310 g_free (priv->filename);
311 priv->filename = NULL;
313 g_free (priv->description);
314 priv->description = NULL;
316 g_free (priv->content_hash);
317 priv->content_hash = NULL;
319 g_free (priv->service_name);
320 priv->service_name = NULL;
322 G_OBJECT_CLASS (empathy_ft_handler_parent_class)->finalize (object);
325 static void
326 empathy_ft_handler_class_init (EmpathyFTHandlerClass *klass)
328 GObjectClass *object_class = G_OBJECT_CLASS (klass);
329 GParamSpec *param_spec;
331 g_type_class_add_private (klass, sizeof (EmpathyFTHandlerPriv));
333 object_class->get_property = do_get_property;
334 object_class->set_property = do_set_property;
335 object_class->dispose = do_dispose;
336 object_class->finalize = do_finalize;
338 /* properties */
341 * EmpathyFTHandler:account:
343 * The local #TpAccount for the file transfer
345 param_spec = g_param_spec_object ("account",
346 "account", "The remote account",
347 TP_TYPE_ACCOUNT,
348 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY);
349 g_object_class_install_property (object_class, PROP_ACCOUNT, param_spec);
352 * EmpathyFTHandler:contact:
354 * The remote #TpContact for the transfer
356 param_spec = g_param_spec_object ("contact",
357 "contact", "The remote contact",
358 TP_TYPE_CONTACT,
359 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY);
360 g_object_class_install_property (object_class, PROP_CONTACT, param_spec);
363 * EmpathyFTHandler:content-type:
365 * The content type of the file being transferred
367 param_spec = g_param_spec_string ("content-type",
368 "content-type", "The content type of the file", NULL,
369 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
370 g_object_class_install_property (object_class,
371 PROP_CONTENT_TYPE, param_spec);
374 * EmpathyFTHandler:description:
376 * The description of the file being transferred
378 param_spec = g_param_spec_string ("description",
379 "description", "The description of the file", NULL,
380 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
381 g_object_class_install_property (object_class,
382 PROP_DESCRIPTION, param_spec);
385 * EmpathyFTHandler:filename:
387 * The name of the file being transferred
389 param_spec = g_param_spec_string ("filename",
390 "filename", "The name of the file", NULL,
391 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
392 g_object_class_install_property (object_class,
393 PROP_FILENAME, param_spec);
396 * EmpathyFTHandler:modification-time:
398 * The modification time of the file being transferred
400 param_spec = g_param_spec_uint64 ("modification-time",
401 "modification-time", "The mtime of the file", 0,
402 G_MAXUINT64, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
403 g_object_class_install_property (object_class,
404 PROP_MODIFICATION_TIME, param_spec);
407 * EmpathyFTHandler:total-bytes:
409 * The size (in bytes) of the file being transferred
411 param_spec = g_param_spec_uint64 ("total-bytes",
412 "total-bytes", "The size of the file", 0,
413 G_MAXUINT64, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
414 g_object_class_install_property (object_class,
415 PROP_TOTAL_BYTES, param_spec);
418 * EmpathyFTHandler:transferred-bytes:
420 * The number of the bytes already transferred
422 param_spec = g_param_spec_uint64 ("transferred-bytes",
423 "transferred-bytes", "The number of bytes already transferred", 0,
424 G_MAXUINT64, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
425 g_object_class_install_property (object_class,
426 PROP_TRANSFERRED_BYTES, param_spec);
429 * EmpathyFTHandler:gfile:
431 * The #GFile object where the transfer actually happens
433 param_spec = g_param_spec_object ("gfile",
434 "gfile", "The GFile we're handling",
435 G_TYPE_FILE,
436 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
437 g_object_class_install_property (object_class, PROP_G_FILE, param_spec);
440 * EmpathyFTHandler:channel:
442 * The underlying #TpFileTransferChannel managing the transfer
444 param_spec = g_param_spec_object ("channel",
445 "channel", "The file transfer channel",
446 TP_TYPE_FILE_TRANSFER_CHANNEL,
447 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY);
448 g_object_class_install_property (object_class, PROP_CHANNEL, param_spec);
450 param_spec = g_param_spec_int64 ("user-action-time", "user action time",
451 "User action time",
452 0, G_MAXINT64, 0,
453 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY);
454 g_object_class_install_property (object_class, PROP_USER_ACTION_TIME,
455 param_spec);
457 /* signals */
460 * EmpathyFTHandler::transfer-started
461 * @handler: the object which has received the signal
462 * @channel: the #TpFileTransferChannel for which the transfer has started
464 * This signal is emitted when the actual transfer starts.
466 signals[TRANSFER_STARTED] =
467 g_signal_new ("transfer-started", G_TYPE_FROM_CLASS (klass),
468 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
469 g_cclosure_marshal_generic,
470 G_TYPE_NONE,
471 1, TP_TYPE_FILE_TRANSFER_CHANNEL);
474 * EmpathyFTHandler::transfer-done
475 * @handler: the object which has received the signal
476 * @channel: the #TpFileTransferChannel for which the transfer has started
478 * This signal will be emitted when the actual transfer is completed
479 * successfully.
481 signals[TRANSFER_DONE] =
482 g_signal_new ("transfer-done", G_TYPE_FROM_CLASS (klass),
483 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
484 g_cclosure_marshal_generic,
485 G_TYPE_NONE,
486 1, TP_TYPE_FILE_TRANSFER_CHANNEL);
489 * EmpathyFTHandler::transfer-error
490 * @handler: the object which has received the signal
491 * @error: a #GError
493 * This signal can be emitted anytime between the call to
494 * empathy_ft_handler_start_transfer() and the last expected signal
495 * (::transfer-done or ::hashing-done), and it's guaranteed to be the last
496 * signal coming from the handler, meaning that no other operation will
497 * take place after this signal.
499 signals[TRANSFER_ERROR] =
500 g_signal_new ("transfer-error", G_TYPE_FROM_CLASS (klass),
501 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
502 g_cclosure_marshal_generic,
503 G_TYPE_NONE,
504 1, G_TYPE_POINTER);
507 * EmpathyFTHandler::transfer-progress
508 * @handler: the object which has received the signal
509 * @current_bytes: the bytes currently transferred
510 * @total_bytes: the total bytes of the handler
511 * @remaining_time: the number of seconds remaining for the transfer
512 * to be completed
513 * @speed: the current speed of the transfer (in KB/s)
515 * This signal is emitted to notify clients of the progress of the
516 * transfer.
518 signals[TRANSFER_PROGRESS] =
519 g_signal_new ("transfer-progress", G_TYPE_FROM_CLASS (klass),
520 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
521 g_cclosure_marshal_generic,
522 G_TYPE_NONE,
523 4, G_TYPE_UINT64, G_TYPE_UINT64, G_TYPE_UINT, G_TYPE_DOUBLE);
526 * EmpathyFTHandler::hashing-started
527 * @handler: the object which has received the signal
529 * This signal is emitted when the hashing operation of the handler
530 * is started. Note that this might happen or not, depending on the CM
531 * and remote contact capabilities. Clients shoud use
532 * empathy_ft_handler_get_use_hash() before calling
533 * empathy_ft_handler_start_transfer() to know whether they should connect
534 * to this signal.
536 signals[HASHING_STARTED] =
537 g_signal_new ("hashing-started", G_TYPE_FROM_CLASS (klass),
538 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
539 g_cclosure_marshal_generic,
540 G_TYPE_NONE, 0);
543 * EmpathyFTHandler::hashing-progress
544 * @handler: the object which has received the signal
545 * @current_bytes: the bytes currently hashed
546 * @total_bytes: the total bytes of the handler
548 * This signal is emitted to notify clients of the progress of the
549 * hashing operation.
551 signals[HASHING_PROGRESS] =
552 g_signal_new ("hashing-progress", G_TYPE_FROM_CLASS (klass),
553 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
554 g_cclosure_marshal_generic,
555 G_TYPE_NONE,
556 2, G_TYPE_UINT64, G_TYPE_UINT64);
559 * EmpathyFTHandler::hashing-done
560 * @handler: the object which has received the signal
562 * This signal is emitted when the hashing operation of the handler
563 * is completed.
565 signals[HASHING_DONE] =
566 g_signal_new ("hashing-done", G_TYPE_FROM_CLASS (klass),
567 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
568 g_cclosure_marshal_generic,
569 G_TYPE_NONE, 0);
572 static void
573 empathy_ft_handler_init (EmpathyFTHandler *self)
575 EmpathyFTHandlerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
576 EMPATHY_TYPE_FT_HANDLER, EmpathyFTHandlerPriv);
578 self->priv = priv;
579 priv->cancellable = g_cancellable_new ();
582 /* private functions */
584 static void
585 hash_data_free (HashingData *data)
587 g_free (data->buffer);
589 if (data->stream != NULL)
590 g_object_unref (data->stream);
592 if (data->checksum != NULL)
593 g_checksum_free (data->checksum);
595 if (data->error != NULL)
596 g_error_free (data->error);
598 if (data->handler != NULL)
599 g_object_unref (data->handler);
601 g_slice_free (HashingData, data);
604 static GChecksumType
605 tp_file_hash_to_g_checksum (TpFileHashType type)
607 GChecksumType retval;
609 switch (type)
611 case TP_FILE_HASH_TYPE_MD5:
612 retval = G_CHECKSUM_MD5;
613 break;
614 case TP_FILE_HASH_TYPE_SHA1:
615 retval = G_CHECKSUM_SHA1;
616 break;
617 case TP_FILE_HASH_TYPE_SHA256:
618 retval = G_CHECKSUM_SHA256;
619 break;
620 case TP_FILE_HASH_TYPE_NONE:
621 default:
622 g_assert_not_reached ();
623 break;
626 return retval;
629 static void
630 check_hash_incoming (EmpathyFTHandler *handler)
632 HashingData *hash_data;
633 EmpathyFTHandlerPriv *priv = handler->priv;
635 if (!tp_str_empty (priv->content_hash))
637 hash_data = g_slice_new0 (HashingData);
638 hash_data->total_bytes = priv->total_bytes;
639 hash_data->handler = g_object_ref (handler);
640 hash_data->checksum = g_checksum_new
641 (tp_file_hash_to_g_checksum (priv->content_hash_type));
643 g_signal_emit (handler, signals[HASHING_STARTED], 0);
645 g_io_scheduler_push_job (do_hash_job_incoming, hash_data, NULL,
646 G_PRIORITY_DEFAULT, priv->cancellable);
650 static void
651 emit_error_signal (EmpathyFTHandler *handler,
652 const GError *error)
654 EmpathyFTHandlerPriv *priv = handler->priv;
656 DEBUG ("Error in transfer: %s\n", error->message);
658 if (!g_cancellable_is_cancelled (priv->cancellable))
659 g_cancellable_cancel (priv->cancellable);
661 g_signal_emit (handler, signals[TRANSFER_ERROR], 0, error);
664 static gint64
665 time_get_current (void)
667 GDateTime *now;
668 gint64 result;
670 now = g_date_time_new_now_utc ();
671 result = g_date_time_to_unix (now);
672 g_date_time_unref (now);
674 return result;
677 static void
678 update_remaining_time_and_speed (EmpathyFTHandler *handler,
679 guint64 transferred_bytes)
681 EmpathyFTHandlerPriv *priv = handler->priv;
682 gint64 elapsed_time, current_time;
683 guint64 transferred, last_transferred_bytes;
684 gdouble speed;
685 gint remaining_time;
687 last_transferred_bytes = priv->transferred_bytes;
688 priv->transferred_bytes = transferred_bytes;
690 current_time = time_get_current ();
691 elapsed_time = current_time - priv->last_update_time;
693 if (elapsed_time >= 1)
695 transferred = transferred_bytes - last_transferred_bytes;
696 speed = (gdouble) transferred / (gdouble) elapsed_time;
697 remaining_time = (priv->total_bytes - priv->transferred_bytes) / speed;
698 priv->speed = speed;
699 priv->remaining_time = remaining_time;
700 priv->last_update_time = current_time;
704 static void
705 ft_transfer_transferred_bytes_cb (TpFileTransferChannel *channel,
706 GParamSpec *pspec,
707 EmpathyFTHandler *handler)
709 EmpathyFTHandlerPriv *priv = handler->priv;
710 guint64 bytes;
712 (void)pspec; /* suppress unused-parameter warning */
714 if (empathy_ft_handler_is_cancelled (handler))
715 return;
717 bytes = tp_file_transfer_channel_get_transferred_bytes (channel);
719 if (priv->transferred_bytes == 0)
721 priv->last_update_time = time_get_current ();
722 g_signal_emit (handler, signals[TRANSFER_STARTED], 0, channel);
725 if (priv->transferred_bytes != bytes)
727 update_remaining_time_and_speed (handler, bytes);
729 g_signal_emit (handler, signals[TRANSFER_PROGRESS], 0,
730 bytes, priv->total_bytes, priv->remaining_time,
731 priv->speed);
735 static void
736 ft_transfer_provide_cb (GObject *source,
737 GAsyncResult *result,
738 gpointer user_data)
740 TpFileTransferChannel *channel = TP_FILE_TRANSFER_CHANNEL (source);
741 EmpathyFTHandler *handler = user_data;
742 GError *error = NULL;
744 if (!tp_file_transfer_channel_provide_file_finish (channel, result, &error))
746 emit_error_signal (handler, error);
747 g_clear_error (&error);
751 static void
752 ft_transfer_accept_cb (GObject *source,
753 GAsyncResult *result,
754 gpointer user_data)
756 TpFileTransferChannel *channel = TP_FILE_TRANSFER_CHANNEL (source);
757 EmpathyFTHandler *handler = user_data;
758 GError *error = NULL;
760 if (!tp_file_transfer_channel_accept_file_finish (channel, result, &error))
762 emit_error_signal (handler, error);
763 g_clear_error (&error);
767 static GError *
768 error_from_state_change_reason (TpFileTransferStateChangeReason reason)
770 const char *string;
771 GError *retval = NULL;
773 string = NULL;
775 switch (reason)
777 case TP_FILE_TRANSFER_STATE_CHANGE_REASON_NONE:
778 string = _("No reason was specified");
779 break;
780 case TP_FILE_TRANSFER_STATE_CHANGE_REASON_REQUESTED:
781 string = _("The change in state was requested");
782 break;
783 case TP_FILE_TRANSFER_STATE_CHANGE_REASON_LOCAL_STOPPED:
784 string = _("You canceled the file transfer");
785 break;
786 case TP_FILE_TRANSFER_STATE_CHANGE_REASON_REMOTE_STOPPED:
787 string = _("The other participant canceled the file transfer");
788 break;
789 case TP_FILE_TRANSFER_STATE_CHANGE_REASON_LOCAL_ERROR:
790 string = _("Error while trying to transfer the file");
791 break;
792 case TP_FILE_TRANSFER_STATE_CHANGE_REASON_REMOTE_ERROR:
793 string = _("The other participant is unable to transfer the file");
794 break;
795 default:
796 string = _("Unknown reason");
797 break;
800 retval = g_error_new_literal (EMPATHY_FT_ERROR_QUARK,
801 EMPATHY_FT_ERROR_TP_ERROR, string);
803 return retval;
806 static void
807 ft_transfer_state_cb (TpFileTransferChannel *channel,
808 GParamSpec *pspec,
809 EmpathyFTHandler *handler)
811 EmpathyFTHandlerPriv *priv = handler->priv;
812 TpFileTransferStateChangeReason reason;
813 TpFileTransferState state = tp_file_transfer_channel_get_state (
814 channel, &reason);
816 (void)pspec; /* suppress unused-parameter warning */
818 if (state == TP_FILE_TRANSFER_STATE_COMPLETED)
820 priv->is_completed = TRUE;
821 g_signal_emit (handler, signals[TRANSFER_DONE], 0, channel);
823 tp_channel_close_async (TP_CHANNEL (channel), NULL, NULL);
825 if (empathy_ft_handler_is_incoming (handler) && priv->use_hash)
827 check_hash_incoming (handler);
830 else if (state == TP_FILE_TRANSFER_STATE_CANCELLED)
832 GError *error = error_from_state_change_reason (reason);
833 emit_error_signal (handler, error);
834 g_clear_error (&error);
838 static void
839 ft_handler_create_channel_cb (GObject *source,
840 GAsyncResult *result,
841 gpointer user_data)
843 EmpathyFTHandler *handler = user_data;
844 EmpathyFTHandlerPriv *priv = handler->priv;
845 GError *error = NULL;
846 TpChannel *channel;
848 DEBUG ("Dispatcher create channel CB");
850 channel = tp_account_channel_request_create_and_handle_channel_finish (
851 TP_ACCOUNT_CHANNEL_REQUEST (source), result, NULL, &error);
853 if (channel == NULL)
854 DEBUG ("Failed to request FT channel: %s", error->message);
855 else
856 g_cancellable_set_error_if_cancelled (priv->cancellable, &error);
858 if (error != NULL)
860 emit_error_signal (handler, error);
862 g_clear_object (&channel);
863 g_error_free (error);
864 return;
867 priv->channel = TP_FILE_TRANSFER_CHANNEL (channel);
869 tp_g_signal_connect_object (priv->channel, "notify::state",
870 G_CALLBACK (ft_transfer_state_cb), handler, 0);
871 tp_g_signal_connect_object (priv->channel, "notify::transferred-bytes",
872 G_CALLBACK (ft_transfer_transferred_bytes_cb), handler, 0);
874 tp_file_transfer_channel_provide_file_async (priv->channel, priv->gfile,
875 ft_transfer_provide_cb, handler);
878 static void
879 ft_handler_push_to_dispatcher (EmpathyFTHandler *handler)
881 EmpathyFTHandlerPriv *priv = handler->priv;
882 TpAccountChannelRequest *req;
884 DEBUG ("Pushing request to the dispatcher");
886 req = tp_account_channel_request_new (priv->account, priv->request,
887 priv->user_action_time);
889 tp_account_channel_request_create_and_handle_channel_async (req, NULL,
890 ft_handler_create_channel_cb, handler);
892 g_object_unref (req);
895 static void
896 ft_handler_populate_outgoing_request (EmpathyFTHandler *handler)
898 guint contact_handle;
899 EmpathyFTHandlerPriv *priv = handler->priv;
900 gchar *uri;
902 contact_handle = tp_contact_get_handle (priv->contact);
903 uri = g_file_get_uri (priv->gfile);
905 priv->request = tp_asv_new (
906 TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
907 TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER,
908 TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
909 TP_HANDLE_TYPE_CONTACT,
910 TP_PROP_CHANNEL_TARGET_HANDLE, G_TYPE_UINT,
911 contact_handle,
912 TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_CONTENT_TYPE, G_TYPE_STRING,
913 priv->content_type,
914 TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_FILENAME, G_TYPE_STRING,
915 priv->filename,
916 TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_SIZE, G_TYPE_UINT64,
917 priv->total_bytes,
918 TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_DATE, G_TYPE_UINT64,
919 priv->mtime,
920 TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_URI, G_TYPE_STRING, uri,
921 NULL);
923 if (priv->service_name != NULL)
924 tp_asv_set_string (priv->request, TP_PROP_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA_SERVICE_NAME, priv->service_name);
926 if (priv->description != NULL)
927 tp_asv_set_string (priv->request, TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_DESCRIPTION, priv->description);
929 g_free (uri);
932 static gboolean
933 hash_job_done (gpointer user_data)
935 HashingData *hash_data = user_data;
936 EmpathyFTHandler *handler = hash_data->handler;
937 EmpathyFTHandlerPriv *priv;
938 GError *error = NULL;
940 DEBUG ("Closing stream after hashing.");
942 priv = handler->priv;
944 if (hash_data->error != NULL)
946 error = hash_data->error;
947 hash_data->error = NULL;
948 goto cleanup;
951 DEBUG ("Got file hash %s", g_checksum_get_string (hash_data->checksum));
953 if (empathy_ft_handler_is_incoming (handler))
955 if (g_strcmp0 (g_checksum_get_string (hash_data->checksum),
956 priv->content_hash))
958 DEBUG ("Hash mismatch when checking incoming handler: "
959 "received %s, calculated %s", priv->content_hash,
960 g_checksum_get_string (hash_data->checksum));
962 error = g_error_new_literal (EMPATHY_FT_ERROR_QUARK,
963 EMPATHY_FT_ERROR_HASH_MISMATCH,
964 _("File transfer completed, but the file was corrupted"));
965 goto cleanup;
967 else
969 DEBUG ("Hash verification matched, received %s, calculated %s",
970 priv->content_hash,
971 g_checksum_get_string (hash_data->checksum));
974 else
976 /* set the checksum in the request...
977 * org.freedesktop.Telepathy.Channel.Type.FileTransfer.ContentHash
979 tp_asv_set_string (priv->request,
980 TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_CONTENT_HASH,
981 g_checksum_get_string (hash_data->checksum));
984 cleanup:
986 if (error != NULL)
988 emit_error_signal (handler, error);
989 g_clear_error (&error);
991 else
993 g_signal_emit (handler, signals[HASHING_DONE], 0);
995 if (!empathy_ft_handler_is_incoming (handler))
996 /* the request is complete now, push it to the dispatcher */
997 ft_handler_push_to_dispatcher (handler);
1000 hash_data_free (hash_data);
1002 return FALSE;
1005 static gboolean
1006 emit_hashing_progress (gpointer user_data)
1008 HashingData *hash_data = user_data;
1010 g_signal_emit (hash_data->handler, signals[HASHING_PROGRESS], 0,
1011 (guint64) hash_data->total_read, (guint64) hash_data->total_bytes);
1013 return FALSE;
1016 static gboolean
1017 do_hash_job (GIOSchedulerJob *job,
1018 GCancellable *cancellable,
1019 gpointer user_data)
1021 HashingData *hash_data = user_data;
1022 gssize bytes_read;
1023 GError *error = NULL;
1025 again:
1026 if (hash_data->buffer == NULL)
1027 hash_data->buffer = g_malloc0 (BUFFER_SIZE);
1029 bytes_read = g_input_stream_read (hash_data->stream, hash_data->buffer,
1030 BUFFER_SIZE, cancellable, &error);
1031 if (error != NULL)
1032 goto out;
1034 hash_data->total_read += bytes_read;
1036 /* we now have the chunk */
1037 if (bytes_read > 0)
1039 g_checksum_update (hash_data->checksum, hash_data->buffer, bytes_read);
1040 g_io_scheduler_job_send_to_mainloop_async (job, emit_hashing_progress,
1041 hash_data, NULL);
1043 g_free (hash_data->buffer);
1044 hash_data->buffer = NULL;
1046 goto again;
1048 else
1050 g_input_stream_close (hash_data->stream, cancellable, &error);
1053 out:
1054 if (error != NULL)
1055 hash_data->error = error;
1057 g_io_scheduler_job_send_to_mainloop_async (job, hash_job_done,
1058 hash_data, NULL);
1060 return FALSE;
1063 static gboolean
1064 do_hash_job_incoming (GIOSchedulerJob *job,
1065 GCancellable *cancellable,
1066 gpointer user_data)
1068 HashingData *hash_data = user_data;
1069 EmpathyFTHandler *handler = hash_data->handler;
1070 EmpathyFTHandlerPriv *priv = handler->priv;
1071 GError *error = NULL;
1073 DEBUG ("checking integrity for incoming handler");
1075 /* need to get the stream first */
1076 hash_data->stream =
1077 G_INPUT_STREAM (g_file_read (priv->gfile, cancellable, &error));
1079 if (error != NULL)
1081 hash_data->error = error;
1082 g_io_scheduler_job_send_to_mainloop_async (job, hash_job_done,
1083 hash_data, NULL);
1084 return FALSE;
1087 return do_hash_job (job, cancellable, user_data);
1090 static void
1091 ft_handler_read_async_cb (GObject *source,
1092 GAsyncResult *res,
1093 gpointer user_data)
1095 GFileInputStream *stream;
1096 GError *error = NULL;
1097 HashingData *hash_data;
1098 EmpathyFTHandler *handler = user_data;
1099 EmpathyFTHandlerPriv *priv = handler->priv;
1101 (void)source; /* suppress unused-parameter warning */
1103 DEBUG ("GFile read async CB.");
1105 stream = g_file_read_finish (priv->gfile, res, &error);
1106 if (error != NULL)
1108 emit_error_signal (handler, error);
1109 g_clear_error (&error);
1111 return;
1114 hash_data = g_slice_new0 (HashingData);
1115 hash_data->stream = G_INPUT_STREAM (stream);
1116 hash_data->total_bytes = priv->total_bytes;
1117 hash_data->handler = g_object_ref (handler);
1118 /* FIXME: MD5 is the only ContentHashType supported right now */
1119 hash_data->checksum = g_checksum_new (G_CHECKSUM_MD5);
1121 tp_asv_set_uint32 (priv->request,
1122 TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_CONTENT_HASH_TYPE,
1123 TP_FILE_HASH_TYPE_MD5);
1125 g_signal_emit (handler, signals[HASHING_STARTED], 0);
1127 g_io_scheduler_push_job (do_hash_job, hash_data, NULL,
1128 G_PRIORITY_DEFAULT, priv->cancellable);
1131 static void
1132 callbacks_data_free (gpointer user_data)
1134 CallbacksData *data = user_data;
1136 if (data->handler != NULL)
1137 g_object_unref (data->handler);
1139 g_slice_free (CallbacksData, data);
1142 static gint
1143 cmp_uint (
1144 gconstpointer a,
1145 gconstpointer b)
1147 return *(guint *) a - *(guint *) b;
1150 static gboolean
1151 set_content_hash_type_from_classes (EmpathyFTHandler *handler,
1152 GPtrArray *classes)
1154 GArray *possible_values;
1155 guint value;
1156 gboolean valid;
1157 EmpathyFTHandlerPriv *priv = handler->priv;
1158 gboolean support_ft = FALSE;
1159 guint i;
1161 possible_values = g_array_new (TRUE, TRUE, sizeof (guint));
1163 for (i = 0; i < classes->len; i++)
1165 GHashTable *fixed;
1166 GStrv allowed;
1167 const gchar *chan_type;
1169 tp_value_array_unpack (g_ptr_array_index (classes, i), 2,
1170 &fixed, &allowed);
1172 chan_type = tp_asv_get_string (fixed, TP_PROP_CHANNEL_CHANNEL_TYPE);
1174 if (tp_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER))
1175 continue;
1177 if (tp_asv_get_uint32 (fixed, TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL) !=
1178 TP_HANDLE_TYPE_CONTACT)
1179 continue;
1181 support_ft = TRUE;
1183 value = tp_asv_get_uint32
1184 (fixed, TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_CONTENT_HASH_TYPE,
1185 &valid);
1187 if (valid)
1188 g_array_append_val (possible_values, value);
1191 if (!support_ft)
1193 g_array_unref (possible_values);
1194 return FALSE;
1197 if (possible_values->len == 0)
1199 /* there are no channel classes with hash support, disable it. */
1200 priv->use_hash = FALSE;
1201 priv->content_hash_type = TP_FILE_HASH_TYPE_NONE;
1203 goto out;
1206 priv->use_hash = TRUE;
1208 if (possible_values->len == 1)
1210 priv->content_hash_type = g_array_index (possible_values, guint, 0);
1212 else
1214 /* order the array and pick the first non zero, so that MD5
1215 * is the preferred value.
1217 g_array_sort (possible_values, cmp_uint);
1219 if (g_array_index (possible_values, guint, 0) == 0)
1220 priv->content_hash_type = g_array_index (possible_values, guint, 1);
1221 else
1222 priv->content_hash_type = g_array_index (possible_values, guint, 0);
1225 out:
1226 g_array_unref (possible_values);
1228 DEBUG ("Hash enabled %s; setting content hash type as %u",
1229 priv->use_hash ? "True" : "False", priv->content_hash_type);
1231 return TRUE;
1234 static void
1235 check_hashing (CallbacksData *data)
1237 EmpathyFTHandler *handler = data->handler;
1238 EmpathyFTHandlerPriv *priv = handler->priv;
1239 GError *myerr = NULL;
1240 TpCapabilities *caps;
1241 GPtrArray *classes;
1242 TpConnection *conn;
1244 conn = tp_account_get_connection (priv->account);
1246 caps = tp_connection_get_capabilities (conn);
1247 if (caps == NULL)
1249 data->callback (handler, NULL, data->user_data);
1250 goto out;
1253 classes = tp_capabilities_get_channel_classes (caps);
1255 /* set whether we support hash and the type of it */
1256 if (!set_content_hash_type_from_classes (handler, classes))
1258 g_set_error_literal (&myerr, EMPATHY_FT_ERROR_QUARK,
1259 EMPATHY_FT_ERROR_NOT_SUPPORTED,
1260 _("File transfer not supported by remote contact"));
1262 if (!g_cancellable_is_cancelled (priv->cancellable))
1263 g_cancellable_cancel (priv->cancellable);
1265 data->callback (handler, myerr, data->user_data);
1266 g_clear_error (&myerr);
1268 else
1270 /* get back to the caller now */
1271 data->callback (handler, NULL, data->user_data);
1274 out:
1275 callbacks_data_free (data);
1278 static void
1279 ft_handler_complete_request (EmpathyFTHandler *handler)
1281 EmpathyFTHandlerPriv *priv = handler->priv;
1283 /* populate the request table with all the known properties */
1284 ft_handler_populate_outgoing_request (handler);
1286 if (priv->use_hash)
1287 /* start hashing the file */
1288 g_file_read_async (priv->gfile, G_PRIORITY_DEFAULT,
1289 priv->cancellable, ft_handler_read_async_cb, handler);
1290 else
1291 /* push directly the handler to the dispatcher */
1292 ft_handler_push_to_dispatcher (handler);
1295 static void
1296 ft_handler_gfile_ready_cb (GObject *source,
1297 GAsyncResult *res,
1298 CallbacksData *cb_data)
1300 GFileInfo *info;
1301 GError *error = NULL;
1302 GTimeVal mtime;
1303 EmpathyFTHandlerPriv *priv = cb_data->handler->priv;
1305 (void)source; /* suppress unused-parameter warning */
1307 DEBUG ("Got GFileInfo.");
1309 info = g_file_query_info_finish (priv->gfile, res, &error);
1311 if (error != NULL)
1312 goto out;
1314 if (g_file_info_get_file_type (info) != G_FILE_TYPE_REGULAR)
1316 error = g_error_new_literal (EMPATHY_FT_ERROR_QUARK,
1317 EMPATHY_FT_ERROR_INVALID_SOURCE_FILE,
1318 _("The selected file is not a regular file"));
1319 goto out;
1322 priv->total_bytes = g_file_info_get_size (info);
1323 if (priv->total_bytes == 0)
1325 error = g_error_new_literal (EMPATHY_FT_ERROR_QUARK,
1326 EMPATHY_FT_ERROR_EMPTY_SOURCE_FILE,
1327 _("The selected file is empty"));
1328 goto out;
1331 priv->content_type = g_strdup (g_file_info_get_content_type (info));
1332 priv->filename = g_strdup (g_file_info_get_display_name (info));
1333 g_file_info_get_modification_time (info, &mtime);
1334 priv->mtime = mtime.tv_sec;
1335 priv->transferred_bytes = 0;
1336 priv->description = NULL;
1338 g_object_unref (info);
1340 out:
1341 if (error != NULL)
1343 if (!g_cancellable_is_cancelled (priv->cancellable))
1344 g_cancellable_cancel (priv->cancellable);
1346 cb_data->callback (cb_data->handler, error, cb_data->user_data);
1347 g_error_free (error);
1349 callbacks_data_free (cb_data);
1351 else
1353 /* see if FT/hashing are allowed */
1354 check_hashing (cb_data);
1358 static void
1359 channel_prepared_cb (
1360 GObject *source,
1361 GAsyncResult *result,
1362 gpointer user_data)
1364 TpFileTransferChannel *channel = TP_FILE_TRANSFER_CHANNEL (source);
1365 CallbacksData *cb_data = user_data;
1366 EmpathyFTHandler *handler = cb_data->handler;
1367 EmpathyFTHandlerPriv *priv = handler->priv;
1368 GHashTable *properties;
1369 GError *error = NULL;
1371 if (!tp_proxy_prepare_finish (channel, result, &error))
1373 if (!g_cancellable_is_cancelled (priv->cancellable))
1374 g_cancellable_cancel (priv->cancellable);
1376 cb_data->callback (handler, error, cb_data->user_data);
1377 g_clear_error (&error);
1378 callbacks_data_free (cb_data);
1379 return;
1382 properties = tp_channel_borrow_immutable_properties (TP_CHANNEL (channel));
1384 priv->content_hash = g_strdup (
1385 tp_asv_get_string (properties, "ContentHash"));
1387 priv->content_hash_type = tp_asv_get_uint32 (
1388 properties, "ContentHashType", NULL);
1390 priv->contact = g_object_ref (tp_channel_get_target_contact (TP_CHANNEL (channel)));
1392 cb_data->callback (handler, NULL, cb_data->user_data);
1396 /* public methods */
1399 * empathy_ft_handler_new_outgoing:
1400 * @account: the #TpAccount to send @source to
1401 * @contact: the #TpContact to send @source to
1402 * @source: the #GFile to send
1403 * @callback: callback to be called when the handler has been created
1404 * @user_data: user data to be passed to @callback
1406 * Triggers the creation of a new #EmpathyFTHandler for an outgoing transfer.
1408 void
1409 empathy_ft_handler_new_outgoing (
1410 TpAccount *account,
1411 TpContact *contact,
1412 GFile *source,
1413 gint64 action_time,
1414 EmpathyFTHandlerReadyCallback callback,
1415 gpointer user_data)
1417 EmpathyFTHandler *handler;
1418 CallbacksData *data;
1419 EmpathyFTHandlerPriv *priv;
1421 DEBUG ("New handler outgoing");
1423 g_return_if_fail (TP_IS_ACCOUNT (account));
1424 g_return_if_fail (TP_IS_CONTACT (contact));
1425 g_return_if_fail (G_IS_FILE (source));
1427 handler = g_object_new (EMPATHY_TYPE_FT_HANDLER,
1428 "account", account,
1429 "contact", contact,
1430 "gfile", source,
1431 "user-action-time", action_time,
1432 NULL);
1434 priv = handler->priv;
1436 data = g_slice_new0 (CallbacksData);
1437 data->callback = callback;
1438 data->user_data = user_data;
1439 data->handler = g_object_ref (handler);
1441 /* start collecting info about the file */
1442 g_file_query_info_async (priv->gfile,
1443 G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME ","
1444 G_FILE_ATTRIBUTE_STANDARD_SIZE ","
1445 G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
1446 G_FILE_ATTRIBUTE_STANDARD_TYPE ","
1447 G_FILE_ATTRIBUTE_TIME_MODIFIED,
1448 G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT,
1449 NULL, (GAsyncReadyCallback) ft_handler_gfile_ready_cb, data);
1452 void
1453 empathy_ft_handler_set_service_name (
1454 EmpathyFTHandler *self,
1455 const gchar *service_name)
1457 g_free (self->priv->service_name);
1458 self->priv->service_name = g_strdup (service_name);
1461 void
1462 empathy_ft_handler_set_description (
1463 EmpathyFTHandler *self,
1464 const gchar *description)
1466 g_free (self->priv->description);
1467 self->priv->description = g_strdup (description);
1471 * empathy_ft_handler_new_incoming:
1472 * @channel: the #TpFileTransferChannel proxy to the incoming channel
1473 * @callback: callback to be called when the handler has been created
1474 * @user_data: user data to be passed to @callback
1476 * Triggers the creation of a new #EmpathyFTHandler for an incoming transfer.
1477 * Note that for the handler to be useful, you will have to set a destination
1478 * file with empathy_ft_handler_incoming_set_destination() after the handler
1479 * is ready.
1481 void
1482 empathy_ft_handler_new_incoming (TpFileTransferChannel *channel,
1483 EmpathyFTHandlerReadyCallback callback,
1484 gpointer user_data)
1486 EmpathyFTHandler *handler;
1487 CallbacksData *data;
1488 EmpathyFTHandlerPriv *priv;
1489 GQuark features[] = { TP_CHANNEL_FEATURE_CONTACTS, 0 };
1491 g_return_if_fail (TP_IS_FILE_TRANSFER_CHANNEL (channel));
1493 handler = g_object_new (EMPATHY_TYPE_FT_HANDLER,
1494 "channel", channel, NULL);
1496 priv = handler->priv;
1498 data = g_slice_new0 (CallbacksData);
1499 data->callback = callback;
1500 data->user_data = user_data;
1501 data->handler = g_object_ref (handler);
1503 priv->total_bytes = tp_file_transfer_channel_get_size (channel);
1505 priv->transferred_bytes = tp_file_transfer_channel_get_transferred_bytes (
1506 channel);
1508 priv->filename = g_strdup (tp_file_transfer_channel_get_filename (channel));
1510 priv->content_type = g_strdup (tp_file_transfer_channel_get_mime_type (
1511 channel));
1513 priv->description = g_strdup (tp_file_transfer_channel_get_description (
1514 channel));
1516 tp_proxy_prepare_async (channel, features,
1517 channel_prepared_cb, data);
1521 * empathy_ft_handler_start_transfer:
1522 * @handler: an #EmpathyFTHandler
1524 * Starts the transfer machinery. After this call, the transfer and hashing
1525 * signals will be emitted by the handler.
1527 void
1528 empathy_ft_handler_start_transfer (EmpathyFTHandler *handler)
1530 EmpathyFTHandlerPriv *priv;
1532 g_return_if_fail (EMPATHY_IS_FT_HANDLER (handler));
1534 priv = handler->priv;
1536 if (priv->channel == NULL)
1538 ft_handler_complete_request (handler);
1540 else
1542 /* TODO: add support for resume. */
1543 tp_file_transfer_channel_accept_file_async (priv->channel,
1544 priv->gfile, 0, ft_transfer_accept_cb, handler);
1546 tp_g_signal_connect_object (priv->channel, "notify::state",
1547 G_CALLBACK (ft_transfer_state_cb), handler, 0);
1548 tp_g_signal_connect_object (priv->channel, "notify::transferred-bytes",
1549 G_CALLBACK (ft_transfer_transferred_bytes_cb), handler, 0);
1554 * empathy_ft_handler_cancel_transfer:
1555 * @handler: an #EmpathyFTHandler
1557 * Cancels an ongoing handler operation. Note that this doesn't destroy
1558 * the object, which will keep all the properties, altough it won't be able
1559 * to do any more I/O.
1561 void
1562 empathy_ft_handler_cancel_transfer (EmpathyFTHandler *handler)
1564 EmpathyFTHandlerPriv *priv;
1566 g_return_if_fail (EMPATHY_IS_FT_HANDLER (handler));
1568 priv = handler->priv;
1570 /* if we don't have a channel, we are hashing, so
1571 * we can just cancel the GCancellable to stop it.
1573 if (priv->channel == NULL)
1574 g_cancellable_cancel (priv->cancellable);
1575 else
1576 tp_channel_close_async (TP_CHANNEL (priv->channel), NULL, NULL);
1580 * empathy_ft_handler_incoming_set_destination:
1581 * @handler: an #EmpathyFTHandler
1582 * @destination: the #GFile where the transfer should be saved
1584 * Sets the destination of the incoming handler to be @destination.
1585 * Note that calling this method is mandatory before starting the transfer
1586 * for incoming handlers.
1588 void
1589 empathy_ft_handler_incoming_set_destination (EmpathyFTHandler *handler,
1590 GFile *destination)
1592 EmpathyFTHandlerPriv *priv;
1594 g_return_if_fail (EMPATHY_IS_FT_HANDLER (handler));
1595 g_return_if_fail (G_IS_FILE (destination));
1597 priv = handler->priv;
1599 g_object_set (handler, "gfile", destination, NULL);
1601 /* check if hash is supported. if it isn't, set use_hash to FALSE
1602 * anyway, so that clients won't be expecting us to checksum.
1604 if (tp_str_empty (priv->content_hash) ||
1605 priv->content_hash_type == TP_FILE_HASH_TYPE_NONE)
1606 priv->use_hash = FALSE;
1607 else
1608 priv->use_hash = TRUE;
1612 * empathy_ft_handler_get_filename:
1613 * @handler: an #EmpathyFTHandler
1615 * Returns the name of the file being transferred.
1617 * Return value: the name of the file being transferred
1619 const char *
1620 empathy_ft_handler_get_filename (EmpathyFTHandler *handler)
1622 EmpathyFTHandlerPriv *priv;
1624 g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), NULL);
1626 priv = handler->priv;
1628 return priv->filename;
1631 const char *
1632 empathy_ft_handler_get_description (EmpathyFTHandler *handler)
1634 EmpathyFTHandlerPriv *priv;
1636 g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), NULL);
1638 priv = handler->priv;
1640 return priv->description;
1644 * empathy_ft_handler_get_content_type:
1645 * @handler: an #EmpathyFTHandler
1647 * Returns the content type of the file being transferred.
1649 * Return value: the content type of the file being transferred
1651 const char *
1652 empathy_ft_handler_get_content_type (EmpathyFTHandler *handler)
1654 EmpathyFTHandlerPriv *priv;
1656 g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), NULL);
1658 priv = handler->priv;
1660 return priv->content_type;
1664 * empathy_ft_handler_get_contact:
1665 * @handler: an #EmpathyFTHandler
1667 * Returns the remote #TpContact at the other side of the transfer.
1669 * Return value: the remote #TpContact for @handler
1671 TpContact *
1672 empathy_ft_handler_get_contact (EmpathyFTHandler *handler)
1674 EmpathyFTHandlerPriv *priv;
1676 g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), NULL);
1678 priv = handler->priv;
1680 return priv->contact;
1684 * empathy_ft_handler_get_gfile:
1685 * @handler: an #EmpathyFTHandler
1687 * Returns the #GFile where the transfer is being read/saved.
1689 * Return value: the #GFile where the transfer is being read/saved
1691 GFile *
1692 empathy_ft_handler_get_gfile (EmpathyFTHandler *handler)
1694 EmpathyFTHandlerPriv *priv;
1696 g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), NULL);
1698 priv = handler->priv;
1700 return priv->gfile;
1704 * empathy_ft_handler_get_use_hash:
1705 * @handler: an #EmpathyFTHandler
1707 * Returns whether @handler has checksumming enabled. This can depend on
1708 * the CM and the remote contact capabilities.
1710 * Return value: %TRUE if the handler has checksumming enabled,
1711 * %FALSE otherwise.
1713 gboolean
1714 empathy_ft_handler_get_use_hash (EmpathyFTHandler *handler)
1716 EmpathyFTHandlerPriv *priv;
1718 g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), FALSE);
1720 priv = handler->priv;
1722 return priv->use_hash;
1726 * empathy_ft_handler_is_incoming:
1727 * @handler: an #EmpathyFTHandler
1729 * Returns whether @handler is incoming or outgoing.
1731 * Return value: %TRUE if the handler is incoming, %FALSE otherwise.
1733 gboolean
1734 empathy_ft_handler_is_incoming (EmpathyFTHandler *handler)
1736 EmpathyFTHandlerPriv *priv;
1738 g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), FALSE);
1740 priv = handler->priv;
1742 if (priv->channel == NULL)
1743 return FALSE;
1745 return !tp_channel_get_requested ((TpChannel *) priv->channel);
1749 * empathy_ft_handler_get_transferred_bytes:
1750 * @handler: an #EmpathyFTHandler
1752 * Returns the number of bytes already transferred by the handler.
1754 * Return value: the number of bytes already transferred by the handler.
1756 guint64
1757 empathy_ft_handler_get_transferred_bytes (EmpathyFTHandler *handler)
1759 EmpathyFTHandlerPriv *priv;
1761 g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), 0);
1763 priv = handler->priv;
1765 return priv->transferred_bytes;
1769 * empathy_ft_handler_get_total_bytes:
1770 * @handler: an #EmpathyFTHandler
1772 * Returns the total size of the file being transferred by the handler.
1774 * Return value: a number of bytes indicating the total size of the file being
1775 * transferred by the handler.
1777 guint64
1778 empathy_ft_handler_get_total_bytes (EmpathyFTHandler *handler)
1780 EmpathyFTHandlerPriv *priv;
1782 g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), 0);
1784 priv = handler->priv;
1786 return priv->total_bytes;
1790 * empathy_ft_handler_is_completed:
1791 * @handler: an #EmpathyFTHandler
1793 * Returns whether the transfer for @handler has been completed succesfully.
1795 * Return value: %TRUE if the handler has been transferred correctly, %FALSE
1796 * otherwise
1798 gboolean
1799 empathy_ft_handler_is_completed (EmpathyFTHandler *handler)
1801 EmpathyFTHandlerPriv *priv;
1803 g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), FALSE);
1805 priv = handler->priv;
1807 return priv->is_completed;
1811 * empathy_ft_handler_is_cancelled:
1812 * @handler: an #EmpathyFTHandler
1814 * Returns whether the transfer for @handler has been cancelled or has stopped
1815 * due to an error.
1817 * Return value: %TRUE if the transfer for @handler has been cancelled
1818 * or has stopped due to an error, %FALSE otherwise.
1820 gboolean
1821 empathy_ft_handler_is_cancelled (EmpathyFTHandler *handler)
1823 EmpathyFTHandlerPriv *priv;
1825 g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), FALSE);
1827 priv = handler->priv;
1829 return g_cancellable_is_cancelled (priv->cancellable);