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