web -> desktop
This commit is contained in:
55
src/app.js
55
src/app.js
@ -1,55 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import './app.css';
|
||||
import './router';
|
||||
import PagesPie from './pages-pie.js';
|
||||
import registerServiceWorker from './registerServiceWorker';
|
||||
import injectTapEventPlugin from 'react-tap-event-plugin';
|
||||
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
|
||||
|
||||
var io = require("socket.io-client");
|
||||
window.torrentSocket = io(document.location.protocol + '//' + document.location.hostname + (process.env.NODE_ENV === 'production' ? '/' : ':8095/'));
|
||||
|
||||
// Needed for onTouchTap
|
||||
// http://stackoverflow.com/a/34015469/988941
|
||||
injectTapEventPlugin();
|
||||
|
||||
registerServiceWorker();
|
||||
|
||||
let loadersCount = 0;
|
||||
let appReady = false;
|
||||
window.customLoader = (func, onLoading, onLoaded) => {
|
||||
loadersCount++;
|
||||
if(onLoading) {
|
||||
onLoading();
|
||||
}
|
||||
return (...args) => {
|
||||
func(...args);
|
||||
if(onLoaded) {
|
||||
onLoaded();
|
||||
}
|
||||
loadersCount--;
|
||||
}
|
||||
};
|
||||
|
||||
window.isReady = () => {
|
||||
return (appReady && loadersCount === 0)
|
||||
}
|
||||
|
||||
class App extends Component {
|
||||
componentDidMount() {
|
||||
window.router()
|
||||
appReady = true;
|
||||
}
|
||||
componentWillUnmount() {
|
||||
appReady = false;
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<MuiThemeProvider>
|
||||
<PagesPie />
|
||||
</MuiThemeProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
86
src/app/app.js
Normal file
86
src/app/app.js
Normal file
@ -0,0 +1,86 @@
|
||||
import React, { Component } from 'react';
|
||||
import './app.css';
|
||||
import './router';
|
||||
import PagesPie from './pages-pie.js';
|
||||
//import registerServiceWorker from './registerServiceWorker';
|
||||
import injectTapEventPlugin from 'react-tap-event-plugin';
|
||||
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
|
||||
|
||||
const { ipcRenderer, remote } = require('electron');
|
||||
|
||||
//var io = require("socket.io-client");
|
||||
//window.torrentSocket = io(document.location.protocol + '//' + document.location.hostname + (process.env.NODE_ENV === 'production' ? '/' : ':8095/'));
|
||||
window.torrentSocket = {}
|
||||
window.torrentSocket.callbacks = {}
|
||||
window.torrentSocket.on = (name, func) => {
|
||||
ipcRenderer.on(name, (event, data) => {
|
||||
func(data)
|
||||
});
|
||||
}
|
||||
window.torrentSocket.off = (name, func) => {
|
||||
if(!func)
|
||||
ipcRenderer.removeListener(name);
|
||||
else
|
||||
ipcRenderer.removeListener(name, func);
|
||||
}
|
||||
window.torrentSocket.emit = (name, ...data) => {
|
||||
if(typeof data[data.length - 1] === 'function')
|
||||
{
|
||||
const id = Math.random().toString(36).substring(5)
|
||||
window.torrentSocket.callbacks[id] = data[data.length - 1];
|
||||
data[data.length - 1] = {callback: id}
|
||||
}
|
||||
ipcRenderer.send(name, data)
|
||||
}
|
||||
ipcRenderer.on('callback', (event, id, data) => {
|
||||
const callback = window.torrentSocket.callbacks[id]
|
||||
if(callback)
|
||||
callback(data)
|
||||
delete window.torrentSocket.callbacks[id]
|
||||
});
|
||||
|
||||
|
||||
// Needed for onTouchTap
|
||||
// http://stackoverflow.com/a/34015469/988941
|
||||
injectTapEventPlugin();
|
||||
|
||||
//registerServiceWorker();
|
||||
|
||||
let loadersCount = 0;
|
||||
let appReady = false;
|
||||
window.customLoader = (func, onLoading, onLoaded) => {
|
||||
loadersCount++;
|
||||
if(onLoading) {
|
||||
onLoading();
|
||||
}
|
||||
return (...args) => {
|
||||
func(...args);
|
||||
if(onLoaded) {
|
||||
onLoaded();
|
||||
}
|
||||
loadersCount--;
|
||||
}
|
||||
};
|
||||
|
||||
window.isReady = () => {
|
||||
return (appReady && loadersCount === 0)
|
||||
}
|
||||
|
||||
class App extends Component {
|
||||
componentDidMount() {
|
||||
window.router()
|
||||
appReady = true;
|
||||
}
|
||||
componentWillUnmount() {
|
||||
appReady = false;
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<MuiThemeProvider>
|
||||
<PagesPie />
|
||||
</MuiThemeProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
@ -22,7 +22,6 @@ export default (props) => {
|
||||
C243.779,80.572,238.768,71.728,220.195,71.427z"/>
|
||||
</svg>
|
||||
|
||||
<iframe data-aa='405459' src='//ad.a-ads.com/405459?size=468x60' scrolling='no' style={{width: '100%', height: '60px', border: '0px', padding: '0', overflow: 'hidden'}} allowTransparency='true'></iframe>
|
||||
<div className='fs0-75 pad0-75'>Don't hesitate and visit the banners, we are trying to survive among dark blue sea</div>
|
||||
</div>
|
||||
)
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 218 KiB After Width: | Height: | Size: 218 KiB |
@ -34,7 +34,7 @@ export default class IndexPage extends Page {
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div id='index-window'>
|
||||
<Header />
|
||||
<Search />
|
||||
<div className='column center w100p pad0-75'>
|
@ -13,5 +13,5 @@ import './index.css';
|
||||
|
||||
ReactDOM.render(
|
||||
<App />,
|
||||
document.getElementById('root')
|
||||
document.getElementById('mount-point')
|
||||
);
|
@ -1,6 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import InputRange from 'react-input-range';
|
||||
import 'react-input-range/lib/css/index.css';
|
||||
import './input-range.css';
|
||||
import formatBytes from './format-bytes'
|
||||
|
||||
import SelectField from 'material-ui/SelectField';
|
83
src/app/input-range.css
Normal file
83
src/app/input-range.css
Normal file
@ -0,0 +1,83 @@
|
||||
.input-range__slider {
|
||||
appearance: none;
|
||||
background: #3f51b5;
|
||||
border: 1px solid #3f51b5;
|
||||
border-radius: 100%;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
height: 1rem;
|
||||
margin-left: -0.5rem;
|
||||
margin-top: -0.65rem;
|
||||
outline: none;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transition: transform 0.3s ease-out, box-shadow 0.3s ease-out;
|
||||
width: 1rem; }
|
||||
.input-range__slider:active {
|
||||
transform: scale(1.3); }
|
||||
.input-range__slider:focus {
|
||||
box-shadow: 0 0 0 5px rgba(63, 81, 181, 0.2); }
|
||||
.input-range--disabled .input-range__slider {
|
||||
background: #cccccc;
|
||||
border: 1px solid #cccccc;
|
||||
box-shadow: none;
|
||||
transform: none; }
|
||||
|
||||
.input-range__slider-container {
|
||||
transition: left 0.3s ease-out; }
|
||||
|
||||
.input-range__label {
|
||||
color: #aaaaaa;
|
||||
font-family: "Helvetica Neue", san-serif;
|
||||
font-size: 0.8rem;
|
||||
transform: translateZ(0);
|
||||
white-space: nowrap; }
|
||||
|
||||
.input-range__label--min,
|
||||
.input-range__label--max {
|
||||
bottom: -1.4rem;
|
||||
position: absolute; }
|
||||
|
||||
.input-range__label--min {
|
||||
left: 0; }
|
||||
|
||||
.input-range__label--max {
|
||||
right: 0; }
|
||||
|
||||
.input-range__label--value {
|
||||
position: absolute;
|
||||
top: -1.8rem; }
|
||||
|
||||
.input-range__label-container {
|
||||
left: -50%;
|
||||
position: relative; }
|
||||
.input-range__label--max .input-range__label-container {
|
||||
left: 50%; }
|
||||
|
||||
.input-range__track {
|
||||
background: #eeeeee;
|
||||
border-radius: 0.3rem;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
height: 0.3rem;
|
||||
position: relative;
|
||||
transition: left 0.3s ease-out, width 0.3s ease-out; }
|
||||
.input-range--disabled .input-range__track {
|
||||
background: #eeeeee; }
|
||||
|
||||
.input-range__track--background {
|
||||
left: 0;
|
||||
margin-top: -0.15rem;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%; }
|
||||
|
||||
.input-range__track--active {
|
||||
background: #3f51b5; }
|
||||
|
||||
.input-range {
|
||||
height: 1rem;
|
||||
position: relative;
|
||||
width: 100%; }
|
||||
|
||||
/*# sourceMappingURL=input-range.css.map */
|
@ -1,6 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import InputRange from 'react-input-range';
|
||||
import 'react-input-range/lib/css/index.css';
|
||||
import './input-range.css';
|
||||
import formatBytes from './format-bytes'
|
||||
|
||||
import SelectField from 'material-ui/SelectField';
|
@ -1,5 +1,4 @@
|
||||
import router from 'page';
|
||||
window.router = router;
|
||||
//import router from 'page';
|
||||
import PagesPie from './pages-pie.js';
|
||||
|
||||
import IndexPage from './index-page.js'
|
||||
@ -8,6 +7,48 @@ import DMCAPage from './dmca-page.js'
|
||||
import AdminPage from './admin-page.js'
|
||||
import TopPage from './top-page.js'
|
||||
|
||||
let routers = {}
|
||||
const router = (page, callback) => {
|
||||
if(!callback)
|
||||
{
|
||||
if(!page)
|
||||
routers['/'].callback()
|
||||
else
|
||||
{
|
||||
const p = page.split('/')
|
||||
const pg = routers[`${p[0]}/${p[1]}`]
|
||||
if(!pg)
|
||||
return
|
||||
|
||||
p.splice(0, 2)
|
||||
const params = {}
|
||||
for(let i = 0; i < p.length; i++)
|
||||
{
|
||||
params[pg.args[i]] = p[i]
|
||||
}
|
||||
console.log(params)
|
||||
|
||||
pg.callback({
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const p = page.split('/')
|
||||
routers[`${p[0]}/${p[1]}`] = {callback}
|
||||
routers[`${p[0]}/${p[1]}`].args = []
|
||||
for(let i = 2; i < p.length; i++)
|
||||
{
|
||||
if(p[i].startsWith(':'))
|
||||
routers[`${p[0]}/${p[1]}`].args.push(p[i].substring(1))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
window.router = router;
|
||||
|
||||
router('/', () => {
|
||||
//singleton
|
||||
let pie = new PagesPie;
|
@ -19,6 +19,37 @@ import formatBytes from './format-bytes'
|
||||
|
||||
let session;
|
||||
|
||||
class TorrentsStatistic extends Component {
|
||||
constructor(props)
|
||||
{
|
||||
super(props)
|
||||
|
||||
this.stats = props.stats || {}
|
||||
}
|
||||
componentDidMount()
|
||||
{
|
||||
this.newTorrentFunc = (torrent) => {
|
||||
this.stats.size += torrent.size;
|
||||
this.stats.torrents++;
|
||||
this.stats.files += torrent.files;
|
||||
this.forceUpdate()
|
||||
}
|
||||
|
||||
window.torrentSocket.on('newTorrent', this.newTorrentFunc);
|
||||
}
|
||||
componentWillUnmount()
|
||||
{
|
||||
if(this.newTorrentFunc)
|
||||
window.torrentSocket.off('newTorrent', this.newTorrentFunc);
|
||||
}
|
||||
render()
|
||||
{
|
||||
return (
|
||||
<div className='fs0-75 pad0-75' style={{color: 'rgba(0, 0, 0, 0.541176)'}}>you have information about {this.stats.torrents} torrents and around {this.stats.files} files and { formatBytes(this.stats.size, 1) } of data</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default class Search extends Component {
|
||||
constructor(props)
|
||||
{
|
||||
@ -299,10 +330,8 @@ export default class Search extends Component {
|
||||
}
|
||||
{
|
||||
this.stats
|
||||
?
|
||||
<div className='fs0-75 pad0-75' style={{color: 'rgba(0, 0, 0, 0.541176)'}}>we have information about {this.stats.torrents} torrents and around {this.stats.files} files and { formatBytes(this.stats.size, 1) } of data</div>
|
||||
:
|
||||
null
|
||||
&&
|
||||
<TorrentsStatistic stats={this.stats} />
|
||||
}
|
||||
{
|
||||
this.state.searchingIndicator
|
@ -314,6 +314,10 @@ export default class TorrentPage extends Page {
|
||||
target="_self"
|
||||
label="Download"
|
||||
secondary={true}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
window.open(`magnet:?xt=urn:btih:${this.torrent.hash}`, '_self')
|
||||
}}
|
||||
icon={<svg fill='white' viewBox="0 0 24 24"><path d="M17.374 20.235c2.444-2.981 6.626-8.157 6.626-8.157l-3.846-3.092s-2.857 3.523-6.571 8.097c-4.312 5.312-11.881-2.41-6.671-6.671 4.561-3.729 8.097-6.57 8.097-6.57l-3.092-3.842s-5.173 4.181-8.157 6.621c-2.662 2.175-3.76 4.749-3.76 7.24 0 5.254 4.867 10.139 10.121 10.139 2.487 0 5.064-1.095 7.253-3.765zm4.724-7.953l-1.699 2.111-1.74-1.397 1.701-2.114 1.738 1.4zm-10.386-10.385l1.4 1.738-2.113 1.701-1.397-1.74 2.11-1.699z"/></svg>}
|
||||
/>
|
||||
<div className='fs0-75 pad0-75 center column' style={{color: 'rgba(0, 0, 0, 0.541176)'}}><div>BTIH:</div><div>{this.torrent.hash}</div></div>
|
311
src/background/background.js
Normal file
311
src/background/background.js
Normal file
@ -0,0 +1,311 @@
|
||||
// This is main process of Electron, started as first thing when your
|
||||
// app starts. It runs through entire life of your application.
|
||||
// It doesn't have any windows which you can see on screen, but we can open
|
||||
// window from here.
|
||||
|
||||
import path from "path";
|
||||
import url from "url";
|
||||
import { app, Menu, ipcMain, Tray } from "electron";
|
||||
import { devMenuTemplate } from "./menu/dev_menu_template";
|
||||
import { editMenuTemplate } from "./menu/edit_menu_template";
|
||||
import createWindow from "./helpers/window";
|
||||
|
||||
// Special module holding environment variables which you declared
|
||||
// in config/env_xxx.json file.
|
||||
import env from "env";
|
||||
import spiderCall from './spider'
|
||||
|
||||
const { spawn, exec } = require('child_process')
|
||||
const fs = require('fs')
|
||||
|
||||
const setApplicationMenu = () => {
|
||||
const menus = [editMenuTemplate];
|
||||
if (env.name !== "production") {
|
||||
menus.push(devMenuTemplate);
|
||||
}
|
||||
Menu.setApplicationMenu(Menu.buildFromTemplate(menus));
|
||||
};
|
||||
|
||||
// Save userData in separate folders for each environment.
|
||||
// Thanks to this you can use production and development versions of the app
|
||||
// on same machine like those are two separate apps.
|
||||
if (env.name !== "production") {
|
||||
const userDataPath = app.getPath("userData");
|
||||
app.setPath("userData", `${userDataPath} (${env.name})`);
|
||||
}
|
||||
|
||||
let sphinx = undefined
|
||||
let spider = undefined
|
||||
|
||||
const util = require('util');
|
||||
if (!fs.existsSync(app.getPath("userData"))){
|
||||
fs.mkdirSync(app.getPath("userData"));
|
||||
}
|
||||
const logFile = fs.createWriteStream(app.getPath("userData") + '/rats.log', {flags : 'w'});
|
||||
const logStdout = process.stdout;
|
||||
|
||||
console.log = (...d) => {
|
||||
logFile.write(util.format(...d) + '\n');
|
||||
logStdout.write(util.format(...d) + '\n');
|
||||
};
|
||||
|
||||
const getSphinxPath = () => {
|
||||
if (fs.existsSync('./searchd')) {
|
||||
return './searchd'
|
||||
}
|
||||
|
||||
if (/^win/.test(process.platform) && fs.existsSync('./searchd.exe')) {
|
||||
return './searchd.exe'
|
||||
}
|
||||
|
||||
if (fs.existsSync(fs.realpathSync(__dirname) + '/searchd')) {
|
||||
return fs.realpathSync(__dirname) + '/searchd'
|
||||
}
|
||||
|
||||
if (fs.existsSync(fs.realpathSync(path.join(__dirname, '/../../..')) + '/searchd')) {
|
||||
return fs.realpathSync(path.join(__dirname, '/../../..')) + '/searchd'
|
||||
}
|
||||
|
||||
try {
|
||||
if (process.platform === 'darwin' && fs.existsSync(fs.realpathSync(path.join(__dirname, '/../../../MacOS')) + '/searchd')) {
|
||||
return fs.realpathSync(path.join(__dirname, '/../../../MacOS')) + '/searchd'
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
if (/^win/.test(process.platform) && fs.existsSync('imports/win/searchd.exe')) {
|
||||
return 'imports/win/searchd.exe'
|
||||
}
|
||||
|
||||
if (process.platform === 'linux' && fs.existsSync('imports/linux/searchd')) {
|
||||
return 'imports/linux/searchd'
|
||||
}
|
||||
|
||||
if (process.platform === 'darwin' && fs.existsSync('imports/darwin/searchd')) {
|
||||
return 'imports/darwin/searchd'
|
||||
}
|
||||
|
||||
return 'searchd'
|
||||
}
|
||||
|
||||
const writeSphinxConfig = (path) => {
|
||||
const config = `
|
||||
index torrents
|
||||
{
|
||||
type = rt
|
||||
path = ${path}/database/torrents
|
||||
|
||||
rt_attr_string = hash
|
||||
rt_attr_string = name
|
||||
rt_field = nameIndex
|
||||
rt_attr_bigint = size
|
||||
rt_attr_uint = files
|
||||
rt_attr_uint = piecelength
|
||||
rt_attr_timestamp = added
|
||||
rt_attr_string = ipv4
|
||||
rt_attr_uint = port
|
||||
rt_attr_string = contentType
|
||||
rt_attr_string = contentCategory
|
||||
rt_attr_uint = seeders
|
||||
rt_attr_uint = leechers
|
||||
rt_attr_uint = completed
|
||||
rt_attr_timestamp = trackersChecked
|
||||
rt_attr_uint = good
|
||||
rt_attr_uint = bad
|
||||
}
|
||||
|
||||
index files
|
||||
{
|
||||
type = rt
|
||||
path = ${path}/database/files
|
||||
|
||||
rt_attr_string = path
|
||||
rt_field = pathIndex
|
||||
rt_attr_string = hash
|
||||
rt_attr_bigint = size
|
||||
}
|
||||
|
||||
index statistic
|
||||
{
|
||||
type = rt
|
||||
path = ${path}/database/statistic
|
||||
|
||||
rt_attr_bigint = size
|
||||
rt_attr_bigint = files
|
||||
rt_attr_uint = torrents
|
||||
}
|
||||
|
||||
searchd
|
||||
{
|
||||
listen = 9312
|
||||
listen = 9306:mysql41
|
||||
read_timeout = 5
|
||||
max_children = 30
|
||||
seamless_rotate = 1
|
||||
preopen_indexes = 1
|
||||
unlink_old = 1
|
||||
workers = threads # for RT to work
|
||||
pid_file = ${path}/searchd.pid
|
||||
log = ${path}/searchd.log
|
||||
query_log = ${path}/query.log
|
||||
binlog_path = ${path}
|
||||
}
|
||||
`;
|
||||
|
||||
if (!fs.existsSync(`${path}/database`)){
|
||||
fs.mkdirSync(`${path}/database`);
|
||||
}
|
||||
|
||||
fs.writeFileSync(`${path}/sphinx.conf`, config)
|
||||
console.log(`writed sphinx config to ${path}`)
|
||||
}
|
||||
|
||||
const sphinxPath = path.resolve(getSphinxPath())
|
||||
console.log('Sphinx Path:', sphinxPath)
|
||||
let closerPath = sphinxPath.replace('searchd', 'consolekill')
|
||||
if(/^win/.test(process.platform))
|
||||
{
|
||||
console.log('windows console closer path: ', closerPath)
|
||||
console.log('cmd path', process.env.COMSPEC || 'cmd')
|
||||
}
|
||||
|
||||
const startSphinx = (callback) => {
|
||||
const sphinxConfigDirectory = app.getPath("userData")
|
||||
writeSphinxConfig(sphinxConfigDirectory)
|
||||
|
||||
if(/^win/.test(process.platform))
|
||||
sphinx = spawn(process.env.COMSPEC || 'cmd', ['/c', sphinxPath, '--config', `${sphinxConfigDirectory}/sphinx.conf`])
|
||||
else
|
||||
sphinx = spawn(sphinxPath, ['--config', `${sphinxConfigDirectory}/sphinx.conf`])
|
||||
|
||||
sphinx.stdout.on('data', (data) => {
|
||||
console.log(`sphinx: ${data}`)
|
||||
if (data.includes('accepting connections')) {
|
||||
console.log('catched sphinx start')
|
||||
if(callback)
|
||||
callback()
|
||||
}
|
||||
})
|
||||
|
||||
sphinx.on('close', (code, signal) => {
|
||||
console.log(`sphinx closed with code ${code} and signal ${signal}`)
|
||||
app.quit()
|
||||
})
|
||||
}
|
||||
|
||||
let tray = undefined
|
||||
|
||||
app.on("ready", () => {
|
||||
startSphinx(() => {
|
||||
setApplicationMenu();
|
||||
|
||||
const mainWindow = createWindow("main", {
|
||||
width: 1000,
|
||||
height: 600
|
||||
});
|
||||
|
||||
mainWindow.loadURL(
|
||||
url.format({
|
||||
pathname: path.join(__dirname, "app.html"),
|
||||
protocol: "file:",
|
||||
slashes: true
|
||||
})
|
||||
);
|
||||
|
||||
if (env.name === "development") {
|
||||
mainWindow.openDevTools();
|
||||
}
|
||||
|
||||
tray = new Tray('resources/icons/512x512.png')
|
||||
|
||||
tray.on('click', () => {
|
||||
mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show()
|
||||
})
|
||||
mainWindow.on('show', () => {
|
||||
tray.setHighlightMode('always')
|
||||
})
|
||||
mainWindow.on('hide', () => {
|
||||
tray.setHighlightMode('never')
|
||||
})
|
||||
|
||||
mainWindow.on('minimize', (event) => {
|
||||
event.preventDefault();
|
||||
mainWindow.hide();
|
||||
});
|
||||
|
||||
var contextMenu = Menu.buildFromTemplate([
|
||||
{ label: 'Show', click: function(){
|
||||
mainWindow.show();
|
||||
} },
|
||||
{ label: 'Quit', click: function(){
|
||||
app.isQuiting = true;
|
||||
if (sphinx)
|
||||
stop()
|
||||
else
|
||||
app.quit()
|
||||
} }
|
||||
]);
|
||||
|
||||
tray.setContextMenu(contextMenu)
|
||||
tray.setToolTip('Rats on The Boat search')
|
||||
|
||||
mainWindow.webContents.on('will-navigate', e => { e.preventDefault() })
|
||||
mainWindow.webContents.on('new-window', (event, url, frameName) => {
|
||||
if(frameName == '_self')
|
||||
{
|
||||
event.preventDefault()
|
||||
mainWindow.loadURL(url)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
spider = spiderCall((...data) => mainWindow.webContents.send(...data), (message, callback) => {
|
||||
ipcMain.on(message, (event, arg) => {
|
||||
if(Array.isArray(arg) && typeof arg[arg.length - 1] === 'object' && arg[arg.length - 1].callback)
|
||||
{
|
||||
const id = arg[arg.length - 1].callback
|
||||
arg[arg.length - 1] = (responce) => {
|
||||
mainWindow.webContents.send('callback', id, responce)
|
||||
}
|
||||
}
|
||||
callback.apply(null, arg)
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
let stopProtect = false
|
||||
const stop = () => {
|
||||
if(stopProtect)
|
||||
return
|
||||
stopProtect = true
|
||||
|
||||
if(tray)
|
||||
tray.destroy()
|
||||
|
||||
if(spider)
|
||||
{
|
||||
if(/^win/.test(process.platform))
|
||||
spider.stop(() => exec(`${closerPath} ${sphinx.pid}`))
|
||||
else
|
||||
spider.stop(() => sphinx.kill())
|
||||
}
|
||||
else
|
||||
{
|
||||
if(/^win/.test(process.platform))
|
||||
exec(`${closerPath} ${sphinx.pid}`)
|
||||
else
|
||||
sphinx.kill()
|
||||
}
|
||||
}
|
||||
|
||||
app.on("window-all-closed", () => {
|
||||
if (sphinx)
|
||||
stop()
|
||||
else
|
||||
app.quit()
|
||||
});
|
||||
|
||||
app.on('before-quit', () => {
|
||||
if (sphinx)
|
||||
stop()
|
||||
})
|
98
src/background/bt/client.js
Normal file
98
src/background/bt/client.js
Normal file
@ -0,0 +1,98 @@
|
||||
'use strict'
|
||||
|
||||
const Emiter = require('events')
|
||||
var util = require('util');
|
||||
var net = require('net');
|
||||
|
||||
var PeerQueue = require('./peer-queue');
|
||||
var Wire = require('./wire');
|
||||
const debug = require('debug')('downloader');
|
||||
const config = require('../config')
|
||||
|
||||
class Client extends Emiter
|
||||
{
|
||||
constructor(options) {
|
||||
super();
|
||||
this.timeout = config.downloader.timeout;
|
||||
this.maxConnections = config.downloader.maxConnections;
|
||||
debug('timeout', this.timeout)
|
||||
debug('maxConnections', this.maxConnections)
|
||||
this.activeConnections = 0;
|
||||
this.peers = new PeerQueue(this.maxConnections);
|
||||
this.on('download', this._download);
|
||||
|
||||
// if (typeof options.ignore === 'function') {
|
||||
// this.ignore = options.ignore;
|
||||
//}
|
||||
//else {
|
||||
this.ignore = function (infohash, rinfo, ignore) {
|
||||
ignore(false);
|
||||
};
|
||||
// }
|
||||
}
|
||||
|
||||
_next(infohash, successful) {
|
||||
var req = this.peers.shift(infohash, successful);
|
||||
if (req) {
|
||||
this.ignore(req.infohash.toString('hex'), req.rinfo, (drop) => {
|
||||
if (!drop) {
|
||||
this.emit('download', req.rinfo, req.infohash);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_download(rinfo, infohash)
|
||||
{
|
||||
debug('start download', infohash.toString('hex'), 'connections', this.activeConnections);
|
||||
this.activeConnections++;
|
||||
|
||||
var successful = false;
|
||||
var socket = new net.Socket();
|
||||
|
||||
socket.setTimeout(this.timeout || 5000);
|
||||
socket.connect(rinfo.port, rinfo.address, () => {
|
||||
var wire = new Wire(infohash);
|
||||
socket.pipe(wire).pipe(socket);
|
||||
|
||||
wire.on('metadata', (metadata, infoHash) => {
|
||||
successful = true;
|
||||
debug('successfuly downloader', infoHash, rinfo);
|
||||
this.emit('complete', metadata, infoHash, rinfo);
|
||||
socket.destroy();
|
||||
});
|
||||
|
||||
wire.on('fail', () => {
|
||||
socket.destroy();
|
||||
});
|
||||
|
||||
wire.sendHandshake();
|
||||
});
|
||||
|
||||
socket.on('error', (err) => {
|
||||
socket.destroy();
|
||||
});
|
||||
|
||||
socket.on('timeout', (err) => {
|
||||
socket.destroy();
|
||||
});
|
||||
|
||||
socket.once('close', () => {
|
||||
this.activeConnections--;
|
||||
this._next(infohash, successful);
|
||||
});
|
||||
}
|
||||
|
||||
add(rinfo, infohash) {
|
||||
this.peers.push({infohash: infohash, rinfo: rinfo});
|
||||
if (this.activeConnections < this.maxConnections && this.peers.length() > 0) {
|
||||
this._next();
|
||||
}
|
||||
}
|
||||
|
||||
isIdle() {
|
||||
return this.peers.length() === 0;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Client;
|
33
src/background/bt/cpu-usage.js
Normal file
33
src/background/bt/cpu-usage.js
Normal file
@ -0,0 +1,33 @@
|
||||
let startTime = process.hrtime()
|
||||
let startUsage = process.cpuUsage()
|
||||
|
||||
let keepTime = process.hrtime()
|
||||
let keepUsage = process.cpuUsage()
|
||||
let sw = false
|
||||
|
||||
setInterval(() => {
|
||||
if(!sw) {
|
||||
keepTime = process.hrtime();
|
||||
keepUsage = process.cpuUsage();
|
||||
sw = true;
|
||||
} else {
|
||||
startTime = keepTime;
|
||||
startUsage = keepUsage;
|
||||
sw = false;
|
||||
}
|
||||
}, 500)
|
||||
|
||||
module.exports = () => {
|
||||
function secNSec2ms (secNSec) {
|
||||
return secNSec[0] * 1000 + secNSec[1] / 1000000
|
||||
}
|
||||
|
||||
var elapTime = process.hrtime(startTime)
|
||||
var elapUsage = process.cpuUsage(startUsage)
|
||||
|
||||
var elapTimeMS = secNSec2ms(elapTime)
|
||||
var elapUserMS = elapUsage.user
|
||||
var elapSystMS = elapUsage.system
|
||||
|
||||
return Math.round(100 * ((elapUserMS + elapSystMS) / 1000) / elapTimeMS)
|
||||
}
|
55
src/background/bt/peer-queue.js
Normal file
55
src/background/bt/peer-queue.js
Normal file
@ -0,0 +1,55 @@
|
||||
'use strict';
|
||||
|
||||
var PeerQueue = function (maxSize, perLimit) {
|
||||
this.maxSize = maxSize || 200;
|
||||
this.perLimit = perLimit || 10;
|
||||
this.peers = {};
|
||||
this.reqs = [];
|
||||
};
|
||||
|
||||
PeerQueue.prototype._shift = function () {
|
||||
if (this.length() > 0) {
|
||||
var req = this.reqs.shift();
|
||||
this.peers[req.infohash.toString('hex')] = [];
|
||||
return req;
|
||||
}
|
||||
};
|
||||
|
||||
PeerQueue.prototype.push = function (peer) {
|
||||
var infohashHex = peer.infohash.toString('hex');
|
||||
var peers = this.peers[infohashHex];
|
||||
|
||||
if (peers && peers.length < this.perLimit) {
|
||||
peers.push(peer);
|
||||
}
|
||||
else if (this.length() < this.maxSize) {
|
||||
this.reqs.push(peer);
|
||||
}
|
||||
};
|
||||
|
||||
PeerQueue.prototype.shift = function (infohash, successful) {
|
||||
if (infohash) {
|
||||
var infohashHex = infohash.toString('hex');
|
||||
if (successful === true) {
|
||||
delete this.peers[infohashHex];
|
||||
}
|
||||
else {
|
||||
var peers = this.peers[infohashHex];
|
||||
if (peers) {
|
||||
if (peers.length > 0) {
|
||||
return peers.shift();
|
||||
}
|
||||
else {
|
||||
delete this.peers[infohashHex];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return this._shift();
|
||||
};
|
||||
|
||||
PeerQueue.prototype.length = function () {
|
||||
return this.reqs.length;
|
||||
};
|
||||
|
||||
module.exports = PeerQueue;
|
285
src/background/bt/spider.js
Normal file
285
src/background/bt/spider.js
Normal file
@ -0,0 +1,285 @@
|
||||
'use strict'
|
||||
|
||||
const dgram = require('dgram')
|
||||
const Emiter = require('events')
|
||||
const bencode = require('bencode')
|
||||
const {Table, Node} = require('./table')
|
||||
const Token = require('./token')
|
||||
const cpuUsage = require('./cpu-usage')
|
||||
const config = require('../config')
|
||||
const fs = require('fs')
|
||||
|
||||
const _debug = require('debug')
|
||||
const cpuDebug = _debug('spider:cpu')
|
||||
const trafficDebug = _debug('spider:traffic')
|
||||
|
||||
const bootstraps = [{
|
||||
address: 'router.bittorrent.com',
|
||||
port: 6881
|
||||
}, {
|
||||
address: 'router.utorrent.com',
|
||||
port: 6881
|
||||
}, {
|
||||
address: 'dht.transmissionbt.com',
|
||||
port: 6881
|
||||
}, {
|
||||
address: 'dht.aelitis.com',
|
||||
port: 6881
|
||||
}]
|
||||
|
||||
function isValidPort(port) {
|
||||
return port > 0 && port < (1 << 16)
|
||||
}
|
||||
|
||||
function generateTid() {
|
||||
return parseInt(Math.random() * 99).toString()
|
||||
}
|
||||
|
||||
class Spider extends Emiter {
|
||||
constructor(client) {
|
||||
super()
|
||||
const options = arguments.length? arguments[0]: {}
|
||||
this.udp = dgram.createSocket('udp4')
|
||||
this.table = new Table(options.tableCaption || 1000)
|
||||
this.bootstraps = options.bootstraps || bootstraps
|
||||
this.token = new Token()
|
||||
this.client = client
|
||||
this.ignore = false; // ignore all requests
|
||||
this.initialized = false;
|
||||
this.trafficSpeed = 0
|
||||
|
||||
this.walkInterval = config.spider.walkInterval;
|
||||
this.cpuLimit = config.spider.cpuLimit;
|
||||
this.cpuInterval = config.spider.cpuInterval;
|
||||
}
|
||||
|
||||
send(message, address) {
|
||||
const data = bencode.encode(message)
|
||||
this.udp.send(data, 0, data.length, address.port, address.address)
|
||||
}
|
||||
|
||||
findNode(id, address) {
|
||||
const message = {
|
||||
t: generateTid(),
|
||||
y: 'q',
|
||||
q: 'find_node',
|
||||
a: {
|
||||
id: id,
|
||||
target: Node.generateID()
|
||||
}
|
||||
}
|
||||
this.send(message, address)
|
||||
}
|
||||
|
||||
join() {
|
||||
this.bootstraps.forEach((bootstrap) => {
|
||||
this.findNode(this.table.id, bootstrap)
|
||||
})
|
||||
}
|
||||
|
||||
walk() {
|
||||
if(this.closing)
|
||||
return
|
||||
|
||||
if(!this.client || this.client.isIdle()) {
|
||||
if(
|
||||
!this.ignore
|
||||
&& (this.cpuLimit <= 0 || cpuUsage() < this.cpuLimit + this.cpuInterval)
|
||||
&& (config.trafficMax <= 0 || this.trafficSpeed == 0 || this.trafficSpeed < config.trafficMax)
|
||||
)
|
||||
{
|
||||
const node = this.table.shift()
|
||||
if (node) {
|
||||
this.findNode(Node.neighbor(node.id, this.table.id), {address: node.address, port: node.port})
|
||||
}
|
||||
}
|
||||
}
|
||||
setTimeout(()=>this.walk(), this.walkInterval)
|
||||
}
|
||||
|
||||
onFoundNodes(data) {
|
||||
const nodes = Node.decodeNodes(data)
|
||||
nodes.forEach((node) => {
|
||||
if (node.id != this.table.id && isValidPort(node.port)) {
|
||||
this.table.add(node)
|
||||
}
|
||||
})
|
||||
this.emit('nodes', nodes)
|
||||
}
|
||||
|
||||
onFindNodeRequest(message, address) {
|
||||
if(this.cpuLimit > 0 && cpuUsage() > this.cpuLimit) {
|
||||
return
|
||||
}
|
||||
|
||||
if(config.trafficIgnoreDHT && config.trafficMax > 0 && this.trafficSpeed > 0 && this.trafficSpeed > config.trafficMax) {
|
||||
return
|
||||
}
|
||||
|
||||
const {t: tid, a: {id: nid, target: infohash}} = message
|
||||
|
||||
if (tid === undefined || target.length != 20 || nid.length != 20) {
|
||||
return
|
||||
}
|
||||
|
||||
this.send({
|
||||
t: tid,
|
||||
y: 'r',
|
||||
r: {
|
||||
id: Node.neighbor(nid, this.table.id),
|
||||
nodes: Node.encodeNodes(this.table.first())
|
||||
}
|
||||
}, address)
|
||||
}
|
||||
|
||||
onGetPeersRequest(message, address) {
|
||||
if(this.cpuLimit > 0 && cpuUsage() > this.cpuLimit) {
|
||||
return
|
||||
}
|
||||
|
||||
if(config.trafficIgnoreDHT && config.trafficMax > 0 && this.trafficSpeed > 0 && this.trafficSpeed > config.trafficMax) {
|
||||
return
|
||||
}
|
||||
|
||||
const {t: tid, a: {id: nid, info_hash: infohash}} = message
|
||||
|
||||
if (tid === undefined || infohash.length != 20 || nid.length != 20) {
|
||||
return
|
||||
}
|
||||
|
||||
this.send({
|
||||
t: tid,
|
||||
y: 'r',
|
||||
r: {
|
||||
id: Node.neighbor(nid, this.table.id),
|
||||
nodes: Node.encodeNodes(this.table.first()),
|
||||
token: this.token.token
|
||||
}
|
||||
}, address)
|
||||
|
||||
this.emit('unensureHash', infohash.toString('hex').toUpperCase())
|
||||
}
|
||||
|
||||
onAnnouncePeerRequest(message, address) {
|
||||
let {t: tid, a: {info_hash: infohash, token: token, id: id, implied_port: implied, port: port}} = message
|
||||
if (!tid) return
|
||||
|
||||
if (!this.token.isValid(token)) return
|
||||
|
||||
port = (implied != undefined && implied != 0) ? address.port : (port || 0)
|
||||
if (!isValidPort(port)) return
|
||||
|
||||
this.send({ t: tid, y: 'r', r: { id: Node.neighbor(id, this.table.id) } }, address)
|
||||
|
||||
let addressPair = {
|
||||
address: address.address,
|
||||
port: port
|
||||
};
|
||||
this.emit('ensureHash', infohash.toString('hex').toUpperCase(), addressPair)
|
||||
if(this.client && !this.ignore) {
|
||||
cpuDebug('cpu usage:' + cpuUsage())
|
||||
if(this.cpuLimit <= 0 || cpuUsage() <= this.cpuLimit + this.cpuInterval) {
|
||||
this.client.add(addressPair, infohash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onPingRequest(message, address) {
|
||||
if(this.cpuLimit > 0 && cpuUsage() > this.cpuLimit) {
|
||||
return
|
||||
}
|
||||
|
||||
if(config.trafficIgnoreDHT && config.trafficMax > 0 && this.trafficSpeed > 0 && this.trafficSpeed > config.trafficMax) {
|
||||
return
|
||||
}
|
||||
|
||||
this.send({ t: message.t, y: 'r', r: { id: Node.neighbor(message.a.id, this.table.id) } }, address)
|
||||
}
|
||||
|
||||
parse(data, address) {
|
||||
try {
|
||||
const message = bencode.decode(data)
|
||||
if (message.y.toString() == 'r' && message.r.nodes) {
|
||||
this.onFoundNodes(message.r.nodes)
|
||||
} else if (message.y.toString() == 'q') {
|
||||
switch(message.q.toString()) {
|
||||
case 'get_peers':
|
||||
this.onGetPeersRequest(message, address)
|
||||
break
|
||||
case 'announce_peer':
|
||||
this.onAnnouncePeerRequest(message, address)
|
||||
break
|
||||
case 'find_node':
|
||||
this.onFindNodeRequest(message, address)
|
||||
break
|
||||
case 'ping':
|
||||
this.onPingRequest(message, address)
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
listen(port) {
|
||||
if(this.initialized)
|
||||
return
|
||||
this.initialized = true
|
||||
|
||||
this.udp.bind(port)
|
||||
this.udp.on('listening', () => {
|
||||
console.log(`Listen DHT protocol on ${this.udp.address().address}:${this.udp.address().port}`)
|
||||
})
|
||||
this.udp.on('message', (data, addr) => {
|
||||
this.parse(data, addr)
|
||||
})
|
||||
this.udp.on('error', (err) => {})
|
||||
this.joinInterval = setInterval(() => {
|
||||
if(!this.client || this.client.isIdle()) {
|
||||
this.join()
|
||||
}
|
||||
}, 3000)
|
||||
this.join()
|
||||
this.walk()
|
||||
|
||||
if(config.trafficMax > 0)
|
||||
{
|
||||
trafficDebug('inore dht traffic', config.trafficIgnoreDHT)
|
||||
const path = `/sys/class/net/${config.trafficInterface}/statistics/rx_bytes`
|
||||
if(fs.existsSync(path))
|
||||
{
|
||||
trafficDebug('limitation', config.trafficMax / 1024, 'kbps/s')
|
||||
let traffic = 0
|
||||
this.trafficInterval = setInterval(() => {
|
||||
fs.readFile(path, (err, newTraffic) => {
|
||||
if(err)
|
||||
return
|
||||
|
||||
if(traffic === 0)
|
||||
traffic = newTraffic
|
||||
|
||||
this.trafficSpeed = (newTraffic - traffic) / config.trafficUpdateTime
|
||||
|
||||
trafficDebug('traffic rx', this.trafficSpeed / 1024, 'kbps/s')
|
||||
|
||||
traffic = newTraffic
|
||||
})
|
||||
}, 1000 * config.trafficUpdateTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close(callback)
|
||||
{
|
||||
clearInterval(this.joinInterval)
|
||||
if(this.trafficInterval)
|
||||
clearInterval(this.trafficInterval)
|
||||
this.closing = true
|
||||
this.udp.close(() => {
|
||||
this.initialized = false
|
||||
if(callback)
|
||||
callback()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Spider
|
72
src/background/bt/table.js
Normal file
72
src/background/bt/table.js
Normal file
@ -0,0 +1,72 @@
|
||||
'use strict'
|
||||
|
||||
const crypto = require('crypto')
|
||||
|
||||
class Node {
|
||||
static generateID() {
|
||||
return crypto.createHash('sha1').update(crypto.randomBytes(20)).digest()
|
||||
}
|
||||
|
||||
constructor(id) {
|
||||
this.id = id || Node.generateNodeID()
|
||||
}
|
||||
|
||||
static neighbor(target, id) {
|
||||
return Buffer.concat([target.slice(0, 10), id.slice(10)])
|
||||
}
|
||||
|
||||
static encodeNodes(nodes) {
|
||||
return Buffer.concat(nodes.map((node)=> Buffer.concat([node.id, Node.encodeIP(node.address), Node.encodePort(node.port)])))
|
||||
}
|
||||
|
||||
static decodeNodes(data) {
|
||||
const nodes = []
|
||||
for (let i = 0; i + 26 <= data.length; i += 26) {
|
||||
nodes.push({
|
||||
id: data.slice(i, i + 20),
|
||||
address: `${data[i + 20]}.${data[i + 21]}.${data[i + 22]}.${data[i + 23]}`,
|
||||
port: data.readUInt16BE(i + 24)
|
||||
})
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
static encodeIP(ip) {
|
||||
return Buffer.from(ip.split('.').map((i)=>parseInt(i)))
|
||||
}
|
||||
|
||||
static encodePort(port) {
|
||||
const data = Buffer.alloc(2)
|
||||
data.writeUInt16BE(port, 0)
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
class Table{
|
||||
constructor(cap) {
|
||||
this.id = Node.generateID()
|
||||
this.nodes = []
|
||||
this.caption = cap
|
||||
}
|
||||
add(node) {
|
||||
if (this.nodes.length < this.caption) {
|
||||
this.nodes.push(node)
|
||||
}
|
||||
}
|
||||
shift() {
|
||||
return this.nodes.shift()
|
||||
}
|
||||
size() {
|
||||
return this.nodes.length;
|
||||
}
|
||||
first() {
|
||||
if(this.nodes.length >= 8) {
|
||||
return this.nodes.slice(0, 8)
|
||||
}else if(this.nodes.length > 0) {
|
||||
return new Array(8).join().split(',').map(()=> this.nodes[0])
|
||||
}
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {Table, Node}
|
16
src/background/bt/token.js
Normal file
16
src/background/bt/token.js
Normal file
@ -0,0 +1,16 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = class {
|
||||
constructor() {
|
||||
this.generate()
|
||||
setInterval(()=> this.generate(), 60000*15)
|
||||
}
|
||||
|
||||
isValid(t) {
|
||||
return t.toString() === this.token.toString()
|
||||
}
|
||||
|
||||
generate() {
|
||||
this.token = new Buffer([parseInt(Math.random()*200), parseInt(Math.random()*200)])
|
||||
}
|
||||
}
|
129
src/background/bt/udp-tracker-request.js
Normal file
129
src/background/bt/udp-tracker-request.js
Normal file
@ -0,0 +1,129 @@
|
||||
const dgram = require('dgram');
|
||||
const server = dgram.createSocket("udp4")
|
||||
const config = require('../config');
|
||||
const debug = require('debug')('peers-scrape');
|
||||
|
||||
const ACTION_CONNECT = 0
|
||||
const ACTION_ANNOUNCE = 1
|
||||
const ACTION_SCRAPE = 2
|
||||
const ACTION_ERROR = 3
|
||||
|
||||
const connectionIdHigh = 0x417
|
||||
const connectionIdLow = 0x27101980
|
||||
const requests = {};
|
||||
|
||||
let message = function (buf, host, port) {
|
||||
server.send(buf, 0, buf.length, port, host, function(err, bytes) {
|
||||
if (err) {
|
||||
console.log(err.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
let connectTracker = function(connection) {
|
||||
debug('start screape connection');
|
||||
let buffer = new Buffer(16);
|
||||
|
||||
const transactionId = Math.floor((Math.random()*100000)+1);
|
||||
|
||||
buffer.fill(0);
|
||||
|
||||
buffer.writeUInt32BE(connectionIdHigh, 0);
|
||||
buffer.writeUInt32BE(connectionIdLow, 4);
|
||||
buffer.writeUInt32BE(ACTION_CONNECT, 8);
|
||||
buffer.writeUInt32BE(transactionId, 12);
|
||||
|
||||
// очистка старых соединений
|
||||
for(transaction in requests) {
|
||||
if((new Date).getTime() - requests[transaction].date.getTime() > config.udpTrackersTimeout) {
|
||||
delete requests[transaction];
|
||||
}
|
||||
}
|
||||
|
||||
requests[transactionId] = connection;
|
||||
message(buffer, connection.host, connection.port);
|
||||
};
|
||||
|
||||
let scrapeTorrent = function (connectionIdHigh, connectionIdLow, transactionId) {
|
||||
let connection = requests[transactionId];
|
||||
if(!connection)
|
||||
return;
|
||||
|
||||
debug('start scrape');
|
||||
let buffer = new Buffer(56)
|
||||
|
||||
buffer.fill(0);
|
||||
|
||||
buffer.writeUInt32BE(connectionIdHigh, 0);
|
||||
buffer.writeUInt32BE(connectionIdLow, 4);
|
||||
buffer.writeUInt32BE(ACTION_SCRAPE, 8);
|
||||
buffer.writeUInt32BE(transactionId, 12);
|
||||
buffer.write(connection.hash, 16, buffer.length, 'hex');
|
||||
|
||||
// do scrape
|
||||
message(buffer, connection.host, connection.port);
|
||||
};
|
||||
|
||||
server.on("message", function (msg, rinfo) {
|
||||
let buffer = new Buffer(msg)
|
||||
|
||||
const action = buffer.readUInt32BE(0, 4);
|
||||
const transactionId = buffer.readUInt32BE(4, 4);
|
||||
|
||||
if(!(transactionId in requests))
|
||||
return;
|
||||
|
||||
debug("returned action: " + action);
|
||||
debug("returned transactionId: " + transactionId);
|
||||
|
||||
if (action === ACTION_CONNECT) {
|
||||
debug("connect response");
|
||||
|
||||
let connectionIdHigh = buffer.readUInt32BE(8, 4);
|
||||
let connectionIdLow = buffer.readUInt32BE(12, 4);
|
||||
|
||||
scrapeTorrent(connectionIdHigh, connectionIdLow, transactionId);
|
||||
|
||||
} else if (action === ACTION_SCRAPE) {
|
||||
debug("scrape response");
|
||||
|
||||
let seeders = buffer.readUInt32BE(8, 4);
|
||||
let completed = buffer.readUInt32BE(12, 4);
|
||||
let leechers = buffer.readUInt32BE(16, 4);
|
||||
|
||||
let connection = requests[transactionId];
|
||||
connection.callback({
|
||||
host: connection.host,
|
||||
port: connection.port,
|
||||
hash: connection.hash,
|
||||
seeders,
|
||||
completed,
|
||||
leechers
|
||||
})
|
||||
delete requests[transactionId];
|
||||
} else if (action === ACTION_ERROR) {
|
||||
delete requests[transactionId];
|
||||
console.log("error in scrape response");
|
||||
}
|
||||
});
|
||||
|
||||
let getPeersStatistic = (host, port, hash, callback) => {
|
||||
let connection = {
|
||||
host, port, hash, callback, date: new Date()
|
||||
}
|
||||
connectTracker(connection);
|
||||
}
|
||||
|
||||
server.on("listening", function () {
|
||||
var address = server.address();
|
||||
console.log("listening udp tracker respose on " + address.address + ":" + address.port);
|
||||
});
|
||||
|
||||
server.bind(config.udpTrackersPort);
|
||||
|
||||
module.exports = getPeersStatistic;
|
||||
|
||||
//getPeersStatistic('tracker.glotorrents.com', 6969, "d096ff66557a5ea7030680967610e38b37434ea8", (data) => {
|
||||
// console.log(data)
|
||||
//});
|
||||
|
247
src/background/bt/wire.js
Normal file
247
src/background/bt/wire.js
Normal file
@ -0,0 +1,247 @@
|
||||
'use strict';
|
||||
|
||||
var stream = require('stream');
|
||||
var crypto = require('crypto');
|
||||
var util = require('util');
|
||||
|
||||
var BitField = require('bitfield');
|
||||
var bencode = require('bencode');
|
||||
|
||||
var {Node} = require('./table');
|
||||
|
||||
var BT_RESERVED = new Buffer([0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01]);
|
||||
var BT_PROTOCOL = new Buffer('BitTorrent protocol');
|
||||
var PIECE_LENGTH = Math.pow(2, 14);
|
||||
var MAX_METADATA_SIZE = 10000000;
|
||||
var BITFIELD_GROW = 1000;
|
||||
var EXT_HANDSHAKE_ID = 0;
|
||||
var BT_MSG_ID = 20;
|
||||
|
||||
var Wire = function(infohash) {
|
||||
stream.Duplex.call(this);
|
||||
|
||||
this._bitfield = new BitField(0, { grow: BITFIELD_GROW });
|
||||
this._infohash = infohash;
|
||||
|
||||
this._buffer = [];
|
||||
this._bufferSize = 0;
|
||||
|
||||
this._next = null;
|
||||
this._nextSize = 0;
|
||||
|
||||
this._metadata = null;
|
||||
this._metadataSize = null;
|
||||
this._numPieces = 0;
|
||||
this._ut_metadata = null;
|
||||
|
||||
this._onHandshake();
|
||||
}
|
||||
|
||||
util.inherits(Wire, stream.Duplex);
|
||||
|
||||
Wire.prototype._onMessageLength = function (buffer) {
|
||||
if (buffer.length >= 4) {
|
||||
var length = buffer.readUInt32BE(0);
|
||||
if (length > 0) {
|
||||
this._register(length, this._onMessage)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Wire.prototype._onMessage = function (buffer) {
|
||||
this._register(4, this._onMessageLength)
|
||||
if (buffer[0] == BT_MSG_ID) {
|
||||
this._onExtended(buffer.readUInt8(1), buffer.slice(2));
|
||||
}
|
||||
};
|
||||
|
||||
Wire.prototype._onExtended = function(ext, buf) {
|
||||
if (ext === 0) {
|
||||
try {
|
||||
this._onExtHandshake(bencode.decode(buf));
|
||||
}
|
||||
catch (err) {
|
||||
this._fail();
|
||||
}
|
||||
}
|
||||
else {
|
||||
this._onPiece(buf);
|
||||
}
|
||||
};
|
||||
|
||||
Wire.prototype._register = function (size, next) {
|
||||
this._nextSize = size;
|
||||
this._next = next;
|
||||
};
|
||||
|
||||
Wire.prototype.end = function() {
|
||||
stream.Duplex.prototype.end.apply(this, arguments);
|
||||
};
|
||||
|
||||
Wire.prototype._onHandshake = function() {
|
||||
this._register(1, function(buffer) {
|
||||
if (buffer.length == 0) {
|
||||
this.end();
|
||||
return this._fail();
|
||||
}
|
||||
var pstrlen = buffer.readUInt8(0);
|
||||
this._register(pstrlen + 48, function(handshake) {
|
||||
var protocol = handshake.slice(0, pstrlen);
|
||||
if (protocol.toString() !== BT_PROTOCOL.toString()) {
|
||||
this.end();
|
||||
this._fail();
|
||||
return;
|
||||
}
|
||||
handshake = handshake.slice(pstrlen);
|
||||
if ( !!(handshake[5] & 0x10) ) {
|
||||
this._register(4, this._onMessageLength);
|
||||
this._sendExtHandshake();
|
||||
}
|
||||
else {
|
||||
this._fail();
|
||||
}
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Wire.prototype._onExtHandshake = function(extHandshake) {
|
||||
if (!extHandshake.metadata_size || !extHandshake.m.ut_metadata
|
||||
|| extHandshake.metadata_size > MAX_METADATA_SIZE) {
|
||||
this._fail();
|
||||
return;
|
||||
}
|
||||
|
||||
this._metadataSize = extHandshake.metadata_size;
|
||||
this._numPieces = Math.ceil(this._metadataSize / PIECE_LENGTH);
|
||||
this._ut_metadata = extHandshake.m.ut_metadata;
|
||||
|
||||
this._requestPieces();
|
||||
}
|
||||
|
||||
Wire.prototype._requestPieces = function() {
|
||||
this._metadata = new Buffer(this._metadataSize);
|
||||
for (var piece = 0; piece < this._numPieces; piece++) {
|
||||
this._requestPiece(piece);
|
||||
}
|
||||
};
|
||||
|
||||
Wire.prototype._requestPiece = function(piece) {
|
||||
var msg = Buffer.concat([
|
||||
new Buffer([BT_MSG_ID]),
|
||||
new Buffer([this._ut_metadata]),
|
||||
bencode.encode({msg_type: 0, piece: piece})
|
||||
]);
|
||||
this._sendMessage(msg);
|
||||
};
|
||||
|
||||
Wire.prototype._sendPacket = function(packet) {
|
||||
this.push(packet);
|
||||
};
|
||||
|
||||
Wire.prototype._sendMessage = function(msg) {
|
||||
var buf = new Buffer(4);
|
||||
buf.writeUInt32BE(msg.length, 0);
|
||||
this._sendPacket(Buffer.concat([buf, msg]));
|
||||
};
|
||||
|
||||
Wire.prototype.sendHandshake = function() {
|
||||
var peerID = Node.generateID();
|
||||
var packet = Buffer.concat([
|
||||
new Buffer([BT_PROTOCOL.length]),
|
||||
BT_PROTOCOL, BT_RESERVED, this._infohash, peerID
|
||||
]);
|
||||
this._sendPacket(packet);
|
||||
};
|
||||
|
||||
Wire.prototype._sendExtHandshake = function() {
|
||||
var msg = Buffer.concat([
|
||||
new Buffer([BT_MSG_ID]),
|
||||
new Buffer([EXT_HANDSHAKE_ID]),
|
||||
bencode.encode({m: {ut_metadata: 1}})
|
||||
]);
|
||||
this._sendMessage(msg);
|
||||
};
|
||||
|
||||
Wire.prototype._onPiece = function(piece) {
|
||||
var dict, trailer;
|
||||
try {
|
||||
var str = piece.toString();
|
||||
var trailerIndex = str.indexOf('ee') + 2;
|
||||
dict = bencode.decode(str.substring(0, trailerIndex));
|
||||
trailer = piece.slice(trailerIndex);
|
||||
}
|
||||
catch (err) {
|
||||
this._fail();
|
||||
return;
|
||||
}
|
||||
if (dict.msg_type != 1) {
|
||||
this._fail();
|
||||
return;
|
||||
}
|
||||
if (trailer.length > PIECE_LENGTH) {
|
||||
this._fail();
|
||||
return;
|
||||
}
|
||||
trailer.copy(this._metadata, dict.piece * PIECE_LENGTH);
|
||||
this._bitfield.set(dict.piece);
|
||||
this._checkDone();
|
||||
};
|
||||
|
||||
Wire.prototype._checkDone = function () {
|
||||
var done = true;
|
||||
for (var piece = 0; piece < this._numPieces; piece++) {
|
||||
if (!this._bitfield.get(piece)) {
|
||||
done = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!done) {
|
||||
return
|
||||
}
|
||||
this._onDone(this._metadata);
|
||||
};
|
||||
|
||||
Wire.prototype._onDone = function(metadata) {
|
||||
try {
|
||||
var info = bencode.decode(metadata).info;
|
||||
if (info) {
|
||||
metadata = bencode.encode(info);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
this._fail();
|
||||
return;
|
||||
}
|
||||
var infohash = crypto.createHash('sha1').update(metadata).digest('hex');
|
||||
if (this._infohash.toString('hex') != infohash ) {
|
||||
this._fail();
|
||||
return false;
|
||||
}
|
||||
this.emit('metadata', {info: bencode.decode(metadata, 'utf8')}, this._infohash);
|
||||
};
|
||||
|
||||
Wire.prototype._fail = function() {
|
||||
this.emit('fail');
|
||||
};
|
||||
|
||||
Wire.prototype._write = function (buf, encoding, next) {
|
||||
this._bufferSize += buf.length;
|
||||
this._buffer.push(buf);
|
||||
|
||||
while (this._bufferSize >= this._nextSize) {
|
||||
var buffer = Buffer.concat(this._buffer);
|
||||
this._bufferSize -= this._nextSize;
|
||||
this._buffer = this._bufferSize
|
||||
? [buffer.slice(this._nextSize)]
|
||||
: [];
|
||||
this._next(buffer.slice(0, this._nextSize));
|
||||
}
|
||||
|
||||
next(null);
|
||||
}
|
||||
|
||||
Wire.prototype._read = function() {
|
||||
// do nothing
|
||||
};
|
||||
|
||||
module.exports = Wire;
|
83
src/background/config.js
Normal file
83
src/background/config.js
Normal file
@ -0,0 +1,83 @@
|
||||
let config = {
|
||||
indexer: true,
|
||||
|
||||
domain: 'ratsontheboat.org',
|
||||
httpPort: 8095,
|
||||
spiderPort: 4445,
|
||||
udpTrackersPort: 4446,
|
||||
udpTrackersTimeout: 3 * 60 * 1000,
|
||||
|
||||
sitemapMaxSize: 25000,
|
||||
|
||||
sphinx: {
|
||||
host : 'localhost',
|
||||
port : 9306,
|
||||
connectionLimit: 30
|
||||
},
|
||||
|
||||
mysql: {
|
||||
host : 'localhost',
|
||||
user : 'btsearch',
|
||||
password : 'pirateal100x',
|
||||
database : 'btsearch',
|
||||
connectionLimit: 40
|
||||
},
|
||||
|
||||
spider: {
|
||||
walkInterval: 5,
|
||||
cpuLimit: 0,
|
||||
cpuInterval: 10,
|
||||
},
|
||||
|
||||
downloader: {
|
||||
maxConnections: 200,
|
||||
timeout: 5000
|
||||
},
|
||||
|
||||
cleanup: true,
|
||||
cleanupDiscLimit: 7 * 1024 * 1024 * 1024,
|
||||
spaceQuota: false,
|
||||
spaceDiskLimit: 7 * 1024 * 1024 * 1024,
|
||||
|
||||
trafficInterface: 'enp2s0',
|
||||
trafficMax: 0,
|
||||
trafficUpdateTime: 3, //secs
|
||||
trafficIgnoreDHT: false
|
||||
}
|
||||
|
||||
const fs = require('fs');
|
||||
const debug = require('debug')('config')
|
||||
|
||||
const configProxy = new Proxy(config, {
|
||||
set: (target, prop, value, receiver) => {
|
||||
target[prop] = value
|
||||
|
||||
if(!fs.existsSync('config.json'))
|
||||
fs.writeFileSync('config.json', '{}')
|
||||
|
||||
fs.readFile('config.json', 'utf8', (err, data) => {
|
||||
let obj = JSON.parse(data)
|
||||
obj[prop] = value;
|
||||
fs.writeFileSync('config.json', JSON.stringify(obj, null, 4), 'utf8');
|
||||
debug('saving config.json:', prop, '=', value)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
config.load = () => {
|
||||
debug('loading configuration')
|
||||
if(fs.existsSync('config.json'))
|
||||
{
|
||||
debug('finded configuration config.json')
|
||||
const data = fs.readFileSync('config.json', 'utf8')
|
||||
const obj = JSON.parse(data);
|
||||
for(let prop in obj)
|
||||
{
|
||||
config[prop] = obj[prop]
|
||||
debug('config.json:', prop, '=', obj[prop])
|
||||
}
|
||||
}
|
||||
return configProxy
|
||||
}
|
||||
|
||||
module.exports = configProxy.load()
|
3
src/background/config.json
Normal file
3
src/background/config.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"indexer": true
|
||||
}
|
84
src/background/helpers/window.js
Normal file
84
src/background/helpers/window.js
Normal file
@ -0,0 +1,84 @@
|
||||
// This helper remembers the size and position of your windows (and restores
|
||||
// them in that place after app relaunch).
|
||||
// Can be used for more than one window, just construct many
|
||||
// instances of it and give each different name.
|
||||
|
||||
import { app, BrowserWindow, screen } from "electron";
|
||||
import jetpack from "fs-jetpack";
|
||||
|
||||
export default (name, options) => {
|
||||
const userDataDir = jetpack.cwd(app.getPath("userData"));
|
||||
const stateStoreFile = `window-state-${name}.json`;
|
||||
const defaultSize = {
|
||||
width: options.width,
|
||||
height: options.height
|
||||
};
|
||||
let state = {};
|
||||
let win;
|
||||
|
||||
const restore = () => {
|
||||
let restoredState = {};
|
||||
try {
|
||||
restoredState = userDataDir.read(stateStoreFile, "json");
|
||||
} catch (err) {
|
||||
// For some reason json can't be read (might be corrupted).
|
||||
// No worries, we have defaults.
|
||||
}
|
||||
return Object.assign({}, defaultSize, restoredState);
|
||||
};
|
||||
|
||||
const getCurrentPosition = () => {
|
||||
const position = win.getPosition();
|
||||
const size = win.getSize();
|
||||
return {
|
||||
x: position[0],
|
||||
y: position[1],
|
||||
width: size[0],
|
||||
height: size[1]
|
||||
};
|
||||
};
|
||||
|
||||
const windowWithinBounds = (windowState, bounds) => {
|
||||
return (
|
||||
windowState.x >= bounds.x &&
|
||||
windowState.y >= bounds.y &&
|
||||
windowState.x + windowState.width <= bounds.x + bounds.width &&
|
||||
windowState.y + windowState.height <= bounds.y + bounds.height
|
||||
);
|
||||
};
|
||||
|
||||
const resetToDefaults = () => {
|
||||
const bounds = screen.getPrimaryDisplay().bounds;
|
||||
return Object.assign({}, defaultSize, {
|
||||
x: (bounds.width - defaultSize.width) / 2,
|
||||
y: (bounds.height - defaultSize.height) / 2
|
||||
});
|
||||
};
|
||||
|
||||
const ensureVisibleOnSomeDisplay = windowState => {
|
||||
const visible = screen.getAllDisplays().some(display => {
|
||||
return windowWithinBounds(windowState, display.bounds);
|
||||
});
|
||||
if (!visible) {
|
||||
// Window is partially or fully not visible now.
|
||||
// Reset it to safe defaults.
|
||||
return resetToDefaults();
|
||||
}
|
||||
return windowState;
|
||||
};
|
||||
|
||||
const saveState = () => {
|
||||
if (!win.isMinimized() && !win.isMaximized()) {
|
||||
Object.assign(state, getCurrentPosition());
|
||||
}
|
||||
userDataDir.write(stateStoreFile, state, { atomic: true });
|
||||
};
|
||||
|
||||
state = ensureVisibleOnSomeDisplay(restore());
|
||||
|
||||
win = new BrowserWindow(Object.assign({}, options, state));
|
||||
|
||||
win.on("close", saveState);
|
||||
|
||||
return win;
|
||||
};
|
28
src/background/menu/dev_menu_template.js
Normal file
28
src/background/menu/dev_menu_template.js
Normal file
@ -0,0 +1,28 @@
|
||||
import { app, BrowserWindow } from "electron";
|
||||
|
||||
export const devMenuTemplate = {
|
||||
label: "Development",
|
||||
submenu: [
|
||||
{
|
||||
label: "Reload",
|
||||
accelerator: "CmdOrCtrl+R",
|
||||
click: () => {
|
||||
BrowserWindow.getFocusedWindow().webContents.reloadIgnoringCache();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "Toggle DevTools",
|
||||
accelerator: "Alt+CmdOrCtrl+I",
|
||||
click: () => {
|
||||
BrowserWindow.getFocusedWindow().toggleDevTools();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "Quit",
|
||||
accelerator: "CmdOrCtrl+Q",
|
||||
click: () => {
|
||||
app.quit();
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
12
src/background/menu/edit_menu_template.js
Normal file
12
src/background/menu/edit_menu_template.js
Normal file
@ -0,0 +1,12 @@
|
||||
export const editMenuTemplate = {
|
||||
label: "Edit",
|
||||
submenu: [
|
||||
{ label: "Undo", accelerator: "CmdOrCtrl+Z", selector: "undo:" },
|
||||
{ label: "Redo", accelerator: "Shift+CmdOrCtrl+Z", selector: "redo:" },
|
||||
{ type: "separator" },
|
||||
{ label: "Cut", accelerator: "CmdOrCtrl+X", selector: "cut:" },
|
||||
{ label: "Copy", accelerator: "CmdOrCtrl+C", selector: "copy:" },
|
||||
{ label: "Paste", accelerator: "CmdOrCtrl+V", selector: "paste:" },
|
||||
{ label: "Select All", accelerator: "CmdOrCtrl+A", selector: "selectAll:" }
|
||||
]
|
||||
};
|
979
src/background/spider.js
Normal file
979
src/background/spider.js
Normal file
@ -0,0 +1,979 @@
|
||||
const config = require('./config');
|
||||
const client = new (require('./bt/client'))
|
||||
const spider = new (require('./bt/spider'))(client)
|
||||
const mysql = require('mysql');
|
||||
const getPeersStatisticUDP = require('./bt/udp-tracker-request')
|
||||
|
||||
//var express = require('express');
|
||||
//var app = express();
|
||||
//var server = require('http').Server(app);
|
||||
//var io = require('socket.io')(server);
|
||||
//var sm = require('sitemap');
|
||||
//var phantomjs = require('phantomjs-prebuilt')
|
||||
var ipaddr = require('ipaddr.js');
|
||||
//const disk = require('diskusage');
|
||||
const os = require('os');
|
||||
let rootPath = os.platform() === 'win32' ? 'c:' : '/';
|
||||
|
||||
const _debug = require('debug')
|
||||
const cleanupDebug = _debug('main:cleanup');
|
||||
const balanceDebug = _debug('main:balance');
|
||||
const fakeTorrentsDebug = _debug('main:fakeTorrents');
|
||||
const quotaDebug = _debug('main:quota');
|
||||
|
||||
const {torrentTypeDetect} = require('../app/content');
|
||||
|
||||
// Start server
|
||||
//server.listen(config.httpPort);
|
||||
//console.log('Listening web server on', config.httpPort, 'port')
|
||||
|
||||
module.exports = function (send, recive)
|
||||
{
|
||||
|
||||
let torrentsId = 1;
|
||||
let filesId = 1;
|
||||
|
||||
let mysqlPool = mysql.createPool({
|
||||
connectionLimit: config.mysql.connectionLimit,
|
||||
host : config.sphinx.host,
|
||||
port : config.sphinx.port
|
||||
});
|
||||
|
||||
let sphinx = mysql.createPool({
|
||||
connectionLimit: config.sphinx.connectionLimit,
|
||||
host : config.sphinx.host,
|
||||
port : config.sphinx.port
|
||||
});
|
||||
|
||||
const udpTrackers = [
|
||||
{
|
||||
host: 'tracker.coppersurfer.tk',
|
||||
port: 6969
|
||||
},
|
||||
{
|
||||
host: 'tracker.leechers-paradise.org',
|
||||
port: 6969
|
||||
},
|
||||
{
|
||||
host: 'tracker.opentrackr.org',
|
||||
port: 1337
|
||||
},
|
||||
{
|
||||
host: '9.rarbg.me',
|
||||
port: 2710
|
||||
}
|
||||
]
|
||||
|
||||
let mysqlSingle;
|
||||
function handleListenerDisconnect() {
|
||||
mysqlSingle = mysql.createConnection({
|
||||
host : config.sphinx.host,
|
||||
port : config.sphinx.port
|
||||
});
|
||||
|
||||
mysqlSingle.connect(function(mysqlError) {
|
||||
if (mysqlError) {
|
||||
console.error('error connecting: ' + mysqlError.stack);
|
||||
return;
|
||||
}
|
||||
|
||||
mysqlSingle.query("SELECT MAX(`id`) as mx from torrents", (err, rows) => {
|
||||
if(err)
|
||||
return
|
||||
|
||||
if(rows[0] && rows[0].mx >= 1)
|
||||
torrentsId = rows[0].mx + 1;
|
||||
})
|
||||
|
||||
mysqlSingle.query("SELECT MAX(`id`) as mx from files", (err, rows) => {
|
||||
if(err)
|
||||
return
|
||||
|
||||
if(rows[0] &&rows[0].mx >= 1)
|
||||
filesId = rows[0].mx + 1;
|
||||
})
|
||||
});
|
||||
|
||||
mysqlSingle.on('error', function(err) {
|
||||
console.log('db error', err);
|
||||
if(err.code === 'PROTOCOL_CONNECTION_LOST') { // Connection to the MySQL server is usually
|
||||
handleListenerDisconnect(); // lost due to either server restart, or a
|
||||
} else { // connnection idle timeout (the wait_timeout
|
||||
throw err; // server variable configures this)
|
||||
}
|
||||
});
|
||||
|
||||
const query = mysqlSingle.query;
|
||||
mysqlSingle.query = (...args) => {
|
||||
let callback, i;
|
||||
for(i = 1; i < args.length; i++)
|
||||
{
|
||||
if(typeof args[i] == 'function')
|
||||
{
|
||||
callback = args[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(callback)
|
||||
{
|
||||
pushDatabaseBalance();
|
||||
args[i] = (...a) => {
|
||||
popDatabaseBalance();
|
||||
callback(...a)
|
||||
}
|
||||
}
|
||||
else if(args.length <= 2)
|
||||
{
|
||||
pushDatabaseBalance();
|
||||
args.push(() => {
|
||||
popDatabaseBalance();
|
||||
});
|
||||
}
|
||||
query.apply(mysqlSingle, args)
|
||||
}
|
||||
|
||||
mysqlSingle.insertValues = (table, values, callback) => {
|
||||
let names = '';
|
||||
let data = '';
|
||||
for(const val in values)
|
||||
{
|
||||
names += '`' + val + '`,';
|
||||
data += mysqlSingle.escape(values[val]) + ',';
|
||||
}
|
||||
names = names.slice(0, -1)
|
||||
data = data.slice(0, -1)
|
||||
let query = `INSERT INTO ${table}(${names}) VALUES(${data})`;
|
||||
if(callback)
|
||||
return mysqlSingle.query(query, (...responce) => callback(...responce))
|
||||
else
|
||||
return mysqlSingle.query(query)
|
||||
}
|
||||
}
|
||||
handleListenerDisconnect();
|
||||
|
||||
/*
|
||||
app.use(express.static('build', {index: false}));
|
||||
|
||||
app.get('/sitemap.xml', function(req, res) {
|
||||
mysqlPool.query('SELECT count(*) as cnt FROM `torrents` WHERE contentCategory != \'xxx\' OR contentCategory IS NULL', function (error, rows, fields) {
|
||||
if(!rows) {
|
||||
return;
|
||||
}
|
||||
let urls = []
|
||||
for(let i = 0; i < Math.ceil(rows[0].cnt / config.sitemapMaxSize); i++)
|
||||
urls.push(`http://${config.domain}/sitemap${i+1}.xml`);
|
||||
|
||||
res.header('Content-Type', 'application/xml');
|
||||
res.send( sm.buildSitemapIndex({
|
||||
urls
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/sitemap:id.xml', function(req, res) {
|
||||
if(req.params.id < 1)
|
||||
return;
|
||||
|
||||
let page = (req.params.id - 1) * config.sitemapMaxSize
|
||||
|
||||
mysqlPool.query('SELECT hash FROM `torrents` WHERE contentCategory != \'xxx\' OR contentCategory IS NULL LIMIT ?, ?', [page, config.sitemapMaxSize], function (error, rows, fields) {
|
||||
if(!rows) {
|
||||
return;
|
||||
}
|
||||
let sitemap = sm.createSitemap ({
|
||||
hostname: 'http://' + config.domain,
|
||||
cacheTime: 600000
|
||||
});
|
||||
sitemap.add({url: '/'});
|
||||
for(let i = 0; i < rows.length; i++)
|
||||
sitemap.add({url: '/torrent/' + rows[i].hash});
|
||||
|
||||
sitemap.toXML( function (err, xml) {
|
||||
if (err) {
|
||||
return res.status(500).end();
|
||||
}
|
||||
res.header('Content-Type', 'application/xml');
|
||||
res.send( xml );
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
app.get('*', function(req, res)
|
||||
{
|
||||
if(typeof req.query['_escaped_fragment_'] != 'undefined')
|
||||
{
|
||||
let program = phantomjs.exec('phantom.js', 'http://' + config.domain + req.path)
|
||||
let body = '';
|
||||
let timeout = setTimeout(() => {
|
||||
program.kill();
|
||||
}, 45000)
|
||||
program.stderr.pipe(process.stderr)
|
||||
program.stdout.on('data', (chunk) => {
|
||||
body += chunk;
|
||||
});
|
||||
program.on('exit', code => {
|
||||
clearTimeout(timeout);
|
||||
res.header('Content-Type', 'text/html');
|
||||
res.send( body );
|
||||
})
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
res.sendfile(__dirname + '/build/index.html');
|
||||
});
|
||||
*/
|
||||
|
||||
// start
|
||||
|
||||
function baseRowData(row)
|
||||
{
|
||||
return {
|
||||
hash: row.hash,
|
||||
name: row.name,
|
||||
size: row.size,
|
||||
files: row.files,
|
||||
filesList: row.filesList,
|
||||
piecelength: row.piecelength,
|
||||
added: row.added ? (typeof row.added === 'object' ? row.added.getTime() : row.added) : (new Date()).getTime(),
|
||||
contentType: row.contentType || row.contenttype,
|
||||
contentCategory: row.contentCategory || row.contentcategory,
|
||||
seeders: row.seeders,
|
||||
completed: row.completed,
|
||||
leechers: row.leechers,
|
||||
trackersChecked: row.trackersChecked ? row.trackersChecked.getTime() : undefined,
|
||||
good: row.good,
|
||||
bad: row.bad,
|
||||
}
|
||||
}
|
||||
|
||||
let topCache = {};
|
||||
setInterval(() => {
|
||||
topCache = {};
|
||||
}, 24 * 60 * 60 * 1000);
|
||||
|
||||
|
||||
//io.on('connection', function(socket)
|
||||
//{
|
||||
recive('recentTorrents', function(callback)
|
||||
{
|
||||
if(typeof callback != 'function')
|
||||
return;
|
||||
|
||||
mysqlPool.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;
|
||||
|
||||
mysqlPool.query('SELECT count(*) AS torrents, sum(size) AS sz FROM `torrents`', function (error, rows, fields) {
|
||||
if(!rows) {
|
||||
console.error(error)
|
||||
callback(undefined)
|
||||
return;
|
||||
}
|
||||
|
||||
let result = {torrents: rows[0].torrents || 0, size: rows[0].sz || 0}
|
||||
|
||||
mysqlPool.query('SELECT count(*) AS files FROM `files`', function (error, rows, fields) {
|
||||
if(!rows) {
|
||||
console.error(error)
|
||||
callback(undefined)
|
||||
return;
|
||||
}
|
||||
|
||||
result.files = rows[0].files || 0
|
||||
|
||||
callback(result)
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
recive('torrent', function(hash, options, callback)
|
||||
{
|
||||
if(hash.length != 40)
|
||||
return;
|
||||
|
||||
if(typeof callback != 'function')
|
||||
return;
|
||||
|
||||
mysqlPool.query('SELECT * FROM `torrents` WHERE `hash` = ?', hash, function (error, rows, fields) {
|
||||
if(!rows || rows.length == 0) {
|
||||
callback(undefined)
|
||||
return;
|
||||
}
|
||||
let torrent = rows[0];
|
||||
|
||||
if(options.files)
|
||||
{
|
||||
mysqlPool.query('SELECT * FROM `files` WHERE `hash` = ?', hash, function (error, rows, fields) {
|
||||
torrent.filesList = rows;
|
||||
callback(baseRowData(torrent))
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
callback(baseRowData(torrent))
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
recive('searchTorrent', 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, 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 = ' + mysqlPool.escape(navigation.type) + ' ';
|
||||
}
|
||||
if(navigation.size)
|
||||
{
|
||||
if(navigation.size.max > 0)
|
||||
where += ' and size < ' + mysqlPool.escape(navigation.size.max) + ' ';
|
||||
if(navigation.size.min > 0)
|
||||
where += ' and size > ' + mysqlPool.escape(navigation.size.min) + ' ';
|
||||
}
|
||||
if(navigation.files)
|
||||
{
|
||||
if(navigation.files.max > 0)
|
||||
where += ' and files < ' + mysqlPool.escape(navigation.files.max) + ' ';
|
||||
if(navigation.files.min > 0)
|
||||
where += ' and files > ' + mysqlPool.escape(navigation.files.min) + ' ';
|
||||
}
|
||||
console.log(navigation, where)
|
||||
|
||||
let searchList = [];
|
||||
//args.splice(orderBy && orderBy.length > 0 ? 1 : 0, 1);
|
||||
//mysqlPool.query('SELECT * FROM `torrents` WHERE `name` like \'%' + text + '%\' ' + where + ' ' + order + ' LIMIT ?,?', args, function (error, rows, fields) {
|
||||
sphinx.query('SELECT * FROM `torrents` WHERE MATCH(?) ' + where + ' ' + order + ' LIMIT ?,?', args, function (error, rows, fields) {
|
||||
if(!rows) {
|
||||
console.log(error)
|
||||
callback(undefined)
|
||||
return;
|
||||
}
|
||||
rows.forEach((row) => {
|
||||
searchList.push(baseRowData(row));
|
||||
});
|
||||
callback(searchList);
|
||||
});
|
||||
});
|
||||
|
||||
recive('searchFiles', 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, 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 = ' + mysqlPool.escape(navigation.type) + ' ';
|
||||
}
|
||||
if(navigation.size)
|
||||
{
|
||||
if(navigation.size.max > 0)
|
||||
where += ' and torrentSize < ' + mysqlPool.escape(navigation.size.max) + ' ';
|
||||
if(navigation.size.min > 0)
|
||||
where += ' and torrentSize > ' + mysqlPool.escape(navigation.size.min) + ' ';
|
||||
}
|
||||
if(navigation.files)
|
||||
{
|
||||
if(navigation.files.max > 0)
|
||||
where += ' and files < ' + mysqlPool.escape(navigation.files.max) + ' ';
|
||||
if(navigation.files.min > 0)
|
||||
where += ' and files > ' + mysqlPool.escape(navigation.files.min) + ' ';
|
||||
}
|
||||
*/
|
||||
|
||||
let search = {};
|
||||
//args.splice(orderBy && orderBy.length > 0 ? 1 : 0, 1);
|
||||
//mysqlPool.query('SELECT * FROM `files` inner join torrents on(torrents.hash = files.hash) WHERE files.path like \'%' + text + '%\' ' + where + ' ' + order + ' LIMIT ?,?', args, function (error, rows, fields) {
|
||||
sphinx.query('SELECT * FROM `files` WHERE MATCH(?) ' + where + ' ' + order + ' LIMIT ?,?', args, function (error, files, fields) {
|
||||
if(!files) {
|
||||
console.log(error)
|
||||
callback(undefined)
|
||||
return;
|
||||
}
|
||||
if(files.length === 0)
|
||||
{
|
||||
callback(undefined)
|
||||
return;
|
||||
}
|
||||
for(const file of files)
|
||||
{
|
||||
if(!search[file.hash])
|
||||
{
|
||||
search[file.hash] = { path: [] }
|
||||
}
|
||||
search[file.hash].path.push(file.path)
|
||||
}
|
||||
const inSql = Object.keys(search).map(hash => sphinx.escape(hash)).join(',');
|
||||
sphinx.query(`SELECT * FROM torrents WHERE hash IN(${inSql})`, (err, torrents) => {
|
||||
if(!torrents) {
|
||||
console.log(err)
|
||||
return;
|
||||
}
|
||||
|
||||
for(const torrent of torrents)
|
||||
{
|
||||
search[torrent.hash] = Object.assign(torrent, search[torrent.hash])
|
||||
}
|
||||
|
||||
callback(Object.values(search));
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
recive('checkTrackers', function(hash)
|
||||
{
|
||||
if(hash.length != 40)
|
||||
return;
|
||||
|
||||
updateTorrentTrackers(hash);
|
||||
});
|
||||
|
||||
recive('topTorrents', function(type, callback)
|
||||
{
|
||||
let where = '';
|
||||
let max = 20;
|
||||
if(type && type.length > 0)
|
||||
{
|
||||
where += ' and contentType = ' + mysqlPool.escape(type) + ' ';
|
||||
max = 15;
|
||||
|
||||
if(type == 'hours')
|
||||
{
|
||||
where = ' and `added` > ' + Math.floor(Date.now() / 1000) - (60 * 60 * 24)
|
||||
}
|
||||
if(type == 'week')
|
||||
{
|
||||
where = ' and `added` > ' + Math.floor(Date.now() / 1000) - (60 * 60 * 24 * 7)
|
||||
}
|
||||
if(type == '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 ${max}`;
|
||||
if(topCache[query])
|
||||
{
|
||||
callback(topCache[query]);
|
||||
return;
|
||||
}
|
||||
mysqlPool.query(query, function (error, rows) {
|
||||
if(!rows || rows.length == 0) {
|
||||
callback(undefined)
|
||||
return;
|
||||
}
|
||||
|
||||
rows = rows.map((row) => {
|
||||
return baseRowData(row);
|
||||
});
|
||||
topCache[query] = rows;
|
||||
callback(rows);
|
||||
});
|
||||
});
|
||||
|
||||
recive('admin', function(callback)
|
||||
{
|
||||
if(typeof callback != 'function')
|
||||
return;
|
||||
|
||||
callback({
|
||||
dhtDisabled: !config.indexer
|
||||
})
|
||||
});
|
||||
|
||||
recive('setAdmin', function(options, callback)
|
||||
{
|
||||
if(typeof options !== 'object')
|
||||
return;
|
||||
|
||||
config.indexer = !options.dhtDisabled;
|
||||
spider.ignore = !config.indexer;
|
||||
|
||||
if(!config.indexer)
|
||||
showFakeTorrents()
|
||||
else {
|
||||
hideFakeTorrents()
|
||||
spider.listen(config.spiderPort)
|
||||
}
|
||||
|
||||
if(typeof callback === 'function')
|
||||
callback(true)
|
||||
});
|
||||
|
||||
let socketIPV4 = () => {
|
||||
let ip = socket.request.connection.remoteAddress;
|
||||
if (ipaddr.IPv4.isValid(ip)) {
|
||||
// all ok
|
||||
} else if (ipaddr.IPv6.isValid(ip)) {
|
||||
let ipv6 = ipaddr.IPv6.parse(ip);
|
||||
if (ipv6.isIPv4MappedAddress()) {
|
||||
ip = ipv6.toIPv4Address().toString();
|
||||
}
|
||||
}
|
||||
return ip
|
||||
};
|
||||
|
||||
recive('vote', function(hash, isGood, callback)
|
||||
{
|
||||
if(hash.length != 40)
|
||||
return;
|
||||
|
||||
if(typeof callback != 'function')
|
||||
return;
|
||||
|
||||
const ip = socketIPV4();
|
||||
isGood = !!isGood;
|
||||
|
||||
mysqlPool.query('SELECT * FROM `torrents_actions` WHERE `hash` = ? AND (`action` = \'good\' OR `action` = \'bad\') AND ipv4 = ?', [hash, ip], function (error, rows, fields) {
|
||||
if(!rows) {
|
||||
console.error(error);
|
||||
}
|
||||
if(rows.length > 0) {
|
||||
callback(false)
|
||||
return
|
||||
}
|
||||
|
||||
mysqlPool.query('SELECT good, bad FROM `torrents` WHERE `hash` = ?', hash, function (error, rows, fields) {
|
||||
if(!rows || rows.length == 0)
|
||||
return;
|
||||
|
||||
let {good, bad} = rows[0];
|
||||
const action = isGood ? 'good' : 'bad';
|
||||
mysqlPool.query('INSERT INTO `torrents_actions` SET ?', {hash, action, ipv4: ip}, function(err, result) {
|
||||
if(!result) {
|
||||
console.error(err);
|
||||
}
|
||||
mysqlPool.query('UPDATE torrents SET ' + action + ' = ' + action + ' + 1 WHERE hash = ?', hash, function(err, result) {
|
||||
if(!result) {
|
||||
console.error(err);
|
||||
}
|
||||
if(isGood) {
|
||||
good++;
|
||||
} else {
|
||||
bad++;
|
||||
}
|
||||
send('vote', {
|
||||
hash, good, bad
|
||||
});
|
||||
callback(true)
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
//});
|
||||
|
||||
|
||||
let undoneQueries = 0;
|
||||
let pushDatabaseBalance = () => {
|
||||
undoneQueries++;
|
||||
if(undoneQueries >= 5000)
|
||||
{
|
||||
balanceDebug('start balance mysql, queries:', undoneQueries);
|
||||
spider.ignore = true;
|
||||
}
|
||||
};
|
||||
let popDatabaseBalance = () => {
|
||||
undoneQueries--;
|
||||
balanceDebug('balanced, queries left:', undoneQueries);
|
||||
if(undoneQueries == 0)
|
||||
{
|
||||
balanceDebug('balance done');
|
||||
spider.ignore = !config.indexer;
|
||||
}
|
||||
};
|
||||
|
||||
// обновление статистики
|
||||
/*
|
||||
setInterval(() => {
|
||||
let stats = {};
|
||||
mysqlPool.query('SELECT COUNT(*) as tornum FROM `torrents`', function (error, rows, fields) {
|
||||
stats.torrents = rows[0].tornum;
|
||||
mysqlPool.query('SELECT COUNT(*) as filesnum, SUM(`size`) as filesizes FROM `files`', function (error, rows, fields) {
|
||||
stats.files = rows[0].filesnum;
|
||||
stats.size = rows[0].filesizes;
|
||||
send('newStatistic', stats);
|
||||
mysqlPool.query('DELETE FROM `statistic`', function (err, result) {
|
||||
if(!result) {
|
||||
console.error(err);
|
||||
}
|
||||
mysqlPool.query('INSERT INTO `statistic` SET ?', stats, function(err, result) {
|
||||
if(!result) {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
||||
}, 10 * 60 * 1000)
|
||||
*/
|
||||
|
||||
const updateTorrentTrackers = (hash) => {
|
||||
let maxSeeders = 0, maxLeechers = 0, maxCompleted = 0;
|
||||
mysqlSingle.query('UPDATE torrents SET trackersChecked = ? WHERE hash = ?', [Math.floor(Date.now() / 1000), hash], function(err, result) {
|
||||
if(!result) {
|
||||
console.error(err);
|
||||
return
|
||||
}
|
||||
|
||||
udpTrackers.forEach((tracker) => {
|
||||
getPeersStatisticUDP(tracker.host, tracker.port, hash, ({seeders, completed, leechers}) => {
|
||||
if(seeders == 0 && completed == 0 && leechers == 0)
|
||||
return;
|
||||
|
||||
if(seeders < maxSeeders)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if(seeders == maxSeeders && leechers < maxLeechers)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if(seeders == maxSeeders && leechers == maxLeechers && completed <= maxCompleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
maxSeeders = seeders;
|
||||
maxLeechers = leechers;
|
||||
maxCompleted = completed;
|
||||
let checkTime = new Date();
|
||||
|
||||
mysqlSingle.query('UPDATE torrents SET seeders = ?, completed = ?, leechers = ?, trackersChecked = ? WHERE hash = ?', [seeders, completed, leechers, Math.floor(checkTime.getTime() / 1000), hash], function(err, result) {
|
||||
if(!result) {
|
||||
console.error(err);
|
||||
return
|
||||
}
|
||||
|
||||
send('trackerTorrentUpdate', {
|
||||
hash,
|
||||
seeders,
|
||||
completed,
|
||||
leechers,
|
||||
trackersChecked: checkTime.getTime()
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const cleanupTorrents = (cleanTorrents = 1) => {
|
||||
if(!config.cleanup)
|
||||
return;
|
||||
|
||||
/*
|
||||
disk.check(rootPath, function(err, info) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
} else {
|
||||
const {available, free, total} = info;
|
||||
|
||||
if(free < config.cleanupDiscLimit)
|
||||
{
|
||||
mysqlSingle.query(`SELECT * FROM torrents WHERE added < DATE_SUB(NOW(), INTERVAL 6 hour) ORDER BY seeders ASC, files DESC, leechers ASC, completed ASC LIMIT ${cleanTorrents}`, function(err, torrents) {
|
||||
if(!torrents)
|
||||
return;
|
||||
|
||||
torrents.forEach((torrent) => {
|
||||
if(torrent.seeders > 0){
|
||||
cleanupDebug('this torrent is ok', torrent.name);
|
||||
return
|
||||
}
|
||||
|
||||
cleanupDebug('cleanup torrent', torrent.name, '[seeders', torrent.seeders, ', files', torrent.files, ']', 'free', (free / (1024 * 1024)) + "mb");
|
||||
|
||||
mysqlSingle.query('DELETE FROM files WHERE hash = ?', torrent.hash);
|
||||
mysqlSingle.query('DELETE FROM torrents WHERE hash = ?', torrent.hash);
|
||||
})
|
||||
});
|
||||
}
|
||||
else
|
||||
cleanupDebug('enough free space', (free / (1024 * 1024)) + "mb");
|
||||
}
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
const updateTorrent = (metadata, infohash, rinfo) => {
|
||||
console.log('writing torrent', metadata.info.name, 'to database');
|
||||
|
||||
const hash = infohash.toString('hex');
|
||||
let size = metadata.info.length ? metadata.info.length : 0;
|
||||
let filesCount = 1;
|
||||
let filesArray = [];
|
||||
if(metadata.info.files && metadata.info.files.length > 0)
|
||||
{
|
||||
filesCount = metadata.info.files.length;
|
||||
size = 0;
|
||||
|
||||
for(let i = 0; i < metadata.info.files.length; i++)
|
||||
{
|
||||
let file = metadata.info.files[i];
|
||||
let filePath = file.path.join('/');
|
||||
let fileQ = {
|
||||
id: filesId++,
|
||||
hash: hash,
|
||||
path: filePath,
|
||||
pathIndex: filePath,
|
||||
size: file.length,
|
||||
};
|
||||
filesArray.push(fileQ);
|
||||
size += file.length;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
let fileQ = {
|
||||
id: filesId++,
|
||||
hash: hash,
|
||||
path: metadata.info.name,
|
||||
pathIndex: metadata.info.name,
|
||||
size: size,
|
||||
};
|
||||
filesArray.push(fileQ);
|
||||
}
|
||||
|
||||
let filesToAdd = filesArray.length;
|
||||
mysqlSingle.query('SELECT count(*) as files_count FROM files WHERE hash = ?', [hash], function(err, rows) {
|
||||
if(!rows)
|
||||
return
|
||||
|
||||
const db_files = rows[0]['files_count'];
|
||||
if(db_files !== filesCount)
|
||||
{
|
||||
mysqlSingle.query('DELETE FROM files WHERE hash = ?', hash, function (err, result) {
|
||||
if(err)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
filesArray.forEach((file) => {
|
||||
mysqlSingle.insertValues('files', file, function(err, result) {
|
||||
if(!result) {
|
||||
console.log(file);
|
||||
console.error(err);
|
||||
return
|
||||
}
|
||||
if(--filesToAdd === 0) {
|
||||
send('filesReady', hash);
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
var torrentQ = {
|
||||
id: torrentsId++,
|
||||
hash: hash,
|
||||
name: metadata.info.name,
|
||||
nameIndex: metadata.info.name,
|
||||
size: size,
|
||||
files: filesCount,
|
||||
piecelength: metadata.info['piece length'],
|
||||
ipv4: rinfo.address,
|
||||
port: rinfo.port,
|
||||
added: Math.floor(Date.now() / 1000)
|
||||
};
|
||||
|
||||
torrentTypeDetect(torrentQ, filesArray);
|
||||
|
||||
mysqlSingle.query("SELECT id FROM torrents WHERE hash = ?", hash, (err, single) => {
|
||||
if(!single)
|
||||
{
|
||||
console.log(err)
|
||||
return
|
||||
}
|
||||
|
||||
if(single.length > 0)
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
mysqlSingle.insertValues('torrents', torrentQ, function(err, result) {
|
||||
if(result) {
|
||||
send('newTorrent', {
|
||||
hash: hash,
|
||||
name: metadata.info.name,
|
||||
size: size,
|
||||
files: filesCount,
|
||||
piecelength: metadata.info['piece length'],
|
||||
contentType: torrentQ.contentType,
|
||||
contentCategory: torrentQ.contentCategory,
|
||||
});
|
||||
updateTorrentTrackers(hash);
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log(torrentQ);
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
client.on('complete', function (metadata, infohash, rinfo) {
|
||||
|
||||
cleanupTorrents(1); // clean old torrents before writing new
|
||||
|
||||
if(config.spaceQuota && config.spaceDiskLimit > 0)
|
||||
{
|
||||
disk.check(rootPath, function(err, info) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
} else {
|
||||
const {available, free, total} = info;
|
||||
|
||||
if(free >= config.spaceDiskLimit)
|
||||
{
|
||||
hideFakeTorrents(); // also enable fake torrents;
|
||||
updateTorrent(metadata, infohash, rinfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
quotaDebug('ignore torrent', metadata.info.name, 'free space', (free / (1024 * 1024)) + "mb");
|
||||
showFakeTorrents(); // also enable fake torrents;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
updateTorrent(metadata, infohash, rinfo);
|
||||
}
|
||||
});
|
||||
|
||||
// spider.on('nodes', (nodes)=>console.log('foundNodes'))
|
||||
|
||||
let fakeTorrents = [];
|
||||
function showFakeTorrentsPage(page)
|
||||
{
|
||||
mysqlSingle.query('SELECT * FROM torrents LIMIT ?, 100', [page], function(err, torrents) {
|
||||
if(!torrents)
|
||||
return;
|
||||
|
||||
torrents.forEach((torrent, index) => {
|
||||
const fk = fakeTorrents.push(setTimeout(() => {
|
||||
delete fakeTorrents[fk-1];
|
||||
send('newTorrent', baseRowData(torrent));
|
||||
updateTorrentTrackers(torrent.hash);
|
||||
fakeTorrentsDebug('fake torrent', torrents.name, 'index, page:', index, page);
|
||||
}, 700 * index))
|
||||
})
|
||||
|
||||
const fk = fakeTorrents.push(setTimeout(()=>{
|
||||
delete fakeTorrents[fk-1];
|
||||
showFakeTorrentsPage(torrents.length > 0 ? page + torrents.length : 0)
|
||||
}, 700 * torrents.length))
|
||||
});
|
||||
}
|
||||
|
||||
function showFakeTorrents()
|
||||
{
|
||||
fakeTorrentsDebug('showing fake torrents');
|
||||
hideFakeTorrents()
|
||||
showFakeTorrentsPage(0);
|
||||
}
|
||||
|
||||
function hideFakeTorrents()
|
||||
{
|
||||
fakeTorrents.forEach((fk) => {
|
||||
clearTimeout(fk)
|
||||
})
|
||||
fakeTorrents = []
|
||||
fakeTorrentsDebug('hidding fake torrents');
|
||||
}
|
||||
|
||||
if(config.indexer) {
|
||||
spider.listen(config.spiderPort)
|
||||
} else {
|
||||
showFakeTorrents();
|
||||
}
|
||||
|
||||
if(config.cleanup && config.indexer)
|
||||
{
|
||||
cleanupDebug('cleanup enabled');
|
||||
cleanupDebug('cleanup disc limit', (config.cleanupDiscLimit / (1024 * 1024)) + 'mb');
|
||||
}
|
||||
|
||||
if(config.spaceQuota)
|
||||
{
|
||||
quotaDebug('disk quota enabled');
|
||||
}
|
||||
|
||||
this.stop = (callback) => {
|
||||
console.log('closing spider')
|
||||
mysqlPool.end(() => spider.close(() => {
|
||||
mysqlSingle.destroy()
|
||||
callback()
|
||||
}))
|
||||
}
|
||||
return this
|
||||
|
||||
}
|
Reference in New Issue
Block a user