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