]> granicus.if.org Git - transmission/commitdiff
(trunk web) minor code sorting in Transmission.js: group the inspector functions...
authorJordan Lee <jordan@transmissionbt.com>
Fri, 26 Aug 2011 01:46:07 +0000 (01:46 +0000)
committerJordan Lee <jordan@transmissionbt.com>
Fri, 26 Aug 2011 01:46:07 +0000 (01:46 +0000)
web/javascript/transmission.js

index eb3f99c8e11c26f78ccdeb263d1fbe41e9cb0115..a8efeff2765675521d8e302c75dab756c0c0e46b 100644 (file)
@@ -721,21 +721,6 @@ Transmission.prototype =
                }
        },
 
-       inspectorTabClicked: function(ev, tab) {
-               if (iPhone) ev.stopPropagation();
-
-               // select this tab and deselect the others
-               $(tab).addClass('selected').siblings().removeClass('selected');
-
-               // show this tab and hide the others
-               $('#'+tab.id+'_container').show().siblings('.inspector_container').hide();
-
-               this.hideiPhoneAddressbar();
-               this.updatePeersLists();
-               this.updateTrackersLists();
-               this.updateFileList();
-       },
-
        filesSelectAllClicked: function() {
                var t = this._file_torrent;
                if (t)
@@ -1131,761 +1116,359 @@ Transmission.prototype =
                return false; // to prevent the event from bubbling up
        },
 
