transmission/web/javascript/torrent.js

406 lines
12 KiB
JavaScript
Raw Normal View History

/*
* 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(controller, data)
{
this.initialize(controller, 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(controller, 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) {
result = []
for(var i=0, tracker; tracker=trackerStats[i]; ++i) {
tier = result[tracker.tier] || []
tier.push(tracker)
result[tracker.tier] = tier
}
return result
},
initMetaData: function(data) {
var f = this.fields
// populate the metadata fields
for(var i=0, key; key=Torrent._MetaDataFields[i]; ++i) {
if(key in data) {
f[key] = data[key]
if(key === 'name')
f.collatedName = data.name.toLowerCase()
}
}
// populate the files array
if(data.files) {
for(var i=0, row; row=data.files[i]; ++i) {
this._files[i] = {
'index': i,
'torrent': this,
'length': row.length,
'name': row.name
}
}
}
},
refreshMetaData: function(data)
2009-05-25 13:31:03 +00:00
{
this.initMetaData(data)
this.fireDataChanged()
},
2010-06-21 13:14:33 +00:00
refresh: function(data)
{
// FIXME: unnecessary coupling... this should be handled by transmission.js
if(this.needsMetaData() && (data.metadataPercentComplete >= 1))
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)
f[key] = data[key]
this._trackerStats = this.buildTrackerStats(data.trackerStats)
if (data.fileStats)
this.refreshFiles(data)
this.fireDataChanged()
},
refreshFiles: function(data) {
for(var i=0; i<data.fileStats.length; ++i) {
var src = data.fileStats[i]
var tgt = this._files[i]
if(!tgt)
tgt = this._files[i] = { }
tgt.wanted = src.wanted
tgt.priority = src.priority
tgt.bytesCompleted = src.bytesCompleted
2009-05-22 22:45:09 +00:00
}
},
fireDataChanged: function()
{
$(this).trigger('dataChanged',[])
},
/****
*****
****/
// simple accessors
getCollatedName: function() { return this.fields.collatedName },
getComment: function() { return this.fields.comment },
getCreator: function() { return this.fields.creator },
getDateAdded: function() { return this.fields.addedDate },
getDateCreated: function() { return this.fields.dateCreated },
getDesiredAvailable: function() { return this.fields.desiredAvailable },
getDownloadDir: function() { return this.fields.downloadDir },
getDownloadSpeed: function() { return this.fields.rateDownload },
getDownloadedEver: function() { return this.fields.downloadedEver },
getError: function() { return this.fields.error },
getErrorString: function() { return this.fields.errorString },
getETA: function() { return this.fields.eta },
getHashString: function() { return this.fields.hashString },
getHaveValid: function() { return this.fields.haveValid },
getHave: function() { return this.getHaveValid() + this.fields.haveUnchecked },
getId: function() { return this.fields.id },
getLeftUntilDone: function() { return this.fields.leftUntilDone },
getMetadataPercentComplete: function() { return this.fields.metadataPercentComplete },
getName: function() { return this.fields.name },
getPeers: function() { return this.fields.peers },
getPeersConnected: function() { return this.fields.peersConnected },
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 },
getRecheckProgress: function() { return this.fields.recheckProgress },
getSeedRatioLimit: function() { return this.fields.seedRatioLimit },
getSeedRatioMode: function() { return this.fields.seedRatioMode },
getSizeWhenDone: function() { return this.fields.sizeWhenDone },
getStatus: function() { return this.fields.status },
getTotalSize: function() { return this.fields.totalSize },
getUploadSpeed: function() { return this.fields.rateUpload },
getUploadRatio: function() { return this.fields.uploadRatio },
getUploadedEver: function() { return this.fields.uploadedEver },
getWebseedsSendingToUs: function() { return this.fields.webseedsSendingToUs },
isFinished: function() { return this.fields.isFinished },
// derived accessors
isSeeding: function() { return this.getStatus() === Torrent._StatusSeed },
isStopped: function() { return this.getStatus() === Torrent._StatusStopped },
isChecking: function() { return this.getStatus() === Torrent._StatusCheck },
isDownloading: function() { return this.getStatus() === Torrent._StatusDownload },
isDone: function() { return this.getLeftUntilDone() < 1 },
needsMetaData: function(){ return this.getMetadataPercentComplete() < 1 },
getActivity: function() { return this.getDownloadSpeed() + this.getUploadSpeed() },
getPercentDoneStr: function() { return Transmission.fmt.percentString(100*this.getPercentDone()) },
getPercentDone: function() {
var finalSize = this.getSizeWhenDone()
if(!finalSize) return 1.0
var left = this.getLeftUntilDone()
if(!left) return 1.0
return (finalSize - left) / finalSize
},
getStateString: function() {
switch(this.getStatus()) {
case Torrent._StatusStopped: return this.isFinished() ? 'Seeding complete' : 'Paused'
case Torrent._StatusCheckWait: return 'Queued for verification'
case Torrent._StatusCheck: return 'Verifying local data'
case Torrent._StatusDownloadWait: return 'Queued for download'
case Torrent._StatusDownload: return 'Downloading'
case Torrent._StatusSeedWait: return 'Queued for seeding'
case Torrent._StatusSeed: return 'Seeding'
default: return 'error'
}
},
trackerStats: function() { return this._trackerStats },
seedRatioLimit: function(controller){
switch(this.getSeedRatioMode()) {
case Torrent._RatioUseGlobal: return controller.seedRatioLimit()
case Torrent._RatioUseLocal: return this.getSeedRatioLimit()
default: return -1
}
},
getErrorMessage: function() {
var str = this.getErrorString()
switch(this.getError()) {
case Torrent._ErrTrackerWarning:
return 'Tracker returned a warning: ' + str
case Torrent._ErrTrackerError:
return 'Tracker returned an error: ' + str
case Torrent._ErrLocalError:
return 'Error: ' + str
default:
return null
}
},
/****
*****
****/
/**
* @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(filter, search)
{
var pass = false
var s = this.getStatus()
2010-06-21 13:14:33 +00:00
switch(filter)
{
2010-06-17 04:38:03 +00:00
case Prefs._FilterActive:
pass = this.getPeersGettingFromUs() > 0
|| this.getPeersSendingToUs() > 0
|| this.getWebseedsSendingToUs() > 0
|| this.isChecking()
break
case Prefs._FilterSeeding:
pass = (s === Torrent._StatusSeed) || (s === Torrent._StatusSeedWait)
break
case Prefs._FilterDownloading:
pass = (s === Torrent._StatusDownload) || (s === Torrent._StatusDownloadWait)
break
case Prefs._FilterPaused:
pass = this.isStopped()
break
case Prefs._FilterFinished:
pass = this.isFinished()
break
default:
pass = true
break
}
2010-06-21 13:14:33 +00:00
if(!pass)
return false
2010-06-21 13:14:33 +00:00
if(!search || !search.length)
return pass
2010-06-21 13:14:33 +00:00
return this.getCollatedName().indexOf(search.toLowerCase()) !== -1
}
}
/***
****
**** 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 (a - b) || Torrent.compareByState(ta, tb)
}
Torrent.compareByRatio = function(a, b)
{
var a = Math.ratio(ta.getUploadedEver(), ta.getDownloadedEver())
var b = Math.ratio(tb.getUploadedEver(), tb.getDownloadedEver())
return (a - b) || 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._SortByName:
torrents.sort(this.compareByName)
break
case Prefs._SortByRatio:
torrents.sort(this.compareByRatio)
break
default:
console.warn("unknown sort method: " + sortMethod)
break
}
2010-06-21 13:14:33 +00:00
if(sortDirection === Prefs._SortDescending)
torrents.reverse()
2010-06-21 13:14:33 +00:00
return torrents
}