fix eslint

This commit is contained in:
Alexey Kasyanchuk
2018-06-18 00:06:28 +03:00
parent 93b1b91f56
commit fd4ba2c392
61 changed files with 5528 additions and 5528 deletions

View File

@ -4,17 +4,17 @@ import Page from './page';
import RecentTorrents from './recent-torrents' import RecentTorrents from './recent-torrents'
export default class ActivityPage extends Page { export default class ActivityPage extends Page {
constructor(props) { constructor(props) {
super(props) super(props)
this.setTitle('Rats On The Boat - Content Search Engine'); this.setTitle('Rats On The Boat - Content Search Engine');
} }
render() { render() {
return ( return (
<div className='column center'> <div className='column center'>
<div className='column center w100p pad0-75'> <div className='column center w100p pad0-75'>
<RecentTorrents /> <RecentTorrents />
</div> </div>
</div> </div>
); );
} }
} }

View File

@ -20,52 +20,52 @@ if(typeof WEB !== 'undefined')
} }
else else
{ {
const { ipcRenderer, remote } = require('electron'); const { ipcRenderer, remote } = require('electron');
window.currentWindow = remote.getCurrentWindow() window.currentWindow = remote.getCurrentWindow()
window.torrentSocket = {} window.torrentSocket = {}
window.torrentSocket.callbacks = {} window.torrentSocket.callbacks = {}
window.torrentSocket.listeners = {} window.torrentSocket.listeners = {}
window.torrentSocket.on = (name, func) => { window.torrentSocket.on = (name, func) => {
const newListener = (event, ...data) => { const newListener = (event, ...data) => {
func(...data) func(...data)
}
window.torrentSocket.listeners[func] = newListener
ipcRenderer.on(name, newListener);
}
window.torrentSocket.off = (name, func) => {
if(!func)
ipcRenderer.removeAllListeners(name);
else
{
const realListener = window.torrentSocket.listeners[func]
if(realListener)
{
ipcRenderer.removeListener(name, realListener);
delete window.torrentSocket.listeners[func]
} }
} window.torrentSocket.listeners[func] = newListener
} ipcRenderer.on(name, newListener);
window.torrentSocket.emit = (name, ...data) => { }
if(typeof data[data.length - 1] === 'function') window.torrentSocket.off = (name, func) => {
{ if(!func)
const id = Math.random().toString(36).substring(5) ipcRenderer.removeAllListeners(name);
window.torrentSocket.callbacks[id] = data[data.length - 1]; else
data[data.length - 1] = {callback: id} {
} const realListener = window.torrentSocket.listeners[func]
ipcRenderer.send(name, data) if(realListener)
} {
ipcRenderer.on('callback', (event, id, data) => { ipcRenderer.removeListener(name, realListener);
const callback = window.torrentSocket.callbacks[id] delete window.torrentSocket.listeners[func]
if(callback) }
callback(data) }
delete window.torrentSocket.callbacks[id] }
}); 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]
});
ipcRenderer.on('url', (event, url) => { ipcRenderer.on('url', (event, url) => {
console.log('url', url) console.log('url', url)
router(url) router(url)
}); });
} }
@ -164,8 +164,8 @@ class App extends Component {
<div> <div>
{ {
checkNotModal checkNotModal
&& &&
<Header /> <Header />
} }
<PagesPie /> <PagesPie />
<Footer /> <Footer />

View File

@ -1,259 +1,259 @@
const XXX_BLOCK_WORDS = ['incestcash', 'asacp', 'xondemand', 'yankscash', 'klixxx', 'cybersitter', 'safesurf', const XXX_BLOCK_WORDS = ['incestcash', 'asacp', 'xondemand', 'yankscash', 'klixxx', 'cybersitter', 'safesurf',
'surfwatch', 'netcash', 'watersport', 'fuck', 'threesome', 'tits', 'surfwatch', 'netcash', 'watersport', 'fuck', 'threesome', 'tits',
'masturbating', 'incest', 'bestiality', 'analintercourse', 'analsex', 'animesex', 'masturbating', 'incest', 'bestiality', 'analintercourse', 'analsex', 'animesex',
'anitablonde', 'autosex', 'blackass', 'blackasses', 'boner', 'boobcruise', 'anitablonde', 'autosex', 'blackass', 'blackasses', 'boner', 'boobcruise',
'boobies', 'bordello', 'braless', 'brothel', 'callgirl', 'callgirls', 'boobies', 'bordello', 'braless', 'brothel', 'callgirl', 'callgirls',
'clit', 'clitoris', 'clits', 'cums', 'cybererotic', 'cybererotica', 'clit', 'clitoris', 'clits', 'cums', 'cybererotic', 'cybererotica',
'cybersex', 'cybersexx', 'ejaculation', 'erotica', 'eroticfilm', 'eroticfilms', 'cybersex', 'cybersexx', 'ejaculation', 'erotica', 'eroticfilm', 'eroticfilms',
'eroticism', 'eroticphoto', 'eroticphotography', 'eroticphotos', 'erotics', 'eroticsex', 'eroticism', 'eroticphoto', 'eroticphotography', 'eroticphotos', 'erotics', 'eroticsex',
'eroticsexstories', 'eroticstories', 'eroticstory', 'erotik', 'erotika', 'antasysex', 'eroticsexstories', 'eroticstories', 'eroticstory', 'erotik', 'erotika', 'antasysex',
'gaysex', 'gruppensex', 'horny', 'jackoff', 'lesbo', 'lesbos', 'gaysex', 'gruppensex', 'horny', 'jackoff', 'lesbo', 'lesbos',
'makinglove', 'motherfucker', 'horsesex', 'dogsex', 'snakesex', 'sextracker', 'makinglove', 'motherfucker', 'horsesex', 'dogsex', 'snakesex', 'sextracker',
'tranny', 'bdsm', 'porn', 'beastality', 'bizarre', 'blowjob', 'tranny', 'bdsm', 'porn', 'beastality', 'bizarre', 'blowjob',
'bondage', 'bondages', 'celebs', 'cum', 'cunt', 'transsexual', 'bondage', 'bondages', 'celebs', 'cum', 'cunt', 'transsexual',
'transsexuals', 'cumbath', 'cumshot', 'dildo', 'dildos', 'transvestites', 'transsexuals', 'cumbath', 'cumshot', 'dildo', 'dildos', 'transvestites',
'transvestite', 'dominatrix', 'spank', 'dungeon', 'sexbilder', 'dungeons', 'transvestite', 'dominatrix', 'spank', 'dungeon', 'sexbilder', 'dungeons',
'pussie', 'ebony', 'fisting', 'gayshop', 'facesitting', 'sexshop', 'pussie', 'ebony', 'fisting', 'gayshop', 'facesitting', 'sexshop',
'pussy', 'bukkake', 'escort', 'blowjobs', 'facial', 'facialized', 'pussy', 'bukkake', 'escort', 'blowjobs', 'facial', 'facialized',
'facials', 'fetish', 'footfetish', 'fuckingpussy', 'fucking', 'hardcore', 'facials', 'fetish', 'footfetish', 'fuckingpussy', 'fucking', 'hardcore',
'hentai', 'hustlerlatex', 'latex', 'lolita', 'lolitas', 'mlm', 'hentai', 'hustlerlatex', 'latex', 'lolita', 'lolitas', 'mlm',
'oralsex', 'peepshow', 'peepshows', 'phonesex', 'pornstars', 'preteen', 'oralsex', 'peepshow', 'peepshows', 'phonesex', 'pornstars', 'preteen',
'preteens', 'pussies', 'shemale', 'shemales', 'sluts', 'whores', 'preteens', 'pussies', 'shemale', 'shemales', 'sluts', 'whores',
'whore', 'spanking', 'strapon', 'twinks', 'upskirts', 'voyeur', 'whore', 'spanking', 'strapon', 'twinks', 'upskirts', 'voyeur',
'whip', 'whipping', 'xxx', 'sexkey', 'femdom', 'gangbang', 'whip', 'whipping', 'xxx', 'sexkey', 'femdom', 'gangbang',
'gloryhole', 'adultbuffet', 'adultcartoons', 'adultchat', 'adultchatnetwork', 'adultdvd', 'gloryhole', 'adultbuffet', 'adultcartoons', 'adultchat', 'adultchatnetwork', 'adultdvd',
'adultentertainment', 'adultentertainmenthouse', 'adulterotica', 'adultfilms', 'adultfree', 'adultfreepics', 'adultentertainment', 'adultentertainmenthouse', 'adulterotica', 'adultfilms', 'adultfree', 'adultfreepics',
'adultfreepix', 'adultfun', 'adulthardcore', 'adultimages', 'adultjpeg', 'adultlink', 'adultfreepix', 'adultfun', 'adulthardcore', 'adultimages', 'adultjpeg', 'adultlink',
'adultlinks', 'adultmovie', 'adultmovies', 'adultmpeg', 'adultmpegs', 'adultpass', 'adultlinks', 'adultmovie', 'adultmovies', 'adultmpeg', 'adultmpegs', 'adultpass',
'adultpersonals', 'adultphotos', 'adultpic', 'adultpics', 'adultpicture', 'adultpictures', 'adultpersonals', 'adultphotos', 'adultpic', 'adultpics', 'adultpicture', 'adultpictures',
'adultpix', 'adultporn', 'adultpornography', 'adultsearch', 'adultsex', 'adultsights', 'adultpix', 'adultporn', 'adultpornography', 'adultsearch', 'adultsex', 'adultsights',
'adultsites', 'adultsonly', 'adultstars', 'adultstories', 'adultvideo', 'adultvideos', 'adultsites', 'adultsonly', 'adultstars', 'adultstories', 'adultvideo', 'adultvideos',
'adultweb', 'adultxxx', 'alt.sex', 'altsex', 'alt.sex.exhibitionism', 'alt.sex.pictures', 'adultweb', 'adultxxx', 'alt.sex', 'altsex', 'alt.sex.exhibitionism', 'alt.sex.pictures',
'altsexst', 'alt.sex.stories', 'alt.sex.voyeurism', 'amateurerotica', 'amateurfotos', 'amateurhardcore', 'altsexst', 'alt.sex.stories', 'alt.sex.voyeurism', 'amateurerotica', 'amateurfotos', 'amateurhardcore',
'amateurindex', 'amateurnaked', 'amateurnudes', 'amateurporn', 'amateurpussy', 'amateursex', 'amateurindex', 'amateurnaked', 'amateurnudes', 'amateurporn', 'amateurpussy', 'amateursex',
'amateursxxx', 'amatuerhardcore', 'amatuersex', 'ampland', 'analaction', 'analcum', 'amateursxxx', 'amatuerhardcore', 'amatuersex', 'ampland', 'analaction', 'analcum',
'analfuck', 'analfucking', 'analgallery', 'analingus', 'analpics', 'analpicture', 'analfuck', 'analfucking', 'analgallery', 'analingus', 'analpics', 'analpicture',
'analsex', 'analsexpics', 'analsexpictures', 'analxxx', 'animalbeastiality', 'animehentai', 'analsex', 'analsexpics', 'analsexpictures', 'analxxx', 'animalbeastiality', 'animehentai',
'animeporno', 'animexxx', 'asiacarrera', 'assfuck', 'assfucking', 'asshole', 'animeporno', 'animexxx', 'asiacarrera', 'assfuck', 'assfucking', 'asshole',
'assholes', 'asslick', 'asspics', 'assworship', 'autofellatio', 'babepix', 'assholes', 'asslick', 'asspics', 'assworship', 'autofellatio', 'babepix',
'badgirls', 'bakedchicks', 'baldcunt', 'baldpussies', 'baldpussy', 'barecelebs', 'badgirls', 'bakedchicks', 'baldcunt', 'baldpussies', 'baldpussy', 'barecelebs',
'barenaked', 'barepussy', 'beastiality', 'beaverboy', 'beavershots', 'bigballs', 'barenaked', 'barepussy', 'beastiality', 'beaverboy', 'beavershots', 'bigballs',
'bigblackboobs', 'bigblackcocks', 'bigblackdicks', 'bigblacktits', 'bigboob', 'bigboobs', 'bigblackboobs', 'bigblackcocks', 'bigblackdicks', 'bigblacktits', 'bigboob', 'bigboobs',
'bigbreast', 'bigbreastlovers', 'bigbreasts', 'bigbutt', 'bigbutts', 'bigclit', 'bigbreast', 'bigbreastlovers', 'bigbreasts', 'bigbutt', 'bigbutts', 'bigclit',
'bigclitoris', 'bigclits', 'bigcock', 'bigcocks', 'bigcunt', 'bigcunts', 'bigclitoris', 'bigclits', 'bigcock', 'bigcocks', 'bigcunt', 'bigcunts',
'bigdick', 'bigdicks', 'biggestboobs', 'biggestdick', 'biggesttit', 'biggesttits', 'bigdick', 'bigdicks', 'biggestboobs', 'biggestdick', 'biggesttit', 'biggesttits',
'bighairyballs', 'bighardons', 'bighooters', 'bignipples', 'bigpussies', 'bigpussy', 'bighairyballs', 'bighardons', 'bighooters', 'bignipples', 'bigpussies', 'bigpussy',
'bigtit', 'bigtits', 'bigtitties', 'bigtitts', 'bizarresex', 'bizarrexxx', 'bigtit', 'bigtits', 'bigtitties', 'bigtitts', 'bizarresex', 'bizarrexxx',
'bizzaresex', 'blackbondage', 'blackboobs', 'blackbooty', 'blackbutt', 'blackcock', 'bizzaresex', 'blackbondage', 'blackboobs', 'blackbooty', 'blackbutt', 'blackcock',
'blackcocks', 'blackcum', 'blackcunt', 'blackcunts', 'blackdick', 'blackdicks', 'blackcocks', 'blackcum', 'blackcunt', 'blackcunts', 'blackdick', 'blackdicks',
'blackerotica', 'blackhardcore', 'blacknudes', 'blackonblondes', 'blackporn', 'blackporno', 'blackerotica', 'blackhardcore', 'blacknudes', 'blackonblondes', 'blackporn', 'blackporno',
'blackpornography', 'blackpussy', 'blacksex', 'blacksluts', 'blacksonblondes', 'blacktit', 'blackpornography', 'blackpussy', 'blacksex', 'blacksluts', 'blacksonblondes', 'blacktit',
'blacktits', 'blacktwat', 'blackxxx', 'blondepussy', 'blondsgay', 'bondagefree', 'blacktits', 'blacktwat', 'blackxxx', 'blondepussy', 'blondsgay', 'bondagefree',
'bondagegallery', 'bondagelinks', 'bondagepics', 'bondagepictures', 'bondagesex', 'bondagestories', 'bondagegallery', 'bondagelinks', 'bondagepics', 'bondagepictures', 'bondagesex', 'bondagestories',
'bondagestory', 'bukake', 'bustybabes', 'bustyblondes', 'bustyceleb', 'butt-fuck', 'bondagestory', 'bukake', 'bustybabes', 'bustyblondes', 'bustyceleb', 'butt-fuck',
'buttfuck', 'buttfucker', 'buttfuckers', 'buttfucking', 'buttholes', 'buttman', 'buttfuck', 'buttfucker', 'buttfuckers', 'buttfucking', 'buttholes', 'buttman',
'buttpics', 'buttplug', 'buttthumbnails', 'cartoonporn', 'cartoonsex', 'cartoonsxxx', 'buttpics', 'buttplug', 'buttthumbnails', 'cartoonporn', 'cartoonsex', 'cartoonsxxx',
'cartoonxxx', 'celebritiesnaked', 'celebritiesnude', 'celebritiesxxx', 'celebritybush', 'celebritybutts', 'cartoonxxx', 'celebritiesnaked', 'celebritiesnude', 'celebritiesxxx', 'celebritybush', 'celebritybutts',
'celebritynude', 'celebritynudes', 'celebrityporn', 'celebritypussy', 'celebritysex', 'celebrityxxx', 'celebritynude', 'celebritynudes', 'celebrityporn', 'celebritypussy', 'celebritysex', 'celebrityxxx',
'celebsxxx', 'celebxxx', 'centerfolds', 'chaseylain', 'chatsex', 'cheerleadersxxx', 'celebsxxx', 'celebxxx', 'centerfolds', 'chaseylain', 'chatsex', 'cheerleadersxxx',
'cheerleaderxxx', 'chickswithdicks', 'christycanyon', 'cicciolina', 'clitpics', 'clitpictures', 'cheerleaderxxx', 'chickswithdicks', 'christycanyon', 'cicciolina', 'clitpics', 'clitpictures',
'closeuppussy', 'comixxx', 'crotchless', 'cruisingforsex', 'cumbaths', 'cumcoveredcunts', 'closeuppussy', 'comixxx', 'crotchless', 'cruisingforsex', 'cumbaths', 'cumcoveredcunts',
'cumcunt', 'cumdrinker', 'cumdrinkers', 'cumdrinking', 'cumdrops', 'cumeater', 'cumcunt', 'cumdrinker', 'cumdrinkers', 'cumdrinking', 'cumdrops', 'cumeater',
'cumeaters', 'cumeating', 'cumface', 'cumfaces', 'cumfacial', 'cumfacials', 'cumeaters', 'cumeating', 'cumface', 'cumfaces', 'cumfacial', 'cumfacials',
'cumgallery', 'cumgargle', 'cumguzzlers', 'cumincunt', 'cumjunkies', 'cumlovers', 'cumgallery', 'cumgargle', 'cumguzzlers', 'cumincunt', 'cumjunkies', 'cumlovers',
'cummpeg', 'cumpic', 'cumpics', 'cumpicture', 'cumpictures', 'cumpussy', 'cummpeg', 'cumpic', 'cumpics', 'cumpicture', 'cumpictures', 'cumpussy',
'cumsluts', 'cumsucker', 'cumsuckers', 'cumsucking', 'cumswallow', 'cumswallowers', 'cumsluts', 'cumsucker', 'cumsuckers', 'cumsucking', 'cumswallow', 'cumswallowers',
'cumswallowing', 'cumtasting', 'cumthumbnails', 'cunilingus', 'cunnilingus', 'cuntcum', 'cumswallowing', 'cumtasting', 'cumthumbnails', 'cunilingus', 'cunnilingus', 'cuntcum',
'cuntfuck', 'cuntfucking', 'cuntjuice', 'cuntlapper', 'cuntlick', 'cuntlickers', 'cuntfuck', 'cuntfucking', 'cuntjuice', 'cuntlapper', 'cuntlick', 'cuntlickers',
'cuntlicking', 'cuntpics', 'cuntpictures', 'cunts', 'cuntstories', 'cuntsuckers', 'cuntlicking', 'cuntpics', 'cuntpictures', 'cunts', 'cuntstories', 'cuntsuckers',
'cuntz', 'cyberlust', 'cybernude', 'cyberporn', 'cyberpornlinks', 'cyberpornsexlinks', 'cuntz', 'cyberlust', 'cybernude', 'cyberporn', 'cyberpornlinks', 'cyberpornsexlinks',
'cyberslut', 'damateur', 'danniashe', 'dicksex', 'dicksuckers', 'dicksucking', 'cyberslut', 'damateur', 'danniashe', 'dicksex', 'dicksuckers', 'dicksucking',
'digixxx', 'directporn', 'dirtypictures', 'dirtysex', 'doggiestyle', 'doggystyle', 'digixxx', 'directporn', 'dirtypictures', 'dirtysex', 'doggiestyle', 'doggystyle',
'domatrix', 'dominatrixes', 'downblouse', 'drinkingcum', 'drippingcunt', 'drippingcunts', 'domatrix', 'dominatrixes', 'downblouse', 'drinkingcum', 'drippingcunt', 'drippingcunts',
'drippingpussy', 'easypic.com', 'eatcum', 'eatingcum', 'eatingpussy', 'eatpussy', 'drippingpussy', 'easypic.com', 'eatcum', 'eatingcum', 'eatingpussy', 'eatpussy',
'ebony+ayres', 'ebonyporn', 'ebonypussy', 'ebonysex', 'ebonyxxx', 'enormoustits', 'ebony+ayres', 'ebonyporn', 'ebonypussy', 'ebonysex', 'ebonyxxx', 'enormoustits',
'erosvillage', 'eroticanime', 'eroticart', 'eroticas', 'eroticastories', 'eroticfiction', 'erosvillage', 'eroticanime', 'eroticart', 'eroticas', 'eroticastories', 'eroticfiction',
'eroticlesbianstories', 'eroticmovie', 'eroticmovies', 'erotico', 'eroticos', 'eroticpics', 'eroticlesbianstories', 'eroticmovie', 'eroticmovies', 'erotico', 'eroticos', 'eroticpics',
'eroticpicture', 'eroticpictures', 'eroticsites', 'eroticsounds', 'erotictales', 'erotictext', 'eroticpicture', 'eroticpictures', 'eroticsites', 'eroticsounds', 'erotictales', 'erotictext',
'eroticvideo', 'eroticvideos', 'eroticwomen', 'eroticwriting', 'erotikchat', 'erotique', 'eroticvideo', 'eroticvideos', 'eroticwomen', 'eroticwriting', 'erotikchat', 'erotique',
'erotismo', 'escortservice', 'escortservices', 'eurosex', 'explicitsex', 'facecum', 'erotismo', 'escortservice', 'escortservices', 'eurosex', 'explicitsex', 'facecum',
'facesit', 'facesitters', 'fastporn', 'fatass', 'fatsex', 'feetfetish', 'facesit', 'facesitters', 'fastporn', 'fatass', 'fatsex', 'feetfetish',
'feetsex', 'felatio', 'fellatio', 'fellations', 'fetishwear', 'fettegirls', 'feetsex', 'felatio', 'fellatio', 'fellations', 'fetishwear', 'fettegirls',
'fingerbang', 'fingerfuck', 'flesh4free', 'footjobs', 'footlicking', 'footworship', 'fingerbang', 'fingerfuck', 'flesh4free', 'footjobs', 'footlicking', 'footworship',
'fornication', 'freeanal', 'freeanalsex', 'freeass', 'freebigboobs', 'freebigtit', 'fornication', 'freeanal', 'freeanalsex', 'freeass', 'freebigboobs', 'freebigtit',
'freebigtits', 'freeblackcunt', 'freeblackpussy', 'freeblowjob', 'freeblowjobs', 'freebondage', 'freebigtits', 'freeblackcunt', 'freeblackpussy', 'freeblowjob', 'freeblowjobs', 'freebondage',
'freeboobs', 'freecum', 'freecumshot', 'freecumshots', 'freecunt', 'freecunts', 'freeboobs', 'freecum', 'freecumshot', 'freecumshots', 'freecunt', 'freecunts',
'freedick', 'freeerotic', 'freeerotica', 'freeeroticstories', 'freefuck', 'freefucking', 'freedick', 'freeerotic', 'freeerotica', 'freeeroticstories', 'freefuck', 'freefucking',
'freefuckpics', 'freegay', 'freegaypics', 'freehardcore', 'freehardcorepics', 'freehardcorepictures', 'freefuckpics', 'freegay', 'freegaypics', 'freehardcore', 'freehardcorepics', 'freehardcorepictures',
'freehardcoreporn', 'freehardcoresex', 'freehardcoresexpics', 'freehentai', 'freehooters', 'freelargehooters', 'freehardcoreporn', 'freehardcoresex', 'freehardcoresexpics', 'freehentai', 'freehooters', 'freelargehooters',
'freelesbian', 'freelesbianporn', 'freelesbiansex', 'freenakedpic', 'freenakedpics', 'freenakedpictures', 'freelesbian', 'freelesbianporn', 'freelesbiansex', 'freenakedpic', 'freenakedpics', 'freenakedpictures',
'freenakedwomen', 'freenude', 'freenudecelebs', 'freenudephotos', 'freenudepics', 'freenudes', 'freenakedwomen', 'freenude', 'freenudecelebs', 'freenudephotos', 'freenudepics', 'freenudes',
'freeporn', 'freepornmovies', 'freeporno', 'freepornography', 'freepornopics', 'freepornopictures', 'freeporn', 'freepornmovies', 'freeporno', 'freepornography', 'freepornopics', 'freepornopictures',
'freepornos', 'freepornpic', 'freepornpics', 'freepornpictures', 'freeporns', 'freepornsite', 'freepornos', 'freepornpic', 'freepornpics', 'freepornpictures', 'freeporns', 'freepornsite',
'freepornsites', 'freepussy', 'freepussypic', 'freepussypics', 'freepussypictures', 'freesex', 'freepornsites', 'freepussy', 'freepussypic', 'freepussypics', 'freepussypictures', 'freesex',
'freesexchat', 'freesexmovies', 'freesexnet', 'freesexphotos', 'freesexpic', 'freesexpics', 'freesexchat', 'freesexmovies', 'freesexnet', 'freesexphotos', 'freesexpic', 'freesexpics',
'freesexpicture', 'freesexpictures', 'freesexsites', 'freesexstories', 'freesexvideos', 'freesexycam', 'freesexpicture', 'freesexpictures', 'freesexsites', 'freesexstories', 'freesexvideos', 'freesexycam',
'freeshemale', 'freesmut', 'freetit', 'freetits', 'freevoyeur', 'freexxx', 'freeshemale', 'freesmut', 'freetit', 'freetits', 'freevoyeur', 'freexxx',
'freexxxmovie', 'freexxxmovies', 'freexxxmpeg', 'freexxxphotos', 'freexxxpic', 'freexxxpics', 'freexxxmovie', 'freexxxmovies', 'freexxxmpeg', 'freexxxphotos', 'freexxxpic', 'freexxxpics',
'freexxxpicture', 'freexxxpictures', 'freexxxstories', 'freexxxvideo', 'freexxxx', 'fuking', 'freexxxpicture', 'freexxxpictures', 'freexxxstories', 'freexxxvideo', 'freexxxx', 'fuking',
'gangbangs', 'gratishardcoregalerie', 'hardcorecelebs', 'hardcorefisting', 'hardcorefree', 'hardcorefucking', 'gangbangs', 'gratishardcoregalerie', 'hardcorecelebs', 'hardcorefisting', 'hardcorefree', 'hardcorefucking',
'hardcorehooters', 'hardcorejunkie', 'hardcorejunky', 'hardcoremovie', 'hardcoremovies', 'hardcorepic', 'hardcorehooters', 'hardcorejunkie', 'hardcorejunky', 'hardcoremovie', 'hardcoremovies', 'hardcorepic',
'hardcorepics', 'hardcorepictures', 'hardcorepix', 'hardcoreporn', 'hardcoreporno', 'hardcorepornography', 'hardcorepics', 'hardcorepictures', 'hardcorepix', 'hardcoreporn', 'hardcoreporno', 'hardcorepornography',
'hardcorepornos', 'hardcorepussy', 'hardcoresamples', 'hardcoresex', 'hardcoresexpictures', 'hardcorestories', 'hardcorepornos', 'hardcorepussy', 'hardcoresamples', 'hardcoresex', 'hardcoresexpictures', 'hardcorestories',
'hardcorethumbnails', 'hardcorevideo', 'hardcorevideos', 'hardcorexxx', 'harddicks', 'hardnipples', 'hardcorethumbnails', 'hardcorevideo', 'hardcorevideos', 'hardcorexxx', 'harddicks', 'hardnipples',
'hardons', 'hardporn', 'indiasex', 'interacialhardcore', 'interacialsex', 'interacialxxx', 'hardons', 'hardporn', 'indiasex', 'interacialhardcore', 'interacialsex', 'interacialxxx',
'intercoursepositions', 'internetsex', 'interracialfucking', 'interracialhardcore', 'interracialporn', 'interracialpornography', 'intercoursepositions', 'internetsex', 'interracialfucking', 'interracialhardcore', 'interracialporn', 'interracialpornography',
'interracialsex', 'interracialsexstories', 'interracialxxx', 'intimatesex', 'ittybittytitty', 'japaneseporn', 'interracialsex', 'interracialsexstories', 'interracialxxx', 'intimatesex', 'ittybittytitty', 'japaneseporn',
'japanesesex', 'japansex', 'japanxxx', 'jennacam.com', 'jennajameson', 'jennicam', 'japanesesex', 'japansex', 'japanxxx', 'jennacam.com', 'jennajameson', 'jennicam',
'jerkoff', 'jism', 'jiz', 'jizz', 'juicycunts', 'juicypussy', 'jerkoff', 'jism', 'jiz', 'jizz', 'juicycunts', 'juicypussy',
'justhardcore', 'karasamateurs', 'karasxxx', 'kascha', 'kaylakleevage', 'kobetai', 'justhardcore', 'karasamateurs', 'karasxxx', 'kascha', 'kaylakleevage', 'kobetai',
'koreasex', 'lapdance', 'largeclits', 'largecunts', 'largedicks', 'largehooters', 'koreasex', 'lapdance', 'largeclits', 'largecunts', 'largedicks', 'largehooters',
'largepussy', 'largetit', 'largetits', 'latinapussy', 'latinasex', 'latinosex', 'largepussy', 'largetit', 'largetits', 'latinapussy', 'latinasex', 'latinosex',
'latinoxxx', 'latinxxx', 'legsex', 'lesbianerotica', 'lesbianhardcore', 'lesbianorgies', 'latinoxxx', 'latinxxx', 'legsex', 'lesbianerotica', 'lesbianhardcore', 'lesbianorgies',
'lesbianorgy', 'lesbianpics', 'lesbianpink', 'lesbianporn', 'lesbian-porno', 'lesbianporno', 'lesbianorgy', 'lesbianpics', 'lesbianpink', 'lesbianporn', 'lesbian-porno', 'lesbianporno',
'lesbianpornography', 'lesbianpornos', 'lesbianpussy', 'lesbiansex', 'lesbiansexpics', 'lesbiansexstories', 'lesbianpornography', 'lesbianpornos', 'lesbianpussy', 'lesbiansex', 'lesbiansexpics', 'lesbiansexstories',
'lesbianxxx', 'lickadick', 'lickingpussy', 'lindalovelace', 'lingeriexxx', 'literotica', 'lesbianxxx', 'lickadick', 'lickingpussy', 'lindalovelace', 'lingeriexxx', 'literotica',
'littleboobs', 'littlepussy', 'littletits', 'livefucking', 'liveporn', 'livesex', 'littleboobs', 'littlepussy', 'littletits', 'livefucking', 'liveporn', 'livesex',
'livesexcam', 'livesexcams', 'longdicks', 'lovedoll', 'lovedolls', 'magazinessex', 'livesexcam', 'livesexcams', 'longdicks', 'lovedoll', 'lovedolls', 'magazinessex',
'makinglove', 'malaysex', 'malenudes', 'malesex', 'mangaporno', 'mangax', 'makinglove', 'malaysex', 'malenudes', 'malesex', 'mangaporno', 'mangax',
'mangaxxx', 'manpics', 'mansex', 'marilynchambers', 'massivecocks', 'masterbating', 'mangaxxx', 'manpics', 'mansex', 'marilynchambers', 'massivecocks', 'masterbating',
'masturbation', 'masturbationvideos', 'maturefucking', 'maturesex', 'maturexxx', 'megaboobs', 'masturbation', 'masturbationvideos', 'maturefucking', 'maturesex', 'maturexxx', 'megaboobs',
'megaporno', 'megapussy', 'megatits', 'mensdicks', 'mensex', 'm.i.l.f.', 'megaporno', 'megapussy', 'megatits', 'mensdicks', 'mensex', 'm.i.l.f.',
'milf', 'milfhunterpic', 'milfmpegsample', 'modelsex', 'mondoporn', 'monstercocks', 'milf', 'milfhunterpic', 'milfmpegsample', 'modelsex', 'mondoporn', 'monstercocks',
'monsterdicks', 'motherfucker', 'moviepost.com', 'mpegsex', 'mpegxxx', 'muffdiving', 'monsterdicks', 'motherfucker', 'moviepost.com', 'mpegsex', 'mpegxxx', 'muffdiving',
'myslutwife', 'nacktfotos', 'nakedbabes', 'nakedblackwomen', 'nakedcelebrity', 'nakedcelebs', 'myslutwife', 'nacktfotos', 'nakedbabes', 'nakedblackwomen', 'nakedcelebrity', 'nakedcelebs',
'nakedcheerleader', 'nakedchicks', 'nakedgirls', 'nakedguys', 'nakedladies', 'nakedlady', 'nakedcheerleader', 'nakedchicks', 'nakedgirls', 'nakedguys', 'nakedladies', 'nakedlady',
'nakedman', 'nakedmen', 'nakedness', 'nakedphotographs', 'nakedphotography', 'nakedphotos', 'nakedman', 'nakedmen', 'nakedness', 'nakedphotographs', 'nakedphotography', 'nakedphotos',
'nakedpic', 'nakedpics', 'nakedpicture', 'nakedpictures', 'nakedpussy', 'nakedstars', 'nakedpic', 'nakedpics', 'nakedpicture', 'nakedpictures', 'nakedpussy', 'nakedstars',
'nakedwife', 'nakedwoman', 'nakedwomen', 'nastychat', 'nastypussy', 'nastysex', 'nakedwife', 'nakedwoman', 'nakedwomen', 'nastychat', 'nastypussy', 'nastysex',
'nastythumbs', 'naturaltits', 'naughty.com', 'naughtylinks', 'naughtylinx', 'naughtylynx', 'nastythumbs', 'naturaltits', 'naughty.com', 'naughtylinks', 'naughtylinx', 'naughtylynx',
'naughtynurses', 'netsex', 'niceass', 'nicetits', 'nikkinova', 'nikkityler', 'naughtynurses', 'netsex', 'niceass', 'nicetits', 'nikkinova', 'nikkityler',
'nipples', 'nookie', 'nookies', 'nudeactress', 'nudeactresses', 'nudeamateur', 'nipples', 'nookie', 'nookies', 'nudeactress', 'nudeactresses', 'nudeamateur',
'nudeamateurs', 'nudeasianwomen', 'nudebabes', 'nudebigboobs', 'nudeblack', 'nudeblackwomen', 'nudeamateurs', 'nudeasianwomen', 'nudebabes', 'nudebigboobs', 'nudeblack', 'nudeblackwomen',
'nudeblondes', 'nudeceleb', 'nudeceleberties', 'nudecelebraties', 'nudecelebrites', 'nudecelebrities', 'nudeblondes', 'nudeceleb', 'nudeceleberties', 'nudecelebraties', 'nudecelebrites', 'nudecelebrities',
'nudecelebrity', 'nudecelebs', 'nudecollegegirls', 'nudefemale', 'nudefemales', 'nudefree', 'nudecelebrity', 'nudecelebs', 'nudecollegegirls', 'nudefemale', 'nudefemales', 'nudefree',
'nudegay', 'nudeimages', 'nudeladies', 'nudelesbians', 'nudemale', 'nudemales', 'nudegay', 'nudeimages', 'nudeladies', 'nudelesbians', 'nudemale', 'nudemales',
'nudeman', 'nudemen', 'nudepic', 'nudepics', 'nudepicture', 'nudepictures', 'nudeman', 'nudemen', 'nudepic', 'nudepics', 'nudepicture', 'nudepictures',
'nudepornography', 'nudepussy', 'nuderaider', 'nuderedheads', 'nudes', 'nudesex', 'nudepornography', 'nudepussy', 'nuderaider', 'nuderedheads', 'nudes', 'nudesex',
'nudestar', 'nudestars', 'nudevideoconferencing', 'nudewoman', 'nudewomen', 'nudism', 'nudestar', 'nudestars', 'nudevideoconferencing', 'nudewoman', 'nudewomen', 'nudism',
'nudist', 'nudists', 'nudity', 'nylonfetish', 'nylonsex', 'nympho', 'nudist', 'nudists', 'nudity', 'nylonfetish', 'nylonsex', 'nympho',
'nymphos', 'olderbabes', 'oldersex', 'oldersluts', 'oldpussy', 'oldsex', 'nymphos', 'olderbabes', 'oldersex', 'oldersluts', 'oldpussy', 'oldsex',
'oldsluts', 'openlegs', 'openpussy', 'oral4free', 'oral-sex', 'oralsexpictures', 'oldsluts', 'openlegs', 'openpussy', 'oral4free', 'oral-sex', 'oralsexpictures',
'orgie', 'orgies', 'orgypics', 'orgys', 'pantyhosefetish', 'pantyhosesex', 'orgie', 'orgies', 'orgypics', 'orgys', 'pantyhosefetish', 'pantyhosesex',
'peepcam', 'persiankitty', 'persiankitty.com', 'perverted', 'picsxxx', 'picturesex', 'peepcam', 'persiankitty', 'persiankitty.com', 'perverted', 'picsxxx', 'picturesex',
'picturesofsex', 'picturessex', 'pimpserver', 'pimpserver.com', 'pinkpussy', 'pissing', 'picturesofsex', 'picturessex', 'pimpserver', 'pimpserver.com', 'pinkpussy', 'pissing',
'pixxx', 'poontang', 'pornagraphy', 'porncast', 'porncity', 'porndirectory', 'pixxx', 'poontang', 'pornagraphy', 'porncast', 'porncity', 'porndirectory',
'porne', 'pornfree', 'pornmovies', 'pornno', 'pornnude', 'porno', 'porne', 'pornfree', 'pornmovies', 'pornno', 'pornnude', 'porno',
'pornoadult', 'pornocartoons', 'pornochat', 'pornodeluxe', 'pornoe', 'pornofilms', 'pornoadult', 'pornocartoons', 'pornochat', 'pornodeluxe', 'pornoe', 'pornofilms',
'pornofree', 'pornogame', 'pornografi', 'pornografia', 'pornografie', 'pornografy', 'pornofree', 'pornogame', 'pornografi', 'pornografia', 'pornografie', 'pornografy',
'pornograph', 'pornographi', 'pornographia', 'pornographic', 'pornographicpictures', 'pornographics', 'pornograph', 'pornographi', 'pornographia', 'pornographic', 'pornographicpictures', 'pornographics',
'pornographicvideo', 'pornographicvideos', 'pornographie', 'pornography', 'pornographyfree', 'pornographyphotos', 'pornographicvideo', 'pornographicvideos', 'pornographie', 'pornography', 'pornographyfree', 'pornographyphotos',
'pornographypictures', 'pornography-sex', 'pornographysex', 'pornoland', 'pornolinks', 'pornolynx', 'pornographypictures', 'pornography-sex', 'pornographysex', 'pornoland', 'pornolinks', 'pornolynx',
'pornomagazines', 'pornomovies', 'pornompeg', 'pornophotos', 'pornopic', 'pornopics', 'pornomagazines', 'pornomovies', 'pornompeg', 'pornophotos', 'pornopic', 'pornopics',
'pornopicture', 'pornopictures', 'pornos', 'pornosex', 'pornosite', 'pornosites', 'pornopicture', 'pornopictures', 'pornos', 'pornosex', 'pornosite', 'pornosites',
'pornostar', 'pornostories', 'pornovideo', 'pornovideos', 'pornoxxx', 'pornphotos', 'pornostar', 'pornostories', 'pornovideo', 'pornovideos', 'pornoxxx', 'pornphotos',
'pornpics', 'pornpictures', 'pornpix', 'pornpost', 'pornqueens', 'pornrated', 'pornpics', 'pornpictures', 'pornpix', 'pornpost', 'pornqueens', 'pornrated',
'porns', 'pornsex', 'pornstar', 'pornstories', 'porntrack', 'pornvideo', 'porns', 'pornsex', 'pornstar', 'pornstories', 'porntrack', 'pornvideo',
'pornvideos', 'pornypics', 'porstars', 'privatesex', 'privatex', 'privatexx', 'pornvideos', 'pornypics', 'porstars', 'privatesex', 'privatex', 'privatexx',
'privatexxx', 'prono', 'pronography', 'publicnudity', 'puffynipples', 'purple+passion', 'privatexxx', 'prono', 'pronography', 'publicnudity', 'puffynipples', 'purple+passion',
'pussyboard', 'pussycam', 'pussycams', 'pussycloseup', 'pussycloseups', 'pussycum', 'pussyboard', 'pussycam', 'pussycams', 'pussycloseup', 'pussycloseups', 'pussycum',
'pussycunt', 'pussyeater', 'pussyeaters', 'pussyeating', 'pussyfuck', 'pussyfucking', 'pussycunt', 'pussyeater', 'pussyeaters', 'pussyeating', 'pussyfuck', 'pussyfucking',
'pussyhair', 'pussyheaven', 'pussyjuice', 'pussylickers', 'pussylicking', 'pussylink', 'pussyhair', 'pussyheaven', 'pussyjuice', 'pussylickers', 'pussylicking', 'pussylink',
'pussylips', 'pussyphotos', 'pussyporno', 'pussys', 'pussysex', 'pussyshots', 'pussylips', 'pussyphotos', 'pussyporno', 'pussys', 'pussysex', 'pussyshots',
'pussythumbnails', 'pussytits', 'pusy', 'racqueldarrian', 'rape', 'rawlinks', 'pussythumbnails', 'pussytits', 'pusy', 'racqueldarrian', 'rape', 'rawlinks',
'rawpussy', 'rawsex', 'realhardcore', 'realsex', 'redneckporn', 'redpussy', 'rawpussy', 'rawsex', 'realhardcore', 'realsex', 'redneckporn', 'redpussy',
'rubberfetish', 'russianxxx', 'scat', 'schiffernude', 'seka', 'seniorsex', 'rubberfetish', 'russianxxx', 'scat', 'schiffernude', 'seka', 'seniorsex',
'sex4free', 'sex66', 'sexacts', 'sexadult', 'sexaids', 'sexamateur', 'sex4free', 'sex66', 'sexacts', 'sexadult', 'sexaids', 'sexamateur',
'sexanal', 'sexandpictures', 'sexaphone', 'sexaudio', 'sexavi', 'sexbondage', 'sexanal', 'sexandpictures', 'sexaphone', 'sexaudio', 'sexavi', 'sexbondage',
'sexboner', 'sexbuttfucker', 'sexcam', 'sexcams', 'sexchat', 'sexchatrooms', 'sexboner', 'sexbuttfucker', 'sexcam', 'sexcams', 'sexchat', 'sexchatrooms',
'sexcites', 'sexclub', 'sexclubs', 'sex.com', 'sexdating', 'sexe', 'sexcites', 'sexclub', 'sexclubs', 'sex.com', 'sexdating', 'sexe',
'sexfantasies', 'sexfantasy', 'sexfilm', 'sexgallery', 'sexgame', 'sexgames', 'sexfantasies', 'sexfantasy', 'sexfilm', 'sexgallery', 'sexgame', 'sexgames',
'sexgirl', 'sexgirls', 'sexgroup', 'sexguide', 'sexhardcore', 'sexhphoto', 'sexgirl', 'sexgirls', 'sexgroup', 'sexguide', 'sexhardcore', 'sexhphoto',
'sexhungry', 'sexhungryjoe', 'sexhungryjoes', 'sexi', 'seximages', 'sexis', 'sexhungry', 'sexhungryjoe', 'sexhungryjoes', 'sexi', 'seximages', 'sexis',
'sexjapan', 'sex.jpg', 'sexlinda', 'sexlinks', 'sexlinx', 'sexlive', 'sexjapan', 'sex.jpg', 'sexlinda', 'sexlinks', 'sexlinx', 'sexlive',
'sexmagazine', 'sexmagazines', 'sexmature', 'sexmelayu', 'sexmodels', 'sexmovie', 'sexmagazine', 'sexmagazines', 'sexmature', 'sexmelayu', 'sexmodels', 'sexmovie',
'sexmovies', 'sexmpeg', 'sexnude', 'sexnudity', 'sexo', 'sexoanal', 'sexmovies', 'sexmpeg', 'sexnude', 'sexnudity', 'sexo', 'sexoanal',
'sexole', 'sexontheinternet', 'sexoral', 'sexpasswords', 'sexphoto', 'sexphotos', 'sexole', 'sexontheinternet', 'sexoral', 'sexpasswords', 'sexphoto', 'sexphotos',
'sexpic', 'sexpicnet', 'sexpics', 'sexpicture', 'sexpictures', 'sexpicturesfree', 'sexpic', 'sexpicnet', 'sexpics', 'sexpicture', 'sexpictures', 'sexpicturesfree',
'sexpix', 'sexplaza', 'sexporn', 'sexporno', 'sexpositions', 'sexpussy', 'sexpix', 'sexplaza', 'sexporn', 'sexporno', 'sexpositions', 'sexpussy',
'sexroulette', 'sexs', 'sexsamples', 'sexsearch', 'sexsex', 'sexsexsex', 'sexroulette', 'sexs', 'sexsamples', 'sexsearch', 'sexsex', 'sexsexsex',
'sexshare', 'sexshops', 'sexshow', 'sexshows', 'sexsite', 'sexsites', 'sexshare', 'sexshops', 'sexshow', 'sexshows', 'sexsite', 'sexsites',
'sexslave', 'sexsound', 'sexsounds', 'sexsource', 'sexspaces', 'sexspaces.com', 'sexslave', 'sexsound', 'sexsounds', 'sexsource', 'sexspaces', 'sexspaces.com',
'sexstories', 'sexstory', 'sexstorys', 'sextalk', 'sexteen', 'sexthumbnails', 'sexstories', 'sexstory', 'sexstorys', 'sextalk', 'sexteen', 'sexthumbnails',
'sex-toons', 'sextoons', 'sextour', 'sextoy', 'sextoys', 'sextpus', 'sex-toons', 'sextoons', 'sextour', 'sextoy', 'sextoys', 'sextpus',
'sextracker.com', 'sextropolis', 'sexualbondage', 'sexualfantasies', 'sexualfantasy', 'sexualintercourse', 'sextracker.com', 'sextropolis', 'sexualbondage', 'sexualfantasies', 'sexualfantasy', 'sexualintercourse',
'sexualpictures', 'sexualpleasure', 'sexualpositions', 'sexuncensored', 'sexvideo', 'sexvision', 'sexualpictures', 'sexualpleasure', 'sexualpositions', 'sexuncensored', 'sexvideo', 'sexvision',
'sexvote', 'sexwomen', 'sexworld', 'sexx', 'sexxx', 'sexxxx', 'sexvote', 'sexwomen', 'sexworld', 'sexx', 'sexxx', 'sexxxx',
'sexxxxx', 'sexyamateurs', 'sexybabes', 'sexyblack', 'sexyboobs', 'sexybookmark', 'sexxxxx', 'sexyamateurs', 'sexybabes', 'sexyblack', 'sexyboobs', 'sexybookmark',
'sexybookmarka', 'sexybookmarks', 'sexybutts', 'sexycunts', 'sexygirl', 'sexygirls', 'sexybookmarka', 'sexybookmarks', 'sexybutts', 'sexycunts', 'sexygirl', 'sexygirls',
'sexyladies', 'sexylady', 'sexylegs', 'sexylingerie', 'sexymen', 'sexyphotos', 'sexyladies', 'sexylady', 'sexylegs', 'sexylingerie', 'sexymen', 'sexyphotos',
'sexypics', 'sexypictures', 'sexypost', 'sexypussies', 'sexypussy', 'sexysites', 'sexypics', 'sexypictures', 'sexypost', 'sexypussies', 'sexypussy', 'sexysites',
'sexystories', 'sexywoman', 'sexywomen', 'shavedcunt', 'shavedcunts', 'shavedpussies', 'sexystories', 'sexywoman', 'sexywomen', 'shavedcunt', 'shavedcunts', 'shavedpussies',
'shavedpussy', 'shavedpussypics', 'shavedsluts', 'shavedwomen', 'shavenpussies', 'shavenpussy', 'shavedpussy', 'shavedpussypics', 'shavedsluts', 'shavedwomen', 'shavenpussies', 'shavenpussy',
'shavepussy', 'sheboy', 'she-male', 'shemale+video', 'shemaleyum', 'showcam', 'shavepussy', 'sheboy', 'she-male', 'shemale+video', 'shemaleyum', 'showcam',
'showcams', 'showercam', 'showercams', 'slavesex', 'smallboobs', 'smallbreasts', 'showcams', 'showercam', 'showercams', 'slavesex', 'smallboobs', 'smallbreasts',
'smalldicks', 'smallpussy', 'smalltits', 'smut', 'smut69', 'smutland', 'smalldicks', 'smallpussy', 'smalltits', 'smut', 'smut69', 'smutland',
'smutserver', 'smutshack', 'softcore', 'softporn', 'softpornography', 'spankingpage', 'smutserver', 'smutshack', 'softcore', 'softporn', 'softpornography', 'spankingpage',
'spreadpussy', 'spycamadult', 'stockingsex', 'storieserotic', 'storiessex', 'straightsex', 'spreadpussy', 'spycamadult', 'stockingsex', 'storieserotic', 'storiessex', 'straightsex',
'stretchedcunt', 'stripclub', 'stripclubs', 'stripshow', 'striptease', 'strokeit', 'stretchedcunt', 'stripclub', 'stripclubs', 'stripshow', 'striptease', 'strokeit',
'strokeme', 'suckdick', 'sucksex', 'superchicken', 'supersex', 'supertits', 'strokeme', 'suckdick', 'sucksex', 'superchicken', 'supersex', 'supertits',
'swallowcum', 'swinger', 'swingerclub', 'swingers', 'swollenclits', 'syberporn', 'swallowcum', 'swinger', 'swingerclub', 'swingers', 'swollenclits', 'syberporn',
'sylviasaint', 'teenanal', 'teenanalsex', 'teenhardcore', 'teenie', 'teenies', 'sylviasaint', 'teenanal', 'teenanalsex', 'teenhardcore', 'teenie', 'teenies',
'teennude', 'teenpics', 'teenporn', 'teenporno', 'teenpussy', 'teensex', 'teennude', 'teenpics', 'teenporn', 'teenporno', 'teenpussy', 'teensex',
'teenslut', 'teensluts', 'teensuck', 'teenxxx', 'tgp', 'thaipussy', 'teenslut', 'teensluts', 'teensuck', 'teenxxx', 'tgp', 'thaipussy',
'thaisex', 'thehun', 'threesomes', 'thumblords', 'thumbnailssex', 'thumbzilla', 'thaisex', 'thehun', 'threesomes', 'thumblords', 'thumbnailssex', 'thumbzilla',
'tiffanytowers', 'tightcunt', 'tightpussies', 'tightpussy', 'tinypussy', 'tinytits', 'tiffanytowers', 'tightcunt', 'tightpussies', 'tightpussy', 'tinypussy', 'tinytits',
'tinytitties', 'tisandass', 'tit', 'titbondage', 'titfuck', 'titfucking', 'tinytitties', 'tisandass', 'tit', 'titbondage', 'titfuck', 'titfucking',
'tities', 'titman', 'titsandass', 'titsass', 'titties', 'titts', 'tities', 'titman', 'titsandass', 'titsass', 'titties', 'titts',
'titty', 'tittyfuck', 'tittyfucking', 'tittys', 'tokyotopless', 'tommysbookmark', 'titty', 'tittyfuck', 'tittyfucking', 'tittys', 'tokyotopless', 'tommysbookmark',
'toplesswomen', 'trannies', 'transexual', 'transsexuels', 'twat', 'twats', 'toplesswomen', 'trannies', 'transexual', 'transsexuels', 'twat', 'twats',
'twink', 'ultradonkey', 'ultrahardcore', 'uncutcocks', 'vaginalintercourse', 'vaginapictures', 'twink', 'ultradonkey', 'ultrahardcore', 'uncutcocks', 'vaginalintercourse', 'vaginapictures',
'videoporno', 'videosex', 'videoxxx', 'vintageporn', 'virtualsex', 'vividtv', 'videoporno', 'videosex', 'videoxxx', 'vintageporn', 'virtualsex', 'vividtv',
'wank', 'wankers', 'wanking', 'wendywhoppers', 'wetcunt', 'wetcunts', 'wank', 'wankers', 'wanking', 'wendywhoppers', 'wetcunt', 'wetcunts',
'wetdick', 'wetpanties', 'wetpussies', 'wetpussy', 'wetsex', 'wetvagina', 'wetdick', 'wetpanties', 'wetpussies', 'wetpussy', 'wetsex', 'wetvagina',
'whitepussy', 'whitesex', 'whitetits', 'whitexxx', 'wifesharing', 'wifeswapping', 'whitepussy', 'whitesex', 'whitetits', 'whitexxx', 'wifesharing', 'wifeswapping',
'wildpussy', 'wildsex', 'womensex', 'worldsex', 'worldsexguide', 'x-rated', 'wildpussy', 'wildsex', 'womensex', 'worldsex', 'worldsexguide', 'x-rated',
'xrated', 'x-ratedmovie', 'x-ratedmovies', 'x-ratedvideo', 'x-ratedvideos', 'xxx4free', 'xrated', 'x-ratedmovie', 'x-ratedmovies', 'x-ratedvideo', 'x-ratedvideos', 'xxx4free',
'xxxadult', 'xxxadultmovies', 'xxxadultvideo', 'xxxadultvideos', 'xxxbabes', 'xxxcam', 'xxxadult', 'xxxadultmovies', 'xxxadultvideo', 'xxxadultvideos', 'xxxbabes', 'xxxcam',
'xxxcelebrities', 'xxxfree', 'xxxfreepics', 'xxxgalleries', 'xxxhardcore', 'xxxjapan', 'xxxcelebrities', 'xxxfree', 'xxxfreepics', 'xxxgalleries', 'xxxhardcore', 'xxxjapan',
'xxxlinks', 'xxxmodels', 'xxxmovie', 'xxxmovies', 'xxxmpeg', 'xxxpassword', 'xxxlinks', 'xxxmodels', 'xxxmovie', 'xxxmovies', 'xxxmpeg', 'xxxpassword',
'xxxphoto', 'xxxphotos', 'xxxpic', 'xxx-pics', 'xxxpics', 'xxxpicture', 'xxxphoto', 'xxxphotos', 'xxxpic', 'xxx-pics', 'xxxpics', 'xxxpicture',
'xxxpictures', 'xxxpicturesfree', 'xxxpicx', 'xxxpirtures', 'xxxporn', 'xxxporno', 'xxxpictures', 'xxxpicturesfree', 'xxxpicx', 'xxxpirtures', 'xxxporn', 'xxxporno',
'xxxpussy', 'xxx-rated', 'xxxrated', 'xxxsex', 'xxxsites', 'xxxsluts', 'xxxpussy', 'xxx-rated', 'xxxrated', 'xxxsex', 'xxxsites', 'xxxsluts',
'xxxstories', 'xxxteen', 'xxxthumbnails', 'xxxthumbs', 'xxxtoons', 'xxxtoys', 'xxxstories', 'xxxteen', 'xxxthumbnails', 'xxxthumbs', 'xxxtoons', 'xxxtoys',
'xxxvideo', 'xxxvideos', 'xxxwomen', 'xxxx', 'xxxxx', 'xxxxxx', 'xxxvideo', 'xxxvideos', 'xxxwomen', 'xxxx', 'xxxxx', 'xxxxxx',
'filthyfarm', 'adultcheck', 'erotic', 'mistresses', 'naughty', 'filthyfarm', 'adultcheck', 'erotic', 'mistresses', 'naughty',
'adult', 'amateur', 'amateurs', 'anal', 'analau', 'poker', 'adult', 'amateur', 'amateurs', 'anal', 'analau', 'poker',
'anally', 'asian', 'swinging', 'ass', 'orgy', 'sexvideos', 'anally', 'asian', 'swinging', 'ass', 'orgy', 'sexvideos',
'babe', 'babes', 'bare', 'glamour', 'casino', 'bed', 'babe', 'babes', 'bare', 'glamour', 'casino', 'bed',
'vibrator', 'bikini', 'bisexual', 'blonde', 'breasted', 'breasts', 'vibrator', 'bikini', 'bisexual', 'blonde', 'breasted', 'breasts',
'busty', 'butts', 'candy', 'caning', 'caps', 'catholic', 'busty', 'butts', 'candy', 'caning', 'caps', 'catholic',
'chained', 'chested', 'chubby', 'close-ups', 'coupling', 'chained', 'chested', 'chubby', 'close-ups', 'coupling',
'cucumber', 'doll', 'dolls', 'dolly', 'dominating', 'drinking', 'cucumber', 'doll', 'dolls', 'dolly', 'dominating', 'drinking',
'drunk', 'fat', 'feet', 'fingering', 'fishnet', 'flasher', 'drunk', 'fat', 'feet', 'fingering', 'fishnet', 'flasher',
'footjob', 'foreplay', 'foreplaying', 'girlfriend', 'girlfriends', 'gymnast', 'footjob', 'foreplay', 'foreplaying', 'girlfriend', 'girlfriends', 'gymnast',
'hairy', 'holly', 'horse', 'housewife', 'houswife', 'insertion', 'hairy', 'holly', 'horse', 'housewife', 'houswife', 'insertion',
'insertions', 'interracial', 'jerka', 'juicy', 'kissing', 'kitty', 'insertions', 'interracial', 'jerka', 'juicy', 'kissing', 'kitty',
'lactating', 'ladyboy', 'lance', 'latina', 'latins', 'leather', 'lactating', 'ladyboy', 'lance', 'latina', 'latins', 'leather',
'legs', 'lesbian', 'lesbians', 'licking', 'lingerie', 'lubricants', 'legs', 'lesbian', 'lesbians', 'licking', 'lingerie', 'lubricants',
'mature', 'midget', 'minor', 'mistress', 'nude', 'mature', 'midget', 'minor', 'mistress', 'nude',
'older', 'olez', 'oral', 'panties', 'pantyhose', 'pedro', 'older', 'olez', 'oral', 'panties', 'pantyhose', 'pedro',
'peeing', 'penetration', 'pigtailed', 'pleasure', 'plump', 'peeing', 'penetration', 'pigtailed', 'pleasure', 'plump',
'plumper', 'poser', 'posers', 'poses', 'posing', 'prosecuted', 'plumper', 'poser', 'posers', 'poses', 'posing', 'prosecuted',
'punishment', 'reproducing', 'sex', 'shaved', 'shock', 'slave', 'punishment', 'reproducing', 'sex', 'shaved', 'shock', 'slave',
'sleeping', 'speculums', 'spread', 'spreading', 'squirting', 'stripper', 'sleeping', 'speculums', 'spread', 'spreading', 'squirting', 'stripper',
'stripping', 'swapping', 'thong', 'topless', 'toying', 'stripping', 'swapping', 'thong', 'topless', 'toying',
'trix', 'undressing', 'uniform', 'whipcream', 'brazzers', 'порно', 'trix', 'undressing', 'uniform', 'whipcream', 'brazzers', 'порно',
'порн', 'лесб', 'гей', 'геи', 'прон' 'порн', 'лесб', 'гей', 'геи', 'прон'
]; ];
const XXX_VERY_BAD_WORDS = [ const XXX_VERY_BAD_WORDS = [
'catgoddess', 'gracel', 'fatman', 'falko', 'pthc', 'catgoddess', 'gracel', 'fatman', 'falko', 'pthc',
'ptsc', 'yukikax', 'ls-models', '3yo', '4yo', '5yo', '6yo', '7yo', '8yo', '9yo', 'ptsc', 'yukikax', 'ls-models', '3yo', '4yo', '5yo', '6yo', '7yo', '8yo', '9yo',
'10yo', '11yo', '12yo', '13yo', '14yo', '15yo', '16yo' '10yo', '11yo', '12yo', '13yo', '14yo', '15yo', '16yo'
] ]
module.exports = { module.exports = {
XXX_BLOCK_WORDS, XXX_BLOCK_WORDS,
XXX_VERY_BAD_WORDS XXX_VERY_BAD_WORDS
}; };

View File

@ -4,23 +4,23 @@ import ReactMarkdown from 'react-markdown'
import fs from 'fs' import fs from 'fs'
export default class ChangeLog extends Page { export default class ChangeLog extends Page {
constructor(props) { constructor(props) {
super(props) super(props)
this.setTitle('Changelog'); this.setTitle('Changelog');
let changelogPath = 'CHANGELOG.md' let changelogPath = 'CHANGELOG.md'
if(!fs.existsSync(changelogPath)) if(!fs.existsSync(changelogPath))
changelogPath = 'resources/CHANGELOG.md' changelogPath = 'resources/CHANGELOG.md'
this.changelog = fs.readFileSync(changelogPath) this.changelog = fs.readFileSync(changelogPath)
if(!this.changelog) if(!this.changelog)
throw new Error('no changelog file') throw new Error('no changelog file')
} }
render() { render() {
return ( return (
<div className='pad0-75'> <div className='pad0-75'>
<ReactMarkdown skipHtml={true} source={this.changelog} /> <ReactMarkdown skipHtml={true} source={this.changelog} />
</div> </div>
); );
} }
} }

View File

@ -6,13 +6,13 @@ export default class BTComponent extends Component {
// Свайп действия // Свайп действия
if( if(
this.props.onSwipeLeft || this.props.onSwipeLeft ||
this.props.onSwipeRight || this.props.onSwipeRight ||
this.props.onSwipeTop || this.props.onSwipeTop ||
this.props.onSwipeBottom || this.props.onSwipeBottom ||
this.onSwipeLeft || this.onSwipeLeft ||
this.onSwipeRight || this.onSwipeRight ||
this.onSwipeTop || this.onSwipeTop ||
this.onSwipeBottom this.onSwipeBottom
) )
{ {
this.swipeFunctions = listenSwipe(this, { this.swipeFunctions = listenSwipe(this, {

View File

@ -9,302 +9,302 @@ import Slider from 'material-ui/Slider'
import fs from 'fs' import fs from 'fs'
let dialog let dialog
if(typeof WEB === 'undefined') if(typeof WEB === 'undefined')
dialog = require('electron').remote.dialog dialog = require('electron').remote.dialog
export default class ConfigPage extends Page { export default class ConfigPage extends Page {
constructor(props) { constructor(props) {
super(props) super(props)
this.setTitle('Rats settings'); this.setTitle('Rats settings');
this.options = {} this.options = {}
} }
componentDidMount() { componentDidMount() {
this.loadSettings() this.loadSettings()
} }
loadSettings() { loadSettings() {
window.torrentSocket.emit('config', window.customLoader((options) => { window.torrentSocket.emit('config', window.customLoader((options) => {
this.options = options; this.options = options;
console.log(this.options) console.log(this.options)
this.forceUpdate(); this.forceUpdate();
})); }));
} }
saveSettings() { saveSettings() {
window.torrentSocket.emit('setConfig', this.options) window.torrentSocket.emit('setConfig', this.options)
this.settingsSavedMessage = true this.settingsSavedMessage = true
this.forceUpdate() this.forceUpdate()
setTimeout(() => { setTimeout(() => {
this.settingsSavedMessage = false this.settingsSavedMessage = false
this.forceUpdate() this.forceUpdate()
}, 1000) }, 1000)
} }
render() { render() {
return ( return (
<div> <div>
<div className='row center pad0-75'> <div className='row center pad0-75'>
<RaisedButton label={__('Back to main page')} primary={true} onClick={() => { <RaisedButton label={__('Back to main page')} primary={true} onClick={() => {
window.router('/') window.router('/')
}} /> }} />
</div> </div>
<div className='column center w100p pad0-75'> <div className='column center w100p pad0-75'>
<Toggle <Toggle
style={{marginTop: '10px'}} style={{marginTop: '10px'}}
label={__('Enabled network scanning')} label={__('Enabled network scanning')}
toggled={this.options.indexer} toggled={this.options.indexer}
onToggle={(e, checked) => { onToggle={(e, checked) => {
this.options.indexer = checked this.options.indexer = checked
if(!this.options.indexer) if(!this.options.indexer)
this.options.p2p = false this.options.p2p = false
this.forceUpdate() this.forceUpdate()
}} }}
/> />
<div className='column w100p'> <div className='column w100p'>
<div className='row inline w100p'> <div className='row inline w100p'>
<div style={{flex: 1}}>{__('Scanning port')}</div> <div style={{flex: 1}}>{__('Scanning port')}</div>
<TextField <TextField
style={{width: 65}} style={{width: 65}}
hintText={__('Port')} hintText={__('Port')}
errorText={this.options.spiderPort > 0 ? undefined : __('This field is required')} errorText={this.options.spiderPort > 0 ? undefined : __('This field is required')}
value={this.options.spiderPort} value={this.options.spiderPort}
onChange={(e, value) => { onChange={(e, value) => {
if(!value) if(!value)
value = 0 value = 0
if(value > 65535) if(value > 65535)
value = 65535 value = 65535
this.options.spiderPort = parseInt(value) this.options.spiderPort = parseInt(value)
this.forceUpdate() this.forceUpdate()
}} }}
/> />
</div> </div>
<div className='fs0-75' style={{color: 'grey'}}>* {__('For current work TCP and UDP ports must be fully open and forward in case of router usage')}</div> <div className='fs0-75' style={{color: 'grey'}}>* {__('For current work TCP and UDP ports must be fully open and forward in case of router usage')}</div>
</div> </div>
<div className='column w100p'> <div className='column w100p'>
<div className='row inline w100p'> <div className='row inline w100p'>
<div style={{flex: 1}}>{__('Trackers responce port')}</div> <div style={{flex: 1}}>{__('Trackers responce port')}</div>
<TextField <TextField
style={{width: 65}} style={{width: 65}}
hintText="Port" hintText="Port"
errorText={this.options.udpTrackersPort > 0 ? undefined : __('This field is required')} errorText={this.options.udpTrackersPort > 0 ? undefined : __('This field is required')}
value={this.options.udpTrackersPort} value={this.options.udpTrackersPort}
onChange={(e, value) => { onChange={(e, value) => {
if(!value) if(!value)
value = 0 value = 0
if(value > 65535) if(value > 65535)
value = 65535 value = 65535
this.options.udpTrackersPort = parseInt(value) this.options.udpTrackersPort = parseInt(value)
this.forceUpdate() this.forceUpdate()
}} }}
/> />
</div> </div>
<div className='fs0-75' style={{color: 'grey'}}>* {__('For current work UDP port must be fully open and forward in case of router usage')}</div> <div className='fs0-75' style={{color: 'grey'}}>* {__('For current work UDP port must be fully open and forward in case of router usage')}</div>
</div> </div>
<Toggle <Toggle
style={{marginTop: '10px'}} style={{marginTop: '10px'}}
label={__('Enabled UPnP')} label={__('Enabled UPnP')}
toggled={this.options.upnp} toggled={this.options.upnp}
onToggle={(e, checked) => { onToggle={(e, checked) => {
this.options.upnp = checked this.options.upnp = checked
this.forceUpdate() this.forceUpdate()
}} }}
/> />
<div className='row inline w100p'> <div className='row inline w100p'>
<div style={{flex: 1}}>{__('Collection directory')}</div> <div style={{flex: 1}}>{__('Collection directory')}</div>
<TextField <TextField
hintText={__('Db path')} hintText={__('Db path')}
errorText={this.options.dbPath && this.options.dbPath.length > 0 ? undefined : __('This field is required')} errorText={this.options.dbPath && this.options.dbPath.length > 0 ? undefined : __('This field is required')}
value={this.options.dbPath} value={this.options.dbPath}
onChange={(e, value) => { onChange={(e, value) => {
if(!fs.existsSync(value)) if(!fs.existsSync(value))
return return
this.options.dbPath = value this.options.dbPath = value
this.forceUpdate() this.forceUpdate()
}} }}
/> />
<RaisedButton style={{marginLeft: 20}} label={__('Browse')} primary={true} onClick={() => { <RaisedButton style={{marginLeft: 20}} label={__('Browse')} primary={true} onClick={() => {
if(!dialog) if(!dialog)
return return
const dir = dialog.showOpenDialog({properties: ['openDirectory']})[0] const dir = dialog.showOpenDialog({properties: ['openDirectory']})[0]
if(dir) if(dir)
{ {
this.options.dbPath = dir this.options.dbPath = dir
this.forceUpdate() this.forceUpdate()
} }
}} /> }} />
</div> </div>
<div className='row inline w100p'> <div className='row inline w100p'>
<div style={{flex: 1}}>{__('Download torrents directory')}</div> <div style={{flex: 1}}>{__('Download torrents directory')}</div>
<TextField <TextField
hintText={__('Download path')} hintText={__('Download path')}
value={this.options.client && this.options.client.downloadPath} value={this.options.client && this.options.client.downloadPath}
onChange={(e, value) => { onChange={(e, value) => {
if(!fs.existsSync(value)) if(!fs.existsSync(value))
return return
this.options.client.downloadPath = value this.options.client.downloadPath = value
this.forceUpdate() this.forceUpdate()
}} }}
/> />
<RaisedButton style={{marginLeft: 20}} label={__('Browse')} primary={true} onClick={() => { <RaisedButton style={{marginLeft: 20}} label={__('Browse')} primary={true} onClick={() => {
if(!dialog) if(!dialog)
return return
const dir = dialog.showOpenDialog({properties: ['openDirectory']})[0] const dir = dialog.showOpenDialog({properties: ['openDirectory']})[0]
if(dir) if(dir)
{ {
this.options.client.downloadPath = dir this.options.client.downloadPath = dir
this.forceUpdate() this.forceUpdate()
} }
}} /> }} />
</div> </div>
<Toggle <Toggle
style={{marginTop: '10px'}} style={{marginTop: '10px'}}
label={__('Hide to tray on application minimize')} label={__('Hide to tray on application minimize')}
toggled={this.options.trayOnMinimize} toggled={this.options.trayOnMinimize}
onToggle={(e, checked) => { onToggle={(e, checked) => {
this.options.trayOnMinimize = checked this.options.trayOnMinimize = checked
this.forceUpdate() this.forceUpdate()
}} }}
/> />
<Toggle <Toggle
style={{marginTop: '10px'}} style={{marginTop: '10px'}}
label={__('Hide to tray on application close')} label={__('Hide to tray on application close')}
toggled={this.options.trayOnClose} toggled={this.options.trayOnClose}
onToggle={(e, checked) => { onToggle={(e, checked) => {
this.options.trayOnClose = checked this.options.trayOnClose = checked
this.forceUpdate() this.forceUpdate()
}} }}
/> />
<div style={{marginTop: 10}}>{__('P2P Rats network settings')}:</div> <div style={{marginTop: 10}}>{__('P2P Rats network settings')}:</div>
<Toggle <Toggle
style={{marginTop: '10px'}} style={{marginTop: '10px'}}
label={__('Enabled p2p search')} label={__('Enabled p2p search')}
toggled={this.options.p2p} toggled={this.options.p2p}
onToggle={(e, checked) => { onToggle={(e, checked) => {
this.options.p2p = this.options.indexer && checked this.options.p2p = this.options.indexer && checked
this.forceUpdate() this.forceUpdate()
}} }}
/> />
<div className='column w100p'> <div className='column w100p'>
<Toggle <Toggle
style={{marginTop: '10px'}} style={{marginTop: '10px'}}
label={__('Enabled bootstrap peers')} label={__('Enabled bootstrap peers')}
toggled={this.options.p2pBootstrap} toggled={this.options.p2pBootstrap}
onToggle={(e, checked) => { onToggle={(e, checked) => {
this.options.p2pBootstrap = checked this.options.p2pBootstrap = checked
this.forceUpdate() this.forceUpdate()
}} }}
/> />
<div className='fs0-75' style={{color: 'grey'}}>* {__('Use extrnral bootstrap nodes to get p2p peers when network setted wrong or need external source')}</div> <div className='fs0-75' style={{color: 'grey'}}>* {__('Use extrnral bootstrap nodes to get p2p peers when network setted wrong or need external source')}</div>
</div> </div>
<div className='column w100p'> <div className='column w100p'>
<div className='row inline w100p'> <div className='row inline w100p'>
<div style={{flex: 1}}>{__('Max peers limit')} ({__('current')}: {this.options.p2pConnections})</div> <div style={{flex: 1}}>{__('Max peers limit')} ({__('current')}: {this.options.p2pConnections})</div>
<Slider <Slider
min={10} min={10}
max={25} max={25}
step={1} step={1}
style={{width: 300}} style={{width: 300}}
value={this.options.p2pConnections} value={this.options.p2pConnections}
onChange={(event, value) => { onChange={(event, value) => {
this.options.p2pConnections = value this.options.p2pConnections = value
this.forceUpdate() this.forceUpdate()
}} }}
/> />
</div> </div>
</div> </div>
<div className='column w100p'> <div className='column w100p'>
<Toggle <Toggle
style={{marginTop: '10px'}} style={{marginTop: '10px'}}
label={__('P2P torrents replication')} label={__('P2P torrents replication')}
toggled={this.options.p2pReplication} toggled={this.options.p2pReplication}
onToggle={(e, checked) => { onToggle={(e, checked) => {
this.options.p2pReplication = checked this.options.p2pReplication = checked
this.forceUpdate() this.forceUpdate()
}} }}
/> />
<div className='fs0-75' style={{color: 'grey'}}>* {__('Enable torrents replication from another rats clients. Dont recomended if torrent scanner works correct')}.</div> <div className='fs0-75' style={{color: 'grey'}}>* {__('Enable torrents replication from another rats clients. Dont recomended if torrent scanner works correct')}.</div>
</div> </div>
<div style={{marginTop: 10}}>{__('Torrent network scanner settings')}:</div> <div style={{marginTop: 10}}>{__('Torrent network scanner settings')}:</div>
<div className='column w100p'> <div className='column w100p'>
<div className='row inline w100p'> <div className='row inline w100p'>
<div style={{flex: 1}}>{__('Scanner walk speed')} ({__('current')}: {this.options.spider && this.options.spider.walkInterval}) [{__('affected after program reload')}]</div> <div style={{flex: 1}}>{__('Scanner walk speed')} ({__('current')}: {this.options.spider && this.options.spider.walkInterval}) [{__('affected after program reload')}]</div>
<Slider <Slider
min={1} min={1}
max={150} max={150}
step={1} step={1}
style={{width: 300}} style={{width: 300}}
value={this.options.spider && this.options.spider.walkInterval} value={this.options.spider && this.options.spider.walkInterval}
onChange={(event, value) => { onChange={(event, value) => {
this.options.spider.walkInterval = value this.options.spider.walkInterval = value
this.forceUpdate() this.forceUpdate()
}} }}
/> />
</div> </div>
<div className='fs0-75' style={{color: 'grey'}}>* {__('Low value')} - {__('fast initial scanning and high cpu usage')}. {__('High Value')} - {__('low cpu usage but very slow scanning')}. <div className='fs0-75' style={{color: 'grey'}}>* {__('Low value')} - {__('fast initial scanning and high cpu usage')}. {__('High Value')} - {__('low cpu usage but very slow scanning')}.
{__('Good value between')} 3-60. {__('Defaul value')}: 5</div> {__('Good value between')} 3-60. {__('Defaul value')}: 5</div>
</div> </div>
<div className='column w100p'> <div className='column w100p'>
<div className='row inline w100p'> <div className='row inline w100p'>
<div style={{flex: 1}}>{__('Nodes usage')} ({__('current')}: {this.options.spider && this.options.spider.nodesUsage})</div> <div style={{flex: 1}}>{__('Nodes usage')} ({__('current')}: {this.options.spider && this.options.spider.nodesUsage})</div>
<Slider <Slider
min={0} min={0}
max={1000} max={1000}
step={1} step={1}
style={{width: 300}} style={{width: 300}}
value={this.options.spider && this.options.spider.nodesUsage} value={this.options.spider && this.options.spider.nodesUsage}
onChange={(event, value) => { onChange={(event, value) => {
this.options.spider.nodesUsage = value this.options.spider.nodesUsage = value
this.forceUpdate() this.forceUpdate()
}} }}
/> />
</div> </div>
<div className='fs0-75' style={{color: 'grey'}}>* {__('Low Value')} - {__('very low usage of nodes, low network traffic, slow torrent scanning')}. {__('High value')} - {__('high traffic, fast scanning, high routers usage')}. <div className='fs0-75' style={{color: 'grey'}}>* {__('Low Value')} - {__('very low usage of nodes, low network traffic, slow torrent scanning')}. {__('High value')} - {__('high traffic, fast scanning, high routers usage')}.
{__('Recomended value between')} 10-1000. {__('Defaul value')}: 100. 0 - {__('Ignore this option')} ({__('no limit')}). {__('Recomended value between')} 10-1000. {__('Defaul value')}: 100. 0 - {__('Ignore this option')} ({__('no limit')}).
</div> </div>
</div> </div>
<div className='column w100p'> <div className='column w100p'>
<div className='row inline w100p'> <div className='row inline w100p'>
<div style={{flex: 1}}>{__('Reduce network packages')} ({__('current')}: {this.options.spider && this.options.spider.packagesLimit})</div> <div style={{flex: 1}}>{__('Reduce network packages')} ({__('current')}: {this.options.spider && this.options.spider.packagesLimit})</div>
<Slider <Slider
min={0} min={0}
max={2000} max={2000}
step={1} step={1}
style={{width: 300}} style={{width: 300}}
value={this.options.spider && this.options.spider.packagesLimit} value={this.options.spider && this.options.spider.packagesLimit}
onChange={(event, value) => { onChange={(event, value) => {
this.options.spider.packagesLimit = value this.options.spider.packagesLimit = value
this.forceUpdate() this.forceUpdate()
}} }}
/> />
</div> </div>
<div className='fs0-75' style={{color: 'grey'}}>* {__('Low Value')} - {__('ignore more usless network packages, lower traffic and routers usage')}. {__('High Value')} - {__('high traffic and router usage in prospect')}. <div className='fs0-75' style={{color: 'grey'}}>* {__('Low Value')} - {__('ignore more usless network packages, lower traffic and routers usage')}. {__('High Value')} - {__('high traffic and router usage in prospect')}.
{__('Recomended value between')} 300-2000. {__('Defaul value')}: 500. 0 - {__('Ignore this option')} ({__('no limit')}). {__('Recomended value between')} 300-2000. {__('Defaul value')}: 500. 0 - {__('Ignore this option')} ({__('no limit')}).
</div> </div>
</div> </div>
{ {
this.settingsSavedMessage this.settingsSavedMessage
&& &&
<div style={{color: 'green'}}>{__('Settings saved')}</div> <div style={{color: 'green'}}>{__('Settings saved')}</div>
} }
<div className='row center pad0-75'> <div className='row center pad0-75'>
<RaisedButton label={__('Save Settings')} primary={true} onClick={() => { <RaisedButton label={__('Save Settings')} primary={true} onClick={() => {
this.saveSettings() this.saveSettings()
}} /> }} />
</div> </div>
</div> </div>
</div> </div>
); );
} }
} }

View File

@ -236,7 +236,7 @@ const ContentCategoryProp = 'contentCategory';
const { const {
XXX_BLOCK_WORDS, XXX_BLOCK_WORDS,
XXX_VERY_BAD_WORDS XXX_VERY_BAD_WORDS
} = require('./bad-words'); } = require('./bad-words');
// блокируем порнографию // блокируем порнографию
@ -270,7 +270,7 @@ const detectSubCategory = (torrent, files, typesPriority, contentType) => {
fileCheck = fileCheck.join('.'); fileCheck = fileCheck.join('.');
blockBadName(torrent, fileCheck); blockBadName(torrent, fileCheck);
return torrent[ContentTypeProp] == 'bad'; return torrent[ContentTypeProp] == 'bad';
}) })
} }
@ -304,7 +304,7 @@ const torrentTypeDetect = (torrent, files) => {
} }
} }
let priority = Object.keys(typesPriority).sort(function(a, b){ let priority = Object.keys(typesPriority).sort(function(a, b){
return typesPriority[b] - typesPriority[a] return typesPriority[b] - typesPriority[a]
}); });
if(priority.length > 0) if(priority.length > 0)
torrent[ContentTypeProp] = priority[0]; torrent[ContentTypeProp] = priority[0];

View File

@ -7,27 +7,27 @@ import NavigationClose from 'material-ui/svg-icons/navigation/close';
export default class DMCAPage extends Page { export default class DMCAPage extends Page {
render() { render() {
return ( return (
<div className='w100p column'> <div className='w100p column'>
<AppBar <AppBar
title="DMCA / Copyright" title="DMCA / Copyright"
iconElementLeft={<IconButton onClick={()=>{ window.router('/') }}><NavigationClose /></IconButton>} iconElementLeft={<IconButton onClick={()=>{ window.router('/') }}><NavigationClose /></IconButton>}
/> />
<div className='column w100p pad1 center'> <div className='column w100p pad1 center'>
<div>RatsOnTheBoat.org is in compliance with 17 U.S.C. § 512, the Digital Millennium Copyright Act ("DMCA") and the Directive 2001/29/EC of the European Parliament.</div> <div>RatsOnTheBoat.org is in compliance with 17 U.S.C. § 512, the Digital Millennium Copyright Act ("DMCA") and the Directive 2001/29/EC of the European Parliament.</div>
<div className='fs1-5 pad0-75'>Content status</div> <div className='fs1-5 pad0-75'>Content status</div>
Our main goal is collect and make analysis of information from the torrent network. We don't save/distribute any real content/data and also don't save any torrents files - we are respect DMCA law. Our main goal is collect and make analysis of information from the torrent network. We don't save/distribute any real content/data and also don't save any torrents files - we are respect DMCA law.
Information collected automaticly and the real source are torrent clients based on torrent protocol. Information collected automaticly and the real source are torrent clients based on torrent protocol.
<div className='fs1-5 pad0-75'>Block mechanisms (for rightholders)</div> <div className='fs1-5 pad0-75'>Block mechanisms (for rightholders)</div>
Right holders can block/remove content that they responsible for. Contact administration or left application about content removal. Right holders can block/remove content that they responsible for. Contact administration or left application about content removal.
</div> </div>
</div> </div>
); );
} }
} }

View File

@ -10,48 +10,48 @@ export default class TopPage extends Page {
downloads = [] downloads = []
constructor(props) { constructor(props) {
super(props) super(props)
this.setTitle('Current Downloads'); this.setTitle('Current Downloads');
} }
getDownloads() getDownloads()
{ {
window.torrentSocket.emit('downloads', window.customLoader((downloads) => { window.torrentSocket.emit('downloads', window.customLoader((downloads) => {
this.downloads = downloads this.downloads = downloads
this.forceUpdate() this.forceUpdate()
})) }))
} }
componentDidMount() componentDidMount()
{ {
super.componentDidMount(); super.componentDidMount();
this.getDownloads() this.getDownloads()
this.downloading = () => this.getDownloads() this.downloading = () => this.getDownloads()
window.torrentSocket.on('downloading', this.downloading); window.torrentSocket.on('downloading', this.downloading);
this.downloadDone = () => this.getDownloads() this.downloadDone = () => this.getDownloads()
window.torrentSocket.on('downloadDone', this.downloadDone); window.torrentSocket.on('downloadDone', this.downloadDone);
} }
componentWillUnmount() componentWillUnmount()
{ {
if(this.downloading) if(this.downloading)
window.torrentSocket.off('downloading', this.downloading); window.torrentSocket.off('downloading', this.downloading);
if(this.downloadDone) if(this.downloadDone)
window.torrentSocket.off('downloadDone', this.downloadDone); window.torrentSocket.off('downloadDone', this.downloadDone);
} }
render() { render() {
return ( return (
<div> <div>
<div className='column center w100p pad0-75'> <div className='column center w100p pad0-75'>
<RaisedButton label={__('Back to main page')} primary={true} onClick={() => { <RaisedButton label={__('Back to main page')} primary={true} onClick={() => {
window.router('/') window.router('/')
}} /> }} />
<List style={{paddingBottom: '70px'}} className='animated recent-torrents'> <List style={{paddingBottom: '70px'}} className='animated recent-torrents'>
{ {
this.downloads.map((download, index) => { this.downloads.map((download, index) => {
return <TorrentLine key={index} torrent={download.torrentObject} download={download} /> return <TorrentLine key={index} torrent={download.torrentObject} download={download} />
}) })
} }
</List> </List>
</div> </div>
</div> </div>
); );
} }
} }

View File

@ -8,89 +8,89 @@ import MenuItem from 'material-ui/MenuItem';
import Feed from './feed'; import Feed from './feed';
export default class FeedPage extends Page { export default class FeedPage extends Page {
constructor(props) { constructor(props) {
super(props) super(props)
this.setTitle('Rats On The Boat - Content Search Engine'); this.setTitle('Rats On The Boat - Content Search Engine');
} }
componentDidMount() componentDidMount()
{ {
Search.instance().onSearchUpdate = () => this.forceUpdate() Search.instance().onSearchUpdate = () => this.forceUpdate()
} }
componentWillUnmount() componentWillUnmount()
{ {
Search.instance().onSearchUpdate = () => {} Search.instance().onSearchUpdate = () => {}
} }
render() { render() {
const orderText = (text, field) => { const orderText = (text, field) => {
if(field !== Search.instance().state.orderBy) if(field !== Search.instance().state.orderBy)
return text; return text;
if(Search.instance().state.orderDesc) if(Search.instance().state.orderDesc)
return text + ' ⇩' return text + ' ⇩'
else else
return text + ' ⇧' return text + ' ⇧'
} }
return ( return (
<div id='index-window' className='column center'> <div id='index-window' className='column center'>
<div className='torrents-container'> <div className='torrents-container'>
<SearchResults <SearchResults
torrentsSearchResults={Search.instance().searchTorrents} torrentsSearchResults={Search.instance().searchTorrents}
filesSearchResults={Search.instance().searchFiles} filesSearchResults={Search.instance().searchFiles}
currentSearching={Search.instance().state.searchingIndicator} currentSearching={Search.instance().state.searchingIndicator}
searchText={Search.instance().currentSearch} searchText={Search.instance().currentSearch}
resultSelector={ resultSelector={
<SelectField <SelectField
floatingLabelText={__("Sort by")} floatingLabelText={__("Sort by")}
floatingLabelFixed={true} floatingLabelFixed={true}
value={Search.instance().state.orderBy} value={Search.instance().state.orderBy}
onChange={(event, index, value) => { onChange={(event, index, value) => {
event.preventDefault(); // fix overclick on torrent event.preventDefault(); // fix overclick on torrent
if(value === 'none') { if(value === 'none') {
Search.instance().setState({orderBy: null}, () => { Search.instance().setState({orderBy: null}, () => {
Search.instance().search(true) Search.instance().search(true)
}) })
return; return;
} }
if(value === Search.instance().state.orderBy) if(value === Search.instance().state.orderBy)
{ {
Search.instance().setState({orderDesc: !Search.instance().state.orderDesc}, () => { Search.instance().setState({orderDesc: !Search.instance().state.orderDesc}, () => {
Search.instance().search(true) Search.instance().search(true)
}) })
return; return;
} }
Search.instance().setState({ Search.instance().setState({
orderBy: value, orderBy: value,
orderDesc: (value === 'seeders' || value === 'completed' || value === 'added') ? true : Search.instance().state.orderDesc orderDesc: (value === 'seeders' || value === 'completed' || value === 'added') ? true : Search.instance().state.orderDesc
}, () => { }, () => {
Search.instance().search(true) Search.instance().search(true)
}) })
}} }}
> >
<MenuItem value='none' primaryText={__('None')} /> <MenuItem value='none' primaryText={__('None')} />
<MenuItem value='seeders' primaryText={orderText(__('Seeders'), 'seeders')} /> <MenuItem value='seeders' primaryText={orderText(__('Seeders'), 'seeders')} />
<MenuItem value='name' primaryText={orderText(__('Name'), 'name')} /> <MenuItem value='name' primaryText={orderText(__('Name'), 'name')} />
<MenuItem value='files' primaryText={orderText(__('Files'), 'files')} /> <MenuItem value='files' primaryText={orderText(__('Files'), 'files')} />
<MenuItem value='size' primaryText={orderText(__('Size'), 'size')} /> <MenuItem value='size' primaryText={orderText(__('Size'), 'size')} />
<MenuItem value='added' primaryText={orderText(__('Added date'), 'added')} /> <MenuItem value='added' primaryText={orderText(__('Added date'), 'added')} />
<MenuItem value='completed' primaryText={orderText(__('Completed'), 'completed')} /> <MenuItem value='completed' primaryText={orderText(__('Completed'), 'completed')} />
</SelectField> </SelectField>
} }
moreTorrentsEnabled={Search.instance().moreSearchTorrents && !Search.instance().state.searchingIndicator} moreTorrentsEnabled={Search.instance().moreSearchTorrents && !Search.instance().state.searchingIndicator}
moreFilesEnabled={Search.instance().moreSearchFiles && !Search.instance().state.searchingIndicator} moreFilesEnabled={Search.instance().moreSearchFiles && !Search.instance().state.searchingIndicator}
onMoreTorrents={() => Search.instance().moreTorrents()} onMoreTorrents={() => Search.instance().moreTorrents()}
onMoreFiles={() => Search.instance().moreFiles()} onMoreFiles={() => Search.instance().moreFiles()}
moreTorrentsIndicator={Search.instance().state.moreTorrentsIndicator} moreTorrentsIndicator={Search.instance().state.moreTorrentsIndicator}
moreFilesIndicator={Search.instance().state.moreFilesIndicator} moreFilesIndicator={Search.instance().state.moreFilesIndicator}
/> />
</div> </div>
<div className='column center w100p pad0-75'> <div className='column center w100p pad0-75'>
<Feed /> <Feed />
</div> </div>
</div> </div>
); );
} }
} }

View File

@ -5,32 +5,32 @@ import Divider from 'material-ui/Divider';
import Subheader from 'material-ui/Subheader'; import Subheader from 'material-ui/Subheader';
export default class RecentTorrents extends Component { export default class RecentTorrents extends Component {
constructor() { constructor() {
super() super()
this.torrents = []; this.torrents = [];
} }
componentDidMount() { componentDidMount() {
window.torrentSocket.emit('feed', window.customLoader((data) => { window.torrentSocket.emit('feed', window.customLoader((data) => {
if(data) { if(data) {
this.torrents = data; this.torrents = data;
console.log(data) console.log(data)
this.forceUpdate(); this.forceUpdate();
} }
})) }))
} }
render() { render() {
return ( return (
<List className='animated torrents-container'> <List className='animated torrents-container'>
<Subheader className='recent-title' inset={true}> <Subheader className='recent-title' inset={true}>
{__('Feed')} {__('Feed')}
</Subheader> </Subheader>
<Divider /> <Divider />
{ {
this.torrents.map((torrent, index) =>{ this.torrents.map((torrent, index) =>{
return <TorrentLine key={index} torrent={torrent} />; return <TorrentLine key={index} torrent={torrent} />;
}) })
} }
</List> </List>
); );
} }
} }

View File

@ -11,182 +11,182 @@ import MenuItem from 'material-ui/MenuItem';
import fs from 'fs' import fs from 'fs'
export default class ConfigPage extends Page { export default class ConfigPage extends Page {
constructor(props) { constructor(props) {
super(props) super(props)
this.setTitle('Rats filters'); this.setTitle('Rats filters');
this.options = {} this.options = {}
} }
componentDidMount() { componentDidMount() {
this.loadSettings() this.loadSettings()
} }
loadSettings() { loadSettings() {
window.torrentSocket.emit('config', window.customLoader((options) => { window.torrentSocket.emit('config', window.customLoader((options) => {
this.options = options; this.options = options;
console.log(this.options) console.log(this.options)
this.forceUpdate(); this.forceUpdate();
})); }));
} }
saveSettings() { saveSettings() {
window.torrentSocket.emit('setConfig', this.options) window.torrentSocket.emit('setConfig', this.options)
this.settingsSavedMessage = true this.settingsSavedMessage = true
this.forceUpdate() this.forceUpdate()
setTimeout(() => { setTimeout(() => {
this.settingsSavedMessage = false this.settingsSavedMessage = false
this.forceUpdate() this.forceUpdate()
}, 1000) }, 1000)
} }
render() { render() {
return ( return (
<div> <div>
<div className='row center pad0-75'> <div className='row center pad0-75'>
<RaisedButton label={__("Back to main page")} primary={true} onClick={() => { <RaisedButton label={__("Back to main page")} primary={true} onClick={() => {
window.router('/') window.router('/')
}} /> }} />
</div> </div>
<div className='column center w100p pad0-75'> <div className='column center w100p pad0-75'>
<div className='column w100p'> <div className='column w100p'>
<div className='row inline w100p'> <div className='row inline w100p'>
<div style={{flex: 1}}>{__('Max files per torrent')} ({__('current')}: {this.options.filters && this.options.filters.maxFiles})</div> <div style={{flex: 1}}>{__('Max files per torrent')} ({__('current')}: {this.options.filters && this.options.filters.maxFiles})</div>
<Slider <Slider
min={0} min={0}
max={50000} max={50000}
step={1} step={1}
style={{width: 300}} style={{width: 300}}
value={this.options.spider && this.options.filters.maxFiles} value={this.options.spider && this.options.filters.maxFiles}
onChange={(event, value) => { onChange={(event, value) => {
this.options.filters.maxFiles = value this.options.filters.maxFiles = value
this.forceUpdate() this.forceUpdate()
}} }}
/> />
<TextField <TextField
hintText={__('Max files')} hintText={__('Max files')}
className='pad0-75' className='pad0-75'
style={{width: 200}} style={{width: 200}}
value={this.options.filters && this.options.filters.maxFiles} value={this.options.filters && this.options.filters.maxFiles}
onChange={(e, value) => { onChange={(e, value) => {
if(!this.options.filters) if(!this.options.filters)
return return
this.options.filters.maxFiles = parseInt(value) this.options.filters.maxFiles = parseInt(value)
this.forceUpdate() this.forceUpdate()
}} }}
/> />
</div> </div>
<div className='fs0-75' style={{color: 'grey'}}>* 0 - {__('Disabled')}. <div className='fs0-75' style={{color: 'grey'}}>* 0 - {__('Disabled')}.
</div> </div>
</div> </div>
<div className='column w100p'> <div className='column w100p'>
<div className='row inline w100p'> <div className='row inline w100p'>
<div style={{flex: 1}}>{__('Torrent name regular extension filtering')}</div> <div style={{flex: 1}}>{__('Torrent name regular extension filtering')}</div>
<TextField <TextField
hintText="regex" hintText="regex"
className='pad0-75' className='pad0-75'
style={{width: 400}} style={{width: 400}}
value={this.options.filters && this.options.filters.namingRegExp} value={this.options.filters && this.options.filters.namingRegExp}
onChange={(e, value) => { onChange={(e, value) => {
if(!this.options.filters) if(!this.options.filters)
return return
this.options.filters.namingRegExp = value this.options.filters.namingRegExp = value
this.forceUpdate() this.forceUpdate()
}} }}
/> />
<SelectField <SelectField
style={{marginLeft: 15}} style={{marginLeft: 15}}
floatingLabelText={__('Examples')} floatingLabelText={__('Examples')}
value={this.options.filters && this.options.filters.namingRegExp} value={this.options.filters && this.options.filters.namingRegExp}
onChange={(event, index, value) => { onChange={(event, index, value) => {
if(!this.options.filters) if(!this.options.filters)
return return
this.options.filters.namingRegExp = value this.options.filters.namingRegExp = value
this.forceUpdate() this.forceUpdate()
}} }}
> >
<MenuItem value={String.raw`^[А-Яа-я0-9A-Za-z.!@?#"$%&:;() *\+,\/;\-=[\\\]\^_{|}<>\u0400-\u04FF]+$`} primaryText={__('Russian + English only (With symbols)')} /> <MenuItem value={String.raw`^[А-Яа-я0-9A-Za-z.!@?#"$%&:;() *\+,\/;\-=[\\\]\^_{|}<>\u0400-\u04FF]+$`} primaryText={__('Russian + English only (With symbols)')} />
<MenuItem value={'^[0-9A-Za-z.!@?#"$%&:;() *\+,\/;\-=[\\\]\^_{|}<>]+$'} primaryText={__('English only (With symbols)')} /> <MenuItem value={'^[0-9A-Za-z.!@?#"$%&:;() *\+,\/;\-=[\\\]\^_{|}<>]+$'} primaryText={__('English only (With symbols)')} />
<MenuItem value={'^((?!badword).)*$'} primaryText={__('Ignore badword')} /> <MenuItem value={'^((?!badword).)*$'} primaryText={__('Ignore badword')} />
</SelectField> </SelectField>
</div> </div>
<Toggle <Toggle
style={{marginTop: '10px'}} style={{marginTop: '10px'}}
label={__('Negative regular extension filtering')} label={__('Negative regular extension filtering')}
toggled={this.options.filters && this.options.filters.namingRegExpNegative} toggled={this.options.filters && this.options.filters.namingRegExpNegative}
onToggle={(e, checked) => { onToggle={(e, checked) => {
if(!this.options.filters) if(!this.options.filters)
return return
this.options.filters.namingRegExpNegative = checked this.options.filters.namingRegExpNegative = checked
this.forceUpdate() this.forceUpdate()
}} }}
/> />
<div className='fs0-75' style={{color: 'grey'}}> <div className='fs0-75' style={{color: 'grey'}}>
* - {__('clean string means disabled')} * - {__('clean string means disabled')}
</div> </div>
</div> </div>
<Toggle <Toggle
style={{marginTop: '10px'}} style={{marginTop: '10px'}}
label={__('Adult filter')} label={__('Adult filter')}
toggled={this.options.filters && this.options.filters.adultFilter} toggled={this.options.filters && this.options.filters.adultFilter}
onToggle={(e, checked) => { onToggle={(e, checked) => {
if(!this.options.filters) if(!this.options.filters)
return return
this.options.filters.adultFilter = checked this.options.filters.adultFilter = checked
this.forceUpdate() this.forceUpdate()
}} }}
/> />
{ {
this.toRemoveProbably && this.toRemoveProbably > 0 this.toRemoveProbably && this.toRemoveProbably > 0
? ?
<div style={{color: 'orange'}}>{__('Torrents to clean')}: {this.toRemoveProbably}</div> <div style={{color: 'orange'}}>{__('Torrents to clean')}: {this.toRemoveProbably}</div>
: :
null null
} }
{ {
this.toRemove && this.toRemove > 0 this.toRemove && this.toRemove > 0
? ?
<div style={{color: 'red'}}>{__('Torrents cleaned')}: {this.toRemove}</div> <div style={{color: 'red'}}>{__('Torrents cleaned')}: {this.toRemove}</div>
: :
null null
} }
{ {
this.settingsSavedMessage this.settingsSavedMessage
&& &&
<div style={{color: 'green'}}>{__('Settings saved')}</div> <div style={{color: 'green'}}>{__('Settings saved')}</div>
} }
<div className='row center pad0-75'> <div className='row center pad0-75'>
<RaisedButton label={__('Check torrents')} primary={true} onClick={() => { <RaisedButton label={__('Check torrents')} primary={true} onClick={() => {
window.torrentSocket.emit('removeTorrents', true, window.customLoader((toRemove) => { window.torrentSocket.emit('removeTorrents', true, window.customLoader((toRemove) => {
this.toRemoveProbably = toRemove this.toRemoveProbably = toRemove
this.forceUpdate() this.forceUpdate()
})); }));
}} /> }} />
<RaisedButton label={__('Clean torrents')} secondary={true} onClick={() => { <RaisedButton label={__('Clean torrents')} secondary={true} onClick={() => {
window.torrentSocket.emit('removeTorrents', false, window.customLoader((toRemove) => { window.torrentSocket.emit('removeTorrents', false, window.customLoader((toRemove) => {
this.toRemove = toRemove this.toRemove = toRemove
this.forceUpdate() this.forceUpdate()
})); }));
}} /> }} />
</div> </div>
<div className='row center pad0-75'> <div className='row center pad0-75'>
<RaisedButton label={__('Save Settings')} primary={true} onClick={() => { <RaisedButton label={__('Save Settings')} primary={true} onClick={() => {
this.saveSettings() this.saveSettings()
}} /> }} />
</div> </div>
</div> </div>
</div> </div>
); );
} }
} }

View File

@ -3,18 +3,18 @@ import React from 'react';
export default (props) => { export default (props) => {
return ( return (
<div className='column center' style={{color: 'grey', marginTop: '12px'}}> <div className='column center' style={{color: 'grey', marginTop: '12px'}}>
<svg style={{height: '100px', fill: 'grey'}} viewBox="0 0 264.725 264.725"> <svg style={{height: '100px', fill: 'grey'}} viewBox="0 0 264.725 264.725">
<path d="M220.195,71.427c-0.589-7.654-9.135-15.619-17.979-16.209c-8.844-0.584-17.398,0.301-12.087,6.483 <path d="M220.195,71.427c-0.589-7.654-9.135-15.619-17.979-16.209c-8.844-0.584-17.398,0.301-12.087,6.483
c5.308,6.188,7.074,12.091,4.423,11.212c-2.66-0.896-13.267-7.08-45.104-2.066c-4.126,1.17-21.221-12.682-44.513-12.977 c5.308,6.188,7.074,12.091,4.423,11.212c-2.66-0.896-13.267-7.08-45.104-2.066c-4.126,1.17-21.221-12.682-44.513-12.977
c-23.283-0.295-40.381,6.346-64.85,72.296c-2.356,5.828-18.866,19.386-27.71,25.865C3.536,162.529,0.007,169.787,0,182.763 c-23.283-0.295-40.381,6.346-64.85,72.296c-2.356,5.828-18.866,19.386-27.71,25.865C3.536,162.529,0.007,169.787,0,182.763
c-0.018,18.158,25.934,27.187,81.648,26.889c55.715-0.292,85.195-9.388,85.195-9.388c-62.789,6.773-158.907,10.52-158.907-18.687 c-0.018,18.158,25.934,27.187,81.648,26.889c55.715-0.292,85.195-9.388,85.195-9.388c-62.789,6.773-158.907,10.52-158.907-18.687
c0-20.641,28.321-28.47,36.281-28.184c7.958,0.3,13.562,12.673,33.307,5.603c3.247-0.295,1.48,4.423-1.18,7.369 c0-20.641,28.321-28.47,36.281-28.184c7.958,0.3,13.562,12.673,33.307,5.603c3.247-0.295,1.48,4.423-1.18,7.369
c-2.651,2.942-0.586,6.487,9.73,6.487c10.315,0,41.183,0.295,47.707,0c6.531-0.299,11.839-11.792-9.384-12.68 c-2.651,2.942-0.586,6.487,9.73,6.487c10.315,0,41.183,0.295,47.707,0c6.531-0.299,11.839-11.792-9.384-12.68
c-18.548,0.311,12.023-5.773,15.915-21.813c0.709-3.927,8.84-4.139,15.918-4.119c20.777,0.029,34.485,38.193,38.912,38.338 c-18.548,0.311,12.023-5.773,15.915-21.813c0.709-3.927,8.84-4.139,15.918-4.119c20.777,0.029,34.485,38.193,38.912,38.338
c4.416,0.15,17.979,1.621,17.683-4.273c-0.292-5.897-11.491-3.241-13.854-6.487c-2.359-3.234-10.023-15.504-7.366-21.104 c4.416,0.15,17.979,1.621,17.683-4.273c-0.292-5.897-11.491-3.241-13.854-6.487c-2.359-3.234-10.023-15.504-7.366-21.104
c2.65-5.59,12.674-21.229,24.463-22.988c11.789-1.777,42.451,7.361,47.459,0c5.012-7.372-6.783-11.512-15.918-28.611 c2.65-5.59,12.674-21.229,24.463-22.988c11.789-1.777,42.451,7.361,47.459,0c5.012-7.372-6.783-11.512-15.918-28.611
C243.779,80.572,238.768,71.728,220.195,71.427z"/> C243.779,80.572,238.768,71.728,220.195,71.427z"/>
</svg> </svg>
</div> </div>
) )
} }

View File

@ -1,8 +1,8 @@
export default function formatBytes(bytes,decimals) { export default function formatBytes(bytes,decimals) {
if(bytes == 0) return '0 Byte'; if(bytes == 0) return '0 Byte';
var k = 1000; // or 1024 for binary var k = 1000; // or 1024 for binary
var dm = decimals + 1 || 3; var dm = decimals + 1 || 3;
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
var i = Math.floor(Math.log(bytes) / Math.log(k)); var i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
} }

View File

@ -5,64 +5,64 @@ import RaisedButton from 'material-ui/RaisedButton';
import Search from './search' import Search from './search'
class Header extends React.Component { class Header extends React.Component {
constructor(props) constructor(props)
{ {
super(props) super(props)
this.header = React.createRef(); this.header = React.createRef();
} }
componentDidMount() componentDidMount()
{ {
window.onscroll = () => { window.onscroll = () => {
if (window.pageYOffset >= 15) if (window.pageYOffset >= 15)
{ {
const scrollHeight = Math.max( const scrollHeight = Math.max(
document.body.scrollHeight, document.documentElement.scrollHeight, document.body.scrollHeight, document.documentElement.scrollHeight,
document.body.offsetHeight, document.documentElement.offsetHeight, document.body.offsetHeight, document.documentElement.offsetHeight,
document.body.clientHeight, document.documentElement.clientHeight document.body.clientHeight, document.documentElement.clientHeight
); );
if(scrollHeight - 240 < document.documentElement.clientHeight) if(scrollHeight - 240 < document.documentElement.clientHeight)
{ {
return return
} }
if(!this.stickyHeader) if(!this.stickyHeader)
{ {
this.stickyHeader = true this.stickyHeader = true
this.header.current.classList.add("sticky"); this.header.current.classList.add("sticky");
} }
} }
else else
{ {
if(this.stickyHeader) if(this.stickyHeader)
{ {
this.stickyHeader = false this.stickyHeader = false
this.header.current.classList.remove("sticky"); this.header.current.classList.remove("sticky");
} }
} }
}; };
} }
componentWillUnmount() componentWillUnmount()
{ {
window.onscroll = null window.onscroll = null
} }
render() render()
{ {
return ( return (
<div ref={this.header} className='header'> <div ref={this.header} className='header'>
<Card className='w100p header-main' style={{position: 'fixed', zIndex: 2}}> <Card className='w100p header-main' style={{position: 'fixed', zIndex: 2}}>
<CardMedia <CardMedia
overlay={<CardTitle className='header-title' title={__('Yarrr, Landlubbers!')} style={{paddingTop: 2}} subtitle={ overlay={<CardTitle className='header-title' title={__('Yarrr, Landlubbers!')} style={{paddingTop: 2}} subtitle={
<div> <div>
<div className='row' style={{position: 'absolute', top: -65}}> <div className='row' style={{position: 'absolute', top: -65}}>
<svg className='clickable' <svg className='clickable'
onClick={() => { onClick={() => {
window.router('/config') window.router('/config')
}} }}
fill='white' style={{height: 45, margin: 4}} viewBox="0 0 932.179 932.179"> fill='white' style={{height: 45, margin: 4}} viewBox="0 0 932.179 932.179">
<g> <g>
<path d="M61.2,341.538c4.9,16.8,11.7,33,20.3,48.2l-24.5,30.9c-8,10.1-7.1,24.5,1.9,33.6l42.2,42.2c9.1,9.1,23.5,9.899,33.6,1.899 <path d="M61.2,341.538c4.9,16.8,11.7,33,20.3,48.2l-24.5,30.9c-8,10.1-7.1,24.5,1.9,33.6l42.2,42.2c9.1,9.1,23.5,9.899,33.6,1.899
l30.7-24.3c15.8,9.101,32.6,16.2,50.1,21.2l4.6,39.5c1.5,12.8,12.3,22.4,25.1,22.4h59.7c12.8,0,23.6-9.601,25.1-22.4l4.4-38.1 l30.7-24.3c15.8,9.101,32.6,16.2,50.1,21.2l4.6,39.5c1.5,12.8,12.3,22.4,25.1,22.4h59.7c12.8,0,23.6-9.601,25.1-22.4l4.4-38.1
c18.8-4.9,36.8-12.2,53.7-21.7l29.7,23.5c10.1,8,24.5,7.1,33.6-1.9l42.2-42.2c9.1-9.1,9.9-23.5,1.9-33.6l-23.1-29.3 c18.8-4.9,36.8-12.2,53.7-21.7l29.7,23.5c10.1,8,24.5,7.1,33.6-1.9l42.2-42.2c9.1-9.1,9.9-23.5,1.9-33.6l-23.1-29.3
c9.6-16.601,17.1-34.3,22.1-52.8l35.6-4.1c12.801-1.5,22.4-12.3,22.4-25.1v-59.7c0-12.8-9.6-23.6-22.4-25.1l-35.1-4.1 c9.6-16.601,17.1-34.3,22.1-52.8l35.6-4.1c12.801-1.5,22.4-12.3,22.4-25.1v-59.7c0-12.8-9.6-23.6-22.4-25.1l-35.1-4.1
@ -71,7 +71,7 @@ class Header extends React.Component {
c-19.8,5.3-38.7,13.3-56.3,23.8l-27.5-21.8c-10.1-8-24.5-7.1-33.6,1.9l-42.2,42.2c-9.1,9.1-9.9,23.5-1.9,33.6l23,29.1 c-19.8,5.3-38.7,13.3-56.3,23.8l-27.5-21.8c-10.1-8-24.5-7.1-33.6,1.9l-42.2,42.2c-9.1,9.1-9.9,23.5-1.9,33.6l23,29.1
c-9.2,16.6-16.2,34.3-20.8,52.7l-36.8,4.2c-12.8,1.5-22.4,12.3-22.4,25.1v59.7c0,12.8,9.6,23.6,22.4,25.1L61.2,341.538z c-9.2,16.6-16.2,34.3-20.8,52.7l-36.8,4.2c-12.8,1.5-22.4,12.3-22.4,25.1v59.7c0,12.8,9.6,23.6,22.4,25.1L61.2,341.538z
M277.5,180.038c54.4,0,98.7,44.3,98.7,98.7s-44.3,98.7-98.7,98.7c-54.399,0-98.7-44.3-98.7-98.7S223.1,180.038,277.5,180.038z"/> M277.5,180.038c54.4,0,98.7,44.3,98.7,98.7s-44.3,98.7-98.7,98.7c-54.399,0-98.7-44.3-98.7-98.7S223.1,180.038,277.5,180.038z"/>
<path d="M867.699,356.238l-31.5-26.6c-9.699-8.2-24-7.8-33.199,0.9l-17.4,16.3c-14.699-7.1-30.299-12.1-46.4-15l-4.898-24 <path d="M867.699,356.238l-31.5-26.6c-9.699-8.2-24-7.8-33.199,0.9l-17.4,16.3c-14.699-7.1-30.299-12.1-46.4-15l-4.898-24
c-2.5-12.4-14-21-26.602-20l-41.1,3.5c-12.6,1.1-22.5,11.4-22.9,24.1l-0.799,24.4c-15.801,5.7-30.701,13.5-44.301,23.3 c-2.5-12.4-14-21-26.602-20l-41.1,3.5c-12.6,1.1-22.5,11.4-22.9,24.1l-0.799,24.4c-15.801,5.7-30.701,13.5-44.301,23.3
l-20.799-13.8c-10.602-7-24.701-5-32.9,4.7l-26.6,31.7c-8.201,9.7-7.801,24,0.898,33.2l18.201,19.399 l-20.799-13.8c-10.602-7-24.701-5-32.9,4.7l-26.6,31.7c-8.201,9.7-7.801,24,0.898,33.2l18.201,19.399
c-6.301,14.2-10.801,29.101-13.4,44.4l-26,5.3c-12.4,2.5-21,14-20,26.601l3.5,41.1c1.1,12.6,11.4,22.5,24.1,22.9l28.1,0.899 c-6.301,14.2-10.801,29.101-13.4,44.4l-26,5.3c-12.4,2.5-21,14-20,26.601l3.5,41.1c1.1,12.6,11.4,22.5,24.1,22.9l28.1,0.899
@ -82,7 +82,7 @@ class Header extends React.Component {
c-5.201-14.6-12.201-28.399-20.9-41.2l13.699-20.6C879.4,378.638,877.4,364.438,867.699,356.238z M712.801,593.837 c-5.201-14.6-12.201-28.399-20.9-41.2l13.699-20.6C879.4,378.638,877.4,364.438,867.699,356.238z M712.801,593.837
c-44.4,3.801-83.602-29.3-87.301-73.699c-3.801-44.4,29.301-83.601,73.699-87.301c44.4-3.8,83.602,29.301,87.301,73.7 c-44.4,3.801-83.602-29.3-87.301-73.699c-3.801-44.4,29.301-83.601,73.699-87.301c44.4-3.8,83.602,29.301,87.301,73.7
C790.301,550.938,757.199,590.138,712.801,593.837z"/> C790.301,550.938,757.199,590.138,712.801,593.837z"/>
<path d="M205,704.438c-12.6,1.3-22.3,11.899-22.4,24.6l-0.3,25.3c-0.2,12.7,9.2,23.5,21.8,25.101l18.6,2.399 <path d="M205,704.438c-12.6,1.3-22.3,11.899-22.4,24.6l-0.3,25.3c-0.2,12.7,9.2,23.5,21.8,25.101l18.6,2.399
c3.1,11.301,7.5,22.101,13.2,32.301l-12,14.8c-8,9.899-7.4,24.1,1.5,33.2l17.7,18.1c8.9,9.1,23.1,10.1,33.2,2.3l14.899-11.5 c3.1,11.301,7.5,22.101,13.2,32.301l-12,14.8c-8,9.899-7.4,24.1,1.5,33.2l17.7,18.1c8.9,9.1,23.1,10.1,33.2,2.3l14.899-11.5
c10.5,6.2,21.601,11.101,33.2,14.5l2,19.2c1.3,12.6,11.9,22.3,24.6,22.4l25.301,0.3c12.699,0.2,23.5-9.2,25.1-21.8l2.3-18.2 c10.5,6.2,21.601,11.101,33.2,14.5l2,19.2c1.3,12.6,11.9,22.3,24.6,22.4l25.301,0.3c12.699,0.2,23.5-9.2,25.1-21.8l2.3-18.2
c12.601-3.101,24.601-7.8,36-14l14,11.3c9.9,8,24.101,7.4,33.201-1.5l18.1-17.7c9.1-8.899,10.1-23.1,2.301-33.2L496.6,818.438 c12.601-3.101,24.601-7.8,36-14l14,11.3c9.9,8,24.101,7.4,33.201-1.5l18.1-17.7c9.1-8.899,10.1-23.1,2.301-33.2L496.6,818.438
@ -93,47 +93,47 @@ class Header extends React.Component {
l-18.2,17.801c-9.1,8.899-10.1,23.1-2.3,33.199l10.7,13.801c-6.2,11-11.1,22.699-14.3,35L205,704.438z M368.3,675.837 l-18.2,17.801c-9.1,8.899-10.1,23.1-2.3,33.199l10.7,13.801c-6.2,11-11.1,22.699-14.3,35L205,704.438z M368.3,675.837
c36.3,0.4,65.399,30.301,65,66.601c-0.4,36.3-30.301,65.399-66.601,65c-36.3-0.4-65.399-30.3-65-66.601 c36.3,0.4,65.399,30.301,65,66.601c-0.4,36.3-30.301,65.399-66.601,65c-36.3-0.4-65.399-30.3-65-66.601
C302.1,704.538,332,675.438,368.3,675.837z"/> C302.1,704.538,332,675.438,368.3,675.837z"/>
</g> </g>
</svg> </svg>
<svg className='clickable' <svg className='clickable'
onClick={() => { onClick={() => {
window.router('/filters') window.router('/filters')
}} }}
fill='white' style={{height: 45, margin: 4}} viewBox="0 0 512 512"> fill='white' style={{height: 45, margin: 4}} viewBox="0 0 512 512">
<g> <g>
<g> <g>
<path d="M256,103.536c-58.559,0-106.2,47.641-106.2,106.2c0,4.512,3.657,8.169,8.169,8.169s8.169-3.658,8.169-8.169 <path d="M256,103.536c-58.559,0-106.2,47.641-106.2,106.2c0,4.512,3.657,8.169,8.169,8.169s8.169-3.658,8.169-8.169
c0-49.55,40.313-89.862,89.862-89.862c49.549,0,89.862,40.311,89.862,89.862c0,4.512,3.658,8.169,8.169,8.169 c0-49.55,40.313-89.862,89.862-89.862c49.549,0,89.862,40.311,89.862,89.862c0,4.512,3.658,8.169,8.169,8.169
c4.513,0,8.169-3.658,8.169-8.169C362.2,151.177,314.559,103.536,256,103.536z"/> c4.513,0,8.169-3.658,8.169-8.169C362.2,151.177,314.559,103.536,256,103.536z"/>
</g> </g>
</g> </g>
<g> <g>
<g> <g>
<path d="M256,136.213c-40.541,0-73.523,32.982-73.523,73.523c0,4.512,3.657,8.169,8.169,8.169s8.169-3.658,8.169-8.169 <path d="M256,136.213c-40.541,0-73.523,32.982-73.523,73.523c0,4.512,3.657,8.169,8.169,8.169s8.169-3.658,8.169-8.169
c0-31.532,25.654-57.185,57.185-57.185s57.185,25.653,57.185,57.185c0,4.512,3.657,8.169,8.169,8.169 c0-31.532,25.654-57.185,57.185-57.185s57.185,25.653,57.185,57.185c0,4.512,3.657,8.169,8.169,8.169
c4.513,0,8.169-3.658,8.169-8.169C329.523,169.195,296.541,136.213,256,136.213z"/> c4.513,0,8.169-3.658,8.169-8.169C329.523,169.195,296.541,136.213,256,136.213z"/>
</g> </g>
</g> </g>
<g> <g>
<g> <g>
<path d="M503.801,234.245H8.199c-4.513,0-8.169,3.658-8.169,8.169v43.569c0,2.721,1.354,5.263,3.612,6.781L167.331,402.76 <path d="M503.801,234.245H8.199c-4.513,0-8.169,3.658-8.169,8.169v43.569c0,2.721,1.354,5.263,3.612,6.781L167.331,402.76
c5.092,3.857,9.699,13.038,9.699,19.379v81.693c0,4.512,3.656,8.169,8.169,8.169h141.6c4.513,0,8.169-3.658,8.169-8.169v-81.693 c5.092,3.857,9.699,13.038,9.699,19.379v81.693c0,4.512,3.656,8.169,8.169,8.169h141.6c4.513,0,8.169-3.658,8.169-8.169v-81.693
c0-6.34,4.606-15.522,9.699-19.379l163.691-109.995c2.258-1.517,3.612-4.06,3.612-6.781v-43.569 c0-6.34,4.606-15.522,9.699-19.379l163.691-109.995c2.258-1.517,3.612-4.06,3.612-6.781v-43.569
C511.97,237.903,508.313,234.245,503.801,234.245z M495.632,277.815H157.969c-4.513,0-8.169,3.658-8.169,8.169 C511.97,237.903,508.313,234.245,503.801,234.245z M495.632,277.815H157.969c-4.513,0-8.169,3.658-8.169,8.169
s3.657,8.169,8.169,8.169h319.028l-141.61,95.159c-0.1,0.068-0.199,0.137-0.296,0.209c-9.383,6.93-16.458,20.951-16.458,32.616 s3.657,8.169,8.169,8.169h319.028l-141.61,95.159c-0.1,0.068-0.199,0.137-0.296,0.209c-9.383,6.93-16.458,20.951-16.458,32.616
v73.523H193.37v-73.523c0-11.665-7.076-25.686-16.458-32.616c-0.098-0.072-0.197-0.142-0.296-0.209L35.003,294.153h90.289 v73.523H193.37v-73.523c0-11.665-7.076-25.686-16.458-32.616c-0.098-0.072-0.197-0.142-0.296-0.209L35.003,294.153h90.289
c4.513,0,8.169-3.658,8.169-8.169s-3.657-8.169-8.169-8.169H16.368v-27.231h479.263V277.815z"/> c4.513,0,8.169-3.658,8.169-8.169s-3.657-8.169-8.169-8.169H16.368v-27.231h479.263V277.815z"/>
</g> </g>
</g> </g>
<g> <g>
<g> <g>
<path d="M296.504,397.63h-81.006c-4.513,0-8.169,3.658-8.169,8.169c0,4.512,3.657,8.169,8.169,8.169h81.006 <path d="M296.504,397.63h-81.006c-4.513,0-8.169,3.658-8.169,8.169c0,4.512,3.657,8.169,8.169,8.169h81.006
c4.513,0,8.169-3.658,8.169-8.169C304.674,401.288,301.016,397.63,296.504,397.63z"/> c4.513,0,8.169-3.658,8.169-8.169C304.674,401.288,301.016,397.63,296.504,397.63z"/>
</g> </g>
</g> </g>
<g> <g>
<g> <g>
<path d="M458.171,168.573l-36.481-3.382c-3.895-14.477-9.68-28.351-17.251-41.376l23.416-28.2 <path d="M458.171,168.573l-36.481-3.382c-3.895-14.477-9.68-28.351-17.251-41.376l23.416-28.2
c2.696-3.246,2.475-8.011-0.509-10.995l-46.545-46.545c-2.983-2.983-7.749-3.206-10.996-0.509L341.51,61.063 c2.696-3.246,2.475-8.011-0.509-10.995l-46.545-46.545c-2.983-2.983-7.749-3.206-10.996-0.509L341.51,61.063
c-13.017-7.52-26.88-13.258-41.343-17.113l-3.386-36.534C296.391,3.213,292.866,0,288.647,0h-65.826 c-13.017-7.52-26.88-13.258-41.343-17.113l-3.386-36.534C296.391,3.213,292.866,0,288.647,0h-65.826
c-4.22,0-7.745,3.213-8.134,7.416l-3.401,36.68c-14.413,3.894-28.222,9.657-41.187,17.193L141.73,37.731 c-4.22,0-7.745,3.213-8.134,7.416l-3.401,36.68c-14.413,3.894-28.222,9.657-41.187,17.193L141.73,37.731
@ -146,56 +146,56 @@ class Header extends React.Component {
c2.986,1.866,6.834,1.607,9.545-0.645l27.023-22.438l36.013,36.013l-22.371,26.941c-2.255,2.717-2.511,6.575-0.632,9.565 c2.986,1.866,6.834,1.607,9.545-0.645l27.023-22.438l36.013,36.013l-22.371,26.941c-2.255,2.717-2.511,6.575-0.632,9.565
c9.189,14.625,15.814,30.514,19.69,47.225c0.795,3.429,3.699,5.964,7.203,6.287l34.829,3.229v25.582 c9.189,14.625,15.814,30.514,19.69,47.225c0.795,3.429,3.699,5.964,7.203,6.287l34.829,3.229v25.582
c0,4.512,3.657,8.169,8.169,8.169s8.169-3.658,8.169-8.169v-33.029C465.586,172.488,462.373,168.963,458.171,168.573z"/> c0,4.512,3.657,8.169,8.169,8.169s8.169-3.658,8.169-8.169v-33.029C465.586,172.488,462.373,168.963,458.171,168.573z"/>
</g> </g>
</g> </g>
</svg> </svg>
</div> </div>
{__('Welcome to')} ROTB! {__('This is file search engine based on the torrents from the internet')}. {__('Welcome to')} ROTB! {__('This is file search engine based on the torrents from the internet')}.
{__('Here you can easily find torrent or file that you intrested for')}. {__('We are not responsible for any content')}: {__('Here you can easily find torrent or file that you intrested for')}. {__('We are not responsible for any content')}:
{__('this is only information about content that collected automatically')}! {__('this is only information about content that collected automatically')}!
</div>} />} </div>} />}
> >
<div className='row header-row' style={{ <div className='row header-row' style={{
padding: '15px', padding: '15px',
background: `url('${Background}') no-repeat`, background: `url('${Background}') no-repeat`,
backgroundSize: 'cover', backgroundSize: 'cover',
transition: '1s' transition: '1s'
}}> }}>
<RaisedButton <RaisedButton
label={__('Feed')} label={__('Feed')}
onClick={() => { onClick={() => {
window.router('/') window.router('/')
}} }}
backgroundColor='#69238c' backgroundColor='#69238c'
labelColor='white' labelColor='white'
style={{height: 60, borderRadius: 6, margin: 5, zIndex: 1}} style={{height: 60, borderRadius: 6, margin: 5, zIndex: 1}}
buttonStyle={{borderRadius: 5}} buttonStyle={{borderRadius: 5}}
icon={<svg fill='white' style={{height: 28}} viewBox="0 0 64.051 64.051"> icon={<svg fill='white' style={{height: 28}} viewBox="0 0 64.051 64.051">
<g> <g>
<path d="M8,0v2c16,0,31.173,7.065,41.472,19.386C57.567,31.065,62.051,43.358,62.051,56h2c0-13.11-4.649-25.858-13.044-35.897 <path d="M8,0v2c16,0,31.173,7.065,41.472,19.386C57.567,31.065,62.051,43.358,62.051,56h2c0-13.11-4.649-25.858-13.044-35.897
C40.326,7.327,25,0,8,0z"/> C40.326,7.327,25,0,8,0z"/>
<path d="M50.769,56h2C52.769,31.343,33,11.282,8,11.282v2C32,13.282,50.769,32.445,50.769,56z"/> <path d="M50.769,56h2C52.769,31.343,33,11.282,8,11.282v2C32,13.282,50.769,32.445,50.769,56z"/>
<path d="M8,22.564v2c17,0,31.486,14.102,31.486,31.436h2C41.486,37.563,26,22.564,8,22.564z"/> <path d="M8,22.564v2c17,0,31.486,14.102,31.486,31.436h2C41.486,37.563,26,22.564,8,22.564z"/>
<path d="M30.205,56C30.205,43.784,20,33.846,8,33.846v2c11,0,20.205,9.041,20.205,20.154H30.205z"/> <path d="M30.205,56C30.205,43.784,20,33.846,8,33.846v2c11,0,20.205,9.041,20.205,20.154H30.205z"/>
<path d="M16.103,56c0-4.439-3.612-8.051-8.052-8.051S0,51.561,0,56s3.611,8.051,8.051,8.051S16.103,60.439,16.103,56z M2,56 <path d="M16.103,56c0-4.439-3.612-8.051-8.052-8.051S0,51.561,0,56s3.611,8.051,8.051,8.051S16.103,60.439,16.103,56z M2,56
c0-3.336,2.715-6.051,6.051-6.051c3.337,0,6.052,2.715,6.052,6.051s-2.715,6.051-6.052,6.051C4.715,62.051,2,59.336,2,56z"/> c0-3.336,2.715-6.051,6.051-6.051c3.337,0,6.052,2.715,6.052,6.051s-2.715,6.051-6.052,6.051C4.715,62.051,2,59.336,2,56z"/>
</g> </g>
</svg> </svg>
} }
/> />
<RaisedButton <RaisedButton
label={__('Downloads')} label={__('Downloads')}
onClick={() => { onClick={() => {
window.router('/downloads') window.router('/downloads')
}} }}
backgroundColor='#2080E4' backgroundColor='#2080E4'
labelColor='white' labelColor='white'
style={{height: 60, borderRadius: 6, margin: 5, zIndex: 1}} style={{height: 60, borderRadius: 6, margin: 5, zIndex: 1}}
buttonStyle={{borderRadius: 5}} buttonStyle={{borderRadius: 5}}
icon={<svg fill='white' style={{height: 30}} viewBox="0 0 548.176 548.176"> icon={<svg fill='white' style={{height: 30}} viewBox="0 0 548.176 548.176">
<path d="M524.326,297.352c-15.896-19.89-36.21-32.782-60.959-38.684c7.81-11.8,11.704-24.934,11.704-39.399 <path d="M524.326,297.352c-15.896-19.89-36.21-32.782-60.959-38.684c7.81-11.8,11.704-24.934,11.704-39.399
c0-20.177-7.139-37.401-21.409-51.678c-14.273-14.272-31.498-21.411-51.675-21.411c-18.083,0-33.879,5.901-47.39,17.703 c0-20.177-7.139-37.401-21.409-51.678c-14.273-14.272-31.498-21.411-51.675-21.411c-18.083,0-33.879,5.901-47.39,17.703
c-11.225-27.41-29.171-49.393-53.817-65.95c-24.646-16.562-51.818-24.842-81.514-24.842c-40.349,0-74.802,14.279-103.353,42.83 c-11.225-27.41-29.171-49.393-53.817-65.95c-24.646-16.562-51.818-24.842-81.514-24.842c-40.349,0-74.802,14.279-103.353,42.83
c-28.553,28.544-42.825,62.999-42.825,103.351c0,2.474,0.191,6.567,0.571,12.275c-22.459,10.469-40.349,26.171-53.676,47.106 c-28.553,28.544-42.825,62.999-42.825,103.351c0,2.474,0.191,6.567,0.571,12.275c-22.459,10.469-40.349,26.171-53.676,47.106
@ -206,82 +206,82 @@ class Header extends React.Component {
c0-2.474,0.905-4.616,2.712-6.427c1.809-1.805,3.949-2.708,6.423-2.708h54.823c2.478,0,4.609,0.9,6.427,2.708 c0-2.474,0.905-4.616,2.712-6.427c1.809-1.805,3.949-2.708,6.423-2.708h54.823c2.478,0,4.609,0.9,6.427,2.708
c1.804,1.811,2.707,3.953,2.707,6.427v100.497h63.954c2.665,0,4.855,0.855,6.563,2.566c1.714,1.711,2.562,3.901,2.562,6.567 c1.804,1.811,2.707,3.953,2.707,6.427v100.497h63.954c2.665,0,4.855,0.855,6.563,2.566c1.714,1.711,2.562,3.901,2.562,6.567
C365.438,303.789,364.494,306.064,362.595,308.344z"/> C365.438,303.789,364.494,306.064,362.595,308.344z"/>
</svg> </svg>
} }
/> />
<RaisedButton <RaisedButton
label={__('Top')} label={__('Top')}
onClick={() => { onClick={() => {
window.router('/top') window.router('/top')
}} }}
backgroundColor='#B1CE57' backgroundColor='#B1CE57'
labelColor='white' labelColor='white'
style={{height: 60, width: 120, borderRadius: 5, margin: 5, zIndex: 1}} style={{height: 60, width: 120, borderRadius: 5, margin: 5, zIndex: 1}}
buttonStyle={{borderRadius: 5}} buttonStyle={{borderRadius: 5}}
icon={<svg fill='white' style={{height: 30}} viewBox="0 0 284.929 284.929"> icon={<svg fill='white' style={{height: 30}} viewBox="0 0 284.929 284.929">
<g> <g>
<path d="M17.128,167.872c1.903,1.902,4.093,2.854,6.567,2.854c2.474,0,4.664-0.952,6.567-2.854L142.466,55.666l112.208,112.206 <path d="M17.128,167.872c1.903,1.902,4.093,2.854,6.567,2.854c2.474,0,4.664-0.952,6.567-2.854L142.466,55.666l112.208,112.206
c1.902,1.902,4.093,2.854,6.563,2.854c2.478,0,4.668-0.952,6.57-2.854l14.274-14.277c1.902-1.902,2.847-4.093,2.847-6.563 c1.902,1.902,4.093,2.854,6.563,2.854c2.478,0,4.668-0.952,6.57-2.854l14.274-14.277c1.902-1.902,2.847-4.093,2.847-6.563
c0-2.475-0.951-4.665-2.847-6.567L149.028,7.419c-1.901-1.906-4.088-2.853-6.562-2.853s-4.665,0.95-6.567,2.853L2.856,140.464 c0-2.475-0.951-4.665-2.847-6.567L149.028,7.419c-1.901-1.906-4.088-2.853-6.562-2.853s-4.665,0.95-6.567,2.853L2.856,140.464
C0.95,142.367,0,144.554,0,147.034c0,2.468,0.953,4.658,2.856,6.561L17.128,167.872z"/> C0.95,142.367,0,144.554,0,147.034c0,2.468,0.953,4.658,2.856,6.561L17.128,167.872z"/>
<path d="M149.028,117.055c-1.901-1.906-4.088-2.856-6.562-2.856s-4.665,0.953-6.567,2.856L2.856,250.1 <path d="M149.028,117.055c-1.901-1.906-4.088-2.856-6.562-2.856s-4.665,0.953-6.567,2.856L2.856,250.1
C0.95,252.003,0,254.192,0,256.67c0,2.472,0.953,4.661,2.856,6.564l14.272,14.276c1.903,1.903,4.093,2.848,6.567,2.848 C0.95,252.003,0,254.192,0,256.67c0,2.472,0.953,4.661,2.856,6.564l14.272,14.276c1.903,1.903,4.093,2.848,6.567,2.848
c2.474,0,4.664-0.951,6.567-2.848l112.204-112.209l112.208,112.209c1.902,1.903,4.093,2.852,6.563,2.852 c2.474,0,4.664-0.951,6.567-2.848l112.204-112.209l112.208,112.209c1.902,1.903,4.093,2.852,6.563,2.852
c2.478,0,4.668-0.948,6.57-2.852l14.274-14.276c1.902-1.903,2.847-4.093,2.847-6.564c0-2.478-0.951-4.667-2.847-6.57 c2.478,0,4.668-0.948,6.57-2.852l14.274-14.276c1.902-1.903,2.847-4.093,2.847-6.564c0-2.478-0.951-4.667-2.847-6.57
L149.028,117.055z"/> L149.028,117.055z"/>
</g> </g>
</svg> </svg>
} }
/> />
<RaisedButton <RaisedButton
label={__('Activity')} label={__('Activity')}
onClick={() => { onClick={() => {
window.router('/activity') window.router('/activity')
}} }}
backgroundColor='#2a5cba' backgroundColor='#2a5cba'
labelColor='white' labelColor='white'
style={{height: 60, width: 150, borderRadius: 5, margin: 5, zIndex: 1}} style={{height: 60, width: 150, borderRadius: 5, margin: 5, zIndex: 1}}
buttonStyle={{borderRadius: 5}} buttonStyle={{borderRadius: 5}}
icon={<svg fill='white' style={{height: 30}} viewBox="0 0 352.352 352.352"> icon={<svg fill='white' style={{height: 30}} viewBox="0 0 352.352 352.352">
<g> <g>
<path d="M255.432,37.172c-7.956-15.3-20.808-28.152-36.107-36.108c-4.284-2.448-10.404-0.612-11.628,4.896 <path d="M255.432,37.172c-7.956-15.3-20.808-28.152-36.107-36.108c-4.284-2.448-10.404-0.612-11.628,4.896
c-1.225,4.284-3.061,8.568-4.896,12.24C153.84-4.444,95.699,20.036,58.979,54.308c-41.616,39.168-62.424,102.816-45.9,157.896 c-1.225,4.284-3.061,8.568-4.896,12.24C153.84-4.444,95.699,20.036,58.979,54.308c-41.616,39.168-62.424,102.816-45.9,157.896
c1.837,7.344,12.24,4.283,11.017-3.061c-7.956-51.408,6.12-99.756,40.392-138.924c37.332-42.228,82.008-45.9,133.416-40.392 c1.837,7.344,12.24,4.283,11.017-3.061c-7.956-51.408,6.12-99.756,40.392-138.924c37.332-42.228,82.008-45.9,133.416-40.392
c-1.224,3.672-1.836,7.344-1.836,11.016c0,3.06,1.225,4.896,3.673,5.508c0,1.224,0,3.06,0.611,4.284 c-1.224,3.672-1.836,7.344-1.836,11.016c0,3.06,1.225,4.896,3.673,5.508c0,1.224,0,3.06,0.611,4.284
c3.672,7.956,12.853,6.732,20.196,5.508c11.016-1.224,22.644-3.672,32.436-8.568C256.656,45.74,257.268,40.231,255.432,37.172z c3.672,7.956,12.853,6.732,20.196,5.508c11.016-1.224,22.644-3.672,32.436-8.568C256.656,45.74,257.268,40.231,255.432,37.172z
M210.144,40.844c-0.611,0-1.224,0-2.447,0c1.224-7.344,5.508-14.688,9.791-22.032c7.345,4.896,13.465,11.016,18.973,18.36 M210.144,40.844c-0.611,0-1.224,0-2.447,0c1.224-7.344,5.508-14.688,9.791-22.032c7.345,4.896,13.465,11.016,18.973,18.36
c-5.508,1.836-11.017,2.448-16.524,3.06C216.876,40.844,213.204,40.844,210.144,40.844z"/> c-5.508,1.836-11.017,2.448-16.524,3.06C216.876,40.844,213.204,40.844,210.144,40.844z"/>
<path d="M297.048,318.08c58.752-74.664,80.172-204.408-9.18-265.608c-5.509-3.672-11.628,4.896-6.732,9.18 <path d="M297.048,318.08c58.752-74.664,80.172-204.408-9.18-265.608c-5.509-3.672-11.628,4.896-6.732,9.18
c71.604,67.32,63.036,173.196,4.896,246.636c-1.836-1.837-4.284-3.061-6.12-4.284c0-6.12-7.956-11.017-12.853-5.508 c71.604,67.32,63.036,173.196,4.896,246.636c-1.836-1.837-4.284-3.061-6.12-4.284c0-6.12-7.956-11.017-12.853-5.508
c-11.016,12.239-14.688,27.539-18.972,43.451c-1.224,4.896,2.448,10.404,7.956,10.404c17.136,0,34.884-3.672,50.796-11.016 c-11.016,12.239-14.688,27.539-18.972,43.451c-1.224,4.896,2.448,10.404,7.956,10.404c17.136,0,34.884-3.672,50.796-11.016
c4.284-2.448,4.896-9.181,1.836-12.24C303.78,324.812,300.72,321.752,297.048,318.08z M273.18,314.407 c4.284-2.448,4.896-9.181,1.836-12.24C303.78,324.812,300.72,321.752,297.048,318.08z M273.18,314.407
c5.508,4.896,10.404,10.404,15.3,15.912c-7.344,2.448-15.3,3.672-23.256,4.896C267.06,327.872,269.508,320.527,273.18,314.407z"/> c5.508,4.896,10.404,10.404,15.3,15.912c-7.344,2.448-15.3,3.672-23.256,4.896C267.06,327.872,269.508,320.527,273.18,314.407z"/>
<path d="M219.936,321.14c-36.72,14.076-63.648,15.912-100.979-0.612c-27.54-12.239-51.408-31.212-71.604-52.02 <path d="M219.936,321.14c-36.72,14.076-63.648,15.912-100.979-0.612c-27.54-12.239-51.408-31.212-71.604-52.02
c4.896-3.672,9.792-7.344,15.3-11.016c4.896-3.673,4.896-9.793,0-13.465c-13.464-9.18-29.376-16.523-45.288-20.808 c4.896-3.672,9.792-7.344,15.3-11.016c4.896-3.673,4.896-9.793,0-13.465c-13.464-9.18-29.376-16.523-45.288-20.808
c-4.284-1.224-8.567,1.224-9.792,5.508c-3.06,11.017-4.284,21.42-2.447,33.048c1.224,7.956,5.508,20.809,13.464,24.48 c-4.284-1.224-8.567,1.224-9.792,5.508c-3.06,11.017-4.284,21.42-2.447,33.048c1.224,7.956,5.508,20.809,13.464,24.48
c3.672,1.836,9.792,0,9.18-5.508l0,0c3.06-1.836,6.12-3.061,9.18-4.896c19.584,28.764,51.408,50.796,83.232,63.036 c3.672,1.836,9.792,0,9.18-5.508l0,0c3.06-1.836,6.12-3.061,9.18-4.896c19.584,28.764,51.408,50.796,83.232,63.036
c30.6,12.24,80.784,22.644,105.264-7.344C229.728,327.26,226.668,318.691,219.936,321.14z M21.035,260.552 c30.6,12.24,80.784,22.644,105.264-7.344C229.728,327.26,226.668,318.691,219.936,321.14z M21.035,260.552
c-1.836-6.732-1.224-13.464,0-19.584c9.181,3.06,17.748,7.344,26.316,12.24c-6.732,6.731-14.688,11.628-22.644,17.136 c-1.836-6.732-1.224-13.464,0-19.584c9.181,3.06,17.748,7.344,26.316,12.24c-6.732,6.731-14.688,11.628-22.644,17.136
C23.483,266.06,21.647,263.611,21.035,260.552z"/> C23.483,266.06,21.647,263.611,21.035,260.552z"/>
</g> </g>
</svg> </svg>
} }
/> />
<div className='fs0-85 pad0-75 column search-panel' style={{ <div className='fs0-85 pad0-75 column search-panel' style={{
marginLeft: 'auto', marginLeft: 'auto',
marginTop: '-10px', marginTop: '-10px',
zIndex: 2 zIndex: 2
}}> }}>
<Search /> <Search />
</div>
</div> </div>
</div> </CardMedia>
</CardMedia> </Card>
</Card> <div className='clear-header-space' style={{transition: '1.0s'}} />
<div className='clear-header-space' style={{transition: '1.0s'}} /> </div>
</div> )
)
} }
} }
export {Header} export {Header}

View File

@ -12,6 +12,6 @@ import './css/izi/animations.css';
import './index.css'; import './index.css';
ReactDOM.render( ReactDOM.render(
<App />, <App />,
document.getElementById('mount-point') document.getElementById('mount-point')
); );

View File

@ -8,65 +8,65 @@ import MenuItem from 'material-ui/MenuItem';
import Checkbox from 'material-ui/Checkbox'; import Checkbox from 'material-ui/Checkbox';
export default class InputFilesFilter extends Component { export default class InputFilesFilter extends Component {
constructor(props) constructor(props)
{ {
super(props) super(props)
this.state = { this.state = {
files: this.props.value || { min: 0, max: 50 }, files: this.props.value || { min: 0, max: 50 },
enabled: false || this.props.enabled, enabled: false || this.props.enabled,
filesMax: this.props.filesMax || 100 // 1mb filesMax: this.props.filesMax || 100 // 1mb
} }
} }
setState(val) setState(val)
{ {
if(val.filesMax && this.state.files.max > val.filesMax) if(val.filesMax && this.state.files.max > val.filesMax)
val.files = {min: this.state.files.min, max: val.filesMax}; val.files = {min: this.state.files.min, max: val.filesMax};
if(val.filesMax && this.state.files.min > val.filesMax) if(val.filesMax && this.state.files.min > val.filesMax)
val.files = {min: 0, max: val.files ? val.files.max || this.state.files.max : this.state.files.max }; val.files = {min: 0, max: val.files ? val.files.max || this.state.files.max : this.state.files.max };
super.setState(val, () => { super.setState(val, () => {
if(this.props.onChange) if(this.props.onChange)
this.props.onChange({ this.props.onChange({
enabled: this.state.enabled, enabled: this.state.enabled,
filesMax: this.state.filesMax, filesMax: this.state.filesMax,
files: !this.state.enabled ? {min: 0, max: 0} : this.state.files files: !this.state.enabled ? {min: 0, max: 0} : this.state.files
}) })
}) })
} }
render() { render() {
return ( return (
<div className='filter-row row inline w100p'> <div className='filter-row row inline w100p'>
<Checkbox <Checkbox
label={__("Files filter")} label={__("Files filter")}
checked={this.state.enabled} checked={this.state.enabled}
style={{width: 150, display: 'flex', minWidth: 130}} style={{width: 150, display: 'flex', minWidth: 130}}
onCheck={() => this.setState({enabled: !this.state.enabled})} onCheck={() => this.setState({enabled: !this.state.enabled})}
/> />
<div className='filter-control-row row inline w100p' style={{opacity: this.state.enabled ? 1 : 0.4, transition: '0.5s', paddingLeft: 9}}> <div className='filter-control-row row inline w100p' style={{opacity: this.state.enabled ? 1 : 0.4, transition: '0.5s', paddingLeft: 9}}>
<InputRange <InputRange
maxValue={this.state.filesMax} maxValue={this.state.filesMax}
minValue={0} minValue={0}
value={this.state.files} value={this.state.files}
style={this.props.style} style={this.props.style}
className={this.props.className} className={this.props.className}
onChange={files => this.setState({ files })} onChange={files => this.setState({ files })}
/> />
<SelectField <SelectField
floatingLabelText={__('Size type')} floatingLabelText={__('Size type')}
value={this.state.filesMax} value={this.state.filesMax}
onChange={(event, index, value) => this.setState({filesMax: value})} onChange={(event, index, value) => this.setState({filesMax: value})}
className='filter-control-border' className='filter-control-border'
> >
<MenuItem value={10} primaryText={"10 " + __("Files or less")} /> <MenuItem value={10} primaryText={"10 " + __("Files or less")} />
<MenuItem value={100} primaryText={"100 " + __("Files or less")} /> <MenuItem value={100} primaryText={"100 " + __("Files or less")} />
<MenuItem value={1000} primaryText={"1000 " + __("Files or less")} /> <MenuItem value={1000} primaryText={"1000 " + __("Files or less")} />
<MenuItem value={10000} primaryText={"10000 " + __("Files or less")} /> <MenuItem value={10000} primaryText={"10000 " + __("Files or less")} />
<MenuItem value={100000} primaryText={"100000 " + __("Files or less")} /> <MenuItem value={100000} primaryText={"100000 " + __("Files or less")} />
<MenuItem value={1000000} primaryText={"1000000 " + __("Files or less")} /> <MenuItem value={1000000} primaryText={"1000000 " + __("Files or less")} />
</SelectField> </SelectField>
</div> </div>
</div> </div>
); );
} }
} }

View File

@ -8,67 +8,67 @@ import MenuItem from 'material-ui/MenuItem';
import Checkbox from 'material-ui/Checkbox'; import Checkbox from 'material-ui/Checkbox';
export default class InputSize extends Component { export default class InputSize extends Component {
constructor(props) constructor(props)
{ {
super(props) super(props)
this.state = { this.state = {
size: this.props.value || { min: 0, max: 500 * 1024 }, size: this.props.value || { min: 0, max: 500 * 1024 },
enabled: false || this.props.enabled, enabled: false || this.props.enabled,
maxSize: this.props.maxSize || 1024 * 1024 // 1mb maxSize: this.props.maxSize || 1024 * 1024 // 1mb
} }
} }
setState(val) setState(val)
{ {
if(val.maxSize && this.state.size.max > val.maxSize) if(val.maxSize && this.state.size.max > val.maxSize)
val.size = {min: this.state.size.min, max: val.maxSize}; val.size = {min: this.state.size.min, max: val.maxSize};
if(val.maxSize && this.state.size.min > val.maxSize) if(val.maxSize && this.state.size.min > val.maxSize)
val.size = {min: 0, max: val.size ? val.size.max || this.state.size.max : this.state.size.max }; val.size = {min: 0, max: val.size ? val.size.max || this.state.size.max : this.state.size.max };
super.setState(val, () => { super.setState(val, () => {
if(this.props.onChange) if(this.props.onChange)
this.props.onChange({ this.props.onChange({
enabled: this.state.enabled, enabled: this.state.enabled,
maxSize: this.state.maxSize, maxSize: this.state.maxSize,
size: !this.state.enabled ? {min: 0, max: 0} : this.state.size size: !this.state.enabled ? {min: 0, max: 0} : this.state.size
}) })
}) })
} }
render() { render() {
return ( return (
<div className='filter-row row inline w100p'> <div className='filter-row row inline w100p'>
<Checkbox <Checkbox
label={__('Size filter')} label={__('Size filter')}
checked={this.state.enabled} checked={this.state.enabled}
style={{width: 150, display: 'flex', minWidth: 130}} style={{width: 150, display: 'flex', minWidth: 130}}
onCheck={() => this.setState({enabled: !this.state.enabled})} onCheck={() => this.setState({enabled: !this.state.enabled})}
/> />
<div className='filter-control-row row inline w100p' style={{opacity: this.state.enabled ? 1 : 0.4, transition: '0.5s', paddingLeft: 9}}> <div className='filter-control-row row inline w100p' style={{opacity: this.state.enabled ? 1 : 0.4, transition: '0.5s', paddingLeft: 9}}>
<InputRange <InputRange
maxValue={this.state.maxSize} maxValue={this.state.maxSize}
minValue={0} minValue={0}
value={this.state.size} value={this.state.size}
formatLabel={size => formatBytes(size)} formatLabel={size => formatBytes(size)}
style={this.props.style} style={this.props.style}
className={this.props.className} className={this.props.className}
onChange={size => this.setState({ size })} onChange={size => this.setState({ size })}
/> />
<SelectField <SelectField
floatingLabelText={__('Size type max')} floatingLabelText={__('Size type max')}
value={this.state.maxSize} value={this.state.maxSize}
onChange={(event, index, value) => this.setState({maxSize: value})} onChange={(event, index, value) => this.setState({maxSize: value})}
className='filter-control-border' className='filter-control-border'
> >
<MenuItem value={1024} primaryText="KB" /> <MenuItem value={1024} primaryText="KB" />
<MenuItem value={1024 * 1024} primaryText="MB" /> <MenuItem value={1024 * 1024} primaryText="MB" />
<MenuItem value={1024 * 1024 * 100} primaryText="100 MB" /> <MenuItem value={1024 * 1024 * 100} primaryText="100 MB" />
<MenuItem value={1024 * 1024 * 1024} primaryText="GB" /> <MenuItem value={1024 * 1024 * 1024} primaryText="GB" />
<MenuItem value={10 * 1024 * 1024 * 1024} primaryText="10 GB" /> <MenuItem value={10 * 1024 * 1024 * 1024} primaryText="10 GB" />
<MenuItem value={100 * 1024 * 1024 * 1024} primaryText="100 GB" /> <MenuItem value={100 * 1024 * 1024 * 1024} primaryText="100 GB" />
<MenuItem value={1024 * 1024 * 1024 * 1024} primaryText="TB" /> <MenuItem value={1024 * 1024 * 1024 * 1024} primaryText="TB" />
</SelectField> </SelectField>
</div> </div>
</div> </div>
); );
} }
} }

View File

@ -2,84 +2,84 @@ import React, { Component } from 'react';
import singleton from './singleton' import singleton from './singleton'
class PagesPie extends Component { class PagesPie extends Component {
pie = []; pie = [];
open(pages, params) { open(pages, params) {
if (params && params.replace) { if (params && params.replace) {
if (params.replace === 'all') { if (params.replace === 'all') {
this.pie = []; this.pie = [];
} else } else
if (params.replace === 'last') { if (params.replace === 'last') {
this.pie.pop(); this.pie.pop();
} }
this.forceUpdate(); this.forceUpdate();
delete params.replace; delete params.replace;
} }
setTimeout(() => { setTimeout(() => {
if (Array.isArray(pages)) { if (Array.isArray(pages)) {
for (let i in pages) { for (let i in pages) {
this.pie.push({ this.pie.push({
Page: pages[i], Page: pages[i],
params: params params: params
}); });
} }
} else { } else {
this.pie.push({ this.pie.push({
Page: pages, Page: pages,
params: params params: params
}); });
} }
this.forceUpdate(); this.forceUpdate();
}, 0); }, 0);
} }
close(count) { close(count) {
if (count && typeof count === 'number') { if (count && typeof count === 'number') {
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
this.pie.pop(); this.pie.pop();
} }
} else { } else {
this.pie.pop(); this.pie.pop();
} }
this.forceUpdate(); this.forceUpdate();
} }
findOpened(windowType) { findOpened(windowType) {
for (let i in this.refs) { for (let i in this.refs) {
if(this.refs[i] instanceof windowType) if(this.refs[i] instanceof windowType)
return this.refs[i]; return this.refs[i];
} }
} }
// ОТРИСОВКА // ОТРИСОВКА
render() { render() {
if (this.pie.length > 0) { if (this.pie.length > 0) {
return ( return (
<div <div
className={'pie full-size ' + (this.props.className || '')} className={'pie full-size ' + (this.props.className || '')}
> >
{ {
this.pie.map(({Page, params}, index) => { this.pie.map(({Page, params}, index) => {
let focus = false; let focus = false;
if (index === this.pie.length-1) { if (index === this.pie.length-1) {
focus = true; focus = true;
} }
return ( return (
<Page <Page
focused={focus} focused={focus}
closeHandler={() => { index> 0 ? this.close() : null}} closeHandler={() => { index> 0 ? this.close() : null}}
index={index} index={index}
key={index} key={index}
ref={index} ref={index}
{...params} {...params}
> >
</Page> </Page>
) )
}) })
} }
</div> </div>
) )
} else { } else {
return null return null
} }
} }
} }
export default singleton(PagesPie) export default singleton(PagesPie)

View File

@ -8,152 +8,152 @@ import FlatButton from 'material-ui/FlatButton';
import RefreshIndicator from 'material-ui/RefreshIndicator'; import RefreshIndicator from 'material-ui/RefreshIndicator';
export default class RecentTorrents extends Component { export default class RecentTorrents extends Component {
constructor() { constructor() {
super() super()
this.torrents = []; this.torrents = [];
this.torrentsAssoc = {}; this.torrentsAssoc = {};
this.displayQueue = []; this.displayQueue = [];
this.displayQueueAssoc = {}; this.displayQueueAssoc = {};
this.maxQueueSize = 1000; this.maxQueueSize = 1000;
this.maxDisplaySize = 10; this.maxDisplaySize = 10;
this.state = { this.state = {
pause: false, pause: false,
searchingIndicator: false searchingIndicator: false
} }
} }
componentDidMount() { componentDidMount() {
window.torrentSocket.emit('recentTorrents', window.customLoader((data) => { window.torrentSocket.emit('recentTorrents', window.customLoader((data) => {
if(data) { if(data) {
this.torrents = data; this.torrents = data;
//this.forceUpdate(); // вызывается через searchingIndicator //this.forceUpdate(); // вызывается через searchingIndicator
} }
this.displayNewTorrent = () => { this.displayNewTorrent = () => {
if(!this.displayNewTorrent) { if(!this.displayNewTorrent) {
return; return;
} }
if(this.displayQueue.length == 0) { if(this.displayQueue.length == 0) {
setTimeout(this.displayNewTorrent, 1000); setTimeout(this.displayNewTorrent, 1000);
return; return;
} }
const speed = 850; const speed = 850;
if(this.state.pause) { if(this.state.pause) {
setTimeout(this.displayNewTorrent, speed); setTimeout(this.displayNewTorrent, speed);
return; return;
} }
let torrent = this.displayQueue.shift(); let torrent = this.displayQueue.shift();
this.torrents.unshift(torrent); this.torrents.unshift(torrent);
this.torrentsAssoc[torrent.hash] = torrent; this.torrentsAssoc[torrent.hash] = torrent;
if(this.torrents.length > this.maxDisplaySize) { if(this.torrents.length > this.maxDisplaySize) {
let toDelete = this.torrents.pop() let toDelete = this.torrents.pop()
delete this.torrentsAssoc[toDelete.hash]; delete this.torrentsAssoc[toDelete.hash];
delete this.displayQueueAssoc[toDelete.hash]; delete this.displayQueueAssoc[toDelete.hash];
} }
this.displayTorrentCounterValue = this.displayQueue.length; this.displayTorrentCounterValue = this.displayQueue.length;
this.forceUpdate(); this.forceUpdate();
setTimeout(this.displayNewTorrent, speed); setTimeout(this.displayNewTorrent, speed);
} }
this.displayNewTorrent(); this.displayNewTorrent();
this.displayTorrentCounterValue = 0; this.displayTorrentCounterValue = 0;
this.displayTorrentCounter = setInterval(() => { this.displayTorrentCounter = setInterval(() => {
if(this.displayTorrentCounterValue != this.displayQueue.length) { if(this.displayTorrentCounterValue != this.displayQueue.length) {
this.displayTorrentCounterValue = this.displayQueue.length; this.displayTorrentCounterValue = this.displayQueue.length;
this.forceUpdate(); this.forceUpdate();
} }
}, 40); }, 40);
}, () => { }, () => {
this.setState({ this.setState({
searchingIndicator: true searchingIndicator: true
}); });
}, () => { }, () => {
this.setState({ this.setState({
searchingIndicator: false searchingIndicator: false
}); });
})); }));
this.newTorrentFunc = (torrent) => { this.newTorrentFunc = (torrent) => {
if(this.displayQueue.length < this.maxQueueSize) { if(this.displayQueue.length < this.maxQueueSize) {
this.displayQueue.push(torrent); this.displayQueue.push(torrent);
this.displayQueueAssoc[torrent.hash] = torrent; this.displayQueueAssoc[torrent.hash] = torrent;
} }
}; };
window.torrentSocket.on('newTorrent', this.newTorrentFunc); window.torrentSocket.on('newTorrent', this.newTorrentFunc);
this.tracketUpdate = (statistic) => { this.tracketUpdate = (statistic) => {
if(statistic.hash in this.displayQueueAssoc) if(statistic.hash in this.displayQueueAssoc)
{ {
Object.assign(this.displayQueueAssoc[statistic.hash], statistic); Object.assign(this.displayQueueAssoc[statistic.hash], statistic);
if(statistic.hash in this.torrentsAssoc) { if(statistic.hash in this.torrentsAssoc) {
this.forceUpdate(); this.forceUpdate();
} }
} }
} }
window.torrentSocket.on('trackerTorrentUpdate', this.tracketUpdate); window.torrentSocket.on('trackerTorrentUpdate', this.tracketUpdate);
} }
pauseAndContinue() { pauseAndContinue() {
this.setState({ this.setState({
pause: !this.state.pause pause: !this.state.pause
}); });
} }
componentWillUnmount() { componentWillUnmount() {
if(this.newTorrentFunc) if(this.newTorrentFunc)
window.torrentSocket.off('newTorrent', this.newTorrentFunc); window.torrentSocket.off('newTorrent', this.newTorrentFunc);
if(this.tracketUpdate) if(this.tracketUpdate)
window.torrentSocket.off('trackerTorrentUpdate', this.tracketUpdate); window.torrentSocket.off('trackerTorrentUpdate', this.tracketUpdate);
if(this.displayNewTorrent) if(this.displayNewTorrent)
delete this.displayNewTorrent; delete this.displayNewTorrent;
if(this.displayTorrentCounter) if(this.displayTorrentCounter)
clearInterval(this.displayTorrentCounter); clearInterval(this.displayTorrentCounter);
} }
render() { render() {
const style = { const style = {
refresh: { refresh: {
display: 'inline-block', display: 'inline-block',
position: 'relative', position: 'relative',
}, },
}; };
if(this.state.searchingIndicator) { if(this.state.searchingIndicator) {
return ( return (
<div className='pad1'> <div className='pad1'>
<RefreshIndicator <RefreshIndicator
size={50} size={50}
left={0} left={0}
top={0} top={0}
loadingColor="#FF9800" loadingColor="#FF9800"
status="loading" status="loading"
style={style.refresh} style={style.refresh}
/> />
</div> </div>
); );
} }
if(!this.torrents || this.torrents.length == 0) if(!this.torrents || this.torrents.length == 0)
return null; return null;
return ( return (
<List className='animated recent-torrents torrents-container'> <List className='animated recent-torrents torrents-container'>
<Subheader className='recent-title' inset={true}> <Subheader className='recent-title' inset={true}>
<FlatButton style={{marginRight: '8px'}} primary={true} label={__('top')} labelStyle={{color: "#a4c639"}} onClick={() =>{ <FlatButton style={{marginRight: '8px'}} primary={true} label={__('top')} labelStyle={{color: "#a4c639"}} onClick={() =>{
window.router('/top'); window.router('/top');
}} /> }} />
<FlatButton style={{marginRight: '8px'}} label={!this.state.pause ? __('running') : __('stoped')} secondary={this.state.pause} primary={!this.state.pause} onClick={() =>{ <FlatButton style={{marginRight: '8px'}} label={!this.state.pause ? __('running') : __('stoped')} secondary={this.state.pause} primary={!this.state.pause} onClick={() =>{
this.pauseAndContinue() this.pauseAndContinue()
}} /> }} />
{__('Most recent torrents')}{this.displayQueue.length > 0 ? ` (${__('and')} ${this.displayQueue.length} ${__('more')})` : null} {__('Most recent torrents')}{this.displayQueue.length > 0 ? ` (${__('and')} ${this.displayQueue.length} ${__('more')})` : null}
</Subheader> </Subheader>
<Divider /> <Divider />
{ {
this.torrents.map((torrent, index) =>{ this.torrents.map((torrent, index) =>{
return <TorrentLine key={index} torrent={torrent} />; return <TorrentLine key={index} torrent={torrent} />;
}) })
} }
</List> </List>
); );
} }
} }

View File

@ -9,105 +9,105 @@
// This link also includes instructions on opting out of this behavior. // This link also includes instructions on opting out of this behavior.
const isLocalhost = Boolean( const isLocalhost = Boolean(
window.location.hostname === 'localhost' || window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address. // [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' || window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4. // 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match( window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
) )
); );
export default function register() { export default function register() {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW. // The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location); const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
if (publicUrl.origin !== window.location.origin) { if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin // Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to // from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
return; return;
} }
window.addEventListener('load', () => { window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (!isLocalhost) { if (!isLocalhost) {
// Is not local host. Just register service worker // Is not local host. Just register service worker
registerValidSW(swUrl); registerValidSW(swUrl);
} else { } else {
// This is running on localhost. Lets check if a service worker still exists or not. // This is running on localhost. Lets check if a service worker still exists or not.
checkValidServiceWorker(swUrl); checkValidServiceWorker(swUrl);
} }
}); });
} }
} }
function registerValidSW(swUrl) { function registerValidSW(swUrl) {
navigator.serviceWorker navigator.serviceWorker
.register(swUrl) .register(swUrl)
.then(registration => { .then(registration => {
registration.onupdatefound = () => { registration.onupdatefound = () => {
const installingWorker = registration.installing; const installingWorker = registration.installing;
installingWorker.onstatechange = () => { installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') { if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) { if (navigator.serviceWorker.controller) {
// At this point, the old content will have been purged and // At this point, the old content will have been purged and
// the fresh content will have been added to the cache. // the fresh content will have been added to the cache.
// It's the perfect time to display a "New content is // It's the perfect time to display a "New content is
// available; please refresh." message in your web app. // available; please refresh." message in your web app.
console.log('New content is available; please refresh.'); console.log('New content is available; please refresh.');
} else { } else {
// At this point, everything has been precached. // At this point, everything has been precached.
// It's the perfect time to display a // It's the perfect time to display a
// "Content is cached for offline use." message. // "Content is cached for offline use." message.
console.log('Content is cached for offline use.'); console.log('Content is cached for offline use.');
} }
} }
}; };
}; };
}) })
.catch(error => { .catch(error => {
console.error('Error during service worker registration:', error); console.error('Error during service worker registration:', error);
}); });
} }
function checkValidServiceWorker(swUrl) { function checkValidServiceWorker(swUrl) {
// Check if the service worker can be found. If it can't reload the page. // Check if the service worker can be found. If it can't reload the page.
fetch(swUrl) fetch(swUrl)
.then(response => { .then(response => {
// Ensure service worker exists, and that we really are getting a JS file. // Ensure service worker exists, and that we really are getting a JS file.
if ( if (
response.status === 404 || response.status === 404 ||
response.headers.get('content-type').indexOf('javascript') === -1 response.headers.get('content-type').indexOf('javascript') === -1
) { ) {
// No service worker found. Probably a different app. Reload the page. // No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => { navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => { registration.unregister().then(() => {
window.location.reload(); window.location.reload();
}); });
}); });
} else { } else {
// Service worker found. Proceed as normal. // Service worker found. Proceed as normal.
registerValidSW(swUrl); registerValidSW(swUrl);
} }
}) })
.catch(() => { .catch(() => {
console.log( console.log(
'No internet connection found. App is running in offline mode.' 'No internet connection found. App is running in offline mode.'
); );
}); });
} }
export function unregister() { export function unregister() {
if ('serviceWorker' in navigator) { if ('serviceWorker' in navigator) {
return navigator.serviceWorker.ready.then(registration => { return navigator.serviceWorker.ready.then(registration => {
console.log("Unregister service worker"); console.log("Unregister service worker");
return registration.unregister(); return registration.unregister();
}); });
} else { } else {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
resolve(); resolve();
}) })
} }
} }

View File

@ -31,7 +31,7 @@ const router = (page, callback) => {
params[pg.args[i]] = p[i] params[pg.args[i]] = p[i]
} }
console.log(params) console.log(params)
pg.callback({ pg.callback({
params params
}) })

View File

@ -5,52 +5,52 @@ import InputSize from './input-size';
import FilesFilterInput from './input-files-filter'; import FilesFilterInput from './input-files-filter';
export default class AdvancedSearchControl extends Component { export default class AdvancedSearchControl extends Component {
constructor(props) constructor(props)
{ {
super(props) super(props)
this.state = { this.state = {
type: undefined, type: undefined,
size: {min: 0, max: 0}, size: {min: 0, max: 0},
maxSize: 1024 * 1024 * 1024, maxSize: 1024 * 1024 * 1024,
sizeEnabled: false, sizeEnabled: false,
filesEnabled: false, filesEnabled: false,
files: {min: 0, max: 0}, files: {min: 0, max: 0},
filesMax: 100, filesMax: 100,
} }
if(this.props.state) if(this.props.state)
this.state = Object.assign(this.state, this.props.state) this.state = Object.assign(this.state, this.props.state)
} }
setState(val) setState(val)
{ {
super.setState(val, (v) => { super.setState(val, (v) => {
if(this.props.onChange) if(this.props.onChange)
this.props.onChange(this.state) this.props.onChange(this.state)
}) })
} }
render() { render() {
return ( return (
<div className='column w100p' style={{maxWidth: 750, overflow: 'hidden', padding: '0px 18px 15px', background: 'white', borderRadius: 3}}> <div className='column w100p' style={{maxWidth: 750, overflow: 'hidden', padding: '0px 18px 15px', background: 'white', borderRadius: 3}}>
<SelectField <SelectField
floatingLabelText={__('Filter content type')} floatingLabelText={__('Filter content type')}
value={this.state.type} value={this.state.type}
onChange={(event, index, value) => this.setState({type: value})} onChange={(event, index, value) => this.setState({type: value})}
> >
<MenuItem value={undefined} primaryText="" /> <MenuItem value={undefined} primaryText="" />
<MenuItem value='video' primaryText={__('Video')} /> <MenuItem value='video' primaryText={__('Video')} />
<MenuItem value='audio' primaryText={__('Audio')} /> <MenuItem value='audio' primaryText={__('Audio')} />
<MenuItem value='pictures' primaryText={__('Pictures')} /> <MenuItem value='pictures' primaryText={__('Pictures')} />
<MenuItem value='books' primaryText={__('Books')} /> <MenuItem value='books' primaryText={__('Books')} />
<MenuItem value='application' primaryText={__('Applications')} /> <MenuItem value='application' primaryText={__('Applications')} />
<MenuItem value='archive' primaryText={__('Archives')} /> <MenuItem value='archive' primaryText={__('Archives')} />
<MenuItem value='disc' primaryText={__('Disk Images')} /> <MenuItem value='disc' primaryText={__('Disk Images')} />
</SelectField> </SelectField>
<div className='w100p'> <div className='w100p'>
<InputSize value={this.state.size} enabled={this.state.sizeEnabled} maxSize={this.state.maxSize} onChange={({size, maxSize, enabled}) => this.setState({size, maxSize, sizeEnabled: enabled})} /> <InputSize value={this.state.size} enabled={this.state.sizeEnabled} maxSize={this.state.maxSize} onChange={({size, maxSize, enabled}) => this.setState({size, maxSize, sizeEnabled: enabled})} />
</div> </div>
<div className='w100p'> <div className='w100p'>
<FilesFilterInput value={this.state.files} filesMax={this.state.filesMax} enabled={this.state.filesEnabled} onChange={({files, filesMax, enabled}) => this.setState({files, filesMax, filesEnabled: enabled})} /> <FilesFilterInput value={this.state.files} filesMax={this.state.filesMax} enabled={this.state.filesEnabled} onChange={({files, filesMax, enabled}) => this.setState({files, filesMax, filesEnabled: enabled})} />
</div> </div>
</div> </div>
); );
} }
} }

View File

@ -7,130 +7,130 @@ import Divider from 'material-ui/Divider';
import LinearProgress from 'material-ui/LinearProgress'; import LinearProgress from 'material-ui/LinearProgress';
export default class SearchResults extends Component { export default class SearchResults extends Component {
render() { render() {
return ( return (
<List style={{minWidth: '20em'}}> <List style={{minWidth: '20em'}}>
{ {
(this.props.torrentsSearchResults && this.props.torrentsSearchResults.length > 0) (this.props.torrentsSearchResults && this.props.torrentsSearchResults.length > 0)
|| (this.props.filesSearchResults && this.props.filesSearchResults.length > 0) || (this.props.filesSearchResults && this.props.filesSearchResults.length > 0)
? ?
<div> <div>
<Subheader className='row center w100p' style={{paddingLeft: 0}} inset={true}><span>{__('Search results for')}</span> <span style={{marginLeft: '0.4em'}}><b>{this.props.searchText}</b></span></Subheader> <Subheader className='row center w100p' style={{paddingLeft: 0}} inset={true}><span>{__('Search results for')}</span> <span style={{marginLeft: '0.4em'}}><b>{this.props.searchText}</b></span></Subheader>
<div className='w100p row center' style={{marginTop: '-16px'}}>{this.props.resultSelector}</div> <div className='w100p row center' style={{marginTop: '-16px'}}>{this.props.resultSelector}</div>
</div> </div>
: :
null null
} }
{ {
this.props.torrentsSearchResults && this.props.torrentsSearchResults.length > 0 this.props.torrentsSearchResults && this.props.torrentsSearchResults.length > 0
? ?
this.props.torrentsSearchResults.map((torrent, index) =>{ this.props.torrentsSearchResults.map((torrent, index) =>{
return( return(
<TorrentLine torrent={torrent} key={index} /> <TorrentLine torrent={torrent} key={index} />
); );
}) })
: :
null null
} }
{ {
this.props.moreTorrentsEnabled && !this.props.moreTorrentsIndicator this.props.moreTorrentsEnabled && !this.props.moreTorrentsIndicator
? ?
<div> <div>
<ListItem innerDivStyle={{textAlign: 'center', padding: '1em'}} primaryText={<span>{__('More Torrents')}</span>} onClick={() => { <ListItem innerDivStyle={{textAlign: 'center', padding: '1em'}} primaryText={<span>{__('More Torrents')}</span>} onClick={() => {
if(this.props.onMoreTorrents) if(this.props.onMoreTorrents)
this.props.onMoreTorrents(); this.props.onMoreTorrents();
}} /> }} />
<Divider /> <Divider />
</div> </div>
: :
null null
} }
{ {
this.props.moreTorrentsIndicator this.props.moreTorrentsIndicator
? ?
<div style={{padding: '0.8em'}}> <div style={{padding: '0.8em'}}>
<LinearProgress mode="indeterminate" /> <LinearProgress mode="indeterminate" />
</div> </div>
: :
null null
} }
{ {
this.props.filesSearchResults && this.props.filesSearchResults.length > 0 this.props.filesSearchResults && this.props.filesSearchResults.length > 0
? ?
this.props.filesSearchResults.map((torrent, index) =>{ this.props.filesSearchResults.map((torrent, index) =>{
return( return(
<TorrentLine torrent={torrent} key={index} /> <TorrentLine torrent={torrent} key={index} />
); );
}) })
: :
null null
} }
{ {
this.props.moreFilesEnabled && !this.props.moreFilesIndicator this.props.moreFilesEnabled && !this.props.moreFilesIndicator
? ?
<div> <div>
<ListItem innerDivStyle={{textAlign: 'center', padding: '1em'}} primaryText={__('More Files')} onClick={() => { <ListItem innerDivStyle={{textAlign: 'center', padding: '1em'}} primaryText={__('More Files')} onClick={() => {
if(this.props.onMoreFiles) if(this.props.onMoreFiles)
this.props.onMoreFiles(); this.props.onMoreFiles();
}} /> }} />
<Divider /> <Divider />
</div> </div>
: :
null null
} }
{ {
this.props.moreFilesIndicator this.props.moreFilesIndicator
? ?
<div style={{padding: '0.8em'}}> <div style={{padding: '0.8em'}}>
<LinearProgress mode="indeterminate" /> <LinearProgress mode="indeterminate" />
</div> </div>
: :
null null
} }
{ {
this.props.torrentsSearchResults && this.props.torrentsSearchResults.length == 0 this.props.torrentsSearchResults && this.props.torrentsSearchResults.length == 0
&& this.props.filesSearchResults && this.props.filesSearchResults.length == 0 && this.props.filesSearchResults && this.props.filesSearchResults.length == 0
&& !this.props.currentSearching && !this.props.currentSearching
? ?
<div className='row inline center w100p pad0-75'> <div className='row inline center w100p pad0-75'>
<svg style={{fill: 'grey', height: '30px'}} viewBox="0 0 264.695 264.695"> <svg style={{fill: 'grey', height: '30px'}} viewBox="0 0 264.695 264.695">
<g> <g>
<path d="M219.171,216.785c-4.762,0-10.322,2.3-16.672,6.881l-35.211-12.968l35.734-12.978 <path d="M219.171,216.785c-4.762,0-10.322,2.3-16.672,6.881l-35.211-12.968l35.734-12.978
c6.003,3.888,11.558,5.833,16.682,5.833c5.639,0,9.347-2.917,11.117-8.733c0.351-1.235,0.527-2.57,0.527-3.981 c6.003,3.888,11.558,5.833,16.682,5.833c5.639,0,9.347-2.917,11.117-8.733c0.351-1.235,0.527-2.57,0.527-3.981
c0-7.397-4.766-11.378-14.295-11.9c3.876-3.882,5.828-7.687,5.828-11.392c0-3.871-2.039-7.149-6.092-9.797 c0-7.397-4.766-11.378-14.295-11.9c3.876-3.882,5.828-7.687,5.828-11.392c0-3.871-2.039-7.149-6.092-9.797
c-2.118-1.049-4.325-1.584-6.615-1.584c-7.769,0-13.064,6.258-15.887,18.797l-61.941,23.039l-61.94-22.504 c-2.118-1.049-4.325-1.584-6.615-1.584c-7.769,0-13.064,6.258-15.887,18.797l-61.941,23.039l-61.94-22.504
c-2.823-12.885-8.125-19.332-15.885-19.332c-2.293,0-4.501,0.535-6.62,1.584c-3.876,2.647-5.82,5.926-5.82,9.797 c-2.823-12.885-8.125-19.332-15.885-19.332c-2.293,0-4.501,0.535-6.62,1.584c-3.876,2.647-5.82,5.926-5.82,9.797
c0,3.705,1.944,7.51,5.82,11.392c-9.701,0.522-14.555,4.503-14.555,11.901c0,1.41,0.179,2.746,0.526,3.98 c0,3.705,1.944,7.51,5.82,11.392c-9.701,0.522-14.555,4.503-14.555,11.901c0,1.41,0.179,2.746,0.526,3.98
c1.946,5.816,5.651,8.733,11.122,8.733c5.113,0,10.671-1.945,16.677-5.832l35.998,12.977l-35.476,12.698 c1.946,5.816,5.651,8.733,11.122,8.733c5.113,0,10.671-1.945,16.677-5.832l35.998,12.977l-35.476,12.698
c-6.175-4.406-11.637-6.611-16.402-6.611c-5.654,0-9.623,2.918-11.919,8.733c-0.348,1.235-0.526,2.553-0.526,3.975 c-6.175-4.406-11.637-6.611-16.402-6.611c-5.654,0-9.623,2.918-11.919,8.733c-0.348,1.235-0.526,2.553-0.526,3.975
c0,7.405,4.853,11.385,14.555,11.907c-3.876,3.883-5.82,7.688-5.82,11.393c0,3.869,1.944,7.134,5.82,9.797 c0,7.405,4.853,11.385,14.555,11.907c-3.876,3.883-5.82,7.688-5.82,11.393c0,3.869,1.944,7.134,5.82,9.797
c2.477,1.412,4.854,2.105,7.153,2.105c7.227,0,12.443-6.176,15.619-18.525l61.673-22.504l61.678,22.504 c2.477,1.412,4.854,2.105,7.153,2.105c7.227,0,12.443-6.176,15.619-18.525l61.673-22.504l61.678,22.504
c3.178,12.35,8.475,18.525,15.882,18.525c2.121,0,4.407-0.693,6.884-2.105c4.052-2.663,6.092-5.928,6.092-9.797 c3.178,12.35,8.475,18.525,15.882,18.525c2.121,0,4.407-0.693,6.884-2.105c4.052-2.663,6.092-5.928,6.092-9.797
c0-3.705-1.953-7.51-5.828-11.393c9.528-0.522,14.295-4.502,14.295-11.907c0-1.422-0.177-2.739-0.527-3.975 c0-3.705-1.953-7.51-5.828-11.393c9.528-0.522,14.295-4.502,14.295-11.907c0-1.422-0.177-2.739-0.527-3.975
C228.702,219.702,224.82,216.785,219.171,216.785z"/> C228.702,219.702,224.82,216.785,219.171,216.785z"/>
<path d="M48.436,128.904c9.703,11.114,23.379,19.242,41.035,24.346v3.986c0,4.936,1.672,9.086,5.025,12.433 <path d="M48.436,128.904c9.703,11.114,23.379,19.242,41.035,24.346v3.986c0,4.936,1.672,9.086,5.025,12.433
c3.35,3.358,7.498,5.211,12.441,5.563c5.116,0.357,8.905-0.528,11.378-2.646c3.879,2.817,8.204,4.229,12.974,4.229 c3.35,3.358,7.498,5.211,12.441,5.563c5.116,0.357,8.905-0.528,11.378-2.646c3.879,2.817,8.204,4.229,12.974,4.229
c4.41,0,8.474-1.316,12.175-3.963c2.471,1.934,6.087,2.738,10.856,2.381c4.937-0.528,9.089-2.426,12.44-5.689 c4.41,0,8.474-1.316,12.175-3.963c2.471,1.934,6.087,2.738,10.856,2.381c4.937-0.528,9.089-2.426,12.44-5.689
c3.35-3.281,5.025-7.371,5.025-12.307v-2.91c19.057-4.945,33.795-13.237,44.21-24.898c10.059-11.109,15.087-24.253,15.087-39.435 c3.35-3.281,5.025-7.371,5.025-12.307v-2.91c19.057-4.945,33.795-13.237,44.21-24.898c10.059-11.109,15.087-24.253,15.087-39.435
c0-3.359-0.355-6.886-1.063-10.597c-3.525-22.571-13.938-41.201-31.229-55.844C180.612,7.856,158.464,0,132.347,0 c0-3.359-0.355-6.886-1.063-10.597c-3.525-22.571-13.938-41.201-31.229-55.844C180.612,7.856,158.464,0,132.347,0
c-26.123,0-48.27,7.767-66.44,23.282C48.61,38.118,38.289,56.825,34.937,79.396c-0.709,3.711-1.064,7.238-1.064,10.597 c-26.123,0-48.27,7.767-66.44,23.282C48.61,38.118,38.289,56.825,34.937,79.396c-0.709,3.711-1.064,7.238-1.064,10.597
C33.873,104.817,38.724,117.778,48.436,128.904L48.436,128.904z M152.865,60.749c5.206-6.085,11.514-9.13,18.922-9.13 C33.873,104.817,38.724,117.778,48.436,128.904L48.436,128.904z M152.865,60.749c5.206-6.085,11.514-9.13,18.922-9.13
c7.592,0,13.986,3.045,19.194,9.13c5.2,6.076,7.81,13.446,7.81,22.087c0,8.649-2.609,16.021-7.81,22.108 c7.592,0,13.986,3.045,19.194,9.13c5.2,6.076,7.81,13.446,7.81,22.087c0,8.649-2.609,16.021-7.81,22.108
c-5.208,6.097-11.603,9.13-19.194,9.13c-7.408,0-13.716-3.033-18.922-9.13c-5.211-6.087-7.814-13.459-7.814-22.108 c-5.208,6.097-11.603,9.13-19.194,9.13c-7.408,0-13.716-3.033-18.922-9.13c-5.211-6.087-7.814-13.459-7.814-22.108
C145.05,74.195,147.654,66.825,152.865,60.749z M124.805,121.428c2.556-3.307,5.065-4.968,7.542-4.968 C145.05,74.195,147.654,66.825,152.865,60.749z M124.805,121.428c2.556-3.307,5.065-4.968,7.542-4.968
c2.47,0,4.802,1.831,7.012,5.509c2.205,3.662,3.317,7.145,3.317,10.469c0,5.062-3.361,7.581-10.067,7.581 c2.47,0,4.802,1.831,7.012,5.509c2.205,3.662,3.317,7.145,3.317,10.469c0,5.062-3.361,7.581-10.067,7.581
c-4.414,0-7.677-1.136-9.792-3.396c-1.237-1.411-1.849-3.147-1.849-5.249C120.969,128.065,122.245,124.752,124.805,121.428z c-4.414,0-7.677-1.136-9.792-3.396c-1.237-1.411-1.849-3.147-1.849-5.249C120.969,128.065,122.245,124.752,124.805,121.428z
M71.465,60.749c5.295-6.085,11.65-9.13,19.059-9.13c7.406,0,13.762,3.045,19.06,9.13c5.296,6.076,7.948,13.446,7.948,22.087 M71.465,60.749c5.295-6.085,11.65-9.13,19.059-9.13c7.406,0,13.762,3.045,19.06,9.13c5.296,6.076,7.948,13.446,7.948,22.087
c0,8.649-2.651,16.021-7.948,22.108c-5.297,6.097-11.654,9.13-19.06,9.13c-7.409,0-13.764-3.033-19.059-9.13 c0,8.649-2.651,16.021-7.948,22.108c-5.297,6.097-11.654,9.13-19.06,9.13c-7.409,0-13.764-3.033-19.059-9.13
c-5.292-6.087-7.944-13.459-7.944-22.108C63.521,74.195,66.173,66.825,71.465,60.749z"/> c-5.292-6.087-7.944-13.459-7.944-22.108C63.521,74.195,66.173,66.825,71.465,60.749z"/>
</g> </g>
</svg> </svg>
<div className='fs0-85 pad0-75' style={{color: 'grey'}}>{__('no torrents for')} <b>{this.props.searchText}</b> {__('were found')}</div> <div className='fs0-85 pad0-75' style={{color: 'grey'}}>{__('no torrents for')} <b>{this.props.searchText}</b> {__('were found')}</div>
</div> </div>
: :
null null
} }
</List> </List>
); );
} }
} }

View File

@ -17,332 +17,332 @@ import _ from 'lodash'
import singleton from './singleton'; import singleton from './singleton';
class Search extends Component { class Search extends Component {
constructor(props) constructor(props)
{ {
super(props) super(props)
this.onSearchUpdate = () => {} this.onSearchUpdate = () => {}
this.state = { this.state = {
searchingIndicator: false, searchingIndicator: false,
safeSearchText: __('safe search enabled'), safeSearchText: __('safe search enabled'),
safeSearchColor: 'rgb(0, 188, 212)', safeSearchColor: 'rgb(0, 188, 212)',
moreTorrentsIndicator: false, moreTorrentsIndicator: false,
moreFilesIndicator: false, moreFilesIndicator: false,
orderBy: null, orderBy: null,
orderDesc: false, orderDesc: false,
advancedSearch: false, advancedSearch: false,
} }
this.searchLimit = 10 this.searchLimit = 10
this.advanced = {} this.advanced = {}
this.searchError = undefined; this.searchError = undefined;
} }
search(oldSearch) { search(oldSearch) {
window.router('/') window.router('/')
this.setState({ this.setState({
searchingIndicator: true searchingIndicator: true
}); });
this.onSearchUpdate('indicator') this.onSearchUpdate('indicator')
this.searchTorrents = []; this.searchTorrents = [];
this.moreSearchTorrents = true; this.moreSearchTorrents = true;
this.searchFiles = []; this.searchFiles = [];
this.moreSearchFiles = true; this.moreSearchFiles = true;
this.currentSearch = this.searchValue; this.currentSearch = this.searchValue;
let queries = 2; let queries = 2;
let searchTorrentsParams = { let searchTorrentsParams = {
limit: this.searchLimit, limit: this.searchLimit,
safeSearch: !this.notSafeSearch, safeSearch: !this.notSafeSearch,
orderBy: this.state.orderBy, orderBy: this.state.orderBy,
orderDesc: this.state.orderDesc, orderDesc: this.state.orderDesc,
}; };
if(this.state.advancedSearch && this.advanced) if(this.state.advancedSearch && this.advanced)
searchTorrentsParams = Object.assign(searchTorrentsParams, this.advanced); searchTorrentsParams = Object.assign(searchTorrentsParams, this.advanced);
window.torrentSocket.emit('searchTorrent', oldSearch ? this.currentSearch : this.searchValue, searchTorrentsParams, window.customLoader((torrents) => { window.torrentSocket.emit('searchTorrent', oldSearch ? this.currentSearch : this.searchValue, searchTorrentsParams, window.customLoader((torrents) => {
if(torrents) { if(torrents) {
this.searchTorrents = torrents; this.searchTorrents = torrents;
if(torrents.length != this.searchLimit) if(torrents.length != this.searchLimit)
this.moreSearchTorrents = false; this.moreSearchTorrents = false;
} }
else else
{ {
this.moreSearchTorrents = false; this.moreSearchTorrents = false;
} }
if(--queries == 0) { if(--queries == 0) {
this.setState({ this.setState({
searchingIndicator: false searchingIndicator: false
}); });
} }
this.onSearchUpdate('torrents') this.onSearchUpdate('torrents')
})); }));
let searchFilesParams = { let searchFilesParams = {
limit: this.searchLimit, limit: this.searchLimit,
safeSearch: !this.notSafeSearch, safeSearch: !this.notSafeSearch,
orderBy: this.state.orderBy, orderBy: this.state.orderBy,
orderDesc: this.state.orderDesc, orderDesc: this.state.orderDesc,
}; };
if(this.state.advancedSearch && this.advanced) if(this.state.advancedSearch && this.advanced)
searchFilesParams = Object.assign(searchFilesParams, this.advanced); searchFilesParams = Object.assign(searchFilesParams, this.advanced);
window.torrentSocket.emit('searchFiles', oldSearch ? this.currentSearch : this.searchValue, searchFilesParams, window.customLoader((torrents) => { window.torrentSocket.emit('searchFiles', oldSearch ? this.currentSearch : this.searchValue, searchFilesParams, window.customLoader((torrents) => {
if(torrents) { if(torrents) {
this.searchFiles = torrents; this.searchFiles = torrents;
let files = 0; let files = 0;
torrents.forEach((torrent) => { torrents.forEach((torrent) => {
if(torrent.path && torrent.path.length > 0) if(torrent.path && torrent.path.length > 0)
files += torrent.path.length files += torrent.path.length
}); });
if(files != this.searchLimit) if(files != this.searchLimit)
this.moreSearchFiles = false; this.moreSearchFiles = false;
} }
else else
{ {
this.moreSearchFiles = false; this.moreSearchFiles = false;
} }
if(--queries == 0) { if(--queries == 0) {
this.setState({ this.setState({
searchingIndicator: false searchingIndicator: false
}); });
} }
this.onSearchUpdate('files') this.onSearchUpdate('files')
})); }));
} }
moreTorrents() { moreTorrents() {
this.setState({moreTorrentsIndicator: true}); this.setState({moreTorrentsIndicator: true});
this.onSearchUpdate('indicator') this.onSearchUpdate('indicator')
window.torrentSocket.emit('searchTorrent', this.currentSearch, { window.torrentSocket.emit('searchTorrent', this.currentSearch, {
index: this.searchTorrents.length, index: this.searchTorrents.length,
limit: this.searchLimit, limit: this.searchLimit,
safeSearch: !this.notSafeSearch, safeSearch: !this.notSafeSearch,
orderBy: this.state.orderBy, orderBy: this.state.orderBy,
orderDesc: this.state.orderDesc, orderDesc: this.state.orderDesc,
}, window.customLoader((torrents) => { }, window.customLoader((torrents) => {
if(torrents) { if(torrents) {
this.searchTorrents = this.searchTorrents.concat(torrents); this.searchTorrents = this.searchTorrents.concat(torrents);
if(torrents.length != this.searchLimit) if(torrents.length != this.searchLimit)
this.moreSearchTorrents = false; this.moreSearchTorrents = false;
this.setState({moreTorrentsIndicator: false}); this.setState({moreTorrentsIndicator: false});
this.onSearchUpdate('more torrents') this.onSearchUpdate('more torrents')
} }
})); }));
} }
calcTorrentsFiles(torrents) calcTorrentsFiles(torrents)
{ {
let files = 0; let files = 0;
torrents.forEach((torrent) => { torrents.forEach((torrent) => {
if(torrent.path && torrent.path.length > 0) if(torrent.path && torrent.path.length > 0)
files += torrent.path.length files += torrent.path.length
}); });
return files return files
} }
moreFiles() { moreFiles() {
let index = 0; let index = 0;
this.searchFiles.forEach((torrent) => { this.searchFiles.forEach((torrent) => {
if(torrent.path && torrent.path.length > 0) if(torrent.path && torrent.path.length > 0)
index += torrent.path.length; index += torrent.path.length;
}); });
this.setState({moreFilesIndicator: true}); this.setState({moreFilesIndicator: true});
this.onSearchUpdate('indicator') this.onSearchUpdate('indicator')
window.torrentSocket.emit('searchFiles', this.currentSearch, { window.torrentSocket.emit('searchFiles', this.currentSearch, {
index: index, index: index,
limit: this.searchLimit, limit: this.searchLimit,
safeSearch: !this.notSafeSearch, safeSearch: !this.notSafeSearch,
orderBy: this.state.orderBy, orderBy: this.state.orderBy,
orderDesc: this.state.orderDesc, orderDesc: this.state.orderDesc,
}, window.customLoader((torrents) => { }, window.customLoader((torrents) => {
if(torrents) { if(torrents) {
this.searchFiles = this.searchFiles.concat(torrents); this.searchFiles = this.searchFiles.concat(torrents);
if(this.calcTorrentsFiles(torrents) != this.searchLimit) if(this.calcTorrentsFiles(torrents) != this.searchLimit)
this.moreSearchFiles = false; this.moreSearchFiles = false;
this.mergeFiles() this.mergeFiles()
this.setState({moreFilesIndicator: false}); this.setState({moreFilesIndicator: false});
this.onSearchUpdate('more files') this.onSearchUpdate('more files')
} }
})); }));
} }
mergeFiles() mergeFiles()
{ {
for(let i = 0; i < this.searchFiles.length; i++) for(let i = 0; i < this.searchFiles.length; i++)
{ {
for(let j = i + 1; j < this.searchFiles.length; j++) for(let j = i + 1; j < this.searchFiles.length; j++)
{ {
if(this.searchFiles[i].hash != this.searchFiles[j].hash) if(this.searchFiles[i].hash != this.searchFiles[j].hash)
continue continue
if(!this.searchFiles[i].remove) if(!this.searchFiles[i].remove)
{ {
this.searchFiles[i].path = this.searchFiles[i].path.concat(this.searchFiles[j].path) this.searchFiles[i].path = this.searchFiles[i].path.concat(this.searchFiles[j].path)
} }
this.searchFiles[j].remove = true this.searchFiles[j].remove = true
} }
} }
this.searchFiles = this.searchFiles.filter(torrent => !torrent.remove) this.searchFiles = this.searchFiles.filter(torrent => !torrent.remove)
} }
componentDidMount() { componentDidMount() {
this.newStatisticFunc = (statistic) => { this.newStatisticFunc = (statistic) => {
if(statistic) { if(statistic) {
this.stats = statistic; this.stats = statistic;
this.forceUpdate(); this.forceUpdate();
} }
}; };
window.torrentSocket.emit('statistic', window.customLoader(this.newStatisticFunc)); window.torrentSocket.emit('statistic', window.customLoader(this.newStatisticFunc));
window.torrentSocket.on('newStatistic', this.newStatisticFunc); window.torrentSocket.on('newStatistic', this.newStatisticFunc);
this.remoteSearchTorrent = (torrents) => { this.remoteSearchTorrent = (torrents) => {
if(!torrents) if(!torrents)
return return
if(torrents.length === this.searchLimit) if(torrents.length === this.searchLimit)
this.moreSearchTorrents = true; this.moreSearchTorrents = true;
this.searchTorrents = _.unionBy(this.searchTorrents, torrents, 'hash') this.searchTorrents = _.unionBy(this.searchTorrents, torrents, 'hash')
this.onSearchUpdate('remote torrents') this.onSearchUpdate('remote torrents')
} }
window.torrentSocket.on('remoteSearchTorrent', this.remoteSearchTorrent); window.torrentSocket.on('remoteSearchTorrent', this.remoteSearchTorrent);
this.remoteSearchFiles = (torrents) => { this.remoteSearchFiles = (torrents) => {
if(!torrents) if(!torrents)
return return
if(torrents.length > 0 && this.calcTorrentsFiles(torrents) === this.searchLimit) if(torrents.length > 0 && this.calcTorrentsFiles(torrents) === this.searchLimit)
this.moreSearchFiles = true; this.moreSearchFiles = true;
this.searchFiles = _.unionBy(this.searchFiles, torrents, 'hash') this.searchFiles = _.unionBy(this.searchFiles, torrents, 'hash')
this.mergeFiles() this.mergeFiles()
this.onSearchUpdate('remote files') this.onSearchUpdate('remote files')
} }
window.torrentSocket.on('remoteSearchFiles', this.remoteSearchFiles); window.torrentSocket.on('remoteSearchFiles', this.remoteSearchFiles);
} }
componentWillUnmount() componentWillUnmount()
{ {
if(this.newStatisticFunc) if(this.newStatisticFunc)
window.torrentSocket.off('newStatistic', this.newStatisticFunc); window.torrentSocket.off('newStatistic', this.newStatisticFunc);
if(this.remoteSearchTorrent) if(this.remoteSearchTorrent)
window.torrentSocket.off('remoteSearchTorrent', this.remoteSearchTorrent); window.torrentSocket.off('remoteSearchTorrent', this.remoteSearchTorrent);
if(this.remoteSearchFiles) if(this.remoteSearchFiles)
window.torrentSocket.off('remoteSearchFiles', this.remoteSearchFiles); window.torrentSocket.off('remoteSearchFiles', this.remoteSearchFiles);
} }
setSafeSearch(ch) { setSafeSearch(ch) {
this.notSafeSearch = ch; this.notSafeSearch = ch;
if(ch) if(ch)
{ {
return {safeSearchText: __('safe search disabled'), safeSearchColor: '#EC407A'} return {safeSearchText: __('safe search disabled'), safeSearchColor: '#EC407A'}
} }
else else
{ {
return {safeSearchText: __('safe search enabled'), safeSearchColor: 'rgb(0, 188, 212)'} return {safeSearchText: __('safe search enabled'), safeSearchColor: 'rgb(0, 188, 212)'}
} }
} }
render() { render() {
const style = { const style = {
refresh: { refresh: {
display: 'inline-block', display: 'inline-block',
position: 'relative', position: 'relative',
}, },
}; };
return ( return (
<div className="column w100p center"> <div className="column w100p center">
<div className='row inline w100p pad0-75 search-row' style={{minWidth: '35em', backgroundColor: 'white', paddingTop: 0, paddingBottom: this.searchError ? 17 : 0, margin: 5, borderRadius: 3}}> <div className='row inline w100p pad0-75 search-row' style={{minWidth: '35em', backgroundColor: 'white', paddingTop: 0, paddingBottom: this.searchError ? 17 : 0, margin: 5, borderRadius: 3}}>
<TextField <TextField
style={{marginTop: -12}} style={{marginTop: -12}}
hintText={__('Search torrent or file')} hintText={__('Search torrent or file')}
floatingLabelText={__('What to search?')} floatingLabelText={__('What to search?')}
fullWidth={true} fullWidth={true}
ref='searchInput' ref='searchInput'
defaultValue={this.searchValue} defaultValue={this.searchValue}
errorText={this.searchError} errorText={this.searchError}
onKeyPress={(e) => { onKeyPress={(e) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
this.search(); this.search();
} }
}} }}
onChange={e => { onChange={e => {
this.searchValue = e.target.value this.searchValue = e.target.value
if(this.searchValue.length < 3 && this.searchValue.length > 0) if(this.searchValue.length < 3 && this.searchValue.length > 0)
this.searchError = __('too short string for search'); this.searchError = __('too short string for search');
else else
this.searchError = undefined; this.searchError = undefined;
this.forceUpdate() this.forceUpdate()
}} }}
/> />
<div style={{width: 25, height: 25, margin: 2}}> <div style={{width: 25, height: 25, margin: 2}}>
<Checkbox <Checkbox
ref='safeSearch' ref='safeSearch'
checked={this.notSafeSearch ? true : false} checked={this.notSafeSearch ? true : false}
checkedIcon={<Visibility />} checkedIcon={<Visibility />}
uncheckedIcon={<VisibilityOff />} uncheckedIcon={<VisibilityOff />}
iconStyle={{fill: this.state.safeSearchColor}} iconStyle={{fill: this.state.safeSearchColor}}
onCheck={(ev, ch) => { onCheck={(ev, ch) => {
this.setState(this.setSafeSearch(ch)); this.setState(this.setSafeSearch(ch));
}} }}
style={{paddingBottom: '0.8em'}} style={{paddingBottom: '0.8em'}}
/> />
</div> </div>
<div style={{width: 25, height: 25, margin: 2}}> <div style={{width: 25, height: 25, margin: 2}}>
<Checkbox <Checkbox
ref='advancedSearch' ref='advancedSearch'
checked={this.state.advancedSearch} checked={this.state.advancedSearch}
checkedIcon={<RemoveIcon />} checkedIcon={<RemoveIcon />}
uncheckedIcon={<AddIcon />} uncheckedIcon={<AddIcon />}
iconStyle={{fill: 'black'}} iconStyle={{fill: 'black'}}
onCheck={(ev, ch) => { onCheck={(ev, ch) => {
this.setState({advancedSearch: ch}); this.setState({advancedSearch: ch});
}} }}
style={{paddingBottom: '0.8em'}} style={{paddingBottom: '0.8em'}}
/> />
</div> </div>
<RaisedButton style={{marginLeft: '10px'}} label={__('Search')} primary={true} onClick={() =>{ <RaisedButton style={{marginLeft: '10px'}} label={__('Search')} primary={true} onClick={() =>{
this.search() this.search()
}} /> }} />
</div> </div>
{ {
this.state.advancedSearch this.state.advancedSearch
&& &&
<AdvancedSearch onChange={(state) => { <AdvancedSearch onChange={(state) => {
this.advanced = state; this.advanced = state;
}} state={this.advanced} /> }} state={this.advanced} />
} }
{ {
this.stats this.stats
&& &&
<TorrentsStatistic stats={this.stats} /> <TorrentsStatistic stats={this.stats} />
} }
{ {
this.state.searchingIndicator this.state.searchingIndicator
? ?
<div className='pad1'> <div className='pad1'>
<RefreshIndicator <RefreshIndicator
size={50} size={50}
left={0} left={0}
top={0} top={0}
loadingColor="#FF9800" loadingColor="#FF9800"
status="loading" status="loading"
style={style.refresh} style={style.refresh}
/> />
</div> </div>
: :
null null
} }
</div> </div>
); );
} }
} }
export default singleton(Search) export default singleton(Search)

View File

@ -1,6 +1,6 @@
export default (Superclass) => { export default (Superclass) => {
let instance; let instance;
return class Singleton extends Superclass { return class Singleton extends Superclass {
constructor(props) { constructor(props) {
super(props) super(props)
@ -14,12 +14,12 @@ export default (Superclass) => {
if (instance) { if (instance) {
return instance; return instance;
} else { } else {
return new Singleton(); return new Singleton();
} }
} }
static do(key, ...params) { static do(key, ...params) {
if ( typeof this.instance()[key] === 'function') { if ( typeof this.instance()[key] === 'function') {
return this.instance()[key](...params); return this.instance()[key](...params);
} else { } else {
return this.instance()[key]; return this.instance()[key];
} }

View File

@ -12,171 +12,171 @@ import {Tabs, Tab} from 'material-ui/Tabs';
import _ from 'lodash' import _ from 'lodash'
export default class TopPage extends Page { export default class TopPage extends Page {
constructor(props) { constructor(props) {
super(props) super(props)
this.setTitle('Rats On The Boat - Torrents top'); this.setTitle('Rats On The Boat - Torrents top');
this.topTorrents = {}; this.topTorrents = {};
this.types = ['main', 'video', 'audio', 'books', 'pictures', 'application', 'archive'] this.types = ['main', 'video', 'audio', 'books', 'pictures', 'application', 'archive']
this.descriptions = { this.descriptions = {
main: __('All'), main: __('All'),
video: __('Video'), video: __('Video'),
audio: __('Audio/Music'), audio: __('Audio/Music'),
books: __('Books'), books: __('Books'),
pictures: __('Pictures/Images'), pictures: __('Pictures/Images'),
application: __('Apps/Games'), application: __('Apps/Games'),
archive: __('Archives') archive: __('Archives')
} }
this.times = { this.times = {
overall: __('Overall'), overall: __('Overall'),
hours: __('Last hour'), hours: __('Last hour'),
week: __('Last week'), week: __('Last week'),
month: __('Last month') month: __('Last month')
} }
this.state = {type: 'main', time: 'overall'} this.state = {type: 'main', time: 'overall'}
} }
loadMoreTorrents(type, time) loadMoreTorrents(type, time)
{ {
time = time ? time : this.state.time time = time ? time : this.state.time
window.torrentSocket.emit('topTorrents', window.torrentSocket.emit('topTorrents',
type == 'main' ? null : type, type == 'main' ? null : type,
{index: (this.topTorrents[type] && this.topTorrents[type][time] && this.topTorrents[type][time].length) || 0, time}, {index: (this.topTorrents[type] && this.topTorrents[type][time] && this.topTorrents[type][time].length) || 0, time},
window.customLoader((data) => { window.customLoader((data) => {
if(!this.topTorrents[type]) if(!this.topTorrents[type])
this.topTorrents[type] = {} this.topTorrents[type] = {}
if(!this.topTorrents[type][time]) if(!this.topTorrents[type][time])
this.topTorrents[type][time] = [] this.topTorrents[type][time] = []
if(data && data.length > 0) if(data && data.length > 0)
{ {
this.topTorrents[type][time] = this.topTorrents[type][time].concat(data); this.topTorrents[type][time] = this.topTorrents[type][time].concat(data);
this._update() this._update()
} }
}) })
) )
} }
_update() _update()
{ {
if(this.timeForce) if(this.timeForce)
return return
this.timeForce = setTimeout(() => { this.timeForce = setTimeout(() => {
delete this.timeForce delete this.timeForce
this.forceUpdate() this.forceUpdate()
}, 550) }, 550)
} }
componentDidMount() componentDidMount()
{ {
super.componentDidMount(); super.componentDidMount();
for(const type of this.types) for(const type of this.types)
{ {
this.loadMoreTorrents(type) this.loadMoreTorrents(type)
} }
this.remoteTopTorrents = ({torrents, type, time}) => { this.remoteTopTorrents = ({torrents, type, time}) => {
if(!torrents) if(!torrents)
return return
time = time ? time : 'overall' time = time ? time : 'overall'
type = type ? type : 'main' type = type ? type : 'main'
this.topTorrents[type][time] = _.orderBy(_.unionBy(this.topTorrents[type][time], torrents, 'hash'), ['seeders'], ['desc']) this.topTorrents[type][time] = _.orderBy(_.unionBy(this.topTorrents[type][time], torrents, 'hash'), ['seeders'], ['desc'])
this._update(); this._update();
} }
window.torrentSocket.on('remoteTopTorrents', this.remoteTopTorrents); window.torrentSocket.on('remoteTopTorrents', this.remoteTopTorrents);
} }
componentWillUnmount() componentWillUnmount()
{ {
if(this.remoteTopTorrents) if(this.remoteTopTorrents)
window.torrentSocket.off('remoteTopTorrents', this.remoteTopTorrents); window.torrentSocket.off('remoteTopTorrents', this.remoteTopTorrents);
} }
render() { render() {
return ( return (
<div> <div>
<div className='column center w100p'> <div className='column center w100p'>
{ {
Object.keys(this.topTorrents).length == 0 Object.keys(this.topTorrents).length == 0
&& &&
<div className='pad0-75 w100p '> <div className='pad0-75 w100p '>
<LinearProgress mode="indeterminate" /> <LinearProgress mode="indeterminate" />
</div> </div>
} }
<Tabs <Tabs
className='w100p' className='w100p'
value={this.state.type} value={this.state.type}
onChange={(type) => { onChange={(type) => {
this.setState({type}); this.setState({type});
// lost other content // lost other content
if(!this.topTorrents[type][this.state.time]) if(!this.topTorrents[type][this.state.time])
{ {
this.loadMoreTorrents(type, this.state.time) this.loadMoreTorrents(type, this.state.time)
} }
}} }}
tabItemContainerStyle={{flexWrap: 'wrap', alignItems: 'stretch'}} tabItemContainerStyle={{flexWrap: 'wrap', alignItems: 'stretch'}}
inkBarStyle={{display: 'none'}} inkBarStyle={{display: 'none'}}
> >
{ {
this.types.map((type, index) => { this.types.map((type, index) => {
if(!this.topTorrents[type]) if(!this.topTorrents[type])
return null; return null;
return ( return (
<Tab buttonStyle={type === this.state.type ? {fontWeight: 'bold'} : undefined} style={{minWidth: 150}} key={index} label={this.descriptions[type]} value={type}> <Tab buttonStyle={type === this.state.type ? {fontWeight: 'bold'} : undefined} style={{minWidth: 150}} key={index} label={this.descriptions[type]} value={type}>
<Tabs <Tabs
className='w100p' className='w100p'
value={this.state.time} value={this.state.time}
onChange={(time) => { onChange={(time) => {
this.setState({time}) this.setState({time})
// lost other content // lost other content
if(!this.topTorrents[type][time]) if(!this.topTorrents[type][time])
{ {
this.loadMoreTorrents(type, time) this.loadMoreTorrents(type, time)
} }
}} }}
tabItemContainerStyle={{flexWrap: 'wrap', alignItems: 'stretch'}} tabItemContainerStyle={{flexWrap: 'wrap', alignItems: 'stretch'}}
inkBarStyle={{display: 'none'}} inkBarStyle={{display: 'none'}}
> >
{ {
Object.keys(this.times).map((time, index) => { Object.keys(this.times).map((time, index) => {
const torrents = this.topTorrents[type][time]; const torrents = this.topTorrents[type][time];
if(!torrents) if(!torrents)
return ( return (
<Tab buttonStyle={time === this.state.time ? {fontWeight: 'bold'} : undefined} style={{minWidth: 150}} key={index} label={this.times[time]} value={time}> <Tab buttonStyle={time === this.state.time ? {fontWeight: 'bold'} : undefined} style={{minWidth: 150}} key={index} label={this.times[time]} value={time}>
<div className='pad0-75 w100p '> <div className='pad0-75 w100p '>
<LinearProgress mode="indeterminate" /> <LinearProgress mode="indeterminate" />
</div> </div>
</Tab> </Tab>
) )
return ( return (
<Tab buttonStyle={time === this.state.time ? {fontWeight: 'bold'} : undefined} style={{minWidth: 150}} key={index} label={this.times[time]} value={time}> <Tab buttonStyle={time === this.state.time ? {fontWeight: 'bold'} : undefined} style={{minWidth: 150}} key={index} label={this.times[time]} value={time}>
<List style={{minWidth: '20em'}}> <List style={{minWidth: '20em'}}>
{ {
torrents.map((torrent, index) => { torrents.map((torrent, index) => {
return <TorrentLine key={index} torrent={torrent} /> return <TorrentLine key={index} torrent={torrent} />
}) })
} }
{ {
torrents.length > 0 torrents.length > 0
&& &&
<div> <div>
<ListItem innerDivStyle={{textAlign: 'center', padding: '1em'}} primaryText={<span>{__('More Torrents')}</span>} onClick={() => { <ListItem innerDivStyle={{textAlign: 'center', padding: '1em'}} primaryText={<span>{__('More Torrents')}</span>} onClick={() => {
this.loadMoreTorrents(type) this.loadMoreTorrents(type)
}} /> }} />
<Divider /> <Divider />
</div> </div>
} }
</List> </List>
</Tab>) </Tab>)
}) })
} }
</Tabs> </Tabs>
</Tab> </Tab>
) )
}) })
} }
</Tabs> </Tabs>
</div> </div>
</div> </div>
); );
} }
} }

View File

@ -53,34 +53,34 @@ const treeToTorrentFiles = (tree) => {
arr.push(<ListItem arr.push(<ListItem
key={file} key={file}
primaryText={file} primaryText={file}
secondaryText={formatBytes(tree[file].__sizeBT)} secondaryText={formatBytes(tree[file].__sizeBT)}
nestedItems={treeToTorrentFiles(tree[file])} nestedItems={treeToTorrentFiles(tree[file])}
primaryTogglesNestedList={true} primaryTogglesNestedList={true}
innerDivStyle={{wordBreak: 'break-word'}} innerDivStyle={{wordBreak: 'break-word'}}
leftIcon={tree[file] && Object.keys(tree[file]).length > 1 ? <FileFolder /> : contentIcon(fileTypeDetect({path: file}))} leftIcon={tree[file] && Object.keys(tree[file]).length > 1 ? <FileFolder /> : contentIcon(fileTypeDetect({path: file}))}
/>); />);
} }
return arr; return arr;
} }
const TorrentFiles = (props) => { const TorrentFiles = (props) => {
let filesList = props.torrent.filesList; let filesList = props.torrent.filesList;
let tree = buildFilesTree(filesList); let tree = buildFilesTree(filesList);
return ( return (
<List className='w100p'> <List className='w100p'>
{ {
filesList.length > 0 filesList.length > 0
? ?
<div className='w100p'> <div className='w100p'>
<Subheader inset={true}>{__('Content of the torrent')}:</Subheader> <Subheader inset={true}>{__('Content of the torrent')}:</Subheader>
{treeToTorrentFiles(tree)} {treeToTorrentFiles(tree)}
</div> </div>
: :
<div className='column center'> <div className='column center'>
<span className='pad0-75'>{__('Processing files')}...</span> <span className='pad0-75'>{__('Processing files')}...</span>
<LinearProgress mode="indeterminate" /> <LinearProgress mode="indeterminate" />
</div> </div>
} }
</List> </List>
); );
}; };
@ -89,84 +89,84 @@ const TorrentInformation = (props) => {
let torrent = props.torrent; let torrent = props.torrent;
return ( return (
<List className='w100p'> <List className='w100p'>
<Subheader inset={true}>{__('Information about torrent')}</Subheader> <Subheader inset={true}>{__('Information about torrent')}</Subheader>
<ListItem <ListItem
//leftAvatar={<Avatar icon={<ActionAssignment />} backgroundColor={blue500} />} //leftAvatar={<Avatar icon={<ActionAssignment />} backgroundColor={blue500} />}
rightIcon={<ActionInfo />} rightIcon={<ActionInfo />}
primaryText={__('Torrent Name')} primaryText={__('Torrent Name')}
secondaryText={<span className='break-word' style={{whiteSpace: 'normal'}}>{torrent.name}</span>} secondaryText={<span className='break-word' style={{whiteSpace: 'normal'}}>{torrent.name}</span>}
/> />
<ListItem <ListItem
// leftAvatar={<Avatar icon={<EditorInsertChart />} backgroundColor={yellow600} />} // leftAvatar={<Avatar icon={<EditorInsertChart />} backgroundColor={yellow600} />}
rightIcon={<ActionInfo />} rightIcon={<ActionInfo />}
primaryText={__('Torrent Size')} primaryText={__('Torrent Size')}
secondaryText={formatBytes(torrent.size)} secondaryText={formatBytes(torrent.size)}
/> />
<ListItem <ListItem
// leftAvatar={<Avatar icon={<EditorInsertChart />} backgroundColor={yellow600} />} // leftAvatar={<Avatar icon={<EditorInsertChart />} backgroundColor={yellow600} />}
rightIcon={<ActionInfo />} rightIcon={<ActionInfo />}
primaryText={__('Torrent contains files')} primaryText={__('Torrent contains files')}
secondaryText={torrent.files} secondaryText={torrent.files}
onClick={() => { onClick={() => {
if(!props.parent) if(!props.parent)
return return
props.parent.setState({ props.parent.setState({
value: 'files' value: 'files'
}) })
}} }}
/> />
<ListItem <ListItem
// leftAvatar={<Avatar icon={<EditorInsertChart />} backgroundColor={yellow600} />} // leftAvatar={<Avatar icon={<EditorInsertChart />} backgroundColor={yellow600} />}
rightIcon={<ActionInfo />} rightIcon={<ActionInfo />}
primaryText={__('Indexed/Added torrent date')} primaryText={__('Indexed/Added torrent date')}
secondaryText={moment(torrent.added * 1000).format('MMMM Do YYYY, hh:mm')} secondaryText={moment(torrent.added * 1000).format('MMMM Do YYYY, hh:mm')}
/> />
<ListItem <ListItem
// leftAvatar={<Avatar icon={<EditorInsertChart />} backgroundColor={yellow600} />} // leftAvatar={<Avatar icon={<EditorInsertChart />} backgroundColor={yellow600} />}
rightIcon={<ActionInfo />} rightIcon={<ActionInfo />}
primaryText={__('Content type')} primaryText={__('Content type')}
secondaryText={torrent.contentType || 'unknown'} secondaryText={torrent.contentType || 'unknown'}
/> />
<ListItem <ListItem
// leftAvatar={<Avatar icon={<EditorInsertChart />} backgroundColor={yellow600} />} // leftAvatar={<Avatar icon={<EditorInsertChart />} backgroundColor={yellow600} />}
rightIcon={<ActionInfo />} rightIcon={<ActionInfo />}
primaryText={__('Category')} primaryText={__('Category')}
secondaryText={torrent.contentCategory || 'unknown'} secondaryText={torrent.contentCategory || 'unknown'}
/> />
</List> </List>
); );
} }
export default class TorrentPage extends Page { export default class TorrentPage extends Page {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
value: 'info', value: 'info',
searchingIndicator: false, searchingIndicator: false,
voting: false, voting: false,
voted: false, voted: false,
askDownloading: false, askDownloading: false,
downloading: false, downloading: false,
downloadProgress: {} downloadProgress: {}
}; };
this.setTitle('Information about torrent'); this.setTitle('Information about torrent');
} }
changeTab(tab) { changeTab(tab) {
if(this.state.value != tab) { if(this.state.value != tab) {
this.setState({ this.setState({
value: tab value: tab
}); });
console.log('change'); console.log('change');
} }
} }
onSwipeRight() { onSwipeRight() {
this.changeTab('files'); this.changeTab('files');
} }
onSwipeLeft() { onSwipeLeft() {
this.changeTab('info'); this.changeTab('info');
} }
handleChange = (value) => { handleChange = (value) => {
if(value == 'main') { if(value == 'main') {
@ -174,18 +174,18 @@ export default class TorrentPage extends Page {
return; return;
} }
this.setState({ this.setState({
value: value, value: value,
}); });
}; };
getTorrentInfo() { getTorrentInfo() {
window.torrentSocket.emit('torrent', this.props.hash, {files: true, peer: this.props.peer}, window.customLoader((data) => { window.torrentSocket.emit('torrent', this.props.hash, {files: true, peer: this.props.peer}, window.customLoader((data) => {
if(data) { if(data) {
this.torrent = data this.torrent = data
this.setTitle(this.torrent.name + ' - Rats On The Boat'); this.setTitle(this.torrent.name + ' - Rats On The Boat');
if(this.torrent.contentCategory == 'xxx') { if(this.torrent.contentCategory == 'xxx') {
this.setMetaTag('robots', 'noindex'); this.setMetaTag('robots', 'noindex');
} }
//this.forceUpdate(); // вызывается через searchingIndicator //this.forceUpdate(); // вызывается через searchingIndicator
// Получаем более новую статистику пира // Получаем более новую статистику пира
@ -194,195 +194,195 @@ export default class TorrentPage extends Page {
} }
} }
}, () => { }, () => {
this.setState({ this.setState({
searchingIndicator: true searchingIndicator: true
}); });
}, () => { }, () => {
this.setState({ this.setState({
searchingIndicator: false searchingIndicator: false
}); });
})); }));
} }
componentDidMount() { componentDidMount() {
super.componentDidMount(); super.componentDidMount();
this.filesUpdated = (hash) => { this.filesUpdated = (hash) => {
if(this.props.hash != hash) if(this.props.hash != hash)
return; return;
this.getTorrentInfo(); this.getTorrentInfo();
} }
window.torrentSocket.on('filesReady', this.filesUpdated); window.torrentSocket.on('filesReady', this.filesUpdated);
this.trackerUpdate = (info) => { this.trackerUpdate = (info) => {
if(this.props.hash != info.hash) if(this.props.hash != info.hash)
return; return;
if(!this.torrent) if(!this.torrent)
return; return;
Object.assign(this.torrent, info); Object.assign(this.torrent, info);
this.forceUpdate(); this.forceUpdate();
} }
window.torrentSocket.on('trackerTorrentUpdate', this.trackerUpdate); window.torrentSocket.on('trackerTorrentUpdate', this.trackerUpdate);
this.onVotes = async ({hash, good, bad, selfVote}) => { this.onVotes = async ({hash, good, bad, selfVote}) => {
if(this.props.hash != hash) if(this.props.hash != hash)
return; return;
if(!this.torrent) if(!this.torrent)
return return
this.torrent.good = good; this.torrent.good = good;
this.torrent.bad = bad; this.torrent.bad = bad;
this.state.voted = selfVote; this.state.voted = selfVote;
this.forceUpdate(); this.forceUpdate();
} }
window.torrentSocket.on('votes', this.onVotes); window.torrentSocket.on('votes', this.onVotes);
this.downloading = (hash) => { this.downloading = (hash) => {
if(this.props.hash != hash) if(this.props.hash != hash)
return; return;
this.setState({downloading: true}) this.setState({downloading: true})
} }
window.torrentSocket.on('downloading', this.downloading); window.torrentSocket.on('downloading', this.downloading);
this.downloadDone = (hash, canceled) => { this.downloadDone = (hash, canceled) => {
if(this.props.hash != hash) if(this.props.hash != hash)
return; return;
this.setState({ this.setState({
downloading: false, downloading: false,
askDownloading: !canceled askDownloading: !canceled
}) })
} }
window.torrentSocket.on('downloadDone', this.downloadDone); window.torrentSocket.on('downloadDone', this.downloadDone);
this.downloadProgress = (hash, progress) => { this.downloadProgress = (hash, progress) => {
if(this.props.hash != hash) if(this.props.hash != hash)
return; return;
this.setState({downloadProgress: progress}) this.setState({downloadProgress: progress})
} }
window.torrentSocket.on('downloadProgress', this.downloadProgress); window.torrentSocket.on('downloadProgress', this.downloadProgress);
this.getTorrentInfo(); this.getTorrentInfo();
} }
componentWillUnmount() { componentWillUnmount() {
if(this.filesUpdated) if(this.filesUpdated)
window.torrentSocket.off('filesReady', this.filesUpdated); window.torrentSocket.off('filesReady', this.filesUpdated);
if(this.trackerUpdate) if(this.trackerUpdate)
window.torrentSocket.off('trackerTorrentUpdate', this.trackerUpdate); window.torrentSocket.off('trackerTorrentUpdate', this.trackerUpdate);
if(this.onVotes) if(this.onVotes)
window.torrentSocket.off('votes', this.onVotes); window.torrentSocket.off('votes', this.onVotes);
if(this.torrent && this.torrent.contentCategory == 'xxx') { if(this.torrent && this.torrent.contentCategory == 'xxx') {
this.removeMetaTag('robots'); this.removeMetaTag('robots');
} }
if(this.downloading) if(this.downloading)
window.torrentSocket.off('downloading', this.downloading); window.torrentSocket.off('downloading', this.downloading);
if(this.downloadDone) if(this.downloadDone)
window.torrentSocket.off('downloadDone', this.downloadDone); window.torrentSocket.off('downloadDone', this.downloadDone);
if(this.downloadProgress) if(this.downloadProgress)
window.torrentSocket.off('downloadProgress', this.downloadProgress); window.torrentSocket.off('downloadProgress', this.downloadProgress);
} }
vote(good) { vote(good) {
if(!this.torrent) if(!this.torrent)
return; return;
this.setState({ this.setState({
voting: true voting: true
}); });
window.torrentSocket.emit('vote', this.torrent.hash, !!good, window.customLoader((success) => { window.torrentSocket.emit('vote', this.torrent.hash, !!good, window.customLoader((success) => {
this.setState({ this.setState({
voted: true, voted: true,
voting: false voting: false
}); });
})); }));
} }
render() { render() {
const style = { const style = {
refresh: { refresh: {
display: 'inline-block', display: 'inline-block',
position: 'relative', position: 'relative',
}, },
}; };
if(this.state.searchingIndicator) { if(this.state.searchingIndicator) {
return ( return (
<div className='pad1 w100p column center'> <div className='pad1 w100p column center'>
<RefreshIndicator <RefreshIndicator
size={50} size={50}
left={0} left={0}
top={0} top={0}
loadingColor="#FF9800" loadingColor="#FF9800"
status="loading" status="loading"
style={style.refresh} style={style.refresh}
/> />
</div> </div>
); );
} }
let torrentRating; let torrentRating;
if(this.torrent) { if(this.torrent) {
torrentRating = Math.round(rating(this.torrent.good, this.torrent.bad) * 100); torrentRating = Math.round(rating(this.torrent.good, this.torrent.bad) * 100);
} }
return ( return (
<div className="w100p column center"> <div className="w100p column center">
{ {
this.torrent this.torrent
? ?
<Tabs <Tabs
className='w100p' className='w100p'
value={this.state.value} value={this.state.value}
onChange={this.handleChange} onChange={this.handleChange}
> >
<Tab label={__('Back to main')} value="main" /> <Tab label={__('Back to main')} value="main" />
<Tab label={__('Information')} value="info" > <Tab label={__('Information')} value="info" >
<div className='column w100p'> <div className='column w100p'>
<div className='row w100p torrent-information-row'> <div className='row w100p torrent-information-row'>
<div className='info-table'> <div className='info-table'>
<TorrentInformation torrent={this.torrent} parent={this} /> <TorrentInformation torrent={this.torrent} parent={this} />
</div> </div>
<div style={{flexBasis: '40%'}} className='column center w100p'> <div style={{flexBasis: '40%'}} className='column center w100p'>
<img src={NoImage} className='pad0-75' style={{height: '200px'}} /> <img src={NoImage} className='pad0-75' style={{height: '200px'}} />
<RaisedButton <RaisedButton
href={`magnet:?xt=urn:btih:${this.torrent.hash}`} href={`magnet:?xt=urn:btih:${this.torrent.hash}`}
target="_self" target="_self"
label="Magnet" label="Magnet"
secondary={true} secondary={true}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
window.open(`magnet:?xt=urn:btih:${this.torrent.hash}`, '_self') 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>} 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>}
/> />
{ {
!this.state.askDownloading && !this.state.downloading !this.state.askDownloading && !this.state.downloading
&& &&
<RaisedButton <RaisedButton
href={`magnet:?xt=urn:btih:${this.torrent.hash}`} href={`magnet:?xt=urn:btih:${this.torrent.hash}`}
target="_self" target="_self"
label={__('Download')} label={__('Download')}
backgroundColor='#00C853' backgroundColor='#00C853'
labelColor='white' labelColor='white'
style={{marginTop: 8}} style={{marginTop: 8}}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
this.setState({askDownloading: true}) this.setState({askDownloading: true})
window.torrentSocket.emit('download', this.torrent) window.torrentSocket.emit('download', this.torrent)
}} }}
icon={ icon={
<svg viewBox="0 0 56 56" fill='white'> <svg viewBox="0 0 56 56" fill='white'>
<g> <g>
<path d="M35.586,41.586L31,46.172V28c0-1.104-0.896-2-2-2s-2,0.896-2,2v18.172l-4.586-4.586c-0.781-0.781-2.047-0.781-2.828,0 <path d="M35.586,41.586L31,46.172V28c0-1.104-0.896-2-2-2s-2,0.896-2,2v18.172l-4.586-4.586c-0.781-0.781-2.047-0.781-2.828,0
s-0.781,2.047,0,2.828l7.999,7.999c0.093,0.094,0.196,0.177,0.307,0.251c0.047,0.032,0.099,0.053,0.148,0.081 s-0.781,2.047,0,2.828l7.999,7.999c0.093,0.094,0.196,0.177,0.307,0.251c0.047,0.032,0.099,0.053,0.148,0.081
c0.065,0.036,0.127,0.075,0.196,0.103c0.065,0.027,0.133,0.042,0.2,0.062c0.058,0.017,0.113,0.04,0.173,0.051 c0.065,0.036,0.127,0.075,0.196,0.103c0.065,0.027,0.133,0.042,0.2,0.062c0.058,0.017,0.113,0.04,0.173,0.051
C28.738,52.986,28.869,53,29,53s0.262-0.014,0.392-0.04c0.06-0.012,0.115-0.034,0.173-0.051c0.067-0.02,0.135-0.035,0.2-0.062 C28.738,52.986,28.869,53,29,53s0.262-0.014,0.392-0.04c0.06-0.012,0.115-0.034,0.173-0.051c0.067-0.02,0.135-0.035,0.2-0.062
c0.069-0.028,0.131-0.067,0.196-0.103c0.05-0.027,0.101-0.049,0.148-0.081c0.11-0.074,0.213-0.157,0.307-0.251l7.999-7.999 c0.069-0.028,0.131-0.067,0.196-0.103c0.05-0.027,0.101-0.049,0.148-0.081c0.11-0.074,0.213-0.157,0.307-0.251l7.999-7.999
c0.781-0.781,0.781-2.047,0-2.828S36.367,40.805,35.586,41.586z"/> c0.781-0.781,0.781-2.047,0-2.828S36.367,40.805,35.586,41.586z"/>
<path d="M47.835,18.986c-0.137-0.019-2.457-0.335-4.684,0.002C43.1,18.996,43.049,19,42.999,19c-0.486,0-0.912-0.354-0.987-0.85 <path d="M47.835,18.986c-0.137-0.019-2.457-0.335-4.684,0.002C43.1,18.996,43.049,19,42.999,19c-0.486,0-0.912-0.354-0.987-0.85
c-0.083-0.546,0.292-1.056,0.838-1.139c1.531-0.233,3.062-0.196,4.083-0.124C46.262,9.135,39.83,3,32.085,3 c-0.083-0.546,0.292-1.056,0.838-1.139c1.531-0.233,3.062-0.196,4.083-0.124C46.262,9.135,39.83,3,32.085,3
C27.388,3,22.667,5.379,19.8,9.129C21.754,10.781,23,13.246,23,16c0,0.553-0.447,1-1,1s-1-0.447-1-1 C27.388,3,22.667,5.379,19.8,9.129C21.754,10.781,23,13.246,23,16c0,0.553-0.447,1-1,1s-1-0.447-1-1
c0-2.462-1.281-4.627-3.209-5.876c-0.227-0.147-0.462-0.277-0.702-0.396c-0.069-0.034-0.139-0.069-0.21-0.101 c0-2.462-1.281-4.627-3.209-5.876c-0.227-0.147-0.462-0.277-0.702-0.396c-0.069-0.034-0.139-0.069-0.21-0.101
@ -391,120 +391,120 @@ export default class TorrentPage extends Page {
l0.012,0.21l-0.009,0.16C7.008,16.744,7,16.873,7,17v0.63l-0.567,0.271C2.705,19.688,0,24,0,28.154C0,34.135,4.865,39,10.845,39H25 l0.012,0.21l-0.009,0.16C7.008,16.744,7,16.873,7,17v0.63l-0.567,0.271C2.705,19.688,0,24,0,28.154C0,34.135,4.865,39,10.845,39H25
V28c0-2.209,1.791-4,4-4s4,1.791,4,4v11h2.353c0.059,0,0.116-0.005,0.174-0.009l0.198-0.011l0.271,0.011 V28c0-2.209,1.791-4,4-4s4,1.791,4,4v11h2.353c0.059,0,0.116-0.005,0.174-0.009l0.198-0.011l0.271,0.011
C36.053,38.995,36.11,39,36.169,39h9.803C51.501,39,56,34.501,56,28.972C56,24.161,52.49,19.872,47.835,18.986z"/> C36.053,38.995,36.11,39,36.169,39h9.803C51.501,39,56,34.501,56,28.972C56,24.161,52.49,19.872,47.835,18.986z"/>
</g> </g>
</svg> </svg>
} }
/> />
} }
{ {
this.state.downloading this.state.downloading
&& &&
<div className='column center pad0-75' style={{width: '300px'}}> <div className='column center pad0-75' style={{width: '300px'}}>
<div className='fs0-75' style={{color: 'rgb(0, 188, 212)'}}>{__('downloading')} {this.state.downloadProgress && (this.state.downloadProgress.progress * 100).toFixed(1)}%</div> <div className='fs0-75' style={{color: 'rgb(0, 188, 212)'}}>{__('downloading')} {this.state.downloadProgress && (this.state.downloadProgress.progress * 100).toFixed(1)}%</div>
<LinearProgress <LinearProgress
style={{marginTop: 3}} style={{marginTop: 3}}
mode="determinate" mode="determinate"
value={this.state.downloadProgress && (this.state.downloadProgress.progress ? this.state.downloadProgress.progress : 0) * 100} value={this.state.downloadProgress && (this.state.downloadProgress.progress ? this.state.downloadProgress.progress : 0) * 100}
/> />
<FlatButton <FlatButton
onClick={() => { onClick={() => {
window.torrentSocket.emit('downloadCancel', this.torrent.hash) window.torrentSocket.emit('downloadCancel', this.torrent.hash)
}} }}
label={__('Cancel download')} label={__('Cancel download')}
secondary={true} secondary={true}
icon={<svg fill='rgb(255, 64, 129)' viewBox="0 0 18 18"><path d="M9 1C4.58 1 1 4.58 1 9s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm4 10.87L11.87 13 9 10.13 6.13 13 5 11.87 7.87 9 5 6.13 6.13 5 9 7.87 11.87 5 13 6.13 10.13 9 13 11.87z"/></svg>} icon={<svg fill='rgb(255, 64, 129)' viewBox="0 0 18 18"><path d="M9 1C4.58 1 1 4.58 1 9s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm4 10.87L11.87 13 9 10.13 6.13 13 5 11.87 7.87 9 5 6.13 6.13 5 9 7.87 11.87 5 13 6.13 10.13 9 13 11.87z"/></svg>}
/> />
</div> </div>
} }
<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> <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>
{ {
this.torrent.seeders || this.torrent.leechers || this.torrent.completed this.torrent.seeders || this.torrent.leechers || this.torrent.completed
? ?
<div className='fs0-85 pad0-75 center column'> <div className='fs0-85 pad0-75 center column'>
<div className='pad0-25' style={{color: '#00C853'}}>{__('seeders')}: {this.torrent.seeders}</div> <div className='pad0-25' style={{color: '#00C853'}}>{__('seeders')}: {this.torrent.seeders}</div>
<div className='pad0-25' style={{color: '#AA00FF'}}>{__('leechers')}: {this.torrent.leechers}</div> <div className='pad0-25' style={{color: '#AA00FF'}}>{__('leechers')}: {this.torrent.leechers}</div>
<div className='pad0-25' style={{color: '#FF6D00'}}>{__('completed')}: {this.torrent.completed}</div> <div className='pad0-25' style={{color: '#FF6D00'}}>{__('completed')}: {this.torrent.completed}</div>
</div> </div>
: :
null null
} }
{ {
!this.state.voted && !this.state.voting !this.state.voted && !this.state.voting
? ?
<div className='row pad0-25'> <div className='row pad0-25'>
<RaisedButton <RaisedButton
label={__('Good') + ` (${this.torrent.good})`} label={__('Good') + ` (${this.torrent.good})`}
labelColor="white" labelColor="white"
backgroundColor="#00C853" backgroundColor="#00C853"
icon={ icon={
<svg viewBox="0 0 489.543 489.543" fill="white"> <svg viewBox="0 0 489.543 489.543" fill="white">
<g> <g>
<path d="M270.024,0c-22.6,0-15,48.3-15,48.3s-48.3,133.2-94.5,168.7c-9.9,10.4-16.1,21.9-20,31.3l0,0l0,0 <path d="M270.024,0c-22.6,0-15,48.3-15,48.3s-48.3,133.2-94.5,168.7c-9.9,10.4-16.1,21.9-20,31.3l0,0l0,0
c-0.9,2.3-1.7,4.5-2.4,6.5c-3.1,6.3-9.7,16-23.8,24.5l46.2,200.9c0,0,71.5,9.3,143.2,7.8c28.7,2.3,59.1,2.5,83.3-2.7 c-0.9,2.3-1.7,4.5-2.4,6.5c-3.1,6.3-9.7,16-23.8,24.5l46.2,200.9c0,0,71.5,9.3,143.2,7.8c28.7,2.3,59.1,2.5,83.3-2.7
c82.2-17.5,61.6-74.8,61.6-74.8c44.3-33.3,19.1-74.9,19.1-74.9c39.4-41.1,0.7-75.6,0.7-75.6s21.3-33.2-6.2-58.3 c82.2-17.5,61.6-74.8,61.6-74.8c44.3-33.3,19.1-74.9,19.1-74.9c39.4-41.1,0.7-75.6,0.7-75.6s21.3-33.2-6.2-58.3
c-34.3-31.4-127.4-10.5-127.4-10.5l0,0c-6.5,1.1-13.4,2.5-20.8,4.3c0,0-32.2,15,0-82.7C346.324,15.1,292.624,0,270.024,0z"/> c-34.3-31.4-127.4-10.5-127.4-10.5l0,0c-6.5,1.1-13.4,2.5-20.8,4.3c0,0-32.2,15,0-82.7C346.324,15.1,292.624,0,270.024,0z"/>
<path d="M127.324,465.7l-35-166.3c-2-9.5-11.6-17.3-21.3-17.3h-66.8l-0.1,200.8h109.1C123.024,483,129.324,475.2,127.324,465.7z" <path d="M127.324,465.7l-35-166.3c-2-9.5-11.6-17.3-21.3-17.3h-66.8l-0.1,200.8h109.1C123.024,483,129.324,475.2,127.324,465.7z"
/> />
</g> </g>
</svg> </svg>
} }
onClick={() => this.vote(true)} onClick={() => this.vote(true)}
/> />
<RaisedButton <RaisedButton
style={{marginLeft: '9px'}} style={{marginLeft: '9px'}}
label={__('Bad') + ` (${this.torrent.bad})`} label={__('Bad') + ` (${this.torrent.bad})`}
labelColor="white" labelColor="white"
backgroundColor="#D50000" backgroundColor="#D50000"
icon={ icon={
<svg viewBox="0 0 487.643 487.643" fill="white"> <svg viewBox="0 0 487.643 487.643" fill="white">
<g> <g>
<path d="M113.869,209.443l46-200.1c0,0,71.2-9.3,142.6-7.8c28.5-2.3,58.9-2.5,83,2.7c81.9,17.4,61.4,74.5,61.4,74.5 <path d="M113.869,209.443l46-200.1c0,0,71.2-9.3,142.6-7.8c28.5-2.3,58.9-2.5,83,2.7c81.9,17.4,61.4,74.5,61.4,74.5
c44.2,33.2,19,74.6,19,74.6c39.2,41,0.7,75.3,0.7,75.3s21.2,33-6.1,58c-34.2,31.2-126.9,10.5-126.9,10.5l0,0 c44.2,33.2,19,74.6,19,74.6c39.2,41,0.7,75.3,0.7,75.3s21.2,33-6.1,58c-34.2,31.2-126.9,10.5-126.9,10.5l0,0
c-6.4-1.1-13.3-2.5-20.7-4.2c0,0-32.1-15,0,82.4s-21.4,112.3-43.9,112.3s-15-48.1-15-48.1s-48.1-132.7-94.1-168 c-6.4-1.1-13.3-2.5-20.7-4.2c0,0-32.1-15,0,82.4s-21.4,112.3-43.9,112.3s-15-48.1-15-48.1s-48.1-132.7-94.1-168
c-9.9-10.4-16.1-21.8-19.9-31.2l0,0l0,0c-0.9-2.3-1.7-4.5-2.4-6.5C134.469,227.543,127.869,217.843,113.869,209.443z c-9.9-10.4-16.1-21.8-19.9-31.2l0,0l0,0c-0.9-2.3-1.7-4.5-2.4-6.5C134.469,227.543,127.869,217.843,113.869,209.443z
M70.869,206.643c9.7,0,19.2-7.7,21.2-17.2l34.8-165.6c2-9.5-4.3-17.2-14-17.2H4.169l0.1,200H70.869z"/> M70.869,206.643c9.7,0,19.2-7.7,21.2-17.2l34.8-165.6c2-9.5-4.3-17.2-14-17.2H4.169l0.1,200H70.869z"/>
</g> </g>
</svg> </svg>
} }
onClick={() => this.vote(false)} onClick={() => this.vote(false)}
/> />
</div> </div>
: :
this.state.voting this.state.voting
? ?
<div>voting...</div> <div>voting...</div>
: :
<div>Thank you for voting!</div> <div>Thank you for voting!</div>
} }
{ {
this.torrent.good > 0 || this.torrent.bad > 0 this.torrent.good > 0 || this.torrent.bad > 0
? ?
<div className='w100p' style={{padding: '7px 35px', marginTop: '10px'}}> <div className='w100p' style={{padding: '7px 35px', marginTop: '10px'}}>
<LinearProgress <LinearProgress
mode="determinate" mode="determinate"
value={torrentRating} value={torrentRating}
color={torrentRating >= 50 ? '#00E676' : '#FF3D00'} color={torrentRating >= 50 ? '#00E676' : '#FF3D00'}
style={{ style={{
height: '5px', height: '5px',
}} }}
/> />
<div className='row center pad0-75 fs0-85' style={{color: torrentRating >= 50 ? '#00E676' : '#FF3D00'}}>{__('Torrent rating')}: {torrentRating}%</div> <div className='row center pad0-75 fs0-85' style={{color: torrentRating >= 50 ? '#00E676' : '#FF3D00'}}>{__('Torrent rating')}: {torrentRating}%</div>
</div> </div>
: :
null null
} }
</div> </div>
</div> </div>
</div> </div>
</Tab> </Tab>
<Tab label={__('Files')} value="files" > <Tab label={__('Files')} value="files" >
<TorrentFiles torrent={this.torrent} /> <TorrentFiles torrent={this.torrent} />
</Tab> </Tab>
</Tabs> </Tabs>
: :
null null
} }
</div> </div>
); );
} }
} }

View File

@ -2,37 +2,37 @@ import React, { Component } from 'react';
import formatBytes from './format-bytes' import formatBytes from './format-bytes'
export default class TorrentsStatistic extends Component { export default class TorrentsStatistic extends Component {
constructor(props) constructor(props)
{ {
super(props) super(props)
this.stats = props.stats || {} this.stats = props.stats || {}
} }
componentDidMount() componentDidMount()
{ {
this.newTorrentFunc = (torrent) => { this.newTorrentFunc = (torrent) => {
this.stats.size += torrent.size; this.stats.size += torrent.size;
this.stats.torrents++; this.stats.torrents++;
this.stats.files += torrent.files; this.stats.files += torrent.files;
this.forceUpdate() this.forceUpdate()
} }
window.torrentSocket.on('newTorrent', this.newTorrentFunc); window.torrentSocket.on('newTorrent', this.newTorrentFunc);
} }
componentWillUnmount() componentWillUnmount()
{ {
if(this.newTorrentFunc) if(this.newTorrentFunc)
window.torrentSocket.off('newTorrent', this.newTorrentFunc); window.torrentSocket.off('newTorrent', this.newTorrentFunc);
} }
render() render()
{ {
return ( return (
<div style={{position: 'relative', width: '100%', height: 0}}> <div style={{position: 'relative', width: '100%', height: 0}}>
<div className='column w100p counter-statistic' style={{backgroundColor: 'rgba(0,0,0,0.7)', padding: 8, borderRadius: 4, marginTop: 2}}> <div className='column w100p counter-statistic' style={{backgroundColor: 'rgba(0,0,0,0.7)', padding: 8, borderRadius: 4, marginTop: 2}}>
<div className='row w100p' style={{backgroundColor: 'rgba(0,0,0,0.7)', padding: 8, borderRadius: 4}}> <div className='row w100p' style={{backgroundColor: 'rgba(0,0,0,0.7)', padding: 8, borderRadius: 4}}>
<div className='row inline' style={{color: '#e5f442', fontSize: '1.15em', fill: '#e5f442'}}> <div className='row inline' style={{color: '#e5f442', fontSize: '1.15em', fill: '#e5f442'}}>
<svg viewBox="0 0 60 60"> <svg viewBox="0 0 60 60">
<path d="M35,0C23.849,0,14.43,2.588,11.215,6.475C4.669,8.077,0.884,10.775,0.146,13.51C0.062,13.657,0,13.818,0,14v0.5V26v0.5V27 <path d="M35,0C23.849,0,14.43,2.588,11.215,6.475C4.669,8.077,0.884,10.775,0.146,13.51C0.062,13.657,0,13.818,0,14v0.5V26v0.5V27
v11v0.5V39v12c0,0.162,0.043,0.315,0.117,0.451C1.298,56.346,11.864,60,25,60c11.24,0,20.579-2.68,23.786-6.518 v11v0.5V39v12c0,0.162,0.043,0.315,0.117,0.451C1.298,56.346,11.864,60,25,60c11.24,0,20.579-2.68,23.786-6.518
c6.359-1.546,10.366-4.076,11.09-7C59.955,46.34,60,46.175,60,46V34v-0.5V22v-0.5v-12C60,4.895,51.238,0,35,0z M47.805,39.348 c6.359-1.546,10.366-4.076,11.09-7C59.955,46.34,60,46.175,60,46V34v-0.5V22v-0.5v-12C60,4.895,51.238,0,35,0z M47.805,39.348
c-0.04,0.099-0.089,0.198-0.143,0.297c-0.067,0.123-0.142,0.246-0.231,0.369c-0.066,0.093-0.141,0.185-0.219,0.277 c-0.04,0.099-0.089,0.198-0.143,0.297c-0.067,0.123-0.142,0.246-0.231,0.369c-0.066,0.093-0.141,0.185-0.219,0.277
@ -128,115 +128,115 @@ export default class TorrentsStatistic extends Component {
c-0.317,1.941-3.314,3.891-7.972,5.255C49.999,51.063,50,51.031,50,51v-9.828c0.043-0.012,0.083-0.025,0.126-0.037 c-0.317,1.941-3.314,3.891-7.972,5.255C49.999,51.063,50,51.031,50,51v-9.828c0.043-0.012,0.083-0.025,0.126-0.037
c0.4-0.112,0.79-0.227,1.168-0.346c0.004-0.001,0.009-0.003,0.013-0.004c2.961-0.936,5.22-2.099,6.693-3.427v8.346 c0.4-0.112,0.79-0.227,1.168-0.346c0.004-0.001,0.009-0.003,0.013-0.004c2.961-0.936,5.22-2.099,6.693-3.427v8.346
C57.986,45.747,57.976,45.792,57.968,45.838z"/> C57.986,45.747,57.976,45.792,57.968,45.838z"/>
</svg> </svg>
<div style={{marginLeft: '5px'}}>{ formatBytes(this.stats.size, 1) } {__('data')}</div> <div style={{marginLeft: '5px'}}>{ formatBytes(this.stats.size, 1) } {__('data')}</div>
</div> </div>
<div className='row inline' style={{color: '#f48641', fontSize: '1.15em', fill: '#f48641', marginLeft: '20px'}}> <div className='row inline' style={{color: '#f48641', fontSize: '1.15em', fill: '#f48641', marginLeft: '20px'}}>
<svg viewBox="0 0 60 60"> <svg viewBox="0 0 60 60">
<g> <g>
<path d="M60,8.311c0-0.199-0.052-0.382-0.131-0.551c-0.027-0.209-0.112-0.412-0.254-0.579L53.46,0H6.54L0.384,7.182 <path d="M60,8.311c0-0.199-0.052-0.382-0.131-0.551c-0.027-0.209-0.112-0.412-0.254-0.579L53.46,0H6.54L0.384,7.182
C0.242,7.348,0.157,7.55,0.131,7.76C0.052,7.929,0,8.112,0,8.311V19h3v41h54V19h3V8.311z M7.46,2h45.08l4.286,5H3.174L7.46,2z C0.242,7.348,0.157,7.55,0.131,7.76C0.052,7.929,0,8.112,0,8.311V19h3v41h54V19h3V8.311z M7.46,2h45.08l4.286,5H3.174L7.46,2z
M55,58H5V19h50V58z M58,17h-1H3H2V9h56V17z"/> M55,58H5V19h50V58z M58,17h-1H3H2V9h56V17z"/>
<path d="M42,23H18v10h24V23z M40,31H20v-6h20V31z"/> <path d="M42,23H18v10h24V23z M40,31H20v-6h20V31z"/>
<path d="M45,38H15v14h30V38z M43,50H17V40h26V50z"/> <path d="M45,38H15v14h30V38z M43,50H17V40h26V50z"/>
<path d="M22,48h5c0.552,0,1-0.447,1-1s-0.448-1-1-1h-5c-0.552,0-1,0.447-1,1S21.448,48,22,48z"/> <path d="M22,48h5c0.552,0,1-0.447,1-1s-0.448-1-1-1h-5c-0.552,0-1,0.447-1,1S21.448,48,22,48z"/>
<path d="M27,44h11c0.552,0,1-0.447,1-1s-0.448-1-1-1H27c-0.552,0-1,0.447-1,1S26.448,44,27,44z"/> <path d="M27,44h11c0.552,0,1-0.447,1-1s-0.448-1-1-1H27c-0.552,0-1,0.447-1,1S26.448,44,27,44z"/>
<path d="M22,44c0.26,0,0.52-0.11,0.71-0.29C22.89,43.52,23,43.26,23,43c0-0.261-0.11-0.521-0.29-0.71c-0.38-0.37-1.04-0.37-1.42,0 <path d="M22,44c0.26,0,0.52-0.11,0.71-0.29C22.89,43.52,23,43.26,23,43c0-0.261-0.11-0.521-0.29-0.71c-0.38-0.37-1.04-0.37-1.42,0
C21.11,42.479,21,42.739,21,43c0,0.27,0.11,0.52,0.29,0.71C21.48,43.89,21.73,44,22,44z"/> C21.11,42.479,21,42.739,21,43c0,0.27,0.11,0.52,0.29,0.71C21.48,43.89,21.73,44,22,44z"/>
<path d="M31.29,46.29C31.11,46.479,31,46.739,31,47c0,0.26,0.11,0.52,0.29,0.71C31.48,47.89,31.74,48,32,48 <path d="M31.29,46.29C31.11,46.479,31,46.739,31,47c0,0.26,0.11,0.52,0.29,0.71C31.48,47.89,31.74,48,32,48
c0.26,0,0.52-0.11,0.71-0.29C32.89,47.52,33,47.26,33,47c0-0.261-0.11-0.521-0.29-0.71C32.34,45.92,31.66,45.92,31.29,46.29z"/> c0.26,0,0.52-0.11,0.71-0.29C32.89,47.52,33,47.26,33,47c0-0.261-0.11-0.521-0.29-0.71C32.34,45.92,31.66,45.92,31.29,46.29z"/>
<path d="M38,46h-1c-0.552,0-1,0.447-1,1s0.448,1,1,1h1c0.552,0,1-0.447,1-1S38.552,46,38,46z"/> <path d="M38,46h-1c-0.552,0-1,0.447-1,1s0.448,1,1,1h1c0.552,0,1-0.447,1-1S38.552,46,38,46z"/>
</g> </g>
</svg> </svg>
<div style={{marginLeft: '5px'}}>{this.stats.torrents} {__('torrents')}</div> <div style={{marginLeft: '5px'}}>{this.stats.torrents} {__('torrents')}</div>
</div> </div>
<div className='row inline' style={{color: '#f441e2', fontSize: '1.15em', fill: '#f441e2', marginLeft: '20px'}}> <div className='row inline' style={{color: '#f441e2', fontSize: '1.15em', fill: '#f441e2', marginLeft: '20px'}}>
<svg viewBox="0 0 60 60"> <svg viewBox="0 0 60 60">
<g> <g>
<path d="M42.5,22h-25c-0.552,0-1,0.447-1,1s0.448,1,1,1h25c0.552,0,1-0.447,1-1S43.052,22,42.5,22z"/> <path d="M42.5,22h-25c-0.552,0-1,0.447-1,1s0.448,1,1,1h25c0.552,0,1-0.447,1-1S43.052,22,42.5,22z"/>
<path d="M17.5,16h10c0.552,0,1-0.447,1-1s-0.448-1-1-1h-10c-0.552,0-1,0.447-1,1S16.948,16,17.5,16z"/> <path d="M17.5,16h10c0.552,0,1-0.447,1-1s-0.448-1-1-1h-10c-0.552,0-1,0.447-1,1S16.948,16,17.5,16z"/>
<path d="M42.5,30h-25c-0.552,0-1,0.447-1,1s0.448,1,1,1h25c0.552,0,1-0.447,1-1S43.052,30,42.5,30z"/> <path d="M42.5,30h-25c-0.552,0-1,0.447-1,1s0.448,1,1,1h25c0.552,0,1-0.447,1-1S43.052,30,42.5,30z"/>
<path d="M42.5,38h-25c-0.552,0-1,0.447-1,1s0.448,1,1,1h25c0.552,0,1-0.447,1-1S43.052,38,42.5,38z"/> <path d="M42.5,38h-25c-0.552,0-1,0.447-1,1s0.448,1,1,1h25c0.552,0,1-0.447,1-1S43.052,38,42.5,38z"/>
<path d="M42.5,46h-25c-0.552,0-1,0.447-1,1s0.448,1,1,1h25c0.552,0,1-0.447,1-1S43.052,46,42.5,46z"/> <path d="M42.5,46h-25c-0.552,0-1,0.447-1,1s0.448,1,1,1h25c0.552,0,1-0.447,1-1S43.052,46,42.5,46z"/>
<path d="M38.914,0H6.5v60h47V14.586L38.914,0z M39.5,3.414L50.086,14H39.5V3.414z M8.5,58V2h29v14h14v42H8.5z"/> <path d="M38.914,0H6.5v60h47V14.586L38.914,0z M39.5,3.414L50.086,14H39.5V3.414z M8.5,58V2h29v14h14v42H8.5z"/>
</g> </g>
</svg> </svg>
<div style={{marginLeft: '5px'}}>{this.stats.files} {__('files')}</div> <div style={{marginLeft: '5px'}}>{this.stats.files} {__('files')}</div>
</div> </div>
</div> </div>
<div className='row w100p' style={{backgroundColor: 'rgba(0,0,0,0.7)', padding: 8, borderRadius: 4, marginTop: 5}}> <div className='row w100p' style={{backgroundColor: 'rgba(0,0,0,0.7)', padding: 8, borderRadius: 4, marginTop: 5}}>
<div className='row inline' style={{color: window.peers > 0 ? '#19c632' : 'white', fontSize: '1.15em', fill: window.peers > 0 ? '#19c632' : 'white'}}> <div className='row inline' style={{color: window.peers > 0 ? '#19c632' : 'white', fontSize: '1.15em', fill: window.peers > 0 ? '#19c632' : 'white'}}>
<svg viewBox="0 0 47 47"> <svg viewBox="0 0 47 47">
<g> <g>
<path d="M17.567,15.938l-2.859-2.702c0.333-0.605,0.539-1.29,0.539-2.029c0-2.342-1.897-4.239-4.24-4.239 <path d="M17.567,15.938l-2.859-2.702c0.333-0.605,0.539-1.29,0.539-2.029c0-2.342-1.897-4.239-4.24-4.239
c-2.343,0-4.243,1.896-4.243,4.239c0,2.343,1.9,4.241,4.243,4.241c0.826,0,1.59-0.246,2.242-0.654l2.855,2.699 c-2.343,0-4.243,1.896-4.243,4.239c0,2.343,1.9,4.241,4.243,4.241c0.826,0,1.59-0.246,2.242-0.654l2.855,2.699
C16.536,16.922,17.023,16.399,17.567,15.938z"/> C16.536,16.922,17.023,16.399,17.567,15.938z"/>
<path d="M29.66,15.6l3.799-6.393c0.374,0.107,0.762,0.184,1.169,0.184c2.347,0,4.244-1.898,4.244-4.241 <path d="M29.66,15.6l3.799-6.393c0.374,0.107,0.762,0.184,1.169,0.184c2.347,0,4.244-1.898,4.244-4.241
c0-2.342-1.897-4.239-4.244-4.239c-2.343,0-4.239,1.896-4.239,4.239c0,1.163,0.469,2.214,1.227,2.981l-3.787,6.375 c0-2.342-1.897-4.239-4.244-4.239c-2.343,0-4.239,1.896-4.239,4.239c0,1.163,0.469,2.214,1.227,2.981l-3.787,6.375
C28.48,14.801,29.094,15.169,29.66,15.6z"/> C28.48,14.801,29.094,15.169,29.66,15.6z"/>
<path d="M42.762,20.952c-1.824,0-3.369,1.159-3.968,2.775l-5.278-0.521c0,0.04,0.006,0.078,0.006,0.117 <path d="M42.762,20.952c-1.824,0-3.369,1.159-3.968,2.775l-5.278-0.521c0,0.04,0.006,0.078,0.006,0.117
c0,0.688-0.076,1.36-0.213,2.009l5.276,0.521c0.319,2.024,2.062,3.576,4.177,3.576c2.342,0,4.238-1.896,4.238-4.238 c0,0.688-0.076,1.36-0.213,2.009l5.276,0.521c0.319,2.024,2.062,3.576,4.177,3.576c2.342,0,4.238-1.896,4.238-4.238
C47,22.85,45.104,20.952,42.762,20.952z"/> C47,22.85,45.104,20.952,42.762,20.952z"/>
<path d="M28.197,37.624l-1.18-5.156c-0.666,0.232-1.359,0.398-2.082,0.481l1.182,5.157c-1.355,0.709-2.29,2.11-2.29,3.746 <path d="M28.197,37.624l-1.18-5.156c-0.666,0.232-1.359,0.398-2.082,0.481l1.182,5.157c-1.355,0.709-2.29,2.11-2.29,3.746
c0,2.342,1.896,4.237,4.243,4.237c2.342,0,4.238-1.896,4.238-4.237C32.311,39.553,30.479,37.692,28.197,37.624z"/> c0,2.342,1.896,4.237,4.243,4.237c2.342,0,4.238-1.896,4.238-4.237C32.311,39.553,30.479,37.692,28.197,37.624z"/>
<path d="M14.357,25.37l-6.57,2.201c-0.758-1.158-2.063-1.926-3.548-1.926C1.896,25.645,0,27.542,0,29.884 <path d="M14.357,25.37l-6.57,2.201c-0.758-1.158-2.063-1.926-3.548-1.926C1.896,25.645,0,27.542,0,29.884
c0,2.345,1.896,4.242,4.239,4.242c2.341,0,4.242-1.897,4.242-4.242c0-0.098-0.021-0.188-0.029-0.284l6.591-2.207 c0,2.345,1.896,4.242,4.239,4.242c2.341,0,4.242-1.897,4.242-4.242c0-0.098-0.021-0.188-0.029-0.284l6.591-2.207
C14.746,26.752,14.51,26.077,14.357,25.37z"/> C14.746,26.752,14.51,26.077,14.357,25.37z"/>
<circle cx="23.83" cy="23.323" r="7.271"/> <circle cx="23.83" cy="23.323" r="7.271"/>
</g> </g>
</svg> </svg>
<div style={{marginLeft: '5px'}}>{window.peers} {__('peers')}</div> <div style={{marginLeft: '5px'}}>{window.peers} {__('peers')}</div>
</div> </div>
<div className='row inline' style={{color: window.peersTorrents > 0 ? '#19c687' : 'white', fontSize: '1.15em', fill: window.peersTorrents > 0 ? '#19c687' : 'white', marginLeft: '20px'}}> <div className='row inline' style={{color: window.peersTorrents > 0 ? '#19c687' : 'white', fontSize: '1.15em', fill: window.peersTorrents > 0 ? '#19c687' : 'white', marginLeft: '20px'}}>
<svg viewBox="0 0 481.6 481.6"> <svg viewBox="0 0 481.6 481.6">
<g> <g>
<path d="M381.6,309.4c-27.7,0-52.4,13.2-68.2,33.6l-132.3-73.9c3.1-8.9,4.8-18.5,4.8-28.4c0-10-1.7-19.5-4.9-28.5l132.2-73.8 <path d="M381.6,309.4c-27.7,0-52.4,13.2-68.2,33.6l-132.3-73.9c3.1-8.9,4.8-18.5,4.8-28.4c0-10-1.7-19.5-4.9-28.5l132.2-73.8
c15.7,20.5,40.5,33.8,68.3,33.8c47.4,0,86.1-38.6,86.1-86.1S429,0,381.5,0s-86.1,38.6-86.1,86.1c0,10,1.7,19.6,4.9,28.5 c15.7,20.5,40.5,33.8,68.3,33.8c47.4,0,86.1-38.6,86.1-86.1S429,0,381.5,0s-86.1,38.6-86.1,86.1c0,10,1.7,19.6,4.9,28.5
l-132.1,73.8c-15.7-20.6-40.5-33.8-68.3-33.8c-47.4,0-86.1,38.6-86.1,86.1s38.7,86.1,86.2,86.1c27.8,0,52.6-13.3,68.4-33.9 l-132.1,73.8c-15.7-20.6-40.5-33.8-68.3-33.8c-47.4,0-86.1,38.6-86.1,86.1s38.7,86.1,86.2,86.1c27.8,0,52.6-13.3,68.4-33.9
l132.2,73.9c-3.2,9-5,18.7-5,28.7c0,47.4,38.6,86.1,86.1,86.1s86.1-38.6,86.1-86.1S429.1,309.4,381.6,309.4z M381.6,27.1 l132.2,73.9c-3.2,9-5,18.7-5,28.7c0,47.4,38.6,86.1,86.1,86.1s86.1-38.6,86.1-86.1S429.1,309.4,381.6,309.4z M381.6,27.1
c32.6,0,59.1,26.5,59.1,59.1s-26.5,59.1-59.1,59.1s-59.1-26.5-59.1-59.1S349.1,27.1,381.6,27.1z M100,299.8 c32.6,0,59.1,26.5,59.1,59.1s-26.5,59.1-59.1,59.1s-59.1-26.5-59.1-59.1S349.1,27.1,381.6,27.1z M100,299.8
c-32.6,0-59.1-26.5-59.1-59.1s26.5-59.1,59.1-59.1s59.1,26.5,59.1,59.1S132.5,299.8,100,299.8z M381.6,454.5 c-32.6,0-59.1-26.5-59.1-59.1s26.5-59.1,59.1-59.1s59.1,26.5,59.1,59.1S132.5,299.8,100,299.8z M381.6,454.5
c-32.6,0-59.1-26.5-59.1-59.1c0-32.6,26.5-59.1,59.1-59.1s59.1,26.5,59.1,59.1C440.7,428,414.2,454.5,381.6,454.5z"/> c-32.6,0-59.1-26.5-59.1-59.1c0-32.6,26.5-59.1,59.1-59.1s59.1,26.5,59.1,59.1C440.7,428,414.2,454.5,381.6,454.5z"/>
</g> </g>
</svg> </svg>
<div style={{marginLeft: '5px'}}>{window.peersTorrents} {__('remote torrents')}</div> <div style={{marginLeft: '5px'}}>{window.peersTorrents} {__('remote torrents')}</div>
</div> </div>
<div className='row inline' style={{color: window.p2pStatus == 2 ? '#78c619' : (window.p2pStatus == 1 ? '#c68319' : '#c6194a'), fontSize: '1.15em', fill: window.p2pStatus == 2 ? '#78c619' : (window.p2pStatus == 1 ? '#c68319' : '#c6194a'), marginLeft: '20px'}}> <div className='row inline' style={{color: window.p2pStatus == 2 ? '#78c619' : (window.p2pStatus == 1 ? '#c68319' : '#c6194a'), fontSize: '1.15em', fill: window.p2pStatus == 2 ? '#78c619' : (window.p2pStatus == 1 ? '#c68319' : '#c6194a'), marginLeft: '20px'}}>
{ {
window.p2pStatus == 0 window.p2pStatus == 0
&& &&
<div className='row inline w100p'> <div className='row inline w100p'>
<svg viewBox="0 0 54.908 54.908"> <svg viewBox="0 0 54.908 54.908">
<g> <g>
<path d="M54.615,19.123c-7.243-7.244-16.89-11.233-27.161-11.233S7.537,11.878,0.293,19.123c-0.391,0.391-0.391,1.023,0,1.414 <path d="M54.615,19.123c-7.243-7.244-16.89-11.233-27.161-11.233S7.537,11.878,0.293,19.123c-0.391,0.391-0.391,1.023,0,1.414
s1.023,0.391,1.414,0C8.573,13.67,17.717,9.889,27.454,9.889s18.881,3.781,25.747,10.647c0.195,0.195,0.451,0.293,0.707,0.293 s1.023,0.391,1.414,0C8.573,13.67,17.717,9.889,27.454,9.889s18.881,3.781,25.747,10.647c0.195,0.195,0.451,0.293,0.707,0.293
s0.512-0.098,0.707-0.293C55.006,20.146,55.006,19.513,54.615,19.123z"/> s0.512-0.098,0.707-0.293C55.006,20.146,55.006,19.513,54.615,19.123z"/>
<path d="M6.171,25c-0.391,0.391-0.391,1.023,0,1.414c0.195,0.195,0.451,0.293,0.707,0.293s0.512-0.098,0.707-0.293 <path d="M6.171,25c-0.391,0.391-0.391,1.023,0,1.414c0.195,0.195,0.451,0.293,0.707,0.293s0.512-0.098,0.707-0.293
c10.955-10.956,28.781-10.956,39.737,0c0.391,0.391,1.023,0.391,1.414,0s0.391-1.023,0-1.414C37.002,13.266,17.907,13.264,6.171,25 c10.955-10.956,28.781-10.956,39.737,0c0.391,0.391,1.023,0.391,1.414,0s0.391-1.023,0-1.414C37.002,13.266,17.907,13.264,6.171,25
z"/> z"/>
<path d="M27.454,24.508c-5.825,0-11.295,2.263-15.404,6.371c-0.391,0.391-0.391,1.023,0,1.414s1.023,0.391,1.414,0 <path d="M27.454,24.508c-5.825,0-11.295,2.263-15.404,6.371c-0.391,0.391-0.391,1.023,0,1.414s1.023,0.391,1.414,0
c3.731-3.73,8.699-5.785,13.99-5.785c5.291,0,10.259,2.055,13.99,5.785c0.195,0.195,0.451,0.293,0.707,0.293 c3.731-3.73,8.699-5.785,13.99-5.785c5.291,0,10.259,2.055,13.99,5.785c0.195,0.195,0.451,0.293,0.707,0.293
s0.512-0.098,0.707-0.293c0.391-0.391,0.391-1.023,0-1.414C38.75,26.771,33.279,24.508,27.454,24.508z"/> s0.512-0.098,0.707-0.293c0.391-0.391,0.391-1.023,0-1.414C38.75,26.771,33.279,24.508,27.454,24.508z"/>
<path d="M27.454,33.916c-3.612,0-6.551,2.939-6.551,6.552s2.939,6.552,6.551,6.552c3.613,0,6.552-2.939,6.552-6.552 <path d="M27.454,33.916c-3.612,0-6.551,2.939-6.551,6.552s2.939,6.552,6.551,6.552c3.613,0,6.552-2.939,6.552-6.552
S31.067,33.916,27.454,33.916z M27.454,45.019c-2.51,0-4.551-2.042-4.551-4.552s2.042-4.552,4.551-4.552s4.552,2.042,4.552,4.552 S31.067,33.916,27.454,33.916z M27.454,45.019c-2.51,0-4.551-2.042-4.551-4.552s2.042-4.552,4.551-4.552s4.552,2.042,4.552,4.552
S29.964,45.019,27.454,45.019z"/> S29.964,45.019,27.454,45.019z"/>
</g> </g>
</svg> </svg>
<div style={{marginLeft: '5px'}}>{__('not available')}</div> <div style={{marginLeft: '5px'}}>{__('not available')}</div>
</div> </div>
} }
{ {
window.p2pStatus == 1 window.p2pStatus == 1
&& &&
<div className='row inline w100p'> <div className='row inline w100p'>
<svg viewBox="0 0 611.989 611.988"> <svg viewBox="0 0 611.989 611.988">
<g> <g>
<path d="M305.994,417.769c-30.85,0-55.887,25.037-55.887,55.887s25.038,55.887,55.887,55.887s55.887-25.037,55.887-55.887 <path d="M305.994,417.769c-30.85,0-55.887,25.037-55.887,55.887s25.038,55.887,55.887,55.887s55.887-25.037,55.887-55.887
S336.843,417.769,305.994,417.769z M605.436,222.369C530.697,133.434,421.549,82.446,305.994,82.446 S336.843,417.769,305.994,417.769z M605.436,222.369C530.697,133.434,421.549,82.446,305.994,82.446
S81.309,133.434,6.551,222.369c-9.93,11.811-8.402,29.434,3.428,39.363c5.234,4.396,11.587,6.558,17.939,6.558 S81.309,133.434,6.551,222.369c-9.93,11.811-8.402,29.434,3.428,39.363c5.234,4.396,11.587,6.558,17.939,6.558
c7.973,0,15.891-3.391,21.423-9.967c64.084-76.248,157.639-119.989,256.652-119.989c99.013,0,192.568,43.741,256.651,119.971 c7.973,0,15.891-3.391,21.423-9.967c64.084-76.248,157.639-119.989,256.652-119.989c99.013,0,192.568,43.741,256.651,119.971
@ -249,18 +249,18 @@ export default class TorrentsStatistic extends Component {
c7.973,0,15.891-3.39,21.405-9.966c21.368-25.429,52.552-40.016,85.544-40.016s64.177,14.587,85.544,40.016 c7.973,0,15.891-3.39,21.405-9.966c21.368-25.429,52.552-40.016,85.544-40.016s64.177,14.587,85.544,40.016
c5.533,6.595,13.45,9.966,21.405,9.966c6.353,0,12.724-2.142,17.958-6.557c11.83-9.93,13.357-27.553,3.428-39.363 c5.533,6.595,13.45,9.966,21.405,9.966c6.353,0,12.724-2.142,17.958-6.557c11.83-9.93,13.357-27.553,3.428-39.363
C402.324,327.846,355.546,305.994,305.994,305.994z"/> C402.324,327.846,355.546,305.994,305.994,305.994z"/>
</g> </g>
</svg> </svg>
<div style={{marginLeft: '5px'}}>{__('redirect')}</div> <div style={{marginLeft: '5px'}}>{__('redirect')}</div>
</div> </div>
} }
{ {
window.p2pStatus == 2 window.p2pStatus == 2
&& &&
<div className='row inline w100p'> <div className='row inline w100p'>
<svg viewBox="0 0 611.989 611.988"> <svg viewBox="0 0 611.989 611.988">
<g> <g>
<path d="M305.994,417.769c-30.85,0-55.887,25.037-55.887,55.887s25.038,55.887,55.887,55.887s55.887-25.037,55.887-55.887 <path d="M305.994,417.769c-30.85,0-55.887,25.037-55.887,55.887s25.038,55.887,55.887,55.887s55.887-25.037,55.887-55.887
S336.843,417.769,305.994,417.769z M605.436,222.369C530.697,133.434,421.549,82.446,305.994,82.446 S336.843,417.769,305.994,417.769z M605.436,222.369C530.697,133.434,421.549,82.446,305.994,82.446
S81.309,133.434,6.551,222.369c-9.93,11.811-8.402,29.434,3.428,39.363c5.234,4.396,11.587,6.558,17.939,6.558 S81.309,133.434,6.551,222.369c-9.93,11.811-8.402,29.434,3.428,39.363c5.234,4.396,11.587,6.558,17.939,6.558
c7.973,0,15.891-3.391,21.423-9.967c64.084-76.248,157.639-119.989,256.652-119.989c99.013,0,192.568,43.741,256.651,119.971 c7.973,0,15.891-3.391,21.423-9.967c64.084-76.248,157.639-119.989,256.652-119.989c99.013,0,192.568,43.741,256.651,119.971
@ -273,16 +273,16 @@ export default class TorrentsStatistic extends Component {
c7.973,0,15.891-3.39,21.405-9.966c21.368-25.429,52.552-40.016,85.544-40.016s64.177,14.587,85.544,40.016 c7.973,0,15.891-3.39,21.405-9.966c21.368-25.429,52.552-40.016,85.544-40.016s64.177,14.587,85.544,40.016
c5.533,6.595,13.45,9.966,21.405,9.966c6.353,0,12.724-2.142,17.958-6.557c11.83-9.93,13.357-27.553,3.428-39.363 c5.533,6.595,13.45,9.966,21.405,9.966c6.353,0,12.724-2.142,17.958-6.557c11.83-9.93,13.357-27.553,3.428-39.363
C402.324,327.846,355.546,305.994,305.994,305.994z"/> C402.324,327.846,355.546,305.994,305.994,305.994z"/>
</g> </g>
</svg> </svg>
<div style={{marginLeft: '5px'}}>{__('direct')}</div> <div style={{marginLeft: '5px'}}>{__('direct')}</div>
</div> </div>
} }
</div> </div>
</div> </div>
</div> </div>
</div> </div>
) )
} }
} }

View File

@ -11,12 +11,12 @@ import LinearProgress from 'material-ui/LinearProgress';
let rating = require('./rating'); let rating = require('./rating');
const contentIcon = (type, category, fill = 'grey') => { const contentIcon = (type, category, fill = 'grey') => {
if(category == 'xxx') if(category == 'xxx')
{ {
return ( return (
<svg viewBox="0 0 18.282 18.282" fill={fill}> <svg viewBox="0 0 18.282 18.282" fill={fill}>
<g> <g>
<path d="M16.435,3.832H1.847C0.827,3.832,0,4.659,0,5.678v6.925c0,1.021,0.827,1.848,1.847,1.848h14.588 <path d="M16.435,3.832H1.847C0.827,3.832,0,4.659,0,5.678v6.925c0,1.021,0.827,1.848,1.847,1.848h14.588
c1.021,0,1.847-0.827,1.847-1.848V5.678C18.282,4.659,17.455,3.832,16.435,3.832z M3.194,7.123H2.583v4.042h0.611v0.54H1.876V6.583 c1.021,0,1.847-0.827,1.847-1.848V5.678C18.282,4.659,17.455,3.832,16.435,3.832z M3.194,7.123H2.583v4.042h0.611v0.54H1.876V6.583
h1.318V7.123z M6.197,10.986l-0.392-0.784C5.644,9.9,5.541,9.676,5.419,9.425H5.406c-0.09,0.251-0.199,0.476-0.334,0.777 h1.318V7.123z M6.197,10.986l-0.392-0.784C5.644,9.9,5.541,9.676,5.419,9.425H5.406c-0.09,0.251-0.199,0.476-0.334,0.777
l-0.36,0.784H3.593l1.254-2.191L3.638,6.654h1.125l0.379,0.791C5.27,7.709,5.367,7.921,5.47,8.165h0.013 l-0.36,0.784H3.593l1.254-2.191L3.638,6.654h1.125l0.379,0.791C5.27,7.709,5.367,7.921,5.47,8.165h0.013
@ -27,87 +27,87 @@ const contentIcon = (type, category, fill = 'grey') => {
c-0.16-0.302-0.263-0.526-0.386-0.777h-0.012c-0.091,0.251-0.2,0.476-0.335,0.777l-0.36,0.784h-1.117l1.253-2.191l-1.209-2.141 c-0.16-0.302-0.263-0.526-0.386-0.777h-0.012c-0.091,0.251-0.2,0.476-0.335,0.777l-0.36,0.784h-1.117l1.253-2.191l-1.209-2.141
h1.125l0.379,0.791c0.129,0.264,0.226,0.476,0.328,0.72h0.013c0.104-0.276,0.187-0.47,0.296-0.72l0.366-0.791h1.118l-1.221,2.114 h1.125l0.379,0.791c0.129,0.264,0.226,0.476,0.328,0.72h0.013c0.104-0.276,0.187-0.47,0.296-0.72l0.366-0.791h1.118l-1.221,2.114
l1.286,2.218C15.071,10.986,13.94,10.986,13.94,10.986z M16.756,11.705h-1.311v-0.54h0.611V7.116h-0.611v-0.54h1.311V11.705z"/> l1.286,2.218C15.071,10.986,13.94,10.986,13.94,10.986z M16.756,11.705h-1.311v-0.54h0.611V7.116h-0.611v-0.54h1.311V11.705z"/>
</g> </g>
</svg> </svg>
) )
} }
switch(type) switch(type)
{ {
case 'video': case 'video':
return ( return (
<svg viewBox="0 0 491.858 491.858" fill={fill}> <svg viewBox="0 0 491.858 491.858" fill={fill}>
<path d="M357.714,423.331c0,9.328-10.676,16.891-23.847,16.891H23.847C10.676,440.222,0,432.659,0,423.331V203.735 <path d="M357.714,423.331c0,9.328-10.676,16.891-23.847,16.891H23.847C10.676,440.222,0,432.659,0,423.331V203.735
c0-9.33,10.676-16.892,23.847-16.892h310.02c13.171,0,23.847,7.564,23.847,16.892V423.331L357.714,423.331z"/> c0-9.33,10.676-16.892,23.847-16.892h310.02c13.171,0,23.847,7.564,23.847,16.892V423.331L357.714,423.331z"/>
<circle cx="89.428" cy="118.706" r="59.619"/> <circle cx="89.428" cy="118.706" r="59.619"/>
<circle cx="253.381" cy="103.801" r="74.524"/> <circle cx="253.381" cy="103.801" r="74.524"/>
<path d="M491.858,447.677c0,0-1.986,14.904-15.899,14.904c-13.912,0-103.34-83.42-103.34-94.397V258.882 <path d="M491.858,447.677c0,0-1.986,14.904-15.899,14.904c-13.912,0-103.34-83.42-103.34-94.397V258.882
c0-10.976,87.443-94.398,103.34-94.398c15.899,0,15.899,14.905,15.899,14.905V447.677z"/> c0-10.976,87.443-94.398,103.34-94.398c15.899,0,15.899,14.905,15.899,14.905V447.677z"/>
</svg> </svg>
) )
case 'audio': case 'audio':
return ( return (
<svg viewBox="0 0 46 46" fill={fill}> <svg viewBox="0 0 46 46" fill={fill}>
<path d="M28.38,0c-0.551,0-1.097,0.153-1.579,0.444c-0.046,0.027-0.09,0.059-0.13,0.093L13.121,12H2.487c-0.553,0-1,0.447-1,1v19 <path d="M28.38,0c-0.551,0-1.097,0.153-1.579,0.444c-0.046,0.027-0.09,0.059-0.13,0.093L13.121,12H2.487c-0.553,0-1,0.447-1,1v19
c0,0.553,0.447,1,1,1h10.61L26.64,45.436c0.05,0.046,0.104,0.086,0.161,0.12C27.284,45.847,27.83,46,28.38,46 c0,0.553,0.447,1,1,1h10.61L26.64,45.436c0.05,0.046,0.104,0.086,0.161,0.12C27.284,45.847,27.83,46,28.38,46
c1.713,0,3.106-1.416,3.106-3.156V3.156C31.487,1.416,30.093,0,28.38,0z M14.487,31c0,0.553-0.447,1-1,1s-1-0.447-1-1v-4 c1.713,0,3.106-1.416,3.106-3.156V3.156C31.487,1.416,30.093,0,28.38,0z M14.487,31c0,0.553-0.447,1-1,1s-1-0.447-1-1v-4
c0-0.553,0.447-1,1-1s1,0.447,1,1V31z M14.487,18c0,0.553-0.447,1-1,1s-1-0.447-1-1v-4c0-0.553,0.447-1,1-1s1,0.447,1,1V18z"/> c0-0.553,0.447-1,1-1s1,0.447,1,1V31z M14.487,18c0,0.553-0.447,1-1,1s-1-0.447-1-1v-4c0-0.553,0.447-1,1-1s1,0.447,1,1V18z"/>
<path d="M44.513,22.5c0-5.972-4.009-11.302-9.749-12.962c-0.533-0.151-1.084,0.152-1.238,0.684 <path d="M44.513,22.5c0-5.972-4.009-11.302-9.749-12.962c-0.533-0.151-1.084,0.152-1.238,0.684
c-0.153,0.53,0.152,1.085,0.684,1.238c4.889,1.413,8.304,5.953,8.304,11.04s-3.415,9.627-8.304,11.04 c-0.153,0.53,0.152,1.085,0.684,1.238c4.889,1.413,8.304,5.953,8.304,11.04s-3.415,9.627-8.304,11.04
c-0.531,0.153-0.837,0.708-0.684,1.238c0.127,0.438,0.526,0.723,0.961,0.723c0.092,0,0.185-0.013,0.277-0.039 c-0.531,0.153-0.837,0.708-0.684,1.238c0.127,0.438,0.526,0.723,0.961,0.723c0.092,0,0.185-0.013,0.277-0.039
C40.504,33.802,44.513,28.472,44.513,22.5z"/> C40.504,33.802,44.513,28.472,44.513,22.5z"/>
</svg> </svg>
) )
case 'pictures': case 'pictures':
return ( return (
<svg viewBox="0 0 58 58" fill={fill}> <svg viewBox="0 0 58 58" fill={fill}>
<path d="M57,6H1C0.448,6,0,6.447,0,7v44c0,0.553,0.448,1,1,1h56c0.552,0,1-0.447,1-1V7C58,6.447,57.552,6,57,6z M16,17 <path d="M57,6H1C0.448,6,0,6.447,0,7v44c0,0.553,0.448,1,1,1h56c0.552,0,1-0.447,1-1V7C58,6.447,57.552,6,57,6z M16,17
c3.071,0,5.569,2.498,5.569,5.569c0,3.07-2.498,5.568-5.569,5.568s-5.569-2.498-5.569-5.568C10.431,19.498,12.929,17,16,17z c3.071,0,5.569,2.498,5.569,5.569c0,3.07-2.498,5.568-5.569,5.568s-5.569-2.498-5.569-5.568C10.431,19.498,12.929,17,16,17z
M52.737,35.676c-0.373,0.406-1.006,0.435-1.413,0.062L40.063,25.414l-9.181,10.054l4.807,4.807c0.391,0.391,0.391,1.023,0,1.414 M52.737,35.676c-0.373,0.406-1.006,0.435-1.413,0.062L40.063,25.414l-9.181,10.054l4.807,4.807c0.391,0.391,0.391,1.023,0,1.414
s-1.023,0.391-1.414,0L23.974,31.389L7.661,45.751C7.471,45.918,7.235,46,7,46c-0.277,0-0.553-0.114-0.751-0.339 s-1.023,0.391-1.414,0L23.974,31.389L7.661,45.751C7.471,45.918,7.235,46,7,46c-0.277,0-0.553-0.114-0.751-0.339
c-0.365-0.415-0.325-1.047,0.09-1.412l17.017-14.982c0.396-0.348,0.994-0.329,1.368,0.044l4.743,4.743l9.794-10.727 c-0.365-0.415-0.325-1.047,0.09-1.412l17.017-14.982c0.396-0.348,0.994-0.329,1.368,0.044l4.743,4.743l9.794-10.727
c0.179-0.196,0.429-0.313,0.694-0.325c0.264-0.006,0.524,0.083,0.72,0.262l12,11C53.083,34.636,53.11,35.269,52.737,35.676z"/> c0.179-0.196,0.429-0.313,0.694-0.325c0.264-0.006,0.524,0.083,0.72,0.262l12,11C53.083,34.636,53.11,35.269,52.737,35.676z"/>
</svg> </svg>
) )
case 'application': case 'application':
return ( return (
<svg viewBox="0 0 483.85 483.85" fill={fill}> <svg viewBox="0 0 483.85 483.85" fill={fill}>
<path d="M471.325,211.856l-56.9-56.9c-23.4-23.4-9.1-48.1,16.4-49.6c42-2.6,65.6-47.4,31.3-84.7c-37.3-34.2-81.9-10.7-84.5,31.2 <path d="M471.325,211.856l-56.9-56.9c-23.4-23.4-9.1-48.1,16.4-49.6c42-2.6,65.6-47.4,31.3-84.7c-37.3-34.2-81.9-10.7-84.5,31.2
c-1.6,25.5-26.5,39.9-49.8,16.6l-55.7-55.7c-16.7-16.7-43.8-16.7-60.5,0l-56.4,56.4c-23.4,23.4-48.2,8.9-49.8-16.6 c-1.6,25.5-26.5,39.9-49.8,16.6l-55.7-55.7c-16.7-16.7-43.8-16.7-60.5,0l-56.4,56.4c-23.4,23.4-48.2,8.9-49.8-16.6
c-2.6-42-47.6-65.9-84.9-31.6c-34.4,37.4-10.5,82.4,31.5,85c25.5,1.6,40,26.5,16.7,49.9l-56.2,56.1c-16.7,16.7-16.7,43.8,0,60.5 c-2.6-42-47.6-65.9-84.9-31.6c-34.4,37.4-10.5,82.4,31.5,85c25.5,1.6,40,26.5,16.7,49.9l-56.2,56.1c-16.7,16.7-16.7,43.8,0,60.5
l55.7,55.7c23.4,23.3,9.5,47.6-16,49.2c-42,2.6-65.5,47.3-31.2,84.6c37.3,34.3,81.8,10.9,84.4-31.1c1.6-25.5,26-39.5,49.4-16.2 l55.7,55.7c23.4,23.3,9.5,47.6-16,49.2c-42,2.6-65.5,47.3-31.2,84.6c37.3,34.3,81.8,10.9,84.4-31.1c1.6-25.5,26-39.5,49.4-16.2
l56.2,56.2c17,17,44.8,17,61.8,0.1l39.4-39.4l16.9-16.9c22.1-23.1,7.8-47.4-17.4-49c-42-2.6-65.8-47.6-31.5-84.9 l56.2,56.2c17,17,44.8,17,61.8,0.1l39.4-39.4l16.9-16.9c22.1-23.1,7.8-47.4-17.4-49c-42-2.6-65.8-47.6-31.5-84.9
c37.3-34.3,82.3-10.4,84.9,31.6c1.6,25.2,25.8,39.4,48.9,17.3l15.3-15.3l41.2-41.2c0.1-0.1,0.1-0.1,0.2-0.2l0.6-0.6 c37.3-34.3,82.3-10.4,84.9,31.6c1.6,25.2,25.8,39.4,48.9,17.3l15.3-15.3l41.2-41.2c0.1-0.1,0.1-0.1,0.2-0.2l0.6-0.6
C488.025,255.656,488.025,228.556,471.325,211.856z"/> C488.025,255.656,488.025,228.556,471.325,211.856z"/>
</svg> </svg>
) )
case 'books': case 'books':
return ( return (
<svg viewBox="0 0 296.999 296.999" fill={fill}> <svg viewBox="0 0 296.999 296.999" fill={fill}>
<g> <g>
<path d="M45.432,35.049c-0.008,0-0.017,0-0.025,0c-2.809,0-5.451,1.095-7.446,3.085c-2.017,2.012-3.128,4.691-3.128,7.543 <path d="M45.432,35.049c-0.008,0-0.017,0-0.025,0c-2.809,0-5.451,1.095-7.446,3.085c-2.017,2.012-3.128,4.691-3.128,7.543
v159.365c0,5.844,4.773,10.61,10.641,10.625c24.738,0.059,66.184,5.215,94.776,35.136V84.023c0-1.981-0.506-3.842-1.461-5.382 v159.365c0,5.844,4.773,10.61,10.641,10.625c24.738,0.059,66.184,5.215,94.776,35.136V84.023c0-1.981-0.506-3.842-1.461-5.382
C115.322,40.849,70.226,35.107,45.432,35.049z"/> C115.322,40.849,70.226,35.107,45.432,35.049z"/>
<path d="M262.167,205.042V45.676c0-2.852-1.111-5.531-3.128-7.543c-1.995-1.99-4.639-3.085-7.445-3.085c-0.009,0-0.018,0-0.026,0 <path d="M262.167,205.042V45.676c0-2.852-1.111-5.531-3.128-7.543c-1.995-1.99-4.639-3.085-7.445-3.085c-0.009,0-0.018,0-0.026,0
c-24.793,0.059-69.889,5.801-93.357,43.593c-0.955,1.54-1.46,3.401-1.46,5.382v166.779 c-24.793,0.059-69.889,5.801-93.357,43.593c-0.955,1.54-1.46,3.401-1.46,5.382v166.779
c28.592-29.921,70.038-35.077,94.776-35.136C257.394,215.651,262.167,210.885,262.167,205.042z"/> c28.592-29.921,70.038-35.077,94.776-35.136C257.394,215.651,262.167,210.885,262.167,205.042z"/>
<path d="M286.373,71.801h-7.706v133.241c0,14.921-12.157,27.088-27.101,27.125c-20.983,0.05-55.581,4.153-80.084,27.344 <path d="M286.373,71.801h-7.706v133.241c0,14.921-12.157,27.088-27.101,27.125c-20.983,0.05-55.581,4.153-80.084,27.344
c42.378-10.376,87.052-3.631,112.512,2.171c3.179,0.724,6.464-0.024,9.011-2.054c2.538-2.025,3.994-5.052,3.994-8.301V82.427 c42.378-10.376,87.052-3.631,112.512,2.171c3.179,0.724,6.464-0.024,9.011-2.054c2.538-2.025,3.994-5.052,3.994-8.301V82.427
C297,76.568,292.232,71.801,286.373,71.801z"/> C297,76.568,292.232,71.801,286.373,71.801z"/>
<path d="M18.332,205.042V71.801h-7.706C4.768,71.801,0,76.568,0,82.427v168.897c0,3.25,1.456,6.276,3.994,8.301 <path d="M18.332,205.042V71.801h-7.706C4.768,71.801,0,76.568,0,82.427v168.897c0,3.25,1.456,6.276,3.994,8.301
c2.545,2.029,5.827,2.78,9.011,2.054c25.46-5.803,70.135-12.547,112.511-2.171c-24.502-23.19-59.1-27.292-80.083-27.342 c2.545,2.029,5.827,2.78,9.011,2.054c25.46-5.803,70.135-12.547,112.511-2.171c-24.502-23.19-59.1-27.292-80.083-27.342
C30.49,232.13,18.332,219.963,18.332,205.042z"/> C30.49,232.13,18.332,219.963,18.332,205.042z"/>
</g> </g>
</svg> </svg>
) )
case 'archive': case 'archive':
return ( return (
<svg viewBox="0 0 390 390" fill={fill}> <svg viewBox="0 0 390 390" fill={fill}>
<g> <g>
<path d="M182.681,205.334c0,5.21,4.227,9.436,9.436,9.436h5.765c5.21,0,9.436-4.226,9.436-9.436c0-5.211-4.226-9.438-9.436-9.438 <path d="M182.681,205.334c0,5.21,4.227,9.436,9.436,9.436h5.765c5.21,0,9.436-4.226,9.436-9.436c0-5.211-4.226-9.438-9.436-9.438
h-5.765C186.908,195.897,182.681,200.123,182.681,205.334z"/> h-5.765C186.908,195.897,182.681,200.123,182.681,205.334z"/>
<path d="M383.889,126.058c-4.478-5.191-10.868-8.314-17.674-8.686V64.899c0-25.562-20.797-46.359-46.361-46.359h-75.278 <path d="M383.889,126.058c-4.478-5.191-10.868-8.314-17.674-8.686V64.899c0-25.562-20.797-46.359-46.361-46.359h-75.278
c-25.052,0-38.351,10.578-48.062,18.303c-7.807,6.208-12.518,9.955-22.626,9.955H65.099c-22.78,0-41.313,18.062-41.313,40.264 c-25.052,0-38.351,10.578-48.062,18.303c-7.807,6.208-12.518,9.955-22.626,9.955H65.099c-22.78,0-41.313,18.062-41.313,40.264
v30.311c-6.806,0.371-13.196,3.494-17.674,8.686c-4.78,5.541-6.912,12.888-5.839,20.125l30.194,203.803 v30.311c-6.806,0.371-13.196,3.494-17.674,8.686c-4.78,5.541-6.912,12.888-5.839,20.125l30.194,203.803
c1.828,12.338,12.417,21.475,24.89,21.475h279.286c12.473,0,23.063-9.137,24.891-21.475l30.195-203.802 c1.828,12.338,12.417,21.475,24.89,21.475h279.286c12.473,0,23.063-9.137,24.891-21.475l30.195-203.802
@ -123,115 +123,115 @@ const contentIcon = (type, category, fill = 'grey') => {
h-32.443c-2.399,0-4.345-1.944-4.345-4.345V279.28c0-2.4,1.946-4.346,4.345-4.346h32.443c2.399,0,4.345,1.945,4.345,4.346V289.708z h-32.443c-2.399,0-4.345-1.944-4.345-4.345V279.28c0-2.4,1.946-4.346,4.345-4.346h32.443c2.399,0,4.345,1.945,4.345,4.346V289.708z
M335.919,117.333H54.081V87.062c0-6.239,5.602-9.968,11.019-9.968h108.788c20.687,0,32.219-9.173,41.484-16.542 M335.919,117.333H54.081V87.062c0-6.239,5.602-9.968,11.019-9.968h108.788c20.687,0,32.219-9.173,41.484-16.542
c8.552-6.803,14.732-11.717,29.204-11.717h75.278c8.858,0,16.066,7.206,16.066,16.064V117.333z"/> c8.552-6.803,14.732-11.717,29.204-11.717h75.278c8.858,0,16.066,7.206,16.066,16.064V117.333z"/>
</g> </g>
</svg> </svg>
) )
case 'disc': case 'disc':
return ( return (
<svg viewBox="0 0 49.652 49.652" fill={fill}> <svg viewBox="0 0 49.652 49.652" fill={fill}>
<g> <g>
<circle cx="24.826" cy="24.825" r="3.529"/> <circle cx="24.826" cy="24.825" r="3.529"/>
<path d="M42.381,7.271C37.693,2.582,31.458,0,24.826,0C18.195,0,11.96,2.583,7.271,7.271c-9.68,9.68-9.68,25.43,0,35.11 <path d="M42.381,7.271C37.693,2.582,31.458,0,24.826,0C18.195,0,11.96,2.583,7.271,7.271c-9.68,9.68-9.68,25.43,0,35.11
c4.689,4.688,10.923,7.271,17.555,7.271c6.632,0,12.867-2.582,17.555-7.271C52.061,32.701,52.061,16.951,42.381,7.271z c4.689,4.688,10.923,7.271,17.555,7.271c6.632,0,12.867-2.582,17.555-7.271C52.061,32.701,52.061,16.951,42.381,7.271z
M24.86,45.002l0.039-12.587c-1.967,0.019-3.941-0.719-5.442-2.22c-2.965-2.965-2.964-7.772,0-10.737 M24.86,45.002l0.039-12.587c-1.967,0.019-3.941-0.719-5.442-2.22c-2.965-2.965-2.964-7.772,0-10.737
c0.022-0.022,0.047-0.04,0.069-0.062l-8.935-8.936c4.059-4.072,9.234-6.027,14.363-5.91l-0.039,12.689 c0.022-0.022,0.047-0.04,0.069-0.062l-8.935-8.936c4.059-4.072,9.234-6.027,14.363-5.91l-0.039,12.689
c1.915,0.022,3.82,0.759,5.28,2.219c2.942,2.942,2.96,7.699,0.063,10.668l8.967,8.968C35.166,43.164,29.99,45.119,24.86,45.002z" c1.915,0.022,3.82,0.759,5.28,2.219c2.942,2.942,2.96,7.699,0.063,10.668l8.967,8.968C35.166,43.164,29.99,45.119,24.86,45.002z"
/> />
</g> </g>
</svg> </svg>
) )
default: default:
return ( return (
<svg viewBox="0 0 123.769 123.769" fill={fill}> <svg viewBox="0 0 123.769 123.769" fill={fill}>
<g> <g>
<path d="M76.05,1.568l-10.101,9.3c-2.3,2.1-5.8,2.1-8.1,0l-10.2-9.2c-3.1-2.8-8-1.7-9.6,2.1l-8.3,20h64.2l-8.3-20.1 <path d="M76.05,1.568l-10.101,9.3c-2.3,2.1-5.8,2.1-8.1,0l-10.2-9.2c-3.1-2.8-8-1.7-9.6,2.1l-8.3,20h64.2l-8.3-20.1
C84.05-0.131,79.149-1.231,76.05,1.568z"/> C84.05-0.131,79.149-1.231,76.05,1.568z"/>
<path d="M10.749,42.068c-2.9,1.4-1.8,5.7,1.3,5.7h49.8h49.701c3.199,0,4.199-4.3,1.399-5.7l-12.2-6.3h-77.8L10.749,42.068z"/> <path d="M10.749,42.068c-2.9,1.4-1.8,5.7,1.3,5.7h49.8h49.701c3.199,0,4.199-4.3,1.399-5.7l-12.2-6.3h-77.8L10.749,42.068z"/>
<path d="M0.549,90.168l5.3,28.801c0.5,2.899,3,4.8,5.9,4.8h50.1h50.201c2.899,0,5.399-2,5.899-4.8l5.3-28.801 <path d="M0.549,90.168l5.3,28.801c0.5,2.899,3,4.8,5.9,4.8h50.1h50.201c2.899,0,5.399-2,5.899-4.8l5.3-28.801
c0.5-2.8-1-5.6-3.699-6.699c-12.801-5-26.2-7.7-36.801-9.301c-2.699-0.399-5.3,1.101-6.3,3.5l-10.1,22.9c-1.8,4-7.5,4-9.201-0.1 c0.5-2.8-1-5.6-3.699-6.699c-12.801-5-26.2-7.7-36.801-9.301c-2.699-0.399-5.3,1.101-6.3,3.5l-10.1,22.9c-1.8,4-7.5,4-9.201-0.1
l-9.8-22.7c-1.1-2.5-3.7-4-6.4-3.601c-10.6,1.5-24,4.301-36.7,9.301C1.549,84.469-0.051,87.269,0.549,90.168z"/> l-9.8-22.7c-1.1-2.5-3.7-4-6.4-3.601c-10.6,1.5-24,4.301-36.7,9.301C1.549,84.469-0.051,87.269,0.549,90.168z"/>
</g> </g>
</svg> </svg>
) )
} }
}; };
export {contentIcon} export {contentIcon}
export default class Torrent extends Component { export default class Torrent extends Component {
state = { state = {
downloading: false, downloading: false,
askDownloading: false, askDownloading: false,
downloadProgress: {} downloadProgress: {}
} }
constructor(props) constructor(props)
{ {
super(props) super(props)
if(props.download) if(props.download)
{ {
const { progress, downloaded, speed } = props.download const { progress, downloaded, speed } = props.download
this.state.downloadProgress = { this.state.downloadProgress = {
progress, downloaded, speed progress, downloaded, speed
} }
} }
} }
componentDidMount() componentDidMount()
{ {
this.downloading = (hash) => { this.downloading = (hash) => {
if(this.props.torrent.hash != hash) if(this.props.torrent.hash != hash)
return; return;
this.setState({downloading: true}) this.setState({downloading: true})
} }
window.torrentSocket.on('downloading', this.downloading); window.torrentSocket.on('downloading', this.downloading);
this.downloadDone = (hash, canceled) => { this.downloadDone = (hash, canceled) => {
if(this.props.torrent.hash != hash) if(this.props.torrent.hash != hash)
return; return;
this.setState({ this.setState({
downloading: false, downloading: false,
askDownloading: !canceled askDownloading: !canceled
}) })
} }
window.torrentSocket.on('downloadDone', this.downloadDone); window.torrentSocket.on('downloadDone', this.downloadDone);
this.downloadProgress = (hash, progress) => { this.downloadProgress = (hash, progress) => {
if(this.props.torrent.hash != hash) if(this.props.torrent.hash != hash)
return; return;
this.setState({ this.setState({
downloading: true, downloading: true,
askDownloading: true, askDownloading: true,
downloadProgress: progress downloadProgress: progress
}) })
} }
window.torrentSocket.on('downloadProgress', this.downloadProgress); window.torrentSocket.on('downloadProgress', this.downloadProgress);
} }
componentWillUnmount() componentWillUnmount()
{ {
if(this.downloading) if(this.downloading)
window.torrentSocket.off('downloading', this.downloading); window.torrentSocket.off('downloading', this.downloading);
if(this.downloadDone) if(this.downloadDone)
window.torrentSocket.off('downloadDone', this.downloadDone); window.torrentSocket.off('downloadDone', this.downloadDone);
if(this.downloadProgress) if(this.downloadProgress)
window.torrentSocket.off('downloadProgress', this.downloadProgress); window.torrentSocket.off('downloadProgress', this.downloadProgress);
} }
render() render()
{ {
const torrent = this.props.torrent; const torrent = this.props.torrent;
let torrentRating = -1 let torrentRating = -1
if(torrent.good > 0 || torrent.bad > 0) if(torrent.good > 0 || torrent.bad > 0)
torrentRating = Math.round(rating(torrent.good, torrent.bad) * 100); torrentRating = Math.round(rating(torrent.good, torrent.bad) * 100);
return ( return (
<div> <div>
<ListItem <ListItem
onClick={(e) => { onClick={(e) => {
const link = '/torrent/' + torrent.hash; const link = '/torrent/' + torrent.hash;
if(e.button === 1) if(e.button === 1)
return false; return false;
/* /*
if(e.ctrlKey && e.button === 0) { if(e.ctrlKey && e.button === 0) {
let win = window.open(link, '_blank'); let win = window.open(link, '_blank');
//win.focus(); //win.focus();
@ -239,105 +239,105 @@ export default class Torrent extends Component {
} }
*/ */
PagesPie.instance().open(TorrentPage, {replace: 'all', hash: torrent.hash, peer: torrent.peer}) PagesPie.instance().open(TorrentPage, {replace: 'all', hash: torrent.hash, peer: torrent.peer})
}} }}
primaryText={ primaryText={
<a href={'/torrent/' + torrent.hash} ref={(node) => { <a href={'/torrent/' + torrent.hash} ref={(node) => {
if(node) if(node)
node.onclick = () => { return false } node.onclick = () => { return false }
}}> }}>
<span className='break-word' style={{ <span className='break-word' style={{
color: torrent.contentCategory != 'xxx' ? (torrent.peer ? '#5643db' : 'black') : (torrent.peer ? '#9083e2' : 'grey') color: torrent.contentCategory != 'xxx' ? (torrent.peer ? '#5643db' : 'black') : (torrent.peer ? '#9083e2' : 'grey')
}}> }}>
{torrent.name} {torrent.name}
</span> </span>
</a> </a>
} }
secondaryText={ secondaryText={
<a href={'/torrent/' + torrent.hash} ref={(node) => { <a href={'/torrent/' + torrent.hash} ref={(node) => {
if(node) if(node)
node.onclick = () => { return false } node.onclick = () => { return false }
}}> }}>
<div className='column' style={{height: 'auto', whiteSpace: 'normal', paddingTop: '0.30em'}}> <div className='column' style={{height: 'auto', whiteSpace: 'normal', paddingTop: '0.30em'}}>
<div className='row w100p inline'> <div className='row w100p inline'>
<div style={{color: torrent.contentCategory != 'xxx' ? (torrent.peer ? '#5252d1' : 'black') : (torrent.peer ? '#9083e2' : 'grey')}}> <div style={{color: torrent.contentCategory != 'xxx' ? (torrent.peer ? '#5252d1' : 'black') : (torrent.peer ? '#9083e2' : 'grey')}}>
{ {
formatBytes(torrent.size, 1) + ' (' + torrent.files + ' files)' formatBytes(torrent.size, 1) + ' (' + torrent.files + ' files)'
} }
</div> </div>
{ {
this.state.downloading this.state.downloading
&& &&
<LinearProgress <LinearProgress
style={{width: '44%', marginLeft: 20}} style={{width: '44%', marginLeft: 20}}
mode="determinate" mode="determinate"
value={this.state.downloadProgress && (this.state.downloadProgress.progress ? this.state.downloadProgress.progress : 0) * 100} value={this.state.downloadProgress && (this.state.downloadProgress.progress ? this.state.downloadProgress.progress : 0) * 100}
/> />
} }
</div> </div>
{ {
torrent.path && torrent.path.length > 0 torrent.path && torrent.path.length > 0
? ?
torrent.path.map((path, index) => { torrent.path.map((path, index) => {
return <div key={index} className='break-word fs0-75' style={{paddingTop: '0.3em', marginLeft: '0.6em'}}>{path}</div> return <div key={index} className='break-word fs0-75' style={{paddingTop: '0.3em', marginLeft: '0.6em'}}>{path}</div>
}) })
: :
null null
} }
{ {
torrent.seeders || torrent.leechers || torrent.completed torrent.seeders || torrent.leechers || torrent.completed
? ?
<div className='break-word fs0-85' style={{paddingTop: '0.35em'}}> <div className='break-word fs0-85' style={{paddingTop: '0.35em'}}>
<span style={{color: (torrent.seeders > 0 ? '#00C853' : 'grey')}}>{torrent.seeders} {__('seeders')}</span> <span style={{color: (torrent.seeders > 0 ? '#00C853' : 'grey')}}>{torrent.seeders} {__('seeders')}</span>
<span style={{color: (torrent.leechers > 0 ? '#AA00FF' : 'grey'), marginLeft: '12px'}}>{torrent.leechers} {__('leechers')}</span> <span style={{color: (torrent.leechers > 0 ? '#AA00FF' : 'grey'), marginLeft: '12px'}}>{torrent.leechers} {__('leechers')}</span>
<span style={{color: (torrent.completed > 0 ? '#FF6D00' : 'grey'), marginLeft: '12px'}}>{torrent.completed} {__('completed')}</span> <span style={{color: (torrent.completed > 0 ? '#FF6D00' : 'grey'), marginLeft: '12px'}}>{torrent.completed} {__('completed')}</span>
</div> </div>
: :
null null
} }
{ {
(torrent.good > 0 || torrent.bad > 0) (torrent.good > 0 || torrent.bad > 0)
&& &&
<div className='row w100p inline' style={{maxWidth: 600}}> <div className='row w100p inline' style={{maxWidth: 600}}>
<LinearProgress <LinearProgress
mode="determinate" mode="determinate"
value={torrentRating} value={torrentRating}
color={torrentRating >= 50 ? '#00E676' : '#FF3D00'} color={torrentRating >= 50 ? '#00E676' : '#FF3D00'}
style={{ style={{
height: '5px', height: '5px',
}} }}
/> />
<div className='row center pad0-5 fs0-85 text-nowrap' style={{color: torrentRating >= 50 ? '#00E676' : '#FF3D00', width: '190px'}}>{__('Torrent rating')}: {torrentRating}%</div> <div className='row center pad0-5 fs0-85 text-nowrap' style={{color: torrentRating >= 50 ? '#00E676' : '#FF3D00', width: '190px'}}>{__('Torrent rating')}: {torrentRating}%</div>
</div> </div>
} }
</div> </div>
</a> </a>
} }
leftIcon={contentIcon(torrent.contentType, torrent.contentCategory, torrent.contentCategory != 'xxx' ? (torrent.peer ? '#6f5ee0' : 'grey') : (torrent.peer ? '#9083e2' : '#d3d3d3'))} leftIcon={contentIcon(torrent.contentType, torrent.contentCategory, torrent.contentCategory != 'xxx' ? (torrent.peer ? '#6f5ee0' : 'grey') : (torrent.peer ? '#9083e2' : '#d3d3d3'))}
rightIcon={ rightIcon={
<div className='row inline' style={{width: 63}}> <div className='row inline' style={{width: 63}}>
{ {
!this.state.askDownloading && !this.state.downloading !this.state.askDownloading && !this.state.downloading
? ?
<a href={`magnet:?xt=urn:btih:${torrent.hash}`}> <a href={`magnet:?xt=urn:btih:${torrent.hash}`}>
<svg style={{ <svg style={{
height: '24px', height: '24px',
marginRight: 12, marginRight: 12,
fill: torrent.contentCategory != 'xxx' ? (torrent.peer ? '#5643db' : 'black') : (torrent.peer ? '#9083e2' : 'grey') fill: torrent.contentCategory != 'xxx' ? (torrent.peer ? '#5643db' : 'black') : (torrent.peer ? '#9083e2' : 'grey')
}} onClick={(e) => { }} onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
this.setState({askDownloading: true}) this.setState({askDownloading: true})
window.torrentSocket.emit('download', torrent) window.torrentSocket.emit('download', torrent)
}} viewBox="0 0 56 56"> }} viewBox="0 0 56 56">
<g> <g>
<path d="M35.586,41.586L31,46.172V28c0-1.104-0.896-2-2-2s-2,0.896-2,2v18.172l-4.586-4.586c-0.781-0.781-2.047-0.781-2.828,0 <path d="M35.586,41.586L31,46.172V28c0-1.104-0.896-2-2-2s-2,0.896-2,2v18.172l-4.586-4.586c-0.781-0.781-2.047-0.781-2.828,0
s-0.781,2.047,0,2.828l7.999,7.999c0.093,0.094,0.196,0.177,0.307,0.251c0.047,0.032,0.099,0.053,0.148,0.081 s-0.781,2.047,0,2.828l7.999,7.999c0.093,0.094,0.196,0.177,0.307,0.251c0.047,0.032,0.099,0.053,0.148,0.081
c0.065,0.036,0.127,0.075,0.196,0.103c0.065,0.027,0.133,0.042,0.2,0.062c0.058,0.017,0.113,0.04,0.173,0.051 c0.065,0.036,0.127,0.075,0.196,0.103c0.065,0.027,0.133,0.042,0.2,0.062c0.058,0.017,0.113,0.04,0.173,0.051
C28.738,52.986,28.869,53,29,53s0.262-0.014,0.392-0.04c0.06-0.012,0.115-0.034,0.173-0.051c0.067-0.02,0.135-0.035,0.2-0.062 C28.738,52.986,28.869,53,29,53s0.262-0.014,0.392-0.04c0.06-0.012,0.115-0.034,0.173-0.051c0.067-0.02,0.135-0.035,0.2-0.062
c0.069-0.028,0.131-0.067,0.196-0.103c0.05-0.027,0.101-0.049,0.148-0.081c0.11-0.074,0.213-0.157,0.307-0.251l7.999-7.999 c0.069-0.028,0.131-0.067,0.196-0.103c0.05-0.027,0.101-0.049,0.148-0.081c0.11-0.074,0.213-0.157,0.307-0.251l7.999-7.999
c0.781-0.781,0.781-2.047,0-2.828S36.367,40.805,35.586,41.586z"/> c0.781-0.781,0.781-2.047,0-2.828S36.367,40.805,35.586,41.586z"/>
<path d="M47.835,18.986c-0.137-0.019-2.457-0.335-4.684,0.002C43.1,18.996,43.049,19,42.999,19c-0.486,0-0.912-0.354-0.987-0.85 <path d="M47.835,18.986c-0.137-0.019-2.457-0.335-4.684,0.002C43.1,18.996,43.049,19,42.999,19c-0.486,0-0.912-0.354-0.987-0.85
c-0.083-0.546,0.292-1.056,0.838-1.139c1.531-0.233,3.062-0.196,4.083-0.124C46.262,9.135,39.83,3,32.085,3 c-0.083-0.546,0.292-1.056,0.838-1.139c1.531-0.233,3.062-0.196,4.083-0.124C46.262,9.135,39.83,3,32.085,3
C27.388,3,22.667,5.379,19.8,9.129C21.754,10.781,23,13.246,23,16c0,0.553-0.447,1-1,1s-1-0.447-1-1 C27.388,3,22.667,5.379,19.8,9.129C21.754,10.781,23,13.246,23,16c0,0.553-0.447,1-1,1s-1-0.447-1-1
c0-2.462-1.281-4.627-3.209-5.876c-0.227-0.147-0.462-0.277-0.702-0.396c-0.069-0.034-0.139-0.069-0.21-0.101 c0-2.462-1.281-4.627-3.209-5.876c-0.227-0.147-0.462-0.277-0.702-0.396c-0.069-0.034-0.139-0.069-0.21-0.101
@ -346,39 +346,39 @@ export default class Torrent extends Component {
l0.012,0.21l-0.009,0.16C7.008,16.744,7,16.873,7,17v0.63l-0.567,0.271C2.705,19.688,0,24,0,28.154C0,34.135,4.865,39,10.845,39H25 l0.012,0.21l-0.009,0.16C7.008,16.744,7,16.873,7,17v0.63l-0.567,0.271C2.705,19.688,0,24,0,28.154C0,34.135,4.865,39,10.845,39H25
V28c0-2.209,1.791-4,4-4s4,1.791,4,4v11h2.353c0.059,0,0.116-0.005,0.174-0.009l0.198-0.011l0.271,0.011 V28c0-2.209,1.791-4,4-4s4,1.791,4,4v11h2.353c0.059,0,0.116-0.005,0.174-0.009l0.198-0.011l0.271,0.011
C36.053,38.995,36.11,39,36.169,39h9.803C51.501,39,56,34.501,56,28.972C56,24.161,52.49,19.872,47.835,18.986z"/> C36.053,38.995,36.11,39,36.169,39h9.803C51.501,39,56,34.501,56,28.972C56,24.161,52.49,19.872,47.835,18.986z"/>
</g> </g>
</svg> </svg>
</a> </a>
: :
this.state.askDownloading && !this.state.downloading this.state.askDownloading && !this.state.downloading
? ?
<img src={Spinner24} /> <img src={Spinner24} />
: :
this.state.askDownloading && this.state.downloading this.state.askDownloading && this.state.downloading
&& &&
<a href={`magnet:?xt=urn:btih:${torrent.hash}`}> <a href={`magnet:?xt=urn:btih:${torrent.hash}`}>
<svg style={{ <svg style={{
height: '24px', height: '24px',
fill: torrent.contentCategory != 'xxx' ? (torrent.peer ? '#5643db' : 'black') : (torrent.peer ? '#9083e2' : 'grey') fill: torrent.contentCategory != 'xxx' ? (torrent.peer ? '#5643db' : 'black') : (torrent.peer ? '#9083e2' : 'grey')
}} onClick={(e) => { }} onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
window.torrentSocket.emit('downloadCancel', torrent.hash) window.torrentSocket.emit('downloadCancel', torrent.hash)
}} viewBox="0 0 18 18"><path d="M9 1C4.58 1 1 4.58 1 9s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm4 10.87L11.87 13 9 10.13 6.13 13 5 11.87 7.87 9 5 6.13 6.13 5 9 7.87 11.87 5 13 6.13 10.13 9 13 11.87z"/></svg> }} viewBox="0 0 18 18"><path d="M9 1C4.58 1 1 4.58 1 9s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm4 10.87L11.87 13 9 10.13 6.13 13 5 11.87 7.87 9 5 6.13 6.13 5 9 7.87 11.87 5 13 6.13 10.13 9 13 11.87z"/></svg>
</a> </a>
} }
<a style={{float: 'right'}} href={`magnet:?xt=urn:btih:${torrent.hash}`}> <a style={{float: 'right'}} href={`magnet:?xt=urn:btih:${torrent.hash}`}>
<svg style={{ <svg style={{
height: '24px', height: '24px',
fill: torrent.contentCategory != 'xxx' ? (torrent.peer ? '#5643db' : 'black') : (torrent.peer ? '#9083e2' : 'grey') fill: torrent.contentCategory != 'xxx' ? (torrent.peer ? '#5643db' : 'black') : (torrent.peer ? '#9083e2' : 'grey')
}} onClick={(e) => { }} onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
var win = window.open(`magnet:?xt=urn:btih:${torrent.hash}`, '_self'); var win = window.open(`magnet:?xt=urn:btih:${torrent.hash}`, '_self');
}} viewBox="0 0 24 24"> }} viewBox="0 0 24 24">
<path d="M15.82 10.736l-5.451 6.717c-.561.691-1.214 1.042-1.94 1.042-1.144 <path d="M15.82 10.736l-5.451 6.717c-.561.691-1.214 1.042-1.94 1.042-1.144
0-2.327-.899-2.753-2.091-.214-.6-.386-1.76.865-2.784 3.417-2.794 6.716-5.446 0-2.327-.899-2.753-2.091-.214-.6-.386-1.76.865-2.784 3.417-2.794 6.716-5.446
6.716-5.446l-3.363-4.174s-4.532 3.657-6.771 5.487c-2.581 2.108-3.123 4.468-3.123 6.716-5.446l-3.363-4.174s-4.532 3.657-6.771 5.487c-2.581 2.108-3.123 4.468-3.123
6.075 0 4.416 4.014 8.438 8.42 8.438 1.604 0 3.963-.543 6.084-3.128 1.835-2.237 6.075 0 4.416 4.014 8.438 8.42 8.438 1.604 0 3.963-.543 6.084-3.128 1.835-2.237
@ -390,12 +390,12 @@ export default class Torrent extends Component {
4.151zm6.152-7.934l4.318-2.88-1.575-.638 1.889-2.414-4.421 2.788 1.716.695-1.927 4.151zm6.152-7.934l4.318-2.88-1.575-.638 1.889-2.414-4.421 2.788 1.716.695-1.927
2.449zm-7.292-7.186l4.916-1.667-1.356-1.022 2.448-2.006-4.991 1.712 2.449zm-7.292-7.186l4.916-1.667-1.356-1.022 2.448-2.006-4.991 1.712
1.478 1.114-2.495 1.869z"/></svg> 1.478 1.114-2.495 1.869z"/></svg>
</a> </a>
</div> </div>
} }
/> />
<Divider /> <Divider />
</div> </div>
) )
} }
} }

View File

@ -2,94 +2,94 @@ import ReactDOM from 'react-dom';
let listenSwipe = (component, handlers) => let listenSwipe = (component, handlers) =>
{ {
let element = ReactDOM.findDOMNode(component), let element = ReactDOM.findDOMNode(component),
startX, startX,
startY, startY,
distanceX, distanceX,
distanceY, distanceY,
threshold = 110, threshold = 110,
allowedTime = 280, allowedTime = 280,
thresholdAlternativeAxis = 145, thresholdAlternativeAxis = 145,
elapsedTime, elapsedTime,
startTime; startTime;
let touchFunctions = { let touchFunctions = {
touchstart : (e) => { touchstart : (e) => {
let touchObject = e.changedTouches[0] let touchObject = e.changedTouches[0]
distanceX = 0; distanceX = 0;
distanceY = 0; distanceY = 0;
startX = touchObject.pageX startX = touchObject.pageX
startY = touchObject.pageY startY = touchObject.pageY
startTime = new Date().getTime() startTime = new Date().getTime()
if(handlers && handlers.preventDefault) if(handlers && handlers.preventDefault)
e.preventDefault(); e.preventDefault();
if(handlers && handlers.initSwipe) if(handlers && handlers.initSwipe)
handlers.initSwipe.call(component); handlers.initSwipe.call(component);
}, },
touchmove : (e) => { touchmove : (e) => {
if(handlers && handlers.preventDefault) if(handlers && handlers.preventDefault)
e.preventDefault(); e.preventDefault();
}, },
touchend : (e) => { touchend : (e) => {
let touchObject = e.changedTouches[0]; let touchObject = e.changedTouches[0];
distanceX = touchObject.pageX - startX; distanceX = touchObject.pageX - startX;
distanceY = touchObject.pageY - startY; distanceY = touchObject.pageY - startY;
elapsedTime = new Date().getTime() - startTime; // get time elapsed elapsedTime = new Date().getTime() - startTime; // get time elapsed
let params = { let params = {
startX, startX,
startY, startY,
endX: touchObject.pageX, endX: touchObject.pageX,
endY: touchObject.pageY, endY: touchObject.pageY,
distanceX, distanceX,
distanceY distanceY
}; };
if (elapsedTime <= allowedTime) if (elapsedTime <= allowedTime)
{ {
if(distanceX >= threshold && Math.abs(distanceY) <= thresholdAlternativeAxis) if(distanceX >= threshold && Math.abs(distanceY) <= thresholdAlternativeAxis)
{ {
if(handlers && handlers.left) if(handlers && handlers.left)
handlers.left.call(component, params); handlers.left.call(component, params);
} else } else
if(-distanceX >= threshold && Math.abs(distanceY) <= thresholdAlternativeAxis) { if(-distanceX >= threshold && Math.abs(distanceY) <= thresholdAlternativeAxis) {
if(handlers && handlers.right) if(handlers && handlers.right)
handlers.right.call(component, params); handlers.right.call(component, params);
} else } else
if(distanceY >= threshold && Math.abs(distanceX) <= thresholdAlternativeAxis) { if(distanceY >= threshold && Math.abs(distanceX) <= thresholdAlternativeAxis) {
if(handlers && handlers.top) if(handlers && handlers.top)
handlers.top.call(component, params); handlers.top.call(component, params);
} else } else
if(-distanceY >= threshold && Math.abs(distanceX) <= thresholdAlternativeAxis) { if(-distanceY >= threshold && Math.abs(distanceX) <= thresholdAlternativeAxis) {
if(handlers && handlers.bottom) if(handlers && handlers.bottom)
handlers.bottom.call(component, params); handlers.bottom.call(component, params);
} }
} }
if(handlers && handlers.preventDefault) if(handlers && handlers.preventDefault)
e.preventDefault(); e.preventDefault();
} }
}; };
element.addEventListener('touchstart', touchFunctions.touchstart, false); element.addEventListener('touchstart', touchFunctions.touchstart, false);
element.addEventListener('touchmove', touchFunctions.touchmove, false); element.addEventListener('touchmove', touchFunctions.touchmove, false);
element.addEventListener('touchend', touchFunctions.touchend, false); element.addEventListener('touchend', touchFunctions.touchend, false);
return touchFunctions; return touchFunctions;
} }
let removeSwipeListener = (component, touchFunctions) => { let removeSwipeListener = (component, touchFunctions) => {
let element = ReactDOM.findDOMNode(component); let element = ReactDOM.findDOMNode(component);
element.removeEventListener('touchstart', touchFunctions.touchstart); element.removeEventListener('touchstart', touchFunctions.touchstart);
element.removeEventListener('touchmove', touchFunctions.touchmove); element.removeEventListener('touchmove', touchFunctions.touchmove);
element.removeEventListener('touchend', touchFunctions.touchend); element.removeEventListener('touchend', touchFunctions.touchend);
} }
export { listenSwipe, removeSwipeListener } export { listenSwipe, removeSwipeListener }

View File

@ -35,17 +35,17 @@ module.exports = async ({
return; return;
sphinx.query('SELECT * FROM `torrents` ORDER BY added DESC LIMIT 0,10', function (error, rows, fields) { sphinx.query('SELECT * FROM `torrents` ORDER BY added DESC LIMIT 0,10', function (error, rows, fields) {
if(!rows) { if(!rows) {
callback(undefined) callback(undefined)
return; return;
} }
let torrents = []; let torrents = [];
rows.forEach((row) => { rows.forEach((row) => {
torrents.push(baseRowData(row)); torrents.push(baseRowData(row));
}); });
callback(torrents) callback(torrents)
}); });
}); });
@ -55,25 +55,25 @@ module.exports = async ({
return; return;
sphinx.query('SELECT count(*) AS torrents, sum(size) AS sz FROM `torrents`', function (error, rows, fields) { sphinx.query('SELECT count(*) AS torrents, sum(size) AS sz FROM `torrents`', function (error, rows, fields) {
if(!rows) { if(!rows) {
console.error(error) console.error(error)
callback(undefined) callback(undefined)
return; return;
}
let result = {torrents: rows[0].torrents || 0, size: rows[0].sz || 0}
sphinx.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 let result = {torrents: rows[0].torrents || 0, size: rows[0].sz || 0}
callback(result) sphinx.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)
})
}); });
}); });
@ -112,43 +112,43 @@ module.exports = async ({
} }
sphinx.query('SELECT * FROM `torrents` WHERE `hash` = ?', hash, async function (error, rows, fields) { sphinx.query('SELECT * FROM `torrents` WHERE `hash` = ?', hash, async function (error, rows, fields) {
if(!rows || rows.length == 0) { if(!rows || rows.length == 0) {
callback(undefined) callback(undefined)
return; return;
}
let torrent = rows[0];
if(options.files)
{
torrent.filesList = await sphinx.query('SELECT * FROM `files` WHERE `hash` = ? LIMIT 50000', hash);
callback(baseRowData(torrent))
}
else
{
callback(baseRowData(torrent))
}
if(torrentClientHashMap[hash])
{
const torrent = torrentClient.get(torrentClientHashMap[hash])
if(torrent)
{
send('downloading', torrent.infoHash)
} }
} let torrent = rows[0];
// get votes if(options.files)
const {good, bad, selfVote} = await getVotes(hash) {
send('votes', { torrent.filesList = await sphinx.query('SELECT * FROM `files` WHERE `hash` = ? LIMIT 50000', hash);
hash, good, bad, selfVote callback(baseRowData(torrent))
}); }
if(torrent.good != good || torrent.bad != bad) else
{ {
console.log('finded new rating on', torrent.name, 'update votes to it') callback(baseRowData(torrent))
torrent.good = good }
torrent.bad = bad
updateTorrentToDB(torrent) if(torrentClientHashMap[hash])
} {
const torrent = torrentClient.get(torrentClientHashMap[hash])
if(torrent)
{
send('downloading', torrent.infoHash)
}
}
// get votes
const {good, bad, selfVote} = await getVotes(hash)
send('votes', {
hash, good, bad, selfVote
});
if(torrent.good != good || torrent.bad != bad)
{
console.log('finded new rating on', torrent.name, 'update votes to it')
torrent.good = good
torrent.bad = bad
updateTorrentToDB(torrent)
}
}); });
} }
@ -167,27 +167,27 @@ module.exports = async ({
p2p.on('randomTorrents', (nil, callback) => { p2p.on('randomTorrents', (nil, callback) => {
if(typeof callback != 'function') if(typeof callback != 'function')
return; return;
sphinx.query('SELECT * FROM `torrents` ORDER BY rand() limit 5', (error, torrents) => { sphinx.query('SELECT * FROM `torrents` ORDER BY rand() limit 5', (error, torrents) => {
if(!torrents || torrents.length == 0) { if(!torrents || torrents.length == 0) {
callback(undefined) callback(undefined)
return; return;
} }
let hashes = {} let hashes = {}
for(const torrent of torrents) for(const torrent of torrents)
{ {
delete torrent.id delete torrent.id
hashes[torrent.hash] = torrent hashes[torrent.hash] = torrent
} }
const inSql = Object.keys(hashes).map(hash => sphinx.escape(hash)).join(','); const inSql = Object.keys(hashes).map(hash => sphinx.escape(hash)).join(',');
sphinx.query(`SELECT * FROM files WHERE hash IN(${inSql}) limit 50000`, (error, files) => { sphinx.query(`SELECT * FROM files WHERE hash IN(${inSql}) limit 50000`, (error, files) => {
if(!files) if(!files)
{ {
files = [] files = []
} }
files.forEach((file) => { files.forEach((file) => {
if(!hashes[file.hash].filesList) if(!hashes[file.hash].filesList)
hashes[file.hash].filesList = [] hashes[file.hash].filesList = []
@ -277,13 +277,13 @@ module.exports = async ({
sphinx.query('SELECT * FROM `torrents` WHERE MATCH(?) ' + 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) { if(!rows) {
console.log(error) console.log(error)
callback(undefined) callback(undefined)
return; return;
} }
rows.forEach((row) => { rows.forEach((row) => {
searchList.push(baseRowData(row)); searchList.push(baseRowData(row));
}); });
callback(searchList); callback(searchList);
}); });
} }
@ -328,37 +328,37 @@ module.exports = async ({
let where = ''; let where = '';
/* /*
if(orderBy && orderBy.length > 0) if(orderBy && orderBy.length > 0)
{ {
const orderDesc = navigation.orderDesc ? 'DESC' : 'ASC'; const orderDesc = navigation.orderDesc ? 'DESC' : 'ASC';
args.splice(1, 0, orderBy); args.splice(1, 0, orderBy);
order = 'ORDER BY ?? ' + orderDesc; order = 'ORDER BY ?? ' + orderDesc;
} }
*/ */
/* /*
if(safeSearch) if(safeSearch)
{ {
where += " and contentCategory != 'xxx' "; where += " and contentCategory != 'xxx' ";
} }
if(navigation.type && navigation.type.length > 0) if(navigation.type && navigation.type.length > 0)
{ {
where += ' and contentType = ' + sphinx.escape(navigation.type) + ' '; where += ' and contentType = ' + sphinx.escape(navigation.type) + ' ';
} }
if(navigation.size) if(navigation.size)
{ {
if(navigation.size.max > 0) if(navigation.size.max > 0)
where += ' and torrentSize < ' + sphinx.escape(navigation.size.max) + ' '; where += ' and torrentSize < ' + sphinx.escape(navigation.size.max) + ' ';
if(navigation.size.min > 0) if(navigation.size.min > 0)
where += ' and torrentSize > ' + sphinx.escape(navigation.size.min) + ' '; where += ' and torrentSize > ' + sphinx.escape(navigation.size.min) + ' ';
} }
if(navigation.files) if(navigation.files)
{ {
if(navigation.files.max > 0) if(navigation.files.max > 0)
where += ' and files < ' + sphinx.escape(navigation.files.max) + ' '; where += ' and files < ' + sphinx.escape(navigation.files.max) + ' ';
if(navigation.files.min > 0) if(navigation.files.min > 0)
where += ' and files > ' + sphinx.escape(navigation.files.min) + ' '; where += ' and files > ' + sphinx.escape(navigation.files.min) + ' ';
} }
*/ */
let search = {}; let search = {};
//args.splice(orderBy && orderBy.length > 0 ? 1 : 0, 1); //args.splice(orderBy && orderBy.length > 0 ? 1 : 0, 1);
@ -366,8 +366,8 @@ module.exports = async ({
sphinx.query('SELECT * FROM `files` WHERE MATCH(?) ' + where + ' ' + order + ' LIMIT ?,?', args, function (error, files, fields) { sphinx.query('SELECT * FROM `files` WHERE MATCH(?) ' + where + ' ' + order + ' LIMIT ?,?', args, function (error, files, fields) {
if(!files) { if(!files) {
console.log(error) console.log(error)
callback(undefined) callback(undefined)
return; return;
} }
if(files.length === 0) if(files.length === 0)
{ {
@ -392,7 +392,7 @@ module.exports = async ({
for(const torrent of torrents) for(const torrent of torrents)
{ {
search[torrent.hash] = Object.assign(baseRowData(torrent), search[torrent.hash]) search[torrent.hash] = Object.assign(baseRowData(torrent), search[torrent.hash])
// temporary ignore adult content in search (workaroud) // temporary ignore adult content in search (workaroud)
if(safeSearch && search[torrent.hash].contentCategory == 'xxx') if(safeSearch && search[torrent.hash].contentCategory == 'xxx')
delete search[torrent.hash] delete search[torrent.hash]
@ -466,7 +466,7 @@ module.exports = async ({
where += ' and `added` > ' + (Math.floor(Date.now() / 1000) - (60 * 60 * 24 * 30)) 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}`; const query = `SELECT * FROM torrents WHERE seeders > 0 and contentCategory != 'xxx' ${where} ORDER BY seeders DESC LIMIT ${index},${limit}`;
if(topCache[query]) if(topCache[query])
{ {
@ -478,10 +478,10 @@ module.exports = async ({
callback(undefined) callback(undefined)
return; return;
} }
rows = rows.map((row) => baseRowData(row)); rows = rows.map((row) => baseRowData(row));
topCache[query] = rows; topCache[query] = rows;
callback(rows); callback(rows);
}); });
} }
@ -571,7 +571,7 @@ module.exports = async ({
{ {
spider.announceHashes = [] spider.announceHashes = []
} }
if(typeof callback === 'function') if(typeof callback === 'function')
callback(true) callback(true)
}); });
@ -739,9 +739,9 @@ module.exports = async ({
if(!temp || !temp.torrent) if(!temp || !temp.torrent)
return return
const { torrent } = temp const { torrent } = temp
if(torrent.hash !== record.torrentHash) if(torrent.hash !== record.torrentHash)
return return
@ -760,7 +760,7 @@ module.exports = async ({
// update feed // update feed
if(record.vote !== 'good') if(record.vote !== 'good')
return return
feed.add(torrent) feed.add(torrent)
send('feedUpdate', { send('feedUpdate', {
feed: feed.feed feed: feed.feed
@ -787,10 +787,10 @@ module.exports = async ({
p2p.emit('feed', null, (remoteFeed) => { p2p.emit('feed', null, (remoteFeed) => {
if(!remoteFeed) if(!remoteFeed)
return return
if(remoteFeed.length <= feed.size()) if(remoteFeed.length <= feed.size())
return return
console.log('replace our feed with remote feed') console.log('replace our feed with remote feed')
feed.feed = remoteFeed feed.feed = remoteFeed
send('feedUpdate', { send('feedUpdate', {
@ -799,5 +799,5 @@ module.exports = async ({
}); });
}, 1000) }, 1000)
}) })
} }

View File

@ -28,17 +28,17 @@ require('electron-context-menu')({})
// Thanks to this you can use production and development versions of the app // Thanks to this you can use production and development versions of the app
// on same machine like those are two separate apps. // on same machine like those are two separate apps.
if (env.name !== "production") { if (env.name !== "production") {
const userDataPath = app.getPath("userData"); const userDataPath = app.getPath("userData");
app.setPath("userData", `${userDataPath} (${env.name})`); app.setPath("userData", `${userDataPath} (${env.name})`);
} }
// portative version // portative version
let portative = false let portative = false
if(env.name === "production") { if(env.name === "production") {
if(fs.existsSync(path.dirname(process.execPath) + `/data`)) if(fs.existsSync(path.dirname(process.execPath) + `/data`))
{ {
portative = true; portative = true;
app.setPath("userData", path.dirname(process.execPath) + `/data`); app.setPath("userData", path.dirname(process.execPath) + `/data`);
} }
} }
const resourcesPath = env.name === "production" ? process.resourcesPath : 'resources' const resourcesPath = env.name === "production" ? process.resourcesPath : 'resources'
@ -54,33 +54,33 @@ let sphinx = undefined
let spider = undefined let spider = undefined
const setApplicationMenu = () => { const setApplicationMenu = () => {
const settingsMenuTemplate = settingsMenuTemplateFunc(appConfig, (lang) => { const settingsMenuTemplate = settingsMenuTemplateFunc(appConfig, (lang) => {
// update menu translation // update menu translation
changeLanguage(lang, () => setApplicationMenu()) changeLanguage(lang, () => setApplicationMenu())
}) })
const menus = [editMenuTemplateFunc(), manageMenuTemplateFunc(), settingsMenuTemplate, aboutMenuTemplateFunc()]; const menus = [editMenuTemplateFunc(), manageMenuTemplateFunc(), settingsMenuTemplate, aboutMenuTemplateFunc()];
if (env.name !== "production") { if (env.name !== "production") {
menus.push(devMenuTemplate); menus.push(devMenuTemplate);
} }
// append version as disabled menu item // append version as disabled menu item
menus.push({ menus.push({
label: app.getVersion() label: app.getVersion()
}) })
Menu.setApplicationMenu(Menu.buildFromTemplate(menus)); Menu.setApplicationMenu(Menu.buildFromTemplate(menus));
}; };
const util = require('util'); const util = require('util');
if (!fs.existsSync(app.getPath("userData"))){ if (!fs.existsSync(app.getPath("userData"))){
fs.mkdirSync(app.getPath("userData")); fs.mkdirSync(app.getPath("userData"));
} }
const logFile = fs.createWriteStream(app.getPath("userData") + '/rats.log', {flags : 'w'}); const logFile = fs.createWriteStream(app.getPath("userData") + '/rats.log', {flags : 'w'});
const logStdout = process.stdout; const logStdout = process.stdout;
console.log = (...d) => { console.log = (...d) => {
const date = (new Date).toLocaleTimeString() const date = (new Date).toLocaleTimeString()
logFile.write(`[${date}] ` + util.format(...d) + '\n'); logFile.write(`[${date}] ` + util.format(...d) + '\n');
logStdout.write(util.format(...d) + '\n'); logStdout.write(util.format(...d) + '\n');
}; };
// print os info // print os info
@ -94,21 +94,21 @@ console.log('Total memory:', (os.totalmem() / (1024 * 1024)).toFixed(2), 'MB')
console.log('Free memory:', (os.freemem() / (1024 * 1024)).toFixed(2), 'MB') console.log('Free memory:', (os.freemem() / (1024 * 1024)).toFixed(2), 'MB')
if(portative) if(portative)
console.log('portative compability') console.log('portative compability')
const shouldQuit = app.makeSingleInstance(function(commandLine, workingDirectory) { const shouldQuit = app.makeSingleInstance(function(commandLine, workingDirectory) {
// Someone tried to run a second instance, we should focus our window. // Someone tried to run a second instance, we should focus our window.
console.log('openned second application, just focus this one') console.log('openned second application, just focus this one')
if (mainWindow) { if (mainWindow) {
if (mainWindow.isMinimized()) if (mainWindow.isMinimized())
mainWindow.restore(); mainWindow.restore();
mainWindow.focus(); mainWindow.focus();
} }
}); });
if (shouldQuit) { if (shouldQuit) {
console.log('closed because of second application') console.log('closed because of second application')
app.exit(0); app.exit(0);
} }
// log autoupdate // log autoupdate
@ -116,162 +116,162 @@ const log = require('electron-log')
log.transports.file.level = false; log.transports.file.level = false;
log.transports.console.level = false; log.transports.console.level = false;
log.transports.console = function(msg) { log.transports.console = function(msg) {
const text = util.format.apply(util, msg.data); const text = util.format.apply(util, msg.data);
console.log(text); console.log(text);
}; };
autoUpdater.logger = log; autoUpdater.logger = log;
autoUpdater.on('update-downloaded', () => { autoUpdater.on('update-downloaded', () => {
console.log('update-downloaded lats quitAndInstall'); console.log('update-downloaded lats quitAndInstall');
if (env.name === "production") { if (env.name === "production") {
dialog.showMessageBox({ dialog.showMessageBox({
type: 'info', type: 'info',
title: 'Found Updates', title: 'Found Updates',
message: 'Found updates, do you want update now?', message: 'Found updates, do you want update now?',
buttons: ['Sure', 'No'] buttons: ['Sure', 'No']
}, (buttonIndex) => { }, (buttonIndex) => {
if (buttonIndex === 0) { if (buttonIndex === 0) {
const isSilent = true; const isSilent = true;
const isForceRunAfter = true; const isForceRunAfter = true;
autoUpdater.quitAndInstall(isSilent, isForceRunAfter); autoUpdater.quitAndInstall(isSilent, isForceRunAfter);
} }
}) })
} }
}) })
let tray = undefined let tray = undefined
app.on("ready", () => { app.on("ready", () => {
sphinx = startSphinx(() => { sphinx = startSphinx(() => {
mainWindow = createWindow("main", { mainWindow = createWindow("main", {
width: 1000, width: 1000,
height: 600 height: 600
}); });
dbPatcher(() => { dbPatcher(() => {
changeLanguage(appConfig.language, () => setApplicationMenu()) changeLanguage(appConfig.language, () => setApplicationMenu())
mainWindow.loadURL( mainWindow.loadURL(
url.format({ url.format({
pathname: path.join(__dirname, "app.html"), pathname: path.join(__dirname, "app.html"),
protocol: "file:", protocol: "file:",
slashes: true slashes: true
}) })
); );
if (env.name === "development") { if (env.name === "development") {
mainWindow.openDevTools(); mainWindow.openDevTools();
} }
if(process.platform === 'darwin') if(process.platform === 'darwin')
tray = new Tray(`${resourcesPath}/icons/19x19.png`) tray = new Tray(`${resourcesPath}/icons/19x19.png`)
else else
tray = new Tray(`${resourcesPath}/icons/512x512.png`) tray = new Tray(`${resourcesPath}/icons/512x512.png`)
tray.on('click', () => { tray.on('click', () => {
mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show() mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show()
}) })
mainWindow.on('show', () => { mainWindow.on('show', () => {
tray.setHighlightMode('always') tray.setHighlightMode('always')
}) })
mainWindow.on('hide', () => { mainWindow.on('hide', () => {
tray.setHighlightMode('never') tray.setHighlightMode('never')
}) })
mainWindow.on('close', (event) => { mainWindow.on('close', (event) => {
if (!app.isQuiting && appConfig.trayOnClose && process.platform !== 'linux') { if (!app.isQuiting && appConfig.trayOnClose && process.platform !== 'linux') {
event.preventDefault() event.preventDefault()
mainWindow.hide() mainWindow.hide()
return return
} }
}) })
mainWindow.on('closed', () => { mainWindow.on('closed', () => {
mainWindow = undefined mainWindow = undefined
}) })
mainWindow.on('minimize', (event) => { mainWindow.on('minimize', (event) => {
if(appConfig.trayOnMinimize) if(appConfig.trayOnMinimize)
{ {
event.preventDefault(); event.preventDefault();
mainWindow.hide(); mainWindow.hide();
} }
}); });
var contextMenu = Menu.buildFromTemplate([ var contextMenu = Menu.buildFromTemplate([
{ label: 'Show', click: function(){ { label: 'Show', click: function(){
mainWindow.show(); mainWindow.show();
} }, } },
{ label: 'Quit', click: function(){ { label: 'Quit', click: function(){
app.isQuiting = true; app.isQuiting = true;
if (sphinx) if (sphinx)
stop() stop()
else else
app.quit() app.quit()
} } } }
]); ]);
tray.setContextMenu(contextMenu) tray.setContextMenu(contextMenu)
tray.setToolTip('Rats on The Boat search') tray.setToolTip('Rats on The Boat search')
mainWindow.webContents.on('will-navigate', e => { e.preventDefault() }) mainWindow.webContents.on('will-navigate', e => { e.preventDefault() })
mainWindow.webContents.on('new-window', (event, url, frameName) => { mainWindow.webContents.on('new-window', (event, url, frameName) => {
if(frameName == '_self') if(frameName == '_self')
{ {
event.preventDefault() event.preventDefault()
mainWindow.loadURL(url) mainWindow.loadURL(url)
} }
}) })
if (env.name === "production" && !portative) { autoUpdater.checkForUpdates() } if (env.name === "production" && !portative) { autoUpdater.checkForUpdates() }
spider = new spiderCall((...data) => { spider = new spiderCall((...data) => {
if(mainWindow) if(mainWindow)
mainWindow.webContents.send(...data) mainWindow.webContents.send(...data)
}, (message, callback) => { }, (message, callback) => {
ipcMain.on(message, (event, arg) => { ipcMain.on(message, (event, arg) => {
if(Array.isArray(arg) && typeof arg[arg.length - 1] === 'object' && arg[arg.length - 1].callback) if(Array.isArray(arg) && typeof arg[arg.length - 1] === 'object' && arg[arg.length - 1].callback)
{ {
const id = arg[arg.length - 1].callback const id = arg[arg.length - 1].callback
arg[arg.length - 1] = (responce) => { arg[arg.length - 1] = (responce) => {
mainWindow.webContents.send('callback', id, responce) mainWindow.webContents.send('callback', id, responce)
} }
} }
callback.apply(null, arg) callback.apply(null, arg)
}) })
}, app.getPath("userData"), app.getVersion(), env.name) }, app.getPath("userData"), app.getVersion(), env.name)
}, mainWindow, sphinx) }, mainWindow, sphinx)
}, app.getPath("userData"), () => app.quit()) }, app.getPath("userData"), () => app.quit())
}); });
let stopProtect = false let stopProtect = false
const stop = () => { const stop = () => {
if(stopProtect) if(stopProtect)
return return
stopProtect = true stopProtect = true
if(tray) if(tray)
tray.destroy() tray.destroy()
if(spider) if(spider)
{ {
spider.stop(() => sphinx.stop()) spider.stop(() => sphinx.stop())
} }
else else
{ {
sphinx.stop() sphinx.stop()
} }
} }
app.on("window-all-closed", () => { app.on("window-all-closed", () => {
if (sphinx) if (sphinx)
stop() stop()
else else
app.quit() app.quit()
}); });
app.on('before-quit', () => { app.on('before-quit', () => {
app.isQuiting = true app.isQuiting = true
if (sphinx) if (sphinx)
stop() stop()
}) })

View File

@ -11,88 +11,88 @@ const config = require('../config')
class Client extends Emiter class Client extends Emiter
{ {
constructor(options) { constructor(options) {
super(); super();
this.timeout = config.downloader.timeout; this.timeout = config.downloader.timeout;
this.maxConnections = config.downloader.maxConnections; this.maxConnections = config.downloader.maxConnections;
debug('timeout', this.timeout) debug('timeout', this.timeout)
debug('maxConnections', this.maxConnections) debug('maxConnections', this.maxConnections)
this.activeConnections = 0; this.activeConnections = 0;
this.peers = new PeerQueue(this.maxConnections); this.peers = new PeerQueue(this.maxConnections);
this.on('download', this._download); this.on('download', this._download);
// if (typeof options.ignore === 'function') { // if (typeof options.ignore === 'function') {
// this.ignore = options.ignore; // this.ignore = options.ignore;
//} //}
//else { //else {
this.ignore = function (infohash, rinfo, ignore) { this.ignore = function (infohash, rinfo, ignore) {
ignore(false); ignore(false);
}; };
// } // }
} }
_next(infohash, successful) { _next(infohash, successful) {
var req = this.peers.shift(infohash, successful); var req = this.peers.shift(infohash, successful);
if (req) { if (req) {
this.ignore(req.infohash.toString('hex'), req.rinfo, (drop) => { this.ignore(req.infohash.toString('hex'), req.rinfo, (drop) => {
if (!drop) { if (!drop) {
this.emit('download', req.rinfo, req.infohash); this.emit('download', req.rinfo, req.infohash);
} }
}); });
} }
} }
_download(rinfo, infohash) _download(rinfo, infohash)
{ {
debug('start download', infohash.toString('hex'), 'connections', this.activeConnections); debug('start download', infohash.toString('hex'), 'connections', this.activeConnections);
this.activeConnections++; this.activeConnections++;
var successful = false; var successful = false;
var socket = new net.Socket(); var socket = new net.Socket();
socket.setTimeout(this.timeout || 5000); socket.setTimeout(this.timeout || 5000);
socket.connect(rinfo.port, rinfo.address, () => { socket.connect(rinfo.port, rinfo.address, () => {
var wire = new Wire(infohash); var wire = new Wire(infohash);
socket.pipe(wire).pipe(socket); socket.pipe(wire).pipe(socket);
wire.on('metadata', (metadata, infoHash) => { wire.on('metadata', (metadata, infoHash) => {
successful = true; successful = true;
debug('successfuly downloader', infoHash, rinfo); debug('successfuly downloader', infoHash, rinfo);
this.emit('complete', metadata, infoHash, rinfo); this.emit('complete', metadata, infoHash, rinfo);
socket.destroy(); socket.destroy();
}); });
wire.on('fail', () => { wire.on('fail', () => {
socket.destroy(); socket.destroy();
}); });
wire.sendHandshake(); wire.sendHandshake();
}); });
socket.on('error', (err) => { socket.on('error', (err) => {
socket.destroy(); socket.destroy();
}); });
socket.on('timeout', (err) => { socket.on('timeout', (err) => {
socket.destroy(); socket.destroy();
}); });
socket.once('close', () => { socket.once('close', () => {
this.activeConnections--; this.activeConnections--;
this._next(infohash, successful); this._next(infohash, successful);
}); });
} }
add(rinfo, infohash) { add(rinfo, infohash) {
this.peers.push({infohash: infohash, rinfo: rinfo}); this.peers.push({infohash: infohash, rinfo: rinfo});
if (this.activeConnections < this.maxConnections && this.peers.length() > 0) { if (this.activeConnections < this.maxConnections && this.peers.length() > 0) {
this._next(); this._next();
} }
} }
isIdle() { isIdle() {
return this.peers.length() === 0; return this.peers.length() === 0;
} }
} }
module.exports = Client; module.exports = Client;

View File

@ -8,28 +8,28 @@ let sw = false
const cpuTimer = setInterval(() => { const cpuTimer = setInterval(() => {
if(!sw) { if(!sw) {
keepTime = process.hrtime(); keepTime = process.hrtime();
keepUsage = process.cpuUsage(); keepUsage = process.cpuUsage();
sw = true; sw = true;
} else { } else {
startTime = keepTime; startTime = keepTime;
startUsage = keepUsage; startUsage = keepUsage;
sw = false; sw = false;
} }
}, 500) }, 500)
cpuTimer.unref() cpuTimer.unref()
module.exports = () => { module.exports = () => {
function secNSec2ms (secNSec) { function secNSec2ms (secNSec) {
return secNSec[0] * 1000 + secNSec[1] / 1000000 return secNSec[0] * 1000 + secNSec[1] / 1000000
} }
var elapTime = process.hrtime(startTime) var elapTime = process.hrtime(startTime)
var elapUsage = process.cpuUsage(startUsage) var elapUsage = process.cpuUsage(startUsage)
var elapTimeMS = secNSec2ms(elapTime) var elapTimeMS = secNSec2ms(elapTime)
var elapUserMS = elapUsage.user var elapUserMS = elapUsage.user
var elapSystMS = elapUsage.system var elapSystMS = elapUsage.system
return Math.round(100 * ((elapUserMS + elapSystMS) / 1000) / elapTimeMS) return Math.round(100 * ((elapUserMS + elapSystMS) / 1000) / elapTimeMS)
} }

View File

@ -1,55 +1,55 @@
'use strict'; 'use strict';
var PeerQueue = function (maxSize, perLimit) { var PeerQueue = function (maxSize, perLimit) {
this.maxSize = maxSize || 200; this.maxSize = maxSize || 200;
this.perLimit = perLimit || 10; this.perLimit = perLimit || 10;
this.peers = {}; this.peers = {};
this.reqs = []; this.reqs = [];
}; };
PeerQueue.prototype._shift = function () { PeerQueue.prototype._shift = function () {
if (this.length() > 0) { if (this.length() > 0) {
var req = this.reqs.shift(); var req = this.reqs.shift();
this.peers[req.infohash.toString('hex')] = []; this.peers[req.infohash.toString('hex')] = [];
return req; return req;
} }
}; };
PeerQueue.prototype.push = function (peer) { PeerQueue.prototype.push = function (peer) {
var infohashHex = peer.infohash.toString('hex'); var infohashHex = peer.infohash.toString('hex');
var peers = this.peers[infohashHex]; var peers = this.peers[infohashHex];
if (peers && peers.length < this.perLimit) { if (peers && peers.length < this.perLimit) {
peers.push(peer); peers.push(peer);
} }
else if (this.length() < this.maxSize) { else if (this.length() < this.maxSize) {
this.reqs.push(peer); this.reqs.push(peer);
} }
}; };
PeerQueue.prototype.shift = function (infohash, successful) { PeerQueue.prototype.shift = function (infohash, successful) {
if (infohash) { if (infohash) {
var infohashHex = infohash.toString('hex'); var infohashHex = infohash.toString('hex');
if (successful === true) { if (successful === true) {
delete this.peers[infohashHex]; delete this.peers[infohashHex];
} }
else { else {
var peers = this.peers[infohashHex]; var peers = this.peers[infohashHex];
if (peers) { if (peers) {
if (peers.length > 0) { if (peers.length > 0) {
return peers.shift(); return peers.shift();
} }
else { else {
delete this.peers[infohashHex]; delete this.peers[infohashHex];
} }
} }
} }
} }
return this._shift(); return this._shift();
}; };
PeerQueue.prototype.length = function () { PeerQueue.prototype.length = function () {
return this.reqs.length; return this.reqs.length;
}; };
module.exports = PeerQueue; module.exports = PeerQueue;

View File

@ -9,312 +9,312 @@ const config = require('../config')
const fs = require('fs') const fs = require('fs')
const bootstraps = [{ const bootstraps = [{
address: 'router.bittorrent.com', address: 'router.bittorrent.com',
port: 6881 port: 6881
}, { }, {
address: 'router.utorrent.com', address: 'router.utorrent.com',
port: 6881 port: 6881
}, { }, {
address: 'dht.transmissionbt.com', address: 'dht.transmissionbt.com',
port: 6881 port: 6881
}, { }, {
address: 'dht.aelitis.com', address: 'dht.aelitis.com',
port: 6881 port: 6881
}] }]
function isValidPort(port) { function isValidPort(port) {
return port > 0 && port < (1 << 16) return port > 0 && port < (1 << 16)
} }
function generateTid() { function generateTid() {
return parseInt(Math.random() * 99).toString() return parseInt(Math.random() * 99).toString()
} }
class Spider extends Emiter { class Spider extends Emiter {
constructor(client) { constructor(client) {
super() super()
const options = arguments.length? arguments[0]: {} const options = arguments.length? arguments[0]: {}
this.table = new Table(options.tableCaption || 1000) this.table = new Table(options.tableCaption || 1000)
this.bootstraps = options.bootstraps || bootstraps this.bootstraps = options.bootstraps || bootstraps
this.token = new Token() this.token = new Token()
this.client = client this.client = client
this.ignore = false; // ignore all requests this.ignore = false; // ignore all requests
this.initialized = false; this.initialized = false;
this.walkInterval = config.spider.walkInterval; this.walkInterval = config.spider.walkInterval;
this.foundSpeed = 0; this.foundSpeed = 0;
this.foundCounter = 0; this.foundCounter = 0;
setInterval(() => { setInterval(() => {
this.foundSpeed = this.foundCounter; this.foundSpeed = this.foundCounter;
this.foundCounter = 0; this.foundCounter = 0;
}, 1000) }, 1000)
this.announceHashes = [] this.announceHashes = []
} }
send(message, address) { send(message, address) {
const data = bencode.encode(message) const data = bencode.encode(message)
this.udp.send(data, 0, data.length, address.port, address.address) this.udp.send(data, 0, data.length, address.port, address.address)
} }
findNode(id, address) { findNode(id, address) {
const message = { const message = {
t: generateTid(), t: generateTid(),
y: 'q', y: 'q',
q: 'find_node', q: 'find_node',
a: { a: {
id: id, id: id,
target: Node.generateID() target: Node.generateID()
} }
} }
this.send(message, address) this.send(message, address)
} }
getPeersRequest(infoHash, address) { getPeersRequest(infoHash, address) {
const message = { const message = {
t: generateTid(), t: generateTid(),
y: 'q', y: 'q',
q: 'get_peers', q: 'get_peers',
a: { a: {
id: this.table.id, id: this.table.id,
info_hash: infoHash info_hash: infoHash
} }
} }
this.send(message, address) this.send(message, address)
} }
announcePeer(infoHash, token, address, port) announcePeer(infoHash, token, address, port)
{ {
const message = { const message = {
t: generateTid(), t: generateTid(),
y: 'q', y: 'q',
q: 'announce_peer', q: 'announce_peer',
a: { a: {
id: this.table.id, id: this.table.id,
token: token, token: token,
info_hash: infoHash, info_hash: infoHash,
port: port, port: port,
implied_port: port ? 0 : 1 implied_port: port ? 0 : 1
} }
} }
this.send(message, address) this.send(message, address)
} }
join() { join() {
this.bootstraps.forEach((bootstrap) => { this.bootstraps.forEach((bootstrap) => {
this.findNode(this.table.id, bootstrap) this.findNode(this.table.id, bootstrap)
}) })
} }
walk() { walk() {
if(this.closing) if(this.closing)
return return
if(!this.client || this.client.isIdle()) { if(!this.client || this.client.isIdle()) {
if(!this.ignore) if(!this.ignore)
{ {
const node = this.table.shift() const node = this.table.shift()
if (node && (config.spider.nodesUsage === 0 || parseInt(Math.random() * this.table.nodes.length / config.spider.nodesUsage) === 0)) { if (node && (config.spider.nodesUsage === 0 || parseInt(Math.random() * this.table.nodes.length / config.spider.nodesUsage) === 0)) {
this.findNode(Node.neighbor(node.id, this.table.id), {address: node.address, port: node.port}) this.findNode(Node.neighbor(node.id, this.table.id), {address: node.address, port: node.port})
} }
} }
} }
setTimeout(()=>this.walk(), this.walkInterval) setTimeout(()=>this.walk(), this.walkInterval)
} }
onFoundNodes(data, token, address) { onFoundNodes(data, token, address) {
const nodes = Node.decodeNodes(data) const nodes = Node.decodeNodes(data)
nodes.forEach((node) => { nodes.forEach((node) => {
if (node.id != this.table.id && isValidPort(node.port)) { if (node.id != this.table.id && isValidPort(node.port)) {
this.table.add(node) this.table.add(node)
} }
}) })
this.emit('nodes', nodes) this.emit('nodes', nodes)
// announce torrents // announce torrents
if(token) if(token)
{ {
for(const hash of this.announceHashes) for(const hash of this.announceHashes)
{ {
this.announcePeer(hash, token, address) this.announcePeer(hash, token, address)
} }
} }
} }
onFoundPeers(peers, token, address) { onFoundPeers(peers, token, address) {
if(token) if(token)
{ {
for(const hash of this.announceHashes) for(const hash of this.announceHashes)
{ {
this.announcePeer(hash, token, address) this.announcePeer(hash, token, address)
} }
} }
if(!peers || peers.length == 0) if(!peers || peers.length == 0)
return; return;
const ips = Node.decodeCompactIP(peers) const ips = Node.decodeCompactIP(peers)
this.emit('peer', ips) this.emit('peer', ips)
} }
onFindNodeRequest(message, address) { onFindNodeRequest(message, address) {
if(config.spider.packagesLimit !== 0 && this.foundSpeed > config.spider.packagesLimit) if(config.spider.packagesLimit !== 0 && this.foundSpeed > config.spider.packagesLimit)
{ {
return return
} }
const {t: tid, a: {id: nid, target: infohash}} = message const {t: tid, a: {id: nid, target: infohash}} = message
if (tid === undefined || target.length != 20 || nid.length != 20) { if (tid === undefined || target.length != 20 || nid.length != 20) {
return return
} }
this.send({ this.send({
t: tid, t: tid,
y: 'r', y: 'r',
r: { r: {
id: Node.neighbor(nid, this.table.id), id: Node.neighbor(nid, this.table.id),
nodes: Node.encodeNodes(this.table.first()) nodes: Node.encodeNodes(this.table.first())
} }
}, address) }, address)
// also check hashes of alive ones // also check hashes of alive ones
for(const hash of this.announceHashes) for(const hash of this.announceHashes)
{ {
this.getPeersRequest(hash, address) this.getPeersRequest(hash, address)
} }
} }
onGetPeersRequest(message, address) { onGetPeersRequest(message, address) {
if(config.spider.packagesLimit !== 0 && this.foundSpeed > config.spider.packagesLimit) if(config.spider.packagesLimit !== 0 && this.foundSpeed > config.spider.packagesLimit)
{ {
return return
} }
const {t: tid, a: {id: nid, info_hash: infohash}} = message const {t: tid, a: {id: nid, info_hash: infohash}} = message
if (tid === undefined || infohash.length != 20 || nid.length != 20) { if (tid === undefined || infohash.length != 20 || nid.length != 20) {
return return
} }
this.send({ this.send({
t: tid, t: tid,
y: 'r', y: 'r',
r: { r: {
id: Node.neighbor(nid, this.table.id), id: Node.neighbor(nid, this.table.id),
nodes: Node.encodeNodes(this.table.first()), nodes: Node.encodeNodes(this.table.first()),
token: this.token.token token: this.token.token
} }
}, address) }, address)
this.emit('unensureHash', infohash.toString('hex').toUpperCase()) this.emit('unensureHash', infohash.toString('hex').toUpperCase())
// also check hashes of alive ones // also check hashes of alive ones
for(const hash of this.announceHashes) for(const hash of this.announceHashes)
{ {
this.getPeersRequest(hash, address) this.getPeersRequest(hash, address)
} }
} }
onAnnouncePeerRequest(message, address) { onAnnouncePeerRequest(message, address) {
let {t: tid, a: {info_hash: infohash, token: token, id: id, implied_port: implied, port: port}} = message let {t: tid, a: {info_hash: infohash, token: token, id: id, implied_port: implied, port: port}} = message
if (!tid) return if (!tid) return
if (!this.token.isValid(token)) return if (!this.token.isValid(token)) return
port = (implied != undefined && implied != 0) ? address.port : (port || 0) port = (implied != undefined && implied != 0) ? address.port : (port || 0)
if (!isValidPort(port)) return if (!isValidPort(port)) return
this.send({ t: tid, y: 'r', r: { id: Node.neighbor(id, this.table.id) } }, address) this.send({ t: tid, y: 'r', r: { id: Node.neighbor(id, this.table.id) } }, address)
let addressPair = { let addressPair = {
address: address.address, address: address.address,
port: port port: port
}; };
this.emit('ensureHash', infohash.toString('hex').toUpperCase(), addressPair) this.emit('ensureHash', infohash.toString('hex').toUpperCase(), addressPair)
if(this.client && !this.ignore) { if(this.client && !this.ignore) {
this.client.add(addressPair, infohash); this.client.add(addressPair, infohash);
} }
} }
onPingRequest(message, address) { onPingRequest(message, address) {
if(config.spider.packagesLimit !== 0 && this.foundSpeed > config.spider.packagesLimit) if(config.spider.packagesLimit !== 0 && this.foundSpeed > config.spider.packagesLimit)
{ {
return return
} }
this.send({ t: message.t, y: 'r', r: { id: Node.neighbor(message.a.id, this.table.id) } }, address) this.send({ t: message.t, y: 'r', r: { id: Node.neighbor(message.a.id, this.table.id) } }, address)
} }
parse(data, address) { parse(data, address) {
try { try {
const message = bencode.decode(data) const message = bencode.decode(data)
if (message.y.toString() == 'r') { if (message.y.toString() == 'r') {
if(message.r.nodes) { if(message.r.nodes) {
this.foundCounter++; this.foundCounter++;
this.onFoundNodes(message.r.nodes, message.r.token, address) this.onFoundNodes(message.r.nodes, message.r.token, address)
} else if(message.r.values) { } else if(message.r.values) {
this.onFoundPeers(message.r.values, message.r.token, address) this.onFoundPeers(message.r.values, message.r.token, address)
} }
} else if (message.y.toString() == 'q') { } else if (message.y.toString() == 'q') {
this.foundCounter++; this.foundCounter++;
switch(message.q.toString()) { switch(message.q.toString()) {
case 'get_peers': case 'get_peers':
this.onGetPeersRequest(message, address) this.onGetPeersRequest(message, address)
break break
case 'announce_peer': case 'announce_peer':
this.onAnnouncePeerRequest(message, address) this.onAnnouncePeerRequest(message, address)
break break
case 'find_node': case 'find_node':
this.onFindNodeRequest(message, address) this.onFindNodeRequest(message, address)
break break
case 'ping': case 'ping':
this.onPingRequest(message, address) this.onPingRequest(message, address)
break break
} }
} }
} catch (err) {} } catch (err) {}
} }
listen(port) { listen(port) {
if(this.initialized) if(this.initialized)
return return
this.initialized = true this.initialized = true
this.closing = false this.closing = false
this.udp = dgram.createSocket('udp4') this.udp = dgram.createSocket('udp4')
this.udp.bind(port) this.udp.bind(port)
this.udp.on('listening', () => { this.udp.on('listening', () => {
console.log(`Listen DHT protocol on ${this.udp.address().address}:${this.udp.address().port}`) console.log(`Listen DHT protocol on ${this.udp.address().address}:${this.udp.address().port}`)
}) })
this.udp.on('message', (data, addr) => { this.udp.on('message', (data, addr) => {
this.parse(data, addr) this.parse(data, addr)
}) })
this.udp.on('error', (err) => {}) this.udp.on('error', (err) => {})
this.joinInterval = setInterval(() => { this.joinInterval = setInterval(() => {
if(!this.client || this.client.isIdle()) { if(!this.client || this.client.isIdle()) {
this.join() this.join()
} }
}, 3000) }, 3000)
this.join() this.join()
this.walk() this.walk()
} }
close(callback) close(callback)
{ {
if(!this.initialized) { if(!this.initialized) {
if(callback) if(callback)
callback() callback()
return return
} }
clearInterval(this.joinInterval) clearInterval(this.joinInterval)
this.closing = true this.closing = true
this.udp.close(() => { this.udp.close(() => {
this.initialized = false this.initialized = false
if(callback) if(callback)
callback() callback()
}) })
} }
} }
module.exports = Spider module.exports = Spider

View File

@ -13,119 +13,119 @@ const connectionIdLow = 0x27101980
const requests = {}; const requests = {};
let message = function (buf, host, port) { let message = function (buf, host, port) {
server.send(buf, 0, buf.length, port, host, function(err, bytes) { server.send(buf, 0, buf.length, port, host, function(err, bytes) {
if (err) { if (err) {
console.log(err.message); console.log(err.message);
} }
}); });
}; };
let connectTracker = function(connection) { let connectTracker = function(connection) {
debug('start screape connection'); debug('start screape connection');
let buffer = new Buffer(16); let buffer = new Buffer(16);
const transactionId = Math.floor((Math.random()*100000)+1); const transactionId = Math.floor((Math.random()*100000)+1);
buffer.fill(0); buffer.fill(0);
buffer.writeUInt32BE(connectionIdHigh, 0); buffer.writeUInt32BE(connectionIdHigh, 0);
buffer.writeUInt32BE(connectionIdLow, 4); buffer.writeUInt32BE(connectionIdLow, 4);
buffer.writeUInt32BE(ACTION_CONNECT, 8); buffer.writeUInt32BE(ACTION_CONNECT, 8);
buffer.writeUInt32BE(transactionId, 12); buffer.writeUInt32BE(transactionId, 12);
// очистка старых соединений // очистка старых соединений
for(const transaction in requests) { for(const transaction in requests) {
if((new Date).getTime() - requests[transaction].date.getTime() > config.udpTrackersTimeout) { if((new Date).getTime() - requests[transaction].date.getTime() > config.udpTrackersTimeout) {
delete requests[transaction]; delete requests[transaction];
} }
} }
requests[transactionId] = connection; requests[transactionId] = connection;
message(buffer, connection.host, connection.port); message(buffer, connection.host, connection.port);
}; };
let scrapeTorrent = function (connectionIdHigh, connectionIdLow, transactionId) { let scrapeTorrent = function (connectionIdHigh, connectionIdLow, transactionId) {
let connection = requests[transactionId]; let connection = requests[transactionId];
if(!connection) if(!connection)
return; return;
if(!connection.hash || connection.hash.length != 40) if(!connection.hash || connection.hash.length != 40)
return return
debug('start scrape'); debug('start scrape');
let buffer = new Buffer(56) let buffer = new Buffer(56)
buffer.fill(0); buffer.fill(0);
buffer.writeUInt32BE(connectionIdHigh, 0); buffer.writeUInt32BE(connectionIdHigh, 0);
buffer.writeUInt32BE(connectionIdLow, 4); buffer.writeUInt32BE(connectionIdLow, 4);
buffer.writeUInt32BE(ACTION_SCRAPE, 8); buffer.writeUInt32BE(ACTION_SCRAPE, 8);
buffer.writeUInt32BE(transactionId, 12); buffer.writeUInt32BE(transactionId, 12);
try try
{ {
buffer.write(connection.hash, 16, buffer.length, 'hex'); buffer.write(connection.hash, 16, buffer.length, 'hex');
// do scrape // do scrape
message(buffer, connection.host, connection.port); message(buffer, connection.host, connection.port);
} catch(error) } catch(error)
{ {
console.log('ERROR on scrape', error) console.log('ERROR on scrape', error)
} }
}; };
server.on("message", function (msg, rinfo) { server.on("message", function (msg, rinfo) {
let buffer = new Buffer(msg) let buffer = new Buffer(msg)
const action = buffer.readUInt32BE(0, 4); const action = buffer.readUInt32BE(0, 4);
const transactionId = buffer.readUInt32BE(4, 4); const transactionId = buffer.readUInt32BE(4, 4);
if(!(transactionId in requests)) if(!(transactionId in requests))
return; return;
debug("returned action: " + action); debug("returned action: " + action);
debug("returned transactionId: " + transactionId); debug("returned transactionId: " + transactionId);
if (action === ACTION_CONNECT) { if (action === ACTION_CONNECT) {
debug("connect response"); debug("connect response");
let connectionIdHigh = buffer.readUInt32BE(8, 4); let connectionIdHigh = buffer.readUInt32BE(8, 4);
let connectionIdLow = buffer.readUInt32BE(12, 4); let connectionIdLow = buffer.readUInt32BE(12, 4);
scrapeTorrent(connectionIdHigh, connectionIdLow, transactionId); scrapeTorrent(connectionIdHigh, connectionIdLow, transactionId);
} else if (action === ACTION_SCRAPE) { } else if (action === ACTION_SCRAPE) {
debug("scrape response"); debug("scrape response");
let seeders = buffer.readUInt32BE(8, 4); let seeders = buffer.readUInt32BE(8, 4);
let completed = buffer.readUInt32BE(12, 4); let completed = buffer.readUInt32BE(12, 4);
let leechers = buffer.readUInt32BE(16, 4); let leechers = buffer.readUInt32BE(16, 4);
let connection = requests[transactionId]; let connection = requests[transactionId];
connection.callback({ connection.callback({
host: connection.host, host: connection.host,
port: connection.port, port: connection.port,
hash: connection.hash, hash: connection.hash,
seeders, seeders,
completed, completed,
leechers leechers
}) })
delete requests[transactionId]; delete requests[transactionId];
} else if (action === ACTION_ERROR) { } else if (action === ACTION_ERROR) {
delete requests[transactionId]; delete requests[transactionId];
console.log("error in scrape response"); console.log("error in scrape response");
} }
}); });
let getPeersStatistic = (host, port, hash, callback) => { let getPeersStatistic = (host, port, hash, callback) => {
let connection = { let connection = {
host, port, hash, callback, date: new Date() host, port, hash, callback, date: new Date()
} }
connectTracker(connection); connectTracker(connection);
} }
server.on("listening", function () { server.on("listening", function () {
var address = server.address(); var address = server.address();
console.log("listening udp tracker respose on " + address.address + ":" + address.port); console.log("listening udp tracker respose on " + address.address + ":" + address.port);
}); });
server.bind(config.udpTrackersPort); server.bind(config.udpTrackersPort);

View File

@ -18,230 +18,230 @@ var EXT_HANDSHAKE_ID = 0;
var BT_MSG_ID = 20; var BT_MSG_ID = 20;
var Wire = function(infohash) { var Wire = function(infohash) {
stream.Duplex.call(this); stream.Duplex.call(this);
this._bitfield = new BitField(0, { grow: BITFIELD_GROW }); this._bitfield = new BitField(0, { grow: BITFIELD_GROW });
this._infohash = infohash; this._infohash = infohash;
this._buffer = []; this._buffer = [];
this._bufferSize = 0; this._bufferSize = 0;
this._next = null; this._next = null;
this._nextSize = 0; this._nextSize = 0;
this._metadata = null; this._metadata = null;
this._metadataSize = null; this._metadataSize = null;
this._numPieces = 0; this._numPieces = 0;
this._ut_metadata = null; this._ut_metadata = null;
this._onHandshake(); this._onHandshake();
} }
util.inherits(Wire, stream.Duplex); util.inherits(Wire, stream.Duplex);
Wire.prototype._onMessageLength = function (buffer) { Wire.prototype._onMessageLength = function (buffer) {
if (buffer.length >= 4) { if (buffer.length >= 4) {
var length = buffer.readUInt32BE(0); var length = buffer.readUInt32BE(0);
if (length > 0) { if (length > 0) {
this._register(length, this._onMessage) this._register(length, this._onMessage)
} }
} }
}; };
Wire.prototype._onMessage = function (buffer) { Wire.prototype._onMessage = function (buffer) {
this._register(4, this._onMessageLength) this._register(4, this._onMessageLength)
if (buffer[0] == BT_MSG_ID) { if (buffer[0] == BT_MSG_ID) {
this._onExtended(buffer.readUInt8(1), buffer.slice(2)); this._onExtended(buffer.readUInt8(1), buffer.slice(2));
} }
}; };
Wire.prototype._onExtended = function(ext, buf) { Wire.prototype._onExtended = function(ext, buf) {
if (ext === 0) { if (ext === 0) {
try { try {
this._onExtHandshake(bencode.decode(buf)); this._onExtHandshake(bencode.decode(buf));
} }
catch (err) { catch (err) {
this._fail(); this._fail();
} }
} }
else { else {
this._onPiece(buf); this._onPiece(buf);
} }
}; };
Wire.prototype._register = function (size, next) { Wire.prototype._register = function (size, next) {
this._nextSize = size; this._nextSize = size;
this._next = next; this._next = next;
}; };
Wire.prototype.end = function() { Wire.prototype.end = function() {
stream.Duplex.prototype.end.apply(this, arguments); stream.Duplex.prototype.end.apply(this, arguments);
}; };
Wire.prototype._onHandshake = function() { Wire.prototype._onHandshake = function() {
this._register(1, function(buffer) { this._register(1, function(buffer) {
if (buffer.length == 0) { if (buffer.length == 0) {
this.end(); this.end();
return this._fail(); return this._fail();
} }
var pstrlen = buffer.readUInt8(0); var pstrlen = buffer.readUInt8(0);
this._register(pstrlen + 48, function(handshake) { this._register(pstrlen + 48, function(handshake) {
var protocol = handshake.slice(0, pstrlen); var protocol = handshake.slice(0, pstrlen);
if (protocol.toString() !== BT_PROTOCOL.toString()) { if (protocol.toString() !== BT_PROTOCOL.toString()) {
this.end(); this.end();
this._fail(); this._fail();
return; return;
} }
handshake = handshake.slice(pstrlen); handshake = handshake.slice(pstrlen);
if ( !!(handshake[5] & 0x10) ) { if ( !!(handshake[5] & 0x10) ) {
this._register(4, this._onMessageLength); this._register(4, this._onMessageLength);
this._sendExtHandshake(); this._sendExtHandshake();
} }
else { else {
this._fail(); this._fail();
} }
}.bind(this)); }.bind(this));
}.bind(this)); }.bind(this));
}; };
Wire.prototype._onExtHandshake = function(extHandshake) { Wire.prototype._onExtHandshake = function(extHandshake) {
if (!extHandshake.metadata_size || !extHandshake.m.ut_metadata if (!extHandshake.metadata_size || !extHandshake.m.ut_metadata
|| extHandshake.metadata_size > MAX_METADATA_SIZE) { || extHandshake.metadata_size > MAX_METADATA_SIZE) {
this._fail(); this._fail();
return; return;
} }
this._metadataSize = extHandshake.metadata_size; this._metadataSize = extHandshake.metadata_size;
this._numPieces = Math.ceil(this._metadataSize / PIECE_LENGTH); this._numPieces = Math.ceil(this._metadataSize / PIECE_LENGTH);
this._ut_metadata = extHandshake.m.ut_metadata; this._ut_metadata = extHandshake.m.ut_metadata;
this._requestPieces(); this._requestPieces();
} }
Wire.prototype._requestPieces = function() { Wire.prototype._requestPieces = function() {
this._metadata = new Buffer(this._metadataSize); this._metadata = new Buffer(this._metadataSize);
for (var piece = 0; piece < this._numPieces; piece++) { for (var piece = 0; piece < this._numPieces; piece++) {
this._requestPiece(piece); this._requestPiece(piece);
} }
}; };
Wire.prototype._requestPiece = function(piece) { Wire.prototype._requestPiece = function(piece) {
var msg = Buffer.concat([ var msg = Buffer.concat([
new Buffer([BT_MSG_ID]), new Buffer([BT_MSG_ID]),
new Buffer([this._ut_metadata]), new Buffer([this._ut_metadata]),
bencode.encode({msg_type: 0, piece: piece}) bencode.encode({msg_type: 0, piece: piece})
]); ]);
this._sendMessage(msg); this._sendMessage(msg);
}; };
Wire.prototype._sendPacket = function(packet) { Wire.prototype._sendPacket = function(packet) {
this.push(packet); this.push(packet);
}; };
Wire.prototype._sendMessage = function(msg) { Wire.prototype._sendMessage = function(msg) {
var buf = new Buffer(4); var buf = new Buffer(4);
buf.writeUInt32BE(msg.length, 0); buf.writeUInt32BE(msg.length, 0);
this._sendPacket(Buffer.concat([buf, msg])); this._sendPacket(Buffer.concat([buf, msg]));
}; };
Wire.prototype.sendHandshake = function() { Wire.prototype.sendHandshake = function() {
var peerID = Node.generateID(); var peerID = Node.generateID();
var packet = Buffer.concat([ var packet = Buffer.concat([
new Buffer([BT_PROTOCOL.length]), new Buffer([BT_PROTOCOL.length]),
BT_PROTOCOL, BT_RESERVED, this._infohash, peerID BT_PROTOCOL, BT_RESERVED, this._infohash, peerID
]); ]);
this._sendPacket(packet); this._sendPacket(packet);
}; };
Wire.prototype._sendExtHandshake = function() { Wire.prototype._sendExtHandshake = function() {
var msg = Buffer.concat([ var msg = Buffer.concat([
new Buffer([BT_MSG_ID]), new Buffer([BT_MSG_ID]),
new Buffer([EXT_HANDSHAKE_ID]), new Buffer([EXT_HANDSHAKE_ID]),
bencode.encode({m: {ut_metadata: 1}}) bencode.encode({m: {ut_metadata: 1}})
]); ]);
this._sendMessage(msg); this._sendMessage(msg);
}; };
Wire.prototype._onPiece = function(piece) { Wire.prototype._onPiece = function(piece) {
var dict, trailer; var dict, trailer;
try { try {
var str = piece.toString(); var str = piece.toString();
var trailerIndex = str.indexOf('ee') + 2; var trailerIndex = str.indexOf('ee') + 2;
dict = bencode.decode(str.substring(0, trailerIndex)); dict = bencode.decode(str.substring(0, trailerIndex));
trailer = piece.slice(trailerIndex); trailer = piece.slice(trailerIndex);
} }
catch (err) { catch (err) {
this._fail(); this._fail();
return; return;
} }
if (dict.msg_type != 1) { if (dict.msg_type != 1) {
this._fail(); this._fail();
return; return;
} }
if (trailer.length > PIECE_LENGTH) { if (trailer.length > PIECE_LENGTH) {
this._fail(); this._fail();
return; return;
} }
trailer.copy(this._metadata, dict.piece * PIECE_LENGTH); trailer.copy(this._metadata, dict.piece * PIECE_LENGTH);
this._bitfield.set(dict.piece); this._bitfield.set(dict.piece);
this._checkDone(); this._checkDone();
}; };
Wire.prototype._checkDone = function () { Wire.prototype._checkDone = function () {
var done = true; var done = true;
for (var piece = 0; piece < this._numPieces; piece++) { for (var piece = 0; piece < this._numPieces; piece++) {
if (!this._bitfield.get(piece)) { if (!this._bitfield.get(piece)) {
done = false; done = false;
break; break;
} }
} }
if (!done) { if (!done) {
return return
} }
this._onDone(this._metadata); this._onDone(this._metadata);
}; };
Wire.prototype._onDone = function(metadata) { Wire.prototype._onDone = function(metadata) {
try { try {
var info = bencode.decode(metadata).info; var info = bencode.decode(metadata).info;
if (info) { if (info) {
metadata = bencode.encode(info); metadata = bencode.encode(info);
} }
} }
catch (err) { catch (err) {
this._fail(); this._fail();
return; return;
} }
var infohash = crypto.createHash('sha1').update(metadata).digest('hex'); var infohash = crypto.createHash('sha1').update(metadata).digest('hex');
if (this._infohash.toString('hex') != infohash ) { if (this._infohash.toString('hex') != infohash ) {
this._fail(); this._fail();
return false; return false;
} }
this.emit('metadata', {info: bencode.decode(metadata, 'utf8')}, this._infohash); this.emit('metadata', {info: bencode.decode(metadata, 'utf8')}, this._infohash);
}; };
Wire.prototype._fail = function() { Wire.prototype._fail = function() {
this.emit('fail'); this.emit('fail');
}; };
Wire.prototype._write = function (buf, encoding, next) { Wire.prototype._write = function (buf, encoding, next) {
this._bufferSize += buf.length; this._bufferSize += buf.length;
this._buffer.push(buf); this._buffer.push(buf);
while (this._bufferSize >= this._nextSize) { while (this._bufferSize >= this._nextSize) {
var buffer = Buffer.concat(this._buffer); var buffer = Buffer.concat(this._buffer);
this._bufferSize -= this._nextSize; this._bufferSize -= this._nextSize;
this._buffer = this._bufferSize this._buffer = this._bufferSize
? [buffer.slice(this._nextSize)] ? [buffer.slice(this._nextSize)]
: []; : [];
this._next(buffer.slice(0, this._nextSize)); this._next(buffer.slice(0, this._nextSize));
} }
next(null); next(null);
} }
Wire.prototype._read = function() { Wire.prototype._read = function() {
// do nothing // do nothing
}; };
module.exports = Wire; module.exports = Wire;

View File

@ -1,10 +1,10 @@
// https://stackoverflow.com/questions/15270902/check-for-internet-connectivity-in-nodejs // https://stackoverflow.com/questions/15270902/check-for-internet-connectivity-in-nodejs
module.exports = function checkInternet(cb) { module.exports = function checkInternet(cb) {
require('dns').lookup('google.com',function(err) { require('dns').lookup('google.com',function(err) {
if (err && err.code == "ENOTFOUND") { if (err && err.code == "ENOTFOUND") {
cb(false); cb(false);
} else { } else {
cb(true); cb(true);
} }
}) })
} }

View File

@ -11,7 +11,7 @@ let config = {
udpTrackersTimeout: 3 * 60 * 1000, udpTrackersTimeout: 3 * 60 * 1000,
peerId: undefined, peerId: undefined,
language: 'en', language: 'en',
p2p: true, p2p: true,
p2pConnections: 10, p2pConnections: 10,
p2pBootstrap: true, p2pBootstrap: true,
@ -25,9 +25,9 @@ let config = {
sitemapMaxSize: 25000, sitemapMaxSize: 25000,
sphinx: { sphinx: {
host : '127.0.0.1', host : '127.0.0.1',
port : 9306, port : 9306,
connectionLimit: 30 connectionLimit: 30
}, },
spider: { spider: {
@ -79,8 +79,8 @@ const configProxy = new Proxy(config, {
target[prop] = value target[prop] = value
if(!fs.existsSync(configPath)) if(!fs.existsSync(configPath))
fs.writeFileSync(configPath, '{}') fs.writeFileSync(configPath, '{}')
const data = fs.readFileSync(configPath) const data = fs.readFileSync(configPath)

View File

@ -13,31 +13,31 @@ const currentVersion = 4
module.exports = async (callback, mainWindow, sphinxApp) => { module.exports = async (callback, mainWindow, sphinxApp) => {
const sphinx = await single().waitConnection() const sphinx = await single().waitConnection()
const setVersion = async (version) => { const setVersion = async (version) => {
await sphinx.query(`delete from version where id = 1`) await sphinx.query(`delete from version where id = 1`)
await sphinx.query(`insert into version(id, version) values(1, ${version})`) await sphinx.query(`insert into version(id, version) values(1, ${version})`)
if(sphinxApp) if(sphinxApp)
fs.writeFileSync(`${sphinxApp.directoryPath}/version.vrs`, version) fs.writeFileSync(`${sphinxApp.directoryPath}/version.vrs`, version)
} }
let patchWindow; let patchWindow;
const openPatchWindow = () => { const openPatchWindow = () => {
if(patchWindow) if(patchWindow)
return return
if(!BrowserWindow) if(!BrowserWindow)
return return
if(mainWindow) if(mainWindow)
mainWindow.hide() mainWindow.hide()
patchWindow = new BrowserWindow({width: 800, height: 400, closable: false}) patchWindow = new BrowserWindow({width: 800, height: 400, closable: false})
patchWindow.setMenu(null) patchWindow.setMenu(null)
patchWindow.loadURL("data:text/html;charset=utf-8," + encodeURI(` patchWindow.loadURL("data:text/html;charset=utf-8," + encodeURI(`
<html> <html>
<head><title>Database patching...</title></head> <head><title>Database patching...</title></head>
<style> <style>
@ -97,146 +97,146 @@ module.exports = async (callback, mainWindow, sphinxApp) => {
</body> </body>
</html> </html>
`)) `))
} }
const patch = async (version) => { const patch = async (version) => {
console.log('db version', version) console.log('db version', version)
switch(version) switch(version)
{ {
case 1: case 1:
{ {
console.log('patch db to version 2') console.log('patch db to version 2')
openPatchWindow() openPatchWindow()
let i = 1 let i = 1
const torrents = (await sphinx.query("SELECT COUNT(*) AS c FROM torrents"))[0].c const torrents = (await sphinx.query("SELECT COUNT(*) AS c FROM torrents"))[0].c
const files = (await sphinx.query("SELECT COUNT(*) AS c FROM files"))[0].c const files = (await sphinx.query("SELECT COUNT(*) AS c FROM files"))[0].c
await forBigTable(sphinx, 'torrents', async (torrent) => { await forBigTable(sphinx, 'torrents', async (torrent) => {
console.log('update index', torrent.id, torrent.name, '[', i, 'of', torrents, ']') console.log('update index', torrent.id, torrent.name, '[', i, 'of', torrents, ']')
if(patchWindow) if(patchWindow)
patchWindow.webContents.send('reindex', {field: torrent.name, index: i++, all: torrents, torrent: true}) patchWindow.webContents.send('reindex', {field: torrent.name, index: i++, all: torrents, torrent: true})
torrent.nameIndex = torrent.name torrent.nameIndex = torrent.name
await sphinx.query(`DELETE FROM torrents WHERE id = ${torrent.id}`) await sphinx.query(`DELETE FROM torrents WHERE id = ${torrent.id}`)
await sphinx.insertValues('torrents', torrent) await sphinx.insertValues('torrents', torrent)
}) })
i = 1 i = 1
await forBigTable(sphinx, 'files', async (file) => { await forBigTable(sphinx, 'files', async (file) => {
console.log('update index', file.id, file.path, '[', i, 'of', files, ']') console.log('update index', file.id, file.path, '[', i, 'of', files, ']')
if(patchWindow) if(patchWindow)
patchWindow.webContents.send('reindex', {field: file.path, index: i++, all: files}) patchWindow.webContents.send('reindex', {field: file.path, index: i++, all: files})
file.pathIndex = file.path file.pathIndex = file.path
await sphinx.query(`DELETE FROM files WHERE id = ${file.id}`) await sphinx.query(`DELETE FROM files WHERE id = ${file.id}`)
await sphinx.insertValues('files', file) await sphinx.insertValues('files', file)
}) })
await setVersion(2) await setVersion(2)
} }
case 2: case 2:
{ {
openPatchWindow() openPatchWindow()
console.log('optimizing torrents') console.log('optimizing torrents')
if(patchWindow) if(patchWindow)
patchWindow.webContents.send('optimize', {field: 'torrents'}) patchWindow.webContents.send('optimize', {field: 'torrents'})
sphinx.query(`OPTIMIZE INDEX torrents`) sphinx.query(`OPTIMIZE INDEX torrents`)
await sphinxApp.waitOptimized('torrents') await sphinxApp.waitOptimized('torrents')
console.log('optimizing files') console.log('optimizing files')
if(patchWindow) if(patchWindow)
patchWindow.webContents.send('optimize', {field: 'files'}) patchWindow.webContents.send('optimize', {field: 'files'})
sphinx.query(`OPTIMIZE INDEX files`) sphinx.query(`OPTIMIZE INDEX files`)
await sphinxApp.waitOptimized('files') await sphinxApp.waitOptimized('files')
await setVersion(3) await setVersion(3)
} }
case 3: case 3:
{ {
openPatchWindow() openPatchWindow()
// block xxx // block xxx
let bad = 0 let bad = 0
let i = 1 let i = 1
const torrents = (await sphinx.query("SELECT COUNT(*) AS c FROM torrents"))[0].c const torrents = (await sphinx.query("SELECT COUNT(*) AS c FROM torrents"))[0].c
await forBigTable(sphinx, 'torrents', async (torrent) => { await forBigTable(sphinx, 'torrents', async (torrent) => {
console.log('update index', torrent.id, torrent.name, '[', i, 'of', torrents, '] - delete:', bad) console.log('update index', torrent.id, torrent.name, '[', i, 'of', torrents, '] - delete:', bad)
if(patchWindow) if(patchWindow)
patchWindow.webContents.send('reindex', {field: torrent.name, index: i++, all: torrents, torrent: true}) patchWindow.webContents.send('reindex', {field: torrent.name, index: i++, all: torrents, torrent: true})
if(torrent.contentcategory == 'xxx') if(torrent.contentcategory == 'xxx')
{ {
delete torrent.contentcategory delete torrent.contentcategory
delete torrent.contenttype delete torrent.contenttype
torrent = await getTorrent(sphinx, null, torrent) // get files torrent = await getTorrent(sphinx, null, torrent) // get files
torrentTypeDetect(torrent, torrent.filesList) torrentTypeDetect(torrent, torrent.filesList)
if(torrent.contentType == 'bad') if(torrent.contentType == 'bad')
{ {
console.log('remove bad torrent', torrent.name) console.log('remove bad torrent', torrent.name)
bad++ bad++
await sphinx.query(`DELETE FROM torrents WHERE hash = '${torrent.hash}'`) await sphinx.query(`DELETE FROM torrents WHERE hash = '${torrent.hash}'`)
await sphinx.query(`DELETE FROM files WHERE hash = '${torrent.hash}'`) await sphinx.query(`DELETE FROM files WHERE hash = '${torrent.hash}'`)
} }
} }
}) })
console.log('removed', bad, 'torrents') console.log('removed', bad, 'torrents')
await setVersion(4) await setVersion(4)
} }
} }
console.log('db patch done') console.log('db patch done')
sphinx.destroy() sphinx.destroy()
if(patchWindow) if(patchWindow)
{ {
patchWindow.destroy() patchWindow.destroy()
if(mainWindow) if(mainWindow)
mainWindow.show() mainWindow.show()
} }
callback() callback()
} }
// init of db, we can set version to last // init of db, we can set version to last
if(sphinxApp && sphinxApp.isInitDb) if(sphinxApp && sphinxApp.isInitDb)
{ {
console.log('new db, set version to last version', currentVersion) console.log('new db, set version to last version', currentVersion)
await setVersion(currentVersion) await setVersion(currentVersion)
} }
sphinx.query('select * from version', async (err, version) => { sphinx.query('select * from version', async (err, version) => {
if(err) if(err)
{ {
console.log('error on version get on db patch') console.log('error on version get on db patch')
return return
} }
if(!version || !version[0] || !version[0].version) if(!version || !version[0] || !version[0].version)
{ {
if(sphinxApp && fs.existsSync(`${sphinxApp.directoryPath}/version.vrs`)) if(sphinxApp && fs.existsSync(`${sphinxApp.directoryPath}/version.vrs`))
{ {
const ver = parseInt(fs.readFileSync(`${sphinxApp.directoryPath}/version.vrs`)) const ver = parseInt(fs.readFileSync(`${sphinxApp.directoryPath}/version.vrs`))
if(ver > 0) if(ver > 0)
{ {
console.log('readed version from version.vrs', ver) console.log('readed version from version.vrs', ver)
patch(ver) patch(ver)
} }
else else
{ {
console.log('error: bad version in version.vrs') console.log('error: bad version in version.vrs')
} }
} }
else else
{ {
console.log('version not founded, set db version to 1') console.log('version not founded, set db version to 1')
await setVersion(1) await setVersion(1)
patch(1) patch(1)
} }
} }
else else
{ {
patch(version[0].version) patch(version[0].version)
} }
}) })
} }

View File

@ -2,67 +2,67 @@ const path = require('path')
const fs = require('fs') const fs = require('fs')
module.exports = (app) => { module.exports = (app) => {
if (fs.existsSync(`./${app}`)) { if (fs.existsSync(`./${app}`)) {
return `./${app}` return `./${app}`
} }
if (/^win/.test(process.platform) && fs.existsSync(`./${app}.exe`)) { if (/^win/.test(process.platform) && fs.existsSync(`./${app}.exe`)) {
return `./${app}.exe` return `./${app}.exe`
} }
if (/^win/.test(process.platform) && fs.existsSync(`./${process.arch}/${app}.exe`)) { if (/^win/.test(process.platform) && fs.existsSync(`./${process.arch}/${app}.exe`)) {
return `./${process.arch}/${app}.exe` return `./${process.arch}/${app}.exe`
} }
if(/^win/.test(process.platform) && fs.existsSync(path.dirname(process.execPath) + `/${app}.exe`)) { if(/^win/.test(process.platform) && fs.existsSync(path.dirname(process.execPath) + `/${app}.exe`)) {
return path.dirname(process.execPath) + `/${app}.exe` return path.dirname(process.execPath) + `/${app}.exe`
} }
if(/^win/.test(process.platform) && fs.existsSync(path.dirname(process.execPath) + `/${process.arch}/${app}.exe`)) { if(/^win/.test(process.platform) && fs.existsSync(path.dirname(process.execPath) + `/${process.arch}/${app}.exe`)) {
return path.dirname(process.execPath) + `/${process.arch}/${app}.exe` return path.dirname(process.execPath) + `/${process.arch}/${app}.exe`
} }
if (fs.existsSync(fs.realpathSync(__dirname) + `/${app}`)) { if (fs.existsSync(fs.realpathSync(__dirname) + `/${app}`)) {
return fs.realpathSync(__dirname) + `/${app}` return fs.realpathSync(__dirname) + `/${app}`
} }
if (fs.existsSync(fs.realpathSync(__dirname) + `/${process.arch}/${app}`)) { if (fs.existsSync(fs.realpathSync(__dirname) + `/${process.arch}/${app}`)) {
return fs.realpathSync(__dirname) + `/${process.arch}/${app}` return fs.realpathSync(__dirname) + `/${process.arch}/${app}`
} }
if (fs.existsSync(fs.realpathSync(path.join(__dirname, '/../../..')) + `/${app}`)) { if (fs.existsSync(fs.realpathSync(path.join(__dirname, '/../../..')) + `/${app}`)) {
return fs.realpathSync(path.join(__dirname, '/../../..')) + `/${app}` return fs.realpathSync(path.join(__dirname, '/../../..')) + `/${app}`
} }
if (fs.existsSync(fs.realpathSync(path.join(__dirname, '/../../..')) + `/${process.arch}/${app}`)) { if (fs.existsSync(fs.realpathSync(path.join(__dirname, '/../../..')) + `/${process.arch}/${app}`)) {
return fs.realpathSync(path.join(__dirname, '/../../..')) + `/${process.arch}/${app}` return fs.realpathSync(path.join(__dirname, '/../../..')) + `/${process.arch}/${app}`
} }
try { try {
if (process.platform === 'darwin' && fs.existsSync(fs.realpathSync(path.join(__dirname, '/../../../MacOS')) + `/${app}`)) { if (process.platform === 'darwin' && fs.existsSync(fs.realpathSync(path.join(__dirname, '/../../../MacOS')) + `/${app}`)) {
return fs.realpathSync(path.join(__dirname, '/../../../MacOS')) + `/${app}` return fs.realpathSync(path.join(__dirname, '/../../../MacOS')) + `/${app}`
} }
} catch (e) {} } catch (e) {}
if (/^win/.test(process.platform) && fs.existsSync(`imports/win/${app}.exe`)) { if (/^win/.test(process.platform) && fs.existsSync(`imports/win/${app}.exe`)) {
return `imports/win/${app}.exe` return `imports/win/${app}.exe`
} }
if (/^win/.test(process.platform) && fs.existsSync(`imports/win/${process.arch}/${app}.exe`)) { if (/^win/.test(process.platform) && fs.existsSync(`imports/win/${process.arch}/${app}.exe`)) {
return `imports/win/${process.arch}/${app}.exe` return `imports/win/${process.arch}/${app}.exe`
} }
if (process.platform === 'linux' && fs.existsSync(`imports/linux/${app}`)) { if (process.platform === 'linux' && fs.existsSync(`imports/linux/${app}`)) {
return `imports/linux/${app}` return `imports/linux/${app}`
} }
if (process.platform === 'linux' && fs.existsSync(`imports/linux/${process.arch}/${app}`)) { if (process.platform === 'linux' && fs.existsSync(`imports/linux/${process.arch}/${app}`)) {
return `imports/linux/${process.arch}/${app}` return `imports/linux/${process.arch}/${app}`
} }
if (process.platform === 'darwin' && fs.existsSync(`imports/darwin/${app}`)) { if (process.platform === 'darwin' && fs.existsSync(`imports/darwin/${app}`)) {
return `imports/darwin/${app}` return `imports/darwin/${app}`
} }
return `${app}` return `${app}`
} }

View File

@ -1,22 +1,22 @@
module.exports = class Feed { module.exports = class Feed {
constructor({sphinx}) constructor({sphinx})
{ {
this.feed = [] this.feed = []
this.sphinx = sphinx this.sphinx = sphinx
this.loaded = false this.loaded = false
this.max = 1000 this.max = 1000
} }
size() size()
{ {
return this.feed.length return this.feed.length
} }
async save() { async save() {
if(!this.loaded) if(!this.loaded)
return // feed not loaded on begining, ignore saving return // feed not loaded on begining, ignore saving
console.log('saving feed') console.log('saving feed')
await this.sphinx.query('delete from feed where id > 0') await this.sphinx.query('delete from feed where id > 0')
let id = 0 let id = 0
return Promise.all( return Promise.all(
@ -24,77 +24,77 @@ module.exports = class Feed {
async record => await this.sphinx.query('insert into feed(id, data) values(?, ?)', [++id, JSON.stringify(record)]) async record => await this.sphinx.query('insert into feed(id, data) values(?, ?)', [++id, JSON.stringify(record)])
) )
) )
} }
async load() { async load() {
this.feed = await this.sphinx.query('select * from feed limit 1000') this.feed = await this.sphinx.query('select * from feed limit 1000')
if(this.feed && this.feed.length > 0) if(this.feed && this.feed.length > 0)
this.feed = this.feed.map(f => JSON.parse(f.data)) this.feed = this.feed.map(f => JSON.parse(f.data))
else else
this.feed = [] this.feed = []
this._order() this._order()
this.loaded = true this.loaded = true
console.log('lodead feed') console.log('lodead feed')
} }
clear() clear()
{ {
console.log('clearing feed') console.log('clearing feed')
this.feed = [] this.feed = []
} }
add(data) { add(data) {
let index = -1 let index = -1
if(data.hash) if(data.hash)
index = this.feed.findIndex(element => element.hash === data.hash) index = this.feed.findIndex(element => element.hash === data.hash)
if(index >= 0) if(index >= 0)
this.feed[index] = Object.assign(this.feed[index], data) // just push up element this.feed[index] = Object.assign(this.feed[index], data) // just push up element
else else
{ {
if(typeof data == 'object') if(typeof data == 'object')
{ {
data.feedDate = Math.floor(Date.now() / 1000) data.feedDate = Math.floor(Date.now() / 1000)
} }
if(this.feed.length >= this.max) if(this.feed.length >= this.max)
{ {
//cleanup //cleanup
for(let i = this.feed.length - 1; i <= 0; i--) for(let i = this.feed.length - 1; i <= 0; i--)
if(this._compare(this.feed[i]) <= 0) if(this._compare(this.feed[i]) <= 0)
this.feed.pop() this.feed.pop()
else else
break break
if(this.feed.length >= this.max) if(this.feed.length >= this.max)
this.feed[this.feed.length - 1] = data // replace last one this.feed[this.feed.length - 1] = data // replace last one
else else
this.feed.push(data) // insert this.feed.push(data) // insert
} }
else else
{ {
this.feed.push(data) // insert this.feed.push(data) // insert
} }
} }
this._order() this._order()
} }
_order() { _order() {
this.feed.sort((a, b) => this._compare(b) - this._compare(a)) this.feed.sort((a, b) => this._compare(b) - this._compare(a))
} }
_compare(x) _compare(x)
{ {
const rating = (x && x.good) || 0 const rating = (x && x.good) || 0
const comments = 0 const comments = 0
const time = Math.floor(Date.now() / 1000) - x.feedDate const time = Math.floor(Date.now() / 1000) - x.feedDate
const maxTime = 600000 const maxTime = 600000
if(time > maxTime) if(time > maxTime)
time = maxTime time = maxTime
const relativeTime = (maxTime - time) / maxTime const relativeTime = (maxTime - time) / maxTime
return relativeTime * relativeTime + rating * 1.5 * relativeTime + comments * 4 * relativeTime return relativeTime * relativeTime + rating * 1.5 * relativeTime + comments * 4 * relativeTime
} }
} }

View File

@ -1,24 +1,24 @@
module.exports = (sphinx, table, callback, doneCallback, max = 1000, where = '') => new Promise((done) => { module.exports = (sphinx, table, callback, doneCallback, max = 1000, where = '') => new Promise((done) => {
const checker = (index = 0) => { const checker = (index = 0) => {
sphinx.query(`SELECT * FROM ${table} WHERE id > ${index} ${where} LIMIT ${max}`, (err, torrents) => { sphinx.query(`SELECT * FROM ${table} WHERE id > ${index} ${where} LIMIT ${max}`, (err, torrents) => {
const finish = () => { const finish = () => {
if(err) if(err)
console.log('big table parse error', err) console.log('big table parse error', err)
if(doneCallback) if(doneCallback)
doneCallback(true) doneCallback(true)
done(true) done(true)
} }
if(!err && torrents.length > 0) if(!err && torrents.length > 0)
Promise.all(torrents.map(callback)).then(() => { Promise.all(torrents.map(callback)).then(() => {
if(torrents.length === max) if(torrents.length === max)
checker(torrents[torrents.length - 1].id) checker(torrents[torrents.length - 1].id)
else else
finish() finish()
}) })
else else
finish() finish()
}); });
} }
checker() checker()
}) })

View File

@ -7,78 +7,78 @@ import { app, BrowserWindow, screen } from "electron";
import jetpack from "fs-jetpack"; import jetpack from "fs-jetpack";
export default (name, options) => { export default (name, options) => {
const userDataDir = jetpack.cwd(app.getPath("userData")); const userDataDir = jetpack.cwd(app.getPath("userData"));
const stateStoreFile = `window-state-${name}.json`; const stateStoreFile = `window-state-${name}.json`;
const defaultSize = { const defaultSize = {
width: options.width, width: options.width,
height: options.height height: options.height
}; };
let state = {}; let state = {};
let win; let win;
const restore = () => { const restore = () => {
let restoredState = {}; let restoredState = {};
try { try {
restoredState = userDataDir.read(stateStoreFile, "json"); restoredState = userDataDir.read(stateStoreFile, "json");
} catch (err) { } catch (err) {
// For some reason json can't be read (might be corrupted). // For some reason json can't be read (might be corrupted).
// No worries, we have defaults. // No worries, we have defaults.
} }
return Object.assign({}, defaultSize, restoredState); return Object.assign({}, defaultSize, restoredState);
}; };
const getCurrentPosition = () => { const getCurrentPosition = () => {
const position = win.getPosition(); const position = win.getPosition();
const size = win.getSize(); const size = win.getSize();
return { return {
x: position[0], x: position[0],
y: position[1], y: position[1],
width: size[0], width: size[0],
height: size[1] height: size[1]
}; };
}; };
const windowWithinBounds = (windowState, bounds) => { const windowWithinBounds = (windowState, bounds) => {
return ( return (
windowState.x >= bounds.x && windowState.x >= bounds.x &&
windowState.y >= bounds.y && windowState.y >= bounds.y &&
windowState.x + windowState.width <= bounds.x + bounds.width && windowState.x + windowState.width <= bounds.x + bounds.width &&
windowState.y + windowState.height <= bounds.y + bounds.height windowState.y + windowState.height <= bounds.y + bounds.height
); );
}; };
const resetToDefaults = () => { const resetToDefaults = () => {
const bounds = screen.getPrimaryDisplay().bounds; const bounds = screen.getPrimaryDisplay().bounds;
return Object.assign({}, defaultSize, { return Object.assign({}, defaultSize, {
x: (bounds.width - defaultSize.width) / 2, x: (bounds.width - defaultSize.width) / 2,
y: (bounds.height - defaultSize.height) / 2 y: (bounds.height - defaultSize.height) / 2
}); });
}; };
const ensureVisibleOnSomeDisplay = windowState => { const ensureVisibleOnSomeDisplay = windowState => {
const visible = screen.getAllDisplays().some(display => { const visible = screen.getAllDisplays().some(display => {
return windowWithinBounds(windowState, display.bounds); return windowWithinBounds(windowState, display.bounds);
}); });
if (!visible) { if (!visible) {
// Window is partially or fully not visible now. // Window is partially or fully not visible now.
// Reset it to safe defaults. // Reset it to safe defaults.
return resetToDefaults(); return resetToDefaults();
} }
return windowState; return windowState;
}; };
const saveState = () => { const saveState = () => {
if (!win.isMinimized() && !win.isMaximized()) { if (!win.isMinimized() && !win.isMaximized()) {
Object.assign(state, getCurrentPosition()); Object.assign(state, getCurrentPosition());
} }
userDataDir.write(stateStoreFile, state, { atomic: true }); userDataDir.write(stateStoreFile, state, { atomic: true });
}; };
state = ensureVisibleOnSomeDisplay(restore()); state = ensureVisibleOnSomeDisplay(restore());
win = new BrowserWindow(Object.assign({}, options, state)); win = new BrowserWindow(Object.assign({}, options, state));
win.on("close", saveState); win.on("close", saveState);
return win; return win;
}; };

View File

@ -4,94 +4,94 @@ import url from "url";
import __ from '../../app/translation' import __ from '../../app/translation'
export const aboutMenuTemplateFunc = () => ({ export const aboutMenuTemplateFunc = () => ({
label: __("About"), label: __("About"),
submenu: [ submenu: [
{ {
label: __("Changelog"), label: __("Changelog"),
accelerator: "CmdOrCtrl+]", accelerator: "CmdOrCtrl+]",
click: () => { click: () => {
const win = new BrowserWindow({ const win = new BrowserWindow({
parent: BrowserWindow.getFocusedWindow(), parent: BrowserWindow.getFocusedWindow(),
modal: true modal: true
}) })
win.setMenu(null) win.setMenu(null)
win.loadURL(url.format({ win.loadURL(url.format({
pathname: path.join(__dirname, "app.html"), pathname: path.join(__dirname, "app.html"),
protocol: "file:", protocol: "file:",
slashes: true slashes: true
})) }))
win.webContents.on('did-finish-load', () => { win.webContents.on('did-finish-load', () => {
setTimeout(() => win.send('url', '/changelog'), 0) setTimeout(() => win.send('url', '/changelog'), 0)
}); });
const handleRedirect = (e, url) => { const handleRedirect = (e, url) => {
if(url != win.webContents.getURL()) { if(url != win.webContents.getURL()) {
e.preventDefault() e.preventDefault()
shell.openExternal(url) shell.openExternal(url)
} }
} }
win.webContents.on('will-navigate', handleRedirect) win.webContents.on('will-navigate', handleRedirect)
win.webContents.on('new-window', handleRedirect) win.webContents.on('new-window', handleRedirect)
}, },
}, },
{ {
label: __("Bug Report"), label: __("Bug Report"),
accelerator: "CmdOrCtrl+[", accelerator: "CmdOrCtrl+[",
click: () => { click: () => {
shell.openExternal('https://github.com/DEgITx/rats-search/issues') shell.openExternal('https://github.com/DEgITx/rats-search/issues')
}, },
}, },
{ {
label: __("Donate"), label: __("Donate"),
accelerator: "CmdOrCtrl+*", accelerator: "CmdOrCtrl+*",
click: () => { click: () => {
const win = new BrowserWindow({ const win = new BrowserWindow({
parent: BrowserWindow.getFocusedWindow(), parent: BrowserWindow.getFocusedWindow(),
modal: true, modal: true,
width: 1000 width: 1000
}) })
win.setMenu(null) win.setMenu(null)
win.loadURL(url.format({ win.loadURL(url.format({
pathname: path.join(__dirname, "donate.html"), pathname: path.join(__dirname, "donate.html"),
protocol: "file:", protocol: "file:",
slashes: true slashes: true
})) }))
const handleRedirect = (e, url) => { const handleRedirect = (e, url) => {
if(url != win.webContents.getURL()) { if(url != win.webContents.getURL()) {
if(!url.includes('patreon')) if(!url.includes('patreon'))
return return
e.preventDefault() e.preventDefault()
shell.openExternal(url) shell.openExternal(url)
} }
} }
win.webContents.on('will-navigate', handleRedirect) win.webContents.on('will-navigate', handleRedirect)
win.webContents.on('new-window', handleRedirect) win.webContents.on('new-window', handleRedirect)
}, },
}, },
{ {
label: __("Help (Documentation)"), label: __("Help (Documentation)"),
accelerator: "CmdOrCtrl+?", accelerator: "CmdOrCtrl+?",
click: () => { click: () => {
shell.openExternal('https://github.com/DEgITx/rats-search/blob/master/docs/MANUAL.md') shell.openExternal('https://github.com/DEgITx/rats-search/blob/master/docs/MANUAL.md')
}, },
}, },
{ {
label: __("Support (Discussion)"), label: __("Support (Discussion)"),
accelerator: "CmdOrCtrl+>", accelerator: "CmdOrCtrl+>",
click: () => { click: () => {
shell.openExternal('https://discord.gg/t9GQtxA') shell.openExternal('https://discord.gg/t9GQtxA')
}, },
}, },
{ {
label: __("About (GitHub)"), label: __("About (GitHub)"),
accelerator: "CmdOrCtrl+<", accelerator: "CmdOrCtrl+<",
click: () => { click: () => {
shell.openExternal('https://github.com/DEgITx/rats-search') shell.openExternal('https://github.com/DEgITx/rats-search')
}, },
} }
] ]
}); });

View File

@ -4,45 +4,45 @@ import path from 'path'
import __, { translationsDir } from '../../app/translation' import __, { translationsDir } from '../../app/translation'
export const settingsMenuTemplateFunc = (config, onLanguageChange) => ({ export const settingsMenuTemplateFunc = (config, onLanguageChange) => ({
label: __("Settings"), label: __("Settings"),
submenu: [ submenu: [
{ {
label: __("Main Settings"), label: __("Main Settings"),
accelerator: "CmdOrCtrl+O", accelerator: "CmdOrCtrl+O",
click: () => { click: () => {
BrowserWindow.getFocusedWindow().webContents.send('url', '/config') BrowserWindow.getFocusedWindow().webContents.send('url', '/config')
} }
}, },
{ {
label: __("Torrents Filters"), label: __("Torrents Filters"),
accelerator: "CmdOrCtrl+\\", accelerator: "CmdOrCtrl+\\",
click: () => { click: () => {
BrowserWindow.getFocusedWindow().webContents.send('url', '/filters') BrowserWindow.getFocusedWindow().webContents.send('url', '/filters')
} }
}, },
{ {
label: __("Language"), label: __("Language"),
submenu: (() => { submenu: (() => {
const translations = [] const translations = []
const translationsDirectory = translationsDir() const translationsDirectory = translationsDir()
fs.readdirSync(translationsDirectory).forEach(translation => { fs.readdirSync(translationsDirectory).forEach(translation => {
const translationJson = JSON.parse(fs.readFileSync(`${translationsDirectory}/${translation}`, 'utf8')) const translationJson = JSON.parse(fs.readFileSync(`${translationsDirectory}/${translation}`, 'utf8'))
const lang = path.basename(translation, '.json') const lang = path.basename(translation, '.json')
translations.push({ translations.push({
label: translationJson.nameOriginal, label: translationJson.nameOriginal,
type: 'checkbox', type: 'checkbox',
checked: config.language === lang, checked: config.language === lang,
click: () => { click: () => {
BrowserWindow.getFocusedWindow().webContents.send('changeLanguage', lang) BrowserWindow.getFocusedWindow().webContents.send('changeLanguage', lang)
config.language = lang config.language = lang
if(onLanguageChange) if(onLanguageChange)
onLanguageChange(lang) onLanguageChange(lang)
console.log('changed translation to:', lang) console.log('changed translation to:', lang)
} }
}) })
}) })
return translations return translations
})() })()
} }
] ]
}); });

View File

@ -1,28 +1,28 @@
import { app, BrowserWindow } from "electron"; import { app, BrowserWindow } from "electron";
export const devMenuTemplate = { export const devMenuTemplate = {
label: "Development", label: "Development",
submenu: [ submenu: [
{ {
label: "Reload", label: "Reload",
accelerator: "CmdOrCtrl+R", accelerator: "CmdOrCtrl+R",
click: () => { click: () => {
BrowserWindow.getFocusedWindow().webContents.reloadIgnoringCache(); BrowserWindow.getFocusedWindow().webContents.reloadIgnoringCache();
} }
}, },
{ {
label: "Toggle DevTools", label: "Toggle DevTools",
accelerator: "Alt+CmdOrCtrl+I", accelerator: "Alt+CmdOrCtrl+I",
click: () => { click: () => {
BrowserWindow.getFocusedWindow().toggleDevTools(); BrowserWindow.getFocusedWindow().toggleDevTools();
} }
}, },
{ {
label: "Quit", label: "Quit",
accelerator: "CmdOrCtrl+Q", accelerator: "CmdOrCtrl+Q",
click: () => { click: () => {
app.quit(); app.quit();
} }
} }
] ]
}; };

View File

@ -1,14 +1,14 @@
import __ from '../../app/translation' import __ from '../../app/translation'
export const editMenuTemplateFunc = () => ({ export const editMenuTemplateFunc = () => ({
label: __("Edit"), label: __("Edit"),
submenu: [ submenu: [
{ label: __("Undo"), accelerator: "CmdOrCtrl+Z", selector: "undo:" }, { label: __("Undo"), accelerator: "CmdOrCtrl+Z", selector: "undo:" },
{ label: __("Redo"), accelerator: "Shift+CmdOrCtrl+Z", selector: "redo:" }, { label: __("Redo"), accelerator: "Shift+CmdOrCtrl+Z", selector: "redo:" },
{ type: "separator" }, { type: "separator" },
{ label: __("Cut"), accelerator: "CmdOrCtrl+X", selector: "cut:" }, { label: __("Cut"), accelerator: "CmdOrCtrl+X", selector: "cut:" },
{ label: __("Copy"), accelerator: "CmdOrCtrl+C", selector: "copy:" }, { label: __("Copy"), accelerator: "CmdOrCtrl+C", selector: "copy:" },
{ label: __("Paste"), accelerator: "CmdOrCtrl+V", selector: "paste:" }, { label: __("Paste"), accelerator: "CmdOrCtrl+V", selector: "paste:" },
{ label: __("Select All"), accelerator: "CmdOrCtrl+A", selector: "selectAll:" } { label: __("Select All"), accelerator: "CmdOrCtrl+A", selector: "selectAll:" }
] ]
}); });

View File

@ -4,28 +4,28 @@ import url from "url";
import __ from '../../app/translation' import __ from '../../app/translation'
export const manageMenuTemplateFunc = () => ({ export const manageMenuTemplateFunc = () => ({
label: __("Manage"), label: __("Manage"),
submenu: [ submenu: [
{ {
label: __("Downloads"), label: __("Downloads"),
accelerator: "CmdOrCtrl+d", accelerator: "CmdOrCtrl+d",
click: () => { click: () => {
BrowserWindow.getFocusedWindow().webContents.send('url', '/downloads') BrowserWindow.getFocusedWindow().webContents.send('url', '/downloads')
}, },
}, },
{ {
label: __("Search"), label: __("Search"),
accelerator: "CmdOrCtrl+n", accelerator: "CmdOrCtrl+n",
click: () => { click: () => {
BrowserWindow.getFocusedWindow().webContents.send('url', '/') BrowserWindow.getFocusedWindow().webContents.send('url', '/')
}, },
}, },
{ {
label: __("Top"), label: __("Top"),
accelerator: "CmdOrCtrl+t", accelerator: "CmdOrCtrl+t",
click: () => { click: () => {
BrowserWindow.getFocusedWindow().webContents.send('url', '/top') BrowserWindow.getFocusedWindow().webContents.send('url', '/top')
}, },
} }
] ]
}); });

View File

@ -2,43 +2,43 @@ const mysql = require('mysql');
const config = require('./config'); const config = require('./config');
const expand = (sphinx) => { const expand = (sphinx) => {
const queryCall = sphinx.query.bind(sphinx) const queryCall = sphinx.query.bind(sphinx)
sphinx.query = (sql, args, callback) => new Promise((resolve, reject) => { sphinx.query = (sql, args, callback) => new Promise((resolve, reject) => {
if(typeof args === 'function' || typeof args === 'undefined') if(typeof args === 'function' || typeof args === 'undefined')
{ {
queryCall(sql, (err, res) => { queryCall(sql, (err, res) => {
if(err) if(err)
reject(err) reject(err)
else else
resolve(res) resolve(res)
if(args) if(args)
args(err, res) args(err, res)
}) })
} }
else else
{ {
queryCall(sql, args, (err, res) => { queryCall(sql, args, (err, res) => {
if(err) if(err)
reject(err) reject(err)
else else
resolve(res) resolve(res)
if(callback) if(callback)
callback(err, res) callback(err, res)
}) })
} }
}) })
sphinx.insertValues = (table, values, callback) => new Promise((resolve) => { sphinx.insertValues = (table, values, callback) => new Promise((resolve) => {
let names = ''; let names = '';
let data = ''; let data = '';
for(const val in values) for(const val in values)
{ {
if(values[val] === null) if(values[val] === null)
continue; continue;
names += '`' + val + '`,'; names += '`' + val + '`,';
data += sphinx.escape(values[val]) + ','; data += sphinx.escape(values[val]) + ',';
} }
@ -46,101 +46,101 @@ const expand = (sphinx) => {
data = data.slice(0, -1) data = data.slice(0, -1)
let query = `INSERT INTO ${table}(${names}) VALUES(${data})`; let query = `INSERT INTO ${table}(${names}) VALUES(${data})`;
queryCall(query, (...responce) => { queryCall(query, (...responce) => {
if(callback) if(callback)
callback(...responce) callback(...responce)
resolve(...responce) resolve(...responce)
}) })
}) })
sphinx.updateValues = (table, values, whereObject, callback) => new Promise((resolve) => { sphinx.updateValues = (table, values, whereObject, callback) => new Promise((resolve) => {
let set = '' let set = ''
for(const val in values) for(const val in values)
{ {
if(values[val] === null) if(values[val] === null)
continue; continue;
if(typeof values[val] == 'object') if(typeof values[val] == 'object')
continue; continue;
// skip text indexes (manticore bug https://github.com/manticoresoftware/manticoresearch/issues/84) // skip text indexes (manticore bug https://github.com/manticoresoftware/manticoresearch/issues/84)
if(typeof values[val] == 'string') if(typeof values[val] == 'string')
continue; continue;
set += '`' + val + '` = ' + sphinx.escape(values[val]) + ','; set += '`' + val + '` = ' + sphinx.escape(values[val]) + ',';
} }
if(set.length == 0) if(set.length == 0)
return return
set = set.slice(0, -1) set = set.slice(0, -1)
let where = '' let where = ''
for(const w in whereObject) for(const w in whereObject)
{ {
if(whereObject[w] === null) if(whereObject[w] === null)
continue; continue;
where += '`' + w + '` = ' + sphinx.escape(whereObject[w]) + ' and'; where += '`' + w + '` = ' + sphinx.escape(whereObject[w]) + ' and';
} }
if(where.length == 0) if(where.length == 0)
return return
where = where.slice(0, -3) where = where.slice(0, -3)
const query = `UPDATE ${table} SET ${set} WHERE ${where}`; const query = `UPDATE ${table} SET ${set} WHERE ${where}`;
queryCall(query, (...responce) => { queryCall(query, (...responce) => {
if(callback) if(callback)
callback(...responce) callback(...responce)
resolve(...responce) resolve(...responce)
}) })
}) })
return sphinx return sphinx
} }
const pool = () => { const pool = () => {
let sphinx = mysql.createPool({ let sphinx = mysql.createPool({
connectionLimit: config.sphinx.connectionLimit, connectionLimit: config.sphinx.connectionLimit,
host : config.sphinx.host, host : config.sphinx.host,
port : config.sphinx.port port : config.sphinx.port
}); });
return expand(sphinx) return expand(sphinx)
} }
let mysqlSingle; let mysqlSingle;
const single = (callback) => { const single = (callback) => {
mysqlSingle = mysql.createConnection({ mysqlSingle = mysql.createConnection({
host : config.sphinx.host, host : config.sphinx.host,
port : config.sphinx.port port : config.sphinx.port
}); });
let promiseResolve; let promiseResolve;
const connectionPromise = new Promise((resolve) => { const connectionPromise = new Promise((resolve) => {
promiseResolve = resolve promiseResolve = resolve
}) })
mysqlSingle.waitConnection = () => connectionPromise; mysqlSingle.waitConnection = () => connectionPromise;
mysqlSingle.connect((mysqlError) => { mysqlSingle.connect((mysqlError) => {
if (mysqlError) { if (mysqlError) {
console.error('error connecting: ' + mysqlError.stack); console.error('error connecting: ' + mysqlError.stack);
return; return;
} }
if(callback) if(callback)
callback(mysqlSingle) callback(mysqlSingle)
promiseResolve(mysqlSingle) promiseResolve(mysqlSingle)
}); });
mysqlSingle.on('error', (err) => { mysqlSingle.on('error', (err) => {
console.log('db error', err); console.log('db error', err);
if(err.code === 'PROTOCOL_CONNECTION_LOST') { // Connection to the MySQL server is usually if(err.code === 'PROTOCOL_CONNECTION_LOST') { // Connection to the MySQL server is usually
mysqlSingle = undefined mysqlSingle = undefined
single(); // lost due to either server restart, or a single(); // lost due to either server restart, or a
} else { // connnection idle timeout (the wait_timeout } else { // connnection idle timeout (the wait_timeout
throw err; // server variable configures this) throw err; // server variable configures this)
} }
}); });
mysqlSingle = expand(mysqlSingle) mysqlSingle = expand(mysqlSingle)
return mysqlSingle return mysqlSingle
} }
module.exports = {pool, single} module.exports = {pool, single}

View File

@ -127,7 +127,7 @@ class p2p {
// all ok don't need to start any ssh tunnels // all ok don't need to start any ssh tunnels
if(isAvailable) if(isAvailable)
{ {
console.log('tcp p2p port is reachable - all ok') console.log('tcp p2p port is reachable - all ok')
return; return;
} }
@ -152,7 +152,7 @@ class p2p {
this.externalPeers = [] this.externalPeers = []
return return
} }
console.log('ssh tunnel success, redirect peers to ssh') console.log('ssh tunnel success, redirect peers to ssh')
this.p2pStatus = 1 this.p2pStatus = 1
@ -224,7 +224,7 @@ class p2p {
delete callbacks[message.id]; delete callbacks[message.id];
} }
}); });
const emit = (type, data, callback) => { const emit = (type, data, callback) => {
const id = Math.random().toString(36).substring(5) const id = Math.random().toString(36).substring(5)
if(callback) if(callback)
@ -301,7 +301,7 @@ class p2p {
console.log('close peer connection', address) console.log('close peer connection', address)
} }
}) })
socket.on('error', (err) => {}) socket.on('error', (err) => {})
socket.connect(address.port, address.address); socket.connect(address.port, address.address);

View File

@ -22,16 +22,16 @@ const socketMessages = {}
io.on('connection', (socket) => io.on('connection', (socket) =>
{ {
for(const message in socketMessages) for(const message in socketMessages)
{ {
socket.on(message, socketMessages[message]) socket.on(message, socketMessages[message])
} }
}) })
sphinx = startSphinx(() => { sphinx = startSphinx(() => {
dbPatcher(() => { dbPatcher(() => {
spider = spiderCall((...data) => io.sockets.emit(...data), (message, callback) => { spider = spiderCall((...data) => io.sockets.emit(...data), (message, callback) => {
socketMessages[message] = callback socketMessages[message] = callback
}, path.resolve(packageJson.serverDataDirectory), packageJson.version, 'production') }, path.resolve(packageJson.serverDataDirectory), packageJson.version, 'production')
}, null, sphinx) }, null, sphinx)
}, path.resolve(packageJson.serverDataDirectory), () => {}) }, path.resolve(packageJson.serverDataDirectory), () => {})

View File

@ -3,12 +3,12 @@
* @param {Array} a items An array containing the items. * @param {Array} a items An array containing the items.
*/ */
module.exports = function shuffle(a) { module.exports = function shuffle(a) {
let j, x, i; let j, x, i;
for (i = a.length - 1; i > 0; i--) { for (i = a.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1)); j = Math.floor(Math.random() * (i + 1));
x = a[i]; x = a[i];
a[i] = a[j]; a[i] = a[j];
a[j] = x; a[j] = x;
} }
return a return a
} }

View File

@ -1,7 +1,7 @@
const path = require("path"); const path = require("path");
let env let env
try{ try{
env = require("env"); env = require("env");
} catch(e){} } catch(e){}
const appPath = require('./electronAppPath') const appPath = require('./electronAppPath')
const fs = require('fs') const fs = require('fs')
@ -10,7 +10,7 @@ const { spawn, exec } = require('child_process')
const appConfig = require('./config') const appConfig = require('./config')
const writeSphinxConfig = (path, dbPath) => { const writeSphinxConfig = (path, dbPath) => {
let config = ` let config = `
index torrents index torrents
{ {
type = rt type = rt
@ -92,115 +92,115 @@ const writeSphinxConfig = (path, dbPath) => {
} }
`; `;
// clear dir in test env // clear dir in test env
if(env && env.name === 'test') if(env && env.name === 'test')
{ {
if (fs.existsSync(`${dbPath}/database`)) { if (fs.existsSync(`${dbPath}/database`)) {
fs.readdirSync(`${dbPath}/database`).forEach(function(file, index){ fs.readdirSync(`${dbPath}/database`).forEach(function(file, index){
const curPath = `${dbPath}/database` + "/" + file; const curPath = `${dbPath}/database` + "/" + file;
if (!fs.lstatSync(curPath).isDirectory()) { if (!fs.lstatSync(curPath).isDirectory()) {
fs.unlinkSync(curPath); fs.unlinkSync(curPath);
} }
}); });
fs.readdirSync(path).forEach(function(file, index){ fs.readdirSync(path).forEach(function(file, index){
if(!file.startsWith('binlog')) if(!file.startsWith('binlog'))
return; return;
const curPath = path + "/" + file; const curPath = path + "/" + file;
if (!fs.lstatSync(curPath).isDirectory()) { if (!fs.lstatSync(curPath).isDirectory()) {
fs.unlinkSync(curPath); fs.unlinkSync(curPath);
} }
}); });
} }
} }
// clean query.log because it too large and don't consist any good info // clean query.log because it too large and don't consist any good info
if(fs.existsSync(`${path}/query.log`)) if(fs.existsSync(`${path}/query.log`))
{ {
fs.unlinkSync(`${path}/query.log`) fs.unlinkSync(`${path}/query.log`)
} }
let isInitDb = false let isInitDb = false
if (!fs.existsSync(`${dbPath}/database`)){ if (!fs.existsSync(`${dbPath}/database`)){
fs.mkdirSync(`${dbPath}/database`); fs.mkdirSync(`${dbPath}/database`);
isInitDb = true isInitDb = true
} }
if(/^win/.test(process.platform)) if(/^win/.test(process.platform))
config = iconv.encode(config, 'win1251') config = iconv.encode(config, 'win1251')
fs.writeFileSync(`${path}/sphinx.conf`, config) fs.writeFileSync(`${path}/sphinx.conf`, config)
console.log(`writed sphinx config to ${path}`) console.log(`writed sphinx config to ${path}`)
console.log('db path:', dbPath) console.log('db path:', dbPath)
return {isInitDb} return {isInitDb}
} }
module.exports = (callback, dataDirectory, onClose) => { module.exports = (callback, dataDirectory, onClose) => {
const sphinxPath = path.resolve(appPath('searchd')) const sphinxPath = path.resolve(appPath('searchd'))
console.log('Sphinx Path:', sphinxPath) console.log('Sphinx Path:', sphinxPath)
const sphinxConfigDirectory = dataDirectory const sphinxConfigDirectory = dataDirectory
appConfig['dbPath'] = appConfig.dbPath && appConfig.dbPath.length > 0 ? appConfig.dbPath : sphinxConfigDirectory; appConfig['dbPath'] = appConfig.dbPath && appConfig.dbPath.length > 0 ? appConfig.dbPath : sphinxConfigDirectory;
// on portable dir can move database directory // on portable dir can move database directory
if(!fs.existsSync(appConfig.dbPath) && fs.existsSync(sphinxConfigDirectory)) if(!fs.existsSync(appConfig.dbPath) && fs.existsSync(sphinxConfigDirectory))
{ {
appConfig['dbPath'] = sphinxConfigDirectory appConfig['dbPath'] = sphinxConfigDirectory
} }
const { isInitDb } = writeSphinxConfig(sphinxConfigDirectory, appConfig.dbPath) const { isInitDb } = writeSphinxConfig(sphinxConfigDirectory, appConfig.dbPath)
const config = `${sphinxConfigDirectory}/sphinx.conf` const config = `${sphinxConfigDirectory}/sphinx.conf`
const options = ['--config', config] const options = ['--config', config]
if(!(/^win/.test(process.platform))) if(!(/^win/.test(process.platform)))
{ {
options.push('--nodetach') options.push('--nodetach')
} }
const sphinx = spawn(sphinxPath, options) const sphinx = spawn(sphinxPath, options)
// remeber initizalizing of db // remeber initizalizing of db
sphinx.isInitDb = isInitDb sphinx.isInitDb = isInitDb
sphinx.directoryPath = appConfig.dbPath sphinx.directoryPath = appConfig.dbPath
sphinx.directoryPathDb = appConfig.dbPath + '/database' sphinx.directoryPathDb = appConfig.dbPath + '/database'
const optimizeResolvers = {} const optimizeResolvers = {}
sphinx.stdout.on('data', (data) => { sphinx.stdout.on('data', (data) => {
console.log(`sphinx: ${data}`) console.log(`sphinx: ${data}`)
if (data.includes('accepting connections')) { if (data.includes('accepting connections')) {
console.log('catched sphinx start') console.log('catched sphinx start')
if(callback) if(callback)
callback() callback()
} }
const checkOptimized = String(data).match(/index ([\w]+): optimized/) const checkOptimized = String(data).match(/index ([\w]+): optimized/)
if(checkOptimized) if(checkOptimized)
{ {
if(optimizeResolvers[checkOptimized[1]]) if(optimizeResolvers[checkOptimized[1]])
{ {
console.log('resolve optimizer', checkOptimized[1]) console.log('resolve optimizer', checkOptimized[1])
optimizeResolvers[checkOptimized[1]]() optimizeResolvers[checkOptimized[1]]()
} }
} }
}) })
sphinx.on('close', (code, signal) => { sphinx.on('close', (code, signal) => {
console.log(`sphinx closed with code ${code} and signal ${signal}`) console.log(`sphinx closed with code ${code} and signal ${signal}`)
if(onClose) if(onClose)
onClose() onClose()
}) })
sphinx.stop = () => { sphinx.stop = () => {
console.log('sphinx closing...') console.log('sphinx closing...')
exec(`"${sphinxPath}" --config "${config}" --stopwait`) exec(`"${sphinxPath}" --config "${config}" --stopwait`)
} }
sphinx.waitOptimized = (table) => new Promise((resolve) => { sphinx.waitOptimized = (table) => new Promise((resolve) => {
optimizeResolvers[table] = () => { optimizeResolvers[table] = () => {
delete optimizeResolvers[table]; delete optimizeResolvers[table];
resolve() resolve()
} }
}) })
return sphinx return sphinx
} }

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,7 @@ const startSSH = (port, host, user, password, callback) => {
if(tryies-- <= 0) if(tryies-- <= 0)
{ {
if(callback) if(callback)
callback(false) callback(false)
return return
} }

View File

@ -3,184 +3,184 @@ const EventEmitter = require('events');
const forBigTable = require('./forBigTable') const forBigTable = require('./forBigTable')
module.exports = class P2PStore extends EventEmitter { module.exports = class P2PStore extends EventEmitter {
constructor(p2p, sphinx) constructor(p2p, sphinx)
{ {
super() super()
this.id = 0 this.id = 0
this.synchronized = false this.synchronized = false
console.log('connect p2p store...') console.log('connect p2p store...')
this.p2p = p2p this.p2p = p2p
this.sphinx = sphinx this.sphinx = sphinx
this.sphinx.query("SELECT MAX(`id`) as mx from store", (err, rows) => { this.sphinx.query("SELECT MAX(`id`) as mx from store", (err, rows) => {
if(err) if(err)
return return
if(rows[0] && rows[0].mx >= 1) if(rows[0] && rows[0].mx >= 1)
this.id = rows[0].mx; this.id = rows[0].mx;
console.log('store db index', this.id) console.log('store db index', this.id)
let lock = false let lock = false
this.p2p.events.on('peer', () => { this.p2p.events.on('peer', () => {
if(lock) if(lock)
return return
lock = true lock = true
setTimeout(() => this.sync(), 1000) setTimeout(() => this.sync(), 1000)
}) })
}) })
this.p2p.on('dbStore', (record) => { this.p2p.on('dbStore', (record) => {
if(!record || record.id - 1 !== this.id) if(!record || record.id - 1 !== this.id)
{ {
console.log('out of range peerdb store', record.id) console.log('out of range peerdb store', record.id)
return return
} }
this._syncRecord(record, () => { this._syncRecord(record, () => {
// redirect other peers that record are stored // redirect other peers that record are stored
this.p2p.emit('dbStore', record) this.p2p.emit('dbStore', record)
}) })
}) })
this.p2p.on('dbSync', ({id} = {}, callback) => { this.p2p.on('dbSync', ({id} = {}, callback) => {
console.log('ask to sync db from', id, 'version') console.log('ask to sync db from', id, 'version')
if(typeof id === 'undefined' || id >= this.id) if(typeof id === 'undefined' || id >= this.id)
{ {
callback(false) callback(false)
return return
} }
// back // back
this.sphinx.query(`select * from store where id > ${id}`, (err, records) => { this.sphinx.query(`select * from store where id > ${id}`, (err, records) => {
if(err) if(err)
{ {
console.log(err) console.log(err)
return return
} }
if(records.length > 0) if(records.length > 0)
callback({ callback({
records, records,
index: this.id index: this.id
}) })
}) })
}) })
} }
sync() sync()
{ {
console.log('sync db on version', this.id) console.log('sync db on version', this.id)
const processSync = (data, nil, peer) => { const processSync = (data, nil, peer) => {
if(!data || !data.records) if(!data || !data.records)
return return
const oldIndex = this.id const oldIndex = this.id
data.records.forEach(record => this._syncRecord(record)) data.records.forEach(record => this._syncRecord(record))
// peer also can contain another part of store records, try to sync them all // peer also can contain another part of store records, try to sync them all
if(data.index >= 0 if(data.index >= 0
&& oldIndex < this.id // last sync update of store must be successful, otherwise no point to try sync db from this peer && oldIndex < this.id // last sync update of store must be successful, otherwise no point to try sync db from this peer
&& this.id < data.index) && this.id < data.index)
{ {
console.log('continue sync store from', this.id, 'index', 'peer', peer.peerId) console.log('continue sync store from', this.id, 'index', 'peer', peer.peerId)
peer.emit('dbSync', {id: this.id}, processSync) peer.emit('dbSync', {id: this.id}, processSync)
} }
} }
this.p2p.emit('dbSync', {id: this.id}, processSync) this.p2p.emit('dbSync', {id: this.id}, processSync)
this.synchronized = true this.synchronized = true
} }
_syncRecord(record, callback) _syncRecord(record, callback)
{ {
if(!record) if(!record)
return return
if(!record.id) if(!record.id)
return return
if(record.id <= this.id) if(record.id <= this.id)
return return
if(typeof record.data !== 'object') if(typeof record.data !== 'object')
record.data = JSON.parse(record.data) record.data = JSON.parse(record.data)
// check hash // check hash
if(objectHash(record.data) !== record.hash) if(objectHash(record.data) !== record.hash)
{ {
console.log('wrong hash for sync peerdb') console.log('wrong hash for sync peerdb')
return return
} }
// set myself to false // set myself to false
record.myself = false record.myself = false
// push to db // push to db
console.log('sync peerdb record', record.id) console.log('sync peerdb record', record.id)
this._pushToDb(record) this._pushToDb(record)
this.id = record.id this.id = record.id
// redirect to next // redirect to next
if(callback) if(callback)
callback() callback()
} }
_pushToDb(value, callback) _pushToDb(value, callback)
{ {
const data = this.sphinx.escape(JSON.stringify(value.data)) const data = this.sphinx.escape(JSON.stringify(value.data))
this.sphinx.query( this.sphinx.query(
`insert into store(id, hash, peerId, data` + (value.index || value.data._index ? ', storeIndex' : '') + `) `insert into store(id, hash, peerId, data` + (value.index || value.data._index ? ', storeIndex' : '') + `)
values('${value.id}', '${value.hash}', '${value.peerId || value.peerid}', ${data}` + (value.index || value.data._index ? ',' + this.sphinx.escape(value.index || value.data._index) : '') + ')', values('${value.id}', '${value.hash}', '${value.peerId || value.peerid}', ${data}` + (value.index || value.data._index ? ',' + this.sphinx.escape(value.index || value.data._index) : '') + ')',
(err) => { (err) => {
if(err) if(err)
{ {
console.log(err) console.log(err)
return return
} }
if(callback) if(callback)
callback() callback()
}) })
this.emit('store', value) this.emit('store', value)
} }
store(obj) store(obj)
{ {
if(!this.synchronized) if(!this.synchronized)
{ {
console.log('cant store item on unsync db') console.log('cant store item on unsync db')
return false return false
} }
// clean temp from object // clean temp from object
const temp = obj._temp const temp = obj._temp
delete obj._temp delete obj._temp
const value = { const value = {
id: ++this.id, id: ++this.id,
hash: objectHash(obj), hash: objectHash(obj),
data: obj, data: obj,
index: obj._index, index: obj._index,
peerId: this.p2p.peerId, peerId: this.p2p.peerId,
myself: true, myself: true,
temp temp
} }
console.log('store object', value.id) console.log('store object', value.id)
this._pushToDb(value, () => { this._pushToDb(value, () => {
// store record // store record
this.p2p.emit('dbStore', value) this.p2p.emit('dbStore', value)
}) })
return true return true
} }
async find(index) async find(index)
{ {
const records = [] const records = []
await forBigTable(this.sphinx, 'store', (record) => records.push(record), null, 1000, `and match(${this.sphinx.escape(index)})`) await forBigTable(this.sphinx, 'store', (record) => records.push(record), null, 1000, `and match(${this.sphinx.escape(index)})`)
return records.map( ({data, peerid}) => Object.assign(JSON.parse(data), { _peerId: peerid }) ) return records.map( ({data, peerid}) => Object.assign(JSON.parse(data), { _peerId: peerid }) )
} }
} }

View File

@ -5,85 +5,85 @@ const glob = require('glob')
const path = require('path') const path = require('path')
module.exports = { module.exports = {
mode: 'development', mode: 'development',
//mode: 'production', //mode: 'production',
entry: path.resolve("src/app/index.js"), entry: path.resolve("src/app/index.js"),
output: { output: {
path: path.resolve('web'), path: path.resolve('web'),
}, },
module: { module: {
rules: [ rules: [
{ {
test: /\.js$/, test: /\.js$/,
exclude: /node_modules/, exclude: /node_modules/,
use: ["babel-loader"] use: ["babel-loader"]
}, },
{ {
test: /\.css$/, test: /\.css$/,
use: ["style-loader", "css-loader"] use: ["style-loader", "css-loader"]
}, },
{ {
test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/, test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/,
use: ['url-loader'] use: ['url-loader']
} }
] ]
}, },
plugins: [ plugins: [
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
inject: true, inject: true,
template: 'app/app.html', template: 'app/app.html',
minify: { minify: {
removeComments: true, removeComments: true,
collapseWhitespace: true, collapseWhitespace: true,
removeRedundantAttributes: true, removeRedundantAttributes: true,
useShortDoctype: true, useShortDoctype: true,
removeEmptyAttributes: true, removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true, removeStyleLinkTypeAttributes: true,
keepClosingSlash: true, keepClosingSlash: true,
minifyJS: true, minifyJS: true,
minifyCSS: true, minifyCSS: true,
minifyURLs: true, minifyURLs: true,
}, },
}), }),
new webpack.DefinePlugin({WEB: true}), new webpack.DefinePlugin({WEB: true}),
// Generate a service worker script that will precache, and keep up to date, // Generate a service worker script that will precache, and keep up to date,
// the HTML & assets that are part of the Webpack build. // the HTML & assets that are part of the Webpack build.
new SWPrecacheWebpackPlugin({ new SWPrecacheWebpackPlugin({
// By default, a cache-busting query parameter is appended to requests // By default, a cache-busting query parameter is appended to requests
// used to populate the caches, to ensure the responses are fresh. // used to populate the caches, to ensure the responses are fresh.
// If a URL is already hashed by Webpack, then there is no concern // If a URL is already hashed by Webpack, then there is no concern
// about it being stale, and the cache-busting can be skipped. // about it being stale, and the cache-busting can be skipped.
dontCacheBustUrlsMatching: /\.\w{8}\./, dontCacheBustUrlsMatching: /\.\w{8}\./,
filename: 'service-worker.js', filename: 'service-worker.js',
logger(message) { logger(message) {
if (message.indexOf('Total precache size is') === 0) { if (message.indexOf('Total precache size is') === 0) {
// This message occurs for every build and is a bit too noisy. // This message occurs for every build and is a bit too noisy.
return; return;
} }
if (message.indexOf('Skipping static resource') === 0) { if (message.indexOf('Skipping static resource') === 0) {
// This message obscures real errors so we ignore it. // This message obscures real errors so we ignore it.
// https://github.com/facebookincubator/create-react-app/issues/2612 // https://github.com/facebookincubator/create-react-app/issues/2612
return; return;
} }
console.log(message); console.log(message);
}, },
minify: true, minify: true,
// For unknown URLs, fallback to the index page // For unknown URLs, fallback to the index page
navigateFallback: 'index.html', navigateFallback: 'index.html',
// Ignores URLs starting from /__ (useful for Firebase): // Ignores URLs starting from /__ (useful for Firebase):
// https://github.com/facebookincubator/create-react-app/issues/2237#issuecomment-302693219 // https://github.com/facebookincubator/create-react-app/issues/2237#issuecomment-302693219
navigateFallbackWhitelist: [/^(?!\/__).*/], navigateFallbackWhitelist: [/^(?!\/__).*/],
// Don't precache sourcemaps (they're large) and build asset manifest: // Don't precache sourcemaps (they're large) and build asset manifest:
staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/], staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
mergeStaticsConfig: true, mergeStaticsConfig: true,
staticFileGlobs: glob.sync('public/images/**/*.*').concat(glob.sync('public/sounds/**/*.*')), staticFileGlobs: glob.sync('public/images/**/*.*').concat(glob.sync('public/sounds/**/*.*')),
stripPrefix: 'public/', stripPrefix: 'public/',
}), }),
], ],
node: { node: {
dgram: 'empty', dgram: 'empty',
fs: 'empty', fs: 'empty',
net: 'empty', net: 'empty',
tls: 'empty', tls: 'empty',
}, },
}; };

View File

@ -3,12 +3,12 @@ const webpack = require('webpack');
let compiler = webpack(config); let compiler = webpack(config);
compiler.run((err, stats) => { compiler.run((err, stats) => {
if(err) if(err)
throw new Error(err) throw new Error(err)
if(stats.compilation.errors && stats.compilation.errors.length > 0) if(stats.compilation.errors && stats.compilation.errors.length > 0)
console.error('compilation errors', stats.compilation.errors) console.error('compilation errors', stats.compilation.errors)
else else
console.log('succesfully builded web version') console.log('succesfully builded web version')
}) })