]> git.0d.be Git - empathy.git/blob - libempathy/empathy-ft-handler.c
Refuse sending empty or special files (directories, char/block devices, etc.)
[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   if (g_file_info_get_file_type (info) != G_FILE_TYPE_REGULAR)
1153     {
1154       error = g_error_new_literal (EMPATHY_FT_ERROR_QUARK,
1155           EMPATHY_FT_ERROR_INVALID_SOURCE_FILE,
1156           _("The selected file is not a regular file"));
1157       goto out;
1158     }
1159
1160   priv->total_bytes = g_file_info_get_size (info);
1161   if (priv->total_bytes == 0)
1162     {
1163       error = g_error_new_literal (EMPATHY_FT_ERROR_QUARK,
1164           EMPATHY_FT_ERROR_EMPTY_SOURCE_FILE,
1165           _("The selected file is empty"));
1166       goto out;
1167     }
1168
1169   priv->content_type = g_strdup (g_file_info_get_content_type (info));
1170   priv->filename = g_strdup (g_file_info_get_display_name (info));
1171   g_file_info_get_modification_time (info, &mtime);
1172   priv->mtime = mtime.tv_sec;
1173   priv->transferred_bytes = 0;
1174   priv->description = NULL;
1175
1176   g_object_unref (info);
1177
1178 out:
1179   if (error != NULL)
1180     {
1181       if (!g_cancellable_is_cancelled (priv->cancellable))
1182         g_cancellable_cancel (priv->cancellable);
1183
1184       cb_data->callback (cb_data->handler, error, cb_data->user_data);
1185       g_error_free (error);
1186
1187       callbacks_data_free (cb_data);
1188     }
1189   else
1190     {
1191       /* see if FT/hashing are allowed */
1192       empathy_dispatcher_find_requestable_channel_classes_async
1193           (priv->dispatcher, empathy_contact_get_connection (priv->contact),
1194            TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, TP_HANDLE_TYPE_CONTACT,
1195            find_ft_channel_classes_cb, cb_data,
1196            TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentHashType", NULL);
1197     }
1198 }
1199
1200 static void
1201 contact_factory_contact_cb (EmpathyTpContactFactory *factory,
1202     EmpathyContact *contact,
1203     const GError *error,
1204     gpointer user_data,
1205     GObject *weak_object)
1206 {
1207   CallbacksData *cb_data = user_data;
1208   EmpathyFTHandler *handler = EMPATHY_FT_HANDLER (weak_object);
1209   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
1210
1211   if (error != NULL)
1212     {
1213       if (!g_cancellable_is_cancelled (priv->cancellable))
1214         g_cancellable_cancel (priv->cancellable);
1215
1216       cb_data->callback (handler, (GError *) error, cb_data->user_data);
1217       callbacks_data_free (cb_data);
1218       return;
1219     }
1220
1221   priv->contact = contact;
1222
1223   cb_data->callback (handler, NULL, cb_data->user_data);
1224 }
1225
1226 static void
1227 channel_get_all_properties_cb (TpProxy *proxy,
1228     GHashTable *properties,
1229     const GError *error,
1230     gpointer user_data,
1231     GObject *weak_object)
1232 {
1233   CallbacksData *cb_data = user_data;
1234   EmpathyFTHandler *handler = EMPATHY_FT_HANDLER (weak_object);
1235   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
1236   EmpathyTpContactFactory *c_factory;
1237   TpHandle c_handle;
1238
1239   if (error != NULL)
1240     {
1241       if (!g_cancellable_is_cancelled (priv->cancellable))
1242         g_cancellable_cancel (priv->cancellable);
1243
1244       cb_data->callback (handler, (GError *) error, cb_data->user_data);
1245
1246       callbacks_data_free (cb_data);
1247       return;
1248     }
1249
1250   priv->total_bytes = g_value_get_uint64 (
1251       g_hash_table_lookup (properties, "Size"));
1252
1253   priv->transferred_bytes = g_value_get_uint64 (
1254       g_hash_table_lookup (properties, "TransferredBytes"));
1255
1256   priv->filename = g_value_dup_string (
1257       g_hash_table_lookup (properties, "Filename"));
1258
1259   priv->content_hash = g_value_dup_string (
1260       g_hash_table_lookup (properties, "ContentHash"));
1261
1262   priv->content_hash_type = g_value_get_uint (
1263       g_hash_table_lookup (properties, "ContentHashType"));
1264
1265   priv->content_type = g_value_dup_string (
1266       g_hash_table_lookup (properties, "ContentType"));
1267
1268   priv->description = g_value_dup_string (
1269       g_hash_table_lookup (properties, "Description"));
1270
1271   c_factory = empathy_tp_contact_factory_dup_singleton
1272       (tp_channel_borrow_connection (TP_CHANNEL (proxy)));
1273   c_handle = tp_channel_get_handle (TP_CHANNEL (proxy), NULL);
1274   empathy_tp_contact_factory_get_from_handle (c_factory, c_handle,
1275       contact_factory_contact_cb, cb_data, callbacks_data_free,
1276       G_OBJECT (handler));
1277
1278   g_object_unref (c_factory);
1279 }
1280
1281 /* public methods */
1282
1283 /**
1284  * empathy_ft_handler_new_outgoing:
1285  * @contact: the #EmpathyContact to send @source to
1286  * @source: the #GFile to send
1287  * @callback: callback to be called when the handler has been created
1288  * @user_data: user data to be passed to @callback
1289  *
1290  * Triggers the creation of a new #EmpathyFTHandler for an outgoing transfer.
1291  */
1292 void
1293 empathy_ft_handler_new_outgoing (EmpathyContact *contact,
1294     GFile *source,
1295     EmpathyFTHandlerReadyCallback callback,
1296     gpointer user_data)
1297 {
1298   EmpathyFTHandler *handler;
1299   CallbacksData *data;
1300   EmpathyFTHandlerPriv *priv;
1301
1302   DEBUG ("New handler outgoing");
1303
1304   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1305   g_return_if_fail (G_IS_FILE (source));
1306
1307   handler = g_object_new (EMPATHY_TYPE_FT_HANDLER,
1308       "contact", contact, "gfile", source, NULL);
1309
1310   priv = GET_PRIV (handler);
1311
1312   data = g_slice_new0 (CallbacksData);
1313   data->callback = callback;
1314   data->user_data = user_data;
1315   data->handler = g_object_ref (handler);
1316
1317   /* start collecting info about the file */
1318   g_file_query_info_async (priv->gfile,
1319       G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME ","
1320       G_FILE_ATTRIBUTE_STANDARD_SIZE ","
1321       G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
1322       G_FILE_ATTRIBUTE_STANDARD_TYPE ","
1323       G_FILE_ATTRIBUTE_TIME_MODIFIED,
1324       G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT,
1325       NULL, (GAsyncReadyCallback) ft_handler_gfile_ready_cb, data);
1326 }
1327
1328 /**
1329  * empathy_ft_handler_new_incoming:
1330  * @tp_file: the #EmpathyTpFile wrapping the incoming channel
1331  * @callback: callback to be called when the handler has been created
1332  * @user_data: user data to be passed to @callback
1333  *
1334  * Triggers the creation of a new #EmpathyFTHandler for an incoming transfer.
1335  * Note that for the handler to be useful, you will have to set a destination
1336  * file with empathy_ft_handler_incoming_set_destination() after the handler
1337  * is ready.
1338  */
1339 void
1340 empathy_ft_handler_new_incoming (EmpathyTpFile *tp_file,
1341     EmpathyFTHandlerReadyCallback callback,
1342     gpointer user_data)
1343 {
1344   EmpathyFTHandler *handler;
1345   TpChannel *channel;
1346   CallbacksData *data;
1347
1348   g_return_if_fail (EMPATHY_IS_TP_FILE (tp_file));
1349
1350   handler = g_object_new (EMPATHY_TYPE_FT_HANDLER,
1351       "tp-file", tp_file, NULL);
1352
1353   g_object_get (tp_file, "channel", &channel, NULL);
1354
1355   data = g_slice_new0 (CallbacksData);
1356   data->callback = callback;
1357   data->user_data = user_data;
1358   data->handler = g_object_ref (handler);
1359
1360   tp_cli_dbus_properties_call_get_all (channel,
1361       -1, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER,
1362       channel_get_all_properties_cb, data, NULL, G_OBJECT (handler));
1363 }
1364
1365 /**
1366  * empathy_ft_handler_start_transfer:
1367  * @handler: an #EmpathyFTHandler
1368  *
1369  * Starts the transfer machinery. After this call, the transfer and hashing
1370  * signals will be emitted by the handler.
1371  */
1372 void
1373 empathy_ft_handler_start_transfer (EmpathyFTHandler *handler)
1374 {
1375   EmpathyFTHandlerPriv *priv;
1376
1377   g_return_if_fail (EMPATHY_IS_FT_HANDLER (handler));
1378
1379   priv = GET_PRIV (handler);
1380
1381   if (priv->tpfile == NULL)
1382     {
1383       ft_handler_complete_request (handler);
1384     }
1385   else
1386     {
1387       /* TODO: add support for resume. */
1388       empathy_tp_file_accept (priv->tpfile, 0, priv->gfile, priv->cancellable,
1389           ft_transfer_progress_callback, handler,
1390           ft_transfer_operation_callback, handler);
1391     }
1392 }
1393
1394 /**
1395  * empathy_ft_handler_cancel_transfer:
1396  * @handler: an #EmpathyFTHandler
1397  *
1398  * Cancels an ongoing handler operation. Note that this doesn't destroy
1399  * the object, which will keep all the properties, altough it won't be able
1400  * to do any more I/O.
1401  */
1402 void
1403 empathy_ft_handler_cancel_transfer (EmpathyFTHandler *handler)
1404 {
1405   EmpathyFTHandlerPriv *priv;
1406
1407   g_return_if_fail (EMPATHY_IS_FT_HANDLER (handler));
1408
1409   priv = GET_PRIV (handler);
1410
1411   /* if we don't have an EmpathyTpFile, we are hashing, so
1412    * we can just cancel the GCancellable to stop it.
1413    */
1414   if (priv->tpfile == NULL)
1415     g_cancellable_cancel (priv->cancellable);
1416   else
1417     empathy_tp_file_cancel (priv->tpfile);
1418 }
1419
1420 /**
1421  * empathy_ft_handler_incoming_set_destination:
1422  * @handler: an #EmpathyFTHandler
1423  * @destination: the #GFile where the transfer should be saved
1424  *
1425  * Sets the destination of the incoming handler to be @destination.
1426  * Note that calling this method is mandatory before starting the transfer
1427  * for incoming handlers.
1428  */
1429 void
1430 empathy_ft_handler_incoming_set_destination (EmpathyFTHandler *handler,
1431     GFile *destination)
1432 {
1433   EmpathyFTHandlerPriv *priv;
1434
1435   g_return_if_fail (EMPATHY_IS_FT_HANDLER (handler));
1436   g_return_if_fail (G_IS_FILE (destination));
1437
1438   priv = GET_PRIV (handler);
1439
1440   g_object_set (handler, "gfile", destination, NULL);
1441
1442   /* check if hash is supported. if it isn't, set use_hash to FALSE
1443    * anyway, so that clients won't be expecting us to checksum.
1444    */
1445   if (EMP_STR_EMPTY (priv->content_hash) ||
1446       priv->content_hash_type == TP_FILE_HASH_TYPE_NONE)
1447     priv->use_hash = FALSE;
1448   else
1449     priv->use_hash = TRUE;
1450 }
1451
1452 /**
1453  * empathy_ft_handler_get_filename:
1454  * @handler: an #EmpathyFTHandler
1455  *
1456  * Returns the name of the file being transferred.
1457  *
1458  * Return value: the name of the file being transferred
1459  */
1460 const char *
1461 empathy_ft_handler_get_filename (EmpathyFTHandler *handler)
1462 {
1463   EmpathyFTHandlerPriv *priv;
1464
1465   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), NULL);
1466
1467   priv = GET_PRIV (handler);
1468
1469   return priv->filename;
1470 }
1471
1472 /**
1473  * empathy_ft_handler_get_content_type:
1474  * @handler: an #EmpathyFTHandler
1475  *
1476  * Returns the content type of the file being transferred.
1477  *
1478  * Return value: the content type of the file being transferred
1479  */
1480 const char *
1481 empathy_ft_handler_get_content_type (EmpathyFTHandler *handler)
1482 {
1483   EmpathyFTHandlerPriv *priv;
1484
1485   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), NULL);
1486
1487   priv = GET_PRIV (handler);
1488
1489   return priv->content_type;
1490 }
1491
1492 /**
1493  * empathy_ft_handler_get_contact:
1494  * @handler: an #EmpathyFTHandler
1495  *
1496  * Returns the remote #EmpathyContact at the other side of the transfer.
1497  *
1498  * Return value: the remote #EmpathyContact for @handler
1499  */
1500 EmpathyContact *
1501 empathy_ft_handler_get_contact (EmpathyFTHandler *handler)
1502 {
1503   EmpathyFTHandlerPriv *priv;
1504
1505   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), NULL);
1506
1507   priv = GET_PRIV (handler);
1508
1509   return priv->contact;
1510 }
1511
1512 /**
1513  * empathy_ft_handler_get_gfile:
1514  * @handler: an #EmpathyFTHandler
1515  *
1516  * Returns the #GFile where the transfer is being read/saved.
1517  *
1518  * Return value: the #GFile where the transfer is being read/saved
1519  */
1520 GFile *
1521 empathy_ft_handler_get_gfile (EmpathyFTHandler *handler)
1522 {
1523   EmpathyFTHandlerPriv *priv;
1524
1525   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), NULL);
1526
1527   priv = GET_PRIV (handler);
1528
1529   return priv->gfile;
1530 }
1531
1532 /**
1533  * empathy_ft_handler_get_use_hash:
1534  * @handler: an #EmpathyFTHandler
1535  *
1536  * Returns whether @handler has checksumming enabled. This can depend on
1537  * the CM and the remote contact capabilities.
1538  *
1539  * Return value: %TRUE if the handler has checksumming enabled,
1540  * %FALSE otherwise.
1541  */
1542 gboolean
1543 empathy_ft_handler_get_use_hash (EmpathyFTHandler *handler)
1544 {
1545   EmpathyFTHandlerPriv *priv;
1546
1547   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), FALSE);
1548
1549   priv = GET_PRIV (handler);
1550
1551   return priv->use_hash;
1552 }
1553
1554 /**
1555  * empathy_ft_handler_is_incoming:
1556  * @handler: an #EmpathyFTHandler
1557  *
1558  * Returns whether @handler is incoming or outgoing.
1559  *
1560  * Return value: %TRUE if the handler is incoming, %FALSE otherwise.
1561  */
1562 gboolean
1563 empathy_ft_handler_is_incoming (EmpathyFTHandler *handler)
1564 {
1565   EmpathyFTHandlerPriv *priv;
1566
1567   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), FALSE);
1568
1569   priv = GET_PRIV (handler);
1570
1571   if (priv->tpfile == NULL)
1572     return FALSE;
1573
1574   return empathy_tp_file_is_incoming (priv->tpfile);
1575 }
1576
1577 /**
1578  * empathy_ft_handler_get_transferred_bytes:
1579  * @handler: an #EmpathyFTHandler
1580  *
1581  * Returns the number of bytes already transferred by the handler.
1582  *
1583  * Return value: the number of bytes already transferred by the handler.
1584  */
1585 guint64
1586 empathy_ft_handler_get_transferred_bytes (EmpathyFTHandler *handler)
1587 {
1588   EmpathyFTHandlerPriv *priv;
1589
1590   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), 0);
1591
1592   priv = GET_PRIV (handler);
1593
1594   return priv->transferred_bytes;
1595 }
1596
1597 /**
1598  * empathy_ft_handler_get_total_bytes:
1599  * @handler: an #EmpathyFTHandler
1600  *
1601  * Returns the total size of the file being transferred by the handler.
1602  *
1603  * Return value: a number of bytes indicating the total size of the file being
1604  * transferred by the handler.
1605  */
1606 guint64
1607 empathy_ft_handler_get_total_bytes (EmpathyFTHandler *handler)
1608 {
1609   EmpathyFTHandlerPriv *priv;
1610
1611   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), 0);
1612
1613   priv = GET_PRIV (handler);
1614
1615   return priv->total_bytes;
1616 }
1617
1618 /**
1619  * empathy_ft_handler_is_completed:
1620  * @handler: an #EmpathyFTHandler
1621  *
1622  * Returns whether the transfer for @handler has been completed succesfully.
1623  *
1624  * Return value: %TRUE if the handler has been transferred correctly, %FALSE
1625  * otherwise
1626  */
1627 gboolean
1628 empathy_ft_handler_is_completed (EmpathyFTHandler *handler)
1629 {
1630   EmpathyFTHandlerPriv *priv;
1631
1632   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), FALSE);
1633
1634   priv = GET_PRIV (handler);
1635
1636   return priv->is_completed;
1637 }
1638
1639 /**
1640  * empathy_ft_handler_is_cancelled:
1641  * @handler: an #EmpathyFTHandler
1642  *
1643  * Returns whether the transfer for @handler has been cancelled or has stopped
1644  * due to an error.
1645  *
1646  * Return value: %TRUE if the transfer for @handler has been cancelled
1647  * or has stopped due to an error, %FALSE otherwise.
1648  */
1649 gboolean
1650 empathy_ft_handler_is_cancelled (EmpathyFTHandler *handler)
1651 {
1652   EmpathyFTHandlerPriv *priv;
1653
1654   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), FALSE);
1655
1656   priv = GET_PRIV (handler);
1657
1658   return g_cancellable_is_cancelled (priv->cancellable);
1659 }