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