]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tp-file.c
Fixed copyright and author headers. (Jonny Lamb)
[empathy.git] / libempathy / empathy-tp-file.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2007-2008 Collabora Ltd.
4  * Copyright (C) 2007 Marco Barisione <marco@barisione.org>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  * Authors: Marco Barisione <marco@barisione.org>
21  *          Jonny Lamb <jonny.lamb@collabora.co.uk>
22  */
23
24 #include <config.h>
25
26 #include <string.h>
27 #include <unistd.h>
28 #include <sys/types.h>
29 #include <sys/socket.h>
30 #include <sys/un.h>
31
32 #include <glib/gi18n.h>
33
34 #include <gio/gio.h>
35 #include <gio/gunixinputstream.h>
36 #include <gio/gunixoutputstream.h>
37
38 #include <libtelepathy/tp-conn.h>
39 #include <libtelepathy/tp-helpers.h>
40 #include <libtelepathy/tp-props-iface.h>
41
42 #include <telepathy-glib/proxy-subclass.h>
43
44 #include "empathy-tp-file.h"
45 #include "empathy-contact-factory.h"
46 #include "empathy-marshal.h"
47 #include "empathy-time.h"
48 #include "empathy-utils.h"
49
50 #define DEBUG_FLAG EMPATHY_DEBUG_FT
51 #include "empathy-debug.h"
52
53 /**
54  * SECTION:empathy-tp-file
55  * @short_description: File channel
56  * @see_also: #EmpathyTpFile, #EmpathyContact, empathy_send_file()
57  * @include: libempthy/empathy-tp-file.h
58  *
59  * The #EmpathyTpFile object represents a Telepathy file channel.
60  */
61
62 /**
63  * EMPATHY_TP_FILE_UNKNOWN_SIZE:
64  *
65  * Value used for the "size" or "estimated-size" properties when the size of
66  * the transferred file is unknown.
67  */
68
69 static void empathy_tp_file_class_init (EmpathyTpFileClass *klass);
70 static void empathy_tp_file_init (EmpathyTpFile *tp_file);
71 static void tp_file_finalize (GObject *object);
72 static GObject *tp_file_constructor (GType type, guint n_props,
73     GObjectConstructParam *props);
74 static void tp_file_get_property (GObject *object, guint param_id,
75     GValue *value, GParamSpec *pspec);
76 static void tp_file_set_property (GObject *object, guint param_id,
77     const GValue *value, GParamSpec *pspec);
78 static void tp_file_destroy_cb (TpChannel *file_chan, EmpathyTpFile *tp_file);
79 static void tp_file_closed_cb (TpChannel *file_chan, EmpathyTpFile *tp_file,
80     GObject *weak_object);
81 static void tp_file_state_changed_cb (DBusGProxy *file_iface, guint state,
82     guint reason, EmpathyTpFile *tp_file);
83 static void tp_file_transferred_bytes_changed_cb (TpProxy *proxy,
84     guint64 count, EmpathyTpFile *tp_file, GObject *weak_object);
85 static void copy_stream (GInputStream *in, GOutputStream *out,
86     GCancellable *cancellable);
87
88 /* EmpathyTpFile object */
89
90 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
91            EMPATHY_TYPE_TP_FILE, EmpathyTpFilePriv))
92
93 typedef struct _EmpathyTpFilePriv  EmpathyTpFilePriv;
94
95 struct _EmpathyTpFilePriv {
96   EmpathyContactFactory *factory;
97   McAccount *account;
98   gchar *id;
99   MissionControl *mc;
100   TpChannel *channel;
101
102   EmpathyTpFile *cached_empathy_file;
103   EmpathyContact *contact;
104   GInputStream *in_stream;
105   GOutputStream *out_stream;
106   gboolean incoming;
107   gchar *filename;
108   EmpFileTransferState state;
109   EmpFileTransferStateChangeReason state_change_reason;
110   guint64 size;
111   guint64 transferred_bytes;
112   gint64 start_time;
113   gchar *unix_socket_path;
114   gchar *content_hash;
115   EmpFileHashType content_hash_type;
116   gchar *content_type;
117   gchar *description;
118   GCancellable *cancellable;
119 };
120
121 enum {
122   PROP_0,
123   PROP_ACCOUNT,
124   PROP_CHANNEL,
125   PROP_STATE,
126   PROP_INCOMING,
127   PROP_FILENAME,
128   PROP_SIZE,
129   PROP_CONTENT_TYPE,
130   PROP_TRANSFERRED_BYTES,
131   PROP_CONTENT_HASH_TYPE,
132   PROP_CONTENT_HASH,
133   PROP_IN_STREAM,
134 };
135
136 G_DEFINE_TYPE (EmpathyTpFile, empathy_tp_file, G_TYPE_OBJECT);
137
138 static void
139 empathy_tp_file_class_init (EmpathyTpFileClass *klass)
140 {
141   GObjectClass *object_class = G_OBJECT_CLASS (klass);
142
143   object_class->finalize = tp_file_finalize;
144   object_class->constructor = tp_file_constructor;
145   object_class->get_property = tp_file_get_property;
146   object_class->set_property = tp_file_set_property;
147
148   /* Construct-only properties */
149   g_object_class_install_property (object_class,
150       PROP_ACCOUNT,
151       g_param_spec_object ("account",
152           "channel Account",
153           "The account associated with the channel",
154           MC_TYPE_ACCOUNT,
155           G_PARAM_READWRITE |
156           G_PARAM_CONSTRUCT_ONLY));
157
158   g_object_class_install_property (object_class,
159       PROP_CHANNEL,
160       g_param_spec_object ("channel",
161           "telepathy channel",
162           "The file transfer channel",
163           TP_TYPE_CHANNEL,
164           G_PARAM_READWRITE |
165           G_PARAM_CONSTRUCT_ONLY));
166
167   g_object_class_install_property (object_class,
168       PROP_STATE,
169       g_param_spec_uint ("state",
170           "state of the transfer",
171           "The file transfer state",
172           0,
173           G_MAXUINT,
174           G_MAXUINT,
175           G_PARAM_READWRITE |
176           G_PARAM_CONSTRUCT));
177
178   g_object_class_install_property (object_class,
179       PROP_INCOMING,
180       g_param_spec_boolean ("incoming",
181           "incoming",
182           "Whether the transfer is incoming",
183           FALSE,
184           G_PARAM_READWRITE |
185           G_PARAM_CONSTRUCT));
186
187   g_object_class_install_property (object_class,
188       PROP_FILENAME,
189       g_param_spec_string ("filename",
190           "name of the transfer",
191           "The file transfer filename",
192           "",
193           G_PARAM_READWRITE));
194
195   g_object_class_install_property (object_class,
196       PROP_SIZE,
197       g_param_spec_uint64 ("size",
198           "size of the file",
199           "The file transfer size",
200           0,
201           G_MAXUINT64,
202           G_MAXUINT64,
203           G_PARAM_READWRITE));
204
205   g_object_class_install_property (object_class,
206       PROP_CONTENT_TYPE,
207       g_param_spec_string ("content-type",
208           "file transfer content-type",
209           "The file transfer content-type",
210           "",
211           G_PARAM_READWRITE));
212
213   g_object_class_install_property (object_class,
214       PROP_CONTENT_HASH_TYPE,
215       g_param_spec_uint ("content-hash-type",
216           "file transfer hash type",
217           "The type of the file transfer hash",
218           0,
219           G_MAXUINT,
220           0,
221           G_PARAM_READWRITE));
222
223   g_object_class_install_property (object_class,
224       PROP_CONTENT_HASH,
225       g_param_spec_string ("content-hash",
226           "file transfer hash",
227           "The hash of the transfer's contents",
228           "",
229           G_PARAM_READWRITE));
230
231   g_object_class_install_property (object_class,
232       PROP_TRANSFERRED_BYTES,
233       g_param_spec_uint64 ("transferred-bytes",
234           "bytes transferred",
235           "The number of bytes transferred",
236           0,
237           G_MAXUINT64,
238           0,
239           G_PARAM_READWRITE));
240
241   g_object_class_install_property (object_class,
242       PROP_IN_STREAM,
243       g_param_spec_object ("in-stream",
244           "transfer input stream",
245           "The input stream for file transfer",
246           G_TYPE_INPUT_STREAM,
247           G_PARAM_READWRITE));
248
249   g_type_class_add_private (object_class, sizeof (EmpathyTpFilePriv));
250 }
251
252 static void
253 empathy_tp_file_init (EmpathyTpFile *tp_file)
254 {
255 }
256
257 static void
258 tp_file_finalize (GObject *object)
259 {
260   EmpathyTpFilePriv *priv;
261   EmpathyTpFile *tp_file;
262
263   tp_file = EMPATHY_TP_FILE (object);
264   priv = GET_PRIV (tp_file);
265
266   if (priv->channel)
267     {
268       DEBUG ("Closing channel..");
269       g_signal_handlers_disconnect_by_func (priv->channel,
270           tp_file_destroy_cb, object);
271       tp_cli_channel_run_close (priv->channel, -1, NULL, NULL);
272       if (G_IS_OBJECT (priv->channel))
273         g_object_unref (priv->channel);
274     }
275
276   if (priv->factory)
277     {
278       g_object_unref (priv->factory);
279     }
280   if (priv->account)
281     {
282       g_object_unref (priv->account);
283     }
284   if (priv->mc)
285     {
286       g_object_unref (priv->mc);
287     }
288
289   g_free (priv->id);
290   g_free (priv->filename);
291   g_free (priv->unix_socket_path);
292   g_free (priv->description);
293   g_free (priv->content_hash);
294   g_free (priv->content_type);
295
296   if (priv->in_stream)
297     g_object_unref (priv->in_stream);
298
299   if (priv->out_stream)
300     g_object_unref (priv->out_stream);
301
302   if (priv->contact)
303     g_object_unref (priv->contact);
304
305   if (priv->cancellable)
306     g_object_unref (priv->cancellable);
307
308   G_OBJECT_CLASS (empathy_tp_file_parent_class)->finalize (object);
309 }
310
311 static GObject *
312 tp_file_constructor (GType type,
313                      guint n_props,
314                      GObjectConstructParam *props)
315 {
316   GObject *tp_file;
317   EmpathyTpFilePriv *priv;
318   GError *error = NULL;
319   GHashTable *properties;
320   TpHandle handle;
321
322   tp_file = G_OBJECT_CLASS (empathy_tp_file_parent_class)->constructor (type,
323       n_props, props);
324
325   priv = GET_PRIV (tp_file);
326
327   priv->factory = empathy_contact_factory_new ();
328   priv->mc = empathy_mission_control_new ();
329
330   tp_cli_channel_connect_to_closed (priv->channel,
331       (tp_cli_channel_signal_callback_closed) tp_file_closed_cb,
332       tp_file,
333       NULL, NULL, NULL);
334
335   emp_cli_channel_type_file_connect_to_file_transfer_state_changed (
336       TP_PROXY (priv->channel),
337       (emp_cli_channel_type_file_signal_callback_file_transfer_state_changed)
338           tp_file_state_changed_cb,
339       tp_file,
340       NULL, NULL, NULL);
341
342   emp_cli_channel_type_file_connect_to_transferred_bytes_changed (
343       TP_PROXY (priv->channel),
344       (emp_cli_channel_type_file_signal_callback_transferred_bytes_changed)
345           tp_file_transferred_bytes_changed_cb,
346       tp_file,
347       NULL, NULL, NULL);
348
349
350   handle = tp_channel_get_handle (priv->channel, NULL);
351   priv->contact = empathy_contact_factory_get_from_handle (priv->factory,
352       priv->account,
353       (guint) handle);
354
355   if (!tp_cli_dbus_properties_run_get_all (priv->channel,
356       -1, EMP_IFACE_CHANNEL_TYPE_FILE, &properties, &error, NULL))
357     {
358       DEBUG ("Failed to get properties: %s",
359           error ? error->message : "No error given");
360       g_clear_error (&error);
361       return NULL;
362     }
363
364   priv->size = g_value_get_uint64 (g_hash_table_lookup (properties, "Size"));
365
366   priv->state = g_value_get_uint (g_hash_table_lookup (properties, "State"));
367
368   /* Invalid reason, so empathy_file_get_state_change_reason() can give
369    * a warning if called for a not closed file transfer. */
370   priv->state_change_reason = -1;
371
372   priv->transferred_bytes = g_value_get_uint64 (g_hash_table_lookup (
373       properties, "TransferredBytes"));
374
375   priv->filename = g_value_dup_string (g_hash_table_lookup (properties,
376       "Filename"));
377
378   priv->content_hash = g_value_dup_string (g_hash_table_lookup (properties,
379       "ContentHash"));
380
381   priv->description = g_value_dup_string (g_hash_table_lookup (properties,
382       "Description"));
383
384   if (priv->state == EMP_FILE_TRANSFER_STATE_LOCAL_PENDING)
385     priv->incoming = TRUE;
386
387   g_hash_table_destroy (properties);
388
389   return tp_file;
390 }
391
392 static void
393 tp_file_get_property (GObject *object,
394                       guint param_id,
395                       GValue *value,
396                       GParamSpec *pspec)
397 {
398   EmpathyTpFilePriv *priv;
399   EmpathyTpFile *tp_file;
400
401   priv = GET_PRIV (object);
402   tp_file = EMPATHY_TP_FILE (object);
403
404   switch (param_id)
405     {
406       case PROP_ACCOUNT:
407         g_value_set_object (value, priv->account);
408         break;
409       case PROP_CHANNEL:
410         g_value_set_object (value, priv->channel);
411         break;
412       default:
413         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
414         break;
415     };
416 }
417
418 static void
419 tp_file_channel_set_dbus_property (gpointer proxy,
420                                    const gchar *property,
421                                    const GValue *value)
422 {
423         DEBUG ("Setting %s property", property);
424         tp_cli_dbus_properties_run_set (TP_PROXY (proxy),
425             -1,
426             EMP_IFACE_CHANNEL_TYPE_FILE,
427             property,
428             value,
429             NULL, NULL);
430         DEBUG ("done");
431 }
432
433
434 static void
435 tp_file_set_property (GObject *object,
436                       guint param_id,
437                       const GValue *value,
438                       GParamSpec *pspec)
439 {
440   EmpathyTpFilePriv *priv;
441
442   priv = GET_PRIV (object);
443
444   switch (param_id)
445     {
446       case PROP_ACCOUNT:
447         priv->account = g_object_ref (g_value_get_object (value));
448         break;
449       case PROP_CHANNEL:
450         priv->channel = g_object_ref (g_value_get_object (value));
451         break;
452       case PROP_STATE:
453         priv->state = g_value_get_uint (value);
454         break;
455       case PROP_INCOMING:
456         priv->incoming = g_value_get_boolean (value);
457         break;
458       case PROP_FILENAME:
459         g_free (priv->filename);
460         priv->filename = g_value_dup_string (value);
461         tp_file_channel_set_dbus_property (priv->channel, "Filename", value);
462         break;
463       case PROP_SIZE:
464         priv->size = g_value_get_uint64 (value);
465         tp_file_channel_set_dbus_property (priv->channel, "Size", value);
466         break;
467       case PROP_CONTENT_TYPE:
468         tp_file_channel_set_dbus_property (priv->channel, "ContentType", value);
469         g_free (priv->content_type);
470         priv->content_type = g_value_dup_string (value);
471         break;
472       case PROP_CONTENT_HASH:
473         tp_file_channel_set_dbus_property (priv->channel, "ContentHash", value);
474         g_free (priv->content_hash);
475         priv->content_hash = g_value_dup_string (value);
476         break;
477       case PROP_IN_STREAM:
478         if (priv->in_stream)
479           g_object_unref (priv->in_stream);
480         priv->in_stream = g_object_ref (g_value_get_object (value));
481         break;
482       default:
483         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
484         break;
485     };
486 }
487
488 /**
489  * empathy_tp_file_new:
490  * @account: the #McAccount for the channel
491  * @channel: a Telepathy channel
492  *
493  * Creates a new #EmpathyTpFile wrapping @channel.
494  *
495  * Returns: a new #EmpathyTpFile
496  */
497 EmpathyTpFile *
498 empathy_tp_file_new (McAccount *account,
499                   TpChannel *channel)
500 {
501   return g_object_new (EMPATHY_TYPE_TP_FILE,
502       "account", account,
503       "channel", channel,
504       NULL);
505 }
506
507 /**
508  * empathy_tp_file_get_id:
509  * @tp_file: an #EmpathyTpFile
510  *
511  * Returns the ID of @tp_file.
512  *
513  * Returns: the ID
514  */
515 const gchar *
516 empathy_tp_file_get_id (EmpathyTpFile *tp_file)
517 {
518   EmpathyTpFilePriv *priv;
519
520   g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file), NULL);
521
522   priv = GET_PRIV (tp_file);
523
524   return priv->id;
525 }
526
527 /**
528  * empathy_tp_file_get_channel
529  * @tp_file: an #EmpathyTpFile
530  *
531  * Returns the Telepathy file transfer channel
532  *
533  * Returns: the #TpChannel
534  */
535 TpChannel *
536 empathy_tp_file_get_channel (EmpathyTpFile *tp_file)
537 {
538   EmpathyTpFilePriv *priv;
539
540   g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file), NULL);
541
542   priv = GET_PRIV (tp_file);
543
544   return priv->channel;
545 }
546
547 static void
548 tp_file_destroy_cb (TpChannel *file_channel,
549                     EmpathyTpFile *tp_file)
550 {
551   EmpathyTpFilePriv *priv;
552
553   priv = GET_PRIV (tp_file);
554
555   DEBUG ("Channel Closed or CM crashed");
556
557   g_object_unref (priv->channel);
558   priv->channel = NULL;
559 }
560
561 static void
562 tp_file_closed_cb (TpChannel *file_channel,
563                    EmpathyTpFile *tp_file,
564                    GObject *weak_object)
565 {
566   EmpathyTpFilePriv *priv;
567
568   priv = GET_PRIV (tp_file);
569
570   /* The channel is closed, do just like if the proxy was destroyed */
571   g_signal_handlers_disconnect_by_func (priv->channel,
572       tp_file_destroy_cb,
573       tp_file);
574   tp_file_destroy_cb (file_channel, tp_file);
575 }
576
577 static gint64
578 get_time_msec (void)
579 {
580   GTimeVal tv;
581
582   g_get_current_time (&tv);
583   return ((gint64) tv.tv_sec) * 1000 + tv.tv_usec / 1000;
584 }
585
586 static gint
587 _get_local_socket (EmpathyTpFile *tp_file)
588 {
589   gint fd;
590   size_t path_len;
591   struct sockaddr_un addr;
592   EmpathyTpFilePriv *priv;
593
594   priv = GET_PRIV (tp_file);
595
596   if (G_STR_EMPTY (priv->unix_socket_path))
597     return -1;
598
599   fd = socket (PF_UNIX, SOCK_STREAM, 0);
600   if (fd < 0)
601     return -1;
602
603   memset (&addr, 0, sizeof (addr));
604   addr.sun_family = AF_UNIX;
605   path_len = strlen (priv->unix_socket_path);
606   strncpy (addr.sun_path, priv->unix_socket_path, path_len);
607
608   if (connect (fd, (struct sockaddr*) &addr,
609       sizeof (addr)) < 0)
610     {
611       close (fd);
612       return -1;
613     }
614
615   return fd;
616 }
617
618 /**
619  * empathy_tp_file_accept:
620  * @tp_file: an #EmpathyTpFile
621  *
622  * Accepts a file transfer that's in the "local pending" state (i.e.
623  * EMP_FILE_TRANSFER_STATE_LOCAL_PENDING).
624  */
625 void
626 empathy_tp_file_accept (EmpathyTpFile *tp_file,
627                         guint64 offset)
628 {
629   EmpathyTpFilePriv *priv;
630   GValue *address;
631   GValue nothing = { 0 };
632   GError *error = NULL;
633
634   g_return_if_fail (EMPATHY_IS_TP_FILE (tp_file));
635
636   priv = GET_PRIV (tp_file);
637
638   g_return_if_fail (priv->out_stream != NULL);
639
640   DEBUG ("Accepting file: filename=%s", priv->filename);
641
642   g_value_init (&nothing, G_TYPE_STRING);
643   g_value_set_string (&nothing, "");
644
645   if (!emp_cli_channel_type_file_run_accept_file (TP_PROXY (priv->channel),
646       -1, TP_SOCKET_ADDRESS_TYPE_UNIX, TP_SOCKET_ACCESS_CONTROL_LOCALHOST,
647       &nothing, offset, &address, &error, NULL))
648     {
649       DEBUG ("Accept error: %s",
650           error ? error->message : "No message given");
651       g_clear_error (&error);
652       return;
653     }
654
655   if (priv->unix_socket_path)
656     g_free (priv->unix_socket_path);
657
658   priv->unix_socket_path = g_value_dup_string (address);
659   g_value_unset (address);
660
661   DEBUG ("Got unix socket path: %s", priv->unix_socket_path);
662 }
663
664 void
665 empathy_tp_file_offer (EmpathyTpFile *tp_file)
666 {
667   EmpathyTpFilePriv *priv;
668   GValue *address;
669   GError *error = NULL;
670   GValue nothing = { 0 };
671
672   g_return_if_fail (EMPATHY_IS_TP_FILE (tp_file));
673
674   priv = GET_PRIV (tp_file);
675
676   g_value_init (&nothing, G_TYPE_STRING);
677   g_value_set_string (&nothing, "");
678
679   if (!emp_cli_channel_type_file_run_offer_file (TP_PROXY (priv->channel),
680       -1, TP_SOCKET_ADDRESS_TYPE_UNIX, TP_SOCKET_ACCESS_CONTROL_LOCALHOST,
681       &nothing, &address, &error, NULL))
682     {
683       DEBUG ("OfferFile error: %s",
684           error ? error->message : "No message given");
685       g_clear_error (&error);
686       return;
687     }
688
689   if (priv->unix_socket_path)
690     g_free (priv->unix_socket_path);
691
692   priv->unix_socket_path = g_value_dup_string (address);
693   g_value_unset (address);
694
695   DEBUG ("Got unix socket path: %s", priv->unix_socket_path);
696 }
697
698 static void
699 receive_tp_file (EmpathyTpFile *tp_file)
700 {
701   EmpathyTpFilePriv *priv;
702   GInputStream *socket_stream;
703   gint socket_fd;
704
705   priv = GET_PRIV (tp_file);
706
707   socket_fd = _get_local_socket (tp_file);
708
709   if (socket_fd < 0)
710     return;
711
712   socket_stream = g_unix_input_stream_new (socket_fd, TRUE);
713
714   priv->cancellable = g_cancellable_new ();
715
716   copy_stream (socket_stream, priv->out_stream, priv->cancellable);
717
718   g_object_unref (socket_stream);
719 }
720
721 static void
722 send_tp_file (EmpathyTpFile *tp_file)
723 {
724   gint socket_fd;
725   GOutputStream *socket_stream;
726   EmpathyTpFilePriv *priv;
727
728   priv = GET_PRIV (tp_file);
729
730   DEBUG ("Sending file content: filename=%s",
731            priv->filename);
732
733   g_return_if_fail (priv->in_stream);
734
735   socket_fd = _get_local_socket (tp_file);
736   if (socket_fd < 0)
737     {
738       DEBUG ("failed to get local socket fd");
739       return;
740     }
741   DEBUG ("got local socket fd");
742   socket_stream = g_unix_output_stream_new (socket_fd, TRUE);
743
744   priv->cancellable = g_cancellable_new ();
745
746   copy_stream (priv->in_stream, socket_stream, priv->cancellable);
747
748   g_object_unref (socket_stream);
749 }
750
751 static void
752 tp_file_state_changed_cb (DBusGProxy *tp_file_iface,
753                           EmpFileTransferState state,
754                           EmpFileTransferStateChangeReason reason,
755                           EmpathyTpFile *tp_file)
756 {
757   EmpathyTpFilePriv *priv;
758
759   priv = GET_PRIV (tp_file);
760
761   DEBUG ("File transfer state changed: filename=%s, "
762       "old state=%u, state=%u, reason=%u",
763       priv->filename, priv->state, state, reason);
764
765   if (state == EMP_FILE_TRANSFER_STATE_OPEN)
766     priv->start_time = get_time_msec ();
767
768   DEBUG ("state = %u, incoming = %s, in_stream = %s, out_stream = %s",
769       state, priv->incoming ? "yes" : "no",
770       priv->in_stream ? "present" : "not present",
771       priv->out_stream ? "present" : "not present");
772
773   if (state == EMP_FILE_TRANSFER_STATE_OPEN && !priv->incoming &&
774       priv->in_stream)
775     send_tp_file (tp_file);
776   else if (state == EMP_FILE_TRANSFER_STATE_OPEN && priv->incoming &&
777       priv->out_stream)
778     receive_tp_file (tp_file);
779
780   priv->state = state;
781   priv->state_change_reason = reason;
782
783   g_object_notify (G_OBJECT (tp_file), "state");
784 }
785
786 static void
787 tp_file_transferred_bytes_changed_cb (TpProxy *proxy,
788                                       guint64 count,
789                                       EmpathyTpFile *tp_file,
790                                       GObject *weak_object)
791 {
792   EmpathyTpFilePriv *priv;
793
794   priv = GET_PRIV (tp_file);
795
796   if (priv->transferred_bytes == count)
797     return;
798
799   priv->transferred_bytes = count;
800
801   g_object_notify (G_OBJECT (tp_file), "transferred-bytes");
802 }
803
804 EmpathyContact *
805 empathy_tp_file_get_contact (EmpathyTpFile *tp_file)
806 {
807   EmpathyTpFilePriv *priv;
808
809   priv = GET_PRIV (tp_file);
810
811   return priv->contact;
812 }
813
814 GInputStream *
815 empathy_tp_file_get_input_stream (EmpathyTpFile *tp_file)
816 {
817   EmpathyTpFilePriv *priv;
818
819   priv = GET_PRIV (tp_file);
820
821   return priv->in_stream;
822 }
823
824 GOutputStream *
825 empathy_tp_file_get_output_stream (EmpathyTpFile *tp_file)
826 {
827   EmpathyTpFilePriv *priv;
828
829   priv = GET_PRIV (tp_file);
830
831   return priv->out_stream;
832 }
833
834 const gchar *
835 empathy_tp_file_get_filename (EmpathyTpFile *tp_file)
836 {
837   EmpathyTpFilePriv *priv;
838
839   priv = GET_PRIV (tp_file);
840
841   return priv->filename;
842 }
843
844 gboolean
845 empathy_tp_file_get_incoming (EmpathyTpFile *tp_file)
846 {
847   EmpathyTpFilePriv *priv;
848
849   priv = GET_PRIV (tp_file);
850
851   return priv->incoming;
852 }
853
854 EmpFileTransferState
855 empathy_tp_file_get_state (EmpathyTpFile *tp_file)
856 {
857   EmpathyTpFilePriv *priv;
858
859   priv = GET_PRIV (tp_file);
860
861   return priv->state;
862 }
863
864 EmpFileTransferStateChangeReason
865 empathy_tp_file_get_state_change_reason (EmpathyTpFile *tp_file)
866 {
867   EmpathyTpFilePriv *priv;
868
869   priv = GET_PRIV (tp_file);
870
871   g_return_val_if_fail (priv->state_change_reason >= 0,
872       EMP_FILE_TRANSFER_STATE_CHANGE_REASON_NONE);
873
874   return priv->state_change_reason;
875 }
876
877 guint64
878 empathy_tp_file_get_size (EmpathyTpFile *tp_file)
879 {
880   EmpathyTpFilePriv *priv;
881
882   priv = GET_PRIV (tp_file);
883
884   return priv->size;
885 }
886
887 guint64
888 empathy_tp_file_get_transferred_bytes (EmpathyTpFile *tp_file)
889 {
890   EmpathyTpFilePriv *priv;
891
892   priv = GET_PRIV (tp_file);
893
894   return priv->transferred_bytes;
895 }
896
897 gint
898 empathy_tp_file_get_remaining_time (EmpathyTpFile *tp_file)
899 {
900   EmpathyTpFilePriv *priv;
901   gint64 curr_time, elapsed_time;
902   gdouble time_per_byte;
903   gdouble remaining_time;
904
905   priv = GET_PRIV (tp_file);
906
907   if (priv->size == EMPATHY_TP_FILE_UNKNOWN_SIZE)
908     return -1;
909
910   if (priv->transferred_bytes == priv->size)
911     return 0;
912
913   curr_time = get_time_msec ();
914   elapsed_time = curr_time - priv->start_time;
915   time_per_byte = (gdouble) elapsed_time / (gdouble) priv->transferred_bytes;
916   remaining_time = (time_per_byte * (priv->size - priv->transferred_bytes)) / 1000;
917
918   return (gint) (remaining_time + 0.5);
919 }
920
921 void
922 empathy_tp_file_cancel (EmpathyTpFile *tp_file)
923 {
924   EmpathyTpFilePriv *priv;
925
926   priv = GET_PRIV (tp_file);
927
928   tp_cli_channel_run_close (priv->channel, -1, NULL, NULL);
929
930   g_cancellable_cancel (priv->cancellable);
931 }
932
933 void
934 empathy_tp_file_set_input_stream (EmpathyTpFile *tp_file,
935                                   GInputStream *in_stream)
936 {
937   EmpathyTpFilePriv *priv;
938
939   priv = GET_PRIV (tp_file);
940
941   if (priv->in_stream == in_stream)
942     return;
943
944   if (priv->incoming)
945     g_warning ("Setting an input stream for incoming file "
946          "transfers is useless");
947
948   if (priv->in_stream)
949     g_object_unref (priv->in_stream);
950
951   if (in_stream)
952     g_object_ref (in_stream);
953
954   priv->in_stream = in_stream;
955
956   g_object_notify (G_OBJECT (tp_file), "in-stream");
957 }
958
959 void
960 empathy_tp_file_set_output_stream (EmpathyTpFile *tp_file,
961                                    GOutputStream *out_stream)
962 {
963   EmpathyTpFilePriv *priv;
964
965   priv = GET_PRIV (tp_file);
966
967   if (priv->out_stream == out_stream)
968     return;
969
970   if (!priv->incoming)
971     g_warning ("Setting an output stream for outgoing file "
972          "transfers is useless");
973
974   if (priv->out_stream)
975     g_object_unref (priv->out_stream);
976
977   if (out_stream)
978     g_object_ref (out_stream);
979
980   priv->out_stream = out_stream;
981 }
982
983 void
984 empathy_tp_file_set_filename (EmpathyTpFile *tp_file,
985                               const gchar *filename)
986 {
987   EmpathyTpFilePriv *priv;
988
989   priv = GET_PRIV (tp_file);
990
991   g_return_if_fail (filename != NULL);
992
993   if (priv->filename && strcmp (filename, priv->filename) == 0)
994     return;
995
996   g_free (priv->filename);
997   priv->filename = g_strdup (filename);
998
999   g_object_notify (G_OBJECT (tp_file), "filename");
1000 }
1001
1002 /* Functions to copy the content of a GInputStream to a GOutputStream */
1003
1004 #define N_BUFFERS 2
1005 #define BUFFER_SIZE 4096
1006
1007 typedef struct {
1008   GInputStream *in;
1009   GOutputStream *out;
1010   GCancellable  *cancellable;
1011   char *buff[N_BUFFERS]; /* the temporary buffers */
1012   gsize count[N_BUFFERS]; /* how many bytes are used in the buffers */
1013   gboolean is_full[N_BUFFERS]; /* whether the buffers contain data */
1014   gint curr_read; /* index of the buffer used for reading */
1015   gint curr_write; /* index of the buffer used for writing */
1016   gboolean is_reading; /* we are reading */
1017   gboolean is_writing; /* we are writing */
1018   guint n_closed; /* number of streams that have been closed */
1019 } CopyData;
1020
1021 static void schedule_next (CopyData *copy);
1022
1023 static void
1024 free_copy_data_if_closed (CopyData *copy)
1025 {
1026   gint i;
1027
1028   /* Free the data only if both the input and output streams have
1029    * been closed. */
1030   copy->n_closed++;
1031   if (copy->n_closed < 2)
1032     return;
1033
1034   if (copy->in != NULL)
1035     g_object_unref (copy->in);
1036
1037   if (copy->out != NULL)
1038     g_object_unref (copy->out);
1039
1040   for (i = 0; i < N_BUFFERS; i++)
1041     g_free (copy->buff[i]);
1042
1043   g_object_unref (copy->cancellable);
1044   g_free (copy);
1045 }
1046
1047 static void
1048 io_error (CopyData *copy,
1049           GError *error)
1050 {
1051   g_cancellable_cancel (copy->cancellable);
1052
1053   if (error == NULL)
1054     g_warning ("I/O error");
1055   else if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED)
1056     ; /* Ignore cancellations */
1057   else
1058     g_warning ("I/O error: %d: %s\n", error->code, error->message);
1059
1060   if (copy->in != NULL)
1061     g_input_stream_close (copy->in, NULL, NULL);
1062
1063   if (copy->out != NULL)
1064     g_output_stream_close (copy->out, NULL, NULL);
1065
1066   free_copy_data_if_closed (copy);
1067 }
1068
1069 static void
1070 close_done (GObject *source_object,
1071             GAsyncResult *res,
1072             gpointer user_data)
1073 {
1074   CopyData *copy = user_data;
1075
1076   g_object_unref (source_object);
1077   free_copy_data_if_closed (copy);
1078 }
1079
1080 static void
1081 write_done_cb (GObject *source_object,
1082                GAsyncResult *res,
1083                gpointer user_data)
1084 {
1085   CopyData *copy = user_data;
1086   gssize count_write;
1087   GError *error = NULL;
1088
1089   count_write = g_output_stream_write_finish (copy->out, res, &error);
1090
1091   if (count_write <= 0)
1092     {
1093       io_error (copy, error);
1094       g_error_free (error);
1095       return;
1096     }
1097
1098   copy->is_full[copy->curr_write] = FALSE;
1099   copy->curr_write = (copy->curr_write + 1) % N_BUFFERS;
1100   copy->is_writing = FALSE;
1101
1102   schedule_next (copy);
1103 }
1104
1105 static void
1106 read_done_cb (GObject *source_object,
1107               GAsyncResult *res,
1108               gpointer user_data)
1109 {
1110   CopyData *copy = user_data;
1111   gssize count_read;
1112   GError *error = NULL;
1113
1114   count_read = g_input_stream_read_finish (copy->in, res, &error);
1115
1116   if (count_read == 0)
1117     {
1118       g_input_stream_close_async (copy->in, 0, copy->cancellable,
1119           close_done, copy);
1120       copy->in = NULL;
1121     }
1122   else if (count_read < 0)
1123     {
1124       io_error (copy, error);
1125       g_error_free (error);
1126       return;
1127     }
1128
1129   copy->count[copy->curr_read] = count_read;
1130   copy->is_full[copy->curr_read] = TRUE;
1131   copy->curr_read = (copy->curr_read + 1) % N_BUFFERS;
1132   copy->is_reading = FALSE;
1133
1134   schedule_next (copy);
1135 }
1136
1137 static void
1138 schedule_next (CopyData *copy)
1139 {
1140   if (copy->in != NULL &&
1141       !copy->is_reading &&
1142       !copy->is_full[copy->curr_read])
1143     {
1144       /* We are not reading and the current buffer is empty, so
1145        * start an async read. */
1146       copy->is_reading = TRUE;
1147       g_input_stream_read_async (copy->in,
1148           copy->buff[copy->curr_read],
1149           BUFFER_SIZE, 0, copy->cancellable,
1150           read_done_cb, copy);
1151     }
1152
1153   if (!copy->is_writing &&
1154       copy->is_full[copy->curr_write])
1155     {
1156       if (copy->count[copy->curr_write] == 0)
1157         {
1158           /* The last read on the buffer read 0 bytes, this
1159            * means that we got an EOF, so we can close
1160            * the output channel. */
1161           g_output_stream_close_async (copy->out, 0,
1162               copy->cancellable,
1163               close_done, copy);
1164       copy->out = NULL;
1165         }
1166       else
1167         {
1168           /* We are not writing and the current buffer contains
1169            * data, so start an async write. */
1170           copy->is_writing = TRUE;
1171           g_output_stream_write_async (copy->out,
1172               copy->buff[copy->curr_write],
1173               copy->count[copy->curr_write],
1174               0, copy->cancellable,
1175               write_done_cb, copy);
1176         }
1177     }
1178 }
1179
1180 static void
1181 copy_stream (GInputStream *in,
1182              GOutputStream *out,
1183              GCancellable *cancellable)
1184 {
1185   CopyData *copy;
1186   gint i;
1187
1188   g_return_if_fail (in != NULL);
1189   g_return_if_fail (out != NULL);
1190
1191   copy = g_new0 (CopyData, 1);
1192   copy->in = g_object_ref (in);
1193   copy->out = g_object_ref (out);
1194
1195   if (cancellable != NULL)
1196     copy->cancellable = g_object_ref (cancellable);
1197   else
1198     copy->cancellable = g_cancellable_new ();
1199
1200   for (i = 0; i < N_BUFFERS; i++)
1201     copy->buff[i] = g_malloc (BUFFER_SIZE);
1202
1203   schedule_next (copy);
1204 }