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 13192 2012-02-03 17:12:17Z 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 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 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
= gtr_hbox_new( FALSE
, 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
= gtr_hbox_new( FALSE
, 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
;
545 hig_workarea_finish( t
, &row
);
556 activityString( int activity
, bool finished
)
560 case TR_STATUS_CHECK_WAIT
: return _( "Queued for verification" );
561 case TR_STATUS_CHECK
: return _( "Verifying local data" );
562 case TR_STATUS_DOWNLOAD_WAIT
: return _( "Queued for download" );
563 case TR_STATUS_DOWNLOAD
: return C_( "Verb", "Downloading" );
564 case TR_STATUS_SEED_WAIT
: return _( "Queued for seeding" );
565 case TR_STATUS_SEED
: return C_( "Verb", "Seeding" );
566 case TR_STATUS_STOPPED
: return finished
? _( "Finished" ) : _( "Paused" );
572 /* Only call gtk_text_buffer_set_text() if the new text differs from the old.
573 * This way if the user has text selected, refreshing won't deselect it */
575 gtr_text_buffer_set_text( GtkTextBuffer
* b
, const char * str
)
578 GtkTextIter start
, end
;
583 gtk_text_buffer_get_bounds( b
, &start
, &end
);
584 old_str
= gtk_text_buffer_get_text( b
, &start
, &end
, FALSE
);
586 if( ( old_str
== NULL
) || strcmp( old_str
, str
) )
587 gtk_text_buffer_set_text( b
, str
, -1 );
593 get_short_date_string( time_t t
)
599 return g_strdup( _( "N/A" ) );
601 tr_localtime_r( &t
, &tm
);
602 strftime( buf
, sizeof( buf
), "%d %b %Y", &tm
);
603 return g_locale_to_utf8( buf
, -1, NULL
, NULL
, NULL
);
607 refreshInfo( struct DetailsImpl
* di
, tr_torrent
** torrents
, int n
)
611 const char * mixed
= _( "Mixed" );
612 const char * no_torrent
= _( "No Torrents Selected" );
613 const char * stateString
;
615 uint64_t sizeWhenDone
= 0;
616 const tr_stat
** stats
= g_new( const tr_stat
*, n
);
617 const tr_info
** infos
= g_new( const tr_info
*, n
);
618 for( i
=0; i
<n
; ++i
) {
619 stats
[i
] = tr_torrentStatCached( torrents
[i
] );
620 infos
[i
] = tr_torrentInfo( torrents
[i
] );
627 const bool baseline
= infos
[0]->isPrivate
;
629 if( baseline
!= infos
[i
]->isPrivate
)
634 str
= _( "Private to this tracker -- DHT and PEX disabled" );
636 str
= _( "Public torrent" );
638 gtr_label_set_text( GTK_LABEL( di
->privacy_lb
), str
);
645 const char * creator
= infos
[0]->creator
? infos
[0]->creator
: "";
646 const time_t date
= infos
[0]->dateCreated
;
647 char * datestr
= get_short_date_string( date
);
648 gboolean mixed_creator
= FALSE
;
649 gboolean mixed_date
= FALSE
;
651 for( i
=1; i
<n
; ++i
) {
652 mixed_creator
|= strcmp( creator
, infos
[i
]->creator
? infos
[i
]->creator
: "" );
653 mixed_date
|= ( date
!= infos
[i
]->dateCreated
);
655 if( mixed_date
&& mixed_creator
)
659 g_snprintf( buf
, sizeof( buf
), _( "Created by %1$s" ), creator
);
660 else if( mixed_creator
|| !*creator
)
661 g_snprintf( buf
, sizeof( buf
), _( "Created on %1$s" ), datestr
);
663 g_snprintf( buf
, sizeof( buf
), _( "Created by %1$s on %2$s" ), creator
, datestr
);
669 gtr_label_set_text( GTK_LABEL( di
->origin_lb
), str
);
676 const char * baseline
= infos
[0]->comment
? infos
[0]->comment
: "";
678 if( strcmp( baseline
, infos
[i
]->comment
? infos
[i
]->comment
: "" ) )
685 gtr_text_buffer_set_text( di
->comment_buffer
, str
);
691 const char * baseline
= tr_torrentGetDownloadDir( torrents
[0] );
693 if( strcmp( baseline
, tr_torrentGetDownloadDir( torrents
[i
] ) ) )
700 gtr_label_set_text( GTK_LABEL( di
->destination_lb
), str
);
706 const tr_torrent_activity activity
= stats
[0]->activity
;
707 bool allFinished
= stats
[0]->finished
;
708 for( i
=1; i
<n
; ++i
) {
709 if( activity
!= stats
[i
]->activity
)
711 if( !stats
[i
]->finished
)
714 str
= i
<n
? mixed
: activityString( activity
, allFinished
);
717 gtr_label_set_text( GTK_LABEL( di
->state_lb
), str
);
724 const time_t baseline
= stats
[0]->startDate
;
726 if( baseline
!= stats
[i
]->startDate
)
730 else if( ( baseline
<=0 ) || ( stats
[0]->activity
== TR_STATUS_STOPPED
) )
733 str
= tr_strltime( buf
, time(NULL
)-baseline
, sizeof( buf
) );
735 gtr_label_set_text( GTK_LABEL( di
->date_started_lb
), str
);
742 const int baseline
= stats
[0]->eta
;
744 if( baseline
!= stats
[i
]->eta
)
748 else if( baseline
< 0 )
749 str
= _( "Unknown" );
751 str
= tr_strltime( buf
, baseline
, sizeof( buf
) );
753 gtr_label_set_text( GTK_LABEL( di
->eta_lb
), str
);
761 int32_t pieceSize
= 0;
762 for( i
=0; i
<n
; ++i
) {
763 size
+= infos
[i
]->totalSize
;
764 pieces
+= infos
[i
]->pieceCount
;
766 pieceSize
= infos
[i
]->pieceSize
;
767 else if( pieceSize
!= (int)infos
[i
]->pieceSize
)
770 tr_strlsize( sizebuf
, size
, sizeof( sizebuf
) );
773 else if( pieceSize
>= 0 ) {
775 tr_formatter_mem_B( piecebuf
, pieceSize
, sizeof( piecebuf
) );
776 g_snprintf( buf
, sizeof( buf
),
777 ngettext( "%1$s (%2$'d piece @ %3$s)",
778 "%1$s (%2$'d pieces @ %3$s)", pieces
),
779 sizebuf
, pieces
, piecebuf
);
782 g_snprintf( buf
, sizeof( buf
),
783 ngettext( "%1$s (%2$'d piece)",
784 "%1$s (%2$'d pieces)", pieces
),
788 gtr_label_set_text( GTK_LABEL( di
->size_lb
), str
);
796 uint64_t leftUntilDone
= 0;
797 uint64_t haveUnchecked
= 0;
798 uint64_t haveValid
= 0;
799 uint64_t available
= 0;
800 for( i
=0; i
<n
; ++i
) {
801 const tr_stat
* st
= stats
[i
];
802 haveUnchecked
+= st
->haveUnchecked
;
803 haveValid
+= st
->haveValid
;
804 sizeWhenDone
+= st
->sizeWhenDone
;
805 leftUntilDone
+= st
->leftUntilDone
;
806 available
+= st
->sizeWhenDone
- st
->leftUntilDone
+ st
->desiredAvailable
;
809 char buf2
[32], unver
[64], total
[64], avail
[32];
810 const double d
= sizeWhenDone
? ( 100.0 * available
) / sizeWhenDone
: 0;
811 const double ratio
= 100.0 * ( sizeWhenDone
? ( haveValid
+ haveUnchecked
) / (double)sizeWhenDone
: 1 );
812 tr_strlpercent( avail
, d
, sizeof( avail
) );
813 tr_strlpercent( buf2
, ratio
, sizeof( buf2
) );
814 tr_strlsize( total
, haveUnchecked
+ haveValid
, sizeof( total
) );
815 tr_strlsize( unver
, haveUnchecked
, sizeof( unver
) );
816 if( !haveUnchecked
&& !leftUntilDone
)
817 g_snprintf( buf
, sizeof( buf
), _( "%1$s (%2$s%%)" ), total
, buf2
);
818 else if( !haveUnchecked
)
819 g_snprintf( buf
, sizeof( buf
), _( "%1$s (%2$s%% of %3$s%% Available)" ), total
, buf2
, avail
);
821 g_snprintf( buf
, sizeof( buf
), _( "%1$s (%2$s%% of %3$s%% Available); %4$s Unverified" ), total
, buf2
, avail
, unver
);
825 gtr_label_set_text( GTK_LABEL( di
->have_lb
), str
);
831 char dbuf
[64], fbuf
[64];
833 for( i
=0; i
<n
; ++i
) {
834 d
+= stats
[i
]->downloadedEver
;
835 f
+= stats
[i
]->corruptEver
;
837 tr_strlsize( dbuf
, d
, sizeof( dbuf
) );
838 tr_strlsize( fbuf
, f
, sizeof( fbuf
) );
840 g_snprintf( buf
, sizeof( buf
), _( "%1$s (+%2$s corrupt)" ), dbuf
, fbuf
);
842 tr_strlcpy( buf
, dbuf
, sizeof( buf
) );
845 gtr_label_set_text( GTK_LABEL( di
->dl_lb
), str
);
856 for( i
=0; i
<n
; ++i
) {
857 up
+= stats
[i
]->uploadedEver
;
858 down
+= stats
[i
]->downloadedEver
;
860 tr_strlsize( upstr
, up
, sizeof( upstr
) );
861 tr_strlratio( ratiostr
, tr_getRatio( up
, down
), sizeof( ratiostr
) );
862 g_snprintf( buf
, sizeof( buf
), _( "%s (Ratio: %s)" ), upstr
, ratiostr
);
865 gtr_label_set_text( GTK_LABEL( di
->ul_lb
), str
);
871 str
= infos
[0]->hashString
;
874 gtr_label_set_text( GTK_LABEL( di
->hash_lb
), str
);
880 const char * baseline
= stats
[0]->errorString
;
882 if( strcmp( baseline
, stats
[i
]->errorString
) )
890 str
= _( "No errors" );
891 gtr_label_set_text( GTK_LABEL( di
->error_lb
), str
);
900 if( latest
< stats
[i
]->activityDate
)
901 latest
= stats
[i
]->activityDate
;
905 const int period
= time( NULL
) - latest
;
907 tr_strlcpy( buf
, _( "Active now" ), sizeof( buf
) );
910 tr_strltime( tbuf
, period
, sizeof( tbuf
) );
911 g_snprintf( buf
, sizeof( buf
), _( "%1$s ago" ), tbuf
);
916 gtr_label_set_text( GTK_LABEL( di
->last_activity_lb
), str
);
923 info_page_new( struct DetailsImpl
* di
)
927 GtkWidget
*l
, *w
, *fr
, *sw
;
928 GtkWidget
*t
= hig_workarea_create( );
930 hig_workarea_add_section_title( t
, &row
, _( "Activity" ) );
933 l
= di
->size_lb
= gtk_label_new( NULL
);
934 gtk_label_set_single_line_mode( GTK_LABEL( l
), TRUE
);
935 hig_workarea_add_row( t
, &row
, _( "Torrent size:" ), l
, NULL
);
938 l
= di
->have_lb
= gtk_label_new( NULL
);
939 gtk_label_set_single_line_mode( GTK_LABEL( l
), TRUE
);
940 hig_workarea_add_row( t
, &row
, _( "Have:" ), l
, NULL
);
943 l
= di
->dl_lb
= gtk_label_new( NULL
);
944 gtk_label_set_single_line_mode( GTK_LABEL( l
), TRUE
);
945 hig_workarea_add_row( t
, &row
, _( "Downloaded:" ), l
, NULL
);
948 l
= di
->ul_lb
= gtk_label_new( NULL
);
949 gtk_label_set_single_line_mode( GTK_LABEL( l
), TRUE
);
950 hig_workarea_add_row( t
, &row
, _( "Uploaded:" ), l
, NULL
);
953 l
= di
->state_lb
= gtk_label_new( NULL
);
954 gtk_label_set_single_line_mode( GTK_LABEL( l
), TRUE
);
955 hig_workarea_add_row( t
, &row
, _( "State:" ), l
, NULL
);
958 l
= di
->date_started_lb
= gtk_label_new( NULL
);
959 gtk_label_set_single_line_mode( GTK_LABEL( l
), TRUE
);
960 hig_workarea_add_row( t
, &row
, _( "Running time:" ), l
, NULL
);
963 l
= di
->eta_lb
= gtk_label_new( NULL
);
964 gtk_label_set_single_line_mode( GTK_LABEL( l
), TRUE
);
965 hig_workarea_add_row( t
, &row
, _( "Remaining time:" ), l
, NULL
);
968 l
= di
->last_activity_lb
= gtk_label_new( NULL
);
969 gtk_label_set_single_line_mode( GTK_LABEL( l
), TRUE
);
970 hig_workarea_add_row( t
, &row
, _( "Last activity:" ), l
, NULL
);
973 l
= g_object_new( GTK_TYPE_LABEL
, "selectable", TRUE
,
974 "ellipsize", PANGO_ELLIPSIZE_END
,
976 hig_workarea_add_row( t
, &row
, _( "Error:" ), l
, NULL
);
980 hig_workarea_add_section_divider( t
, &row
);
981 hig_workarea_add_section_title( t
, &row
, _( "Details" ) );
984 l
= g_object_new( GTK_TYPE_LABEL
, "selectable", TRUE
,
985 "ellipsize", PANGO_ELLIPSIZE_END
,
987 hig_workarea_add_row( t
, &row
, _( "Location:" ), l
, NULL
);
988 di
->destination_lb
= l
;
991 l
= g_object_new( GTK_TYPE_LABEL
, "selectable", TRUE
,
992 "ellipsize", PANGO_ELLIPSIZE_END
,
994 hig_workarea_add_row( t
, &row
, _( "Hash:" ), l
, NULL
);
998 l
= gtk_label_new( NULL
);
999 gtk_label_set_single_line_mode( GTK_LABEL( l
), TRUE
);
1000 hig_workarea_add_row( t
, &row
, _( "Privacy:" ), l
, NULL
);
1004 l
= g_object_new( GTK_TYPE_LABEL
, "selectable", TRUE
,
1005 "ellipsize", PANGO_ELLIPSIZE_END
,
1007 hig_workarea_add_row( t
, &row
, _( "Origin:" ), l
, NULL
);
1011 b
= di
->comment_buffer
= gtk_text_buffer_new( NULL
);
1012 w
= gtk_text_view_new_with_buffer( b
);
1013 gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW( w
), GTK_WRAP_WORD
);
1014 gtk_text_view_set_editable( GTK_TEXT_VIEW( w
), FALSE
);
1015 sw
= gtk_scrolled_window_new( NULL
, NULL
);
1016 gtk_widget_set_size_request( sw
, 350, 36 );
1017 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( sw
),
1018 GTK_POLICY_AUTOMATIC
,
1019 GTK_POLICY_AUTOMATIC
);
1020 gtk_container_add( GTK_CONTAINER( sw
), w
);
1021 fr
= gtk_frame_new( NULL
);
1022 gtk_frame_set_shadow_type( GTK_FRAME( fr
), GTK_SHADOW_IN
);
1023 gtk_container_add( GTK_CONTAINER( fr
), sw
);
1024 w
= hig_workarea_add_tall_row( t
, &row
, _( "Comment:" ), fr
, NULL
);
1025 gtk_misc_set_alignment( GTK_MISC( w
), 0.0f
, 0.0f
);
1027 hig_workarea_add_section_divider( t
, &row
);
1028 hig_workarea_finish( t
, &row
);
1031 hig_workarea_finish( t
, &row
);
1044 WEBSEED_COL_WAS_UPDATED
,
1046 WEBSEED_COL_DOWNLOAD_RATE_DOUBLE
,
1047 WEBSEED_COL_DOWNLOAD_RATE_STRING
,
1052 getWebseedColumnNames( int column
)
1056 case WEBSEED_COL_URL
: return _( "Webseeds" );
1057 case WEBSEED_COL_DOWNLOAD_RATE_DOUBLE
:
1058 case WEBSEED_COL_DOWNLOAD_RATE_STRING
: return _( "Down" );
1063 static GtkListStore
*
1064 webseed_model_new( void )
1066 return gtk_list_store_new( N_WEBSEED_COLS
,
1067 G_TYPE_STRING
, /* key */
1068 G_TYPE_BOOLEAN
, /* was-updated */
1069 G_TYPE_STRING
, /* url */
1070 G_TYPE_DOUBLE
, /* download rate double */
1071 G_TYPE_STRING
); /* download rate string */
1077 PEER_COL_WAS_UPDATED
,
1079 PEER_COL_ADDRESS_COLLATED
,
1080 PEER_COL_DOWNLOAD_RATE_DOUBLE
,
1081 PEER_COL_DOWNLOAD_RATE_STRING
,
1082 PEER_COL_UPLOAD_RATE_DOUBLE
,
1083 PEER_COL_UPLOAD_RATE_STRING
,
1086 PEER_COL_UPLOAD_REQUEST_COUNT_INT
,
1087 PEER_COL_UPLOAD_REQUEST_COUNT_STRING
,
1088 PEER_COL_DOWNLOAD_REQUEST_COUNT_INT
,
1089 PEER_COL_DOWNLOAD_REQUEST_COUNT_STRING
,
1090 PEER_COL_BLOCKS_DOWNLOADED_COUNT_INT
,
1091 PEER_COL_BLOCKS_DOWNLOADED_COUNT_STRING
,
1092 PEER_COL_BLOCKS_UPLOADED_COUNT_INT
,
1093 PEER_COL_BLOCKS_UPLOADED_COUNT_STRING
,
1094 PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_INT
,
1095 PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_STRING
,
1096 PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_INT
,
1097 PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_STRING
,
1098 PEER_COL_ENCRYPTION_STOCK_ID
,
1100 PEER_COL_TORRENT_NAME
,
1105 getPeerColumnName( int column
)
1109 case PEER_COL_ADDRESS
: return _( "Address" );
1110 case PEER_COL_DOWNLOAD_RATE_STRING
:
1111 case PEER_COL_DOWNLOAD_RATE_DOUBLE
: return _( "Down" );
1112 case PEER_COL_UPLOAD_RATE_STRING
:
1113 case PEER_COL_UPLOAD_RATE_DOUBLE
: return _( "Up" );
1114 case PEER_COL_CLIENT
: return _( "Client" );
1115 case PEER_COL_PROGRESS
: return _( "%" );
1116 case PEER_COL_UPLOAD_REQUEST_COUNT_INT
:
1117 case PEER_COL_UPLOAD_REQUEST_COUNT_STRING
: return _( "Up Reqs" );
1118 case PEER_COL_DOWNLOAD_REQUEST_COUNT_INT
:
1119 case PEER_COL_DOWNLOAD_REQUEST_COUNT_STRING
: return _( "Dn Reqs" );
1120 case PEER_COL_BLOCKS_DOWNLOADED_COUNT_INT
:
1121 case PEER_COL_BLOCKS_DOWNLOADED_COUNT_STRING
: return _( "Dn Blocks" );
1122 case PEER_COL_BLOCKS_UPLOADED_COUNT_INT
:
1123 case PEER_COL_BLOCKS_UPLOADED_COUNT_STRING
: return _( "Up Blocks" );
1124 case PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_INT
:
1125 case PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_STRING
: return _( "We Cancelled" );
1126 case PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_INT
:
1127 case PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_STRING
: return _( "They Cancelled" );
1128 case PEER_COL_FLAGS
: return _( "Flags" );
1133 static GtkListStore
*
1134 peer_store_new( void )
1136 return gtk_list_store_new( N_PEER_COLS
,
1137 G_TYPE_STRING
, /* key */
1138 G_TYPE_BOOLEAN
, /* was-updated */
1139 G_TYPE_STRING
, /* address */
1140 G_TYPE_STRING
, /* collated address */
1141 G_TYPE_DOUBLE
, /* download speed int */
1142 G_TYPE_STRING
, /* download speed string */
1143 G_TYPE_DOUBLE
, /* upload speed int */
1144 G_TYPE_STRING
, /* upload speed string */
1145 G_TYPE_STRING
, /* client */
1146 G_TYPE_INT
, /* progress [0..100] */
1147 G_TYPE_INT
, /* upload request count int */
1148 G_TYPE_STRING
, /* upload request count string */
1149 G_TYPE_INT
, /* download request count int */
1150 G_TYPE_STRING
, /* download request count string */
1151 G_TYPE_INT
, /* # blocks downloaded int */
1152 G_TYPE_STRING
, /* # blocks downloaded string */
1153 G_TYPE_INT
, /* # blocks uploaded int */
1154 G_TYPE_STRING
, /* # blocks uploaded string */
1155 G_TYPE_INT
, /* # blocks cancelled by client int */
1156 G_TYPE_STRING
, /* # blocks cancelled by client string */
1157 G_TYPE_INT
, /* # blocks cancelled by peer int */
1158 G_TYPE_STRING
, /* # blocks cancelled by peer string */
1159 G_TYPE_STRING
, /* encryption stock id */
1160 G_TYPE_STRING
, /* flagString */
1161 G_TYPE_STRING
); /* torrent name */
1165 initPeerRow( GtkListStore
* store
,
1168 const char * torrentName
,
1169 const tr_peer_stat
* peer
)
1172 char collated_name
[128];
1173 const char * client
= peer
->client
;
1175 if( !client
|| !strcmp( client
, "Unknown Client" ) )
1178 if( sscanf( peer
->addr
, "%d.%d.%d.%d", q
, q
+1, q
+2, q
+3 ) != 4 )
1179 g_strlcpy( collated_name
, peer
->addr
, sizeof( collated_name
) );
1181 g_snprintf( collated_name
, sizeof( collated_name
),
1182 "%03d.%03d.%03d.%03d", q
[0], q
[1], q
[2], q
[3] );
1184 gtk_list_store_set( store
, iter
,
1185 PEER_COL_ADDRESS
, peer
->addr
,
1186 PEER_COL_ADDRESS_COLLATED
, collated_name
,
1187 PEER_COL_CLIENT
, client
,
1188 PEER_COL_ENCRYPTION_STOCK_ID
, peer
->isEncrypted
? "transmission-lock" : NULL
,
1190 PEER_COL_TORRENT_NAME
, torrentName
,
1195 refreshPeerRow( GtkListStore
* store
,
1197 const tr_peer_stat
* peer
)
1199 char up_speed
[64] = { '\0' };
1200 char down_speed
[64] = { '\0' };
1201 char up_count
[64] = { '\0' };
1202 char down_count
[64] = { '\0' };
1203 char blocks_to_peer
[64] = { '\0' };
1204 char blocks_to_client
[64] = { '\0' };
1205 char cancelled_by_peer
[64] = { '\0' };
1206 char cancelled_by_client
[64] = { '\0' };
1208 if( peer
->rateToPeer_KBps
> 0.01 )
1209 tr_formatter_speed_KBps( up_speed
, peer
->rateToPeer_KBps
, sizeof( up_speed
) );
1211 if( peer
->rateToClient_KBps
> 0 )
1212 tr_formatter_speed_KBps( down_speed
, peer
->rateToClient_KBps
, sizeof( down_speed
) );
1214 if( peer
->pendingReqsToPeer
> 0 )
1215 g_snprintf( down_count
, sizeof( down_count
), "%d", peer
->pendingReqsToPeer
);
1217 if( peer
->pendingReqsToClient
> 0 )
1218 g_snprintf( up_count
, sizeof( down_count
), "%d", peer
->pendingReqsToClient
);
1220 if( peer
->blocksToPeer
> 0 )
1221 g_snprintf( blocks_to_peer
, sizeof( blocks_to_peer
), "%"PRIu32
, peer
->blocksToPeer
);
1223 if( peer
->blocksToClient
> 0 )
1224 g_snprintf( blocks_to_client
, sizeof( blocks_to_client
), "%"PRIu32
, peer
->blocksToClient
);
1226 if( peer
->cancelsToPeer
> 0 )
1227 g_snprintf( cancelled_by_client
, sizeof( cancelled_by_client
), "%"PRIu32
, peer
->cancelsToPeer
);
1229 if( peer
->cancelsToClient
> 0 )
1230 g_snprintf( cancelled_by_peer
, sizeof( cancelled_by_peer
), "%"PRIu32
, peer
->cancelsToClient
);
1232 gtk_list_store_set( store
, iter
,
1233 PEER_COL_PROGRESS
, (int)( 100.0 * peer
->progress
),
1234 PEER_COL_UPLOAD_REQUEST_COUNT_INT
, peer
->pendingReqsToClient
,
1235 PEER_COL_UPLOAD_REQUEST_COUNT_STRING
, up_count
,
1236 PEER_COL_DOWNLOAD_REQUEST_COUNT_INT
, peer
->pendingReqsToPeer
,
1237 PEER_COL_DOWNLOAD_REQUEST_COUNT_STRING
, down_count
,
1238 PEER_COL_DOWNLOAD_RATE_DOUBLE
, peer
->rateToClient_KBps
,
1239 PEER_COL_DOWNLOAD_RATE_STRING
, down_speed
,
1240 PEER_COL_UPLOAD_RATE_DOUBLE
, peer
->rateToPeer_KBps
,
1241 PEER_COL_UPLOAD_RATE_STRING
, up_speed
,
1242 PEER_COL_FLAGS
, peer
->flagStr
,
1243 PEER_COL_WAS_UPDATED
, TRUE
,
1244 PEER_COL_BLOCKS_DOWNLOADED_COUNT_INT
, (int)peer
->blocksToClient
,
1245 PEER_COL_BLOCKS_DOWNLOADED_COUNT_STRING
, blocks_to_client
,
1246 PEER_COL_BLOCKS_UPLOADED_COUNT_INT
, (int)peer
->blocksToPeer
,
1247 PEER_COL_BLOCKS_UPLOADED_COUNT_STRING
, blocks_to_peer
,
1248 PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_INT
, (int)peer
->cancelsToPeer
,
1249 PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_STRING
, cancelled_by_client
,
1250 PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_INT
, (int)peer
->cancelsToClient
,
1251 PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_STRING
, cancelled_by_peer
,
1256 refreshPeerList( struct DetailsImpl
* di
, tr_torrent
** torrents
, int n
)
1261 GtkTreeModel
* model
;
1262 GHashTable
* hash
= di
->peer_hash
;
1263 GtkListStore
* store
= di
->peer_store
;
1264 struct tr_peer_stat
** peers
;
1266 /* step 1: get all the peers */
1267 peers
= g_new( struct tr_peer_stat
*, n
);
1268 peerCount
= g_new( int, n
);
1269 for( i
=0; i
<n
; ++i
)
1270 peers
[i
] = tr_torrentPeers( torrents
[i
], &peerCount
[i
] );
1272 /* step 2: mark all the peers in the list as not-updated */
1273 model
= GTK_TREE_MODEL( store
);
1274 if( gtk_tree_model_iter_nth_child( model
, &iter
, NULL
, 0 ) ) do
1275 gtk_list_store_set( store
, &iter
, PEER_COL_WAS_UPDATED
, FALSE
, -1 );
1276 while( gtk_tree_model_iter_next( model
, &iter
) );
1278 /* step 3: add any new peers */
1279 for( i
=0; i
<n
; ++i
) {
1281 const tr_torrent
* tor
= torrents
[i
];
1282 for( j
=0; j
<peerCount
[i
]; ++j
) {
1283 const tr_peer_stat
* s
= &peers
[i
][j
];
1285 g_snprintf( key
, sizeof(key
), "%d.%s", tr_torrentId(tor
), s
->addr
);
1286 if( g_hash_table_lookup( hash
, key
) == NULL
) {
1288 gtk_list_store_append( store
, &iter
);
1289 initPeerRow( store
, &iter
, key
, tr_torrentName( tor
), s
);
1290 p
= gtk_tree_model_get_path( model
, &iter
);
1291 g_hash_table_insert( hash
, g_strdup( key
),
1292 gtk_tree_row_reference_new( model
, p
) );
1293 gtk_tree_path_free( p
);
1298 /* step 4: update the peers */
1299 for( i
=0; i
<n
; ++i
) {
1301 const tr_torrent
* tor
= torrents
[i
];
1302 for( j
=0; j
<peerCount
[i
]; ++j
) {
1303 const tr_peer_stat
* s
= &peers
[i
][j
];
1305 GtkTreeRowReference
* ref
;
1307 g_snprintf( key
, sizeof(key
), "%d.%s", tr_torrentId(tor
), s
->addr
);
1308 ref
= g_hash_table_lookup( hash
, key
);
1309 p
= gtk_tree_row_reference_get_path( ref
);
1310 gtk_tree_model_get_iter( model
, &iter
, p
);
1311 refreshPeerRow( store
, &iter
, s
);
1312 gtk_tree_path_free( p
);
1316 /* step 5: remove peers that have disappeared */
1317 model
= GTK_TREE_MODEL( store
);
1318 if( gtk_tree_model_iter_nth_child( model
, &iter
, NULL
, 0 ) ) {
1319 gboolean more
= TRUE
;
1322 gtk_tree_model_get( model
, &iter
, PEER_COL_WAS_UPDATED
, &b
, -1 );
1324 more
= gtk_tree_model_iter_next( model
, &iter
);
1327 gtk_tree_model_get( model
, &iter
, PEER_COL_KEY
, &key
, -1 );
1328 g_hash_table_remove( hash
, key
);
1329 more
= gtk_list_store_remove( store
, &iter
);
1335 /* step 6: cleanup */
1336 for( i
=0; i
<n
; ++i
)
1337 tr_torrentPeersFree( peers
[i
], peerCount
[i
] );
1339 tr_free( peerCount
);
1343 refreshWebseedList( struct DetailsImpl
* di
, tr_torrent
** torrents
, int n
)
1348 GHashTable
* hash
= di
->webseed_hash
;
1349 GtkListStore
* store
= di
->webseed_store
;
1350 GtkTreeModel
* model
= GTK_TREE_MODEL( store
);
1352 /* step 1: mark all webseeds as not-updated */
1353 if( gtk_tree_model_iter_nth_child( model
, &iter
, NULL
, 0 ) ) do
1354 gtk_list_store_set( store
, &iter
, WEBSEED_COL_WAS_UPDATED
, FALSE
, -1 );
1355 while( gtk_tree_model_iter_next( model
, &iter
) );
1357 /* step 2: add any new webseeds */
1358 for( i
=0; i
<n
; ++i
) {
1360 const tr_torrent
* tor
= torrents
[i
];
1361 const tr_info
* inf
= tr_torrentInfo( tor
);
1362 total
+= inf
->webseedCount
;
1363 for( j
=0; j
<inf
->webseedCount
; ++j
) {
1365 const char * url
= inf
->webseeds
[j
];
1366 g_snprintf( key
, sizeof(key
), "%d.%s", tr_torrentId( tor
), url
);
1367 if( g_hash_table_lookup( hash
, key
) == NULL
) {
1369 gtk_list_store_append( store
, &iter
);
1370 gtk_list_store_set( store
, &iter
, WEBSEED_COL_URL
, url
,
1371 WEBSEED_COL_KEY
, key
,
1373 p
= gtk_tree_model_get_path( model
, &iter
);
1374 g_hash_table_insert( hash
, g_strdup( key
),
1375 gtk_tree_row_reference_new( model
, p
) );
1376 gtk_tree_path_free( p
);
1381 /* step 3: update the webseeds */
1382 for( i
=0; i
<n
; ++i
) {
1384 const tr_torrent
* tor
= torrents
[i
];
1385 const tr_info
* inf
= tr_torrentInfo( tor
);
1386 double * speeds_KBps
= tr_torrentWebSpeeds_KBps( tor
);
1387 for( j
=0; j
<inf
->webseedCount
; ++j
) {
1390 const char * url
= inf
->webseeds
[j
];
1392 GtkTreeRowReference
* ref
;
1393 g_snprintf( key
, sizeof(key
), "%d.%s", tr_torrentId( tor
), url
);
1394 ref
= g_hash_table_lookup( hash
, key
);
1395 p
= gtk_tree_row_reference_get_path( ref
);
1396 gtk_tree_model_get_iter( model
, &iter
, p
);
1397 if( speeds_KBps
[j
] > 0 )
1398 tr_formatter_speed_KBps( buf
, speeds_KBps
[j
], sizeof( buf
) );
1401 gtk_list_store_set( store
, &iter
,
1402 WEBSEED_COL_DOWNLOAD_RATE_DOUBLE
, speeds_KBps
[j
],
1403 WEBSEED_COL_DOWNLOAD_RATE_STRING
, buf
,
1404 WEBSEED_COL_WAS_UPDATED
, TRUE
,
1406 gtk_tree_path_free( p
);
1408 tr_free( speeds_KBps
);
1411 /* step 4: remove webseeds that have disappeared */
1412 if( gtk_tree_model_iter_nth_child( model
, &iter
, NULL
, 0 ) ) {
1413 gboolean more
= TRUE
;
1416 gtk_tree_model_get( model
, &iter
, WEBSEED_COL_WAS_UPDATED
, &b
, -1 );
1418 more
= gtk_tree_model_iter_next( model
, &iter
);
1421 gtk_tree_model_get( model
, &iter
, WEBSEED_COL_KEY
, &key
, -1 );
1423 g_hash_table_remove( hash
, key
);
1424 more
= gtk_list_store_remove( store
, &iter
);
1430 /* most of the time there are no webseeds...
1431 don't waste space showing an empty list */
1433 gtk_widget_show( di
->webseed_view
);
1435 gtk_widget_hide( di
->webseed_view
);
1439 refreshPeers( struct DetailsImpl
* di
, tr_torrent
** torrents
, int n
)
1441 refreshPeerList( di
, torrents
, n
);
1442 refreshWebseedList( di
, torrents
, n
);
1446 onPeerViewQueryTooltip( GtkWidget
* widget
,
1449 gboolean keyboard_tip
,
1450 GtkTooltip
* tooltip
,
1453 gboolean show_tip
= FALSE
;
1454 GtkTreeModel
* model
;
1457 if( gtk_tree_view_get_tooltip_context( GTK_TREE_VIEW( widget
),
1458 &x
, &y
, keyboard_tip
,
1459 &model
, NULL
, &iter
) )
1461 struct DetailsImpl
* di
= gdi
;
1465 char * markup
= NULL
;
1466 char * flagstr
= NULL
;
1467 GString
* gstr
= di
->gstr
;
1468 gtk_tree_model_get( model
, &iter
, PEER_COL_TORRENT_NAME
, &name
,
1469 PEER_COL_ADDRESS
, &addr
,
1470 PEER_COL_FLAGS
, &flagstr
,
1473 g_string_truncate( gstr
, 0 );
1474 markup
= g_markup_escape_text( name
, -1 );
1475 g_string_append_printf( gstr
, "<b>%s</b>\n%s\n \n", markup
, addr
);
1478 for( pch
= flagstr
; pch
&& *pch
; ++pch
)
1480 const char * s
= NULL
;
1483 case 'O': s
= _( "Optimistic unchoke" ); break;
1484 case 'D': s
= _( "Downloading from this peer" ); break;
1485 case 'd': s
= _( "We would download from this peer if they would let us" ); break;
1486 case 'U': s
= _( "Uploading to peer" ); break;
1487 case 'u': s
= _( "We would upload to this peer if they asked" ); break;
1488 case 'K': s
= _( "Peer has unchoked us, but we're not interested" ); break;
1489 case '?': s
= _( "We unchoked this peer, but they're not interested" ); break;
1490 case 'E': s
= _( "Encrypted connection" ); break;
1491 case 'X': s
= _( "Peer was found through Peer Exchange (PEX)" ); break;
1492 case 'H': s
= _( "Peer was found through DHT" ); break;
1493 case 'I': s
= _( "Peer is an incoming connection" ); break;
1494 case 'T': s
= _( "Peer is connected over µTP" ); break;
1497 g_string_append_printf( gstr
, "%c: %s\n", *pch
, s
);
1499 if( gstr
->len
) /* remove the last linefeed */
1500 g_string_set_size( gstr
, gstr
->len
- 1 );
1502 gtk_tooltip_set_markup( tooltip
, gstr
->str
);
1514 setPeerViewColumns( GtkTreeView
* peer_view
)
1518 const bool more
= gtr_pref_flag_get( PREF_KEY_SHOW_MORE_PEER_INFO
);
1519 int view_columns
[32];
1520 GtkTreeViewColumn
* c
;
1521 GtkCellRenderer
* r
;
1523 view_columns
[n
++] = PEER_COL_ENCRYPTION_STOCK_ID
;
1524 view_columns
[n
++] = PEER_COL_UPLOAD_RATE_STRING
;
1525 if( more
) view_columns
[n
++] = PEER_COL_UPLOAD_REQUEST_COUNT_STRING
;
1526 view_columns
[n
++] = PEER_COL_DOWNLOAD_RATE_STRING
;
1527 if( more
) view_columns
[n
++] = PEER_COL_DOWNLOAD_REQUEST_COUNT_STRING
;
1528 if( more
) view_columns
[n
++] = PEER_COL_BLOCKS_DOWNLOADED_COUNT_STRING
;
1529 if( more
) view_columns
[n
++] = PEER_COL_BLOCKS_UPLOADED_COUNT_STRING
;
1530 if( more
) view_columns
[n
++] = PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_STRING
;
1531 if( more
) view_columns
[n
++] = PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_STRING
;
1532 view_columns
[n
++] = PEER_COL_PROGRESS
;
1533 view_columns
[n
++] = PEER_COL_FLAGS
;
1534 view_columns
[n
++] = PEER_COL_ADDRESS
;
1535 view_columns
[n
++] = PEER_COL_CLIENT
;
1537 /* remove any existing columns */
1538 while(( c
= gtk_tree_view_get_column( peer_view
, 0 )))
1539 gtk_tree_view_remove_column( peer_view
, c
);
1541 for( i
=0; i
<n
; ++i
)
1543 const int col
= view_columns
[i
];
1544 const char * t
= getPeerColumnName( col
);
1549 case PEER_COL_ADDRESS
:
1550 r
= gtk_cell_renderer_text_new( );
1551 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "text", col
, NULL
);
1552 sort_col
= PEER_COL_ADDRESS_COLLATED
;
1555 case PEER_COL_CLIENT
:
1556 r
= gtk_cell_renderer_text_new( );
1557 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "text", col
, NULL
);
1560 case PEER_COL_PROGRESS
:
1561 r
= gtk_cell_renderer_progress_new( );
1562 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "value", PEER_COL_PROGRESS
, NULL
);
1565 case PEER_COL_ENCRYPTION_STOCK_ID
:
1566 r
= gtk_cell_renderer_pixbuf_new( );
1567 g_object_set( r
, "xalign", (gfloat
)0.0,
1568 "yalign", (gfloat
)0.5,
1570 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "stock-id", PEER_COL_ENCRYPTION_STOCK_ID
, NULL
);
1571 gtk_tree_view_column_set_sizing( c
, GTK_TREE_VIEW_COLUMN_FIXED
);
1572 gtk_tree_view_column_set_fixed_width( c
, 20 );
1575 case PEER_COL_DOWNLOAD_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_DOWNLOAD_REQUEST_COUNT_INT
;
1580 case PEER_COL_UPLOAD_REQUEST_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_UPLOAD_REQUEST_COUNT_INT
;
1586 case PEER_COL_BLOCKS_DOWNLOADED_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_DOWNLOADED_COUNT_INT
;
1591 case PEER_COL_BLOCKS_UPLOADED_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_BLOCKS_UPLOADED_COUNT_INT
;
1597 case PEER_COL_REQS_CANCELLED_BY_CLIENT_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_CLIENT_COUNT_INT
;
1602 case PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_STRING
:
1603 r
= gtk_cell_renderer_text_new( );
1604 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "text", col
, NULL
);
1605 sort_col
= PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_INT
;
1608 case PEER_COL_DOWNLOAD_RATE_STRING
:
1609 r
= gtk_cell_renderer_text_new( );
1610 g_object_set( G_OBJECT( r
), "xalign", 1.0f
, NULL
);
1611 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "text", col
, NULL
);
1612 sort_col
= PEER_COL_DOWNLOAD_RATE_DOUBLE
;
1614 case PEER_COL_UPLOAD_RATE_STRING
:
1615 r
= gtk_cell_renderer_text_new( );
1616 g_object_set( G_OBJECT( r
), "xalign", 1.0f
, NULL
);
1617 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "text", col
, NULL
);
1618 sort_col
= PEER_COL_UPLOAD_RATE_DOUBLE
;
1621 case PEER_COL_FLAGS
:
1622 r
= gtk_cell_renderer_text_new( );
1623 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "text", col
, NULL
);
1630 gtk_tree_view_column_set_resizable( c
, FALSE
);
1631 gtk_tree_view_column_set_sort_column_id( c
, sort_col
);
1632 gtk_tree_view_append_column( GTK_TREE_VIEW( peer_view
), c
);
1635 /* the 'expander' column has a 10-pixel margin on the left
1636 that doesn't look quite correct in any of these columns...
1637 so create a non-visible column and assign it as the
1638 'expander column. */
1640 GtkTreeViewColumn
*c
= gtk_tree_view_column_new( );
1641 gtk_tree_view_column_set_visible( c
, FALSE
);
1642 gtk_tree_view_append_column( GTK_TREE_VIEW( peer_view
), c
);
1643 gtk_tree_view_set_expander_column( GTK_TREE_VIEW( peer_view
), c
);
1648 onMorePeerInfoToggled( GtkToggleButton
* button
, struct DetailsImpl
* di
)
1650 const char * key
= PREF_KEY_SHOW_MORE_PEER_INFO
;
1651 const gboolean value
= gtk_toggle_button_get_active( button
);
1652 gtr_core_set_pref_bool( di
->core
, key
, value
);
1653 setPeerViewColumns( GTK_TREE_VIEW( di
->peer_view
) );
1657 peer_page_new( struct DetailsImpl
* di
)
1661 GtkListStore
*store
;
1662 GtkWidget
*v
, *w
, *ret
, *sw
, *vbox
;
1663 GtkWidget
*webtree
= NULL
;
1665 GtkTreeViewColumn
* c
;
1666 GtkCellRenderer
* r
;
1670 store
= di
->webseed_store
= webseed_model_new( );
1671 v
= gtk_tree_view_new_with_model( GTK_TREE_MODEL( store
) );
1672 g_signal_connect( v
, "button-release-event", G_CALLBACK( on_tree_view_button_released
), NULL
);
1673 gtk_tree_view_set_rules_hint( GTK_TREE_VIEW( v
), TRUE
);
1674 g_object_unref( store
);
1676 str
= getWebseedColumnNames( WEBSEED_COL_URL
);
1677 r
= gtk_cell_renderer_text_new( );
1678 g_object_set( G_OBJECT( r
), "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
1679 c
= gtk_tree_view_column_new_with_attributes( str
, r
, "text", WEBSEED_COL_URL
, NULL
);
1680 g_object_set( G_OBJECT( c
), "expand", TRUE
, NULL
);
1681 gtk_tree_view_column_set_sort_column_id( c
, WEBSEED_COL_URL
);
1682 gtk_tree_view_append_column( GTK_TREE_VIEW( v
), c
);
1684 str
= getWebseedColumnNames( WEBSEED_COL_DOWNLOAD_RATE_STRING
);
1685 r
= gtk_cell_renderer_text_new( );
1686 c
= gtk_tree_view_column_new_with_attributes( str
, r
, "text", WEBSEED_COL_DOWNLOAD_RATE_STRING
, NULL
);
1687 gtk_tree_view_column_set_sort_column_id( c
, WEBSEED_COL_DOWNLOAD_RATE_DOUBLE
);
1688 gtk_tree_view_append_column( GTK_TREE_VIEW( v
), c
);
1690 w
= gtk_scrolled_window_new( NULL
, NULL
);
1691 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( w
),
1692 GTK_POLICY_AUTOMATIC
,
1693 GTK_POLICY_AUTOMATIC
);
1694 gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( w
),
1696 gtk_container_add( GTK_CONTAINER( w
), v
);
1699 di
->webseed_view
= w
;
1703 store
= di
->peer_store
= peer_store_new( );
1704 m
= gtk_tree_model_sort_new_with_model( GTK_TREE_MODEL( store
) );
1705 gtk_tree_sortable_set_sort_column_id( GTK_TREE_SORTABLE( m
),
1707 GTK_SORT_DESCENDING
);
1708 v
= GTK_WIDGET( g_object_new( GTK_TYPE_TREE_VIEW
,
1711 "has-tooltip", TRUE
,
1715 g_signal_connect( v
, "query-tooltip",
1716 G_CALLBACK( onPeerViewQueryTooltip
), di
);
1717 g_object_unref( store
);
1718 g_signal_connect( v
, "button-release-event",
1719 G_CALLBACK( on_tree_view_button_released
), NULL
);
1721 setPeerViewColumns( GTK_TREE_VIEW( v
) );
1723 w
= sw
= gtk_scrolled_window_new( NULL
, NULL
);
1724 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( w
),
1725 GTK_POLICY_AUTOMATIC
,
1726 GTK_POLICY_AUTOMATIC
);
1727 gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( w
),
1729 gtk_container_add( GTK_CONTAINER( w
), v
);
1731 vbox
= gtr_vbox_new( FALSE
, GUI_PAD
);
1732 gtk_container_set_border_width( GTK_CONTAINER( vbox
), GUI_PAD_BIG
);
1734 #if GTK_CHECK_VERSION(3,2,0)
1735 v
= gtk_paned_new( GTK_ORIENTATION_VERTICAL
);
1737 v
= gtk_vpaned_new( );
1739 gtk_paned_pack1( GTK_PANED( v
), webtree
, FALSE
, TRUE
);
1740 gtk_paned_pack2( GTK_PANED( v
), sw
, TRUE
, TRUE
);
1741 gtk_box_pack_start( GTK_BOX( vbox
), v
, TRUE
, TRUE
, 0 );
1743 w
= gtk_check_button_new_with_mnemonic( _( "Show _more details" ) );
1744 di
->more_peer_details_check
= w
;
1745 b
= gtr_pref_flag_get( PREF_KEY_SHOW_MORE_PEER_INFO
);
1746 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( w
), b
);
1747 g_signal_connect( w
, "toggled", G_CALLBACK( onMorePeerInfoToggled
), di
);
1748 gtk_box_pack_start( GTK_BOX( vbox
), w
, FALSE
, FALSE
, 0 );
1751 /* ip-to-GtkTreeRowReference */
1752 di
->peer_hash
= g_hash_table_new_full( g_str_hash
,
1754 (GDestroyNotify
)g_free
,
1755 (GDestroyNotify
)gtk_tree_row_reference_free
);
1757 /* url-to-GtkTreeRowReference */
1758 di
->webseed_hash
= g_hash_table_new_full( g_str_hash
,
1760 (GDestroyNotify
)g_free
,
1761 (GDestroyNotify
)gtk_tree_row_reference_free
);
1774 /* if it's been longer than a minute, don't bother showing the seconds */
1776 tr_strltime_rounded( char * buf
, time_t t
, size_t buflen
)
1778 if( t
> 60 ) t
-= ( t
% 60 );
1779 tr_strltime( buf
, t
, buflen
);
1783 buildTrackerSummary( GString
* gstr
, const char * key
, const tr_tracker_stat
* st
, gboolean showScrape
)
1787 const time_t now
= time( NULL
);
1788 const char * err_markup_begin
= "<span color=\"red\">";
1789 const char * err_markup_end
= "</span>";
1790 const char * timeout_markup_begin
= "<span color=\"#224466\">";
1791 const char * timeout_markup_end
= "</span>";
1792 const char * success_markup_begin
= "<span color=\"#008B00\">";
1793 const char * success_markup_end
= "</span>";
1797 g_string_append( gstr
, st
->isBackup
? "<i>" : "<b>" );
1799 str
= g_markup_printf_escaped( "%s - %s", st
->host
, key
);
1801 str
= g_markup_printf_escaped( "%s", st
->host
);
1802 g_string_append( gstr
, str
);
1804 g_string_append( gstr
, st
->isBackup
? "</i>" : "</b>" );
1809 if( st
->hasAnnounced
&& st
->announceState
!= TR_TRACKER_INACTIVE
)
1811 g_string_append_c( gstr
, '\n' );
1812 tr_strltime_rounded( timebuf
, now
- st
->lastAnnounceTime
, sizeof( timebuf
) );
1813 if( st
->lastAnnounceSucceeded
)
1814 g_string_append_printf( gstr
, _( "Got a list of %1$s%2$'d peers%3$s %4$s ago" ),
1815 success_markup_begin
, st
->lastAnnouncePeerCount
, success_markup_end
,
1817 else if( st
->lastAnnounceTimedOut
)
1818 g_string_append_printf( gstr
, _( "Peer list request %1$stimed out%2$s %3$s ago; will retry" ),
1819 timeout_markup_begin
, timeout_markup_end
, timebuf
);
1821 g_string_append_printf( gstr
, _( "Got an error %1$s\"%2$s\"%3$s %4$s ago" ),
1822 err_markup_begin
, st
->lastAnnounceResult
, err_markup_end
, timebuf
);
1825 switch( st
->announceState
)
1827 case TR_TRACKER_INACTIVE
:
1828 g_string_append_c( gstr
, '\n' );
1829 g_string_append( gstr
, _( "No updates scheduled" ) );
1831 case TR_TRACKER_WAITING
:
1832 tr_strltime_rounded( timebuf
, st
->nextAnnounceTime
- now
, sizeof( timebuf
) );
1833 g_string_append_c( gstr
, '\n' );
1834 g_string_append_printf( gstr
, _( "Asking for more peers in %s" ), timebuf
);
1836 case TR_TRACKER_QUEUED
:
1837 g_string_append_c( gstr
, '\n' );
1838 g_string_append( gstr
, _( "Queued to ask for more peers" ) );
1840 case TR_TRACKER_ACTIVE
:
1841 tr_strltime_rounded( timebuf
, now
- st
->lastAnnounceStartTime
, sizeof( timebuf
) );
1842 g_string_append_c( gstr
, '\n' );
1843 g_string_append_printf( gstr
, _( "Asking for more peers now… <small>%s</small>" ), timebuf
);
1849 if( st
->hasScraped
) {
1850 g_string_append_c( gstr
, '\n' );
1851 tr_strltime_rounded( timebuf
, now
- st
->lastScrapeTime
, sizeof( timebuf
) );
1852 if( st
->lastScrapeSucceeded
)
1853 g_string_append_printf( gstr
, _( "Tracker had %s%'d seeders and %'d leechers%s %s ago" ),
1854 success_markup_begin
, st
->seederCount
, st
->leecherCount
, success_markup_end
,
1857 g_string_append_printf( gstr
, _( "Got a scrape error \"%s%s%s\" %s ago" ), err_markup_begin
, st
->lastScrapeResult
, err_markup_end
, timebuf
);
1860 switch( st
->scrapeState
)
1862 case TR_TRACKER_INACTIVE
:
1864 case TR_TRACKER_WAITING
:
1865 g_string_append_c( gstr
, '\n' );
1866 tr_strltime_rounded( timebuf
, st
->nextScrapeTime
- now
, sizeof( timebuf
) );
1867 g_string_append_printf( gstr
, _( "Asking for peer counts in %s" ), timebuf
);
1869 case TR_TRACKER_QUEUED
:
1870 g_string_append_c( gstr
, '\n' );
1871 g_string_append( gstr
, _( "Queued to ask for peer counts" ) );
1873 case TR_TRACKER_ACTIVE
:
1874 g_string_append_c( gstr
, '\n' );
1875 tr_strltime_rounded( timebuf
, now
- st
->lastScrapeStartTime
, sizeof( timebuf
) );
1876 g_string_append_printf( gstr
, _( "Asking for peer counts now… <small>%s</small>" ), timebuf
);
1885 TRACKER_COL_TORRENT_ID
,
1887 TRACKER_COL_IS_BACKUP
,
1888 TRACKER_COL_TRACKER_ID
,
1889 TRACKER_COL_FAVICON
,
1890 TRACKER_COL_WAS_UPDATED
,
1896 trackerVisibleFunc( GtkTreeModel
* model
, GtkTreeIter
* iter
, gpointer data
)
1899 struct DetailsImpl
* di
= data
;
1902 if( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( di
->all_check
) ) )
1905 /* don't show the backups... */
1906 gtk_tree_model_get( model
, iter
, TRACKER_COL_IS_BACKUP
, &isBackup
, -1 );
1911 tracker_list_get_current_torrent_id( struct DetailsImpl
* di
)
1913 int torrent_id
= -1;
1915 /* if there's only one torrent in the dialog, always use it */
1916 if( torrent_id
< 0 )
1917 if( g_slist_length( di
->ids
) == 1 )
1918 torrent_id
= GPOINTER_TO_INT( di
->ids
->data
);
1920 /* otherwise, use the selected tracker's torrent */
1921 if( torrent_id
< 0 ) {
1923 GtkTreeModel
* model
;
1924 GtkTreeSelection
* sel
= gtk_tree_view_get_selection( GTK_TREE_VIEW( di
->tracker_view
) );
1925 if( gtk_tree_selection_get_selected( sel
, &model
, &iter
) )
1926 gtk_tree_model_get( model
, &iter
, TRACKER_COL_TORRENT_ID
, &torrent_id
, -1 );
1933 tracker_list_get_current_torrent( struct DetailsImpl
* di
)
1935 const int torrent_id
= tracker_list_get_current_torrent_id( di
);
1936 return gtr_core_find_torrent( di
->core
, torrent_id
);
1940 favicon_ready_cb( gpointer pixbuf
, gpointer vreference
)
1943 GtkTreeRowReference
* reference
= vreference
;
1945 if( pixbuf
!= NULL
)
1947 GtkTreePath
* path
= gtk_tree_row_reference_get_path( reference
);
1948 GtkTreeModel
* model
= gtk_tree_row_reference_get_model( reference
);
1950 if( gtk_tree_model_get_iter( model
, &iter
, path
) )
1951 gtk_list_store_set( GTK_LIST_STORE( model
), &iter
,
1952 TRACKER_COL_FAVICON
, pixbuf
,
1955 gtk_tree_path_free( path
);
1957 g_object_unref( pixbuf
);
1960 gtk_tree_row_reference_free( reference
);
1964 refreshTracker( struct DetailsImpl
* di
, tr_torrent
** torrents
, int n
)
1968 tr_tracker_stat
** stats
;
1970 GtkTreeModel
* model
;
1971 GString
* gstr
= di
->gstr
; /* buffer for temporary strings */
1972 GHashTable
* hash
= di
->tracker_hash
;
1973 GtkListStore
* store
= di
->tracker_store
;
1974 tr_session
* session
= gtr_core_session( di
->core
);
1975 const gboolean showScrape
= gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( di
->scrape_check
) );
1977 /* step 1: get all the trackers */
1978 statCount
= g_new0( int, n
);
1979 stats
= g_new0( tr_tracker_stat
*, n
);
1980 for( i
=0; i
<n
; ++i
)
1981 stats
[i
] = tr_torrentTrackers( torrents
[i
], &statCount
[i
] );
1983 /* step 2: mark all the trackers in the list as not-updated */
1984 model
= GTK_TREE_MODEL( store
);
1985 if( gtk_tree_model_iter_nth_child( model
, &iter
, NULL
, 0 ) ) do
1986 gtk_list_store_set( store
, &iter
, TRACKER_COL_WAS_UPDATED
, FALSE
, -1 );
1987 while( gtk_tree_model_iter_next( model
, &iter
) );
1989 /* step 3: add any new trackers */
1990 for( i
=0; i
<n
; ++i
) {
1992 const int jn
= statCount
[i
];
1993 for( j
=0; j
<jn
; ++j
) {
1994 const tr_torrent
* tor
= torrents
[i
];
1995 const tr_tracker_stat
* st
= &stats
[i
][j
];
1996 const int torrent_id
= tr_torrentId( tor
);
1998 /* build the key to find the row */
1999 g_string_truncate( gstr
, 0 );
2000 g_string_append_printf( gstr
, "%d\t%d\t%s", torrent_id
, st
->tier
, st
->announce
);
2002 if( g_hash_table_lookup( hash
, gstr
->str
) == NULL
) {
2005 GtkTreeRowReference
* ref
;
2007 gtk_list_store_insert_with_values( store
, &iter
, -1,
2008 TRACKER_COL_TORRENT_ID
, torrent_id
,
2009 TRACKER_COL_TRACKER_ID
, st
->id
,
2010 TRACKER_COL_KEY
, gstr
->str
,
2013 p
= gtk_tree_model_get_path( model
, &iter
);
2014 ref
= gtk_tree_row_reference_new( model
, p
);
2015 g_hash_table_insert( hash
, g_strdup( gstr
->str
), ref
);
2016 ref
= gtk_tree_row_reference_new( model
, p
);
2017 gtr_get_favicon_from_url( session
, st
->announce
, favicon_ready_cb
, ref
);
2018 gtk_tree_path_free( p
);
2023 /* step 4: update the peers */
2024 for( i
=0; i
<n
; ++i
)
2027 const tr_torrent
* tor
= torrents
[i
];
2028 const char * summary_name
= n
>1 ? tr_torrentName( tor
) : NULL
;
2029 for( j
=0; j
<statCount
[i
]; ++j
)
2032 GtkTreeRowReference
* ref
;
2033 const tr_tracker_stat
* st
= &stats
[i
][j
];
2035 /* build the key to find the row */
2036 g_string_truncate( gstr
, 0 );
2037 g_string_append_printf( gstr
, "%d\t%d\t%s", tr_torrentId( tor
), st
->tier
, st
->announce
);
2038 ref
= g_hash_table_lookup( hash
, gstr
->str
);
2039 p
= gtk_tree_row_reference_get_path( ref
);
2040 gtk_tree_model_get_iter( model
, &iter
, p
);
2042 /* update the row */
2043 g_string_truncate( gstr
, 0 );
2044 buildTrackerSummary( gstr
, summary_name
, st
, showScrape
);
2045 gtk_list_store_set( store
, &iter
, TRACKER_COL_TEXT
, gstr
->str
,
2046 TRACKER_COL_IS_BACKUP
, st
->isBackup
,
2047 TRACKER_COL_TRACKER_ID
, st
->id
,
2048 TRACKER_COL_WAS_UPDATED
, TRUE
,
2052 gtk_tree_path_free( p
);
2056 /* step 5: remove trackers that have disappeared */
2057 if( gtk_tree_model_iter_nth_child( model
, &iter
, NULL
, 0 ) ) {
2058 gboolean more
= TRUE
;
2061 gtk_tree_model_get( model
, &iter
, TRACKER_COL_WAS_UPDATED
, &b
, -1 );
2063 more
= gtk_tree_model_iter_next( model
, &iter
);
2066 gtk_tree_model_get( model
, &iter
, TRACKER_COL_KEY
, &key
, -1 );
2067 g_hash_table_remove( hash
, key
);
2068 more
= gtk_list_store_remove( store
, &iter
);
2074 gtk_widget_set_sensitive( di
->edit_trackers_button
,
2075 tracker_list_get_current_torrent_id( di
) >= 0 );
2078 for( i
=0; i
<n
; ++i
)
2079 tr_torrentTrackersFree( stats
[i
], statCount
[i
] );
2081 g_free( statCount
);
2085 onScrapeToggled( GtkToggleButton
* button
, struct DetailsImpl
* di
)
2087 const char * key
= PREF_KEY_SHOW_MORE_TRACKER_INFO
;
2088 const gboolean value
= gtk_toggle_button_get_active( button
);
2089 gtr_core_set_pref_bool( di
->core
, key
, value
);
2094 onBackupToggled( GtkToggleButton
* button
, struct DetailsImpl
* di
)
2096 const char * key
= PREF_KEY_SHOW_BACKUP_TRACKERS
;
2097 const gboolean value
= gtk_toggle_button_get_active( button
);
2098 gtr_core_set_pref_bool( di
->core
, key
, value
);
2103 on_edit_trackers_response( GtkDialog
* dialog
, int response
, gpointer data
)
2105 gboolean do_destroy
= TRUE
;
2106 struct DetailsImpl
* di
= data
;
2108 if( response
== GTK_RESPONSE_ACCEPT
)
2112 GtkTextIter start
, end
;
2113 const int torrent_id
= GPOINTER_TO_INT( g_object_get_qdata( G_OBJECT( dialog
), TORRENT_ID_KEY
) );
2114 GtkTextBuffer
* text_buffer
= g_object_get_qdata( G_OBJECT( dialog
), TEXT_BUFFER_KEY
);
2115 tr_torrent
* tor
= gtr_core_find_torrent( di
->core
, torrent_id
);
2119 tr_tracker_info
* trackers
;
2120 char ** tracker_strings
;
2121 char * tracker_text
;
2123 /* build the array of trackers */
2124 gtk_text_buffer_get_bounds( text_buffer
, &start
, &end
);
2125 tracker_text
= gtk_text_buffer_get_text( text_buffer
, &start
, &end
, FALSE
);
2126 tracker_strings
= g_strsplit( tracker_text
, "\n", 0 );
2127 for( i
=0; tracker_strings
[i
]; )
2129 trackers
= g_new0( tr_tracker_info
, i
);
2130 for( i
=n
=tier
=0; tracker_strings
[i
]; ++i
) {
2131 const char * str
= tracker_strings
[i
];
2135 trackers
[n
].tier
= tier
;
2136 trackers
[n
].announce
= tracker_strings
[i
];
2141 /* update the torrent */
2142 if( tr_torrentSetAnnounceList( tor
, trackers
, n
) )
2146 const char * text
= _( "List contains invalid URLs" );
2147 w
= gtk_message_dialog_new( GTK_WINDOW( dialog
),
2150 GTK_BUTTONS_CLOSE
, "%s", text
);
2151 gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w
), "%s", _( "Please correct the errors and try again." ) );
2152 gtk_dialog_run( GTK_DIALOG( w
) );
2153 gtk_widget_destroy( w
);
2159 g_strfreev( tracker_strings
);
2160 g_free( tracker_text
);
2165 gtk_widget_destroy( GTK_WIDGET( dialog
) );
2169 get_editable_tracker_list( GString
* gstr
, const tr_torrent
* tor
)
2173 const tr_info
* inf
= tr_torrentInfo( tor
);
2174 for( i
=0; i
<inf
->trackerCount
; ++i
) {
2175 const tr_tracker_info
* t
= &inf
->trackers
[i
];
2176 if( tier
!= t
->tier
) {
2178 g_string_append_c( gstr
, '\n' );
2180 g_string_append_printf( gstr
, "%s\n", t
->announce
);
2183 g_string_truncate( gstr
, gstr
->len
-1 );
2187 on_edit_trackers( GtkButton
* button
, gpointer data
)
2189 struct DetailsImpl
* di
= data
;
2190 tr_torrent
* tor
= tracker_list_get_current_torrent( di
);
2195 GtkWidget
*w
, *d
, *fr
, *t
, *l
, *sw
;
2196 GtkWindow
* win
= GTK_WINDOW( gtk_widget_get_toplevel( GTK_WIDGET( button
) ) );
2197 GString
* gstr
= di
->gstr
; /* buffer for temporary strings */
2198 const int torrent_id
= tr_torrentId( tor
);
2200 g_string_truncate( gstr
, 0 );
2201 g_string_append_printf( gstr
, _( "%s - Edit Trackers" ), tr_torrentName( tor
) );
2202 d
= gtk_dialog_new_with_buttons( gstr
->str
, win
,
2203 GTK_DIALOG_MODAL
|GTK_DIALOG_DESTROY_WITH_PARENT
,
2204 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
2205 GTK_STOCK_SAVE
, GTK_RESPONSE_ACCEPT
,
2207 g_signal_connect( d
, "response", G_CALLBACK( on_edit_trackers_response
), data
);
2210 t
= hig_workarea_create( );
2211 hig_workarea_add_section_title( t
, &row
, _( "Tracker Announce URLs" ) );
2213 l
= gtk_label_new( NULL
);
2214 gtk_label_set_markup( GTK_LABEL( l
), _( "To add a backup URL, add it on the line after the primary URL.\n"
2215 "To add another primary URL, add it after a blank line." ) );
2216 gtk_label_set_justify( GTK_LABEL( l
), GTK_JUSTIFY_LEFT
);
2217 gtk_misc_set_alignment( GTK_MISC( l
), 0.0, 0.5 );
2218 hig_workarea_add_wide_control( t
, &row
, l
);
2220 w
= gtk_text_view_new( );
2221 gtk_widget_set_size_request( w
, 500u, 166u );
2222 g_string_truncate( gstr
, 0 );
2223 get_editable_tracker_list( gstr
, tor
);
2224 gtk_text_buffer_set_text( gtk_text_view_get_buffer( GTK_TEXT_VIEW( w
) ), gstr
->str
, -1 );
2225 fr
= gtk_frame_new( NULL
);
2226 gtk_frame_set_shadow_type( GTK_FRAME( fr
), GTK_SHADOW_IN
);
2227 sw
= gtk_scrolled_window_new( NULL
, NULL
);
2228 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( sw
),
2229 GTK_POLICY_AUTOMATIC
,
2230 GTK_POLICY_AUTOMATIC
);
2231 gtk_container_add( GTK_CONTAINER( sw
), w
);
2232 gtk_container_add( GTK_CONTAINER( fr
), sw
);
2233 hig_workarea_add_wide_tall_control( t
, &row
, fr
);
2235 hig_workarea_finish( t
, &row
);
2236 gtr_dialog_set_content( GTK_DIALOG( d
), t
);
2238 g_object_set_qdata( G_OBJECT( d
), TORRENT_ID_KEY
, GINT_TO_POINTER( torrent_id
) );
2239 g_object_set_qdata( G_OBJECT( d
), TEXT_BUFFER_KEY
, gtk_text_view_get_buffer( GTK_TEXT_VIEW( w
) ) );
2240 gtk_widget_show( d
);
2245 on_tracker_list_selection_changed( GtkTreeSelection
* sel
, gpointer gdi
)
2247 struct DetailsImpl
* di
= gdi
;
2248 const int n
= gtk_tree_selection_count_selected_rows( sel
);
2249 tr_torrent
* tor
= tracker_list_get_current_torrent( di
);
2251 gtk_widget_set_sensitive( di
->remove_tracker_button
, n
>0 );
2252 gtk_widget_set_sensitive( di
->add_tracker_button
, tor
!=NULL
);
2253 gtk_widget_set_sensitive( di
->edit_trackers_button
, tor
!=NULL
);
2257 on_add_tracker_response( GtkDialog
* dialog
, int response
, gpointer gdi
)
2259 gboolean destroy
= TRUE
;
2261 if( response
== GTK_RESPONSE_ACCEPT
)
2263 struct DetailsImpl
* di
= gdi
;
2264 GtkWidget
* e
= GTK_WIDGET( g_object_get_qdata( G_OBJECT( dialog
), URL_ENTRY_KEY
) );
2265 const int torrent_id
= GPOINTER_TO_INT( g_object_get_qdata( G_OBJECT( dialog
), TORRENT_ID_KEY
) );
2266 char * url
= g_strdup( gtk_entry_get_text( GTK_ENTRY( e
) ) );
2271 if( tr_urlIsValidTracker( url
) )
2273 char * json
= g_strdup_printf(
2275 " \"method\": \"torrent-set\",\n"
2276 " \"arguments\": { \"id\": %d, \"trackerAdd\": [ \"%s\" ] }\n"
2279 gtr_core_exec_json( di
->core
, json
);
2285 gtr_unrecognized_url_dialog( GTK_WIDGET( dialog
), url
);
2294 gtk_widget_destroy( GTK_WIDGET( dialog
) );
2298 on_tracker_list_add_button_clicked( GtkButton
* button UNUSED
, gpointer gdi
)
2300 struct DetailsImpl
* di
= gdi
;
2301 tr_torrent
* tor
= tracker_list_get_current_torrent( di
);
2309 GString
* gstr
= di
->gstr
; /* buffer for temporary strings */
2311 g_string_truncate( gstr
, 0 );
2312 g_string_append_printf( gstr
, _( "%s - Add Tracker" ), tr_torrentName( tor
) );
2313 w
= gtk_dialog_new_with_buttons( gstr
->str
, GTK_WINDOW( di
->dialog
),
2314 GTK_DIALOG_DESTROY_WITH_PARENT
,
2315 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
2316 GTK_STOCK_ADD
, GTK_RESPONSE_ACCEPT
,
2318 gtk_dialog_set_alternative_button_order( GTK_DIALOG( w
),
2319 GTK_RESPONSE_ACCEPT
,
2320 GTK_RESPONSE_CANCEL
,
2322 g_signal_connect( w
, "response", G_CALLBACK( on_add_tracker_response
), gdi
);
2325 t
= hig_workarea_create( );
2326 hig_workarea_add_section_title( t
, &row
, _( "Tracker" ) );
2327 e
= gtk_entry_new( );
2328 gtk_widget_set_size_request( e
, 400, -1 );
2329 gtr_paste_clipboard_url_into_entry( e
);
2330 g_object_set_qdata( G_OBJECT( w
), URL_ENTRY_KEY
, e
);
2331 g_object_set_qdata( G_OBJECT( w
), TORRENT_ID_KEY
, GINT_TO_POINTER( tr_torrentId( tor
) ) );
2332 hig_workarea_add_row( t
, &row
, _( "_Announce URL:" ), e
, NULL
);
2333 gtr_dialog_set_content( GTK_DIALOG( w
), t
);
2334 gtk_widget_show_all( w
);
2339 on_tracker_list_remove_button_clicked( GtkButton
* button UNUSED
, gpointer gdi
)
2342 GtkTreeModel
* model
;
2343 struct DetailsImpl
* di
= gdi
;
2344 GtkTreeView
* v
= GTK_TREE_VIEW( di
->tracker_view
);
2345 GtkTreeSelection
* sel
= gtk_tree_view_get_selection( v
);
2347 if( gtk_tree_selection_get_selected( sel
, &model
, &iter
) )
2352 gtk_tree_model_get( model
, &iter
, TRACKER_COL_TRACKER_ID
, &tracker_id
,
2353 TRACKER_COL_TORRENT_ID
, &torrent_id
,
2355 json
= g_strdup_printf( "{\n"
2356 " \"method\": \"torrent-set\",\n"
2357 " \"arguments\": { \"id\": %d, \"trackerRemove\": [ %d ] }\n"
2359 torrent_id
, tracker_id
);
2360 gtr_core_exec_json( di
->core
, json
);
2367 tracker_page_new( struct DetailsImpl
* di
)
2370 GtkCellRenderer
* r
;
2371 GtkTreeViewColumn
* c
;
2372 GtkTreeSelection
* sel
;
2373 GtkWidget
*vbox
, *sw
, *w
, *v
, *hbox
;
2374 const int pad
= ( GUI_PAD
+ GUI_PAD_BIG
) / 2;
2376 vbox
= gtr_vbox_new( FALSE
, GUI_PAD
);
2377 gtk_container_set_border_width( GTK_CONTAINER( vbox
), GUI_PAD_BIG
);
2379 di
->tracker_store
= gtk_list_store_new( TRACKER_N_COLS
, G_TYPE_INT
,
2386 di
->tracker_hash
= g_hash_table_new_full( g_str_hash
,
2388 (GDestroyNotify
)g_free
,
2389 (GDestroyNotify
)gtk_tree_row_reference_free
);
2390 di
->trackers_filtered
= gtk_tree_model_filter_new( GTK_TREE_MODEL( di
->tracker_store
), NULL
);
2391 gtk_tree_model_filter_set_visible_func( GTK_TREE_MODEL_FILTER( di
->trackers_filtered
),
2392 trackerVisibleFunc
, di
, NULL
);
2394 hbox
= gtr_hbox_new( FALSE
, GUI_PAD_BIG
);
2396 v
= di
->tracker_view
= gtk_tree_view_new_with_model( GTK_TREE_MODEL( di
->trackers_filtered
) );
2397 g_object_unref( di
->trackers_filtered
);
2398 gtk_tree_view_set_headers_visible( GTK_TREE_VIEW( v
), FALSE
);
2399 g_signal_connect( v
, "button-press-event", G_CALLBACK( on_tree_view_button_pressed
), NULL
);
2400 g_signal_connect( v
, "button-release-event", G_CALLBACK( on_tree_view_button_released
), NULL
);
2401 gtk_tree_view_set_rules_hint( GTK_TREE_VIEW( v
), TRUE
);
2403 sel
= gtk_tree_view_get_selection( GTK_TREE_VIEW( v
) );
2404 g_signal_connect( sel
, "changed", G_CALLBACK( on_tracker_list_selection_changed
), di
);
2406 c
= gtk_tree_view_column_new( );
2407 gtk_tree_view_column_set_title( c
, _( "Trackers" ) );
2408 gtk_tree_view_append_column( GTK_TREE_VIEW( v
), c
);
2410 r
= gtk_cell_renderer_pixbuf_new( );
2411 g_object_set( r
, "width", 20 + (GUI_PAD_SMALL
*2), "xpad", GUI_PAD_SMALL
, "ypad", pad
, "yalign", 0.0f
, NULL
);
2412 gtk_tree_view_column_pack_start( c
, r
, FALSE
);
2413 gtk_tree_view_column_add_attribute( c
, r
, "pixbuf", TRACKER_COL_FAVICON
);
2415 r
= gtk_cell_renderer_text_new( );
2416 g_object_set( G_OBJECT( r
), "ellipsize", PANGO_ELLIPSIZE_END
, "xpad", GUI_PAD_SMALL
, "ypad", pad
, NULL
);
2417 gtk_tree_view_column_pack_start( c
, r
, TRUE
);
2418 gtk_tree_view_column_add_attribute( c
, r
, "markup", TRACKER_COL_TEXT
);
2420 sw
= gtk_scrolled_window_new( NULL
, NULL
);
2421 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( sw
), GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
2422 gtk_container_add( GTK_CONTAINER( sw
), v
);
2423 w
= gtk_frame_new( NULL
);
2424 gtk_frame_set_shadow_type( GTK_FRAME( w
), GTK_SHADOW_IN
);
2425 gtk_container_add( GTK_CONTAINER( w
), sw
);
2427 gtk_box_pack_start( GTK_BOX( hbox
), w
, TRUE
, TRUE
, 0 );
2429 v
= gtr_vbox_new( FALSE
, GUI_PAD
);
2431 w
= gtk_button_new_with_mnemonic( _( "_Add" ) );
2432 di
->add_tracker_button
= w
;
2433 g_signal_connect( w
, "clicked", G_CALLBACK( on_tracker_list_add_button_clicked
), di
);
2434 gtk_box_pack_start( GTK_BOX( v
), w
, FALSE
, FALSE
, 0 );
2436 w
= gtk_button_new_with_mnemonic( _( "_Edit" ) );
2437 gtk_button_set_image( GTK_BUTTON( w
), gtk_image_new_from_stock( GTK_STOCK_EDIT
, GTK_ICON_SIZE_BUTTON
) );
2438 g_signal_connect( w
, "clicked", G_CALLBACK( on_edit_trackers
), di
);
2439 di
->edit_trackers_button
= w
;
2440 gtk_box_pack_start( GTK_BOX( v
), w
, FALSE
, FALSE
, 0 );
2442 w
= gtk_button_new_with_mnemonic( _( "_Remove" ) );
2443 di
->remove_tracker_button
= w
;
2444 g_signal_connect( w
, "clicked", G_CALLBACK( on_tracker_list_remove_button_clicked
), di
);
2445 gtk_box_pack_start( GTK_BOX( v
), w
, FALSE
, FALSE
, 0 );
2447 gtk_box_pack_start( GTK_BOX( hbox
), v
, FALSE
, FALSE
, 0 );
2449 gtk_box_pack_start( GTK_BOX( vbox
), hbox
, TRUE
, TRUE
, 0 );
2451 w
= gtk_check_button_new_with_mnemonic( _( "Show _more details" ) );
2452 di
->scrape_check
= w
;
2453 b
= gtr_pref_flag_get( PREF_KEY_SHOW_MORE_TRACKER_INFO
);
2454 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( w
), b
);
2455 g_signal_connect( w
, "toggled", G_CALLBACK( onScrapeToggled
), di
);
2456 gtk_box_pack_start( GTK_BOX( vbox
), w
, FALSE
, FALSE
, 0 );
2458 w
= gtk_check_button_new_with_mnemonic( _( "Show _backup trackers" ) );
2460 b
= gtr_pref_flag_get( PREF_KEY_SHOW_BACKUP_TRACKERS
);
2461 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( w
), b
);
2462 g_signal_connect( w
, "toggled", G_CALLBACK( onBackupToggled
), di
);
2463 gtk_box_pack_start( GTK_BOX( vbox
), w
, FALSE
, FALSE
, 0 );
2474 refresh( struct DetailsImpl
* di
)
2477 tr_torrent
** torrents
= getTorrents( di
, &n
);
2479 refreshInfo( di
, torrents
, n
);
2480 refreshPeers( di
, torrents
, n
);
2481 refreshTracker( di
, torrents
, n
);
2482 refreshOptions( di
, torrents
, n
);
2485 gtk_dialog_response( GTK_DIALOG( di
->dialog
), GTK_RESPONSE_CLOSE
);
2491 periodic_refresh( gpointer data
)
2498 details_free( gpointer gdata
)
2500 struct DetailsImpl
* data
= gdata
;
2501 g_source_remove( data
->periodic_refresh_tag
);
2502 g_hash_table_destroy( data
->tracker_hash
);
2503 g_hash_table_destroy( data
->webseed_hash
);
2504 g_hash_table_destroy( data
->peer_hash
);
2505 g_string_free( data
->gstr
, TRUE
);
2506 g_slist_free( data
->ids
);
2511 gtr_torrent_details_dialog_new( GtkWindow
* parent
, TrCore
* core
)
2513 GtkWidget
*d
, *n
, *v
, *w
, *l
;
2514 struct DetailsImpl
* di
= g_new0( struct DetailsImpl
, 1 );
2516 /* one-time setup */
2519 ARG_KEY
= g_quark_from_static_string( "tr-arg-key" );
2520 DETAILS_KEY
= g_quark_from_static_string( "tr-details-data-key" );
2521 TORRENT_ID_KEY
= g_quark_from_static_string( "tr-torrent-id-key" );
2522 TEXT_BUFFER_KEY
= g_quark_from_static_string( "tr-text-buffer-key" );
2523 URL_ENTRY_KEY
= g_quark_from_static_string( "tr-url-entry-key" );
2526 /* create the dialog */
2528 di
->gstr
= g_string_new( NULL
);
2529 d
= gtk_dialog_new_with_buttons( NULL
, parent
, 0,
2530 GTK_STOCK_CLOSE
, GTK_RESPONSE_CLOSE
,
2533 gtk_window_set_role( GTK_WINDOW( d
), "tr-info" );
2534 g_signal_connect_swapped( d
, "response",
2535 G_CALLBACK( gtk_widget_destroy
), d
);
2536 gtk_container_set_border_width( GTK_CONTAINER( d
), GUI_PAD
);
2537 g_object_set_qdata_full( G_OBJECT( d
), DETAILS_KEY
, di
, details_free
);
2539 n
= gtk_notebook_new( );
2540 gtk_container_set_border_width( GTK_CONTAINER( n
), GUI_PAD
);
2542 w
= info_page_new( di
);
2543 l
= gtk_label_new( _( "Information" ) );
2544 gtk_notebook_append_page( GTK_NOTEBOOK( n
), w
, l
);
2546 w
= peer_page_new( di
);
2547 l
= gtk_label_new( _( "Peers" ) );
2548 gtk_notebook_append_page( GTK_NOTEBOOK( n
), w
, l
);
2550 w
= tracker_page_new( di
);
2551 l
= gtk_label_new( _( "Trackers" ) );
2552 gtk_notebook_append_page( GTK_NOTEBOOK( n
), w
, l
);
2554 v
= gtr_vbox_new( FALSE
, 0 );
2555 di
->file_list
= gtr_file_list_new( core
, 0 );
2556 di
->file_label
= gtk_label_new( _( "File listing not available for combined torrent properties" ) );
2557 gtk_box_pack_start( GTK_BOX( v
), di
->file_list
, TRUE
, TRUE
, 0 );
2558 gtk_box_pack_start( GTK_BOX( v
), di
->file_label
, TRUE
, TRUE
, 0 );
2559 gtk_container_set_border_width( GTK_CONTAINER( v
), GUI_PAD_BIG
);
2560 l
= gtk_label_new( _( "Files" ) );
2561 gtk_notebook_append_page( GTK_NOTEBOOK( n
), v
, l
);
2563 w
= options_page_new( di
);
2564 l
= gtk_label_new( _( "Options" ) );
2565 gtk_notebook_append_page( GTK_NOTEBOOK( n
), w
, l
);
2567 gtr_dialog_set_content( GTK_DIALOG( d
), n
);
2568 di
->periodic_refresh_tag
= gdk_threads_add_timeout_seconds( SECONDARY_WINDOW_REFRESH_INTERVAL_SECONDS
,
2569 periodic_refresh
, di
);
2574 gtr_torrent_details_dialog_set_torrents( GtkWidget
* w
, GSList
* ids
)
2577 const int len
= g_slist_length( ids
);
2578 struct DetailsImpl
* di
= g_object_get_qdata( G_OBJECT( w
), DETAILS_KEY
);
2580 g_slist_free( di
->ids
);
2581 di
->ids
= g_slist_copy( ids
);
2585 const int id
= GPOINTER_TO_INT( ids
->data
);
2586 tr_torrent
* tor
= gtr_core_find_torrent( di
->core
, id
);
2587 const tr_info
* inf
= tr_torrentInfo( tor
);
2588 g_snprintf( title
, sizeof( title
), _( "%s Properties" ), inf
->name
);
2590 gtr_file_list_set_torrent( di
->file_list
, id
);
2591 gtk_widget_show( di
->file_list
);
2592 gtk_widget_hide( di
->file_label
);
2596 gtr_file_list_clear( di
->file_list
);
2597 gtk_widget_hide( di
->file_list
);
2598 gtk_widget_show( di
->file_label
);
2599 g_snprintf( title
, sizeof( title
), _( "%'d Torrent Properties" ), len
);
2602 gtk_window_set_title( GTK_WINDOW( w
), title
);