]> git.0d.be Git - empathy.git/blob - libempathy/empathy-ft-handler.c
Use proper DEBUG() messages
[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 <extensions/extensions.h>
25 #include <glib.h>
26 #include <glib/gi18n.h>
27 #include <telepathy-glib/util.h>
28
29 #include "empathy-ft-handler.h"
30 #include "empathy-contact-factory.h"
31 #include "empathy-dispatcher.h"
32 #include "empathy-marshal.h"
33 #include "empathy-utils.h"
34
35 #define DEBUG_FLAG EMPATHY_DEBUG_FT
36 #include "empathy-debug.h"
37
38 G_DEFINE_TYPE (EmpathyFTHandler, empathy_ft_handler, G_TYPE_OBJECT)
39
40 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyFTHandler)
41
42 #define BUFFER_SIZE 4096
43
44 enum {
45   PROP_TP_FILE = 1,
46   PROP_G_FILE,
47   PROP_CONTACT
48 };
49
50 enum {
51   HASHING_STARTED,
52   HASHING_PROGRESS,
53   HASHING_DONE,
54   TRANSFER_STARTED,
55   TRANSFER_PROGRESS,
56   TRANSFER_DONE,
57   TRANSFER_ERROR,
58   LAST_SIGNAL
59 };
60
61 typedef struct {
62   GInputStream *stream;
63   gboolean done_reading;
64   GError *error;
65   guchar *buffer;
66   GChecksum *checksum;
67   gssize total_read;
68   guint64 total_bytes;
69   EmpathyFTHandler *handler;
70 } HashingData;
71
72 typedef struct {
73   EmpathyFTHandlerReadyCallback callback;
74   gpointer user_data;
75   EmpathyFTHandler *handler;
76 } CallbacksData;
77
78 /* private data */
79 typedef struct {
80   gboolean dispose_run;
81   GFile *gfile;
82   EmpathyTpFile *tpfile;
83   GCancellable *cancellable;
84
85   /* request for the new transfer */
86   GHashTable *request;
87
88   /* transfer properties */
89   EmpathyContact *contact;
90   gchar *content_type;
91   gchar *filename;
92   gchar *description;
93   guint64 total_bytes;
94   guint64 transferred_bytes;
95   guint64 mtime;
96   gchar *content_hash;
97   EmpFileHashType content_hash_type;
98   EmpFileTransferState current_state;
99
100   gboolean is_completed;
101   gboolean is_cancelled;
102 } EmpathyFTHandlerPriv;
103
104 static guint signals[LAST_SIGNAL] = { 0 };
105
106 /* prototypes */
107 static void schedule_hash_chunk (HashingData *hash_data);
108
109 /* GObject implementations */
110 static void
111 do_get_property (GObject *object,
112                  guint property_id,
113                  GValue *value,
114                  GParamSpec *pspec)
115 {
116   EmpathyFTHandlerPriv *priv = GET_PRIV (object);
117
118   switch (property_id)
119     {
120       case PROP_CONTACT:
121         g_value_set_object (value, priv->contact);
122         break;
123       case PROP_G_FILE:
124         g_value_set_object (value, priv->gfile);
125         break;
126       case PROP_TP_FILE:
127         g_value_set_object (value, priv->tpfile);
128         break;
129       default:
130         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
131     }
132 }
133
134 static void
135 do_set_property (GObject *object,
136                  guint property_id, 
137                  const GValue *value,
138                  GParamSpec *pspec)
139 {
140   EmpathyFTHandlerPriv *priv = GET_PRIV (object);
141
142   switch (property_id)
143     {
144       case PROP_CONTACT:
145         priv->contact = g_value_dup_object (value);
146         break;
147       case PROP_G_FILE:
148         priv->gfile = g_value_dup_object (value);
149         break;
150       case PROP_TP_FILE:
151         priv->tpfile = g_value_dup_object (value);
152         break;
153       default:
154         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
155     }
156 }
157
158 static void
159 do_dispose (GObject *object)
160 {
161   EmpathyFTHandlerPriv *priv = GET_PRIV (object);
162
163   if (priv->dispose_run)
164     return;
165
166   priv->dispose_run = TRUE;
167
168   if (priv->contact) {
169     g_object_unref (priv->contact);
170     priv->contact = NULL;
171   }
172
173   if (priv->gfile) {
174     g_object_unref (priv->gfile);
175     priv->gfile = NULL;
176   }
177
178   if (priv->tpfile) {
179     empathy_tp_file_close (priv->tpfile);
180     g_object_unref (priv->tpfile);
181     priv->tpfile = NULL;
182   }
183
184   if (priv->cancellable) {
185     g_object_unref (priv->cancellable);
186     priv->cancellable = NULL;
187   }
188   
189   G_OBJECT_CLASS (empathy_ft_handler_parent_class)->dispose (object);
190 }
191
192 static void
193 do_finalize (GObject *object)
194 {
195   EmpathyFTHandlerPriv *priv = GET_PRIV (object);
196
197   g_free (priv->content_type);
198   priv->content_type = NULL;
199
200   g_free (priv->filename);
201   priv->filename = NULL;
202
203   g_free (priv->description);
204   priv->description = NULL;
205
206   g_free (priv->content_hash);
207   priv->content_hash = NULL;
208
209   if (priv->request != NULL)
210     {
211       g_hash_table_destroy (priv->request);
212       priv->request = NULL;
213     }
214
215   G_OBJECT_CLASS (empathy_ft_handler_parent_class)->finalize (object);
216 }
217
218 static void
219 empathy_ft_handler_class_init (EmpathyFTHandlerClass *klass)
220 {
221   GObjectClass *object_class = G_OBJECT_CLASS (klass);
222   GParamSpec *param_spec;
223
224   g_type_class_add_private (klass, sizeof (EmpathyFTHandlerPriv));
225
226   object_class->get_property = do_get_property;
227   object_class->set_property = do_set_property;
228   object_class->dispose = do_dispose;
229   object_class->finalize = do_finalize;
230
231   /* properties */
232   param_spec = g_param_spec_object ("contact",
233     "contact", "The remote contact",
234     EMPATHY_TYPE_CONTACT,
235     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
236   g_object_class_install_property (object_class, PROP_CONTACT, param_spec);
237
238   param_spec = g_param_spec_object ("gfile",
239     "gfile", "The GFile we're handling",
240     G_TYPE_FILE,
241     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
242   g_object_class_install_property (object_class, PROP_G_FILE, param_spec);
243
244   param_spec = g_param_spec_object ("tp-file",
245     "tp-file", "The file's channel wrapper",
246     EMPATHY_TYPE_TP_FILE,
247     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
248   g_object_class_install_property (object_class, PROP_TP_FILE, param_spec);
249
250   /* signals */
251   signals[TRANSFER_STARTED] =
252     g_signal_new ("transfer-started", G_TYPE_FROM_CLASS (klass),
253         G_SIGNAL_RUN_LAST, 0, NULL, NULL,
254         g_cclosure_marshal_VOID__OBJECT,
255         G_TYPE_NONE,
256         1, EMPATHY_TYPE_TP_FILE);
257
258   signals[TRANSFER_DONE] =
259     g_signal_new ("transfer-done", G_TYPE_FROM_CLASS (klass),
260         G_SIGNAL_RUN_LAST, 0, NULL, NULL,
261         g_cclosure_marshal_VOID__OBJECT,
262         G_TYPE_NONE,
263         1, EMPATHY_TYPE_TP_FILE);
264
265   signals[TRANSFER_ERROR] =
266     g_signal_new ("transfer-error", G_TYPE_FROM_CLASS (klass),
267         G_SIGNAL_RUN_LAST, 0, NULL, NULL,
268         g_cclosure_marshal_VOID__POINTER,
269         G_TYPE_NONE,
270         1, G_TYPE_POINTER);
271
272   signals[TRANSFER_PROGRESS] =
273     g_signal_new ("transfer-progress", G_TYPE_FROM_CLASS (klass),
274         G_SIGNAL_RUN_LAST, 0, NULL, NULL,
275         _empathy_marshal_VOID__UINT64_UINT64,
276         G_TYPE_NONE,
277         2, G_TYPE_UINT64, G_TYPE_UINT64);
278
279   signals[HASHING_STARTED] =
280     g_signal_new ("hashing-started", G_TYPE_FROM_CLASS (klass),
281         G_SIGNAL_RUN_LAST, 0, NULL, NULL,
282         g_cclosure_marshal_VOID__VOID,
283         G_TYPE_NONE, 0);
284
285   signals[HASHING_PROGRESS] =
286     g_signal_new ("hashing-progress", G_TYPE_FROM_CLASS (klass),
287         G_SIGNAL_RUN_LAST, 0, NULL, NULL,
288         _empathy_marshal_VOID__UINT64_UINT64,
289         G_TYPE_NONE,
290         2, G_TYPE_UINT64, G_TYPE_UINT64);
291
292   signals[HASHING_DONE] =
293     g_signal_new ("hashing-done", G_TYPE_FROM_CLASS (klass),
294         G_SIGNAL_RUN_LAST, 0, NULL, NULL,
295         g_cclosure_marshal_VOID__VOID,
296         G_TYPE_NONE, 0);
297 }
298
299 static void
300 empathy_ft_handler_init (EmpathyFTHandler *self)
301 {
302   EmpathyFTHandlerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
303     EMPATHY_TYPE_FT_HANDLER, EmpathyFTHandlerPriv);
304
305   self->priv = priv;
306 }
307
308 /* private functions */
309
310 static void
311 hash_data_free (HashingData *data)
312 {
313   if (data->buffer != NULL)
314     {
315       g_free (data->buffer);
316       data->buffer = NULL;
317     }
318
319   if (data->stream != NULL)
320     {
321       g_object_unref (data->stream);
322       data->stream = NULL;
323     }
324
325   if (data->checksum != NULL)
326     {
327       g_checksum_free (data->checksum);
328       data->checksum = NULL;
329     }
330
331   if (data->error != NULL)
332     {
333       g_error_free (data->error);
334       data->error = NULL;
335     }
336   if (data->handler != NULL)
337     {
338       g_object_unref (data->handler);
339       data->handler = NULL;
340     }
341
342   g_slice_free (HashingData, data);
343 }
344
345 static void
346 ft_transfer_operation_callback (EmpathyTpFile *tp_file,
347                                 const GError *error,
348                                 gpointer user_data)
349 {
350   EmpathyFTHandler *handler = user_data;
351   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
352
353   DEBUG ("Transfer operation callback, error %p", error);
354
355   if (error != NULL)
356     {
357       priv->is_cancelled = TRUE;
358       g_signal_emit (handler, signals[TRANSFER_ERROR], 0, error);
359     }
360   else 
361     {
362       priv->is_completed = TRUE;
363       g_signal_emit (handler, signals[TRANSFER_DONE], 0, tp_file);
364     }
365 }
366
367 static void
368 ft_transfer_progress_callback (EmpathyTpFile *tp_file,
369                                guint64 transferred_bytes,
370                                gpointer user_data)
371 {
372   EmpathyFTHandler *handler = user_data;
373   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
374
375   if (priv->transferred_bytes != transferred_bytes)
376     {
377       priv->transferred_bytes = transferred_bytes;
378       g_signal_emit (handler, signals[TRANSFER_PROGRESS], 0,
379           transferred_bytes, priv->total_bytes);
380     }
381 }
382
383 static void
384 ft_handler_create_channel_cb (EmpathyDispatchOperation *operation,
385                               const GError *error,
386                               gpointer user_data)
387 {
388   EmpathyFTHandler *handler = user_data;
389   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
390   GError *my_error = NULL;
391
392   DEBUG ("Dispatcher create channel CB");
393
394   if (error != NULL)
395     {
396       priv->is_cancelled = TRUE;
397       g_signal_emit (handler, signals[TRANSFER_ERROR], 0, error);
398       return;
399     }
400
401   g_cancellable_set_error_if_cancelled (priv->cancellable, &my_error);
402
403   if (my_error != NULL)
404     {
405       priv->is_cancelled = TRUE;
406       g_signal_emit (handler, signals[TRANSFER_ERROR], 0, my_error);
407       g_clear_error (&my_error);
408
409       return;
410     }
411
412   priv->tpfile = g_object_ref
413       (empathy_dispatch_operation_get_channel_wrapper (operation));
414
415   g_signal_emit (handler, signals[TRANSFER_STARTED], 0, priv->tpfile);
416
417   empathy_tp_file_offer (priv->tpfile, priv->gfile, priv->cancellable,
418       ft_transfer_progress_callback, handler,
419       ft_transfer_operation_callback, handler);
420
421   empathy_dispatch_operation_claim (operation);
422 }
423
424 static void
425 ft_handler_push_to_dispatcher (EmpathyFTHandler *handler)
426 {
427   EmpathyDispatcher *dispatcher;
428   McAccount *account;
429   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
430
431   DEBUG ("Pushing request to the dispatcher");
432
433   dispatcher = empathy_dispatcher_dup_singleton ();
434   account = empathy_contact_get_account (priv->contact);
435
436   empathy_dispatcher_create_channel (dispatcher, account, priv->request,
437       ft_handler_create_channel_cb, handler);
438
439   g_object_unref (dispatcher);
440 }
441
442 static gboolean
443 ft_handler_check_if_allowed (EmpathyFTHandler *handler)
444 {
445   EmpathyDispatcher *dispatcher;
446   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
447   McAccount *account;
448   GStrv allowed;
449   gboolean res = TRUE;
450
451   dispatcher = empathy_dispatcher_dup_singleton ();
452   account = empathy_contact_get_account (priv->contact);
453
454   allowed = empathy_dispatcher_find_channel_class (dispatcher, account,
455       TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, TP_HANDLE_TYPE_CONTACT);
456
457   if (!tp_strv_contains ((const gchar * const *) allowed,
458       TP_IFACE_CHANNEL ".TargetHandle"))
459     res = FALSE;
460
461   g_object_unref (dispatcher);
462
463   return res;
464 }
465
466 static void
467 ft_handler_populate_outgoing_request (EmpathyFTHandler *handler)
468 {
469   guint contact_handle;
470   GHashTable *request;
471   GValue *value;
472   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
473
474   request = priv->request = g_hash_table_new_full (g_str_hash, g_str_equal,
475             NULL, (GDestroyNotify) tp_g_value_slice_free);
476
477   contact_handle = empathy_contact_get_handle (priv->contact);
478
479   /* org.freedesktop.Telepathy.Channel.ChannelType */
480   value = tp_g_value_slice_new (G_TYPE_STRING);
481   g_value_set_string (value, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER);
482   g_hash_table_insert (request, TP_IFACE_CHANNEL ".ChannelType", value);
483
484   /* org.freedesktop.Telepathy.Channel.TargetHandleType */
485   value = tp_g_value_slice_new (G_TYPE_UINT);
486   g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT);
487   g_hash_table_insert (request, TP_IFACE_CHANNEL ".TargetHandleType", value);
488
489   /* org.freedesktop.Telepathy.Channel.TargetHandle */
490   value = tp_g_value_slice_new (G_TYPE_UINT);
491   g_value_set_uint (value, contact_handle);
492   g_hash_table_insert (request, TP_IFACE_CHANNEL ".TargetHandle", value);
493
494   /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.ContentType */
495   value = tp_g_value_slice_new (G_TYPE_STRING);
496   g_value_set_string (value, priv->content_type);
497   g_hash_table_insert (request,
498       TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentType", value);
499
500   /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.Filename */
501   value = tp_g_value_slice_new (G_TYPE_STRING);
502   g_value_set_string (value, priv->filename);
503   g_hash_table_insert (request,
504       TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Filename", value);
505
506   /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.Size */
507   value = tp_g_value_slice_new (G_TYPE_UINT64);
508   g_value_set_uint64 (value, (guint64) priv->total_bytes);
509   g_hash_table_insert (request,
510       TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Size", value);
511
512   /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.Date */
513   value = tp_g_value_slice_new (G_TYPE_UINT64);
514   g_value_set_uint64 (value, (guint64) priv->mtime);
515   g_hash_table_insert (request,
516       TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Date", value);
517 }
518
519 static void
520 hash_job_async_close_stream_cb (GObject *source,
521                                 GAsyncResult *res,
522                                 gpointer user_data)
523 {
524   HashingData *hash_data = user_data;
525   EmpathyFTHandler *handler = hash_data->handler;
526   EmpathyFTHandlerPriv *priv;
527   GError *error = NULL;
528   GValue *value;
529
530   DEBUG ("Closing stream after hashing.");
531
532   priv = GET_PRIV (handler);
533
534   /* if we're here we for sure have done reading, check if we stopped due
535    * to an error.
536    */
537   g_input_stream_close_finish (hash_data->stream, res, &error);
538   if (error != NULL)
539     {
540       if (hash_data->error != NULL)
541         {
542           /* if we already stopped due to an error, probably we're completely
543            * hosed for some reason. just return the first read error
544            * to the user.
545            */
546           g_clear_error (&error);
547           error = hash_data->error;
548         }
549
550       goto cleanup;
551     }
552
553   if (hash_data->error != NULL)
554     {
555       error = hash_data->error;
556       goto cleanup;
557     }
558
559   /* set the checksum in the request */
560
561   DEBUG ("Got file hash %s", g_checksum_get_string (hash_data->checksum));
562
563   /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.ContentHash */
564   value = tp_g_value_slice_new (G_TYPE_STRING);
565   g_value_set_string (value, g_checksum_get_string (hash_data->checksum));
566   g_hash_table_insert (priv->request,
567       TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentHash", value);
568
569 cleanup:
570   hash_data_free (hash_data);
571
572   if (error != NULL)
573     {
574       priv->is_cancelled = TRUE;
575       g_signal_emit (handler, signals[TRANSFER_ERROR], 0, error);
576       g_clear_error (&error);
577     }
578   else
579     {
580       g_signal_emit (handler, signals[HASHING_DONE], 0);
581
582       /* the request is complete now, push it to the dispatcher */
583       ft_handler_push_to_dispatcher (handler);
584     }
585 }
586
587 static void
588 hash_job_async_read_cb (GObject *source,
589                         GAsyncResult *res,
590                         gpointer user_data)
591 {
592   HashingData *hash_data = user_data;
593   gssize bytes_read;
594   GError *error = NULL;
595
596   DEBUG ("Reading a chunk for hashing.");
597
598   bytes_read = g_input_stream_read_finish (hash_data->stream, res, &error);
599   if (error != NULL)
600     {
601       hash_data->error = error;
602       hash_data->done_reading = TRUE;
603       goto out;
604     }
605
606   hash_data->total_read += bytes_read;
607
608   /* we now have the chunk */
609   if (bytes_read == 0)
610     {
611       hash_data->done_reading = TRUE;
612       goto out;
613     }
614   else
615     {
616       g_checksum_update (hash_data->checksum, hash_data->buffer, bytes_read);
617       g_signal_emit (hash_data->handler, signals[HASHING_PROGRESS], 0,
618           (guint64) hash_data->total_read, (guint64) hash_data->total_bytes);
619     }
620
621 out:
622   g_free (hash_data->buffer);
623   hash_data->buffer = NULL;
624
625   schedule_hash_chunk (hash_data);
626 }
627
628 static void
629 schedule_hash_chunk (HashingData *hash_data)
630 {
631   EmpathyFTHandlerPriv *priv;
632
633   priv = GET_PRIV (hash_data->handler);
634
635   if (hash_data->done_reading)
636     {
637       g_input_stream_close_async (hash_data->stream, G_PRIORITY_DEFAULT,
638           priv->cancellable, hash_job_async_close_stream_cb, hash_data);
639     }
640   else
641     {
642       if (hash_data->buffer == NULL)
643         hash_data->buffer = g_malloc0 (BUFFER_SIZE);
644
645       g_input_stream_read_async (hash_data->stream, hash_data->buffer,
646           BUFFER_SIZE, G_PRIORITY_DEFAULT, priv->cancellable,
647           hash_job_async_read_cb, hash_data);
648     }
649 }
650
651 static void
652 ft_handler_read_async_cb (GObject *source,
653                           GAsyncResult *res,
654                           gpointer user_data)
655 {
656   GFileInputStream *stream;
657   GError *error = NULL;
658   HashingData *hash_data;
659   GValue *value;
660   EmpathyFTHandler *handler = user_data;
661   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
662
663   DEBUG ("GFile read async CB.");
664
665   stream = g_file_read_finish (priv->gfile, res, &error);
666   if (error != NULL)
667     {
668       priv->is_cancelled = TRUE;
669       g_signal_emit (handler, signals[TRANSFER_ERROR], 0, error);
670       g_clear_error (&error);
671
672       return;
673     }
674
675   hash_data = g_slice_new0 (HashingData);
676   hash_data->stream = G_INPUT_STREAM (stream);
677   hash_data->done_reading = FALSE;
678   hash_data->total_bytes = priv->total_bytes;
679   hash_data->handler = g_object_ref (handler);
680   /* FIXME: should look at the CM capabilities before setting the
681    * checksum type?
682    */
683   hash_data->checksum = g_checksum_new (G_CHECKSUM_MD5);
684
685   /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.ContentHashType */
686   value = tp_g_value_slice_new (G_TYPE_UINT);
687   g_value_set_uint (value, EMP_FILE_HASH_TYPE_MD5);
688   g_hash_table_insert (priv->request,
689       TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentHashType", value);
690
691   g_signal_emit (handler, signals[HASHING_STARTED], 0);
692
693   schedule_hash_chunk (hash_data);
694 }
695
696 static void
697 ft_handler_complete_request (EmpathyFTHandler *handler)
698
699   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
700   GError *myerr = NULL;
701
702   /* check if FT is allowed before firing up the I/O machinery */
703   if (!ft_handler_check_if_allowed (handler))
704     {
705       g_set_error_literal (&myerr, EMPATHY_FT_ERROR_QUARK,
706           EMPATHY_FT_ERROR_NOT_SUPPORTED,
707           _("File transfer not supported by remote contact"));
708
709       priv->is_cancelled = TRUE;
710       g_signal_emit (handler, signals[TRANSFER_ERROR], 0, myerr);
711       g_clear_error (&myerr);
712
713       return;
714     }
715
716   /* populate the request table with all the known properties */
717   ft_handler_populate_outgoing_request (handler);
718
719   /* now start hashing the file */
720   g_file_read_async (priv->gfile, G_PRIORITY_DEFAULT,
721       priv->cancellable, ft_handler_read_async_cb, handler);
722 }
723
724 static void
725 callbacks_data_free (gpointer user_data)
726 {
727   CallbacksData *data = user_data;
728
729   if (data->handler)
730     g_object_unref (data->handler);
731
732   g_slice_free (CallbacksData, data);
733 }
734
735 static void
736 ft_handler_gfile_ready_cb (GObject *source,
737                            GAsyncResult *res,
738                            CallbacksData *cb_data)
739 {
740   GFileInfo *info;
741   GError *error = NULL;
742   GTimeVal mtime;
743   EmpathyFTHandlerPriv *priv = GET_PRIV (cb_data->handler);
744
745   DEBUG ("Got GFileInfo.");
746
747   info = g_file_query_info_finish (priv->gfile, res, &error);
748
749   if (error != NULL)
750     goto out;
751
752   priv->content_type = g_strdup (g_file_info_get_content_type (info));
753   priv->filename = g_strdup (g_file_info_get_display_name (info));
754   priv->total_bytes = g_file_info_get_size (info);
755   g_file_info_get_modification_time (info, &mtime);
756   priv->mtime = mtime.tv_sec;
757   priv->transferred_bytes = 0;
758   priv->description = NULL;
759
760   g_object_unref (info);
761
762 out:
763   if (error == NULL)
764     {
765       cb_data->callback (cb_data->handler, NULL, cb_data->user_data);
766     }
767   else
768     {
769       cb_data->callback (NULL, error, cb_data->user_data);
770       g_error_free (error);
771       g_object_unref (cb_data->handler);
772     }
773
774   callbacks_data_free (cb_data);
775 }
776
777 static void
778 ft_handler_contact_ready_cb (EmpathyContact *contact,
779                              const GError *error,
780                              gpointer user_data,
781                              GObject *weak_object)  
782 {
783   CallbacksData *cb_data = user_data;
784   EmpathyFTHandlerPriv *priv = GET_PRIV (weak_object);
785
786   g_assert (priv->contact != NULL);
787   g_assert (priv->gfile != NULL);
788
789   DEBUG ("Contact is ready.");
790
791   /* start collecting info about the file */
792   g_file_query_info_async (priv->gfile,
793       G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME ","
794       G_FILE_ATTRIBUTE_STANDARD_SIZE ","
795       G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
796       G_FILE_ATTRIBUTE_TIME_MODIFIED,
797       G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT,
798       NULL, (GAsyncReadyCallback) ft_handler_gfile_ready_cb,
799       cb_data);
800 }
801
802 static void
803 channel_get_all_properties_cb (TpProxy *proxy,
804                                GHashTable *properties,
805                                const GError *error,
806                                gpointer user_data,
807                                GObject *weak_object)
808 {
809   CallbacksData *cb_data = user_data;
810   EmpathyFTHandler *handler = EMPATHY_FT_HANDLER (weak_object);
811   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
812   EmpathyContactFactory *c_factory;
813   guint c_handle;
814   McAccount *account;
815
816   if (error != NULL)
817     {
818       cb_data->callback (NULL, (GError *) error, cb_data->user_data);
819       g_object_unref (handler);
820       return;
821     }
822
823   priv->total_bytes = g_value_get_uint64 (
824       g_hash_table_lookup (properties, "Size"));
825
826   priv->transferred_bytes = g_value_get_uint64 (
827       g_hash_table_lookup (properties, "TransferredBytes"));
828
829   priv->filename = g_value_dup_string (
830       g_hash_table_lookup (properties, "Filename"));
831
832   priv->content_hash = g_value_dup_string (
833       g_hash_table_lookup (properties, "ContentHash"));
834
835   priv->content_hash_type = g_value_get_uint (
836       g_hash_table_lookup (properties, "ContentHashType"));
837
838   priv->content_type = g_value_dup_string (
839       g_hash_table_lookup (properties, "ContentType"));
840
841   priv->description = g_value_dup_string (
842       g_hash_table_lookup (properties, "Description"));
843
844   g_hash_table_destroy (properties);
845
846   c_factory = empathy_contact_factory_dup_singleton ();
847   account = empathy_channel_get_account (TP_CHANNEL (proxy));
848   c_handle = tp_channel_get_handle (TP_CHANNEL (proxy), NULL);
849   priv->contact = empathy_contact_factory_get_from_handle
850       (c_factory, account, c_handle);
851
852   g_object_unref (c_factory);
853   g_object_unref (account);
854
855   cb_data->callback (handler, NULL, cb_data->user_data);
856 }
857
858 /* public methods */
859
860 void
861 empathy_ft_handler_new_outgoing (EmpathyContact *contact,
862                                  GFile *source,
863                                  EmpathyFTHandlerReadyCallback callback,
864                                  gpointer user_data)
865 {
866   EmpathyFTHandler *handler;
867   CallbacksData *data;
868   EmpathyFTHandlerPriv *priv;
869
870   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
871   g_return_if_fail (G_IS_FILE (source));
872
873   handler = g_object_new (EMPATHY_TYPE_FT_HANDLER,
874       "contact", contact, "gfile", source, NULL);
875
876   priv = GET_PRIV (handler);
877
878   data = g_slice_new0 (CallbacksData);
879   data->callback = callback;
880   data->user_data = user_data;
881   data->handler = g_object_ref (handler);
882
883   empathy_contact_call_when_ready (priv->contact,
884       EMPATHY_CONTACT_READY_HANDLE,
885       ft_handler_contact_ready_cb, data, NULL, G_OBJECT (handler));
886 }
887
888 void
889 empathy_ft_handler_new_incoming (EmpathyTpFile *tp_file,
890                                  EmpathyFTHandlerReadyCallback callback,
891                                  gpointer user_data)
892 {
893   EmpathyFTHandler *handler;
894   TpChannel *channel;
895   CallbacksData *data;
896
897   g_return_if_fail (EMPATHY_IS_TP_FILE (tp_file));
898
899   handler = g_object_new (EMPATHY_TYPE_FT_HANDLER,
900       "tp-file", tp_file, NULL);
901
902   g_object_get (tp_file, "channel", &channel, NULL);
903
904   data = g_slice_new0 (CallbacksData);
905   data->callback = callback;
906   data->user_data = user_data;
907   data->handler = g_object_ref (handler);
908
909   tp_cli_dbus_properties_call_get_all (channel,
910       -1, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER,
911       channel_get_all_properties_cb, data, callbacks_data_free, G_OBJECT (handler));
912 }
913
914 void
915 empathy_ft_handler_start_transfer (EmpathyFTHandler *handler,
916                                    GCancellable *cancellable)
917 {
918   EmpathyFTHandlerPriv *priv;
919
920   g_return_if_fail (EMPATHY_IS_FT_HANDLER (handler));
921
922   priv = GET_PRIV (handler);
923   priv->cancellable = g_object_ref (cancellable);
924
925   if (priv->tpfile == NULL)
926     {
927       ft_handler_complete_request (handler);
928     }
929   else
930     {
931       /* emit the start signal now, so that we can catch errors in the
932        * op callback.
933        */
934       g_signal_emit (handler, signals[TRANSFER_STARTED], 0, priv->tpfile);
935
936       /* TODO: add support for resume. */
937       empathy_tp_file_accept (priv->tpfile, 0, priv->gfile, priv->cancellable,
938           ft_transfer_progress_callback, handler,
939           ft_transfer_operation_callback, handler);
940     }
941 }
942
943 void
944 empathy_ft_handler_incoming_set_destination (EmpathyFTHandler *handler,
945                                              GFile *destination)
946 {
947   g_return_if_fail (EMPATHY_IS_FT_HANDLER (handler));
948   g_return_if_fail (G_IS_FILE (destination));
949
950   g_object_set (handler, "gfile", destination, NULL);
951 }
952
953 const char *
954 empathy_ft_handler_get_filename (EmpathyFTHandler *handler)
955 {
956   EmpathyFTHandlerPriv *priv;
957
958   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), NULL);
959
960   priv = GET_PRIV (handler);
961
962   return priv->filename;
963 }
964
965 const char *
966 empathy_ft_handler_get_content_type (EmpathyFTHandler *handler)
967 {
968   EmpathyFTHandlerPriv *priv;
969
970   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), NULL);
971
972   priv = GET_PRIV (handler);
973
974   return priv->content_type;
975 }
976
977 EmpathyContact *
978 empathy_ft_handler_get_contact (EmpathyFTHandler *handler)
979 {
980   EmpathyFTHandlerPriv *priv;
981
982   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), NULL);
983
984   priv = GET_PRIV (handler);
985
986   return priv->contact;
987 }
988
989 GFile *
990 empathy_ft_handler_get_gfile (EmpathyFTHandler *handler)
991 {
992   EmpathyFTHandlerPriv *priv;
993
994   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), NULL);
995
996   priv = GET_PRIV (handler);
997
998   return priv->gfile;
999 }
1000
1001 EmpFileTransferState
1002 empathy_ft_handler_get_state (EmpathyFTHandler *handler,
1003                               char **state_string)
1004 {
1005   EmpathyFTHandlerPriv *priv;
1006
1007   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), -1);
1008
1009   priv = GET_PRIV (handler);
1010
1011   return priv->current_state;
1012 }
1013
1014 gboolean
1015 empathy_ft_handler_is_incoming (EmpathyFTHandler *handler)
1016 {
1017   EmpathyFTHandlerPriv *priv;
1018
1019   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), FALSE);
1020
1021   priv = GET_PRIV (handler);
1022
1023   if (priv->tpfile == NULL)
1024     return FALSE;
1025
1026   return empathy_tp_file_is_incoming (priv->tpfile);  
1027 }
1028
1029 guint64
1030 empathy_ft_handler_get_transferred_bytes (EmpathyFTHandler *handler)
1031 {
1032   EmpathyFTHandlerPriv *priv;
1033
1034   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), 0);
1035
1036   priv = GET_PRIV (handler);
1037
1038   return priv->transferred_bytes;
1039 }
1040
1041 guint64
1042 empathy_ft_handler_get_total_bytes (EmpathyFTHandler *handler)
1043 {
1044   EmpathyFTHandlerPriv *priv;
1045
1046   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), 0);
1047
1048   priv = GET_PRIV (handler);
1049
1050   return priv->total_bytes;
1051 }
1052
1053 gboolean
1054 empathy_ft_handler_is_completed (EmpathyFTHandler *handler)
1055 {
1056   EmpathyFTHandlerPriv *priv;
1057
1058   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), FALSE);
1059
1060   priv = GET_PRIV (handler);
1061
1062   return priv->is_completed;
1063 }
1064
1065 gboolean
1066 empathy_ft_handler_is_cancelled (EmpathyFTHandler *handler)
1067 {
1068   EmpathyFTHandlerPriv *priv;
1069
1070   g_return_val_if_fail (EMPATHY_IS_FT_HANDLER (handler), FALSE);
1071
1072   priv = GET_PRIV (handler);
1073
1074   return priv->is_cancelled;
1075 }