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