2 * empathy-ft-handler.c - Source for EmpathyFTHandler
3 * Copyright (C) 2009 Collabora Ltd.
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 * Author: Cosimo Cecchi <cosimo.cecchi@collabora.co.uk>
22 /* empathy-ft-handler.c */
24 #include <extensions/extensions.h>
26 #include <glib/gi18n.h>
27 #include <telepathy-glib/util.h>
29 #include "empathy-ft-handler.h"
30 #include "empathy-dispatcher.h"
31 #include "empathy-marshal.h"
32 #include "empathy-utils.h"
34 #define DEBUG_FLAG EMPATHY_DEBUG_FT
35 #include "empathy-debug.h"
37 G_DEFINE_TYPE (EmpathyFTHandler, empathy_ft_handler, G_TYPE_OBJECT)
39 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyFTHandler)
41 #define BUFFER_SIZE 4096
61 EmpathyFTHandler *handler;
68 RequestData *req_data;
70 gboolean done_reading;
80 EmpathyContact *contact;
82 EmpathyTpFile *tpfile;
83 GCancellable *cancellable;
84 } EmpathyFTHandlerPriv;
86 static guint signals[LAST_SIGNAL] = { 0 };
89 static void schedule_hash_chunk (HashingData *hash_data);
91 /* GObject implementations */
93 do_get_property (GObject *object,
98 EmpathyFTHandlerPriv *priv = GET_PRIV (object);
103 g_value_set_object (value, priv->contact);
106 g_value_set_object (value, priv->gfile);
109 g_value_set_object (value, priv->tpfile);
112 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
117 do_set_property (GObject *object,
122 EmpathyFTHandlerPriv *priv = GET_PRIV (object);
127 priv->contact = g_value_dup_object (value);
130 priv->gfile = g_value_dup_object (value);
133 priv->tpfile = g_value_dup_object (value);
136 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
141 do_dispose (GObject *object)
143 EmpathyFTHandlerPriv *priv = GET_PRIV (object);
145 if (priv->dispose_run)
148 priv->dispose_run = TRUE;
151 g_object_unref (priv->contact);
152 priv->contact = NULL;
156 g_object_unref (priv->gfile);
161 empathy_tp_file_close (priv->tpfile);
162 g_object_unref (priv->tpfile);
166 if (priv->cancellable) {
167 g_object_unref (priv->cancellable);
168 priv->cancellable = NULL;
171 G_OBJECT_CLASS (empathy_ft_handler_parent_class)->dispose (object);
175 do_finalize (GObject *object)
177 G_OBJECT_CLASS (empathy_ft_handler_parent_class)->finalize (object);
181 empathy_ft_handler_class_init (EmpathyFTHandlerClass *klass)
183 GObjectClass *object_class = G_OBJECT_CLASS (klass);
184 GParamSpec *param_spec;
186 g_type_class_add_private (klass, sizeof (EmpathyFTHandlerPriv));
188 object_class->get_property = do_get_property;
189 object_class->set_property = do_set_property;
190 object_class->dispose = do_dispose;
191 object_class->finalize = do_finalize;
194 param_spec = g_param_spec_object ("contact",
195 "contact", "The remote contact",
196 EMPATHY_TYPE_CONTACT,
197 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
198 g_object_class_install_property (object_class, PROP_CONTACT, param_spec);
200 param_spec = g_param_spec_object ("gfile",
201 "gfile", "The GFile we're handling",
203 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
204 g_object_class_install_property (object_class, PROP_G_FILE, param_spec);
206 param_spec = g_param_spec_object ("tp-file",
207 "tp-file", "The file's channel wrapper",
208 EMPATHY_TYPE_TP_FILE,
209 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
210 g_object_class_install_property (object_class, PROP_TP_FILE, param_spec);
213 signals[TRANSFER_STARTED] =
214 g_signal_new ("transfer-started", G_TYPE_FROM_CLASS (klass),
215 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
216 g_cclosure_marshal_VOID__OBJECT,
218 1, EMPATHY_TYPE_TP_FILE);
220 signals[TRANSFER_DONE] =
221 g_signal_new ("transfer-done", G_TYPE_FROM_CLASS (klass),
222 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
223 g_cclosure_marshal_VOID__OBJECT,
225 1, EMPATHY_TYPE_TP_FILE);
227 signals[TRANSFER_ERROR] =
228 g_signal_new ("transfer-error", G_TYPE_FROM_CLASS (klass),
229 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
230 g_cclosure_marshal_VOID__POINTER,
234 signals[TRANSFER_PROGRESS] =
235 g_signal_new ("transfer-progress", G_TYPE_FROM_CLASS (klass),
236 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
237 _empathy_marshal_VOID__OBJECT_UINT64_UINT64,
239 2, EMPATHY_TYPE_TP_FILE, G_TYPE_UINT64, G_TYPE_UINT64);
241 signals[HASHING_STARTED] =
242 g_signal_new ("hashing-started", G_TYPE_FROM_CLASS (klass),
243 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
244 g_cclosure_marshal_VOID__VOID,
247 signals[HASHING_PROGRESS] =
248 g_signal_new ("hashing-progress", G_TYPE_FROM_CLASS (klass),
249 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
250 _empathy_marshal_VOID__UINT64_UINT64,
252 2, G_TYPE_UINT64, G_TYPE_UINT64);
254 signals[HASHING_DONE] =
255 g_signal_new ("hashing-done", G_TYPE_FROM_CLASS (klass),
256 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
257 g_cclosure_marshal_VOID__VOID,
262 empathy_ft_handler_init (EmpathyFTHandler *self)
264 EmpathyFTHandlerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
265 EMPATHY_TYPE_FT_HANDLER, EmpathyFTHandlerPriv);
270 /* private functions */
273 hash_data_free (HashingData *data)
275 if (data->buffer != NULL)
277 g_free (data->buffer);
281 if (data->stream != NULL)
283 g_object_unref (data->stream);
287 if (data->checksum != NULL)
289 g_checksum_free (data->checksum);
290 data->checksum = NULL;
293 if (data->error != NULL)
295 g_error_free (data->error);
299 g_slice_free (HashingData, data);
303 request_data_free (RequestData *data)
305 if (data->gfile != NULL)
307 g_object_unref (data->gfile);
311 if (data->request != NULL)
313 g_hash_table_unref (data->request);
314 data->request = NULL;
317 g_slice_free (RequestData, data);
321 request_data_new (EmpathyFTHandler *handler, GFile *gfile)
325 ret = g_slice_new0 (RequestData);
326 ret->request = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
327 (GDestroyNotify) tp_g_value_slice_free);
328 ret->handler = g_object_ref (handler);
329 ret->gfile = g_object_ref (gfile);
335 ft_handler_create_channel_cb (EmpathyDispatchOperation *operation,
339 RequestData *req_data = user_data;
340 GError *myerr = NULL;
341 EmpathyFTHandlerPriv *priv = GET_PRIV (req_data->handler);
343 DEBUG ("FT: dispatcher create channel CB");
347 /* TODO: error handling */
351 priv->tpfile = g_object_ref
352 (empathy_dispatch_operation_get_channel_wrapper (operation));
353 empathy_tp_file_offer (priv->tpfile, req_data->gfile, &myerr);
354 empathy_dispatch_operation_claim (operation);
357 request_data_free (req_data);
361 ft_handler_push_to_dispatcher (RequestData *req_data)
363 EmpathyDispatcher *dispatcher;
365 EmpathyFTHandlerPriv *priv = GET_PRIV (req_data->handler);
367 DEBUG ("FT: pushing request to the dispatcher");
369 dispatcher = empathy_dispatcher_dup_singleton ();
370 account = empathy_contact_get_account (priv->contact);
372 empathy_dispatcher_create_channel (dispatcher, account, req_data->request,
373 ft_handler_create_channel_cb, req_data);
375 g_object_unref (dispatcher);
379 ft_handler_check_if_allowed (EmpathyFTHandler *handler)
381 EmpathyDispatcher *dispatcher;
382 EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
387 dispatcher = empathy_dispatcher_dup_singleton ();
388 account = empathy_contact_get_account (priv->contact);
390 allowed = empathy_dispatcher_find_channel_class (dispatcher, account,
391 EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, TP_HANDLE_TYPE_CONTACT);
393 if (!tp_strv_contains ((const gchar * const *) allowed,
394 TP_IFACE_CHANNEL ".TargetHandle"))
397 g_object_unref (dispatcher);
403 ft_handler_populate_outgoing_request (RequestData *req_data,
404 GFileInfo *file_info)
406 guint contact_handle;
407 const char *content_type;
408 const char *display_name;
412 GHashTable *request = req_data->request;
413 EmpathyFTHandlerPriv *priv = GET_PRIV (req_data->handler);
415 /* gather all the information */
416 contact_handle = empathy_contact_get_handle (priv->contact);
418 content_type = g_file_info_get_content_type (file_info);
419 display_name = g_file_info_get_display_name (file_info);
420 size = g_file_info_get_size (file_info);
421 g_file_info_get_modification_time (file_info, &mtime);
423 /* org.freedesktop.Telepathy.Channel.ChannelType */
424 value = tp_g_value_slice_new (G_TYPE_STRING);
425 g_value_set_string (value, EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER);
426 g_hash_table_insert (request, TP_IFACE_CHANNEL ".ChannelType", value);
428 /* org.freedesktop.Telepathy.Channel.TargetHandleType */
429 value = tp_g_value_slice_new (G_TYPE_UINT);
430 g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT);
431 g_hash_table_insert (request, TP_IFACE_CHANNEL ".TargetHandleType", value);
433 /* org.freedesktop.Telepathy.Channel.TargetHandle */
434 value = tp_g_value_slice_new (G_TYPE_UINT);
435 g_value_set_uint (value, contact_handle);
436 g_hash_table_insert (request, TP_IFACE_CHANNEL ".TargetHandle", value);
438 /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.ContentType */
439 value = tp_g_value_slice_new (G_TYPE_STRING);
440 g_value_set_string (value, content_type);
441 g_hash_table_insert (request,
442 EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentType", value);
444 /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.Filename */
445 value = tp_g_value_slice_new (G_TYPE_STRING);
446 g_value_set_string (value, display_name);
447 g_hash_table_insert (request,
448 EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Filename", value);
450 /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.Size */
451 value = tp_g_value_slice_new (G_TYPE_UINT64);
452 g_value_set_uint64 (value, (guint64) size);
453 g_hash_table_insert (request,
454 EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Size", value);
456 /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.Date */
457 value = tp_g_value_slice_new (G_TYPE_UINT64);
458 g_value_set_uint64 (value, (guint64) mtime.tv_sec);
459 g_hash_table_insert (request,
460 EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Date", value);
464 hash_job_async_close_stream_cb (GObject *source,
468 HashingData *hash_data = user_data;
469 RequestData *req_data = hash_data->req_data;
470 GError *error = NULL;
474 DEBUG ("FT: closing stream after hashing.");
476 /* if we're here we for sure have done reading, check if we stopped due
479 g_input_stream_close_finish (hash_data->stream, res, &error);
482 if (hash_data->error != NULL)
484 /* if we already stopped due to an error, probably we're completely
485 * hosed for some reason. just return the first read error
488 g_clear_error (&error);
489 error = hash_data->error;
495 if (hash_data->error != NULL)
497 error = hash_data->error;
501 /* set the checksum in the request */
502 request = req_data->request;
504 DEBUG ("FT: got file hash %s", g_checksum_get_string (hash_data->checksum));
506 /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.ContentHash */
507 value = tp_g_value_slice_new (G_TYPE_STRING);
508 g_value_set_string (value, g_checksum_get_string (hash_data->checksum));
509 g_hash_table_insert (request,
510 EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentHash", value);
513 hash_data_free (hash_data);
517 g_signal_emit (req_data->handler, signals[TRANSFER_ERROR], 0, error);
518 g_clear_error (&error);
519 request_data_free (req_data);
523 /* the request is complete now, push it to the dispatcher */
524 ft_handler_push_to_dispatcher (req_data);
529 hash_job_async_read_cb (GObject *source,
533 HashingData *hash_data = user_data;
534 RequestData *req_data = hash_data->req_data;
536 GError *error = NULL;
538 DEBUG ("FT: reading a chunk for hashing.");
540 bytes_read = g_input_stream_read_finish (hash_data->stream, res, &error);
543 hash_data->error = error;
544 hash_data->done_reading = TRUE;
548 hash_data->total_read += bytes_read;
550 /* we now have the chunk */
553 hash_data->done_reading = TRUE;
558 g_checksum_update (hash_data->checksum, hash_data->buffer, bytes_read);
559 g_signal_emit (req_data->handler, signals[HASHING_PROGRESS], 0,
560 (guint64) hash_data->total_read, (guint64) req_data->total_size);
564 g_free (hash_data->buffer);
565 hash_data->buffer = NULL;
567 schedule_hash_chunk (hash_data);
571 schedule_hash_chunk (HashingData *hash_data)
573 EmpathyFTHandlerPriv *priv;
574 RequestData *req_data = hash_data->req_data;
576 priv = GET_PRIV (req_data->handler);
578 if (hash_data->done_reading)
580 g_input_stream_close_async (hash_data->stream, G_PRIORITY_DEFAULT,
581 priv->cancellable, hash_job_async_close_stream_cb, hash_data);
585 if (hash_data->buffer == NULL)
586 hash_data->buffer = g_malloc0 (BUFFER_SIZE);
588 g_input_stream_read_async (hash_data->stream, hash_data->buffer,
589 BUFFER_SIZE, G_PRIORITY_DEFAULT, priv->cancellable,
590 hash_job_async_read_cb, hash_data);
595 ft_handler_read_async_cb (GObject *source,
599 GFileInputStream *stream;
600 GError *error = NULL;
601 HashingData *hash_data;
604 RequestData *req_data = user_data;
606 DEBUG ("FT: GFile read async CB.");
608 stream = g_file_read_finish (req_data->gfile, res, &error);
611 g_signal_emit (req_data->handler, signals[TRANSFER_ERROR], 0, error);
613 request_data_free (req_data);
614 g_clear_error (&error);
619 hash_data = g_slice_new0 (HashingData);
620 hash_data->stream = G_INPUT_STREAM (stream);
621 hash_data->done_reading = FALSE;
622 hash_data->req_data = req_data;
623 /* FIXME: should look at the CM capabilities before setting the
626 hash_data->checksum = g_checksum_new (G_CHECKSUM_MD5);
628 request = req_data->request;
630 /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.ContentHashType */
631 value = tp_g_value_slice_new (G_TYPE_UINT);
632 g_value_set_uint (value, EMP_FILE_HASH_TYPE_MD5);
633 g_hash_table_insert (request,
634 EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentHashType", value);
636 g_signal_emit (req_data->handler, signals[HASHING_STARTED], 0);
638 schedule_hash_chunk (hash_data);
642 ft_handler_gfile_ready_cb (GObject *source,
644 RequestData *req_data)
647 GError *error = NULL;
648 EmpathyFTHandlerPriv *priv = GET_PRIV (req_data->handler);
650 DEBUG ("FT: got GFileInfo.");
652 info = g_file_query_info_finish (req_data->gfile, res, &error);
655 g_signal_emit (req_data->handler, signals[TRANSFER_ERROR], 0, error);
657 request_data_free (req_data);
658 g_clear_error (&error);
663 ft_handler_populate_outgoing_request (req_data, info);
665 req_data->total_size = g_file_info_get_size (info);
667 /* now start hashing the file */
668 g_file_read_async (req_data->gfile, G_PRIORITY_DEFAULT,
669 priv->cancellable, ft_handler_read_async_cb, req_data);
673 ft_handler_contact_ready_cb (EmpathyContact *contact,
676 GObject *weak_object)
678 RequestData *req_data = user_data;
679 EmpathyFTHandlerPriv *priv = GET_PRIV (req_data->handler);
680 GError *myerr = NULL;
682 g_assert (priv->contact != NULL);
683 g_assert (priv->gfile != NULL);
685 DEBUG ("FT: contact is ready.");
687 g_cancellable_set_error_if_cancelled (priv->cancellable, &myerr);
693 myerr = g_error_copy (error);
697 /* check if FT is allowed before firing up the I/O machinery */
698 if (!ft_handler_check_if_allowed (req_data->handler))
700 g_set_error_literal (&myerr, EMPATHY_FT_ERROR_QUARK,
701 EMPATHY_FT_ERROR_NOT_SUPPORTED,
702 _("File transfer not supported by remote contact"));
709 g_signal_emit (req_data->handler, signals[TRANSFER_ERROR], 0, myerr);
711 request_data_free (req_data);
712 g_clear_error (&myerr);
717 /* start collecting info about the file */
718 g_file_query_info_async (req_data->gfile,
719 G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME ","
720 G_FILE_ATTRIBUTE_STANDARD_SIZE ","
721 G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
722 G_FILE_ATTRIBUTE_TIME_MODIFIED,
723 G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT,
724 priv->cancellable, (GAsyncReadyCallback) ft_handler_gfile_ready_cb,
731 empathy_ft_handler_new_outgoing (EmpathyContact *contact,
734 g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
735 g_return_val_if_fail (G_IS_FILE (source), NULL);
737 return g_object_new (EMPATHY_TYPE_FT_HANDLER,
738 "contact", contact, "gfile", source, NULL);
742 empathy_ft_handler_new_incoming (EmpathyTpFile *tp_file,
745 g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file), NULL);
746 g_return_val_if_fail (G_IS_FILE (destination), NULL);
748 return g_object_new (EMPATHY_TYPE_FT_HANDLER,
749 "tp-file", tp_file, "gfile", destination, NULL);
753 empathy_ft_handler_start_transfer (EmpathyFTHandler *handler,
754 GCancellable *cancellable)
757 EmpathyFTHandlerPriv *priv;
758 GError *error = NULL;
760 g_return_if_fail (EMPATHY_IS_FT_HANDLER (handler));
762 priv = GET_PRIV (handler);
763 priv->cancellable = g_object_ref (cancellable);
765 if (priv->tpfile == NULL)
767 data = request_data_new (handler, priv->gfile);
768 empathy_contact_call_when_ready (priv->contact,
769 EMPATHY_CONTACT_READY_HANDLE,
770 ft_handler_contact_ready_cb, data, NULL, G_OBJECT (handler));
774 /* TODO: add support for resume. */
775 empathy_tp_file_accept (priv->tpfile, 0, priv->gfile, &error);