]> git.0d.be Git - empathy.git/blob - src/empathy-ft-manager.c
Merge branch 'change-audio'
[empathy.git] / src / empathy-ft-manager.c
1 /*
2  * Copyright (C) 2003, 2004 Xan Lopez
3  * Copyright (C) 2007 Marco Barisione <marco@barisione.org>
4  * Copyright (C) 2008-2009 Collabora Ltd.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This program 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  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA  02110-1301  USA
20  *
21  * Authors: Xan Lopez
22  *          Marco Barisione <marco@barisione.org>
23  *          Jonny Lamb <jonny.lamb@collabora.co.uk>
24  *          Xavier Claessens <xclaesse@gmail.com>
25  *          Cosimo Cecchi <cosimo.cecchi@collabora.co.uk>
26  */
27
28 /* The original file transfer manager code was copied from Epiphany */
29
30 #include "config.h"
31
32 #include <string.h>
33
34 #include <glib/gi18n.h>
35 #include <gtk/gtk.h>
36 #include <gdk/gdkkeysyms.h>
37
38 #define DEBUG_FLAG EMPATHY_DEBUG_FT
39 #include <libempathy/empathy-debug.h>
40 #include <libempathy/empathy-tp-file.h>
41 #include <libempathy/empathy-utils.h>
42
43 #include <libempathy-gtk/empathy-ui-utils.h>
44 #include <libempathy-gtk/empathy-geometry.h>
45 #include <libempathy-gtk/empathy-images.h>
46
47 #include "empathy-ft-manager.h"
48
49 enum
50 {
51   COL_PERCENT,
52   COL_ICON,
53   COL_MESSAGE,
54   COL_REMAINING,
55   COL_FT_OBJECT
56 };
57
58 typedef struct {
59   GtkTreeModel *model;
60   GHashTable *ft_handler_to_row_ref;
61
62   /* Widgets */
63   GtkWidget *window;
64   GtkWidget *treeview;
65   GtkWidget *open_button;
66   GtkWidget *abort_button;
67   GtkWidget *clear_button;
68 } EmpathyFTManagerPriv;
69
70 enum
71 {
72   RESPONSE_OPEN  = 1,
73   RESPONSE_STOP  = 2,
74   RESPONSE_CLEAR = 3,
75   RESPONSE_CLOSE = 4
76 };
77
78 G_DEFINE_TYPE (EmpathyFTManager, empathy_ft_manager, G_TYPE_OBJECT);
79
80 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyFTManager)
81
82 static EmpathyFTManager *manager_singleton = NULL;
83
84 static void ft_handler_hashing_started_cb (EmpathyFTHandler *handler,
85     EmpathyFTManager *manager);
86
87 static gchar *
88 ft_manager_format_interval (guint interval)
89 {
90   gint hours, mins, secs;
91
92   hours = interval / 3600;
93   interval -= hours * 3600;
94   mins = interval / 60;
95   interval -= mins * 60;
96   secs = interval;
97
98   if (hours > 0)
99     /* Translators: time left, when it is more than one hour */
100     return g_strdup_printf (_("%u:%02u.%02u"), hours, mins, secs);
101   else
102     /* Translators: time left, when is is less than one hour */
103     return g_strdup_printf (_("%02u.%02u"), mins, secs);
104 }
105
106 static void
107 ft_manager_update_buttons (EmpathyFTManager *manager)
108 {
109   GtkTreeSelection *selection;
110   GtkTreeModel *model;
111   GtkTreeIter iter;
112   EmpathyFTHandler *handler;
113   gboolean open_enabled = FALSE;
114   gboolean abort_enabled = FALSE;
115   gboolean clear_enabled = FALSE;
116   gboolean is_completed, is_cancelled;
117   GHashTableIter hash_iter;
118   EmpathyFTManagerPriv *priv = GET_PRIV (manager);
119
120   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
121
122   if (gtk_tree_selection_get_selected (selection, &model, &iter))
123     {
124       gtk_tree_model_get (model, &iter, COL_FT_OBJECT, &handler, -1);
125
126       is_completed = empathy_ft_handler_is_completed (handler);
127       is_cancelled = empathy_ft_handler_is_cancelled (handler);
128
129       /* I can open the file if the transfer is completed and was incoming */
130       open_enabled = (is_completed && empathy_ft_handler_is_incoming (handler));
131
132       /* I can abort if the transfer is not already finished */
133       abort_enabled = (is_cancelled == FALSE && is_completed == FALSE);
134
135       g_object_unref (handler);
136     }
137
138   g_hash_table_iter_init (&hash_iter, priv->ft_handler_to_row_ref);
139
140   while (g_hash_table_iter_next (&hash_iter, (gpointer *) &handler, NULL))
141     {
142       if (empathy_ft_handler_is_completed (handler) ||
143           empathy_ft_handler_is_cancelled (handler))
144           clear_enabled = TRUE;
145
146       if (clear_enabled)
147         break;
148     }
149
150   gtk_widget_set_sensitive (priv->open_button, open_enabled);
151   gtk_widget_set_sensitive (priv->abort_button, abort_enabled);
152
153   if (clear_enabled)
154     gtk_widget_set_sensitive (priv->clear_button, TRUE);
155 }
156
157 static void
158 ft_manager_selection_changed (GtkTreeSelection *selection,
159                               EmpathyFTManager *manager)
160 {
161   ft_manager_update_buttons (manager);
162 }
163
164 static void
165 ft_manager_progress_cell_data_func (GtkTreeViewColumn *col,
166                                     GtkCellRenderer *renderer,
167                                     GtkTreeModel *model,
168                                     GtkTreeIter *iter,
169                                     gpointer user_data)
170 {
171   const gchar *text = NULL;
172   gint percent;
173
174   gtk_tree_model_get (model, iter, COL_PERCENT, &percent, -1);
175
176   if (percent < 0)
177     {
178       percent = 0;
179       text = C_("file transfer percent", "Unknown");
180     }
181
182   g_object_set (renderer, "text", text, "value", percent, NULL);
183 }
184
185 static GtkTreeRowReference *
186 ft_manager_get_row_from_handler (EmpathyFTManager *manager,
187                                  EmpathyFTHandler *handler)
188 {
189   EmpathyFTManagerPriv *priv = GET_PRIV (manager);
190
191   return g_hash_table_lookup (priv->ft_handler_to_row_ref, handler);
192 }
193
194 static void
195 ft_manager_remove_file_from_model (EmpathyFTManager *manager,
196                                    EmpathyFTHandler *handler)
197 {
198   GtkTreeRowReference *row_ref;
199   GtkTreeSelection *selection;
200   GtkTreePath *path = NULL;
201   GtkTreeIter iter;
202   gboolean update_selection;
203   EmpathyFTManagerPriv *priv = GET_PRIV (manager);
204
205   row_ref = ft_manager_get_row_from_handler (manager, handler);
206   g_return_if_fail (row_ref);
207
208   DEBUG ("Removing file transfer from window: contact=%s, filename=%s",
209       empathy_contact_get_alias (empathy_ft_handler_get_contact (handler)),
210       empathy_ft_handler_get_filename (handler));
211
212   /* Get the iter from the row_ref */
213   path = gtk_tree_row_reference_get_path (row_ref);
214   gtk_tree_model_get_iter (priv->model, &iter, path);
215   gtk_tree_path_free (path);
216
217   /* We have to update the selection only if we are removing the selected row */
218   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
219   update_selection = gtk_tree_selection_iter_is_selected (selection, &iter);
220
221   /* Remove tp_file's row. After that iter points to the next row */
222   if (!gtk_list_store_remove (GTK_LIST_STORE (priv->model), &iter))
223     {
224       gint n_row;
225
226       /* There is no next row, set iter to the last row */
227       n_row = gtk_tree_model_iter_n_children (priv->model, NULL);
228       if (n_row > 0)
229         gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, n_row - 1);
230       else
231         update_selection = FALSE;
232     }
233
234   if (update_selection)
235     gtk_tree_selection_select_iter (selection, &iter);
236 }
237
238 static gboolean
239 remove_finished_transfer_foreach (gpointer key,
240                                   gpointer value,
241                                   gpointer user_data)
242 {
243   EmpathyFTHandler *handler = key;
244   EmpathyFTManager *manager = user_data;
245
246   if (empathy_ft_handler_is_completed (handler) ||
247       empathy_ft_handler_is_cancelled (handler))
248     {
249       ft_manager_remove_file_from_model (manager, handler);
250       return TRUE;
251     }
252
253   return FALSE;
254 }
255
256 static gchar *
257 ft_manager_format_progress_bytes_and_percentage (guint64 current,
258                                                  guint64 total,
259                                                  gdouble speed,
260                                                  int *percentage)
261 {
262   char *total_str, *current_str, *retval;
263   char *speed_str = NULL;
264
265   total_str = g_format_size_for_display (total);
266   current_str = g_format_size_for_display (current);
267
268   if (speed > 0)
269     speed_str = g_format_size_for_display ((goffset) speed);
270
271   /* translators: first %s is the currently processed size, second %s is
272    * the total file size */
273   retval = speed_str ?
274     g_strdup_printf (_("%s of %s at %s/s"), current_str, total_str, speed_str) :
275     g_strdup_printf (_("%s of %s"), current_str, total_str);
276
277   g_free (total_str);
278   g_free (current_str);
279   g_free (speed_str);
280
281   if (percentage != NULL)
282     {
283       if (total != 0)
284         *percentage = current * 100 / total;
285       else
286         *percentage = -1;
287     }
288
289   return retval;
290 }
291
292 static gchar *
293 ft_manager_format_contact_info (EmpathyFTHandler *handler)
294 {
295   gboolean incoming;
296   const char *filename, *contact_name, *first_line_format;
297   char *retval;
298
299   incoming = empathy_ft_handler_is_incoming (handler);
300   contact_name = empathy_contact_get_alias
301     (empathy_ft_handler_get_contact (handler));
302   filename = empathy_ft_handler_get_filename (handler);
303
304   if (incoming)
305     /* translators: first %s is filename, second %s is the contact name */
306     first_line_format = _("Receiving \"%s\" from %s");
307   else
308     /* translators: first %s is filename, second %s is the contact name */
309     first_line_format = _("Sending \"%s\" to %s");
310
311   retval = g_strdup_printf (first_line_format, filename, contact_name);
312
313   return retval;
314 }
315
316 static gchar *
317 ft_manager_format_error_message (EmpathyFTHandler *handler,
318                                  const GError *error)
319 {
320   const char *contact_name, *filename;
321   EmpathyContact *contact;
322   char *first_line, *message;
323   gboolean incoming;
324
325   contact_name = NULL;
326   incoming = empathy_ft_handler_is_incoming (handler);
327
328   contact = empathy_ft_handler_get_contact (handler);
329   if (contact)
330     contact_name = empathy_contact_get_alias (contact);
331
332   filename = empathy_ft_handler_get_filename (handler);
333
334   if (incoming)
335     /* filename/contact_name here are either both NULL or both valid */
336     if (filename && contact_name)
337       /* translators: first %s is filename, second %s
338        * is the contact name */
339       first_line = g_strdup_printf (_("Error receiving \"%s\" from %s"), filename,
340           contact_name);
341     else
342       first_line = g_strdup (_("Error receiving a file"));
343   else
344     /* translators: first %s is filename, second %s
345      * is the contact name */
346     if (filename && contact_name)
347       first_line = g_strdup_printf (_("Error sending \"%s\" to %s"), filename,
348           contact_name);
349     else
350       first_line = g_strdup (_("Error sending a file"));
351
352   message = g_strdup_printf ("%s\n%s", first_line, error->message);
353
354   g_free (first_line);
355
356   return message;
357 }
358
359 static void
360 ft_manager_update_handler_message (EmpathyFTManager *manager,
361                                    GtkTreeRowReference *row_ref,
362                                    const char *message)
363 {
364   GtkTreePath *path;
365   GtkTreeIter iter;
366   EmpathyFTManagerPriv *priv = GET_PRIV (manager);
367
368   /* Set new value in the store */
369   path = gtk_tree_row_reference_get_path (row_ref);
370   gtk_tree_model_get_iter (priv->model, &iter, path);
371   gtk_list_store_set (GTK_LIST_STORE (priv->model),
372       &iter,
373       COL_MESSAGE, message ? message : "",
374       -1);
375
376   gtk_tree_path_free (path);
377 }
378
379 static void
380 ft_manager_update_handler_progress (EmpathyFTManager *manager,
381                                     GtkTreeRowReference *row_ref,
382                                     int percentage)
383 {
384   GtkTreePath *path;
385   GtkTreeIter iter;
386   EmpathyFTManagerPriv *priv = GET_PRIV (manager);
387
388   /* Set new value in the store */
389   path = gtk_tree_row_reference_get_path (row_ref);
390   gtk_tree_model_get_iter (priv->model, &iter, path);
391   gtk_list_store_set (GTK_LIST_STORE (priv->model),
392       &iter,
393       COL_PERCENT, percentage,
394       -1);
395
396   gtk_tree_path_free (path);
397
398 }
399
400 static void
401 ft_manager_update_handler_time (EmpathyFTManager *manager,
402                                 GtkTreeRowReference *row_ref,
403                                 guint remaining_time)
404 {
405   GtkTreePath *path;
406   GtkTreeIter iter;
407   EmpathyFTManagerPriv *priv = GET_PRIV (manager);
408   char *remaining_str;
409
410   remaining_str = ft_manager_format_interval (remaining_time);
411
412   /* Set new value in the store */
413   path = gtk_tree_row_reference_get_path (row_ref);
414   gtk_tree_model_get_iter (priv->model, &iter, path);
415   gtk_list_store_set (GTK_LIST_STORE (priv->model),
416       &iter,
417       COL_REMAINING, remaining_str,
418       -1);
419
420   gtk_tree_path_free (path);
421   g_free (remaining_str);
422 }
423
424 static void
425 ft_manager_clear_handler_time (EmpathyFTManager *manager,
426                                GtkTreeRowReference *row_ref)
427 {
428   GtkTreePath *path;
429   GtkTreeIter iter;
430   EmpathyFTManagerPriv *priv = GET_PRIV (manager);
431
432   /* Set new value in the store */
433   path = gtk_tree_row_reference_get_path (row_ref);
434   gtk_tree_model_get_iter (priv->model, &iter, path);
435   gtk_list_store_set (GTK_LIST_STORE (priv->model),
436       &iter,
437       COL_REMAINING, NULL,
438       -1);
439
440   gtk_tree_path_free (path);
441 }
442
443 static void
444 ft_handler_transfer_error_cb (EmpathyFTHandler *handler,
445                               GError *error,
446                               EmpathyFTManager *manager)
447 {
448   char *message;
449   GtkTreeRowReference *row_ref;
450
451   DEBUG ("Transfer error %s", error->message);
452
453   row_ref = ft_manager_get_row_from_handler (manager, handler);
454   g_return_if_fail (row_ref != NULL);
455
456   message = ft_manager_format_error_message (handler, error);
457
458   ft_manager_update_handler_message (manager, row_ref, message);
459   ft_manager_clear_handler_time (manager, row_ref);
460   ft_manager_update_buttons (manager);
461
462   g_free (message);
463 }
464
465 static void
466 do_real_transfer_done (EmpathyFTManager *manager,
467                        EmpathyFTHandler *handler)
468 {
469   const char *contact_name;
470   const char *filename;
471   char *first_line, *second_line, *message;
472   char *uri;
473   gboolean incoming;
474   GtkTreeRowReference *row_ref;
475   GtkRecentManager *recent_manager;
476   GFile *file;
477
478   row_ref = ft_manager_get_row_from_handler (manager, handler);
479   g_return_if_fail (row_ref != NULL);
480
481   incoming = empathy_ft_handler_is_incoming (handler);
482   contact_name = empathy_contact_get_alias
483     (empathy_ft_handler_get_contact (handler));
484   filename = empathy_ft_handler_get_filename (handler);
485
486   if (incoming)
487     /* translators: first %s is filename, second %s
488      * is the contact name */
489     first_line = g_strdup_printf (_("\"%s\" received from %s"), filename,
490         contact_name);
491   else
492     /* translators: first %s is filename, second %s
493      * is the contact name */
494     first_line = g_strdup_printf (_("\"%s\" sent to %s"), filename,
495         contact_name);
496
497   second_line = g_strdup (_("File transfer completed"));
498
499   message = g_strdup_printf ("%s\n%s", first_line, second_line);
500   ft_manager_update_handler_message (manager, row_ref, message);
501   ft_manager_clear_handler_time (manager, row_ref);
502
503   /* update buttons */
504   ft_manager_update_buttons (manager);
505
506   g_free (message);
507   g_free (first_line);
508   g_free (second_line);
509
510   recent_manager = gtk_recent_manager_get_default ();
511   file = empathy_ft_handler_get_gfile (handler);
512   uri = g_file_get_uri (file);
513
514   gtk_recent_manager_add_item (recent_manager, uri);
515
516   g_free (uri);
517 }
518
519 static void
520 ft_handler_transfer_done_cb (EmpathyFTHandler *handler,
521                              EmpathyTpFile *tp_file,
522                              EmpathyFTManager *manager)
523 {
524   if (empathy_ft_handler_is_incoming (handler) &&
525       empathy_ft_handler_get_use_hash (handler))
526     {
527       DEBUG ("Transfer done, waiting for hashing-started");
528
529       /* connect to the signal and return early */
530       g_signal_connect (handler, "hashing-started",
531           G_CALLBACK (ft_handler_hashing_started_cb), manager);
532
533       return;
534     }
535
536   DEBUG ("Transfer done, no hashing");
537
538   do_real_transfer_done (manager, handler);
539 }
540
541 static void
542 ft_handler_transfer_progress_cb (EmpathyFTHandler *handler,
543                                  guint64 current_bytes,
544                                  guint64 total_bytes,
545                                  guint remaining_time,
546                                  gdouble speed,
547                                  EmpathyFTManager *manager)
548 {
549   char *first_line, *second_line, *message;
550   int percentage;
551   GtkTreeRowReference *row_ref;
552
553   DEBUG ("Transfer progress");
554
555   row_ref = ft_manager_get_row_from_handler (manager, handler);
556   g_return_if_fail (row_ref != NULL);
557
558   first_line = ft_manager_format_contact_info (handler);
559   second_line = ft_manager_format_progress_bytes_and_percentage
560     (current_bytes, total_bytes, speed, &percentage);
561
562   message = g_strdup_printf ("%s\n%s", first_line, second_line);
563
564   ft_manager_update_handler_message (manager, row_ref, message);
565   ft_manager_update_handler_progress (manager, row_ref, percentage);
566
567   if (remaining_time > 0)
568     ft_manager_update_handler_time (manager, row_ref, remaining_time);
569
570   g_free (message);
571   g_free (first_line);
572   g_free (second_line);
573 }
574
575 static void
576 ft_handler_transfer_started_cb (EmpathyFTHandler *handler,
577                                 EmpathyTpFile *tp_file,
578                                 EmpathyFTManager *manager)
579 {
580   guint64 transferred_bytes, total_bytes;
581
582   DEBUG ("Transfer started");
583
584   g_signal_connect (handler, "transfer-progress",
585       G_CALLBACK (ft_handler_transfer_progress_cb), manager);
586   g_signal_connect (handler, "transfer-done",
587       G_CALLBACK (ft_handler_transfer_done_cb), manager);
588
589   transferred_bytes = empathy_ft_handler_get_transferred_bytes (handler);
590   total_bytes = empathy_ft_handler_get_total_bytes (handler);
591
592   ft_handler_transfer_progress_cb (handler, transferred_bytes, total_bytes,
593       0, -1, manager);
594 }
595
596 static void
597 ft_handler_hashing_done_cb (EmpathyFTHandler *handler,
598                             EmpathyFTManager *manager)
599 {
600   GtkTreeRowReference *row_ref;
601   char *first_line, *second_line, *message;
602
603   DEBUG ("Hashing done");
604
605   /* update the message */
606   if (empathy_ft_handler_is_incoming (handler))
607     {
608       do_real_transfer_done (manager, handler);
609       return;
610     }
611
612   row_ref = ft_manager_get_row_from_handler (manager, handler);
613   g_return_if_fail (row_ref != NULL);
614
615   first_line = ft_manager_format_contact_info (handler);
616   second_line = g_strdup (_("Waiting for the other participant's response"));
617   message = g_strdup_printf ("%s\n%s", first_line, second_line);
618
619   ft_manager_update_handler_message (manager, row_ref, message);
620
621   g_free (message);
622   g_free (first_line);
623   g_free (second_line);
624
625   g_signal_connect (handler, "transfer-started",
626       G_CALLBACK (ft_handler_transfer_started_cb), manager);
627 }
628
629 static void
630 ft_handler_hashing_progress_cb (EmpathyFTHandler *handler,
631                                 guint64 current_bytes,
632                                 guint64 total_bytes,
633                                 EmpathyFTManager *manager)
634 {
635   char *first_line, *second_line, *message;
636   GtkTreeRowReference *row_ref;
637
638   row_ref = ft_manager_get_row_from_handler (manager, handler);
639   g_return_if_fail (row_ref != NULL);
640
641   if (empathy_ft_handler_is_incoming (handler))
642       first_line = g_strdup_printf (_("Checking integrity of \"%s\""),
643           empathy_ft_handler_get_filename (handler));
644   else
645       first_line =  g_strdup_printf (_("Hashing \"%s\""),
646           empathy_ft_handler_get_filename (handler));
647
648   second_line = ft_manager_format_progress_bytes_and_percentage
649     (current_bytes, total_bytes, -1, NULL);
650
651   message = g_strdup_printf ("%s\n%s", first_line, second_line);
652
653   ft_manager_update_handler_message (manager, row_ref, message);
654
655   g_free (message);
656   g_free (first_line);
657   g_free (second_line);
658 }
659
660 static void
661 ft_handler_hashing_started_cb (EmpathyFTHandler *handler,
662                                EmpathyFTManager *manager)
663 {
664   char *message, *first_line, *second_line;
665   GtkTreeRowReference *row_ref;
666
667   DEBUG ("Hashing started");
668
669   g_signal_connect (handler, "hashing-progress",
670      G_CALLBACK (ft_handler_hashing_progress_cb), manager);
671   g_signal_connect (handler, "hashing-done",
672      G_CALLBACK (ft_handler_hashing_done_cb), manager);
673
674   row_ref = ft_manager_get_row_from_handler (manager, handler);
675   g_return_if_fail (row_ref != NULL);
676
677   first_line = ft_manager_format_contact_info (handler);
678
679   if (empathy_ft_handler_is_incoming (handler))
680       second_line = g_strdup_printf (_("Checking integrity of \"%s\""),
681           empathy_ft_handler_get_filename (handler));
682   else
683       second_line = g_strdup_printf (_("Hashing \"%s\""),
684           empathy_ft_handler_get_filename (handler));
685
686   message = g_strdup_printf ("%s\n%s", first_line, second_line);
687
688   ft_manager_update_handler_message (manager, row_ref, message);
689
690   g_free (first_line);
691   g_free (second_line);
692   g_free (message);
693 }
694
695 static void
696 ft_manager_start_transfer (EmpathyFTManager *manager,
697                            EmpathyFTHandler *handler)
698 {
699   gboolean is_outgoing;
700
701   is_outgoing = !empathy_ft_handler_is_incoming (handler);
702
703   DEBUG ("Start transfer, is outgoing %s",
704       is_outgoing ? "True" : "False");
705
706   /* now connect the signals */
707   g_signal_connect (handler, "transfer-error",
708       G_CALLBACK (ft_handler_transfer_error_cb), manager);
709
710   if (is_outgoing && empathy_ft_handler_get_use_hash (handler)) {
711     g_signal_connect (handler, "hashing-started",
712         G_CALLBACK (ft_handler_hashing_started_cb), manager);
713   } else {
714     /* either incoming or outgoing without hash */
715     g_signal_connect (handler, "transfer-started",
716         G_CALLBACK (ft_handler_transfer_started_cb), manager);
717   }
718
719   empathy_ft_handler_start_transfer (handler);
720 }
721
722 static void
723 ft_manager_add_handler_to_list (EmpathyFTManager *manager,
724                                 EmpathyFTHandler *handler,
725                                 const GError *error)
726 {
727   GtkTreeRowReference *row_ref;
728   GtkTreeIter iter;
729   GtkTreeSelection *selection;
730   GtkTreePath *path;
731   GIcon *icon;
732   const char *content_type, *second_line;
733   char *first_line, *message;
734   EmpathyFTManagerPriv *priv = GET_PRIV (manager);
735
736   icon = NULL;
737
738   /* get the icon name from the mime-type of the file. */
739   content_type = empathy_ft_handler_get_content_type (handler);
740
741   if (content_type != NULL)
742     icon = g_content_type_get_icon (content_type);
743
744   /* append the handler in the store */
745   gtk_list_store_insert_with_values (GTK_LIST_STORE (priv->model),
746       &iter, G_MAXINT, COL_FT_OBJECT, handler,
747       COL_ICON, icon, -1);
748
749   if (icon != NULL)
750     g_object_unref (icon);
751
752   /* insert the new row_ref in the hash table  */
753   path = gtk_tree_model_get_path (GTK_TREE_MODEL (priv->model), &iter);
754   row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (priv->model), path);
755   gtk_tree_path_free (path);
756   g_hash_table_insert (priv->ft_handler_to_row_ref, g_object_ref (handler),
757       row_ref);
758
759   /* select the new row */
760   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
761   gtk_tree_selection_select_iter (selection, &iter);
762
763   if (error != NULL)
764     {
765       message = ft_manager_format_error_message (handler, error);
766       ft_manager_update_handler_message (manager, row_ref, message);
767
768       g_free (message);
769       return;
770     }
771
772   /* update the row with the initial values.
773    * the only case where we postpone this is in case we're managing
774    * an outgoing+hashing transfer, as the hashing started signal will
775    * take care of updating the information.
776    */
777   if (empathy_ft_handler_is_incoming (handler) ||
778       !empathy_ft_handler_get_use_hash (handler)) {
779     first_line = ft_manager_format_contact_info (handler);
780     second_line = _("Waiting for the other participant's response");
781     message = g_strdup_printf ("%s\n%s", first_line, second_line);
782
783     ft_manager_update_handler_message (manager, row_ref, message);
784
785     g_free (first_line);
786     g_free (message);
787   }
788
789   /* hook up the signals and start the transfer */
790   ft_manager_start_transfer (manager, handler);
791 }
792
793 static void
794 ft_manager_clear (EmpathyFTManager *manager)
795 {
796   EmpathyFTManagerPriv *priv;
797
798   DEBUG ("Clearing file transfer list");
799
800   priv = GET_PRIV (manager);
801
802   /* Remove completed and cancelled transfers */
803   g_hash_table_foreach_remove (priv->ft_handler_to_row_ref,
804       remove_finished_transfer_foreach, manager);
805
806   /* set the clear button back to insensitive */
807   gtk_widget_set_sensitive (priv->clear_button, FALSE);
808 }
809
810 static void
811 ft_manager_open (EmpathyFTManager *manager)
812 {
813   GtkTreeSelection *selection;
814   GtkTreeIter iter;
815   GtkTreeModel *model;
816   EmpathyFTHandler *handler;
817   EmpathyFTManagerPriv *priv = GET_PRIV (manager);
818
819   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
820
821   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
822     return;
823
824   gtk_tree_model_get (model, &iter, COL_FT_OBJECT, &handler, -1);
825
826   if (empathy_ft_handler_is_completed (handler)){
827     char *uri;
828     GFile *file;
829
830     file = empathy_ft_handler_get_gfile (handler);
831     uri = g_file_get_uri (file);
832
833     DEBUG ("Opening URI: %s", uri);
834     empathy_url_show (GTK_WIDGET (priv->window), uri);
835     g_free (uri);
836   }
837
838   g_object_unref (handler);
839 }
840
841 static void
842 ft_manager_stop (EmpathyFTManager *manager)
843 {
844   GtkTreeSelection *selection;
845   GtkTreeIter iter;
846   GtkTreeModel *model;
847   EmpathyFTHandler *handler;
848   EmpathyFTManagerPriv *priv;
849
850   priv = GET_PRIV (manager);
851
852   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
853
854   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
855     return;
856
857   gtk_tree_model_get (model, &iter, COL_FT_OBJECT, &handler, -1);
858   g_return_if_fail (handler != NULL);
859
860   DEBUG ("Stopping file transfer: contact=%s, filename=%s",
861       empathy_contact_get_alias (empathy_ft_handler_get_contact (handler)),
862       empathy_ft_handler_get_filename (handler));
863
864   empathy_ft_handler_cancel_transfer (handler);
865
866   g_object_unref (handler);
867 }
868
869 static gboolean
870 close_window (EmpathyFTManager *manager)
871 {
872   EmpathyFTManagerPriv *priv = GET_PRIV (manager);
873
874   DEBUG ("%p", manager);
875
876   /* remove all the completed/cancelled/errored transfers */
877   ft_manager_clear (manager);
878
879   if (g_hash_table_size (priv->ft_handler_to_row_ref) > 0)
880     {
881       /* There is still FTs on flight, just hide the window */
882       DEBUG ("Hiding window");
883       gtk_widget_hide (priv->window);
884       return TRUE;
885     }
886
887   return FALSE;
888 }
889
890 static void
891 ft_manager_response_cb (GtkWidget *widget,
892                         gint response,
893                         EmpathyFTManager *manager)
894 {
895   EmpathyFTManagerPriv *priv = GET_PRIV (manager);
896
897   switch (response)
898     {
899       case RESPONSE_CLEAR:
900         ft_manager_clear (manager);
901         break;
902       case RESPONSE_OPEN:
903         ft_manager_open (manager);
904         break;
905       case RESPONSE_STOP:
906         ft_manager_stop (manager);
907         break;
908       case RESPONSE_CLOSE:
909         if (!close_window (manager))
910           gtk_widget_destroy (priv->window);
911         break;
912       case GTK_RESPONSE_NONE:
913       case GTK_RESPONSE_DELETE_EVENT:
914         /* Do nothing */
915         break;
916       default:
917         g_assert_not_reached ();
918     }
919 }
920
921 static gboolean
922 ft_manager_delete_event_cb (GtkWidget *widget,
923                             GdkEvent *event,
924                             EmpathyFTManager *manager)
925 {
926   return close_window (manager);
927 }
928
929 static void
930 ft_manager_destroy_cb (GtkWidget *widget,
931                        EmpathyFTManager *manager)
932 {
933   DEBUG ("%p", manager);
934
935   g_object_unref (manager);
936 }
937
938 static gboolean
939 ft_view_button_press_event_cb (GtkWidget *widget,
940                                GdkEventKey *event,
941                                EmpathyFTManager *manager)
942 {
943
944   if (event->type != GDK_2BUTTON_PRESS)
945       return FALSE;
946
947   ft_manager_open (manager);
948
949   return FALSE;
950 }
951
952 static gboolean
953 ft_manager_key_press_event_cb (GtkWidget *widget,
954                                GdkEventKey *event,
955                                gpointer user_data)
956 {
957   if ((event->state & GDK_CONTROL_MASK && event->keyval == GDK_KEY_w)
958       || event->keyval == GDK_KEY_Escape)
959     {
960       gtk_widget_destroy (widget);
961       return TRUE;
962     }
963
964   return FALSE;
965 }
966
967 static void
968 ft_manager_build_ui (EmpathyFTManager *manager)
969 {
970   GtkBuilder *gui;
971   GtkTreeView *view;
972   GtkListStore *liststore;
973   GtkTreeViewColumn *column;
974   GtkCellRenderer *renderer;
975   GtkTreeSelection *selection;
976   gchar *filename;
977   EmpathyFTManagerPriv *priv = GET_PRIV (manager);
978
979   filename = empathy_file_lookup ("empathy-ft-manager.ui", "src");
980   gui = empathy_builder_get_file (filename,
981       "ft_manager_dialog", &priv->window,
982       "ft_list", &priv->treeview,
983       "clear_button", &priv->clear_button,
984       "open_button", &priv->open_button,
985       "abort_button", &priv->abort_button,
986       NULL);
987   g_free (filename);
988
989   empathy_builder_connect (gui, manager,
990       "ft_manager_dialog", "destroy", ft_manager_destroy_cb,
991       "ft_manager_dialog", "response", ft_manager_response_cb,
992       "ft_manager_dialog", "delete-event", ft_manager_delete_event_cb,
993       "ft_manager_dialog", "key-press-event", ft_manager_key_press_event_cb,
994       NULL);
995
996   empathy_builder_unref_and_keep_widget (gui, priv->window);
997
998   /* Window geometry. */
999   empathy_geometry_bind (GTK_WINDOW (priv->window), "ft-manager");
1000
1001   /* Setup the tree view */
1002   view = GTK_TREE_VIEW (priv->treeview);
1003   selection = gtk_tree_view_get_selection (view);
1004   gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
1005   g_signal_connect (selection, "changed",
1006       G_CALLBACK (ft_manager_selection_changed), manager);
1007   g_signal_connect (view, "button-press-event",
1008                     G_CALLBACK (ft_view_button_press_event_cb),
1009                     manager);
1010   gtk_tree_view_set_headers_visible (view, TRUE);
1011   gtk_tree_view_set_enable_search (view, FALSE);
1012
1013   /* Setup the model */
1014   liststore = gtk_list_store_new (5,
1015       G_TYPE_INT,     /* percent */
1016       G_TYPE_ICON,    /* icon */
1017       G_TYPE_STRING,  /* message */
1018       G_TYPE_STRING,  /* remaining */
1019       G_TYPE_OBJECT); /* ft_handler */
1020   gtk_tree_view_set_model (view, GTK_TREE_MODEL (liststore));
1021   priv->model = GTK_TREE_MODEL (liststore);
1022   g_object_unref (liststore);
1023
1024   /* Progress column */
1025   column = gtk_tree_view_column_new ();
1026   gtk_tree_view_column_set_title (column, _("%"));
1027   gtk_tree_view_column_set_sort_column_id (column, COL_PERCENT);
1028   gtk_tree_view_insert_column (view, column, -1);
1029
1030   renderer = gtk_cell_renderer_progress_new ();
1031   g_object_set (renderer, "xalign", 0.5, NULL);
1032   gtk_tree_view_column_pack_start (column, renderer, FALSE);
1033   gtk_tree_view_column_set_cell_data_func (column, renderer,
1034       ft_manager_progress_cell_data_func, NULL, NULL);
1035
1036   /* Icon and filename column*/
1037   column = gtk_tree_view_column_new ();
1038   gtk_tree_view_column_set_title (column, _("File"));
1039   gtk_tree_view_column_set_expand (column, TRUE);
1040   gtk_tree_view_column_set_resizable (column, TRUE);
1041   gtk_tree_view_column_set_sort_column_id (column, COL_MESSAGE);
1042   gtk_tree_view_column_set_spacing (column, 3);
1043   gtk_tree_view_insert_column (view, column, -1);
1044
1045   renderer = gtk_cell_renderer_pixbuf_new ();
1046   g_object_set (renderer, "xpad", 3,
1047       "stock-size", GTK_ICON_SIZE_DND, NULL);
1048   gtk_tree_view_column_pack_start (column, renderer, FALSE);
1049   gtk_tree_view_column_set_attributes (column, renderer,
1050       "gicon", COL_ICON, NULL);
1051
1052   renderer = gtk_cell_renderer_text_new ();
1053   g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1054   gtk_tree_view_column_pack_start (column, renderer, TRUE);
1055   gtk_tree_view_column_set_attributes (column, renderer,
1056       "text", COL_MESSAGE, NULL);
1057
1058   /* Remaining time column */
1059   column = gtk_tree_view_column_new ();
1060   gtk_tree_view_column_set_title (column, _("Remaining"));
1061   gtk_tree_view_column_set_sort_column_id (column, COL_REMAINING);
1062   gtk_tree_view_insert_column (view, column, -1);
1063
1064   renderer = gtk_cell_renderer_text_new ();
1065   g_object_set (renderer, "xalign", 0.5, NULL);
1066   gtk_tree_view_column_pack_start (column, renderer, FALSE);
1067   gtk_tree_view_column_set_attributes (column, renderer,
1068       "text", COL_REMAINING, NULL);
1069
1070   /* clear button should be sensitive only if there are completed/cancelled
1071    * handlers in the store.
1072    */
1073   gtk_widget_set_sensitive (priv->clear_button, FALSE);
1074 }
1075
1076 /* GObject method overrides */
1077
1078 static void
1079 empathy_ft_manager_finalize (GObject *object)
1080 {
1081   EmpathyFTManagerPriv *priv = GET_PRIV (object);
1082
1083   DEBUG ("FT Manager %p", object);
1084
1085   g_hash_table_destroy (priv->ft_handler_to_row_ref);
1086
1087   G_OBJECT_CLASS (empathy_ft_manager_parent_class)->finalize (object);
1088 }
1089
1090 static void
1091 empathy_ft_manager_init (EmpathyFTManager *manager)
1092 {
1093   EmpathyFTManagerPriv *priv;
1094
1095   priv = G_TYPE_INSTANCE_GET_PRIVATE ((manager), EMPATHY_TYPE_FT_MANAGER,
1096       EmpathyFTManagerPriv);
1097
1098   manager->priv = priv;
1099
1100   priv->ft_handler_to_row_ref = g_hash_table_new_full (g_direct_hash,
1101       g_direct_equal, (GDestroyNotify) g_object_unref,
1102       (GDestroyNotify) gtk_tree_row_reference_free);
1103
1104   ft_manager_build_ui (manager);
1105 }
1106
1107 static GObject *
1108 empathy_ft_manager_constructor (GType type,
1109                                 guint n_props,
1110                                 GObjectConstructParam *props)
1111 {
1112   GObject *retval;
1113
1114   if (manager_singleton)
1115     {
1116       retval = G_OBJECT (manager_singleton);
1117     }
1118   else
1119     {
1120       retval = G_OBJECT_CLASS (empathy_ft_manager_parent_class)->constructor
1121           (type, n_props, props);
1122
1123       manager_singleton = EMPATHY_FT_MANAGER (retval);
1124       g_object_add_weak_pointer (retval, (gpointer) &manager_singleton);
1125     }
1126
1127   return retval;
1128 }
1129
1130 static void
1131 empathy_ft_manager_class_init (EmpathyFTManagerClass *klass)
1132 {
1133   GObjectClass *object_class = G_OBJECT_CLASS (klass);
1134
1135   object_class->finalize = empathy_ft_manager_finalize;
1136   object_class->constructor = empathy_ft_manager_constructor;
1137
1138   g_type_class_add_private (object_class, sizeof (EmpathyFTManagerPriv));
1139 }
1140
1141 /* public methods */
1142
1143 void
1144 empathy_ft_manager_add_handler (EmpathyFTHandler *handler)
1145 {
1146   EmpathyFTManager *manager;
1147   EmpathyFTManagerPriv *priv;
1148
1149   DEBUG ("Adding handler");
1150
1151   g_return_if_fail (EMPATHY_IS_FT_HANDLER (handler));
1152
1153   manager = g_object_new (EMPATHY_TYPE_FT_MANAGER, NULL);
1154   priv = GET_PRIV (manager);
1155
1156   ft_manager_add_handler_to_list (manager, handler, NULL);
1157   gtk_window_present (GTK_WINDOW (priv->window));
1158 }
1159
1160 void
1161 empathy_ft_manager_display_error (EmpathyFTHandler *handler,
1162                                   const GError *error)
1163 {
1164   EmpathyFTManager *manager;
1165   EmpathyFTManagerPriv *priv;
1166
1167   g_return_if_fail (EMPATHY_IS_FT_HANDLER (handler));
1168   g_return_if_fail (error != NULL);
1169
1170   manager = g_object_new (EMPATHY_TYPE_FT_MANAGER, NULL);
1171   priv = GET_PRIV (manager);
1172
1173   ft_manager_add_handler_to_list (manager, handler, error);
1174   gtk_window_present (GTK_WINDOW (priv->window));
1175 }
1176
1177 void
1178 empathy_ft_manager_show (void)
1179 {
1180   EmpathyFTManager *manager;
1181   EmpathyFTManagerPriv *priv;
1182
1183   manager = g_object_new (EMPATHY_TYPE_FT_MANAGER, NULL);
1184   priv = GET_PRIV (manager);
1185
1186   gtk_window_present (GTK_WINDOW (priv->window));
1187 }