]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tp-file.c
Don't set NM presence state on connect when it was unset
[empathy.git] / libempathy / empathy-tp-file.c
1 /*
2  * Copyright (C) 2007-2008 Collabora Ltd.
3  * Copyright (C) 2007 Marco Barisione <marco@barisione.org>
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  * Authors: Marco Barisione <marco@barisione.org>
20  *          Jonny Lamb <jonny.lamb@collabora.co.uk>
21  */
22
23 #include <config.h>
24
25 #include <string.h>
26 #include <unistd.h>
27 #include <sys/types.h>
28 #include <sys/socket.h>
29 #include <sys/un.h>
30
31 #include <glib/gi18n-lib.h>
32
33 #include <gio/gio.h>
34 #include <gio/gunixinputstream.h>
35 #include <gio/gunixoutputstream.h>
36
37 #include <telepathy-glib/proxy-subclass.h>
38 #include <telepathy-glib/util.h>
39
40 #include "empathy-tp-file.h"
41 #include "empathy-tp-contact-factory.h"
42 #include "empathy-marshal.h"
43 #include "empathy-time.h"
44 #include "empathy-utils.h"
45
46 #include "extensions/extensions.h"
47
48 #define DEBUG_FLAG EMPATHY_DEBUG_FT
49 #include "empathy-debug.h"
50
51 /**
52  * SECTION:empathy-tp-file
53  * @title: EmpathyTpFile
54  * @short_description: Object which represents a Telepathy file channel
55  * @include: libempathy/empathy-tp-file.h
56  *
57  * #EmpathyTpFile is an object which represents a Telepathy file channel.
58  */
59
60 /**
61  * EmpathyTpFile:
62  * @parent: parent object
63  *
64  * Object which represents a Telepathy file channel.
65  */
66
67 /**
68  * EMPATHY_TP_FILE_UNKNOWN_SIZE:
69  *
70  * Value used for the "size" or "estimated-size" properties when the size of
71  * the transferred file is unknown.
72  */
73
74 /* Functions to copy the content of a GInputStream to a GOutputStream */
75
76 #define N_BUFFERS 2
77 #define BUFFER_SIZE 4096
78 #define STALLED_TIMEOUT 5
79
80 typedef struct {
81   GInputStream *in;
82   GOutputStream *out;
83   GCancellable  *cancellable;
84   char *buff[N_BUFFERS]; /* the temporary buffers */
85   gsize count[N_BUFFERS]; /* how many bytes are used in the buffers */
86   gboolean is_full[N_BUFFERS]; /* whether the buffers contain data */
87   gint curr_read; /* index of the buffer used for reading */
88   gint curr_write; /* index of the buffer used for writing */
89   gboolean is_reading; /* we are reading */
90   gboolean is_writing; /* we are writing */
91   guint n_closed; /* number of streams that have been closed */
92   gint ref_count;
93 } CopyData;
94
95 static void schedule_next (CopyData *copy);
96
97 static void
98 copy_data_unref (CopyData *copy)
99 {
100   if (--copy->ref_count == 0)
101     {
102       gint i;
103
104       /* Free the data only if both the input and output streams have
105        * been closed. */
106       copy->n_closed++;
107       if (copy->n_closed < 2)
108         return;
109
110       if (copy->in != NULL)
111         g_object_unref (copy->in);
112
113       if (copy->out != NULL)
114         g_object_unref (copy->out);
115
116       for (i = 0; i < N_BUFFERS; i++)
117         g_free (copy->buff[i]);
118
119       g_object_unref (copy->cancellable);
120       g_free (copy);
121     }
122 }
123
124 static void
125 io_error (CopyData *copy,
126           GError *error)
127 {
128   g_cancellable_cancel (copy->cancellable);
129
130   if (error == NULL)
131     g_warning ("I/O error");
132   else if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED)
133     ; /* Ignore cancellations */
134   else
135     g_warning ("I/O error: %d: %s\n", error->code, error->message);
136
137   if (copy->in != NULL)
138     g_input_stream_close (copy->in, NULL, NULL);
139
140   if (copy->out != NULL)
141     g_output_stream_close (copy->out, NULL, NULL);
142
143   copy_data_unref (copy);
144 }
145
146 static void
147 close_done (GObject *source_object,
148             GAsyncResult *res,
149             gpointer user_data)
150 {
151   CopyData *copy = user_data;
152
153   g_object_unref (source_object);
154   copy_data_unref (copy);
155 }
156
157 static void
158 write_done_cb (GObject *source_object,
159                GAsyncResult *res,
160                gpointer user_data)
161 {
162   CopyData *copy = user_data;
163   gssize count_write;
164   GError *error = NULL;
165
166   count_write = g_output_stream_write_finish (copy->out, res, &error);
167
168   if (count_write <= 0)
169     {
170       io_error (copy, error);
171       g_error_free (error);
172       return;
173     }
174
175   copy->is_full[copy->curr_write] = FALSE;
176   copy->curr_write = (copy->curr_write + 1) % N_BUFFERS;
177   copy->is_writing = FALSE;
178
179   schedule_next (copy);
180 }
181
182 static void
183 read_done_cb (GObject *source_object,
184               GAsyncResult *res,
185               gpointer user_data)
186 {
187   CopyData *copy = user_data;
188   gssize count_read;
189   GError *error = NULL;
190
191   count_read = g_input_stream_read_finish (copy->in, res, &error);
192
193   if (count_read == 0)
194     {
195       g_input_stream_close_async (copy->in, 0, copy->cancellable,
196           close_done, copy);
197       copy->in = NULL;
198     }
199   else if (count_read < 0)
200     {
201       io_error (copy, error);
202       g_error_free (error);
203       return;
204     }
205
206   copy->count[copy->curr_read] = count_read;
207   copy->is_full[copy->curr_read] = TRUE;
208   copy->curr_read = (copy->curr_read + 1) % N_BUFFERS;
209   copy->is_reading = FALSE;
210
211   schedule_next (copy);
212 }
213
214 static void
215 schedule_next (CopyData *copy)
216 {
217   if (copy->in != NULL &&
218       !copy->is_reading &&
219       !copy->is_full[copy->curr_read])
220     {
221       /* We are not reading and the current buffer is empty, so
222        * start an async read. */
223       copy->is_reading = TRUE;
224       g_input_stream_read_async (copy->in,
225           copy->buff[copy->curr_read],
226           BUFFER_SIZE, 0, copy->cancellable,
227           read_done_cb, copy);
228     }
229
230   if (!copy->is_writing &&
231       copy->is_full[copy->curr_write])
232     {
233       if (copy->count[copy->curr_write] == 0)
234         {
235           /* The last read on the buffer read 0 bytes, this
236            * means that we got an EOF, so we can close
237            * the output channel. */
238           g_output_stream_close_async (copy->out, 0,
239               copy->cancellable,
240               close_done, copy);
241       copy->out = NULL;
242         }
243       else
244         {
245           /* We are not writing and the current buffer contains
246            * data, so start an async write. */
247           copy->is_writing = TRUE;
248           g_output_stream_write_async (copy->out,
249               copy->buff[copy->curr_write],
250               copy->count[copy->curr_write],
251               0, copy->cancellable,
252               write_done_cb, copy);
253         }
254     }
255 }
256
257 static void
258 copy_stream (GInputStream *in,
259              GOutputStream *out,
260              GCancellable *cancellable)
261 {
262   CopyData *copy;
263   gint i;
264
265   g_return_if_fail (in != NULL);
266   g_return_if_fail (out != NULL);
267
268   copy = g_new0 (CopyData, 1);
269   copy->in = g_object_ref (in);
270   copy->out = g_object_ref (out);
271   copy->ref_count = 1;
272
273   if (cancellable != NULL)
274     copy->cancellable = g_object_ref (cancellable);
275   else
276     copy->cancellable = g_cancellable_new ();
277
278   for (i = 0; i < N_BUFFERS; i++)
279     copy->buff[i] = g_malloc (BUFFER_SIZE);
280
281   schedule_next (copy);
282 }
283
284 /* EmpathyTpFile object */
285
286 struct _EmpathyTpFilePriv {
287   EmpathyTpContactFactory *factory;
288   MissionControl *mc;
289   TpChannel *channel;
290   gboolean ready;
291
292   EmpathyContact *contact;
293   GInputStream *in_stream;
294   GOutputStream *out_stream;
295
296   /* org.freedesktop.Telepathy.Channel.Type.FileTransfer D-Bus properties */
297   TpFileTransferState state;
298   gchar *content_type;
299   gchar *filename;
300   guint64 size;
301   TpFileHashType content_hash_type;
302   gchar *content_hash;
303   gchar *description;
304   guint64 transferred_bytes;
305
306   gboolean incoming;
307   TpFileTransferStateChangeReason state_change_reason;
308   time_t last_update_time;
309   guint64 last_update_transferred_bytes;
310   gdouble speed;
311   gint remaining_time;
312   guint stalled_id;
313   GValue *socket_address;
314   GCancellable *cancellable;
315 };
316
317 enum {
318   PROP_0,
319   PROP_CHANNEL,
320   PROP_STATE,
321   PROP_INCOMING,
322   PROP_READY,
323   PROP_FILENAME,
324   PROP_SIZE,
325   PROP_CONTENT_TYPE,
326   PROP_TRANSFERRED_BYTES,
327   PROP_CONTENT_HASH_TYPE,
328   PROP_CONTENT_HASH,
329 };
330
331 enum {
332         REFRESH,
333         LAST_SIGNAL
334 };
335
336 static guint signals[LAST_SIGNAL];
337
338 G_DEFINE_TYPE (EmpathyTpFile, empathy_tp_file, G_TYPE_OBJECT);
339
340 static void
341 empathy_tp_file_init (EmpathyTpFile *tp_file)
342 {
343   EmpathyTpFilePriv *priv;
344
345   priv = G_TYPE_INSTANCE_GET_PRIVATE ((tp_file),
346       EMPATHY_TYPE_TP_FILE, EmpathyTpFilePriv);
347
348   tp_file->priv = priv;
349 }
350
351 static void
352 tp_file_invalidated_cb (TpProxy       *proxy,
353                         guint          domain,
354                         gint           code,
355                         gchar         *message,
356                         EmpathyTpFile *tp_file)
357 {
358   DEBUG ("Channel invalidated: %s", message);
359
360   if (tp_file->priv->state != TP_FILE_TRANSFER_STATE_COMPLETED &&
361       tp_file->priv->state != TP_FILE_TRANSFER_STATE_CANCELLED)
362     {
363       /* The channel is not in a finished state, an error occured */
364       tp_file->priv->state = TP_FILE_TRANSFER_STATE_CANCELLED;
365       tp_file->priv->state_change_reason =
366           TP_FILE_TRANSFER_STATE_CHANGE_REASON_LOCAL_ERROR;
367       g_object_notify (G_OBJECT (tp_file), "state");
368     }
369 }
370
371 static void
372 tp_file_finalize (GObject *object)
373 {
374   EmpathyTpFile *tp_file = EMPATHY_TP_FILE (object);
375
376   if (tp_file->priv->channel)
377     {
378       g_signal_handlers_disconnect_by_func (tp_file->priv->channel,
379           tp_file_invalidated_cb, object);
380       g_object_unref (tp_file->priv->channel);
381       tp_file->priv->channel = NULL;
382     }
383
384   if (tp_file->priv->factory)
385     {
386       g_object_unref (tp_file->priv->factory);
387     }
388   if (tp_file->priv->mc)
389     {
390       g_object_unref (tp_file->priv->mc);
391     }
392
393   g_free (tp_file->priv->filename);
394   if (tp_file->priv->socket_address != NULL)
395     tp_g_value_slice_free (tp_file->priv->socket_address);
396   g_free (tp_file->priv->description);
397   g_free (tp_file->priv->content_hash);
398   g_free (tp_file->priv->content_type);
399
400   if (tp_file->priv->in_stream)
401     g_object_unref (tp_file->priv->in_stream);
402
403   if (tp_file->priv->out_stream)
404     g_object_unref (tp_file->priv->out_stream);
405
406   if (tp_file->priv->contact)
407     g_object_unref (tp_file->priv->contact);
408
409   if (tp_file->priv->cancellable)
410     g_object_unref (tp_file->priv->cancellable);
411
412   if (tp_file->priv->stalled_id != 0)
413     g_source_remove (tp_file->priv->stalled_id);
414
415   G_OBJECT_CLASS (empathy_tp_file_parent_class)->finalize (object);
416 }
417
418 static gboolean
419 tp_file_stalled_cb (EmpathyTpFile *tp_file)
420 {
421   /* We didn't get transferred bytes update for a while, the transfer is
422    * stalled. */
423
424   tp_file->priv->speed = 0;
425   tp_file->priv->remaining_time = -1;
426   g_signal_emit (tp_file, signals[REFRESH], 0);
427
428   return FALSE;
429 }
430
431 static void
432 tp_file_start_transfer (EmpathyTpFile *tp_file)
433 {
434   gint fd;
435   struct sockaddr_un addr;
436   GArray *array;
437
438   fd = socket (PF_UNIX, SOCK_STREAM, 0);
439   if (fd < 0)
440     {
441       DEBUG ("Failed to create socket, closing channel");
442       empathy_tp_file_cancel (tp_file);
443       return;
444     }
445
446   array = g_value_get_boxed (tp_file->priv->socket_address);
447
448   memset (&addr, 0, sizeof (addr));
449   addr.sun_family = AF_UNIX;
450   strncpy (addr.sun_path, array->data, array->len);
451
452   if (connect (fd, (struct sockaddr*) &addr, sizeof (addr)) < 0)
453     {
454       DEBUG ("Failed to connect socket, closing channel");
455       empathy_tp_file_cancel (tp_file);
456       close (fd);
457       return;
458     }
459
460   DEBUG ("Start the transfer");
461
462   tp_file->priv->last_update_time = empathy_time_get_current ();
463   tp_file->priv->last_update_transferred_bytes = tp_file->priv->transferred_bytes;
464   tp_file->priv->stalled_id = g_timeout_add_seconds (STALLED_TIMEOUT,
465     (GSourceFunc) tp_file_stalled_cb, tp_file);
466
467   tp_file->priv->cancellable = g_cancellable_new ();
468   if (tp_file->priv->incoming)
469     {
470       GInputStream *socket_stream;
471
472       socket_stream = g_unix_input_stream_new (fd, TRUE);
473       copy_stream (socket_stream, tp_file->priv->out_stream,
474           tp_file->priv->cancellable);
475       g_object_unref (socket_stream);
476     }
477   else
478     {
479       GOutputStream *socket_stream;
480
481       socket_stream = g_unix_output_stream_new (fd, TRUE);
482       copy_stream (tp_file->priv->in_stream, socket_stream,
483           tp_file->priv->cancellable);
484       g_object_unref (socket_stream);
485     }
486 }
487
488 static void
489 tp_file_state_changed_cb (TpChannel *channel,
490                           guint state,
491                           guint reason,
492                           gpointer user_data,
493                           GObject *weak_object)
494 {
495   EmpathyTpFile *tp_file = EMPATHY_TP_FILE (weak_object);
496
497   if (state == tp_file->priv->state)
498     return;
499
500   DEBUG ("File transfer state changed:\n"
501       "\tfilename = %s, old state = %u, state = %u, reason = %u\n"
502       "\tincoming = %s, in_stream = %s, out_stream = %s",
503       tp_file->priv->filename, tp_file->priv->state, state, reason,
504       tp_file->priv->incoming ? "yes" : "no",
505       tp_file->priv->in_stream ? "present" : "not present",
506       tp_file->priv->out_stream ? "present" : "not present");
507
508   /* If the channel is open AND we have the socket path, we can start the
509    * transfer. The socket path could be NULL if we are not doing the actual
510    * data transfer but are just an observer for the channel. */
511   if (state == TP_FILE_TRANSFER_STATE_OPEN &&
512       tp_file->priv->socket_address != NULL)
513     tp_file_start_transfer (tp_file);
514
515   tp_file->priv->state = state;
516   tp_file->priv->state_change_reason = reason;
517
518   g_object_notify (G_OBJECT (tp_file), "state");
519 }
520
521 static void
522 tp_file_transferred_bytes_changed_cb (TpChannel *channel,
523                                       guint64 count,
524                                       gpointer user_data,
525                                       GObject *weak_object)
526 {
527   EmpathyTpFile *tp_file = EMPATHY_TP_FILE (weak_object);
528   time_t curr_time, elapsed_time;
529   guint64 transferred_bytes;
530
531   /* If we didn't progress since last update, return */
532   if (tp_file->priv->transferred_bytes == count)
533     return;
534
535   /* Update the transferred bytes count */
536   tp_file->priv->transferred_bytes = count;
537   g_object_notify (G_OBJECT (tp_file), "transferred-bytes");
538
539   /* We got a progress, reset the stalled timeout */
540   if (tp_file->priv->stalled_id != 0)
541     g_source_remove (tp_file->priv->stalled_id);
542   tp_file->priv->stalled_id = g_timeout_add_seconds (STALLED_TIMEOUT,
543     (GSourceFunc) tp_file_stalled_cb, tp_file);
544
545   /* Calculate the transfer speed and remaining time estimation. We recalculate
546    * that each second to get more dynamic values that react faster to network
547    * changes. This is better than calculating the average from the begining of
548    * the transfer, I think. */
549   curr_time = empathy_time_get_current ();
550   elapsed_time = curr_time - tp_file->priv->last_update_time;
551   if (elapsed_time >= 1)
552     {
553       transferred_bytes = count - tp_file->priv->last_update_transferred_bytes;
554       tp_file->priv->speed = (gdouble) transferred_bytes / (gdouble) elapsed_time;
555       tp_file->priv->remaining_time = (tp_file->priv->size - count) /
556         tp_file->priv->speed;
557       tp_file->priv->last_update_transferred_bytes = count;
558       tp_file->priv->last_update_time = curr_time;
559
560       g_signal_emit (tp_file, signals[REFRESH], 0);
561     }
562 }
563
564 static void
565 tp_file_check_if_ready (EmpathyTpFile *tp_file)
566 {
567   if (tp_file->priv->ready || tp_file->priv->contact == NULL ||
568       tp_file->priv->state == TP_FILE_TRANSFER_STATE_NONE)
569     return;
570
571   tp_file->priv->ready = TRUE;
572   g_object_notify (G_OBJECT (tp_file), "ready");
573 }
574
575 static void
576 tp_file_got_contact_cb (EmpathyTpContactFactory *factory,
577                         EmpathyContact *contact,
578                         const GError *error,
579                         gpointer user_data,
580                         GObject *weak_object)
581 {
582   EmpathyTpFile *tp_file = EMPATHY_TP_FILE (weak_object);
583
584   if (error)
585     {
586       DEBUG ("Error: %s", error->message);
587       empathy_tp_file_cancel (tp_file);
588       return;
589     }
590
591   tp_file->priv->contact = g_object_ref (contact);
592   tp_file_check_if_ready (tp_file);
593 }
594
595 static void
596 tp_file_get_all_cb (TpProxy *proxy,
597                     GHashTable *properties,
598                     const GError *error,
599                     gpointer user_data,
600                     GObject *file_obj)
601 {
602   EmpathyTpFile *tp_file = EMPATHY_TP_FILE (file_obj);
603
604   if (error)
605     {
606       DEBUG ("Error: %s", error->message);
607       tp_cli_channel_call_close (tp_file->priv->channel, -1, NULL, NULL, NULL,
608           NULL);
609       return;
610     }
611
612   tp_file->priv->size = g_value_get_uint64 (
613       g_hash_table_lookup (properties, "Size"));
614   g_object_notify (file_obj, "size");
615
616   tp_file->priv->state = g_value_get_uint (
617       g_hash_table_lookup (properties, "State"));
618   g_object_notify (file_obj, "state");
619
620   tp_file->priv->transferred_bytes = g_value_get_uint64 (
621       g_hash_table_lookup (properties, "TransferredBytes"));
622   g_object_notify (file_obj, "transferred-bytes");
623
624   tp_file->priv->filename = g_value_dup_string (
625       g_hash_table_lookup (properties, "Filename"));
626   g_object_notify (file_obj, "filename");
627
628   tp_file->priv->content_hash = g_value_dup_string (
629       g_hash_table_lookup (properties, "ContentHash"));
630   g_object_notify (file_obj, "content-hash");
631
632   tp_file->priv->content_hash_type = g_value_get_uint (
633       g_hash_table_lookup (properties, "ContentHashType"));
634   g_object_notify (file_obj, "content-hash-type");
635
636   tp_file->priv->content_type = g_value_dup_string (
637       g_hash_table_lookup (properties, "ContentType"));
638   g_object_notify (file_obj, "content-type");
639
640   tp_file->priv->description = g_value_dup_string (
641       g_hash_table_lookup (properties, "Description"));
642
643   tp_file_check_if_ready (tp_file);
644 }
645
646 static void
647 tp_file_get_requested_cb (TpProxy *proxy,
648                           const GValue *requested,
649                           const GError *error,
650                           gpointer user_data,
651                           GObject *weak_object)
652 {
653   EmpathyTpFile *tp_file = EMPATHY_TP_FILE (weak_object);
654
655   if (error)
656     {
657       DEBUG ("Error: %s", error->message);
658       tp_cli_channel_call_close (tp_file->priv->channel, -1, NULL, NULL, NULL,
659           NULL);
660       return;
661     }
662
663   tp_file->priv->incoming = !g_value_get_boolean (requested);
664   g_object_notify (G_OBJECT (tp_file), "incoming");
665
666   tp_file_check_if_ready (tp_file);
667 }
668
669 static GObject *
670 tp_file_constructor (GType type,
671                      guint n_props,
672                      GObjectConstructParam *props)
673 {
674   GObject *file_obj;
675   EmpathyTpFile *tp_file;
676   TpHandle handle;
677   TpConnection *connection;
678
679   file_obj = G_OBJECT_CLASS (empathy_tp_file_parent_class)->constructor (type,
680       n_props, props);
681
682   tp_file = EMPATHY_TP_FILE (file_obj);
683
684   connection = tp_channel_borrow_connection (tp_file->priv->channel);
685   tp_file->priv->factory = empathy_tp_contact_factory_dup_singleton (connection);
686   tp_file->priv->mc = empathy_mission_control_dup_singleton ();
687   tp_file->priv->state_change_reason =
688       TP_FILE_TRANSFER_STATE_CHANGE_REASON_NONE;
689
690   g_signal_connect (tp_file->priv->channel, "invalidated",
691     G_CALLBACK (tp_file_invalidated_cb), tp_file);
692
693   tp_cli_channel_type_file_transfer_connect_to_file_transfer_state_changed (
694       tp_file->priv->channel, tp_file_state_changed_cb, NULL, NULL,
695       G_OBJECT (tp_file), NULL);
696
697   tp_cli_channel_type_file_transfer_connect_to_transferred_bytes_changed (
698       tp_file->priv->channel, tp_file_transferred_bytes_changed_cb,
699       NULL, NULL, G_OBJECT (tp_file), NULL);
700
701   tp_cli_dbus_properties_call_get (tp_file->priv->channel, -1,
702       TP_IFACE_CHANNEL, "Requested",
703       tp_file_get_requested_cb, NULL, NULL, file_obj);
704
705   tp_cli_dbus_properties_call_get_all (tp_file->priv->channel, -1,
706       TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER,
707       tp_file_get_all_cb, NULL, NULL, file_obj);
708
709   handle = tp_channel_get_handle (tp_file->priv->channel, NULL);
710   empathy_tp_contact_factory_get_from_handle (tp_file->priv->factory,
711       handle, tp_file_got_contact_cb, NULL, NULL, file_obj);
712
713   return file_obj;
714 }
715
716 static void
717 tp_file_get_property (GObject *object,
718                       guint param_id,
719                       GValue *value,
720                       GParamSpec *pspec)
721 {
722   EmpathyTpFile *tp_file;
723
724   tp_file = EMPATHY_TP_FILE (object);
725
726   switch (param_id)
727     {
728       case PROP_CHANNEL:
729         g_value_set_object (value, tp_file->priv->channel);
730         break;
731       case PROP_INCOMING:
732         g_value_set_boolean (value, tp_file->priv->incoming);
733         break;
734       case PROP_STATE:
735         g_value_set_uint (value, tp_file->priv->state);
736         break;
737       case PROP_CONTENT_TYPE:
738         g_value_set_string (value, tp_file->priv->content_type);
739         break;
740       case PROP_FILENAME:
741         g_value_set_string (value, tp_file->priv->filename);
742         break;
743       case PROP_SIZE:
744         g_value_set_uint64 (value, tp_file->priv->size);
745         break;
746       case PROP_CONTENT_HASH_TYPE:
747         g_value_set_uint (value, tp_file->priv->content_hash_type);
748         break;
749       case PROP_CONTENT_HASH:
750         g_value_set_string (value, tp_file->priv->content_hash);
751         break;
752       case PROP_TRANSFERRED_BYTES:
753         g_value_set_uint64 (value, tp_file->priv->transferred_bytes);
754         break;
755       case PROP_READY:
756         g_value_set_boolean (value, tp_file->priv->ready);
757         break;
758       default:
759         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
760         break;
761     };
762 }
763
764 static void
765 tp_file_channel_set_dbus_property (gpointer proxy,
766                                    const gchar *property,
767                                    const GValue *value)
768 {
769         DEBUG ("Setting %s property", property);
770         tp_cli_dbus_properties_call_set (TP_PROXY (proxy), -1,
771             TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, property, value,
772             NULL, NULL, NULL, NULL);
773 }
774
775 static void
776 tp_file_set_property (GObject *object,
777                       guint param_id,
778                       const GValue *value,
779                       GParamSpec *pspec)
780 {
781   EmpathyTpFile *tp_file = (EmpathyTpFile *) object;
782   switch (param_id)
783     {
784       case PROP_CHANNEL:
785         tp_file->priv->channel = g_object_ref (g_value_get_object (value));
786         break;
787       case PROP_STATE:
788         tp_file->priv->state = g_value_get_uint (value);
789         break;
790       case PROP_INCOMING:
791         tp_file->priv->incoming = g_value_get_boolean (value);
792         break;
793       case PROP_FILENAME:
794         g_free (tp_file->priv->filename);
795         tp_file->priv->filename = g_value_dup_string (value);
796         tp_file_channel_set_dbus_property (tp_file->priv->channel,
797             "Filename", value);
798         break;
799       case PROP_SIZE:
800         tp_file->priv->size = g_value_get_uint64 (value);
801         tp_file_channel_set_dbus_property (tp_file->priv->channel,
802             "Size", value);
803         break;
804       case PROP_CONTENT_TYPE:
805         tp_file_channel_set_dbus_property (tp_file->priv->channel,
806             "ContentType", value);
807         g_free (tp_file->priv->content_type);
808         tp_file->priv->content_type = g_value_dup_string (value);
809         break;
810       case PROP_CONTENT_HASH:
811         tp_file_channel_set_dbus_property (tp_file->priv->channel,
812             "ContentHash", value);
813         g_free (tp_file->priv->content_hash);
814         tp_file->priv->content_hash = g_value_dup_string (value);
815         break;
816       default:
817         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
818         break;
819     };
820 }
821
822 static GHashTable *ft_table = NULL;
823
824 static void
825 tp_file_weak_notify_cb (gpointer channel,
826                         GObject *tp_file)
827 {
828   g_hash_table_remove (ft_table, channel);
829 }
830
831 /**
832  * empathy_tp_file_new:
833  * @channel: a #TpChannel
834  *
835  * Creates a new #EmpathyTpFile wrapping @channel, or return a new ref to an
836  * existing #EmpathyTpFile for that channel. The returned #EmpathyTpFile
837  * should be unrefed with g_object_unref() when finished with.
838  *
839  * Return value: a new #EmpathyTpFile
840  */
841 EmpathyTpFile *
842 empathy_tp_file_new (TpChannel *channel)
843 {
844   EmpathyTpFile *tp_file;
845
846   g_return_val_if_fail (TP_IS_CHANNEL (channel), NULL);
847
848   if (ft_table != NULL)
849     {
850       tp_file = g_hash_table_lookup (ft_table, channel);
851       if (tp_file != NULL) {
852         return g_object_ref (tp_file);
853       }
854     }
855   else
856     ft_table = g_hash_table_new_full (empathy_proxy_hash,
857       empathy_proxy_equal, (GDestroyNotify) g_object_unref, NULL);
858
859   tp_file = g_object_new (EMPATHY_TYPE_TP_FILE,
860       "channel", channel,
861       NULL);
862
863   g_hash_table_insert (ft_table, g_object_ref (channel), tp_file);
864   g_object_weak_ref (G_OBJECT (tp_file), tp_file_weak_notify_cb, channel);
865
866   return tp_file;
867 }
868
869 /**
870  * empathy_tp_file_get_channel
871  * @tp_file: an #EmpathyTpFile
872  *
873  * Returns the #TpChannel associated with @tp_file.
874  *
875  * Returns: the #TpChannel associated with @tp_file
876  */
877 TpChannel *
878 empathy_tp_file_get_channel (EmpathyTpFile *tp_file)
879 {
880   g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file), NULL);
881
882   return tp_file->priv->channel;
883 }
884
885 static void
886 tp_file_method_cb (TpChannel *channel,
887                    const GValue *address,
888                    const GError *error,
889                    gpointer user_data,
890                    GObject *weak_object)
891 {
892   EmpathyTpFile *tp_file = (EmpathyTpFile *) weak_object;
893   GArray *array;
894
895   if (error)
896     {
897       DEBUG ("Error: %s", error->message);
898       empathy_tp_file_cancel (tp_file);
899       return;
900     }
901
902   if (G_VALUE_TYPE (address) == DBUS_TYPE_G_UCHAR_ARRAY)
903     {
904       tp_file->priv->socket_address = tp_g_value_slice_dup (address);
905     }
906   else if (G_VALUE_TYPE (address) == G_TYPE_STRING)
907     {
908       /* Old bugged version of telepathy-salut used to store the address
909        * as a 's' instead of an 'ay' */
910       const gchar *path;
911
912       path = g_value_get_string (address);
913       array = g_array_sized_new (TRUE, FALSE, sizeof (gchar), strlen (path));
914       g_array_insert_vals (array, 0, path, strlen (path));
915
916       tp_file->priv->socket_address = tp_g_value_slice_new (
917           DBUS_TYPE_G_UCHAR_ARRAY);
918       g_value_set_boxed (tp_file->priv->socket_address, array);
919
920       g_array_free (array, TRUE);
921     }
922   else
923     {
924       DEBUG ("Wrong address type: %s", G_VALUE_TYPE_NAME (address));
925       empathy_tp_file_cancel (tp_file);
926       return;
927     }
928
929   array = g_value_get_boxed (tp_file->priv->socket_address);
930   DEBUG ("Got unix socket path: %s", array->data);
931
932   if (tp_file->priv->state == TP_FILE_TRANSFER_STATE_OPEN)
933     tp_file_start_transfer (tp_file);
934 }
935
936 /**
937  * empathy_tp_file_accept:
938  * @tp_file: an #EmpathyTpFile
939  * @offset: position where to start the transfer
940  * @gfile: a #GFile where to write transfered data
941  * @error: a #GError set if there is an error when opening @gfile
942  *
943  * Accepts a file transfer that's in the "local pending" state (i.e.
944  * %TP_FILE_TRANSFER_STATE_LOCAL_PENDING).
945  */
946 void
947 empathy_tp_file_accept (EmpathyTpFile *tp_file,
948                         guint64 offset,
949                         GFile *gfile,
950                         GError **error)
951 {
952   GValue nothing = { 0 };
953
954   g_return_if_fail (EMPATHY_IS_TP_FILE (tp_file));
955   g_return_if_fail (G_IS_FILE (gfile));
956
957   tp_file->priv->out_stream = G_OUTPUT_STREAM (g_file_replace (gfile, NULL,
958         FALSE, 0, NULL, error));
959   if (error && *error)
960     return;
961
962   g_free (tp_file->priv->filename);
963   tp_file->priv->filename = g_file_get_basename (gfile);
964   g_object_notify (G_OBJECT (tp_file), "filename");
965
966   DEBUG ("Accepting file: filename=%s", tp_file->priv->filename);
967
968   g_value_init (&nothing, G_TYPE_STRING);
969   g_value_set_static_string (&nothing, "");
970
971   tp_cli_channel_type_file_transfer_call_accept_file (tp_file->priv->channel,
972       -1, TP_SOCKET_ADDRESS_TYPE_UNIX, TP_SOCKET_ACCESS_CONTROL_LOCALHOST,
973       &nothing, offset, tp_file_method_cb, NULL, NULL, G_OBJECT (tp_file));
974 }
975
976 /**
977  * empathy_tp_file_offer:
978  * @tp_file: an #EmpathyTpFile
979  * @gfile: a #GFile where to read the data to transfer
980  * @error: a #GError set if there is an error when opening @gfile
981  *
982  * Offers a file transfer that's in the "not offered" state (i.e.
983  * %TP_FILE_TRANSFER_STATE_NOT_OFFERED).
984  */
985 void
986 empathy_tp_file_offer (EmpathyTpFile *tp_file, GFile *gfile, GError **error)
987 {
988   GValue nothing = { 0 };
989
990   g_return_if_fail (EMPATHY_IS_TP_FILE (tp_file));
991
992   tp_file->priv->in_stream = G_INPUT_STREAM (g_file_read (gfile, NULL, error));
993   if (error && *error)
994         return;
995
996   g_value_init (&nothing, G_TYPE_STRING);
997   g_value_set_static_string (&nothing, "");
998
999   tp_cli_channel_type_file_transfer_call_provide_file (tp_file->priv->channel,
1000       -1, TP_SOCKET_ADDRESS_TYPE_UNIX, TP_SOCKET_ACCESS_CONTROL_LOCALHOST,
1001       &nothing, tp_file_method_cb, NULL, NULL, G_OBJECT (tp_file));
1002 }
1003
1004 /**
1005  * empathy_tp_file_get_contact:
1006  * @tp_file: an #EmpathyTpFile
1007  *
1008  * Returns the #EmpathyContact that @tp_file is open with.
1009  *
1010  * Return value: the #EmpathyContact that @tp_file is open with.
1011  */
1012 EmpathyContact *
1013 empathy_tp_file_get_contact (EmpathyTpFile *tp_file)
1014 {
1015   g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file), NULL);
1016   return tp_file->priv->contact;
1017 }
1018
1019 const gchar *
1020 empathy_tp_file_get_filename (EmpathyTpFile *tp_file)
1021 {
1022   g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file), NULL);
1023   return tp_file->priv->filename;
1024 }
1025
1026 /**
1027  * empathy_tp_file_is_incoming:
1028  * @tp_file: an #EmpathyTpFile
1029  *
1030  * Returns whether @tp_file is incoming.
1031  *
1032  * Return value: %TRUE if the @tp_file is incoming, otherwise %FALSE
1033  */
1034 gboolean
1035 empathy_tp_file_is_incoming (EmpathyTpFile *tp_file)
1036 {
1037   g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file), FALSE);
1038   return tp_file->priv->incoming;
1039 }
1040
1041 /**
1042  * empathy_tp_file_get_state:
1043  * @tp_file: an #EmpathyTpFile
1044  * @reason: return location for state change reason, or %NULL
1045  *
1046  * Gets the current state of @tp_file. If @reason is not %NULL, then
1047  * it is set to the reason of the last state change.
1048  *
1049  * Return value: a #TpFileTransferState
1050  */
1051 TpFileTransferState
1052 empathy_tp_file_get_state (EmpathyTpFile *tp_file,
1053                            TpFileTransferStateChangeReason *reason)
1054 {
1055   g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file),
1056       TP_FILE_TRANSFER_STATE_NONE);
1057
1058   if (reason != NULL)
1059     *reason = tp_file->priv->state_change_reason;
1060
1061   return tp_file->priv->state;
1062 }
1063
1064 /**
1065  * empathy_tp_file_get_size:
1066  * @tp_file: an #EmpathyTpFile
1067  *
1068  * Gets the size of the file being transferred over @tp_file, in bytes.
1069  *
1070  * Return value: the size of the file being transferred, in bytes
1071  */
1072 guint64
1073 empathy_tp_file_get_size (EmpathyTpFile *tp_file)
1074 {
1075   g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file),
1076       EMPATHY_TP_FILE_UNKNOWN_SIZE);
1077   return tp_file->priv->size;
1078 }
1079
1080 /**
1081  * empathy_tp_file_get_transferred_bytes:
1082  * @tp_file: an #EmpathyTpFile
1083  *
1084  * Gets the number of transferred bytes of @tp_file so far, in bytes.
1085  *
1086  * Return value: number of transferred bytes of @tp_file, in bytes
1087  */
1088 guint64
1089 empathy_tp_file_get_transferred_bytes (EmpathyTpFile *tp_file)
1090 {
1091   g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file), 0);
1092   return tp_file->priv->transferred_bytes;
1093 }
1094
1095 /**
1096  * empathy_tp_file_get_remaining_time:
1097  * @tp_file: a #EmpathyTpFile
1098  *
1099  * Gets the estimated time remaining of @tp_file, in seconds.
1100  *
1101  * Return value: the estimated time remaining of @tp_file, in seconds
1102  **/
1103 gint
1104 empathy_tp_file_get_remaining_time (EmpathyTpFile *tp_file)
1105 {
1106   g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file), -1);
1107
1108   if (tp_file->priv->size == EMPATHY_TP_FILE_UNKNOWN_SIZE)
1109     return -1;
1110
1111   if (tp_file->priv->transferred_bytes == tp_file->priv->size)
1112     return 0;
1113
1114   return tp_file->priv->remaining_time;
1115 }
1116
1117 /**
1118  * empathy_tp_file_get_speed:
1119  * @tp_file: an #EmpathyTpFile
1120  *
1121  * Gets the current speed of the transfer @tp_file, in bytes per
1122  * second.
1123  *
1124  * Return value: the current speed of the transfer @tp_file, in
1125  *               bytes per second
1126  **/
1127 gdouble
1128 empathy_tp_file_get_speed (EmpathyTpFile *tp_file)
1129 {
1130   g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file), 0);
1131
1132   if (tp_file->priv->transferred_bytes == tp_file->priv->size)
1133     return 0;
1134
1135   return tp_file->priv->speed;
1136 }
1137
1138 const gchar *
1139 empathy_tp_file_get_content_type (EmpathyTpFile *tp_file)
1140 {
1141   g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file), NULL);
1142   return tp_file->priv->content_type;
1143 }
1144
1145 /**
1146  * empathy_tp_file_cancel:
1147  * @tp_file: an #EmpathyTpFile
1148  *
1149  * Cancels the file transfer, @tp_file.
1150  */
1151 void
1152 empathy_tp_file_cancel (EmpathyTpFile *tp_file)
1153 {
1154   g_return_if_fail (EMPATHY_IS_TP_FILE (tp_file));
1155
1156   DEBUG ("Closing channel..");
1157   tp_cli_channel_call_close (tp_file->priv->channel, -1,
1158     NULL, NULL, NULL, NULL);
1159
1160   if (tp_file->priv->cancellable != NULL)
1161     g_cancellable_cancel (tp_file->priv->cancellable);
1162 }
1163
1164 /**
1165  * empathy_tp_file_is_ready:
1166  * @tp_file: an #EmpathyTpFile
1167  *
1168  * Returns whether the file channel @tp_file is ready for use.
1169  *
1170  * @tp_file is classed as ready if its state is no longer
1171  * %TP_FILE_TRANSFER_STATE_NONE, or if details about the remote
1172  * contact have been fully received.
1173  *
1174  * Return value: %TRUE if @tp_file is ready for use
1175  */
1176 gboolean
1177 empathy_tp_file_is_ready (EmpathyTpFile *tp_file)
1178 {
1179   g_return_val_if_fail (EMPATHY_IS_TP_FILE (tp_file), FALSE);
1180
1181   return tp_file->priv->ready;
1182 }
1183
1184 static void
1185 empathy_tp_file_class_init (EmpathyTpFileClass *klass)
1186 {
1187   GObjectClass *object_class = G_OBJECT_CLASS (klass);
1188
1189   object_class->finalize = tp_file_finalize;
1190   object_class->constructor = tp_file_constructor;
1191   object_class->get_property = tp_file_get_property;
1192   object_class->set_property = tp_file_set_property;
1193
1194   /* Construct-only properties */
1195
1196   /**
1197    * EmpathyTpFile:channel:
1198    *
1199    * The #TpChannel associated with the #EmpathyTpFile.
1200    */
1201   g_object_class_install_property (object_class,
1202       PROP_CHANNEL,
1203       g_param_spec_object ("channel",
1204           "telepathy channel",
1205           "The file transfer channel",
1206           TP_TYPE_CHANNEL,
1207           G_PARAM_READWRITE |
1208           G_PARAM_CONSTRUCT_ONLY));
1209
1210   /**
1211    * EmpathyTpFile:state:
1212    *
1213    * The #TpFileTransferState of the #EmpathyTpFile.
1214    */
1215   g_object_class_install_property (object_class,
1216       PROP_STATE,
1217       g_param_spec_uint ("state",
1218           "state of the transfer",
1219           "The file transfer state",
1220           0,
1221           G_MAXUINT,
1222           G_MAXUINT,
1223           G_PARAM_READWRITE |
1224           G_PARAM_CONSTRUCT));
1225
1226   /**
1227    * EmpathyTpFile:incoming:
1228    *
1229    * Whether the #EmpathyTpFile is incoming.
1230    */
1231   g_object_class_install_property (object_class,
1232       PROP_INCOMING,
1233       g_param_spec_boolean ("incoming",
1234           "incoming",
1235           "Whether the transfer is incoming",
1236           FALSE,
1237           G_PARAM_READWRITE |
1238           G_PARAM_CONSTRUCT));
1239
1240   /**
1241    * EmpathyTpFile:ready:
1242    *
1243    * Whether the #EmpathyTpFile is ready to use. This property returns
1244    * the same as empathy_tp_file_is_ready().
1245    */
1246   g_object_class_install_property (object_class,
1247       PROP_READY,
1248       g_param_spec_boolean ("ready",
1249           "ready",
1250           "Whether the object is ready",
1251           FALSE,
1252           G_PARAM_READABLE));
1253
1254   /**
1255    * EmpathyTpFile:filename:
1256    *
1257    * The name of the file being transferred.
1258    */
1259   g_object_class_install_property (object_class,
1260       PROP_FILENAME,
1261       g_param_spec_string ("filename",
1262           "name of the transfer",
1263           "The file transfer filename",
1264           "",
1265           G_PARAM_READWRITE));
1266
1267   /**
1268    * EmpathyTpFile:size:
1269    *
1270    * The size of the file being transferred.
1271    */
1272   g_object_class_install_property (object_class,
1273       PROP_SIZE,
1274       g_param_spec_uint64 ("size",
1275           "size of the file",
1276           "The file transfer size",
1277           0,
1278           G_MAXUINT64,
1279           G_MAXUINT64,
1280           G_PARAM_READWRITE));
1281
1282   /**
1283    * EmpathyTpFile:content-type:
1284    *
1285    * The content-type of the file being transferred.
1286    */
1287   g_object_class_install_property (object_class,
1288       PROP_CONTENT_TYPE,
1289       g_param_spec_string ("content-type",
1290           "file transfer content-type",
1291           "The file transfer content-type",
1292           "",
1293           G_PARAM_READWRITE));
1294
1295   /**
1296    * EmpathyTpFile:content-hash-type:
1297    *
1298    * The type of hash type stored in #EmpathyTpFile:content-hash,
1299    * from #TpFileHashType.
1300    */
1301   g_object_class_install_property (object_class,
1302       PROP_CONTENT_HASH_TYPE,
1303       g_param_spec_uint ("content-hash-type",
1304           "file transfer hash type",
1305           "The type of the file transfer hash",
1306           0,
1307           G_MAXUINT,
1308           0,
1309           G_PARAM_READWRITE));
1310
1311   /**
1312    * EmpathyTpFile:content-hash:
1313    *
1314    * A hash of the contents of the file being transferred.
1315    */
1316   g_object_class_install_property (object_class,
1317       PROP_CONTENT_HASH,
1318       g_param_spec_string ("content-hash",
1319           "file transfer hash",
1320           "The hash of the transfer's contents",
1321           "",
1322           G_PARAM_READWRITE));
1323
1324   /**
1325    * EmpathyTpFile:transferred-bytes:
1326    *
1327    * The number of bytes transferred in the #EmpathyTpFile.
1328    */
1329   g_object_class_install_property (object_class,
1330       PROP_TRANSFERRED_BYTES,
1331       g_param_spec_uint64 ("transferred-bytes",
1332           "bytes transferred",
1333           "The number of bytes transferred",
1334           0,
1335           G_MAXUINT64,
1336           0,
1337           G_PARAM_READWRITE));
1338
1339   /**
1340    * EmpathyTpFile::refresh:
1341    * @tp_file: the #EmpathyTpFile
1342    *
1343    * The progress of @tp_file has changed. This can either be an update
1344    * in the number of bytes transferred, or it can be to inform of the
1345    * transfer stalling.
1346    *
1347    * This signal is designed for clients to provide more user feedback
1348    * when something to do with @tp_file changes. To avoid emitting this
1349    * signal too much, it is guaranteed that it will only ever be fired
1350    * at least every two seconds.
1351    */
1352   signals[REFRESH] = g_signal_new ("refresh", G_TYPE_FROM_CLASS (klass),
1353       G_SIGNAL_RUN_LAST, 0, NULL, NULL,
1354       g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
1355
1356   g_type_class_add_private (object_class, sizeof (EmpathyTpFilePriv));
1357 }
1358