-       /*
-        * Update the inspector with the latest data for the selected torrents
-        */
-       updateInspector: function()
-       {
-               if (!this[Prefs._ShowInspector])
-                       return;
 
-               var torrents = this.getSelectedTorrents();
-               if (!torrents.length && iPhone) {
-                       this.setInspectorVisible(false);
-                       return;
-               }
+       onTorrentChanged: function(ev)
+       {
+               this.refilterSoon();
+       
+               // if this torrent is in the inspector, refresh the inspector
+               if (this[Prefs._ShowInspector])
+                       if (this.getSelectedTorrentIds().indexOf(ev.target.getId()) !== -1)
+                               this.updateInspector();
+       },
 
-               var creator = 'N/A';
-               var comment = 'N/A';
-               var download_dir = 'N/A';
-               var date_created = 'N/A';
-               var error = 'None';
-               var hash = 'N/A';
-               var have_public = false;
-               var have_private = false;
-               var name;
-               var sizeWhenDone = 0;
-               var sizeDone = 0;
-               var total_completed = 0;
-               var total_download = 0;
-               var total_download_peers = 0;
-               var total_download_speed = 0;
-               var total_availability = 0;
-               var total_size = 0;
-               var total_state = [ ];
-               var pieces = 'N/A';
-               var total_upload = 0;
-               var total_upload_peers = 0;
-               var total_upload_speed = 0;
-               var total_verified = 0;
-               var na = 'N/A';
-               var tab = this._inspector._info_tab;
+       updateFromTorrentGet: function(updates, removed_ids)
+       {
+               var new_ids = [];
 
-               $("#torrent_inspector_size, .inspector_row div").css('color', '#222');
+               for (var i=0, o; o=updates[i]; ++i) {
+                       var t;
+                       var id = o.id;
+                       if ((t = this._torrents[id]))
+                               t.refresh(o);
+                       else {
+                               t = this._torrents[id] = new Torrent(o);
+                               $(t).bind('dataChanged',function(ev) {tr.onTorrentChanged(ev);});
+                               new_ids.push(id);
+                       }
+               }
 
-               if (torrents.length == 0)
-               {
-                       setInnerHTML(tab.name, 'No Selection');
-                       setInnerHTML(tab.size, na);
-                       setInnerHTML(tab.pieces, na);
-                       setInnerHTML(tab.hash, na);
-                       setInnerHTML(tab.state, na);
-                       setInnerHTML(tab.download_speed, na);
-                       setInnerHTML(tab.upload_speed, na);
-                       setInnerHTML(tab.uploaded, na);
-                       setInnerHTML(tab.downloaded, na);
-                       setInnerHTML(tab.availability, na);
-                       setInnerHTML(tab.ratio, na);
-                       setInnerHTML(tab.have, na);
-                       setInnerHTML(tab.upload_to, na);
-                       setInnerHTML(tab.download_from, na);
-                       setInnerHTML(tab.secure, na);
-                       setInnerHTML(tab.creator_date, na);
-                       setInnerHTML(tab.progress, na);
-                       setInnerHTML(tab.comment, na);
-                       setInnerHTML(tab.creator, na);
-                       setInnerHTML(tab.download_dir, na);
-                       setInnerHTML(tab.error, na);
-                       this.updateFileList();
-                       this.updatePeersLists();
-                       this.updateTrackersLists();
-                       $("#torrent_inspector_size, .inspector_row > div:contains('N/A')").css('color', '#666');
-                       return;
+               if (new_ids.length) {
+                       var tr = this;
+                       this.remote.getTorrentInitial(new_ids, function(a,b){tr.updateFromTorrentGet(a,b);});
+                       this.refilterSoon();
                }
 
-               name = torrents.length == 1
-                       ? torrents[0].getName()
-                       : torrents.length+' Transfers Selected';
+               if (removed_ids) {
+                       this.deleteTorrents(removed_ids);
+                       this.refilterSoon();
+               }
+       },
 
-               if (torrents.length == 1)
-               {
-                       var text;
-                       var t = torrents[0];
-                       var err = t.getErrorMessage();
-                       if (err)
-                               error = err;
-                       if ((text = t.getComment()))
-                               comment = text;
-                       if ((text = t.getCreator()))
-                               creator = text;
-                       if ((text = t.getDownloadDir()))
-                               download_dir = text;
+       refreshTorrents: function(ids) {
+               if (!ids)
+                       ids = 'recently-active';
+               var tr = this;
+               this.remote.getTorrentStats(ids, function(a,b){tr.updateFromTorrentGet(a,b);});
+       },
+       initializeAllTorrents: function() {
+               var tr = this;
+               this.remote.getTorrentInitial(null, function(a,b){tr.updateFromTorrentGet(a,b);});
+       },
+       refreshMetadata: function(ids) {
+               var tr = this;
+               this.remote.getTorrentMetadata(ids, function(a,b){tr.updateFromTorrentGet(a,b);});
+       },
+       refreshInspectorTorrents: function(full) {
+               var tr = this;
+               var ids = tr.getSelectedTorrentIds();
+               if (ids.length > 0)
+                       this.remote.getTorrentDetails(ids, full, function(a,b){tr.updateFromTorrentGet(a,b);});
+       },
 
-                       hash = t.getHashString();
-                       pieces = [ t.getPieceCount(), 'pieces @', Transmission.fmt.mem(t.getPieceSize()) ].join(' ');
-                       date_created = Transmission.fmt.timestamp(t.getDateCreated());
-               }
+       onRowClicked: function(ev, row)
+       {
+               // Prevents click carrying to parent element
+               // which deselects all on click
+               ev.stopPropagation();
+               // but still hide the context menu if it is showing
+               $('#jqContextMenu').hide();
 
-               for (var i=0, t; t=torrents[i]; ++i) {
-                       var l = t.getLeftUntilDone();
-                       var d = t.getSizeWhenDone();
-                       sizeWhenDone         += d;
-                       sizeDone             += d - l;
-                       total_completed      += t.getHave();
-                       total_verified       += t.getHaveValid();
-                       total_size           += t.getTotalSize();
-                       total_upload         += t.getUploadedEver();
-                       total_download       += t.getDownloadedEver();
-                       total_upload_speed   += t.getUploadSpeed();
-                       total_download_speed += t.getDownloadSpeed();
-                       total_upload_peers   += t.getPeersGettingFromUs();
-                       total_download_peers += t.getPeersSendingToUs();
-                       total_availability   += sizeWhenDone - l + t.getDesiredAvailable();
+               // 'Apple' button emulation on PC :
+               // Need settable meta-key and ctrl-key variables for mac emulation
+               var meta_key = ev.metaKey;
+               if (ev.ctrlKey && navigator.appVersion.toLowerCase().indexOf("mac") == -1)
+                       meta_key = true;
 
-                       var s = t.getStateString();
-                       if (total_state.indexOf(s) == -1)
-                               total_state.push(s);
+               // Shift-Click - selects a range from the last-clicked row to this one
+               if (iPhone) {
+                       if (row.isSelected())
+                               this.setInspectorVisible(true);
+                       this.setSelectedRow(row);
 
-                       if (t.getPrivateFlag())
-                               have_private = true;
-                       else
-                               have_public = true;
-               }
+               } else if (ev.shiftKey) {
+                       this.selectRange(row);
+                       // Need to deselect any selected text
+                       window.focus();
 
-               var private_string = '';
-               var fmt = Transmission.fmt;
-               if (have_private && have_public) private_string = 'Mixed';
-               else if (have_private) private_string = 'Private Torrent';
-               else if (have_public) private_string = 'Public Torrent';
+               // Apple-Click, not selected
+               } else if (!row.isSelected() && meta_key) {
+                       this.selectRow(row);
 
-               setInnerHTML(tab.name, name);
-               setInnerHTML(tab.size, torrents.length ? fmt.size(total_size) : na);
-               setInnerHTML(tab.pieces, pieces);
-               setInnerHTML(tab.hash, hash);
-               setInnerHTML(tab.state, total_state.join('/'));
-               setInnerHTML(tab.download_speed, torrents.length ? fmt.speedBps(total_download_speed) : na);
-               setInnerHTML(tab.upload_speed, torrents.length ? fmt.speedBps(total_upload_speed) : na);
-               setInnerHTML(tab.uploaded, torrents.length ? fmt.size(total_upload) : na);
-               setInnerHTML(tab.downloaded, torrents.length ? fmt.size(total_download) : na);
-               setInnerHTML(tab.availability, torrents.length ? fmt.percentString(Math.ratio(total_availability*100, sizeWhenDone)) + '%' : na);
-               setInnerHTML(tab.ratio, torrents.length ? fmt.ratioString(Math.ratio(total_upload, total_download)) : na);
-               setInnerHTML(tab.have, torrents.length ? fmt.size(total_completed) + ' (' + fmt.size(total_verified) + ' verified)' : na);
-               setInnerHTML(tab.upload_to, torrents.length ? total_upload_peers : na);
-               setInnerHTML(tab.download_from, torrents.length ? total_download_peers : na);
-               setInnerHTML(tab.secure, private_string);
-               setInnerHTML(tab.creator_date, date_created);
-               setInnerHTML(tab.progress, torrents.length ? fmt.percentString(Math.ratio(sizeDone*100, sizeWhenDone)) + '%' : na);
-               setInnerHTML(tab.comment, comment);
-               setInnerHTML(tab.creator, creator);
-               setInnerHTML(tab.download_dir, download_dir);
-               setInnerHTML(tab.error, error);
+               // Regular Click, not selected
+               } else if (!row.isSelected()) {
+                       this.setSelectedRow(row);
 
-               this.updatePeersLists();
-               this.updateTrackersLists();
-               $(".inspector_row > div:contains('N/A')").css('color', '#666');
-               this.updateFileList();
-       },
+               // Apple-Click, selected
+               } else if (row.isSelected() && meta_key) {
+                       this.deselectRow(row);
 
-       onFileWantedToggled: function(row, want) {
-               var command = want ? 'files-wanted' : 'files-unwanted';
-               this.changeFileCommand(command, [ row ]);
-       },
-       onFilePriorityToggled: function(row, priority) {
-               var command;
-               switch(priority) {
-                       case -1: command = 'priority-low'; break;
-                       case  1: command = 'priority-high'; break;
-                       default: command = 'priority-normal'; break;
+               // Regular Click, selected
+               } else if (row.isSelected()) {
+                       this.setSelectedRow(row);
                }
-               this.changeFileCommand(command, [ row ]);
+
+               this._last_torrent_clicked = row.getTorrentId();
        },
-       clearFileList: function()
+
+       deleteTorrents: function(torrent_ids)
        {
-               $(this._inspector_file_list).empty();
-               delete this._file_torrent;
-               delete this._file_rows;
-       },
-       updateFileList: function()
-       {
-               if (!$(this._inspector_file_list).is(':visible'))
-                       return;
-
-               var sel = this.getSelectedTorrents();
-               if (sel.length !== 1) {
-                       this.clearFileList();
-                       return;
+               if (torrent_ids && torrent_ids.length)
+               {
+                       for (var i=0, id; id=torrent_ids[i]; ++i)
+                               delete this._torrents[id];
+                       this.refilter();
                }
+       },
 
-               var torrent = sel[0];
-               if (torrent === this._files_torrent)
-                       if(torrent.getFileCount() === (this._files ? this._files.length: 0))
-                               return;
+       updateStatusbar: function()
+       {
+               this.refreshFilterButton();
 
-               // build the file list
-               this.clearFileList();
-               this._file_torrent = torrent;
-               var n = torrent.getFileCount();
-               this._file_rows = [];
-               var fragment = document.createDocumentFragment();
-               var tr = this;
-               for (var i=0; i<n; ++i) {
-                       var row = this._file_rows[i] = new FileRow(torrent, i);
-                       fragment.appendChild(row.getElement());
-                       $(row).bind('wantedToggled',function(e,row,want) {tr.onFileWantedToggled(row,want);});
-                       $(row).bind('priorityToggled',function(e,row,priority) {tr.onFilePriorityToggled(row,priority);});
+               // up/down speed
+               var u=0, d=0;
+               var torrents = this.getAllTorrents();
+               for (var i=0, row; row=torrents[i]; ++i) {
+                       u += row.getUploadSpeed();
+                       d += row.getDownloadSpeed();
                }
-               this._inspector_file_list.appendChild(fragment);
+               setInnerHTML($('#statusbar #speed-up-label')[0], '&uarr; ' + Transmission.fmt.speedBps(u));
+               setInnerHTML($('#statusbar #speed-dn-label')[0], '&darr; ' + Transmission.fmt.speedBps(d));
        },
 
-       updatePeersLists: function()
+       /*
+        * Select a torrent file to upload
+        * FIXME
+        */
+       uploadTorrentFile: function(confirmed)
        {
-               if (!$(this._inspector_peers_list).is(':visible'))
-                       return;
-
-               var html = [ ];
-               var fmt = Transmission.fmt;
-               var torrents = this.getSelectedTorrents();
-
-               for (var k=0, torrent; torrent=torrents[k]; ++k) {
-                       var peers = torrent.getPeers();
-                       html.push('<div class="inspector_group">');
-                       if (torrents.length > 1) {
-                               html.push('<div class="inspector_torrent_label">', torrent.getName(), '</div>');
-                       }
-                       if (!peers || !peers.length) {
-                               html.push('<br></div>'); // firefox won't paint the top border if the div is empty
-                               continue;
+               // Display the upload dialog
+               if (! confirmed) {
+                               $('input#torrent_upload_file').attr('value', '');
+                               $('input#torrent_upload_url').attr('value', '');
+                               $('input#torrent_auto_start').attr('checked', $('#prefs_form #auto_start')[0].checked);
+                               $('#upload_container').show();
+                $('#torrent_upload_url').focus();
+                       if (!iPhone && Safari3) {
+                               setTimeout("$('div#upload_container div.dialog_window').css('top', '0px');",10);
                        }
-                       html.push('<table class="peer_list">',
-                                  '<tr class="inspector_peer_entry even">',
-                                  '<th class="encryptedCol"></th>',
-                                  '<th class="upCol">Up</th>',
-                                  '<th class="downCol">Down</th>',
-                                  '<th class="percentCol">%</th>',
-                                  '<th class="statusCol">Status</th>',
-                                  '<th class="addressCol">Address</th>',
-                                  '<th class="clientCol">Client</th>',
-                                  '</tr>');
-                       for (var i=0, peer; peer=peers[i]; ++i) {
-                               var parity = ((i+1) % 2 == 0 ? 'even' : 'odd');
-                               html.push('<tr class="inspector_peer_entry ', parity, '">',
-                                          '<td>', (peer.isEncrypted ? '<img src="images/graphics/lock_icon.png" alt="Encrypted"/>' : ''), '</td>',
-                                          '<td>', (peer.rateToPeer ? fmt.speedBps(peer.rateToPeer) : ''), '</td>',
-                                          '<td>', (peer.rateToClient ? fmt.speedBps(peer.rateToClient) : ''), '</td>',
-                                          '<td class="percentCol">', Math.floor(peer.progress*100), '%', '</td>',
-                                          '<td>', fmt.peerStatus(peer.flagStr), '</td>',
-                                          '<td>', peer.address, '</td>',
-                                          '<td class="clientCol">', peer.clientName, '</td>',
-                                          '</tr>');
+
+               // Submit the upload form
+               } else {
+                       var tr = this;
+                       var args = { };
+                       var paused = !$('#torrent_auto_start').is(':checked');
+                       if ('' != $('#torrent_upload_url').val()) {
+                               tr.remote.addTorrentByUrl($('#torrent_upload_url').val(), { paused: paused });
+                       } else {
+                               args.url = '../upload?paused=' + paused;
+                               args.type = 'POST';
+                               args.data = { 'X-Transmission-Session-Id' : tr.remote._token };
+                               args.dataType = 'xml';
+                               args.iframe = true;
+                               args.success = function() {
+                                       tr.refreshTorrents();
+                                       tr.togglePeriodicRefresh(true);
+                               };
+                               tr.togglePeriodicRefresh(false);
+                               $('#torrent_upload_form').ajaxSubmit(args);
                        }
-                       html.push('</table></div>');
                }
-
-               setInnerHTML(this._inspector_peers_list, html.join(''));
        },
 
-       updateTrackersLists: function() {
-               if (!$(this._inspector_trackers_list).is(':visible'))
-                       return;
+       removeSelectedTorrents: function() {
+               var torrents = this.getSelectedTorrents();
+               if (torrents.length)
+                       this.promptToRemoveTorrents(torrents);
+       },
 
-               var tr = this;
-               var html = [ ];
-               var na = 'N/A';
+       removeSelectedTorrentsAndData: function() {
                var torrents = this.getSelectedTorrents();
+               if (torrents.length)
+                       this.promptToRemoveTorrentsAndData(torrents);
+       },
 
-               // By building up the HTML as as string, then have the browser
-               // turn this into a DOM tree, this is a fast operation.
-               for (var i=0, torrent; torrent=torrents[i]; ++i)
+       promptToRemoveTorrents:function(torrents)
+       {
+               if (torrents.length == 1)
                {
-                       html.push ('<div class="inspector_group">');
+                       var torrent = torrents[0];
+                       var header = 'Remove ' + torrent.getName() + '?';
+                       var message = 'Once removed, continuing the transfer will require the torrent file. Are you sure you want to remove it?';
+                       dialog.confirm(header, message, 'Remove', 'transmission.removeTorrents', torrents);
+               }
+               else
+               {
+                       var header = 'Remove ' + torrents.length + ' transfers?';
+                       var message = 'Once removed, continuing the transfers will require the torrent files. Are you sure you want to remove them?';
+                       dialog.confirm(header, message, 'Remove', 'transmission.removeTorrents', torrents);
+               }
+       },
 
-                       if (torrents.length > 1)
-                               html.push('<div class="inspector_torrent_label">', torrent.getName(), '</div>');
+       promptToRemoveTorrentsAndData:function(torrents)
+       {
+               if (torrents.length == 1)
+               {
+                       var torrent = torrents[0],
+                               header = 'Remove ' + torrent.getName() + ' and delete data?',
+                               message = 'All data downloaded for this torrent will be deleted. Are you sure you want to remove it?';
+                       dialog.confirm(header, message, 'Remove', 'transmission.removeTorrentsAndData', torrents);
+               }
+               else
+               {
+                       var header = 'Remove ' + torrents.length + ' transfers and delete data?',
+                               message = 'All data downloaded for these torrents will be deleted. Are you sure you want to remove them?';
+                       dialog.confirm(header, message, 'Remove', 'transmission.removeTorrentsAndData', torrents);
+               }
+       },
 
-                       var tier = -1;
-                       var trackers = torrent.getTrackers();
-                       for (var j=0, tracker; tracker=trackers[j]; ++j)
-                       {
-                               if (tier != tracker.tier)
-                               {
-                                       if (tier !== -1) // close previous tier
-                                               html.push('</ul></div>');
-       
-                                       tier = tracker.tier;
+       removeTorrents: function(torrents) {
+               var torrent_ids = jQuery.map(torrents, function(t) { return t.getId(); });
+               var tr = this;
+               this.remote.removeTorrents(torrent_ids, function() { tr.refreshTorrents();});
+       },
 
-                                       html.push('<div class="inspector_group_label">',
-                                                 'Tier ', tier, '</div>',
-                                                 '<ul class="tier_list">');
-                               }
+       removeTorrentsAndData: function(torrents) {
+               this.remote.removeTorrentsAndData(torrents);
+       },
 
-                               var lastAnnounceStatusHash = tr.lastAnnounceStatus(tracker);
-                               var announceState = tr.announceState(tracker);
-                               var lastScrapeStatusHash = tr.lastScrapeStatus(tracker);
+       verifySelectedTorrents: function() {
+               this.verifyTorrents(this.getSelectedTorrents());
+       },
 
-                               // Display construction
-                               var parity = ((j+1) % 2 == 0 ? 'even' : 'odd');
-                               html.push('<li class="inspector_tracker_entry ', parity, '"><div class="tracker_host" title="', tracker.announce, '">',
-                                         tracker.host, '</div>',
-                                         '<div class="tracker_activity">',
-                                         '<div>', lastAnnounceStatusHash['label'], ': ', lastAnnounceStatusHash['value'], '</div>',
-                                         '<div>', announceState, '</div>',
-                                         '<div>', lastScrapeStatusHash['label'], ': ', lastScrapeStatusHash['value'], '</div>',
-                                         '</div><table class="tracker_stats">',
-                                         '<tr><th>Seeders:</th><td>', (tracker.seederCount > -1 ? tracker.seederCount : na), '</td></tr>',
-                                         '<tr><th>Leechers:</th><td>', (tracker.leecherCount > -1 ? tracker.leecherCount : na), '</td></tr>',
-                                         '<tr><th>Downloads:</th><td>', (tracker.downloadCount > -1 ? tracker.downloadCount : na), '</td></tr>',
-                                         '</table></li>');
-                       }
-                       if (tier !== -1) // close last tier
-                                       html.push('</ul></div>');
+       reannounceSelectedTorrents: function() {
+               this.reannounceTorrents(this.getSelectedTorrents());
+       },
 
-                       html.push('</div>'); // inspector_group
-               }
+       startSelectedTorrents: function(force) {
+               this.startTorrents(this.getSelectedTorrents(), force);
+       },
+       startAllTorrents: function() {
+               this.startTorrents(this.getAllTorrents(), false);
+       },
+       startTorrent: function(torrent) {
+               this.startTorrents([ torrent ], false);
+       },
+       startTorrents: function(torrents, force) {
+               var torrent_ids = jQuery.map(torrents, function(t) { return t.getId(); });
+               var tr = this;
+               this.remote.startTorrents(torrent_ids, force, function() { tr.refreshTorrents(torrent_ids); });
+       },
+       verifyTorrent: function(torrent) {
+               this.verifyTorrents([ torrent ]);
+       },
+       verifyTorrents: function(torrents) {
+               var tr = this;
+               var torrent_ids = jQuery.map(torrents, function(t) { return t.getId(); });
+               this.remote.verifyTorrents(torrent_ids, function() { tr.refreshTorrents(torrent_ids); });
+       },
 
-               setInnerHTML(this._inspector_trackers_list, html.join(''));
+       reannounceTorrent: function(torrent) {
+               this.reannounceTorrents([ torrent ]);
+       },
+       reannounceTorrents: function(torrents) {
+               var tr = this;
+               var torrent_ids = jQuery.map(torrents, function(t) { return t.getId(); });
+               this.remote.reannounceTorrents(torrent_ids, function() { tr.refreshTorrents(torrent_ids); });
        },
 
-       lastAnnounceStatus: function(tracker) {
-               var lastAnnounceLabel = 'Last Announce';
-               var lastAnnounce = [ 'N/A' ];
-               if (tracker.hasAnnounced) {
-                       var lastAnnounceTime = Transmission.fmt.timestamp(tracker.lastAnnounceTime);
-                       if (tracker.lastAnnounceSucceeded) {
-                               lastAnnounce = [ lastAnnounceTime, ' (got ',  Transmission.fmt.plural(tracker.lastAnnouncePeerCount, 'peer'), ')' ];
-                       } else {
-                               lastAnnounceLabel = 'Announce error';
-                               lastAnnounce = [ (tracker.lastAnnounceResult ? (tracker.lastAnnounceResult + ' - ') : ''), lastAnnounceTime ];
-                       }
-               }
-               return { 'label':lastAnnounceLabel, 'value':lastAnnounce.join('') };
-       },
-
-       announceState: function(tracker) {
-               var announceState = '';
-               switch (tracker.announceState) {
-                       case Torrent._TrackerActive:
-                               announceState = 'Announce in progress';
-                               break;
-                       case Torrent._TrackerWaiting:
-                               var timeUntilAnnounce = tracker.nextAnnounceTime - ((new Date()).getTime() / 1000);
-                               if (timeUntilAnnounce < 0) {
-                                       timeUntilAnnounce = 0;
-                               }
-                               announceState = 'Next announce in ' + Transmission.fmt.timeInterval(timeUntilAnnounce);
-                               break;
-                       case Torrent._TrackerQueued:
-                               announceState = 'Announce is queued';
-                               break;
-                       case Torrent._TrackerInactive:
-                               announceState = tracker.isBackup ?
-                                       'Tracker will be used as a backup' :
-                                       'Announce not scheduled';
-                               break;
-                       default:
-                               announceState = 'unknown announce state: ' + tracker.announceState;
-               }
-               return announceState;
+       stopSelectedTorrents: function() {
+               this.stopTorrents(this.getSelectedTorrents());
        },
-
-       lastScrapeStatus: function(tracker) {
-               var lastScrapeLabel = 'Last Scrape';
-               var lastScrape = 'N/A';
-               if (tracker.hasScraped) {
-                       var lastScrapeTime = Transmission.fmt.timestamp(tracker.lastScrapeTime);
-                       if (tracker.lastScrapeSucceeded) {
-                               lastScrape = lastScrapeTime;
-                       } else {
-                               lastScrapeLabel = 'Scrape error';
-                               lastScrape = (tracker.lastScrapeResult ? tracker.lastScrapeResult + ' - ' : '') + lastScrapeTime;
-                       }
-               }
-               return {'label':lastScrapeLabel, 'value':lastScrape};
+       stopAllTorrents: function() {
+               this.stopTorrents(this.getAllTorrents());
        },
-
-       toggleInspector: function()
-       {
-               this.setInspectorVisible(!this[Prefs._ShowInspector]);
+       stopTorrent: function(torrent) {
+               this.stopTorrents([ torrent ]);
        },
-       setInspectorVisible: function(visible)
-       {
-               // we collect extra stats on torrents when they're in the inspector...
-               clearInterval(this._periodic_inspector_refresh);
-               delete this._periodic_inspector_refresh;
-               if (visible) {
-                       var tr = this;
-                       this._periodic_inspector_refresh = setInterval(function() {tr.refreshInspectorTorrents(false);},2000);
-                       this.refreshInspectorTorrents(true);
-               }
-
-               // update the ui widgetry
-               $('#torrent_inspector').toggle(visible);
-               if (iPhone) {
-                       $('body').toggleClass('inspector_showing',visible);
-                       $('#inspector_close').toggle(visible);
-                       this.hideiPhoneAddressbar();
-               } else {
-                       var w = visible ? $('#torrent_inspector').width() + 1 + 'px' : '0px';
-                       $('#torrent_container')[0].style.right = w;
-               }
-
-               setInnerHTML($('ul li#context_toggle_inspector')[0], (visible?'Hide':'Show')+' Inspector');
-               this.setPref(Prefs._ShowInspector, visible);
-               if (visible)
-                       this.updateInspector();
+       stopTorrents: function(torrents) {
+               var torrent_ids = jQuery.map(torrents, function(t) { return t.getId(); });
+               var tr = this;
+               this.remote.stopTorrents(torrent_ids,   function() { tr.refreshTorrents(torrent_ids);});
        },
-
-       onTorrentChanged: function(ev)
-       {
-               this.refilterSoon();
-       
-               // if this torrent is in the inspector, refresh the inspector
-               if (this[Prefs._ShowInspector])
-                       if (this.getSelectedTorrentIds().indexOf(ev.target.getId()) !== -1)
-                               this.updateInspector();
+       changeFileCommand: function(command, rows) {
+               this.remote.changeFileCommand(command, rows);
        },
 
-       updateFromTorrentGet: function(updates, removed_ids)
-       {
-               var new_ids = [];
-
-               for (var i=0, o; o=updates[i]; ++i) {
-                       var t;
-                       var id = o.id;
-                       if ((t = this._torrents[id]))
-                               t.refresh(o);
-                       else {
-                               t = this._torrents[id] = new Torrent(o);
-                               $(t).bind('dataChanged',function(ev) {tr.onTorrentChanged(ev);});
-                               new_ids.push(id);
+       hideiPhoneAddressbar: function(timeInSeconds) {
+               if (iPhone) {
+                       var delayLength = timeInSeconds ? timeInSeconds*1000 : 150;
+                       // not currently supported on iPhone
+                       if (/*document.body.scrollTop!=1 && */scroll_timeout==null) {
+                               var tr = this;
+                               scroll_timeout = setTimeout(function() {tr.doToolbarHide();}, delayLength);
                        }
                }
-
-               if (new_ids.length) {
-                       var tr = this;
-                       this.remote.getTorrentInitial(new_ids, function(a,b){tr.updateFromTorrentGet(a,b);});
-                       this.refilterSoon();
-               }
-
-               if (removed_ids) {
-                       this.deleteTorrents(removed_ids);
-                       this.refilterSoon();
-               }
+       },
+       doToolbarHide: function() {
+               window.scrollTo(0,1);
+               scroll_timeout=null;
        },
 
-       refreshTorrents: function(ids) {
-               if (!ids)
-                       ids = 'recently-active';
+       // Queue
+       moveTop: function() {
                var tr = this;
-               this.remote.getTorrentStats(ids, function(a,b){tr.updateFromTorrentGet(a,b);});
+               var torrent_ids = this.getSelectedTorrentIds();
+               this.remote.moveTorrentsToTop(torrent_ids, function() { tr.refreshTorrents(torrent_ids);});
        },
-       initializeAllTorrents: function() {
+       moveUp: function() {
                var tr = this;
-               this.remote.getTorrentInitial(null, function(a,b){tr.updateFromTorrentGet(a,b);});
+               var torrent_ids = this.getSelectedTorrentIds();
+               this.remote.moveTorrentsUp(torrent_ids, function() { tr.refreshTorrents(torrent_ids);});
        },
-       refreshMetadata: function(ids) {
+       moveDown: function() {
                var tr = this;
-               this.remote.getTorrentMetadata(ids, function(a,b){tr.updateFromTorrentGet(a,b);});
+               var torrent_ids = this.getSelectedTorrentIds();
+               this.remote.moveTorrentsDown(torrent_ids, function() { tr.refreshTorrents(torrent_ids);});
        },
-       refreshInspectorTorrents: function(full) {
+       moveBottom: function() {
                var tr = this;
-               var ids = tr.getSelectedTorrentIds();
-               if (ids.length > 0)
-                       this.remote.getTorrentDetails(ids, full, function(a,b){tr.updateFromTorrentGet(a,b);});
+               var torrent_ids = this.getSelectedTorrentIds();
+               this.remote.moveTorrentsToBottom(torrent_ids, function() { tr.refreshTorrents(torrent_ids);});
        },
 
-       onRowClicked: function(ev, row)
-       {
-               // Prevents click carrying to parent element
-               // which deselects all on click
-               ev.stopPropagation();
-               // but still hide the context menu if it is showing
-               $('#jqContextMenu').hide();
-
-               // 'Apple' button emulation on PC :
-               // Need settable meta-key and ctrl-key variables for mac emulation
-               var meta_key = ev.metaKey;
-               if (ev.ctrlKey && navigator.appVersion.toLowerCase().indexOf("mac") == -1)
-                       meta_key = true;
-
-               // Shift-Click - selects a range from the last-clicked row to this one
-               if (iPhone) {
-                       if (row.isSelected())
-                               this.setInspectorVisible(true);
-                       this.setSelectedRow(row);
-
-               } else if (ev.shiftKey) {
-                       this.selectRange(row);
-                       // Need to deselect any selected text
-                       window.focus();
-
-               // Apple-Click, not selected
-               } else if (!row.isSelected() && meta_key) {
-                       this.selectRow(row);
-
-               // Regular Click, not selected
-               } else if (!row.isSelected()) {
-                       this.setSelectedRow(row);
 
-               // Apple-Click, selected
-               } else if (row.isSelected() && meta_key) {
-                       this.deselectRow(row);
+       /***
+       ****
+       ***/
 
-               // Regular Click, selected
-               } else if (row.isSelected()) {
-                       this.setSelectedRow(row);
-               }
+       onToggleRunningClicked: function(ev)
+       {
+               var torrent = ev.data.r.getTorrent();
 
-               this._last_torrent_clicked = row.getTorrentId();
+               if (torrent.isStopped())
+                       this.startTorrent(torrent);
+               else
+                       this.stopTorrent(torrent);
        },
 
-       deleteTorrents: function(torrent_ids)
+       setEnabled: function(key, flag)
        {
-               if (torrent_ids && torrent_ids.length)
-               {
-                       for (var i=0, id; id=torrent_ids[i]; ++i)
-                               delete this._torrents[id];
-                       this.refilter();
-               }
+               $(key).toggleClass('disabled', !flag);
        },
 
-       updateStatusbar: function()
+       updateButtonStates: function()
        {
-               this.refreshFilterButton();
+               var showing_dialog = new RegExp("(prefs_showing|dialog_showing|open_showing)").test(document.body.className);
+               this._toolbar_buttons.toggleClass('disabled', showing_dialog);
 
-               // up/down speed
-               var u=0, d=0;
-               var torrents = this.getAllTorrents();
-               for (var i=0, row; row=torrents[i]; ++i) {
-                       u += row.getUploadSpeed();
-                       d += row.getDownloadSpeed();
-               }
-               setInnerHTML($('#statusbar #speed-up-label')[0], '&uarr; ' + Transmission.fmt.speedBps(u));
-               setInnerHTML($('#statusbar #speed-dn-label')[0], '&darr; ' + Transmission.fmt.speedBps(d));
-       },
-
-       /*
-        * Select a torrent file to upload
-        * FIXME
-        */
-       uploadTorrentFile: function(confirmed)
-       {
-               // Display the upload dialog
-               if (! confirmed) {
-                               $('input#torrent_upload_file').attr('value', '');
-                               $('input#torrent_upload_url').attr('value', '');
-                               $('input#torrent_auto_start').attr('checked', $('#prefs_form #auto_start')[0].checked);
-                               $('#upload_container').show();
-                $('#torrent_upload_url').focus();
-                       if (!iPhone && Safari3) {
-                               setTimeout("$('div#upload_container div.dialog_window').css('top', '0px');",10);
-                       }
-
-               // Submit the upload form
-               } else {
-                       var tr = this;
-                       var args = { };
-                       var paused = !$('#torrent_auto_start').is(':checked');
-                       if ('' != $('#torrent_upload_url').val()) {
-                               tr.remote.addTorrentByUrl($('#torrent_upload_url').val(), { paused: paused });
-                       } else {
-                               args.url = '../upload?paused=' + paused;
-                               args.type = 'POST';
-                               args.data = { 'X-Transmission-Session-Id' : tr.remote._token };
-                               args.dataType = 'xml';
-                               args.iframe = true;
-                               args.success = function() {
-                                       tr.refreshTorrents();
-                                       tr.togglePeriodicRefresh(true);
-                               };
-                               tr.togglePeriodicRefresh(false);
-                               $('#torrent_upload_form').ajaxSubmit(args);
-                       }
-               }
-       },
-
-       removeSelectedTorrents: function() {
-               var torrents = this.getSelectedTorrents();
-               if (torrents.length)
-                       this.promptToRemoveTorrents(torrents);
-       },
-
-       removeSelectedTorrentsAndData: function() {
-               var torrents = this.getSelectedTorrents();
-               if (torrents.length)
-                       this.promptToRemoveTorrentsAndData(torrents);
-       },
-
-       promptToRemoveTorrents:function(torrents)
-       {
-               if (torrents.length == 1)
-               {
-                       var torrent = torrents[0];
-                       var header = 'Remove ' + torrent.getName() + '?';
-                       var message = 'Once removed, continuing the transfer will require the torrent file. Are you sure you want to remove it?';
-                       dialog.confirm(header, message, 'Remove', 'transmission.removeTorrents', torrents);
-               }
-               else
-               {
-                       var header = 'Remove ' + torrents.length + ' transfers?';
-                       var message = 'Once removed, continuing the transfers will require the torrent files. Are you sure you want to remove them?';
-                       dialog.confirm(header, message, 'Remove', 'transmission.removeTorrents', torrents);
-               }
-       },
-
-       promptToRemoveTorrentsAndData:function(torrents)
-       {
-               if (torrents.length == 1)
-               {
-                       var torrent = torrents[0],
-                               header = 'Remove ' + torrent.getName() + ' and delete data?',
-                               message = 'All data downloaded for this torrent will be deleted. Are you sure you want to remove it?';
-                       dialog.confirm(header, message, 'Remove', 'transmission.removeTorrentsAndData', torrents);
-               }
-               else
-               {
-                       var header = 'Remove ' + torrents.length + ' transfers and delete data?',
-                               message = 'All data downloaded for these torrents will be deleted. Are you sure you want to remove them?';
-                       dialog.confirm(header, message, 'Remove', 'transmission.removeTorrentsAndData', torrents);
-               }
-       },
-
-       removeTorrents: function(torrents) {
-               var torrent_ids = jQuery.map(torrents, function(t) { return t.getId(); });
-               var tr = this;
-               this.remote.removeTorrents(torrent_ids, function() { tr.refreshTorrents();});
-       },
-
-       removeTorrentsAndData: function(torrents) {
-               this.remote.removeTorrentsAndData(torrents);
-       },
-
-       verifySelectedTorrents: function() {
-               this.verifyTorrents(this.getSelectedTorrents());
-       },
-
-       reannounceSelectedTorrents: function() {
-               this.reannounceTorrents(this.getSelectedTorrents());
-       },
-
-       startSelectedTorrents: function(force) {
-               this.startTorrents(this.getSelectedTorrents(), force);
-       },
-       startAllTorrents: function() {
-               this.startTorrents(this.getAllTorrents(), false);
-       },
-       startTorrent: function(torrent) {
-               this.startTorrents([ torrent ], false);
-       },
-       startTorrents: function(torrents, force) {
-               var torrent_ids = jQuery.map(torrents, function(t) { return t.getId(); });
-               var tr = this;
-               this.remote.startTorrents(torrent_ids, force, function() { tr.refreshTorrents(torrent_ids); });
-       },
-       verifyTorrent: function(torrent) {
-               this.verifyTorrents([ torrent ]);
-       },
-       verifyTorrents: function(torrents) {
-               var tr = this;
-               var torrent_ids = jQuery.map(torrents, function(t) { return t.getId(); });
-               this.remote.verifyTorrents(torrent_ids, function() { tr.refreshTorrents(torrent_ids); });
-       },
-
-       reannounceTorrent: function(torrent) {
-               this.reannounceTorrents([ torrent ]);
-       },
-       reannounceTorrents: function(torrents) {
-               var tr = this;
-               var torrent_ids = jQuery.map(torrents, function(t) { return t.getId(); });
-               this.remote.reannounceTorrents(torrent_ids, function() { tr.refreshTorrents(torrent_ids); });
-       },
-
-       stopSelectedTorrents: function() {
-               this.stopTorrents(this.getSelectedTorrents());
-       },
-       stopAllTorrents: function() {
-               this.stopTorrents(this.getAllTorrents());
-       },
-       stopTorrent: function(torrent) {
-               this.stopTorrents([ torrent ]);
-       },
-       stopTorrents: function(torrents) {
-               var torrent_ids = jQuery.map(torrents, function(t) { return t.getId(); });
-               var tr = this;
-               this.remote.stopTorrents(torrent_ids,   function() { tr.refreshTorrents(torrent_ids);});
-       },
-       changeFileCommand: function(command, rows) {
-               this.remote.changeFileCommand(command, rows);
-       },
-
-       hideiPhoneAddressbar: function(timeInSeconds) {
-               if (iPhone) {
-                       var delayLength = timeInSeconds ? timeInSeconds*1000 : 150;
-                       // not currently supported on iPhone
-                       if (/*document.body.scrollTop!=1 && */scroll_timeout==null) {
-                               var tr = this;
-                               scroll_timeout = setTimeout(function() {tr.doToolbarHide();}, delayLength);
-                       }
-               }
-       },
-       doToolbarHide: function() {
-               window.scrollTo(0,1);
-               scroll_timeout=null;
-       },
-
-       // Queue
-       moveTop: function() {
-               var tr = this;
-               var torrent_ids = this.getSelectedTorrentIds();
-               this.remote.moveTorrentsToTop(torrent_ids, function() { tr.refreshTorrents(torrent_ids);});
-       },
-       moveUp: function() {
-               var tr = this;
-               var torrent_ids = this.getSelectedTorrentIds();
-               this.remote.moveTorrentsUp(torrent_ids, function() { tr.refreshTorrents(torrent_ids);});
-       },
-       moveDown: function() {
-               var tr = this;
-               var torrent_ids = this.getSelectedTorrentIds();
-               this.remote.moveTorrentsDown(torrent_ids, function() { tr.refreshTorrents(torrent_ids);});
-       },
-       moveBottom: function() {
-               var tr = this;
-               var torrent_ids = this.getSelectedTorrentIds();
-               this.remote.moveTorrentsToBottom(torrent_ids, function() { tr.refreshTorrents(torrent_ids);});
-       },
-
-
-       /***
-       ****
-       ***/
-
-       onToggleRunningClicked: function(ev)
-       {
-               var torrent = ev.data.r.getTorrent();
-
-               if (torrent.isStopped())
-                       this.startTorrent(torrent);
-               else
-                       this.stopTorrent(torrent);
-       },
-
-       setEnabled: function(key, flag)
-       {
-               $(key).toggleClass('disabled', !flag);
-       },
-
-       updateButtonStates: function()
-       {
-               var showing_dialog = new RegExp("(prefs_showing|dialog_showing|open_showing)").test(document.body.className);
-               this._toolbar_buttons.toggleClass('disabled', showing_dialog);
-
-               if (!showing_dialog)
-               {
-                       var haveSelection = false;
-                       var haveActive = false;
-                       var haveActiveSelection = false;
-                       var havePaused = false;
-                       var havePausedSelection = false;
+               if (!showing_dialog)
+               {
+                       var haveSelection = false;
+                       var haveActive = false;
+                       var haveActiveSelection = false;
+                       var havePaused = false;
+                       var havePausedSelection = false;
 
                        for (var i=0, row; row=this._rows[i]; ++i) {
                                var isStopped = row.getTorrent().isStopped();
@@ -1912,6 +1495,430 @@ Transmission.prototype =
                }
        },
 
+       /****
+       *****
+       *****  INSPECTOR
+       *****
+       ****/
+
+       inspectorTabClicked: function(ev, tab)
+       {
+               if (iPhone) ev.stopPropagation();
+
+               // select this tab and deselect the others
+               $(tab).addClass('selected').siblings().removeClass('selected');
+
+               // show this tab and hide the others
+               $('#'+tab.id+'_container').show().siblings('.inspector_container').hide();
+
+               this.hideiPhoneAddressbar();
+               this.updatePeersLists();
+               this.updateTrackersLists();
+               this.updateFileList();
+       },
+       /*
+        * Update the inspector with the latest data for the selected torrents
+        */
+       updateInspector: function()
+       {
+               if (!this[Prefs._ShowInspector])
+                       return;
+
+               var torrents = this.getSelectedTorrents();
+               if (!torrents.length && iPhone) {
+                       this.setInspectorVisible(false);
+                       return;
+               }
+
+               var creator = 'N/A';
+               var comment = 'N/A';
+               var download_dir = 'N/A';
+               var date_created = 'N/A';
+               var error = 'None';
+               var hash = 'N/A';
+               var have_public = false;
+               var have_private = false;
+               var name;
+               var sizeWhenDone = 0;
+               var sizeDone = 0;
+               var total_completed = 0;
+               var total_download = 0;
+               var total_download_peers = 0;
+               var total_download_speed = 0;
+               var total_availability = 0;
+               var total_size = 0;
+               var total_state = [ ];
+               var pieces = 'N/A';
+               var total_upload = 0;
+               var total_upload_peers = 0;
+               var total_upload_speed = 0;
+               var total_verified = 0;
+               var na = 'N/A';
+               var tab = this._inspector._info_tab;
+
+               $("#torrent_inspector_size, .inspector_row div").css('color', '#222');
+
+               if (torrents.length == 0)
+               {
+                       setInnerHTML(tab.name, 'No Selection');
+                       setInnerHTML(tab.size, na);
+                       setInnerHTML(tab.pieces, na);
+                       setInnerHTML(tab.hash, na);
+                       setInnerHTML(tab.state, na);
+                       setInnerHTML(tab.download_speed, na);
+                       setInnerHTML(tab.upload_speed, na);
+                       setInnerHTML(tab.uploaded, na);
+                       setInnerHTML(tab.downloaded, na);
+                       setInnerHTML(tab.availability, na);
+                       setInnerHTML(tab.ratio, na);
+                       setInnerHTML(tab.have, na);
+                       setInnerHTML(tab.upload_to, na);
+                       setInnerHTML(tab.download_from, na);
+                       setInnerHTML(tab.secure, na);
+                       setInnerHTML(tab.creator_date, na);
+                       setInnerHTML(tab.progress, na);
+                       setInnerHTML(tab.comment, na);
+                       setInnerHTML(tab.creator, na);
+                       setInnerHTML(tab.download_dir, na);
+                       setInnerHTML(tab.error, na);
+                       this.updateFileList();
+                       this.updatePeersLists();
+                       this.updateTrackersLists();
+                       $("#torrent_inspector_size, .inspector_row > div:contains('N/A')").css('color', '#666');
+                       return;
+               }
+
+               name = torrents.length == 1
+                       ? torrents[0].getName()
+                       : torrents.length+' Transfers Selected';
+
+               if (torrents.length == 1)
+               {
+                       var text;
+                       var t = torrents[0];
+                       var err = t.getErrorMessage();
+                       if (err)
+                               error = err;
+                       if ((text = t.getComment()))
+                               comment = text;
+                       if ((text = t.getCreator()))
+                               creator = text;
+                       if ((text = t.getDownloadDir()))
+                               download_dir = text;
+
+                       hash = t.getHashString();
+                       pieces = [ t.getPieceCount(), 'pieces @', Transmission.fmt.mem(t.getPieceSize()) ].join(' ');
+                       date_created = Transmission.fmt.timestamp(t.getDateCreated());
+               }
+
+               for (var i=0, t; t=torrents[i]; ++i) {
+                       var l = t.getLeftUntilDone();
+                       var d = t.getSizeWhenDone();
+                       sizeWhenDone         += d;
+                       sizeDone             += d - l;
+                       total_completed      += t.getHave();
+                       total_verified       += t.getHaveValid();
+                       total_size           += t.getTotalSize();
+                       total_upload         += t.getUploadedEver();
+                       total_download       += t.getDownloadedEver();
+                       total_upload_speed   += t.getUploadSpeed();
+                       total_download_speed += t.getDownloadSpeed();
+                       total_upload_peers   += t.getPeersGettingFromUs();
+                       total_download_peers += t.getPeersSendingToUs();
+                       total_availability   += sizeWhenDone - l + t.getDesiredAvailable();
+
+                       var s = t.getStateString();
+                       if (total_state.indexOf(s) == -1)
+                               total_state.push(s);
+
+                       if (t.getPrivateFlag())
+                               have_private = true;
+                       else
+                               have_public = true;
+               }
+
+               var private_string = '';
+               var fmt = Transmission.fmt;
+               if (have_private && have_public) private_string = 'Mixed';
+               else if (have_private) private_string = 'Private Torrent';
+               else if (have_public) private_string = 'Public Torrent';
+
+               setInnerHTML(tab.name, name);
+               setInnerHTML(tab.size, torrents.length ? fmt.size(total_size) : na);
+               setInnerHTML(tab.pieces, pieces);
+               setInnerHTML(tab.hash, hash);
+               setInnerHTML(tab.state, total_state.join('/'));
+               setInnerHTML(tab.download_speed, torrents.length ? fmt.speedBps(total_download_speed) : na);
+               setInnerHTML(tab.upload_speed, torrents.length ? fmt.speedBps(total_upload_speed) : na);
+               setInnerHTML(tab.uploaded, torrents.length ? fmt.size(total_upload) : na);
+               setInnerHTML(tab.downloaded, torrents.length ? fmt.size(total_download) : na);
+               setInnerHTML(tab.availability, torrents.length ? fmt.percentString(Math.ratio(total_availability*100, sizeWhenDone)) + '%' : na);
+               setInnerHTML(tab.ratio, torrents.length ? fmt.ratioString(Math.ratio(total_upload, total_download)) : na);
+               setInnerHTML(tab.have, torrents.length ? fmt.size(total_completed) + ' (' + fmt.size(total_verified) + ' verified)' : na);
+               setInnerHTML(tab.upload_to, torrents.length ? total_upload_peers : na);
+               setInnerHTML(tab.download_from, torrents.length ? total_download_peers : na);
+               setInnerHTML(tab.secure, private_string);
+               setInnerHTML(tab.creator_date, date_created);
+               setInnerHTML(tab.progress, torrents.length ? fmt.percentString(Math.ratio(sizeDone*100, sizeWhenDone)) + '%' : na);
+               setInnerHTML(tab.comment, comment);
+               setInnerHTML(tab.creator, creator);
+               setInnerHTML(tab.download_dir, download_dir);
+               setInnerHTML(tab.error, error);
+
+               this.updatePeersLists();
+               this.updateTrackersLists();
+               $(".inspector_row > div:contains('N/A')").css('color', '#666');
+               this.updateFileList();
+       },
+
+       onFileWantedToggled: function(row, want) {
+               var command = want ? 'files-wanted' : 'files-unwanted';
+               this.changeFileCommand(command, [ row ]);
+       },
+       onFilePriorityToggled: function(row, priority) {
+               var command;
+               switch(priority) {
+                       case -1: command = 'priority-low'; break;
+                       case  1: command = 'priority-high'; break;
+                       default: command = 'priority-normal'; break;
+               }
+               this.changeFileCommand(command, [ row ]);
+       },
+       clearFileList: function()
+       {
+               $(this._inspector_file_list).empty();
+               delete this._file_torrent;
+               delete this._file_rows;
+       },
+       updateFileList: function()
+       {
+               if (!$(this._inspector_file_list).is(':visible'))
+                       return;
+
+               var sel = this.getSelectedTorrents();
+               if (sel.length !== 1) {
+                       this.clearFileList();
+                       return;
+               }
+
+               var torrent = sel[0];
+               if (torrent === this._files_torrent)
+                       if(torrent.getFileCount() === (this._files ? this._files.length: 0))
+                               return;
+
+               // build the file list
+               this.clearFileList();
+               this._file_torrent = torrent;
+               var n = torrent.getFileCount();
+               this._file_rows = [];
+               var fragment = document.createDocumentFragment();
+               var tr = this;
+               for (var i=0; i<n; ++i) {
+                       var row = this._file_rows[i] = new FileRow(torrent, i);
+                       fragment.appendChild(row.getElement());
+                       $(row).bind('wantedToggled',function(e,row,want) {tr.onFileWantedToggled(row,want);});
+                       $(row).bind('priorityToggled',function(e,row,priority) {tr.onFilePriorityToggled(row,priority);});
+               }
+               this._inspector_file_list.appendChild(fragment);
+       },
+
+       updatePeersLists: function()
+       {
+               if (!$(this._inspector_peers_list).is(':visible'))
+                       return;
+
+               var html = [ ];
+               var fmt = Transmission.fmt;
+               var torrents = this.getSelectedTorrents();
+
+               for (var k=0, torrent; torrent=torrents[k]; ++k) {
+                       var peers = torrent.getPeers();
+                       html.push('<div class="inspector_group">');
+                       if (torrents.length > 1) {
+                               html.push('<div class="inspector_torrent_label">', torrent.getName(), '</div>');
+                       }
+                       if (!peers || !peers.length) {
+                               html.push('<br></div>'); // firefox won't paint the top border if the div is empty
+                               continue;
+                       }
+                       html.push('<table class="peer_list">',
+                                  '<tr class="inspector_peer_entry even">',
+                                  '<th class="encryptedCol"></th>',
+                                  '<th class="upCol">Up</th>',
+                                  '<th class="downCol">Down</th>',
+                                  '<th class="percentCol">%</th>',
+                                  '<th class="statusCol">Status</th>',
+                                  '<th class="addressCol">Address</th>',
+                                  '<th class="clientCol">Client</th>',
+                                  '</tr>');
+                       for (var i=0, peer; peer=peers[i]; ++i) {
+                               var parity = ((i+1) % 2 == 0 ? 'even' : 'odd');
+                               html.push('<tr class="inspector_peer_entry ', parity, '">',
+                                          '<td>', (peer.isEncrypted ? '<img src="images/graphics/lock_icon.png" alt="Encrypted"/>' : ''), '</td>',
+                                          '<td>', (peer.rateToPeer ? fmt.speedBps(peer.rateToPeer) : ''), '</td>',
+                                          '<td>', (peer.rateToClient ? fmt.speedBps(peer.rateToClient) : ''), '</td>',
+                                          '<td class="percentCol">', Math.floor(peer.progress*100), '%', '</td>',
+                                          '<td>', fmt.peerStatus(peer.flagStr), '</td>',
+                                          '<td>', peer.address, '</td>',
+                                          '<td class="clientCol">', peer.clientName, '</td>',
+                                          '</tr>');
+                       }
+                       html.push('</table></div>');
+               }
+
+               setInnerHTML(this._inspector_peers_list, html.join(''));
+       },
+
+       updateTrackersLists: function() {
+               if (!$(this._inspector_trackers_list).is(':visible'))
+                       return;
+
+               var tr = this;
+               var html = [ ];
+               var na = 'N/A';
+               var torrents = this.getSelectedTorrents();
+
+               // By building up the HTML as as string, then have the browser
+               // turn this into a DOM tree, this is a fast operation.
+               for (var i=0, torrent; torrent=torrents[i]; ++i)
+               {
+                       html.push ('<div class="inspector_group">');
+
+                       if (torrents.length > 1)
+                               html.push('<div class="inspector_torrent_label">', torrent.getName(), '</div>');
+
+                       var tier = -1;
+                       var trackers = torrent.getTrackers();
+                       for (var j=0, tracker; tracker=trackers[j]; ++j)
+                       {
+                               if (tier != tracker.tier)
+                               {
+                                       if (tier !== -1) // close previous tier
+                                               html.push('</ul></div>');
+       
+                                       tier = tracker.tier;
+
+                                       html.push('<div class="inspector_group_label">',
+                                                 'Tier ', tier, '</div>',
+                                                 '<ul class="tier_list">');
+                               }
+
+                               var lastAnnounceStatusHash = tr.lastAnnounceStatus(tracker);
+                               var announceState = tr.announceState(tracker);
+                               var lastScrapeStatusHash = tr.lastScrapeStatus(tracker);
+
+                               // Display construction
+                               var parity = ((j+1) % 2 == 0 ? 'even' : 'odd');
+                               html.push('<li class="inspector_tracker_entry ', parity, '"><div class="tracker_host" title="', tracker.announce, '">',
+                                         tracker.host, '</div>',
+                                         '<div class="tracker_activity">',
+                                         '<div>', lastAnnounceStatusHash['label'], ': ', lastAnnounceStatusHash['value'], '</div>',
+                                         '<div>', announceState, '</div>',
+                                         '<div>', lastScrapeStatusHash['label'], ': ', lastScrapeStatusHash['value'], '</div>',
+                                         '</div><table class="tracker_stats">',
+                                         '<tr><th>Seeders:</th><td>', (tracker.seederCount > -1 ? tracker.seederCount : na), '</td></tr>',
+                                         '<tr><th>Leechers:</th><td>', (tracker.leecherCount > -1 ? tracker.leecherCount : na), '</td></tr>',
+                                         '<tr><th>Downloads:</th><td>', (tracker.downloadCount > -1 ? tracker.downloadCount : na), '</td></tr>',
+                                         '</table></li>');
+                       }
+                       if (tier !== -1) // close last tier
+                                       html.push('</ul></div>');
+
+                       html.push('</div>'); // inspector_group
+               }
+
+               setInnerHTML(this._inspector_trackers_list, html.join(''));
+       },
+
+       lastAnnounceStatus: function(tracker) {
+               var lastAnnounceLabel = 'Last Announce';
+               var lastAnnounce = [ 'N/A' ];
+               if (tracker.hasAnnounced) {
+                       var lastAnnounceTime = Transmission.fmt.timestamp(tracker.lastAnnounceTime);
+                       if (tracker.lastAnnounceSucceeded) {
+                               lastAnnounce = [ lastAnnounceTime, ' (got ',  Transmission.fmt.plural(tracker.lastAnnouncePeerCount, 'peer'), ')' ];
+                       } else {
+                               lastAnnounceLabel = 'Announce error';
+                               lastAnnounce = [ (tracker.lastAnnounceResult ? (tracker.lastAnnounceResult + ' - ') : ''), lastAnnounceTime ];
+                       }
+               }
+               return { 'label':lastAnnounceLabel, 'value':lastAnnounce.join('') };
+       },
+
+       announceState: function(tracker) {
+               var announceState = '';
+               switch (tracker.announceState) {
+                       case Torrent._TrackerActive:
+                               announceState = 'Announce in progress';
+                               break;
+                       case Torrent._TrackerWaiting:
+                               var timeUntilAnnounce = tracker.nextAnnounceTime - ((new Date()).getTime() / 1000);
+                               if (timeUntilAnnounce < 0) {
+                                       timeUntilAnnounce = 0;
+                               }
+                               announceState = 'Next announce in ' + Transmission.fmt.timeInterval(timeUntilAnnounce);
+                               break;
+                       case Torrent._TrackerQueued:
+                               announceState = 'Announce is queued';
+                               break;
+                       case Torrent._TrackerInactive:
+                               announceState = tracker.isBackup ?
+                                       'Tracker will be used as a backup' :
+                                       'Announce not scheduled';
+                               break;
+                       default:
+                               announceState = 'unknown announce state: ' + tracker.announceState;
+               }
+               return announceState;
+       },
+
+       lastScrapeStatus: function(tracker) {
+               var lastScrapeLabel = 'Last Scrape';
+               var lastScrape = 'N/A';
+               if (tracker.hasScraped) {
+                       var lastScrapeTime = Transmission.fmt.timestamp(tracker.lastScrapeTime);
+                       if (tracker.lastScrapeSucceeded) {
+                               lastScrape = lastScrapeTime;
+                       } else {
+                               lastScrapeLabel = 'Scrape error';
+                               lastScrape = (tracker.lastScrapeResult ? tracker.lastScrapeResult + ' - ' : '') + lastScrapeTime;
+                       }
+               }
+               return {'label':lastScrapeLabel, 'value':lastScrape};
+       },
+
+       toggleInspector: function()
+       {
+               this.setInspectorVisible(!this[Prefs._ShowInspector]);
+       },
+       setInspectorVisible: function(visible)
+       {
+               // we collect extra stats on torrents when they're in the inspector...
+               clearInterval(this._periodic_inspector_refresh);
+               delete this._periodic_inspector_refresh;
+               if (visible) {
+                       var tr = this;
+                       this._periodic_inspector_refresh = setInterval(function() {tr.refreshInspectorTorrents(false);},2000);
+                       this.refreshInspectorTorrents(true);
+               }
+
+               // update the ui widgetry
+               $('#torrent_inspector').toggle(visible);
+               if (iPhone) {
+                       $('body').toggleClass('inspector_showing',visible);
+                       $('#inspector_close').toggle(visible);
+                       this.hideiPhoneAddressbar();
+               } else {
+                       var w = visible ? $('#torrent_inspector').width() + 1 + 'px' : '0px';
+                       $('#torrent_container')[0].style.right = w;
+               }
+
+               setInnerHTML($('ul li#context_toggle_inspector')[0], (visible?'Hide':'Show')+' Inspector');
+               this.setPref(Prefs._ShowInspector, visible);
+               if (visible)
+                       this.updateInspector();
+       },
+
        /****
        *****
        *****  FILTER