function Inspector(controller) {
- var data = {
- controller: null,
- elements: { },
- torrents: [ ]
- },
-
- needsExtraInfo = function (torrents) {
- var i, id, tor;
-
- for (i = 0; tor = torrents[i]; i++)
- if (!tor.hasExtraInfo())
- return true;
-
- return false;
- },
-
- refreshTorrents = function () {
- var fields,
- ids = $.map(data.torrents.slice(0), function (t) {return t.getId();});
-
- if (ids && ids.length)
- {
- fields = ['id'].concat(Torrent.Fields.StatsExtra);
-
- if (needsExtraInfo(data.torrents))
- $.merge(fields, Torrent.Fields.InfoExtra);
-
- data.controller.updateTorrents(ids, fields);
- }
- },
-
- onTabClicked = function (ev) {
- var tab = ev.currentTarget;
-
- if (isMobileDevice)
- ev.stopPropagation();
-
- // select this tab and deselect the others
- $(tab).addClass('selected').siblings().removeClass('selected');
-
- // show this tab and hide the others
- $('#' + tab.id.replace('tab','page')).show().siblings('.inspector-page').hide();
-
- updateInspector();
- },
-
- updateInspector = function () {
- var e = data.elements,
- torrents = data.torrents,
- name;
-
- // update the name, which is shown on all the pages
- if (!torrents || !torrents.length)
- name = 'No Selection';
- else if(torrents.length === 1)
- name = torrents[0].getName();
- else
- name = '' + torrents.length+' Transfers Selected';
- setTextContent(e.name_lb, name || na);
-
- // update the visible page
- if ($(e.info_page).is(':visible'))
- updateInfoPage();
- else if ($(e.peers_page).is(':visible'))
- updatePeersPage();
- else if ($(e.trackers_page).is(':visible'))
- updateTrackersPage();
- else if ($(e.files_page).is(':visible'))
- updateFilesPage();
- },
-
- /****
- ***** GENERAL INFO PAGE
- ****/
-
- updateInfoPage = function () {
- var torrents = data.torrents,
- e = data.elements,
- fmt = Transmission.fmt,
- none = 'None',
- mixed = 'Mixed',
- unknown = 'Unknown',
- isMixed, allPaused, allFinished,
- str,
- baseline, it, s, i, t,
- sizeWhenDone = 0,
- leftUntilDone = 0,
- available = 0,
- haveVerified = 0,
- haveUnverified = 0,
- verifiedPieces = 0,
- stateString,
- latest,
- pieces,
- size,
- pieceSize,
- creator, mixed_creator,
- date, mixed_date,
- v, u, f, d, pct,
- uri,
- now = Date.now();
-
- //
- // state_lb
- //
-
- if(torrents.length <1)
- str = none;
- else {
- isMixed = false;
- allPaused = true;
- allFinished = true;
-
- baseline = torrents[0].getStatus();
- for(i=0; t=torrents[i]; ++i) {
- it = t.getStatus();
- if(it != baseline)
- isMixed = true;
- if(!t.isStopped())
- allPaused = allFinished = false;
- if(!t.isFinished())
- allFinished = false;
- }
- if( isMixed )
- str = mixed;
- else if( allFinished )
- str = 'Finished';
- else if( allPaused )
- str = 'Paused';
- else
- str = torrents[0].getStateString();
- }
- setTextContent(e.state_lb, str);
- stateString = str;
-
- //
- // have_lb
- //
-
- if(torrents.length < 1)
- str = none;
- else {
- baseline = torrents[0].getStatus();
- for(i=0; t=torrents[i]; ++i) {
- if(!t.needsMetaData()) {
- haveUnverified += t.getHaveUnchecked();
- v = t.getHaveValid();
- haveVerified += v;
- if(t.getPieceSize())
- verifiedPieces += v / t.getPieceSize();
- sizeWhenDone += t.getSizeWhenDone();
- leftUntilDone += t.getLeftUntilDone();
- available += (t.getHave()) + t.getDesiredAvailable();
- }
- }
-
- d = 100.0 * ( sizeWhenDone ? ( sizeWhenDone - leftUntilDone ) / sizeWhenDone : 1 );
- str = fmt.percentString( d );
-
- if( !haveUnverified && !leftUntilDone )
- str = fmt.size(haveVerified) + ' (100%)';
- else if( !haveUnverified )
- str = fmt.size(haveVerified) + ' of ' + fmt.size(sizeWhenDone) + ' (' + str +'%)';
- else
- str = fmt.size(haveVerified) + ' of ' + fmt.size(sizeWhenDone) + ' (' + str +'%), ' + fmt.size(haveUnverified) + ' Unverified';
- }
- setTextContent(e.have_lb, str);
-
- //
- // availability_lb
- //
-
- if(torrents.length < 1)
- str = none;
- else if( sizeWhenDone == 0 )
- str = none;
- else
- str = '' + fmt.percentString( ( 100.0 * available ) / sizeWhenDone ) + '%';
- setTextContent(e.availability_lb, str);
-
- //
- // downloaded_lb
- //
-
- if(torrents.length < 1)
- str = none;
- else {
- d = f = 0;
- for(i=0; t=torrents[i]; ++i) {
- d += t.getDownloadedEver();
- f += t.getFailedEver();
- }
- if(f)
- str = fmt.size(d) + ' (' + fmt.size(f) + ' corrupt)';
- else
- str = fmt.size(d);
- }
- setTextContent(e.downloaded_lb, str);
-
- //
- // uploaded_lb
- //
-
- if(torrents.length < 1)
- str = none;
- else {
- d = u = 0;
- if(torrents.length == 1) {
- d = torrents[0].getDownloadedEver();
- u = torrents[0].getUploadedEver();
-
- if (d == 0)
- d = torrents[0].getHaveValid();
- }
- else {
- for(i=0; t=torrents[i]; ++i) {
- d += t.getDownloadedEver();
- u += t.getUploadedEver();
- }
- }
- str = fmt.size(u) + ' (Ratio: ' + fmt.ratioString( Math.ratio(u,d))+')';
- }
- setTextContent(e.uploaded_lb, str);
-
- //
- // running time
- //
-
- if(torrents.length < 1)
- str = none;
- else {
- allPaused = true;
- baseline = torrents[0].getStartDate();
- for(i=0; t=torrents[i]; ++i) {
- if(baseline != t.getStartDate())
- baseline = 0;
- if(!t.isStopped())
- allPaused = false;
- }
- if(allPaused)
- str = stateString; // paused || finished
- else if(!baseline)
- str = mixed;
- else
- str = fmt.timeInterval(now/1000 - baseline);
- }
- setTextContent(e.running_time_lb, str);
-
- //
- // remaining time
- //
-
- str = '';
- if(torrents.length < 1)
- str = none;
- else {
- baseline = torrents[0].getETA();
- for(i=0; t=torrents[i]; ++i) {
- if(baseline != t.getETA()) {
- str = mixed;
- break;
- }
- }
- }
- if(!str.length) {
- if(baseline < 0)
- str = unknown;
- else
- str = fmt.timeInterval(baseline);
- }
- setTextContent(e.remaining_time_lb, str);
-
- //
- // last activity
- //
-
- latest = -1;
- if(torrents.length < 1)
- str = none;
- else {
- baseline = torrents[0].getLastActivity();
- for(i=0; t=torrents[i]; ++i) {
- d = t.getLastActivity();
- if(latest < d)
- latest = d;
- }
- d = now/1000 - latest; // seconds since last activity
- if(d < 0)
- str = none;
- else if(d < 5)
- str = 'Active now';
- else
- str = fmt.timeInterval(d) + ' ago';
- }
- setTextContent(e.last_activity_lb, str);
-
- //
- // error
- //
-
- if(torrents.length < 1)
- str = none;
- else {
- str = torrents[0].getErrorString();
- for(i=0; t=torrents[i]; ++i) {
- if(str != t.getErrorString()) {
- str = mixed;
- break;
- }
- }
- }
- setTextContent(e.error_lb, str || none);
-
- //
- // size
- //
-
- if(torrents.length < 1)
- str = none;
- else {
- pieces = 0;
- size = 0;
- pieceSize = torrents[0].getPieceSize();
- for(i=0; t=torrents[i]; ++i) {
- pieces += t.getPieceCount();
- size += t.getTotalSize();
- if(pieceSize != t.getPieceSize())
- pieceSize = 0;
- }
- if(!size)
- str = none;
- else if(pieceSize > 0)
- str = fmt.size(size) + ' (' + pieces.toStringWithCommas() + ' pieces @ ' + fmt.mem(pieceSize) + ')';
- else
- str = fmt.size(size) + ' (' + pieces.toStringWithCommas() + ' pieces)';
- }
- setTextContent(e.size_lb, str);
-
- //
- // hash
- //
-
- if(torrents.length < 1)
- str = none;
- else {
- str = torrents[0].getHashString();
- for(i=0; t=torrents[i]; ++i) {
- if(str != t.getHashString()) {
- str = mixed;
- break;
- }
- }
- }
- setTextContent(e.hash_lb, str);
-
- //
- // privacy
- //
-
- if(torrents.length < 1)
- str = none;
- else {
- baseline = torrents[0].getPrivateFlag();
- str = baseline ? 'Private to this tracker -- DHT and PEX disabled' : 'Public torrent';
- for(i=0; t=torrents[i]; ++i) {
- if(baseline != t.getPrivateFlag()) {
- str = mixed;
- break;
- }
- }
- }
- setTextContent(e.privacy_lb, str);
-
- //
- // comment
- //
-
- if(torrents.length < 1)
- str = none;
- else {
- str = torrents[0].getComment();
- for(i=0; t=torrents[i]; ++i) {
- if(str != t.getComment()) {
- str = mixed;
- break;
- }
- }
- }
- if(!str)
- str = none;
- uri = parseUri(str);
- if (uri.protocol == 'http' || uri.parseUri == 'https') {
- str = encodeURI(str);
- setInnerHTML(e.comment_lb, '<a href="' + str + '" target="_blank" >' + str + '</a>');
- }
- else
- setTextContent(e.comment_lb, str);
-
- //
- // origin
- //
-
- if(torrents.length < 1)
- str = none;
- else {
- mixed_creator = false;
- mixed_date = false;
- creator = torrents[0].getCreator();
- date = torrents[0].getDateCreated();
- for(i=0; t=torrents[i]; ++i) {
- if(creator != t.getCreator())
- mixed_creator = true;
- if(date != t.getDateCreated())
- mixed_date = true;
- }
- var empty_creator = !creator || !creator.length,
- empty_date = !date;
- if(mixed_creator || mixed_date)
- str = mixed;
- else if(empty_creator && empty_date)
- str = unknown;
- else if(empty_date && !empty_creator)
- str = 'Created by ' + creator;
- else if(empty_creator && !empty_date)
- str = 'Created on ' + (new Date(date*1000)).toDateString();
- else
- str = 'Created by ' + creator + ' on ' + (new Date(date*1000)).toDateString();
- }
- setTextContent(e.origin_lb, str);
-
- //
- // foldername
- //
-
- if(torrents.length < 1)
- str = none;
- else {
- str = torrents[0].getDownloadDir();
- for(i=0; t=torrents[i]; ++i) {
- if(str != t.getDownloadDir()) {
- str = mixed;
- break;
- }
- }
- }
- setTextContent(e.foldername_lb, str);
- },
-
- /****
- ***** FILES PAGE
- ****/
-
- changeFileCommand = function(fileIndices, command) {
- var torrentId = data.file_torrent.getId();
- data.controller.changeFileCommand(torrentId, fileIndices, command);
- },
-
- onFileWantedToggled = function(ev, fileIndices, want) {
- changeFileCommand(fileIndices, want?'files-wanted':'files-unwanted');
- },
-
- onFilePriorityToggled = function(ev, fileIndices, priority) {
- var command;
- switch(priority) {
- case -1: command = 'priority-low'; break;
- case 1: command = 'priority-high'; break;
- default: command = 'priority-normal'; break;
- }
- changeFileCommand(fileIndices, command);
- },
-
- onNameClicked = function(ev, fileRow, fileIndices) {
- $(fileRow.getElement()).siblings().slideToggle();
- },
-
- clearFileList = function() {
- $(data.elements.file_list).empty();
- delete data.file_torrent;
- delete data.file_torrent_n;
- delete data.file_rows;
- },
-
- createFileTreeModel = function (tor) {
- var i, j, n, name, tokens, walk, tree, token, sub,
- leaves = [ ],
- tree = { children: { }, file_indices: [ ] };
-
- n = tor.getFileCount();
- for (i=0; i<n; ++i) {
- name = tor.getFile(i).name;
- tokens = name.split('/');
- walk = tree;
- for (j=0; j<tokens.length; ++j) {
- token = tokens[j];
- sub = walk.children[token];
- if (!sub) {
- walk.children[token] = sub = {
- name: token,
- parent: walk,
- children: { },
- file_indices: [ ],
- depth: j
- };
- }
- walk = sub;
- }
- walk.file_index = i;
- delete walk.children;
- leaves.push (walk);
- }
-
- for (i=0; i<leaves.length; ++i) {
- walk = leaves[i];
- j = walk.file_index;
- do {
- walk.file_indices.push (j);
- walk = walk.parent;
- } while (walk);
- }
-
- return tree;
- },
-
- addNodeToView = function (tor, parent, sub, i) {
- var row;
- row = new FileRow(tor, sub.depth, sub.name, sub.file_indices, i%2);
- data.file_rows.push(row);
- parent.appendChild(row.getElement());
- $(row).bind('wantedToggled',onFileWantedToggled);
- $(row).bind('priorityToggled',onFilePriorityToggled);
- $(row).bind('nameClicked',onNameClicked);
- },
-
- addSubtreeToView = function (tor, parent, sub, i) {
- var key, div;
- div = document.createElement('div');
- if (sub.parent)
- addNodeToView (tor, div, sub, i++);
- if (sub.children)
- for (key in sub.children)
- i = addSubtreeToView (tor, div, sub.children[key]);
- parent.appendChild(div);
- return i;
- },
-
- updateFilesPage = function() {
- var i, n, tor, fragment, tree,
- file_list = data.elements.file_list,
- torrents = data.torrents;
-
- // only show one torrent at a time
- if (torrents.length !== 1) {
- clearFileList();
- return;
- }
-
- tor = torrents[0];
- n = tor ? tor.getFileCount() : 0;
- if (tor!=data.file_torrent || n!=data.file_torrent_n) {
- // rebuild the file list...
- clearFileList();
- data.file_torrent = tor;
- data.file_torrent_n = n;
- data.file_rows = [ ];
- fragment = document.createDocumentFragment();
- tree = createFileTreeModel (tor);
- addSubtreeToView (tor, fragment, tree, 0);
- file_list.appendChild (fragment);
- } else {
- // ...refresh the already-existing file list
- for (i=0, n=data.file_rows.length; i<n; ++i)
- data.file_rows[i].refresh();
- }
- },
-
- /****
- ***** PEERS PAGE
- ****/
-
- updatePeersPage = function() {
- var i, k, tor, peers, peer, parity,
- html = [],
- fmt = Transmission.fmt,
- peers_list = data.elements.peers_list,
- torrents = data.torrents;
-
- for (k=0; tor=torrents[k]; ++k)
- {
- peers = tor.getPeers();
- html.push('<div class="inspector_group">');
- if (torrents.length > 1) {
- html.push('<div class="inspector_torrent_label">', sanitizeText(tor.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 (i=0; peer=peers[i]; ++i) {
- parity = (i%2) ? 'odd' : 'even';
- html.push('<tr class="inspector_peer_entry ', parity, '">',
- '<td>', (peer.isEncrypted ? '<div class="encrypted-peer-cell" title="Encrypted Connection">'
- : '<div class="unencrypted-peer-cell">'), '</div>', '</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>', sanitizeText(peer.address), '</td>',
- '<td class="clientCol">', sanitizeText(peer.clientName), '</td>',
- '</tr>');
- }
- html.push('</table></div>');
- }
-
- setInnerHTML(peers_list, html.join(''));
- },
-
- /****
- ***** TRACKERS PAGE
- ****/
-
- getAnnounceState = function(tracker) {
- var timeUntilAnnounce, s = '';
- switch (tracker.announceState) {
- case Torrent._TrackerActive:
- s = 'Announce in progress';
- break;
- case Torrent._TrackerWaiting:
- timeUntilAnnounce = tracker.nextAnnounceTime - ((new Date()).getTime() / 1000);
- if (timeUntilAnnounce < 0) {
- timeUntilAnnounce = 0;
- }
- s = 'Next announce in ' + Transmission.fmt.timeInterval(timeUntilAnnounce);
- break;
- case Torrent._TrackerQueued:
- s = 'Announce is queued';
- break;
- case Torrent._TrackerInactive:
- s = tracker.isBackup ?
- 'Tracker will be used as a backup' :
- 'Announce not scheduled';
- break;
- default:
- s = 'unknown announce state: ' + tracker.announceState;
- }
- return s;
- },
-
- lastAnnounceStatus = function(tracker) {
-
- var lastAnnounceLabel = 'Last Announce',
- lastAnnounce = [ 'N/A' ],
- lastAnnounceTime;
-
- if (tracker.hasAnnounced) {
- lastAnnounceTime = Transmission.fmt.timestamp(tracker.lastAnnounceTime);
- if (tracker.lastAnnounceSucceeded) {
- lastAnnounce = [ lastAnnounceTime, ' (got ', Transmission.fmt.countString('peer','peers',tracker.lastAnnouncePeerCount), ')' ];
- } else {
- lastAnnounceLabel = 'Announce error';
- lastAnnounce = [ (tracker.lastAnnounceResult ? (tracker.lastAnnounceResult + ' - ') : ''), lastAnnounceTime ];
- }
- }
- return { 'label':lastAnnounceLabel, 'value':lastAnnounce.join('') };
- },
-
- lastScrapeStatus = function(tracker) {
-
- var lastScrapeLabel = 'Last Scrape',
- lastScrape = 'N/A',
- lastScrapeTime;
-
- if (tracker.hasScraped) {
- 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};
- },
-
- updateTrackersPage = function() {
- var i, j, tier, tracker, trackers, tor,
- html, parity, lastAnnounceStatusHash,
- announceState, lastScrapeStatusHash,
- na = 'N/A',
- trackers_list = data.elements.trackers_list,
- torrents = data.torrents;
-
- // By building up the HTML as as string, then have the browser
- // turn this into a DOM tree, this is a fast operation.
- html = [];
- for (i=0; tor=torrents[i]; ++i)
- {
- html.push ('<div class="inspector_group">');
-
- if (torrents.length > 1)
- html.push('<div class="inspector_torrent_label">', sanitizeText(tor.getName()), '</div>');
-
- tier = -1;
- trackers = tor.getTrackers();
- for (j=0; 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+1, '</div>',
- '<ul class="tier_list">');
- }
-
- // Display construction
- lastAnnounceStatusHash = lastAnnounceStatus(tracker);
- announceState = getAnnounceState(tracker);
- lastScrapeStatusHash = lastScrapeStatus(tracker);
- parity = (j%2) ? 'odd' : 'even';
- html.push('<li class="inspector_tracker_entry ', parity, '"><div class="tracker_host" title="', sanitizeText(tracker.announce), '">',
- sanitizeText(tracker.host || tracker.announce), '</div>',
- '<div class="tracker_activity">',
- '<div>', lastAnnounceStatusHash['label'], ': ', sanitizeText(lastAnnounceStatusHash['value']), '</div>',
- '<div>', announceState, '</div>',
- '<div>', lastScrapeStatusHash['label'], ': ', sanitizeText(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 (trackers_list, html.join(''));
- },
-
- initialize = function (controller) {
-
- var ti = '#torrent_inspector_';
-
- data.controller = controller;
-
- $('.inspector-tab').click(onTabClicked);
-
- data.elements.info_page = $('#inspector-page-info')[0];
- data.elements.files_page = $('#inspector-page-files')[0];
- data.elements.peers_page = $('#inspector-page-peers')[0];
- data.elements.trackers_page = $('#inspector-page-trackers')[0];
-
- data.elements.file_list = $('#inspector_file_list')[0];
- data.elements.peers_list = $('#inspector_peers_list')[0];
- data.elements.trackers_list = $('#inspector_trackers_list')[0];
-
- data.elements.have_lb = $('#inspector-info-have')[0];
- data.elements.availability_lb = $('#inspector-info-availability')[0];
- data.elements.downloaded_lb = $('#inspector-info-downloaded')[0];
- data.elements.uploaded_lb = $('#inspector-info-uploaded')[0];
- data.elements.state_lb = $('#inspector-info-state')[0];
- data.elements.running_time_lb = $('#inspector-info-running-time')[0];
- data.elements.remaining_time_lb = $('#inspector-info-remaining-time')[0];
- data.elements.last_activity_lb = $('#inspector-info-last-activity')[0];
- data.elements.error_lb = $('#inspector-info-error')[0];
- data.elements.size_lb = $('#inspector-info-size')[0];
- data.elements.foldername_lb = $('#inspector-info-location')[0];
- data.elements.hash_lb = $('#inspector-info-hash')[0];
- data.elements.privacy_lb = $('#inspector-info-privacy')[0];
- data.elements.origin_lb = $('#inspector-info-origin')[0];
- data.elements.comment_lb = $('#inspector-info-comment')[0];
- data.elements.name_lb = $('#torrent_inspector_name')[0];
-
- // force initial 'N/A' updates on all the pages
- updateInspector();
- updateInfoPage();
- updatePeersPage();
- updateTrackersPage();
- updateFilesPage();
- };
-
- /****
- ***** PUBLIC FUNCTIONS
- ****/
-
- this.setTorrents = function (torrents) {
- var d = data;
-
- // update the inspector when a selected torrent's data changes.
- $(d.torrents).unbind('dataChanged.inspector');
- $(torrents).bind('dataChanged.inspector', $.proxy(updateInspector,this));
- d.torrents = torrents;
-
- // periodically ask for updates to the inspector's torrents
- clearInterval(d.refreshInterval);
- d.refreshInterval = setInterval($.proxy(refreshTorrents,this), 2000);
- refreshTorrents();
-
- // refresh the inspector's UI
- updateInspector();
- };
-
- initialize (controller);
+ var data = {
+ controller: null,
+ elements: {},
+ torrents: []
+ },
+
+ needsExtraInfo = function (torrents) {
+ var i, id, tor;
+
+ for (i = 0; tor = torrents[i]; i++)
+ if (!tor.hasExtraInfo())
+ return true;
+
+ return false;
+ },
+
+ refreshTorrents = function (callback) {
+ var fields,
+ ids = $.map(data.torrents.slice(0), function (t) {
+ return t.getId();
+ });
+
+ if (ids && ids.length) {
+ fields = ['id'].concat(Torrent.Fields.StatsExtra);
+
+ if (needsExtraInfo(data.torrents)) {
+ $.merge(fields, Torrent.Fields.InfoExtra);
+ }
+
+ data.controller.updateTorrents(ids, fields, callback);
+ }
+ },
+
+ onTabClicked = function (ev) {
+ var tab = ev.currentTarget;
+
+ if (isMobileDevice) {
+ ev.stopPropagation();
+ }
+
+ // select this tab and deselect the others
+ $(tab).addClass('selected').siblings().removeClass('selected');
+
+ // show this tab and hide the others
+ $('#' + tab.id.replace('tab', 'page')).show().siblings('.inspector-page').hide();
+
+ updateInspector();
+ },
+
+ updateInspector = function () {
+ var e = data.elements,
+ torrents = data.torrents,
+ name;
+
+ // update the name, which is shown on all the pages
+ if (!torrents || !torrents.length) {
+ name = 'No Selection';
+ } else if (torrents.length === 1) {
+ name = torrents[0].getName();
+ } else {
+ name = '' + torrents.length + ' Transfers Selected';
+ }
+ setTextContent(e.name_lb, name || na);
+
+ // update the visible page
+ if ($(e.info_page).is(':visible')) {
+ updateInfoPage();
+ } else if ($(e.peers_page).is(':visible')) {
+ updatePeersPage();
+ } else if ($(e.trackers_page).is(':visible')) {
+ updateTrackersPage();
+ } else if ($(e.files_page).is(':visible')) {
+ updateFilesPage();
+ }
+ },
+
+ /****
+ ***** GENERAL INFO PAGE
+ ****/
+
+ updateInfoPage = function () {
+ var torrents = data.torrents,
+ e = data.elements,
+ fmt = Transmission.fmt,
+ none = 'None',
+ mixed = 'Mixed',
+ unknown = 'Unknown',
+ isMixed, allPaused, allFinished,
+ str,
+ baseline, it, s, i, t,
+ sizeWhenDone = 0,
+ leftUntilDone = 0,
+ available = 0,
+ haveVerified = 0,
+ haveUnverified = 0,
+ verifiedPieces = 0,
+ stateString,
+ latest,
+ pieces,
+ size,
+ pieceSize,
+ creator, mixed_creator,
+ date, mixed_date,
+ v, u, f, d, pct,
+ uri,
+ now = Date.now();
+
+ //
+ // state_lb
+ //
+
+ if (torrents.length < 1) {
+ str = none;
+ } else {
+ isMixed = false;
+ allPaused = true;
+ allFinished = true;
+
+ baseline = torrents[0].getStatus();
+ for (i = 0; t = torrents[i]; ++i) {
+ it = t.getStatus();
+ if (it != baseline) {
+ isMixed = true;
+ }
+ if (!t.isStopped()) {
+ allPaused = allFinished = false;
+ }
+ if (!t.isFinished()) {
+ allFinished = false;
+ }
+ }
+ if (isMixed) {
+ str = mixed;
+ } else if (allFinished) {
+ str = 'Finished';
+ } else if (allPaused) {
+ str = 'Paused';
+ } else {
+ str = torrents[0].getStateString();
+ }
+ }
+ setTextContent(e.state_lb, str);
+ stateString = str;
+
+ //
+ // have_lb
+ //
+
+ if (torrents.length < 1)
+ str = none;
+ else {
+ baseline = torrents[0].getStatus();
+ for (i = 0; t = torrents[i]; ++i) {
+ if (!t.needsMetaData()) {
+ haveUnverified += t.getHaveUnchecked();
+ v = t.getHaveValid();
+ haveVerified += v;
+ if (t.getPieceSize()) {
+ verifiedPieces += v / t.getPieceSize();
+ }
+ sizeWhenDone += t.getSizeWhenDone();
+ leftUntilDone += t.getLeftUntilDone();
+ available += (t.getHave()) + t.getDesiredAvailable();
+ }
+ }
+
+ d = 100.0 * (sizeWhenDone ? (sizeWhenDone - leftUntilDone) / sizeWhenDone : 1);
+ str = fmt.percentString(d);
+
+ if (!haveUnverified && !leftUntilDone) {
+ str = fmt.size(haveVerified) + ' (100%)';
+ } else if (!haveUnverified) {
+ str = fmt.size(haveVerified) + ' of ' + fmt.size(sizeWhenDone) + ' (' + str + '%)';
+ } else {
+ str = fmt.size(haveVerified) + ' of ' + fmt.size(sizeWhenDone) + ' (' + str + '%), ' + fmt.size(haveUnverified) + ' Unverified';
+ }
+ }
+ setTextContent(e.have_lb, str);
+
+ //
+ // availability_lb
+ //
+
+ if (torrents.length < 1) {
+ str = none;
+ } else if (sizeWhenDone == 0) {
+ str = none;
+ } else {
+ str = '' + fmt.percentString((100.0 * available) / sizeWhenDone) + '%';
+ };
+ setTextContent(e.availability_lb, str);
+
+ //
+ // downloaded_lb
+ //
+
+ if (torrents.length < 1) {
+ str = none;
+ } else {
+ d = f = 0;
+ for (i = 0; t = torrents[i]; ++i) {
+ d += t.getDownloadedEver();
+ f += t.getFailedEver();
+ };
+ if (f) {
+ str = fmt.size(d) + ' (' + fmt.size(f) + ' corrupt)';
+ } else {
+ str = fmt.size(d);
+ };
+ };
+ setTextContent(e.downloaded_lb, str);
+
+ //
+ // uploaded_lb
+ //
+
+ if (torrents.length < 1) {
+ str = none;
+ } else {
+ d = u = 0;
+ if (torrents.length == 1) {
+ d = torrents[0].getDownloadedEver();
+ u = torrents[0].getUploadedEver();
+
+ if (d == 0) {
+ d = torrents[0].getHaveValid();
+ };
+ } else {
+ for (i = 0; t = torrents[i]; ++i) {
+ d += t.getDownloadedEver();
+ u += t.getUploadedEver();
+ };
+ };
+ str = fmt.size(u) + ' (Ratio: ' + fmt.ratioString(Math.ratio(u, d)) + ')';
+ };
+ setTextContent(e.uploaded_lb, str);
+
+ //
+ // running time
+ //
+
+ if (torrents.length < 1) {
+ str = none;
+ } else {
+ allPaused = true;
+ baseline = torrents[0].getStartDate();
+ for (i = 0; t = torrents[i]; ++i) {
+ if (baseline != t.getStartDate()) {
+ baseline = 0;
+ }
+ if (!t.isStopped()) {
+ allPaused = false;
+ }
+ }
+ if (allPaused) {
+ str = stateString; // paused || finished}
+ } else if (!baseline) {
+ str = mixed;
+ } else {
+ str = fmt.timeInterval(now / 1000 - baseline);
+ }
+ };
+
+ setTextContent(e.running_time_lb, str);
+
+ //
+ // remaining time
+ //
+
+ str = '';
+ if (torrents.length < 1) {
+ str = none;
+ } else {
+ baseline = torrents[0].getETA();
+ for (i = 0; t = torrents[i]; ++i) {
+ if (baseline != t.getETA()) {
+ str = mixed;
+ break;
+ }
+ }
+ }
+ if (!str.length) {
+ if (baseline < 0) {
+ str = unknown;
+ } else {
+ str = fmt.timeInterval(baseline);
+ }
+ }
+ setTextContent(e.remaining_time_lb, str);
+
+ //
+ // last activity
+ //
+
+ latest = -1;
+ if (torrents.length < 1) {
+ str = none;
+ } else {
+ baseline = torrents[0].getLastActivity();
+ for (i = 0; t = torrents[i]; ++i) {
+ d = t.getLastActivity();
+ if (latest < d) {
+ latest = d;
+ };
+ };
+ d = now / 1000 - latest; // seconds since last activity
+ if (d < 0) {
+ str = none;
+ } else if (d < 5) {
+ str = 'Active now';
+ } else {
+ str = fmt.timeInterval(d) + ' ago';
+ };
+ };
+ setTextContent(e.last_activity_lb, str);
+
+ //
+ // error
+ //
+
+ if (torrents.length < 1) {
+ str = none;
+ } else {
+ str = torrents[0].getErrorString();
+ for (i = 0; t = torrents[i]; ++i) {
+ if (str != t.getErrorString()) {
+ str = mixed;
+ break;
+ };
+ };
+ };
+ setTextContent(e.error_lb, str || none);
+
+ //
+ // size
+ //
+
+ if (torrents.length < 1) {
+ {
+ str = none;
+ };
+ } else {
+ pieces = 0;
+ size = 0;
+ pieceSize = torrents[0].getPieceSize();
+ for (i = 0; t = torrents[i]; ++i) {
+ pieces += t.getPieceCount();
+ size += t.getTotalSize();
+ if (pieceSize != t.getPieceSize()) {
+ pieceSize = 0;
+ }
+ };
+ if (!size) {
+ str = none;
+ } else if (pieceSize > 0) {
+ str = fmt.size(size) + ' (' + pieces.toStringWithCommas() + ' pieces @ ' + fmt.mem(pieceSize) + ')';
+ } else {
+ str = fmt.size(size) + ' (' + pieces.toStringWithCommas() + ' pieces)';
+ };
+ };
+ setTextContent(e.size_lb, str);
+
+ //
+ // hash
+ //
+
+ if (torrents.length < 1) {
+ str = none;
+ } else {
+ str = torrents[0].getHashString();
+ for (i = 0; t = torrents[i]; ++i) {
+ if (str != t.getHashString()) {
+ str = mixed;
+ break;
+ };
+ };
+ };
+ setTextContent(e.hash_lb, str);
+
+ //
+ // privacy
+ //
+
+ if (torrents.length < 1) {
+ str = none;
+ } else {
+ baseline = torrents[0].getPrivateFlag();
+ str = baseline ? 'Private to this tracker -- DHT and PEX disabled' : 'Public torrent';
+ for (i = 0; t = torrents[i]; ++i) {
+ if (baseline != t.getPrivateFlag()) {
+ str = mixed;
+ break;
+ };
+ };
+ };
+ setTextContent(e.privacy_lb, str);
+
+ //
+ // comment
+ //
+
+ if (torrents.length < 1) {
+ str = none;
+ } else {
+ str = torrents[0].getComment();
+ for (i = 0; t = torrents[i]; ++i) {
+ if (str != t.getComment()) {
+ str = mixed;
+ break;
+ };
+ };
+ };
+ if (!str) {
+ str = none;
+ }
+ uri = parseUri(str);
+ if (uri.protocol == 'http' || uri.protocol == 'https') {
+ str = encodeURI(str);
+ setInnerHTML(e.comment_lb, '<a href="' + str + '" target="_blank" >' + str + '</a>');
+ } else {
+ setTextContent(e.comment_lb, str);
+ };
+
+ //
+ // origin
+ //
+
+ if (torrents.length < 1) {
+ str = none;
+ } else {
+ mixed_creator = false;
+ mixed_date = false;
+ creator = torrents[0].getCreator();
+ date = torrents[0].getDateCreated();
+ for (i = 0; t = torrents[i]; ++i) {
+ if (creator != t.getCreator()) {
+ mixed_creator = true;
+ };
+ if (date != t.getDateCreated()) {
+ mixed_date = true;
+ };
+ };
+ var empty_creator = !creator || !creator.length;
+ var empty_date = !date;
+ if (mixed_creator || mixed_date) {
+ str = mixed;
+ } else if (empty_creator && empty_date) {
+ str = unknown;
+ } else if (empty_date && !empty_creator) {
+ str = 'Created by ' + creator;
+ } else if (empty_creator && !empty_date) {
+ str = 'Created on ' + (new Date(date * 1000)).toDateString();
+ } else {
+ str = 'Created by ' + creator + ' on ' + (new Date(date * 1000)).toDateString();
+ };
+ };
+ setTextContent(e.origin_lb, str);
+
+ //
+ // foldername
+ //
+
+ if (torrents.length < 1) {
+ str = none;
+ } else {
+ str = torrents[0].getDownloadDir();
+ for (i = 0; t = torrents[i]; ++i) {
+ if (str != t.getDownloadDir()) {
+ str = mixed;
+ break;
+ };
+ };
+ };
+ setTextContent(e.foldername_lb, str);
+ },
+
+ /****
+ ***** FILES PAGE
+ ****/
+
+ changeFileCommand = function (fileIndices, command) {
+ var torrentId = data.file_torrent.getId();
+ data.controller.changeFileCommand(torrentId, fileIndices, command);
+ },
+
+ onFileWantedToggled = function (ev, fileIndices, want) {
+ changeFileCommand(fileIndices, want ? 'files-wanted' : 'files-unwanted');
+ },
+
+ onFilePriorityToggled = function (ev, fileIndices, priority) {
+ var command;
+ switch (priority) {
+ case -1:
+ command = 'priority-low';
+ break;
+ case 1:
+ command = 'priority-high';
+ break;
+ default:
+ command = 'priority-normal';
+ break;
+ }
+ changeFileCommand(fileIndices, command);
+ },
+
+ onNameClicked = function (ev, fileRow, fileIndices) {
+ $(fileRow.getElement()).siblings().slideToggle();
+ },
+
+ clearFileList = function () {
+ $(data.elements.file_list).empty();
+ delete data.file_torrent;
+ delete data.file_torrent_n;
+ delete data.file_rows;
+ },
+
+ createFileTreeModel = function (tor) {
+ var i, j, n, name, tokens, walk, tree, token, sub,
+ leaves = [],
+ tree = {
+ children: {},
+ file_indices: []
+ };
+
+ n = tor.getFileCount();
+ for (i = 0; i < n; ++i) {
+ name = tor.getFile(i).name;
+ tokens = name.split('/');
+ walk = tree;
+ for (j = 0; j < tokens.length; ++j) {
+ token = tokens[j];
+ sub = walk.children[token];
+ if (!sub) {
+ walk.children[token] = sub = {
+ name: token,
+ parent: walk,
+ children: {},
+ file_indices: [],
+ depth: j
+ };
+ }
+ walk = sub;
+ }
+ walk.file_index = i;
+ delete walk.children;
+ leaves.push(walk);
+ }
+
+ for (i = 0; i < leaves.length; ++i) {
+ walk = leaves[i];
+ j = walk.file_index;
+ do {
+ walk.file_indices.push(j);
+ walk = walk.parent;
+ } while (walk);
+ }
+
+ return tree;
+ },
+
+ addNodeToView = function (tor, parent, sub, i) {
+ var row;
+ row = new FileRow(tor, sub.depth, sub.name, sub.file_indices, i % 2);
+ data.file_rows.push(row);
+ parent.appendChild(row.getElement());
+ $(row).bind('wantedToggled', onFileWantedToggled);
+ $(row).bind('priorityToggled', onFilePriorityToggled);
+ $(row).bind('nameClicked', onNameClicked);
+ },
+
+ addSubtreeToView = function (tor, parent, sub, i) {
+ var key, div;
+ div = document.createElement('div');
+ if (sub.parent) {
+ addNodeToView(tor, div, sub, i++);
+ }
+ if (sub.children) {
+ for (key in sub.children) {
+ i = addSubtreeToView(tor, div, sub.children[key]);
+ }
+ }
+ parent.appendChild(div);
+ return i;
+ },
+
+ updateFilesPage = function () {
+ var i, n, tor, fragment, tree,
+ file_list = data.elements.file_list,
+ torrents = data.torrents;
+
+ // only show one torrent at a time
+ if (torrents.length !== 1) {
+ clearFileList();
+ return;
+ }
+
+ tor = torrents[0];
+ n = tor ? tor.getFileCount() : 0;
+ if (tor != data.file_torrent || n != data.file_torrent_n) {
+ // rebuild the file list...
+ clearFileList();
+ data.file_torrent = tor;
+ data.file_torrent_n = n;
+ data.file_rows = [];
+ fragment = document.createDocumentFragment();
+ tree = createFileTreeModel(tor);
+ addSubtreeToView(tor, fragment, tree, 0);
+ file_list.appendChild(fragment);
+ } else {
+ // ...refresh the already-existing file list
+ for (i = 0, n = data.file_rows.length; i < n; ++i)
+ data.file_rows[i].refresh();
+ }
+ },
+
+ /****
+ ***** PEERS PAGE
+ ****/
+
+ updatePeersPage = function () {
+ var i, k, tor, peers, peer, parity,
+ html = [],
+ fmt = Transmission.fmt,
+ peers_list = data.elements.peers_list,
+ torrents = data.torrents;
+
+ for (k = 0; tor = torrents[k]; ++k) {
+ peers = tor.getPeers();
+ html.push('<div class="inspector_group">');
+ if (torrents.length > 1) {
+ html.push('<div class="inspector_torrent_label">', sanitizeText(tor.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 (i = 0; peer = peers[i]; ++i) {
+ parity = (i % 2) ? 'odd' : 'even';
+ html.push('<tr class="inspector_peer_entry ', parity, '">',
+ '<td>', (peer.isEncrypted ? '<div class="encrypted-peer-cell" title="Encrypted Connection">' : '<div class="unencrypted-peer-cell">'), '</div>', '</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>', sanitizeText(peer.address), '</td>',
+ '<td class="clientCol">', sanitizeText(peer.clientName), '</td>',
+ '</tr>');
+ }
+ html.push('</table></div>');
+ }
+
+ setInnerHTML(peers_list, html.join(''));
+ },
+
+ /****
+ ***** TRACKERS PAGE
+ ****/
+
+ getAnnounceState = function (tracker) {
+ var timeUntilAnnounce, s = '';
+ switch (tracker.announceState) {
+ case Torrent._TrackerActive:
+ s = 'Announce in progress';
+ break;
+ case Torrent._TrackerWaiting:
+ timeUntilAnnounce = tracker.nextAnnounceTime - ((new Date()).getTime() / 1000);
+ if (timeUntilAnnounce < 0) {
+ timeUntilAnnounce = 0;
+ }
+ s = 'Next announce in ' + Transmission.fmt.timeInterval(timeUntilAnnounce);
+ break;
+ case Torrent._TrackerQueued:
+ s = 'Announce is queued';
+ break;
+ case Torrent._TrackerInactive:
+ s = tracker.isBackup ?
+ 'Tracker will be used as a backup' :
+ 'Announce not scheduled';
+ break;
+ default:
+ s = 'unknown announce state: ' + tracker.announceState;
+ }
+ return s;
+ },
+
+ lastAnnounceStatus = function (tracker) {
+
+ var lastAnnounceLabel = 'Last Announce',
+ lastAnnounce = ['N/A'],
+ lastAnnounceTime;
+
+ if (tracker.hasAnnounced) {
+ lastAnnounceTime = Transmission.fmt.timestamp(tracker.lastAnnounceTime);
+ if (tracker.lastAnnounceSucceeded) {
+ lastAnnounce = [lastAnnounceTime, ' (got ', Transmission.fmt.countString('peer', 'peers', tracker.lastAnnouncePeerCount), ')'];
+ } else {
+ lastAnnounceLabel = 'Announce error';
+ lastAnnounce = [(tracker.lastAnnounceResult ? (tracker.lastAnnounceResult + ' - ') : ''), lastAnnounceTime];
+ }
+ }
+ return {
+ 'label': lastAnnounceLabel,
+ 'value': lastAnnounce.join('')
+ };
+ },
+
+ lastScrapeStatus = function (tracker) {
+
+ var lastScrapeLabel = 'Last Scrape',
+ lastScrape = 'N/A',
+ lastScrapeTime;
+
+ if (tracker.hasScraped) {
+ 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
+ };
+ },
+
+ updateTrackersPage = function () {
+ var i, j, tier, tracker, trackers, tor,
+ html, parity, lastAnnounceStatusHash,
+ announceState, lastScrapeStatusHash,
+ na = 'N/A',
+ trackers_list = data.elements.trackers_list,
+ torrents = data.torrents;
+
+ // By building up the HTML as as string, then have the browser
+ // turn this into a DOM tree, this is a fast operation.
+ html = [];
+ for (i = 0; tor = torrents[i]; ++i) {
+ html.push('<div class="inspector_group">');
+
+ if (torrents.length > 1) {
- html.push('<div class="inspector_torrent_label">', tor.getName(), '</div>');
++ html.push('<div class="inspector_torrent_label">', sanitizeText(tor.getName()), '</div>');
+ }
+
+ tier = -1;
+ trackers = tor.getTrackers();
+ for (j = 0; 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 + 1, '</div>',
+ '<ul class="tier_list">');
+ }
+
+ // Display construction
+ lastAnnounceStatusHash = lastAnnounceStatus(tracker);
+ announceState = getAnnounceState(tracker);
+ lastScrapeStatusHash = lastScrapeStatus(tracker);
+ parity = (j % 2) ? 'odd' : 'even';
+ html.push('<li class="inspector_tracker_entry ', parity, '"><div class="tracker_host" title="', sanitizeText(tracker.announce), '">',
+ sanitizeText(tracker.host || tracker.announce), '</div>',
+ '<div class="tracker_activity">',
- '<div>', lastAnnounceStatusHash['label'], ': ', lastAnnounceStatusHash['value'], '</div>',
++ '<div>', lastAnnounceStatusHash['label'], ': ', sanitizeText(lastAnnounceStatusHash['value']), '</div>',
+ '<div>', announceState, '</div>',
- '<div>', lastScrapeStatusHash['label'], ': ', lastScrapeStatusHash['value'], '</div>',
++ '<div>', lastScrapeStatusHash['label'], ': ', sanitizeText(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(trackers_list, html.join(''));
+ },
+
+ initialize = function (controller) {
+
+ var ti = '#torrent_inspector_';
+
+ data.controller = controller;
+
+ $('.inspector-tab').click(onTabClicked);
+
+ data.elements.info_page = $('#inspector-page-info')[0];
+ data.elements.files_page = $('#inspector-page-files')[0];
+ data.elements.peers_page = $('#inspector-page-peers')[0];
+ data.elements.trackers_page = $('#inspector-page-trackers')[0];
+
+ data.elements.file_list = $('#inspector_file_list')[0];
+ data.elements.peers_list = $('#inspector_peers_list')[0];
+ data.elements.trackers_list = $('#inspector_trackers_list')[0];
+
+ data.elements.have_lb = $('#inspector-info-have')[0];
+ data.elements.availability_lb = $('#inspector-info-availability')[0];
+ data.elements.downloaded_lb = $('#inspector-info-downloaded')[0];
+ data.elements.uploaded_lb = $('#inspector-info-uploaded')[0];
+ data.elements.state_lb = $('#inspector-info-state')[0];
+ data.elements.running_time_lb = $('#inspector-info-running-time')[0];
+ data.elements.remaining_time_lb = $('#inspector-info-remaining-time')[0];
+ data.elements.last_activity_lb = $('#inspector-info-last-activity')[0];
+ data.elements.error_lb = $('#inspector-info-error')[0];
+ data.elements.size_lb = $('#inspector-info-size')[0];
+ data.elements.foldername_lb = $('#inspector-info-location')[0];
+ data.elements.hash_lb = $('#inspector-info-hash')[0];
+ data.elements.privacy_lb = $('#inspector-info-privacy')[0];
+ data.elements.origin_lb = $('#inspector-info-origin')[0];
+ data.elements.comment_lb = $('#inspector-info-comment')[0];
+ data.elements.name_lb = $('#torrent_inspector_name')[0];
+
+ // force initial 'N/A' updates on all the pages
+ updateInspector();
+ updateInfoPage();
+ updatePeersPage();
+ updateTrackersPage();
+ updateFilesPage();
+ };
+
+ /****
+ ***** PUBLIC FUNCTIONS
+ ****/
+
+ this.setTorrents = function (torrents) {
+ var d = data,
+ that = this;
+
+ // update the inspector when a selected torrent's data changes.
+ $(d.torrents).unbind('dataChanged.inspector');
+ $(torrents).bind('dataChanged.inspector', $.proxy(updateInspector, this));
+ d.torrents = torrents;
+
+ // periodically ask for updates to the inspector's torrents
+ clearTimeout(d.refreshTimeout);
+
+ function callback() {
+ refreshTorrents(rescheduleTimeout);
+ }
+
+ function rescheduleTimeout() {
+ d.refreshTimeout = setTimeout(callback, 2000);
+ }
+
+ rescheduleTimeout();
+ refreshTorrents();
+
+ // refresh the inspector's UI
+ updateInspector();
+ };
+
+ initialize(controller);
};