mirror of
https://github.com/taobataoma/meanTorrent.git
synced 2026-01-16 12:22:26 +01:00
215 lines
4.8 KiB
JavaScript
215 lines
4.8 KiB
JavaScript
'use strict';
|
|
|
|
function now() {
|
|
return Math.floor(new Date().getTime() / 1000);
|
|
}
|
|
|
|
const EVENT_NONE = 0;
|
|
const EVENT_COMPLETED = 1;
|
|
const EVENT_STARTED = 2;
|
|
const EVENT_STOPPED = 3;
|
|
|
|
function event(e) {
|
|
switch (e) {
|
|
case 'completed':
|
|
return EVENT_COMPLETED;
|
|
case 'started':
|
|
return EVENT_STARTED;
|
|
case 'stopped':
|
|
return EVENT_STOPPED;
|
|
}
|
|
|
|
return EVENT_NONE;
|
|
}
|
|
|
|
|
|
const PEERSTATE_SEEDER = 0;
|
|
const PEERSTATE_LEECHER = 1;
|
|
|
|
const PEER_COMPACT_SIZE = 6;
|
|
|
|
const ANNOUNCE_INTERVAL = 60;
|
|
|
|
function Peer(ip, port, left) {
|
|
if (!(this instanceof Peer))
|
|
return new Peer(ip, port, left);
|
|
|
|
this.compact = this._compact(ip, port);
|
|
|
|
this.state = (left > 0) ? PEERSTATE_LEECHER : PEERSTATE_SEEDER;
|
|
|
|
this.touch();
|
|
}
|
|
|
|
Peer.prototype = {
|
|
touch: function () {
|
|
this.lastAction = now();
|
|
},
|
|
timedOut: function (n) {
|
|
return n - this.lastAction > ANNOUNCE_INTERVAL * 2;
|
|
},
|
|
_compact: function (ip, port) {
|
|
var b = new Buffer(PEER_COMPACT_SIZE);
|
|
|
|
var parts = ip.split('.');
|
|
if (parts.length !== 4)
|
|
throw new Error('ip length error');
|
|
|
|
for (var i = 0; i < 4; i++)
|
|
b[i] = parseInt(parts[i], 10);
|
|
|
|
b[4] = (port >> 8) & 0xff;
|
|
b[5] = port & 0xff;
|
|
|
|
return b;
|
|
}
|
|
};
|
|
|
|
function File() {
|
|
if (!(this instanceof File))
|
|
return new File();
|
|
|
|
this.peerList = [];
|
|
this.peerDict = {};
|
|
|
|
this.downloads = 0;
|
|
this.seeders = 0;
|
|
this.leechers = 0;
|
|
|
|
this.lastCompact = now();
|
|
}
|
|
|
|
File.prototype = {
|
|
addPeer: function (peerId, peer, event) {
|
|
// Check if it is time to compact the peer list
|
|
var n = now();
|
|
if (this.seeders + this.leechers < this.peerList.length / 2 && this.peerList.length > 10 || (n - this.lastCompact) > ANNOUNCE_INTERVAL * 2) {
|
|
var newPeerList = [];
|
|
var i = 0;
|
|
for (var p in this.peerDict) {
|
|
if (!this.peerDict.hasOwnProperty(p))
|
|
continue;
|
|
|
|
var tmpPeer = this.peerList[this.peerDict[p]];
|
|
|
|
// Check if the peer is still alive
|
|
if (tmpPeer.timedOut(n)) {
|
|
if (tmpPeer.state === PEERSTATE_LEECHER)
|
|
this.leechers--;
|
|
else
|
|
this.seeders--;
|
|
|
|
delete this.peerDict[p];
|
|
continue;
|
|
}
|
|
|
|
newPeerList.push(tmpPeer);
|
|
this.peerDict[p] = i++;
|
|
}
|
|
|
|
this.peerList = newPeerList;
|
|
|
|
this.lastCompact = n;
|
|
}
|
|
|
|
if (event === EVENT_COMPLETED && peer.state === PEERSTATE_SEEDER)
|
|
this.downloads++;
|
|
|
|
// Check if the peer already exists
|
|
if (this.peerDict.hasOwnProperty(peerId)) {
|
|
var index = this.peerDict[peerId];
|
|
var oldPeer = this.peerList[index];
|
|
|
|
if (event === EVENT_STOPPED) {
|
|
if (oldPeer.state === PEERSTATE_LEECHER)
|
|
this.leechers--;
|
|
else
|
|
this.seeders--;
|
|
|
|
delete this.peerList[index];
|
|
delete this.peerDict[peerId];
|
|
} else {
|
|
// TODO: Should probably update compact in the old peer. So we
|
|
// handle the case if the user switched IP or Port. But we
|
|
// probably only want to do it if they differ
|
|
// oldPeer.compact = peer.compact;
|
|
|
|
if (oldPeer.state !== peer.state) {
|
|
if (peer.state === PEERSTATE_LEECHER) {
|
|
this.leechers++;
|
|
this.seeders--;
|
|
} else {
|
|
this.leechers--;
|
|
this.seeders++;
|
|
}
|
|
|
|
oldPeer.state = peer.state;
|
|
}
|
|
}
|
|
|
|
peer = oldPeer;
|
|
peer.touch();
|
|
|
|
} else if (event !== EVENT_STOPPED) {
|
|
this.peerDict[peerId] = this.peerList.length;
|
|
this.peerList.push(peer);
|
|
|
|
if (peer.state === PEERSTATE_LEECHER)
|
|
this.leechers++;
|
|
else
|
|
this.seeders++;
|
|
}
|
|
|
|
return peer;
|
|
},
|
|
writePeers: function (b, count, selfPeer) {
|
|
var c = 0;
|
|
var i = 0;
|
|
var p;
|
|
|
|
if (count > this.seeders + this.leechers) {
|
|
for (i = this.peerList.length - 1; i >= 0; i--) {
|
|
p = this.peerList[i];
|
|
if (p !== undefined && p !== selfPeer)
|
|
p.compact.copy(b, c++ * PEER_COMPACT_SIZE);
|
|
}
|
|
} else {
|
|
var m = Math.min(this.peerList.length, count);
|
|
for (i = 0; i < m; i++) {
|
|
var index = Math.floor(Math.random() * this.peerList.length);
|
|
p = this.peerList[index];
|
|
if (p !== undefined && p !== selfPeer)
|
|
p.compact.copy(b, c++ * PEER_COMPACT_SIZE);
|
|
}
|
|
}
|
|
|
|
return c * PEER_COMPACT_SIZE;
|
|
}
|
|
};
|
|
|
|
function Tracker() {
|
|
if (!(this instanceof Tracker))
|
|
return new Tracker();
|
|
|
|
this.files = {};
|
|
}
|
|
|
|
Tracker.prototype = {
|
|
getFile: function (infoHash) {
|
|
if (this.files.hasOwnProperty(infoHash))
|
|
return this.files[infoHash];
|
|
|
|
return this.addFile(infoHash);
|
|
},
|
|
addFile: function (infoHash) {
|
|
return (this.files[infoHash] = new File());
|
|
}
|
|
};
|
|
|
|
exports.PEER_COMPACT_SIZE = PEER_COMPACT_SIZE;
|
|
exports.ANNOUNCE_INTERVAL = ANNOUNCE_INTERVAL;
|
|
|
|
exports.event = event;
|
|
exports.Peer = Peer;
|
|
exports.Tracker = Tracker;
|