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