2 * Copyright © Dave Perrett and Malcolm Jarvis
3 * This code is licensed under the GPL version 2.
4 * For details, see http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
9 function Transmission(){
13 Transmission.prototype =
15 /*--------------------------------------------
17 * C O N S T R U C T O R
19 *--------------------------------------------*/
21 initialize: function()
23 // Initialize the helper classes
24 this.remote = new TransmissionRemote(this);
26 // Initialize the implementation fields
27 this._current_search = '';
31 // Initialize the clutch preferences
32 Prefs.getClutchPrefs( this );
38 $('#pause_all_link').bind('click', function(e){ tr.stopAllClicked(e); });
39 $('#resume_all_link').bind('click', function(e){ tr.startAllClicked(e); });
40 $('#pause_selected_link').bind('click', function(e){ tr.stopSelectedClicked(e); } );
41 $('#resume_selected_link').bind('click', function(e){ tr.startSelectedClicked(e); });
42 $('#remove_link').bind('click', function(e){ tr.removeClicked(e); });
43 $('#filter_all_link').parent().bind('click', function(e){ tr.showAllClicked(e); });
44 $('#filter_active_link').parent().bind('click', function(e){ tr.showActiveClicked(e); });
45 $('#filter_downloading_link').parent().bind('click', function(e){ tr.showDownloadingClicked(e); });
46 $('#filter_seeding_link').parent().bind('click', function(e){ tr.showSeedingClicked(e); });
47 $('#filter_paused_link').parent().bind('click', function(e){ tr.showPausedClicked(e); });
48 $('#filter_finished_link').parent().bind('click', function(e){ tr.showFinishedClicked(e); });
49 $('#prefs_save_button').bind('click', function(e) { tr.savePrefsClicked(e); return false;});
50 $('#prefs_cancel_button').bind('click', function(e){ tr.cancelPrefsClicked(e); return false; });
51 $('#stats_close_button').bind('click', function(e){ tr.closeStatsClicked(e); return false; });
52 $('.inspector_tab').bind('click', function(e){ tr.inspectorTabClicked(e, this); });
53 $('.file_wanted_control').live('click', function(e){ tr.fileWantedClicked(e, this); });
54 $('.file_priority_control').live('click', function(e){ tr.filePriorityClicked(e, this); });
55 $('#files_select_all').live('click', function(e){ tr.filesSelectAllClicked(e, this); });
56 $('#files_deselect_all').live('click', function(e){ tr.filesDeselectAllClicked(e, this); });
57 $('#open_link').bind('click', function(e){ tr.openTorrentClicked(e); });
58 $('#upload_confirm_button').bind('click', function(e){ tr.confirmUploadClicked(e); return false;});
59 $('#upload_cancel_button').bind('click', function(e){ tr.cancelUploadClicked(e); return false; });
60 $('#turtle_button').bind('click', function(e){ tr.toggleTurtleClicked(e); return false; });
61 $('#prefs_tab_general_tab').click(function(e){ changeTab(this, 'prefs_tab_general') });
62 $('#prefs_tab_speed_tab').click(function(e){ changeTab(this, 'prefs_tab_speed') });
63 $('#torrent_upload_form').submit(function(){ $('#upload_confirm_button').click(); return false; });
66 $('#inspector_close').bind('click', function(e){ tr.hideInspector(); });
67 $('#preferences_link').bind('click', function(e){ tr.releaseClutchPreferencesButton(e); });
69 $(document).bind('keydown', function(e){ tr.keyDown(e); });
70 $('#torrent_container').bind('click', function(e){ tr.deselectAll( true ); });
71 $('#filter_toggle_link').bind('click', function(e){ tr.toggleFilterClicked(e); });
72 $('#inspector_link').bind('click', function(e){ tr.toggleInspectorClicked(e); });
74 this.setupSearchBox();
75 this.createContextMenu();
76 this.createSettingsMenu();
78 this.initTurtleDropDowns();
80 this._torrent_list = $('#torrent_list')[0];
81 this._inspector_file_list = $('#inspector_file_list')[0];
82 this._inspector_peers_list = $('#inspector_peers_list')[0];
83 this._inspector_trackers_list = $('#inspector_trackers_list')[0];
84 this._inspector_tab_files = $('#inspector_tab_files')[0];
85 this._toolbar_buttons = $('#torrent_global_menu ul li');
86 this._toolbar_pause_button = $('li#pause_selected')[0];
87 this._toolbar_pause_all_button = $('li#pause_all')[0];
88 this._toolbar_start_button = $('li#resume_selected')[0];
89 this._toolbar_start_all_button = $('li#resume_all')[0];
90 this._toolbar_remove_button = $('li#remove')[0];
91 this._context_pause_button = $('li#context_pause_selected')[0];
92 this._context_start_button = $('li#context_resume_selected')[0];
94 var ti = '#torrent_inspector_';
95 this._inspector = { };
96 this._inspector._info_tab = { };
97 this._inspector._info_tab.availability = $(ti+'availability')[0];
98 this._inspector._info_tab.comment = $(ti+'comment')[0];
99 this._inspector._info_tab.creator_date = $(ti+'creator_date')[0];
100 this._inspector._info_tab.creator = $(ti+'creator')[0];
101 this._inspector._info_tab.download_dir = $(ti+'download_dir')[0];
102 this._inspector._info_tab.downloaded = $(ti+'downloaded')[0];
103 this._inspector._info_tab.download_from = $(ti+'download_from')[0];
104 this._inspector._info_tab.download_speed = $(ti+'download_speed')[0];
105 this._inspector._info_tab.error = $(ti+'error')[0];
106 this._inspector._info_tab.hash = $(ti+'hash')[0];
107 this._inspector._info_tab.have = $(ti+'have')[0];
108 this._inspector._info_tab.name = $(ti+'name')[0];
109 this._inspector._info_tab.progress = $(ti+'progress')[0];
110 this._inspector._info_tab.ratio = $(ti+'ratio')[0];
111 this._inspector._info_tab.secure = $(ti+'secure')[0];
112 this._inspector._info_tab.size = $(ti+'size')[0];
113 this._inspector._info_tab.state = $(ti+'state')[0];
114 this._inspector._info_tab.pieces = $(ti+'pieces')[0];
115 this._inspector._info_tab.uploaded = $(ti+'uploaded')[0];
116 this._inspector._info_tab.upload_speed = $(ti+'upload_speed')[0];
117 this._inspector._info_tab.upload_to = $(ti+'upload_to')[0];
119 // Setup the preference box
120 this.setupPrefConstraints();
122 // Setup the prefs gui
123 this.initializeSettings( );
125 // Get preferences & torrents from the daemon
128 this.loadDaemonPrefs( async );
129 this.loadDaemonStats( async );
130 this.initializeAllTorrents();
132 this.togglePeriodicRefresh( true );
133 this.togglePeriodicSessionRefresh( true );
136 loadDaemonPrefs: function( async ){
138 this.remote.loadDaemonPrefs( function(data){
139 var o = data.arguments;
140 Prefs.getClutchPrefs( o );
145 loadDaemonStats: function( async ){
147 this.remote.loadDaemonStats( function(data){
148 var o = data.arguments;
153 preloadImages: function() {
156 'images/buttons/info_general.png',
157 'images/buttons/info_activity.png',
158 'images/buttons/info_files.png',
159 'images/buttons/toolbar_buttons.png',
160 'images/graphics/filter_bar.png',
161 'images/graphics/iphone_chrome.png',
162 'images/graphics/logo.png'
166 'images/buttons/info_general.png',
167 'images/buttons/info_activity.png',
168 'images/buttons/info_files.png',
169 'images/buttons/tab_backgrounds.png',
170 'images/buttons/toolbar_buttons.png',
171 'images/buttons/torrent_buttons.png',
172 'images/buttons/file_wanted_buttons.png',
173 'images/buttons/file_priority_buttons.png',
174 'images/graphics/chrome.png',
175 'images/graphics/filter_bar.png',
176 'images/graphics/logo.png',
177 'images/graphics/transfer_arrows.png',
178 'images/progress/progress.png'
182 loadImages: function() {
183 for( var i=0, row; row=arguments[i]; ++i )
184 jQuery("<img>").attr("src", row);
188 * Set up the preference validation
190 setupPrefConstraints: function() {
191 // only allow integers for speed limit & port options
192 $('div.preference input[@type=text]:not(#download_location)').blur( function() {
193 this.value = this.value.replace(/[^0-9]/gi, '');
194 if (this.value == '') {
195 if ($(this).is('#refresh_rate')) {
205 * Load the clutch prefs and init the GUI according to those prefs
207 initializeSettings: function( )
209 Prefs.getClutchPrefs( this );
211 // iPhone conditions in the section allow us to not
212 // include transmenu js to save some bandwidth; if we
213 // start using prefs on iPhone we need to weed
214 // transmenu refs out of that too.
216 $('#filter_' + this[Prefs._FilterMode] + '_link').parent().addClass('selected');
218 if (!iPhone) $('#sort_by_' + this[Prefs._SortMethod] ).selectMenuItem();
220 if (!iPhone && ( this[Prefs._SortDirection] == Prefs._SortDescending ) )
221 $('#reverse_sort_order').selectMenuItem();
223 if( this[Prefs._ShowFilter] )
226 if( !iPhone && this[Prefs._ShowInspector] )
227 this.showInspector( );
229 if( !iPhone && this[Prefs._CompactDisplayState] )
230 $('#compact_view').selectMenuItem();
234 * Set up the search box
236 setupSearchBox: function()
239 var search_box = $('#torrent_search');
240 search_box.bind('keyup click', {transmission: this}, function(event) {
241 tr.setSearch(this.value);
243 if (!$.browser.safari)
245 search_box.addClass('blur');
246 search_box[0].value = 'Filter';
247 search_box.bind('blur', {transmission: this}, function(event) {
248 if (this.value == '') {
249 $(this).addClass('blur');
250 this.value = 'Filter';
253 }).bind('focus', {}, function(event) {
254 if ($(this).is('.blur')) {
256 $(this).removeClass('blur');
262 contextStopSelected: function( ) {
263 this.stopSelectedTorrents( );
265 contextStartSelected: function( ) {
266 this.startSelectedTorrents( );
268 contextRemoveSelected: function( ) {
269 this.removeSelectedTorrents( );
271 contextRemoveDataSelected: function( ) {
272 this.removeSelectedTorrentsAndData( );
274 contextVerifySelected: function( ) {
275 this.verifySelectedTorrents( );
277 contextToggleInspector: function( ) {
278 this.toggleInspector( );
280 contextSelectAll: function( ) {
281 this.selectAll( true );
283 contextDeselectAll: function( ) {
284 this.deselectAll( true );
288 * Create the torrent right-click menu
290 createContextMenu: function() {
293 context_pause_selected: function(e){ tr.contextStopSelected(e); },
294 context_resume_selected: function(e){ tr.contextStartSelected(e); },
295 context_remove: function(e){ tr.contextRemoveSelected(e); },
296 context_removedata: function(e){ tr.contextRemoveDataSelected(e); },
297 context_verify: function(e){ tr.contextVerifySelected(e); },
298 context_toggle_inspector: function(e){ tr.contextToggleInspector(e); },
299 context_select_all: function(e){ tr.contextSelectAll(e); },
300 context_deselect_all: function(e){ tr.contextDeselectAll(e); }
303 // Setup the context menu
304 $('ul#torrent_list').contextMenu('torrent_context_menu', {
306 menuStyle: Menu.context.menu_style,
307 itemStyle: Menu.context.item_style,
308 itemHoverStyle: Menu.context.item_hover_style,
309 itemDisabledStyle: Menu.context.item_disabled_style,
311 boundingElement: $('div#torrent_container'),
312 boundingRightPad: 20,
313 boundingBottomPad: 5,
314 onContextMenu: function(e) {
315 var closestRow = $(e.target).closest('.torrent')[0]._torrent;
316 if(!closestRow.isSelected())
317 tr.setSelectedTorrent( closestRow, true );
324 * Create the footer settings menu
326 createSettingsMenu: function() {
328 $('#settings_menu').transMenu({
329 selected_char: '✔',
331 onClick: function(e){ return tr.processSettingsMenuEvent(e); }
334 $('#unlimited_download_rate').selectMenuItem();
335 $('#unlimited_upload_rate').selectMenuItem();
339 initTurtleDropDowns: function() {
340 var i, out, hour, mins, start, end, value, content;
341 // Build the list of times
343 start = $('#turtle_start_time')[0];
344 end = $('#turtle_end_time')[0];
345 for (i = 0; i < 24 * 4; i++) {
346 hour = parseInt(i / 4);
347 mins = ((i % 4) * 15);
350 content = hour + ":" + (mins == 0 ? "00" : mins);
351 start.options[i] = new Option(content, value);
352 end.options[i] = new Option(content, value);
356 /*--------------------------------------------
360 *--------------------------------------------*/
362 getAllTorrents: function()
365 for(var key in this._torrents)
366 torrents.push(this._torrents[key]);
370 getVisibleTorrents: function()
373 for( var i=0, row; row=this._rows[i]; ++i )
374 if( row._torrent && ( row[0].style.display != 'none' ) )
375 torrents.push( row._torrent );
379 getSelectedTorrents: function()
381 var v = this.getVisibleTorrents( );
383 for( var i=0, row; row=v[i]; ++i )
384 if( row.isSelected( ) )
389 getDeselectedTorrents: function() {
390 var visible_torrent_ids = jQuery.map(this.getVisibleTorrents(), function(t) { return t.id(); } );
392 jQuery.each( this.getAllTorrents( ), function() {
393 var visible = (-1 != jQuery.inArray(this.id(), visible_torrent_ids));
394 if (!this.isSelected() || !visible)
400 getVisibleRows: function()
403 for( var i=0, row; row=this._rows[i]; ++i )
404 if( row[0].style.display != 'none' )
409 getTorrentIndex: function( rows, torrent )
411 for( var i=0, row; row=rows[i]; ++i )
412 if( row._torrent == torrent )
417 setPref: function( key, val )
420 Prefs.setValue( key, val );
423 scrollToElement: function( e )
428 var container = $('#torrent_container');
429 var scrollTop = container.scrollTop( );
430 var innerHeight = container.innerHeight( );
432 var offsetTop = e[0].offsetTop;
433 var offsetHeight = e.outerHeight( );
435 if( offsetTop < scrollTop )
436 container.scrollTop( offsetTop );
437 else if( innerHeight + scrollTop < offsetTop + offsetHeight )
438 container.scrollTop( offsetTop + offsetHeight - innerHeight );
441 seedRatioLimit: function(){
442 if(this._prefs && this._prefs['seedRatioLimited'])
443 return this._prefs['seedRatioLimit'];
448 /*--------------------------------------------
452 *--------------------------------------------*/
454 setSelectedTorrent: function( torrent, doUpdate ) {
456 this.selectTorrent( torrent, doUpdate );
459 selectElement: function( e, doUpdate ) {
460 e.addClass('selected');
461 this.scrollToElement( e );
463 this.selectionChanged( );
465 selectRow: function( rowIndex, doUpdate ) {
466 this.selectElement( this._rows[rowIndex], doUpdate );
468 selectTorrent: function( torrent, doUpdate ) {
469 if( torrent._element )
470 this.selectElement( torrent._element, doUpdate );
473 deselectElement: function( e, doUpdate ) {
474 e.removeClass('selected');
476 this.selectionChanged( );
478 deselectTorrent: function( torrent, doUpdate ) {
479 if( torrent._element )
480 this.deselectElement( torrent._element, doUpdate );
483 selectAll: function( doUpdate ) {
485 for( var i=0, row; row=tr._rows[i]; ++i )
486 tr.selectElement( row );
488 tr.selectionChanged();
490 deselectAll: function( doUpdate ) {
492 for( var i=0, row; row=tr._rows[i]; ++i )
493 tr.deselectElement( row );
494 tr._last_torrent_clicked = null;
496 tr.selectionChanged( );
500 * Select a range from this torrent to the last clicked torrent
502 selectRange: function( torrent, doUpdate )
504 if( !this._last_torrent_clicked )
506 this.selectTorrent( torrent );
508 else // select the range between the prevous & current
510 var rows = this.getVisibleRows( );
511 var i = this.getTorrentIndex( rows, this._last_torrent_clicked );
512 var end = this.getTorrentIndex( rows, torrent );
513 var step = i < end ? 1 : -1;
514 for( ; i!=end; i+=step )
520 this.selectionChanged( );
523 selectionChanged: function()
525 this.updateButtonStates();
526 this.updateInspector();
527 this.updateSelectedData();
530 /*--------------------------------------------
532 * E V E N T F U N C T I O N S
534 *--------------------------------------------*/
539 keyDown: function(event)
542 var sel = tr.getSelectedTorrents( );
543 var rows = tr.getVisibleRows( );
546 if( event.keyCode == 40 ) // down arrow
548 var t = sel.length ? sel[sel.length-1] : null;
549 i = t==null ? null : tr.getTorrentIndex(rows,t)+1;
550 if( i == rows.length || i == null )
553 else if( event.keyCode == 38 ) // up arrow
555 var t = sel.length ? sel[0] : null
556 i = t==null ? null : tr.getTorrentIndex(rows,t)-1;
557 if( i == -1 || i == null )
561 if( 0<=i && i<rows.length ) {
563 tr.selectRow( i, true );
567 isButtonEnabled: function(e) {
568 var p = e.target ? e.target.parentNode : e.srcElement.parentNode;
569 return p.className!='disabled' && p.parentNode.className!='disabled';
572 stopAllClicked: function( event ) {
574 if( tr.isButtonEnabled( event ) ) {
575 tr.stopAllTorrents( );
576 tr.hideiPhoneAddressbar( );
580 stopSelectedClicked: function( event ) {
582 if( tr.isButtonEnabled( event ) ) {
583 tr.stopSelectedTorrents( );
584 tr.hideiPhoneAddressbar( );
588 startAllClicked: function( event ) {
590 if( tr.isButtonEnabled( event ) ) {
591 tr.startAllTorrents( );
592 tr.hideiPhoneAddressbar( );
596 startSelectedClicked: function( event ) {
598 if( tr.isButtonEnabled( event ) ) {
599 tr.startSelectedTorrents( );
600 tr.hideiPhoneAddressbar( );
604 openTorrentClicked: function( event ) {
606 if( tr.isButtonEnabled( event ) ) {
607 $('body').addClass('open_showing');
608 tr.uploadTorrentFile( );
610 tr.updateButtonStates();
613 hideUploadDialog: function( ) {
614 $('body.open_showing').removeClass('open_showing');
615 if (!iPhone && Safari3) {
616 $('div#upload_container div.dialog_window').css('top', '-205px');
617 setTimeout("$('#upload_container').hide();",500);
619 $('#upload_container').hide();
621 this.updateButtonStates();
624 cancelUploadClicked: function(event) {
625 this.hideUploadDialog( );
628 confirmUploadClicked: function(event) {
629 this.uploadTorrentFile( true );
630 this.hideUploadDialog( );
633 cancelPrefsClicked: function(event) {
634 this.hidePrefsDialog( );
637 savePrefsClicked: function(event)
639 // handle the clutch prefs locally
641 var rate = parseInt( $('#prefs_form #refresh_rate')[0].value );
642 if( rate != tr[Prefs._RefreshRate] ) {
643 tr.setPref( Prefs._RefreshRate, rate );
644 tr.togglePeriodicRefresh( false );
645 tr.togglePeriodicRefresh( true );
648 var up_bytes = parseInt( $('#prefs_form #upload_rate' )[0].value );
649 var dn_bytes = parseInt( $('#prefs_form #download_rate')[0].value );
650 var turtle_up_bytes = parseInt( $('#prefs_form #turtle_upload_rate' )[0].value );
651 var turtle_dn_bytes = parseInt( $('#prefs_form #turtle_download_rate')[0].value );
653 // pass the new prefs upstream to the RPC server
655 o[RPC._StartAddedTorrent] = $('#prefs_form #auto_start')[0].checked;
656 o[RPC._PeerPort] = parseInt( $('#prefs_form #port')[0].value );
657 o[RPC._UpSpeedLimit] = up_bytes;
658 o[RPC._DownSpeedLimit] = dn_bytes;
659 o[RPC._DownloadDir] = $('#prefs_form #download_location')[0].value;
660 o[RPC._UpSpeedLimited] = $('#prefs_form #limit_upload' )[0].checked;
661 o[RPC._DownSpeedLimited] = $('#prefs_form #limit_download')[0].checked;
662 o[RPC._Encryption] = $('#prefs_form #encryption')[0].checked
663 ? RPC._EncryptionRequired
664 : RPC._EncryptionPreferred;
665 o[RPC._TurtleDownSpeedLimit] = turtle_dn_bytes;
666 o[RPC._TurtleUpSpeedLimit] = turtle_up_bytes;
667 o[RPC._TurtleTimeEnabled] = $('#prefs_form #turtle_schedule')[0].checked;
668 o[RPC._TurtleTimeBegin] = parseInt( $('#prefs_form #turtle_start_time').val() );
669 o[RPC._TurtleTimeEnd] = parseInt( $('#prefs_form #turtle_end_time').val() );
670 o[RPC._TurtleTimeDay] = parseInt( $('#prefs_form #turtle_days').val() );
672 tr.remote.savePrefs( o );
674 tr.hidePrefsDialog( );
677 closeStatsClicked: function(event) {
678 this.hideStatsDialog( );
681 removeClicked: function( event ) {
683 if( tr.isButtonEnabled( event ) ) {
684 tr.removeSelectedTorrents( );
685 tr.hideiPhoneAddressbar( );
689 toggleInspectorClicked: function( event ) {
691 if( tr.isButtonEnabled( event ) )
692 tr.toggleInspector( );
695 inspectorTabClicked: function(event, tab) {
696 if (iPhone) event.stopPropagation();
698 // Select the clicked tab, unselect the others,
699 // and display the appropriate info
700 var tab_ids = $(tab).parent('#inspector_tabs').find('.inspector_tab').map(
701 function() { return $(this).attr('id'); }
703 for( var i=0, row; row=tab_ids[i]; ++i ) {
705 $('#'+row).addClass('selected');
706 $('#'+row+'_container').show();
708 $('#'+row).removeClass('selected');
709 $('#'+row+'_container').hide();
712 this.hideiPhoneAddressbar();
714 this.updateVisibleFileLists();
715 this.updatePeersLists();
716 this.updateTrackersLists();
719 fileWantedClicked: function(event, element){
720 this.extractFileFromElement(element).fileWantedControlClicked(event);
723 filePriorityClicked: function(event, element){
724 this.extractFileFromElement(element).filePriorityControlClicked(event, element);
727 filesSelectAllClicked: function(event) {
729 var ids = jQuery.map(this.getSelectedTorrents( ), function(t) { return t.id(); } );
730 var files_list = this.toggleFilesWantedDisplay(ids, true);
731 for (i = 0; i < ids.length; ++i) {
732 if (files_list[i].length)
733 this.remote.filesSelectAll( [ ids[i] ], files_list[i], function() { tr.refreshTorrents( ids ); } );
737 filesDeselectAllClicked: function(event) {
739 var ids = jQuery.map(this.getSelectedTorrents( ), function(t) { return t.id(); } );
740 var files_list = this.toggleFilesWantedDisplay(ids, false);
741 for (i = 0; i < ids.length; ++i) {
742 if (files_list[i].length)
743 this.remote.filesDeselectAll( [ ids[i] ], files_list[i], function() { tr.refreshTorrents( ids ); } );
747 extractFileFromElement: function(element) {
748 var match = $(element).closest('.inspector_torrent_file_list_entry').attr('id').match(/^t(\d+)f(\d+)$/);
749 var torrent_id = match[1];
750 var file_id = match[2];
751 var torrent = this._torrents[torrent_id];
752 return torrent._file_view[file_id];
755 toggleFilesWantedDisplay: function(ids, wanted) {
756 var i, j, k, torrent, files_list = [ ];
757 for (i = 0; i < ids.length; ++i) {
758 torrent = this._torrents[ids[i]];
760 for (j = k = 0; j < torrent._file_view.length; ++j) {
761 if (torrent._file_view[j].isEditable() && torrent._file_view[j]._wanted != wanted) {
762 torrent._file_view[j].setWanted(wanted, false);
763 files_list[i][k++] = j;
766 torrent.refreshFileView;
771 toggleFilterClicked: function(event) {
772 if (this.isButtonEnabled(event))
775 setFilter: function( mode )
777 // update the radiobuttons
780 case Prefs._FilterAll: c = '#filter_all_link'; break;
781 case Prefs._FilterActive: c = '#filter_active_link'; break;
782 case Prefs._FilterSeeding: c = '#filter_seeding_link'; break;
783 case Prefs._FilterDownloading: c = '#filter_downloading_link'; break;
784 case Prefs._FilterPaused: c = '#filter_paused_link'; break;
785 case Prefs._FilterFinished: c = '#filter_finished_link'; break;
787 $(c).parent().siblings().removeClass('selected');
788 $(c).parent().addClass('selected');
791 this.setPref( Prefs._FilterMode, mode );
794 showAllClicked: function( event ) {
795 this.setFilter( Prefs._FilterAll );
797 showActiveClicked: function( event ) {
798 this.setFilter( Prefs._FilterActive );
800 showDownloadingClicked: function( event ) {
801 this.setFilter( Prefs._FilterDownloading );
803 showSeedingClicked: function(event) {
804 this.setFilter( Prefs._FilterSeeding );
806 showPausedClicked: function(event) {
807 this.setFilter( Prefs._FilterPaused );
809 showFinishedClicked: function(event) {
810 this.setFilter( Prefs._FilterFinished );
814 * 'Clutch Preferences' was clicked (iPhone only)
816 releaseClutchPreferencesButton: function(event) {
817 $('div#prefs_container div#pref_error').hide();
818 $('div#prefs_container h2.dialog_heading').show();
819 this.showPrefsDialog( );
823 * Turn the periodic ajax torrents refresh on & off
825 togglePeriodicRefresh: function(state) {
827 if (state && this._periodic_refresh == null) {
829 if( !this[Prefs._RefreshRate] )
830 this[Prefs._RefreshRate] = 5;
831 remote = this.remote;
832 this._periodic_refresh = setInterval(function(){ tr.refreshTorrents(); }, this[Prefs._RefreshRate] * 1000 );
834 clearInterval(this._periodic_refresh);
835 this._periodic_refresh = null;
840 * Turn the periodic ajax torrents refresh on & off for the selected torrents
842 periodicTorrentUpdate: function( ids ) {
845 var curIds = this._extra_data_ids;
848 if( ids.length == curIds.length ) {
849 var duplicate = true;
850 for(var i = 0; i < ids.length; i++ ) {
851 if( ids[i] != curIds[i] ) {
856 if( duplicate ) return;
858 tr.refreshTorrents(ids);
859 clearInterval(this._metadata_refresh);
861 if( !this[Prefs._RefreshRate] ) this[Prefs._RefreshRate] = 5;
862 this._metadata_refresh = setInterval(function(){ tr.refreshTorrents(ids); }, this[Prefs._RefreshRate] * 1000 );
863 this._extra_data_ids = ids;
865 clearInterval(this._metadata_refresh);
866 this._metadata_refresh = null;
867 this._extra_data_ids = null;
872 * Turn the periodic ajax session refresh on & off
874 togglePeriodicSessionRefresh: function(state) {
876 if (state && this._periodic_session_refresh == null) {
878 if( !this[Prefs._SessionRefreshRate] )
879 this[Prefs._SessionRefreshRate] = 5;
880 remote = this.remote;
881 this._periodic_session_refresh = setInterval(
882 function(){ tr.loadDaemonPrefs(); }, this[Prefs._SessionRefreshRate] * 1000
885 clearInterval(this._periodic_session_refresh);
886 this._periodic_session_refresh = null;
891 * Turn the periodic ajax stats refresh on & off
893 togglePeriodicStatsRefresh: function(state) {
895 if (state && this._periodic_stats_refresh == null) {
897 if( !this[Prefs._SessionRefreshRate] )
898 this[Prefs._SessionRefreshRate] = 5;
899 remote = this.remote;
900 this._periodic_stats_refresh = setInterval(
901 function(){ tr.loadDaemonStats(); }, this[Prefs._SessionRefreshRate] * 1000
904 clearInterval(this._periodic_stats_refresh);
905 this._periodic_stats_refresh = null;
909 toggleTurtleClicked: function() {
911 this[Prefs._TurtleState] = !this[Prefs._TurtleState];
914 args[RPC._TurtleState] = this[Prefs._TurtleState];
915 this.remote.savePrefs( args );
918 updateSelectedData: function()
920 var ids = jQuery.map(this.getSelectedTorrents( ), function(t) { return t.id(); } );
922 this.periodicTorrentUpdate( ids );
924 this.periodicTorrentUpdate( false );
927 updateTurtleButton: function() {
929 var w = $('#turtle_button');
930 if ( this[Prefs._TurtleState] ) {
931 w.addClass('turtleEnabled');
932 w.removeClass('turtleDisabled');
933 t = [ 'Click to disable Temporary Speed Limits' ];
935 w.removeClass('turtleEnabled');
936 w.addClass('turtleDisabled');
937 t = [ 'Click to enable Temporary Speed Limits' ];
939 t.push( '(', Transmission.fmt.speed(this._prefs[RPC._TurtleUpSpeedLimit]), 'up,',
940 Transmission.fmt.speed(this._prefs[RPC._TurtleDownSpeedLimit]), 'down)' );
941 w.attr( 'title', t.join(' ') );
944 /*--------------------------------------------
946 * I N T E R F A C E F U N C T I O N S
948 *--------------------------------------------*/
950 showPrefsDialog: function( ) {
951 $('body').addClass('prefs_showing');
952 $('#prefs_container').show();
953 this.hideiPhoneAddressbar();
955 setTimeout("$('div#prefs_container div.dialog_window').css('top', '0px');",10);
956 this.updateButtonStates( );
957 this.togglePeriodicSessionRefresh(false);
960 hidePrefsDialog: function( )
962 $('body.prefs_showing').removeClass('prefs_showing');
964 this.hideiPhoneAddressbar();
965 $('#prefs_container').hide();
966 } else if (Safari3) {
967 $('div#prefs_container div.dialog_window').css('top', '-425px');
968 setTimeout("$('#prefs_container').hide();",500);
970 $('#prefs_container').hide();
972 this.updateButtonStates( );
973 this.togglePeriodicSessionRefresh(true);
977 * Process got some new session data from the server
979 updatePrefs: function( prefs )
981 // remember them for later
984 var up_limited = prefs[RPC._UpSpeedLimited];
985 var dn_limited = prefs[RPC._DownSpeedLimited];
986 var up_limit_k = prefs[RPC._UpSpeedLimit];
987 var dn_limit_k = prefs[RPC._DownSpeedLimit];
988 var turtle_up_limit_k = prefs[RPC._TurtleUpSpeedLimit];
989 var turtle_dn_limit_k = prefs[RPC._TurtleDownSpeedLimit];
992 Transmission.fmt.updateUnits( prefs.units );
994 $('div.download_location input')[0].value = prefs[RPC._DownloadDir];
995 $('div.port input')[0].value = prefs[RPC._PeerPort];
996 $('div.auto_start input')[0].checked = prefs[RPC._StartAddedTorrent];
997 $('input#limit_download')[0].checked = dn_limited;
998 $('input#download_rate')[0].value = dn_limit_k;
999 $('input#limit_upload')[0].checked = up_limited;
1000 $('input#upload_rate')[0].value = up_limit_k;
1001 $('input#refresh_rate')[0].value = prefs[Prefs._RefreshRate];
1002 $('div.encryption input')[0].checked = prefs[RPC._Encryption] == RPC._EncryptionRequired;
1003 $('input#turtle_download_rate')[0].value = turtle_dn_limit_k;
1004 $('input#turtle_upload_rate')[0].value = turtle_up_limit_k;
1005 $('input#turtle_schedule')[0].checked = prefs[RPC._TurtleTimeEnabled];
1006 $('select#turtle_start_time').val( prefs[RPC._TurtleTimeBegin] );
1007 $('select#turtle_end_time').val( prefs[RPC._TurtleTimeEnd] );
1008 $('select#turtle_days').val( prefs[RPC._TurtleTimeDay] );
1009 $('#transmission_version').text( prefs[RPC._DaemonVersion] );
1013 setInnerHTML( $('#limited_download_rate')[0], [ 'Limit (', Transmission.fmt.speed(dn_limit_k), ')' ].join('') );
1014 var key = dn_limited ? '#limited_download_rate'
1015 : '#unlimited_download_rate';
1016 $(key).deselectMenuSiblings().selectMenuItem();
1018 setInnerHTML( $('#limited_upload_rate')[0], [ 'Limit (', Transmission.fmt.speed(up_limit_k), ')' ].join('') );
1019 key = up_limited ? '#limited_upload_rate'
1020 : '#unlimited_upload_rate';
1021 $(key).deselectMenuSiblings().selectMenuItem();
1024 this[Prefs._TurtleState] = prefs[RPC._TurtleState];
1025 this.updateTurtleButton();
1028 showStatsDialog: function( ) {
1029 this.loadDaemonStats();
1030 $('body').addClass('stats_showing');
1031 $('#stats_container').show();
1032 this.hideiPhoneAddressbar();
1034 setTimeout("$('div#stats_container div.dialog_window').css('top', '0px');",10);
1035 this.updateButtonStates( );
1036 this.togglePeriodicStatsRefresh(true);
1039 hideStatsDialog: function( ){
1040 $('body.stats_showing').removeClass('stats_showing');
1042 this.hideiPhoneAddressbar();
1043 $('#stats_container').hide();
1044 } else if (Safari3) {
1045 $('div#stats_container div.dialog_window').css('top', '-425px');
1046 setTimeout("$('#stats_container').hide();",500);
1048 $('#stats_container').hide();
1050 this.updateButtonStates( );
1051 this.togglePeriodicStatsRefresh(false);
1055 * Process got some new session stats from the server
1057 updateStats: function( stats )
1059 // can't think of a reason to remember this
1060 //this._stats = stats;
1062 var fmt = Transmission.fmt;
1063 var session = stats["current-stats"];
1064 var total = stats["cumulative-stats"];
1066 setInnerHTML( $('#stats_session_uploaded')[0], fmt.size(session["uploadedBytes"]) );
1067 setInnerHTML( $('#stats_session_downloaded')[0], fmt.size(session["downloadedBytes"]) );
1068 setInnerHTML( $('#stats_session_ratio')[0], fmt.ratioString(Math.ratio(session["uploadedBytes"],session["downloadedBytes"])));
1069 setInnerHTML( $('#stats_session_duration')[0], fmt.timeInterval(session["secondsActive"]) );
1070 setInnerHTML( $('#stats_total_count')[0], total["sessionCount"] + " times" );
1071 setInnerHTML( $('#stats_total_uploaded')[0], fmt.size(total["uploadedBytes"]) );
1072 setInnerHTML( $('#stats_total_downloaded')[0], fmt.size(total["downloadedBytes"]) );
1073 setInnerHTML( $('#stats_total_ratio')[0], fmt.ratioString(Math.ratio(total["uploadedBytes"],total["downloadedBytes"])));
1074 setInnerHTML( $('#stats_total_duration')[0], fmt.timeInterval(total["secondsActive"]) );
1077 setSearch: function( search ) {
1078 this._current_search = search ? search.trim() : null;
1082 setSortMethod: function( sort_method ) {
1083 this.setPref( Prefs._SortMethod, sort_method );
1087 setSortDirection: function( direction ) {
1088 this.setPref( Prefs._SortDirection, direction );
1093 * Process an event in the footer-menu
1095 processSettingsMenuEvent: function(event) {
1097 var $element = $(event.target);
1099 // Figure out which menu has been clicked
1100 switch ($element.parent()[0].id) {
1102 // Display the preferences dialog
1103 case 'footer_super_menu':
1104 if ($element[0].id == 'preferences') {
1105 $('div#prefs_container div#pref_error').hide();
1106 $('div#prefs_container h2.dialog_heading').show();
1107 tr.showPrefsDialog( );
1109 else if ($element[0].id == 'statistics') {
1110 $('div#stats_container div#stats_error').hide();
1111 $('div#stats_container h2.dialog_heading').show();
1112 tr.showStatsDialog( );
1114 else if ($element[0].id == 'compact_view') {
1115 this.setPref( Prefs._CompactDisplayState, !this[Prefs._CompactDisplayState])
1116 if(this[Prefs._CompactDisplayState])
1117 $element.selectMenuItem();
1119 $element.deselectMenuItem();
1120 this.refreshDisplay( );
1122 else if ($element[0].id == 'homepage') {
1123 window.open('http://www.transmissionbt.com/');
1125 else if ($element[0].id == 'tipjar') {
1126 window.open('http://www.transmissionbt.com/donate.php');
1130 // Limit the download rate
1131 case 'footer_download_rate_menu':
1133 if ($element.is('#unlimited_download_rate')) {
1134 $element.deselectMenuSiblings().selectMenuItem();
1135 args[RPC._DownSpeedLimited] = false;
1137 var rate_str = ($element[0].innerHTML).replace(/[^0-9]/ig, '');
1138 var rate_val = parseInt( rate_str );
1139 setInnerHTML( $('#limited_download_rate')[0], [ 'Limit (', Transmission.fmt.speed(rate_val), ')' ].join('') );
1140 $('#limited_download_rate').deselectMenuSiblings().selectMenuItem();
1141 $('div.preference input#download_rate')[0].value = rate_str;
1142 args[RPC._DownSpeedLimit] = rate_val;
1143 args[RPC._DownSpeedLimited] = true;
1145 $('div.preference input#limit_download')[0].checked = args[RPC._DownSpeedLimited];
1146 tr.remote.savePrefs( args );
1149 // Limit the upload rate
1150 case 'footer_upload_rate_menu':
1152 if ($element.is('#unlimited_upload_rate')) {
1153 $element.deselectMenuSiblings().selectMenuItem();
1154 args[RPC._UpSpeedLimited] = false;
1156 var rate_str = ($element[0].innerHTML).replace(/[^0-9]/ig, '');
1157 var rate_val = parseInt( rate_str );
1158 setInnerHTML( $('#limited_upload_rate')[0], [ 'Limit (', Transmission.fmt.speed(rate_val), ')' ].join('') );
1159 $('#limited_upload_rate').deselectMenuSiblings().selectMenuItem();
1160 $('div.preference input#upload_rate')[0].value = rate_str;
1161 args[RPC._UpSpeedLimit] = rate_val;
1162 args[RPC._UpSpeedLimited] = true;
1164 $('div.preference input#limit_upload')[0].checked = args[RPC._UpSpeedLimited];
1165 tr.remote.savePrefs( args );
1168 // Sort the torrent list
1169 case 'footer_sort_menu':
1171 // The 'reverse sort' option state can be toggled independently of the other options
1172 if ($element.is('#reverse_sort_order')) {
1173 if(!$element.is('#reverse_sort_order.active')) break;
1175 if ($element.menuItemIsSelected()) {
1176 $element.deselectMenuItem();
1177 dir = Prefs._SortAscending;
1179 $element.selectMenuItem();
1180 dir = Prefs._SortDescending;
1182 tr.setSortDirection( dir );
1184 // Otherwise, deselect all other options (except reverse-sort) and select this one
1186 $element.parent().find('span.selected').each( function() {
1187 if (! $element.parent().is('#reverse_sort_order')) {
1188 $element.parent().deselectMenuItem();
1191 $element.selectMenuItem();
1192 var method = $element[0].id.replace(/sort_by_/, '');
1193 tr.setSortMethod( method );
1197 $('#settings_menu').trigger('closemenu');
1198 return false; // to prevent the event from bubbling up
1201 setLastTorrentClicked: function( torrent )
1203 this._last_torrent_clicked = torrent;
1207 * Update the inspector with the latest data for the selected torrents
1209 updateInspector: function()
1211 if( !this[Prefs._ShowInspector] )
1214 var torrents = this.getSelectedTorrents( );
1215 if( !torrents.length && iPhone ) {
1216 this.hideInspector();
1220 var creator = 'N/A';
1221 var comment = 'N/A';
1222 var download_dir = 'N/A';
1223 var date_created = 'N/A';
1226 var have_public = false;
1227 var have_private = false;
1229 var sizeWhenDone = 0;
1231 var total_completed = 0;
1232 var total_download = 0;
1233 var total_download_peers = 0;
1234 var total_download_speed = 0;
1235 var total_availability = 0;
1238 var total_state = [ ];
1240 var total_upload = 0;
1241 var total_upload_peers = 0;
1242 var total_upload_speed = 0;
1243 var total_verified = 0;
1245 var tab = this._inspector._info_tab;
1247 $("#torrent_inspector_size, .inspector_row div").css('color', '#222');
1249 if( torrents.length == 0 )
1251 setInnerHTML( tab.name, 'No Selection' );
1252 setInnerHTML( tab.size, na );
1253 setInnerHTML( tab.pieces, na );
1254 setInnerHTML( tab.hash, na );
1255 setInnerHTML( tab.state, na );
1256 setInnerHTML( tab.download_speed, na );
1257 setInnerHTML( tab.upload_speed, na );
1258 setInnerHTML( tab.uploaded, na );
1259 setInnerHTML( tab.downloaded, na );
1260 setInnerHTML( tab.availability, na );
1261 setInnerHTML( tab.ratio, na );
1262 setInnerHTML( tab.have, na );
1263 setInnerHTML( tab.upload_to, na );
1264 setInnerHTML( tab.download_from, na );
1265 setInnerHTML( tab.secure, na );
1266 setInnerHTML( tab.creator_date, na );
1267 setInnerHTML( tab.progress, na );
1268 setInnerHTML( tab.comment, na );
1269 setInnerHTML( tab.creator, na );
1270 setInnerHTML( tab.download_dir, na );
1271 setInnerHTML( tab.error, na );
1272 this.updateVisibleFileLists();
1273 this.updatePeersLists();
1274 this.updateTrackersLists();
1275 $("#torrent_inspector_size, .inspector_row > div:contains('N/A')").css('color', '#666');
1279 name = torrents.length == 1
1280 ? torrents[0].name()
1281 : torrents.length+' Transfers Selected';
1283 if( torrents.length == 1 )
1285 var t = torrents[0];
1286 var err = t.getErrorMessage( );
1290 comment = t._comment ;
1292 creator = t._creator ;
1293 if( t._download_dir)
1294 download_dir = t._download_dir;
1297 pieces = [ t._pieceCount, 'pieces @', Transmission.fmt.mem(t._pieceSize) ].join(' ');
1298 date_created = Transmission.fmt.timestamp( t._creator_date );
1301 for( var i=0, t; t=torrents[i]; ++i ) {
1302 sizeWhenDone += t._sizeWhenDone;
1303 sizeDone += t._sizeWhenDone - t._leftUntilDone;
1304 total_completed += t.completed();
1305 total_verified += t._verified;
1306 total_size += t.size();
1307 total_upload += t.uploadTotal();
1308 total_download += t.downloadTotal();
1309 total_upload_speed += t.uploadSpeed();
1310 total_download_speed += t.downloadSpeed();
1311 total_upload_peers += t.peersGettingFromUs();
1312 total_download_peers += t.peersSendingToUs();
1313 total_availability += t._sizeWhenDone - t._leftUntilDone + t._desiredAvailable;
1315 var s = t.stateStr();
1316 if( total_state.indexOf( s ) == -1 )
1317 total_state.push( s );
1320 have_private = true;
1325 var private_string = '';
1326 var fmt = Transmission.fmt;
1327 if( have_private && have_public ) private_string = 'Mixed';
1328 else if( have_private ) private_string = 'Private Torrent';
1329 else if( have_public ) private_string = 'Public Torrent';
1331 setInnerHTML( tab.name, name );
1332 setInnerHTML( tab.size, torrents.length ? fmt.size( total_size ) : na );
1333 setInnerHTML( tab.pieces, pieces );
1334 setInnerHTML( tab.hash, hash );
1335 setInnerHTML( tab.state, total_state.join('/') );
1336 setInnerHTML( tab.download_speed, torrents.length ? fmt.speedBps( total_download_speed ) : na );
1337 setInnerHTML( tab.upload_speed, torrents.length ? fmt.speedBps( total_upload_speed ) : na );
1338 setInnerHTML( tab.uploaded, torrents.length ? fmt.size( total_upload ) : na );
1339 setInnerHTML( tab.downloaded, torrents.length ? fmt.size( total_download ) : na );
1340 setInnerHTML( tab.availability, torrents.length ? fmt.percentString(Math.ratio( total_availability*100, sizeWhenDone )) + '%' : na );
1341 setInnerHTML( tab.ratio, torrents.length ? fmt.ratioString(Math.ratio( total_upload, total_download )) : na );
1342 setInnerHTML( tab.have, torrents.length ? fmt.size(total_completed) + ' (' + fmt.size(total_verified) + ' verified)' : na );
1343 setInnerHTML( tab.upload_to, torrents.length ? total_upload_peers : na );
1344 setInnerHTML( tab.download_from, torrents.length ? total_download_peers : na );
1345 setInnerHTML( tab.secure, private_string );
1346 setInnerHTML( tab.creator_date, date_created );
1347 setInnerHTML( tab.progress, torrents.length ? fmt.percentString(Math.ratio( sizeDone*100, sizeWhenDone )) + '%' : na );
1348 setInnerHTML( tab.comment, comment == na ? comment : comment.replace(/\//g, '/​') );
1349 setInnerHTML( tab.creator, creator );
1350 setInnerHTML( tab.download_dir, download_dir == na ? download_dir : download_dir.replace(/([\/_\.])/g, "$1​") );
1351 setInnerHTML( tab.error, error );
1353 this.updatePeersLists();
1354 this.updateTrackersLists();
1355 $(".inspector_row > div:contains('N/A')").css('color', '#666');
1356 this.updateVisibleFileLists();
1359 fileListIsVisible: function() {
1360 return this._inspector_tab_files.className.indexOf('selected') != -1;
1363 updateVisibleFileLists: function() {
1364 if( this.fileListIsVisible( ) === true ) {
1365 var selected = this.getSelectedTorrents();
1366 jQuery.each( selected, function() { this.showFileList(); } );
1367 jQuery.each( this.getDeselectedTorrents(), function() { this.hideFileList(); } );
1368 // Check if we need to display the select all buttions
1369 if ( !selected.length ) {
1370 if ( $("#select_all_button_container").is(':visible') )
1371 $("#select_all_button_container").hide();
1373 if ( !$("#select_all_button_container").is(':visible') )
1374 $("#select_all_button_container").show();
1379 updatePeersLists: function() {
1382 var fmt = Transmission.fmt;
1383 var torrents = this.getSelectedTorrents( );
1384 if( $(this._inspector_peers_list).is(':visible') ) {
1385 for( var k=0, torrent; torrent=torrents[k]; ++k ) {
1386 html.push( '<div class="inspector_group">' );
1387 if( torrents.length > 1 ) {
1388 html.push( '<div class="inspector_torrent_label">', torrent._name, '</div>' );
1390 if( torrent._peers.length == 0 ) {
1391 html.push( '<br></div>' ); // firefox won't paint the top border if the div is empty
1394 html.push( '<table class="peer_list">',
1395 '<tr class="inspector_peer_entry even">',
1396 '<th class="encryptedCol"></th>',
1397 '<th class="upCol">Up</th>',
1398 '<th class="downCol">Down</th>',
1399 '<th class="percentCol">%</th>',
1400 '<th class="statusCol">Status</th>',
1401 '<th class="addressCol">Address</th>',
1402 '<th class="clientCol">Client</th>',
1404 for( var i=0, peer; peer=torrent._peers[i]; ++i ) {
1405 var parity = ((i+1) % 2 == 0 ? 'even' : 'odd');
1406 html.push( '<tr class="inspector_peer_entry ', parity, '">',
1407 '<td>', (peer.isEncrypted ? '<img src="images/graphics/lock_icon.png" alt="Encrypted"/>' : ''), '</td>',
1408 '<td>', ( peer.rateToPeer ? fmt.speedBps(peer.rateToPeer) : '' ), '</td>',
1409 '<td>', ( peer.rateToClient ? fmt.speedBps(peer.rateToClient) : '' ), '</td>',
1410 '<td class="percentCol">', Math.floor(peer.progress*100), '%', '</td>',
1411 '<td>', peer.flagStr, '</td>',
1412 '<td>', peer.address, '</td>',
1413 '<td class="clientCol">', peer.clientName, '</td>',
1416 html.push( '</table></div>' );
1419 setInnerHTML(this._inspector_peers_list, html.join('') );
1422 updateTrackersLists: function() {
1423 // By building up the HTML as as string, then have the browser
1424 // turn this into a DOM tree, this is a fast operation.
1428 var torrents = this.getSelectedTorrents( );
1429 if( $(this._inspector_trackers_list).is(':visible') ) {
1430 for( var k=0, torrent; torrent = torrents[k]; ++k ) {
1431 html.push( '<div class="inspector_group">' );
1432 if( torrents.length > 1 ) {
1433 html.push( '<div class="inspector_torrent_label">', torrent._name, '</div>' );
1435 for( var i=0, tier; tier=torrent._trackerStats[i]; ++i ) {
1436 html.push( '<div class="inspector_group_label">',
1437 'Tier ', (i + 1), '</div>',
1438 '<ul class="tier_list">' );
1439 for( var j=0, tracker; tracker=tier[j]; ++j ) {
1440 var lastAnnounceStatusHash = tr.lastAnnounceStatus(tracker);
1441 var announceState = tr.announceState(tracker);
1442 var lastScrapeStatusHash = tr.lastScrapeStatus(tracker);
1444 // Display construction
1445 var parity = ((j+1) % 2 == 0 ? 'even' : 'odd');
1446 html.push( '<li class="inspector_tracker_entry ', parity, '"><div class="tracker_host" title="', tracker.announce, '">',
1447 tracker.host, '</div>',
1448 '<div class="tracker_activity">',
1449 '<div>', lastAnnounceStatusHash['label'], ': ', lastAnnounceStatusHash['value'], '</div>',
1450 '<div>', announceState, '</div>',
1451 '<div>', lastScrapeStatusHash['label'], ': ', lastScrapeStatusHash['value'], '</div>',
1452 '</div><table class="tracker_stats">',
1453 '<tr><th>Seeders:</th><td>', (tracker.seederCount > -1 ? tracker.seederCount : na), '</td></tr>',
1454 '<tr><th>Leechers:</th><td>', (tracker.leecherCount > -1 ? tracker.leecherCount : na), '</td></tr>',
1455 '<tr><th>Downloads:</th><td>', (tracker.downloadCount > -1 ? tracker.downloadCount : na), '</td></tr>',
1458 html.push( '</ul>' );
1460 html.push( '</div>' );
1463 setInnerHTML(this._inspector_trackers_list, html.join(''));
1466 lastAnnounceStatus: function(tracker){
1467 var lastAnnounceLabel = 'Last Announce';
1468 var lastAnnounce = [ 'N/A' ];
1469 if (tracker.hasAnnounced) {
1470 var lastAnnounceTime = Transmission.fmt.timestamp(tracker.lastAnnounceTime);
1471 if (tracker.lastAnnounceSucceeded) {
1472 lastAnnounce = [ lastAnnounceTime, ' (got ', Transmission.fmt.plural(tracker.lastAnnouncePeerCount, 'peer'), ')' ];
1474 lastAnnounceLabel = 'Announce error';
1475 lastAnnounce = [ (tracker.lastAnnounceResult ? (tracker.lastAnnounceResult + ' - ') : ''), lastAnnounceTime ];
1478 return { 'label':lastAnnounceLabel, 'value':lastAnnounce.join('') };
1481 announceState: function(tracker){
1482 var announceState = '';
1483 switch (tracker.announceState) {
1484 case Torrent._TrackerActive:
1485 announceState = 'Announce in progress';
1487 case Torrent._TrackerWaiting:
1488 var timeUntilAnnounce = tracker.nextAnnounceTime - ((new Date()).getTime() / 1000);
1489 if(timeUntilAnnounce < 0){
1490 timeUntilAnnounce = 0;
1492 announceState = 'Next announce in ' + Transmission.fmt.timeInterval(timeUntilAnnounce);
1494 case Torrent._TrackerQueued:
1495 announceState = 'Announce is queued';
1497 case Torrent._TrackerInactive:
1498 announceState = tracker.isBackup ?
1499 'Tracker will be used as a backup' :
1500 'Announce not scheduled';
1503 announceState = 'unknown announce state: ' + tracker.announceState;
1505 return announceState;
1508 lastScrapeStatus: function(tracker){
1509 var lastScrapeLabel = 'Last Scrape';
1510 var lastScrape = 'N/A';
1511 if (tracker.hasScraped) {
1512 var lastScrapeTime = Transmission.fmt.timestamp(tracker.lastScrapeTime);
1513 if (tracker.lastScrapeSucceeded) {
1514 lastScrape = lastScrapeTime;
1516 lastScrapeLabel = 'Scrape error';
1517 lastScrape = (tracker.lastScrapeResult ? tracker.lastScrapeResult + ' - ' : '') + lastScrapeTime;
1520 return {'label':lastScrapeLabel, 'value':lastScrape}
1524 * Toggle the visibility of the inspector (used by the context menu)
1526 toggleInspector: function() {
1527 if( this[Prefs._ShowInspector] )
1528 this.hideInspector( );
1530 this.showInspector( );
1533 showInspector: function() {
1534 $('#torrent_inspector').show();
1536 $('body').addClass('inspector_showing');
1537 $('#inspector_close').show();
1538 this.hideiPhoneAddressbar();
1540 var w = $('#torrent_inspector').width() + 1 + 'px';
1541 $('#torrent_filter_bar')[0].style.right = w;
1542 $('#torrent_container')[0].style.right = w;
1545 setInnerHTML( $('ul li#context_toggle_inspector')[0], 'Hide Inspector' );
1547 this.setPref( Prefs._ShowInspector, true );
1548 this.updateInspector( );
1549 this.refreshDisplay( );
1553 * Hide the inspector
1555 hideInspector: function() {
1557 $('#torrent_inspector').hide();
1560 this.deselectAll( );
1561 $('body.inspector_showing').removeClass('inspector_showing');
1562 $('#inspector_close').hide();
1563 this.hideiPhoneAddressbar();
1565 $('#torrent_filter_bar')[0].style.right = '0px';
1566 $('#torrent_container')[0].style.right = '0px';
1567 setInnerHTML( $('ul li#context_toggle_inspector')[0], 'Show Inspector' );
1570 this.setPref( Prefs._ShowInspector, false );
1571 this.refreshDisplay( );
1575 * Toggle the visibility of the filter bar
1577 toggleFilter: function() {
1578 if( this[Prefs._ShowFilter] )
1584 showFilter: function( ) {
1585 var container_top = parseInt($('#torrent_container').position().top) + $('#torrent_filter_bar').height() + 1;
1586 $('#torrent_container').css('top', container_top + 'px');
1587 $('#torrent_filter_bar').show();
1588 this.setPref( Prefs._ShowFilter, true );
1591 hideFilter: function()
1593 var container_top = parseInt($('#torrent_container').css('top')) - $('#torrent_filter_bar').height() - 1;
1594 $('#torrent_container').css('top', container_top + 'px');
1595 $('#torrent_filter_bar').hide();
1596 this.setPref( Prefs._ShowFilter, false );
1597 this.setFilter( Prefs._FilterAll );
1600 refreshMetaData: function(ids) {
1602 this.remote.getMetaDataFor(ids, function(active, removed){ tr.updateMetaData(active); });
1605 updateMetaData: function( torrents )
1608 var refresh_files_for = [ ];
1609 jQuery.each( torrents, function( ) {
1610 var t = tr._torrents[ this.id ];
1612 t.refreshMetaData( this );
1613 if( t.isSelected( ) )
1614 refresh_files_for.push( t.id( ) );
1617 if( refresh_files_for.length > 0 )
1618 tr.remote.loadTorrentFiles( refresh_files_for );
1621 refreshTorrents: function(ids) {
1624 ids = 'recently-active';
1626 this.remote.getUpdatedDataFor(ids, function(active, removed){ tr.updateTorrentsData(active, removed); });
1629 updateTorrentsData: function( updated, removed_ids ) {
1631 var new_torrent_ids = [];
1632 var refresh_files_for = [];
1633 jQuery.each( updated, function() {
1634 var t = tr._torrents[this.id];
1638 refresh_files_for.push(t.id());
1641 new_torrent_ids.push(this.id);
1644 if(refresh_files_for.length > 0)
1645 tr.remote.loadTorrentFiles( refresh_files_for );
1647 if(new_torrent_ids.length > 0)
1648 tr.remote.getInitialDataFor(new_torrent_ids, function(torrents){ tr.addTorrents(torrents) } );
1650 var removedAny = tr.deleteTorrents(removed_ids);
1652 if( ( new_torrent_ids.length != 0 ) || removedAny ) {
1653 tr.hideiPhoneAddressbar();
1654 tr.deselectAll( true );
1660 updateTorrentsFileData: function( torrents ){
1662 var listIsVisible = tr.fileListIsVisible( );
1663 jQuery.each( torrents, function() {
1664 var t = tr._torrents[this.id];
1666 t.refreshFileModel(this);
1667 if( listIsVisible && t.isSelected())
1668 t.refreshFileView();
1673 initializeAllTorrents: function(){
1675 this.remote.getInitialDataFor( null ,function(torrents) { tr.addTorrents(torrents); } );
1678 addTorrents: function( new_torrents )
1680 var transferFragment = document.createDocumentFragment( );
1681 var fileFragment = document.createDocumentFragment( );
1683 for( var i=0, row; row=new_torrents[i]; ++i ) {
1684 var new_torrent = new Torrent( transferFragment, fileFragment, this, row );
1685 this._torrents[new_torrent.id()] = new_torrent;
1688 this._inspector_file_list.appendChild( fileFragment );
1689 this._torrent_list.appendChild( transferFragment );
1694 deleteTorrents: function(torrent_ids){
1695 if(typeof torrent_ids == 'undefined')
1698 var removedAny = false;
1699 $.each( torrent_ids, function(index, id){
1700 var torrent = tr._torrents[id];
1704 var e = torrent.element();
1707 for( var i=0, row; row = tr._rows[i]; ++i ) {
1708 if( row._id == torrent._id )
1711 e = tr._rows[row_index];
1715 delete e._torrent; //remove circular refernce to help IE garbage collect
1716 tr._rows.splice(row_index, 1)
1720 torrent.hideFileList();
1721 torrent.deleteFiles();
1722 delete tr._torrents[torrent.id()];
1729 refreshDisplay: function( )
1731 var torrents = this.getVisibleTorrents();
1732 for( var i=0; torrents[i]; ++i )
1733 torrents[i].refreshHTML();
1737 * Set the alternating background colors for torrents
1739 setTorrentBgColors: function( )
1741 var rows = this.getVisibleRows( );
1742 for( var i=0, row; row=rows[i]; ++i ) {
1743 var wasEven = row[0].className.indexOf('even') != -1;
1744 var isEven = ((i+1) % 2 == 0);
1745 if( wasEven != isEven )
1746 row.toggleClass('even', isEven);
1750 updateStatusbar: function()
1752 var torrents = this.getAllTorrents();
1753 var torrentCount = torrents.length;
1754 var visibleCount = this.getVisibleTorrents().length;
1756 // calculate the overall speed
1759 for( var i=0, row; row=torrents[i]; ++i ) {
1760 upSpeed += row.uploadSpeed( );
1761 downSpeed += row.downloadSpeed( );
1764 // update torrent count label
1766 if( torrentCount == visibleCount )
1767 s = torrentCount + ' Transfers';
1769 s = visibleCount + ' of ' + torrentCount + ' Transfers';
1770 setInnerHTML( $('#torrent_global_transfer')[0], s );
1772 // update the speeds
1773 s = Transmission.fmt.speedBps( upSpeed );
1774 if( iPhone ) s = 'UL: ' + s;
1775 setInnerHTML( $('#torrent_global_upload')[0], s );
1778 s = Transmission.fmt.speedBps( downSpeed );
1779 if( iPhone ) s = 'DL: ' + s;
1780 setInnerHTML( $('#torrent_global_download')[0], s );
1784 * Select a torrent file to upload
1787 uploadTorrentFile: function(confirmed)
1789 // Display the upload dialog
1791 $('input#torrent_upload_file').attr('value', '');
1792 $('input#torrent_upload_url').attr('value', '');
1793 $('input#torrent_auto_start').attr('checked', $('#prefs_form #auto_start')[0].checked);
1794 $('#upload_container').show();
1795 $('#torrent_upload_url').focus();
1796 if (!iPhone && Safari3) {
1797 setTimeout("$('div#upload_container div.dialog_window').css('top', '0px');",10);
1800 // Submit the upload form
1804 var paused = !$('#torrent_auto_start').is(':checked');
1805 if ('' != $('#torrent_upload_url').val()) {
1806 tr.remote.addTorrentByUrl($('#torrent_upload_url').val(), { paused: paused });
1808 args.url = '/transmission/upload?paused=' + paused;
1810 args.data = { 'X-Transmission-Session-Id' : tr.remote._token };
1811 args.dataType = 'xml';
1813 args.success = function( data ) {
1814 tr.refreshTorrents();
1815 tr.togglePeriodicRefresh( true );
1817 tr.togglePeriodicRefresh( false );
1818 $('#torrent_upload_form').ajaxSubmit( args );
1823 removeSelectedTorrents: function() {
1824 var torrents = this.getSelectedTorrents( );
1825 if( torrents.length )
1826 this.promptToRemoveTorrents( torrents );
1829 removeSelectedTorrentsAndData: function() {
1830 var torrents = this.getSelectedTorrents( );
1831 if( torrents.length )
1832 this.promptToRemoveTorrentsAndData( torrents );
1835 promptToRemoveTorrents:function( torrents )
1837 if( torrents.length == 1 )
1839 var torrent = torrents[0];
1840 var header = 'Remove ' + torrent.name() + '?';
1841 var message = 'Once removed, continuing the transfer will require the torrent file. Are you sure you want to remove it?';
1842 dialog.confirm( header, message, 'Remove', 'transmission.removeTorrents', torrents );
1846 var header = 'Remove ' + torrents.length + ' transfers?';
1847 var message = 'Once removed, continuing the transfers will require the torrent files. Are you sure you want to remove them?';
1848 dialog.confirm( header, message, 'Remove', 'transmission.removeTorrents', torrents );
1852 promptToRemoveTorrentsAndData:function( torrents )
1854 if( torrents.length == 1 )
1856 var torrent = torrents[0],
1857 header = 'Remove ' + torrent.name() + ' and delete data?',
1858 message = 'All data downloaded for this torrent will be deleted. Are you sure you want to remove it?';
1859 dialog.confirm( header, message, 'Remove', 'transmission.removeTorrentsAndData', torrents );
1863 var header = 'Remove ' + torrents.length + ' transfers and delete data?',
1864 message = 'All data downloaded for these torrents will be deleted. Are you sure you want to remove them?';
1865 dialog.confirm( header, message, 'Remove', 'transmission.removeTorrentsAndData', torrents );
1869 removeTorrents: function( torrents ) {
1870 var torrent_ids = jQuery.map(torrents, function(t) { return t.id(); } );
1872 this.remote.removeTorrents( torrent_ids, function(){ tr.refreshTorrents() } );
1875 removeTorrentsAndData: function( torrents ) {
1876 this.remote.removeTorrentsAndData( torrents );
1879 verifySelectedTorrents: function() {
1880 this.verifyTorrents( this.getSelectedTorrents( ) );
1883 startSelectedTorrents: function( ) {
1884 this.startTorrents( this.getSelectedTorrents( ) );
1886 startAllTorrents: function( ) {
1887 this.startTorrents( this.getAllTorrents( ) );
1889 startTorrent: function( torrent ) {
1890 this.startTorrents( [ torrent ] );
1892 startTorrents: function( torrents ) {
1893 var torrent_ids = jQuery.map(torrents, function(t) { return t.id(); } );
1895 this.remote.startTorrents( torrent_ids, function(){ tr.refreshTorrents(torrent_ids) } );
1897 verifyTorrent: function( torrent ) {
1898 this.verifyTorrents( [ torrent ] );
1900 verifyTorrents: function( torrents ) {
1901 var torrent_ids = jQuery.map(torrents, function(t) { return t.id(); } );
1903 this.remote.verifyTorrents( torrent_ids, function(){ tr.refreshTorrents(torrent_ids) } );
1906 stopSelectedTorrents: function( ) {
1907 this.stopTorrents( this.getSelectedTorrents( ) );
1909 stopAllTorrents: function( ) {
1910 this.stopTorrents( this.getAllTorrents( ) );
1912 stopTorrent: function( torrent ) {
1913 this.stopTorrents( [ torrent ] );
1915 stopTorrents: function( torrents ) {
1916 var torrent_ids = jQuery.map(torrents, function(t) { return t.id(); } );
1918 this.remote.stopTorrents( torrent_ids, function(){ tr.refreshTorrents(torrent_ids )} );
1920 changeFileCommand: function(command, torrent, file) {
1921 this.remote.changeFileCommand(command, torrent, file)
1924 hideiPhoneAddressbar: function(timeInSeconds) {
1927 var delayLength = timeInSeconds ? timeInSeconds*1000 : 150;
1928 // not currently supported on iPhone
1929 if(/*document.body.scrollTop!=1 && */scroll_timeout==null) {
1930 scroll_timeout = setTimeout(function(){ tr.doToolbarHide(); }, delayLength);
1934 doToolbarHide: function() {
1935 window.scrollTo(0,1);
1936 scroll_timeout=null;
1943 refilter: function()
1945 // decide which torrents to keep showing
1946 var allTorrents = this.getAllTorrents( );
1948 for( var i=0, t; t=allTorrents[i]; ++i )
1949 if( t.test( this[Prefs._FilterMode], this._current_search ) )
1953 Torrent.sortTorrents( keep, this[Prefs._SortMethod],
1954 this[Prefs._SortDirection] );
1956 // make a backup of the selection
1957 var sel = this.getSelectedTorrents( );
1958 this.deselectAll( );
1960 // hide the ones we're not keeping
1961 for( var i=keep.length, e; e=this._rows[i]; ++i ) {
1963 e[0].style.display = 'none';
1966 // show the ones we're keeping
1967 sel.sort( Torrent.compareById );
1968 for( var i=0, len=keep.length; i<len; ++i ) {
1969 var e = this._rows[i];
1970 e[0].style.display = 'block';
1973 if( Torrent.indexOf( sel, t.id() ) != -1 )
1974 this.selectElement( e );
1978 this.setTorrentBgColors( );
1979 this.updateStatusbar( );
1980 this.selectionChanged( );
1981 this.refreshDisplay( );
1984 setEnabled: function( key, flag )
1986 $(key).toggleClass( 'disabled', !flag );
1989 updateButtonStates: function()
1991 var showing_dialog = new RegExp("(prefs_showing|dialog_showing|open_showing)").test(document.body.className);
1992 this._toolbar_buttons.toggleClass( 'disabled', showing_dialog );
1994 if (!showing_dialog)
1996 var torrents = this.getVisibleTorrents( );
1997 var haveSelection = false;
1998 var haveActive = false;
1999 var haveActiveSelection = false;
2000 var havePaused = false;
2001 var havePausedSelection = false;
2003 for( var i=0, len=torrents.length; !haveSelection && i<len; ++i ) {
2004 var isActive = torrents[i].isActive( );
2005 var isSelected = torrents[i].isSelected( );
2006 if( isActive ) haveActive = true;
2007 if( !isActive ) havePaused = true;
2008 if( isSelected ) haveSelection = true;
2009 if( isSelected && isActive ) haveActiveSelection = true;
2010 if( isSelected && !isActive ) havePausedSelection = true;
2013 this.setEnabled( this._toolbar_pause_button, haveActiveSelection );
2014 this.setEnabled( this._context_pause_button, haveActiveSelection );
2015 this.setEnabled( this._toolbar_start_button, havePausedSelection );
2016 this.setEnabled( this._context_start_button, havePausedSelection );
2017 this.setEnabled( this._toolbar_remove_button, haveSelection );
2018 this.setEnabled( this._toolbar_pause_all_button, haveActive );
2019 this.setEnabled( this._toolbar_start_all_button, havePaused );