]> git.0d.be Git - empathy.git/blob - libempathy/empathy-ft-handler.c
dfaeceabfa6d8311d1b4fcca4d11222610eab0d9
[empathy.git] / libempathy / empathy-ft-handler.c
1 /*
2  * empathy-ft-handler.c - Source for EmpathyFTHandler
3  * Copyright (C) 2009 Collabora Ltd.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  *
19  * Author: Cosimo Cecchi <cosimo.cecchi@collabora.co.uk>
20  */
21  
22 /* empathy-ft-handler.c */
23
24 #include <extensions/extensions.h>
25 #include <glib.h>
26 #include <glib/gi18n.h>
27 #include <telepathy-glib/util.h>
28
29 #include "empathy-ft-handler.h"
30 #include "empathy-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   EmpathyFTHandler *handler;
62   GFile *gfile;
63   GHashTable *request;
64   goffset total_size;
65 } RequestData;
66
67 typedef struct {
68   RequestData *req_data;
69   GInputStream *stream;
70   gboolean done_reading;
71   GError *error;
72   guchar *buffer;
73   GChecksum *checksum;
74   gssize total_read;
75 } HashingData;
76
77 /* private data */
78 typedef struct {
79   gboolean dispose_run;
80   EmpathyContact *contact;
81   GFile *gfile;
82   EmpathyTpFile *tpfile;
83   GCancellable *cancellable;
84 } EmpathyFTHandlerPriv;
85
86 static guint signals[LAST_SIGNAL] = { 0 };
87
88 /* prototypes */
89 static void schedule_hash_chunk (HashingData *hash_data);
90
91 /* GObject implementations */
92 static void
93 do_get_property (GObject *object,
94                  guint property_id,
95                  GValue *value,
96                  GParamSpec *pspec)
97 {
98   EmpathyFTHandlerPriv *priv = GET_PRIV (object);
99
100   switch (property_id)
101     {
102       case PROP_CONTACT:
103         g_value_set_object (value, priv->contact);
104         break;
105       case PROP_G_FILE:
106         g_value_set_object (value, priv->gfile);
107         break;
108       case PROP_TP_FILE:
109         g_value_set_object (value, priv->tpfile);
110         break;
111       default:
112         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
113     }
114 }
115
116 static void
117 do_set_property (GObject *object,
118                  guint property_id, 
119                  const GValue *value,
120                  GParamSpec *pspec)
121 {
122   EmpathyFTHandlerPriv *priv = GET_PRIV (object);
123
124   switch (property_id)
125     {
126       case PROP_CONTACT:
127         priv->contact = g_value_dup_object (value);
128         break;
129       case PROP_G_FILE:
130         priv->gfile = g_value_dup_object (value);
131         break;
132       case PROP_TP_FILE:
133         priv->tpfile = g_value_dup_object (value);
134         break;
135       default:
136         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
137     }
138 }
139
140 static void
141 do_dispose (GObject *object)
142 {
143   EmpathyFTHandlerPriv *priv = GET_PRIV (object);
144
145   if (priv->dispose_run)
146     return;
147
148   priv->dispose_run = TRUE;
149
150   if (priv->contact) {
151     g_object_unref (priv->contact);
152     priv->contact = NULL;
153   }
154
155   if (priv->gfile) {
156     g_object_unref (priv->gfile);
157     priv->gfile = NULL;
158   }
159
160   if (priv->tpfile) {
161     empathy_tp_file_close (priv->tpfile);
162     g_object_unref (priv->tpfile);
163     priv->tpfile = NULL;
164   }
165
166   if (priv->cancellable) {
167     g_object_unref (priv->cancellable);
168     priv->cancellable = NULL;
169   }
170   
171   G_OBJECT_CLASS (empathy_ft_handler_parent_class)->dispose (object);
172 }
173
174 static void
175 do_finalize (GObject *object)
176 {
177   G_OBJECT_CLASS (empathy_ft_handler_parent_class)->finalize (object);
178 }
179
180 static void
181 empathy_ft_handler_class_init (EmpathyFTHandlerClass *klass)
182 {
183   GObjectClass *object_class = G_OBJECT_CLASS (klass);
184   GParamSpec *param_spec;
185
186   g_type_class_add_private (klass, sizeof (EmpathyFTHandlerPriv));
187
188   object_class->get_property = do_get_property;
189   object_class->set_property = do_set_property;
190   object_class->dispose = do_dispose;
191   object_class->finalize = do_finalize;
192
193   /* properties */
194   param_spec = g_param_spec_object ("contact",
195     "contact", "The remote contact",
196     EMPATHY_TYPE_CONTACT,
197     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
198   g_object_class_install_property (object_class, PROP_CONTACT, param_spec);
199
200   param_spec = g_param_spec_object ("gfile",
201     "gfile", "The GFile we're handling",
202     G_TYPE_FILE,
203     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
204   g_object_class_install_property (object_class, PROP_G_FILE, param_spec);
205
206   param_spec = g_param_spec_object ("tp-file",
207     "tp-file", "The file's channel wrapper",
208     EMPATHY_TYPE_TP_FILE,
209     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
210   g_object_class_install_property (object_class, PROP_TP_FILE, param_spec);
211
212   /* signals */
213   signals[TRANSFER_STARTED] =
214     g_signal_new ("transfer-started", G_TYPE_FROM_CLASS (klass),
215         G_SIGNAL_RUN_LAST, 0, NULL, NULL,
216         g_cclosure_marshal_VOID__OBJECT,
217         G_TYPE_NONE,
218         1, EMPATHY_TYPE_TP_FILE);
219
220   signals[TRANSFER_DONE] =
221     g_signal_new ("transfer-done", G_TYPE_FROM_CLASS (klass),
222         G_SIGNAL_RUN_LAST, 0, NULL, NULL,
223         g_cclosure_marshal_VOID__OBJECT,
224         G_TYPE_NONE,
225         1, EMPATHY_TYPE_TP_FILE);
226
227   signals[TRANSFER_ERROR] =
228     g_signal_new ("transfer-error", G_TYPE_FROM_CLASS (klass),
229         G_SIGNAL_RUN_LAST, 0, NULL, NULL,
230         g_cclosure_marshal_VOID__POINTER,
231         G_TYPE_NONE,
232         1, G_TYPE_POINTER);
233
234   signals[TRANSFER_PROGRESS] =
235     g_signal_new ("transfer-progress", G_TYPE_FROM_CLASS (klass),
236         G_SIGNAL_RUN_LAST, 0, NULL, NULL,
237         _empathy_marshal_VOID__OBJECT_UINT64_UINT64,
238         G_TYPE_NONE,
239         2, EMPATHY_TYPE_TP_FILE, G_TYPE_UINT64, G_TYPE_UINT64);
240
241   signals[HASHING_STARTED] =
242     g_signal_new ("hashing-started", G_TYPE_FROM_CLASS (klass),
243         G_SIGNAL_RUN_LAST, 0, NULL, NULL,
244         g_cclosure_marshal_VOID__VOID,
245         G_TYPE_NONE, 0);
246
247   signals[HASHING_PROGRESS] =
248     g_signal_new ("hashing-progress", G_TYPE_FROM_CLASS (klass),
249         G_SIGNAL_RUN_LAST, 0, NULL, NULL,
250         _empathy_marshal_VOID__UINT64_UINT64,
251         G_TYPE_NONE,
252         2, G_TYPE_UINT64, G_TYPE_UINT64);
253
254   signals[HASHING_DONE] =
255     g_signal_new ("hashing-done", G_TYPE_FROM_CLASS (klass),
256         G_SIGNAL_RUN_LAST, 0, NULL, NULL,
257         g_cclosure_marshal_VOID__VOID,
258         G_TYPE_NONE, 0);
259 }
260
261 static void
262 empathy_ft_handler_init (EmpathyFTHandler *self)
263 {
264   EmpathyFTHandlerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
265     EMPATHY_TYPE_FT_HANDLER, EmpathyFTHandlerPriv);
266
267   self->priv = priv;
268 }
269
270 /* private functions */
271
272 static void
273 hash_data_free (HashingData *data)
274 {
275   if (data->buffer != NULL)
276     {
277       g_free (data->buffer);
278       data->buffer = NULL;
279     }
280
281   if (data->stream != NULL)
282     {
283       g_object_unref (data->stream);
284       data->stream = NULL;
285     }
286
287   if (data->checksum != NULL)
288     {
289       g_checksum_free (data->checksum);
290       data->checksum = NULL;
291     }
292
293   if (data->error != NULL)
294     {
295       g_error_free (data->error);
296       data->error = NULL;
297     }
298
299   g_slice_free (HashingData, data);
300 }
301
302 static void
303 request_data_free (RequestData *data)
304 {
305   if (data->gfile != NULL)
306     {
307       g_object_unref (data->gfile);
308       data->gfile = NULL;
309     }
310
311   if (data->request != NULL)
312     {
313       g_hash_table_unref (data->request);
314       data->request = NULL;
315     }
316
317   g_slice_free (RequestData, data);
318 }
319
320 static RequestData *
321 request_data_new (EmpathyFTHandler *handler, GFile *gfile)
322 {
323   RequestData *ret;
324
325   ret = g_slice_new0 (RequestData);
326   ret->request = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
327       (GDestroyNotify) tp_g_value_slice_free);
328   ret->handler = g_object_ref (handler);
329   ret->gfile = g_object_ref (gfile);
330
331   return ret;
332 }
333
334 static void
335 ft_handler_create_channel_cb (EmpathyDispatchOperation *operation,
336                               const GError *error,
337                               gpointer user_data)
338 {
339   RequestData *req_data = user_data;
340   GError *myerr = NULL;
341   EmpathyFTHandlerPriv *priv = GET_PRIV (req_data->handler);
342
343   DEBUG ("FT: dispatcher create channel CB");
344
345   if (error != NULL)
346     {
347       /* TODO: error handling */
348       goto out;
349     }
350
351   priv->tpfile = g_object_ref
352       (empathy_dispatch_operation_get_channel_wrapper (operation));
353   empathy_tp_file_offer (priv->tpfile, req_data->gfile, &myerr);
354   empathy_dispatch_operation_claim (operation);
355
356 out:
357   request_data_free (req_data);
358 }
359
360 static void
361 ft_handler_push_to_dispatcher (RequestData *req_data)
362 {
363   EmpathyDispatcher *dispatcher;
364   McAccount *account;
365   EmpathyFTHandlerPriv *priv = GET_PRIV (req_data->handler);
366
367   DEBUG ("FT: pushing request to the dispatcher");
368
369   dispatcher = empathy_dispatcher_dup_singleton ();
370   account = empathy_contact_get_account (priv->contact);
371
372   empathy_dispatcher_create_channel (dispatcher, account, req_data->request,
373       ft_handler_create_channel_cb, req_data);
374
375   g_object_unref (dispatcher);
376 }
377
378 static gboolean
379 ft_handler_check_if_allowed (EmpathyFTHandler *handler)
380 {
381   EmpathyDispatcher *dispatcher;
382   EmpathyFTHandlerPriv *priv = GET_PRIV (handler);
383   McAccount *account;
384   GStrv allowed;
385   gboolean res = TRUE;
386
387   dispatcher = empathy_dispatcher_dup_singleton ();
388   account = empathy_contact_get_account (priv->contact);
389
390   allowed = empathy_dispatcher_find_channel_class (dispatcher, account,
391       EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, TP_HANDLE_TYPE_CONTACT);
392
393   if (!tp_strv_contains ((const gchar * const *) allowed,
394       TP_IFACE_CHANNEL ".TargetHandle"))
395     res = FALSE;
396
397   g_object_unref (dispatcher);
398
399   return res;
400 }
401
402 static void
403 ft_handler_populate_outgoing_request (RequestData *req_data,
404                                       GFileInfo *file_info)
405 {
406   guint contact_handle;
407   const char *content_type;
408   const char *display_name;
409   goffset size;
410   GTimeVal mtime;
411   GValue *value;
412   GHashTable *request = req_data->request;
413   EmpathyFTHandlerPriv *priv = GET_PRIV (req_data->handler);
414
415   /* gather all the information */
416   contact_handle = empathy_contact_get_handle (priv->contact);
417
418   content_type = g_file_info_get_content_type (file_info);
419   display_name = g_file_info_get_display_name (file_info);
420   size = g_file_info_get_size (file_info);
421   g_file_info_get_modification_time (file_info, &mtime);
422
423   /* org.freedesktop.Telepathy.Channel.ChannelType */
424   value = tp_g_value_slice_new (G_TYPE_STRING);
425   g_value_set_string (value, EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER);
426   g_hash_table_insert (request, TP_IFACE_CHANNEL ".ChannelType", value);
427
428   /* org.freedesktop.Telepathy.Channel.TargetHandleType */
429   value = tp_g_value_slice_new (G_TYPE_UINT);
430   g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT);
431   g_hash_table_insert (request, TP_IFACE_CHANNEL ".TargetHandleType", value);
432
433   /* org.freedesktop.Telepathy.Channel.TargetHandle */
434   value = tp_g_value_slice_new (G_TYPE_UINT);
435   g_value_set_uint (value, contact_handle);
436   g_hash_table_insert (request, TP_IFACE_CHANNEL ".TargetHandle", value);
437
438   /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.ContentType */
439   value = tp_g_value_slice_new (G_TYPE_STRING);
440   g_value_set_string (value, content_type);
441   g_hash_table_insert (request,
442       EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentType", value);
443
444   /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.Filename */
445   value = tp_g_value_slice_new (G_TYPE_STRING);
446   g_value_set_string (value, display_name);
447   g_hash_table_insert (request,
448       EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Filename", value);
449
450   /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.Size */
451   value = tp_g_value_slice_new (G_TYPE_UINT64);
452   g_value_set_uint64 (value, (guint64) size);
453   g_hash_table_insert (request,
454       EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Size", value);
455
456   /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.Date */
457   value = tp_g_value_slice_new (G_TYPE_UINT64);
458   g_value_set_uint64 (value, (guint64) mtime.tv_sec);
459   g_hash_table_insert (request,
460       EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".Date", value);
461 }
462
463 static void
464 hash_job_async_close_stream_cb (GObject *source,
465                                 GAsyncResult *res,
466                                 gpointer user_data)
467 {
468   HashingData *hash_data = user_data;
469   RequestData *req_data = hash_data->req_data;
470   GError *error = NULL;
471   GValue *value;
472   GHashTable *request;
473
474   DEBUG ("FT: closing stream after hashing.");
475
476   /* if we're here we for sure have done reading, check if we stopped due
477    * to an error.
478    */
479   g_input_stream_close_finish (hash_data->stream, res, &error);
480   if (error != NULL)
481     {
482       if (hash_data->error != NULL)
483         {
484           /* if we already stopped due to an error, probably we're completely
485            * hosed for some reason. just return the first read error
486            * to the user.
487            */
488           g_clear_error (&error);
489           error = hash_data->error;
490         }
491
492       goto cleanup;
493     }
494
495   if (hash_data->error != NULL)
496     {
497       error = hash_data->error;
498       goto cleanup;
499     }
500
501   /* set the checksum in the request */
502   request = req_data->request;
503
504   DEBUG ("FT: got file hash %s", g_checksum_get_string (hash_data->checksum));
505
506   /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.ContentHash */
507   value = tp_g_value_slice_new (G_TYPE_STRING);
508   g_value_set_string (value, g_checksum_get_string (hash_data->checksum));
509   g_hash_table_insert (request,
510       EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentHash", value);
511
512 cleanup:
513   hash_data_free (hash_data);
514
515   if (error != NULL)
516     {
517       g_signal_emit (req_data->handler, signals[TRANSFER_ERROR], 0, error);
518       g_clear_error (&error);
519       request_data_free (req_data);
520     }
521   else
522     {
523       /* the request is complete now, push it to the dispatcher */
524       ft_handler_push_to_dispatcher (req_data);
525     }
526 }
527
528 static void
529 hash_job_async_read_cb (GObject *source,
530                         GAsyncResult *res,
531                         gpointer user_data)
532 {
533   HashingData *hash_data = user_data;
534   RequestData *req_data = hash_data->req_data;
535   gssize bytes_read;
536   GError *error = NULL;
537
538   DEBUG ("FT: reading a chunk for hashing.");
539
540   bytes_read = g_input_stream_read_finish (hash_data->stream, res, &error);
541   if (error != NULL)
542     {
543       hash_data->error = error;
544       hash_data->done_reading = TRUE;
545       goto out;
546     }
547
548   hash_data->total_read += bytes_read;
549
550   /* we now have the chunk */
551   if (bytes_read == 0)
552     {
553       hash_data->done_reading = TRUE;
554       goto out;
555     }
556   else
557     {
558       g_checksum_update (hash_data->checksum, hash_data->buffer, bytes_read);
559       g_signal_emit (req_data->handler, signals[HASHING_PROGRESS], 0,
560           (guint64) hash_data->total_read, (guint64) req_data->total_size);
561     }
562
563 out:
564   g_free (hash_data->buffer);
565   hash_data->buffer = NULL;
566
567   schedule_hash_chunk (hash_data);
568 }
569
570 static void
571 schedule_hash_chunk (HashingData *hash_data)
572 {
573   EmpathyFTHandlerPriv *priv;
574   RequestData *req_data = hash_data->req_data;
575
576   priv = GET_PRIV (req_data->handler);
577
578   if (hash_data->done_reading)
579     {
580       g_input_stream_close_async (hash_data->stream, G_PRIORITY_DEFAULT,
581           priv->cancellable, hash_job_async_close_stream_cb, hash_data);
582     }
583   else
584     {
585       if (hash_data->buffer == NULL)
586         hash_data->buffer = g_malloc0 (BUFFER_SIZE);
587
588       g_input_stream_read_async (hash_data->stream, hash_data->buffer,
589           BUFFER_SIZE, G_PRIORITY_DEFAULT, priv->cancellable,
590           hash_job_async_read_cb, hash_data);
591     }
592 }
593
594 static void
595 ft_handler_read_async_cb (GObject *source,
596                           GAsyncResult *res,
597                           gpointer user_data)
598 {
599   GFileInputStream *stream;
600   GError *error = NULL;
601   HashingData *hash_data;
602   GHashTable *request;
603   GValue *value;
604   RequestData *req_data = user_data;
605
606   DEBUG ("FT: GFile read async CB.");
607
608   stream = g_file_read_finish (req_data->gfile, res, &error);
609   if (error != NULL)
610     {
611       g_signal_emit (req_data->handler, signals[TRANSFER_ERROR], 0, error);
612
613       request_data_free (req_data);
614       g_clear_error (&error);
615
616       return;
617     }
618
619   hash_data = g_slice_new0 (HashingData);
620   hash_data->stream = G_INPUT_STREAM (stream);
621   hash_data->done_reading = FALSE;
622   hash_data->req_data = req_data;
623   /* FIXME: should look at the CM capabilities before setting the
624    * checksum type?
625    */
626   hash_data->checksum = g_checksum_new (G_CHECKSUM_MD5);
627
628   request = req_data->request;
629
630   /* org.freedesktop.Telepathy.Channel.Type.FileTransfer.ContentHashType */
631   value = tp_g_value_slice_new (G_TYPE_UINT);
632   g_value_set_uint (value, EMP_FILE_HASH_TYPE_MD5);
633   g_hash_table_insert (request,
634       EMP_IFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentHashType", value);
635
636   g_signal_emit (req_data->handler, signals[HASHING_STARTED], 0);
637
638   schedule_hash_chunk (hash_data);
639 }
640
641 static void
642 ft_handler_gfile_ready_cb (GObject *source,
643                            GAsyncResult *res,
644                            RequestData *req_data)
645 {
646   GFileInfo *info;
647   GError *error = NULL;
648   EmpathyFTHandlerPriv *priv = GET_PRIV (req_data->handler);
649
650   DEBUG ("FT: got GFileInfo.");
651
652   info = g_file_query_info_finish (req_data->gfile, res, &error);
653   if (error != NULL)
654     {
655       g_signal_emit (req_data->handler, signals[TRANSFER_ERROR], 0, error);
656
657       request_data_free (req_data);
658       g_clear_error (&error);
659
660       return;
661     }
662
663   ft_handler_populate_outgoing_request (req_data, info);
664
665   req_data->total_size = g_file_info_get_size (info);
666
667   /* now start hashing the file */
668   g_file_read_async (req_data->gfile, G_PRIORITY_DEFAULT,
669       priv->cancellable, ft_handler_read_async_cb, req_data);
670 }
671
672 static void
673 ft_handler_contact_ready_cb (EmpathyContact *contact,
674                              const GError *error,
675                              gpointer user_data,
676                              GObject *weak_object)  
677 {
678   RequestData *req_data = user_data;
679   EmpathyFTHandlerPriv *priv = GET_PRIV (req_data->handler);
680   GError *myerr = NULL;
681
682   g_assert (priv->contact != NULL);
683   g_assert (priv->gfile != NULL);
684
685   DEBUG ("FT: contact is ready.");
686
687   g_cancellable_set_error_if_cancelled (priv->cancellable, &myerr);
688
689   if (myerr == NULL)
690     {
691       if (error != NULL)
692         {
693           myerr = g_error_copy (error);
694         }
695       else
696         {
697           /* check if FT is allowed before firing up the I/O machinery */
698           if (!ft_handler_check_if_allowed (req_data->handler))
699             {
700               g_set_error_literal (&myerr, EMPATHY_FT_ERROR_QUARK,
701                   EMPATHY_FT_ERROR_NOT_SUPPORTED,
702                   _("File transfer not supported by remote contact"));
703             }
704         }
705     }
706
707   if (myerr != NULL)
708     {
709       g_signal_emit (req_data->handler, signals[TRANSFER_ERROR], 0, myerr);
710
711       request_data_free (req_data);
712       g_clear_error (&myerr);
713
714       return;
715     }
716
717   /* start collecting info about the file */
718   g_file_query_info_async (req_data->gfile,
719       G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME ","
720       G_FILE_ATTRIBUTE_STANDARD_SIZE ","
721       G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
722       G_FILE_ATTRIBUTE_TIME_MODIFIED,
723       G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT,
724       priv->cancellable, (GAsyncReadyCallback) ft_handler_gfile_ready_cb,
725       req_data);
726 }
727
728 /* public methods */
729
730 EmpathyFTHandler*
731 empathy_ft_handler_new_outgoing (EmpathyContact *contact,
732                                  GFile *source)
733 {
734   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
735   g_return_val_if_fail (G_IS_FILE (source), NULL);
736
737   return g_object_new (EMPATHY_TYPE_FT_HANDLER,
738       "contact", contact, "gfile", source, NULL);
739 }
740
741 EmpathyFTHandler *
742 empathy_ft_handler_new_incoming (EmpathyTpFile *tp_file,
743                                  GFile *destination)
744 {
745   g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file), NULL);
746   g_return_val_if_fail (G_IS_FILE (destination), NULL);
747
748   return g_object_new (EMPATHY_TYPE_FT_HANDLER,
749       "tp-file", tp_file, "gfile", destination, NULL);
750 }
751
752 void
753 empathy_ft_handler_start_transfer (EmpathyFTHandler *handler,
754                                    GCancellable *cancellable)
755 {
756   RequestData *data;
757   EmpathyFTHandlerPriv *priv;
758   GError *error = NULL;
759
760   g_return_if_fail (EMPATHY_IS_FT_HANDLER (handler));
761
762   priv = GET_PRIV (handler);
763   priv->cancellable = g_object_ref (cancellable);
764
765   if (priv->tpfile == NULL)
766     {
767       data = request_data_new (handler, priv->gfile);
768       empathy_contact_call_when_ready (priv->contact,
769           EMPATHY_CONTACT_READY_HANDLE,
770           ft_handler_contact_ready_cb, data, NULL, G_OBJECT (handler));
771     }
772   else
773     {
774       /* TODO: add support for resume. */
775       empathy_tp_file_accept (priv->tpfile, 0, priv->gfile, &error);
776     }
777 }