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