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