bump product version to 5.0.4.1
[LibreOffice.git] / tubes / source / file-transfer-helper.c
blob12a6c0940930bc0103c61cb00d527614fe6aee3e
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 should 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 #pragma GCC diagnostic push
646 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
647 g_io_scheduler_push_job (do_hash_job_incoming, hash_data, NULL,
648 G_PRIORITY_DEFAULT, priv->cancellable);
649 #pragma GCC diagnostic pop
653 static void
654 emit_error_signal (EmpathyFTHandler *handler,
655 const GError *error)
657 EmpathyFTHandlerPriv *priv = handler->priv;
659 DEBUG ("Error in transfer: %s\n", error->message);
661 if (!g_cancellable_is_cancelled (priv->cancellable))
662 g_cancellable_cancel (priv->cancellable);
664 g_signal_emit (handler, signals[TRANSFER_ERROR], 0, error);
667 static gint64
668 time_get_current (void)
670 GDateTime *now;
671 gint64 result;
673 now = g_date_time_new_now_utc ();
674 result = g_date_time_to_unix (now);
675 g_date_time_unref (now);
677 return result;
680 static void
681 update_remaining_time_and_speed (EmpathyFTHandler *handler,
682 guint64 transferred_bytes)
684 EmpathyFTHandlerPriv *priv = handler->priv;
685 gint64 elapsed_time, current_time;
686 guint64 transferred, last_transferred_bytes;
687 gdouble speed;
688 gint remaining_time;
690 last_transferred_bytes = priv->transferred_bytes;
691 priv->transferred_bytes = transferred_bytes;
693 current_time = time_get_current ();
694 elapsed_time = current_time - priv->last_update_time;
696 if (elapsed_time >= 1)
698 transferred = transferred_bytes - last_transferred_bytes;
699 speed = (gdouble) transferred / (gdouble) elapsed_time;
700 remaining_time = (priv->total_bytes - priv->transferred_bytes) / speed;
701 priv->speed = speed;
702 priv->remaining_time = remaining_time;
703 priv->last_update_time = current_time;
707 static void
708 ft_transfer_transferred_bytes_cb (TpFileTransferChannel *channel,
709 GParamSpec *pspec,
710 EmpathyFTHandler *handler)
712 EmpathyFTHandlerPriv *priv = handler->priv;
713 guint64 bytes;
715 (void)pspec; /* suppress unused-parameter warning */
717 if (empathy_ft_handler_is_cancelled (handler))
718 return;
720 bytes = tp_file_transfer_channel_get_transferred_bytes (channel);
722 if (priv->transferred_bytes == 0)
724 priv->last_update_time = time_get_current ();
725 g_signal_emit (handler, signals[TRANSFER_STARTED], 0, channel);
728 if (priv->transferred_bytes != bytes)
730 update_remaining_time_and_speed (handler, bytes);
732 g_signal_emit (handler, signals[TRANSFER_PROGRESS], 0,
733 bytes, priv->total_bytes, priv->remaining_time,
734 priv->speed);
738 static void
739 ft_transfer_provide_cb (GObject *source,
740 GAsyncResult *result,
741 gpointer user_data)
743 TpFileTransferChannel *channel = TP_FILE_TRANSFER_CHANNEL (source);
744 EmpathyFTHandler *handler = user_data;
745 GError *error = NULL;
747 if (!tp_file_transfer_channel_provide_file_finish (channel, result, &error))
749 emit_error_signal (handler, error);
750 g_clear_error (&error);
754 static void
755 ft_transfer_accept_cb (GObject *source,
756 GAsyncResult *result,
757 gpointer user_data)
759 TpFileTransferChannel *channel = TP_FILE_TRANSFER_CHANNEL (source);
760 EmpathyFTHandler *handler = user_data;
761 GError *error = NULL;
763 if (!tp_file_transfer_channel_accept_file_finish (channel, result, &error))
765 emit_error_signal (handler, error);
766 g_clear_error (&error);
770 static GError *
771 error_from_state_change_reason (TpFileTransferStateChangeReason reason)
773 const char *string;
774 GError *retval = NULL;
776 string = NULL;
778 switch (reason)
780 case TP_FILE_TRANSFER_STATE_CHANGE_REASON_NONE:
781 string = _("No reason was specified");
782 break;
783 case TP_FILE_TRANSFER_STATE_CHANGE_REASON_REQUESTED:
784 string = _("The change in state was requested");
785 break;
786 case TP_FILE_TRANSFER_STATE_CHANGE_REASON_LOCAL_STOPPED:
787 string = _("You canceled the file transfer");
788 break;
789 case TP_FILE_TRANSFER_STATE_CHANGE_REASON_REMOTE_STOPPED:
790 string = _("The other participant canceled the file transfer");
791 break;
792 case TP_FILE_TRANSFER_STATE_CHANGE_REASON_LOCAL_ERROR:
793 string = _("Error while trying to transfer the file");
794 break;
795 case TP_FILE_TRANSFER_STATE_CHANGE_REASON_REMOTE_ERROR:
796 string = _("The other participant is unable to transfer the file");
797 break;
798 default:
799 string = _("Unknown reason");
800 break;
803 retval = g_error_new_literal (EMPATHY_FT_ERROR_QUARK,
804 EMPATHY_FT_ERROR_TP_ERROR, string);
806 return retval;
809 static void
810 ft_transfer_state_cb (TpFileTransferChannel *channel,
811 GParamSpec *pspec,
812 EmpathyFTHandler *handler)
814 EmpathyFTHandlerPriv *priv = handler->priv;
815 TpFileTransferStateChangeReason reason;
816 TpFileTransferState state = tp_file_transfer_channel_get_state (
817 channel, &reason);
819 (void)pspec; /* suppress unused-parameter warning */
821 if (state == TP_FILE_TRANSFER_STATE_COMPLETED)
823 priv->is_completed = TRUE;
824 g_signal_emit (handler, signals[TRANSFER_DONE], 0, channel);
826 tp_channel_close_async (TP_CHANNEL (channel), NULL, NULL);
828 if (empathy_ft_handler_is_incoming (handler) && priv->use_hash)
830 check_hash_incoming (handler);
833 else if (state == TP_FILE_TRANSFER_STATE_CANCELLED)
835 GError *error = error_from_state_change_reason (reason);
836 emit_error_signal (handler, error);
837 g_clear_error (&error);
841 static void
842 ft_handler_create_channel_cb (GObject *source,
843 GAsyncResult *result,
844 gpointer user_data)
846 EmpathyFTHandler *handler = user_data;
847 EmpathyFTHandlerPriv *priv = handler->priv;
848 GError *error = NULL;
849 TpChannel *channel;
851 DEBUG ("Dispatcher create channel CB");
853 channel = tp_account_channel_request_create_and_handle_channel_finish (
854 TP_ACCOUNT_CHANNEL_REQUEST (source), result, NULL, &error);
856 if (channel == NULL)
857 DEBUG ("Failed to request FT channel: %s", error->message);
858 else
859 g_cancellable_set_error_if_cancelled (priv->cancellable, &error);
861 if (error != NULL)
863 emit_error_signal (handler, error);
865 g_clear_object (&channel);
866 g_error_free (error);
867 return;
870 priv->channel = TP_FILE_TRANSFER_CHANNEL (channel);
872 tp_g_signal_connect_object (priv->channel, "notify::state",
873 G_CALLBACK (ft_transfer_state_cb), handler, 0);
874 tp_g_signal_connect_object (priv->channel, "notify::transferred-bytes",
875 G_CALLBACK (ft_transfer_transferred_bytes_cb), handler, 0);
877 tp_file_transfer_channel_provide_file_async (priv->channel, priv->gfile,
878 ft_transfer_provide_cb, handler);
881 static void
882 ft_handler_push_to_dispatcher (EmpathyFTHandler *handler)
884 EmpathyFTHandlerPriv *priv = handler->priv;
885 TpAccountChannelRequest *req;
887 DEBUG ("Pushing request to the dispatcher");
889 req = tp_account_channel_request_new (priv->account, priv->request,
890 priv->user_action_time);
892 tp_account_channel_request_create_and_handle_channel_async (req, NULL,
893 ft_handler_create_channel_cb, handler);
895 g_object_unref (req);
898 static void
899 ft_handler_populate_outgoing_request (EmpathyFTHandler *handler)
901 guint contact_handle;
902 EmpathyFTHandlerPriv *priv = handler->priv;
903 gchar *uri;
905 contact_handle = tp_contact_get_handle (priv->contact);
906 uri = g_file_get_uri (priv->gfile);
908 priv->request = tp_asv_new (
909 TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
910 TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER,
911 TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
912 TP_HANDLE_TYPE_CONTACT,
913 TP_PROP_CHANNEL_TARGET_HANDLE, G_TYPE_UINT,
914 contact_handle,
915 TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_CONTENT_TYPE, G_TYPE_STRING,
916 priv->content_type,
917 TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_FILENAME, G_TYPE_STRING,
918 priv->filename,
919 TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_SIZE, G_TYPE_UINT64,
920 priv->total_bytes,
921 TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_DATE, G_TYPE_UINT64,
922 priv->mtime,
923 TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_URI, G_TYPE_STRING, uri,
924 NULL);
926 if (priv->service_name != NULL)
927 tp_asv_set_string (priv->request, TP_PROP_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA_SERVICE_NAME, priv->service_name);
929 if (priv->description != NULL)
930 tp_asv_set_string (priv->request, TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_DESCRIPTION, priv->description);
932 g_free (uri);
935 static gboolean
936 hash_job_done (gpointer user_data)
938 HashingData *hash_data = user_data;
939 EmpathyFTHandler *handler = hash_data->handler;
940 EmpathyFTHandlerPriv *priv;
941 GError *error = NULL;
943 DEBUG ("Closing stream after hashing.");
945 priv = handler->priv;
947 if (hash_data->error != NULL)
949 error = hash_data->error;
950 hash_data->error = NULL;
951 goto cleanup;
954 DEBUG ("Got file hash %s", g_checksum_get_string (hash_data->checksum));
956 if (empathy_ft_handler_is_incoming (handler))
958 if (g_strcmp0 (g_checksum_get_string (hash_data->checksum),
959 priv->content_hash))
961 DEBUG ("Hash mismatch when checking incoming handler: "
962 "received %s, calculated %s", priv->content_hash,
963 g_checksum_get_string (hash_data->checksum));
965 error = g_error_new_literal (EMPATHY_FT_ERROR_QUARK,
966 EMPATHY_FT_ERROR_HASH_MISMATCH,
967 _("File transfer completed, but the file was corrupted"));
968 goto cleanup;
970 else
972 DEBUG ("Hash verification matched, received %s, calculated %s",
973 priv->content_hash,
974 g_checksum_get_string (hash_data->checksum));
977 else
979 /* set the checksum in the request...
980 * org.freedesktop.Telepathy.Channel.Type.FileTransfer.ContentHash
982 tp_asv_set_string (priv->request,
983 TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_CONTENT_HASH,
984 g_checksum_get_string (hash_data->checksum));
987 cleanup:
989 if (error != NULL)
991 emit_error_signal (handler, error);
992 g_clear_error (&error);
994 else
996 g_signal_emit (handler, signals[HASHING_DONE], 0);
998 if (!empathy_ft_handler_is_incoming (handler))
999 /* the request is complete now, push it to the dispatcher */
1000 ft_handler_push_to_dispatcher (handler);
1003 hash_data_free (hash_data);
1005 return FALSE;
1008 static gboolean
1009 emit_hashing_progress (gpointer user_data)
1011 HashingData *hash_data = user_data;
1013 g_signal_emit (hash_data->handler, signals[HASHING_PROGRESS], 0,
1014 (guint64) hash_data->total_read, (guint64) hash_data->total_bytes);
1016 return FALSE;
1019 static gboolean
1020 do_hash_job (GIOSchedulerJob *job,
1021 GCancellable *cancellable,
1022 gpointer user_data)
1024 HashingData *hash_data = user_data;
1025 gssize bytes_read;
1026 GError *error = NULL;
1028 again:
1029 if (hash_data->buffer == NULL)
1030 hash_data->buffer = g_malloc0 (BUFFER_SIZE);
1032 bytes_read = g_input_stream_read (hash_data->stream, hash_data->buffer,
1033 BUFFER_SIZE, cancellable, &error);
1034 if (error != NULL)
1035 goto out;
1037 hash_data->total_read += bytes_read;
1039 /* we now have the chunk */
1040 if (bytes_read > 0)
1042 g_checksum_update (hash_data->checksum, hash_data->buffer, bytes_read);
1043 #pragma GCC diagnostic push
1044 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1045 g_io_scheduler_job_send_to_mainloop_async (job, emit_hashing_progress,
1046 hash_data, NULL);
1047 #pragma GCC diagnostic pop
1048 g_free (hash_data->buffer);
1049 hash_data->buffer = NULL;
1051 goto again;
1053 else
1055 g_input_stream_close (hash_data->stream, cancellable, &error);
1058 out:
1059 if (error != NULL)
1060 hash_data->error = error;
1062 #pragma GCC diagnostic push
1063 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1064 g_io_scheduler_job_send_to_mainloop_async (job, hash_job_done,
1065 hash_data, NULL);
1066 #pragma GCC diagnostic pop
1068 return FALSE;
1071 static gboolean
1072 do_hash_job_incoming (GIOSchedulerJob *job,
1073 GCancellable *cancellable,
1074 gpointer user_data)
1076 HashingData *hash_data = user_data;
1077 EmpathyFTHandler *handler = hash_data->handler;
1078 EmpathyFTHandlerPriv *priv = handler->priv;
1079 GError *error = NULL;
1081 DEBUG ("checking integrity for incoming handler");
1083 /* need to get the stream first */
1084 hash_data->stream =
1085 G_INPUT_STREAM (g_file_read (priv->gfile, cancellable, &error));
1087 if (error != NULL)
1089 hash_data->error = error;
1090 #pragma GCC diagnostic push
1091 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1092 g_io_scheduler_job_send_to_mainloop_async (job, hash_job_done,
1093 hash_data, NULL);
1094 #pragma GCC diagnostic pop
1095 return FALSE;
1098 return do_hash_job (job, cancellable, user_data);
1101 static void
1102 ft_handler_read_async_cb (GObject *source,
1103 GAsyncResult *res,
1104 gpointer user_data)
1106 GFileInputStream *stream;
1107 GError *error = NULL;
1108 HashingData *hash_data;
1109 EmpathyFTHandler *handler = user_data;
1110 EmpathyFTHandlerPriv *priv = handler->priv;
1112 (void)source; /* suppress unused-parameter warning */
1114 DEBUG ("GFile read async CB.");
1116 stream = g_file_read_finish (priv->gfile, res, &error);
1117 if (error != NULL)
1119 emit_error_signal (handler, error);
1120 g_clear_error (&error);
1122 return;
1125 hash_data = g_slice_new0 (HashingData);
1126 hash_data->stream = G_INPUT_STREAM (stream);
1127 hash_data->total_bytes = priv->total_bytes;
1128 hash_data->handler = g_object_ref (handler);
1129 /* FIXME: MD5 is the only ContentHashType supported right now */
1130 hash_data->checksum = g_checksum_new (G_CHECKSUM_MD5);
1132 tp_asv_set_uint32 (priv->request,
1133 TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_CONTENT_HASH_TYPE,
1134 TP_FILE_HASH_TYPE_MD5);
1136 g_signal_emit (handler, signals[HASHING_STARTED], 0);
1138 #pragma GCC diagnostic push
1139 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1140 g_io_scheduler_push_job (do_hash_job, hash_data, NULL,
1141 G_PRIORITY_DEFAULT, priv->cancellable);
1142 #pragma GCC diagnostic pop
1145 static void
1146 callbacks_data_free (gpointer user_data)
1148 CallbacksData *data = user_data;
1150 if (data->handler != NULL)
1151 g_object_unref (data->handler);
1153 g_slice_free (CallbacksData, data);
1156 static gint
1157 cmp_uint (
1158 gconstpointer a,
1159 gconstpointer b)
1161 return *(guint *) a - *(guint *) b;
1164 static gboolean
1165 set_content_hash_type_from_classes (EmpathyFTHandler *handler,
1166 GPtrArray *classes)
1168 GArray *possible_values;
1169 guint value;
1170 gboolean valid;
1171 EmpathyFTHandlerPriv *priv = handler->priv;
1172 gboolean support_ft = FALSE;
1173 guint i;
1175 possible_values = g_array_new (TRUE, TRUE, sizeof (guint));
1177 for (i = 0; i < classes->len; i++)
1179 GHashTable *fixed;
1180 GStrv allowed;
1181 const gchar *chan_type;
1183 tp_value_array_unpack (g_ptr_array_index (classes, i), 2,
1184 &fixed, &allowed);
1186 chan_type = tp_asv_get_string (fixed, TP_PROP_CHANNEL_CHANNEL_TYPE);
1188 if (tp_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER))
1189 continue;
1191 if (tp_asv_get_uint32 (fixed, TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL) !=
1192 TP_HANDLE_TYPE_CONTACT)
1193 continue;
1195 support_ft = TRUE;
1197 value = tp_asv_get_uint32
1198 (fixed, TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_CONTENT_HASH_TYPE,
1199 &valid);
1201 if (valid)
1202 g_array_append_val (possible_values, value);
1205 if (!support_ft)
1207 g_array_unref (possible_values);
1208 return FALSE;
1211 if (possible_values->len == 0)
1213 /* there are no channel classes with hash support, disable it. */
1214 priv->use_hash = FALSE;
1215 priv->content_hash_type = TP_FILE_HASH_TYPE_NONE;
1217 goto out;
1220 priv->use_hash = TRUE;
1222 if (possible_values->len == 1)
1224 priv->content_hash_type = g_array_index (possible_values, guint, 0);
1226 else
1228 /* order the array and pick the first non zero, so that MD5
1229 * is the preferred value.
1231 g_array_sort (possible_values, cmp_uint);
1233 if (g_array_index (possible_values, guint, 0) == 0)
1234 priv->content_hash_type = g_array_index (possible_values, guint, 1);
1235 else
1236 priv->content_hash_type = g_array_index (possible_values, guint, 0);
1239 out:
1240 g_array_unref (possible_values);
1242 DEBUG ("Hash enabled %s; setting content hash type as %u",
1243 priv->use_hash ? "True" : "False", priv->content_hash_type);
1245 return TRUE;
1248 static void
1249 check_hashing (CallbacksData *data)
1251 EmpathyFTHandler *handler = data->handler;
1252 EmpathyFTHandlerPriv *priv = handler->priv;
1253 GError *myerr = NULL;
1254 TpCapabilities *caps;
1255 GPtrArray *classes;
1256 TpConnection *conn;
1258 conn = tp_account_get_connection (priv->account);
1260 caps = tp_connection_get_capabilities (conn);
1261 if (caps == NULL)
1263 data->callback (handler, NULL, data->user_data);
1264 goto out;
1267 classes = tp_capabilities_get_channel_classes (caps);
1269 /* set whether we support hash and the type of it */
1270 if (!set_content_hash_type_from_classes (handler, classes))
1272 g_set_error_literal (&myerr, EMPATHY_FT_ERROR_QUARK,
1273 EMPATHY_FT_ERROR_NOT_SUPPORTED,
1274 _("File transfer not supported by remote contact"));
1276 if (!g_cancellable_is_cancelled (priv->cancellable))
1277 g_cancellable_cancel (priv->cancellable);
1279 data->callback (handler, myerr, data->user_data);
1280 g_clear_error (&myerr);
1282 else
1284 /* get back to the caller now */
1285 data->callback (handler, NULL, data->user_data);
1288 out:
1289 callbacks_data_free (data);
1292 static void
1293 ft_handler_complete_request (EmpathyFTHandler *handler)
1295 EmpathyFTHandlerPriv *priv = handler->priv;
1297 /* populate the request table with all the known properties */
1298 ft_handler_populate_outgoing_request (handler);
1300 if (priv->use_hash)
1301 /* start hashing the file */
1302 g_file_read_async (priv->gfile, G_PRIORITY_DEFAULT,
1303 priv->cancellable, ft_handler_read_async_cb, handler);
1304 else
1305 /* push directly the handler to the dispatcher */
1306 ft_handler_push_to_dispatcher (handler);
1309 static void
1310 ft_handler_gfile_ready_cb (GObject *source,
1311 GAsyncResult *res,
1312 CallbacksData *cb_data)
1314 GFileInfo *info;
1315 GError *error = NULL;
1316 GTimeVal mtime;
1317 EmpathyFTHandlerPriv *priv = cb_data->handler->priv;
1319 (void)source; /* suppress unused-parameter warning */
1321 DEBUG ("Got GFileInfo.");
1323 info = g_file_query_info_finish (priv->gfile, res, &error);
1325 if (error != NULL)
1326 goto out;
1328 if (g_file_info_get_file_type (info) != G_FILE_TYPE_REGULAR)
1330 error = g_error_new_literal (EMPATHY_FT_ERROR_QUARK,
1331 EMPATHY_FT_ERROR_INVALID_SOURCE_FILE,
1332 _("The selected file is not a regular file"));
1333 goto out;
1336 priv->total_bytes = g_file_info_get_size (info);
1337 if (priv->total_bytes == 0)
1339 error = g_error_new_literal (EMPATHY_FT_ERROR_QUARK,
1340 EMPATHY_FT_ERROR_EMPTY_SOURCE_FILE,
1341 _("The selected file is empty"));
1342 goto out;
1345 priv->content_type = g_strdup (g_file_info_get_content_type (info));
1346 priv->filename = g_strdup (g_file_info_get_display_name (info));
1347 g_file_info_get_modification_time (info, &mtime);
1348 priv->mtime = mtime.tv_sec;
1349 priv->transferred_bytes = 0;
1350 priv->description = NULL;
1352 g_object_unref (info);
1354 out:
1355 if (error != NULL)
1357 if (!g_cancellable_is_cancelled (priv->cancellable))
1358 g_cancellable_cancel (priv->cancellable);
1360 cb_data->callback (cb_data->handler, error, cb_data->user_data);
1361 g_error_free (error);
1363 callbacks_data_free (cb_data);
1365 else
1367 /* see if FT/hashing are allowed */
1368 check_hashing (cb_data);
1372 static void
1373 channel_prepared_cb (
1374 GObject *source,
1375 GAsyncResult *result,
1376 gpointer user_data)
1378 TpFileTransferChannel *channel = TP_FILE_TRANSFER_CHANNEL (source);
1379 CallbacksData *cb_data = user_data;
1380 EmpathyFTHandler *handler = cb_data->handler;
1381 EmpathyFTHandlerPriv *priv = handler->priv;
1382 GHashTable *properties;
1383 GError *error = NULL;
1385 if (!tp_proxy_prepare_finish (channel, result, &error))
1387 if (!g_cancellable_is_cancelled (priv->cancellable))
1388 g_cancellable_cancel (priv->cancellable);
1390 cb_data->callback (handler, error, cb_data->user_data);
1391 g_clear_error (&error);
1392 callbacks_data_free (cb_data);
1393 return;
1395 #pragma GCC diagnostic push
1396 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1397 properties = tp_channel_borrow_immutable_properties (TP_CHANNEL (channel));
1398 #pragma GCC diagnostic pop
1400 priv->content_hash = g_strdup (
1401 tp_asv_get_string (properties, "ContentHash"));
1403 priv->content_hash_type = tp_asv_get_uint32 (
1404 properties, "ContentHashType", NULL);
1406 priv->contact = g_object_ref (tp_channel_get_target_contact (TP_CHANNEL (channel)));
1408 cb_data->callback (handler, NULL, cb_data->user_data);
1412 /* public methods */
1415 * empathy_ft_handler_new_outgoing:
1416 * @account: the #TpAccount to send @source to
1417 * @contact: the #TpContact to send @source to
1418 * @source: the #GFile to send
1419 * @callback: callback to be called when the handler has been created
1420 * @user_data: user data to be passed to @callback
1422 * Triggers the creation of a new #EmpathyFTHandler for an outgoing transfer.
1424 void
1425 empathy_ft_handler_new_outgoing (
1426 TpAccount *account,
1427 TpContact *contact,
1428 GFile *source,
1429 gint64 action_time,
1430 EmpathyFTHandlerReadyCallback callback,
1431 gpointer user_data)
1433 EmpathyFTHandler *handler;
1434 CallbacksData *data;
1435 EmpathyFTHandlerPriv *priv;
1437 DEBUG ("New handler outgoing");
1439 g_return_if_fail (TP_IS_ACCOUNT (account));
1440 g_return_if_fail (TP_IS_CONTACT (contact));
1441 g_return_if_fail (G_IS_FILE (source));
1443 handler = g_object_new (EMPATHY_TYPE_FT_HANDLER,
1444 "account", account,
1445 "contact", contact,
1446 "gfile", source,
1447 "user-action-time", action_time,
1448 NULL);
1450 priv = handler->priv;
1452 data = g_slice_new0 (CallbacksData);
1453 data->callback = callback;
1454 data->user_data = user_data;
1455 data->handler = g_object_ref (handler);
1457 /* start collecting info about the file */
1458 g_file_query_info_async (priv->gfile,
1459 G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME ","
1460 G_FILE_ATTRIBUTE_STANDARD_SIZE ","
1461 G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
1462 G_FILE_ATTRIBUTE_STANDARD_TYPE ","
1463 G_FILE_ATTRIBUTE_TIME_MODIFIED,
1464 G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT,
1465 NULL, (GAsyncReadyCallback) ft_handler_gfile_ready_cb, data);
1468 void
1469 empathy_ft_handler_set_service_name (
1470 EmpathyFTHandler *self,
1471 const gchar *service_name)
1473 g_free (self->priv->service_name);
1474 self->priv->service_name = g_strdup (service_name);
1477 void
1478 empathy_ft_handler_set_description (
1479 EmpathyFTHandler *self,
1480 const gchar *description)
1482 g_free (self->priv->description);
1483 self->priv->description = g_strdup (description);
1487 * empathy_ft_handler_new_incoming:
1488 * @channel: the #TpFileTransferChannel proxy to the incoming channel
1489 * @callback: callback to be called when the handler has been created
1490 * @user_data: user data to be passed to @callback
1492 * Triggers the creation of a new #EmpathyFTHandler for an incoming transfer.
1493 * Note that for the handler to be useful, you will have to set a destination
1494 * file with empathy_ft_handler_incoming_set_destination() after the handler
1495 * is ready.
1497 void
1498 empathy_ft_handler_new_incoming (TpFileTransferChannel *channel,
1499 EmpathyFTHandlerReadyCallback callback,
1500 gpointer user_data)
1502 EmpathyFTHandler *handler;
1503 CallbacksData *data;
1504 EmpathyFTHandlerPriv *priv;
1505 GQuark features[] = { TP_CHANNEL_FEATURE_CONTACTS, 0 };
1507 g_return_if_fail (TP_IS_FILE_TRANSFER_CHANNEL (channel));
1509 handler = g_object_new (EMPATHY_TYPE_FT_HANDLER,
1510 "channel", channel, NULL);
1512 priv = handler->priv;
1514 data = g_slice_new0 (CallbacksData);
1515 data->callback = callback;
1516 data->user_data = user_data;
1517 data->handler = g_object_ref (handler);
1519 priv->total_bytes = tp_file_transfer_channel_get_size (channel);
1521 priv->transferred_bytes = tp_file_transfer_channel_get_transferred_bytes (
1522 channel);
1524 priv->filename = g_strdup (tp_file_transfer_channel_get_filename (channel));
1526 priv->content_type = g_strdup (tp_file_transfer_channel_get_mime_type (
1527 channel));
1529 priv->description = g_strdup (tp_file_transfer_channel_get_description (
1530 channel));
1532 tp_proxy_prepare_async (channel, features,
1533 channel_prepared_cb, data);
1537 * empathy_ft_handler_start_transfer:
1538 * @handler: an #EmpathyFTHandler
1540 * Starts the transfer machinery. After this call, the transfer and hashing
1541 * signals will be emitted by the handler.
1543 void
1544 empathy_ft_handler_start_transfer (EmpathyFTHandler *handler)
1546 EmpathyFTHandlerPriv *priv;
1548 g_return_if_fail (EMPATHY_IS_FT_HANDLER (handler));
1550 priv = handler->priv;
1552 if (priv->channel == NULL)
1554 ft_handler_complete_request (handler);
1556 else
1558 /* TODO: add support for resume. */
1559 tp_file_transfer_channel_accept_file_async (priv->channel,
1560 priv->gfile, 0, ft_transfer_accept_cb, handler);
1562 tp_g_signal_connect_object (priv->channel, "notify::state",
1563 G_CALLBACK (ft_transfer_state_cb), handler, 0);
1564 tp_g_signal_connect_object (priv->channel, "notify::transferred-bytes",
1565 G_CALLBACK (ft_transfer_transferred_bytes_cb), handler, 0);
1570 * empathy_ft_handler_cancel_transfer:
1571 * @handler: an #EmpathyFTHandler
1573 * Cancels an ongoing handler operation. Note that this doesn't destroy
1574 * the object, which will keep all the properties, although it won't be able
1575 * to do any more I/O.
1577 void
1578 empathy_ft_handler_cancel_transfer (EmpathyFTHandler *handler)
1580 EmpathyFTHandlerPriv *priv;
1582 g_return_if_fail (EMPATHY_IS_FT_HANDLER (handler));
1584 priv = handler->priv;
1586 /* if we don't have a channel, we are hashing, so
1587 * we can just cancel the GCancellable to stop it.
1589 if (priv->channel == NULL)
1590 g_cancellable_cancel (priv->cancellable);
1591 else
1592 tp_channel_close_async (TP_CHANNEL (priv->channel), NULL, NULL);
1596 * empathy_ft_handler_incoming_set_destination:
1597 * @handler: an #EmpathyFTHandler
1598 * @destination: the #GFile where the transfer should be saved
1600 * Sets the destination of the incoming handler to be @destination.
1601 * Note that calling this method is mandatory before starting the transfer
1602 * for incoming handlers.
1604 void
1605 empathy_ft_handler_incoming_set_destination (EmpathyFTHandler *handler,
1606 GFile *destination)
1608 EmpathyFTHandlerPriv *priv;
1610 g_return_if_fail (EMPATHY_IS_FT_HANDLER (handler));
1611 g_return_if_fail (G_IS_FILE (destination));
1613 priv = handler->priv;
1615 g_object_set (handler, "gfile", destination, NULL);
1617 /* check if hash is supported. if it isn't, set use_hash to FALSE
1618 * anyway, so that clients won't be expecting us to checksum.
1620 if (tp_str_empty (priv->content_hash) ||
1621 priv->content_hash_type == TP_FILE_HASH_TYPE_NONE)
1622 priv->use_hash = FALSE;
1623 else
1624 priv->use_hash = TRUE;
1628 * empathy_ft_handler_get_filename:
1629 * @handler: an #EmpathyFTHandler
1631 * Returns the name of the file being transferred.
1633 * Return value: the name of the file being transferred
1635 const char *
1636 empathy_ft_handler_get_filename (EmpathyFTHandler *handler)
1638 EmpathyFTHandlerPriv *priv;
1640 g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), NULL);
1642 priv = handler->priv;
1644 return priv->filename;
1647 const char *
1648 empathy_ft_handler_get_description (EmpathyFTHandler *handler)
1650 EmpathyFTHandlerPriv *priv;
1652 g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), NULL);
1654 priv = handler->priv;
1656 return priv->description;
1660 * empathy_ft_handler_get_content_type:
1661 * @handler: an #EmpathyFTHandler
1663 * Returns the content type of the file being transferred.
1665 * Return value: the content type of the file being transferred
1667 const char *
1668 empathy_ft_handler_get_content_type (EmpathyFTHandler *handler)
1670 EmpathyFTHandlerPriv *priv;
1672 g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), NULL);
1674 priv = handler->priv;
1676 return priv->content_type;
1680 * empathy_ft_handler_get_contact:
1681 * @handler: an #EmpathyFTHandler
1683 * Returns the remote #TpContact at the other side of the transfer.
1685 * Return value: the remote #TpContact for @handler
1687 TpContact *
1688 empathy_ft_handler_get_contact (EmpathyFTHandler *handler)
1690 EmpathyFTHandlerPriv *priv;
1692 g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), NULL);
1694 priv = handler->priv;
1696 return priv->contact;
1700 * empathy_ft_handler_get_gfile:
1701 * @handler: an #EmpathyFTHandler
1703 * Returns the #GFile where the transfer is being read/saved.
1705 * Return value: the #GFile where the transfer is being read/saved
1707 GFile *
1708 empathy_ft_handler_get_gfile (EmpathyFTHandler *handler)
1710 EmpathyFTHandlerPriv *priv;
1712 g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), NULL);
1714 priv = handler->priv;
1716 return priv->gfile;
1720 * empathy_ft_handler_get_use_hash:
1721 * @handler: an #EmpathyFTHandler
1723 * Returns whether @handler has checksumming enabled. This can depend on
1724 * the CM and the remote contact capabilities.
1726 * Return value: %TRUE if the handler has checksumming enabled,
1727 * %FALSE otherwise.
1729 gboolean
1730 empathy_ft_handler_get_use_hash (EmpathyFTHandler *handler)
1732 EmpathyFTHandlerPriv *priv;
1734 g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), FALSE);
1736 priv = handler->priv;
1738 return priv->use_hash;
1742 * empathy_ft_handler_is_incoming:
1743 * @handler: an #EmpathyFTHandler
1745 * Returns whether @handler is incoming or outgoing.
1747 * Return value: %TRUE if the handler is incoming, %FALSE otherwise.
1749 gboolean
1750 empathy_ft_handler_is_incoming (EmpathyFTHandler *handler)
1752 EmpathyFTHandlerPriv *priv;
1754 g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), FALSE);
1756 priv = handler->priv;
1758 if (priv->channel == NULL)
1759 return FALSE;
1761 return !tp_channel_get_requested ((TpChannel *) priv->channel);
1765 * empathy_ft_handler_get_transferred_bytes:
1766 * @handler: an #EmpathyFTHandler
1768 * Returns the number of bytes already transferred by the handler.
1770 * Return value: the number of bytes already transferred by the handler.
1772 guint64
1773 empathy_ft_handler_get_transferred_bytes (EmpathyFTHandler *handler)
1775 EmpathyFTHandlerPriv *priv;
1777 g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), 0);
1779 priv = handler->priv;
1781 return priv->transferred_bytes;
1785 * empathy_ft_handler_get_total_bytes:
1786 * @handler: an #EmpathyFTHandler
1788 * Returns the total size of the file being transferred by the handler.
1790 * Return value: a number of bytes indicating the total size of the file being
1791 * transferred by the handler.
1793 guint64
1794 empathy_ft_handler_get_total_bytes (EmpathyFTHandler *handler)
1796 EmpathyFTHandlerPriv *priv;
1798 g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), 0);
1800 priv = handler->priv;
1802 return priv->total_bytes;
1806 * empathy_ft_handler_is_completed:
1807 * @handler: an #EmpathyFTHandler
1809 * Returns whether the transfer for @handler has been completed successfully.
1811 * Return value: %TRUE if the handler has been transferred correctly, %FALSE
1812 * otherwise
1814 gboolean
1815 empathy_ft_handler_is_completed (EmpathyFTHandler *handler)
1817 EmpathyFTHandlerPriv *priv;
1819 g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), FALSE);
1821 priv = handler->priv;
1823 return priv->is_completed;
1827 * empathy_ft_handler_is_cancelled:
1828 * @handler: an #EmpathyFTHandler
1830 * Returns whether the transfer for @handler has been cancelled or has stopped
1831 * due to an error.
1833 * Return value: %TRUE if the transfer for @handler has been cancelled
1834 * or has stopped due to an error, %FALSE otherwise.
1836 gboolean
1837 empathy_ft_handler_is_cancelled (EmpathyFTHandler *handler)
1839 EmpathyFTHandlerPriv *priv;
1841 g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), FALSE);
1843 priv = handler->priv;
1845 return g_cancellable_is_cancelled (priv->cancellable);