]> git.0d.be Git - empathy.git/blob - libempathy/empathy-ft-handler.c
add timestamp arg to empathy_dispatcher_create_channel
[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   DEBUG ("Error in transfer: %s\n", error->message);
623
624   if (!g_cancellable_is_cancelled (priv->cancellable))
625     g_cancellable_cancel (priv->cancellable);
626
627   g_signal_emit (handler, signals[TRANSFER_ERROR], 0, error);
628 }
629
630 static void
631 ft_transfer_operation_callback (EmpathyTpFile *tp_file,
632     const GError *error,
633     gpointer user_data)
634 {
635   EmpathyFTHandler *handler = user_data;
636   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
637
638   DEBUG ("Transfer operation callback, error %p", error);
639
640   if (error != NULL)
641     {
642       emit_error_signal (handler, error);
643     }
644   else
645     {
646       priv->is_completed = TRUE;
647       g_signal_emit (handler, signals[TRANSFER_DONE], 0, tp_file);
648
649       empathy_tp_file_close (tp_file);
650
651       if (empathy_ft_handler_is_incoming (handler) && priv->use_hash)
652         {
653           check_hash_incoming (handler);
654         }
655     }
656 }
657
658 static void
659 update_remaining_time_and_speed (EmpathyFTHandler *handler,
660     guint64 transferred_bytes)
661 {
662   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
663   time_t elapsed_time, current_time;
664   guint64 transferred, last_transferred_bytes;
665   gdouble speed;
666   gint remaining_time;
667
668   last_transferred_bytes = priv->transferred_bytes;
669   priv->transferred_bytes = transferred_bytes;
670
671   current_time = empathy_time_get_current ();
672   elapsed_time = current_time - priv->last_update_time;
673
674   if (elapsed_time >= 1)
675     {
676       transferred = transferred_bytes - last_transferred_bytes;
677       speed = (gdouble) transferred / (gdouble) elapsed_time;
678       remaining_time = (priv->total_bytes - priv->transferred_bytes) / speed;
679       priv->speed = speed;
680       priv->remaining_time = remaining_time;
681       priv->last_update_time = current_time;
682     }
683 }
684
685 static void
686 ft_transfer_progress_callback (EmpathyTpFile *tp_file,
687     guint64 transferred_bytes,
688     gpointer user_data)
689 {
690   EmpathyFTHandler *handler = user_data;
691   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
692
693   if (empathy_ft_handler_is_cancelled (handler))
694     return;
695
696   if (transferred_bytes == 0)
697     {
698       priv->last_update_time = empathy_time_get_current ();
699       g_signal_emit (handler, signals[TRANSFER_STARTED], 0, tp_file);
700     }
701
702   if (priv->transferred_bytes != transferred_bytes)
703     {
704       update_remaining_time_and_speed (handler, transferred_bytes);
705
706       g_signal_emit (handler, signals[TRANSFER_PROGRESS], 0,
707           transferred_bytes, priv->total_bytes, priv->remaining_time,
708           priv->speed);
709     }
710 }
711
712 static void
713 ft_handler_create_channel_cb (EmpathyDispatchOperation *operation,
714     const GError *error,
715     gpointer user_data)
716 {
717   EmpathyFTHandler *handler = user_data;
718   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
719   GError *my_error = (GError *) error;
720
721   DEBUG ("Dispatcher create channel CB");
722
723   if (my_error == NULL)
724     {
725       g_cancellable_set_error_if_cancelled (priv->cancellable, &my_error);
726     }
727
728   if (my_error != NULL)
729     {
730       emit_error_signal (handler, my_error);
731
732       if (my_error != error)
733         g_clear_error (&my_error);
734
735       return;
736     }
737
738   priv->tpfile = g_object_ref
739       (empathy_dispatch_operation_get_channel_wrapper (operation));
740
741   empathy_tp_file_offer (priv->tpfile, priv->gfile, priv->cancellable,
742       ft_transfer_progress_callback, handler,
743       ft_transfer_operation_callback, handler);
744
745   empathy_dispatch_operation_claim (operation);
746 }
747
748 static void
749 ft_handler_push_to_dispatcher (EmpathyFTHandler *handler)
750 {
751   TpConnection *connection;
752   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
753
754   DEBUG ("Pushing request to the dispatcher");
755
756   connection = empathy_contact_get_connection (priv->contact);
757
758   /* I want to own a reference to the request, and destroy it later */
759   empathy_dispatcher_create_channel (priv->dispatcher, connection,
760       g_hash_table_ref (priv->request), EMPATHY_DISPATCHER_NON_USER_ACTION,
761       ft_handler_create_channel_cb, handler);
762 }
763
764 static void
765 ft_handler_populate_outgoing_request (EmpathyFTHandler *handler)
766 {
767   guint contact_handle;
768   GHashTable *request;
769   GValue *value;
770   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
771
772   request = priv->request = g_hash_table_new_full (g_str_hash, g_str_equal,
773             NULL, (GDestroyNotify) tp_g_value_slice_free);
774
775   contact_handle = empathy_contact_get_handle (priv->contact);
776
777   /* org.freedesktop.Telepathy.Channel.ChannelType */
778   value = tp_g_value_slice_new_string (TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER);
779   g_hash_table_insert (request, TP_IFACE_CHANNEL ".ChannelType", value);
780
781   /* org.freedesktop.Telepathy.Channel.TargetHandleType */
782   value = tp_g_value_slice_new_uint (TP_HANDLE_TYPE_CONTACT);
783   g_hash_table_insert (request, TP_IFACE_CHANNEL ".TargetHandleType", value);
784
785   /* org.freedesktop.Telepathy.Channel.TargetHandle */
786   value = tp_g_value_slice_new_uint (contact_handle);
787   g_hash_table_insert (request, TP_IFACE_CHANNEL ".TargetHandle", value);
788
789   /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.ContentType */
790   value = tp_g_value_slice_new_string (priv->content_type);
791   g_hash_table_insert (request,
792       TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentType", value);
793
794   /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.Filename */
795   value = tp_g_value_slice_new_string (priv->filename);
796   g_hash_table_insert (request,
797       TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Filename", value);
798
799   /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.Size */
800   value = tp_g_value_slice_new_uint64 (priv->total_bytes);
801   g_hash_table_insert (request,
802       TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Size", value);
803
804   /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.Date */
805   value = tp_g_value_slice_new_uint64 ((guint64) priv->mtime);
806   g_hash_table_insert (request,
807       TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Date", value);
808 }
809
810 static gboolean
811 hash_job_done (gpointer user_data)
812 {
813   HashingData *hash_data = user_data;
814   EmpathyFTHandler *handler = hash_data->handler;
815   EmpathyFTHandlerPriv *priv;
816   GError *error = NULL;
817   GValue *value;
818
819   DEBUG ("Closing stream after hashing.");
820
821   priv = GET_PRIV (handler);
822
823   if (hash_data->error != NULL)
824     {
825       error = hash_data->error;
826       hash_data->error = NULL;
827       goto cleanup;
828     }
829
830   DEBUG ("Got file hash %s", g_checksum_get_string (hash_data->checksum));
831
832   if (empathy_ft_handler_is_incoming (handler))
833     {
834       if (g_strcmp0 (g_checksum_get_string (hash_data->checksum),
835                      priv->content_hash))
836         {
837           DEBUG ("Hash mismatch when checking incoming handler: "
838                  "received %s, calculated %s", priv->content_hash,
839                  g_checksum_get_string (hash_data->checksum));
840
841           error = g_error_new_literal (EMPATHY_FT_ERROR_QUARK,
842               EMPATHY_FT_ERROR_HASH_MISMATCH,
843               _("The hash of the received file and the "
844                 "sent one do not match"));
845           goto cleanup;
846         }
847       else
848         {
849           DEBUG ("Hash verification matched, received %s, calculated %s",
850                  priv->content_hash,
851                  g_checksum_get_string (hash_data->checksum));
852         }
853     }
854   else
855     {
856       /* set the checksum in the request...
857        * org.freedesktop.Telepathy.Channel.Type.FileTransfer.ContentHash
858        */
859       value = tp_g_value_slice_new_string
860           (g_checksum_get_string (hash_data->checksum));
861       g_hash_table_insert (priv->request,
862           TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentHash", value);
863     }
864
865 cleanup:
866
867   if (error != NULL)
868     {
869       emit_error_signal (handler, error);
870       g_clear_error (&error);
871     }
872   else
873     {
874       g_signal_emit (handler, signals[HASHING_DONE], 0);
875
876       if (!empathy_ft_handler_is_incoming (handler))
877         /* the request is complete now, push it to the dispatcher */
878         ft_handler_push_to_dispatcher (handler);
879     }
880
881   hash_data_free (hash_data);
882
883   return FALSE;
884 }
885
886 static gboolean
887 emit_hashing_progress (gpointer user_data)
888 {
889   HashingData *hash_data = user_data;
890
891   g_signal_emit (hash_data->handler, signals[HASHING_PROGRESS], 0,
892       (guint64) hash_data->total_read, (guint64) hash_data->total_bytes);
893
894   return FALSE;
895 }
896
897 static gboolean
898 do_hash_job (GIOSchedulerJob *job,
899     GCancellable *cancellable,
900     gpointer user_data)
901 {
902   HashingData *hash_data = user_data;
903   gssize bytes_read;
904   EmpathyFTHandlerPriv *priv;
905   GError *error = NULL;
906
907   priv = GET_PRIV (hash_data->handler);
908
909 again:
910   if (hash_data->buffer == NULL)
911     hash_data->buffer = g_malloc0 (BUFFER_SIZE);
912
913   bytes_read = g_input_stream_read (hash_data->stream, hash_data->buffer,
914                                     BUFFER_SIZE, cancellable, &error);
915   if (error != NULL)
916     goto out;
917
918   hash_data->total_read += bytes_read;
919
920   /* we now have the chunk */
921   if (bytes_read > 0)
922     {
923       g_checksum_update (hash_data->checksum, hash_data->buffer, bytes_read);
924       g_io_scheduler_job_send_to_mainloop_async (job, emit_hashing_progress,
925           hash_data, NULL);
926
927       g_free (hash_data->buffer);
928       hash_data->buffer = NULL;
929
930       goto again;
931     }
932   else
933   {
934     g_input_stream_close (hash_data->stream, cancellable, &error);
935   }
936
937 out:
938   if (error != NULL)
939     hash_data->error = error;
940
941   g_io_scheduler_job_send_to_mainloop_async (job, hash_job_done,
942       hash_data, NULL);
943
944   return FALSE;
945 }
946
947 static gboolean
948 do_hash_job_incoming (GIOSchedulerJob *job,
949     GCancellable *cancellable,
950     gpointer user_data)
951 {
952   HashingData *hash_data = user_data;
953   EmpathyFTHandler *handler = hash_data->handler;
954   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
955   GError *error = NULL;
956
957   DEBUG ("checking integrity for incoming handler");
958
959   /* need to get the stream first */
960   hash_data->stream =
961     G_INPUT_STREAM (g_file_read (priv->gfile, cancellable, &error));
962
963   if (error != NULL)
964     {
965       hash_data->error = error;
966       g_io_scheduler_job_send_to_mainloop_async (job, hash_job_done,
967           hash_data, NULL);
968       return FALSE;
969     }
970
971   return do_hash_job (job, cancellable, user_data);
972 }
973
974 static void
975 ft_handler_read_async_cb (GObject *source,
976     GAsyncResult *res,
977     gpointer user_data)
978 {
979   GFileInputStream *stream;
980   GError *error = NULL;
981   HashingData *hash_data;
982   GValue *value;
983   EmpathyFTHandler *handler = user_data;
984   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
985
986   DEBUG ("GFile read async CB.");
987
988   stream = g_file_read_finish (priv->gfile, res, &error);
989   if (error != NULL)
990     {
991       emit_error_signal (handler, error);
992       g_clear_error (&error);
993
994       return;
995     }
996
997   hash_data = g_slice_new0 (HashingData);
998   hash_data->stream = G_INPUT_STREAM (stream);
999   hash_data->total_bytes = priv->total_bytes;
1000   hash_data->handler = g_object_ref (handler);
1001   /* FIXME: MD5 is the only ContentHashType supported right now */
1002   hash_data->checksum = g_checksum_new (G_CHECKSUM_MD5);
1003
1004   /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.ContentHashType */
1005   value = tp_g_value_slice_new_uint (TP_FILE_HASH_TYPE_MD5);
1006   g_hash_table_insert (priv->request,
1007       TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentHashType", value);
1008
1009   g_signal_emit (handler, signals[HASHING_STARTED], 0);
1010
1011   g_io_scheduler_push_job (do_hash_job, hash_data, NULL,
1012       G_PRIORITY_DEFAULT, priv->cancellable);
1013 }
1014
1015 static void
1016 callbacks_data_free (gpointer user_data)
1017 {
1018   CallbacksData *data = user_data;
1019
1020   if (data->handler != NULL)
1021     g_object_unref (data->handler);
1022
1023   g_slice_free (CallbacksData, data);
1024 }
1025
1026 static void
1027 set_content_hash_type_from_classes (EmpathyFTHandler *handler,
1028     GList *classes)
1029 {
1030   GValueArray *class;
1031   GValue *v;
1032   GList *l;
1033   GArray *possible_values;
1034   guint value;
1035   GHashTable *fprops;
1036   gboolean valid;
1037   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
1038
1039   possible_values = g_array_new (TRUE, TRUE, sizeof (guint));
1040
1041   for (l = classes; l != NULL; l = l->next)
1042     {
1043       class = l->data;
1044       v = g_value_array_get_nth (class, 0);
1045       fprops = g_value_get_boxed (v);
1046
1047       value = tp_asv_get_uint32
1048         (fprops, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentHashType",
1049          &valid);
1050
1051       if (valid)
1052         g_array_append_val (possible_values, value);
1053     }
1054
1055   if (possible_values->len == 0)
1056     {
1057       /* there are no channel classes with hash support, disable it. */
1058       priv->use_hash = FALSE;
1059       priv->content_hash_type = TP_FILE_HASH_TYPE_NONE;
1060
1061       goto out;
1062     }
1063
1064   priv->use_hash = TRUE;
1065
1066   if (possible_values->len == 1)
1067     {
1068       priv->content_hash_type = g_array_index (possible_values, guint, 0);
1069     }
1070   else
1071     {
1072       /* order the array and pick the first non zero, so that MD5
1073        * is the preferred value.
1074        */
1075       g_array_sort (possible_values, empathy_uint_compare);
1076
1077       if (g_array_index (possible_values, guint, 0) == 0)
1078         priv->content_hash_type = g_array_index (possible_values, guint, 1);
1079       else
1080         priv->content_hash_type = g_array_index (possible_values, guint, 0);
1081     }
1082
1083 out:
1084   g_array_free (possible_values, TRUE);
1085
1086   DEBUG ("Hash enabled %s; setting content hash type as %u",
1087          priv->use_hash ? "True" : "False", priv->content_hash_type);
1088 }
1089
1090 static void
1091 find_ft_channel_classes_cb (GList *channel_classes,
1092     gpointer user_data)
1093 {
1094   CallbacksData *data = user_data;
1095   EmpathyFTHandler *handler = data->handler;
1096   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
1097   GError *myerr = NULL;
1098
1099   if (channel_classes == NULL)
1100     {
1101       g_set_error_literal (&myerr, EMPATHY_FT_ERROR_QUARK,
1102           EMPATHY_FT_ERROR_NOT_SUPPORTED,
1103           _("File transfer not supported by remote contact"));
1104
1105       if (!g_cancellable_is_cancelled (priv->cancellable))
1106         g_cancellable_cancel (priv->cancellable);
1107
1108       data->callback (handler, myerr, data->user_data);
1109       g_clear_error (&myerr);
1110     }
1111   else
1112     {
1113       /* set whether we support hash and the type of it */
1114       set_content_hash_type_from_classes (handler, channel_classes);
1115
1116       /* get back to the caller now */
1117       data->callback (handler, NULL, data->user_data);
1118     }
1119
1120   callbacks_data_free (data);
1121 }
1122
1123 static void
1124 ft_handler_complete_request (EmpathyFTHandler *handler)
1125 {
1126   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
1127
1128   /* populate the request table with all the known properties */
1129   ft_handler_populate_outgoing_request (handler);
1130
1131   if (priv->use_hash)
1132     /* start hashing the file */
1133     g_file_read_async (priv->gfile, G_PRIORITY_DEFAULT,
1134         priv->cancellable, ft_handler_read_async_cb, handler);
1135   else
1136     /* push directly the handler to the dispatcher */
1137     ft_handler_push_to_dispatcher (handler);
1138 }
1139
1140 static void
1141 ft_handler_gfile_ready_cb (GObject *source,
1142     GAsyncResult *res,
1143     CallbacksData *cb_data)
1144 {
1145   GFileInfo *info;
1146   GError *error = NULL;
1147   GTimeVal mtime;
1148   EmpathyFTHandlerPriv *priv = GET_PRIV (cb_data->handler);
1149
1150   DEBUG ("Got GFileInfo.");
1151
1152   info = g_file_query_info_finish (priv->gfile, res, &error);
1153
1154   if (error != NULL)
1155     goto out;
1156
1157   if (g_file_info_get_file_type (info) != G_FILE_TYPE_REGULAR)
1158     {
1159       error = g_error_new_literal (EMPATHY_FT_ERROR_QUARK,
1160           EMPATHY_FT_ERROR_INVALID_SOURCE_FILE,
1161           _("The selected file is not a regular file"));
1162       goto out;
1163     }
1164
1165   priv->total_bytes = g_file_info_get_size (info);
1166   if (priv->total_bytes == 0)
1167     {
1168       error = g_error_new_literal (EMPATHY_FT_ERROR_QUARK,
1169           EMPATHY_FT_ERROR_EMPTY_SOURCE_FILE,
1170           _("The selected file is empty"));
1171       goto out;
1172     }
1173
1174   priv->content_type = g_strdup (g_file_info_get_content_type (info));
1175   priv->filename = g_strdup (g_file_info_get_display_name (info));
1176   g_file_info_get_modification_time (info, &mtime);
1177   priv->mtime = mtime.tv_sec;
1178   priv->transferred_bytes = 0;
1179   priv->description = NULL;
1180
1181   g_object_unref (info);
1182
1183 out:
1184   if (error != NULL)
1185     {
1186       if (!g_cancellable_is_cancelled (priv->cancellable))
1187         g_cancellable_cancel (priv->cancellable);
1188
1189       cb_data->callback (cb_data->handler, error, cb_data->user_data);
1190       g_error_free (error);
1191
1192       callbacks_data_free (cb_data);
1193     }
1194   else
1195     {
1196       /* see if FT/hashing are allowed */
1197       empathy_dispatcher_find_requestable_channel_classes_async
1198           (priv->dispatcher, empathy_contact_get_connection (priv->contact),
1199            TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, TP_HANDLE_TYPE_CONTACT,
1200            find_ft_channel_classes_cb, cb_data,
1201            TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentHashType", NULL);
1202     }
1203 }
1204
1205 static void
1206 contact_factory_contact_cb (EmpathyTpContactFactory *factory,
1207     EmpathyContact *contact,
1208     const GError *error,
1209     gpointer user_data,
1210     GObject *weak_object)
1211 {
1212   CallbacksData *cb_data = user_data;
1213   EmpathyFTHandler *handler = EMPATHY_FT_HANDLER (weak_object);
1214   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
1215
1216   if (error != NULL)
1217     {
1218       if (!g_cancellable_is_cancelled (priv->cancellable))
1219         g_cancellable_cancel (priv->cancellable);
1220
1221       cb_data->callback (handler, (GError *) error, cb_data->user_data);
1222       callbacks_data_free (cb_data);
1223       return;
1224     }
1225
1226   priv->contact = g_object_ref (contact);
1227
1228   cb_data->callback (handler, NULL, cb_data->user_data);
1229 }
1230
1231 static void
1232 channel_get_all_properties_cb (TpProxy *proxy,
1233     GHashTable *properties,
1234     const GError *error,
1235     gpointer user_data,
1236     GObject *weak_object)
1237 {
1238   CallbacksData *cb_data = user_data;
1239   EmpathyFTHandler *handler = EMPATHY_FT_HANDLER (weak_object);
1240   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
1241   EmpathyTpContactFactory *c_factory;
1242   TpHandle c_handle;
1243
1244   if (error != NULL)
1245     {
1246       if (!g_cancellable_is_cancelled (priv->cancellable))
1247         g_cancellable_cancel (priv->cancellable);
1248
1249       cb_data->callback (handler, (GError *) error, cb_data->user_data);
1250
1251       callbacks_data_free (cb_data);
1252       return;
1253     }
1254
1255   priv->total_bytes = g_value_get_uint64 (
1256       g_hash_table_lookup (properties, "Size"));
1257
1258   priv->transferred_bytes = g_value_get_uint64 (
1259       g_hash_table_lookup (properties, "TransferredBytes"));
1260
1261   priv->filename = g_value_dup_string (
1262       g_hash_table_lookup (properties, "Filename"));
1263
1264   priv->content_hash = g_value_dup_string (
1265       g_hash_table_lookup (properties, "ContentHash"));
1266
1267   priv->content_hash_type = g_value_get_uint (
1268       g_hash_table_lookup (properties, "ContentHashType"));
1269
1270   priv->content_type = g_value_dup_string (
1271       g_hash_table_lookup (properties, "ContentType"));
1272
1273   priv->description = g_value_dup_string (
1274       g_hash_table_lookup (properties, "Description"));
1275
1276   c_factory = empathy_tp_contact_factory_dup_singleton
1277       (tp_channel_borrow_connection (TP_CHANNEL (proxy)));
1278   c_handle = tp_channel_get_handle (TP_CHANNEL (proxy), NULL);
1279   empathy_tp_contact_factory_get_from_handle (c_factory, c_handle,
1280       contact_factory_contact_cb, cb_data, callbacks_data_free,
1281       G_OBJECT (handler));
1282
1283   g_object_unref (c_factory);
1284 }
1285
1286 /* public methods */
1287
1288 /**
1289  * empathy_ft_handler_new_outgoing:
1290  * @contact: the #EmpathyContact to send @source to
1291  * @source: the #GFile to send
1292  * @callback: callback to be called when the handler has been created
1293  * @user_data: user data to be passed to @callback
1294  *
1295  * Triggers the creation of a new #EmpathyFTHandler for an outgoing transfer.
1296  */
1297 void
1298 empathy_ft_handler_new_outgoing (EmpathyContact *contact,
1299     GFile *source,
1300     EmpathyFTHandlerReadyCallback callback,
1301     gpointer user_data)
1302 {
1303   EmpathyFTHandler *handler;
1304   CallbacksData *data;
1305   EmpathyFTHandlerPriv *priv;
1306
1307   DEBUG ("New handler outgoing");
1308
1309   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1310   g_return_if_fail (G_IS_FILE (source));
1311
1312   handler = g_object_new (EMPATHY_TYPE_FT_HANDLER,
1313       "contact", contact, "gfile", source, NULL);
1314
1315   priv = GET_PRIV (handler);
1316
1317   data = g_slice_new0 (CallbacksData);
1318   data->callback = callback;
1319   data->user_data = user_data;
1320   data->handler = g_object_ref (handler);
1321
1322   /* start collecting info about the file */
1323   g_file_query_info_async (priv->gfile,
1324       G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME ","
1325       G_FILE_ATTRIBUTE_STANDARD_SIZE ","
1326       G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
1327       G_FILE_ATTRIBUTE_STANDARD_TYPE ","
1328       G_FILE_ATTRIBUTE_TIME_MODIFIED,
1329       G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT,
1330       NULL, (GAsyncReadyCallback) ft_handler_gfile_ready_cb, data);
1331 }
1332
1333 /**
1334  * empathy_ft_handler_new_incoming:
1335  * @tp_file: the #EmpathyTpFile wrapping the incoming channel
1336  * @callback: callback to be called when the handler has been created
1337  * @user_data: user data to be passed to @callback
1338  *
1339  * Triggers the creation of a new #EmpathyFTHandler for an incoming transfer.
1340  * Note that for the handler to be useful, you will have to set a destination
1341  * file with empathy_ft_handler_incoming_set_destination() after the handler
1342  * is ready.
1343  */
1344 void
1345 empathy_ft_handler_new_incoming (EmpathyTpFile *tp_file,
1346     EmpathyFTHandlerReadyCallback callback,
1347     gpointer user_data)
1348 {
1349   EmpathyFTHandler *handler;
1350   TpChannel *channel;
1351   CallbacksData *data;
1352
1353   g_return_if_fail (EMPATHY_IS_TP_FILE (tp_file));
1354
1355   handler = g_object_new (EMPATHY_TYPE_FT_HANDLER,
1356       "tp-file", tp_file, NULL);
1357
1358   g_object_get (tp_file, "channel", &channel, NULL);
1359
1360   data = g_slice_new0 (CallbacksData);
1361   data->callback = callback;
1362   data->user_data = user_data;
1363   data->handler = g_object_ref (handler);
1364
1365   tp_cli_dbus_properties_call_get_all (channel,
1366       -1, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER,
1367       channel_get_all_properties_cb, data, NULL, G_OBJECT (handler));
1368 }
1369
1370 /**
1371  * empathy_ft_handler_start_transfer:
1372  * @handler: an #EmpathyFTHandler
1373  *
1374  * Starts the transfer machinery. After this call, the transfer and hashing
1375  * signals will be emitted by the handler.
1376  */
1377 void
1378 empathy_ft_handler_start_transfer (EmpathyFTHandler *handler)
1379 {
1380   EmpathyFTHandlerPriv *priv;
1381
1382   g_return_if_fail (EMPATHY_IS_FT_HANDLER (handler));
1383
1384   priv = GET_PRIV (handler);
1385
1386   if (priv->tpfile == NULL)
1387     {
1388       ft_handler_complete_request (handler);
1389     }
1390   else
1391     {
1392       /* TODO: add support for resume. */
1393       empathy_tp_file_accept (priv->tpfile, 0, priv->gfile, priv->cancellable,
1394           ft_transfer_progress_callback, handler,
1395           ft_transfer_operation_callback, handler);
1396     }
1397 }
1398
1399 /**
1400  * empathy_ft_handler_cancel_transfer:
1401  * @handler: an #EmpathyFTHandler
1402  *
1403  * Cancels an ongoing handler operation. Note that this doesn't destroy
1404  * the object, which will keep all the properties, altough it won't be able
1405  * to do any more I/O.
1406  */
1407 void
1408 empathy_ft_handler_cancel_transfer (EmpathyFTHandler *handler)
1409 {
1410   EmpathyFTHandlerPriv *priv;
1411
1412   g_return_if_fail (EMPATHY_IS_FT_HANDLER (handler));
1413
1414   priv = GET_PRIV (handler);
1415
1416   /* if we don't have an EmpathyTpFile, we are hashing, so
1417    * we can just cancel the GCancellable to stop it.
1418    */
1419   if (priv->tpfile == NULL)
1420     g_cancellable_cancel (priv->cancellable);
1421   else
1422     empathy_tp_file_cancel (priv->tpfile);
1423 }
1424
1425 /**
1426  * empathy_ft_handler_incoming_set_destination:
1427  * @handler: an #EmpathyFTHandler
1428  * @destination: the #GFile where the transfer should be saved
1429  *
1430  * Sets the destination of the incoming handler to be @destination.
1431  * Note that calling this method is mandatory before starting the transfer
1432  * for incoming handlers.
1433  */
1434 void
1435 empathy_ft_handler_incoming_set_destination (EmpathyFTHandler *handler,
1436     GFile *destination)
1437 {
1438   EmpathyFTHandlerPriv *priv;
1439
1440   g_return_if_fail (EMPATHY_IS_FT_HANDLER (handler));
1441   g_return_if_fail (G_IS_FILE (destination));
1442
1443   priv = GET_PRIV (handler);
1444
1445   g_object_set (handler, "gfile", destination, NULL);
1446
1447   /* check if hash is supported. if it isn't, set use_hash to FALSE
1448    * anyway, so that clients won't be expecting us to checksum.
1449    */
1450   if (EMP_STR_EMPTY (priv->content_hash) ||
1451       priv->content_hash_type == TP_FILE_HASH_TYPE_NONE)
1452     priv->use_hash = FALSE;
1453   else
1454     priv->use_hash = TRUE;
1455 }
1456
1457 /**
1458  * empathy_ft_handler_get_filename:
1459  * @handler: an #EmpathyFTHandler
1460  *
1461  * Returns the name of the file being transferred.
1462  *
1463  * Return value: the name of the file being transferred
1464  */
1465 const char *
1466 empathy_ft_handler_get_filename (EmpathyFTHandler *handler)
1467 {
1468   EmpathyFTHandlerPriv *priv;
1469
1470   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), NULL);
1471
1472   priv = GET_PRIV (handler);
1473
1474   return priv->filename;
1475 }
1476
1477 /**
1478  * empathy_ft_handler_get_content_type:
1479  * @handler: an #EmpathyFTHandler
1480  *
1481  * Returns the content type of the file being transferred.
1482  *
1483  * Return value: the content type of the file being transferred
1484  */
1485 const char *
1486 empathy_ft_handler_get_content_type (EmpathyFTHandler *handler)
1487 {
1488   EmpathyFTHandlerPriv *priv;
1489
1490   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), NULL);
1491
1492   priv = GET_PRIV (handler);
1493
1494   return priv->content_type;
1495 }
1496
1497 /**
1498  * empathy_ft_handler_get_contact:
1499  * @handler: an #EmpathyFTHandler
1500  *
1501  * Returns the remote #EmpathyContact at the other side of the transfer.
1502  *
1503  * Return value: the remote #EmpathyContact for @handler
1504  */
1505 EmpathyContact *
1506 empathy_ft_handler_get_contact (EmpathyFTHandler *handler)
1507 {
1508   EmpathyFTHandlerPriv *priv;
1509
1510   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), NULL);
1511
1512   priv = GET_PRIV (handler);
1513
1514   return priv->contact;
1515 }
1516
1517 /**
1518  * empathy_ft_handler_get_gfile:
1519  * @handler: an #EmpathyFTHandler
1520  *
1521  * Returns the #GFile where the transfer is being read/saved.
1522  *
1523  * Return value: the #GFile where the transfer is being read/saved
1524  */
1525 GFile *
1526 empathy_ft_handler_get_gfile (EmpathyFTHandler *handler)
1527 {
1528   EmpathyFTHandlerPriv *priv;
1529
1530   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), NULL);
1531
1532   priv = GET_PRIV (handler);
1533
1534   return priv->gfile;
1535 }
1536
1537 /**
1538  * empathy_ft_handler_get_use_hash:
1539  * @handler: an #EmpathyFTHandler
1540  *
1541  * Returns whether @handler has checksumming enabled. This can depend on
1542  * the CM and the remote contact capabilities.
1543  *
1544  * Return value: %TRUE if the handler has checksumming enabled,
1545  * %FALSE otherwise.
1546  */
1547 gboolean
1548 empathy_ft_handler_get_use_hash (EmpathyFTHandler *handler)
1549 {
1550   EmpathyFTHandlerPriv *priv;
1551
1552   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), FALSE);
1553
1554   priv = GET_PRIV (handler);
1555
1556   return priv->use_hash;
1557 }
1558
1559 /**
1560  * empathy_ft_handler_is_incoming:
1561  * @handler: an #EmpathyFTHandler
1562  *
1563  * Returns whether @handler is incoming or outgoing.
1564  *
1565  * Return value: %TRUE if the handler is incoming, %FALSE otherwise.
1566  */
1567 gboolean
1568 empathy_ft_handler_is_incoming (EmpathyFTHandler *handler)
1569 {
1570   EmpathyFTHandlerPriv *priv;
1571
1572   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), FALSE);
1573
1574   priv = GET_PRIV (handler);
1575
1576   if (priv->tpfile == NULL)
1577     return FALSE;
1578
1579   return empathy_tp_file_is_incoming (priv->tpfile);
1580 }
1581
1582 /**
1583  * empathy_ft_handler_get_transferred_bytes:
1584  * @handler: an #EmpathyFTHandler
1585  *
1586  * Returns the number of bytes already transferred by the handler.
1587  *
1588  * Return value: the number of bytes already transferred by the handler.
1589  */
1590 guint64
1591 empathy_ft_handler_get_transferred_bytes (EmpathyFTHandler *handler)
1592 {
1593   EmpathyFTHandlerPriv *priv;
1594
1595   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), 0);
1596
1597   priv = GET_PRIV (handler);
1598
1599   return priv->transferred_bytes;
1600 }
1601
1602 /**
1603  * empathy_ft_handler_get_total_bytes:
1604  * @handler: an #EmpathyFTHandler
1605  *
1606  * Returns the total size of the file being transferred by the handler.
1607  *
1608  * Return value: a number of bytes indicating the total size of the file being
1609  * transferred by the handler.
1610  */
1611 guint64
1612 empathy_ft_handler_get_total_bytes (EmpathyFTHandler *handler)
1613 {
1614   EmpathyFTHandlerPriv *priv;
1615
1616   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), 0);
1617
1618   priv = GET_PRIV (handler);
1619
1620   return priv->total_bytes;
1621 }
1622
1623 /**
1624  * empathy_ft_handler_is_completed:
1625  * @handler: an #EmpathyFTHandler
1626  *
1627  * Returns whether the transfer for @handler has been completed succesfully.
1628  *
1629  * Return value: %TRUE if the handler has been transferred correctly, %FALSE
1630  * otherwise
1631  */
1632 gboolean
1633 empathy_ft_handler_is_completed (EmpathyFTHandler *handler)
1634 {
1635   EmpathyFTHandlerPriv *priv;
1636
1637   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), FALSE);
1638
1639   priv = GET_PRIV (handler);
1640
1641   return priv->is_completed;
1642 }
1643
1644 /**
1645  * empathy_ft_handler_is_cancelled:
1646  * @handler: an #EmpathyFTHandler
1647  *
1648  * Returns whether the transfer for @handler has been cancelled or has stopped
1649  * due to an error.
1650  *
1651  * Return value: %TRUE if the transfer for @handler has been cancelled
1652  * or has stopped due to an error, %FALSE otherwise.
1653  */
1654 gboolean
1655 empathy_ft_handler_is_cancelled (EmpathyFTHandler *handler)
1656 {
1657   EmpathyFTHandlerPriv *priv;
1658
1659   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), FALSE);
1660
1661   priv = GET_PRIV (handler);
1662
1663   return g_cancellable_is_cancelled (priv->cancellable);
1664 }