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