1/** 2 * Copyright �� Jordan Lee, Dave Perrett, Malcolm Jarvis and Bruno Bierbaumer 3 * 4 * This file is licensed under the GPLv2. 5 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 6 */ 7 8function Transmission() 9{ 10 this.initialize(); 11} 12 13Transmission.prototype = 14{ 15 /**** 16 ***** 17 ***** STARTUP 18 ***** 19 ****/ 20 21 initialize: function() 22 { 23 var e; 24 25 // Initialize the helper classes 26 this.remote = new TransmissionRemote(this); 27 this.inspector = new Inspector(this, this.remote); 28 this.prefsDialog = new PrefsDialog(this.remote); 29 $(this.prefsDialog).bind('closed', $.proxy(this.onPrefsDialogClosed,this)); 30 31 this.isMenuEnabled = !isMobileDevice; 32 33 // Initialize the implementation fields 34 this.filterText = ''; 35 this._torrents = {}; 36 this._rows = []; 37 this.dirtyTorrents = {}; 38 this.uriCache = {}; 39 40 // Initialize the clutch preferences 41 Prefs.getClutchPrefs(this); 42 43 // Set up user events 44 $(".numberinput").forceNumeric(); 45 $('#toolbar-pause').click($.proxy(this.stopSelectedClicked,this)); 46 $('#toolbar-start').click($.proxy(this.startSelectedClicked,this)); 47 $('#toolbar-pause-all').click($.proxy(this.stopAllClicked,this)); 48 $('#toolbar-start-all').click($.proxy(this.startAllClicked,this)); 49 $('#toolbar-remove').click($.proxy(this.removeClicked,this)); 50 $('#toolbar-open').click($.proxy(this.openTorrentClicked,this)); 51 52 $('#prefs-button').click($.proxy(this.togglePrefsDialogClicked,this)); 53 54 $('#upload_confirm_button').click($.proxy(this.confirmUploadClicked,this)); 55 $('#upload_cancel_button').click($.proxy(this.hideUploadDialog,this)); 56 57 $('#move_confirm_button').click($.proxy(this.confirmMoveClicked,this)); 58 $('#move_cancel_button').click($.proxy(this.hideMoveDialog,this)); 59 60 $('#turtle-button').click($.proxy(this.toggleTurtleClicked,this)); 61 $('#compact-button').click($.proxy(this.toggleCompactClicked,this)); 62 63 // tell jQuery to copy the dataTransfer property from events over if it exists 64 jQuery.event.props.push("dataTransfer"); 65 66 $('#torrent_upload_form').submit(function() { $('#upload_confirm_button').click(); return false; }); 67 68 $('#toolbar-inspector').click($.proxy(this.toggleInspector,this)); 69 70 e = $('#filter-mode'); 71 e.val(this[Prefs._FilterMode]); 72 e.change($.proxy(this.onFilterModeClicked,this)); 73 $('#filter-tracker').change($.proxy(this.onFilterTrackerClicked,this)); 74 75 if (!isMobileDevice) { 76 $(document).bind('keydown', $.proxy(this.keyDown,this) ); 77 $(document).bind('keyup', $.proxy(this.keyUp, this) ); 78 $('#torrent_container').click( $.proxy(this.deselectAll,this) ); 79 $('#torrent_container').bind('dragover', $.proxy(this.dragenter,this)); 80 $('#torrent_container').bind('dragenter', $.proxy(this.dragenter,this)); 81 $('#torrent_container').bind('drop', $.proxy(this.drop,this)); 82 $('#inspector_link').click( $.proxy(this.toggleInspector,this) ); 83 84 this.setupSearchBox(); 85 this.createContextMenu(); 86 } 87 88 if (this.isMenuEnabled) 89 this.createSettingsMenu(); 90 91 e = {}; 92 e.torrent_list = $('#torrent_list')[0]; 93 e.toolbar_buttons = $('#toolbar ul li'); 94 e.toolbar_pause_button = $('#toolbar-pause')[0]; 95 e.toolbar_start_button = $('#toolbar-start')[0]; 96 e.toolbar_remove_button = $('#toolbar-remove')[0]; 97 this.elements = e; 98 99 // Apply the prefs settings to the gui 100 this.initializeSettings(); 101 102 // Get preferences & torrents from the daemon 103 var async = false; 104 this.loadDaemonPrefs(async); 105 this.loadDaemonStats(async); 106 this.initializeTorrents(); 107 this.refreshTorrents(); 108 this.togglePeriodicSessionRefresh(true); 109 110 this.updateButtonsSoon(); 111 }, 112 113 loadDaemonPrefs: function(async) { 114 this.remote.loadDaemonPrefs(function(data) { 115 var o = data['arguments']; 116 Prefs.getClutchPrefs(o); 117 this.updateGuiFromSession(o); 118 this.sessionProperties = o; 119 }, this, async); 120 }, 121 122 loadImages: function() { 123 for (var i=0, row; row=arguments[i]; ++i) 124 jQuery("<img>").attr("src", row); 125 }, 126 127 /* 128 * Load the clutch prefs and init the GUI according to those prefs 129 */ 130 initializeSettings: function() 131 { 132 Prefs.getClutchPrefs(this); 133 134 if (this.isMenuEnabled) 135 { 136 $('#sort_by_' + this[Prefs._SortMethod]).selectMenuItem(); 137 138 if (this[Prefs._SortDirection] === Prefs._SortDescending) 139 $('#reverse_sort_order').selectMenuItem(); 140 } 141 142 this.initCompactMode(); 143 }, 144 145 /* 146 * Set up the search box 147 */ 148 setupSearchBox: function() 149 { 150 var tr = this; 151 var search_box = $('#torrent_search'); 152 search_box.bind('keyup click', function() { 153 tr.setFilterText(this.value); 154 }); 155 if (!$.browser.safari) 156 { 157 search_box.addClass('blur'); 158 search_box[0].value = 'Filter'; 159 search_box.bind('blur', function() { 160 if (this.value === '') { 161 $(this).addClass('blur'); 162 this.value = 'Filter'; 163 tr.setFilterText(null); 164 } 165 }).bind('focus', function() { 166 if ($(this).is('.blur')) { 167 this.value = ''; 168 $(this).removeClass('blur'); 169 } 170 }); 171 } 172 }, 173 174 /** 175 * Create the torrent right-click menu 176 */ 177 createContextMenu: function() { 178 var tr = this; 179 var bindings = { 180 context_pause_selected: function() { tr.stopSelectedTorrents(); }, 181 context_resume_selected: function() { tr.startSelectedTorrents(false); }, 182 context_resume_now_selected: function() { tr.startSelectedTorrents(true); }, 183 context_move: function() { tr.moveSelectedTorrents(false); }, 184 context_remove: function() { tr.removeSelectedTorrents(); }, 185 context_removedata: function() { tr.removeSelectedTorrentsAndData(); }, 186 context_verify: function() { tr.verifySelectedTorrents(); }, 187 context_reannounce: function() { tr.reannounceSelectedTorrents(); }, 188 context_move_top: function() { tr.moveTop(); }, 189 context_move_up: function() { tr.moveUp(); }, 190 context_move_down: function() { tr.moveDown(); }, 191 context_move_bottom: function() { tr.moveBottom(); }, 192 context_select_all: function() { tr.selectAll(); }, 193 context_deselect_all: function() { tr.deselectAll(); } 194 }; 195 196 // Set up the context menu 197 $('ul#torrent_list').contextMenu('torrent_context_menu', { 198 bindings: bindings, 199 menuStyle: { width: '310px', backgroundColor: '#fff', border: 'none', padding: '5px 0', textAlign: 'left' }, 200 itemStyle: { backgroundColor: 'transparent', margin: '0', padding: '3px 10px 3px 20px', color: '#000', cursor: 'default', border: 'none'}, 201 itemHoverStyle: { backgroundColor: '#24e', color: '#fff', border: 'none'}, 202 shadow: false, 203 boundingElement: $('div#torrent_container'), 204 boundingRightPad: 20, 205 boundingBottomPad: 5, 206 onContextMenu: function(ev) { 207 var element = $(ev.target).closest('.torrent')[0]; 208 var i = $('#torrent_list > li').index(element); 209 if ((i!==-1) && !tr._rows[i].isSelected()) 210 tr.setSelectedRow(tr._rows[i]); 211 return true; 212 } 213 }); 214 }, 215 216 createSettingsMenu: function() { 217 $('#settings_menu').transMenu({ 218 selected_char: '✔', 219 direction: 'up', 220 onClick: $.proxy(this.onMenuClicked,this) 221 }); 222 223 $('#unlimited_download_rate').selectMenuItem(); 224 $('#unlimited_upload_rate').selectMenuItem(); 225 }, 226 227 228 /**** 229 ***** 230 ***** UTILITIES 231 ***** 232 ****/ 233 234 getAllTorrents: function() 235 { 236 var torrents = []; 237 for (var key in this._torrents) 238 torrents.push(this._torrents[key]); 239 return torrents; 240 }, 241 242 getTorrentIds: function(torrents) 243 { 244 return $.map(torrents.slice(0), function(t) {return t.getId();}); 245 }, 246 247 scrollToRow: function(row) 248 { 249 if (isMobileDevice) // FIXME: why? 250 return; 251 252 var list = $('#torrent_container'), 253 scrollTop = list.scrollTop(), 254 innerHeight = list.innerHeight(), 255 offsetTop = row.getElement().offsetTop, 256 offsetHeight = $(row.getElement()).outerHeight(); 257 258 if (offsetTop < scrollTop) 259 list.scrollTop(offsetTop); 260 else if (innerHeight + scrollTop < offsetTop + offsetHeight) 261 list.scrollTop(offsetTop + offsetHeight - innerHeight); 262 }, 263 264 seedRatioLimit: function() { 265 var p = this.sessionProperties; 266 if (p && p.seedRatioLimited) 267 return p.seedRatioLimit; 268 return -1; 269 }, 270 271 setPref: function(key, val) 272 { 273 this[key] = val; 274 Prefs.setValue(key, val); 275 }, 276 277 /**** 278 ***** 279 ***** SELECTION 280 ***** 281 ****/ 282 283 getSelectedRows: function() { 284 return $.grep(this._rows, function(r) {return r.isSelected();}); 285 }, 286 287 getSelectedTorrents: function() { 288 return $.map(this.getSelectedRows(),function(r) { 289 return r.getTorrent(); 290 }); 291 }, 292 293 getSelectedTorrentIds: function() { 294 return this.getTorrentIds(this.getSelectedTorrents()); 295 }, 296 297 setSelectedRow: function(row) { 298 $(this.elements.torrent_list).children('.selected').removeClass('selected'); 299 this.selectRow(row); 300 }, 301 302 selectRow: function(row) { 303 $(row.getElement()).addClass('selected'); 304 this.callSelectionChangedSoon(); 305 }, 306 307 deselectRow: function(row) { 308 $(row.getElement()).removeClass('selected'); 309 this.callSelectionChangedSoon(); 310 }, 311 312 selectAll: function() { 313 $(this.elements.torrent_list).children().addClass('selected'); 314 this.callSelectionChangedSoon(); 315 }, 316 deselectAll: function() { 317 $(this.elements.torrent_list).children('.selected').removeClass('selected'); 318 this.callSelectionChangedSoon(); 319 delete this._last_torrent_clicked; 320 }, 321 322 indexOfLastTorrent: function() { 323 for (var i=0, r; r=this._rows[i]; ++i) 324 if (r.getTorrentId() === this._last_torrent_clicked) 325 return i; 326 return -1; 327 }, 328 329 // Select a range from this row to the last clicked torrent 330 selectRange: function(row) 331 { 332 var last = this.indexOfLastTorrent(); 333 334 if (last === -1) 335 { 336 this.selectRow(row); 337 } 338 else // select the range between the prevous & current 339 { 340 var next = this._rows.indexOf(row); 341 var min = Math.min(last, next); 342 var max = Math.max(last, next); 343 for (var i=min; i<=max; ++i) 344 this.selectRow(this._rows[i]); 345 } 346 347 this.callSelectionChangedSoon(); 348 }, 349 350 selectionChanged: function() 351 { 352 this.updateButtonStates(); 353 354 this.inspector.setTorrents(this.inspectorIsVisible() ? this.getSelectedTorrents() : []); 355 356 clearTimeout(this.selectionChangedTimer); 357 delete this.selectionChangedTimer; 358 359 }, 360 361 callSelectionChangedSoon: function() 362 { 363 if (!this.selectionChangedTimer) 364 { 365 var callback = $.proxy(this.selectionChanged,this), 366 msec = 200; 367 this.selectionChangedTimer = setTimeout(callback, msec); 368 } 369 }, 370 371 /*-------------------------------------------- 372 * 373 * E V E N T F U N C T I O N S 374 * 375 *--------------------------------------------*/ 376 377 /* 378 * Process key event 379 */ 380 keyDown: function(ev) 381 { 382 var handled = false, 383 rows = this._rows, 384 up = ev.keyCode === 38, // up key pressed 385 dn = ev.keyCode === 40, // down key pressed 386 shift = ev.keyCode === 16; // shift key pressed 387 388 if ((up || dn) && rows.length) 389 { 390 var last = this.indexOfLastTorrent(), 391 i = last, 392 anchor = this._shift_index, 393 r, 394 min = 0, 395 max = rows.length - 1; 396 397 if (dn && (i+1 <= max)) 398 ++i; 399 else if (up && (i-1 >= min)) 400 --i; 401 402 var r = rows[i]; 403 404 if (anchor >= 0) 405 { 406 // user is extending the selection 407 // with the shift + arrow keys... 408 if ( ((anchor <= last) && (last < i)) 409 || ((anchor >= last) && (last > i))) 410 { 411 this.selectRow(r); 412 } 413 else if (((anchor >= last) && (i > last)) 414 || ((anchor <= last) && (last > i))) 415 { 416 this.deselectRow(rows[last]); 417 } 418 } 419 else 420 { 421 if (ev.shiftKey) 422 this.selectRange(r); 423 else 424 this.setSelectedRow(r); 425 } 426 this._last_torrent_clicked = r.getTorrentId(); 427 this.scrollToRow(r); 428 handled = true; 429 } 430 else if (shift) 431 { 432 this._shift_index = this.indexOfLastTorrent(); 433 } 434 435 return !handled; 436 }, 437 438 keyUp: function(ev) { 439 if (ev.keyCode === 16) // shift key pressed 440 delete this._shift_index; 441 }, 442 443 isButtonEnabled: function(ev) { 444 var p = (ev.target || ev.srcElement).parentNode; 445 return p.className!=='disabled' 446 && p.parentNode.className!=='disabled'; 447 }, 448 449 stopSelectedClicked: function(ev) { 450 if (this.isButtonEnabled(ev)) { 451 this.stopSelectedTorrents(); 452 this.hideMobileAddressbar(); 453 } 454 }, 455 456 startSelectedClicked: function(ev) { 457 if (this.isButtonEnabled(ev)) { 458 this.startSelectedTorrents(false); 459 this.hideMobileAddressbar(); 460 } 461 }, 462 463 stopAllClicked: function(ev) { 464 if (this.isButtonEnabled(ev)) { 465 this.stopAllTorrents(); 466 this.hideMobileAddressbar(); 467 } 468 }, 469 470 startAllClicked: function(ev) { 471 if (this.isButtonEnabled(ev)) { 472 this.startAllTorrents(false); 473 this.hideMobileAddressbar(); 474 } 475 }, 476 477 openTorrentClicked: function(ev) { 478 if (this.isButtonEnabled(ev)) { 479 $('body').addClass('open_showing'); 480 this.uploadTorrentFile(); 481 this.updateButtonStates(); 482 } 483 }, 484 485 dragenter: function(ev) { 486 if (ev.dataTransfer && ev.dataTransfer.types) { 487 var types = ["text/uri-list", "text/plain"]; 488 for (var i = 0; i < types.length; ++i) { 489 // it would be better to look at the links here; 490 // sadly, with Firefox, trying would throw. 491 if (ev.dataTransfer.types.contains(types[i])) { 492 ev.stopPropagation(); 493 ev.preventDefault(); 494 ev.dropEffect = "copy"; 495 return false; 496 } 497 } 498 } 499 else if (ev.dataTransfer) { 500 ev.dataTransfer.dropEffect = "none"; 501 } 502 return true; 503 }, 504 505 drop: function(ev) 506 { 507 var i, uri, uris=null, 508 types = ["text/uri-list", "text/plain"], 509 paused = this.shouldAddedTorrentsStart(); 510 511 if (!ev.dataTransfer || !ev.dataTransfer.types) 512 return true; 513 514 for (i=0; !uris && i<types.length; ++i) 515 if (ev.dataTransfer.types.contains(types[i])) 516 uris = ev.dataTransfer.getData(types[i]).split("\n"); 517 518 for (i=0; uri=uris[i]; ++i) { 519 if (/^#/.test(uri)) // lines which start with "#" are comments 520 continue; 521 if (/^[a-z-]+:/i.test(uri)) // close enough to a url 522 this.remote.addTorrentByUrl(uri, paused); 523 } 524 525 ev.preventDefault(); 526 return false; 527 }, 528 529 hideUploadDialog: function() { 530 $('body.open_showing').removeClass('open_showing'); 531 $('#upload_container').hide(); 532 this.updateButtonStates(); 533 }, 534 535 confirmUploadClicked: function() { 536 this.uploadTorrentFile(true); 537 this.hideUploadDialog(); 538 }, 539 540 hideMoveDialog: function() { 541 $('#move_container').hide(); 542 this.updateButtonStates(); 543 }, 544 545 confirmMoveClicked: function() { 546 this.moveSelectedTorrents(true); 547 this.hideUploadDialog(); 548 }, 549 550 removeClicked: function(ev) { 551 if (this.isButtonEnabled(ev)) { 552 this.removeSelectedTorrents(); 553 this.hideMobileAddressbar(); 554 } 555 }, 556 557 // turn the periodic ajax session refresh on & off 558 togglePeriodicSessionRefresh: function(enabled) { 559 clearInterval(this.sessionInterval); 560 delete this.sessionInterval; 561 if (enabled) { 562 var callback = $.proxy(this.loadDaemonPrefs,this), 563 msec = 8000; 564 this.sessionInterval = setInterval(callback, msec); 565 } 566 }, 567 568 toggleTurtleClicked: function() 569 { 570 var o = {}; 571 o[RPC._TurtleState] = !$('#turtle-button').hasClass('selected'); 572 this.remote.savePrefs(o); 573 }, 574 575 /*-------------------------------------------- 576 * 577 * I N T E R F A C E F U N C T I O N S 578 * 579 *--------------------------------------------*/ 580 581 onPrefsDialogClosed: function() { 582 $('#prefs-button').removeClass('selected'); 583 }, 584 585 togglePrefsDialogClicked: function(ev) 586 { 587 var e = $('#prefs-button'); 588 589 if (e.hasClass('selected')) 590 this.prefsDialog.close(); 591 else { 592 e.addClass('selected'); 593 this.prefsDialog.show(); 594 } 595 }, 596 597 setFilterText: function(search) { 598 this.filterText = search ? search.trim() : null; 599 this.refilter(true); 600 }, 601 602 setSortMethod: function(sort_method) { 603 this.setPref(Prefs._SortMethod, sort_method); 604 this.refilter(true); 605 }, 606 607 setSortDirection: function(direction) { 608 this.setPref(Prefs._SortDirection, direction); 609 this.refilter(true); 610 }, 611 612 onMenuClicked: function(ev) 613 { 614 var o, dir, 615 id = ev.target.id, 616 remote = this.remote, 617 element = $(ev.target); 618 619 if (element.hasClass('sort-mode')) 620 { 621 element.parent().find('.sort-mode').each(function() { 622 element.parent().deselectMenuItem(); 623 }); 624 element.selectMenuItem(); 625 this.setSortMethod(id.replace(/sort_by_/, '')); 626 } 627 else if (element.hasClass('upload-speed')) 628 { 629 o = {}; 630 o[RPC._UpSpeedLimit] = parseInt(ev.target.innerHTML); 631 o[RPC._UpSpeedLimited] = true; 632 remote.savePrefs(o); 633 } 634 else if (element.hasClass('download-speed')) 635 { 636 o = {}; 637 o[RPC._DownSpeedLimit] = parseInt(ev.target.innerHTML); 638 o[RPC._DownSpeedLimited] = true; 639 remote.savePrefs(o); 640 } 641 else switch (id) 642 { 643 case 'statistics': 644 this.showStatsDialog(); 645 break; 646 647 case 'about-button': 648 o = 'Transmission ' + this.serverVersion; 649 $('#about-dialog #about-title').html(o); 650 $('#about-dialog').dialog({ 651 title: 'About', 652 show: 'fade', 653 hide: 'fade' 654 }); 655 break; 656 657 case 'homepage': 658 window.open('http://www.transmissionbt.com/'); 659 break; 660 661 case 'tipjar': 662 window.open('http://www.transmissionbt.com/donate.php'); 663 break; 664 665 case 'unlimited_download_rate': 666 o = {}; 667 o[RPC._DownSpeedLimited] = false; 668 remote.savePrefs(o); 669 break; 670 671 case 'limited_download_rate': 672 o = {}; 673 o[RPC._DownSpeedLimited] = true; 674 remote.savePrefs(o); 675 break; 676 677 case 'unlimited_upload_rate': 678 o = {}; 679 o[RPC._UpSpeedLimited] = false; 680 remote.savePrefs(o); 681 break; 682 683 case 'limited_upload_rate': 684 o = {}; 685 o[RPC._UpSpeedLimited] = true; 686 remote.savePrefs(o); 687 break; 688 689 case 'reverse_sort_order': 690 if (element.menuItemIsSelected()) { 691 dir = Prefs._SortAscending; 692 element.deselectMenuItem(); 693 } else { 694 dir = Prefs._SortDescending; 695 element.selectMenuItem(); 696 } 697 this.setSortDirection(dir); 698 break; 699 700 case 'toggle_notifications': 701 Notifications && Notifications.toggle(); 702 break; 703 704 default: 705 console.log('unhandled: ' + id); 706 break; 707 708 } 709 $('#settings_menu').trigger('closemenu'); 710 ev.stopImmediatePropagation(); 711 }, 712 713 714 onTorrentChanged: function(ev, tor) 715 { 716 // update our dirty fields 717 this.dirtyTorrents[ tor.getId() ] = true; 718 719 // enqueue ui refreshes 720 this.refilterSoon(); 721 this.updateButtonsSoon(); 722 }, 723 724 updateFromTorrentGet: function(updates, removed_ids) 725 { 726 var i, o, t, id, needed, needinfo = [], 727 callback, fields; 728 729 for (i=0; o=updates[i]; ++i) 730 { 731 id = o.id; 732 if ((t = this._torrents[id])) 733 { 734 needed = t.needsMetaData(); 735 t.refresh(o); 736 if (needed && !t.needsMetaData()) 737 needinfo.push(id); 738 } 739 else { 740 t = this._torrents[id] = new Torrent(o); 741 this.dirtyTorrents[id] = true; 742 callback = $.proxy(this.onTorrentChanged,this); 743 $(t).bind('dataChanged',callback); 744 // do we need more info for this torrent? 745 if(!('name' in t.fields) || !('status' in t.fields)) 746 needinfo.push(id); 747 748 t.notifyOnFieldChange('status', $.proxy(function (newValue, oldValue) { 749 if (oldValue === Torrent._StatusDownload && (newValue == Torrent._StatusSeed || newValue == Torrent._StatusSeedWait)) { 750 $(this).trigger('downloadComplete', [t]); 751 } else if (oldValue === Torrent._StatusSeed && newValue === Torrent._StatusStopped && t.isFinished()) { 752 $(this).trigger('seedingComplete', [t]); 753 } else { 754 $(this).trigger('statusChange', [t]); 755 } 756 }, this)); 757 } 758 } 759 760 if (needinfo.length) { 761 // whee, new torrents! get their initial information. 762 fields = ['id'].concat(Torrent.Fields.Metadata, 763 Torrent.Fields.Stats); 764 this.updateTorrents(needinfo, fields); 765 this.refilterSoon(); 766 } 767 768 if (removed_ids) { 769 this.deleteTorrents(removed_ids); 770 this.refilterSoon(); 771 } 772 }, 773 774 updateTorrents: function(ids, fields) 775 { 776 this.remote.updateTorrents(ids, fields, 777 this.updateFromTorrentGet, this); 778 }, 779 780 refreshTorrents: function() 781 { 782 var callback = $.proxy(this.refreshTorrents,this), 783 msec = this[Prefs._RefreshRate] * 1000, 784 fields = ['id'].concat(Torrent.Fields.Stats); 785 786 // send a request right now 787 this.updateTorrents('recently-active', fields); 788 789 // schedule the next request 790 clearTimeout(this.refreshTorrentsTimeout); 791 this.refreshTorrentsTimeout = setTimeout(callback, msec); 792 }, 793 794 initializeTorrents: function() 795 { 796 var fields = ['id'].concat(Torrent.Fields.Metadata, 797 Torrent.Fields.Stats); 798 this.updateTorrents(null, fields); 799 }, 800 801 onRowClicked: function(ev) 802 { 803 var meta_key = ev.metaKey || ev.ctrlKey, 804 row = ev.currentTarget.row; 805 806 // handle the per-row "torrent_resume" button 807 if (ev.target.className === 'torrent_resume') { 808 this.startTorrent(row.getTorrent()); 809 return; 810 } 811 812 // handle the per-row "torrent_pause" button 813 if (ev.target.className === 'torrent_pause') { 814 this.stopTorrent(row.getTorrent()); 815 return; 816 } 817 818 // Prevents click carrying to parent element 819 // which deselects all on click 820 ev.stopPropagation(); 821 // but still hide the context menu if it is showing 822 $('#jqContextMenu').hide(); 823 824 if (isMobileDevice) { 825 if (row.isSelected()) 826 this.setInspectorVisible(true); 827 this.setSelectedRow(row); 828 829 } else if (ev.shiftKey) { 830 this.selectRange(row); 831 // Need to deselect any selected text 832 window.focus(); 833 834 // Apple-Click, not selected 835 } else if (!row.isSelected() && meta_key) { 836 this.selectRow(row); 837 838 // Regular Click, not selected 839 } else if (!row.isSelected()) { 840 this.setSelectedRow(row); 841 842 // Apple-Click, selected 843 } else if (row.isSelected() && meta_key) { 844 this.deselectRow(row); 845 846 // Regular Click, selected 847 } else if (row.isSelected()) { 848 this.setSelectedRow(row); 849 } 850 851 this._last_torrent_clicked = row.getTorrentId(); 852 }, 853 854 deleteTorrents: function(ids) 855 { 856 var i, id; 857 858 if (ids && ids.length) 859 { 860 for (i=0; id=ids[i]; ++i) { 861 this.dirtyTorrents[id] = true; 862 delete this._torrents[id]; 863 } 864 this.refilter(); 865 } 866 }, 867 868 shouldAddedTorrentsStart: function() 869 { 870 return this.prefsDialog.shouldAddedTorrentsStart(); 871 }, 872 873 /* 874 * Select a torrent file to upload 875 * FIXME 876 */ 877 uploadTorrentFile: function(confirmed) 878 { 879 // Display the upload dialog 880 if (! confirmed) { 881 $('input#torrent_upload_file').attr('value', ''); 882 $('input#torrent_upload_url').attr('value', ''); 883 $('input#torrent_auto_start').attr('checked', this.shouldAddedTorrentsStart()); 884 $('#upload_container').show(); 885 $('#torrent_upload_url').focus(); 886 887 // Submit the upload form 888 } else { 889 var args = {}; 890 var remote = this.remote; 891 var paused = !$('#torrent_auto_start').is(':checked'); 892 if ('' != $('#torrent_upload_url').val()) { 893 remote.addTorrentByUrl($('#torrent_upload_url').val(), { paused: paused }); 894 } else { 895 args.url = '../upload?paused=' + paused; 896 args.type = 'POST'; 897 args.data = { 'X-Transmission-Session-Id' : remote._token }; 898 args.dataType = 'xml'; 899 args.iframe = true; 900 $('#torrent_upload_form').ajaxSubmit(args); 901 } 902 } 903 }, 904 905 promptSetLocation: function(confirmed, torrents) { 906 if (! confirmed) { 907 var path; 908 if (torrents.length === 1) { 909 path = torrents[0].getDownloadDir(); 910 } else { 911 path = $("#download-dir").val(); 912 } 913 $('input#torrent_path').attr('value', path); 914 $('#move_container').show(); 915 $('#torrent_path').focus(); 916 } else { 917 var ids = this.getTorrentIds(torrents); 918 this.remote.moveTorrents( 919 ids, 920 $("input#torrent_path").val(), 921 this.refreshTorrents, 922 this); 923 $('#move_container').hide(); 924 } 925 }, 926 927 moveSelectedTorrents: function(confirmed) { 928 var torrents = this.getSelectedTorrents(); 929 if (torrents.length) 930 this.promptSetLocation(confirmed, torrents); 931 }, 932 933 removeSelectedTorrents: function() { 934 var torrents = this.getSelectedTorrents(); 935 if (torrents.length) 936 this.promptToRemoveTorrents(torrents); 937 }, 938 939 removeSelectedTorrentsAndData: function() { 940 var torrents = this.getSelectedTorrents(); 941 if (torrents.length) 942 this.promptToRemoveTorrentsAndData(torrents); 943 }, 944 945 promptToRemoveTorrents: function(torrents) { 946 if (torrents.length === 1) 947 { 948 var torrent = torrents[0], 949 header = 'Remove ' + torrent.getName() + '?', 950 message = 'Once removed, continuing the transfer will require the torrent file. Are you sure you want to remove it?'; 951 dialog.confirm(header, message, 'Remove', 'transmission.removeTorrents', torrents); 952 } 953 else 954 { 955 var header = 'Remove ' + torrents.length + ' transfers?', 956 message = 'Once removed, continuing the transfers will require the torrent files. Are you sure you want to remove them?'; 957 dialog.confirm(header, message, 'Remove', 'transmission.removeTorrents', torrents); 958 } 959 }, 960 961 promptToRemoveTorrentsAndData:function(torrents) 962 { 963 if (torrents.length === 1) 964 { 965 var torrent = torrents[0], 966 header = 'Remove ' + torrent.getName() + ' and delete data?', 967 message = 'All data downloaded for this torrent will be deleted. Are you sure you want to remove it?'; 968 dialog.confirm(header, message, 'Remove', 'transmission.removeTorrentsAndData', torrents); 969 } 970 else 971 { 972 var header = 'Remove ' + torrents.length + ' transfers and delete data?', 973 message = 'All data downloaded for these torrents will be deleted. Are you sure you want to remove them?'; 974 dialog.confirm(header, message, 'Remove', 'transmission.removeTorrentsAndData', torrents); 975 } 976 }, 977 978 removeTorrents: function(torrents) { 979 var ids = this.getTorrentIds(torrents); 980 this.remote.removeTorrents(ids, this.refreshTorrents, this); 981 }, 982 983 removeTorrentsAndData: function(torrents) { 984 this.remote.removeTorrentsAndData(torrents); 985 }, 986 987 verifySelectedTorrents: function() { 988 this.verifyTorrents(this.getSelectedTorrents()); 989 }, 990 991 reannounceSelectedTorrents: function() { 992 this.reannounceTorrents(this.getSelectedTorrents()); 993 }, 994 995 startAllTorrents: function(force) { 996 this.startTorrents(this.getAllTorrents(), force); 997 }, 998 startSelectedTorrents: function(force) { 999 this.startTorrents(this.getSelectedTorrents(), force); 1000 }, 1001 startTorrent: function(torrent) { 1002 this.startTorrents([ torrent ], false); 1003 }, 1004 1005 startTorrents: function(torrents, force) { 1006 this.remote.startTorrents(this.getTorrentIds(torrents), force, 1007 this.refreshTorrents, this); 1008 }, 1009 verifyTorrent: function(torrent) { 1010 this.verifyTorrents([ torrent ]); 1011 }, 1012 verifyTorrents: function(torrents) { 1013 this.remote.verifyTorrents(this.getTorrentIds(torrents), 1014 this.refreshTorrents, this); 1015 }, 1016 1017 reannounceTorrent: function(torrent) { 1018 this.reannounceTorrents([ torrent ]); 1019 }, 1020 reannounceTorrents: function(torrents) { 1021 this.remote.reannounceTorrents(this.getTorrentIds(torrents), 1022 this.refreshTorrents, this); 1023 }, 1024 1025 stopAllTorrents: function() { 1026 this.stopTorrents(this.getAllTorrents()); 1027 }, 1028 stopSelectedTorrents: function() { 1029 this.stopTorrents(this.getSelectedTorrents()); 1030 }, 1031 stopTorrent: function(torrent) { 1032 this.stopTorrents([ torrent ]); 1033 }, 1034 stopTorrents: function(torrents) { 1035 this.remote.stopTorrents(this.getTorrentIds(torrents), 1036 this.refreshTorrents, this); 1037 }, 1038 changeFileCommand: function(torrentId, rowIndices, command) { 1039 this.remote.changeFileCommand(torrentId, rowIndices, command); 1040 }, 1041 1042 hideMobileAddressbar: function(delaySecs) { 1043 if (isMobileDevice && !scroll_timeout) { 1044 var callback = $.proxy(this.doToolbarHide,this), 1045 msec = delaySecs*1000 || 150; 1046 scroll_timeout = setTimeout(callback,msec); 1047 } 1048 }, 1049 doToolbarHide: function() { 1050 window.scrollTo(0,1); 1051 scroll_timeout=null; 1052 }, 1053 1054 // Queue 1055 moveTop: function() { 1056 this.remote.moveTorrentsToTop(this.getSelectedTorrentIds(), 1057 this.refreshTorrents, this); 1058 }, 1059 moveUp: function() { 1060 this.remote.moveTorrentsUp(this.getSelectedTorrentIds(), 1061 this.refreshTorrents, this); 1062 }, 1063 moveDown: function() { 1064 this.remote.moveTorrentsDown(this.getSelectedTorrentIds(), 1065 this.refreshTorrents, this); 1066 }, 1067 moveBottom: function() { 1068 this.remote.moveTorrentsToBottom(this.getSelectedTorrentIds(), 1069 this.refreshTorrents, this); 1070 }, 1071 1072 /*** 1073 **** 1074 ***/ 1075 1076 updateGuiFromSession: function(o) 1077 { 1078 var limit, limited, e, b, text, 1079 fmt = Transmission.fmt, 1080 menu = $('#settings_menu'); 1081 1082 this.serverVersion = o.version; 1083 1084 this.prefsDialog.set(o); 1085 1086 if (RPC._TurtleState in o) 1087 { 1088 b = o[RPC._TurtleState]; 1089 e = $('#turtle-button'); 1090 text = [ 'Click to ', (b?'disable':'enable'), 1091 ' Temporary Speed Limits (', 1092 fmt.speed(o[RPC._TurtleUpSpeedLimit]), 1093 ' up,', 1094 fmt.speed(o[RPC._TurtleDownSpeedLimit]), 1095 ' down)' ].join(''); 1096 e.toggleClass('selected', b); 1097 e.attr('title', text); 1098 } 1099 1100 if (this.isMenuEnabled && (RPC._DownSpeedLimited in o) 1101 && (RPC._DownSpeedLimit in o)) 1102 { 1103 limit = o[RPC._DownSpeedLimit]; 1104 limited = o[RPC._DownSpeedLimited]; 1105 1106 e = menu.find('#limited_download_rate'); 1107 e.html('Limit (' + fmt.speed(limit) + ')'); 1108 1109 if (!limited) 1110 e = menu.find('#unlimited_download_rate'); 1111 e.deselectMenuSiblings().selectMenuItem(); 1112 } 1113 1114 if (this.isMenuEnabled && (RPC._UpSpeedLimited in o) 1115 && (RPC._UpSpeedLimit in o)) 1116 { 1117 limit = o[RPC._UpSpeedLimit]; 1118 limited = o[RPC._UpSpeedLimited]; 1119 1120 e = menu.find('#limited_upload_rate'); 1121 e.html('Limit (' + fmt.speed(limit) + ')'); 1122 1123 if (!limited) 1124 e = menu.find('#unlimited_upload_rate'); 1125 e.deselectMenuSiblings().selectMenuItem(); 1126 } 1127 }, 1128 1129 updateStatusbar: function() 1130 { 1131 var u=0, d=0, 1132 i, row, text, 1133 fmt = Transmission.fmt, 1134 torrents = this.getAllTorrents(); 1135 1136 // up/down speed 1137 for (i=0; row=torrents[i]; ++i) { 1138 u += row.getUploadSpeed(); 1139 d += row.getDownloadSpeed(); 1140 } 1141 1142 $('#speed-up-container').toggleClass('active', u>0 ); 1143 $('#speed-up-label').text( fmt.speedBps( u ) ); 1144 1145 $('#speed-dn-container').toggleClass('active', d>0 ); 1146 $('#speed-dn-label').text( fmt.speedBps( d ) ); 1147 1148 // visible torrents 1149 $('#filter-count').text( fmt.countString('Transfer','Transfers',this._rows.length ) ); 1150 }, 1151 1152 setEnabled: function(key, flag) 1153 { 1154 $(key).toggleClass('disabled', !flag); 1155 }, 1156 1157 updateFilterSelect: function() 1158 { 1159 var i, names, name, str, o, 1160 e = $('#filter-tracker'), 1161 trackers = this.getTrackers(); 1162 1163 // build a sorted list of names 1164 names = []; 1165 for (name in trackers) 1166 names.push (name); 1167 names.sort(); 1168 1169 // build the new html 1170 if (!this.filterTracker) 1171 str = '<option value="all" selected="selected">All</option>'; 1172 else 1173 str = '<option value="all">All</option>'; 1174 for (i=0; name=names[i]; ++i) { 1175 o = trackers[name]; 1176 str += '<option value="' + o.domain + '"'; 1177 if (trackers[name].domain === this.filterTracker) str += ' selected="selected"'; 1178 str += '>' + name + '</option>'; 1179 } 1180 1181 if (!this.filterTrackersStr || (this.filterTrackersStr !== str)) { 1182 this.filterTrackersStr = str; 1183 $('#filter-tracker').html(str); 1184 } 1185 }, 1186 1187 updateButtonsSoon: function() 1188 { 1189 if (!this.buttonRefreshTimer) 1190 { 1191 var callback = $.proxy(this.updateButtonStates,this), 1192 msec = 100; 1193 this.buttonRefreshTimer = setTimeout(callback, msec); 1194 } 1195 }, 1196 1197 updateButtonStates: function() 1198 { 1199 var e = this.elements, 1200 haveActive = false, 1201 havePaused = false, 1202 haveSel = false, 1203 haveActiveSel = false, 1204 havePausedSel = false; 1205 1206 clearTimeout(this.buttonRefreshTimer); 1207 delete this.buttonRefreshTimer; 1208 1209 for (var i=0, row; row=this._rows[i]; ++i) { 1210 var isStopped = row.getTorrent().isStopped(); 1211 var isSelected = row.isSelected(); 1212 if (!isStopped) haveActive = true; 1213 if (isStopped) havePaused = true; 1214 if (isSelected) haveSel = true; 1215 if (isSelected && !isStopped) haveActiveSel = true; 1216 if (isSelected && isStopped) havePausedSel = true; 1217 } 1218 1219 this.setEnabled(e.toolbar_pause_button, haveActiveSel); 1220 this.setEnabled(e.toolbar_start_button, havePausedSel); 1221 this.setEnabled(e.toolbar_remove_button, haveSel); 1222 }, 1223 1224 /**** 1225 ***** 1226 ***** INSPECTOR 1227 ***** 1228 ****/ 1229 1230 inspectorIsVisible: function() 1231 { 1232 return $('#torrent_inspector').is(':visible'); 1233 }, 1234 toggleInspector: function() 1235 { 1236 this.setInspectorVisible(!this.inspectorIsVisible()); 1237 }, 1238 setInspectorVisible: function(visible) 1239 { 1240 if (visible) 1241 this.inspector.setTorrents(this.getSelectedTorrents()); 1242 1243 // update the ui widgetry 1244 $('#torrent_inspector').toggle(visible); 1245 $('#toolbar-inspector').toggleClass('selected',visible); 1246 this.hideMobileAddressbar(); 1247 if (isMobileDevice) { 1248 $('body').toggleClass('inspector_showing',visible); 1249 } else { 1250 var w = visible ? $('#torrent_inspector').outerWidth() + 1 + 'px' : '0px'; 1251 $('#torrent_container')[0].style.right = w; 1252 } 1253 }, 1254 1255 /**** 1256 ***** 1257 ***** FILTER 1258 ***** 1259 ****/ 1260 1261 refilterSoon: function() 1262 { 1263 if (!this.refilterTimer) { 1264 var tr = this, 1265 callback = function(){tr.refilter(false);}, 1266 msec = 100; 1267 this.refilterTimer = setTimeout(callback, msec); 1268 } 1269 }, 1270 1271 sortRows: function(rows) 1272 { 1273 var i, tor, row, 1274 id2row = {}, 1275 torrents = []; 1276 1277 for (i=0; row=rows[i]; ++i) { 1278 tor = row.getTorrent(); 1279 torrents.push(tor); 1280 id2row[ tor.getId() ] = row; 1281 } 1282 1283 Torrent.sortTorrents(torrents, this[Prefs._SortMethod], 1284 this[Prefs._SortDirection]); 1285 1286 for (i=0; tor=torrents[i]; ++i) 1287 rows[i] = id2row[ tor.getId() ]; 1288 }, 1289 1290 refilter: function(rebuildEverything) 1291 { 1292 var i, e, id, t, row, tmp, rows, clean_rows, dirty_rows, frag, 1293 sort_mode = this[Prefs._SortMethod], 1294 sort_direction = this[Prefs._SortDirection], 1295 filter_mode = this[Prefs._FilterMode], 1296 filter_text = this.filterText, 1297 filter_tracker = this.filterTracker, 1298 renderer = this.torrentRenderer, 1299 list = this.elements.torrent_list, 1300 old_sel_count = $(list).children('.selected').length; 1301 1302 this.updateFilterSelect(); 1303 1304 clearTimeout(this.refilterTimer); 1305 delete this.refilterTimer; 1306 1307 if (rebuildEverything) { 1308 $(list).empty(); 1309 this._rows = []; 1310 for (id in this._torrents) 1311 this.dirtyTorrents[id] = true; 1312 } 1313 1314 // rows that overlap with dirtyTorrents need to be refiltered. 1315 // those that don't are 'clean' and don't need refiltering. 1316 clean_rows = []; 1317 dirty_rows = []; 1318 for (i=0; row=this._rows[i]; ++i) { 1319 if(row.getTorrentId() in this.dirtyTorrents) 1320 dirty_rows.push(row); 1321 else 1322 clean_rows.push(row); 1323 } 1324 1325 // remove the dirty rows from the dom 1326 e = []; 1327 for (i=0; row=dirty_rows[i]; ++i) 1328 e.push (row.getElement()); 1329 $(e).detach(); 1330 1331 // drop any dirty rows that don't pass the filter test 1332 tmp = []; 1333 for (i=0; row=dirty_rows[i]; ++i) { 1334 id = row.getTorrentId(); 1335 t = this._torrents[ id ]; 1336 if (t && t.test(filter_mode, filter_text, filter_tracker)) 1337 tmp.push(row); 1338 delete this.dirtyTorrents[id]; 1339 } 1340 dirty_rows = tmp; 1341 1342 // make new rows for dirty torrents that pass the filter test 1343 // but don't already have a row 1344 for (id in this.dirtyTorrents) { 1345 t = this._torrents[id]; 1346 if (t && t.test(filter_mode, filter_text, filter_tracker)) { 1347 row = new TorrentRow(renderer, this, t); 1348 e = row.getElement(); 1349 e.row = row; 1350 dirty_rows.push(row); 1351 $(e).click($.proxy(this.onRowClicked,this)); 1352 $(e).dblclick($.proxy(this.toggleInspector,this)); 1353 } 1354 } 1355 1356 // sort the dirty rows 1357 this.sortRows (dirty_rows); 1358 1359 // now we have two sorted arrays of rows 1360 // and can do a simple two-way sorted merge. 1361 rows = []; 1362 var ci=0, cmax=clean_rows.length; 1363 var di=0, dmax=dirty_rows.length; 1364 frag = document.createDocumentFragment(); 1365 while (ci!=cmax || di!=dmax) 1366 { 1367 var push_clean; 1368 1369 if (ci==cmax) 1370 push_clean = false; 1371 else if (di==dmax) 1372 push_clean = true; 1373 else { 1374 var c = Torrent.compareTorrents( 1375 clean_rows[ci].getTorrent(), 1376 dirty_rows[di].getTorrent(), 1377 sort_mode, sort_direction); 1378 push_clean = (c < 0); 1379 } 1380 1381 if (push_clean) 1382 rows.push(clean_rows[ci++]); 1383 else { 1384 row = dirty_rows[di++]; 1385 e = row.getElement(); 1386 if (ci !== cmax) 1387 list.insertBefore(e, clean_rows[ci].getElement()); 1388 else 1389 frag.appendChild(e); 1390 rows.push(row); 1391 } 1392 } 1393 list.appendChild(frag); 1394 1395 // update our implementation fields 1396 this._rows = rows; 1397 this.dirtyTorrents = {}; 1398 1399 // jquery's even/odd starts with 1 not 0, so invert its logic 1400 e = [] 1401 for (i=0; row=rows[i]; ++i) 1402 e.push(row.getElement()); 1403 $(e).filter(":odd").addClass('even'); 1404 $(e).filter(":even").removeClass('even'); 1405 1406 // sync gui 1407 this.updateStatusbar(); 1408 if (old_sel_count !== $(list).children('.selected').length) 1409 this.selectionChanged(); 1410 }, 1411 1412 setFilterMode: function(mode) 1413 { 1414 // set the state 1415 this.setPref(Prefs._FilterMode, mode); 1416 1417 // refilter 1418 this.refilter(true); 1419 }, 1420 1421 onFilterModeClicked: function(ev) 1422 { 1423 this.setFilterMode($('#filter-mode').val()); 1424 }, 1425 1426 onFilterTrackerClicked: function(ev) 1427 { 1428 var tracker = $('#filter-tracker').val(); 1429 this.setFilterTracker(tracker==='all' ? null : tracker); 1430 }, 1431 1432 setFilterTracker: function(domain) 1433 { 1434 // update which tracker is selected in the popup 1435 var key = domain ? this.getReadableDomain(domain) : 'all', 1436 id = '#show-tracker-' + key; 1437 $(id).addClass('selected').siblings().removeClass('selected'); 1438 1439 this.filterTracker = domain; 1440 this.refilter(true); 1441 }, 1442 1443 // example: "tracker.ubuntu.com" returns "ubuntu.com" 1444 getDomainName: function(host) 1445 { 1446 var dot = host.indexOf('.'); 1447 if (dot !== host.lastIndexOf('.')) 1448 host = host.slice(dot+1); 1449 return host; 1450 }, 1451 1452 // example: "ubuntu.com" returns "Ubuntu" 1453 getReadableDomain: function(name) 1454 { 1455 if (name.length) 1456 name = name.charAt(0).toUpperCase() + name.slice(1); 1457 var dot = name.indexOf('.'); 1458 if (dot !== -1) 1459 name = name.slice(0, dot); 1460 return name; 1461 }, 1462 1463 getTrackers: function() 1464 { 1465 var ret = {}; 1466 1467 var torrents = this.getAllTorrents(); 1468 for (var i=0, torrent; torrent=torrents[i]; ++i) 1469 { 1470 var names = []; 1471 var trackers = torrent.getTrackers(); 1472 for (var j=0, tracker; tracker=trackers[j]; ++j) 1473 { 1474 var uri, announce = tracker.announce; 1475 1476 if (announce in this.uriCache) 1477 uri = this.uriCache[announce]; 1478 else { 1479 uri = this.uriCache[announce] = parseUri (announce); 1480 uri.domain = this.getDomainName (uri.host); 1481 uri.name = this.getReadableDomain (uri.domain); 1482 } 1483 1484 if (!(uri.name in ret)) 1485 ret[uri.name] = { 'uri': uri, 1486 'domain': uri.domain, 1487 'count': 0 }; 1488 1489 if (names.indexOf(uri.name) === -1) 1490 names.push(uri.name); 1491 } 1492 for (var j=0, name; name=names[j]; ++j) 1493 ret[name].count++; 1494 } 1495 1496 return ret; 1497 }, 1498 1499 /*** 1500 **** 1501 **** Compact Mode 1502 **** 1503 ***/ 1504 1505 toggleCompactClicked: function() 1506 { 1507 this.setCompactMode(!this[Prefs._CompactDisplayState]); 1508 }, 1509 setCompactMode: function(is_compact) 1510 { 1511 var key = Prefs._CompactDisplayState, 1512 was_compact = this[key]; 1513 1514 if (was_compact !== is_compact) { 1515 this.setPref(key, is_compact); 1516 this.onCompactModeChanged(); 1517 } 1518 }, 1519 initCompactMode: function() 1520 { 1521 this.onCompactModeChanged(); 1522 }, 1523 onCompactModeChanged: function() 1524 { 1525 var compact = this[Prefs._CompactDisplayState]; 1526 1527 // update the ui: footer button 1528 $("#compact-button").toggleClass('selected',compact); 1529 1530 // update the ui: torrent list 1531 this.torrentRenderer = compact ? new TorrentRendererCompact() 1532 : new TorrentRendererFull(); 1533 this.refilter(true); 1534 }, 1535 1536 /*** 1537 **** 1538 **** Statistics 1539 **** 1540 ***/ 1541 1542 // turn the periodic ajax stats refresh on & off 1543 togglePeriodicStatsRefresh: function(enabled) { 1544 clearInterval(this.statsInterval); 1545 delete this.statsInterval; 1546 if (enabled) { 1547 var callback = $.proxy(this.loadDaemonStats,this), 1548 msec = 5000; 1549 this.statsInterval = setInterval(callback, msec); 1550 } 1551 }, 1552 1553 loadDaemonStats: function(async) { 1554 this.remote.loadDaemonStats(function(data) { 1555 this.updateStats(data['arguments']); 1556 }, this, async); 1557 }, 1558 1559 // Process new session stats from the server 1560 updateStats: function(stats) 1561 { 1562 var s, ratio, 1563 fmt = Transmission.fmt; 1564 1565 s = stats["current-stats"]; 1566 ratio = Math.ratio(s.uploadedBytes,s.downloadedBytes); 1567 $('#stats-session-uploaded').html(fmt.size(s.uploadedBytes)); 1568 $('#stats-session-downloaded').html(fmt.size(s.downloadedBytes)); 1569 $('#stats-session-ratio').html(fmt.ratioString(ratio)); 1570 $('#stats-session-duration').html(fmt.timeInterval(s.secondsActive)); 1571 1572 s = stats["cumulative-stats"]; 1573 ratio = Math.ratio(s.uploadedBytes,s.downloadedBytes); 1574 $('#stats-total-count').html(s.sessionCount + " times"); 1575 $('#stats-total-uploaded').html(fmt.size(s.uploadedBytes)); 1576 $('#stats-total-downloaded').html(fmt.size(s.downloadedBytes)); 1577 $('#stats-total-ratio').html(fmt.ratioString(ratio)); 1578 $('#stats-total-duration').html(fmt.timeInterval(s.secondsActive)); 1579 }, 1580 1581 1582 showStatsDialog: function() { 1583 this.loadDaemonStats(); 1584 this.hideMobileAddressbar(); 1585 this.togglePeriodicStatsRefresh(true); 1586 $('#stats-dialog').dialog({ 1587 close: $.proxy(this.onStatsDialogClosed,this), 1588 show: 'fade', 1589 hide: 'fade', 1590 title: 'Statistics' 1591 }); 1592 }, 1593 1594 onStatsDialogClosed: function() { 1595 this.hideMobileAddressbar(); 1596 this.togglePeriodicStatsRefresh(false); 1597 } 1598}; 1599