]> granicus.if.org Git - transmission/commitdiff
(trunk web) significant shrinkage of our memory + network footprint by only loading...
authorJordan Lee <jordan@transmissionbt.com>
Thu, 25 Aug 2011 23:06:41 +0000 (23:06 +0000)
committerJordan Lee <jordan@transmissionbt.com>
Thu, 25 Aug 2011 23:06:41 +0000 (23:06 +0000)
web/javascript/file-row.js
web/javascript/torrent.js
web/javascript/transmission.js
web/javascript/transmission.remote.js

index 58f0533844cc8d65d5446a3d11e44db84561f4c7..4ac31da938d99996678434ad61a42de2139a12cb 100644 (file)
@@ -90,7 +90,7 @@ FileRow.prototype =
        {
                var i = this.getIndex();
                var t = this.getTorrent();
-               this.readAttributes(t._files[i]);
+               this.readAttributes(t.getFile(i));
                this.refreshHTML();
        },
 
@@ -98,13 +98,13 @@ FileRow.prototype =
                 return this._done >= this._size;
         },
         isEditable: function () {
-                return (this.getTorrent()._files.length>1) && !this.isDone();
+                return (this.getTorrent().getFileCount()>1) && !this.isDone();
         },
 
         createRow: function(torrent, i)
        {
                var me = this;
-               var file = torrent._files[i];
+               var file = torrent.getFile(i);
                 var name = file.name.substring (file.name.lastIndexOf('/')+1);
 
                 var root = document.createElement('li');
index 1d5b88eccd77eb42e7951a394cb50032cafc8ab9..53ca1682351ee2de6621b44d75883a5afcf59a3d 100644 (file)
@@ -45,25 +45,79 @@ Torrent._TrackerQueued         = 2;
 Torrent._TrackerActive         = 3;
 
 
-// fields whose values never change and are always known
-Torrent._StaticFields = [
-       'hashString', 'id' ];
-
-// fields whose values never change and are known upon constructon OR
-// when a magnet torrent finishes downloading its metadata
-Torrent._MetaDataFields = [
-       'addedDate', 'comment', 'creator', 'dateCreated',
-       'isPrivate', 'name', 'totalSize', 'pieceCount', 'pieceSize' ];
-
-// torrent fields whose values change all the time
-Torrent._DynamicFields = [
-       'desiredAvailable', 'downloadDir', 'downloadedEver', 'error',
-       'errorString', 'eta', 'haveUnchecked', 'haveValid', 'isFinished',
-       'leftUntilDone', 'metadataPercentComplete', 'peers', 'peersConnected',
-       'peersGettingFromUs', 'peersSendingToUs', 'queuePosition',
-       'rateDownload', 'rateUpload', 'recheckProgress', 'seedRatioLimit',
-       'seedRatioMode', 'sizeWhenDone', 'status', 'trackerStats',
-       'uploadedEver', 'uploadRatio', 'webseedsSendingToUs' ];
+Torrent.Fields = { };
+
+// commonly used fields which only need to be loaded once,
+// either on startup or when a magnet finishes downloading its metadata
+// finishes downloading its metadata
+Torrent.Fields.Metadata = [
+       'addedDate',
+       'name',
+       'totalSize'
+];
+
+// commonly used fields which need to be periodically refreshed
+Torrent.Fields.Stats = [
+       'error',
+       'errorString',
+       'eta',
+       'isFinished',
+       'isStalled',
+       'leftUntilDone',
+       'metadataPercentComplete',
+       'peersConnected',
+       'peersGettingFromUs',
+       'peersSendingToUs',
+       'percentDone',
+       'queuePosition',
+       'rateDownload',
+       'rateUpload',
+       'recheckProgress',
+       'seedRatioMode',
+       'sizeWhenDone',
+       'status',
+       'trackers',
+       'uploadedEver',
+       'uploadRatio'
+];
+
+// fields used by the inspector which only need to be loaded once
+Torrent.Fields.InfoExtra = [
+       'comment',
+       'creator',
+       'dateCreated',
+       'files',
+       'hashString',
+       'isPrivate',
+       'pieceCount',
+       'pieceSize'
+];
+
+// fields used in the inspector which need to be periodically refreshed
+Torrent.Fields.StatsExtra = [
+       'activityDate',
+       'desiredAvailable',
+       'downloadDir',
+       'downloadLimit',
+       'downloadLimited',
+       'downloadedEver',
+       'fileStats',
+       'haveUnchecked',
+       'haveValid',
+       'honorsSessionLimits',
+       'manualAnnounceTime',
+       'peer-limit',
+       'peers',
+       'seedIdleLimit',
+       'seedIdleMode',
+       'seedRatioLimit',
+       'startDate',
+       'torrentFile',
+       'trackerStats',
+       'uploadLimited',
+       'uploadLimit',
+       'webseedsSendingToUs'
+];
 
 /***
 ****
@@ -76,129 +130,76 @@ Torrent.prototype =
        initialize: function(data)
        {
                this.fields = {};
-               this._files = [];
-
-               // these fields are set in the ctor and never change
-               for (var i=0, key; key=Torrent._StaticFields[i]; ++i) {
-                       if (key in data) {
-                               this.fields[key] = data[key];
-                       }
-               }
-
-               this.initMetaData(data);
-               this._trackerStats = this.buildTrackerStats(data.trackerStats);
-               this.refresh(data);
+               this.refresh (data);
        },
 
-       buildTrackerStats: function(trackerStats) {
-               var announce = [];
-               var result = [];
-               for (var i=0, tracker; tracker=trackerStats[i]; ++i) {
-                       var tier = result[tracker.tier] || [];
-                       tier.push(tracker);
-                       result[tracker.tier] = tier;
-                       announce.push(tracker.announce);
-               }
-               this.fields.collatedTrackers = announce.join('\t');
-               return result;
+       setField: function(o, name, value)
+       {
+               var changed = !(name in o) || (o[name] !== value);
+               if (changed)
+                       o[name] = value;
+               return changed;
        },
 
-       initMetaData: function(data) {
-
-               var f = this.fields;
+       // fields.files is an array of unions of RPC's "files" and "fileStats" objects.
+       updateFiles: function(files) {
                var changed = false;
-
-               // populate the metadata fields
-               for (var i=0, key; key=Torrent._MetaDataFields[i]; ++i) {
-                       if (key in data) {
-                               if (f[key] !== data[key]) {
-                                       f[key] = data[key];
-                                       if (key === 'name')
-                                               f.collatedName = data.name.toLowerCase();
-                                       changed = true;
-                               }
-                       }
-               }
-
-               // populate the files array
-               if (data.files) {
-                       for (var i=0, row; row=data.files[i]; ++i) {
-                               this._files[i] = {
-                                       'index': i,
-                                       'length': row.length,
-                                       'name': row.name
-                               };
-                       }
+               var myfiles = this.fields.files || [];
+               var keys = [ 'length', 'name', 'bytesCompleted', 'wanted', 'priority' ];
+               for (var i=0, f; f=files[i]; ++i) {
+                       var myfile = myfiles[i] || {};
+                       for (var j=0, key; key=keys[j]; ++j)
+                               if(key in f)
+                                       changed |= this.setField(myfile,key,f[key]);
+                       myfiles[i] = myfile;
                }
-
+               this.fields.files = myfiles;
                return changed;
        },
 
-       refreshMetaData: function(data)
-       {
-               var changed = this.initMetaData(data);
-               if (changed)
-                       this.fireDataChanged();
-               return changed;
+       collateTrackers: function(trackers) {
+               announces = [];
+               for (var i=0, t; t=trackers[i]; ++i)
+                       announces.push(t.announce.toLowerCase());
+               return announces.join('\t');
        },
 
-       refresh: function(data)
+       isField: function(name) {
+               return ( name === 'id' )
+                   || ( Torrent.Fields.Stats.indexOf(name)      !== -1 )
+                   || ( Torrent.Fields.StatsExtra.indexOf(name) !== -1 )
+                   || ( Torrent.Fields.InfoExtra.indexOf(name)  !== -1 )
+                   || ( Torrent.Fields.Metadata.indexOf(name)   !== -1 );
+       },
+
+       refreshFields: function(data)
        {
                var changed = false;
 
-               // FIXME: unnecessary coupling... this should be handled by transmission.js
-               if (this.needsMetaData() && (data.metadataPercentComplete >= 1))
-                       changed |= transmission.refreshMetaData([ this.getId() ]);
-
-               var f = this.fields;
-
-               // refresh the dynamic fields
-               for (var i=0, key; key=Torrent._DynamicFields[i]; ++i) {
-                       if (key in data) {
-                               if (f[key] !== data[key]) {
-                                       f[key] = data[key];
-                                       changed = true;
-                               }
+               for (var key in data) {
+                       if (this.isField(key)) switch (key) {
+                               case 'files':
+                               case 'fileStats': // merge files and fileStats together
+                                       changed |= this.updateFiles(data[key]);
+                                       break;
+                               case 'trackerStats': // 'trackerStats' is a superset of 'trackers'...
+                                       changed |= this.setField(this.fields,'trackers',data[key]);
+                               case 'trackers': // ...so only save 'trackers' if we don't have it already
+                                       if (!(key in this.fields))
+                                               changed |= this.setField(this.fields,key,data[key]);
+                                       break;
+                               default:
+                                       changed |= this.setField(this.fields,key,data[key]);
                        }
                }
 
-               this._trackerStats = this.buildTrackerStats(data.trackerStats);
-
-               if (data.fileStats)
-                       changed |= this.refreshFiles(data);
-
-               if (changed)
-                       this.fireDataChanged();
-       },
-
-       refreshFiles: function(data) {
-               var changed = false;
-               for (var i=0; i<data.fileStats.length; ++i) {
-                       var src = data.fileStats[i];
-                       var tgt = this._files[i];
-                       if (!tgt) {
-                               changed = true;
-                               tgt = this._files[i] = { };
-                       }
-                       if (tgt.wanted !== src.wanted) {
-                               tgt.wanted = src.wanted;
-                               changed = true;
-                       }
-                       if (tgt.priority !== src.priority) {
-                               tgt.priority = src.priority;
-                               changed = true;
-                       }
-                       if (tgt.bytesCompleted !== src.bytesCompleted) {
-                               tgt.bytesCompleted = src.bytesCompleted;
-                               changed = true;
-                       }
-               }
                return changed;
        },
 
-       fireDataChanged: function()
+       refresh: function(data)
        {
-               $(this).trigger('dataChanged');
+               if (this.refreshFields(data))
+                       $(this).trigger('dataChanged');
        },
 
        /****
@@ -219,6 +220,8 @@ Torrent.prototype =
        getError: function() { return this.fields.error; },
        getErrorString: function() { return this.fields.errorString; },
        getETA: function() { return this.fields.eta; },
+       getFile: function(i) { return this.fields.files[i]; },
+       getFileCount: function() { return this.fields.files ? this.fields.files.length : 0; },
        getHashString: function() { return this.fields.hashString; },
        getHaveValid: function() { return this.fields.haveValid; },
        getHave: function() { return this.getHaveValid() + this.fields.haveUnchecked; },
@@ -231,7 +234,6 @@ Torrent.prototype =
        getPeersGettingFromUs: function() { return this.fields.peersGettingFromUs; },
        getPeersSendingToUs: function() { return this.fields.peersSendingToUs; },
        getPieceCount: function() { return this.fields.pieceCount; },
-       getPieceCount: function() { return this.fields.pieceCount; },
        getPieceSize: function() { return this.fields.pieceSize; },
        getPrivateFlag: function() { return this.fields.isPrivate; },
        getQueuePosition: function() { return this.fields.queuePosition; },
@@ -241,6 +243,7 @@ Torrent.prototype =
        getSizeWhenDone: function() { return this.fields.sizeWhenDone; },
        getStatus: function() { return this.fields.status; },
        getTotalSize: function() { return this.fields.totalSize; },
+       getTrackers: function() { return this.fields.trackers; },
        getUploadSpeed: function() { return this.fields.rateUpload; },
        getUploadRatio: function() { return this.fields.uploadRatio; },
        getUploadedEver: function() { return this.fields.uploadedEver; },
@@ -275,7 +278,6 @@ Torrent.prototype =
                        default:                            return 'error';
                }
        },
-       trackerStats: function() { return this._trackerStats; },
        seedRatioLimit: function(controller){
                switch(this.getSeedRatioMode()) {
                        case Torrent._RatioUseGlobal: return controller.seedRatioLimit();
@@ -362,8 +364,14 @@ Torrent.compareById = function(ta, tb)
 };
 Torrent.compareByName = function(ta, tb)
 {
-       return ta.getCollatedName().compareTo(tb.getCollatedName())
-           || Torrent.compareById(ta, tb);
+       var i = 0;
+       var a = ta.getCollatedName();
+       var b = tb.getCollatedName();
+       if (a && b)
+               i = a.compareTo(b);
+       if (i)
+               return i;
+       return Torrent.compareById(ta, tb);
 };
 Torrent.compareByQueue = function(ta, tb)
 {
index 80ae747d6a69a3dc7f8396b1bfefd39f10e7cc2e..7f8918880217c6b5bf071de1a1470d6de56c9450 100644 (file)
@@ -68,13 +68,13 @@ Transmission.prototype =
                $('#torrent_upload_form').submit(function() { $('#upload_confirm_button').click(); return false; });
 
                if (iPhone) {
-                       $('#inspector_close').bind('click', function() { tr.hideInspector(); });
+                       $('#inspector_close').bind('click', function() { tr.setInspectorVisible(false); });
                        $('#preferences_link').bind('click', function(e) { tr.releaseClutchPreferencesButton(e); });
                } else {
                        $(document).bind('keydown',  function(e) { tr.keyDown(e); });
                        $(document).bind('keyup',  function(e) { tr.keyUp(e); });
                        $('#torrent_container').click(function() { tr.deselectAll(true); });
-                       $('#inspector_link').click(function(e) { tr.toggleInspectorClicked(e); });
+                       $('#inspector_link').click(function(e) { tr.toggleInspector(); });
 
                        this.setupSearchBox();
                        this.createContextMenu();
@@ -86,7 +86,6 @@ Transmission.prototype =
                this._inspector_file_list      = $('#inspector_file_list')[0];
                this._inspector_peers_list     = $('#inspector_peers_list')[0];
                this._inspector_trackers_list  = $('#inspector_trackers_list')[0];
-               this._inspector_tab_files      = $('#inspector_tab_files')[0];
                this._toolbar_buttons          = $('#toolbar ul li');
                this._toolbar_pause_button     = $('#toolbar #pause_selected')[0];
                this._toolbar_pause_all_button = $('#toolbar #pause_all')[0];
@@ -221,7 +220,7 @@ Transmission.prototype =
                        $('#reverse_sort_order').selectMenuItem();
 
                if (!iPhone && this[Prefs._ShowInspector])
-                       this.showInspector();
+                       this.setInspectorVisible(true);
 
                this.initCompactMode();
        },
@@ -473,6 +472,9 @@ Transmission.prototype =
 
        selectionChanged: function()
        {
+               if (this[Prefs._ShowInspector])
+                       this.refreshInspectorTorrents(true);
+
                this.updateButtonStates();
                this.updateInspector();
                this.updateSelectedData();
@@ -719,11 +721,6 @@ Transmission.prototype =
                }
        },
 
-       toggleInspectorClicked: function(ev) {
-               if (this.isButtonEnabled(ev))
-                       this.toggleInspector();
-       },
-
        inspectorTabClicked: function(ev, tab) {
                if (iPhone) ev.stopPropagation();
 
@@ -740,19 +737,19 @@ Transmission.prototype =
        },
 
        filesSelectAllClicked: function() {
-               var t = this._files_torrent;
+               var t = this._file_torrent;
                if (t)
                        this.toggleFilesWantedDisplay(t, true);
        },
        filesDeselectAllClicked: function() {
-               var t = this._files_torrent;
+               var t = this._file_torrent;
                if (t)
                        this.toggleFilesWantedDisplay(t, false);
        },
        toggleFilesWantedDisplay: function(torrent, wanted) {
                var rows = [ ];
-               for (var i=0, row; row=this._files[i]; ++i)
-                       if (row.isEditable() && (torrent._files[i].wanted !== wanted))
+               for (var i=0, row; row=this._file_rows[i]; ++i)
+                       if (row.isEditable() && (torrent.getFile(i).wanted !== wanted))
                                rows.push(row);
                if (rows.length > 0) {
                        var command = wanted ? 'files-wanted' : 'files-unwanted';
@@ -1144,7 +1141,7 @@ Transmission.prototype =
 
                var torrents = this.getSelectedTorrents();
                if (!torrents.length && iPhone) {
-                       this.hideInspector();
+                       this.setInspectorVisible(false);
                        return;
                }
 
@@ -1302,137 +1299,149 @@ Transmission.prototype =
                }
                this.changeFileCommand(command, [ row ]);
        },
-       clearFileList: function() {
+       clearFileList: function()
+       {
                $(this._inspector_file_list).empty();
-               delete this._files_torrent;
-               delete this._files;
+               delete this._file_torrent;
+               delete this._file_rows;
        },
-       updateFileList: function() {
-
-               // if the file list is hidden, clear the list
-               if (this._inspector_tab_files.className.indexOf('selected') == -1) {
-                       this.clearFileList();
+       updateFileList: function()
+       {
+               if (!$(this._inspector_file_list).is(':visible'))
                        return;
-               }
 
-               // if not torrent is selected, clear the list
-               var selected_torrents = this.getSelectedTorrents();
-               if (selected_torrents.length != 1) {
+               var sel = this.getSelectedTorrents();
+               if (sel.length !== 1) {
                        this.clearFileList();
                        return;
                }
 
-               // if the active torrent hasn't changed, noop
-               var torrent = selected_torrents[0];
-               if (this._files_torrent === torrent)
-                       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._files_torrent = torrent;
-               var n = torrent._files.length;
-               this._files = new Array(n);
+               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 = new FileRow(torrent, i);
+                       var row = this._file_rows[i] = new FileRow(torrent, i);
                        fragment.appendChild(row.getElement());
-                       this._files[i] = row;
                        $(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);
        },
 
-       refreshFileView: function() {
-               for (var i=0, row; row=this._files[i]; ++i)
-                       row.refresh();
-       },
+       updatePeersLists: function()
+       {
+               if (!$(this._inspector_peers_list).is(':visible'))
+                       return;
 
-       updatePeersLists: function() {
                var html = [ ];
                var fmt = Transmission.fmt;
                var torrents = this.getSelectedTorrents();
-               if ($(this._inspector_peers_list).is(':visible')) {
-                       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.length == 0) {
-                                       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>');
+
+               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() {
-               // By building up the HTML as as string, then have the browser
-               // turn this into a DOM tree, this is a fast operation.
+               if (!$(this._inspector_trackers_list).is(':visible'))
+                       return;
+
                var tr = this;
                var html = [ ];
                var na = 'N/A';
                var torrents = this.getSelectedTorrents();
-               if ($(this._inspector_trackers_list).is(':visible')) {
-                       for (var k=0, torrent; torrent = torrents[k]; ++k) {
-                               html.push ('<div class="inspector_group">');
-                               if (torrents.length > 1) {
-                                       html.push('<div class="inspector_torrent_label">', torrent.getName(), '</div>');
-                               }
-                               for (var i=0, tier; tier=torrent._trackerStats[i]; ++i) {
+
+               // 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 ', (i + 1), '</div>',
-                                                 '<ul class="tier_list">');
-                                       for (var j=0, tracker; tracker=tier[j]; ++j) {
-                                               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>');
-                                       }
-                                       html.push('</ul>');
+                                                 'Tier ', tier, '</div>',
+                                                 '<ul class="tier_list">');
                                }
-                               html.push('</div>');
+
+                               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(''));
        },
 
@@ -1493,128 +1502,95 @@ Transmission.prototype =
                return {'label':lastScrapeLabel, 'value':lastScrape};
        },
 
-       /*
-        * Toggle the visibility of the inspector (used by the context menu)
-        */
-       toggleInspector: function() {
-               if (this[Prefs._ShowInspector])
-                       this.hideInspector();
-               else
-                       this.showInspector();
+       toggleInspector: function()
+       {
+               this.setInspectorVisible(!this[Prefs._ShowInspector]);
        },
-
-       showInspector: function() {
-               $('#torrent_inspector').show();
-               if (iPhone) {
-                       $('body').addClass('inspector_showing');
-                       $('#inspector_close').show();
-                       this.hideiPhoneAddressbar();
-               } else {
-                       var w = $('#torrent_inspector').width() + 1 + 'px';
-                       $('#torrent_container')[0].style.right = w;
+       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);
                }
 
-               setInnerHTML($('ul li#context_toggle_inspector')[0], 'Hide Inspector');
-
-               this.setPref(Prefs._ShowInspector, true);
-               this.updateInspector();
-       },
-
-       /*
-        * Hide the inspector
-        */
-       hideInspector: function() {
-
-               $('#torrent_inspector').hide();
-
+               // update the ui widgetry
+               $('#torrent_inspector').toggle(visible);
                if (iPhone) {
-                       this.deselectAll();
-                       $('body.inspector_showing').removeClass('inspector_showing');
-                       $('#inspector_close').hide();
+                       $('body').toggleClass('inspector_showing',visible);
+                       $('#inspector_close').toggle(visible);
                        this.hideiPhoneAddressbar();
                } else {
-                       $('#torrent_container')[0].style.right = '0px';
-                       setInnerHTML($('ul li#context_toggle_inspector')[0], 'Show Inspector');
+                       var w = visible ? $('#torrent_inspector').width() + 1 + 'px' : '0px';
+                       $('#torrent_container')[0].style.right = w;
                }
 
-               this.setPref(Prefs._ShowInspector, false);
-       },
-
-       refreshMetaData: function(ids) {
-               var tr = this;
-               this.remote.getMetaDataFor(ids, function(active) { tr.updateMetaData(active); });
+               setInnerHTML($('ul li#context_toggle_inspector')[0], (visible?'Hide':'Show')+' Inspector');
+               this.setPref(Prefs._ShowInspector, visible);
+               if (visible)
+                       this.updateInspector();
        },
 
-       updateMetaData: function(torrents)
+       onTorrentChanged: function(ev)
        {
-               var tr = this;
-               var refresh_files_for = [ ];
-               var selected_torrents = this.getSelectedTorrents();
-               jQuery.each(torrents, function() {
-                       var t = tr._torrents[ this.id ];
-                       if (t) {
-                               t.refreshMetaData(this);
-                               if (selected_torrents.indexOf(t) != -1)
-                                       refresh_files_for.push(t.getId());
-                       }
-               });
-               if (refresh_files_for.length > 0)
-                       tr.remote.loadTorrentFiles(refresh_files_for);
+               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();
        },
 
-       refreshTorrents: function(ids) {
-               var tr = this;
-               if (!ids)
-                       ids = 'recently-active';
-
-               this.remote.getUpdatedDataFor(ids, function(active, removed) { tr.updateTorrentsData(active, removed); });
-       },
+       updateFromTorrentGet: function(updates, removed_ids)
+       {
+               var new_ids = [];
 
-       updateTorrentsData: function(updated, removed_ids) {
-               var tr = this;
-               var new_torrent_ids = [];
-               var refresh_files_for = [];
-               var selected_torrents = this.getSelectedTorrents();
-
-               for (var i=0, o; o=updated[i]; ++i) {
-                       var t = tr._torrents[o.id];
-                       if (t == null)
-                               new_torrent_ids.push(o.id);
-                       else {
+               for (var i=0, o; o=updates[i]; ++i) {
+                       var t;
+                       var id = o.id;
+                       if ((t = this._torrents[id]))
                                t.refresh(o);
-                               if (selected_torrents.indexOf(t) != -1)
-                                       refresh_files_for.push(t.getId());
+                       else {
+                               t = this._torrents[id] = new Torrent(o);
+                               $(t).bind('dataChanged',function(ev) {tr.onTorrentChanged(ev);});
+                               new_ids.push(id);
                        }
                }
 
-               if (refresh_files_for.length > 0)
-                       tr.remote.loadTorrentFiles(refresh_files_for);
-
-               if (new_torrent_ids.length > 0)
-                       tr.remote.getInitialDataFor(new_torrent_ids, function(torrents) {tr.addTorrents(torrents);});
-
-               tr.deleteTorrents(removed_ids);
-
-               if (new_torrent_ids.length != 0) {
-                       tr.hideiPhoneAddressbar();
-                       tr.deselectAll(true);
+               if (new_ids.length) {
+                       var tr = this;
+                       this.remote.getTorrentInitial(new_ids, function(a,b){tr.updateFromTorrentGet(a,b);});
+                       this.refilterSoon();
                }
-       },
 
-       updateTorrentsFileData: function(torrents) {
-               for (var i=0, o; o=torrents[i]; ++i) {
-                       var t = this._torrents[o.id];
-                       if (t) {
-                               t.refreshFiles(o);
-                               if (t === this._files_torrent)
-                                       this.refreshFileView();
-                       }
+               if (removed_ids) {
+                       this.deleteTorrents(removed_ids);
+                       this.refilterSoon();
                }
        },
 
+       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.getInitialDataFor(null ,function(torrents) { tr.addTorrents(torrents); });
+               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);});
        },
 
        onRowClicked: function(ev, row)
@@ -1634,7 +1610,7 @@ Transmission.prototype =
                // Shift-Click - selects a range from the last-clicked row to this one
                if (iPhone) {
                        if (row.isSelected())
-                               this.showInspector();
+                               this.setInspectorVisible(true);
                        this.setSelectedRow(row);
 
                } else if (ev.shiftKey) {
@@ -1662,20 +1638,6 @@ Transmission.prototype =
                this._last_torrent_clicked = row.getTorrent().getId();
        },
 
-       addTorrents: function(new_torrents)
-       {
-               var tr = this;
-               var key = 'dataChanged';
-
-               for (var i=0, row; row=new_torrents[i]; ++i) {
-                       var t = new Torrent(row);
-                       $(t).bind(key,function() {tr.refilterSoon();});
-                       this._torrents[t.getId()] = t;
-               }
-
-               this.refilterSoon();
-       },
-
        deleteTorrents: function(torrent_ids)
        {
                if (torrent_ids && torrent_ids.length)
@@ -2197,27 +2159,26 @@ Transmission.prototype =
 
        getTrackers: function()
        {
-               var trackers = {};
+               var ret = {};
 
                var torrents = this.getAllTorrents();
                for (var i=0, torrent; torrent=torrents[i]; ++i) {
                        var names = [];
-                       for (var j=0, tier; tier=torrent._trackerStats[j]; ++j) {
-                               for (var k=0, tracker; tracker=tier[k]; ++k) {
-                                       var uri = parseUri(tracker.announce);
-                                       var domain = this.getDomainName(uri.host);
-                                       var name = this.getReadableDomain(domain);
-                                       if (!(name in trackers))
-                                               trackers[name] = { 'uri': uri, 'domain': domain, 'count': 0 };
-                                       if (names.indexOf(name) === -1)
-                                               names.push(name);
-                               }
+                       var trackers = torrent.getTrackers();
+                       for (var j=0, tracker; tracker=trackers[j]; ++j) {
+                               var uri = parseUri(tracker.announce);
+                               var domain = this.getDomainName(uri.host);
+                               var name = this.getReadableDomain(domain);
+                               if (!(name in ret))
+                                       ret[name] = { 'uri': uri, 'domain': domain, 'count': 0 };
+                               if (names.indexOf(name) === -1)
+                                       names.push(name);
                        }
                        for (var j=0, name; name=names[j]; ++j)
-                               trackers[name].count++;
+                               ret[name].count++;
                }
 
-               return trackers;
+               return ret;
        },
 
        /***
index 8558b922d01137abfb4d8208eadbcacf1298d0bb..325e176b54bdb44369c98052938ed41a5a2c582d 100644 (file)
@@ -30,20 +30,20 @@ RPC._TurtleTimeEnd          = 'alt-speed-time-end';
 RPC._TurtleTimeDay          = 'alt-speed-time-day';
 RPC._PeerLimitGlobal        = 'peer-limit-global';
 RPC._PeerLimitPerTorrent    = 'peer-limit-per-torrent';
-RPC._PexEnabled                    = 'pex-enabled';
+RPC._PexEnabled             = 'pex-enabled';
 RPC._DhtEnabled             = 'dht-enabled';
 RPC._LpdEnabled             = 'lpd-enabled';
 RPC._BlocklistEnabled       = 'blocklist-enabled';
 RPC._BlocklistURL           = 'blocklist-url';
 RPC._BlocklistSize          = 'blocklist-size';
-RPC._UtpEnabled                    = 'utp-enabled';
+RPC._UtpEnabled             = 'utp-enabled';
 RPC._PeerPortRandom         = 'peer-port-random-on-start';
 RPC._PortForwardingEnabled  = 'port-forwarding-enabled';
 RPC._StartAddedTorrent      = 'start-added-torrents';
-RPC._QueueMoveTop                      = 'queue-move-top';
-RPC._QueueMoveBottom           = 'queue-move-bottom';
-RPC._QueueMoveUp                       = 'queue-move-up';
-RPC._QueueMoveDown                     = 'queue-move-down';
+RPC._QueueMoveTop           = 'queue-move-top';
+RPC._QueueMoveBottom        = 'queue-move-bottom';
+RPC._QueueMoveUp            = 'queue-move-up';
+RPC._QueueMoveDown          = 'queue-move-down';
 
 function TransmissionRemote(controller)
 {
@@ -133,28 +133,25 @@ TransmissionRemote.prototype =
                this.sendRequest(o, callback, async);
        },
 
-       getInitialDataFor: function(torrent_ids, callback) {
+       getTorrentInitial: function(torrent_ids, callback) {
                var o = {
                        method: 'torrent-get',
                        arguments: {
-                       fields: Torrent._StaticFields.concat(Torrent._MetaDataFields,
-                                                            Torrent._DynamicFields,
-                                                            [ 'files', 'fileStats' ])
+                               fields: ['id'].concat(Torrent.Fields.Metadata, Torrent.Fields.Stats)
                        }
                };
 
                if (torrent_ids)
                        o.arguments.ids = torrent_ids;
 
-               this.sendRequest(o, function(data){ callback(data.arguments.torrents);});
+               this.sendRequest(o, function(data){ callback(data.arguments.torrents, data.arguments.removed);});
        },
 
-       getMetaDataFor: function(torrent_ids, callback) {
+       getTorrentMetadata: function(torrent_ids, callback) {
                var o = {
                        method: 'torrent-get',
                        arguments: {
-                       fields: Torrent._StaticFields.concat(Torrent._MetaDataFields,
-                                                            ['files', 'fileStats'])
+                               fields: ['id'].concat(Torrent.Fields.Metadata)
                        }
                };
 
@@ -164,26 +161,31 @@ TransmissionRemote.prototype =
                this.sendRequest(o, function(data) {callback(data.arguments.torrents)});
        },
 
-       getUpdatedDataFor: function(torrent_ids, callback) {
+       getTorrentStats: function(torrent_ids, callback) {
                var o = {
                        method: 'torrent-get',
                        arguments: {
                                'ids': torrent_ids,
-                               fields: [ 'id' ].concat(Torrent._DynamicFields)
+                               fields: ['id'].concat(Torrent.Fields.Stats)
                        }
                };
 
                this.sendRequest(o, function(data) {callback(data.arguments.torrents, data.arguments.removed);});
        },
 
-       loadTorrentFiles: function(torrent_ids) {
-               var tr = this._controller;
-               this.sendRequest({
+       /* called for the torrents in the inspector aka details dialog */
+       getTorrentDetails: function(torrent_ids, full, callback) {
+               var f = ['id'].concat(Torrent.Fields.StatsExtra);
+               if (full) // these only need to be loaded once...
+                       f = f.concat(Torrent.Fields.InfoExtra);
+               var o = {
                        method: 'torrent-get',
-                       arguments: { fields: [ 'id', 'fileStats'], ids: torrent_ids }
-               }, function(data) {
-                       tr.updateTorrentsFileData(data.arguments.torrents);
-               });
+                       arguments: {
+                               'ids': torrent_ids,
+                               fields: f,
+                       }
+               };
+               this.sendRequest(o, function(data) {callback(data.arguments.torrents,null)});
        },
 
        changeFileCommand: function(command, rows) {