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