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