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 12025 2011-02-24 15:04:11Z jordan $
15 #include <stdio.h> /* sscanf */
17 #include <glib/gi18n.h>
20 #include <libtransmission/transmission.h>
21 #include <libtransmission/utils.h> /* tr_free */
26 #include "favicon.h" /* gtr_get_favicon() */
27 #include "file-list.h"
32 #define DETAILS_KEY "details-data"
38 GtkWidget
* honor_limits_check
;
39 GtkWidget
* up_limited_check
;
40 GtkWidget
* up_limit_sping
;
41 GtkWidget
* down_limited_check
;
42 GtkWidget
* down_limit_spin
;
43 GtkWidget
* bandwidth_combo
;
45 GtkWidget
* ratio_combo
;
46 GtkWidget
* ratio_spin
;
47 GtkWidget
* idle_combo
;
48 GtkWidget
* idle_spin
;
49 GtkWidget
* max_peers_spin
;
51 guint honor_limits_check_tag
;
52 guint up_limited_check_tag
;
53 guint down_limited_check_tag
;
54 guint down_limit_spin_tag
;
55 guint up_limit_spin_tag
;
56 guint bandwidth_combo_tag
;
57 guint ratio_combo_tag
;
61 guint max_peers_spin_tag
;
69 GtkWidget
* date_started_lb
;
71 GtkWidget
* last_activity_lb
;
74 GtkWidget
* privacy_lb
;
75 GtkWidget
* origin_lb
;
76 GtkWidget
* destination_lb
;
77 GtkTextBuffer
* comment_buffer
;
79 GHashTable
* peer_hash
;
80 GHashTable
* webseed_hash
;
81 GtkListStore
* peer_store
;
82 GtkListStore
* webseed_store
;
83 GtkWidget
* webseed_view
;
84 GtkWidget
* peer_view
;
85 GtkWidget
* more_peer_details_check
;
87 GtkListStore
* tracker_store
;
88 GHashTable
* tracker_hash
;
89 GtkTreeModel
* trackers_filtered
;
90 GtkWidget
* add_tracker_button
;
91 GtkWidget
* edit_trackers_button
;
92 GtkWidget
* remove_tracker_button
;
93 GtkWidget
* tracker_view
;
94 GtkWidget
* scrape_check
;
95 GtkWidget
* all_check
;
97 GtkWidget
* file_list
;
98 GtkWidget
* file_label
;
102 guint periodic_refresh_tag
;
106 getTorrents( struct DetailsImpl
* d
, int * setmeCount
)
108 int n
= g_slist_length( d
->ids
);
109 int torrentCount
= 0;
110 tr_session
* session
= tr_core_session( d
->core
);
111 tr_torrent
** torrents
= NULL
;
113 if( session
!= NULL
)
117 torrents
= g_new( tr_torrent
*, n
);
119 for( l
=d
->ids
; l
!=NULL
; l
=l
->next
) {
120 const int id
= GPOINTER_TO_INT( l
->data
);
121 tr_torrent
* tor
= tr_torrentFindFromId( session
, id
);
123 torrents
[torrentCount
++] = tor
;
127 *setmeCount
= torrentCount
;
138 set_togglebutton_if_different( GtkWidget
* w
, guint tag
, gboolean value
)
140 GtkToggleButton
* toggle
= GTK_TOGGLE_BUTTON( w
);
141 const gboolean currentValue
= gtk_toggle_button_get_active( toggle
);
142 if( currentValue
!= value
)
144 g_signal_handler_block( toggle
, tag
);
145 gtk_toggle_button_set_active( toggle
, value
);
146 g_signal_handler_unblock( toggle
, tag
);
151 set_int_spin_if_different( GtkWidget
* w
, guint tag
, int value
)
153 GtkSpinButton
* spin
= GTK_SPIN_BUTTON( w
);
154 const int currentValue
= gtk_spin_button_get_value_as_int( spin
);
155 if( currentValue
!= value
)
157 g_signal_handler_block( spin
, tag
);
158 gtk_spin_button_set_value( spin
, value
);
159 g_signal_handler_unblock( spin
, tag
);
164 set_double_spin_if_different( GtkWidget
* w
, guint tag
, double value
)
166 GtkSpinButton
* spin
= GTK_SPIN_BUTTON( w
);
167 const double currentValue
= gtk_spin_button_get_value( spin
);
168 if( ( (int)(currentValue
*100) != (int)(value
*100) ) )
170 g_signal_handler_block( spin
, tag
);
171 gtk_spin_button_set_value( spin
, value
);
172 g_signal_handler_unblock( spin
, tag
);
177 unset_combo( GtkWidget
* w
, guint tag
)
179 GtkComboBox
* combobox
= GTK_COMBO_BOX( w
);
181 g_signal_handler_block( combobox
, tag
);
182 gtk_combo_box_set_active( combobox
, -1 );
183 g_signal_handler_unblock( combobox
, tag
);
187 refreshOptions( struct DetailsImpl
* di
, tr_torrent
** torrents
, int n
)
193 /* honor_limits_check */
195 const tr_bool baseline
= tr_torrentUsesSessionLimits( torrents
[0] );
198 if( baseline
!= tr_torrentUsesSessionLimits( torrents
[i
] ) )
201 set_togglebutton_if_different( di
->honor_limits_check
,
202 di
->honor_limits_check_tag
, baseline
);
205 /* down_limited_check */
207 const tr_bool baseline
= tr_torrentUsesSpeedLimit( torrents
[0], TR_DOWN
);
210 if( baseline
!= tr_torrentUsesSpeedLimit( torrents
[i
], TR_DOWN
) )
213 set_togglebutton_if_different( di
->down_limited_check
,
214 di
->down_limited_check_tag
, baseline
);
217 /* down_limit_spin */
219 const int baseline
= tr_torrentGetSpeedLimit_KBps( torrents
[0], TR_DOWN
);
222 if( baseline
!= ( tr_torrentGetSpeedLimit_KBps( torrents
[i
], TR_DOWN
) ) )
225 set_int_spin_if_different( di
->down_limit_spin
,
226 di
->down_limit_spin_tag
, baseline
);
229 /* up_limited_check */
231 const tr_bool baseline
= tr_torrentUsesSpeedLimit( torrents
[0], TR_UP
);
234 if( baseline
!= tr_torrentUsesSpeedLimit( torrents
[i
], TR_UP
) )
237 set_togglebutton_if_different( di
->up_limited_check
,
238 di
->up_limited_check_tag
, baseline
);
243 const int baseline
= tr_torrentGetSpeedLimit_KBps( torrents
[0], TR_UP
);
246 if( baseline
!= ( tr_torrentGetSpeedLimit_KBps( torrents
[i
], TR_UP
) ) )
249 set_int_spin_if_different( di
->up_limit_sping
,
250 di
->up_limit_spin_tag
, baseline
);
253 /* bandwidth_combo */
255 const int baseline
= tr_torrentGetPriority( torrents
[0] );
258 if( baseline
!= tr_torrentGetPriority( torrents
[i
] ) )
261 GtkWidget
* w
= di
->bandwidth_combo
;
262 g_signal_handler_block( w
, di
->bandwidth_combo_tag
);
263 gtr_priority_combo_set_value( GTK_COMBO_BOX( w
), baseline
);
264 g_signal_handler_unblock( w
, di
->bandwidth_combo_tag
);
267 unset_combo( di
->bandwidth_combo
, di
->bandwidth_combo_tag
);
274 const int baseline
= tr_torrentGetRatioMode( torrents
[0] );
276 if( baseline
!= (int)tr_torrentGetRatioMode( torrents
[i
] ) )
279 GtkWidget
* w
= di
->ratio_combo
;
280 g_signal_handler_block( w
, di
->ratio_combo_tag
);
281 gtr_combo_box_set_active_enum( GTK_COMBO_BOX( w
), baseline
);
282 gtr_widget_set_visible( di
->ratio_spin
, baseline
== TR_RATIOLIMIT_SINGLE
);
283 g_signal_handler_unblock( w
, di
->ratio_combo_tag
);
287 const double baseline
= tr_torrentGetRatioLimit( torrents
[0] );
288 set_double_spin_if_different( di
->ratio_spin
,
289 di
->ratio_spin_tag
, baseline
);
296 const int baseline
= tr_torrentGetIdleMode( torrents
[0] );
298 if( baseline
!= (int)tr_torrentGetIdleMode( torrents
[i
] ) )
301 GtkWidget
* w
= di
->idle_combo
;
302 g_signal_handler_block( w
, di
->idle_combo_tag
);
303 gtr_combo_box_set_active_enum( GTK_COMBO_BOX( w
), baseline
);
304 gtr_widget_set_visible( di
->idle_spin
, baseline
== TR_IDLELIMIT_SINGLE
);
305 g_signal_handler_unblock( w
, di
->idle_combo_tag
);
309 const int baseline
= tr_torrentGetIdleLimit( torrents
[0] );
310 set_int_spin_if_different( di
->idle_spin
,
311 di
->idle_spin_tag
, baseline
);
316 const int baseline
= tr_torrentGetPeerLimit( torrents
[0] );
317 set_int_spin_if_different( di
->max_peers_spin
,
318 di
->max_peers_spin_tag
, baseline
);
323 torrent_set_bool( struct DetailsImpl
* di
, const char * key
, gboolean value
)
326 tr_benc top
, *args
, *ids
;
328 tr_bencInitDict( &top
, 2 );
329 tr_bencDictAddStr( &top
, "method", "torrent-set" );
330 args
= tr_bencDictAddDict( &top
, "arguments", 2 );
331 tr_bencDictAddBool( args
, key
, value
);
332 ids
= tr_bencDictAddList( args
, "ids", g_slist_length(di
->ids
) );
333 for( l
=di
->ids
; l
; l
=l
->next
)
334 tr_bencListAddInt( ids
, GPOINTER_TO_INT( l
->data
) );
336 tr_core_exec( di
->core
, &top
);
341 torrent_set_int( struct DetailsImpl
* di
, const char * key
, int value
)
344 tr_benc top
, *args
, *ids
;
346 tr_bencInitDict( &top
, 2 );
347 tr_bencDictAddStr( &top
, "method", "torrent-set" );
348 args
= tr_bencDictAddDict( &top
, "arguments", 2 );
349 tr_bencDictAddInt( args
, key
, value
);
350 ids
= tr_bencDictAddList( args
, "ids", g_slist_length(di
->ids
) );
351 for( l
=di
->ids
; l
; l
=l
->next
)
352 tr_bencListAddInt( ids
, GPOINTER_TO_INT( l
->data
) );
354 tr_core_exec( di
->core
, &top
);
359 torrent_set_real( struct DetailsImpl
* di
, const char * key
, double value
)
362 tr_benc top
, *args
, *ids
;
364 tr_bencInitDict( &top
, 2 );
365 tr_bencDictAddStr( &top
, "method", "torrent-set" );
366 args
= tr_bencDictAddDict( &top
, "arguments", 2 );
367 tr_bencDictAddReal( args
, key
, value
);
368 ids
= tr_bencDictAddList( args
, "ids", g_slist_length(di
->ids
) );
369 for( l
=di
->ids
; l
; l
=l
->next
)
370 tr_bencListAddInt( ids
, GPOINTER_TO_INT( l
->data
) );
372 tr_core_exec( di
->core
, &top
);
377 up_speed_toggled_cb( GtkToggleButton
* tb
, gpointer d
)
379 torrent_set_bool( d
, "uploadLimited", gtk_toggle_button_get_active( tb
) );
383 down_speed_toggled_cb( GtkToggleButton
*tb
, gpointer d
)
385 torrent_set_bool( d
, "downloadLimited", gtk_toggle_button_get_active( tb
) );
389 global_speed_toggled_cb( GtkToggleButton
* tb
, gpointer d
)
391 torrent_set_bool( d
, "honorsSessionLimits", gtk_toggle_button_get_active( tb
) );
395 up_speed_spun_cb( GtkSpinButton
* s
, struct DetailsImpl
* di
)
397 torrent_set_int( di
, "uploadLimit", gtk_spin_button_get_value_as_int( s
) );
401 down_speed_spun_cb( GtkSpinButton
* s
, struct DetailsImpl
* di
)
403 torrent_set_int( di
, "downloadLimit", gtk_spin_button_get_value_as_int( s
) );
407 idle_spun_cb( GtkSpinButton
* s
, struct DetailsImpl
* di
)
409 torrent_set_int( di
, "seedIdleLimit", gtk_spin_button_get_value_as_int( s
) );
413 ratio_spun_cb( GtkSpinButton
* s
, struct DetailsImpl
* di
)
415 torrent_set_real( di
, "seedRatioLimit", gtk_spin_button_get_value( s
) );
419 max_peers_spun_cb( GtkSpinButton
* s
, struct DetailsImpl
* di
)
421 torrent_set_int( di
, "peer-limit", gtk_spin_button_get_value( s
) );
425 onPriorityChanged( GtkComboBox
* combo_box
, struct DetailsImpl
* di
)
427 const tr_priority_t priority
= gtr_priority_combo_get_value( combo_box
);
428 torrent_set_int( di
, "bandwidthPriority", priority
);
432 new_priority_combo( struct DetailsImpl
* di
)
434 GtkWidget
* w
= gtr_priority_combo_new( );
435 di
->bandwidth_combo_tag
= g_signal_connect( w
, "changed", G_CALLBACK( onPriorityChanged
), di
);
439 static void refresh( struct DetailsImpl
* di
);
441 #define ARG_KEY "arg-key"
444 onComboEnumChanged( GtkComboBox
* combo_box
, struct DetailsImpl
* di
)
446 const char * key
= g_object_get_data( G_OBJECT( combo_box
), ARG_KEY
);
447 torrent_set_int( di
, key
, gtr_combo_box_get_active_enum( combo_box
) );
452 ratio_combo_new( void )
454 GtkWidget
* w
= gtr_combo_box_new_enum(
455 _( "Use global settings" ), TR_RATIOLIMIT_GLOBAL
,
456 _( "Seed regardless of ratio" ), TR_RATIOLIMIT_UNLIMITED
,
457 _( "Stop seeding at ratio:" ), TR_RATIOLIMIT_SINGLE
,
459 g_object_set_data_full( G_OBJECT( w
), ARG_KEY
, g_strdup( "seedRatioMode" ), g_free
);
464 idle_combo_new( void )
466 GtkWidget
* w
= gtr_combo_box_new_enum (
467 _( "Use global settings" ), TR_IDLELIMIT_GLOBAL
,
468 _( "Seed regardless of activity" ), TR_IDLELIMIT_UNLIMITED
,
469 _( "Stop seeding if idle for N minutes:" ), TR_IDLELIMIT_SINGLE
,
471 g_object_set_data_full( G_OBJECT( w
), ARG_KEY
, g_strdup( "seedIdleMode" ), g_free
);
476 options_page_new( struct DetailsImpl
* d
)
481 GtkWidget
*t
, *w
, *tb
, *h
;
484 t
= hig_workarea_create( );
485 hig_workarea_add_section_title( t
, &row
, _( "Speed" ) );
487 tb
= hig_workarea_add_wide_checkbutton( t
, &row
, _( "Honor global _limits" ), 0 );
488 d
->honor_limits_check
= tb
;
489 tag
= g_signal_connect( tb
, "toggled", G_CALLBACK( global_speed_toggled_cb
), d
);
490 d
->honor_limits_check_tag
= tag
;
492 g_snprintf( buf
, sizeof( buf
), _( "Limit _download speed (%s):" ), _(speed_K_str
) );
493 tb
= gtk_check_button_new_with_mnemonic( buf
);
494 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( tb
), FALSE
);
495 d
->down_limited_check
= tb
;
496 tag
= g_signal_connect( tb
, "toggled", G_CALLBACK( down_speed_toggled_cb
), d
);
497 d
->down_limited_check_tag
= tag
;
499 w
= gtk_spin_button_new_with_range( 0, INT_MAX
, 5 );
500 tag
= g_signal_connect( w
, "value-changed", G_CALLBACK( down_speed_spun_cb
), d
);
501 d
->down_limit_spin_tag
= tag
;
502 hig_workarea_add_row_w( t
, &row
, tb
, w
, NULL
);
503 d
->down_limit_spin
= w
;
505 g_snprintf( buf
, sizeof( buf
), _( "Limit _upload speed (%s):" ), _(speed_K_str
) );
506 tb
= gtk_check_button_new_with_mnemonic( buf
);
507 d
->up_limited_check
= tb
;
508 tag
= g_signal_connect( tb
, "toggled", G_CALLBACK( up_speed_toggled_cb
), d
);
509 d
->up_limited_check_tag
= tag
;
511 w
= gtk_spin_button_new_with_range( 0, INT_MAX
, 5 );
512 tag
= g_signal_connect( w
, "value-changed", G_CALLBACK( up_speed_spun_cb
), d
);
513 d
->up_limit_spin_tag
= tag
;
514 hig_workarea_add_row_w( t
, &row
, tb
, w
, NULL
);
515 d
->up_limit_sping
= w
;
517 w
= new_priority_combo( d
);
518 hig_workarea_add_row( t
, &row
, _( "Torrent _priority:" ), w
, NULL
);
519 d
->bandwidth_combo
= w
;
521 hig_workarea_add_section_divider( t
, &row
);
522 hig_workarea_add_section_title( t
, &row
, _( "Seeding Limits" ) );
524 h
= gtk_hbox_new( FALSE
, GUI_PAD
);
525 w
= d
->ratio_combo
= ratio_combo_new( );
526 d
->ratio_combo_tag
= g_signal_connect( w
, "changed", G_CALLBACK( onComboEnumChanged
), d
);
527 gtk_box_pack_start( GTK_BOX( h
), w
, TRUE
, TRUE
, 0 );
528 w
= d
->ratio_spin
= gtk_spin_button_new_with_range( 0, 1000, .05 );
529 gtk_entry_set_width_chars( GTK_ENTRY( w
), 7 );
530 d
->ratio_spin_tag
= g_signal_connect( w
, "value-changed", G_CALLBACK( ratio_spun_cb
), d
);
531 gtk_box_pack_start( GTK_BOX( h
), w
, FALSE
, FALSE
, 0 );
532 hig_workarea_add_row( t
, &row
, _( "_Ratio:" ), h
, NULL
);
534 h
= gtk_hbox_new( FALSE
, GUI_PAD
);
535 w
= d
->idle_combo
= idle_combo_new( );
536 d
->idle_combo_tag
= g_signal_connect( w
, "changed", G_CALLBACK( onComboEnumChanged
), d
);
537 gtk_box_pack_start( GTK_BOX( h
), w
, TRUE
, TRUE
, 0 );
538 w
= d
->idle_spin
= gtk_spin_button_new_with_range( 1, INT_MAX
, 5 );
539 d
->idle_spin_tag
= g_signal_connect( w
, "value-changed", G_CALLBACK( idle_spun_cb
), d
);
540 gtk_box_pack_start( GTK_BOX( h
), w
, FALSE
, FALSE
, 0 );
541 hig_workarea_add_row( t
, &row
, _( "_Idle:" ), h
, NULL
);
543 hig_workarea_add_section_divider( t
, &row
);
544 hig_workarea_add_section_title( t
, &row
, _( "Peer Connections" ) );
546 w
= gtk_spin_button_new_with_range( 1, 3000, 5 );
547 hig_workarea_add_row( t
, &row
, _( "_Maximum peers:" ), w
, w
);
548 tag
= g_signal_connect( w
, "value-changed", G_CALLBACK( max_peers_spun_cb
), d
);
549 d
->max_peers_spin
= w
;
550 d
->max_peers_spin_tag
= tag
;
552 hig_workarea_finish( t
, &row
);
563 activityString( int activity
, tr_bool finished
)
567 case TR_STATUS_CHECK_WAIT
: return _( "Waiting to verify local data" );
568 case TR_STATUS_CHECK
: return _( "Verifying local data" );
569 case TR_STATUS_DOWNLOAD
: return _( "Downloading" );
570 case TR_STATUS_SEED
: return _( "Seeding" );
571 case TR_STATUS_STOPPED
: return finished
? _( "Finished" ) : _( "Paused" );
577 /* Only call gtk_text_buffer_set_text() if the new text differs from the old.
578 * This way if the user has text selected, refreshing won't deselect it */
580 gtr_text_buffer_set_text( GtkTextBuffer
* b
, const char * str
)
583 GtkTextIter start
, end
;
588 gtk_text_buffer_get_bounds( b
, &start
, &end
);
589 old_str
= gtk_text_buffer_get_text( b
, &start
, &end
, FALSE
);
591 if( ( old_str
== NULL
) || strcmp( old_str
, str
) )
592 gtk_text_buffer_set_text( b
, str
, -1 );
598 get_short_date_string( time_t t
)
604 return g_strdup( _( "N/A" ) );
606 tr_localtime_r( &t
, &tm
);
607 strftime( buf
, sizeof( buf
), "%d %b %Y", &tm
);
608 return g_locale_to_utf8( buf
, -1, NULL
, NULL
, NULL
);
612 refreshInfo( struct DetailsImpl
* di
, tr_torrent
** torrents
, int n
)
616 const char * mixed
= _( "Mixed" );
617 const char * no_torrent
= _( "No Torrents Selected" );
618 const char * stateString
;
620 uint64_t sizeWhenDone
= 0;
621 const tr_stat
** stats
= g_new( const tr_stat
*, n
);
622 const tr_info
** infos
= g_new( const tr_info
*, n
);
623 for( i
=0; i
<n
; ++i
) {
624 stats
[i
] = tr_torrentStatCached( torrents
[i
] );
625 infos
[i
] = tr_torrentInfo( torrents
[i
] );
632 const tr_bool baseline
= infos
[0]->isPrivate
;
634 if( baseline
!= infos
[i
]->isPrivate
)
639 str
= _( "Private to this tracker -- DHT and PEX disabled" );
641 str
= _( "Public torrent" );
643 gtr_label_set_text( GTK_LABEL( di
->privacy_lb
), str
);
650 const char * creator
= infos
[0]->creator
? infos
[0]->creator
: "";
651 const time_t date
= infos
[0]->dateCreated
;
652 char * datestr
= get_short_date_string( date
);
653 gboolean mixed_creator
= FALSE
;
654 gboolean mixed_date
= FALSE
;
656 for( i
=1; i
<n
; ++i
) {
657 mixed_creator
|= strcmp( creator
, infos
[i
]->creator
? infos
[i
]->creator
: "" );
658 mixed_date
|= ( date
!= infos
[i
]->dateCreated
);
660 if( mixed_date
&& mixed_creator
)
664 g_snprintf( buf
, sizeof( buf
), _( "Created by %1$s" ), creator
);
665 else if( mixed_creator
|| !*creator
)
666 g_snprintf( buf
, sizeof( buf
), _( "Created on %1$s" ), datestr
);
668 g_snprintf( buf
, sizeof( buf
), _( "Created by %1$s on %2$s" ), creator
, datestr
);
674 gtr_label_set_text( GTK_LABEL( di
->origin_lb
), str
);
681 const char * baseline
= infos
[0]->comment
? infos
[0]->comment
: "";
683 if( strcmp( baseline
, infos
[i
]->comment
? infos
[i
]->comment
: "" ) )
690 gtr_text_buffer_set_text( di
->comment_buffer
, str
);
696 const char * baseline
= tr_torrentGetDownloadDir( torrents
[0] );
698 if( strcmp( baseline
, tr_torrentGetDownloadDir( torrents
[i
] ) ) )
705 gtr_label_set_text( GTK_LABEL( di
->destination_lb
), str
);
711 const tr_torrent_activity activity
= stats
[0]->activity
;
712 tr_bool allFinished
= stats
[0]->finished
;
713 for( i
=1; i
<n
; ++i
) {
714 if( activity
!= stats
[i
]->activity
)
716 if( !stats
[i
]->finished
)
719 str
= i
<n
? mixed
: activityString( activity
, allFinished
);
722 gtr_label_set_text( GTK_LABEL( di
->state_lb
), str
);
729 const time_t baseline
= stats
[0]->startDate
;
731 if( baseline
!= stats
[i
]->startDate
)
735 else if( ( baseline
<=0 ) || ( stats
[0]->activity
== TR_STATUS_STOPPED
) )
738 str
= tr_strltime( buf
, time(NULL
)-baseline
, sizeof( buf
) );
740 gtr_label_set_text( GTK_LABEL( di
->date_started_lb
), str
);
747 const int baseline
= stats
[0]->eta
;
749 if( baseline
!= stats
[i
]->eta
)
753 else if( baseline
< 0 )
754 str
= _( "Unknown" );
756 str
= tr_strltime( buf
, baseline
, sizeof( buf
) );
758 gtr_label_set_text( GTK_LABEL( di
->eta_lb
), str
);
766 int32_t pieceSize
= 0;
767 for( i
=0; i
<n
; ++i
) {
768 size
+= infos
[i
]->totalSize
;
769 pieces
+= infos
[i
]->pieceCount
;
771 pieceSize
= infos
[i
]->pieceSize
;
772 else if( pieceSize
!= (int)infos
[i
]->pieceSize
)
775 tr_strlsize( sizebuf
, size
, sizeof( sizebuf
) );
778 else if( pieceSize
>= 0 ) {
780 tr_formatter_mem_B( piecebuf
, pieceSize
, sizeof( piecebuf
) );
781 g_snprintf( buf
, sizeof( buf
),
782 gtr_ngettext( "%1$s (%2$'d piece @ %3$s)",
783 "%1$s (%2$'d pieces @ %3$s)", pieces
),
784 sizebuf
, pieces
, piecebuf
);
787 g_snprintf( buf
, sizeof( buf
),
788 gtr_ngettext( "%1$s (%2$'d piece)",
789 "%1$s (%2$'d pieces)", pieces
),
793 gtr_label_set_text( GTK_LABEL( di
->size_lb
), str
);
801 uint64_t leftUntilDone
= 0;
802 uint64_t haveUnchecked
= 0;
803 uint64_t haveValid
= 0;
804 uint32_t verifiedPieces
= 0;
805 uint64_t available
= 0;
806 for( i
=0; i
<n
; ++i
) {
807 const tr_stat
* st
= stats
[i
];
808 const tr_info
* inf
= infos
[i
];
809 haveUnchecked
+= st
->haveUnchecked
;
810 haveValid
+= st
->haveValid
;
811 verifiedPieces
+= inf
->pieceSize
? st
->haveValid
/ inf
->pieceSize
: 0;
812 sizeWhenDone
+= st
->sizeWhenDone
;
813 leftUntilDone
+= st
->leftUntilDone
;
814 available
+= st
->sizeWhenDone
- st
->leftUntilDone
+ st
->desiredAvailable
;
817 char buf2
[32], unver
[64], total
[64], avail
[32];
818 const double d
= sizeWhenDone
? ( 100.0 * available
) / sizeWhenDone
: 0;
819 const double ratio
= 100.0 * ( sizeWhenDone
? ( haveValid
+ haveUnchecked
) / (double)sizeWhenDone
: 1 );
820 tr_strlpercent( avail
, d
, sizeof( avail
) );
821 tr_strlpercent( buf2
, ratio
, sizeof( buf2
) );
822 tr_strlsize( total
, haveUnchecked
+ haveValid
, sizeof( total
) );
823 tr_strlsize( unver
, haveUnchecked
, sizeof( unver
) );
824 if( !haveUnchecked
&& !leftUntilDone
)
825 g_snprintf( buf
, sizeof( buf
), _( "%1$s (%2$s%%)" ), total
, buf2
);
826 else if( !haveUnchecked
)
827 g_snprintf( buf
, sizeof( buf
), _( "%1$s (%2$s%% of %3$s%% Available)" ), total
, buf2
, avail
);
829 g_snprintf( buf
, sizeof( buf
), _( "%1$s (%2$s%% of %3$s%% Available); %4$s Unverified" ), total
, buf2
, avail
, unver
);
833 gtr_label_set_text( GTK_LABEL( di
->have_lb
), str
);
839 char dbuf
[64], fbuf
[64];
841 for( i
=0; i
<n
; ++i
) {
842 d
+= stats
[i
]->downloadedEver
;
843 f
+= stats
[i
]->corruptEver
;
845 tr_strlsize( dbuf
, d
, sizeof( dbuf
) );
846 tr_strlsize( fbuf
, f
, sizeof( fbuf
) );
848 g_snprintf( buf
, sizeof( buf
), _( "%1$s (+%2$s corrupt)" ), dbuf
, fbuf
);
850 tr_strlcpy( buf
, dbuf
, sizeof( buf
) );
853 gtr_label_set_text( GTK_LABEL( di
->dl_lb
), str
);
864 for( i
=0; i
<n
; ++i
) {
865 up
+= stats
[i
]->uploadedEver
;
866 down
+= stats
[i
]->downloadedEver
;
868 tr_strlsize( upstr
, up
, sizeof( upstr
) );
869 tr_strlratio( ratiostr
, tr_getRatio( up
, down
), sizeof( ratiostr
) );
870 g_snprintf( buf
, sizeof( buf
), _( "%s (Ratio: %s)" ), upstr
, ratiostr
);
873 gtr_label_set_text( GTK_LABEL( di
->ul_lb
), str
);
879 str
= infos
[0]->hashString
;
882 gtr_label_set_text( GTK_LABEL( di
->hash_lb
), str
);
888 const char * baseline
= stats
[0]->errorString
;
890 if( strcmp( baseline
, stats
[i
]->errorString
) )
898 str
= _( "No errors" );
899 gtr_label_set_text( GTK_LABEL( di
->error_lb
), str
);
908 if( latest
< stats
[i
]->activityDate
)
909 latest
= stats
[i
]->activityDate
;
913 const int period
= time( NULL
) - latest
;
915 tr_strlcpy( buf
, _( "Active now" ), sizeof( buf
) );
918 tr_strltime( tbuf
, period
, sizeof( tbuf
) );
919 g_snprintf( buf
, sizeof( buf
), _( "%1$s ago" ), tbuf
);
924 gtr_label_set_text( GTK_LABEL( di
->last_activity_lb
), str
);
931 info_page_new( struct DetailsImpl
* di
)
935 GtkWidget
*l
, *w
, *fr
, *sw
;
936 GtkWidget
*t
= hig_workarea_create( );
938 hig_workarea_add_section_title( t
, &row
, _( "Activity" ) );
941 l
= di
->size_lb
= gtk_label_new( NULL
);
942 gtk_label_set_single_line_mode( GTK_LABEL( l
), TRUE
);
943 hig_workarea_add_row( t
, &row
, _( "Torrent size:" ), l
, NULL
);
946 l
= di
->have_lb
= gtk_label_new( NULL
);
947 gtk_label_set_single_line_mode( GTK_LABEL( l
), TRUE
);
948 hig_workarea_add_row( t
, &row
, _( "Have:" ), l
, NULL
);
951 l
= di
->dl_lb
= gtk_label_new( NULL
);
952 gtk_label_set_single_line_mode( GTK_LABEL( l
), TRUE
);
953 hig_workarea_add_row( t
, &row
, _( "Downloaded:" ), l
, NULL
);
956 l
= di
->ul_lb
= gtk_label_new( NULL
);
957 gtk_label_set_single_line_mode( GTK_LABEL( l
), TRUE
);
958 hig_workarea_add_row( t
, &row
, _( "Uploaded:" ), l
, NULL
);
961 l
= di
->state_lb
= gtk_label_new( NULL
);
962 gtk_label_set_single_line_mode( GTK_LABEL( l
), TRUE
);
963 hig_workarea_add_row( t
, &row
, _( "State:" ), l
, NULL
);
966 l
= di
->date_started_lb
= gtk_label_new( NULL
);
967 gtk_label_set_single_line_mode( GTK_LABEL( l
), TRUE
);
968 hig_workarea_add_row( t
, &row
, _( "Running time:" ), l
, NULL
);
971 l
= di
->eta_lb
= gtk_label_new( NULL
);
972 gtk_label_set_single_line_mode( GTK_LABEL( l
), TRUE
);
973 hig_workarea_add_row( t
, &row
, _( "Remaining time:" ), l
, NULL
);
976 l
= di
->last_activity_lb
= gtk_label_new( NULL
);
977 gtk_label_set_single_line_mode( GTK_LABEL( l
), TRUE
);
978 hig_workarea_add_row( t
, &row
, _( "Last activity:" ), l
, NULL
);
981 l
= g_object_new( GTK_TYPE_LABEL
, "selectable", TRUE
,
982 "ellipsize", PANGO_ELLIPSIZE_END
,
984 hig_workarea_add_row( t
, &row
, _( "Error:" ), l
, NULL
);
988 hig_workarea_add_section_divider( t
, &row
);
989 hig_workarea_add_section_title( t
, &row
, _( "Details" ) );
992 l
= g_object_new( GTK_TYPE_LABEL
, "selectable", TRUE
,
993 "ellipsize", PANGO_ELLIPSIZE_END
,
995 hig_workarea_add_row( t
, &row
, _( "Location:" ), l
, NULL
);
996 di
->destination_lb
= l
;
999 l
= g_object_new( GTK_TYPE_LABEL
, "selectable", TRUE
,
1000 "ellipsize", PANGO_ELLIPSIZE_END
,
1002 hig_workarea_add_row( t
, &row
, _( "Hash:" ), l
, NULL
);
1006 l
= gtk_label_new( NULL
);
1007 gtk_label_set_single_line_mode( GTK_LABEL( l
), TRUE
);
1008 hig_workarea_add_row( t
, &row
, _( "Privacy:" ), l
, NULL
);
1012 l
= g_object_new( GTK_TYPE_LABEL
, "selectable", TRUE
,
1013 "ellipsize", PANGO_ELLIPSIZE_END
,
1015 hig_workarea_add_row( t
, &row
, _( "Origin:" ), l
, NULL
);
1019 b
= di
->comment_buffer
= gtk_text_buffer_new( NULL
);
1020 w
= gtk_text_view_new_with_buffer( b
);
1021 gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW( w
), GTK_WRAP_WORD
);
1022 gtk_text_view_set_editable( GTK_TEXT_VIEW( w
), FALSE
);
1023 sw
= gtk_scrolled_window_new( NULL
, NULL
);
1024 gtk_widget_set_size_request( sw
, 350, 36 );
1025 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( sw
),
1026 GTK_POLICY_AUTOMATIC
,
1027 GTK_POLICY_AUTOMATIC
);
1028 gtk_container_add( GTK_CONTAINER( sw
), w
);
1029 fr
= gtk_frame_new( NULL
);
1030 gtk_frame_set_shadow_type( GTK_FRAME( fr
), GTK_SHADOW_IN
);
1031 gtk_container_add( GTK_CONTAINER( fr
), sw
);
1032 w
= hig_workarea_add_tall_row( t
, &row
, _( "Comment:" ), fr
, NULL
);
1033 gtk_misc_set_alignment( GTK_MISC( w
), 0.0f
, 0.0f
);
1035 hig_workarea_add_section_divider( t
, &row
);
1036 hig_workarea_finish( t
, &row
);
1039 hig_workarea_finish( t
, &row
);
1052 WEBSEED_COL_WAS_UPDATED
,
1054 WEBSEED_COL_DOWNLOAD_RATE_DOUBLE
,
1055 WEBSEED_COL_DOWNLOAD_RATE_STRING
,
1060 getWebseedColumnNames( int column
)
1064 case WEBSEED_COL_URL
: return _( "Webseeds" );
1065 case WEBSEED_COL_DOWNLOAD_RATE_DOUBLE
:
1066 case WEBSEED_COL_DOWNLOAD_RATE_STRING
: return _( "Down" );
1071 static GtkListStore
*
1072 webseed_model_new( void )
1074 return gtk_list_store_new( N_WEBSEED_COLS
,
1075 G_TYPE_STRING
, /* key */
1076 G_TYPE_BOOLEAN
, /* was-updated */
1077 G_TYPE_STRING
, /* url */
1078 G_TYPE_DOUBLE
, /* download rate double */
1079 G_TYPE_STRING
); /* download rate string */
1085 PEER_COL_WAS_UPDATED
,
1087 PEER_COL_ADDRESS_COLLATED
,
1088 PEER_COL_DOWNLOAD_RATE_DOUBLE
,
1089 PEER_COL_DOWNLOAD_RATE_STRING
,
1090 PEER_COL_UPLOAD_RATE_DOUBLE
,
1091 PEER_COL_UPLOAD_RATE_STRING
,
1094 PEER_COL_UPLOAD_REQUEST_COUNT_INT
,
1095 PEER_COL_UPLOAD_REQUEST_COUNT_STRING
,
1096 PEER_COL_DOWNLOAD_REQUEST_COUNT_INT
,
1097 PEER_COL_DOWNLOAD_REQUEST_COUNT_STRING
,
1098 PEER_COL_BLOCKS_DOWNLOADED_COUNT_INT
,
1099 PEER_COL_BLOCKS_DOWNLOADED_COUNT_STRING
,
1100 PEER_COL_BLOCKS_UPLOADED_COUNT_INT
,
1101 PEER_COL_BLOCKS_UPLOADED_COUNT_STRING
,
1102 PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_INT
,
1103 PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_STRING
,
1104 PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_INT
,
1105 PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_STRING
,
1106 PEER_COL_ENCRYPTION_STOCK_ID
,
1112 getPeerColumnName( int column
)
1116 case PEER_COL_ADDRESS
: return _( "Address" );
1117 case PEER_COL_DOWNLOAD_RATE_STRING
:
1118 case PEER_COL_DOWNLOAD_RATE_DOUBLE
: return _( "Down" );
1119 case PEER_COL_UPLOAD_RATE_STRING
:
1120 case PEER_COL_UPLOAD_RATE_DOUBLE
: return _( "Up" );
1121 case PEER_COL_CLIENT
: return _( "Client" );
1122 case PEER_COL_PROGRESS
: return _( "%" );
1123 case PEER_COL_UPLOAD_REQUEST_COUNT_INT
:
1124 case PEER_COL_UPLOAD_REQUEST_COUNT_STRING
: return _( "Up Reqs" );
1125 case PEER_COL_DOWNLOAD_REQUEST_COUNT_INT
:
1126 case PEER_COL_DOWNLOAD_REQUEST_COUNT_STRING
: return _( "Dn Reqs" );
1127 case PEER_COL_BLOCKS_DOWNLOADED_COUNT_INT
:
1128 case PEER_COL_BLOCKS_DOWNLOADED_COUNT_STRING
: return _( "Dn Blocks" );
1129 case PEER_COL_BLOCKS_UPLOADED_COUNT_INT
:
1130 case PEER_COL_BLOCKS_UPLOADED_COUNT_STRING
: return _( "Up Blocks" );
1131 case PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_INT
:
1132 case PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_STRING
: return _( "We Cancelled" );
1133 case PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_INT
:
1134 case PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_STRING
: return _( "They Cancelled" );
1135 case PEER_COL_FLAGS
: return _( "Flags" );
1140 static GtkListStore
*
1141 peer_store_new( void )
1143 return gtk_list_store_new( N_PEER_COLS
,
1144 G_TYPE_STRING
, /* key */
1145 G_TYPE_BOOLEAN
, /* was-updated */
1146 G_TYPE_STRING
, /* address */
1147 G_TYPE_STRING
, /* collated address */
1148 G_TYPE_DOUBLE
, /* download speed int */
1149 G_TYPE_STRING
, /* download speed string */
1150 G_TYPE_DOUBLE
, /* upload speed int */
1151 G_TYPE_STRING
, /* upload speed string */
1152 G_TYPE_STRING
, /* client */
1153 G_TYPE_INT
, /* progress [0..100] */
1154 G_TYPE_INT
, /* upload request count int */
1155 G_TYPE_STRING
, /* upload request count string */
1156 G_TYPE_INT
, /* download request count int */
1157 G_TYPE_STRING
, /* download request count string */
1158 G_TYPE_INT
, /* # blocks downloaded int */
1159 G_TYPE_STRING
, /* # blocks downloaded string */
1160 G_TYPE_INT
, /* # blocks uploaded int */
1161 G_TYPE_STRING
, /* # blocks uploaded string */
1162 G_TYPE_INT
, /* # blocks cancelled by client int */
1163 G_TYPE_STRING
, /* # blocks cancelled by client string */
1164 G_TYPE_INT
, /* # blocks cancelled by peer int */
1165 G_TYPE_STRING
, /* # blocks cancelled by peer string */
1166 G_TYPE_STRING
, /* encryption stock id */
1167 G_TYPE_STRING
); /* flagString */
1171 initPeerRow( GtkListStore
* store
,
1174 const tr_peer_stat
* peer
)
1177 char collated_name
[128];
1178 const char * client
= peer
->client
;
1180 if( !client
|| !strcmp( client
, "Unknown Client" ) )
1183 if( sscanf( peer
->addr
, "%d.%d.%d.%d", q
, q
+1, q
+2, q
+3 ) != 4 )
1184 g_strlcpy( collated_name
, peer
->addr
, sizeof( collated_name
) );
1186 g_snprintf( collated_name
, sizeof( collated_name
),
1187 "%03d.%03d.%03d.%03d", q
[0], q
[1], q
[2], q
[3] );
1189 gtk_list_store_set( store
, iter
,
1190 PEER_COL_ADDRESS
, peer
->addr
,
1191 PEER_COL_ADDRESS_COLLATED
, collated_name
,
1192 PEER_COL_CLIENT
, client
,
1193 PEER_COL_ENCRYPTION_STOCK_ID
, peer
->isEncrypted
? "transmission-lock" : NULL
,
1199 refreshPeerRow( GtkListStore
* store
,
1201 const tr_peer_stat
* peer
)
1203 char up_speed
[64] = { '\0' };
1204 char down_speed
[64] = { '\0' };
1205 char up_count
[64] = { '\0' };
1206 char down_count
[64] = { '\0' };
1207 char blocks_to_peer
[64] = { '\0' };
1208 char blocks_to_client
[64] = { '\0' };
1209 char cancelled_by_peer
[64] = { '\0' };
1210 char cancelled_by_client
[64] = { '\0' };
1212 if( peer
->rateToPeer_KBps
> 0.01 )
1213 tr_formatter_speed_KBps( up_speed
, peer
->rateToPeer_KBps
, sizeof( up_speed
) );
1215 if( peer
->rateToClient_KBps
> 0 )
1216 tr_formatter_speed_KBps( down_speed
, peer
->rateToClient_KBps
, sizeof( down_speed
) );
1218 if( peer
->pendingReqsToPeer
> 0 )
1219 g_snprintf( down_count
, sizeof( down_count
), "%d", peer
->pendingReqsToPeer
);
1221 if( peer
->pendingReqsToClient
> 0 )
1222 g_snprintf( up_count
, sizeof( down_count
), "%d", peer
->pendingReqsToClient
);
1224 if( peer
->blocksToPeer
> 0 )
1225 g_snprintf( blocks_to_peer
, sizeof( blocks_to_peer
), "%"PRIu32
, peer
->blocksToPeer
);
1227 if( peer
->blocksToClient
> 0 )
1228 g_snprintf( blocks_to_client
, sizeof( blocks_to_client
), "%"PRIu32
, peer
->blocksToClient
);
1230 if( peer
->cancelsToPeer
> 0 )
1231 g_snprintf( cancelled_by_client
, sizeof( cancelled_by_client
), "%"PRIu32
, peer
->cancelsToPeer
);
1233 if( peer
->cancelsToClient
> 0 )
1234 g_snprintf( cancelled_by_peer
, sizeof( cancelled_by_peer
), "%"PRIu32
, peer
->cancelsToClient
);
1236 gtk_list_store_set( store
, iter
,
1237 PEER_COL_PROGRESS
, (int)( 100.0 * peer
->progress
),
1238 PEER_COL_UPLOAD_REQUEST_COUNT_INT
, peer
->pendingReqsToClient
,
1239 PEER_COL_UPLOAD_REQUEST_COUNT_STRING
, up_count
,
1240 PEER_COL_DOWNLOAD_REQUEST_COUNT_INT
, peer
->pendingReqsToPeer
,
1241 PEER_COL_DOWNLOAD_REQUEST_COUNT_STRING
, down_count
,
1242 PEER_COL_DOWNLOAD_RATE_DOUBLE
, peer
->rateToClient_KBps
,
1243 PEER_COL_DOWNLOAD_RATE_STRING
, down_speed
,
1244 PEER_COL_UPLOAD_RATE_DOUBLE
, peer
->rateToPeer_KBps
,
1245 PEER_COL_UPLOAD_RATE_STRING
, up_speed
,
1246 PEER_COL_FLAGS
, peer
->flagStr
,
1247 PEER_COL_WAS_UPDATED
, TRUE
,
1248 PEER_COL_BLOCKS_DOWNLOADED_COUNT_INT
, (int)peer
->blocksToClient
,
1249 PEER_COL_BLOCKS_DOWNLOADED_COUNT_STRING
, blocks_to_client
,
1250 PEER_COL_BLOCKS_UPLOADED_COUNT_INT
, (int)peer
->blocksToPeer
,
1251 PEER_COL_BLOCKS_UPLOADED_COUNT_STRING
, blocks_to_peer
,
1252 PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_INT
, (int)peer
->cancelsToPeer
,
1253 PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_STRING
, cancelled_by_client
,
1254 PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_INT
, (int)peer
->cancelsToClient
,
1255 PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_STRING
, cancelled_by_peer
,
1260 refreshPeerList( struct DetailsImpl
* di
, tr_torrent
** torrents
, int n
)
1265 GtkTreeModel
* model
;
1266 GHashTable
* hash
= di
->peer_hash
;
1267 GtkListStore
* store
= di
->peer_store
;
1268 struct tr_peer_stat
** peers
;
1270 /* step 1: get all the peers */
1271 peers
= g_new( struct tr_peer_stat
*, n
);
1272 peerCount
= g_new( int, n
);
1273 for( i
=0; i
<n
; ++i
)
1274 peers
[i
] = tr_torrentPeers( torrents
[i
], &peerCount
[i
] );
1276 /* step 2: mark all the peers in the list as not-updated */
1277 model
= GTK_TREE_MODEL( store
);
1278 if( gtk_tree_model_get_iter_first( model
, &iter
) ) do
1279 gtk_list_store_set( store
, &iter
, PEER_COL_WAS_UPDATED
, FALSE
, -1 );
1280 while( gtk_tree_model_iter_next( model
, &iter
) );
1282 /* step 3: add any new peers */
1283 for( i
=0; i
<n
; ++i
) {
1285 const tr_torrent
* tor
= torrents
[i
];
1286 for( j
=0; j
<peerCount
[i
]; ++j
) {
1287 const tr_peer_stat
* s
= &peers
[i
][j
];
1289 g_snprintf( key
, sizeof(key
), "%d.%s", tr_torrentId(tor
), s
->addr
);
1290 if( g_hash_table_lookup( hash
, key
) == NULL
) {
1292 gtk_list_store_append( store
, &iter
);
1293 initPeerRow( store
, &iter
, key
, s
);
1294 p
= gtk_tree_model_get_path( model
, &iter
);
1295 g_hash_table_insert( hash
, g_strdup( key
),
1296 gtk_tree_row_reference_new( model
, p
) );
1297 gtk_tree_path_free( p
);
1302 /* step 4: update the peers */
1303 for( i
=0; i
<n
; ++i
) {
1305 const tr_torrent
* tor
= torrents
[i
];
1306 for( j
=0; j
<peerCount
[i
]; ++j
) {
1307 const tr_peer_stat
* s
= &peers
[i
][j
];
1309 GtkTreeRowReference
* ref
;
1311 g_snprintf( key
, sizeof(key
), "%d.%s", tr_torrentId(tor
), s
->addr
);
1312 ref
= g_hash_table_lookup( hash
, key
);
1313 p
= gtk_tree_row_reference_get_path( ref
);
1314 gtk_tree_model_get_iter( model
, &iter
, p
);
1315 refreshPeerRow( store
, &iter
, s
);
1316 gtk_tree_path_free( p
);
1320 /* step 5: remove peers that have disappeared */
1321 model
= GTK_TREE_MODEL( store
);
1322 if( gtk_tree_model_get_iter_first( model
, &iter
) ) {
1323 gboolean more
= TRUE
;
1326 gtk_tree_model_get( model
, &iter
, PEER_COL_WAS_UPDATED
, &b
, -1 );
1328 more
= gtk_tree_model_iter_next( model
, &iter
);
1331 gtk_tree_model_get( model
, &iter
, PEER_COL_KEY
, &key
, -1 );
1332 g_hash_table_remove( hash
, key
);
1333 more
= gtk_list_store_remove( store
, &iter
);
1339 /* step 6: cleanup */
1340 for( i
=0; i
<n
; ++i
)
1341 tr_torrentPeersFree( peers
[i
], peerCount
[i
] );
1343 tr_free( peerCount
);
1347 refreshWebseedList( struct DetailsImpl
* di
, tr_torrent
** torrents
, int n
)
1352 GHashTable
* hash
= di
->webseed_hash
;
1353 GtkListStore
* store
= di
->webseed_store
;
1354 GtkTreeModel
* model
= GTK_TREE_MODEL( store
);
1356 /* step 1: mark all webseeds as not-updated */
1357 if( gtk_tree_model_get_iter_first( model
, &iter
) ) do
1358 gtk_list_store_set( store
, &iter
, WEBSEED_COL_WAS_UPDATED
, FALSE
, -1 );
1359 while( gtk_tree_model_iter_next( model
, &iter
) );
1361 /* step 2: add any new webseeds */
1362 for( i
=0; i
<n
; ++i
) {
1364 const tr_torrent
* tor
= torrents
[i
];
1365 const tr_info
* inf
= tr_torrentInfo( tor
);
1366 total
+= inf
->webseedCount
;
1367 for( j
=0; j
<inf
->webseedCount
; ++j
) {
1369 const char * url
= inf
->webseeds
[j
];
1370 g_snprintf( key
, sizeof(key
), "%d.%s", tr_torrentId( tor
), url
);
1371 if( g_hash_table_lookup( hash
, key
) == NULL
) {
1373 gtk_list_store_append( store
, &iter
);
1374 gtk_list_store_set( store
, &iter
, WEBSEED_COL_URL
, url
,
1375 WEBSEED_COL_KEY
, key
,
1377 p
= gtk_tree_model_get_path( model
, &iter
);
1378 g_hash_table_insert( hash
, g_strdup( key
),
1379 gtk_tree_row_reference_new( model
, p
) );
1380 gtk_tree_path_free( p
);
1385 /* step 3: update the webseeds */
1386 for( i
=0; i
<n
; ++i
) {
1388 const tr_torrent
* tor
= torrents
[i
];
1389 const tr_info
* inf
= tr_torrentInfo( tor
);
1390 double * speeds_KBps
= tr_torrentWebSpeeds_KBps( tor
);
1391 for( j
=0; j
<inf
->webseedCount
; ++j
) {
1394 const char * url
= inf
->webseeds
[j
];
1396 GtkTreeRowReference
* ref
;
1397 g_snprintf( key
, sizeof(key
), "%d.%s", tr_torrentId( tor
), url
);
1398 ref
= g_hash_table_lookup( hash
, key
);
1399 p
= gtk_tree_row_reference_get_path( ref
);
1400 gtk_tree_model_get_iter( model
, &iter
, p
);
1401 if( speeds_KBps
[j
] > 0 )
1402 tr_formatter_speed_KBps( buf
, speeds_KBps
[j
], sizeof( buf
) );
1405 gtk_list_store_set( store
, &iter
,
1406 WEBSEED_COL_DOWNLOAD_RATE_DOUBLE
, speeds_KBps
[j
],
1407 WEBSEED_COL_DOWNLOAD_RATE_STRING
, buf
,
1408 WEBSEED_COL_WAS_UPDATED
, TRUE
,
1410 gtk_tree_path_free( p
);
1412 tr_free( speeds_KBps
);
1415 /* step 4: remove webseeds that have disappeared */
1416 if( gtk_tree_model_get_iter_first( model
, &iter
) ) {
1417 gboolean more
= TRUE
;
1420 gtk_tree_model_get( model
, &iter
, WEBSEED_COL_WAS_UPDATED
, &b
, -1 );
1422 more
= gtk_tree_model_iter_next( model
, &iter
);
1425 gtk_tree_model_get( model
, &iter
, WEBSEED_COL_KEY
, &key
, -1 );
1427 g_hash_table_remove( hash
, key
);
1428 more
= gtk_list_store_remove( store
, &iter
);
1434 /* most of the time there are no webseeds...
1435 don't waste space showing an empty list */
1437 gtk_widget_show( di
->webseed_view
);
1439 gtk_widget_hide( di
->webseed_view
);
1443 refreshPeers( struct DetailsImpl
* di
, tr_torrent
** torrents
, int n
)
1445 refreshPeerList( di
, torrents
, n
);
1446 refreshWebseedList( di
, torrents
, n
);
1449 #if GTK_CHECK_VERSION( 2,12,0 )
1451 onPeerViewQueryTooltip( GtkWidget
* widget
,
1454 gboolean keyboard_tip
,
1455 GtkTooltip
* tooltip
,
1456 gpointer user_data UNUSED
)
1458 gboolean show_tip
= FALSE
;
1459 GtkTreeModel
* model
;
1462 if( gtk_tree_view_get_tooltip_context( GTK_TREE_VIEW( widget
),
1463 &x
, &y
, keyboard_tip
,
1464 &model
, NULL
, &iter
) )
1468 GString
* gstr
= g_string_new( NULL
);
1469 gtk_tree_model_get( model
, &iter
, PEER_COL_FLAGS
, &str
, -1 );
1470 for( pch
= str
; pch
&& *pch
; ++pch
)
1472 const char * s
= NULL
;
1475 case 'O': s
= _( "Optimistic unchoke" ); break;
1476 case 'D': s
= _( "Downloading from this peer" ); break;
1477 case 'd': s
= _( "We would download from this peer if they would let us" ); break;
1478 case 'U': s
= _( "Uploading to peer" ); break;
1479 case 'u': s
= _( "We would upload to this peer if they asked" ); break;
1480 case 'K': s
= _( "Peer has unchoked us, but we're not interested" ); break;
1481 case '?': s
= _( "We unchoked this peer, but they're not interested" ); break;
1482 case 'E': s
= _( "Encrypted connection" ); break;
1483 case 'X': s
= _( "Peer was found through Peer Exchange (PEX)" ); break;
1484 case 'H': s
= _( "Peer was found through DHT" ); break;
1485 case 'I': s
= _( "Peer is an incoming connection" ); break;
1488 g_string_append_printf( gstr
, "%c: %s\n", *pch
, s
);
1490 if( gstr
->len
) /* remove the last linefeed */
1491 g_string_set_size( gstr
, gstr
->len
- 1 );
1492 gtk_tooltip_set_text( tooltip
, gstr
->str
);
1493 g_string_free( gstr
, TRUE
);
1503 setPeerViewColumns( GtkTreeView
* peer_view
)
1507 const tr_bool more
= gtr_pref_flag_get( PREF_KEY_SHOW_MORE_PEER_INFO
);
1508 int view_columns
[32];
1509 GtkTreeViewColumn
* c
;
1510 GtkCellRenderer
* r
;
1512 view_columns
[n
++] = PEER_COL_ENCRYPTION_STOCK_ID
;
1513 view_columns
[n
++] = PEER_COL_UPLOAD_RATE_STRING
;
1514 if( more
) view_columns
[n
++] = PEER_COL_UPLOAD_REQUEST_COUNT_STRING
;
1515 view_columns
[n
++] = PEER_COL_DOWNLOAD_RATE_STRING
;
1516 if( more
) view_columns
[n
++] = PEER_COL_DOWNLOAD_REQUEST_COUNT_STRING
;
1517 if( more
) view_columns
[n
++] = PEER_COL_BLOCKS_DOWNLOADED_COUNT_STRING
;
1518 if( more
) view_columns
[n
++] = PEER_COL_BLOCKS_UPLOADED_COUNT_STRING
;
1519 if( more
) view_columns
[n
++] = PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_STRING
;
1520 if( more
) view_columns
[n
++] = PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_STRING
;
1521 view_columns
[n
++] = PEER_COL_PROGRESS
;
1522 view_columns
[n
++] = PEER_COL_FLAGS
;
1523 view_columns
[n
++] = PEER_COL_ADDRESS
;
1524 view_columns
[n
++] = PEER_COL_CLIENT
;
1526 /* remove any existing columns */
1527 while(( c
= gtk_tree_view_get_column( peer_view
, 0 )))
1528 gtk_tree_view_remove_column( peer_view
, c
);
1530 for( i
=0; i
<n
; ++i
)
1532 const int col
= view_columns
[i
];
1533 const char * t
= getPeerColumnName( col
);
1538 case PEER_COL_ADDRESS
:
1539 r
= gtk_cell_renderer_text_new( );
1540 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "text", col
, NULL
);
1541 sort_col
= PEER_COL_ADDRESS_COLLATED
;
1544 case PEER_COL_CLIENT
:
1545 r
= gtk_cell_renderer_text_new( );
1546 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "text", col
, NULL
);
1549 case PEER_COL_PROGRESS
:
1550 r
= gtk_cell_renderer_progress_new( );
1551 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "value", PEER_COL_PROGRESS
, NULL
);
1554 case PEER_COL_ENCRYPTION_STOCK_ID
:
1555 r
= gtk_cell_renderer_pixbuf_new( );
1556 g_object_set( r
, "xalign", (gfloat
)0.0,
1557 "yalign", (gfloat
)0.5,
1559 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "stock-id", PEER_COL_ENCRYPTION_STOCK_ID
, NULL
);
1560 gtk_tree_view_column_set_sizing( c
, GTK_TREE_VIEW_COLUMN_FIXED
);
1561 gtk_tree_view_column_set_fixed_width( c
, 20 );
1564 case PEER_COL_DOWNLOAD_REQUEST_COUNT_STRING
:
1565 r
= gtk_cell_renderer_text_new( );
1566 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "text", col
, NULL
);
1567 sort_col
= PEER_COL_DOWNLOAD_REQUEST_COUNT_INT
;
1569 case PEER_COL_UPLOAD_REQUEST_COUNT_STRING
:
1570 r
= gtk_cell_renderer_text_new( );
1571 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "text", col
, NULL
);
1572 sort_col
= PEER_COL_UPLOAD_REQUEST_COUNT_INT
;
1575 case PEER_COL_BLOCKS_DOWNLOADED_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_BLOCKS_DOWNLOADED_COUNT_INT
;
1580 case PEER_COL_BLOCKS_UPLOADED_COUNT_STRING
:
1581 r
= gtk_cell_renderer_text_new( );
1582 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "text", col
, NULL
);
1583 sort_col
= PEER_COL_BLOCKS_UPLOADED_COUNT_INT
;
1586 case PEER_COL_REQS_CANCELLED_BY_CLIENT_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_REQS_CANCELLED_BY_CLIENT_COUNT_INT
;
1591 case PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_STRING
:
1592 r
= gtk_cell_renderer_text_new( );
1593 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "text", col
, NULL
);
1594 sort_col
= PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_INT
;
1597 case PEER_COL_DOWNLOAD_RATE_STRING
:
1598 r
= gtk_cell_renderer_text_new( );
1599 g_object_set( G_OBJECT( r
), "xalign", 1.0f
, NULL
);
1600 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "text", col
, NULL
);
1601 sort_col
= PEER_COL_DOWNLOAD_RATE_DOUBLE
;
1603 case PEER_COL_UPLOAD_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_UPLOAD_RATE_DOUBLE
;
1610 case PEER_COL_FLAGS
:
1611 r
= gtk_cell_renderer_text_new( );
1612 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "text", col
, NULL
);
1619 gtk_tree_view_column_set_resizable( c
, FALSE
);
1620 gtk_tree_view_column_set_sort_column_id( c
, sort_col
);
1621 gtk_tree_view_append_column( GTK_TREE_VIEW( peer_view
), c
);
1624 /* the 'expander' column has a 10-pixel margin on the left
1625 that doesn't look quite correct in any of these columns...
1626 so create a non-visible column and assign it as the
1627 'expander column. */
1629 GtkTreeViewColumn
*c
= gtk_tree_view_column_new( );
1630 gtk_tree_view_column_set_visible( c
, FALSE
);
1631 gtk_tree_view_append_column( GTK_TREE_VIEW( peer_view
), c
);
1632 gtk_tree_view_set_expander_column( GTK_TREE_VIEW( peer_view
), c
);
1637 onMorePeerInfoToggled( GtkToggleButton
* button
, struct DetailsImpl
* di
)
1639 const char * key
= PREF_KEY_SHOW_MORE_PEER_INFO
;
1640 const gboolean value
= gtk_toggle_button_get_active( button
);
1641 tr_core_set_pref_bool( di
->core
, key
, value
);
1642 setPeerViewColumns( GTK_TREE_VIEW( di
->peer_view
) );
1646 peer_page_new( struct DetailsImpl
* di
)
1650 GtkListStore
*store
;
1651 GtkWidget
*v
, *w
, *ret
, *sw
, *vbox
;
1652 GtkWidget
*webtree
= NULL
;
1654 GtkTreeViewColumn
* c
;
1655 GtkCellRenderer
* r
;
1659 store
= di
->webseed_store
= webseed_model_new( );
1660 v
= gtk_tree_view_new_with_model( GTK_TREE_MODEL( store
) );
1661 g_signal_connect( v
, "button-release-event", G_CALLBACK( on_tree_view_button_released
), NULL
);
1662 gtk_tree_view_set_rules_hint( GTK_TREE_VIEW( v
), TRUE
);
1663 g_object_unref( store
);
1665 str
= getWebseedColumnNames( WEBSEED_COL_URL
);
1666 r
= gtk_cell_renderer_text_new( );
1667 g_object_set( G_OBJECT( r
), "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
1668 c
= gtk_tree_view_column_new_with_attributes( str
, r
, "text", WEBSEED_COL_URL
, NULL
);
1669 g_object_set( G_OBJECT( c
), "expand", TRUE
, NULL
);
1670 gtk_tree_view_column_set_sort_column_id( c
, WEBSEED_COL_URL
);
1671 gtk_tree_view_append_column( GTK_TREE_VIEW( v
), c
);
1673 str
= getWebseedColumnNames( WEBSEED_COL_DOWNLOAD_RATE_STRING
);
1674 r
= gtk_cell_renderer_text_new( );
1675 c
= gtk_tree_view_column_new_with_attributes( str
, r
, "text", WEBSEED_COL_DOWNLOAD_RATE_STRING
, NULL
);
1676 gtk_tree_view_column_set_sort_column_id( c
, WEBSEED_COL_DOWNLOAD_RATE_DOUBLE
);
1677 gtk_tree_view_append_column( GTK_TREE_VIEW( v
), c
);
1679 w
= gtk_scrolled_window_new( NULL
, NULL
);
1680 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( w
),
1681 GTK_POLICY_AUTOMATIC
,
1682 GTK_POLICY_AUTOMATIC
);
1683 gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( w
),
1685 gtk_container_add( GTK_CONTAINER( w
), v
);
1688 di
->webseed_view
= w
;
1692 store
= di
->peer_store
= peer_store_new( );
1693 m
= gtk_tree_model_sort_new_with_model( GTK_TREE_MODEL( store
) );
1694 gtk_tree_sortable_set_sort_column_id( GTK_TREE_SORTABLE( m
),
1696 GTK_SORT_DESCENDING
);
1697 #if GTK_CHECK_VERSION( 2,12,0 )
1698 v
= GTK_WIDGET( g_object_new( GTK_TYPE_TREE_VIEW
,
1701 "has-tooltip", TRUE
,
1704 v
= GTK_WIDGET( g_object_new( GTK_TYPE_TREE_VIEW
,
1711 #if GTK_CHECK_VERSION( 2,12,0 )
1712 g_signal_connect( v
, "query-tooltip",
1713 G_CALLBACK( onPeerViewQueryTooltip
), NULL
);
1715 g_object_unref( store
);
1716 g_signal_connect( v
, "button-release-event",
1717 G_CALLBACK( on_tree_view_button_released
), NULL
);
1719 setPeerViewColumns( GTK_TREE_VIEW( v
) );
1721 w
= sw
= gtk_scrolled_window_new( NULL
, NULL
);
1722 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( w
),
1723 GTK_POLICY_AUTOMATIC
,
1724 GTK_POLICY_AUTOMATIC
);
1725 gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( w
),
1727 gtk_container_add( GTK_CONTAINER( w
), v
);
1729 vbox
= gtk_vbox_new( FALSE
, GUI_PAD
);
1730 gtk_container_set_border_width( GTK_CONTAINER( vbox
), GUI_PAD_BIG
);
1732 v
= gtk_vpaned_new( );
1733 gtk_paned_pack1( GTK_PANED( v
), webtree
, FALSE
, TRUE
);
1734 gtk_paned_pack2( GTK_PANED( v
), sw
, TRUE
, TRUE
);
1735 gtk_box_pack_start( GTK_BOX( vbox
), v
, TRUE
, TRUE
, 0 );
1737 w
= gtk_check_button_new_with_mnemonic( _( "Show _more details" ) );
1738 di
->more_peer_details_check
= w
;
1739 b
= gtr_pref_flag_get( PREF_KEY_SHOW_MORE_PEER_INFO
);
1740 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( w
), b
);
1741 g_signal_connect( w
, "toggled", G_CALLBACK( onMorePeerInfoToggled
), di
);
1742 gtk_box_pack_start( GTK_BOX( vbox
), w
, FALSE
, FALSE
, 0 );
1745 /* ip-to-GtkTreeRowReference */
1746 di
->peer_hash
= g_hash_table_new_full( g_str_hash
,
1748 (GDestroyNotify
)g_free
,
1749 (GDestroyNotify
)gtk_tree_row_reference_free
);
1751 /* url-to-GtkTreeRowReference */
1752 di
->webseed_hash
= g_hash_table_new_full( g_str_hash
,
1754 (GDestroyNotify
)g_free
,
1755 (GDestroyNotify
)gtk_tree_row_reference_free
);
1768 /* if it's been longer than a minute, don't bother showing the seconds */
1770 tr_strltime_rounded( char * buf
, time_t t
, size_t buflen
)
1772 if( t
> 60 ) t
-= ( t
% 60 );
1773 tr_strltime( buf
, t
, buflen
);
1777 buildTrackerSummary( const char * key
, const tr_tracker_stat
* st
, gboolean showScrape
)
1781 const time_t now
= time( NULL
);
1782 GString
* gstr
= g_string_new( NULL
);
1783 const char * err_markup_begin
= "<span color=\"red\">";
1784 const char * err_markup_end
= "</span>";
1785 const char * timeout_markup_begin
= "<span color=\"#224466\">";
1786 const char * timeout_markup_end
= "</span>";
1787 const char * success_markup_begin
= "<span color=\"#008B00\">";
1788 const char * success_markup_end
= "</span>";
1792 const char * host
= st
->host
;
1793 const char * pch
= strstr( host
, "://" );
1796 g_string_append( gstr
, st
->isBackup
? "<i>" : "<b>" );
1798 str
= g_markup_printf_escaped( "%s - %s", host
, key
);
1800 str
= g_markup_printf_escaped( "%s", host
);
1801 g_string_append( gstr
, str
);
1803 g_string_append( gstr
, st
->isBackup
? "</i>" : "</b>" );
1808 if( st
->hasAnnounced
&& st
->announceState
!= TR_TRACKER_INACTIVE
)
1810 g_string_append_c( gstr
, '\n' );
1811 tr_strltime_rounded( timebuf
, now
- st
->lastAnnounceTime
, sizeof( timebuf
) );
1812 if( st
->lastAnnounceSucceeded
)
1813 g_string_append_printf( gstr
, _( "Got a list of %1$s%2$'d peers%3$s %4$s ago" ),
1814 success_markup_begin
, st
->lastAnnouncePeerCount
, success_markup_end
,
1816 else if( st
->lastAnnounceTimedOut
)
1817 g_string_append_printf( gstr
, _( "Peer list request %1$stimed out%2$s %3$s ago; will retry" ),
1818 timeout_markup_begin
, timeout_markup_end
, timebuf
);
1820 g_string_append_printf( gstr
, _( "Got an error %1$s\"%2$s\"%3$s %4$s ago" ),
1821 err_markup_begin
, st
->lastAnnounceResult
, err_markup_end
, timebuf
);
1824 switch( st
->announceState
)
1826 case TR_TRACKER_INACTIVE
:
1827 g_string_append_c( gstr
, '\n' );
1828 g_string_append( gstr
, _( "No updates scheduled" ) );
1830 case TR_TRACKER_WAITING
:
1831 tr_strltime_rounded( timebuf
, st
->nextAnnounceTime
- now
, sizeof( timebuf
) );
1832 g_string_append_c( gstr
, '\n' );
1833 g_string_append_printf( gstr
, _( "Asking for more peers in %s" ), timebuf
);
1835 case TR_TRACKER_QUEUED
:
1836 g_string_append_c( gstr
, '\n' );
1837 g_string_append( gstr
, _( "Queued to ask for more peers" ) );
1839 case TR_TRACKER_ACTIVE
:
1840 tr_strltime_rounded( timebuf
, now
- st
->lastAnnounceStartTime
, sizeof( timebuf
) );
1841 g_string_append_c( gstr
, '\n' );
1842 g_string_append_printf( gstr
, _( "Asking for more peers now... <small>%s</small>" ), timebuf
);
1848 if( st
->hasScraped
) {
1849 g_string_append_c( gstr
, '\n' );
1850 tr_strltime_rounded( timebuf
, now
- st
->lastScrapeTime
, sizeof( timebuf
) );
1851 if( st
->lastScrapeSucceeded
)
1852 g_string_append_printf( gstr
, _( "Tracker had %s%'d seeders and %'d leechers%s %s ago" ),
1853 success_markup_begin
, st
->seederCount
, st
->leecherCount
, success_markup_end
,
1856 g_string_append_printf( gstr
, _( "Got a scrape error \"%s%s%s\" %s ago" ), err_markup_begin
, st
->lastScrapeResult
, err_markup_end
, timebuf
);
1859 switch( st
->scrapeState
)
1861 case TR_TRACKER_INACTIVE
:
1863 case TR_TRACKER_WAITING
:
1864 g_string_append_c( gstr
, '\n' );
1865 tr_strltime_rounded( timebuf
, st
->nextScrapeTime
- now
, sizeof( timebuf
) );
1866 g_string_append_printf( gstr
, _( "Asking for peer counts in %s" ), timebuf
);
1868 case TR_TRACKER_QUEUED
:
1869 g_string_append_c( gstr
, '\n' );
1870 g_string_append( gstr
, _( "Queued to ask for peer counts" ) );
1872 case TR_TRACKER_ACTIVE
:
1873 g_string_append_c( gstr
, '\n' );
1874 tr_strltime_rounded( timebuf
, now
- st
->lastScrapeStartTime
, sizeof( timebuf
) );
1875 g_string_append_printf( gstr
, _( "Asking for peer counts now... <small>%s</small>" ), timebuf
);
1881 return g_string_free( gstr
, FALSE
);
1886 TRACKER_COL_TORRENT_ID
,
1888 TRACKER_COL_IS_BACKUP
,
1889 TRACKER_COL_TRACKER_ID
,
1890 TRACKER_COL_FAVICON
,
1891 TRACKER_COL_WAS_UPDATED
,
1897 trackerVisibleFunc( GtkTreeModel
* model
, GtkTreeIter
* iter
, gpointer data
)
1900 struct DetailsImpl
* di
= data
;
1903 if( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( di
->all_check
) ) )
1906 /* don't show the backups... */
1907 gtk_tree_model_get( model
, iter
, TRACKER_COL_IS_BACKUP
, &isBackup
, -1 );
1912 tracker_list_get_current_torrent_id( struct DetailsImpl
* di
)
1914 int torrent_id
= -1;
1916 /* if there's only one torrent in the dialog, always use it */
1917 if( torrent_id
< 0 )
1918 if( g_slist_length( di
->ids
) == 1 )
1919 torrent_id
= GPOINTER_TO_INT( di
->ids
->data
);
1921 /* otherwise, use the selected tracker's torrent */
1922 if( torrent_id
< 0 ) {
1924 GtkTreeModel
* model
;
1925 GtkTreeSelection
* sel
= gtk_tree_view_get_selection( GTK_TREE_VIEW( di
->tracker_view
) );
1926 if( gtk_tree_selection_get_selected( sel
, &model
, &iter
) )
1927 gtk_tree_model_get( model
, &iter
, TRACKER_COL_TORRENT_ID
, &torrent_id
, -1 );
1934 tracker_list_get_current_torrent( struct DetailsImpl
* di
)
1936 const int torrent_id
= tracker_list_get_current_torrent_id( di
);
1937 tr_session
* session
= tr_core_session( di
->core
);
1938 return tr_torrentFindFromId( session
, torrent_id
);
1942 favicon_ready_cb( gpointer pixbuf
, gpointer vreference
)
1945 GtkTreeRowReference
* reference
= vreference
;
1947 if( pixbuf
!= NULL
)
1949 GtkTreePath
* path
= gtk_tree_row_reference_get_path( reference
);
1950 GtkTreeModel
* model
= gtk_tree_row_reference_get_model( reference
);
1952 if( gtk_tree_model_get_iter( model
, &iter
, path
) )
1953 gtk_list_store_set( GTK_LIST_STORE( model
), &iter
,
1954 TRACKER_COL_FAVICON
, pixbuf
,
1957 gtk_tree_path_free( path
);
1959 g_object_unref( pixbuf
);
1962 gtk_tree_row_reference_free( reference
);
1966 refreshTracker( struct DetailsImpl
* di
, tr_torrent
** torrents
, int n
)
1970 tr_tracker_stat
** stats
;
1972 GtkTreeModel
* model
;
1973 GHashTable
* hash
= di
->tracker_hash
;
1974 GtkListStore
* store
= di
->tracker_store
;
1975 tr_session
* session
= tr_core_session( di
->core
);
1976 const gboolean showScrape
= gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( di
->scrape_check
) );
1978 /* step 1: get all the trackers */
1979 statCount
= g_new0( int, n
);
1980 stats
= g_new0( tr_tracker_stat
*, n
);
1981 for( i
=0; i
<n
; ++i
)
1982 stats
[i
] = tr_torrentTrackers( torrents
[i
], &statCount
[i
] );
1984 /* step 2: mark all the trackers in the list as not-updated */
1985 model
= GTK_TREE_MODEL( store
);
1986 if( gtk_tree_model_get_iter_first( model
, &iter
) ) do
1987 gtk_list_store_set( store
, &iter
, TRACKER_COL_WAS_UPDATED
, FALSE
, -1 );
1988 while( gtk_tree_model_iter_next( model
, &iter
) );
1990 /* step 3: add any new trackers */
1991 for( i
=0; i
<n
; ++i
) {
1993 const int jn
= statCount
[i
];
1994 for( j
=0; j
<jn
; ++j
) {
1995 const tr_torrent
* tor
= torrents
[i
];
1996 const tr_tracker_stat
* st
= &stats
[i
][j
];
1997 const int torrent_id
= tr_torrentId( tor
);
1998 char * key
= g_strdup_printf( "%d\t%d\t%s", torrent_id
, st
->tier
, st
->announce
);
2000 if( g_hash_table_lookup( hash
, key
) == NULL
) {
2003 GtkTreeRowReference
* ref
;
2005 gtk_list_store_insert_with_values( store
, &iter
, -1,
2006 TRACKER_COL_TORRENT_ID
, torrent_id
,
2007 TRACKER_COL_TRACKER_ID
, st
->id
,
2008 TRACKER_COL_KEY
, key
,
2011 p
= gtk_tree_model_get_path( model
, &iter
);
2012 ref
= gtk_tree_row_reference_new( model
, p
);
2013 g_hash_table_insert( hash
, g_strdup( key
), ref
);
2014 ref
= gtk_tree_row_reference_new( model
, p
);
2015 gtr_get_favicon_from_url( session
, st
->announce
, favicon_ready_cb
, ref
);
2016 gtk_tree_path_free( p
);
2023 /* step 4: update the peers */
2024 for( i
=0; i
<n
; ++i
) {
2026 const tr_torrent
* tor
= torrents
[i
];
2027 const char * summary_name
= n
>1 ? tr_torrentInfo( tor
)->name
: NULL
;
2028 for( j
=0; j
<statCount
[i
]; ++j
) {
2029 const tr_tracker_stat
* st
= &stats
[i
][j
];
2030 char * summary
= buildTrackerSummary( summary_name
, st
, showScrape
);
2031 char * key
= g_strdup_printf( "%d\t%d\t%s", tr_torrentId( tor
), st
->tier
, st
->announce
);
2032 GtkTreeRowReference
* ref
= g_hash_table_lookup( hash
, key
);
2033 GtkTreePath
* p
= gtk_tree_row_reference_get_path( ref
);
2034 gtk_tree_model_get_iter( model
, &iter
, p
);
2035 gtk_list_store_set( store
, &iter
, TRACKER_COL_TEXT
, summary
,
2036 TRACKER_COL_IS_BACKUP
, st
->isBackup
,
2037 TRACKER_COL_TRACKER_ID
, st
->id
,
2038 TRACKER_COL_WAS_UPDATED
, TRUE
,
2040 gtk_tree_path_free( p
);
2046 /* step 5: remove trackers that have disappeared */
2047 if( gtk_tree_model_get_iter_first( model
, &iter
) ) {
2048 gboolean more
= TRUE
;
2051 gtk_tree_model_get( model
, &iter
, TRACKER_COL_WAS_UPDATED
, &b
, -1 );
2053 more
= gtk_tree_model_iter_next( model
, &iter
);
2056 gtk_tree_model_get( model
, &iter
, TRACKER_COL_KEY
, &key
, -1 );
2057 g_hash_table_remove( hash
, key
);
2058 more
= gtk_list_store_remove( store
, &iter
);
2064 gtk_widget_set_sensitive( di
->edit_trackers_button
,
2065 tracker_list_get_current_torrent_id( di
) >= 0 );
2068 for( i
=0; i
<n
; ++i
)
2069 tr_torrentTrackersFree( stats
[i
], statCount
[i
] );
2071 g_free( statCount
);
2075 onScrapeToggled( GtkToggleButton
* button
, struct DetailsImpl
* di
)
2077 const char * key
= PREF_KEY_SHOW_MORE_TRACKER_INFO
;
2078 const gboolean value
= gtk_toggle_button_get_active( button
);
2079 tr_core_set_pref_bool( di
->core
, key
, value
);
2084 onBackupToggled( GtkToggleButton
* button
, struct DetailsImpl
* di
)
2086 const char * key
= PREF_KEY_SHOW_BACKUP_TRACKERS
;
2087 const gboolean value
= gtk_toggle_button_get_active( button
);
2088 tr_core_set_pref_bool( di
->core
, key
, value
);
2093 on_edit_trackers_response( GtkDialog
* dialog
, int response
, gpointer data
)
2095 gboolean do_destroy
= TRUE
;
2096 struct DetailsImpl
* di
= data
;
2098 if( response
== GTK_RESPONSE_ACCEPT
)
2102 GtkTextIter start
, end
;
2103 const int torrent_id
= GPOINTER_TO_INT( g_object_get_data( G_OBJECT( dialog
), "torrent-id" ) );
2104 GtkTextBuffer
* text_buffer
= g_object_get_data( G_OBJECT( dialog
), "text-buffer" );
2105 tr_session
* session
= tr_core_session( di
->core
);
2106 tr_torrent
* tor
= tr_torrentFindFromId( session
, 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( const tr_torrent
* tor
)
2164 GString
* gstr
= g_string_new( NULL
);
2165 const tr_info
* inf
= tr_torrentInfo( tor
);
2166 for( i
=0; i
<inf
->trackerCount
; ++i
) {
2167 const tr_tracker_info
* t
= &inf
->trackers
[i
];
2168 if( tier
!= t
->tier
) {
2170 g_string_append_c( gstr
, '\n' );
2172 g_string_append_printf( gstr
, "%s\n", t
->announce
);
2175 g_string_truncate( gstr
, gstr
->len
-1 );
2176 return g_string_free( gstr
, FALSE
);
2180 on_edit_trackers( GtkButton
* button
, gpointer data
)
2182 struct DetailsImpl
* di
= data
;
2183 tr_torrent
* tor
= tracker_list_get_current_torrent( di
);
2188 GtkWidget
*w
, *d
, *fr
, *t
, *l
, *sw
;
2189 GtkWindow
* win
= GTK_WINDOW( gtk_widget_get_toplevel( GTK_WIDGET( button
) ) );
2190 char * text
= get_editable_tracker_list( tor
);
2191 const int torrent_id
= tr_torrentId( tor
);
2192 char * title
= g_strdup_printf( _( "%s - Edit Trackers" ), tr_torrentInfo( tor
)->name
);
2194 d
= gtk_dialog_new_with_buttons( title
, win
,
2195 GTK_DIALOG_MODAL
|GTK_DIALOG_DESTROY_WITH_PARENT
,
2196 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
2197 GTK_STOCK_SAVE
, GTK_RESPONSE_ACCEPT
,
2199 g_signal_connect( d
, "response", G_CALLBACK( on_edit_trackers_response
), data
);
2202 t
= hig_workarea_create( );
2203 hig_workarea_add_section_title( t
, &row
, _( "Tracker Announce URLs" ) );
2205 l
= gtk_label_new( NULL
);
2206 gtk_label_set_markup( GTK_LABEL( l
), _( "To add a backup URL, add it on the line after the primary URL.\n"
2207 "To add another primary URL, add it after a blank line." ) );
2208 gtk_label_set_justify( GTK_LABEL( l
), GTK_JUSTIFY_LEFT
);
2209 gtk_misc_set_alignment( GTK_MISC( l
), 0.0, 0.5 );
2210 hig_workarea_add_wide_control( t
, &row
, l
);
2212 w
= gtk_text_view_new( );
2213 gtk_text_buffer_set_text( gtk_text_view_get_buffer( GTK_TEXT_VIEW( w
) ), text
, -1 );
2214 gtk_widget_set_size_request( w
, 500u, 166u );
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 hig_workarea_add_wide_tall_control( t
, &row
, fr
);
2225 hig_workarea_finish( t
, &row
);
2226 gtr_dialog_set_content( GTK_DIALOG( d
), t
);
2228 g_object_set_data( G_OBJECT( d
), "torrent-id", GINT_TO_POINTER( torrent_id
) );
2229 g_object_set_data( G_OBJECT( d
), "text-buffer", gtk_text_view_get_buffer( GTK_TEXT_VIEW( w
) ) );
2230 gtk_widget_show( d
);
2238 on_tracker_list_selection_changed( GtkTreeSelection
* sel
, gpointer gdi
)
2240 struct DetailsImpl
* di
= gdi
;
2241 const int n
= gtk_tree_selection_count_selected_rows( sel
);
2242 tr_torrent
* tor
= tracker_list_get_current_torrent( di
);
2244 gtk_widget_set_sensitive( di
->remove_tracker_button
, n
>0 );
2245 gtk_widget_set_sensitive( di
->add_tracker_button
, tor
!=NULL
);
2246 gtk_widget_set_sensitive( di
->edit_trackers_button
, tor
!=NULL
);
2250 on_add_tracker_response( GtkDialog
* dialog
, int response
, gpointer gdi
)
2252 gboolean destroy
= TRUE
;
2254 if( response
== GTK_RESPONSE_ACCEPT
)
2256 struct DetailsImpl
* di
= gdi
;
2257 GtkWidget
* e
= GTK_WIDGET( g_object_get_data( G_OBJECT( dialog
), "url-entry" ) );
2258 const int torrent_id
= GPOINTER_TO_INT( g_object_get_data( G_OBJECT( dialog
), "torrent-id" ) );
2259 char * url
= g_strdup( gtk_entry_get_text( GTK_ENTRY( e
) ) );
2264 if( gtr_is_supported_url( url
) )
2266 char * json
= g_strdup_printf(
2268 " \"method\": \"torrent-set\",\n"
2269 " \"arguments\": { \"id\": %d, \"trackerAdd\": [ \"%s\" ] }\n"
2272 tr_core_exec_json( di
->core
, json
);
2278 gtr_unrecognized_url_dialog( GTK_WIDGET( dialog
), url
);
2287 gtk_widget_destroy( GTK_WIDGET( dialog
) );
2291 on_tracker_list_add_button_clicked( GtkButton
* button UNUSED
, gpointer gdi
)
2293 struct DetailsImpl
* di
= gdi
;
2294 tr_torrent
* tor
= tracker_list_get_current_torrent( di
);
2302 char * title
= g_strdup_printf( _( "%s - Add Tracker" ), tr_torrentInfo( tor
)->name
);
2304 w
= gtk_dialog_new_with_buttons( title
, GTK_WINDOW( di
->dialog
),
2305 GTK_DIALOG_DESTROY_WITH_PARENT
,
2306 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
2307 GTK_STOCK_ADD
, GTK_RESPONSE_ACCEPT
,
2309 gtk_dialog_set_alternative_button_order( GTK_DIALOG( w
),
2310 GTK_RESPONSE_ACCEPT
,
2311 GTK_RESPONSE_CANCEL
,
2313 g_signal_connect( w
, "response", G_CALLBACK( on_add_tracker_response
), gdi
);
2316 t
= hig_workarea_create( );
2317 hig_workarea_add_section_title( t
, &row
, _( "Tracker" ) );
2318 e
= gtk_entry_new( );
2319 gtk_widget_set_size_request( e
, 400, -1 );
2320 gtr_paste_clipboard_url_into_entry( e
);
2321 g_object_set_data( G_OBJECT( w
), "url-entry", e
);
2322 g_object_set_data( G_OBJECT( w
), "torrent-id", GINT_TO_POINTER( tr_torrentId( tor
) ) );
2323 hig_workarea_add_row( t
, &row
, _( "_Announce URL:" ), e
, NULL
);
2324 gtr_dialog_set_content( GTK_DIALOG( w
), t
);
2325 gtk_widget_show_all( w
);
2332 on_tracker_list_remove_button_clicked( GtkButton
* button UNUSED
, gpointer gdi
)
2335 GtkTreeModel
* model
;
2336 struct DetailsImpl
* di
= gdi
;
2337 GtkTreeView
* v
= GTK_TREE_VIEW( di
->tracker_view
);
2338 GtkTreeSelection
* sel
= gtk_tree_view_get_selection( v
);
2340 if( gtk_tree_selection_get_selected( sel
, &model
, &iter
) )
2345 gtk_tree_model_get( model
, &iter
, TRACKER_COL_TRACKER_ID
, &tracker_id
,
2346 TRACKER_COL_TORRENT_ID
, &torrent_id
,
2348 json
= g_strdup_printf( "{\n"
2349 " \"method\": \"torrent-set\",\n"
2350 " \"arguments\": { \"id\": %d, \"trackerRemove\": [ %d ] }\n"
2352 torrent_id
, tracker_id
);
2353 tr_core_exec_json( di
->core
, json
);
2360 tracker_page_new( struct DetailsImpl
* di
)
2363 GtkCellRenderer
* r
;
2364 GtkTreeViewColumn
* c
;
2365 GtkTreeSelection
* sel
;
2366 GtkWidget
*vbox
, *sw
, *w
, *v
, *hbox
;
2367 const int pad
= ( GUI_PAD
+ GUI_PAD_BIG
) / 2;
2369 vbox
= gtk_vbox_new( FALSE
, GUI_PAD
);
2370 gtk_container_set_border_width( GTK_CONTAINER( vbox
), GUI_PAD_BIG
);
2372 di
->tracker_store
= gtk_list_store_new( TRACKER_N_COLS
, G_TYPE_INT
,
2379 di
->tracker_hash
= g_hash_table_new_full( g_str_hash
,
2381 (GDestroyNotify
)g_free
,
2382 (GDestroyNotify
)gtk_tree_row_reference_free
);
2383 di
->trackers_filtered
= gtk_tree_model_filter_new( GTK_TREE_MODEL( di
->tracker_store
), NULL
);
2384 gtk_tree_model_filter_set_visible_func( GTK_TREE_MODEL_FILTER( di
->trackers_filtered
),
2385 trackerVisibleFunc
, di
, NULL
);
2387 hbox
= gtk_hbox_new( FALSE
, GUI_PAD_BIG
);
2389 v
= di
->tracker_view
= gtk_tree_view_new_with_model( GTK_TREE_MODEL( di
->trackers_filtered
) );
2390 gtk_tree_view_set_headers_visible( GTK_TREE_VIEW( v
), FALSE
);
2391 g_signal_connect( v
, "button-press-event", G_CALLBACK( on_tree_view_button_pressed
), NULL
);
2392 g_signal_connect( v
, "button-release-event", G_CALLBACK( on_tree_view_button_released
), NULL
);
2393 gtk_tree_view_set_rules_hint( GTK_TREE_VIEW( v
), TRUE
);
2395 sel
= gtk_tree_view_get_selection( GTK_TREE_VIEW( v
) );
2396 g_signal_connect( sel
, "changed", G_CALLBACK( on_tracker_list_selection_changed
), di
);
2398 c
= gtk_tree_view_column_new( );
2399 gtk_tree_view_column_set_title( c
, _( "Trackers" ) );
2400 gtk_tree_view_append_column( GTK_TREE_VIEW( v
), c
);
2402 r
= gtk_cell_renderer_pixbuf_new( );
2403 g_object_set( r
, "width", 20 + (GUI_PAD_SMALL
*2), "xpad", GUI_PAD_SMALL
, "ypad", pad
, "yalign", 0.0f
, NULL
);
2404 gtk_tree_view_column_pack_start( c
, r
, FALSE
);
2405 gtk_tree_view_column_add_attribute( c
, r
, "pixbuf", TRACKER_COL_FAVICON
);
2407 r
= gtk_cell_renderer_text_new( );
2408 g_object_set( G_OBJECT( r
), "ellipsize", PANGO_ELLIPSIZE_END
, "xpad", GUI_PAD_SMALL
, "ypad", pad
, NULL
);
2409 gtk_tree_view_column_pack_start( c
, r
, TRUE
);
2410 gtk_tree_view_column_add_attribute( c
, r
, "markup", TRACKER_COL_TEXT
);
2412 sw
= gtk_scrolled_window_new( NULL
, NULL
);
2413 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( sw
), GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
2414 gtk_container_add( GTK_CONTAINER( sw
), v
);
2415 w
= gtk_frame_new( NULL
);
2416 gtk_frame_set_shadow_type( GTK_FRAME( w
), GTK_SHADOW_IN
);
2417 gtk_container_add( GTK_CONTAINER( w
), sw
);
2419 gtk_box_pack_start( GTK_BOX( hbox
), w
, TRUE
, TRUE
, 0 );
2421 v
= gtk_vbox_new( FALSE
, GUI_PAD
);
2423 w
= gtk_button_new_with_mnemonic( _( "_Add" ) );
2424 di
->add_tracker_button
= w
;
2425 g_signal_connect( w
, "clicked", G_CALLBACK( on_tracker_list_add_button_clicked
), di
);
2426 gtk_box_pack_start( GTK_BOX( v
), w
, FALSE
, FALSE
, 0 );
2428 w
= gtk_button_new_with_mnemonic( _( "_Edit" ) );
2429 gtk_button_set_image( GTK_BUTTON( w
), gtk_image_new_from_stock( GTK_STOCK_EDIT
, GTK_ICON_SIZE_BUTTON
) );
2430 g_signal_connect( w
, "clicked", G_CALLBACK( on_edit_trackers
), di
);
2431 di
->edit_trackers_button
= w
;
2432 gtk_box_pack_start( GTK_BOX( v
), w
, FALSE
, FALSE
, 0 );
2434 w
= gtk_button_new_with_mnemonic( _( "_Remove" ) );
2435 di
->remove_tracker_button
= w
;
2436 g_signal_connect( w
, "clicked", G_CALLBACK( on_tracker_list_remove_button_clicked
), di
);
2437 gtk_box_pack_start( GTK_BOX( v
), w
, FALSE
, FALSE
, 0 );
2439 gtk_box_pack_start( GTK_BOX( hbox
), v
, FALSE
, FALSE
, 0 );
2441 gtk_box_pack_start( GTK_BOX( vbox
), hbox
, TRUE
, TRUE
, 0 );
2443 w
= gtk_check_button_new_with_mnemonic( _( "Show _more details" ) );
2444 di
->scrape_check
= w
;
2445 b
= gtr_pref_flag_get( PREF_KEY_SHOW_MORE_TRACKER_INFO
);
2446 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( w
), b
);
2447 g_signal_connect( w
, "toggled", G_CALLBACK( onScrapeToggled
), di
);
2448 gtk_box_pack_start( GTK_BOX( vbox
), w
, FALSE
, FALSE
, 0 );
2450 w
= gtk_check_button_new_with_mnemonic( _( "Show _backup trackers" ) );
2452 b
= gtr_pref_flag_get( PREF_KEY_SHOW_BACKUP_TRACKERS
);
2453 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( w
), b
);
2454 g_signal_connect( w
, "toggled", G_CALLBACK( onBackupToggled
), di
);
2455 gtk_box_pack_start( GTK_BOX( vbox
), w
, FALSE
, FALSE
, 0 );
2466 refresh( struct DetailsImpl
* di
)
2469 tr_torrent
** torrents
= getTorrents( di
, &n
);
2471 refreshInfo( di
, torrents
, n
);
2472 refreshPeers( di
, torrents
, n
);
2473 refreshTracker( di
, torrents
, n
);
2474 refreshOptions( di
, torrents
, n
);
2477 gtk_dialog_response( GTK_DIALOG( di
->dialog
), GTK_RESPONSE_CLOSE
);
2483 periodic_refresh( gpointer data
)
2490 details_free( gpointer gdata
)
2492 struct DetailsImpl
* data
= gdata
;
2493 g_source_remove( data
->periodic_refresh_tag
);
2494 g_hash_table_destroy( data
->tracker_hash
);
2495 g_hash_table_destroy( data
->webseed_hash
);
2496 g_hash_table_destroy( data
->peer_hash
);
2497 g_slist_free( data
->ids
);
2502 gtr_torrent_details_dialog_new( GtkWindow
* parent
, TrCore
* core
)
2504 GtkWidget
*d
, *n
, *v
, *w
, *l
;
2505 struct DetailsImpl
* di
= g_new0( struct DetailsImpl
, 1 );
2507 /* create the dialog */
2509 d
= gtk_dialog_new_with_buttons( NULL
, parent
, 0,
2510 GTK_STOCK_CLOSE
, GTK_RESPONSE_CLOSE
,
2513 gtk_window_set_role( GTK_WINDOW( d
), "tr-info" );
2514 g_signal_connect_swapped( d
, "response",
2515 G_CALLBACK( gtk_widget_destroy
), d
);
2516 gtk_container_set_border_width( GTK_CONTAINER( d
), GUI_PAD
);
2517 g_object_set_data_full( G_OBJECT( d
), DETAILS_KEY
, di
, details_free
);
2519 n
= gtk_notebook_new( );
2520 gtk_container_set_border_width( GTK_CONTAINER( n
), GUI_PAD
);
2522 w
= info_page_new( di
);
2523 l
= gtk_label_new( _( "Information" ) );
2524 gtk_notebook_append_page( GTK_NOTEBOOK( n
), w
, l
);
2526 w
= peer_page_new( di
);
2527 l
= gtk_label_new( _( "Peers" ) );
2528 gtk_notebook_append_page( GTK_NOTEBOOK( n
), w
, l
);
2530 w
= tracker_page_new( di
);
2531 l
= gtk_label_new( _( "Trackers" ) );
2532 gtk_notebook_append_page( GTK_NOTEBOOK( n
), w
, l
);
2534 v
= gtk_vbox_new( FALSE
, 0 );
2535 di
->file_list
= gtr_file_list_new( core
, 0 );
2536 di
->file_label
= gtk_label_new( _( "File listing not available for combined torrent properties" ) );
2537 gtk_box_pack_start( GTK_BOX( v
), di
->file_list
, TRUE
, TRUE
, 0 );
2538 gtk_box_pack_start( GTK_BOX( v
), di
->file_label
, TRUE
, TRUE
, 0 );
2539 gtk_container_set_border_width( GTK_CONTAINER( v
), GUI_PAD_BIG
);
2540 l
= gtk_label_new( _( "Files" ) );
2541 gtk_notebook_append_page( GTK_NOTEBOOK( n
), v
, l
);
2543 w
= options_page_new( di
);
2544 l
= gtk_label_new( _( "Options" ) );
2545 gtk_notebook_append_page( GTK_NOTEBOOK( n
), w
, l
);
2547 gtr_dialog_set_content( GTK_DIALOG( d
), n
);
2548 di
->periodic_refresh_tag
= gtr_timeout_add_seconds( SECONDARY_WINDOW_REFRESH_INTERVAL_SECONDS
,
2549 periodic_refresh
, di
);
2554 gtr_torrent_details_dialog_set_torrents( GtkWidget
* w
, GSList
* ids
)
2557 const int len
= g_slist_length( ids
);
2558 struct DetailsImpl
* di
= g_object_get_data( G_OBJECT( w
), DETAILS_KEY
);
2560 g_slist_free( di
->ids
);
2561 di
->ids
= g_slist_copy( ids
);
2565 const int id
= GPOINTER_TO_INT( ids
->data
);
2566 tr_session
* session
= tr_core_session( di
->core
);
2567 tr_torrent
* tor
= tr_torrentFindFromId( session
, id
);
2568 const tr_info
* inf
= tr_torrentInfo( tor
);
2569 g_snprintf( title
, sizeof( title
), _( "%s Properties" ), inf
->name
);
2571 gtr_file_list_set_torrent( di
->file_list
, id
);
2572 gtk_widget_show( di
->file_list
);
2573 gtk_widget_hide( di
->file_label
);
2577 gtr_file_list_clear( di
->file_list
);
2578 gtk_widget_hide( di
->file_list
);
2579 gtk_widget_show( di
->file_label
);
2580 g_snprintf( title
, sizeof( title
), _( "%'d Torrent Properties" ), len
);
2583 gtk_window_set_title( GTK_WINDOW( w
), title
);