2 * This file Copyright (C) 2007-2010 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 11443 2010-11-29 12:42:12Z charles $
15 #include <stdio.h> /* sscanf */
17 #include <glib/gi18n.h>
20 #include <libtransmission/transmission.h>
21 #include <libtransmission/utils.h> /* tr_free */
25 #include "favicon.h" /* gtr_get_favicon() */
26 #include "file-list.h"
31 #define DETAILS_KEY "details-data"
37 GtkWidget
* honor_limits_check
;
38 GtkWidget
* up_limited_check
;
39 GtkWidget
* up_limit_sping
;
40 GtkWidget
* down_limited_check
;
41 GtkWidget
* down_limit_spin
;
42 GtkWidget
* bandwidth_combo
;
44 GtkWidget
* ratio_combo
;
45 GtkWidget
* ratio_spin
;
46 GtkWidget
* idle_combo
;
47 GtkWidget
* idle_spin
;
48 GtkWidget
* max_peers_spin
;
50 guint honor_limits_check_tag
;
51 guint up_limited_check_tag
;
52 guint down_limited_check_tag
;
53 guint down_limit_spin_tag
;
54 guint up_limit_spin_tag
;
55 guint bandwidth_combo_tag
;
56 guint ratio_combo_tag
;
60 guint max_peers_spin_tag
;
68 GtkWidget
* date_started_lb
;
70 GtkWidget
* last_activity_lb
;
73 GtkWidget
* privacy_lb
;
74 GtkWidget
* origin_lb
;
75 GtkWidget
* destination_lb
;
76 GtkTextBuffer
* comment_buffer
;
78 GHashTable
* peer_hash
;
79 GHashTable
* webseed_hash
;
80 GtkListStore
* peer_store
;
81 GtkListStore
* webseed_store
;
82 GtkWidget
* webseed_view
;
83 GtkWidget
* peer_view
;
84 GtkWidget
* more_peer_details_check
;
86 GtkListStore
* trackers
;
87 GtkTreeModel
* trackers_filtered
;
88 GtkWidget
* edit_trackers_button
;
89 GtkWidget
* tracker_view
;
90 GtkWidget
* scrape_check
;
91 GtkWidget
* all_check
;
92 GtkTextBuffer
* tracker_buffer
;
94 GtkWidget
* file_list
;
95 GtkWidget
* file_label
;
99 guint periodic_refresh_tag
;
103 getTorrents( struct DetailsImpl
* d
, int * setmeCount
)
105 int n
= g_slist_length( d
->ids
);
106 int torrentCount
= 0;
107 tr_session
* session
= tr_core_session( d
->core
);
108 tr_torrent
** torrents
= NULL
;
110 if( session
!= NULL
)
114 torrents
= g_new( tr_torrent
*, n
);
116 for( l
=d
->ids
; l
!=NULL
; l
=l
->next
) {
117 const int id
= GPOINTER_TO_INT( l
->data
);
118 tr_torrent
* tor
= tr_torrentFindFromId( session
, id
);
120 torrents
[torrentCount
++] = tor
;
124 *setmeCount
= torrentCount
;
135 set_togglebutton_if_different( GtkWidget
* w
, guint tag
, gboolean value
)
137 GtkToggleButton
* toggle
= GTK_TOGGLE_BUTTON( w
);
138 const gboolean currentValue
= gtk_toggle_button_get_active( toggle
);
139 if( currentValue
!= value
)
141 g_signal_handler_block( toggle
, tag
);
142 gtk_toggle_button_set_active( toggle
, value
);
143 g_signal_handler_unblock( toggle
, tag
);
148 set_int_spin_if_different( GtkWidget
* w
, guint tag
, int value
)
150 GtkSpinButton
* spin
= GTK_SPIN_BUTTON( w
);
151 const int currentValue
= gtk_spin_button_get_value_as_int( spin
);
152 if( currentValue
!= value
)
154 g_signal_handler_block( spin
, tag
);
155 gtk_spin_button_set_value( spin
, value
);
156 g_signal_handler_unblock( spin
, tag
);
161 set_double_spin_if_different( GtkWidget
* w
, guint tag
, double value
)
163 GtkSpinButton
* spin
= GTK_SPIN_BUTTON( w
);
164 const double currentValue
= gtk_spin_button_get_value( spin
);
165 if( ( (int)(currentValue
*100) != (int)(value
*100) ) )
167 g_signal_handler_block( spin
, tag
);
168 gtk_spin_button_set_value( spin
, value
);
169 g_signal_handler_unblock( spin
, tag
);
174 unset_combo( GtkWidget
* w
, guint tag
)
176 GtkComboBox
* combobox
= GTK_COMBO_BOX( w
);
178 g_signal_handler_block( combobox
, tag
);
179 gtk_combo_box_set_active( combobox
, -1 );
180 g_signal_handler_unblock( combobox
, tag
);
184 refreshOptions( struct DetailsImpl
* di
, tr_torrent
** torrents
, int n
)
190 /* honor_limits_check */
192 const tr_bool baseline
= tr_torrentUsesSessionLimits( torrents
[0] );
195 if( baseline
!= tr_torrentUsesSessionLimits( torrents
[i
] ) )
198 set_togglebutton_if_different( di
->honor_limits_check
,
199 di
->honor_limits_check_tag
, baseline
);
202 /* down_limited_check */
204 const tr_bool baseline
= tr_torrentUsesSpeedLimit( torrents
[0], TR_DOWN
);
207 if( baseline
!= tr_torrentUsesSpeedLimit( torrents
[i
], TR_DOWN
) )
210 set_togglebutton_if_different( di
->down_limited_check
,
211 di
->down_limited_check_tag
, baseline
);
214 /* down_limit_spin */
216 const int baseline
= tr_torrentGetSpeedLimit_KBps( torrents
[0], TR_DOWN
);
219 if( baseline
!= ( tr_torrentGetSpeedLimit_KBps( torrents
[i
], TR_DOWN
) ) )
222 set_int_spin_if_different( di
->down_limit_spin
,
223 di
->down_limit_spin_tag
, baseline
);
226 /* up_limited_check */
228 const tr_bool baseline
= tr_torrentUsesSpeedLimit( torrents
[0], TR_UP
);
231 if( baseline
!= tr_torrentUsesSpeedLimit( torrents
[i
], TR_UP
) )
234 set_togglebutton_if_different( di
->up_limited_check
,
235 di
->up_limited_check_tag
, baseline
);
240 const int baseline
= tr_torrentGetSpeedLimit_KBps( torrents
[0], TR_UP
);
243 if( baseline
!= ( tr_torrentGetSpeedLimit_KBps( torrents
[i
], TR_UP
) ) )
246 set_int_spin_if_different( di
->up_limit_sping
,
247 di
->up_limit_spin_tag
, baseline
);
250 /* bandwidth_combo */
252 const int baseline
= tr_torrentGetPriority( torrents
[0] );
255 if( baseline
!= tr_torrentGetPriority( torrents
[i
] ) )
258 GtkWidget
* w
= di
->bandwidth_combo
;
259 g_signal_handler_block( w
, di
->bandwidth_combo_tag
);
260 gtr_priority_combo_set_value( GTK_COMBO_BOX( w
), baseline
);
261 g_signal_handler_unblock( w
, di
->bandwidth_combo_tag
);
264 unset_combo( di
->bandwidth_combo
, di
->bandwidth_combo_tag
);
271 const int baseline
= tr_torrentGetRatioMode( torrents
[0] );
273 if( baseline
!= (int)tr_torrentGetRatioMode( torrents
[i
] ) )
276 GtkWidget
* w
= di
->ratio_combo
;
277 g_signal_handler_block( w
, di
->ratio_combo_tag
);
278 gtr_combo_box_set_active_enum( GTK_COMBO_BOX( w
), baseline
);
279 gtr_widget_set_visible( di
->ratio_spin
, baseline
== TR_RATIOLIMIT_SINGLE
);
280 g_signal_handler_unblock( w
, di
->ratio_combo_tag
);
284 const double baseline
= tr_torrentGetRatioLimit( torrents
[0] );
285 set_double_spin_if_different( di
->ratio_spin
,
286 di
->ratio_spin_tag
, baseline
);
293 const int baseline
= tr_torrentGetIdleMode( torrents
[0] );
295 if( baseline
!= (int)tr_torrentGetIdleMode( torrents
[i
] ) )
298 GtkWidget
* w
= di
->idle_combo
;
299 g_signal_handler_block( w
, di
->idle_combo_tag
);
300 gtr_combo_box_set_active_enum( GTK_COMBO_BOX( w
), baseline
);
301 gtr_widget_set_visible( di
->idle_spin
, baseline
== TR_IDLELIMIT_SINGLE
);
302 g_signal_handler_unblock( w
, di
->idle_combo_tag
);
306 const int baseline
= tr_torrentGetIdleLimit( torrents
[0] );
307 set_int_spin_if_different( di
->idle_spin
,
308 di
->idle_spin_tag
, baseline
);
313 const int baseline
= tr_torrentGetPeerLimit( torrents
[0] );
314 set_int_spin_if_different( di
->max_peers_spin
,
315 di
->max_peers_spin_tag
, baseline
);
320 torrent_set_bool( struct DetailsImpl
* di
, const char * key
, gboolean value
)
323 tr_benc top
, *args
, *ids
;
325 tr_bencInitDict( &top
, 2 );
326 tr_bencDictAddStr( &top
, "method", "torrent-set" );
327 args
= tr_bencDictAddDict( &top
, "arguments", 2 );
328 tr_bencDictAddBool( args
, key
, value
);
329 ids
= tr_bencDictAddList( args
, "ids", g_slist_length(di
->ids
) );
330 for( l
=di
->ids
; l
; l
=l
->next
)
331 tr_bencListAddInt( ids
, GPOINTER_TO_INT( l
->data
) );
333 tr_core_exec( di
->core
, &top
);
338 torrent_set_int( struct DetailsImpl
* di
, const char * key
, int value
)
341 tr_benc top
, *args
, *ids
;
343 tr_bencInitDict( &top
, 2 );
344 tr_bencDictAddStr( &top
, "method", "torrent-set" );
345 args
= tr_bencDictAddDict( &top
, "arguments", 2 );
346 tr_bencDictAddInt( args
, key
, value
);
347 ids
= tr_bencDictAddList( args
, "ids", g_slist_length(di
->ids
) );
348 for( l
=di
->ids
; l
; l
=l
->next
)
349 tr_bencListAddInt( ids
, GPOINTER_TO_INT( l
->data
) );
351 tr_core_exec( di
->core
, &top
);
356 torrent_set_real( struct DetailsImpl
* di
, const char * key
, double value
)
359 tr_benc top
, *args
, *ids
;
361 tr_bencInitDict( &top
, 2 );
362 tr_bencDictAddStr( &top
, "method", "torrent-set" );
363 args
= tr_bencDictAddDict( &top
, "arguments", 2 );
364 tr_bencDictAddReal( args
, key
, value
);
365 ids
= tr_bencDictAddList( args
, "ids", g_slist_length(di
->ids
) );
366 for( l
=di
->ids
; l
; l
=l
->next
)
367 tr_bencListAddInt( ids
, GPOINTER_TO_INT( l
->data
) );
369 tr_core_exec( di
->core
, &top
);
374 up_speed_toggled_cb( GtkToggleButton
* tb
, gpointer d
)
376 torrent_set_bool( d
, "uploadLimited", gtk_toggle_button_get_active( tb
) );
380 down_speed_toggled_cb( GtkToggleButton
*tb
, gpointer d
)
382 torrent_set_bool( d
, "downloadLimited", gtk_toggle_button_get_active( tb
) );
386 global_speed_toggled_cb( GtkToggleButton
* tb
, gpointer d
)
388 torrent_set_bool( d
, "honorsSessionLimits", gtk_toggle_button_get_active( tb
) );
392 up_speed_spun_cb( GtkSpinButton
* s
, struct DetailsImpl
* di
)
394 torrent_set_int( di
, "uploadLimit", gtk_spin_button_get_value_as_int( s
) );
398 down_speed_spun_cb( GtkSpinButton
* s
, struct DetailsImpl
* di
)
400 torrent_set_int( di
, "downloadLimit", gtk_spin_button_get_value_as_int( s
) );
404 idle_spun_cb( GtkSpinButton
* s
, struct DetailsImpl
* di
)
406 torrent_set_int( di
, "seedInactiveLimit", gtk_spin_button_get_value_as_int( s
) );
410 ratio_spun_cb( GtkSpinButton
* s
, struct DetailsImpl
* di
)
412 torrent_set_real( di
, "seedRatioLimit", gtk_spin_button_get_value( s
) );
416 max_peers_spun_cb( GtkSpinButton
* s
, struct DetailsImpl
* di
)
418 torrent_set_int( di
, "peer-limit", gtk_spin_button_get_value( s
) );
422 onPriorityChanged( GtkComboBox
* combo_box
, struct DetailsImpl
* di
)
424 const tr_priority_t priority
= gtr_priority_combo_get_value( combo_box
);
425 torrent_set_int( di
, "bandwidthPriority", priority
);
429 new_priority_combo( struct DetailsImpl
* di
)
431 GtkWidget
* w
= gtr_priority_combo_new( );
432 di
->bandwidth_combo_tag
= g_signal_connect( w
, "changed", G_CALLBACK( onPriorityChanged
), di
);
436 static void refresh( struct DetailsImpl
* di
);
438 #define ARG_KEY "arg-key"
441 onComboEnumChanged( GtkComboBox
* combo_box
, struct DetailsImpl
* di
)
443 const char * key
= g_object_get_data( G_OBJECT( combo_box
), ARG_KEY
);
444 torrent_set_int( di
, key
, gtr_combo_box_get_active_enum( combo_box
) );
449 ratio_combo_new( void )
451 GtkWidget
* w
= gtr_combo_box_new_enum( _( "Use global settings" ), TR_RATIOLIMIT_GLOBAL
,
452 _( "Seed regardless of ratio" ), TR_RATIOLIMIT_UNLIMITED
,
453 _( "Stop seeding at ratio:" ), TR_RATIOLIMIT_SINGLE
,
455 g_object_set_data_full( G_OBJECT( w
), ARG_KEY
, g_strdup( "seedRatioMode" ), g_free
);
460 idle_combo_new( void )
462 GtkWidget
* w
= gtr_combo_box_new_enum ( _( "Use global settings" ), TR_IDLELIMIT_GLOBAL
,
463 _( "Seed regardless of activity" ), TR_IDLELIMIT_UNLIMITED
,
464 _( "Stop seeding if idle for N minutes:" ), TR_IDLELIMIT_SINGLE
,
466 g_object_set_data_full( G_OBJECT( w
), ARG_KEY
, g_strdup( "seedIdleMode" ), g_free
);
471 options_page_new( struct DetailsImpl
* d
)
476 GtkWidget
*t
, *w
, *tb
, *h
;
479 t
= hig_workarea_create( );
480 hig_workarea_add_section_title( t
, &row
, _( "Speed" ) );
482 tb
= hig_workarea_add_wide_checkbutton( t
, &row
, _( "Honor global _limits" ), 0 );
483 d
->honor_limits_check
= tb
;
484 tag
= g_signal_connect( tb
, "toggled", G_CALLBACK( global_speed_toggled_cb
), d
);
485 d
->honor_limits_check_tag
= tag
;
487 g_snprintf( buf
, sizeof( buf
), _( "Limit _download speed (%s):" ), _(speed_K_str
) );
488 tb
= gtk_check_button_new_with_mnemonic( buf
);
489 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( tb
), FALSE
);
490 d
->down_limited_check
= tb
;
491 tag
= g_signal_connect( tb
, "toggled", G_CALLBACK( down_speed_toggled_cb
), d
);
492 d
->down_limited_check_tag
= tag
;
494 w
= gtk_spin_button_new_with_range( 0, INT_MAX
, 5 );
495 tag
= g_signal_connect( w
, "value-changed", G_CALLBACK( down_speed_spun_cb
), d
);
496 d
->down_limit_spin_tag
= tag
;
497 hig_workarea_add_row_w( t
, &row
, tb
, w
, NULL
);
498 d
->down_limit_spin
= w
;
500 g_snprintf( buf
, sizeof( buf
), _( "Limit _upload speed (%s):" ), _(speed_K_str
) );
501 tb
= gtk_check_button_new_with_mnemonic( buf
);
502 d
->up_limited_check
= tb
;
503 tag
= g_signal_connect( tb
, "toggled", G_CALLBACK( up_speed_toggled_cb
), d
);
504 d
->up_limited_check_tag
= tag
;
506 w
= gtk_spin_button_new_with_range( 0, INT_MAX
, 5 );
507 tag
= g_signal_connect( w
, "value-changed", G_CALLBACK( up_speed_spun_cb
), d
);
508 d
->up_limit_spin_tag
= tag
;
509 hig_workarea_add_row_w( t
, &row
, tb
, w
, NULL
);
510 d
->up_limit_sping
= w
;
512 w
= new_priority_combo( d
);
513 hig_workarea_add_row( t
, &row
, _( "Torrent _priority:" ), w
, NULL
);
514 d
->bandwidth_combo
= w
;
516 hig_workarea_add_section_divider( t
, &row
);
517 hig_workarea_add_section_title( t
, &row
, _( "Seeding Limits" ) );
519 h
= gtk_hbox_new( FALSE
, GUI_PAD
);
520 w
= d
->ratio_combo
= ratio_combo_new( );
521 d
->ratio_combo_tag
= g_signal_connect( w
, "changed", G_CALLBACK( onComboEnumChanged
), d
);
522 gtk_box_pack_start( GTK_BOX( h
), w
, TRUE
, TRUE
, 0 );
523 w
= d
->ratio_spin
= gtk_spin_button_new_with_range( 0, 1000, .05 );
524 gtk_entry_set_width_chars( GTK_ENTRY( w
), 7 );
525 d
->ratio_spin_tag
= g_signal_connect( w
, "value-changed", G_CALLBACK( ratio_spun_cb
), d
);
526 gtk_box_pack_start( GTK_BOX( h
), w
, FALSE
, FALSE
, 0 );
527 hig_workarea_add_row( t
, &row
, _( "_Ratio:" ), h
, NULL
);
529 h
= gtk_hbox_new( FALSE
, GUI_PAD
);
530 w
= d
->idle_combo
= idle_combo_new( );
531 d
->idle_combo_tag
= g_signal_connect( w
, "changed", G_CALLBACK( onComboEnumChanged
), d
);
532 gtk_box_pack_start( GTK_BOX( h
), w
, TRUE
, TRUE
, 0 );
533 w
= d
->idle_spin
= gtk_spin_button_new_with_range( 1, INT_MAX
, 5 );
534 d
->idle_spin_tag
= g_signal_connect( w
, "value-changed", G_CALLBACK( idle_spun_cb
), d
);
535 gtk_box_pack_start( GTK_BOX( h
), w
, FALSE
, FALSE
, 0 );
536 hig_workarea_add_row( t
, &row
, _( "_Idle:" ), h
, NULL
);
538 hig_workarea_add_section_divider( t
, &row
);
539 hig_workarea_add_section_title( t
, &row
, _( "Peer Connections" ) );
541 w
= gtk_spin_button_new_with_range( 1, 3000, 5 );
542 hig_workarea_add_row( t
, &row
, _( "_Maximum peers:" ), w
, w
);
543 tag
= g_signal_connect( w
, "value-changed", G_CALLBACK( max_peers_spun_cb
), d
);
544 d
->max_peers_spin
= w
;
545 d
->max_peers_spin_tag
= tag
;
547 hig_workarea_finish( t
, &row
);
558 activityString( int activity
, tr_bool finished
)
562 case TR_STATUS_CHECK_WAIT
: return _( "Waiting to verify local data" );
563 case TR_STATUS_CHECK
: return _( "Verifying local data" );
564 case TR_STATUS_DOWNLOAD
: return _( "Downloading" );
565 case TR_STATUS_SEED
: return _( "Seeding" );
566 case TR_STATUS_STOPPED
: return finished
? _( "Finished" ) : _( "Paused" );
572 /* Only call gtk_label_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_label_set_text( GtkLabel
* lb
, const char * newstr
)
577 const char * oldstr
= gtk_label_get_text( lb
);
579 if( ( oldstr
== NULL
) || strcmp( oldstr
, newstr
) )
580 gtk_label_set_text( lb
, newstr
);
583 /* Only call gtk_text_buffer_set_text() if the new text differs from the old.
584 * This way if the user has text selected, refreshing won't deselect it */
586 gtr_text_buffer_set_text( GtkTextBuffer
* b
, const char * str
)
589 GtkTextIter start
, end
;
594 gtk_text_buffer_get_bounds( b
, &start
, &end
);
595 old_str
= gtk_text_buffer_get_text( b
, &start
, &end
, FALSE
);
597 if( ( old_str
== NULL
) || strcmp( old_str
, str
) )
598 gtk_text_buffer_set_text( b
, str
, -1 );
604 get_short_date_string( time_t t
)
610 return g_strdup( _( "N/A" ) );
612 tr_localtime_r( &t
, &tm
);
613 strftime( buf
, sizeof( buf
), "%d %b %Y", &tm
);
614 return g_locale_to_utf8( buf
, -1, NULL
, NULL
, NULL
);
618 refreshInfo( struct DetailsImpl
* di
, tr_torrent
** torrents
, int n
)
622 const char * mixed
= _( "Mixed" );
623 const char * no_torrent
= _( "No Torrents Selected" );
624 const char * stateString
;
626 uint64_t sizeWhenDone
= 0;
627 const tr_stat
** stats
= g_new( const tr_stat
*, n
);
628 const tr_info
** infos
= g_new( const tr_info
*, n
);
629 for( i
=0; i
<n
; ++i
) {
630 stats
[i
] = tr_torrentStatCached( torrents
[i
] );
631 infos
[i
] = tr_torrentInfo( torrents
[i
] );
638 const tr_bool baseline
= infos
[0]->isPrivate
;
640 if( baseline
!= infos
[i
]->isPrivate
)
645 str
= _( "Private to this tracker -- DHT and PEX disabled" );
647 str
= _( "Public torrent" );
649 gtr_label_set_text( GTK_LABEL( di
->privacy_lb
), str
);
656 const char * creator
= infos
[0]->creator
? infos
[0]->creator
: "";
657 const time_t date
= infos
[0]->dateCreated
;
658 char * datestr
= get_short_date_string( date
);
659 gboolean mixed_creator
= FALSE
;
660 gboolean mixed_date
= FALSE
;
662 for( i
=1; i
<n
; ++i
) {
663 mixed_creator
|= strcmp( creator
, infos
[i
]->creator
? infos
[i
]->creator
: "" );
664 mixed_date
|= ( date
!= infos
[i
]->dateCreated
);
666 if( mixed_date
&& mixed_creator
)
670 g_snprintf( buf
, sizeof( buf
), _( "Created by %1$s" ), creator
);
671 else if( mixed_creator
|| !*creator
)
672 g_snprintf( buf
, sizeof( buf
), _( "Created on %1$s" ), datestr
);
674 g_snprintf( buf
, sizeof( buf
), _( "Created by %1$s on %2$s" ), creator
, datestr
);
680 gtr_label_set_text( GTK_LABEL( di
->origin_lb
), str
);
687 const char * baseline
= infos
[0]->comment
? infos
[0]->comment
: "";
689 if( strcmp( baseline
, infos
[i
]->comment
? infos
[i
]->comment
: "" ) )
696 gtr_text_buffer_set_text( di
->comment_buffer
, str
);
702 const char * baseline
= tr_torrentGetDownloadDir( torrents
[0] );
704 if( strcmp( baseline
, tr_torrentGetDownloadDir( torrents
[i
] ) ) )
711 gtr_label_set_text( GTK_LABEL( di
->destination_lb
), str
);
717 const tr_torrent_activity activity
= stats
[0]->activity
;
718 tr_bool allFinished
= stats
[0]->finished
;
719 for( i
=1; i
<n
; ++i
) {
720 if( activity
!= stats
[i
]->activity
)
722 if( !stats
[i
]->finished
)
725 str
= i
<n
? mixed
: activityString( activity
, allFinished
);
728 gtr_label_set_text( GTK_LABEL( di
->state_lb
), str
);
735 const time_t baseline
= stats
[0]->startDate
;
737 if( baseline
!= stats
[i
]->startDate
)
741 else if( ( baseline
<=0 ) || ( stats
[0]->activity
== TR_STATUS_STOPPED
) )
744 str
= tr_strltime( buf
, time(NULL
)-baseline
, sizeof( buf
) );
746 gtr_label_set_text( GTK_LABEL( di
->date_started_lb
), str
);
753 const int baseline
= stats
[0]->eta
;
755 if( baseline
!= stats
[i
]->eta
)
759 else if( baseline
< 0 )
760 str
= _( "Unknown" );
762 str
= tr_strltime( buf
, baseline
, sizeof( buf
) );
764 gtr_label_set_text( GTK_LABEL( di
->eta_lb
), str
);
772 int32_t pieceSize
= 0;
773 for( i
=0; i
<n
; ++i
) {
774 size
+= infos
[i
]->totalSize
;
775 pieces
+= infos
[i
]->pieceCount
;
777 pieceSize
= infos
[i
]->pieceSize
;
778 else if( pieceSize
!= (int)infos
[i
]->pieceSize
)
781 tr_strlsize( sizebuf
, size
, sizeof( sizebuf
) );
784 else if( pieceSize
>= 0 ) {
786 tr_formatter_mem_B( piecebuf
, pieceSize
, sizeof( piecebuf
) );
787 g_snprintf( buf
, sizeof( buf
),
788 gtr_ngettext( "%1$s (%2$'d piece @ %3$s)",
789 "%1$s (%2$'d pieces @ %3$s)", pieces
),
790 sizebuf
, pieces
, piecebuf
);
793 g_snprintf( buf
, sizeof( buf
),
794 gtr_ngettext( "%1$s (%2$'d piece)",
795 "%1$s (%2$'d pieces)", pieces
),
799 gtr_label_set_text( GTK_LABEL( di
->size_lb
), str
);
807 uint64_t leftUntilDone
= 0;
808 uint64_t haveUnchecked
= 0;
809 uint64_t haveValid
= 0;
810 uint32_t verifiedPieces
= 0;
811 uint64_t available
= 0;
812 for( i
=0; i
<n
; ++i
) {
813 const tr_stat
* st
= stats
[i
];
814 const tr_info
* inf
= infos
[i
];
815 haveUnchecked
+= st
->haveUnchecked
;
816 haveValid
+= st
->haveValid
;
817 verifiedPieces
+= inf
->pieceSize
? st
->haveValid
/ inf
->pieceSize
: 0;
818 sizeWhenDone
+= st
->sizeWhenDone
;
819 leftUntilDone
+= st
->leftUntilDone
;
820 available
+= st
->sizeWhenDone
- st
->leftUntilDone
+ st
->desiredAvailable
;
823 char buf2
[32], unver
[64], total
[64], avail
[32];
824 const double d
= sizeWhenDone
? ( 100.0 * available
) / sizeWhenDone
: 0;
825 const double ratio
= 100.0 * ( sizeWhenDone
? ( haveValid
+ haveUnchecked
) / (double)sizeWhenDone
: 1 );
826 tr_strlpercent( avail
, d
, sizeof( avail
) );
827 tr_strlpercent( buf2
, ratio
, sizeof( buf2
) );
828 tr_strlsize( total
, haveUnchecked
+ haveValid
, sizeof( total
) );
829 tr_strlsize( unver
, haveUnchecked
, sizeof( unver
) );
830 if( !haveUnchecked
&& !leftUntilDone
)
831 g_snprintf( buf
, sizeof( buf
), _( "%1$s (%2$s%%)" ), total
, buf2
);
832 else if( !haveUnchecked
)
833 g_snprintf( buf
, sizeof( buf
), _( "%1$s (%2$s%% of %3$s%% Available)" ), total
, buf2
, avail
);
835 g_snprintf( buf
, sizeof( buf
), _( "%1$s (%2$s%% of %3$s%% Available) + %4$s Unverified" ), total
, buf2
, avail
, unver
);
839 gtr_label_set_text( GTK_LABEL( di
->have_lb
), str
);
845 char dbuf
[64], fbuf
[64];
847 for( i
=0; i
<n
; ++i
) {
848 d
+= stats
[i
]->downloadedEver
;
849 f
+= stats
[i
]->corruptEver
;
851 tr_strlsize( dbuf
, d
, sizeof( dbuf
) );
852 tr_strlsize( fbuf
, f
, sizeof( fbuf
) );
854 g_snprintf( buf
, sizeof( buf
), _( "%1$s (+%2$s corrupt)" ), dbuf
, fbuf
);
856 tr_strlcpy( buf
, dbuf
, sizeof( buf
) );
859 gtr_label_set_text( GTK_LABEL( di
->dl_lb
), str
);
870 for( i
=0; i
<n
; ++i
) {
871 up
+= stats
[i
]->uploadedEver
;
872 down
+= stats
[i
]->downloadedEver
;
874 tr_strlsize( upstr
, up
, sizeof( upstr
) );
875 tr_strlratio( ratiostr
, tr_getRatio( up
, down
), sizeof( ratiostr
) );
876 g_snprintf( buf
, sizeof( buf
), _( "%s (Ratio: %s)" ), upstr
, ratiostr
);
879 gtr_label_set_text( GTK_LABEL( di
->ul_lb
), str
);
885 str
= infos
[0]->hashString
;
888 gtr_label_set_text( GTK_LABEL( di
->hash_lb
), str
);
894 const char * baseline
= stats
[0]->errorString
;
896 if( strcmp( baseline
, stats
[i
]->errorString
) )
904 str
= _( "No errors" );
905 gtr_label_set_text( GTK_LABEL( di
->error_lb
), str
);
914 if( latest
< stats
[i
]->activityDate
)
915 latest
= stats
[i
]->activityDate
;
919 const int period
= time( NULL
) - latest
;
921 tr_strlcpy( buf
, _( "Active now" ), sizeof( buf
) );
924 tr_strltime( tbuf
, period
, sizeof( tbuf
) );
925 g_snprintf( buf
, sizeof( buf
), _( "%1$s ago" ), tbuf
);
930 gtr_label_set_text( GTK_LABEL( di
->last_activity_lb
), str
);
937 info_page_new( struct DetailsImpl
* di
)
941 GtkWidget
*l
, *w
, *fr
, *sw
;
942 GtkWidget
*t
= hig_workarea_create( );
944 hig_workarea_add_section_title( t
, &row
, _( "Activity" ) );
947 l
= di
->size_lb
= gtk_label_new( NULL
);
948 hig_workarea_add_row( t
, &row
, _( "Torrent size:" ), l
, NULL
);
951 l
= di
->have_lb
= gtk_label_new( NULL
);
952 hig_workarea_add_row( t
, &row
, _( "Have:" ), l
, NULL
);
955 l
= di
->dl_lb
= gtk_label_new( NULL
);
956 hig_workarea_add_row( t
, &row
, _( "Downloaded:" ), l
, NULL
);
959 l
= di
->ul_lb
= gtk_label_new( NULL
);
960 hig_workarea_add_row( t
, &row
, _( "Uploaded:" ), l
, NULL
);
963 l
= di
->state_lb
= gtk_label_new( NULL
);
964 hig_workarea_add_row( t
, &row
, _( "State:" ), l
, NULL
);
967 l
= di
->date_started_lb
= gtk_label_new( NULL
);
968 hig_workarea_add_row( t
, &row
, _( "Running time:" ), l
, NULL
);
971 l
= di
->eta_lb
= gtk_label_new( NULL
);
972 hig_workarea_add_row( t
, &row
, _( "Remaining time:" ), l
, NULL
);
975 l
= di
->last_activity_lb
= gtk_label_new( NULL
);
976 hig_workarea_add_row( t
, &row
, _( "Last activity:" ), l
, NULL
);
979 l
= g_object_new( GTK_TYPE_LABEL
, "selectable", TRUE
,
980 "ellipsize", PANGO_ELLIPSIZE_END
,
982 hig_workarea_add_row( t
, &row
, _( "Error:" ), l
, NULL
);
986 hig_workarea_add_section_divider( t
, &row
);
987 hig_workarea_add_section_title( t
, &row
, _( "Details" ) );
990 l
= g_object_new( GTK_TYPE_LABEL
, "selectable", TRUE
,
991 "ellipsize", PANGO_ELLIPSIZE_END
,
993 hig_workarea_add_row( t
, &row
, _( "Location:" ), l
, NULL
);
994 di
->destination_lb
= l
;
997 l
= g_object_new( GTK_TYPE_LABEL
, "selectable", TRUE
,
998 "ellipsize", PANGO_ELLIPSIZE_END
,
1000 hig_workarea_add_row( t
, &row
, _( "Hash:" ), l
, NULL
);
1004 l
= gtk_label_new( NULL
);
1005 hig_workarea_add_row( t
, &row
, _( "Privacy:" ), l
, NULL
);
1009 l
= gtk_label_new( NULL
);
1010 hig_workarea_add_row( t
, &row
, _( "Origin:" ), l
, NULL
);
1014 b
= di
->comment_buffer
= gtk_text_buffer_new( NULL
);
1015 w
= gtk_text_view_new_with_buffer( b
);
1016 gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW( w
), GTK_WRAP_WORD
);
1017 gtk_text_view_set_editable( GTK_TEXT_VIEW( w
), FALSE
);
1018 sw
= gtk_scrolled_window_new( NULL
, NULL
);
1019 gtk_widget_set_size_request( sw
, 350, 36 );
1020 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( sw
),
1021 GTK_POLICY_AUTOMATIC
,
1022 GTK_POLICY_AUTOMATIC
);
1023 gtk_container_add( GTK_CONTAINER( sw
), w
);
1024 fr
= gtk_frame_new( NULL
);
1025 gtk_frame_set_shadow_type( GTK_FRAME( fr
), GTK_SHADOW_IN
);
1026 gtk_container_add( GTK_CONTAINER( fr
), sw
);
1027 w
= hig_workarea_add_row( t
, &row
, _( "Comment:" ), fr
, NULL
);
1028 gtk_misc_set_alignment( GTK_MISC( w
), 0.0f
, 0.0f
);
1030 hig_workarea_add_section_divider( t
, &row
);
1031 hig_workarea_finish( t
, &row
);
1034 hig_workarea_finish( t
, &row
);
1047 WEBSEED_COL_WAS_UPDATED
,
1049 WEBSEED_COL_DOWNLOAD_RATE_DOUBLE
,
1050 WEBSEED_COL_DOWNLOAD_RATE_STRING
,
1055 getWebseedColumnNames( int column
)
1059 case WEBSEED_COL_URL
: return _( "Webseeds" );
1060 case WEBSEED_COL_DOWNLOAD_RATE_DOUBLE
:
1061 case WEBSEED_COL_DOWNLOAD_RATE_STRING
: return _( "Down" );
1066 static GtkListStore
*
1067 webseed_model_new( void )
1069 return gtk_list_store_new( N_WEBSEED_COLS
,
1070 G_TYPE_STRING
, /* key */
1071 G_TYPE_BOOLEAN
, /* was-updated */
1072 G_TYPE_STRING
, /* url */
1073 G_TYPE_DOUBLE
, /* download rate double */
1074 G_TYPE_STRING
); /* download rate string */
1080 PEER_COL_WAS_UPDATED
,
1082 PEER_COL_ADDRESS_COLLATED
,
1083 PEER_COL_DOWNLOAD_RATE_DOUBLE
,
1084 PEER_COL_DOWNLOAD_RATE_STRING
,
1085 PEER_COL_UPLOAD_RATE_DOUBLE
,
1086 PEER_COL_UPLOAD_RATE_STRING
,
1089 PEER_COL_UPLOAD_REQUEST_COUNT_INT
,
1090 PEER_COL_UPLOAD_REQUEST_COUNT_STRING
,
1091 PEER_COL_DOWNLOAD_REQUEST_COUNT_INT
,
1092 PEER_COL_DOWNLOAD_REQUEST_COUNT_STRING
,
1093 PEER_COL_BLOCKS_DOWNLOADED_COUNT_INT
,
1094 PEER_COL_BLOCKS_DOWNLOADED_COUNT_STRING
,
1095 PEER_COL_BLOCKS_UPLOADED_COUNT_INT
,
1096 PEER_COL_BLOCKS_UPLOADED_COUNT_STRING
,
1097 PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_INT
,
1098 PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_STRING
,
1099 PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_INT
,
1100 PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_STRING
,
1101 PEER_COL_ENCRYPTION_STOCK_ID
,
1107 getPeerColumnName( int column
)
1111 case PEER_COL_ADDRESS
: return _( "Address" );
1112 case PEER_COL_DOWNLOAD_RATE_STRING
:
1113 case PEER_COL_DOWNLOAD_RATE_DOUBLE
: return _( "Down" );
1114 case PEER_COL_UPLOAD_RATE_STRING
:
1115 case PEER_COL_UPLOAD_RATE_DOUBLE
: return _( "Up" );
1116 case PEER_COL_CLIENT
: return _( "Client" );
1117 case PEER_COL_PROGRESS
: return _( "%" );
1118 case PEER_COL_UPLOAD_REQUEST_COUNT_INT
:
1119 case PEER_COL_UPLOAD_REQUEST_COUNT_STRING
: return _( "Up Reqs" );
1120 case PEER_COL_DOWNLOAD_REQUEST_COUNT_INT
:
1121 case PEER_COL_DOWNLOAD_REQUEST_COUNT_STRING
: return _( "Dn Reqs" );
1122 case PEER_COL_BLOCKS_DOWNLOADED_COUNT_INT
:
1123 case PEER_COL_BLOCKS_DOWNLOADED_COUNT_STRING
: return _( "Dn Blocks" );
1124 case PEER_COL_BLOCKS_UPLOADED_COUNT_INT
:
1125 case PEER_COL_BLOCKS_UPLOADED_COUNT_STRING
: return _( "Up Blocks" );
1126 case PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_INT
:
1127 case PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_STRING
: return _( "We Cancelled" );
1128 case PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_INT
:
1129 case PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_STRING
: return _( "They Cancelled" );
1130 case PEER_COL_STATUS
: return _( "Status" );
1135 static GtkListStore
*
1136 peer_store_new( void )
1138 return gtk_list_store_new( N_PEER_COLS
,
1139 G_TYPE_STRING
, /* key */
1140 G_TYPE_BOOLEAN
, /* was-updated */
1141 G_TYPE_STRING
, /* address */
1142 G_TYPE_STRING
, /* collated address */
1143 G_TYPE_DOUBLE
, /* download speed int */
1144 G_TYPE_STRING
, /* download speed string */
1145 G_TYPE_DOUBLE
, /* upload speed int */
1146 G_TYPE_STRING
, /* upload speed string */
1147 G_TYPE_STRING
, /* client */
1148 G_TYPE_INT
, /* progress [0..100] */
1149 G_TYPE_INT
, /* upload request count int */
1150 G_TYPE_STRING
, /* upload request count string */
1151 G_TYPE_INT
, /* download request count int */
1152 G_TYPE_STRING
, /* download request count string */
1153 G_TYPE_INT
, /* # blocks downloaded int */
1154 G_TYPE_STRING
, /* # blocks downloaded string */
1155 G_TYPE_INT
, /* # blocks uploaded int */
1156 G_TYPE_STRING
, /* # blocks uploaded string */
1157 G_TYPE_INT
, /* # blocks cancelled by client int */
1158 G_TYPE_STRING
, /* # blocks cancelled by client string */
1159 G_TYPE_INT
, /* # blocks cancelled by peer int */
1160 G_TYPE_STRING
, /* # blocks cancelled by peer string */
1161 G_TYPE_STRING
, /* encryption stock id */
1162 G_TYPE_STRING
); /* flagString */
1166 initPeerRow( GtkListStore
* store
,
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
,
1194 refreshPeerRow( GtkListStore
* store
,
1196 const tr_peer_stat
* peer
)
1198 char up_speed
[64] = { '\0' };
1199 char down_speed
[64] = { '\0' };
1200 char up_count
[64] = { '\0' };
1201 char down_count
[64] = { '\0' };
1202 char blocks_to_peer
[64] = { '\0' };
1203 char blocks_to_client
[64] = { '\0' };
1204 char cancelled_by_peer
[64] = { '\0' };
1205 char cancelled_by_client
[64] = { '\0' };
1207 if( peer
->rateToPeer_KBps
> 0.01 )
1208 tr_formatter_speed_KBps( up_speed
, peer
->rateToPeer_KBps
, sizeof( up_speed
) );
1210 if( peer
->rateToClient_KBps
> 0 )
1211 tr_formatter_speed_KBps( down_speed
, peer
->rateToClient_KBps
, sizeof( down_speed
) );
1213 if( peer
->pendingReqsToPeer
> 0 )
1214 g_snprintf( down_count
, sizeof( down_count
), "%d", peer
->pendingReqsToPeer
);
1216 if( peer
->pendingReqsToClient
> 0 )
1217 g_snprintf( up_count
, sizeof( down_count
), "%d", peer
->pendingReqsToClient
);
1219 if( peer
->blocksToPeer
> 0 )
1220 g_snprintf( blocks_to_peer
, sizeof( blocks_to_peer
), "%"PRIu32
, peer
->blocksToPeer
);
1222 if( peer
->blocksToClient
> 0 )
1223 g_snprintf( blocks_to_client
, sizeof( blocks_to_client
), "%"PRIu32
, peer
->blocksToClient
);
1225 if( peer
->cancelsToPeer
> 0 )
1226 g_snprintf( cancelled_by_client
, sizeof( cancelled_by_client
), "%"PRIu32
, peer
->cancelsToPeer
);
1228 if( peer
->cancelsToClient
> 0 )
1229 g_snprintf( cancelled_by_peer
, sizeof( cancelled_by_peer
), "%"PRIu32
, peer
->cancelsToClient
);
1231 gtk_list_store_set( store
, iter
,
1232 PEER_COL_PROGRESS
, (int)( 100.0 * peer
->progress
),
1233 PEER_COL_UPLOAD_REQUEST_COUNT_INT
, peer
->pendingReqsToClient
,
1234 PEER_COL_UPLOAD_REQUEST_COUNT_STRING
, up_count
,
1235 PEER_COL_DOWNLOAD_REQUEST_COUNT_INT
, peer
->pendingReqsToPeer
,
1236 PEER_COL_DOWNLOAD_REQUEST_COUNT_STRING
, down_count
,
1237 PEER_COL_DOWNLOAD_RATE_DOUBLE
, peer
->rateToClient_KBps
,
1238 PEER_COL_DOWNLOAD_RATE_STRING
, down_speed
,
1239 PEER_COL_UPLOAD_RATE_DOUBLE
, peer
->rateToPeer_KBps
,
1240 PEER_COL_UPLOAD_RATE_STRING
, up_speed
,
1241 PEER_COL_STATUS
, peer
->flagStr
,
1242 PEER_COL_WAS_UPDATED
, TRUE
,
1243 PEER_COL_BLOCKS_DOWNLOADED_COUNT_INT
, (int)peer
->blocksToClient
,
1244 PEER_COL_BLOCKS_DOWNLOADED_COUNT_STRING
, blocks_to_client
,
1245 PEER_COL_BLOCKS_UPLOADED_COUNT_INT
, (int)peer
->blocksToPeer
,
1246 PEER_COL_BLOCKS_UPLOADED_COUNT_STRING
, blocks_to_peer
,
1247 PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_INT
, (int)peer
->cancelsToPeer
,
1248 PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_STRING
, cancelled_by_client
,
1249 PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_INT
, (int)peer
->cancelsToClient
,
1250 PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_STRING
, cancelled_by_peer
,
1255 refreshPeerList( struct DetailsImpl
* di
, tr_torrent
** torrents
, int n
)
1260 GtkTreeModel
* model
;
1261 GHashTable
* hash
= di
->peer_hash
;
1262 GtkListStore
* store
= di
->peer_store
;
1263 struct tr_peer_stat
** peers
;
1265 /* step 1: get all the peers */
1266 peers
= g_new( struct tr_peer_stat
*, n
);
1267 peerCount
= g_new( int, n
);
1268 for( i
=0; i
<n
; ++i
)
1269 peers
[i
] = tr_torrentPeers( torrents
[i
], &peerCount
[i
] );
1271 /* step 2: mark all the peers in the list as not-updated */
1272 model
= GTK_TREE_MODEL( store
);
1273 if( gtk_tree_model_get_iter_first( model
, &iter
) ) do
1274 gtk_list_store_set( store
, &iter
, PEER_COL_WAS_UPDATED
, FALSE
, -1 );
1275 while( gtk_tree_model_iter_next( model
, &iter
) );
1277 /* step 3: add any new peers */
1278 for( i
=0; i
<n
; ++i
) {
1280 const tr_torrent
* tor
= torrents
[i
];
1281 for( j
=0; j
<peerCount
[i
]; ++j
) {
1282 const tr_peer_stat
* s
= &peers
[i
][j
];
1284 g_snprintf( key
, sizeof(key
), "%d.%s", tr_torrentId(tor
), s
->addr
);
1285 if( g_hash_table_lookup( hash
, key
) == NULL
) {
1287 gtk_list_store_append( store
, &iter
);
1288 initPeerRow( store
, &iter
, key
, s
);
1289 p
= gtk_tree_model_get_path( model
, &iter
);
1290 g_hash_table_insert( hash
, g_strdup( key
),
1291 gtk_tree_row_reference_new( model
, p
) );
1292 gtk_tree_path_free( p
);
1297 /* step 4: update the peers */
1298 for( i
=0; i
<n
; ++i
) {
1300 const tr_torrent
* tor
= torrents
[i
];
1301 for( j
=0; j
<peerCount
[i
]; ++j
) {
1302 const tr_peer_stat
* s
= &peers
[i
][j
];
1304 GtkTreeRowReference
* ref
;
1306 g_snprintf( key
, sizeof(key
), "%d.%s", tr_torrentId(tor
), s
->addr
);
1307 ref
= g_hash_table_lookup( hash
, key
);
1308 p
= gtk_tree_row_reference_get_path( ref
);
1309 gtk_tree_model_get_iter( model
, &iter
, p
);
1310 refreshPeerRow( store
, &iter
, s
);
1311 gtk_tree_path_free( p
);
1315 /* step 5: remove peers that have disappeared */
1316 model
= GTK_TREE_MODEL( store
);
1317 if( gtk_tree_model_get_iter_first( model
, &iter
) ) {
1318 gboolean more
= TRUE
;
1321 gtk_tree_model_get( model
, &iter
, PEER_COL_WAS_UPDATED
, &b
, -1 );
1323 more
= gtk_tree_model_iter_next( model
, &iter
);
1326 gtk_tree_model_get( model
, &iter
, PEER_COL_KEY
, &key
, -1 );
1327 g_hash_table_remove( hash
, key
);
1328 more
= gtk_list_store_remove( store
, &iter
);
1334 /* step 6: cleanup */
1335 for( i
=0; i
<n
; ++i
)
1336 tr_torrentPeersFree( peers
[i
], peerCount
[i
] );
1338 tr_free( peerCount
);
1342 refreshWebseedList( struct DetailsImpl
* di
, tr_torrent
** torrents
, int n
)
1347 GHashTable
* hash
= di
->webseed_hash
;
1348 GtkListStore
* store
= di
->webseed_store
;
1349 GtkTreeModel
* model
= GTK_TREE_MODEL( store
);
1351 /* step 1: mark all webseeds as not-updated */
1352 if( gtk_tree_model_get_iter_first( model
, &iter
) ) do
1353 gtk_list_store_set( store
, &iter
, WEBSEED_COL_WAS_UPDATED
, FALSE
, -1 );
1354 while( gtk_tree_model_iter_next( model
, &iter
) );
1356 /* step 2: add any new webseeds */
1357 for( i
=0; i
<n
; ++i
) {
1359 const tr_torrent
* tor
= torrents
[i
];
1360 const tr_info
* inf
= tr_torrentInfo( tor
);
1361 total
+= inf
->webseedCount
;
1362 for( j
=0; j
<inf
->webseedCount
; ++j
) {
1364 const char * url
= inf
->webseeds
[j
];
1365 g_snprintf( key
, sizeof(key
), "%d.%s", tr_torrentId( tor
), url
);
1366 if( g_hash_table_lookup( hash
, key
) == NULL
) {
1368 gtk_list_store_append( store
, &iter
);
1369 gtk_list_store_set( store
, &iter
, WEBSEED_COL_URL
, url
,
1370 WEBSEED_COL_KEY
, key
,
1372 p
= gtk_tree_model_get_path( model
, &iter
);
1373 g_hash_table_insert( hash
, g_strdup( key
),
1374 gtk_tree_row_reference_new( model
, p
) );
1375 gtk_tree_path_free( p
);
1380 /* step 3: update the webseeds */
1381 for( i
=0; i
<n
; ++i
) {
1383 const tr_torrent
* tor
= torrents
[i
];
1384 const tr_info
* inf
= tr_torrentInfo( tor
);
1385 double * speeds_KBps
= tr_torrentWebSpeeds_KBps( tor
);
1386 for( j
=0; j
<inf
->webseedCount
; ++j
) {
1389 const char * url
= inf
->webseeds
[j
];
1391 GtkTreeRowReference
* ref
;
1392 g_snprintf( key
, sizeof(key
), "%d.%s", tr_torrentId( tor
), url
);
1393 ref
= g_hash_table_lookup( hash
, key
);
1394 p
= gtk_tree_row_reference_get_path( ref
);
1395 gtk_tree_model_get_iter( model
, &iter
, p
);
1396 if( speeds_KBps
[j
] > 0 )
1397 tr_formatter_speed_KBps( buf
, speeds_KBps
[j
], sizeof( buf
) );
1400 gtk_list_store_set( store
, &iter
, WEBSEED_COL_DOWNLOAD_RATE_DOUBLE
, speeds_KBps
[j
],
1401 WEBSEED_COL_DOWNLOAD_RATE_STRING
, buf
,
1402 WEBSEED_COL_WAS_UPDATED
, TRUE
,
1404 gtk_tree_path_free( p
);
1406 tr_free( speeds_KBps
);
1409 /* step 4: remove webseeds that have disappeared */
1410 if( gtk_tree_model_get_iter_first( model
, &iter
) ) {
1411 gboolean more
= TRUE
;
1414 gtk_tree_model_get( model
, &iter
, WEBSEED_COL_WAS_UPDATED
, &b
, -1 );
1416 more
= gtk_tree_model_iter_next( model
, &iter
);
1419 gtk_tree_model_get( model
, &iter
, WEBSEED_COL_KEY
, &key
, -1 );
1421 g_hash_table_remove( hash
, key
);
1422 more
= gtk_list_store_remove( store
, &iter
);
1428 /* most of the time there are no webseeds...
1429 if that's the case, don't waste space showing an empty list */
1431 gtk_widget_show( di
->webseed_view
);
1433 gtk_widget_hide( di
->webseed_view
);
1437 refreshPeers( struct DetailsImpl
* di
, tr_torrent
** torrents
, int n
)
1439 refreshPeerList( di
, torrents
, n
);
1440 refreshWebseedList( di
, torrents
, n
);
1443 #if GTK_CHECK_VERSION( 2,12,0 )
1445 onPeerViewQueryTooltip( GtkWidget
* widget
,
1448 gboolean keyboard_tip
,
1449 GtkTooltip
* tooltip
,
1450 gpointer user_data UNUSED
)
1452 gboolean show_tip
= FALSE
;
1453 GtkTreeModel
* model
;
1456 if( gtk_tree_view_get_tooltip_context( GTK_TREE_VIEW( widget
),
1457 &x
, &y
, keyboard_tip
,
1458 &model
, NULL
, &iter
) )
1462 GString
* gstr
= g_string_new( NULL
);
1463 gtk_tree_model_get( model
, &iter
, PEER_COL_STATUS
, &str
, -1 );
1464 for( pch
= str
; pch
&& *pch
; ++pch
)
1466 const char * s
= NULL
;
1469 case 'O': s
= _( "Optimistic unchoke" ); break;
1470 case 'D': s
= _( "Downloading from this peer" ); break;
1471 case 'd': s
= _( "We would download from this peer if they would let us" ); break;
1472 case 'U': s
= _( "Uploading to peer" ); break;
1473 case 'u': s
= _( "We would upload to this peer if they asked" ); break;
1474 case 'K': s
= _( "Peer has unchoked us, but we're not interested" ); break;
1475 case '?': s
= _( "We unchoked this peer, but they're not interested" ); break;
1476 case 'E': s
= _( "Encrypted connection" ); break;
1477 case 'X': s
= _( "Peer was discovered through Peer Exchange (PEX)" ); break;
1478 case 'H': s
= _( "Peer was discovered through DHT" ); break;
1479 case 'I': s
= _( "Peer is an incoming connection" ); break;
1482 g_string_append_printf( gstr
, "%c: %s\n", *pch
, s
);
1484 if( gstr
->len
) /* remove the last linefeed */
1485 g_string_set_size( gstr
, gstr
->len
- 1 );
1486 gtk_tooltip_set_text( tooltip
, gstr
->str
);
1487 g_string_free( gstr
, TRUE
);
1497 setPeerViewColumns( GtkTreeView
* peer_view
)
1501 const tr_bool more
= pref_flag_get( PREF_KEY_SHOW_MORE_PEER_INFO
);
1502 int view_columns
[32];
1503 GtkTreeViewColumn
* c
;
1504 GtkCellRenderer
* r
;
1506 view_columns
[n
++] = PEER_COL_ENCRYPTION_STOCK_ID
;
1507 view_columns
[n
++] = PEER_COL_UPLOAD_RATE_STRING
;
1508 if( more
) view_columns
[n
++] = PEER_COL_UPLOAD_REQUEST_COUNT_STRING
;
1509 view_columns
[n
++] = PEER_COL_DOWNLOAD_RATE_STRING
;
1510 if( more
) view_columns
[n
++] = PEER_COL_DOWNLOAD_REQUEST_COUNT_STRING
;
1511 if( more
) view_columns
[n
++] = PEER_COL_BLOCKS_DOWNLOADED_COUNT_STRING
;
1512 if( more
) view_columns
[n
++] = PEER_COL_BLOCKS_UPLOADED_COUNT_STRING
;
1513 if( more
) view_columns
[n
++] = PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_STRING
;
1514 if( more
) view_columns
[n
++] = PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_STRING
;
1515 view_columns
[n
++] = PEER_COL_PROGRESS
;
1516 view_columns
[n
++] = PEER_COL_STATUS
;
1517 view_columns
[n
++] = PEER_COL_ADDRESS
;
1518 view_columns
[n
++] = PEER_COL_CLIENT
;
1520 /* remove any existing columns */
1523 GList
* columns
= gtk_tree_view_get_columns( peer_view
);
1524 for( l
=columns
; l
!=NULL
; l
=l
->next
)
1525 gtk_tree_view_remove_column( peer_view
, l
->data
);
1526 g_list_free( columns
);
1529 for( i
=0; i
<n
; ++i
)
1531 const int col
= view_columns
[i
];
1532 const char * t
= getPeerColumnName( col
);
1537 case PEER_COL_ADDRESS
:
1538 r
= gtk_cell_renderer_text_new( );
1539 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "text", col
, NULL
);
1540 sort_col
= PEER_COL_ADDRESS_COLLATED
;
1543 case PEER_COL_CLIENT
:
1544 r
= gtk_cell_renderer_text_new( );
1545 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "text", col
, NULL
);
1548 case PEER_COL_PROGRESS
:
1549 r
= gtk_cell_renderer_progress_new( );
1550 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "value", PEER_COL_PROGRESS
, NULL
);
1553 case PEER_COL_ENCRYPTION_STOCK_ID
:
1554 r
= gtk_cell_renderer_pixbuf_new( );
1555 g_object_set( r
, "xalign", (gfloat
)0.0,
1556 "yalign", (gfloat
)0.5,
1558 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "stock-id", PEER_COL_ENCRYPTION_STOCK_ID
, NULL
);
1559 gtk_tree_view_column_set_sizing( c
, GTK_TREE_VIEW_COLUMN_FIXED
);
1560 gtk_tree_view_column_set_fixed_width( c
, 20 );
1563 case PEER_COL_DOWNLOAD_REQUEST_COUNT_STRING
:
1564 r
= gtk_cell_renderer_text_new( );
1565 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "text", col
, NULL
);
1566 sort_col
= PEER_COL_DOWNLOAD_REQUEST_COUNT_INT
;
1568 case PEER_COL_UPLOAD_REQUEST_COUNT_STRING
:
1569 r
= gtk_cell_renderer_text_new( );
1570 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "text", col
, NULL
);
1571 sort_col
= PEER_COL_UPLOAD_REQUEST_COUNT_INT
;
1574 case PEER_COL_BLOCKS_DOWNLOADED_COUNT_STRING
:
1575 r
= gtk_cell_renderer_text_new( );
1576 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "text", col
, NULL
);
1577 sort_col
= PEER_COL_BLOCKS_DOWNLOADED_COUNT_INT
;
1579 case PEER_COL_BLOCKS_UPLOADED_COUNT_STRING
:
1580 r
= gtk_cell_renderer_text_new( );
1581 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "text", col
, NULL
);
1582 sort_col
= PEER_COL_BLOCKS_UPLOADED_COUNT_INT
;
1585 case PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_STRING
:
1586 r
= gtk_cell_renderer_text_new( );
1587 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "text", col
, NULL
);
1588 sort_col
= PEER_COL_REQS_CANCELLED_BY_CLIENT_COUNT_INT
;
1590 case PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_STRING
:
1591 r
= gtk_cell_renderer_text_new( );
1592 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "text", col
, NULL
);
1593 sort_col
= PEER_COL_REQS_CANCELLED_BY_PEER_COUNT_INT
;
1596 case PEER_COL_DOWNLOAD_RATE_STRING
:
1597 r
= gtk_cell_renderer_text_new( );
1598 g_object_set( G_OBJECT( r
), "xalign", 1.0f
, NULL
);
1599 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "text", col
, NULL
);
1600 sort_col
= PEER_COL_DOWNLOAD_RATE_DOUBLE
;
1602 case PEER_COL_UPLOAD_RATE_STRING
:
1603 r
= gtk_cell_renderer_text_new( );
1604 g_object_set( G_OBJECT( r
), "xalign", 1.0f
, NULL
);
1605 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "text", col
, NULL
);
1606 sort_col
= PEER_COL_UPLOAD_RATE_DOUBLE
;
1609 case PEER_COL_STATUS
:
1610 r
= gtk_cell_renderer_text_new( );
1611 c
= gtk_tree_view_column_new_with_attributes( t
, r
, "text", col
, NULL
);
1618 gtk_tree_view_column_set_resizable( c
, FALSE
);
1619 gtk_tree_view_column_set_sort_column_id( c
, sort_col
);
1620 gtk_tree_view_append_column( GTK_TREE_VIEW( peer_view
), c
);
1623 /* the 'expander' column has a 10-pixel margin on the left
1624 that doesn't look quite correct in any of these columns...
1625 so create a non-visible column and assign it as the
1626 'expander column. */
1628 GtkTreeViewColumn
*c
= gtk_tree_view_column_new( );
1629 gtk_tree_view_column_set_visible( c
, FALSE
);
1630 gtk_tree_view_append_column( GTK_TREE_VIEW( peer_view
), c
);
1631 gtk_tree_view_set_expander_column( GTK_TREE_VIEW( peer_view
), c
);
1636 onMorePeerInfoToggled( GtkToggleButton
* button
, struct DetailsImpl
* di
)
1638 const char * key
= PREF_KEY_SHOW_MORE_PEER_INFO
;
1639 const gboolean value
= gtk_toggle_button_get_active( button
);
1640 tr_core_set_pref_bool( di
->core
, key
, value
);
1641 setPeerViewColumns( GTK_TREE_VIEW( di
->peer_view
) );
1645 peer_page_new( struct DetailsImpl
* di
)
1649 GtkListStore
*store
;
1650 GtkWidget
*v
, *w
, *ret
, *sw
, *vbox
;
1651 GtkWidget
*webtree
= NULL
;
1653 GtkTreeViewColumn
* c
;
1654 GtkCellRenderer
* r
;
1658 store
= di
->webseed_store
= webseed_model_new( );
1659 v
= gtk_tree_view_new_with_model( GTK_TREE_MODEL( store
) );
1660 g_signal_connect( v
, "button-release-event", G_CALLBACK( on_tree_view_button_released
), NULL
);
1661 gtk_tree_view_set_rules_hint( GTK_TREE_VIEW( v
), TRUE
);
1662 g_object_unref( store
);
1664 str
= getWebseedColumnNames( WEBSEED_COL_URL
);
1665 r
= gtk_cell_renderer_text_new( );
1666 g_object_set( G_OBJECT( r
), "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
1667 c
= gtk_tree_view_column_new_with_attributes( str
, r
, "text", WEBSEED_COL_URL
, NULL
);
1668 g_object_set( G_OBJECT( c
), "expand", TRUE
, NULL
);
1669 gtk_tree_view_column_set_sort_column_id( c
, WEBSEED_COL_URL
);
1670 gtk_tree_view_append_column( GTK_TREE_VIEW( v
), c
);
1672 str
= getWebseedColumnNames( WEBSEED_COL_DOWNLOAD_RATE_STRING
);
1673 r
= gtk_cell_renderer_text_new( );
1674 c
= gtk_tree_view_column_new_with_attributes( str
, r
, "text", WEBSEED_COL_DOWNLOAD_RATE_STRING
, NULL
);
1675 gtk_tree_view_column_set_sort_column_id( c
, WEBSEED_COL_DOWNLOAD_RATE_DOUBLE
);
1676 gtk_tree_view_append_column( GTK_TREE_VIEW( v
), c
);
1678 w
= gtk_scrolled_window_new( NULL
, NULL
);
1679 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( w
),
1680 GTK_POLICY_AUTOMATIC
,
1681 GTK_POLICY_AUTOMATIC
);
1682 gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( w
),
1684 gtk_container_add( GTK_CONTAINER( w
), v
);
1687 di
->webseed_view
= w
;
1691 store
= di
->peer_store
= peer_store_new( );
1692 m
= gtk_tree_model_sort_new_with_model( GTK_TREE_MODEL( store
) );
1693 gtk_tree_sortable_set_sort_column_id( GTK_TREE_SORTABLE( m
),
1695 GTK_SORT_DESCENDING
);
1696 #if GTK_CHECK_VERSION( 2,12,0 )
1697 v
= GTK_WIDGET( g_object_new( GTK_TYPE_TREE_VIEW
,
1700 "has-tooltip", TRUE
,
1703 v
= GTK_WIDGET( g_object_new( GTK_TYPE_TREE_VIEW
,
1710 #if GTK_CHECK_VERSION( 2,12,0 )
1711 g_signal_connect( v
, "query-tooltip",
1712 G_CALLBACK( onPeerViewQueryTooltip
), NULL
);
1714 g_object_unref( store
);
1715 g_signal_connect( v
, "button-release-event",
1716 G_CALLBACK( on_tree_view_button_released
), NULL
);
1718 setPeerViewColumns( GTK_TREE_VIEW( v
) );
1720 w
= sw
= gtk_scrolled_window_new( NULL
, NULL
);
1721 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( w
),
1722 GTK_POLICY_AUTOMATIC
,
1723 GTK_POLICY_AUTOMATIC
);
1724 gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( w
),
1726 gtk_container_add( GTK_CONTAINER( w
), v
);
1728 vbox
= gtk_vbox_new( FALSE
, GUI_PAD
);
1729 gtk_container_set_border_width( GTK_CONTAINER( vbox
), GUI_PAD_BIG
);
1731 v
= gtk_vpaned_new( );
1732 gtk_paned_pack1( GTK_PANED( v
), webtree
, FALSE
, TRUE
);
1733 gtk_paned_pack2( GTK_PANED( v
), sw
, TRUE
, TRUE
);
1734 gtk_box_pack_start( GTK_BOX( vbox
), v
, TRUE
, TRUE
, 0 );
1736 w
= gtk_check_button_new_with_mnemonic( _( "Show _more details" ) );
1737 di
->more_peer_details_check
= w
;
1738 b
= pref_flag_get( PREF_KEY_SHOW_MORE_PEER_INFO
);
1739 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( w
), b
);
1740 g_signal_connect( w
, "toggled", G_CALLBACK( onMorePeerInfoToggled
), di
);
1741 gtk_box_pack_start( GTK_BOX( vbox
), w
, FALSE
, FALSE
, 0 );
1744 /* ip-to-GtkTreeRowReference */
1745 di
->peer_hash
= g_hash_table_new_full( g_str_hash
,
1747 (GDestroyNotify
)g_free
,
1748 (GDestroyNotify
)gtk_tree_row_reference_free
);
1750 /* url-to-GtkTreeRowReference */
1751 di
->webseed_hash
= g_hash_table_new_full( g_str_hash
,
1753 (GDestroyNotify
)g_free
,
1754 (GDestroyNotify
)gtk_tree_row_reference_free
);
1765 /* if it's been longer than a minute, don't bother showing the seconds */
1767 tr_strltime_rounded( char * buf
, time_t t
, size_t buflen
)
1769 if( t
> 60 ) t
-= ( t
% 60 );
1770 tr_strltime( buf
, t
, buflen
);
1774 buildTrackerSummary( const char * key
, const tr_tracker_stat
* st
, gboolean showScrape
)
1778 const time_t now
= time( NULL
);
1779 GString
* gstr
= g_string_new( NULL
);
1780 const char * err_markup_begin
= "<span color=\"red\">";
1781 const char * err_markup_end
= "</span>";
1782 const char * timeout_markup_begin
= "<span color=\"#224466\">";
1783 const char * timeout_markup_end
= "</span>";
1784 const char * success_markup_begin
= "<span color=\"#008B00\">";
1785 const char * success_markup_end
= "</span>";
1789 const char * host
= st
->host
;
1790 const char * pch
= strstr( host
, "://" );
1793 g_string_append( gstr
, st
->isBackup
? "<i>" : "<b>" );
1795 str
= g_markup_printf_escaped( "%s - %s", host
, key
);
1797 str
= g_markup_printf_escaped( "%s", host
);
1798 g_string_append( gstr
, str
);
1800 g_string_append( gstr
, st
->isBackup
? "</i>" : "</b>" );
1805 if( st
->hasAnnounced
&& st
->announceState
!= TR_TRACKER_INACTIVE
)
1807 g_string_append_c( gstr
, '\n' );
1808 tr_strltime_rounded( timebuf
, now
- st
->lastAnnounceTime
, sizeof( timebuf
) );
1809 if( st
->lastAnnounceSucceeded
)
1810 g_string_append_printf( gstr
, _( "Got a list of %1$s%2$'d peers%3$s %4$s ago" ),
1811 success_markup_begin
, st
->lastAnnouncePeerCount
, success_markup_end
,
1813 else if( st
->lastAnnounceTimedOut
)
1814 g_string_append_printf( gstr
, _( "Peer list request %1$stimed out%2$s %3$s ago; will retry" ),
1815 timeout_markup_begin
, timeout_markup_end
, timebuf
);
1817 g_string_append_printf( gstr
, _( "Got an error %1$s\"%2$s\"%3$s %4$s ago" ),
1818 err_markup_begin
, st
->lastAnnounceResult
, err_markup_end
, timebuf
);
1821 switch( st
->announceState
)
1823 case TR_TRACKER_INACTIVE
:
1824 g_string_append_c( gstr
, '\n' );
1825 g_string_append( gstr
, _( "No updates scheduled" ) );
1827 case TR_TRACKER_WAITING
:
1828 tr_strltime_rounded( timebuf
, st
->nextAnnounceTime
- now
, sizeof( timebuf
) );
1829 g_string_append_c( gstr
, '\n' );
1830 g_string_append_printf( gstr
, _( "Asking for more peers in %s" ), timebuf
);
1832 case TR_TRACKER_QUEUED
:
1833 g_string_append_c( gstr
, '\n' );
1834 g_string_append( gstr
, _( "Queued to ask for more peers" ) );
1836 case TR_TRACKER_ACTIVE
:
1837 tr_strltime_rounded( timebuf
, now
- st
->lastAnnounceStartTime
, sizeof( timebuf
) );
1838 g_string_append_c( gstr
, '\n' );
1839 g_string_append_printf( gstr
, _( "Asking for more peers now... <small>%s</small>" ), timebuf
);
1845 if( st
->hasScraped
) {
1846 g_string_append_c( gstr
, '\n' );
1847 tr_strltime_rounded( timebuf
, now
- st
->lastScrapeTime
, sizeof( timebuf
) );
1848 if( st
->lastScrapeSucceeded
)
1849 g_string_append_printf( gstr
, _( "Tracker had %s%'d seeders and %'d leechers%s %s ago" ),
1850 success_markup_begin
, st
->seederCount
, st
->leecherCount
, success_markup_end
,
1853 g_string_append_printf( gstr
, _( "Got a scrape error \"%s%s%s\" %s ago" ), err_markup_begin
, st
->lastScrapeResult
, err_markup_end
, timebuf
);
1856 switch( st
->scrapeState
)
1858 case TR_TRACKER_INACTIVE
:
1860 case TR_TRACKER_WAITING
:
1861 g_string_append_c( gstr
, '\n' );
1862 tr_strltime_rounded( timebuf
, st
->nextScrapeTime
- now
, sizeof( timebuf
) );
1863 g_string_append_printf( gstr
, _( "Asking for peer counts in %s" ), timebuf
);
1865 case TR_TRACKER_QUEUED
:
1866 g_string_append_c( gstr
, '\n' );
1867 g_string_append( gstr
, _( "Queued to ask for peer counts" ) );
1869 case TR_TRACKER_ACTIVE
:
1870 g_string_append_c( gstr
, '\n' );
1871 tr_strltime_rounded( timebuf
, now
- st
->lastScrapeStartTime
, sizeof( timebuf
) );
1872 g_string_append_printf( gstr
, _( "Asking for peer counts now... <small>%s</small>" ), timebuf
);
1878 return g_string_free( gstr
, FALSE
);
1883 TRACKER_COL_TORRENT_ID
,
1884 TRACKER_COL_TRACKER_INDEX
,
1887 TRACKER_COL_TORRENT_NAME
,
1888 TRACKER_COL_TRACKER_NAME
,
1889 TRACKER_COL_FAVICON
,
1894 trackerVisibleFunc( GtkTreeModel
* model
, GtkTreeIter
* iter
, gpointer data
)
1897 struct DetailsImpl
* di
= data
;
1900 if( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( di
->all_check
) ) )
1903 /* don't show the backups... */
1904 gtk_tree_model_get( model
, iter
, TRACKER_COL_BACKUP
, &isBackup
, -1 );
1909 populate_tracker_buffer( GtkTextBuffer
* buffer
, const tr_torrent
* tor
)
1913 GString
* gstr
= g_string_new( NULL
);
1914 const tr_info
* inf
= tr_torrentInfo( tor
);
1915 for( i
=0; i
<inf
->trackerCount
; ++i
) {
1916 const tr_tracker_info
* t
= &inf
->trackers
[i
];
1917 if( tier
!= t
->tier
) {
1919 g_string_append_c( gstr
, '\n' );
1921 g_string_append_printf( gstr
, "%s\n", t
->announce
);
1924 g_string_truncate( gstr
, gstr
->len
-1 );
1925 gtk_text_buffer_set_text( buffer
, gstr
->str
, -1 );
1926 g_string_free( gstr
, TRUE
);
1929 #define TORRENT_PTR_KEY "torrent-pointer"
1932 favicon_ready_cb( gpointer pixbuf
, gpointer vreference
)
1935 GtkTreeRowReference
* reference
= vreference
;
1937 if( pixbuf
!= NULL
)
1939 GtkTreePath
* path
= gtk_tree_row_reference_get_path( reference
);
1940 GtkTreeModel
* model
= gtk_tree_row_reference_get_model( reference
);
1942 if( gtk_tree_model_get_iter( model
, &iter
, path
) )
1943 gtk_list_store_set( GTK_LIST_STORE( model
), &iter
,
1944 TRACKER_COL_FAVICON
, pixbuf
,
1947 gtk_tree_path_free( path
);
1949 g_object_unref( pixbuf
);
1952 gtk_tree_row_reference_free( reference
);
1956 refreshTracker( struct DetailsImpl
* di
, tr_torrent
** torrents
, int n
)
1960 tr_tracker_stat
** stats
;
1962 GtkListStore
* store
= di
->trackers
;
1963 GtkTreeModel
* model
;
1964 const gboolean showScrape
= gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( di
->scrape_check
) );
1966 statCount
= g_new0( int, n
);
1967 stats
= g_new0( tr_tracker_stat
*, n
);
1968 for( i
=0; i
<n
; ++i
)
1969 stats
[i
] = tr_torrentTrackers( torrents
[i
], &statCount
[i
] );
1971 /* "edit trackers" button */
1972 gtk_widget_set_sensitive( di
->edit_trackers_button
, n
==1 );
1974 g_object_set_data( G_OBJECT( di
->edit_trackers_button
), TORRENT_PTR_KEY
, torrents
[0] );
1976 /* build the store if we don't already have it */
1979 GtkTreeModel
* filter
;
1981 store
= gtk_list_store_new( TRACKER_N_COLS
, G_TYPE_INT
,
1989 filter
= gtk_tree_model_filter_new( GTK_TREE_MODEL( store
), NULL
);
1990 gtk_tree_model_filter_set_visible_func( GTK_TREE_MODEL_FILTER( filter
),
1991 trackerVisibleFunc
, di
, NULL
);
1993 di
->trackers
= store
;
1994 di
->trackers_filtered
= filter
;
1996 gtk_tree_view_set_model( GTK_TREE_VIEW( di
->tracker_view
), filter
);
1998 g_object_unref( filter
);
1999 g_object_unref( store
);
2002 if( ( di
->tracker_buffer
== NULL
) && ( n
== 1 ) )
2004 di
->tracker_buffer
= gtk_text_buffer_new( NULL
);
2005 populate_tracker_buffer( di
->tracker_buffer
, torrents
[0] );
2008 /* add any missing rows (FIXME: doesn't handle edited trackers) */
2009 model
= GTK_TREE_MODEL( store
);
2010 if( n
&& !gtk_tree_model_get_iter_first( model
, &iter
) )
2012 tr_session
* session
= tr_core_session( di
->core
);
2014 for( i
=0; i
<n
; ++i
)
2017 const tr_torrent
* tor
= torrents
[i
];
2018 const int torrentId
= tr_torrentId( tor
);
2019 const tr_info
* inf
= tr_torrentInfo( tor
);
2021 for( j
=0; j
<statCount
[i
]; ++j
)
2024 GtkTreeRowReference
* reference
;
2025 const tr_tracker_stat
* st
= &stats
[i
][j
];
2027 gtk_list_store_insert_with_values( store
, &iter
, -1,
2028 TRACKER_COL_TORRENT_ID
, torrentId
,
2029 TRACKER_COL_TRACKER_INDEX
, j
,
2030 TRACKER_COL_TORRENT_NAME
, inf
->name
,
2031 TRACKER_COL_TRACKER_NAME
, st
->host
,
2034 path
= gtk_tree_model_get_path( model
, &iter
);
2035 reference
= gtk_tree_row_reference_new( model
, path
);
2036 gtr_get_favicon_from_url( session
, st
->announce
, favicon_ready_cb
, reference
);
2037 gtk_tree_path_free( path
);
2042 /* update the store */
2043 if( gtk_tree_model_get_iter_first( model
, &iter
) ) do
2048 gtk_tree_model_get( model
, &iter
, TRACKER_COL_TORRENT_ID
, &torrentId
,
2049 TRACKER_COL_TRACKER_INDEX
, &trackerIndex
,
2052 for( i
=0; i
<n
; ++i
)
2053 if( tr_torrentId( torrents
[i
] ) == torrentId
)
2056 if( i
<n
&& trackerIndex
<statCount
[i
] )
2058 const tr_tracker_stat
* st
= &stats
[i
][trackerIndex
];
2059 const char * key
= n
>1 ? tr_torrentInfo( torrents
[i
] )->name
: NULL
;
2060 char * text
= buildTrackerSummary( key
, st
, showScrape
);
2061 gtk_list_store_set( store
, &iter
, TRACKER_COL_TEXT
, text
,
2062 TRACKER_COL_BACKUP
, st
->isBackup
,
2067 while( gtk_tree_model_iter_next( model
, &iter
) );
2070 for( i
=0; i
<n
; ++i
)
2071 tr_torrentTrackersFree( stats
[i
], statCount
[i
] );
2073 g_free( statCount
);
2077 onScrapeToggled( GtkToggleButton
* button
, struct DetailsImpl
* di
)
2079 const char * key
= PREF_KEY_SHOW_MORE_TRACKER_INFO
;
2080 const gboolean value
= gtk_toggle_button_get_active( button
);
2081 tr_core_set_pref_bool( di
->core
, key
, value
);
2086 onBackupToggled( GtkToggleButton
* button
, struct DetailsImpl
* di
)
2088 const char * key
= PREF_KEY_SHOW_BACKUP_TRACKERS
;
2089 const gboolean value
= gtk_toggle_button_get_active( button
);
2090 tr_core_set_pref_bool( di
->core
, key
, value
);
2095 onEditTrackersResponse( GtkDialog
* dialog
, int response
, gpointer data
)
2097 gboolean do_destroy
= TRUE
;
2098 struct DetailsImpl
* di
= data
;
2100 if( response
== GTK_RESPONSE_ACCEPT
)
2104 GtkTextIter start
, end
;
2105 char * tracker_text
;
2106 char ** tracker_strings
;
2107 tr_tracker_info
* trackers
;
2108 tr_torrent
* tor
= g_object_get_data( G_OBJECT( dialog
), TORRENT_PTR_KEY
);
2110 /* build the array of trackers */
2111 gtk_text_buffer_get_bounds( di
->tracker_buffer
, &start
, &end
);
2112 tracker_text
= gtk_text_buffer_get_text( di
->tracker_buffer
, &start
, &end
, FALSE
);
2113 tracker_strings
= g_strsplit( tracker_text
, "\n", 0 );
2114 for( i
=0; tracker_strings
[i
]; )
2116 trackers
= g_new0( tr_tracker_info
, i
);
2117 for( i
=n
=tier
=0; tracker_strings
[i
]; ++i
) {
2118 const char * str
= tracker_strings
[i
];
2122 trackers
[n
].tier
= tier
;
2123 trackers
[n
].announce
= tracker_strings
[i
];
2128 /* update the torrent */
2129 if( !tr_torrentSetAnnounceList( tor
, trackers
, n
) )
2132 const char * text
= _( "List contains invalid URLs" );
2133 w
= gtk_message_dialog_new( GTK_WINDOW( dialog
),
2136 GTK_BUTTONS_CLOSE
, "%s", text
);
2137 gtk_dialog_run( GTK_DIALOG( w
) );
2138 gtk_widget_destroy( w
);
2143 di
->trackers
= NULL
;
2144 di
->tracker_buffer
= NULL
;
2149 g_strfreev( tracker_strings
);
2150 g_free( tracker_text
);
2153 if( response
== GTK_RESPONSE_CANCEL
)
2155 tr_torrent
* tor
= g_object_get_data( G_OBJECT( dialog
), TORRENT_PTR_KEY
);
2156 populate_tracker_buffer( di
->tracker_buffer
, tor
);
2160 gtk_widget_destroy( GTK_WIDGET( dialog
) );
2164 onEditTrackers( GtkButton
* button
, gpointer data
)
2167 GtkWidget
*w
, *d
, *fr
, *t
, *l
, *sw
;
2168 GtkWindow
* win
= GTK_WINDOW( gtk_widget_get_toplevel( GTK_WIDGET( button
) ) );
2169 struct DetailsImpl
* di
= data
;
2171 d
= gtk_dialog_new_with_buttons( _( "Edit Trackers" ), win
,
2172 GTK_DIALOG_MODAL
|GTK_DIALOG_DESTROY_WITH_PARENT
,
2173 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
2174 GTK_STOCK_SAVE
, GTK_RESPONSE_ACCEPT
,
2176 g_object_set_data( G_OBJECT( d
), TORRENT_PTR_KEY
,
2177 g_object_get_data( G_OBJECT( button
), TORRENT_PTR_KEY
) );
2178 g_signal_connect( d
, "response",
2179 G_CALLBACK( onEditTrackersResponse
), data
);
2182 t
= hig_workarea_create( );
2183 hig_workarea_add_section_title( t
, &row
, _( "Tracker Announce URLs" ) );
2185 l
= gtk_label_new( NULL
);
2186 gtk_label_set_markup( GTK_LABEL( l
), _( "To add a backup URL, add it on the line after the primary URL.\n"
2187 "To add another primary URL, add it after a blank line." ) );
2188 gtk_label_set_justify( GTK_LABEL( l
), GTK_JUSTIFY_LEFT
);
2189 gtk_misc_set_alignment( GTK_MISC( l
), 0.0, 0.5 );
2190 hig_workarea_add_wide_control( t
, &row
, l
);
2192 w
= gtk_text_view_new_with_buffer( di
->tracker_buffer
);
2193 gtk_widget_set_size_request( w
, 500u, 166u );
2194 fr
= gtk_frame_new( NULL
);
2195 gtk_frame_set_shadow_type( GTK_FRAME( fr
), GTK_SHADOW_IN
);
2196 sw
= gtk_scrolled_window_new( NULL
, NULL
);
2197 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( sw
),
2198 GTK_POLICY_AUTOMATIC
,
2199 GTK_POLICY_AUTOMATIC
);
2200 gtk_container_add( GTK_CONTAINER( sw
), w
);
2201 gtk_container_add( GTK_CONTAINER( fr
), sw
);
2202 hig_workarea_add_wide_tall_control( t
, &row
, fr
);
2204 hig_workarea_finish( t
, &row
);
2205 gtk_box_pack_start( GTK_BOX( GTK_DIALOG( d
)->vbox
), t
, TRUE
, TRUE
, GUI_PAD_SMALL
);
2206 gtk_widget_show_all( d
);
2210 tracker_page_new( struct DetailsImpl
* di
)
2214 GtkTreeViewColumn
*c
;
2215 GtkWidget
*vbox
, *sw
, *w
, *v
, *hbox
;
2216 const int pad
= ( GUI_PAD
+ GUI_PAD_BIG
) / 2;
2218 vbox
= gtk_vbox_new( FALSE
, GUI_PAD
);
2219 gtk_container_set_border_width( GTK_CONTAINER( vbox
), GUI_PAD_BIG
);
2221 v
= di
->tracker_view
= gtk_tree_view_new( );
2222 gtk_tree_view_set_headers_visible( GTK_TREE_VIEW( v
), FALSE
);
2223 g_signal_connect( v
, "button-press-event",
2224 G_CALLBACK( on_tree_view_button_pressed
), NULL
);
2225 g_signal_connect( v
, "button-release-event",
2226 G_CALLBACK( on_tree_view_button_released
), NULL
);
2227 gtk_tree_view_set_rules_hint( GTK_TREE_VIEW( v
), TRUE
);
2229 c
= gtk_tree_view_column_new( );
2230 gtk_tree_view_column_set_title( c
, _( "Trackers" ) );
2231 gtk_tree_view_append_column( GTK_TREE_VIEW( v
), c
);
2233 r
= gtk_cell_renderer_pixbuf_new( );
2234 g_object_set( r
, "width", 20 + (GUI_PAD_SMALL
*2), "xpad", GUI_PAD_SMALL
, "ypad", pad
, "yalign", 0.0f
, NULL
);
2235 gtk_tree_view_column_pack_start( c
, r
, FALSE
);
2236 gtk_tree_view_column_add_attribute( c
, r
, "pixbuf", TRACKER_COL_FAVICON
);
2238 r
= gtk_cell_renderer_text_new( );
2239 g_object_set( G_OBJECT( r
), "ellipsize", PANGO_ELLIPSIZE_END
, "xpad", GUI_PAD_SMALL
, "ypad", pad
, NULL
);
2240 gtk_tree_view_column_pack_start( c
, r
, TRUE
);
2241 gtk_tree_view_column_add_attribute( c
, r
, "markup", TRACKER_COL_TEXT
);
2243 sw
= gtk_scrolled_window_new( NULL
, NULL
);
2244 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( sw
),
2245 GTK_POLICY_AUTOMATIC
,
2246 GTK_POLICY_AUTOMATIC
);
2247 gtk_container_add( GTK_CONTAINER( sw
), v
);
2248 w
= gtk_frame_new( NULL
);
2249 gtk_frame_set_shadow_type( GTK_FRAME( w
), GTK_SHADOW_IN
);
2250 gtk_container_add( GTK_CONTAINER( w
), sw
);
2251 gtk_box_pack_start( GTK_BOX( vbox
), w
, TRUE
, TRUE
, 0 );
2253 hbox
= gtk_hbox_new( FALSE
, 0 );
2255 w
= gtk_check_button_new_with_mnemonic( _( "Show _more details" ) );
2256 di
->scrape_check
= w
;
2257 b
= pref_flag_get( PREF_KEY_SHOW_MORE_TRACKER_INFO
);
2258 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( w
), b
);
2259 g_signal_connect( w
, "toggled", G_CALLBACK( onScrapeToggled
), di
);
2260 gtk_box_pack_start( GTK_BOX( hbox
), w
, FALSE
, FALSE
, 0 );
2262 w
= gtk_button_new_with_mnemonic( _( "_Edit Trackers" ) );
2263 gtk_button_set_image( GTK_BUTTON( w
), gtk_image_new_from_stock( GTK_STOCK_EDIT
, GTK_ICON_SIZE_BUTTON
) );
2264 g_signal_connect( w
, "clicked", G_CALLBACK( onEditTrackers
), di
);
2265 gtk_box_pack_end( GTK_BOX( hbox
), w
, FALSE
, FALSE
, 0 );
2266 di
->edit_trackers_button
= w
;
2268 gtk_box_pack_start( GTK_BOX( vbox
), hbox
, FALSE
, FALSE
, 0 );
2270 w
= gtk_check_button_new_with_mnemonic( _( "Show _backup trackers" ) );
2272 b
= pref_flag_get( PREF_KEY_SHOW_BACKUP_TRACKERS
);
2273 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( w
), b
);
2274 g_signal_connect( w
, "toggled", G_CALLBACK( onBackupToggled
), di
);
2275 gtk_box_pack_start( GTK_BOX( vbox
), w
, FALSE
, FALSE
, 0 );
2286 refresh( struct DetailsImpl
* di
)
2289 tr_torrent
** torrents
= getTorrents( di
, &n
);
2291 refreshInfo( di
, torrents
, n
);
2292 refreshPeers( di
, torrents
, n
);
2293 refreshTracker( di
, torrents
, n
);
2294 refreshOptions( di
, torrents
, n
);
2297 gtk_dialog_response( GTK_DIALOG( di
->dialog
), GTK_RESPONSE_CLOSE
);
2303 periodic_refresh( gpointer data
)
2310 details_free( gpointer gdata
)
2312 struct DetailsImpl
* data
= gdata
;
2313 g_source_remove( data
->periodic_refresh_tag
);
2314 g_hash_table_destroy( data
->webseed_hash
);
2315 g_hash_table_destroy( data
->peer_hash
);
2316 g_slist_free( data
->ids
);
2321 torrent_inspector_new( GtkWindow
* parent
, TrCore
* core
)
2323 GtkWidget
* d
, * n
, * w
, * l
;
2324 struct DetailsImpl
* di
= g_new0( struct DetailsImpl
, 1 );
2326 /* create the dialog */
2328 d
= gtk_dialog_new_with_buttons( NULL
, parent
, 0,
2329 GTK_STOCK_CLOSE
, GTK_RESPONSE_CLOSE
,
2332 gtk_window_set_role( GTK_WINDOW( d
), "tr-info" );
2333 g_signal_connect_swapped( d
, "response",
2334 G_CALLBACK( gtk_widget_destroy
), d
);
2335 gtk_container_set_border_width( GTK_CONTAINER( d
), GUI_PAD
);
2336 g_object_set_data_full( G_OBJECT( d
), DETAILS_KEY
, di
, details_free
);
2338 n
= gtk_notebook_new( );
2339 gtk_container_set_border_width( GTK_CONTAINER( n
), GUI_PAD
);
2341 w
= info_page_new( di
);
2342 l
= gtk_label_new( _( "Information" ) );
2343 gtk_notebook_append_page( GTK_NOTEBOOK( n
), w
, l
);
2345 w
= peer_page_new( di
);
2346 l
= gtk_label_new( _( "Peers" ) );
2347 gtk_notebook_append_page( GTK_NOTEBOOK( n
), w
, l
);
2349 w
= tracker_page_new( di
);
2350 l
= gtk_label_new( _( "Trackers" ) );
2351 gtk_notebook_append_page( GTK_NOTEBOOK( n
), w
, l
);
2354 GtkWidget
* v
= gtk_vbox_new( FALSE
, 0 );
2355 di
->file_list
= file_list_new( core
, 0 );
2356 di
->file_label
= gtk_label_new( _( "File listing not available for combined torrent properties" ) );
2357 gtk_box_pack_start( GTK_BOX( v
), di
->file_list
, TRUE
, TRUE
, 0 );
2358 gtk_box_pack_start( GTK_BOX( v
), di
->file_label
, TRUE
, TRUE
, 0 );
2359 gtk_container_set_border_width( GTK_CONTAINER( v
), GUI_PAD_BIG
);
2360 l
= gtk_label_new( _( "Files" ) );
2361 gtk_notebook_append_page( GTK_NOTEBOOK( n
), v
, l
);
2364 w
= options_page_new( di
);
2365 l
= gtk_label_new( _( "Options" ) );
2366 gtk_notebook_append_page( GTK_NOTEBOOK( n
), w
, l
);
2368 gtk_box_pack_start( GTK_BOX( GTK_DIALOG( d
)->vbox
), n
, TRUE
, TRUE
, 0 );
2370 di
->periodic_refresh_tag
= gtr_timeout_add_seconds( SECONDARY_WINDOW_REFRESH_INTERVAL_SECONDS
,
2371 periodic_refresh
, di
);
2372 gtk_widget_show_all( GTK_DIALOG( d
)->vbox
);
2377 torrent_inspector_set_torrents( GtkWidget
* w
, GSList
* ids
)
2379 struct DetailsImpl
* di
= g_object_get_data( G_OBJECT( w
), DETAILS_KEY
);
2380 const int len
= g_slist_length( ids
);
2383 g_slist_free( di
->ids
);
2384 di
->ids
= g_slist_copy( ids
);
2388 const int id
= GPOINTER_TO_INT( ids
->data
);
2389 tr_session
* session
= tr_core_session( di
->core
);
2390 tr_torrent
* tor
= tr_torrentFindFromId( session
, id
);
2391 const tr_info
* inf
= tr_torrentInfo( tor
);
2392 g_snprintf( title
, sizeof( title
), _( "%s Properties" ), inf
->name
);
2394 file_list_set_torrent( di
->file_list
, id
);
2395 gtk_widget_show( di
->file_list
);
2396 gtk_widget_hide( di
->file_label
);
2400 file_list_clear( di
->file_list
);
2401 gtk_widget_hide( di
->file_list
);
2402 gtk_widget_show( di
->file_label
);
2403 g_snprintf( title
, sizeof( title
), _( "%'d Torrent Properties" ), len
);
2406 gtk_window_set_title( GTK_WINDOW( w
), title
);