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