1052 lines
25 KiB
JavaScript
1052 lines
25 KiB
JavaScript
const ipaddr = require('ipaddr.js');
|
|
const forBigTable = require('./forBigTable')
|
|
const compareVersions = require('compare-versions');
|
|
const getTorrent = require('./getTorrent')
|
|
const _ = require('lodash')
|
|
const asyncForEach = require('./asyncForEach')
|
|
const cpuUsage = require('./bt/cpu-usage-global')
|
|
const magnetParse = require('./magnetParse')
|
|
const parseTorrentFiles = require('./parsetTorrentFiles')
|
|
|
|
module.exports = async ({
|
|
sphinx,
|
|
sphinxSingle,
|
|
send,
|
|
recive,
|
|
p2p,
|
|
config,
|
|
baseRowData,
|
|
torrentClient,
|
|
spider,
|
|
upnp,
|
|
crypto,
|
|
insertTorrentToDB,
|
|
removeTorrentFromDB,
|
|
updateTorrentToDB,
|
|
checkTorrent,
|
|
setupTorrentRecord,
|
|
p2pStore,
|
|
feed,
|
|
updateTorrentTrackers,
|
|
remoteTrackers
|
|
}) => {
|
|
let torrentClientHashMap = {}
|
|
|
|
let topCache = {};
|
|
setInterval(() => {
|
|
topCache = {};
|
|
}, 24 * 60 * 60 * 1000);
|
|
|
|
|
|
const mergeTorrentsWithDownloads = (torrents, copy) => {
|
|
if(!torrents)
|
|
return torrents
|
|
|
|
if(copy)
|
|
torrents = _.cloneDeep(torrents)
|
|
|
|
const mergeTorrent = (torrent) => {
|
|
const id = torrentClientHashMap[torrent.hash]
|
|
if(id)
|
|
{
|
|
const download = torrentClient.get(id)
|
|
torrent.download = {
|
|
received: download.received,
|
|
downloaded: download.downloaded,
|
|
progress: download.progress,
|
|
downloadSpeed: download.downloadSpeed,
|
|
|
|
removeOnDone: download.removeOnDone,
|
|
paused: torrent.paused || torrent._paused
|
|
}
|
|
torrent.filesList = download.files.length > 0 ? downloadFilesList(download) : torrent.filesList
|
|
}
|
|
}
|
|
|
|
if(Array.isArray(torrents))
|
|
{
|
|
for(const torrent of torrents)
|
|
{
|
|
mergeTorrent(torrent)
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mergeTorrent(torrents)
|
|
}
|
|
|
|
return torrents
|
|
}
|
|
|
|
const mergeTorrentsWithDownloadsFn = (Fn, copy) => (...args) => {
|
|
const callback = args[args.length - 1]
|
|
const rest = args.slice(0, -1)
|
|
Fn(...rest, (data) => callback(mergeTorrentsWithDownloads(data, copy)))
|
|
}
|
|
|
|
const downloadFilesList = (torrent) => torrent.files.map((file, index) => ({
|
|
path: file.path.replace(/\\/g, '/'),
|
|
size: file.length,
|
|
downloadIndex: index,
|
|
downloadSelected: file.selected
|
|
}))
|
|
|
|
|
|
recive('recentTorrents', function(callback)
|
|
{
|
|
if(typeof callback != 'function')
|
|
return;
|
|
|
|
sphinx.query('SELECT * FROM `torrents` ORDER BY added DESC LIMIT 0,10', function (error, rows, fields) {
|
|
if(!rows) {
|
|
callback(undefined)
|
|
return;
|
|
}
|
|
|
|
let torrents = [];
|
|
rows.forEach((row) => {
|
|
torrents.push(baseRowData(row));
|
|
});
|
|
|
|
callback(torrents)
|
|
});
|
|
});
|
|
|
|
recive('statistic', function(callback)
|
|
{
|
|
if(typeof callback != 'function')
|
|
return;
|
|
|
|
callback({
|
|
torrents: p2p.info.torrents,
|
|
size: p2p.info.filesSize,
|
|
files: p2p.info.files
|
|
})
|
|
});
|
|
|
|
const onTorrent = (hash, options, callback) => {
|
|
if(hash.length != 40)
|
|
return;
|
|
|
|
if(typeof callback != 'function')
|
|
return;
|
|
|
|
// remote request
|
|
if(options.peer)
|
|
{
|
|
logT('search', 'remote torrent request to peer')
|
|
const peer = p2p.find(options.peer)
|
|
if(!peer)
|
|
{
|
|
logT('search', 'dont found requested peer in peers')
|
|
callback(undefined)
|
|
return;
|
|
}
|
|
delete options.peer;
|
|
peer.emit('torrent', {hash, options}, (data, nil, address) => {
|
|
logT('search', 'remote torrent result', hash)
|
|
callback(data)
|
|
|
|
if(compareVersions(address.version, '0.19.0') < 0)
|
|
{
|
|
logT('search', 'replication selected torrent now works only with 0.19.0 version, ignore this torrent')
|
|
return
|
|
}
|
|
|
|
if(data)
|
|
insertTorrentToDB(data, true) // copy torrent to our db
|
|
})
|
|
return;
|
|
}
|
|
|
|
sphinx.query('SELECT * FROM `torrents` WHERE `hash` = ?', hash, async function (error, rows, fields) {
|
|
if(!rows || rows.length == 0) {
|
|
callback(undefined)
|
|
return;
|
|
}
|
|
let torrent = rows[0];
|
|
|
|
if(options.files)
|
|
{
|
|
torrent.filesList = parseTorrentFiles(await sphinx.query('SELECT * FROM `files` WHERE `hash` = ?', hash));
|
|
callback(baseRowData(torrent))
|
|
}
|
|
else
|
|
{
|
|
callback(baseRowData(torrent))
|
|
}
|
|
|
|
// get votes
|
|
const {good, bad, selfVote} = await getVotes(hash)
|
|
send('votes', {
|
|
hash, good, bad, selfVote
|
|
});
|
|
if(torrent.good != good || torrent.bad != bad)
|
|
{
|
|
logT('rating', 'finded new rating on', torrent.name, 'update votes to it')
|
|
torrent.good = good
|
|
torrent.bad = bad
|
|
updateTorrentToDB(torrent)
|
|
}
|
|
});
|
|
}
|
|
|
|
recive('torrent', mergeTorrentsWithDownloadsFn(onTorrent));
|
|
p2p.on('torrent', ({hash, options} = {}, callback) => {
|
|
if(!hash)
|
|
return;
|
|
|
|
onTorrent(hash, options, (data) => callback(data))
|
|
})
|
|
|
|
if(config.p2pReplicationServer)
|
|
{
|
|
logT('replication', 'p2p replication server enabled')
|
|
|
|
p2p.on('randomTorrents', (nil, callback) => {
|
|
if(typeof callback != 'function')
|
|
return;
|
|
|
|
const cpu = cpuUsage()
|
|
const limit = Math.max(1, 5 - (cpu / 20) | 0)
|
|
|
|
sphinxSingle.query(`SELECT * FROM torrents ORDER BY rand() limit ${limit}`, (error, torrents) => {
|
|
if(!torrents || torrents.length == 0) {
|
|
callback(undefined)
|
|
return;
|
|
}
|
|
|
|
let hashes = {}
|
|
for(const torrent of torrents)
|
|
{
|
|
delete torrent.id
|
|
hashes[torrent.hash] = torrent
|
|
}
|
|
|
|
const inSql = Object.keys(hashes).map(hash => sphinx.escape(hash)).join(',');
|
|
sphinxSingle.query(`SELECT * FROM files WHERE hash IN(${inSql})`, (error, files) => {
|
|
for(const file of files)
|
|
hashes[file.hash].filesList = parseTorrentFiles(file);
|
|
|
|
callback(Object.values(hashes))
|
|
})
|
|
})
|
|
});
|
|
|
|
if(config.p2pReplication)
|
|
{
|
|
const getReplicationTorrents = (nextTimeout = 5000) => {
|
|
let gotTorrents = 0
|
|
p2p.emit('randomTorrents', null, (torrents, nil, address) => {
|
|
if(!torrents || torrents.length == 0)
|
|
return
|
|
|
|
if(compareVersions(address.version, '0.19.0') < 0)
|
|
{
|
|
logT('replication', 'replication now works only with 0.19.0 version, ignore this torrent')
|
|
return
|
|
}
|
|
|
|
gotTorrents += torrents.length
|
|
|
|
torrents.forEach((torrent) => {
|
|
logT('replication', 'replicate remote torrent', torrent && torrent.name)
|
|
insertTorrentToDB(torrent)
|
|
})
|
|
})
|
|
|
|
setTimeout(() => getReplicationTorrents(gotTorrents > 8 ? gotTorrents * 600 : 10000), nextTimeout)
|
|
}
|
|
// start
|
|
logT('replication', 'replication client is enabled')
|
|
getReplicationTorrents()
|
|
}
|
|
}
|
|
|
|
const isSH1Hash = (hash) =>
|
|
{
|
|
if(hash.length !== 40)
|
|
return false
|
|
return /[0-9a-f]+/i.test(hash)
|
|
}
|
|
|
|
const searchTorrentCall = function(text, navigation, callback, isP2P)
|
|
{
|
|
if(typeof callback != 'function')
|
|
return;
|
|
|
|
if(!text || text.length <= 2) {
|
|
callback(undefined);
|
|
return;
|
|
}
|
|
|
|
// check magnet
|
|
text = magnetParse(text) || text
|
|
|
|
const safeSearch = !!navigation.safeSearch;
|
|
|
|
const index = navigation.index || 0;
|
|
const limit = navigation.limit || 10;
|
|
let args = [text, index, limit];
|
|
const orderBy = navigation.orderBy;
|
|
let order = '';
|
|
let where = '';
|
|
if(orderBy && orderBy.length > 0)
|
|
{
|
|
const orderDesc = navigation.orderDesc ? 'DESC' : 'ASC';
|
|
args.splice(1, 0, orderBy);
|
|
order = 'ORDER BY ?? ' + orderDesc;
|
|
}
|
|
if(safeSearch)
|
|
{
|
|
where += " and contentCategory != 'xxx' ";
|
|
}
|
|
if(navigation.type && navigation.type.length > 0)
|
|
{
|
|
where += ' and contentType = ' + sphinx.escape(navigation.type) + ' ';
|
|
}
|
|
if(navigation.size)
|
|
{
|
|
if(navigation.size.max > 0)
|
|
where += ' and size < ' + sphinx.escape(navigation.size.max) + ' ';
|
|
if(navigation.size.min > 0)
|
|
where += ' and size > ' + sphinx.escape(navigation.size.min) + ' ';
|
|
}
|
|
if(navigation.files)
|
|
{
|
|
if(navigation.files.max > 0)
|
|
where += ' and files < ' + sphinx.escape(navigation.files.max) + ' ';
|
|
if(navigation.files.min > 0)
|
|
where += ' and files > ' + sphinx.escape(navigation.files.min) + ' ';
|
|
}
|
|
|
|
let searchList = [];
|
|
const isSHA1 = isSH1Hash(text)
|
|
sphinx.query('SELECT * FROM `torrents` WHERE ' + (isSHA1 ? 'hash = ?' : 'MATCH(?)') + ' ' + where + ' ' + order + ' LIMIT ?,?', args, function (error, rows, fields) {
|
|
if(!rows) {
|
|
logT('search', error)
|
|
callback(undefined)
|
|
return;
|
|
}
|
|
if(rows.length === 0 && isSHA1 && !isP2P) // trying to get via dht
|
|
{
|
|
logT('search', 'get torrent via infohash with dht')
|
|
torrentClient.getMetadata(text, (torrent) => {
|
|
searchList.push(baseRowData(torrent));
|
|
callback(searchList);
|
|
})
|
|
}
|
|
else
|
|
{
|
|
rows.forEach((row) => {
|
|
searchList.push(baseRowData(row));
|
|
});
|
|
callback(searchList);
|
|
}
|
|
});
|
|
}
|
|
|
|
recive('searchTorrent', mergeTorrentsWithDownloadsFn((text, navigation, callback) => {
|
|
searchTorrentCall(text, navigation, callback)
|
|
p2p.emit('searchTorrent', {text, navigation}, (remote, socketObject) => {
|
|
logT('search', 'remote search results', remote && remote.length)
|
|
if(remote && remote.length > 0)
|
|
{
|
|
const { _socket: socket } = socketObject
|
|
const peer = { address: socket.remoteAddress, port: socket.remotePort }
|
|
remote = remote.map(torrent => Object.assign(torrent, {peer}))
|
|
}
|
|
send('remoteSearchTorrent', mergeTorrentsWithDownloads(remote))
|
|
})
|
|
}));
|
|
|
|
p2p.on('searchTorrent', ({text, navigation} = {}, callback) => {
|
|
if(!text)
|
|
return;
|
|
|
|
searchTorrentCall(text, navigation, (data) => callback(data), true) // 4 args means remote
|
|
})
|
|
|
|
const searchFilesCall = function(text, navigation, callback)
|
|
{
|
|
if(typeof callback != 'function')
|
|
return;
|
|
|
|
if(!text || text.length <= 2) {
|
|
callback(undefined);
|
|
return;
|
|
}
|
|
|
|
const safeSearch = !!navigation.safeSearch;
|
|
|
|
const index = navigation.index || 0;
|
|
const limit = navigation.limit || 10;
|
|
let args = [text, text, index, limit];
|
|
const orderBy = navigation.orderBy;
|
|
let order = '';
|
|
let where = '';
|
|
|
|
let search = {};
|
|
sphinx.query('SELECT *, SNIPPET(path, ?, \'around=100\', \'force_all_words=1\') as snipplet FROM `files` WHERE MATCH(?) ' + where + ' ' + order + ' LIMIT ?,?', args, function (error, searchTorrents) {
|
|
if(!searchTorrents) {
|
|
logTE('search', error)
|
|
callback(undefined)
|
|
return;
|
|
}
|
|
if(searchTorrents.length === 0)
|
|
{
|
|
logT('search', 'not torrents founded for files search');
|
|
callback(undefined)
|
|
return;
|
|
}
|
|
for(const torrent of searchTorrents)
|
|
{
|
|
if(!search[torrent.hash])
|
|
{
|
|
search[torrent.hash] = { path: [] }
|
|
}
|
|
for(const file of torrent.snipplet.split('\n').filter(text => text.includes('<b>')).slice(0, 25))
|
|
{
|
|
search[torrent.hash].path.push(file)
|
|
}
|
|
}
|
|
const inSql = Object.keys(search).map(hash => sphinx.escape(hash)).join(',');
|
|
sphinx.query(`SELECT * FROM torrents WHERE hash IN(${inSql})`, (err, torrents) => {
|
|
if(!torrents) {
|
|
logTE('search', err)
|
|
return;
|
|
}
|
|
|
|
for(const torrent of torrents)
|
|
{
|
|
search[torrent.hash] = Object.assign(baseRowData(torrent), search[torrent.hash])
|
|
|
|
// temporary ignore adult content in search (workaroud)
|
|
if(safeSearch && search[torrent.hash].contentCategory == 'xxx')
|
|
delete search[torrent.hash]
|
|
}
|
|
|
|
let searchResult = Object.values(search)
|
|
|
|
if(orderBy && orderBy.length > 0 && searchResult.length > 0)
|
|
{
|
|
searchResult = _.orderBy(searchResult, [orderBy], [navigation.orderDesc ? 'desc' : 'asc'])
|
|
}
|
|
|
|
callback(searchResult);
|
|
})
|
|
});
|
|
}
|
|
|
|
recive('searchFiles', mergeTorrentsWithDownloadsFn((text, navigation, callback) => {
|
|
searchFilesCall(text, navigation, callback)
|
|
p2p.emit('searchFiles', {text, navigation}, (remote, socketObject) => {
|
|
logT('search', 'remote search files results', remote && remote.length)
|
|
if(remote && remote.length > 0)
|
|
{
|
|
const { _socket: socket } = socketObject
|
|
const peer = { address: socket.remoteAddress, port: socket.remotePort }
|
|
remote = remote.map(torrent => Object.assign(torrent, {peer}))
|
|
}
|
|
send('remoteSearchFiles', mergeTorrentsWithDownloads(remote))
|
|
})
|
|
}));
|
|
|
|
p2p.on('searchFiles', ({text, navigation} = {}, callback) => {
|
|
if(!text)
|
|
return;
|
|
|
|
searchFilesCall(text, navigation, (data) => callback(data))
|
|
})
|
|
|
|
recive('checkTrackers', function(hash)
|
|
{
|
|
if(hash.length != 40)
|
|
return;
|
|
|
|
logT('tracker', 'recheck trackers for', hash)
|
|
updateTorrentTrackers(hash);
|
|
remoteTrackers.update({hash, name: hash})
|
|
});
|
|
|
|
const topTorrentsCall = (type, navigation = {}, callback) => {
|
|
let where = '';
|
|
|
|
const index = parseInt(navigation.index) || 0;
|
|
const limit = parseInt(navigation.limit) || 20;
|
|
const time = navigation.time
|
|
|
|
if(type && type.length > 0)
|
|
{
|
|
where += ' and contentType = ' + sphinx.escape(type) + ' ';
|
|
}
|
|
|
|
if(time)
|
|
{
|
|
if(time == 'hours')
|
|
{
|
|
where += ' and `added` > ' + (Math.floor(Date.now() / 1000) - (60 * 60 * 24))
|
|
}
|
|
else if(time == 'week')
|
|
{
|
|
where += ' and `added` > ' + (Math.floor(Date.now() / 1000) - (60 * 60 * 24 * 7))
|
|
}
|
|
else if(time == 'month')
|
|
{
|
|
where += ' and `added` > ' + (Math.floor(Date.now() / 1000) - (60 * 60 * 24 * 30))
|
|
}
|
|
}
|
|
|
|
const query = `SELECT * FROM torrents WHERE seeders > 0 and contentCategory != 'xxx' ${where} ORDER BY seeders DESC LIMIT ${index},${limit}`;
|
|
if(topCache[query])
|
|
{
|
|
callback(topCache[query]);
|
|
return;
|
|
}
|
|
sphinx.query(query, function (error, rows) {
|
|
if(!rows || rows.length == 0) {
|
|
callback(undefined)
|
|
return;
|
|
}
|
|
|
|
rows = rows.map((row) => baseRowData(row));
|
|
topCache[query] = rows;
|
|
callback(rows);
|
|
});
|
|
}
|
|
|
|
recive('topTorrents', mergeTorrentsWithDownloadsFn((type, navigation, callback) =>
|
|
{
|
|
topTorrentsCall(type, navigation, callback)
|
|
p2p.emit('topTorrents', {type, navigation}, (remote, socketObject) => {
|
|
logT('top', 'remote top results', remote && remote.length)
|
|
if(remote && remote.length > 0)
|
|
{
|
|
const { _socket: socket } = socketObject
|
|
const peer = { address: socket.remoteAddress, port: socket.remotePort }
|
|
remote = remote.map(torrent => Object.assign(torrent, {peer}))
|
|
}
|
|
send('remoteTopTorrents', {torrents: mergeTorrentsWithDownloads(remote), type, time: navigation && navigation.time})
|
|
})
|
|
}));
|
|
|
|
p2p.on('topTorrents', ({type, navigation} = {}, callback) => {
|
|
topTorrentsCall(type, navigation, (data) => callback(data))
|
|
})
|
|
|
|
recive('peers', (callback) =>
|
|
{
|
|
if(typeof callback != 'function')
|
|
return;
|
|
|
|
callback({
|
|
size: p2p.size,
|
|
torrents: p2p.peersList().reduce((prev, peer) => prev + (peer.info ? peer.info.torrents || 0 : 0), 0)
|
|
})
|
|
});
|
|
|
|
recive('p2pStatus', (callback) =>
|
|
{
|
|
if(typeof callback != 'function')
|
|
return;
|
|
|
|
callback(p2p.p2pStatus)
|
|
});
|
|
|
|
recive('config', (callback) =>
|
|
{
|
|
if(typeof callback != 'function')
|
|
return;
|
|
|
|
callback(config)
|
|
});
|
|
|
|
recive('setConfig', (options, callback) =>
|
|
{
|
|
if(typeof options !== 'object')
|
|
return;
|
|
|
|
if(typeof options.indexer !== 'undefined')
|
|
{
|
|
const upSpider = () => {
|
|
if(options.indexer)
|
|
spider.listen(config.spiderPort)
|
|
else
|
|
spider.close()
|
|
}
|
|
|
|
if(options.spiderPort !== config.spiderPort)
|
|
spider.close(upSpider)
|
|
else
|
|
upSpider()
|
|
}
|
|
|
|
if(upnp)
|
|
upnp.ratsUnmap()
|
|
|
|
for(const option in options)
|
|
{
|
|
if(option in config)
|
|
config[option] = options[option]
|
|
}
|
|
|
|
if(upnp)
|
|
upnp.ratsMap()
|
|
|
|
if(config.p2p)
|
|
{
|
|
spider.announceHashes = [crypto.createHash('sha1').update('degrats-v1').digest()]
|
|
}
|
|
else
|
|
{
|
|
spider.announceHashes = []
|
|
}
|
|
|
|
if(typeof callback === 'function')
|
|
callback(true)
|
|
});
|
|
|
|
torrentClient._add = (torrentObject, savePath, callback) =>
|
|
{
|
|
const magnet = `magnet:?xt=urn:btih:${torrentObject.hash}`
|
|
logT('downloader', 'download', magnet)
|
|
if(torrentClient.get(magnet)) {
|
|
logT('downloader', 'aready added')
|
|
if(callback)
|
|
callback(false)
|
|
return
|
|
}
|
|
|
|
const torrent = torrentClient.add(magnet, {path: savePath || config.client.downloadPath})
|
|
torrentClientHashMap[torrent.infoHash] = magnet
|
|
torrent.torrentObject = torrentObject
|
|
|
|
const progress = (bytes) => {
|
|
send('downloadProgress', torrent.infoHash, {
|
|
received: bytes,
|
|
downloaded: torrent.downloaded,
|
|
downloadSpeed: torrent.downloadSpeed,
|
|
progress: torrent.progress,
|
|
timeRemaining: torrent.timeRemaining
|
|
})
|
|
}
|
|
|
|
torrent.on('ready', () => {
|
|
logT('downloader', 'start downloading', torrent.infoHash, 'to', torrent.path)
|
|
send('downloading', torrent.infoHash)
|
|
progress(0) // immediately display progress
|
|
if(torrent._paused)
|
|
{
|
|
delete torrent._paused
|
|
torrent._pause()
|
|
}
|
|
send('filesReady', torrent.infoHash, downloadFilesList(torrent))
|
|
})
|
|
|
|
torrent.on('done', () => {
|
|
logT('downloader', 'download done', torrent.infoHash)
|
|
progress(0) // update progress
|
|
// remove torrent if marked
|
|
if(torrent.removeOnDone)
|
|
{
|
|
torrentClient.remove(magnet, (err) => {
|
|
if(err)
|
|
{
|
|
logT('downloader', 'download removing error', err)
|
|
return
|
|
}
|
|
|
|
delete torrentClientHashMap[torrent.infoHash]
|
|
send('downloadDone', torrent.infoHash)
|
|
})
|
|
}
|
|
else
|
|
{
|
|
send('downloadDone', torrent.infoHash)
|
|
}
|
|
})
|
|
|
|
let now = Date.now()
|
|
torrent.on('download', (bytes) => {
|
|
if(Date.now() - now < 100)
|
|
return
|
|
now = Date.now()
|
|
|
|
progress(bytes)
|
|
})
|
|
|
|
//custom api pause
|
|
torrent._pause = () => {
|
|
logT('downloader', 'pause torrent downloading', torrent.infoHash)
|
|
torrent.pause()
|
|
torrent.wires = [];
|
|
setTimeout(() => {
|
|
if(torrent.paused)
|
|
torrent.wires = [];
|
|
}, 100) // make sure no more wires appears
|
|
}
|
|
|
|
torrent._restoreWires = () => {
|
|
for(const p in torrent._peers){
|
|
const wire = torrent._peers[p].wire
|
|
if(wire)
|
|
torrent.wires.push(wire);
|
|
}
|
|
}
|
|
|
|
torrent._resume = () => {
|
|
logT('downloader', 'resume torrent downloading', torrent.infoHash)
|
|
torrent._restoreWires()
|
|
torrent.resume()
|
|
}
|
|
|
|
// fix wires after pause
|
|
const _destroy = torrent._destroy
|
|
torrent._destroy = (...args) => {
|
|
// fix pause wires closing
|
|
if(torrent.paused)
|
|
{
|
|
torrent._restoreWires()
|
|
}
|
|
return _destroy.call(torrent, ...args)
|
|
}
|
|
|
|
torrent.selectFiles = (selection) => {
|
|
if(Array.isArray(selection))
|
|
{
|
|
if(selection.length !== torrent.files.length)
|
|
{
|
|
logTE('downloader', 'selection map not full', torrent.files.length, selection.length)
|
|
return
|
|
}
|
|
for(let i = 0; i < selection.length; i++)
|
|
{
|
|
torrent.files[i].selected = !!selection[i]
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(let fileId in selection)
|
|
{
|
|
fileId = parseInt(fileId)
|
|
if(fileId >= torrent.files.length)
|
|
{
|
|
logTE('downloader', 'selection map wrong', selection)
|
|
return
|
|
}
|
|
torrent.files[fileId].selected = !!selection[fileId]
|
|
}
|
|
}
|
|
torrent.updateFilesSelection()
|
|
}
|
|
|
|
torrent.updateFilesSelection = () => {
|
|
torrent.deselect(0, torrent.pieces.length - 1, false)
|
|
|
|
for(const file of torrent.files)
|
|
{
|
|
const {selected} = file
|
|
if(typeof selected === 'undefined' || selected)
|
|
file.select()
|
|
else
|
|
file.deselect()
|
|
}
|
|
logT('downloader', 'selection updated')
|
|
}
|
|
|
|
|
|
if(callback)
|
|
callback(true)
|
|
|
|
return torrent
|
|
}
|
|
|
|
recive('download', torrentClient._add);
|
|
|
|
recive('downloadUpdate', (hash, options) =>
|
|
{
|
|
const id = torrentClientHashMap[hash]
|
|
if(!id)
|
|
{
|
|
logT('downloader', 'cant find torrent for removing', hash)
|
|
return
|
|
}
|
|
|
|
const torrent = torrentClient.get(id)
|
|
if(!torrent) {
|
|
logT('downloader', 'no torrent for update founded')
|
|
return
|
|
}
|
|
|
|
if(options.removeOnDone)
|
|
{
|
|
torrent.removeOnDone = options.removeOnDone == 'switch' ? !torrent.removeOnDone : options.removeOnDone
|
|
}
|
|
|
|
if(typeof options.pause !== 'undefined')
|
|
{
|
|
const pause = options.pause == 'switch' ? !torrent.paused : options.pause
|
|
if(pause)
|
|
torrent._pause()
|
|
else
|
|
torrent._resume()
|
|
}
|
|
|
|
send('downloadUpdate', torrent.infoHash, {
|
|
removeOnDone: torrent.removeOnDone,
|
|
paused: torrent.paused || torrent._paused
|
|
})
|
|
})
|
|
|
|
recive('downloadCancel', (hash, callback) =>
|
|
{
|
|
const id = torrentClientHashMap[hash]
|
|
if(!id)
|
|
{
|
|
logT('downloader', 'cant find torrent for removing', hash)
|
|
if(callback)
|
|
callback(false)
|
|
return
|
|
}
|
|
|
|
torrentClient.remove(id, (err) => {
|
|
if(err)
|
|
{
|
|
logT('downloader', 'download removing error', err)
|
|
if(callback)
|
|
callback(false)
|
|
return
|
|
}
|
|
|
|
delete torrentClientHashMap[hash]
|
|
send('downloadDone', hash, true)
|
|
|
|
if(callback)
|
|
callback(true)
|
|
})
|
|
})
|
|
|
|
recive('downloadSelectFiles', ({hash}, files, callback) =>
|
|
{
|
|
logT('downloader', 'call update selection', hash, files.length)
|
|
const id = torrentClientHashMap[hash]
|
|
if(!id)
|
|
{
|
|
logT('downloader', 'cant find torrent for selection', hash)
|
|
if(callback)
|
|
callback(false)
|
|
return
|
|
}
|
|
|
|
const torrent = torrentClient.get(id)
|
|
if(!torrent) {
|
|
logT('downloader', 'no torrent for selection founded')
|
|
return
|
|
}
|
|
|
|
torrent.selectFiles(files)
|
|
send('filesReady', torrent.infoHash, downloadFilesList(torrent))
|
|
|
|
if(callback)
|
|
callback(true)
|
|
})
|
|
|
|
recive('downloads', (callback) =>
|
|
{
|
|
callback(torrentClient.torrents.map(torrent => ({
|
|
torrentObject: torrent.torrentObject,
|
|
received: torrent.received,
|
|
downloaded: torrent.downloaded,
|
|
progress: torrent.progress,
|
|
downloadSpeed: torrent.downloadSpeed,
|
|
|
|
removeOnDone: torrent.removeOnDone,
|
|
paused: torrent.paused || torrent._paused
|
|
})))
|
|
})
|
|
|
|
let removeProtect = false
|
|
recive('removeTorrents', (checkOnly = true, callback) =>
|
|
{
|
|
if(removeProtect)
|
|
return
|
|
removeProtect = true
|
|
|
|
logT('clean', 'checktorrents call')
|
|
|
|
const toRemove = []
|
|
|
|
const done = async () => {
|
|
logT('clean', 'torrents to remove founded', toRemove.length)
|
|
if(checkOnly)
|
|
{
|
|
callback(toRemove.length)
|
|
removeProtect = false
|
|
return
|
|
}
|
|
|
|
await asyncForEach(toRemove, async (torrent, index) => {
|
|
await removeTorrentFromDB(torrent)
|
|
send('cleanTorrent', index + 1, toRemove.length, 'clean');
|
|
})
|
|
callback(toRemove.length)
|
|
removeProtect = false
|
|
logT('clean', 'removed torrents by filter:', toRemove.length)
|
|
}
|
|
|
|
let i = 1
|
|
forBigTable(sphinx, 'torrents', (torrent) => {
|
|
setupTorrentRecord(torrent)
|
|
if(!checkTorrent(torrent))
|
|
{
|
|
toRemove.push({hash: torrent.hash})
|
|
// send info about cleaning takes
|
|
send('cleanTorrent', i++, 0, 'check');
|
|
}
|
|
}, done)
|
|
})
|
|
|
|
const getVotes = async (hash) => {
|
|
const votes = await p2pStore.find(`vote:${hash}`)
|
|
let good = 0
|
|
let bad = 0
|
|
let selfVote = false
|
|
if(votes)
|
|
{
|
|
votes.forEach(({vote, _peerId}) => {
|
|
if(_peerId === p2p.peerId)
|
|
selfVote = true
|
|
|
|
if(vote == 'bad')
|
|
bad++
|
|
else
|
|
good++
|
|
})
|
|
}
|
|
|
|
return {good, bad, selfVote}
|
|
}
|
|
|
|
recive('vote', async (hash, isGood, callback) =>
|
|
{
|
|
if(hash.length != 40)
|
|
return;
|
|
|
|
if(typeof callback != 'function')
|
|
return;
|
|
|
|
isGood = !!isGood;
|
|
|
|
const action = isGood ? 'good' : 'bad';
|
|
let {good, bad, selfVote} = await getVotes(hash)
|
|
|
|
if(!selfVote)
|
|
{
|
|
setTimeout(async () => p2pStore.store({
|
|
type: 'vote',
|
|
torrentHash: hash,
|
|
vote: action,
|
|
_index: `vote:${hash}`,
|
|
_temp: {
|
|
torrent: setupTorrentRecord(await getTorrent(sphinx, hash))
|
|
}
|
|
}), 0)
|
|
good += isGood ? 1 : 0
|
|
bad += !isGood ? 1 : 0
|
|
}
|
|
|
|
send('votes', {
|
|
hash, good, bad
|
|
});
|
|
callback(true)
|
|
|
|
});
|
|
|
|
// store torrent to feed
|
|
await feed.load()
|
|
Object.defineProperty(p2p.info, 'feed', {
|
|
enumerable: true,
|
|
get: () => feed.size()
|
|
});
|
|
Object.defineProperty(p2p.info, 'feedDate', {
|
|
enumerable: true,
|
|
get: () => feed.feedDate
|
|
});
|
|
p2pStore.on('store', async ({data: record, temp, myself}) => {
|
|
if(record.type !== 'vote')
|
|
return
|
|
|
|
if(!temp || !temp.torrent)
|
|
return
|
|
|
|
const { torrent } = temp
|
|
|
|
if(torrent.hash !== record.torrentHash)
|
|
return
|
|
|
|
if(!myself)
|
|
{
|
|
logT('store', 'replicate torrent from store record', torrent.hash)
|
|
await insertTorrentToDB(torrent)
|
|
}
|
|
|
|
let {good, bad} = await getVotes(torrent.hash)
|
|
torrent.good = good
|
|
torrent.bad = bad
|
|
if(torrent.good > 0 || torrent.bad > 0)
|
|
updateTorrentToDB(torrent)
|
|
|
|
// update feed only on some good info
|
|
if(torrent.good < 1)
|
|
return
|
|
|
|
feed.add(torrent)
|
|
send('feedUpdate', {
|
|
feed: feed.feed
|
|
});
|
|
})
|
|
|
|
const feedCall = (callback) =>
|
|
{
|
|
callback(feed.feed)
|
|
}
|
|
recive('feed', mergeTorrentsWithDownloadsFn(feedCall, true)); // don't overwrite feed value
|
|
|
|
p2p.on('feed', (nil, callback) => {
|
|
callback({
|
|
feed: feed.feed,
|
|
feedDate: feed.feedDate
|
|
})
|
|
})
|
|
|
|
// call once to get bigest feed
|
|
p2p.events.on('peer', (peer) => {
|
|
if(peer.info && peer.info.feed)
|
|
{
|
|
if(peer.info.feed > feed.size() // list bigger than our
|
|
|| (peer.info.feed == feed.size() && peer.info.feedDate > feed.feedDate)) // or same but more new
|
|
{
|
|
peer.emit('feed', null, (remoteFeed) => {
|
|
if(!remoteFeed)
|
|
return
|
|
|
|
if(Array.isArray(remoteFeed) || !remoteFeed.feed)
|
|
return // old version call
|
|
|
|
if(remoteFeed.feed.length > feed.size() || (remoteFeed.feed.length == feed.size() && remoteFeed.feedDate > feed.feedDate))
|
|
{
|
|
logT('feed', 'replace our feed with remote feed')
|
|
feed.feed = remoteFeed.feed
|
|
feed.feedDate = remoteFeed.feedDate || 0
|
|
// it can be new torrents replicate all
|
|
feed.feed.forEach(torrent => insertTorrentToDB(torrent, true))
|
|
send('feedUpdate', {
|
|
feed: feed.feed
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
})
|
|
|
|
} |