]> git.0d.be Git - empathy.git/blob - libempathy/empathy-ft-handler.c
hindi translation by Chandan Kumar
[empathy.git] / libempathy / empathy-ft-handler.c
1 /*
2  * empathy-ft-handler.c - Source for EmpathyFTHandler
3  * Copyright (C) 2009 Collabora Ltd.
4  *
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.
9  *
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.
14  *
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
18  *
19  * Author: Cosimo Cecchi <cosimo.cecchi@collabora.co.uk>
20  */
21
22 /* empathy-ft-handler.c */
23
24 #include "config.h"
25
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <telepathy-glib/account-channel-request.h>
29 #include <telepathy-glib/util.h>
30 #include <telepathy-glib/dbus.h>
31 #include <telepathy-glib/interfaces.h>
32
33 #include "empathy-ft-handler.h"
34 #include "empathy-tp-contact-factory.h"
35 #include "empathy-time.h"
36 #include "empathy-utils.h"
37
38 #define DEBUG_FLAG EMPATHY_DEBUG_FT
39 #include "empathy-debug.h"
40
41 /**
42  * SECTION:empathy-ft-handler
43  * @title: EmpathyFTHandler
44  * @short_description: an object representing a File Transfer
45  * @include: libempathy/empathy-ft-handler
46  *
47  * #EmpathyFTHandler is the object which represents a File Transfer with all
48  * its properties.
49  * The creation of an #EmpathyFTHandler is done with
50  * empathy_ft_handler_new_outgoing() or empathy_ft_handler_new_incoming(),
51  * even though clients should not need to call them directly, as
52  * #EmpathyFTFactory does it for them. Remember that for the file transfer
53  * to work with an incoming handler,
54  * empathy_ft_handler_incoming_set_destination() should be called after
55  * empathy_ft_handler_new_incoming(). #EmpathyFTFactory does this
56  * automatically.
57  * It's important to note that, as the creation of the handlers is async, once
58  * an handler is created, it already has all the interesting properties set,
59  * like filename, total bytes, content type and so on, making it useful
60  * to be displayed in an UI.
61  * The transfer API works like a state machine; it has three signals,
62  * ::transfer-started, ::transfer-progress, ::transfer-done, which will be
63  * emitted in the relevant phases.
64  * In addition, if the handler is created with checksumming enabled,
65  * other three signals (::hashing-started, ::hashing-progress, ::hashing-done)
66  * will be emitted before or after the transfer, depending on the direction
67  * (respectively outgoing and incoming) of the handler.
68  * At any time between the call to empathy_ft_handler_start_transfer() and
69  * the last signal, a ::transfer-error can be emitted, indicating that an
70  * error has happened in the operation. The message of the error is localized
71  * to use in an UI.
72  */
73
74 G_DEFINE_TYPE (EmpathyFTHandler, empathy_ft_handler, G_TYPE_OBJECT)
75
76 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyFTHandler)
77
78 #define BUFFER_SIZE 4096
79
80 enum {
81   PROP_CHANNEL = 1,
82   PROP_G_FILE,
83   PROP_CONTACT,
84   PROP_CONTENT_TYPE,
85   PROP_DESCRIPTION,
86   PROP_FILENAME,
87   PROP_MODIFICATION_TIME,
88   PROP_TOTAL_BYTES,
89   PROP_TRANSFERRED_BYTES,
90   PROP_USER_ACTION_TIME
91 };
92
93 enum {
94   HASHING_STARTED,
95   HASHING_PROGRESS,
96   HASHING_DONE,
97   TRANSFER_STARTED,
98   TRANSFER_PROGRESS,
99   TRANSFER_DONE,
100   TRANSFER_ERROR,
101   LAST_SIGNAL
102 };
103
104 typedef struct {
105   GInputStream *stream;
106   GError *error /* comment to make the style checker happy */;
107   guchar *buffer;
108   GChecksum *checksum;
109   gssize total_read;
110   guint64 total_bytes;
111   EmpathyFTHandler *handler;
112 } HashingData;
113
114 typedef struct {
115   EmpathyFTHandlerReadyCallback callback;
116   gpointer user_data;
117   EmpathyFTHandler *handler;
118 } CallbacksData;
119
120 /* private data */
121 typedef struct {
122   gboolean dispose_run;
123
124   GFile *gfile;
125   TpFileTransferChannel *channel;
126   GCancellable *cancellable;
127   gboolean use_hash;
128
129   /* request for the new transfer */
130   GHashTable *request;
131
132   /* transfer properties */
133   EmpathyContact *contact;
134   gchar *content_type;
135   gchar *filename;
136   gchar *description;
137   guint64 total_bytes;
138   guint64 transferred_bytes;
139   guint64 mtime;
140   gchar *content_hash;
141   TpFileHashType content_hash_type;
142
143   gint64 user_action_time;
144
145   /* time and speed */
146   gdouble speed;
147   guint remaining_time;
148   gint64 last_update_time;
149
150   gboolean is_completed;
151 } EmpathyFTHandlerPriv;
152
153 static guint signals[LAST_SIGNAL] = { 0 };
154
155 static gboolean do_hash_job_incoming (GIOSchedulerJob *job,
156     GCancellable *cancellable, gpointer user_data);
157
158 /* GObject implementations */
159 static void
160 do_get_property (GObject *object,
161     guint property_id,
162     GValue *value,
163     GParamSpec *pspec)
164 {
165   EmpathyFTHandlerPriv *priv = GET_PRIV (object);
166
167   switch (property_id)
168     {
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);
201     }
202 }
203
204 static void
205 do_set_property (GObject *object,
206     guint property_id,
207     const GValue *value,
208     GParamSpec *pspec)
209 {
210   EmpathyFTHandlerPriv *priv = GET_PRIV (object);
211
212   switch (property_id)
213     {
214       case PROP_CONTACT:
215         priv->contact = g_value_dup_object (value);
216         break;
217       case PROP_CONTENT_TYPE:
218         priv->content_type = g_value_dup_string (value);
219         break;
220       case PROP_DESCRIPTION:
221         priv->description = g_value_dup_string (value);
222         break;
223       case PROP_FILENAME:
224         priv->filename = g_value_dup_string (value);
225         break;
226       case PROP_MODIFICATION_TIME:
227         priv->mtime = g_value_get_uint64 (value);
228         break;
229       case PROP_TOTAL_BYTES:
230         priv->total_bytes = g_value_get_uint64 (value);
231         break;
232       case PROP_TRANSFERRED_BYTES:
233         priv->transferred_bytes = g_value_get_uint64 (value);
234         break;
235       case PROP_G_FILE:
236         priv->gfile = g_value_dup_object (value);
237         break;
238       case PROP_CHANNEL:
239         priv->channel = g_value_dup_object (value);
240         break;
241       case PROP_USER_ACTION_TIME:
242         priv->user_action_time = g_value_get_int64 (value);
243         break;
244       default:
245         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
246     }
247 }
248
249 static void
250 do_dispose (GObject *object)
251 {
252   EmpathyFTHandlerPriv *priv = GET_PRIV (object);
253
254   if (priv->dispose_run)
255     return;
256
257   priv->dispose_run = TRUE;
258
259   if (priv->contact != NULL) {
260     g_object_unref (priv->contact);
261     priv->contact = NULL;
262   }
263
264   if (priv->gfile != NULL) {
265     g_object_unref (priv->gfile);
266     priv->gfile = NULL;
267   }
268
269   if (priv->channel != NULL) {
270     tp_channel_close_async (TP_CHANNEL (priv->channel), NULL, NULL);
271     g_object_unref (priv->channel);
272     priv->channel = NULL;
273   }
274
275   if (priv->cancellable != NULL) {
276     g_object_unref (priv->cancellable);
277     priv->cancellable = NULL;
278   }
279
280   if (priv->request != NULL)
281     {
282       g_hash_table_unref (priv->request);
283       priv->request = NULL;
284     }
285
286   G_OBJECT_CLASS (empathy_ft_handler_parent_class)->dispose (object);
287 }
288
289 static void
290 do_finalize (GObject *object)
291 {
292   EmpathyFTHandlerPriv *priv = GET_PRIV (object);
293
294   DEBUG ("%p", object);
295
296   g_free (priv->content_type);
297   priv->content_type = NULL;
298
299   g_free (priv->filename);
300   priv->filename = NULL;
301
302   g_free (priv->description);
303   priv->description = NULL;
304
305   g_free (priv->content_hash);
306   priv->content_hash = NULL;
307
308   G_OBJECT_CLASS (empathy_ft_handler_parent_class)->finalize (object);
309 }
310
311 static void
312 empathy_ft_handler_class_init (EmpathyFTHandlerClass *klass)
313 {
314   GObjectClass *object_class = G_OBJECT_CLASS (klass);
315   GParamSpec *param_spec;
316
317   g_type_class_add_private (klass, sizeof (EmpathyFTHandlerPriv));
318
319   object_class->get_property = do_get_property;
320   object_class->set_property = do_set_property;
321   object_class->dispose = do_dispose;
322   object_class->finalize = do_finalize;
323
324   /* properties */
325
326   /**
327    * EmpathyFTHandler:contact:
328    *
329    * The remote #EmpathyContact for the transfer
330    */
331   param_spec = g_param_spec_object ("contact",
332     "contact", "The remote contact",
333     EMPATHY_TYPE_CONTACT,
334     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY);
335   g_object_class_install_property (object_class, PROP_CONTACT, param_spec);
336
337   /**
338    * EmpathyFTHandler:content-type:
339    *
340    * The content type of the file being transferred
341    */
342   param_spec = g_param_spec_string ("content-type",
343     "content-type", "The content type of the file", NULL,
344     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
345   g_object_class_install_property (object_class,
346       PROP_CONTENT_TYPE, param_spec);
347
348   /**
349    * EmpathyFTHandler:description:
350    *
351    * The description of the file being transferred
352    */
353   param_spec = g_param_spec_string ("description",
354     "description", "The description of the file", NULL,
355     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
356   g_object_class_install_property (object_class,
357       PROP_DESCRIPTION, param_spec);
358
359   /**
360    * EmpathyFTHandler:filename:
361    *
362    * The name of the file being transferred
363    */
364   param_spec = g_param_spec_string ("filename",
365     "filename", "The name of the file", NULL,
366     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
367   g_object_class_install_property (object_class,
368       PROP_FILENAME, param_spec);
369
370   /**
371    * EmpathyFTHandler:modification-time:
372    *
373    * The modification time of the file being transferred
374    */
375   param_spec = g_param_spec_uint64 ("modification-time",
376     "modification-time", "The mtime of the file", 0,
377     G_MAXUINT64, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
378   g_object_class_install_property (object_class,
379       PROP_MODIFICATION_TIME, param_spec);
380
381   /**
382    * EmpathyFTHandler:total-bytes:
383    *
384    * The size (in bytes) of the file being transferred
385    */
386   param_spec = g_param_spec_uint64 ("total-bytes",
387     "total-bytes", "The size of the file", 0,
388     G_MAXUINT64, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
389   g_object_class_install_property (object_class,
390       PROP_TOTAL_BYTES, param_spec);
391
392   /**
393    * EmpathyFTHandler:transferred-bytes:
394    *
395    * The number of the bytes already transferred
396    */
397   param_spec = g_param_spec_uint64 ("transferred-bytes",
398     "transferred-bytes", "The number of bytes already transferred", 0,
399     G_MAXUINT64, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
400   g_object_class_install_property (object_class,
401       PROP_TRANSFERRED_BYTES, param_spec);
402
403   /**
404    * EmpathyFTHandler:gfile:
405    *
406    * The #GFile object where the transfer actually happens
407    */
408   param_spec = g_param_spec_object ("gfile",
409     "gfile", "The GFile we're handling",
410     G_TYPE_FILE,
411     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
412   g_object_class_install_property (object_class, PROP_G_FILE, param_spec);
413
414   /**
415    * EmpathyFTHandler:channel:
416    *
417    * The underlying #TpFileTransferChannel managing the transfer
418    */
419   param_spec = g_param_spec_object ("channel",
420     "channel", "The file transfer channel",
421     TP_TYPE_FILE_TRANSFER_CHANNEL,
422     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY);
423   g_object_class_install_property (object_class, PROP_CHANNEL, param_spec);
424
425   param_spec = g_param_spec_int64 ("user-action-time", "user action time",
426     "User action time",
427     0, G_MAXINT64, 0,
428     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY);
429   g_object_class_install_property (object_class, PROP_USER_ACTION_TIME,
430       param_spec);
431
432   /* signals */
433
434   /**
435    * EmpathyFTHandler::transfer-started
436    * @handler: the object which has received the signal
437    * @channel: the #TpFileTransferChannel for which the transfer has started
438    *
439    * This signal is emitted when the actual transfer starts.
440    */
441   signals[TRANSFER_STARTED] =
442     g_signal_new ("transfer-started", G_TYPE_FROM_CLASS (klass),
443         G_SIGNAL_RUN_LAST, 0, NULL, NULL,
444         g_cclosure_marshal_generic,
445         G_TYPE_NONE,
446         1, TP_TYPE_FILE_TRANSFER_CHANNEL);
447
448   /**
449    * EmpathyFTHandler::transfer-done
450    * @handler: the object which has received the signal
451    * @channel: the #TpFileTransferChannel for which the transfer has started
452    *
453    * This signal will be emitted when the actual transfer is completed
454    * successfully.
455    */
456   signals[TRANSFER_DONE] =
457     g_signal_new ("transfer-done", G_TYPE_FROM_CLASS (klass),
458         G_SIGNAL_RUN_LAST, 0, NULL, NULL,
459         g_cclosure_marshal_generic,
460         G_TYPE_NONE,
461         1, TP_TYPE_FILE_TRANSFER_CHANNEL);
462
463   /**
464    * EmpathyFTHandler::transfer-error
465    * @handler: the object which has received the signal
466    * @error: a #GError
467    *
468    * This signal can be emitted anytime between the call to
469    * empathy_ft_handler_start_transfer() and the last expected signal
470    * (::transfer-done or ::hashing-done), and it's guaranteed to be the last
471    * signal coming from the handler, meaning that no other operation will
472    * take place after this signal.
473    */
474   signals[TRANSFER_ERROR] =
475     g_signal_new ("transfer-error", G_TYPE_FROM_CLASS (klass),
476         G_SIGNAL_RUN_LAST, 0, NULL, NULL,
477         g_cclosure_marshal_generic,
478         G_TYPE_NONE,
479         1, G_TYPE_POINTER);
480
481   /**
482    * EmpathyFTHandler::transfer-progress
483    * @handler: the object which has received the signal
484    * @current_bytes: the bytes currently transferred
485    * @total_bytes: the total bytes of the handler
486    * @remaining_time: the number of seconds remaining for the transfer
487    * to be completed
488    * @speed: the current speed of the transfer (in KB/s)
489    *
490    * This signal is emitted to notify clients of the progress of the
491    * transfer.
492    */
493   signals[TRANSFER_PROGRESS] =
494     g_signal_new ("transfer-progress", G_TYPE_FROM_CLASS (klass),
495         G_SIGNAL_RUN_LAST, 0, NULL, NULL,
496         g_cclosure_marshal_generic,
497         G_TYPE_NONE,
498         4, G_TYPE_UINT64, G_TYPE_UINT64, G_TYPE_UINT, G_TYPE_DOUBLE);
499
500   /**
501    * EmpathyFTHandler::hashing-started
502    * @handler: the object which has received the signal
503    *
504    * This signal is emitted when the hashing operation of the handler
505    * is started. Note that this might happen or not, depending on the CM
506    * and remote contact capabilities. Clients shoud use
507    * empathy_ft_handler_get_use_hash() before calling
508    * empathy_ft_handler_start_transfer() to know whether they should connect
509    * to this signal.
510    */
511   signals[HASHING_STARTED] =
512     g_signal_new ("hashing-started", G_TYPE_FROM_CLASS (klass),
513         G_SIGNAL_RUN_LAST, 0, NULL, NULL,
514         g_cclosure_marshal_generic,
515         G_TYPE_NONE, 0);
516
517   /**
518    * EmpathyFTHandler::hashing-progress
519    * @handler: the object which has received the signal
520    * @current_bytes: the bytes currently hashed
521    * @total_bytes: the total bytes of the handler
522    *
523    * This signal is emitted to notify clients of the progress of the
524    * hashing operation.
525    */
526   signals[HASHING_PROGRESS] =
527     g_signal_new ("hashing-progress", G_TYPE_FROM_CLASS (klass),
528         G_SIGNAL_RUN_LAST, 0, NULL, NULL,
529         g_cclosure_marshal_generic,
530         G_TYPE_NONE,
531         2, G_TYPE_UINT64, G_TYPE_UINT64);
532
533   /**
534    * EmpathyFTHandler::hashing-done
535    * @handler: the object which has received the signal
536    *
537    * This signal is emitted when the hashing operation of the handler
538    * is completed.
539    */
540   signals[HASHING_DONE] =
541     g_signal_new ("hashing-done", G_TYPE_FROM_CLASS (klass),
542         G_SIGNAL_RUN_LAST, 0, NULL, NULL,
543         g_cclosure_marshal_generic,
544         G_TYPE_NONE, 0);
545 }
546
547 static void
548 empathy_ft_handler_init (EmpathyFTHandler *self)
549 {
550   EmpathyFTHandlerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
551     EMPATHY_TYPE_FT_HANDLER, EmpathyFTHandlerPriv);
552
553   self->priv = priv;
554   priv->cancellable = g_cancellable_new ();
555 }
556
557 /* private functions */
558
559 static void
560 hash_data_free (HashingData *data)
561 {
562   g_free (data->buffer);
563
564   if (data->stream != NULL)
565     g_object_unref (data->stream);
566
567   if (data->checksum != NULL)
568     g_checksum_free (data->checksum);
569
570   if (data->error != NULL)
571     g_error_free (data->error);
572
573   if (data->handler != NULL)
574     g_object_unref (data->handler);
575
576   g_slice_free (HashingData, data);
577 }
578
579 static GChecksumType
580 tp_file_hash_to_g_checksum (TpFileHashType type)
581 {
582   GChecksumType retval;
583
584   switch (type)
585     {
586       case TP_FILE_HASH_TYPE_MD5:
587         retval = G_CHECKSUM_MD5;
588         break;
589       case TP_FILE_HASH_TYPE_SHA1:
590         retval = G_CHECKSUM_SHA1;
591         break;
592       case TP_FILE_HASH_TYPE_SHA256:
593         retval = G_CHECKSUM_SHA256;
594         break;
595       case TP_FILE_HASH_TYPE_NONE:
596       default:
597         g_assert_not_reached ();
598         break;
599     }
600
601   return retval;
602 }
603
604 static void
605 check_hash_incoming (EmpathyFTHandler *handler)
606 {
607   HashingData *hash_data;
608   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
609
610   if (!EMP_STR_EMPTY (priv->content_hash))
611     {
612       hash_data = g_slice_new0 (HashingData);
613       hash_data->total_bytes = priv->total_bytes;
614       hash_data->handler = g_object_ref (handler);
615       hash_data->checksum = g_checksum_new
616         (tp_file_hash_to_g_checksum (priv->content_hash_type));
617
618       g_signal_emit (handler, signals[HASHING_STARTED], 0);
619
620       g_io_scheduler_push_job (do_hash_job_incoming, hash_data, NULL,
621                                G_PRIORITY_DEFAULT, priv->cancellable);
622     }
623 }
624
625 static void
626 emit_error_signal (EmpathyFTHandler *handler,
627     const GError *error)
628 {
629   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
630
631   DEBUG ("Error in transfer: %s\n", error->message);
632
633   if (!g_cancellable_is_cancelled (priv->cancellable))
634     g_cancellable_cancel (priv->cancellable);
635
636   g_signal_emit (handler, signals[TRANSFER_ERROR], 0, error);
637 }
638
639 static void
640 update_remaining_time_and_speed (EmpathyFTHandler *handler,
641     guint64 transferred_bytes)
642 {
643   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
644   gint64 elapsed_time, current_time;
645   guint64 transferred, last_transferred_bytes;
646   gdouble speed;
647   gint remaining_time;
648
649   last_transferred_bytes = priv->transferred_bytes;
650   priv->transferred_bytes = transferred_bytes;
651
652   current_time = empathy_time_get_current ();
653   elapsed_time = current_time - priv->last_update_time;
654
655   if (elapsed_time >= 1)
656     {
657       transferred = transferred_bytes - last_transferred_bytes;
658       speed = (gdouble) transferred / (gdouble) elapsed_time;
659       remaining_time = (priv->total_bytes - priv->transferred_bytes) / speed;
660       priv->speed = speed;
661       priv->remaining_time = remaining_time;
662       priv->last_update_time = current_time;
663     }
664 }
665
666 static void
667 ft_transfer_transferred_bytes_cb (TpFileTransferChannel *channel,
668     GParamSpec *pspec,
669     EmpathyFTHandler *handler)
670 {
671   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
672   guint64 bytes;
673
674   if (empathy_ft_handler_is_cancelled (handler))
675     return;
676
677   bytes = tp_file_transfer_channel_get_transferred_bytes (channel);
678
679   if (priv->transferred_bytes == 0)
680     {
681       priv->last_update_time = empathy_time_get_current ();
682       g_signal_emit (handler, signals[TRANSFER_STARTED], 0, channel);
683     }
684
685   if (priv->transferred_bytes != bytes)
686     {
687       update_remaining_time_and_speed (handler, bytes);
688
689       g_signal_emit (handler, signals[TRANSFER_PROGRESS], 0,
690           bytes, priv->total_bytes, priv->remaining_time,
691           priv->speed);
692     }
693 }
694
695 static void
696 ft_transfer_provide_cb (GObject *source,
697     GAsyncResult *result,
698     gpointer user_data)
699 {
700   TpFileTransferChannel *channel = TP_FILE_TRANSFER_CHANNEL (source);
701   EmpathyFTHandler *handler = user_data;
702   GError *error = NULL;
703
704   if (!tp_file_transfer_channel_provide_file_finish (channel, result, &error))
705     {
706       emit_error_signal (handler, error);
707       g_clear_error (&error);
708     }
709 }
710
711 static void
712 ft_transfer_accept_cb (GObject *source,
713     GAsyncResult *result,
714     gpointer user_data)
715 {
716   TpFileTransferChannel *channel = TP_FILE_TRANSFER_CHANNEL (source);
717   EmpathyFTHandler *handler = user_data;
718   GError *error = NULL;
719
720   if (!tp_file_transfer_channel_accept_file_finish (channel, result, &error))
721     {
722       emit_error_signal (handler, error);
723       g_clear_error (&error);
724     }
725 }
726
727 static GError *
728 error_from_state_change_reason (TpFileTransferStateChangeReason reason)
729 {
730   const char *string;
731   GError *retval = NULL;
732
733   string = NULL;
734
735   switch (reason)
736     {
737       case TP_FILE_TRANSFER_STATE_CHANGE_REASON_NONE:
738         string = _("No reason was specified");
739         break;
740       case TP_FILE_TRANSFER_STATE_CHANGE_REASON_REQUESTED:
741         string = _("The change in state was requested");
742         break;
743       case TP_FILE_TRANSFER_STATE_CHANGE_REASON_LOCAL_STOPPED:
744         string = _("You canceled the file transfer");
745         break;
746       case TP_FILE_TRANSFER_STATE_CHANGE_REASON_REMOTE_STOPPED:
747         string = _("The other participant canceled the file transfer");
748         break;
749       case TP_FILE_TRANSFER_STATE_CHANGE_REASON_LOCAL_ERROR:
750         string = _("Error while trying to transfer the file");
751         break;
752       case TP_FILE_TRANSFER_STATE_CHANGE_REASON_REMOTE_ERROR:
753         string = _("The other participant is unable to transfer the file");
754         break;
755       default:
756         string = _("Unknown reason");
757         break;
758     }
759
760   retval = g_error_new_literal (EMPATHY_FT_ERROR_QUARK,
761       EMPATHY_FT_ERROR_TP_ERROR, string);
762
763   return retval;
764 }
765
766 static void
767 ft_transfer_state_cb (TpFileTransferChannel *channel,
768     GParamSpec *pspec,
769     EmpathyFTHandler *handler)
770 {
771   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
772   TpFileTransferStateChangeReason reason;
773   TpFileTransferState state = tp_file_transfer_channel_get_state (
774       channel, &reason);
775
776   if (state == TP_FILE_TRANSFER_STATE_COMPLETED)
777     {
778       priv->is_completed = TRUE;
779       g_signal_emit (handler, signals[TRANSFER_DONE], 0, channel);
780
781       tp_channel_close_async (TP_CHANNEL (channel), NULL, NULL);
782
783       if (empathy_ft_handler_is_incoming (handler) && priv->use_hash)
784         {
785           check_hash_incoming (handler);
786         }
787     }
788   else if (state == TP_FILE_TRANSFER_STATE_CANCELLED)
789     {
790       GError *error = error_from_state_change_reason (reason);
791       emit_error_signal (handler, error);
792       g_clear_error (&error);
793     }
794 }
795
796 static void
797 ft_handler_create_channel_cb (GObject *source,
798     GAsyncResult *result,
799     gpointer user_data)
800 {
801   EmpathyFTHandler *handler = user_data;
802   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
803   GError *error = NULL;
804   TpChannel *channel;
805
806   DEBUG ("Dispatcher create channel CB");
807
808   channel = tp_account_channel_request_create_and_handle_channel_finish (
809         TP_ACCOUNT_CHANNEL_REQUEST (source), result, NULL, &error);
810
811   if (channel == NULL)
812     DEBUG ("Failed to request FT channel: %s", error->message);
813   else
814     g_cancellable_set_error_if_cancelled (priv->cancellable, &error);
815
816   if (error != NULL)
817     {
818       emit_error_signal (handler, error);
819
820       g_clear_object (&channel);
821       g_error_free (error);
822       return;
823     }
824
825   priv->channel = TP_FILE_TRANSFER_CHANNEL (channel);
826
827   tp_g_signal_connect_object (priv->channel, "notify::state",
828       G_CALLBACK (ft_transfer_state_cb), handler, 0);
829   tp_g_signal_connect_object (priv->channel, "notify::transferred-bytes",
830       G_CALLBACK (ft_transfer_transferred_bytes_cb), handler, 0);
831
832   tp_file_transfer_channel_provide_file_async (priv->channel, priv->gfile,
833       ft_transfer_provide_cb, handler);
834 }
835
836 static void
837 ft_handler_push_to_dispatcher (EmpathyFTHandler *handler)
838 {
839   TpAccount *account;
840   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
841   TpAccountChannelRequest *req;
842
843   DEBUG ("Pushing request to the dispatcher");
844
845   account = empathy_contact_get_account (priv->contact);
846
847   req = tp_account_channel_request_new (account, priv->request,
848       priv->user_action_time);
849
850   tp_account_channel_request_create_and_handle_channel_async (req, NULL,
851       ft_handler_create_channel_cb, handler);
852
853   g_object_unref (req);
854 }
855
856 static void
857 ft_handler_populate_outgoing_request (EmpathyFTHandler *handler)
858 {
859   guint contact_handle;
860   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
861   gchar *uri;
862
863   contact_handle = empathy_contact_get_handle (priv->contact);
864   uri = g_file_get_uri (priv->gfile);
865
866   priv->request = tp_asv_new (
867       TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
868         TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER,
869       TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
870         TP_HANDLE_TYPE_CONTACT,
871       TP_PROP_CHANNEL_TARGET_HANDLE, G_TYPE_UINT,
872         contact_handle,
873       TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_CONTENT_TYPE, G_TYPE_STRING,
874         priv->content_type,
875       TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_FILENAME, G_TYPE_STRING,
876         priv->filename,
877       TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_SIZE, G_TYPE_UINT64,
878         priv->total_bytes,
879       TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_DATE, G_TYPE_UINT64,
880         priv->mtime,
881       TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_URI, G_TYPE_STRING, uri,
882       NULL);
883
884   g_free (uri);
885 }
886
887 static gboolean
888 hash_job_done (gpointer user_data)
889 {
890   HashingData *hash_data = user_data;
891   EmpathyFTHandler *handler = hash_data->handler;
892   EmpathyFTHandlerPriv *priv;
893   GError *error = NULL;
894
895   DEBUG ("Closing stream after hashing.");
896
897   priv = GET_PRIV (handler);
898
899   if (hash_data->error != NULL)
900     {
901       error = hash_data->error;
902       hash_data->error = NULL;
903       goto cleanup;
904     }
905
906   DEBUG ("Got file hash %s", g_checksum_get_string (hash_data->checksum));
907
908   if (empathy_ft_handler_is_incoming (handler))
909     {
910       if (g_strcmp0 (g_checksum_get_string (hash_data->checksum),
911                      priv->content_hash))
912         {
913           DEBUG ("Hash mismatch when checking incoming handler: "
914                  "received %s, calculated %s", priv->content_hash,
915                  g_checksum_get_string (hash_data->checksum));
916
917           error = g_error_new_literal (EMPATHY_FT_ERROR_QUARK,
918               EMPATHY_FT_ERROR_HASH_MISMATCH,
919               _("File transfer completed, but the file was corrupted"));
920           goto cleanup;
921         }
922       else
923         {
924           DEBUG ("Hash verification matched, received %s, calculated %s",
925                  priv->content_hash,
926                  g_checksum_get_string (hash_data->checksum));
927         }
928     }
929   else
930     {
931       /* set the checksum in the request...
932        * org.freedesktop.Telepathy.Channel.Type.FileTransfer.ContentHash
933        */
934       tp_asv_set_string (priv->request,
935           TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_CONTENT_HASH,
936           g_checksum_get_string (hash_data->checksum));
937     }
938
939 cleanup:
940
941   if (error != NULL)
942     {
943       emit_error_signal (handler, error);
944       g_clear_error (&error);
945     }
946   else
947     {
948       g_signal_emit (handler, signals[HASHING_DONE], 0);
949
950       if (!empathy_ft_handler_is_incoming (handler))
951         /* the request is complete now, push it to the dispatcher */
952         ft_handler_push_to_dispatcher (handler);
953     }
954
955   hash_data_free (hash_data);
956
957   return FALSE;
958 }
959
960 static gboolean
961 emit_hashing_progress (gpointer user_data)
962 {
963   HashingData *hash_data = user_data;
964
965   g_signal_emit (hash_data->handler, signals[HASHING_PROGRESS], 0,
966       (guint64) hash_data->total_read, (guint64) hash_data->total_bytes);
967
968   return FALSE;
969 }
970
971 static gboolean
972 do_hash_job (GIOSchedulerJob *job,
973     GCancellable *cancellable,
974     gpointer user_data)
975 {
976   HashingData *hash_data = user_data;
977   gssize bytes_read;
978   GError *error = NULL;
979
980 again:
981   if (hash_data->buffer == NULL)
982     hash_data->buffer = g_malloc0 (BUFFER_SIZE);
983
984   bytes_read = g_input_stream_read (hash_data->stream, hash_data->buffer,
985                                     BUFFER_SIZE, cancellable, &error);
986   if (error != NULL)
987     goto out;
988
989   hash_data->total_read += bytes_read;
990
991   /* we now have the chunk */
992   if (bytes_read > 0)
993     {
994       g_checksum_update (hash_data->checksum, hash_data->buffer, bytes_read);
995       g_io_scheduler_job_send_to_mainloop_async (job, emit_hashing_progress,
996           hash_data, NULL);
997
998       g_free (hash_data->buffer);
999       hash_data->buffer = NULL;
1000
1001       goto again;
1002     }
1003   else
1004   {
1005     g_input_stream_close (hash_data->stream, cancellable, &error);
1006   }
1007
1008 out:
1009   if (error != NULL)
1010     hash_data->error = error;
1011
1012   g_io_scheduler_job_send_to_mainloop_async (job, hash_job_done,
1013       hash_data, NULL);
1014
1015   return FALSE;
1016 }
1017
1018 static gboolean
1019 do_hash_job_incoming (GIOSchedulerJob *job,
1020     GCancellable *cancellable,
1021     gpointer user_data)
1022 {
1023   HashingData *hash_data = user_data;
1024   EmpathyFTHandler *handler = hash_data->handler;
1025   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
1026   GError *error = NULL;
1027
1028   DEBUG ("checking integrity for incoming handler");
1029
1030   /* need to get the stream first */
1031   hash_data->stream =
1032     G_INPUT_STREAM (g_file_read (priv->gfile, cancellable, &error));
1033
1034   if (error != NULL)
1035     {
1036       hash_data->error = error;
1037       g_io_scheduler_job_send_to_mainloop_async (job, hash_job_done,
1038           hash_data, NULL);
1039       return FALSE;
1040     }
1041
1042   return do_hash_job (job, cancellable, user_data);
1043 }
1044
1045 static void
1046 ft_handler_read_async_cb (GObject *source,
1047     GAsyncResult *res,
1048     gpointer user_data)
1049 {
1050   GFileInputStream *stream;
1051   GError *error = NULL;
1052   HashingData *hash_data;
1053   EmpathyFTHandler *handler = user_data;
1054   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
1055
1056   DEBUG ("GFile read async CB.");
1057
1058   stream = g_file_read_finish (priv->gfile, res, &error);
1059   if (error != NULL)
1060     {
1061       emit_error_signal (handler, error);
1062       g_clear_error (&error);
1063
1064       return;
1065     }
1066
1067   hash_data = g_slice_new0 (HashingData);
1068   hash_data->stream = G_INPUT_STREAM (stream);
1069   hash_data->total_bytes = priv->total_bytes;
1070   hash_data->handler = g_object_ref (handler);
1071   /* FIXME: MD5 is the only ContentHashType supported right now */
1072   hash_data->checksum = g_checksum_new (G_CHECKSUM_MD5);
1073
1074   tp_asv_set_uint32 (priv->request,
1075       TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_CONTENT_HASH_TYPE,
1076       TP_FILE_HASH_TYPE_MD5);
1077
1078   g_signal_emit (handler, signals[HASHING_STARTED], 0);
1079
1080   g_io_scheduler_push_job (do_hash_job, hash_data, NULL,
1081       G_PRIORITY_DEFAULT, priv->cancellable);
1082 }
1083
1084 static void
1085 callbacks_data_free (gpointer user_data)
1086 {
1087   CallbacksData *data = user_data;
1088
1089   if (data->handler != NULL)
1090     g_object_unref (data->handler);
1091
1092   g_slice_free (CallbacksData, data);
1093 }
1094
1095 static gboolean
1096 set_content_hash_type_from_classes (EmpathyFTHandler *handler,
1097     GPtrArray *classes)
1098 {
1099   GArray *possible_values;
1100   guint value;
1101   gboolean valid;
1102   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
1103   gboolean support_ft = FALSE;
1104   guint i;
1105
1106   possible_values = g_array_new (TRUE, TRUE, sizeof (guint));
1107
1108   for (i = 0; i < classes->len; i++)
1109     {
1110       GHashTable *fixed;
1111       GStrv allowed;
1112       const gchar *chan_type;
1113
1114       tp_value_array_unpack (g_ptr_array_index (classes, i), 2,
1115           &fixed, &allowed);
1116
1117       chan_type = tp_asv_get_string (fixed, TP_PROP_CHANNEL_CHANNEL_TYPE);
1118
1119       if (tp_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER))
1120         continue;
1121
1122       if (tp_asv_get_uint32 (fixed, TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL) !=
1123           TP_HANDLE_TYPE_CONTACT)
1124         continue;
1125
1126       support_ft = TRUE;
1127
1128       value = tp_asv_get_uint32
1129         (fixed, TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_CONTENT_HASH_TYPE,
1130          &valid);
1131
1132       if (valid)
1133         g_array_append_val (possible_values, value);
1134     }
1135
1136   if (!support_ft)
1137     {
1138       g_array_unref (possible_values);
1139       return FALSE;
1140     }
1141
1142   if (possible_values->len == 0)
1143     {
1144       /* there are no channel classes with hash support, disable it. */
1145       priv->use_hash = FALSE;
1146       priv->content_hash_type = TP_FILE_HASH_TYPE_NONE;
1147
1148       goto out;
1149     }
1150
1151   priv->use_hash = TRUE;
1152
1153   if (possible_values->len == 1)
1154     {
1155       priv->content_hash_type = g_array_index (possible_values, guint, 0);
1156     }
1157   else
1158     {
1159       /* order the array and pick the first non zero, so that MD5
1160        * is the preferred value.
1161        */
1162       g_array_sort (possible_values, empathy_uint_compare);
1163
1164       if (g_array_index (possible_values, guint, 0) == 0)
1165         priv->content_hash_type = g_array_index (possible_values, guint, 1);
1166       else
1167         priv->content_hash_type = g_array_index (possible_values, guint, 0);
1168     }
1169
1170 out:
1171   g_array_unref (possible_values);
1172
1173   DEBUG ("Hash enabled %s; setting content hash type as %u",
1174          priv->use_hash ? "True" : "False", priv->content_hash_type);
1175
1176   return TRUE;
1177 }
1178
1179 static void
1180 check_hashing (CallbacksData *data)
1181 {
1182   EmpathyFTHandler *handler = data->handler;
1183   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
1184   GError *myerr = NULL;
1185   TpCapabilities *caps;
1186   GPtrArray *classes;
1187   TpConnection *conn;
1188
1189   conn = empathy_contact_get_connection (priv->contact);
1190
1191   caps = tp_connection_get_capabilities (conn);
1192   if (caps == NULL)
1193     {
1194       data->callback (handler, NULL, data->user_data);
1195       goto out;
1196     }
1197
1198   classes = tp_capabilities_get_channel_classes (caps);
1199
1200   /* set whether we support hash and the type of it */
1201   if (!set_content_hash_type_from_classes (handler, classes))
1202     {
1203       g_set_error_literal (&myerr, EMPATHY_FT_ERROR_QUARK,
1204           EMPATHY_FT_ERROR_NOT_SUPPORTED,
1205           _("File transfer not supported by remote contact"));
1206
1207       if (!g_cancellable_is_cancelled (priv->cancellable))
1208         g_cancellable_cancel (priv->cancellable);
1209
1210       data->callback (handler, myerr, data->user_data);
1211       g_clear_error (&myerr);
1212     }
1213   else
1214     {
1215       /* get back to the caller now */
1216       data->callback (handler, NULL, data->user_data);
1217     }
1218
1219 out:
1220   callbacks_data_free (data);
1221 }
1222
1223 static void
1224 ft_handler_complete_request (EmpathyFTHandler *handler)
1225 {
1226   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
1227
1228   /* populate the request table with all the known properties */
1229   ft_handler_populate_outgoing_request (handler);
1230
1231   if (priv->use_hash)
1232     /* start hashing the file */
1233     g_file_read_async (priv->gfile, G_PRIORITY_DEFAULT,
1234         priv->cancellable, ft_handler_read_async_cb, handler);
1235   else
1236     /* push directly the handler to the dispatcher */
1237     ft_handler_push_to_dispatcher (handler);
1238 }
1239
1240 static void
1241 ft_handler_gfile_ready_cb (GObject *source,
1242     GAsyncResult *res,
1243     CallbacksData *cb_data)
1244 {
1245   GFileInfo *info;
1246   GError *error = NULL;
1247   GTimeVal mtime;
1248   EmpathyFTHandlerPriv *priv = GET_PRIV (cb_data->handler);
1249
1250   DEBUG ("Got GFileInfo.");
1251
1252   info = g_file_query_info_finish (priv->gfile, res, &error);
1253
1254   if (error != NULL)
1255     goto out;
1256
1257   if (g_file_info_get_file_type (info) != G_FILE_TYPE_REGULAR)
1258     {
1259       error = g_error_new_literal (EMPATHY_FT_ERROR_QUARK,
1260           EMPATHY_FT_ERROR_INVALID_SOURCE_FILE,
1261           _("The selected file is not a regular file"));
1262       goto out;
1263     }
1264
1265   priv->total_bytes = g_file_info_get_size (info);
1266   if (priv->total_bytes == 0)
1267     {
1268       error = g_error_new_literal (EMPATHY_FT_ERROR_QUARK,
1269           EMPATHY_FT_ERROR_EMPTY_SOURCE_FILE,
1270           _("The selected file is empty"));
1271       goto out;
1272     }
1273
1274   priv->content_type = g_strdup (g_file_info_get_content_type (info));
1275   priv->filename = g_strdup (g_file_info_get_display_name (info));
1276   g_file_info_get_modification_time (info, &mtime);
1277   priv->mtime = mtime.tv_sec;
1278   priv->transferred_bytes = 0;
1279   priv->description = NULL;
1280
1281   g_object_unref (info);
1282
1283 out:
1284   if (error != NULL)
1285     {
1286       if (!g_cancellable_is_cancelled (priv->cancellable))
1287         g_cancellable_cancel (priv->cancellable);
1288
1289       cb_data->callback (cb_data->handler, error, cb_data->user_data);
1290       g_error_free (error);
1291
1292       callbacks_data_free (cb_data);
1293     }
1294   else
1295     {
1296       /* see if FT/hashing are allowed */
1297       check_hashing (cb_data);
1298     }
1299 }
1300
1301 static void
1302 contact_factory_contact_cb (TpConnection *connection,
1303     EmpathyContact *contact,
1304     const GError *error,
1305     gpointer user_data,
1306     GObject *weak_object)
1307 {
1308   CallbacksData *cb_data = user_data;
1309   EmpathyFTHandler *handler = EMPATHY_FT_HANDLER (weak_object);
1310   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
1311
1312   if (error != NULL)
1313     {
1314       if (!g_cancellable_is_cancelled (priv->cancellable))
1315         g_cancellable_cancel (priv->cancellable);
1316
1317       cb_data->callback (handler, (GError *) error, cb_data->user_data);
1318       callbacks_data_free (cb_data);
1319       return;
1320     }
1321
1322   priv->contact = g_object_ref (contact);
1323
1324   cb_data->callback (handler, NULL, cb_data->user_data);
1325 }
1326
1327 static void
1328 channel_get_all_properties_cb (TpProxy *proxy,
1329     GHashTable *properties,
1330     const GError *error,
1331     gpointer user_data,
1332     GObject *weak_object)
1333 {
1334   CallbacksData *cb_data = user_data;
1335   EmpathyFTHandler *handler = EMPATHY_FT_HANDLER (weak_object);
1336   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
1337   TpHandle c_handle;
1338
1339   if (error != NULL)
1340     {
1341       if (!g_cancellable_is_cancelled (priv->cancellable))
1342         g_cancellable_cancel (priv->cancellable);
1343
1344       cb_data->callback (handler, (GError *) error, cb_data->user_data);
1345
1346       callbacks_data_free (cb_data);
1347       return;
1348     }
1349
1350   priv->content_hash = g_value_dup_string (
1351       g_hash_table_lookup (properties, "ContentHash"));
1352
1353   priv->content_hash_type = g_value_get_uint (
1354       g_hash_table_lookup (properties, "ContentHashType"));
1355
1356   c_handle = tp_channel_get_handle (TP_CHANNEL (proxy), NULL);
1357   empathy_tp_contact_factory_get_from_handle (
1358       tp_channel_borrow_connection (TP_CHANNEL (proxy)), c_handle,
1359       contact_factory_contact_cb, cb_data, callbacks_data_free,
1360       G_OBJECT (handler));
1361 }
1362
1363 /* public methods */
1364
1365 /**
1366  * empathy_ft_handler_new_outgoing:
1367  * @contact: the #EmpathyContact to send @source to
1368  * @source: the #GFile to send
1369  * @callback: callback to be called when the handler has been created
1370  * @user_data: user data to be passed to @callback
1371  *
1372  * Triggers the creation of a new #EmpathyFTHandler for an outgoing transfer.
1373  */
1374 void
1375 empathy_ft_handler_new_outgoing (EmpathyContact *contact,
1376     GFile *source,
1377     gint64 action_time,
1378     EmpathyFTHandlerReadyCallback callback,
1379     gpointer user_data)
1380 {
1381   EmpathyFTHandler *handler;
1382   CallbacksData *data;
1383   EmpathyFTHandlerPriv *priv;
1384
1385   DEBUG ("New handler outgoing");
1386
1387   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1388   g_return_if_fail (G_IS_FILE (source));
1389
1390   handler = g_object_new (EMPATHY_TYPE_FT_HANDLER,
1391       "contact", contact,
1392       "gfile", source,
1393       "user-action-time", action_time,
1394       NULL);
1395
1396   priv = GET_PRIV (handler);
1397
1398   data = g_slice_new0 (CallbacksData);
1399   data->callback = callback;
1400   data->user_data = user_data;
1401   data->handler = g_object_ref (handler);
1402
1403   /* start collecting info about the file */
1404   g_file_query_info_async (priv->gfile,
1405       G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME ","
1406       G_FILE_ATTRIBUTE_STANDARD_SIZE ","
1407       G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
1408       G_FILE_ATTRIBUTE_STANDARD_TYPE ","
1409       G_FILE_ATTRIBUTE_TIME_MODIFIED,
1410       G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT,
1411       NULL, (GAsyncReadyCallback) ft_handler_gfile_ready_cb, data);
1412 }
1413
1414 /**
1415  * empathy_ft_handler_new_incoming:
1416  * @channel: the #TpFileTransferChannel proxy to the incoming channel
1417  * @callback: callback to be called when the handler has been created
1418  * @user_data: user data to be passed to @callback
1419  *
1420  * Triggers the creation of a new #EmpathyFTHandler for an incoming transfer.
1421  * Note that for the handler to be useful, you will have to set a destination
1422  * file with empathy_ft_handler_incoming_set_destination() after the handler
1423  * is ready.
1424  */
1425 void
1426 empathy_ft_handler_new_incoming (TpFileTransferChannel *channel,
1427     EmpathyFTHandlerReadyCallback callback,
1428     gpointer user_data)
1429 {
1430   EmpathyFTHandler *handler;
1431   CallbacksData *data;
1432   EmpathyFTHandlerPriv *priv;
1433
1434   g_return_if_fail (TP_IS_FILE_TRANSFER_CHANNEL (channel));
1435
1436   handler = g_object_new (EMPATHY_TYPE_FT_HANDLER,
1437       "channel", channel, NULL);
1438
1439   priv = GET_PRIV (handler);
1440
1441   data = g_slice_new0 (CallbacksData);
1442   data->callback = callback;
1443   data->user_data = user_data;
1444   data->handler = g_object_ref (handler);
1445
1446   priv->total_bytes = tp_file_transfer_channel_get_size (channel);
1447
1448   priv->transferred_bytes = tp_file_transfer_channel_get_transferred_bytes (
1449       channel);
1450
1451   priv->filename = g_strdup (tp_file_transfer_channel_get_filename (channel));
1452
1453   priv->content_type = g_strdup (tp_file_transfer_channel_get_mime_type (
1454       channel));
1455
1456   priv->description = g_strdup (tp_file_transfer_channel_get_description (
1457       channel));
1458
1459   tp_cli_dbus_properties_call_get_all (channel,
1460       -1, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER,
1461       channel_get_all_properties_cb, data, NULL, G_OBJECT (handler));
1462 }
1463
1464 /**
1465  * empathy_ft_handler_start_transfer:
1466  * @handler: an #EmpathyFTHandler
1467  *
1468  * Starts the transfer machinery. After this call, the transfer and hashing
1469  * signals will be emitted by the handler.
1470  */
1471 void
1472 empathy_ft_handler_start_transfer (EmpathyFTHandler *handler)
1473 {
1474   EmpathyFTHandlerPriv *priv;
1475
1476   g_return_if_fail (EMPATHY_IS_FT_HANDLER (handler));
1477
1478   priv = GET_PRIV (handler);
1479
1480   if (priv->channel == NULL)
1481     {
1482       ft_handler_complete_request (handler);
1483     }
1484   else
1485     {
1486       /* TODO: add support for resume. */
1487       tp_file_transfer_channel_accept_file_async (priv->channel,
1488           priv->gfile, 0, ft_transfer_accept_cb, handler);
1489
1490       tp_g_signal_connect_object (priv->channel, "notify::state",
1491           G_CALLBACK (ft_transfer_state_cb), handler, 0);
1492       tp_g_signal_connect_object (priv->channel, "notify::transferred-bytes",
1493           G_CALLBACK (ft_transfer_transferred_bytes_cb), handler, 0);
1494     }
1495 }
1496
1497 /**
1498  * empathy_ft_handler_cancel_transfer:
1499  * @handler: an #EmpathyFTHandler
1500  *
1501  * Cancels an ongoing handler operation. Note that this doesn't destroy
1502  * the object, which will keep all the properties, altough it won't be able
1503  * to do any more I/O.
1504  */
1505 void
1506 empathy_ft_handler_cancel_transfer (EmpathyFTHandler *handler)
1507 {
1508   EmpathyFTHandlerPriv *priv;
1509
1510   g_return_if_fail (EMPATHY_IS_FT_HANDLER (handler));
1511
1512   priv = GET_PRIV (handler);
1513
1514   /* if we don't have a channel, we are hashing, so
1515    * we can just cancel the GCancellable to stop it.
1516    */
1517   if (priv->channel == NULL)
1518     g_cancellable_cancel (priv->cancellable);
1519   else
1520     tp_channel_close_async (TP_CHANNEL (priv->channel), NULL, NULL);
1521 }
1522
1523 /**
1524  * empathy_ft_handler_incoming_set_destination:
1525  * @handler: an #EmpathyFTHandler
1526  * @destination: the #GFile where the transfer should be saved
1527  *
1528  * Sets the destination of the incoming handler to be @destination.
1529  * Note that calling this method is mandatory before starting the transfer
1530  * for incoming handlers.
1531  */
1532 void
1533 empathy_ft_handler_incoming_set_destination (EmpathyFTHandler *handler,
1534     GFile *destination)
1535 {
1536   EmpathyFTHandlerPriv *priv;
1537
1538   g_return_if_fail (EMPATHY_IS_FT_HANDLER (handler));
1539   g_return_if_fail (G_IS_FILE (destination));
1540
1541   priv = GET_PRIV (handler);
1542
1543   g_object_set (handler, "gfile", destination, NULL);
1544
1545   /* check if hash is supported. if it isn't, set use_hash to FALSE
1546    * anyway, so that clients won't be expecting us to checksum.
1547    */
1548   if (EMP_STR_EMPTY (priv->content_hash) ||
1549       priv->content_hash_type == TP_FILE_HASH_TYPE_NONE)
1550     priv->use_hash = FALSE;
1551   else
1552     priv->use_hash = TRUE;
1553 }
1554
1555 /**
1556  * empathy_ft_handler_get_filename:
1557  * @handler: an #EmpathyFTHandler
1558  *
1559  * Returns the name of the file being transferred.
1560  *
1561  * Return value: the name of the file being transferred
1562  */
1563 const char *
1564 empathy_ft_handler_get_filename (EmpathyFTHandler *handler)
1565 {
1566   EmpathyFTHandlerPriv *priv;
1567
1568   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), NULL);
1569
1570   priv = GET_PRIV (handler);
1571
1572   return priv->filename;
1573 }
1574
1575 /**
1576  * empathy_ft_handler_get_content_type:
1577  * @handler: an #EmpathyFTHandler
1578  *
1579  * Returns the content type of the file being transferred.
1580  *
1581  * Return value: the content type of the file being transferred
1582  */
1583 const char *
1584 empathy_ft_handler_get_content_type (EmpathyFTHandler *handler)
1585 {
1586   EmpathyFTHandlerPriv *priv;
1587
1588   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), NULL);
1589
1590   priv = GET_PRIV (handler);
1591
1592   return priv->content_type;
1593 }
1594
1595 /**
1596  * empathy_ft_handler_get_contact:
1597  * @handler: an #EmpathyFTHandler
1598  *
1599  * Returns the remote #EmpathyContact at the other side of the transfer.
1600  *
1601  * Return value: the remote #EmpathyContact for @handler
1602  */
1603 EmpathyContact *
1604 empathy_ft_handler_get_contact (EmpathyFTHandler *handler)
1605 {
1606   EmpathyFTHandlerPriv *priv;
1607
1608   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), NULL);
1609
1610   priv = GET_PRIV (handler);
1611
1612   return priv->contact;
1613 }
1614
1615 /**
1616  * empathy_ft_handler_get_gfile:
1617  * @handler: an #EmpathyFTHandler
1618  *
1619  * Returns the #GFile where the transfer is being read/saved.
1620  *
1621  * Return value: the #GFile where the transfer is being read/saved
1622  */
1623 GFile *
1624 empathy_ft_handler_get_gfile (EmpathyFTHandler *handler)
1625 {
1626   EmpathyFTHandlerPriv *priv;
1627
1628   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), NULL);
1629
1630   priv = GET_PRIV (handler);
1631
1632   return priv->gfile;
1633 }
1634
1635 /**
1636  * empathy_ft_handler_get_use_hash:
1637  * @handler: an #EmpathyFTHandler
1638  *
1639  * Returns whether @handler has checksumming enabled. This can depend on
1640  * the CM and the remote contact capabilities.
1641  *
1642  * Return value: %TRUE if the handler has checksumming enabled,
1643  * %FALSE otherwise.
1644  */
1645 gboolean
1646 empathy_ft_handler_get_use_hash (EmpathyFTHandler *handler)
1647 {
1648   EmpathyFTHandlerPriv *priv;
1649
1650   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), FALSE);
1651
1652   priv = GET_PRIV (handler);
1653
1654   return priv->use_hash;
1655 }
1656
1657 /**
1658  * empathy_ft_handler_is_incoming:
1659  * @handler: an #EmpathyFTHandler
1660  *
1661  * Returns whether @handler is incoming or outgoing.
1662  *
1663  * Return value: %TRUE if the handler is incoming, %FALSE otherwise.
1664  */
1665 gboolean
1666 empathy_ft_handler_is_incoming (EmpathyFTHandler *handler)
1667 {
1668   EmpathyFTHandlerPriv *priv;
1669
1670   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), FALSE);
1671
1672   priv = GET_PRIV (handler);
1673
1674   if (priv->channel == NULL)
1675     return FALSE;
1676
1677   return !tp_channel_get_requested ((TpChannel *) priv->channel);
1678 }
1679
1680 /**
1681  * empathy_ft_handler_get_transferred_bytes:
1682  * @handler: an #EmpathyFTHandler
1683  *
1684  * Returns the number of bytes already transferred by the handler.
1685  *
1686  * Return value: the number of bytes already transferred by the handler.
1687  */
1688 guint64
1689 empathy_ft_handler_get_transferred_bytes (EmpathyFTHandler *handler)
1690 {
1691   EmpathyFTHandlerPriv *priv;
1692
1693   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), 0);
1694
1695   priv = GET_PRIV (handler);
1696
1697   return priv->transferred_bytes;
1698 }
1699
1700 /**
1701  * empathy_ft_handler_get_total_bytes:
1702  * @handler: an #EmpathyFTHandler
1703  *
1704  * Returns the total size of the file being transferred by the handler.
1705  *
1706  * Return value: a number of bytes indicating the total size of the file being
1707  * transferred by the handler.
1708  */
1709 guint64
1710 empathy_ft_handler_get_total_bytes (EmpathyFTHandler *handler)
1711 {
1712   EmpathyFTHandlerPriv *priv;
1713
1714   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), 0);
1715
1716   priv = GET_PRIV (handler);
1717
1718   return priv->total_bytes;
1719 }
1720
1721 /**
1722  * empathy_ft_handler_is_completed:
1723  * @handler: an #EmpathyFTHandler
1724  *
1725  * Returns whether the transfer for @handler has been completed succesfully.
1726  *
1727  * Return value: %TRUE if the handler has been transferred correctly, %FALSE
1728  * otherwise
1729  */
1730 gboolean
1731 empathy_ft_handler_is_completed (EmpathyFTHandler *handler)
1732 {
1733   EmpathyFTHandlerPriv *priv;
1734
1735   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), FALSE);
1736
1737   priv = GET_PRIV (handler);
1738
1739   return priv->is_completed;
1740 }
1741
1742 /**
1743  * empathy_ft_handler_is_cancelled:
1744  * @handler: an #EmpathyFTHandler
1745  *
1746  * Returns whether the transfer for @handler has been cancelled or has stopped
1747  * due to an error.
1748  *
1749  * Return value: %TRUE if the transfer for @handler has been cancelled
1750  * or has stopped due to an error, %FALSE otherwise.
1751  */
1752 gboolean
1753 empathy_ft_handler_is_cancelled (EmpathyFTHandler *handler)
1754 {
1755   EmpathyFTHandlerPriv *priv;
1756
1757   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), FALSE);
1758
1759   priv = GET_PRIV (handler);
1760
1761   return g_cancellable_is_cancelled (priv->cancellable);
1762 }