/* * Copyright © Jordan Lee, Dave Perrett and Malcolm Jarvis * This code is licensed under the GPL version 2. * For details, see http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * * Class Torrent */ function Torrent(data) { this.initialize(data); } /*** **** **** Constants **** ***/ // Torrent.fields.status Torrent._StatusStopped = 0; Torrent._StatusCheckWait = 1; Torrent._StatusCheck = 2; Torrent._StatusDownloadWait = 3; Torrent._StatusDownload = 4; Torrent._StatusSeedWait = 5; Torrent._StatusSeed = 6; // Torrent.fields.seedRatioMode Torrent._RatioUseGlobal = 0; Torrent._RatioUseLocal = 1; Torrent._RatioUnlimited = 2; // Torrent.fields.error Torrent._ErrNone = 0; Torrent._ErrTrackerWarning = 1; Torrent._ErrTrackerError = 2; Torrent._ErrLocalError = 3; // TrackerStats' announceState Torrent._TrackerInactive = 0; Torrent._TrackerWaiting = 1; 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' ]; /*** **** **** Methods **** ***/ 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); }, 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; }, initMetaData: function(data) { var f = this.fields; 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 }; } } return changed; }, refreshMetaData: function(data) { var changed = this.initMetaData(data); if (changed) this.fireDataChanged(); return changed; }, refresh: 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; } } } 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 0 || this.getPeersSendingToUs() > 0 || this.getWebseedsSendingToUs() > 0 || this.isChecking(); case Prefs._FilterSeeding: return (s === Torrent._StatusSeed) || (s === Torrent._StatusSeedWait); case Prefs._FilterDownloading: return (s === Torrent._StatusDownload) || (s === Torrent._StatusDownloadWait); case Prefs._FilterPaused: return this.isStopped(); case Prefs._FilterFinished: return this.isFinished(); default: return true; } }, /** * @param filter one of Prefs._Filter* * @param search substring to look for, or null * @return true if it passes the test, false if it fails */ test: function(state, search, tracker) { // flter by state... var pass = this.testState(state); // maybe filter by text... if (pass && search && search.length) pass = this.getCollatedName().indexOf(search.toLowerCase()) !== -1; // maybe filter by tracker... if (pass && tracker && tracker.length) pass = this.getCollatedTrackers().indexOf(tracker) !== -1; return pass; } }; /*** **** **** SORTING **** ***/ Torrent.compareById = function(ta, tb) { return ta.getId() - tb.getId(); }; Torrent.compareByName = function(ta, tb) { return ta.getCollatedName().compareTo(tb.getCollatedName()) || Torrent.compareById(ta, tb); }; Torrent.compareByQueue = function(ta, tb) { return ta.getQueuePosition() - tb.getQueuePosition(); }; Torrent.compareByAge = function(ta, tb) { var a = ta.getDateAdded(); var b = tb.getDateAdded(); return (b - a) || Torrent.compareByQueue(ta, tb); }; Torrent.compareByState = function(ta, tb) { var a = ta.getStatus(); var b = tb.getStatus(); return (b - a) || Torrent.compareByQueue(ta, tb); }; Torrent.compareByActivity = function(ta, tb) { var a = ta.getActivity(); var b = tb.getActivity(); return (b - a) || Torrent.compareByState(ta, tb); }; Torrent.compareByRatio = function(ta, tb) { var a = ta.getUploadRatio(); var b = tb.getUploadRatio(); if (a < b) return 1; if (a > b) return -1; return Torrent.compareByState(ta, tb); }; Torrent.compareByProgress = function(ta, tb) { var a = ta.getPercentDone(); var b = tb.getPercentDone(); return (a - b) || Torrent.compareByRatio(ta, tb); }; /** * @param torrents an array of Torrent objects * @param sortMethod one of Prefs._SortBy* * @param sortDirection Prefs._SortAscending or Prefs._SortDescending */ Torrent.sortTorrents = function(torrents, sortMethod, sortDirection) { switch(sortMethod) { case Prefs._SortByActivity: torrents.sort(this.compareByActivity); break; case Prefs._SortByAge: torrents.sort(this.compareByAge); break; case Prefs._SortByQueue: torrents.sort(this.compareByQueue); break; case Prefs._SortByProgress: torrents.sort(this.compareByProgress); break; case Prefs._SortByState: torrents.sort(this.compareByState); break; case Prefs._SortByRatio: torrents.sort(this.compareByRatio); break; default: torrents.sort(this.compareByName); break; } if (sortDirection === Prefs._SortDescending) torrents.reverse(); return torrents; };