peerflix-server
Version:
Streaming torrent client for node.js with web ui.
218 lines (187 loc) • 6.22 kB
JavaScript
;
var rangeParser = require('range-parser'),
pump = require('pump'),
_ = require('lodash'),
express = require('express'),
logger = require('morgan'),
bodyParser = require('body-parser'),
multipart = require('connect-multiparty'),
fs = require('fs'),
archiver = require('archiver'),
store = require('./store'),
progress = require('./progressbar'),
stats = require('./stats'),
api = express();
api.use(bodyParser.json())
api.use(logger('dev'));
api.use(function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'OPTIONS, POST, GET, PUT, DELETE');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});
function serialize(torrent) {
if (!torrent.torrent) {
return { infoHash: torrent.infoHash };
}
var pieceLength = torrent.torrent.pieceLength;
return {
infoHash: torrent.infoHash,
name: torrent.torrent.name,
length: torrent.torrent.length,
interested: torrent.amInterested,
ready: torrent.ready,
files: torrent.files.map(function (f) {
// jshint -W016
var start = f.offset / pieceLength | 0;
var end = (f.offset + f.length - 1) / pieceLength | 0;
return {
name: f.name,
path: f.path,
link: '/torrents/' + torrent.infoHash + '/files/' + encodeURIComponent(f.path),
length: f.length,
offset: f.offset,
selected: torrent.selection.some(function (s) {
return s.from <= start && s.to >= end;
})
};
}),
progress: progress(torrent.bitfield.buffer)
};
}
function findTorrent(req, res, next) {
var torrent = req.torrent = store.get(req.params.infoHash);
if (!torrent) {
return res.sendStatus(404);
}
next();
}
api.get('/torrents', function (req, res) {
res.send(store.list().map(serialize));
});
api.post('/torrents', function (req, res) {
store.add(req.body.link, function (err, infoHash) {
if (err) {
console.error(err);
res.status(500).send(err);
} else {
res.send({ infoHash: infoHash });
}
});
});
api.post('/upload', multipart(), function (req, res) {
var file = req.files && req.files.file;
if (!file) {
return res.status(500).send('file is missing');
}
store.add(file.path, function (err, infoHash) {
if (err) {
console.error(err);
res.status(500).send(err);
} else {
res.send({ infoHash: infoHash });
}
fs.unlink(file.path, function (err) {
if (err) {
console.error(err);
}
});
});
});
api.get('/torrents/:infoHash', findTorrent, function (req, res) {
res.send(serialize(req.torrent));
});
api.post('/torrents/:infoHash/start/:index?', findTorrent, function (req, res) {
var index = parseInt(req.params.index);
if (index >= 0 && index < req.torrent.files.length) {
req.torrent.files[index].select();
} else {
req.torrent.files.forEach(function (f) {
f.select();
});
}
res.sendStatus(200);
});
api.post('/torrents/:infoHash/stop/:index?', findTorrent, function (req, res) {
var index = parseInt(req.params.index);
if (index >= 0 && index < req.torrent.files.length) {
req.torrent.files[index].deselect();
} else {
req.torrent.files.forEach(function (f) {
f.deselect();
});
}
res.sendStatus(200);
});
api.post('/torrents/:infoHash/pause', findTorrent, function (req, res) {
req.torrent.swarm.pause();
res.sendStatus(200);
});
api.post('/torrents/:infoHash/resume', findTorrent, function (req, res) {
req.torrent.swarm.resume();
res.sendStatus(200);
});
api.delete('/torrents/:infoHash', findTorrent, function (req, res) {
store.remove(req.torrent.infoHash);
res.sendStatus(200);
});
api.get('/torrents/:infoHash/stats', findTorrent, function (req, res) {
res.send(stats(req.torrent));
});
api.get('/torrents/:infoHash/files', findTorrent, function (req, res) {
var torrent = req.torrent;
var proto = req.get('x-forwarded-proto') || req.protocol;
var host = req.get('x-forwarded-host') || req.get('host');
res.setHeader('Content-Type', 'application/x-mpegurl; charset=utf-8');
res.attachment(torrent.torrent.name + '.m3u');
res.send('#EXTM3U\n' + torrent.files.map(function (f) {
return '#EXTINF:-1,' + f.path + '\n' +
proto + '://' + host + '/torrents/' + torrent.infoHash + '/files/' + encodeURIComponent(f.path);
}).join('\n'));
});
api.all('/torrents/:infoHash/files/:path([^"]+)', findTorrent, function (req, res) {
var torrent = req.torrent, file = _.find(torrent.files, { path: req.params.path });
if (!file) {
return res.sendStatus(404);
}
if (typeof req.query.ffmpeg !== 'undefined') {
return require('./ffmpeg')(req, res, torrent, file);
}
var range = req.headers.range;
range = range && rangeParser(file.length, range)[0];
res.setHeader('Accept-Ranges', 'bytes');
res.type(file.name);
req.connection.setTimeout(3600000);
if (!range) {
res.setHeader('Content-Length', file.length);
if (req.method === 'HEAD') {
return res.end();
}
return pump(file.createReadStream(), res);
}
res.statusCode = 206;
res.setHeader('Content-Length', range.end - range.start + 1);
res.setHeader('Content-Range', 'bytes ' + range.start + '-' + range.end + '/' + file.length);
if (req.method === 'HEAD') {
return res.end();
}
pump(file.createReadStream(range), res);
});
api.get('/torrents/:infoHash/archive', findTorrent, function (req, res) {
var torrent = req.torrent;
res.attachment(torrent.torrent.name + '.zip');
req.connection.setTimeout(3600000);
var archive = archiver('zip');
archive.on('warning', function (err) {
console.error(err);
});
archive.on('error', function (err) {
throw err;
});
pump(archive, res);
torrent.files.forEach(function (f) {
archive.append(f.createReadStream(), { name: f.path });
});
archive.finalize();
});
module.exports = api;