2 * This file Copyright (C) Mnemosyne LLC
4 * This file is licensed by the GPL version 2. Works owned by the
5 * Transmission project are granted a special exemption to clause 2 (b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
10 * $Id: details.c 13739 2013-01-03 23:41:02Z jordan $
14 #include <stdio.h> /* sscanf () */
15 #include <stdlib.h> /* abort () */
16 #include <glib/gi18n.h>
19 #include <libtransmission/transmission.h>
20 #include <libtransmission/utils.h> /* tr_free */
25 #include "favicon.h" /* gtr_get_favicon () */
26 #include "file-list.h"
31 static GQuark ARG_KEY
= 0;
32 static GQuark DETAILS_KEY
= 0;
33 static GQuark TORRENT_ID_KEY
= 0;
34 static GQuark TEXT_BUFFER_KEY
= 0;
35 static GQuark URL_ENTRY_KEY
= 0;
41 GtkWidget
* honor_limits_check
;
42 GtkWidget
* up_limited_check
;
43 GtkWidget
* up_limit_sping
;
44 GtkWidget
* down_limited_check
;
45 GtkWidget
* down_limit_spin
;
46 GtkWidget
* bandwidth_combo
;
48 GtkWidget
* ratio_combo
;
49 GtkWidget
* ratio_spin
;
50 GtkWidget
* idle_combo
;
51 GtkWidget
* idle_spin
;
52 GtkWidget
* max_peers_spin
;
54 gulong honor_limits_check_tag
;
55 gulong up_limited_check_tag
;
56 gulong down_limited_check_tag
;
57 gulong down_limit_spin_tag
;
58 gulong up_limit_spin_tag
;
59 gulong bandwidth_combo_tag
;
60 gulong ratio_combo_tag
;
61 gulong ratio_spin_tag
;
62 gulong idle_combo_tag
;
64 gulong max_peers_spin_tag
;
72 GtkWidget
* date_started_lb
;
74 GtkWidget
* last_activity_lb
;
77 GtkWidget
* privacy_lb
;
78 GtkWidget
* origin_lb
;
79 GtkWidget
* destination_lb
;
80 GtkTextBuffer
* comment_buffer
;
82 GHashTable
* peer_hash
;
83 GHashTable
* webseed_hash
;
84 GtkListStore
* peer_store
;
85 GtkListStore
* webseed_store
;
86 GtkWidget
* webseed_view
;
87 GtkWidget
* peer_view
;
88 GtkWidget
* more_peer_details_check
;
90 GtkListStore
* tracker_store
;
91 GHashTable
* tracker_hash
;
92 GtkTreeModel
* trackers_filtered
;
93 GtkWidget
* add_tracker_button
;
94 GtkWidget
* edit_trackers_button
;
95 GtkWidget
* remove_tracker_button
;
96 GtkWidget
* tracker_view
;
97 GtkWidget
* scrape_check
;
98 GtkWidget
* all_check
;
100 GtkWidget
* file_list
;
101 GtkWidget
* file_label
;
105 guint periodic_refresh_tag
;
111 getTorrents (struct DetailsImpl
* d
, int * setmeCount
)
114 int torrentCount
= 0;
115 const int n
= g_slist_length (d
->ids
);
116 tr_torrent
** torrents
= g_new (tr_torrent
*, n
);
118 for (l
=d
->ids
; l
!=NULL
; l
=l
->next
)
119 if ((torrents
[torrentCount
] = gtr_core_find_torrent (d
->core
, GPOINTER_TO_INT (l
->data
))))
122 *setmeCount
= torrentCount
;
133 set_togglebutton_if_different (GtkWidget
* w
, gulong tag
, gboolean value
)
135 GtkToggleButton
* toggle
= GTK_TOGGLE_BUTTON (w
);
136 const gboolean currentValue
= gtk_toggle_button_get_active (toggle
);
137 if (currentValue
!= value
)
139 g_signal_handler_block (toggle
, tag
);
140 gtk_toggle_button_set_active (toggle
, value
);
141 g_signal_handler_unblock (toggle
, tag
);
146 set_int_spin_if_different (GtkWidget
* w
, gulong tag
, int value
)
148 GtkSpinButton
* spin
= GTK_SPIN_BUTTON (w
);
149 const int currentValue
= gtk_spin_button_get_value_as_int (spin
);
150 if (currentValue
!= value
)
152 g_signal_handler_block (spin
, tag
);
153 gtk_spin_button_set_value (spin
, value
);
154 g_signal_handler_unblock (spin
, tag
);
159 set_double_spin_if_different (GtkWidget
* w
, gulong tag
, double value
)
161 GtkSpinButton
* spin
= GTK_SPIN_BUTTON (w
);
162 const double currentValue
= gtk_spin_button_get_value (spin
);
163 if (((int)(currentValue
*100) != (int)(value
*100)))
165 g_signal_handler_block (spin
, tag
);
166 gtk_spin_button_set_value (spin
, value
);
167 g_signal_handler_unblock (spin
, tag
);
172 unset_combo (GtkWidget
* w
, gulong tag
)
174 GtkComboBox
* combobox
= GTK_COMBO_BOX (w
);
176 g_signal_handler_block (combobox
, tag
);
177 gtk_combo_box_set_active (combobox
, -1);
178 g_signal_handler_unblock (combobox
, tag
);
182 refreshOptions (struct DetailsImpl
* di
, tr_torrent
** torrents
, int n
)
188 /* honor_limits_check */
190 const bool baseline
= tr_torrentUsesSessionLimits (torrents
[0]);
193 if (baseline
!= tr_torrentUsesSessionLimits (torrents
[i
]))
196 set_togglebutton_if_different (di
->honor_limits_check
,
197 di
->honor_limits_check_tag
, baseline
);
200 /* down_limited_check */
202 const bool baseline
= tr_torrentUsesSpeedLimit (torrents
[0], TR_DOWN
);
205 if (baseline
!= tr_torrentUsesSpeedLimit (torrents
[i
], TR_DOWN
))
208 set_togglebutton_if_different (di
->down_limited_check
,
209 di
->down_limited_check_tag
, baseline
);
212 /* down_limit_spin */
214 const unsigned int baseline
= tr_torrentGetSpeedLimit_KBps (torrents
[0], TR_DOWN
);
217 if (baseline
!= (tr_torrentGetSpeedLimit_KBps (torrents
[i
], TR_DOWN
)))
220 set_int_spin_if_different (di
->down_limit_spin
,
221 di
->down_limit_spin_tag
, baseline
);
224 /* up_limited_check */
226 const bool baseline
= tr_torrentUsesSpeedLimit (torrents
[0], TR_UP
);
229 if (baseline
!= tr_torrentUsesSpeedLimit (torrents
[i
], TR_UP
))
232 set_togglebutton_if_different (di
->up_limited_check
,
233 di
->up_limited_check_tag
, baseline
);
238 const unsigned int baseline
= tr_torrentGetSpeedLimit_KBps (torrents
[0], TR_UP
);
241 if (baseline
!= (tr_torrentGetSpeedLimit_KBps (torrents
[i
], TR_UP
)))
244 set_int_spin_if_different (di
->up_limit_sping
,
245 di
->up_limit_spin_tag
, baseline
);
248 /* bandwidth_combo */
250 const int baseline
= tr_torrentGetPriority (torrents
[0]);
253 if (baseline
!= tr_torrentGetPriority (torrents
[i
]))
256 GtkWidget
* w
= di
->bandwidth_combo
;
257 g_signal_handler_block (w
, di
->bandwidth_combo_tag
);
258 gtr_priority_combo_set_value (GTK_COMBO_BOX (w
), baseline
);
259 g_signal_handler_unblock (w
, di
->bandwidth_combo_tag
);
262 unset_combo (di
->bandwidth_combo
, di
->bandwidth_combo_tag
);
269 const int baseline
= tr_torrentGetRatioMode (torrents
[0]);
271 if (baseline
!= (int)tr_torrentGetRatioMode (torrents
[i
]))
274 GtkWidget
* w
= di
->ratio_combo
;
275 g_signal_handler_block (w
, di
->ratio_combo_tag
);
276 gtr_combo_box_set_active_enum (GTK_COMBO_BOX (w
), baseline
);
277 gtr_widget_set_visible (di
->ratio_spin
, baseline
== TR_RATIOLIMIT_SINGLE
);
278 g_signal_handler_unblock (w
, di
->ratio_combo_tag
);
282 const double baseline
= tr_torrentGetRatioLimit (torrents
[0]);
283 set_double_spin_if_different (di
->ratio_spin
,
284 di
->ratio_spin_tag
, baseline
);
291 const int baseline
= tr_torrentGetIdleMode (torrents
[0]);
293 if (baseline
!= (int)tr_torrentGetIdleMode (torrents
[i
]))
296 GtkWidget
* w
= di
->idle_combo
;
297 g_signal_handler_block (w
, di
->idle_combo_tag
);
298 gtr_combo_box_set_active_enum (GTK_COMBO_BOX (w
), baseline
);
299 gtr_widget_set_visible (di
->idle_spin
, baseline
== TR_IDLELIMIT_SINGLE
);
300 g_signal_handler_unblock (w
, di
->idle_combo_tag
);
304 const int baseline
= tr_torrentGetIdleLimit (torrents
[0]);
305 set_int_spin_if_different (di
->idle_spin
,
306 di
->idle_spin_tag
, baseline
);
311 const int baseline
= tr_torrentGetPeerLimit (torrents
[0]);
312 set_int_spin_if_different (di
->max_peers_spin
,
313 di
->max_peers_spin_tag
, baseline
);
318 torrent_set_bool (struct DetailsImpl
* di
, const char * key
, gboolean value
)
321 tr_benc top
, *args
, *ids
;
323 tr_bencInitDict (&top
, 2);
324 tr_bencDictAddStr (&top
, "method", "torrent-set");
325 args
= tr_bencDictAddDict (&top
, "arguments", 2);
326 tr_bencDictAddBool (args
, key
, value
);
327 ids
= tr_bencDictAddList (args
, "ids", g_slist_length (di
->ids
));
328 for (l
=di
->ids
; l
; l
=l
->next
)
329 tr_bencListAddInt (ids
, GPOINTER_TO_INT (l
->data
));
331 gtr_core_exec (di
->core
, &top
);
336 torrent_set_int (struct DetailsImpl
* di
, const char * key
, int value
)
339 tr_benc top
, *args
, *ids
;
341 tr_bencInitDict (&top
, 2);
342 tr_bencDictAddStr (&top
, "method", "torrent-set");
343 args
= tr_bencDictAddDict (&top
, "arguments", 2);
344 tr_bencDictAddInt (args
, key
, value
);
345 ids
= tr_bencDictAddList (args
, "ids", g_slist_length (di
->ids
));
346 for (l
=di
->ids
; l
; l
=l
->next
)
347 tr_bencListAddInt (ids
, GPOINTER_TO_INT (l
->data
));
349 gtr_core_exec (di
->core
, &top
);
354 torrent_set_real (struct DetailsImpl
* di
, const char * key
, double value
)
357 tr_benc top
, *args
, *ids
;
359 tr_bencInitDict (&top
, 2);
360 tr_bencDictAddStr (&top
, "method", "torrent-set");
361 args
= tr_bencDictAddDict (&top
, "arguments", 2);
362 tr_bencDictAddReal (args
, key
, value
);
363 ids
= tr_bencDictAddList (args
, "ids", g_slist_length (di
->ids
));
364 for (l
=di
->ids
; l
; l
=l
->next
)
365 tr_bencListAddInt (ids
, GPOINTER_TO_INT (l
->data
));
367 gtr_core_exec (di
->core
, &top
);
372 up_speed_toggled_cb (GtkToggleButton
* tb
, gpointer d
)
374 torrent_set_bool (d
, "uploadLimited", gtk_toggle_button_get_active (tb
));
378 down_speed_toggled_cb (GtkToggleButton
*tb
, gpointer d
)
380 torrent_set_bool (d
, "downloadLimited", gtk_toggle_button_get_active (tb
));
384 global_speed_toggled_cb (GtkToggleButton
* tb
, gpointer d
)
386 torrent_set_bool (d
, "honorsSessionLimits", gtk_toggle_button_get_active (tb
));
390 up_speed_spun_cb (GtkSpinButton
* s
, struct DetailsImpl
* di
)
392 torrent_set_int (di
, "uploadLimit", gtk_spin_button_get_value_as_int (s
));
396 down_speed_spun_cb (GtkSpinButton
* s
, struct DetailsImpl
* di
)
398 torrent_set_int (di
, "downloadLimit", gtk_spin_button_get_value_as_int (s
));
402 idle_spun_cb (GtkSpinButton
* s
, struct DetailsImpl
* di
)
404 torrent_set_int (di
, "seedIdleLimit", gtk_spin_button_get_value_as_int (s
));
408 ratio_spun_cb (GtkSpinButton
* s
, struct DetailsImpl
* di
)
410 torrent_set_real (di
, "seedRatioLimit", gtk_spin_button_get_value (s
));
414 max_peers_spun_cb (GtkSpinButton
* s
, struct DetailsImpl
* di
)
416 torrent_set_int (di
, "peer-limit", gtk_spin_button_get_value (s
));
420 onPriorityChanged (GtkComboBox
* combo_box
, struct DetailsImpl
* di
)
422 const tr_priority_t priority
= gtr_priority_combo_get_value (combo_box
);
423 torrent_set_int (di
, "bandwidthPriority", priority
);
427 new_priority_combo (struct DetailsImpl
* di
)
429 GtkWidget
* w
= gtr_priority_combo_new ();
430 di
->bandwidth_combo_tag
= g_signal_connect (w
, "changed", G_CALLBACK (onPriorityChanged
), di
);
434 static void refresh (struct DetailsImpl
* di
);
437 onComboEnumChanged (GtkComboBox
* combo_box
, struct DetailsImpl
* di
)
439 const char * key
= g_object_get_qdata (G_OBJECT (combo_box
), ARG_KEY
);
440 torrent_set_int (di
, key
, gtr_combo_box_get_active_enum (combo_box
));
445 ratio_combo_new (void)
447 GtkWidget
* w
= gtr_combo_box_new_enum (
448 _("Use global settings"), TR_RATIOLIMIT_GLOBAL
,
449 _("Seed regardless of ratio"), TR_RATIOLIMIT_UNLIMITED
,
450 _("Stop seeding at ratio:"), TR_RATIOLIMIT_SINGLE
,
452 g_object_set_qdata (G_OBJECT (w
), ARG_KEY
, (gpointer
)"seedRatioMode");
457 idle_combo_new (void)
459 GtkWidget
* w
= gtr_combo_box_new_enum (
460 _("Use global settings"), TR_IDLELIMIT_GLOBAL
,
461 _("Seed regardless of activity"), TR_IDLELIMIT_UNLIMITED
,
462 _("Stop seeding if idle for N minutes:"), TR_IDLELIMIT_SINGLE
,
464 g_object_set_qdata (G_OBJECT (w
), ARG_KEY
, (gpointer
)"seedIdleMode");
469 options_page_new (struct DetailsImpl
* d
)
474 GtkWidget
*t
, *w
, *tb
, *h
;
477 t
= hig_workarea_create ();
478 hig_workarea_add_section_title (t
, &row
, _("Speed"));
480 tb
= hig_workarea_add_wide_checkbutton (t
, &row
, _("Honor global _limits"), 0);
481 d
->honor_limits_check
= tb
;
482 tag
= g_signal_connect (tb
, "toggled", G_CALLBACK (global_speed_toggled_cb
), d
);
483 d
->honor_limits_check_tag
= tag
;
485 g_snprintf (buf
, sizeof (buf
), _("Limit _download speed (%s):"), _ (speed_K_str
));
486 tb
= gtk_check_button_new_with_mnemonic (buf
);
487 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (tb
), FALSE
);
488 d
->down_limited_check
= tb
;
489 tag
= g_signal_connect (tb
, "toggled", G_CALLBACK (down_speed_toggled_cb
), d
);
490 d
->down_limited_check_tag
= tag
;
492 w
= gtk_spin_button_new_with_range (0, INT_MAX
, 5);
493 tag
= g_signal_connect (w
, "value-changed", G_CALLBACK (down_speed_spun_cb
), d
);
494 d
->down_limit_spin_tag
= tag
;
495 hig_workarea_add_row_w (t
, &row
, tb
, w
, NULL
);
496 d
->down_limit_spin
= w
;
498 g_snprintf (buf
, sizeof (buf
), _("Limit _upload speed (%s):"), _ (speed_K_str
));
499 tb
= gtk_check_button_new_with_mnemonic (buf
);
500 d
->up_limited_check
= tb
;
501 tag
= g_signal_connect (tb
, "toggled", G_CALLBACK (up_speed_toggled_cb
), d
);
502 d
->up_limited_check_tag
= tag
;
504 w
= gtk_spin_button_new_with_range (0, INT_MAX
, 5);
505 tag
= g_signal_connect (w
, "value-changed", G_CALLBACK (up_speed_spun_cb
), d
);
506 d
->up_limit_spin_tag
= tag
;
507 hig_workarea_add_row_w (t
, &row
, tb
, w
, NULL
);
508 d
->up_limit_sping
= w
;
510 w
= new_priority_combo (d
);
511 hig_workarea_add_row (t
, &row
, _("Torrent _priority:"), w
, NULL
);
512 d
->bandwidth_combo
= w
;
514 hig_workarea_add_section_divider (t
, &row
);
515 hig_workarea_add_section_title (t
, &row
, _("Seeding Limits"));
517 h
= gtk_box_new (GTK_ORIENTATION_HORIZONTAL
, GUI_PAD
);
518 w
= d
->ratio_combo
= ratio_combo_new ();
519 d
->ratio_combo_tag
= g_signal_connect (w
, "changed", G_CALLBACK (onComboEnumChanged
), d
);
520 gtk_box_pack_start (GTK_BOX (h
), w
, TRUE
, TRUE
, 0);
521 w
= d
->ratio_spin
= gtk_spin_button_new_with_range (0, 1000, .05);
522 gtk_entry_set_width_chars (GTK_ENTRY (w
), 7);
523 d
->ratio_spin_tag
= g_signal_connect (w
, "value-changed", G_CALLBACK (ratio_spun_cb
), d
);
524 gtk_box_pack_start (GTK_BOX (h
), w
, FALSE
, FALSE
, 0);
525 hig_workarea_add_row (t
, &row
, _("_Ratio:"), h
, NULL
);
527 h
= gtk_box_new (GTK_ORIENTATION_HORIZONTAL
, GUI_PAD
);
528 w
= d
->idle_combo
= idle_combo_new ();
529 d
->idle_combo_tag
= g_signal_connect (w
, "changed", G_CALLBACK (onComboEnumChanged
), d
);
530 gtk_box_pack_start (GTK_BOX (h
), w
, TRUE
, TRUE
, 0);
531 w
= d
->idle_spin
= gtk_spin_button_new_with_range (1, INT_MAX
, 5);
532 d
->idle_spin_tag
= g_signal_connect (w
, "value-changed", G_CALLBACK (idle_spun_cb
), d
);
533 gtk_box_pack_start (GTK_BOX (h
), w
, FALSE
, FALSE
, 0);
534 hig_workarea_add_row (t
, &row
, _("_Idle:"), h
, NULL
);
536 hig_workarea_add_section_divider (t
, &row
);
537 hig_workarea_add_section_title (t
, &row
, _("Peer Connections"));
539 w
= gtk_spin_button_new_with_range (1, 3000, 5);
540 hig_workarea_add_row (t
, &row
, _("_Maximum peers:"), w
, w
);
541 tag
= g_signal_connect (w
, "value-changed", G_CALLBACK (max_peers_spun_cb
), d
);
542 d
->max_peers_spin
= w
;
543 d
->max_peers_spin_tag
= tag
;
555 activityString (int activity
, bool finished
)
559 case TR_STATUS_CHECK_WAIT
: return _("Queued for verification");
560 case TR_STATUS_CHECK
: return _("Verifying local data");
561 case TR_STATUS_DOWNLOAD_WAIT
: return _("Queued for download");
562 case TR_STATUS_DOWNLOAD
: return C_("Verb", "Downloading");
563 case TR_STATUS_SEED_WAIT
: return _("Queued for seeding");
564 case TR_STATUS_SEED
: return C_("Verb", "Seeding");
565 case TR_STATUS_STOPPED
: return finished
? _("Finished") : _("Paused");
571 /* Only call gtk_text_buffer_set_text () if the new text differs from the old.
572 * This way if the user has text selected, refreshing won't deselect it */
574 gtr_text_buffer_set_text (GtkTextBuffer
* b
, const char * str
)
577 GtkTextIter start
, end
;
582 gtk_text_buffer_get_bounds (b
, &start
, &end
);
583 old_str
= gtk_text_buffer_get_text (b
, &start
, &end
, FALSE
);
585 if ((old_str
== NULL
) || strcmp (old_str
, str
))
586 gtk_text_buffer_set_text (b
, str
, -1);
592 get_short_date_string (time_t t
)
598 return g_strdup (_("N/A"));
600 tr_localtime_r (&t
, &tm
);
601 strftime (buf
, sizeof (buf
), "%d %b %Y", &tm
);
602 return g_locale_to_utf8 (buf
, -1, NULL
, NULL
, NULL
);
606 refreshInfo (struct DetailsImpl
* di
, tr_torrent
** torrents
, int n
)
610 const char * mixed
= _("Mixed");
611 const char * no_torrent
= _("No Torrents Selected");
612 const char * stateString
;
614 uint64_t sizeWhenDone
= 0;
615 const tr_stat
** stats
= g_new (const tr_stat
*, n
);
616 const tr_info
** infos
= g_new (const tr_info
*, n
);
617 for (i
=0; i
<n
; ++i
) {
618 stats
[i
] = tr_torrentStatCached (torrents
[i
]);
619 infos
[i
] = tr_torrentInfo (torrents
[i
]);
626 const bool baseline
= infos
[0]->isPrivate
;
628 if (baseline
!= infos
[i
]->isPrivate
)
633 str
= _("Private to this tracker -- DHT and PEX disabled");
635 str
= _("Public torrent");
637 gtr_label_set_text (GTK_LABEL (di
->privacy_lb
), str
);
644 const char * creator
= infos
[0]->creator
? infos
[0]->creator
: "";
645 const time_t date
= infos
[0]->dateCreated
;
646 char * datestr
= get_short_date_string (date
);
647 gboolean mixed_creator
= FALSE
;
648 gboolean mixed_date
= FALSE
;
650 for (i
=1; i
<n
; ++i
) {
651 mixed_creator
|= strcmp (creator
, infos
[i
]->creator
? infos
[i
]->creator
: "");
652 mixed_date
|= (date
!= infos
[i
]->dateCreated
);
654 if (mixed_date
&& mixed_creator
)
658 g_snprintf (buf
, sizeof (buf
), _("Created by %1$s"), creator
);
659 else if (mixed_creator
|| !*creator
)
660 g_snprintf (buf
, sizeof (buf
), _("Created on %1$s"), datestr
);
662 g_snprintf (buf
, sizeof (buf
), _("Created by %1$s on %2$s"), creator
, datestr
);
668 gtr_label_set_text (GTK_LABEL (di
->origin_lb
), str
);
675 const char * baseline
= infos
[0]->comment
? infos
[0]->comment
: "";
677 if (strcmp (baseline
, infos
[i
]->comment
? infos
[i
]->comment
: ""))
684 gtr_text_buffer_set_text (di
->comment_buffer
, str
);
690 const char * baseline
= tr_torrentGetDownloadDir (torrents
[0]);
692 if (strcmp (baseline
, tr_torrentGetDownloadDir (torrents
[i
])))
699 gtr_label_set_text (GTK_LABEL (di
->destination_lb
), str
);
705 const tr_torrent_activity activity
= stats
[0]->activity
;
706 bool allFinished
= stats
[0]->finished
;
707 for (i
=1; i
<n
; ++i
) {
708 if (activity
!= stats
[i
]->activity
)
710 if (!stats
[i
]->finished
)
713 str
= i
<n
? mixed
: activityString (activity
, allFinished
);
716 gtr_label_set_text (GTK_LABEL (di
->state_lb
), str
);
723 const time_t baseline
= stats
[0]->startDate
;
725 if (baseline
!= stats
[i
]->startDate
)
729 else if ((baseline
<=0) || (stats
[0]->activity
== TR_STATUS_STOPPED
))
732 str
= tr_strltime (buf
, time (NULL
)-baseline
, sizeof (buf
));
734 gtr_label_set_text (GTK_LABEL (di
->date_started_lb
), str
);
741 const int baseline
= stats
[0]->eta
;
743 if (baseline
!= stats
[i
]->eta
)
747 else if (baseline
< 0)
750 str
= tr_strltime (buf
, baseline
, sizeof (buf
));
752 gtr_label_set_text (GTK_LABEL (di
->eta_lb
), str
);
760 int32_t pieceSize
= 0;
761 for (i
=0; i
<n
; ++i
) {
762 size
+= infos
[i
]->totalSize
;
763 pieces
+= infos
[i
]->pieceCount
;
765 pieceSize
= infos
[i
]->pieceSize
;
766 else if (pieceSize
!= (int)infos
[i
]->pieceSize
)
769 tr_strlsize (sizebuf
, size
, sizeof (sizebuf
));
772 else if (pieceSize
>= 0) {
774 tr_formatter_mem_B (piecebuf
, pieceSize
, sizeof (piecebuf
));
775 g_snprintf (buf
, sizeof (buf
),
776 ngettext ("%1$s (%2$'d piece @ %3$s)",
777 "%1$s (%2$'d pieces @ %3$s)", pieces
),
778 sizebuf
, pieces
, piecebuf
);
781 g_snprintf (buf
, sizeof (buf
),
782 ngettext ("%1$s (%2$'d piece)",
783 "%1$s (%2$'d pieces)", pieces
),
787 gtr_label_set_text (GTK_LABEL (di
->size_lb
), str
);
795 uint64_t leftUntilDone
= 0;
796 uint64_t haveUnchecked
= 0;
797 uint64_t haveValid
= 0;
798 uint64_t available
= 0;
799 for (i
=0; i
<n
; ++i
) {
800 const tr_stat
* st
= stats
[i
];
801 haveUnchecked
+= st
->haveUnchecked
;
802 haveValid
+= st
->haveValid
;
803 sizeWhenDone
+= st
->sizeWhenDone
;
804 leftUntilDone
+= st
->leftUntilDone
;
805 available
+= st
->sizeWhenDone
- st
->leftUntilDone
+ st
->desiredAvailable
;
808 char buf2
[32], unver
[64], total
[64], avail
[32];
809 const double d
= sizeWhenDone
? (100.0 * available
) / sizeWhenDone
: 0;
810 const double ratio
= 100.0 * (sizeWhenDone
? (haveValid
+ haveUnchecked
) / (double)sizeWhenDone
: 1);
811 tr_strlpercent (avail
, d
, sizeof (avail
));
812 tr_strlpercent (buf2
, ratio
, sizeof (buf2
));
813 tr_strlsize (total
, haveUnchecked
+ haveValid
, sizeof (total
));
814 tr_strlsize (unver
, haveUnchecked
, sizeof (unver
));
815 if (!haveUnchecked
&& !leftUntilDone
)
816 g_snprintf (buf
, sizeof (buf
), _("%1$s (%2$s%%)"), total
, buf2
);
817 else if (!haveUnchecked
)
818 g_snprintf (buf
, sizeof (buf
), _("%1$s (%2$s%% of %3$s%% Available)"), total
, buf2
, avail
);
820 g_snprintf (buf
, sizeof (buf
), _("%1$s (%2$s%% of %3$s%% Available); %4$s Unverified"), total
, buf2
, avail
, unver
);
824 gtr_label_set_text (GTK_LABEL (di
->have_lb
), str
);
830 char dbuf
[64], fbuf
[64];
832 for (i
=0; i
<n
; ++i
) {
833 d
+= stats
[i
]->downloadedEver
;
834 f
+= stats
[i
]->corruptEver
;
836 tr_strlsize (dbuf
, d
, sizeof (dbuf
));
837 tr_strlsize (fbuf
, f
, sizeof (fbuf
));
839 g_snprintf (buf
, sizeof (buf
), _("%1$s (+%2$s corrupt)"), dbuf
, fbuf
);
841 tr_strlcpy (buf
, dbuf
, sizeof (buf
));
844 gtr_label_set_text (GTK_LABEL (di
->dl_lb
), str
);
855 for (i
=0; i
<n
; ++i
) {
856 up
+= stats
[i
]->uploadedEver
;
857 down
+= stats
[i
]->downloadedEver
;
859 tr_strlsize (upstr
, up
, sizeof (upstr
));
860 tr_strlratio (ratiostr
, tr_getRatio (up
, down
), sizeof (ratiostr
));
861 g_snprintf (buf
, sizeof (buf
), _("%s (Ratio: %s)"), upstr
, ratiostr
);
864 gtr_label_set_text (GTK_LABEL (di
->ul_lb
), str
);
870 str
= infos
[0]->hashString
;
873 gtr_label_set_text (GTK_LABEL (di
->hash_lb
), str
);
879 const char * baseline
= stats
[0]->errorString
;
881 if (strcmp (baseline
, stats
[i
]->errorString
))
889 str
= _("No errors");
890 gtr_label_set_text (GTK_LABEL (di
->error_lb
), str
);
899 if (latest
< stats
[i
]->activityDate
)
900 latest
= stats
[i
]->activityDate
;
904 const int period
= time (NULL
) - latest
;
906 tr_strlcpy (buf
, _("Active now"), sizeof (buf
));
909 tr_strltime (tbuf
, period
, sizeof (tbuf
));
910 g_snprintf (buf
, sizeof (buf
), _("%1$s ago"), tbuf
);
915 gtr_label_set_text (GTK_LABEL (di
->last_activity_lb
), str
);
922 info_page_new (struct DetailsImpl
* di
)
926 GtkWidget
*l
, *w
, *fr
, *sw
;
927 GtkWidget
*t
= hig_workarea_create ();
929 hig_workarea_add_section_title (t
, &row
, _("Activity"));
932 l
= di
->size_lb
= gtk_label_new (NULL
);
933 gtk_label_set_single_line_mode (GTK_LABEL (l
), TRUE
);
934 hig_workarea_add_row (t
, &row
, _("Torrent size:"), l
, NULL
);
937 l
= di
->have_lb
= gtk_label_new (NULL
);
938 gtk_label_set_single_line_mode (GTK_LABEL (l
), TRUE
);
939 hig_workarea_add_row (t
, &row
, _("Have:"), l
, NULL
);
942 l
= di
->dl_lb
= gtk_label_new (NULL
);
943 gtk_label_set_single_line_mode (GTK_LABEL (l
), TRUE
);
944 hig_workarea_add_row (t
, &row
, _("Downloaded:"), l
, NULL
);
947 l
= di
->ul_lb
= gtk_label_new (NULL
);
948 gtk_label_set_single_line_mode (GTK_LABEL (l
), TRUE
);
949 hig_workarea_add_row (t
, &row
, _("Uploaded:"), l
, NULL
);
952 l
= di
->state_lb
= gtk_label_new (NULL
);
953 gtk_label_set_single_line_mode (GTK_LABEL (l
), TRUE
);
954 hig_workarea_add_row (t
, &row
, _("State:"), l
, NULL
);
957 l
= di
->date_started_lb
= gtk_label_new (NULL
);
958 gtk_label_set_single_line_mode (GTK_LABEL (l
), TRUE
);
959 hig_workarea_add_row (t
, &row
, _("Running time:"), l
, NULL
);
962 l
= di
->eta_lb
= gtk_label_new (NULL
);
963 gtk_label_set_single_line_mode (GTK_LABEL (l
), TRUE
);
964 hig_workarea_add_row (t
, &row
, _("Remaining time:"), l
, NULL
);
967 l
= di
->last_activity_lb
= gtk_label_new (NULL
);
968 gtk_label_set_single_line_mode (GTK_LABEL (l
), TRUE
);
969 hig_workarea_add_row (t
, &row
, _("Last activity:"), l
, NULL
);
972 l
= g_object_new (GTK_TYPE_LABEL
, "selectable", TRUE
,
973 "ellipsize", PANGO_ELLIPSIZE_END
,
975 hig_workarea_add_row (t
, &row
, _("Error:"), l
, NULL
);
979 hig_workarea_add_section_divider (t
, &row
);
980 hig_workarea_add_section_title (t
, &row
, _("Details"));
983 l
= g_object_new (GTK_TYPE_LABEL
, "selectable", TRUE
,
984 "ellipsize", PANGO_ELLIPSIZE_END
,
986 hig_workarea_add_row (t
, &row
, _("Location:"), l
, NULL
);
987 di
->destination_lb
= l
;
990 l
= g_object_new (GTK_TYPE_LABEL
, "selectable", TRUE
,
991 "ellipsize", PANGO_ELLIPSIZE_END
,
993 hig_workarea_add_row (t
, &row
, _("Hash:"), l
, NULL
);
997 l
= gtk_label_new (NULL
);
998 gtk_label_set_single_line_mode (GTK_LABEL (l
), TRUE
);
999 hig_workarea_add_row (t
, &row
, _("Privacy:"), l
, NULL
);
1003 l
= g_object_new (GTK_TYPE_LABEL
, "selectable", TRUE
,
1004 "ellipsize", PANGO_ELLIPSIZE_END
,
1006 hig_workarea_add_row (t
, &row
, _("Origin:"), l
, NULL
);
1010 b
= di
->comment_buffer
= gtk_text_buffer_new (NULL
);
1011 w
= gtk_text_view_new_with_buffer (b
);
1012 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (w
), GTK_WRAP_WORD
);
1013 gtk_text_view_set_editable (GTK_TEXT_VIEW (w
), FALSE
);
1014 sw
= gtk_scrolled_window_new (NULL
, NULL
);
1015 gtk_widget_set_size_request (sw
, 350, 36);
1016 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw
),
1017 GTK_POLICY_AUTOMATIC
,
1018 GTK_POLICY_AUTOMATIC
);
1019 gtk_container_add (GTK_CONTAINER (sw
), w
);
1020 fr
= gtk_frame_new (NULL
);
1021 gtk_frame_set_shadow_type (GTK_FRAME (fr
), GTK_SHADOW_IN
);
1022 gtk_container_add (GTK_CONTAINER (fr
), sw
);
1023 w
= hig_workarea_add_tall_row (t
, &row
, _("Comment:"), fr
, NULL
);
1024 gtk_misc_set_alignment (GTK_MISC (w
), 0.0f
, 0.0f
);
1026 hig_workarea_add_section_divider (t
, &row
);
1039 WEBSEED_COL_WAS_UPDATED
,
1041 WEBSEED_COL_DOWNLOAD_RATE_DOUBLE
,
1042 WEBSEED_COL_DOWNLOAD_RATE_STRING
,
1047 getWebseedColumnNames (int column
)
1051 case WEBSEED_COL_URL
: return _("Web Seeds");
1052 case WEBSEED_COL_DOWNLOAD_RATE_DOUBLE
:
1053 case WEBSEED_COL_DOWNLOAD_RATE_STRING
: return _("Down");
1058 static GtkListStore
*
1059 webseed_model_new (void)
1061 return gtk_list_store_new (N_WEBSEED_COLS
,
1062 G_TYPE_STRING
, /* key */
1063 G_TYPE_BOOLEAN
, /* was-updated */
1064 G_TYPE_STRING
, /* url */
1065 G_TYPE_DOUBLE
, /* download rate double */
1066 G_TYPE_STRING
); /* download rate string */
1072 PEER_COL_WAS_UPDATED
,
1074 PEER_COL_ADDRESS_COLLATED
,
1075 PEER_COL_DOWNLOAD_RATE_DOUBLE
,
1076 PEER_COL_DOWNLOAD_RATE_STRING
,
1077 PEER_COL_UPLOAD_RATE_DOUBLE
,
1078 PEER_COL_UPLOAD_RATE_STRING
,
1081 PEER_COL_UPLOAD_REQUEST_COUNT_INT
,
1082 PEER_COL_UPLOAD_REQUEST_COUNT_STRING
,
1083 PEER_COL_DOWNLOAD_REQUEST_COUNT_INT
,
1084 PEER_COL_DOWNLOAD_REQUEST_COUNT_STRING
,
1085 PEER_COL_BLOCKS_DOWNLOADED_COUNT_INT
,
1086 PEER_COL_BLOCKS_DOWNLOADED_COUNT_STRING
,
1087 PEER_COL_BLOCKS_UPLOADED_COUNT_INT
,
1088 PEER_COL_BLOCKS_UPLOADED_COUNT_STRING
,
1089 PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_INT
,
1090 PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_STRING
,
1091 PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_INT
,
1092 PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_STRING
,
1093 PEER_COL_ENCRYPTION_STOCK_ID
,
1095 PEER_COL_TORRENT_NAME
,
1100 getPeerColumnName (int column
)
1104 case PEER_COL_ADDRESS
: return _("Address");
1105 case PEER_COL_DOWNLOAD_RATE_STRING
:
1106 case PEER_COL_DOWNLOAD_RATE_DOUBLE
: return _("Down");
1107 case PEER_COL_UPLOAD_RATE_STRING
:
1108 case PEER_COL_UPLOAD_RATE_DOUBLE
: return _("Up");
1109 case PEER_COL_CLIENT
: return _("Client");
1110 case PEER_COL_PROGRESS
: return _("%");
1111 case PEER_COL_UPLOAD_REQUEST_COUNT_INT
:
1112 case PEER_COL_UPLOAD_REQUEST_COUNT_STRING
: return _("Up Reqs");
1113 case PEER_COL_DOWNLOAD_REQUEST_COUNT_INT
:
1114 case PEER_COL_DOWNLOAD_REQUEST_COUNT_STRING
: return _("Dn Reqs");
1115 case PEER_COL_BLOCKS_DOWNLOADED_COUNT_INT
:
1116 case PEER_COL_BLOCKS_DOWNLOADED_COUNT_STRING
: return _("Dn Blocks");
1117 case PEER_COL_BLOCKS_UPLOADED_COUNT_INT
:
1118 case PEER_COL_BLOCKS_UPLOADED_COUNT_STRING
: return _("Up Blocks");
1119 case PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_INT
:
1120 case PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_STRING
: return _("We Cancelled");
1121 case PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_INT
:
1122 case PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_STRING
: return _("They Cancelled");
1123 case PEER_COL_FLAGS
: return _("Flags");
1128 static GtkListStore
*
1129 peer_store_new (void)
1131 return gtk_list_store_new (N_PEER_COLS
,
1132 G_TYPE_STRING
, /* key */
1133 G_TYPE_BOOLEAN
, /* was-updated */
1134 G_TYPE_STRING
, /* address */
1135 G_TYPE_STRING
, /* collated address */
1136 G_TYPE_DOUBLE
, /* download speed int */
1137 G_TYPE_STRING
, /* download speed string */
1138 G_TYPE_DOUBLE
, /* upload speed int */
1139 G_TYPE_STRING
, /* upload speed string */
1140 G_TYPE_STRING
, /* client */
1141 G_TYPE_INT
, /* progress [0..100] */
1142 G_TYPE_INT
, /* upload request count int */
1143 G_TYPE_STRING
, /* upload request count string */
1144 G_TYPE_INT
, /* download request count int */
1145 G_TYPE_STRING
, /* download request count string */
1146 G_TYPE_INT
, /* # blocks downloaded int */
1147 G_TYPE_STRING
, /* # blocks downloaded string */
1148 G_TYPE_INT
, /* # blocks uploaded int */
1149 G_TYPE_STRING
, /* # blocks uploaded string */
1150 G_TYPE_INT
, /* # blocks cancelled by client int */
1151 G_TYPE_STRING
, /* # blocks cancelled by client string */
1152 G_TYPE_INT
, /* # blocks cancelled by peer int */
1153 G_TYPE_STRING
, /* # blocks cancelled by peer string */
1154 G_TYPE_STRING
, /* encryption stock id */
1155 G_TYPE_STRING
, /* flagString */
1156 G_TYPE_STRING
); /* torrent name */
1160 initPeerRow (GtkListStore
* store
,
1163 const char * torrentName
,
1164 const tr_peer_stat
* peer
)
1167 char collated_name
[128];
1168 const char * client
= peer
->client
;
1170 if (!client
|| !strcmp (client
, "Unknown Client"))
1173 if (sscanf (peer
->addr
, "%d.%d.%d.%d", q
, q
+1, q
+2, q
+3) != 4)
1174 g_strlcpy (collated_name
, peer
->addr
, sizeof (collated_name
));
1176 g_snprintf (collated_name
, sizeof (collated_name
),
1177 "%03d.%03d.%03d.%03d", q
[0], q
[1], q
[2], q
[3]);
1179 gtk_list_store_set (store
, iter
,
1180 PEER_COL_ADDRESS
, peer
->addr
,
1181 PEER_COL_ADDRESS_COLLATED
, collated_name
,
1182 PEER_COL_CLIENT
, client
,
1183 PEER_COL_ENCRYPTION_STOCK_ID
, peer
->isEncrypted
? "transmission-lock" : NULL
,
1185 PEER_COL_TORRENT_NAME
, torrentName
,
1190 refreshPeerRow (GtkListStore
* store
,
1192 const tr_peer_stat
* peer
)
1194 char up_speed
[64] = { '\0' };
1195 char down_speed
[64] = { '\0' };
1196 char up_count
[64] = { '\0' };
1197 char down_count
[64] = { '\0' };
1198 char blocks_to_peer
[64] = { '\0' };
1199 char blocks_to_client
[64] = { '\0' };
1200 char cancelled_by_peer
[64] = { '\0' };
1201 char cancelled_by_client
[64] = { '\0' };
1203 if (peer
->rateToPeer_KBps
> 0.01)
1204 tr_formatter_speed_KBps (up_speed
, peer
->rateToPeer_KBps
, sizeof (up_speed
));
1206 if (peer
->rateToClient_KBps
> 0)
1207 tr_formatter_speed_KBps (down_speed
, peer
->rateToClient_KBps
, sizeof (down_speed
));
1209 if (peer
->pendingReqsToPeer
> 0)
1210 g_snprintf (down_count
, sizeof (down_count
), "%d", peer
->pendingReqsToPeer
);
1212 if (peer
->pendingReqsToClient
> 0)
1213 g_snprintf (up_count
, sizeof (down_count
), "%d", peer
->pendingReqsToClient
);
1215 if (peer
->blocksToPeer
> 0)
1216 g_snprintf (blocks_to_peer
, sizeof (blocks_to_peer
), "%"PRIu32
, peer
->blocksToPeer
);
1218 if (peer
->blocksToClient
> 0)
1219 g_snprintf (blocks_to_client
, sizeof (blocks_to_client
), "%"PRIu32
, peer
->blocksToClient
);
1221 if (peer
->cancelsToPeer
> 0)
1222 g_snprintf (cancelled_by_client
, sizeof (cancelled_by_client
), "%"PRIu32
, peer
->cancelsToPeer
);
1224 if (peer
->cancelsToClient
> 0)
1225 g_snprintf (cancelled_by_peer
, sizeof (cancelled_by_peer
), "%"PRIu32
, peer
->cancelsToClient
);
1227 gtk_list_store_set (store
, iter
,
1228 PEER_COL_PROGRESS
, (int)(100.0 * peer
->progress
),
1229 PEER_COL_UPLOAD_REQUEST_COUNT_INT
, peer
->pendingReqsToClient
,
1230 PEER_COL_UPLOAD_REQUEST_COUNT_STRING
, up_count
,
1231 PEER_COL_DOWNLOAD_REQUEST_COUNT_INT
, peer
->pendingReqsToPeer
,
1232 PEER_COL_DOWNLOAD_REQUEST_COUNT_STRING
, down_count
,
1233 PEER_COL_DOWNLOAD_RATE_DOUBLE
, peer
->rateToClient_KBps
,
1234 PEER_COL_DOWNLOAD_RATE_STRING
, down_speed
,
1235 PEER_COL_UPLOAD_RATE_DOUBLE
, peer
->rateToPeer_KBps
,
1236 PEER_COL_UPLOAD_RATE_STRING
, up_speed
,
1237 PEER_COL_FLAGS
, peer
->flagStr
,
1238 PEER_COL_WAS_UPDATED
, TRUE
,
1239 PEER_COL_BLOCKS_DOWNLOADED_COUNT_INT
, (int)peer
->blocksToClient
,
1240 PEER_COL_BLOCKS_DOWNLOADED_COUNT_STRING
, blocks_to_client
,
1241 PEER_COL_BLOCKS_UPLOADED_COUNT_INT
, (int)peer
->blocksToPeer
,
1242 PEER_COL_BLOCKS_UPLOADED_COUNT_STRING
, blocks_to_peer
,
1243 PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_INT
, (int)peer
->cancelsToPeer
,
1244 PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_STRING
, cancelled_by_client
,
1245 PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_INT
, (int)peer
->cancelsToClient
,
1246 PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_STRING
, cancelled_by_peer
,
1251 refreshPeerList (struct DetailsImpl
* di
, tr_torrent
** torrents
, int n
)
1256 GtkTreeModel
* model
;
1257 GHashTable
* hash
= di
->peer_hash
;
1258 GtkListStore
* store
= di
->peer_store
;
1259 struct tr_peer_stat
** peers
;
1261 /* step 1: get all the peers */
1262 peers
= g_new (struct tr_peer_stat
*, n
);
1263 peerCount
= g_new (int, n
);
1265 peers
[i
] = tr_torrentPeers (torrents
[i
], &peerCount
[i
]);
1267 /* step 2: mark all the peers in the list as not-updated */
1268 model
= GTK_TREE_MODEL (store
);
1269 if (gtk_tree_model_iter_nth_child (model
, &iter
, NULL
, 0)) do
1270 gtk_list_store_set (store
, &iter
, PEER_COL_WAS_UPDATED
, FALSE
, -1);
1271 while (gtk_tree_model_iter_next (model
, &iter
));
1273 /* step 3: add any new peers */
1274 for (i
=0; i
<n
; ++i
) {
1276 const tr_torrent
* tor
= torrents
[i
];
1277 for (j
=0; j
<peerCount
[i
]; ++j
) {
1278 const tr_peer_stat
* s
= &peers
[i
][j
];
1280 g_snprintf (key
, sizeof (key
), "%d.%s", tr_torrentId (tor
), s
->addr
);
1281 if (g_hash_table_lookup (hash
, key
) == NULL
) {
1283 gtk_list_store_append (store
, &iter
);
1284 initPeerRow (store
, &iter
, key
, tr_torrentName (tor
), s
);
1285 p
= gtk_tree_model_get_path (model
, &iter
);
1286 g_hash_table_insert (hash
, g_strdup (key
),
1287 gtk_tree_row_reference_new (model
, p
));
1288 gtk_tree_path_free (p
);
1293 /* step 4: update the peers */
1294 for (i
=0; i
<n
; ++i
) {
1296 const tr_torrent
* tor
= torrents
[i
];
1297 for (j
=0; j
<peerCount
[i
]; ++j
) {
1298 const tr_peer_stat
* s
= &peers
[i
][j
];
1300 GtkTreeRowReference
* ref
;
1302 g_snprintf (key
, sizeof (key
), "%d.%s", tr_torrentId (tor
), s
->addr
);
1303 ref
= g_hash_table_lookup (hash
, key
);
1304 p
= gtk_tree_row_reference_get_path (ref
);
1305 gtk_tree_model_get_iter (model
, &iter
, p
);
1306 refreshPeerRow (store
, &iter
, s
);
1307 gtk_tree_path_free (p
);
1311 /* step 5: remove peers that have disappeared */
1312 model
= GTK_TREE_MODEL (store
);
1313 if (gtk_tree_model_iter_nth_child (model
, &iter
, NULL
, 0)) {
1314 gboolean more
= TRUE
;
1317 gtk_tree_model_get (model
, &iter
, PEER_COL_WAS_UPDATED
, &b
, -1);
1319 more
= gtk_tree_model_iter_next (model
, &iter
);
1322 gtk_tree_model_get (model
, &iter
, PEER_COL_KEY
, &key
, -1);
1323 g_hash_table_remove (hash
, key
);
1324 more
= gtk_list_store_remove (store
, &iter
);
1330 /* step 6: cleanup */
1332 tr_torrentPeersFree (peers
[i
], peerCount
[i
]);
1334 tr_free (peerCount
);
1338 refreshWebseedList (struct DetailsImpl
* di
, tr_torrent
** torrents
, int n
)
1343 GHashTable
* hash
= di
->webseed_hash
;
1344 GtkListStore
* store
= di
->webseed_store
;
1345 GtkTreeModel
* model
= GTK_TREE_MODEL (store
);
1347 /* step 1: mark all webseeds as not-updated */
1348 if (gtk_tree_model_iter_nth_child (model
, &iter
, NULL
, 0)) do
1349 gtk_list_store_set (store
, &iter
, WEBSEED_COL_WAS_UPDATED
, FALSE
, -1);
1350 while (gtk_tree_model_iter_next (model
, &iter
));
1352 /* step 2: add any new webseeds */
1353 for (i
=0; i
<n
; ++i
) {
1355 const tr_torrent
* tor
= torrents
[i
];
1356 const tr_info
* inf
= tr_torrentInfo (tor
);
1357 total
+= inf
->webseedCount
;
1358 for (j
=0; j
<inf
->webseedCount
; ++j
) {
1360 const char * url
= inf
->webseeds
[j
];
1361 g_snprintf (key
, sizeof (key
), "%d.%s", tr_torrentId (tor
), url
);
1362 if (g_hash_table_lookup (hash
, key
) == NULL
) {
1364 gtk_list_store_append (store
, &iter
);
1365 gtk_list_store_set (store
, &iter
, WEBSEED_COL_URL
, url
,
1366 WEBSEED_COL_KEY
, key
,
1368 p
= gtk_tree_model_get_path (model
, &iter
);
1369 g_hash_table_insert (hash
, g_strdup (key
),
1370 gtk_tree_row_reference_new (model
, p
));
1371 gtk_tree_path_free (p
);
1376 /* step 3: update the webseeds */
1377 for (i
=0; i
<n
; ++i
) {
1379 const tr_torrent
* tor
= torrents
[i
];
1380 const tr_info
* inf
= tr_torrentInfo (tor
);
1381 double * speeds_KBps
= tr_torrentWebSpeeds_KBps (tor
);
1382 for (j
=0; j
<inf
->webseedCount
; ++j
) {
1385 const char * url
= inf
->webseeds
[j
];
1387 GtkTreeRowReference
* ref
;
1388 g_snprintf (key
, sizeof (key
), "%d.%s", tr_torrentId (tor
), url
);
1389 ref
= g_hash_table_lookup (hash
, key
);
1390 p
= gtk_tree_row_reference_get_path (ref
);
1391 gtk_tree_model_get_iter (model
, &iter
, p
);
1392 if (speeds_KBps
[j
] > 0)
1393 tr_formatter_speed_KBps (buf
, speeds_KBps
[j
], sizeof (buf
));
1396 gtk_list_store_set (store
, &iter
,
1397 WEBSEED_COL_DOWNLOAD_RATE_DOUBLE
, speeds_KBps
[j
],
1398 WEBSEED_COL_DOWNLOAD_RATE_STRING
, buf
,
1399 WEBSEED_COL_WAS_UPDATED
, TRUE
,
1401 gtk_tree_path_free (p
);
1403 tr_free (speeds_KBps
);
1406 /* step 4: remove webseeds that have disappeared */
1407 if (gtk_tree_model_iter_nth_child (model
, &iter
, NULL
, 0)) {
1408 gboolean more
= TRUE
;
1411 gtk_tree_model_get (model
, &iter
, WEBSEED_COL_WAS_UPDATED
, &b
, -1);
1413 more
= gtk_tree_model_iter_next (model
, &iter
);
1416 gtk_tree_model_get (model
, &iter
, WEBSEED_COL_KEY
, &key
, -1);
1418 g_hash_table_remove (hash
, key
);
1419 more
= gtk_list_store_remove (store
, &iter
);
1425 /* most of the time there are no webseeds...
1426 don't waste space showing an empty list */
1428 gtk_widget_show (di
->webseed_view
);
1430 gtk_widget_hide (di
->webseed_view
);
1434 refreshPeers (struct DetailsImpl
* di
, tr_torrent
** torrents
, int n
)
1436 refreshPeerList (di
, torrents
, n
);
1437 refreshWebseedList (di
, torrents
, n
);
1441 onPeerViewQueryTooltip (GtkWidget
* widget
,
1444 gboolean keyboard_tip
,
1445 GtkTooltip
* tooltip
,
1448 gboolean show_tip
= FALSE
;
1449 GtkTreeModel
* model
;
1452 if (gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (widget
),
1453 &x
, &y
, keyboard_tip
,
1454 &model
, NULL
, &iter
))
1456 struct DetailsImpl
* di
= gdi
;
1460 char * markup
= NULL
;
1461 char * flagstr
= NULL
;
1462 GString
* gstr
= di
->gstr
;
1463 gtk_tree_model_get (model
, &iter
, PEER_COL_TORRENT_NAME
, &name
,
1464 PEER_COL_ADDRESS
, &addr
,
1465 PEER_COL_FLAGS
, &flagstr
,
1468 g_string_truncate (gstr
, 0);
1469 markup
= g_markup_escape_text (name
, -1);
1470 g_string_append_printf (gstr
, "<b>%s</b>\n%s\n \n", markup
, addr
);
1473 for (pch
= flagstr
; pch
&& *pch
; ++pch
)
1475 const char * s
= NULL
;
1478 case 'O': s
= _("Optimistic unchoke"); break;
1479 case 'D': s
= _("Downloading from this peer"); break;
1480 case 'd': s
= _("We would download from this peer if they would let us"); break;
1481 case 'U': s
= _("Uploading to peer"); break;
1482 case 'u': s
= _("We would upload to this peer if they asked"); break;
1483 case 'K': s
= _("Peer has unchoked us, but we're not interested"); break;
1484 case '?': s
= _("We unchoked this peer, but they're not interested"); break;
1485 case 'E': s
= _("Encrypted connection"); break;
1486 case 'X': s
= _("Peer was found through Peer Exchange (PEX)"); break;
1487 case 'H': s
= _("Peer was found through DHT"); break;
1488 case 'I': s
= _("Peer is an incoming connection"); break;
1489 case 'T': s
= _("Peer is connected over µTP"); break;
1492 g_string_append_printf (gstr
, "%c: %s\n", *pch
, s
);
1494 if (gstr
->len
) /* remove the last linefeed */
1495 g_string_set_size (gstr
, gstr
->len
- 1);
1497 gtk_tooltip_set_markup (tooltip
, gstr
->str
);
1509 setPeerViewColumns (GtkTreeView
* peer_view
)
1513 const bool more
= gtr_pref_flag_get (PREF_KEY_SHOW_MORE_PEER_INFO
);
1514 int view_columns
[32];
1515 GtkTreeViewColumn
* c
;
1516 GtkCellRenderer
* r
;
1518 view_columns
[n
++] = PEER_COL_ENCRYPTION_STOCK_ID
;
1519 view_columns
[n
++] = PEER_COL_UPLOAD_RATE_STRING
;
1520 if (more
) view_columns
[n
++] = PEER_COL_UPLOAD_REQUEST_COUNT_STRING
;
1521 view_columns
[n
++] = PEER_COL_DOWNLOAD_RATE_STRING
;
1522 if (more
) view_columns
[n
++] = PEER_COL_DOWNLOAD_REQUEST_COUNT_STRING
;
1523 if (more
) view_columns
[n
++] = PEER_COL_BLOCKS_DOWNLOADED_COUNT_STRING
;
1524 if (more
) view_columns
[n
++] = PEER_COL_BLOCKS_UPLOADED_COUNT_STRING
;
1525 if (more
) view_columns
[n
++] = PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_STRING
;
1526 if (more
) view_columns
[n
++] = PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_STRING
;
1527 view_columns
[n
++] = PEER_COL_PROGRESS
;
1528 view_columns
[n
++] = PEER_COL_FLAGS
;
1529 view_columns
[n
++] = PEER_COL_ADDRESS
;
1530 view_columns
[n
++] = PEER_COL_CLIENT
;
1532 /* remove any existing columns */
1533 while ((c
= gtk_tree_view_get_column (peer_view
, 0)))
1534 gtk_tree_view_remove_column (peer_view
, c
);
1538 const int col
= view_columns
[i
];
1539 const char * t
= getPeerColumnName (col
);
1544 case PEER_COL_ADDRESS
:
1545 r
= gtk_cell_renderer_text_new ();
1546 c
= gtk_tree_view_column_new_with_attributes (t
, r
, "text", col
, NULL
);
1547 sort_col
= PEER_COL_ADDRESS_COLLATED
;
1550 case PEER_COL_CLIENT
:
1551 r
= gtk_cell_renderer_text_new ();
1552 c
= gtk_tree_view_column_new_with_attributes (t
, r
, "text", col
, NULL
);
1555 case PEER_COL_PROGRESS
:
1556 r
= gtk_cell_renderer_progress_new ();
1557 c
= gtk_tree_view_column_new_with_attributes (t
, r
, "value", PEER_COL_PROGRESS
, NULL
);
1560 case PEER_COL_ENCRYPTION_STOCK_ID
:
1561 r
= gtk_cell_renderer_pixbuf_new ();
1562 g_object_set (r
, "xalign", (gfloat
)0.0,
1563 "yalign", (gfloat
)0.5,
1565 c
= gtk_tree_view_column_new_with_attributes (t
, r
, "stock-id", PEER_COL_ENCRYPTION_STOCK_ID
, NULL
);
1566 gtk_tree_view_column_set_sizing (c
, GTK_TREE_VIEW_COLUMN_FIXED
);
1567 gtk_tree_view_column_set_fixed_width (c
, 20);
1570 case PEER_COL_DOWNLOAD_REQUEST_COUNT_STRING
:
1571 r
= gtk_cell_renderer_text_new ();
1572 c
= gtk_tree_view_column_new_with_attributes (t
, r
, "text", col
, NULL
);
1573 sort_col
= PEER_COL_DOWNLOAD_REQUEST_COUNT_INT
;
1575 case PEER_COL_UPLOAD_REQUEST_COUNT_STRING
:
1576 r
= gtk_cell_renderer_text_new ();
1577 c
= gtk_tree_view_column_new_with_attributes (t
, r
, "text", col
, NULL
);
1578 sort_col
= PEER_COL_UPLOAD_REQUEST_COUNT_INT
;
1581 case PEER_COL_BLOCKS_DOWNLOADED_COUNT_STRING
:
1582 r
= gtk_cell_renderer_text_new ();
1583 c
= gtk_tree_view_column_new_with_attributes (t
, r
, "text", col
, NULL
);
1584 sort_col
= PEER_COL_BLOCKS_DOWNLOADED_COUNT_INT
;
1586 case PEER_COL_BLOCKS_UPLOADED_COUNT_STRING
:
1587 r
= gtk_cell_renderer_text_new ();
1588 c
= gtk_tree_view_column_new_with_attributes (t
, r
, "text", col
, NULL
);
1589 sort_col
= PEER_COL_BLOCKS_UPLOADED_COUNT_INT
;
1592 case PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_STRING
:
1593 r
= gtk_cell_renderer_text_new ();
1594 c
= gtk_tree_view_column_new_with_attributes (t
, r
, "text", col
, NULL
);
1595 sort_col
= PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_INT
;
1597 case PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_STRING
:
1598 r
= gtk_cell_renderer_text_new ();
1599 c
= gtk_tree_view_column_new_with_attributes (t
, r
, "text", col
, NULL
);
1600 sort_col
= PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_INT
;
1603 case PEER_COL_DOWNLOAD_RATE_STRING
:
1604 r
= gtk_cell_renderer_text_new ();
1605 g_object_set (G_OBJECT (r
), "xalign", 1.0f
, NULL
);
1606 c
= gtk_tree_view_column_new_with_attributes (t
, r
, "text", col
, NULL
);
1607 sort_col
= PEER_COL_DOWNLOAD_RATE_DOUBLE
;
1609 case PEER_COL_UPLOAD_RATE_STRING
:
1610 r
= gtk_cell_renderer_text_new ();
1611 g_object_set (G_OBJECT (r
), "xalign", 1.0f
, NULL
);
1612 c
= gtk_tree_view_column_new_with_attributes (t
, r
, "text", col
, NULL
);
1613 sort_col
= PEER_COL_UPLOAD_RATE_DOUBLE
;
1616 case PEER_COL_FLAGS
:
1617 r
= gtk_cell_renderer_text_new ();
1618 c
= gtk_tree_view_column_new_with_attributes (t
, r
, "text", col
, NULL
);
1625 gtk_tree_view_column_set_resizable (c
, FALSE
);
1626 gtk_tree_view_column_set_sort_column_id (c
, sort_col
);
1627 gtk_tree_view_append_column (GTK_TREE_VIEW (peer_view
), c
);
1630 /* the 'expander' column has a 10-pixel margin on the left
1631 that doesn't look quite correct in any of these columns...
1632 so create a non-visible column and assign it as the
1633 'expander column. */
1635 GtkTreeViewColumn
*c
= gtk_tree_view_column_new ();
1636 gtk_tree_view_column_set_visible (c
, FALSE
);
1637 gtk_tree_view_append_column (GTK_TREE_VIEW (peer_view
), c
);
1638 gtk_tree_view_set_expander_column (GTK_TREE_VIEW (peer_view
), c
);
1643 onMorePeerInfoToggled (GtkToggleButton
* button
, struct DetailsImpl
* di
)
1645 const char * key
= PREF_KEY_SHOW_MORE_PEER_INFO
;
1646 const gboolean value
= gtk_toggle_button_get_active (button
);
1647 gtr_core_set_pref_bool (di
->core
, key
, value
);
1648 setPeerViewColumns (GTK_TREE_VIEW (di
->peer_view
));
1652 peer_page_new (struct DetailsImpl
* di
)
1656 GtkListStore
*store
;
1657 GtkWidget
*v
, *w
, *ret
, *sw
, *vbox
;
1658 GtkWidget
*webtree
= NULL
;
1660 GtkTreeViewColumn
* c
;
1661 GtkCellRenderer
* r
;
1665 store
= di
->webseed_store
= webseed_model_new ();
1666 v
= gtk_tree_view_new_with_model (GTK_TREE_MODEL (store
));
1667 g_signal_connect (v
, "button-release-event", G_CALLBACK (on_tree_view_button_released
), NULL
);
1668 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (v
), TRUE
);
1669 g_object_unref (store
);
1671 str
= getWebseedColumnNames (WEBSEED_COL_URL
);
1672 r
= gtk_cell_renderer_text_new ();
1673 g_object_set (G_OBJECT (r
), "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
1674 c
= gtk_tree_view_column_new_with_attributes (str
, r
, "text", WEBSEED_COL_URL
, NULL
);
1675 g_object_set (G_OBJECT (c
), "expand", TRUE
, NULL
);
1676 gtk_tree_view_column_set_sort_column_id (c
, WEBSEED_COL_URL
);
1677 gtk_tree_view_append_column (GTK_TREE_VIEW (v
), c
);
1679 str
= getWebseedColumnNames (WEBSEED_COL_DOWNLOAD_RATE_STRING
);
1680 r
= gtk_cell_renderer_text_new ();
1681 c
= gtk_tree_view_column_new_with_attributes (str
, r
, "text", WEBSEED_COL_DOWNLOAD_RATE_STRING
, NULL
);
1682 gtk_tree_view_column_set_sort_column_id (c
, WEBSEED_COL_DOWNLOAD_RATE_DOUBLE
);
1683 gtk_tree_view_append_column (GTK_TREE_VIEW (v
), c
);
1685 w
= gtk_scrolled_window_new (NULL
, NULL
);
1686 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (w
),
1687 GTK_POLICY_AUTOMATIC
,
1688 GTK_POLICY_AUTOMATIC
);
1689 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (w
),
1691 gtk_container_add (GTK_CONTAINER (w
), v
);
1694 di
->webseed_view
= w
;
1698 store
= di
->peer_store
= peer_store_new ();
1699 m
= gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (store
));
1700 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (m
),
1702 GTK_SORT_DESCENDING
);
1703 v
= GTK_WIDGET (g_object_new (GTK_TYPE_TREE_VIEW
,
1706 "has-tooltip", TRUE
,
1710 g_signal_connect (v
, "query-tooltip",
1711 G_CALLBACK (onPeerViewQueryTooltip
), di
);
1712 g_object_unref (store
);
1713 g_signal_connect (v
, "button-release-event",
1714 G_CALLBACK (on_tree_view_button_released
), NULL
);
1716 setPeerViewColumns (GTK_TREE_VIEW (v
));
1718 w
= sw
= gtk_scrolled_window_new (NULL
, NULL
);
1719 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (w
),
1720 GTK_POLICY_AUTOMATIC
,
1721 GTK_POLICY_AUTOMATIC
);
1722 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (w
),
1724 gtk_container_add (GTK_CONTAINER (w
), v
);
1726 vbox
= gtk_box_new (GTK_ORIENTATION_VERTICAL
, GUI_PAD
);
1727 gtk_container_set_border_width (GTK_CONTAINER (vbox
), GUI_PAD_BIG
);
1729 v
= gtk_paned_new (GTK_ORIENTATION_VERTICAL
);
1730 gtk_paned_pack1 (GTK_PANED (v
), webtree
, FALSE
, TRUE
);
1731 gtk_paned_pack2 (GTK_PANED (v
), sw
, TRUE
, TRUE
);
1732 gtk_box_pack_start (GTK_BOX (vbox
), v
, TRUE
, TRUE
, 0);
1734 w
= gtk_check_button_new_with_mnemonic (_("Show _more details"));
1735 di
->more_peer_details_check
= w
;
1736 b
= gtr_pref_flag_get (PREF_KEY_SHOW_MORE_PEER_INFO
);
1737 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w
), b
);
1738 g_signal_connect (w
, "toggled", G_CALLBACK (onMorePeerInfoToggled
), di
);
1739 gtk_box_pack_start (GTK_BOX (vbox
), w
, FALSE
, FALSE
, 0);
1742 /* ip-to-GtkTreeRowReference */
1743 di
->peer_hash
= g_hash_table_new_full (g_str_hash
,
1745 (GDestroyNotify
)g_free
,
1746 (GDestroyNotify
)gtk_tree_row_reference_free
);
1748 /* url-to-GtkTreeRowReference */
1749 di
->webseed_hash
= g_hash_table_new_full (g_str_hash
,
1751 (GDestroyNotify
)g_free
,
1752 (GDestroyNotify
)gtk_tree_row_reference_free
);
1765 /* if it's been longer than a minute, don't bother showing the seconds */
1767 tr_strltime_rounded (char * buf
, time_t t
, size_t buflen
)
1769 if (t
> 60) t
-= (t
% 60);
1770 tr_strltime (buf
, t
, buflen
);
1774 buildTrackerSummary (GString
* gstr
, const char * key
, const tr_tracker_stat
* st
, gboolean showScrape
)
1778 const time_t now
= time (NULL
);
1779 const char * err_markup_begin
= "<span color=\"red\">";
1780 const char * err_markup_end
= "</span>";
1781 const char * timeout_markup_begin
= "<span color=\"#224466\">";
1782 const char * timeout_markup_end
= "</span>";
1783 const char * success_markup_begin
= "<span color=\"#008B00\">";
1784 const char * success_markup_end
= "</span>";
1788 g_string_append (gstr
, st
->isBackup
? "<i>" : "<b>");
1790 str
= g_markup_printf_escaped ("%s - %s", st
->host
, key
);
1792 str
= g_markup_printf_escaped ("%s", st
->host
);
1793 g_string_append (gstr
, str
);
1795 g_string_append (gstr
, st
->isBackup
? "</i>" : "</b>");
1800 if (st
->hasAnnounced
&& st
->announceState
!= TR_TRACKER_INACTIVE
)
1802 g_string_append_c (gstr
, '\n');
1803 tr_strltime_rounded (timebuf
, now
- st
->lastAnnounceTime
, sizeof (timebuf
));
1804 if (st
->lastAnnounceSucceeded
)
1805 g_string_append_printf (gstr
, _("Got a list of %1$s%2$'d peers%3$s %4$s ago"),
1806 success_markup_begin
, st
->lastAnnouncePeerCount
, success_markup_end
,
1808 else if (st
->lastAnnounceTimedOut
)
1809 g_string_append_printf (gstr
, _("Peer list request %1$stimed out%2$s %3$s ago; will retry"),
1810 timeout_markup_begin
, timeout_markup_end
, timebuf
);
1812 g_string_append_printf (gstr
, _("Got an error %1$s\"%2$s\"%3$s %4$s ago"),
1813 err_markup_begin
, st
->lastAnnounceResult
, err_markup_end
, timebuf
);
1816 switch (st
->announceState
)
1818 case TR_TRACKER_INACTIVE
:
1819 g_string_append_c (gstr
, '\n');
1820 g_string_append (gstr
, _("No updates scheduled"));
1822 case TR_TRACKER_WAITING
:
1823 tr_strltime_rounded (timebuf
, st
->nextAnnounceTime
- now
, sizeof (timebuf
));
1824 g_string_append_c (gstr
, '\n');
1825 g_string_append_printf (gstr
, _("Asking for more peers in %s"), timebuf
);
1827 case TR_TRACKER_QUEUED
:
1828 g_string_append_c (gstr
, '\n');
1829 g_string_append (gstr
, _("Queued to ask for more peers"));
1831 case TR_TRACKER_ACTIVE
:
1832 tr_strltime_rounded (timebuf
, now
- st
->lastAnnounceStartTime
, sizeof (timebuf
));
1833 g_string_append_c (gstr
, '\n');
1834 g_string_append_printf (gstr
, _("Asking for more peers now… <small>%s</small>"), timebuf
);
1840 if (st
->hasScraped
) {
1841 g_string_append_c (gstr
, '\n');
1842 tr_strltime_rounded (timebuf
, now
- st
->lastScrapeTime
, sizeof (timebuf
));
1843 if (st
->lastScrapeSucceeded
)
1844 g_string_append_printf (gstr
, _("Tracker had %s%'d seeders and %'d leechers%s %s ago"),
1845 success_markup_begin
, st
->seederCount
, st
->leecherCount
, success_markup_end
,
1848 g_string_append_printf (gstr
, _("Got a scrape error \"%s%s%s\" %s ago"), err_markup_begin
, st
->lastScrapeResult
, err_markup_end
, timebuf
);
1851 switch (st
->scrapeState
)
1853 case TR_TRACKER_INACTIVE
:
1855 case TR_TRACKER_WAITING
:
1856 g_string_append_c (gstr
, '\n');
1857 tr_strltime_rounded (timebuf
, st
->nextScrapeTime
- now
, sizeof (timebuf
));
1858 g_string_append_printf (gstr
, _("Asking for peer counts in %s"), timebuf
);
1860 case TR_TRACKER_QUEUED
:
1861 g_string_append_c (gstr
, '\n');
1862 g_string_append (gstr
, _("Queued to ask for peer counts"));
1864 case TR_TRACKER_ACTIVE
:
1865 g_string_append_c (gstr
, '\n');
1866 tr_strltime_rounded (timebuf
, now
- st
->lastScrapeStartTime
, sizeof (timebuf
));
1867 g_string_append_printf (gstr
, _("Asking for peer counts now… <small>%s</small>"), timebuf
);
1876 TRACKER_COL_TORRENT_ID
,
1878 TRACKER_COL_IS_BACKUP
,
1879 TRACKER_COL_TRACKER_ID
,
1880 TRACKER_COL_FAVICON
,
1881 TRACKER_COL_WAS_UPDATED
,
1887 trackerVisibleFunc (GtkTreeModel
* model
, GtkTreeIter
* iter
, gpointer data
)
1890 struct DetailsImpl
* di
= data
;
1893 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (di
->all_check
)))
1896 /* don't show the backups... */
1897 gtk_tree_model_get (model
, iter
, TRACKER_COL_IS_BACKUP
, &isBackup
, -1);
1902 tracker_list_get_current_torrent_id (struct DetailsImpl
* di
)
1904 int torrent_id
= -1;
1906 /* if there's only one torrent in the dialog, always use it */
1908 if (g_slist_length (di
->ids
) == 1)
1909 torrent_id
= GPOINTER_TO_INT (di
->ids
->data
);
1911 /* otherwise, use the selected tracker's torrent */
1912 if (torrent_id
< 0) {
1914 GtkTreeModel
* model
;
1915 GtkTreeSelection
* sel
= gtk_tree_view_get_selection (GTK_TREE_VIEW (di
->tracker_view
));
1916 if (gtk_tree_selection_get_selected (sel
, &model
, &iter
))
1917 gtk_tree_model_get (model
, &iter
, TRACKER_COL_TORRENT_ID
, &torrent_id
, -1);
1924 tracker_list_get_current_torrent (struct DetailsImpl
* di
)
1926 const int torrent_id
= tracker_list_get_current_torrent_id (di
);
1927 return gtr_core_find_torrent (di
->core
, torrent_id
);
1931 favicon_ready_cb (gpointer pixbuf
, gpointer vreference
)
1934 GtkTreeRowReference
* reference
= vreference
;
1938 GtkTreePath
* path
= gtk_tree_row_reference_get_path (reference
);
1939 GtkTreeModel
* model
= gtk_tree_row_reference_get_model (reference
);
1941 if (gtk_tree_model_get_iter (model
, &iter
, path
))
1942 gtk_list_store_set (GTK_LIST_STORE (model
), &iter
,
1943 TRACKER_COL_FAVICON
, pixbuf
,
1946 gtk_tree_path_free (path
);
1948 g_object_unref (pixbuf
);
1951 gtk_tree_row_reference_free (reference
);
1955 refreshTracker (struct DetailsImpl
* di
, tr_torrent
** torrents
, int n
)
1959 tr_tracker_stat
** stats
;
1961 GtkTreeModel
* model
;
1962 GString
* gstr
= di
->gstr
; /* buffer for temporary strings */
1963 GHashTable
* hash
= di
->tracker_hash
;
1964 GtkListStore
* store
= di
->tracker_store
;
1965 tr_session
* session
= gtr_core_session (di
->core
);
1966 const gboolean showScrape
= gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (di
->scrape_check
));
1968 /* step 1: get all the trackers */
1969 statCount
= g_new0 (int, n
);
1970 stats
= g_new0 (tr_tracker_stat
*, n
);
1972 stats
[i
] = tr_torrentTrackers (torrents
[i
], &statCount
[i
]);
1974 /* step 2: mark all the trackers in the list as not-updated */
1975 model
= GTK_TREE_MODEL (store
);
1976 if (gtk_tree_model_iter_nth_child (model
, &iter
, NULL
, 0)) do
1977 gtk_list_store_set (store
, &iter
, TRACKER_COL_WAS_UPDATED
, FALSE
, -1);
1978 while (gtk_tree_model_iter_next (model
, &iter
));
1980 /* step 3: add any new trackers */
1981 for (i
=0; i
<n
; ++i
) {
1983 const int jn
= statCount
[i
];
1984 for (j
=0; j
<jn
; ++j
) {
1985 const tr_torrent
* tor
= torrents
[i
];
1986 const tr_tracker_stat
* st
= &stats
[i
][j
];
1987 const int torrent_id
= tr_torrentId (tor
);
1989 /* build the key to find the row */
1990 g_string_truncate (gstr
, 0);
1991 g_string_append_printf (gstr
, "%d\t%d\t%s", torrent_id
, st
->tier
, st
->announce
);
1993 if (g_hash_table_lookup (hash
, gstr
->str
) == NULL
) {
1996 GtkTreeRowReference
* ref
;
1998 gtk_list_store_insert_with_values (store
, &iter
, -1,
1999 TRACKER_COL_TORRENT_ID
, torrent_id
,
2000 TRACKER_COL_TRACKER_ID
, st
->id
,
2001 TRACKER_COL_KEY
, gstr
->str
,
2004 p
= gtk_tree_model_get_path (model
, &iter
);
2005 ref
= gtk_tree_row_reference_new (model
, p
);
2006 g_hash_table_insert (hash
, g_strdup (gstr
->str
), ref
);
2007 ref
= gtk_tree_row_reference_new (model
, p
);
2008 gtr_get_favicon_from_url (session
, st
->announce
, favicon_ready_cb
, ref
);
2009 gtk_tree_path_free (p
);
2014 /* step 4: update the peers */
2018 const tr_torrent
* tor
= torrents
[i
];
2019 const char * summary_name
= n
>1 ? tr_torrentName (tor
) : NULL
;
2020 for (j
=0; j
<statCount
[i
]; ++j
)
2023 GtkTreeRowReference
* ref
;
2024 const tr_tracker_stat
* st
= &stats
[i
][j
];
2026 /* build the key to find the row */
2027 g_string_truncate (gstr
, 0);
2028 g_string_append_printf (gstr
, "%d\t%d\t%s", tr_torrentId (tor
), st
->tier
, st
->announce
);
2029 ref
= g_hash_table_lookup (hash
, gstr
->str
);
2030 p
= gtk_tree_row_reference_get_path (ref
);
2031 gtk_tree_model_get_iter (model
, &iter
, p
);
2033 /* update the row */
2034 g_string_truncate (gstr
, 0);
2035 buildTrackerSummary (gstr
, summary_name
, st
, showScrape
);
2036 gtk_list_store_set (store
, &iter
, TRACKER_COL_TEXT
, gstr
->str
,
2037 TRACKER_COL_IS_BACKUP
, st
->isBackup
,
2038 TRACKER_COL_TRACKER_ID
, st
->id
,
2039 TRACKER_COL_WAS_UPDATED
, TRUE
,
2043 gtk_tree_path_free (p
);
2047 /* step 5: remove trackers that have disappeared */
2048 if (gtk_tree_model_iter_nth_child (model
, &iter
, NULL
, 0)) {
2049 gboolean more
= TRUE
;
2052 gtk_tree_model_get (model
, &iter
, TRACKER_COL_WAS_UPDATED
, &b
, -1);
2054 more
= gtk_tree_model_iter_next (model
, &iter
);
2057 gtk_tree_model_get (model
, &iter
, TRACKER_COL_KEY
, &key
, -1);
2058 g_hash_table_remove (hash
, key
);
2059 more
= gtk_list_store_remove (store
, &iter
);
2065 gtk_widget_set_sensitive (di
->edit_trackers_button
,
2066 tracker_list_get_current_torrent_id (di
) >= 0);
2070 tr_torrentTrackersFree (stats
[i
], statCount
[i
]);
2076 onScrapeToggled (GtkToggleButton
* button
, struct DetailsImpl
* di
)
2078 const char * key
= PREF_KEY_SHOW_MORE_TRACKER_INFO
;
2079 const gboolean value
= gtk_toggle_button_get_active (button
);
2080 gtr_core_set_pref_bool (di
->core
, key
, value
);
2085 onBackupToggled (GtkToggleButton
* button
, struct DetailsImpl
* di
)
2087 const char * key
= PREF_KEY_SHOW_BACKUP_TRACKERS
;
2088 const gboolean value
= gtk_toggle_button_get_active (button
);
2089 gtr_core_set_pref_bool (di
->core
, key
, value
);
2094 on_edit_trackers_response (GtkDialog
* dialog
, int response
, gpointer data
)
2096 gboolean do_destroy
= TRUE
;
2097 struct DetailsImpl
* di
= data
;
2099 if (response
== GTK_RESPONSE_ACCEPT
)
2103 GtkTextIter start
, end
;
2104 const int torrent_id
= GPOINTER_TO_INT (g_object_get_qdata (G_OBJECT (dialog
), TORRENT_ID_KEY
));
2105 GtkTextBuffer
* text_buffer
= g_object_get_qdata (G_OBJECT (dialog
), TEXT_BUFFER_KEY
);
2106 tr_torrent
* tor
= gtr_core_find_torrent (di
->core
, torrent_id
);
2110 tr_tracker_info
* trackers
;
2111 char ** tracker_strings
;
2112 char * tracker_text
;
2114 /* build the array of trackers */
2115 gtk_text_buffer_get_bounds (text_buffer
, &start
, &end
);
2116 tracker_text
= gtk_text_buffer_get_text (text_buffer
, &start
, &end
, FALSE
);
2117 tracker_strings
= g_strsplit (tracker_text
, "\n", 0);
2118 for (i
=0; tracker_strings
[i
];)
2120 trackers
= g_new0 (tr_tracker_info
, i
);
2121 for (i
=n
=tier
=0; tracker_strings
[i
]; ++i
) {
2122 const char * str
= tracker_strings
[i
];
2126 trackers
[n
].tier
= tier
;
2127 trackers
[n
].announce
= tracker_strings
[i
];
2132 /* update the torrent */
2133 if (tr_torrentSetAnnounceList (tor
, trackers
, n
))
2137 const char * text
= _("List contains invalid URLs");
2138 w
= gtk_message_dialog_new (GTK_WINDOW (dialog
),
2141 GTK_BUTTONS_CLOSE
, "%s", text
);
2142 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (w
), "%s", _("Please correct the errors and try again."));
2143 gtk_dialog_run (GTK_DIALOG (w
));
2144 gtk_widget_destroy (w
);
2150 g_strfreev (tracker_strings
);
2151 g_free (tracker_text
);
2156 gtk_widget_destroy (GTK_WIDGET (dialog
));
2160 get_editable_tracker_list (GString
* gstr
, const tr_torrent
* tor
)
2164 const tr_info
* inf
= tr_torrentInfo (tor
);
2165 for (i
=0; i
<inf
->trackerCount
; ++i
) {
2166 const tr_tracker_info
* t
= &inf
->trackers
[i
];
2167 if (tier
!= t
->tier
) {
2169 g_string_append_c (gstr
, '\n');
2171 g_string_append_printf (gstr
, "%s\n", t
->announce
);
2174 g_string_truncate (gstr
, gstr
->len
-1);
2178 on_edit_trackers (GtkButton
* button
, gpointer data
)
2180 struct DetailsImpl
* di
= data
;
2181 tr_torrent
* tor
= tracker_list_get_current_torrent (di
);
2186 GtkWidget
*w
, *d
, *fr
, *t
, *l
, *sw
;
2187 GtkWindow
* win
= GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (button
)));
2188 GString
* gstr
= di
->gstr
; /* buffer for temporary strings */
2189 const int torrent_id
= tr_torrentId (tor
);
2191 g_string_truncate (gstr
, 0);
2192 g_string_append_printf (gstr
, _("%s - Edit Trackers"), tr_torrentName (tor
));
2193 d
= gtk_dialog_new_with_buttons (gstr
->str
, win
,
2194 GTK_DIALOG_MODAL
|GTK_DIALOG_DESTROY_WITH_PARENT
,
2195 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
2196 GTK_STOCK_SAVE
, GTK_RESPONSE_ACCEPT
,
2198 g_signal_connect (d
, "response", G_CALLBACK (on_edit_trackers_response
), data
);
2201 t
= hig_workarea_create ();
2202 hig_workarea_add_section_title (t
, &row
, _("Tracker Announce URLs"));
2204 l
= gtk_label_new (NULL
);
2205 gtk_label_set_markup (GTK_LABEL (l
), _("To add a backup URL, add it on the line after the primary URL.\n"
2206 "To add another primary URL, add it after a blank line."));
2207 gtk_label_set_justify (GTK_LABEL (l
), GTK_JUSTIFY_LEFT
);
2208 gtk_misc_set_alignment (GTK_MISC (l
), 0.0, 0.5);
2209 hig_workarea_add_wide_control (t
, &row
, l
);
2211 w
= gtk_text_view_new ();
2212 g_string_truncate (gstr
, 0);
2213 get_editable_tracker_list (gstr
, tor
);
2214 gtk_text_buffer_set_text (gtk_text_view_get_buffer (GTK_TEXT_VIEW (w
)), gstr
->str
, -1);
2215 fr
= gtk_frame_new (NULL
);
2216 gtk_frame_set_shadow_type (GTK_FRAME (fr
), GTK_SHADOW_IN
);
2217 sw
= gtk_scrolled_window_new (NULL
, NULL
);
2218 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw
),
2219 GTK_POLICY_AUTOMATIC
,
2220 GTK_POLICY_AUTOMATIC
);
2221 gtk_container_add (GTK_CONTAINER (sw
), w
);
2222 gtk_container_add (GTK_CONTAINER (fr
), sw
);
2223 gtk_widget_set_size_request (fr
, 500u, 166u);
2224 hig_workarea_add_wide_tall_control (t
, &row
, fr
);
2226 gtr_dialog_set_content (GTK_DIALOG (d
), t
);
2228 g_object_set_qdata (G_OBJECT (d
), TORRENT_ID_KEY
, GINT_TO_POINTER (torrent_id
));
2229 g_object_set_qdata (G_OBJECT (d
), TEXT_BUFFER_KEY
, gtk_text_view_get_buffer (GTK_TEXT_VIEW (w
)));
2230 gtk_widget_show (d
);
2235 on_tracker_list_selection_changed (GtkTreeSelection
* sel
, gpointer gdi
)
2237 struct DetailsImpl
* di
= gdi
;
2238 const int n
= gtk_tree_selection_count_selected_rows (sel
);
2239 tr_torrent
* tor
= tracker_list_get_current_torrent (di
);
2241 gtk_widget_set_sensitive (di
->remove_tracker_button
, n
>0);
2242 gtk_widget_set_sensitive (di
->add_tracker_button
, tor
!=NULL
);
2243 gtk_widget_set_sensitive (di
->edit_trackers_button
, tor
!=NULL
);
2247 on_add_tracker_response (GtkDialog
* dialog
, int response
, gpointer gdi
)
2249 gboolean destroy
= TRUE
;
2251 if (response
== GTK_RESPONSE_ACCEPT
)
2253 struct DetailsImpl
* di
= gdi
;
2254 GtkWidget
* e
= GTK_WIDGET (g_object_get_qdata (G_OBJECT (dialog
), URL_ENTRY_KEY
));
2255 const int torrent_id
= GPOINTER_TO_INT (g_object_get_qdata (G_OBJECT (dialog
), TORRENT_ID_KEY
));
2256 char * url
= g_strdup (gtk_entry_get_text (GTK_ENTRY (e
)));
2261 if (tr_urlIsValidTracker (url
))
2263 char * json
= g_strdup_printf (
2265 " \"method\": \"torrent-set\",\n"
2266 " \"arguments\": { \"id\": %d, \"trackerAdd\": [ \"%s\" ] }\n"
2269 gtr_core_exec_json (di
->core
, json
);
2275 gtr_unrecognized_url_dialog (GTK_WIDGET (dialog
), url
);
2284 gtk_widget_destroy (GTK_WIDGET (dialog
));
2288 on_tracker_list_add_button_clicked (GtkButton
* button UNUSED
, gpointer gdi
)
2290 struct DetailsImpl
* di
= gdi
;
2291 tr_torrent
* tor
= tracker_list_get_current_torrent (di
);
2299 GString
* gstr
= di
->gstr
; /* buffer for temporary strings */
2301 g_string_truncate (gstr
, 0);
2302 g_string_append_printf (gstr
, _("%s - Add Tracker"), tr_torrentName (tor
));
2303 w
= gtk_dialog_new_with_buttons (gstr
->str
, GTK_WINDOW (di
->dialog
),
2304 GTK_DIALOG_DESTROY_WITH_PARENT
,
2305 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
2306 GTK_STOCK_ADD
, GTK_RESPONSE_ACCEPT
,
2308 gtk_dialog_set_alternative_button_order (GTK_DIALOG (w
),
2309 GTK_RESPONSE_ACCEPT
,
2310 GTK_RESPONSE_CANCEL
,
2312 g_signal_connect (w
, "response", G_CALLBACK (on_add_tracker_response
), gdi
);
2315 t
= hig_workarea_create ();
2316 hig_workarea_add_section_title (t
, &row
, _("Tracker"));
2317 e
= gtk_entry_new ();
2318 gtk_widget_set_size_request (e
, 400, -1);
2319 gtr_paste_clipboard_url_into_entry (e
);
2320 g_object_set_qdata (G_OBJECT (w
), URL_ENTRY_KEY
, e
);
2321 g_object_set_qdata (G_OBJECT (w
), TORRENT_ID_KEY
, GINT_TO_POINTER (tr_torrentId (tor
)));
2322 hig_workarea_add_row (t
, &row
, _("_Announce URL:"), e
, NULL
);
2323 gtr_dialog_set_content (GTK_DIALOG (w
), t
);
2324 gtk_widget_show_all (w
);
2329 on_tracker_list_remove_button_clicked (GtkButton
* button UNUSED
, gpointer gdi
)
2332 GtkTreeModel
* model
;
2333 struct DetailsImpl
* di
= gdi
;
2334 GtkTreeView
* v
= GTK_TREE_VIEW (di
->tracker_view
);
2335 GtkTreeSelection
* sel
= gtk_tree_view_get_selection (v
);
2337 if (gtk_tree_selection_get_selected (sel
, &model
, &iter
))
2342 gtk_tree_model_get (model
, &iter
, TRACKER_COL_TRACKER_ID
, &tracker_id
,
2343 TRACKER_COL_TORRENT_ID
, &torrent_id
,
2345 json
= g_strdup_printf ("{\n"
2346 " \"method\": \"torrent-set\",\n"
2347 " \"arguments\": { \"id\": %d, \"trackerRemove\": [ %d ] }\n"
2349 torrent_id
, tracker_id
);
2350 gtr_core_exec_json (di
->core
, json
);
2357 tracker_page_new (struct DetailsImpl
* di
)
2360 GtkCellRenderer
* r
;
2361 GtkTreeViewColumn
* c
;
2362 GtkTreeSelection
* sel
;
2363 GtkWidget
*vbox
, *sw
, *w
, *v
, *hbox
;
2364 const int pad
= (GUI_PAD
+ GUI_PAD_BIG
) / 2;
2366 vbox
= gtk_box_new (GTK_ORIENTATION_VERTICAL
, GUI_PAD
);
2367 gtk_container_set_border_width (GTK_CONTAINER (vbox
), GUI_PAD_BIG
);
2369 di
->tracker_store
= gtk_list_store_new (TRACKER_N_COLS
, G_TYPE_INT
,
2376 di
->tracker_hash
= g_hash_table_new_full (g_str_hash
,
2378 (GDestroyNotify
)g_free
,
2379 (GDestroyNotify
)gtk_tree_row_reference_free
);
2380 di
->trackers_filtered
= gtk_tree_model_filter_new (GTK_TREE_MODEL (di
->tracker_store
), NULL
);
2381 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (di
->trackers_filtered
),
2382 trackerVisibleFunc
, di
, NULL
);
2384 hbox
= gtk_box_new (GTK_ORIENTATION_HORIZONTAL
, GUI_PAD_BIG
);
2386 v
= di
->tracker_view
= gtk_tree_view_new_with_model (GTK_TREE_MODEL (di
->trackers_filtered
));
2387 g_object_unref (di
->trackers_filtered
);
2388 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (v
), FALSE
);
2389 g_signal_connect (v
, "button-press-event", G_CALLBACK (on_tree_view_button_pressed
), NULL
);
2390 g_signal_connect (v
, "button-release-event", G_CALLBACK (on_tree_view_button_released
), NULL
);
2391 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (v
), TRUE
);
2393 sel
= gtk_tree_view_get_selection (GTK_TREE_VIEW (v
));
2394 g_signal_connect (sel
, "changed", G_CALLBACK (on_tracker_list_selection_changed
), di
);
2396 c
= gtk_tree_view_column_new ();
2397 gtk_tree_view_column_set_title (c
, _("Trackers"));
2398 gtk_tree_view_append_column (GTK_TREE_VIEW (v
), c
);
2400 r
= gtk_cell_renderer_pixbuf_new ();
2401 g_object_set (r
, "width", 20 + (GUI_PAD_SMALL
*2), "xpad", GUI_PAD_SMALL
, "ypad", pad
, "yalign", 0.0f
, NULL
);
2402 gtk_tree_view_column_pack_start (c
, r
, FALSE
);
2403 gtk_tree_view_column_add_attribute (c
, r
, "pixbuf", TRACKER_COL_FAVICON
);
2405 r
= gtk_cell_renderer_text_new ();
2406 g_object_set (G_OBJECT (r
), "ellipsize", PANGO_ELLIPSIZE_END
, "xpad", GUI_PAD_SMALL
, "ypad", pad
, NULL
);
2407 gtk_tree_view_column_pack_start (c
, r
, TRUE
);
2408 gtk_tree_view_column_add_attribute (c
, r
, "markup", TRACKER_COL_TEXT
);
2410 sw
= gtk_scrolled_window_new (NULL
, NULL
);
2411 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw
), GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
2412 gtk_container_add (GTK_CONTAINER (sw
), v
);
2413 w
= gtk_frame_new (NULL
);
2414 gtk_frame_set_shadow_type (GTK_FRAME (w
), GTK_SHADOW_IN
);
2415 gtk_container_add (GTK_CONTAINER (w
), sw
);
2417 gtk_box_pack_start (GTK_BOX (hbox
), w
, TRUE
, TRUE
, 0);
2419 v
= gtk_box_new (GTK_ORIENTATION_VERTICAL
, GUI_PAD
);
2421 w
= gtk_button_new_with_mnemonic (_("_Add"));
2422 di
->add_tracker_button
= w
;
2423 g_signal_connect (w
, "clicked", G_CALLBACK (on_tracker_list_add_button_clicked
), di
);
2424 gtk_box_pack_start (GTK_BOX (v
), w
, FALSE
, FALSE
, 0);
2426 w
= gtk_button_new_with_mnemonic (_("_Edit"));
2427 gtk_button_set_image (GTK_BUTTON (w
), gtk_image_new_from_stock (GTK_STOCK_EDIT
, GTK_ICON_SIZE_BUTTON
));
2428 g_signal_connect (w
, "clicked", G_CALLBACK (on_edit_trackers
), di
);
2429 di
->edit_trackers_button
= w
;
2430 gtk_box_pack_start (GTK_BOX (v
), w
, FALSE
, FALSE
, 0);
2432 w
= gtk_button_new_with_mnemonic (_("_Remove"));
2433 di
->remove_tracker_button
= w
;
2434 g_signal_connect (w
, "clicked", G_CALLBACK (on_tracker_list_remove_button_clicked
), di
);
2435 gtk_box_pack_start (GTK_BOX (v
), w
, FALSE
, FALSE
, 0);
2437 gtk_box_pack_start (GTK_BOX (hbox
), v
, FALSE
, FALSE
, 0);
2439 gtk_box_pack_start (GTK_BOX (vbox
), hbox
, TRUE
, TRUE
, 0);
2441 w
= gtk_check_button_new_with_mnemonic (_("Show _more details"));
2442 di
->scrape_check
= w
;
2443 b
= gtr_pref_flag_get (PREF_KEY_SHOW_MORE_TRACKER_INFO
);
2444 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w
), b
);
2445 g_signal_connect (w
, "toggled", G_CALLBACK (onScrapeToggled
), di
);
2446 gtk_box_pack_start (GTK_BOX (vbox
), w
, FALSE
, FALSE
, 0);
2448 w
= gtk_check_button_new_with_mnemonic (_("Show _backup trackers"));
2450 b
= gtr_pref_flag_get (PREF_KEY_SHOW_BACKUP_TRACKERS
);
2451 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w
), b
);
2452 g_signal_connect (w
, "toggled", G_CALLBACK (onBackupToggled
), di
);
2453 gtk_box_pack_start (GTK_BOX (vbox
), w
, FALSE
, FALSE
, 0);
2464 refresh (struct DetailsImpl
* di
)
2467 tr_torrent
** torrents
= getTorrents (di
, &n
);
2469 refreshInfo (di
, torrents
, n
);
2470 refreshPeers (di
, torrents
, n
);
2471 refreshTracker (di
, torrents
, n
);
2472 refreshOptions (di
, torrents
, n
);
2475 gtk_dialog_response (GTK_DIALOG (di
->dialog
), GTK_RESPONSE_CLOSE
);
2481 periodic_refresh (gpointer data
)
2488 details_free (gpointer gdata
)
2490 struct DetailsImpl
* data
= gdata
;
2491 g_source_remove (data
->periodic_refresh_tag
);
2492 g_hash_table_destroy (data
->tracker_hash
);
2493 g_hash_table_destroy (data
->webseed_hash
);
2494 g_hash_table_destroy (data
->peer_hash
);
2495 g_string_free (data
->gstr
, TRUE
);
2496 g_slist_free (data
->ids
);
2501 gtr_torrent_details_dialog_new (GtkWindow
* parent
, TrCore
* core
)
2503 GtkWidget
*d
, *n
, *v
, *w
, *l
;
2504 struct DetailsImpl
* di
= g_new0 (struct DetailsImpl
, 1);
2506 /* one-time setup */
2509 ARG_KEY
= g_quark_from_static_string ("tr-arg-key");
2510 DETAILS_KEY
= g_quark_from_static_string ("tr-details-data-key");
2511 TORRENT_ID_KEY
= g_quark_from_static_string ("tr-torrent-id-key");
2512 TEXT_BUFFER_KEY
= g_quark_from_static_string ("tr-text-buffer-key");
2513 URL_ENTRY_KEY
= g_quark_from_static_string ("tr-url-entry-key");
2516 /* create the dialog */
2518 di
->gstr
= g_string_new (NULL
);
2519 d
= gtk_dialog_new_with_buttons (NULL
, parent
, 0,
2520 GTK_STOCK_CLOSE
, GTK_RESPONSE_CLOSE
,
2523 gtk_window_set_role (GTK_WINDOW (d
), "tr-info");
2524 g_signal_connect_swapped (d
, "response",
2525 G_CALLBACK (gtk_widget_destroy
), d
);
2526 gtk_container_set_border_width (GTK_CONTAINER (d
), GUI_PAD
);
2527 g_object_set_qdata_full (G_OBJECT (d
), DETAILS_KEY
, di
, details_free
);
2529 n
= gtk_notebook_new ();
2530 gtk_container_set_border_width (GTK_CONTAINER (n
), GUI_PAD
);
2532 w
= info_page_new (di
);
2533 l
= gtk_label_new (_("Information"));
2534 gtk_notebook_append_page (GTK_NOTEBOOK (n
), w
, l
);
2536 w
= peer_page_new (di
);
2537 l
= gtk_label_new (_("Peers"));
2538 gtk_notebook_append_page (GTK_NOTEBOOK (n
), w
, l
);
2540 w
= tracker_page_new (di
);
2541 l
= gtk_label_new (_("Trackers"));
2542 gtk_notebook_append_page (GTK_NOTEBOOK (n
), w
, l
);
2544 v
= gtk_box_new (GTK_ORIENTATION_VERTICAL
, 0);
2545 di
->file_list
= gtr_file_list_new (core
, 0);
2546 di
->file_label
= gtk_label_new (_("File listing not available for combined torrent properties"));
2547 gtk_box_pack_start (GTK_BOX (v
), di
->file_list
, TRUE
, TRUE
, 0);
2548 gtk_box_pack_start (GTK_BOX (v
), di
->file_label
, TRUE
, TRUE
, 0);
2549 gtk_container_set_border_width (GTK_CONTAINER (v
), GUI_PAD_BIG
);
2550 l
= gtk_label_new (_("Files"));
2551 gtk_notebook_append_page (GTK_NOTEBOOK (n
), v
, l
);
2553 w
= options_page_new (di
);
2554 l
= gtk_label_new (_("Options"));
2555 gtk_notebook_append_page (GTK_NOTEBOOK (n
), w
, l
);
2557 gtr_dialog_set_content (GTK_DIALOG (d
), n
);
2558 di
->periodic_refresh_tag
= gdk_threads_add_timeout_seconds (SECONDARY_WINDOW_REFRESH_INTERVAL_SECONDS
,
2559 periodic_refresh
, di
);
2564 gtr_torrent_details_dialog_set_torrents (GtkWidget
* w
, GSList
* ids
)
2567 const int len
= g_slist_length (ids
);
2568 struct DetailsImpl
* di
= g_object_get_qdata (G_OBJECT (w
), DETAILS_KEY
);
2570 g_slist_free (di
->ids
);
2571 di
->ids
= g_slist_copy (ids
);
2575 const int id
= GPOINTER_TO_INT (ids
->data
);
2576 tr_torrent
* tor
= gtr_core_find_torrent (di
->core
, id
);
2577 const tr_info
* inf
= tr_torrentInfo (tor
);
2578 g_snprintf (title
, sizeof (title
), _("%s Properties"), inf
->name
);
2580 gtr_file_list_set_torrent (di
->file_list
, id
);
2581 gtk_widget_show (di
->file_list
);
2582 gtk_widget_hide (di
->file_label
);
2586 gtr_file_list_clear (di
->file_list
);
2587 gtk_widget_hide (di
->file_list
);
2588 gtk_widget_show (di
->file_label
);
2589 g_snprintf (title
, sizeof (title
), _("%'d Torrent Properties"), len
);
2592 gtk_window_set_title (GTK_WINDOW (w
), title
);