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