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 <telepathy-glib/util.h>
28 #include "empathy-ft-handler.h"
29 #include "empathy-dispatcher.h"
30 #include "empathy-utils.h"
32 G_DEFINE_TYPE (EmpathyFTHandler, empathy_ft_handler, G_TYPE_OBJECT)
34 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyFTHandler)
36 #define BUFFER_SIZE 4096
55 EmpathyFTHandler *handler;
61 RequestData *req_data;
63 gboolean done_reading;
72 EmpathyContact *contact;
74 EmpathyTpFile *tpfile;
75 GCancellable *cancellable;
76 } EmpathyFTHandlerPriv;
79 static void schedule_hash_chunk (HashingData *hash_data);
81 /* GObject implementations */
83 do_get_property (GObject *object,
88 EmpathyFTHandlerPriv *priv = GET_PRIV (object);
93 g_value_set_object (value, priv->contact);
96 g_value_set_object (value, priv->gfile);
99 g_value_set_object (value, priv->tpfile);
102 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
107 do_set_property (GObject *object,
112 EmpathyFTHandlerPriv *priv = GET_PRIV (object);
117 priv->contact = g_value_dup_object (value);
120 priv->gfile = g_value_dup_object (value);
123 priv->tpfile = g_value_dup_object (value);
126 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
131 do_dispose (GObject *object)
133 EmpathyFTHandlerPriv *priv = GET_PRIV (object);
135 if (priv->dispose_run)
138 priv->dispose_run = TRUE;
141 g_object_unref (priv->contact);
142 priv->contact = NULL;
146 g_object_unref (priv->gfile);
151 empathy_tp_file_close (priv->tpfile);
152 g_object_unref (priv->tpfile);
156 if (priv->cancellable) {
157 g_object_unref (priv->cancellable);
158 priv->cancellable = NULL;
161 G_OBJECT_CLASS (empathy_ft_handler_parent_class)->dispose (object);
165 do_finalize (GObject *object)
167 G_OBJECT_CLASS (empathy_ft_handler_parent_class)->finalize (object);
171 empathy_ft_handler_class_init (EmpathyFTHandlerClass *klass)
173 GObjectClass *object_class = G_OBJECT_CLASS (klass);
174 GParamSpec *param_spec;
176 g_type_class_add_private (klass, sizeof (EmpathyFTHandlerPriv));
178 object_class->get_property = do_get_property;
179 object_class->set_property = do_set_property;
180 object_class->dispose = do_dispose;
181 object_class->finalize = do_finalize;
184 param_spec = g_param_spec_object ("contact",
185 "contact", "The remote contact",
186 EMPATHY_TYPE_CONTACT,
187 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
188 g_object_class_install_property (object_class, PROP_CONTACT, param_spec);
190 param_spec = g_param_spec_object ("gfile",
191 "gfile", "The GFile we're handling",
193 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
194 g_object_class_install_property (object_class, PROP_G_FILE, param_spec);
196 param_spec = g_param_spec_object ("tp-file",
197 "tp-file", "The file's channel wrapper",
198 EMPATHY_TYPE_TP_FILE,
199 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
200 g_object_class_install_property (object_class, PROP_TP_FILE, param_spec);
203 signals[TRANSFER_STARTED] =
204 g_signal_new ("transfer-started", G_TYPE_FROM_CLASS (klass),
205 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
206 g_cclosure_marshal_VOID__OBJECT,
208 1, EMPATHY_TYPE_TP_FILE);
210 signals[TRANSFER_DONE] =
211 g_signal_new ("transfer-done", G_TYPE_FROM_CLASS (klass),
212 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
213 g_cclosure_marshal_VOID__OBJECT,
215 1, EMPATHY_TYPE_TP_FILE);
217 signals[TRANSFER_ERROR] =
218 g_signal_new ("transfer-error", G_TYPE_FROM_CLASS (klass),
219 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
220 g_cclosure_marshal_VOID__OBJECT_POINTER,
222 2, EMPATHY_TYPE_TP_FILE, G_TYPE_POINTER);
224 signals[TRANSFER_PROGRESS] =
225 g_signal_new ("transfer-progress", G_TYPE_FROM_CLASS (klass),
226 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
227 _empathy_marshal_VOID__OBJECT_UINT64_UINT64,
229 2, EMPATHY_TYPE_TP_FILE, G_TYPE_UINT64, G_TYPE_UINT64);
231 signals[HASHING_STARTED] =
232 g_signal_new ("hashing-started", G_TYPE_FROM_CLASS (klass),
233 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
234 g_cclosure_marshal_VOID__VOID,
237 signals[HASHING_PROGRESS] =
238 g_signal_new ("hashing-progress", G_TYPE_FROM_CLASS (klass),
239 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
240 _empathy_marshal_VOID__UINT64_UINT64,
242 2, G_TYPE_UINT64, G_TYPE_UINT64);
244 signals[HASHING_DONE] =
245 g_signal_new ("hashing-done", G_TYPE_FROM_CLASS (klass),
246 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
247 g_cclosure_marshal_VOID__VOID,
252 empathy_ft_handler_init (EmpathyFTHandler *self)
254 EmpathyFTHandlerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
255 EMPATHY_TYPE_FT_HANDLER, EmpathyFTHandlerPriv);
260 /* private functions */
263 hash_data_free (HashingData *data)
265 if (data->buffer != NULL)
267 g_free (data->buffer);
271 if (data->stream != NULL)
273 g_object_unref (data->stream);
277 if (data->checksum != NULL)
279 g_checksum_free (data->checksum);
280 data->checksum = NULL;
283 if (data->error != NULL)
285 g_error_free (data->error);
289 g_slice_free (HashingData, data);
293 request_data_free (RequestData *data)
295 if (data->gfile != NULL)
297 g_object_unref (data->gfile);
301 if (data->request != NULL)
303 g_hash_table_unref (data->request);
304 data->request = NULL;
307 g_slice_free (RequestData, data);
311 request_data_new (EmpathyFTHandler *handler, GFile *gfile)
315 ret = g_slice_new0 (RequestData);
316 ret->request = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
317 (GDestroyNotify) tp_g_value_slice_free);
318 ret->handler = g_object_ref (handler);
319 ret->gfile = g_object_ref (gfile);
325 ft_handler_create_channel_cb (EmpathyDispatchOperation *operation,
329 RequestData *req_data = user_data;
330 GError *myerr = NULL;
331 EmpathyFTHandlerPriv *priv = GET_PRIV (req_data->handler);
335 /* TODO: error handling */
339 priv->tpfile = g_object_ref
340 (empathy_dispatch_operation_get_channel_wrapper (operation));
341 empathy_tp_file_offer (priv->tpfile, req_data->gfile, &myerr);
342 empathy_dispatch_operation_claim (operation);
345 request_data_free (req_data);
349 ft_handler_push_to_dispatcher (RequestData *req_data)
351 EmpathyDispatcher *dispatcher;
353 EmpathyFTHandlerPriv *priv = GET_PRIV (req_data->handler);
355 dispatcher = empathy_dispatcher_dup_singleton ();
356 account = empathy_contact_get_account (priv->contact);
358 empathy_dispatcher_create_channel (dispatcher, account, req_data->request,
359 ft_handler_create_channel_cb, req_data);
361 g_object_unref (dispatcher);
365 ft_handler_check_if_allowed (EmpathyFTHandler *handler)
367 EmpathyDispatcher *dispatcher;
368 EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
373 dispatcher = empathy_dispatcher_dup_singleton ();
374 account = empathy_contact_get_account (priv->contact);
376 allowed = empathy_dispatcher_find_channel_class (dispatcher, account,
377 EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, TP_HANDLE_TYPE_CONTACT);
379 if (!tp_strv_contains ((const gchar * const *) allowed,
380 TP_IFACE_CHANNEL ".TargetHandle"))
383 g_object_unref (dispatcher);
389 ft_handler_populate_outgoing_request (RequestData *req_data,
390 GFileInfo *file_info)
392 guint contact_handle;
393 const char *content_type;
394 const char *display_name;
398 GHashTable *request = req_data->request;
399 EmpathyFTHandlerPriv *priv = GET_PRIV (req_data->handler);
401 /* gather all the information */
402 contact_handle = empathy_contact_get_handle (priv->contact);
404 content_type = g_file_info_get_content_type (file_info);
405 display_name = g_file_info_get_display_name (file_info);
406 size = g_file_info_get_size (file_info);
407 g_file_info_get_modification_time (file_info, &mtime);
409 /* org.freedesktop.Telepathy.Channel.ChannelType */
410 value = tp_g_value_slice_new (G_TYPE_STRING);
411 g_value_set_string (value, EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER);
412 g_hash_table_insert (request, TP_IFACE_CHANNEL ".ChannelType", value);
414 /* org.freedesktop.Telepathy.Channel.TargetHandleType */
415 value = tp_g_value_slice_new (G_TYPE_UINT);
416 g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT);
417 g_hash_table_insert (request, TP_IFACE_CHANNEL ".TargetHandleType", value);
419 /* org.freedesktop.Telepathy.Channel.TargetHandle */
420 value = tp_g_value_slice_new (G_TYPE_UINT);
421 g_value_set_uint (value, contact_handle);
422 g_hash_table_insert (request, TP_IFACE_CHANNEL ".TargetHandle", value);
424 /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.ContentType */
425 value = tp_g_value_slice_new (G_TYPE_STRING);
426 g_value_set_string (value, content_type);
427 g_hash_table_insert (request,
428 EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentType", value);
430 /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.Filename */
431 value = tp_g_value_slice_new (G_TYPE_STRING);
432 g_value_set_string (value, display_name);
433 g_hash_table_insert (request,
434 EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Filename", value);
436 /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.Size */
437 value = tp_g_value_slice_new (G_TYPE_UINT64);
438 g_value_set_uint64 (value, (guint64) size);
439 g_hash_table_insert (request,
440 EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Size", value);
442 /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.Date */
443 value = tp_g_value_slice_new (G_TYPE_UINT64);
444 g_value_set_uint64 (value, (guint64) mtime.tv_sec);
445 g_hash_table_insert (request,
446 EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Date", value);
450 hash_job_async_close_stream_cb (GObject *source,
454 HashingData *hash_data = user_data;
455 RequestData *req_data = hash_data->req_data;
456 GError *error = NULL;
460 /* if we're here we for sure have done reading, check if we stopped due
463 g_input_stream_close_finish (hash_data->stream, res, &error);
466 if (hash_data->error != NULL)
468 /* if we already stopped due to an error, probably we're completely
469 * hosed for some reason. just return the first read error
472 g_clear_error (&error);
473 error = hash_data->error;
479 if (hash_data->error != NULL)
481 error = hash_data->error;
485 /* set the checksum in the request */
486 request = req_data->request;
488 /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.ContentHash */
489 value = tp_g_value_slice_new (G_TYPE_STRING);
490 g_value_set_string (value, g_checksum_get_string (hash_data->checksum));
491 g_hash_table_insert (request,
492 EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentHash", value);
495 hash_data_free (hash_data);
499 /* TODO: error handling. */
503 /* the request is complete now, push it to the dispatcher */
504 ft_handler_push_to_dispatcher (req_data);
509 hash_job_async_read_cb (GObject *source,
513 HashingData *hash_data = user_data;
515 GError *error = NULL;
517 bytes_read = g_input_stream_read_finish (hash_data->stream, res, &error);
520 hash_data->error = error;
521 hash_data->done_reading = TRUE;
525 /* TODO: notify progress */
527 /* we now have the chunk */
530 hash_data->done_reading = TRUE;
531 schedule_hash_chunk (hash_data);
536 g_checksum_update (hash_data->checksum, hash_data->buffer, bytes_read);
540 g_free (hash_data->buffer);
541 hash_data->buffer = NULL;
543 schedule_hash_chunk (hash_data);
547 schedule_hash_chunk (HashingData *hash_data)
549 if (hash_data->done_reading)
551 g_input_stream_close_async (hash_data->stream, G_PRIORITY_DEFAULT,
552 NULL, hash_job_async_close_stream_cb, hash_data);
556 if (hash_data->buffer == NULL)
557 hash_data->buffer = g_malloc0 (BUFFER_SIZE);
559 g_input_stream_read_async (hash_data->stream, hash_data->buffer,
560 BUFFER_SIZE, G_PRIORITY_DEFAULT, NULL,
561 hash_job_async_read_cb, hash_data);
566 ft_handler_read_async_cb (GObject *source,
570 GFileInputStream *stream;
571 GError *error = NULL;
572 HashingData *hash_data;
575 RequestData *req_data = user_data;
577 stream = g_file_read_finish (req_data->gfile, res, &error);
580 /* TODO: error handling. */
584 hash_data = g_slice_new0 (HashingData);
585 hash_data->stream = G_INPUT_STREAM (stream);
586 hash_data->done_reading = FALSE;
587 hash_data->req_data = req_data;
588 /* FIXME: should look at the CM capabilities before setting the
591 hash_data->checksum = g_checksum_new (G_CHECKSUM_MD5);
593 request = req_data->request;
595 /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.ContentHashType */
596 value = tp_g_value_slice_new (G_TYPE_UINT);
597 g_value_set_uint (value, EMP_FILE_HASH_TYPE_MD5);
598 g_hash_table_insert (request,
599 EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentHashType", value);
601 schedule_hash_chunk (hash_data);
605 ft_handler_gfile_ready_cb (GObject *source,
607 RequestData *req_data)
610 GError *error = NULL;
612 info = g_file_query_info_finish (req_data->gfile, res, &error);
615 /* TODO: error handling. */
619 ft_handler_populate_outgoing_request (req_data, info);
621 /* now start hashing the file */
622 g_file_read_async (req_data->gfile, G_PRIORITY_DEFAULT,
623 NULL, ft_handler_read_async_cb, req_data);
627 ft_handler_contact_ready_cb (EmpathyContact *contact,
630 GObject *weak_object)
632 RequestData *req_data = user_data;
633 EmpathyFTHandlerPriv *priv = GET_PRIV (req_data->handler);
635 g_assert (priv->contact != NULL);
636 g_assert (priv->gfile != NULL);
638 /* check if FT is allowed before firing up the I/O machinery */
639 if (!ft_handler_check_if_allowed (req_data->handler))
641 /* TODO: error handling. */
642 request_data_free (req_data);
646 /* start collecting info about the file */
647 g_file_query_info_async (req_data->gfile,
648 G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME ","
649 G_FILE_ATTRIBUTE_STANDARD_SIZE ","
650 G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
651 G_FILE_ATTRIBUTE_TIME_MODIFIED,
652 G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT,
653 NULL, (GAsyncReadyCallback) ft_handler_gfile_ready_cb,
660 empathy_ft_handler_new_outgoing (EmpathyContact *contact,
663 g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
664 g_return_val_if_fail (G_IS_FILE (source), NULL);
666 return g_object_new (EMPATHY_TYPE_FT_HANDLER,
667 "contact", contact, "gfile", source, NULL);
671 empathy_ft_handler_new_incoming (EmpathyTpFile *tp_file,
674 g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file), NULL);
675 g_return_val_if_fail (G_IS_FILE (destination), NULL);
677 return g_object_new (EMPATHY_TYPE_FT_HANDLER,
678 "tp-file", tp_file, "gfile", destination, NULL);
682 empathy_ft_handler_start_transfer (EmpathyFTHandler *handler,
683 GCancellable *cancellable)
686 EmpathyFTHandlerPriv *priv;
687 GError *error = NULL;
689 g_return_if_fail (EMPATHY_IS_FT_HANDLER (handler));
691 priv = GET_PRIV (handler);
692 priv->cancellable = g_object_ref (cancellable);
694 if (priv->tpfile == NULL)
696 data = request_data_new (handler, priv->gfile);
697 empathy_contact_call_when_ready (priv->contact,
698 EMPATHY_CONTACT_READY_HANDLE,
699 ft_handler_contact_ready_cb, data, NULL, G_OBJECT (handler));
703 /* TODO: add support for resume. */
704 empathy_tp_file_accept (priv->tpfile, 0, priv->gfile, &error);