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'
export default class ActivityPage extends Page {
constructor(props) {
super(props)
this.setTitle('Rats On The Boat - Content Search Engine');
}
render() {
return (
<div className='column center'>
<div className='column center w100p pad0-75'>
<RecentTorrents />
</div>
</div>
);
}
constructor(props) {
super(props)
this.setTitle('Rats On The Boat - Content Search Engine');
}
render() {
return (
<div className='column center'>
<div className='column center w100p pad0-75'>
<RecentTorrents />
</div>
</div>
);
}
}

View File

@ -20,52 +20,52 @@ if(typeof WEB !== 'undefined')
}
else
{
const { ipcRenderer, remote } = require('electron');
window.currentWindow = remote.getCurrentWindow()
const { ipcRenderer, remote } = require('electron');
window.currentWindow = remote.getCurrentWindow()
window.torrentSocket = {}
window.torrentSocket.callbacks = {}
window.torrentSocket.listeners = {}
window.torrentSocket.on = (name, func) => {
const newListener = (event, ...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 = {}
window.torrentSocket.callbacks = {}
window.torrentSocket.listeners = {}
window.torrentSocket.on = (name, func) => {
const newListener = (event, ...data) => {
func(...data)
}
}
}
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]
});
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.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) => {
console.log('url', url)
router(url)
});
ipcRenderer.on('url', (event, url) => {
console.log('url', url)
router(url)
});
}
@ -164,8 +164,8 @@ class App extends Component {
<div>
{
checkNotModal
&&
<Header />
&&
<Header />
}
<PagesPie />
<Footer />

View File

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

View File

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

View File

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

View File

@ -9,302 +9,302 @@ import Slider from 'material-ui/Slider'
import fs from 'fs'
let dialog
if(typeof WEB === 'undefined')
dialog = require('electron').remote.dialog
dialog = require('electron').remote.dialog
export default class ConfigPage extends Page {
constructor(props) {
super(props)
this.setTitle('Rats settings');
this.options = {}
}
componentDidMount() {
this.loadSettings()
}
loadSettings() {
window.torrentSocket.emit('config', window.customLoader((options) => {
this.options = options;
console.log(this.options)
this.forceUpdate();
}));
}
saveSettings() {
window.torrentSocket.emit('setConfig', this.options)
this.settingsSavedMessage = true
this.forceUpdate()
setTimeout(() => {
this.settingsSavedMessage = false
this.forceUpdate()
}, 1000)
}
render() {
return (
<div>
<div className='row center pad0-75'>
<RaisedButton label={__('Back to main page')} primary={true} onClick={() => {
window.router('/')
}} />
</div>
<div className='column center w100p pad0-75'>
<Toggle
style={{marginTop: '10px'}}
label={__('Enabled network scanning')}
toggled={this.options.indexer}
onToggle={(e, checked) => {
this.options.indexer = checked
if(!this.options.indexer)
this.options.p2p = false
this.forceUpdate()
}}
/>
<div className='column w100p'>
<div className='row inline w100p'>
<div style={{flex: 1}}>{__('Scanning port')}</div>
<TextField
style={{width: 65}}
hintText={__('Port')}
errorText={this.options.spiderPort > 0 ? undefined : __('This field is required')}
value={this.options.spiderPort}
onChange={(e, value) => {
if(!value)
value = 0
if(value > 65535)
value = 65535
constructor(props) {
super(props)
this.setTitle('Rats settings');
this.options = {}
}
componentDidMount() {
this.loadSettings()
}
loadSettings() {
window.torrentSocket.emit('config', window.customLoader((options) => {
this.options = options;
console.log(this.options)
this.forceUpdate();
}));
}
saveSettings() {
window.torrentSocket.emit('setConfig', this.options)
this.settingsSavedMessage = true
this.forceUpdate()
setTimeout(() => {
this.settingsSavedMessage = false
this.forceUpdate()
}, 1000)
}
render() {
return (
<div>
<div className='row center pad0-75'>
<RaisedButton label={__('Back to main page')} primary={true} onClick={() => {
window.router('/')
}} />
</div>
<div className='column center w100p pad0-75'>
<Toggle
style={{marginTop: '10px'}}
label={__('Enabled network scanning')}
toggled={this.options.indexer}
onToggle={(e, checked) => {
this.options.indexer = checked
if(!this.options.indexer)
this.options.p2p = false
this.forceUpdate()
}}
/>
<div className='column w100p'>
<div className='row inline w100p'>
<div style={{flex: 1}}>{__('Scanning port')}</div>
<TextField
style={{width: 65}}
hintText={__('Port')}
errorText={this.options.spiderPort > 0 ? undefined : __('This field is required')}
value={this.options.spiderPort}
onChange={(e, value) => {
if(!value)
value = 0
if(value > 65535)
value = 65535
this.options.spiderPort = parseInt(value)
this.forceUpdate()
}}
/>
</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>
this.options.spiderPort = parseInt(value)
this.forceUpdate()
}}
/>
</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 className='column w100p'>
<div className='row inline w100p'>
<div style={{flex: 1}}>{__('Trackers responce port')}</div>
<TextField
style={{width: 65}}
hintText="Port"
errorText={this.options.udpTrackersPort > 0 ? undefined : __('This field is required')}
value={this.options.udpTrackersPort}
onChange={(e, value) => {
if(!value)
value = 0
if(value > 65535)
value = 65535
<div className='column w100p'>
<div className='row inline w100p'>
<div style={{flex: 1}}>{__('Trackers responce port')}</div>
<TextField
style={{width: 65}}
hintText="Port"
errorText={this.options.udpTrackersPort > 0 ? undefined : __('This field is required')}
value={this.options.udpTrackersPort}
onChange={(e, value) => {
if(!value)
value = 0
if(value > 65535)
value = 65535
this.options.udpTrackersPort = parseInt(value)
this.forceUpdate()
}}
/>
</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>
this.options.udpTrackersPort = parseInt(value)
this.forceUpdate()
}}
/>
</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>
<Toggle
style={{marginTop: '10px'}}
label={__('Enabled UPnP')}
toggled={this.options.upnp}
onToggle={(e, checked) => {
this.options.upnp = checked
this.forceUpdate()
}}
/>
<Toggle
style={{marginTop: '10px'}}
label={__('Enabled UPnP')}
toggled={this.options.upnp}
onToggle={(e, checked) => {
this.options.upnp = checked
this.forceUpdate()
}}
/>
<div className='row inline w100p'>
<div style={{flex: 1}}>{__('Collection directory')}</div>
<TextField
hintText={__('Db path')}
errorText={this.options.dbPath && this.options.dbPath.length > 0 ? undefined : __('This field is required')}
value={this.options.dbPath}
onChange={(e, value) => {
if(!fs.existsSync(value))
return
<div className='row inline w100p'>
<div style={{flex: 1}}>{__('Collection directory')}</div>
<TextField
hintText={__('Db path')}
errorText={this.options.dbPath && this.options.dbPath.length > 0 ? undefined : __('This field is required')}
value={this.options.dbPath}
onChange={(e, value) => {
if(!fs.existsSync(value))
return
this.options.dbPath = value
this.forceUpdate()
}}
/>
<RaisedButton style={{marginLeft: 20}} label={__('Browse')} primary={true} onClick={() => {
if(!dialog)
return
const dir = dialog.showOpenDialog({properties: ['openDirectory']})[0]
if(dir)
{
this.options.dbPath = dir
this.forceUpdate()
}
}} />
</div>
this.options.dbPath = value
this.forceUpdate()
}}
/>
<RaisedButton style={{marginLeft: 20}} label={__('Browse')} primary={true} onClick={() => {
if(!dialog)
return
const dir = dialog.showOpenDialog({properties: ['openDirectory']})[0]
if(dir)
{
this.options.dbPath = dir
this.forceUpdate()
}
}} />
</div>
<div className='row inline w100p'>
<div style={{flex: 1}}>{__('Download torrents directory')}</div>
<TextField
hintText={__('Download path')}
value={this.options.client && this.options.client.downloadPath}
onChange={(e, value) => {
if(!fs.existsSync(value))
return
<div className='row inline w100p'>
<div style={{flex: 1}}>{__('Download torrents directory')}</div>
<TextField
hintText={__('Download path')}
value={this.options.client && this.options.client.downloadPath}
onChange={(e, value) => {
if(!fs.existsSync(value))
return
this.options.client.downloadPath = value
this.forceUpdate()
}}
/>
<RaisedButton style={{marginLeft: 20}} label={__('Browse')} primary={true} onClick={() => {
if(!dialog)
return
const dir = dialog.showOpenDialog({properties: ['openDirectory']})[0]
if(dir)
{
this.options.client.downloadPath = dir
this.forceUpdate()
}
}} />
</div>
this.options.client.downloadPath = value
this.forceUpdate()
}}
/>
<RaisedButton style={{marginLeft: 20}} label={__('Browse')} primary={true} onClick={() => {
if(!dialog)
return
const dir = dialog.showOpenDialog({properties: ['openDirectory']})[0]
if(dir)
{
this.options.client.downloadPath = dir
this.forceUpdate()
}
}} />
</div>
<Toggle
style={{marginTop: '10px'}}
label={__('Hide to tray on application minimize')}
toggled={this.options.trayOnMinimize}
onToggle={(e, checked) => {
this.options.trayOnMinimize = checked
this.forceUpdate()
}}
/>
<Toggle
style={{marginTop: '10px'}}
label={__('Hide to tray on application minimize')}
toggled={this.options.trayOnMinimize}
onToggle={(e, checked) => {
this.options.trayOnMinimize = checked
this.forceUpdate()
}}
/>
<Toggle
style={{marginTop: '10px'}}
label={__('Hide to tray on application close')}
toggled={this.options.trayOnClose}
onToggle={(e, checked) => {
this.options.trayOnClose = checked
this.forceUpdate()
}}
/>
<Toggle
style={{marginTop: '10px'}}
label={__('Hide to tray on application close')}
toggled={this.options.trayOnClose}
onToggle={(e, checked) => {
this.options.trayOnClose = checked
this.forceUpdate()
}}
/>
<div style={{marginTop: 10}}>{__('P2P Rats network settings')}:</div>
<Toggle
style={{marginTop: '10px'}}
label={__('Enabled p2p search')}
toggled={this.options.p2p}
onToggle={(e, checked) => {
this.options.p2p = this.options.indexer && checked
this.forceUpdate()
}}
/>
<div className='column w100p'>
<Toggle
style={{marginTop: '10px'}}
label={__('Enabled bootstrap peers')}
toggled={this.options.p2pBootstrap}
onToggle={(e, checked) => {
this.options.p2pBootstrap = checked
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>
<div className='column w100p'>
<div className='row inline w100p'>
<div style={{flex: 1}}>{__('Max peers limit')} ({__('current')}: {this.options.p2pConnections})</div>
<Slider
min={10}
max={25}
step={1}
style={{width: 300}}
value={this.options.p2pConnections}
onChange={(event, value) => {
this.options.p2pConnections = value
this.forceUpdate()
}}
/>
</div>
</div>
<div className='column w100p'>
<Toggle
style={{marginTop: '10px'}}
label={__('P2P torrents replication')}
toggled={this.options.p2pReplication}
onToggle={(e, checked) => {
this.options.p2pReplication = checked
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>
<div style={{marginTop: 10}}>{__('P2P Rats network settings')}:</div>
<Toggle
style={{marginTop: '10px'}}
label={__('Enabled p2p search')}
toggled={this.options.p2p}
onToggle={(e, checked) => {
this.options.p2p = this.options.indexer && checked
this.forceUpdate()
}}
/>
<div className='column w100p'>
<Toggle
style={{marginTop: '10px'}}
label={__('Enabled bootstrap peers')}
toggled={this.options.p2pBootstrap}
onToggle={(e, checked) => {
this.options.p2pBootstrap = checked
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>
<div className='column w100p'>
<div className='row inline w100p'>
<div style={{flex: 1}}>{__('Max peers limit')} ({__('current')}: {this.options.p2pConnections})</div>
<Slider
min={10}
max={25}
step={1}
style={{width: 300}}
value={this.options.p2pConnections}
onChange={(event, value) => {
this.options.p2pConnections = value
this.forceUpdate()
}}
/>
</div>
</div>
<div className='column w100p'>
<Toggle
style={{marginTop: '10px'}}
label={__('P2P torrents replication')}
toggled={this.options.p2pReplication}
onToggle={(e, checked) => {
this.options.p2pReplication = checked
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>
<div style={{marginTop: 10}}>{__('Torrent network scanner settings')}:</div>
<div className='column 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>
<Slider
min={1}
max={150}
step={1}
style={{width: 300}}
value={this.options.spider && this.options.spider.walkInterval}
onChange={(event, value) => {
this.options.spider.walkInterval = value
this.forceUpdate()
}}
/>
</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')}.
{__('Good value between')} 3-60. {__('Defaul value')}: 5</div>
</div>
<div className='column w100p'>
<div className='row inline w100p'>
<div style={{flex: 1}}>{__('Nodes usage')} ({__('current')}: {this.options.spider && this.options.spider.nodesUsage})</div>
<Slider
min={0}
max={1000}
step={1}
style={{width: 300}}
value={this.options.spider && this.options.spider.nodesUsage}
onChange={(event, value) => {
this.options.spider.nodesUsage = value
this.forceUpdate()
}}
/>
</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')}.
{__('Recomended value between')} 10-1000. {__('Defaul value')}: 100. 0 - {__('Ignore this option')} ({__('no limit')}).
</div>
</div>
<div className='column w100p'>
<div className='row inline w100p'>
<div style={{flex: 1}}>{__('Reduce network packages')} ({__('current')}: {this.options.spider && this.options.spider.packagesLimit})</div>
<Slider
min={0}
max={2000}
step={1}
style={{width: 300}}
value={this.options.spider && this.options.spider.packagesLimit}
onChange={(event, value) => {
this.options.spider.packagesLimit = value
this.forceUpdate()
}}
/>
</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')}.
{__('Recomended value between')} 300-2000. {__('Defaul value')}: 500. 0 - {__('Ignore this option')} ({__('no limit')}).
</div>
</div>
<div style={{marginTop: 10}}>{__('Torrent network scanner settings')}:</div>
<div className='column 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>
<Slider
min={1}
max={150}
step={1}
style={{width: 300}}
value={this.options.spider && this.options.spider.walkInterval}
onChange={(event, value) => {
this.options.spider.walkInterval = value
this.forceUpdate()
}}
/>
</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')}.
{__('Good value between')} 3-60. {__('Defaul value')}: 5</div>
</div>
<div className='column w100p'>
<div className='row inline w100p'>
<div style={{flex: 1}}>{__('Nodes usage')} ({__('current')}: {this.options.spider && this.options.spider.nodesUsage})</div>
<Slider
min={0}
max={1000}
step={1}
style={{width: 300}}
value={this.options.spider && this.options.spider.nodesUsage}
onChange={(event, value) => {
this.options.spider.nodesUsage = value
this.forceUpdate()
}}
/>
</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')}.
{__('Recomended value between')} 10-1000. {__('Defaul value')}: 100. 0 - {__('Ignore this option')} ({__('no limit')}).
</div>
</div>
<div className='column w100p'>
<div className='row inline w100p'>
<div style={{flex: 1}}>{__('Reduce network packages')} ({__('current')}: {this.options.spider && this.options.spider.packagesLimit})</div>
<Slider
min={0}
max={2000}
step={1}
style={{width: 300}}
value={this.options.spider && this.options.spider.packagesLimit}
onChange={(event, value) => {
this.options.spider.packagesLimit = value
this.forceUpdate()
}}
/>
</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')}.
{__('Recomended value between')} 300-2000. {__('Defaul value')}: 500. 0 - {__('Ignore this option')} ({__('no limit')}).
</div>
</div>
{
this.settingsSavedMessage
{
this.settingsSavedMessage
&&
<div style={{color: 'green'}}>{__('Settings saved')}</div>
}
}
<div className='row center pad0-75'>
<RaisedButton label={__('Save Settings')} primary={true} onClick={() => {
this.saveSettings()
}} />
</div>
<div className='row center pad0-75'>
<RaisedButton label={__('Save Settings')} primary={true} onClick={() => {
this.saveSettings()
}} />
</div>
</div>
</div>
);
}
</div>
</div>
);
}
}

View File

@ -236,7 +236,7 @@ const ContentCategoryProp = 'contentCategory';
const {
XXX_BLOCK_WORDS,
XXX_VERY_BAD_WORDS
XXX_VERY_BAD_WORDS
} = require('./bad-words');
// блокируем порнографию
@ -270,7 +270,7 @@ const detectSubCategory = (torrent, files, typesPriority, contentType) => {
fileCheck = fileCheck.join('.');
blockBadName(torrent, fileCheck);
return torrent[ContentTypeProp] == 'bad';
})
}
@ -304,7 +304,7 @@ const torrentTypeDetect = (torrent, files) => {
}
}
let priority = Object.keys(typesPriority).sort(function(a, b){
return typesPriority[b] - typesPriority[a]
return typesPriority[b] - typesPriority[a]
});
if(priority.length > 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 {
render() {
return (
<div className='w100p column'>
<AppBar
title="DMCA / Copyright"
iconElementLeft={<IconButton onClick={()=>{ window.router('/') }}><NavigationClose /></IconButton>}
/>
render() {
return (
<div className='w100p column'>
<AppBar
title="DMCA / Copyright"
iconElementLeft={<IconButton onClick={()=>{ window.router('/') }}><NavigationClose /></IconButton>}
/>
<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 className='fs1-5 pad0-75'>Content status</div>
<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 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.
Information collected automaticly and the real source are torrent clients based on torrent protocol.
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.
<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.
</div>
</div>
);
}
Right holders can block/remove content that they responsible for. Contact administration or left application about content removal.
</div>
</div>
);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -3,18 +3,18 @@ import React from 'react';
export default (props) => {
return (
<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
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-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
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
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
C243.779,80.572,238.768,71.728,220.195,71.427z"/>
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-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
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
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
C243.779,80.572,238.768,71.728,220.195,71.427z"/>
</svg>
</div>
</div>
)
}

View File

@ -1,8 +1,8 @@
export default function formatBytes(bytes,decimals) {
if(bytes == 0) return '0 Byte';
var k = 1000; // or 1024 for binary
var dm = decimals + 1 || 3;
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
var i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
if(bytes == 0) return '0 Byte';
var k = 1000; // or 1024 for binary
var dm = decimals + 1 || 3;
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
var i = Math.floor(Math.log(bytes) / Math.log(k));
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'
class Header extends React.Component {
constructor(props)
{
super(props)
this.header = React.createRef();
}
componentDidMount()
{
constructor(props)
{
super(props)
this.header = React.createRef();
}
componentDidMount()
{
window.onscroll = () => {
if (window.pageYOffset >= 15)
{
const scrollHeight = Math.max(
document.body.scrollHeight, document.documentElement.scrollHeight,
document.body.offsetHeight, document.documentElement.offsetHeight,
document.body.clientHeight, document.documentElement.clientHeight
);
const scrollHeight = Math.max(
document.body.scrollHeight, document.documentElement.scrollHeight,
document.body.offsetHeight, document.documentElement.offsetHeight,
document.body.clientHeight, document.documentElement.clientHeight
);
if(scrollHeight - 240 < document.documentElement.clientHeight)
{
return
}
if(scrollHeight - 240 < document.documentElement.clientHeight)
{
return
}
if(!this.stickyHeader)
{
this.stickyHeader = true
this.header.current.classList.add("sticky");
this.stickyHeader = true
this.header.current.classList.add("sticky");
}
}
else
{
if(this.stickyHeader)
{
this.stickyHeader = false
this.header.current.classList.remove("sticky");
this.stickyHeader = false
this.header.current.classList.remove("sticky");
}
}
};
}
componentWillUnmount()
{
window.onscroll = null
}
}
componentWillUnmount()
{
window.onscroll = null
}
render()
{
return (
<div ref={this.header} className='header'>
<Card className='w100p header-main' style={{position: 'fixed', zIndex: 2}}>
<CardMedia
overlay={<CardTitle className='header-title' title={__('Yarrr, Landlubbers!')} style={{paddingTop: 2}} subtitle={
<div>
<div className='row' style={{position: 'absolute', top: -65}}>
<svg className='clickable'
onClick={() => {
window.router('/config')
}}
fill='white' style={{height: 45, margin: 4}} viewBox="0 0 932.179 932.179">
<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
render()
{
return (
<div ref={this.header} className='header'>
<Card className='w100p header-main' style={{position: 'fixed', zIndex: 2}}>
<CardMedia
overlay={<CardTitle className='header-title' title={__('Yarrr, Landlubbers!')} style={{paddingTop: 2}} subtitle={
<div>
<div className='row' style={{position: 'absolute', top: -65}}>
<svg className='clickable'
onClick={() => {
window.router('/config')
}}
fill='white' style={{height: 45, margin: 4}} viewBox="0 0 932.179 932.179">
<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
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
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-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"/>
<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
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
@ -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-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"/>
<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
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
@ -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
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"/>
</g>
</svg>
<svg className='clickable'
onClick={() => {
window.router('/filters')
}}
fill='white' style={{height: 45, margin: 4}} viewBox="0 0 512 512">
<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
</g>
</svg>
<svg className='clickable'
onClick={() => {
window.router('/filters')
}}
fill='white' style={{height: 45, margin: 4}} viewBox="0 0 512 512">
<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
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"/>
</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
</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
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"/>
</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
</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
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
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
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"/>
</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
</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
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>
<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
</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
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-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
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"/>
</g>
</g>
</svg>
</div>
</g>
</g>
</svg>
</div>
{__('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')}:
{__('this is only information about content that collected automatically')}!
</div>} />}
>
<div className='row header-row' style={{
padding: '15px',
background: `url('${Background}') no-repeat`,
backgroundSize: 'cover',
transition: '1s'
}}>
<RaisedButton
label={__('Feed')}
onClick={() => {
window.router('/')
}}
backgroundColor='#69238c'
labelColor='white'
style={{height: 60, borderRadius: 6, margin: 5, zIndex: 1}}
buttonStyle={{borderRadius: 5}}
icon={<svg fill='white' style={{height: 28}} viewBox="0 0 64.051 64.051">
<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
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="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="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"/>
</g>
</svg>
{__('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')}:
{__('this is only information about content that collected automatically')}!
</div>} />}
>
<div className='row header-row' style={{
padding: '15px',
background: `url('${Background}') no-repeat`,
backgroundSize: 'cover',
transition: '1s'
}}>
<RaisedButton
label={__('Feed')}
onClick={() => {
window.router('/')
}}
backgroundColor='#69238c'
labelColor='white'
style={{height: 60, borderRadius: 6, margin: 5, zIndex: 1}}
buttonStyle={{borderRadius: 5}}
icon={<svg fill='white' style={{height: 28}} viewBox="0 0 64.051 64.051">
<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
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="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="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"/>
</g>
</svg>
}
/>
<RaisedButton
label={__('Downloads')}
onClick={() => {
window.router('/downloads')
}}
backgroundColor='#2080E4'
labelColor='white'
style={{height: 60, borderRadius: 6, margin: 5, zIndex: 1}}
buttonStyle={{borderRadius: 5}}
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
}
/>
<RaisedButton
label={__('Downloads')}
onClick={() => {
window.router('/downloads')
}}
backgroundColor='#2080E4'
labelColor='white'
style={{height: 60, borderRadius: 6, margin: 5, zIndex: 1}}
buttonStyle={{borderRadius: 5}}
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
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-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
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"/>
</svg>
}
/>
<RaisedButton
label={__('Top')}
onClick={() => {
window.router('/top')
}}
backgroundColor='#B1CE57'
labelColor='white'
style={{height: 60, width: 120, borderRadius: 5, margin: 5, zIndex: 1}}
buttonStyle={{borderRadius: 5}}
icon={<svg fill='white' style={{height: 30}} viewBox="0 0 284.929 284.929">
<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
</svg>
}
/>
<RaisedButton
label={__('Top')}
onClick={() => {
window.router('/top')
}}
backgroundColor='#B1CE57'
labelColor='white'
style={{height: 60, width: 120, borderRadius: 5, margin: 5, zIndex: 1}}
buttonStyle={{borderRadius: 5}}
icon={<svg fill='white' style={{height: 30}} viewBox="0 0 284.929 284.929">
<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
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.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
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
L149.028,117.055z"/>
</g>
</svg>
}
/>
<RaisedButton
label={__('Activity')}
onClick={() => {
window.router('/activity')
}}
backgroundColor='#2a5cba'
labelColor='white'
style={{height: 60, width: 150, borderRadius: 5, margin: 5, zIndex: 1}}
buttonStyle={{borderRadius: 5}}
icon={<svg fill='white' style={{height: 30}} viewBox="0 0 352.352 352.352">
<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
</g>
</svg>
}
/>
<RaisedButton
label={__('Activity')}
onClick={() => {
window.router('/activity')
}}
backgroundColor='#2a5cba'
labelColor='white'
style={{height: 60, width: 150, borderRadius: 5, margin: 5, zIndex: 1}}
buttonStyle={{borderRadius: 5}}
icon={<svg fill='white' style={{height: 30}} viewBox="0 0 352.352 352.352">
<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
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
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
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"/>
<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
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
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
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
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
C23.483,266.06,21.647,263.611,21.035,260.552z"/>
</g>
</svg>
}
/>
<div className='fs0-85 pad0-75 column search-panel' style={{
marginLeft: 'auto',
marginTop: '-10px',
zIndex: 2
}}>
<Search />
</g>
</svg>
}
/>
<div className='fs0-85 pad0-75 column search-panel' style={{
marginLeft: 'auto',
marginTop: '-10px',
zIndex: 2
}}>
<Search />
</div>
</div>
</div>
</CardMedia>
</Card>
<div className='clear-header-space' style={{transition: '1.0s'}} />
</div>
)
</CardMedia>
</Card>
<div className='clear-header-space' style={{transition: '1.0s'}} />
</div>
)
}
}
}
export {Header}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,130 +7,130 @@ import Divider from 'material-ui/Divider';
import LinearProgress from 'material-ui/LinearProgress';
export default class SearchResults extends Component {
render() {
return (
<List style={{minWidth: '20em'}}>
{
(this.props.torrentsSearchResults && this.props.torrentsSearchResults.length > 0)
|| (this.props.filesSearchResults && this.props.filesSearchResults.length > 0)
?
<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>
<div className='w100p row center' style={{marginTop: '-16px'}}>{this.props.resultSelector}</div>
</div>
:
null
}
{
this.props.torrentsSearchResults && this.props.torrentsSearchResults.length > 0
?
this.props.torrentsSearchResults.map((torrent, index) =>{
return(
<TorrentLine torrent={torrent} key={index} />
);
})
:
null
}
{
this.props.moreTorrentsEnabled && !this.props.moreTorrentsIndicator
?
<div>
<ListItem innerDivStyle={{textAlign: 'center', padding: '1em'}} primaryText={<span>{__('More Torrents')}</span>} onClick={() => {
if(this.props.onMoreTorrents)
this.props.onMoreTorrents();
}} />
<Divider />
</div>
:
null
}
{
this.props.moreTorrentsIndicator
?
<div style={{padding: '0.8em'}}>
<LinearProgress mode="indeterminate" />
</div>
:
null
}
{
this.props.filesSearchResults && this.props.filesSearchResults.length > 0
?
this.props.filesSearchResults.map((torrent, index) =>{
return(
<TorrentLine torrent={torrent} key={index} />
);
})
:
null
}
{
this.props.moreFilesEnabled && !this.props.moreFilesIndicator
?
<div>
<ListItem innerDivStyle={{textAlign: 'center', padding: '1em'}} primaryText={__('More Files')} onClick={() => {
if(this.props.onMoreFiles)
this.props.onMoreFiles();
}} />
<Divider />
</div>
:
null
}
{
this.props.moreFilesIndicator
?
<div style={{padding: '0.8em'}}>
<LinearProgress mode="indeterminate" />
</div>
:
null
}
{
this.props.torrentsSearchResults && this.props.torrentsSearchResults.length == 0
&& this.props.filesSearchResults && this.props.filesSearchResults.length == 0
&& !this.props.currentSearching
?
<div className='row inline center w100p pad0-75'>
<svg style={{fill: 'grey', height: '30px'}} viewBox="0 0 264.695 264.695">
<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
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
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
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
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
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
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"/>
<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
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
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
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
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
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
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
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>
</svg>
<div className='fs0-85 pad0-75' style={{color: 'grey'}}>{__('no torrents for')} <b>{this.props.searchText}</b> {__('were found')}</div>
</div>
:
null
}
</List>
);
}
render() {
return (
<List style={{minWidth: '20em'}}>
{
(this.props.torrentsSearchResults && this.props.torrentsSearchResults.length > 0)
|| (this.props.filesSearchResults && this.props.filesSearchResults.length > 0)
?
<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>
<div className='w100p row center' style={{marginTop: '-16px'}}>{this.props.resultSelector}</div>
</div>
:
null
}
{
this.props.torrentsSearchResults && this.props.torrentsSearchResults.length > 0
?
this.props.torrentsSearchResults.map((torrent, index) =>{
return(
<TorrentLine torrent={torrent} key={index} />
);
})
:
null
}
{
this.props.moreTorrentsEnabled && !this.props.moreTorrentsIndicator
?
<div>
<ListItem innerDivStyle={{textAlign: 'center', padding: '1em'}} primaryText={<span>{__('More Torrents')}</span>} onClick={() => {
if(this.props.onMoreTorrents)
this.props.onMoreTorrents();
}} />
<Divider />
</div>
:
null
}
{
this.props.moreTorrentsIndicator
?
<div style={{padding: '0.8em'}}>
<LinearProgress mode="indeterminate" />
</div>
:
null
}
{
this.props.filesSearchResults && this.props.filesSearchResults.length > 0
?
this.props.filesSearchResults.map((torrent, index) =>{
return(
<TorrentLine torrent={torrent} key={index} />
);
})
:
null
}
{
this.props.moreFilesEnabled && !this.props.moreFilesIndicator
?
<div>
<ListItem innerDivStyle={{textAlign: 'center', padding: '1em'}} primaryText={__('More Files')} onClick={() => {
if(this.props.onMoreFiles)
this.props.onMoreFiles();
}} />
<Divider />
</div>
:
null
}
{
this.props.moreFilesIndicator
?
<div style={{padding: '0.8em'}}>
<LinearProgress mode="indeterminate" />
</div>
:
null
}
{
this.props.torrentsSearchResults && this.props.torrentsSearchResults.length == 0
&& this.props.filesSearchResults && this.props.filesSearchResults.length == 0
&& !this.props.currentSearching
?
<div className='row inline center w100p pad0-75'>
<svg style={{fill: 'grey', height: '30px'}} viewBox="0 0 264.695 264.695">
<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
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
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
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
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
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
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"/>
<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
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
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
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
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
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
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
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>
</svg>
<div className='fs0-85 pad0-75' style={{color: 'grey'}}>{__('no torrents for')} <b>{this.props.searchText}</b> {__('were found')}</div>
</div>
:
null
}
</List>
);
}
}

View File

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

View File

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

View File

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

View File

@ -53,34 +53,34 @@ const treeToTorrentFiles = (tree) => {
arr.push(<ListItem
key={file}
primaryText={file}
secondaryText={formatBytes(tree[file].__sizeBT)}
nestedItems={treeToTorrentFiles(tree[file])}
primaryTogglesNestedList={true}
innerDivStyle={{wordBreak: 'break-word'}}
leftIcon={tree[file] && Object.keys(tree[file]).length > 1 ? <FileFolder /> : contentIcon(fileTypeDetect({path: file}))}
/>);
secondaryText={formatBytes(tree[file].__sizeBT)}
nestedItems={treeToTorrentFiles(tree[file])}
primaryTogglesNestedList={true}
innerDivStyle={{wordBreak: 'break-word'}}
leftIcon={tree[file] && Object.keys(tree[file]).length > 1 ? <FileFolder /> : contentIcon(fileTypeDetect({path: file}))}
/>);
}
return arr;
}
const TorrentFiles = (props) => {
let filesList = props.torrent.filesList;
let filesList = props.torrent.filesList;
let tree = buildFilesTree(filesList);
return (
<List className='w100p'>
{
filesList.length > 0
?
<div className='w100p'>
<Subheader inset={true}>{__('Content of the torrent')}:</Subheader>
{treeToTorrentFiles(tree)}
</div>
:
<div className='column center'>
<span className='pad0-75'>{__('Processing files')}...</span>
<LinearProgress mode="indeterminate" />
</div>
}
{
filesList.length > 0
?
<div className='w100p'>
<Subheader inset={true}>{__('Content of the torrent')}:</Subheader>
{treeToTorrentFiles(tree)}
</div>
:
<div className='column center'>
<span className='pad0-75'>{__('Processing files')}...</span>
<LinearProgress mode="indeterminate" />
</div>
}
</List>
);
};
@ -89,84 +89,84 @@ const TorrentInformation = (props) => {
let torrent = props.torrent;
return (
<List className='w100p'>
<Subheader inset={true}>{__('Information about torrent')}</Subheader>
<ListItem
//leftAvatar={<Avatar icon={<ActionAssignment />} backgroundColor={blue500} />}
rightIcon={<ActionInfo />}
primaryText={__('Torrent Name')}
secondaryText={<span className='break-word' style={{whiteSpace: 'normal'}}>{torrent.name}</span>}
/>
<ListItem
// leftAvatar={<Avatar icon={<EditorInsertChart />} backgroundColor={yellow600} />}
rightIcon={<ActionInfo />}
primaryText={__('Torrent Size')}
secondaryText={formatBytes(torrent.size)}
/>
<ListItem
// leftAvatar={<Avatar icon={<EditorInsertChart />} backgroundColor={yellow600} />}
rightIcon={<ActionInfo />}
primaryText={__('Torrent contains files')}
secondaryText={torrent.files}
onClick={() => {
if(!props.parent)
return
<Subheader inset={true}>{__('Information about torrent')}</Subheader>
<ListItem
//leftAvatar={<Avatar icon={<ActionAssignment />} backgroundColor={blue500} />}
rightIcon={<ActionInfo />}
primaryText={__('Torrent Name')}
secondaryText={<span className='break-word' style={{whiteSpace: 'normal'}}>{torrent.name}</span>}
/>
<ListItem
// leftAvatar={<Avatar icon={<EditorInsertChart />} backgroundColor={yellow600} />}
rightIcon={<ActionInfo />}
primaryText={__('Torrent Size')}
secondaryText={formatBytes(torrent.size)}
/>
<ListItem
// leftAvatar={<Avatar icon={<EditorInsertChart />} backgroundColor={yellow600} />}
rightIcon={<ActionInfo />}
primaryText={__('Torrent contains files')}
secondaryText={torrent.files}
onClick={() => {
if(!props.parent)
return
props.parent.setState({
value: 'files'
})
}}
/>
<ListItem
// leftAvatar={<Avatar icon={<EditorInsertChart />} backgroundColor={yellow600} />}
rightIcon={<ActionInfo />}
primaryText={__('Indexed/Added torrent date')}
secondaryText={moment(torrent.added * 1000).format('MMMM Do YYYY, hh:mm')}
/>
<ListItem
// leftAvatar={<Avatar icon={<EditorInsertChart />} backgroundColor={yellow600} />}
rightIcon={<ActionInfo />}
primaryText={__('Content type')}
secondaryText={torrent.contentType || 'unknown'}
/>
<ListItem
// leftAvatar={<Avatar icon={<EditorInsertChart />} backgroundColor={yellow600} />}
rightIcon={<ActionInfo />}
primaryText={__('Category')}
secondaryText={torrent.contentCategory || 'unknown'}
/>
</List>
props.parent.setState({
value: 'files'
})
}}
/>
<ListItem
// leftAvatar={<Avatar icon={<EditorInsertChart />} backgroundColor={yellow600} />}
rightIcon={<ActionInfo />}
primaryText={__('Indexed/Added torrent date')}
secondaryText={moment(torrent.added * 1000).format('MMMM Do YYYY, hh:mm')}
/>
<ListItem
// leftAvatar={<Avatar icon={<EditorInsertChart />} backgroundColor={yellow600} />}
rightIcon={<ActionInfo />}
primaryText={__('Content type')}
secondaryText={torrent.contentType || 'unknown'}
/>
<ListItem
// leftAvatar={<Avatar icon={<EditorInsertChart />} backgroundColor={yellow600} />}
rightIcon={<ActionInfo />}
primaryText={__('Category')}
secondaryText={torrent.contentCategory || 'unknown'}
/>
</List>
);
}
export default class TorrentPage extends Page {
constructor(props) {
super(props);
this.state = {
value: 'info',
searchingIndicator: false,
voting: false,
voted: false,
askDownloading: false,
downloading: false,
downloadProgress: {}
};
this.setTitle('Information about torrent');
}
constructor(props) {
super(props);
this.state = {
value: 'info',
searchingIndicator: false,
voting: false,
voted: false,
askDownloading: false,
downloading: false,
downloadProgress: {}
};
this.setTitle('Information about torrent');
}
changeTab(tab) {
if(this.state.value != tab) {
this.setState({
value: tab
});
console.log('change');
}
}
onSwipeRight() {
this.changeTab('files');
}
onSwipeLeft() {
this.changeTab('info');
}
changeTab(tab) {
if(this.state.value != tab) {
this.setState({
value: tab
});
console.log('change');
}
}
onSwipeRight() {
this.changeTab('files');
}
onSwipeLeft() {
this.changeTab('info');
}
handleChange = (value) => {
if(value == 'main') {
@ -174,18 +174,18 @@ export default class TorrentPage extends Page {
return;
}
this.setState({
value: value,
});
this.setState({
value: value,
});
};
getTorrentInfo() {
window.torrentSocket.emit('torrent', this.props.hash, {files: true, peer: this.props.peer}, window.customLoader((data) => {
if(data) {
this.torrent = data
this.setTitle(this.torrent.name + ' - Rats On The Boat');
if(this.torrent.contentCategory == 'xxx') {
this.setMetaTag('robots', 'noindex');
}
if(this.torrent.contentCategory == 'xxx') {
this.setMetaTag('robots', 'noindex');
}
//this.forceUpdate(); // вызывается через searchingIndicator
// Получаем более новую статистику пира
@ -194,195 +194,195 @@ export default class TorrentPage extends Page {
}
}
}, () => {
this.setState({
searchingIndicator: true
});
}, () => {
this.setState({
searchingIndicator: false
});
}));
this.setState({
searchingIndicator: true
});
}, () => {
this.setState({
searchingIndicator: false
});
}));
}
componentDidMount() {
super.componentDidMount();
this.filesUpdated = (hash) => {
if(this.props.hash != hash)
return;
return;
this.getTorrentInfo();
}
window.torrentSocket.on('filesReady', this.filesUpdated);
this.trackerUpdate = (info) => {
if(this.props.hash != info.hash)
return;
if(this.props.hash != info.hash)
return;
if(!this.torrent)
return;
if(!this.torrent)
return;
Object.assign(this.torrent, info);
this.forceUpdate();
}
window.torrentSocket.on('trackerTorrentUpdate', this.trackerUpdate);
Object.assign(this.torrent, info);
this.forceUpdate();
}
window.torrentSocket.on('trackerTorrentUpdate', this.trackerUpdate);
this.onVotes = async ({hash, good, bad, selfVote}) => {
if(this.props.hash != hash)
return;
this.onVotes = async ({hash, good, bad, selfVote}) => {
if(this.props.hash != hash)
return;
if(!this.torrent)
return
if(!this.torrent)
return
this.torrent.good = good;
this.torrent.bad = bad;
this.state.voted = selfVote;
this.forceUpdate();
}
window.torrentSocket.on('votes', this.onVotes);
this.torrent.good = good;
this.torrent.bad = bad;
this.state.voted = selfVote;
this.forceUpdate();
}
window.torrentSocket.on('votes', this.onVotes);
this.downloading = (hash) => {
if(this.props.hash != hash)
return;
this.downloading = (hash) => {
if(this.props.hash != hash)
return;
this.setState({downloading: true})
}
window.torrentSocket.on('downloading', this.downloading);
this.setState({downloading: true})
}
window.torrentSocket.on('downloading', this.downloading);
this.downloadDone = (hash, canceled) => {
if(this.props.hash != hash)
return;
this.downloadDone = (hash, canceled) => {
if(this.props.hash != hash)
return;
this.setState({
downloading: false,
askDownloading: !canceled
})
}
window.torrentSocket.on('downloadDone', this.downloadDone);
this.setState({
downloading: false,
askDownloading: !canceled
})
}
window.torrentSocket.on('downloadDone', this.downloadDone);
this.downloadProgress = (hash, progress) => {
if(this.props.hash != hash)
return;
this.downloadProgress = (hash, progress) => {
if(this.props.hash != hash)
return;
this.setState({downloadProgress: progress})
}
window.torrentSocket.on('downloadProgress', this.downloadProgress);
this.setState({downloadProgress: progress})
}
window.torrentSocket.on('downloadProgress', this.downloadProgress);
this.getTorrentInfo();
this.getTorrentInfo();
}
componentWillUnmount() {
if(this.filesUpdated)
window.torrentSocket.off('filesReady', this.filesUpdated);
window.torrentSocket.off('filesReady', this.filesUpdated);
if(this.trackerUpdate)
window.torrentSocket.off('trackerTorrentUpdate', this.trackerUpdate);
if(this.onVotes)
window.torrentSocket.off('votes', this.onVotes);
if(this.torrent && this.torrent.contentCategory == 'xxx') {
this.removeMetaTag('robots');
}
if(this.downloading)
window.torrentSocket.off('downloading', this.downloading);
if(this.downloadDone)
window.torrentSocket.off('downloadDone', this.downloadDone);
if(this.downloadProgress)
window.torrentSocket.off('downloadProgress', this.downloadProgress);
window.torrentSocket.off('trackerTorrentUpdate', this.trackerUpdate);
if(this.onVotes)
window.torrentSocket.off('votes', this.onVotes);
if(this.torrent && this.torrent.contentCategory == 'xxx') {
this.removeMetaTag('robots');
}
if(this.downloading)
window.torrentSocket.off('downloading', this.downloading);
if(this.downloadDone)
window.torrentSocket.off('downloadDone', this.downloadDone);
if(this.downloadProgress)
window.torrentSocket.off('downloadProgress', this.downloadProgress);
}
vote(good) {
if(!this.torrent)
return;
if(!this.torrent)
return;
this.setState({
voting: true
});
window.torrentSocket.emit('vote', this.torrent.hash, !!good, window.customLoader((success) => {
this.setState({
voted: true,
voting: false
});
}));
this.setState({
voting: true
});
window.torrentSocket.emit('vote', this.torrent.hash, !!good, window.customLoader((success) => {
this.setState({
voted: true,
voting: false
});
}));
}
render() {
const style = {
refresh: {
display: 'inline-block',
position: 'relative',
},
};
refresh: {
display: 'inline-block',
position: 'relative',
},
};
if(this.state.searchingIndicator) {
return (
<div className='pad1 w100p column center'>
<RefreshIndicator
size={50}
left={0}
top={0}
loadingColor="#FF9800"
status="loading"
style={style.refresh}
/>
</div>
);
}
if(this.state.searchingIndicator) {
return (
<div className='pad1 w100p column center'>
<RefreshIndicator
size={50}
left={0}
top={0}
loadingColor="#FF9800"
status="loading"
style={style.refresh}
/>
</div>
);
}
let torrentRating;
if(this.torrent) {
torrentRating = Math.round(rating(this.torrent.good, this.torrent.bad) * 100);
}
let torrentRating;
if(this.torrent) {
torrentRating = Math.round(rating(this.torrent.good, this.torrent.bad) * 100);
}
return (
<div className="w100p column center">
{
this.torrent
?
<Tabs
className='w100p'
value={this.state.value}
onChange={this.handleChange}
>
<Tab label={__('Back to main')} value="main" />
<Tab label={__('Information')} value="info" >
<div className='column w100p'>
<div className='row w100p torrent-information-row'>
<div className='info-table'>
<TorrentInformation torrent={this.torrent} parent={this} />
</div>
<div style={{flexBasis: '40%'}} className='column center w100p'>
<img src={NoImage} className='pad0-75' style={{height: '200px'}} />
<RaisedButton
href={`magnet:?xt=urn:btih:${this.torrent.hash}`}
target="_self"
label="Magnet"
secondary={true}
onClick={(e) => {
e.preventDefault();
window.open(`magnet:?xt=urn:btih:${this.torrent.hash}`, '_self')
}}
icon={<svg fill='white' viewBox="0 0 24 24"><path d="M17.374 20.235c2.444-2.981 6.626-8.157 6.626-8.157l-3.846-3.092s-2.857 3.523-6.571 8.097c-4.312 5.312-11.881-2.41-6.671-6.671 4.561-3.729 8.097-6.57 8.097-6.57l-3.092-3.842s-5.173 4.181-8.157 6.621c-2.662 2.175-3.76 4.749-3.76 7.24 0 5.254 4.867 10.139 10.121 10.139 2.487 0 5.064-1.095 7.253-3.765zm4.724-7.953l-1.699 2.111-1.74-1.397 1.701-2.114 1.738 1.4zm-10.386-10.385l1.4 1.738-2.113 1.701-1.397-1.74 2.11-1.699z"/></svg>}
/>
{
!this.state.askDownloading && !this.state.downloading
return (
<div className="w100p column center">
{
this.torrent
?
<Tabs
className='w100p'
value={this.state.value}
onChange={this.handleChange}
>
<Tab label={__('Back to main')} value="main" />
<Tab label={__('Information')} value="info" >
<div className='column w100p'>
<div className='row w100p torrent-information-row'>
<div className='info-table'>
<TorrentInformation torrent={this.torrent} parent={this} />
</div>
<div style={{flexBasis: '40%'}} className='column center w100p'>
<img src={NoImage} className='pad0-75' style={{height: '200px'}} />
<RaisedButton
href={`magnet:?xt=urn:btih:${this.torrent.hash}`}
target="_self"
label="Magnet"
secondary={true}
onClick={(e) => {
e.preventDefault();
window.open(`magnet:?xt=urn:btih:${this.torrent.hash}`, '_self')
}}
icon={<svg fill='white' viewBox="0 0 24 24"><path d="M17.374 20.235c2.444-2.981 6.626-8.157 6.626-8.157l-3.846-3.092s-2.857 3.523-6.571 8.097c-4.312 5.312-11.881-2.41-6.671-6.671 4.561-3.729 8.097-6.57 8.097-6.57l-3.092-3.842s-5.173 4.181-8.157 6.621c-2.662 2.175-3.76 4.749-3.76 7.24 0 5.254 4.867 10.139 10.121 10.139 2.487 0 5.064-1.095 7.253-3.765zm4.724-7.953l-1.699 2.111-1.74-1.397 1.701-2.114 1.738 1.4zm-10.386-10.385l1.4 1.738-2.113 1.701-1.397-1.74 2.11-1.699z"/></svg>}
/>
{
!this.state.askDownloading && !this.state.downloading
&&
<RaisedButton
href={`magnet:?xt=urn:btih:${this.torrent.hash}`}
target="_self"
label={__('Download')}
backgroundColor='#00C853'
labelColor='white'
style={{marginTop: 8}}
onClick={(e) => {
e.preventDefault();
this.setState({askDownloading: true})
window.torrentSocket.emit('download', this.torrent)
}}
icon={
<svg viewBox="0 0 56 56" fill='white'>
<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
href={`magnet:?xt=urn:btih:${this.torrent.hash}`}
target="_self"
label={__('Download')}
backgroundColor='#00C853'
labelColor='white'
style={{marginTop: 8}}
onClick={(e) => {
e.preventDefault();
this.setState({askDownloading: true})
window.torrentSocket.emit('download', this.torrent)
}}
icon={
<svg viewBox="0 0 56 56" fill='white'>
<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
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
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.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
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
@ -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
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"/>
</g>
</svg>
}
</g>
</svg>
}
/>
}
{
this.state.downloading
}
{
this.state.downloading
&&
<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>
<LinearProgress
style={{marginTop: 3}}
mode="determinate"
value={this.state.downloadProgress && (this.state.downloadProgress.progress ? this.state.downloadProgress.progress : 0) * 100}
/>
<FlatButton
onClick={() => {
window.torrentSocket.emit('downloadCancel', this.torrent.hash)
}}
label={__('Cancel download')}
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>}
/>
<div className='fs0-75' style={{color: 'rgb(0, 188, 212)'}}>{__('downloading')} {this.state.downloadProgress && (this.state.downloadProgress.progress * 100).toFixed(1)}%</div>
<LinearProgress
style={{marginTop: 3}}
mode="determinate"
value={this.state.downloadProgress && (this.state.downloadProgress.progress ? this.state.downloadProgress.progress : 0) * 100}
/>
<FlatButton
onClick={() => {
window.torrentSocket.emit('downloadCancel', this.torrent.hash)
}}
label={__('Cancel download')}
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>}
/>
</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
?
<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: '#AA00FF'}}>{__('leechers')}: {this.torrent.leechers}</div>
<div className='pad0-25' style={{color: '#FF6D00'}}>{__('completed')}: {this.torrent.completed}</div>
</div>
:
null
}
{
!this.state.voted && !this.state.voting
?
<div className='row pad0-25'>
<RaisedButton
label={__('Good') + ` (${this.torrent.good})`}
labelColor="white"
backgroundColor="#00C853"
icon={
<svg viewBox="0 0 489.543 489.543" fill="white">
<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
}
<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
?
<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: '#AA00FF'}}>{__('leechers')}: {this.torrent.leechers}</div>
<div className='pad0-25' style={{color: '#FF6D00'}}>{__('completed')}: {this.torrent.completed}</div>
</div>
:
null
}
{
!this.state.voted && !this.state.voting
?
<div className='row pad0-25'>
<RaisedButton
label={__('Good') + ` (${this.torrent.good})`}
labelColor="white"
backgroundColor="#00C853"
icon={
<svg viewBox="0 0 489.543 489.543" fill="white">
<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
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
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"
/>
</g>
</svg>
}
onClick={() => this.vote(true)}
/>
<RaisedButton
style={{marginLeft: '9px'}}
label={__('Bad') + ` (${this.torrent.bad})`}
labelColor="white"
backgroundColor="#D50000"
icon={
<svg viewBox="0 0 487.643 487.643" fill="white">
<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="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>
</svg>
}
onClick={() => this.vote(true)}
/>
<RaisedButton
style={{marginLeft: '9px'}}
label={__('Bad') + ` (${this.torrent.bad})`}
labelColor="white"
backgroundColor="#D50000"
icon={
<svg viewBox="0 0 487.643 487.643" fill="white">
<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
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-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"/>
</g>
</svg>
}
onClick={() => this.vote(false)}
/>
</div>
:
this.state.voting
?
<div>voting...</div>
:
<div>Thank you for voting!</div>
}
{
this.torrent.good > 0 || this.torrent.bad > 0
?
<div className='w100p' style={{padding: '7px 35px', marginTop: '10px'}}>
<LinearProgress
mode="determinate"
value={torrentRating}
color={torrentRating >= 50 ? '#00E676' : '#FF3D00'}
style={{
height: '5px',
}}
/>
<div className='row center pad0-75 fs0-85' style={{color: torrentRating >= 50 ? '#00E676' : '#FF3D00'}}>{__('Torrent rating')}: {torrentRating}%</div>
</div>
:
null
}
</div>
</div>
</div>
</Tab>
<Tab label={__('Files')} value="files" >
<TorrentFiles torrent={this.torrent} />
</Tab>
</Tabs>
:
null
}
</div>
);
</g>
</svg>
}
onClick={() => this.vote(false)}
/>
</div>
:
this.state.voting
?
<div>voting...</div>
:
<div>Thank you for voting!</div>
}
{
this.torrent.good > 0 || this.torrent.bad > 0
?
<div className='w100p' style={{padding: '7px 35px', marginTop: '10px'}}>
<LinearProgress
mode="determinate"
value={torrentRating}
color={torrentRating >= 50 ? '#00E676' : '#FF3D00'}
style={{
height: '5px',
}}
/>
<div className='row center pad0-75 fs0-85' style={{color: torrentRating >= 50 ? '#00E676' : '#FF3D00'}}>{__('Torrent rating')}: {torrentRating}%</div>
</div>
:
null
}
</div>
</div>
</div>
</Tab>
<Tab label={__('Files')} value="files" >
<TorrentFiles torrent={this.torrent} />
</Tab>
</Tabs>
:
null
}
</div>
);
}
}

View File

@ -2,37 +2,37 @@ import React, { Component } from 'react';
import formatBytes from './format-bytes'
export default class TorrentsStatistic extends Component {
constructor(props)
{
super(props)
constructor(props)
{
super(props)
this.stats = props.stats || {}
}
componentDidMount()
{
this.newTorrentFunc = (torrent) => {
this.stats.size += torrent.size;
this.stats.torrents++;
this.stats.files += torrent.files;
this.forceUpdate()
}
this.stats = props.stats || {}
}
componentDidMount()
{
this.newTorrentFunc = (torrent) => {
this.stats.size += torrent.size;
this.stats.torrents++;
this.stats.files += torrent.files;
this.forceUpdate()
}
window.torrentSocket.on('newTorrent', this.newTorrentFunc);
}
componentWillUnmount()
{
if(this.newTorrentFunc)
window.torrentSocket.off('newTorrent', this.newTorrentFunc);
}
render()
{
return (
<div 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='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'}}>
<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
window.torrentSocket.on('newTorrent', this.newTorrentFunc);
}
componentWillUnmount()
{
if(this.newTorrentFunc)
window.torrentSocket.off('newTorrent', this.newTorrentFunc);
}
render()
{
return (
<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='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'}}>
<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
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
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
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"/>
</svg>
<div style={{marginLeft: '5px'}}>{ formatBytes(this.stats.size, 1) } {__('data')}</div>
</div>
</svg>
<div style={{marginLeft: '5px'}}>{ formatBytes(this.stats.size, 1) } {__('data')}</div>
</div>
<div className='row inline' style={{color: '#f48641', fontSize: '1.15em', fill: '#f48641', marginLeft: '20px'}}>
<svg viewBox="0 0 60 60">
<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
<div className='row inline' style={{color: '#f48641', fontSize: '1.15em', fill: '#f48641', marginLeft: '20px'}}>
<svg viewBox="0 0 60 60">
<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
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"/>
<path d="M42,23H18v10h24V23z M40,31H20v-6h20V31z"/>
<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="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="M42,23H18v10h24V23z M40,31H20v-6h20V31z"/>
<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="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
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"/>
<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>
</svg>
<div style={{marginLeft: '5px'}}>{this.stats.torrents} {__('torrents')}</div>
</div>
<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>
</svg>
<div style={{marginLeft: '5px'}}>{this.stats.torrents} {__('torrents')}</div>
</div>
<div className='row inline' style={{color: '#f441e2', fontSize: '1.15em', fill: '#f441e2', marginLeft: '20px'}}>
<svg viewBox="0 0 60 60">
<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="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,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="M38.914,0H6.5v60h47V14.586L38.914,0z M39.5,3.414L50.086,14H39.5V3.414z M8.5,58V2h29v14h14v42H8.5z"/>
</g>
</svg>
<div style={{marginLeft: '5px'}}>{this.stats.files} {__('files')}</div>
</div>
</div>
<div className='row inline' style={{color: '#f441e2', fontSize: '1.15em', fill: '#f441e2', marginLeft: '20px'}}>
<svg viewBox="0 0 60 60">
<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="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,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="M38.914,0H6.5v60h47V14.586L38.914,0z M39.5,3.414L50.086,14H39.5V3.414z M8.5,58V2h29v14h14v42H8.5z"/>
</g>
</svg>
<div style={{marginLeft: '5px'}}>{this.stats.files} {__('files')}</div>
</div>
</div>
<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'}}>
<svg viewBox="0 0 47 47">
<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
<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'}}>
<svg viewBox="0 0 47 47">
<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
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"/>
<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
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
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"/>
<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
C14.746,26.752,14.51,26.077,14.357,25.37z"/>
<circle cx="23.83" cy="23.323" r="7.271"/>
</g>
</svg>
<div style={{marginLeft: '5px'}}>{window.peers} {__('peers')}</div>
</div>
<circle cx="23.83" cy="23.323" r="7.271"/>
</g>
</svg>
<div style={{marginLeft: '5px'}}>{window.peers} {__('peers')}</div>
</div>
<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">
<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
<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">
<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
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
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
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"/>
</g>
</svg>
<div style={{marginLeft: '5px'}}>{window.peersTorrents} {__('remote torrents')}</div>
</div>
</g>
</svg>
<div style={{marginLeft: '5px'}}>{window.peersTorrents} {__('remote torrents')}</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'}}>
{
window.p2pStatus == 0
<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
&&
<div className='row inline w100p'>
<svg viewBox="0 0 54.908 54.908">
<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
<svg viewBox="0 0 54.908 54.908">
<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
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"/>
<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
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
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
S29.964,45.019,27.454,45.019z"/>
</g>
</svg>
<div style={{marginLeft: '5px'}}>{__('not available')}</div>
</g>
</svg>
<div style={{marginLeft: '5px'}}>{__('not available')}</div>
</div>
}
{
window.p2pStatus == 1
}
{
window.p2pStatus == 1
&&
<div className='row inline w100p'>
<svg viewBox="0 0 611.989 611.988">
<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
<svg viewBox="0 0 611.989 611.988">
<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
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
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
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"/>
</g>
</svg>
<div style={{marginLeft: '5px'}}>{__('redirect')}</div>
</g>
</svg>
<div style={{marginLeft: '5px'}}>{__('redirect')}</div>
</div>
}
{
window.p2pStatus == 2
}
{
window.p2pStatus == 2
&&
<div className='row inline w100p'>
<svg viewBox="0 0 611.989 611.988">
<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
<svg viewBox="0 0 611.989 611.988">
<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
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
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
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"/>
</g>
</svg>
<div style={{marginLeft: '5px'}}>{__('direct')}</div>
</g>
</svg>
<div style={{marginLeft: '5px'}}>{__('direct')}</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');
const contentIcon = (type, category, fill = 'grey') => {
if(category == 'xxx')
{
return (
<svg viewBox="0 0 18.282 18.282" fill={fill}>
<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
if(category == 'xxx')
{
return (
<svg viewBox="0 0 18.282 18.282" fill={fill}>
<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
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
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
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"/>
</g>
</svg>
)
}
</g>
</svg>
)
}
switch(type)
{
case 'video':
return (
<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
switch(type)
{
case 'video':
return (
<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
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="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
<circle cx="89.428" cy="118.706" r="59.619"/>
<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
c0-10.976,87.443-94.398,103.34-94.398c15.899,0,15.899,14.905,15.899,14.905V447.677z"/>
</svg>
)
case 'audio':
return (
<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
</svg>
)
case 'audio':
return (
<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
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
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.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"/>
</svg>
</svg>
)
case 'pictures':
return (
<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
)
case 'pictures':
return (
<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
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
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
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>
)
case 'application':
return (
<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
</svg>
)
case 'application':
return (
<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
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
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
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"/>
</svg>
)
case 'books':
return (
<svg viewBox="0 0 296.999 296.999" fill={fill}>
<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
</svg>
)
case 'books':
return (
<svg viewBox="0 0 296.999 296.999" fill={fill}>
<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
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"/>
<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
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
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
C30.49,232.13,18.332,219.963,18.332,205.042z"/>
</g>
</svg>
)
case 'archive':
return (
<svg viewBox="0 0 390 390" fill={fill}>
<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
</g>
</svg>
)
case 'archive':
return (
<svg viewBox="0 0 390 390" fill={fill}>
<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
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
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
@ -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
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"/>
</g>
</svg>
)
case 'disc':
return (
<svg viewBox="0 0 49.652 49.652" fill={fill}>
<g>
<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
</g>
</svg>
)
case 'disc':
return (
<svg viewBox="0 0 49.652 49.652" fill={fill}>
<g>
<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
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
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"
/>
</g>
</svg>
)
default:
return (
<svg viewBox="0 0 123.769 123.769" fill={fill}>
<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
/>
</g>
</svg>
)
default:
return (
<svg viewBox="0 0 123.769 123.769" fill={fill}>
<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
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="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="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
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"/>
</g>
</svg>
)
}
</g>
</svg>
)
}
};
export {contentIcon}
export default class Torrent extends Component {
state = {
downloading: false,
askDownloading: false,
downloadProgress: {}
downloading: false,
askDownloading: false,
downloadProgress: {}
}
constructor(props)
{
super(props)
if(props.download)
{
const { progress, downloaded, speed } = props.download
this.state.downloadProgress = {
progress, downloaded, speed
}
}
super(props)
if(props.download)
{
const { progress, downloaded, speed } = props.download
this.state.downloadProgress = {
progress, downloaded, speed
}
}
}
componentDidMount()
{
this.downloading = (hash) => {
if(this.props.torrent.hash != hash)
return;
this.downloading = (hash) => {
if(this.props.torrent.hash != hash)
return;
this.setState({downloading: true})
}
window.torrentSocket.on('downloading', this.downloading);
this.setState({downloading: true})
}
window.torrentSocket.on('downloading', this.downloading);
this.downloadDone = (hash, canceled) => {
if(this.props.torrent.hash != hash)
return;
this.downloadDone = (hash, canceled) => {
if(this.props.torrent.hash != hash)
return;
this.setState({
downloading: false,
askDownloading: !canceled
})
}
window.torrentSocket.on('downloadDone', this.downloadDone);
this.setState({
downloading: false,
askDownloading: !canceled
})
}
window.torrentSocket.on('downloadDone', this.downloadDone);
this.downloadProgress = (hash, progress) => {
if(this.props.torrent.hash != hash)
return;
this.downloadProgress = (hash, progress) => {
if(this.props.torrent.hash != hash)
return;
this.setState({
downloading: true,
askDownloading: true,
downloadProgress: progress
})
}
window.torrentSocket.on('downloadProgress', this.downloadProgress);
this.setState({
downloading: true,
askDownloading: true,
downloadProgress: progress
})
}
window.torrentSocket.on('downloadProgress', this.downloadProgress);
}
componentWillUnmount()
{
if(this.downloading)
window.torrentSocket.off('downloading', this.downloading);
if(this.downloadDone)
window.torrentSocket.off('downloadDone', this.downloadDone);
if(this.downloadProgress)
window.torrentSocket.off('downloadProgress', this.downloadProgress);
if(this.downloading)
window.torrentSocket.off('downloading', this.downloading);
if(this.downloadDone)
window.torrentSocket.off('downloadDone', this.downloadDone);
if(this.downloadProgress)
window.torrentSocket.off('downloadProgress', this.downloadProgress);
}
render()
{
const torrent = this.props.torrent;
let torrentRating = -1
if(torrent.good > 0 || torrent.bad > 0)
torrentRating = Math.round(rating(torrent.good, torrent.bad) * 100);
const torrent = this.props.torrent;
let torrentRating = -1
if(torrent.good > 0 || torrent.bad > 0)
torrentRating = Math.round(rating(torrent.good, torrent.bad) * 100);
return (
<div>
<ListItem
onClick={(e) => {
const link = '/torrent/' + torrent.hash;
if(e.button === 1)
return false;
return (
<div>
<ListItem
onClick={(e) => {
const link = '/torrent/' + torrent.hash;
if(e.button === 1)
return false;
/*
/*
if(e.ctrlKey && e.button === 0) {
let win = window.open(link, '_blank');
//win.focus();
@ -239,105 +239,105 @@ export default class Torrent extends Component {
}
*/
PagesPie.instance().open(TorrentPage, {replace: 'all', hash: torrent.hash, peer: torrent.peer})
}}
primaryText={
<a href={'/torrent/' + torrent.hash} ref={(node) => {
if(node)
node.onclick = () => { return false }
}}>
<span className='break-word' style={{
color: torrent.contentCategory != 'xxx' ? (torrent.peer ? '#5643db' : 'black') : (torrent.peer ? '#9083e2' : 'grey')
}}>
{torrent.name}
</span>
</a>
}
secondaryText={
<a href={'/torrent/' + torrent.hash} ref={(node) => {
if(node)
node.onclick = () => { return false }
}}>
<div className='column' style={{height: 'auto', whiteSpace: 'normal', paddingTop: '0.30em'}}>
<div className='row w100p inline'>
<div style={{color: torrent.contentCategory != 'xxx' ? (torrent.peer ? '#5252d1' : 'black') : (torrent.peer ? '#9083e2' : 'grey')}}>
{
formatBytes(torrent.size, 1) + ' (' + torrent.files + ' files)'
}
</div>
{
this.state.downloading
PagesPie.instance().open(TorrentPage, {replace: 'all', hash: torrent.hash, peer: torrent.peer})
}}
primaryText={
<a href={'/torrent/' + torrent.hash} ref={(node) => {
if(node)
node.onclick = () => { return false }
}}>
<span className='break-word' style={{
color: torrent.contentCategory != 'xxx' ? (torrent.peer ? '#5643db' : 'black') : (torrent.peer ? '#9083e2' : 'grey')
}}>
{torrent.name}
</span>
</a>
}
secondaryText={
<a href={'/torrent/' + torrent.hash} ref={(node) => {
if(node)
node.onclick = () => { return false }
}}>
<div className='column' style={{height: 'auto', whiteSpace: 'normal', paddingTop: '0.30em'}}>
<div className='row w100p inline'>
<div style={{color: torrent.contentCategory != 'xxx' ? (torrent.peer ? '#5252d1' : 'black') : (torrent.peer ? '#9083e2' : 'grey')}}>
{
formatBytes(torrent.size, 1) + ' (' + torrent.files + ' files)'
}
</div>
{
this.state.downloading
&&
<LinearProgress
style={{width: '44%', marginLeft: 20}}
mode="determinate"
value={this.state.downloadProgress && (this.state.downloadProgress.progress ? this.state.downloadProgress.progress : 0) * 100}
/>
}
</div>
{
torrent.path && torrent.path.length > 0
?
torrent.path.map((path, index) => {
return <div key={index} className='break-word fs0-75' style={{paddingTop: '0.3em', marginLeft: '0.6em'}}>{path}</div>
})
:
null
}
{
torrent.seeders || torrent.leechers || torrent.completed
?
<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.leechers > 0 ? '#AA00FF' : 'grey'), marginLeft: '12px'}}>{torrent.leechers} {__('leechers')}</span>
<span style={{color: (torrent.completed > 0 ? '#FF6D00' : 'grey'), marginLeft: '12px'}}>{torrent.completed} {__('completed')}</span>
</div>
:
null
}
{
(torrent.good > 0 || torrent.bad > 0)
style={{width: '44%', marginLeft: 20}}
mode="determinate"
value={this.state.downloadProgress && (this.state.downloadProgress.progress ? this.state.downloadProgress.progress : 0) * 100}
/>
}
</div>
{
torrent.path && torrent.path.length > 0
?
torrent.path.map((path, index) => {
return <div key={index} className='break-word fs0-75' style={{paddingTop: '0.3em', marginLeft: '0.6em'}}>{path}</div>
})
:
null
}
{
torrent.seeders || torrent.leechers || torrent.completed
?
<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.leechers > 0 ? '#AA00FF' : 'grey'), marginLeft: '12px'}}>{torrent.leechers} {__('leechers')}</span>
<span style={{color: (torrent.completed > 0 ? '#FF6D00' : 'grey'), marginLeft: '12px'}}>{torrent.completed} {__('completed')}</span>
</div>
:
null
}
{
(torrent.good > 0 || torrent.bad > 0)
&&
<div className='row w100p inline' style={{maxWidth: 600}}>
<LinearProgress
mode="determinate"
value={torrentRating}
color={torrentRating >= 50 ? '#00E676' : '#FF3D00'}
style={{
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>
}
</div>
</a>
}
leftIcon={contentIcon(torrent.contentType, torrent.contentCategory, torrent.contentCategory != 'xxx' ? (torrent.peer ? '#6f5ee0' : 'grey') : (torrent.peer ? '#9083e2' : '#d3d3d3'))}
rightIcon={
<div className='row inline' style={{width: 63}}>
{
!this.state.askDownloading && !this.state.downloading
?
<a href={`magnet:?xt=urn:btih:${torrent.hash}`}>
<svg style={{
height: '24px',
marginRight: 12,
fill: torrent.contentCategory != 'xxx' ? (torrent.peer ? '#5643db' : 'black') : (torrent.peer ? '#9083e2' : 'grey')
}} onClick={(e) => {
e.preventDefault();
e.stopPropagation();
this.setState({askDownloading: true})
window.torrentSocket.emit('download', torrent)
}} viewBox="0 0 56 56">
<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
<LinearProgress
mode="determinate"
value={torrentRating}
color={torrentRating >= 50 ? '#00E676' : '#FF3D00'}
style={{
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>
}
</div>
</a>
}
leftIcon={contentIcon(torrent.contentType, torrent.contentCategory, torrent.contentCategory != 'xxx' ? (torrent.peer ? '#6f5ee0' : 'grey') : (torrent.peer ? '#9083e2' : '#d3d3d3'))}
rightIcon={
<div className='row inline' style={{width: 63}}>
{
!this.state.askDownloading && !this.state.downloading
?
<a href={`magnet:?xt=urn:btih:${torrent.hash}`}>
<svg style={{
height: '24px',
marginRight: 12,
fill: torrent.contentCategory != 'xxx' ? (torrent.peer ? '#5643db' : 'black') : (torrent.peer ? '#9083e2' : 'grey')
}} onClick={(e) => {
e.preventDefault();
e.stopPropagation();
this.setState({askDownloading: true})
window.torrentSocket.emit('download', torrent)
}} viewBox="0 0 56 56">
<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
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
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.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
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
@ -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
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"/>
</g>
</g>
</svg>
</a>
:
this.state.askDownloading && !this.state.downloading
?
<img src={Spinner24} />
:
this.state.askDownloading && this.state.downloading
</svg>
</a>
:
this.state.askDownloading && !this.state.downloading
?
<img src={Spinner24} />
:
this.state.askDownloading && this.state.downloading
&&
<a href={`magnet:?xt=urn:btih:${torrent.hash}`}>
<svg style={{
height: '24px',
fill: torrent.contentCategory != 'xxx' ? (torrent.peer ? '#5643db' : 'black') : (torrent.peer ? '#9083e2' : 'grey')
}} onClick={(e) => {
e.preventDefault();
e.stopPropagation();
<svg style={{
height: '24px',
fill: torrent.contentCategory != 'xxx' ? (torrent.peer ? '#5643db' : 'black') : (torrent.peer ? '#9083e2' : 'grey')
}} onClick={(e) => {
e.preventDefault();
e.stopPropagation();
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>
</a>
}
<a style={{float: 'right'}} href={`magnet:?xt=urn:btih:${torrent.hash}`}>
<svg style={{
height: '24px',
fill: torrent.contentCategory != 'xxx' ? (torrent.peer ? '#5643db' : 'black') : (torrent.peer ? '#9083e2' : 'grey')
}} onClick={(e) => {
e.preventDefault();
e.stopPropagation();
var win = window.open(`magnet:?xt=urn:btih:${torrent.hash}`, '_self');
}} 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
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>
</a>
}
<a style={{float: 'right'}} href={`magnet:?xt=urn:btih:${torrent.hash}`}>
<svg style={{
height: '24px',
fill: torrent.contentCategory != 'xxx' ? (torrent.peer ? '#5643db' : 'black') : (torrent.peer ? '#9083e2' : 'grey')
}} onClick={(e) => {
e.preventDefault();
e.stopPropagation();
var win = window.open(`magnet:?xt=urn:btih:${torrent.hash}`, '_self');
}} 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
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.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
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>
</a>
</div>
}
/>
<Divider />
</div>
)
</a>
</div>
}
/>
<Divider />
</div>
)
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -8,28 +8,28 @@ let sw = false
const cpuTimer = setInterval(() => {
if(!sw) {
keepTime = process.hrtime();
keepUsage = process.cpuUsage();
sw = true;
keepUsage = process.cpuUsage();
sw = true;
} else {
startTime = keepTime;
startUsage = keepUsage;
sw = false;
}
}
}, 500)
cpuTimer.unref()
module.exports = () => {
function secNSec2ms (secNSec) {
return secNSec[0] * 1000 + secNSec[1] / 1000000
}
function secNSec2ms (secNSec) {
return secNSec[0] * 1000 + secNSec[1] / 1000000
}
var elapTime = process.hrtime(startTime)
var elapUsage = process.cpuUsage(startUsage)
var elapTime = process.hrtime(startTime)
var elapUsage = process.cpuUsage(startUsage)
var elapTimeMS = secNSec2ms(elapTime)
var elapUserMS = elapUsage.user
var elapSystMS = elapUsage.system
var elapTimeMS = secNSec2ms(elapTime)
var elapUserMS = elapUsage.user
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';
var PeerQueue = function (maxSize, perLimit) {
this.maxSize = maxSize || 200;
this.perLimit = perLimit || 10;
this.peers = {};
this.reqs = [];
this.maxSize = maxSize || 200;
this.perLimit = perLimit || 10;
this.peers = {};
this.reqs = [];
};
PeerQueue.prototype._shift = function () {
if (this.length() > 0) {
var req = this.reqs.shift();
this.peers[req.infohash.toString('hex')] = [];
return req;
}
if (this.length() > 0) {
var req = this.reqs.shift();
this.peers[req.infohash.toString('hex')] = [];
return req;
}
};
PeerQueue.prototype.push = function (peer) {
var infohashHex = peer.infohash.toString('hex');
var peers = this.peers[infohashHex];
var infohashHex = peer.infohash.toString('hex');
var peers = this.peers[infohashHex];
if (peers && peers.length < this.perLimit) {
peers.push(peer);
}
else if (this.length() < this.maxSize) {
this.reqs.push(peer);
}
if (peers && peers.length < this.perLimit) {
peers.push(peer);
}
else if (this.length() < this.maxSize) {
this.reqs.push(peer);
}
};
PeerQueue.prototype.shift = function (infohash, successful) {
if (infohash) {
var infohashHex = infohash.toString('hex');
if (successful === true) {
delete this.peers[infohashHex];
}
else {
var peers = this.peers[infohashHex];
if (peers) {
if (peers.length > 0) {
return peers.shift();
}
else {
delete this.peers[infohashHex];
}
}
}
}
return this._shift();
if (infohash) {
var infohashHex = infohash.toString('hex');
if (successful === true) {
delete this.peers[infohashHex];
}
else {
var peers = this.peers[infohashHex];
if (peers) {
if (peers.length > 0) {
return peers.shift();
}
else {
delete this.peers[infohashHex];
}
}
}
}
return this._shift();
};
PeerQueue.prototype.length = function () {
return this.reqs.length;
return this.reqs.length;
};
module.exports = PeerQueue;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,22 +1,22 @@
module.exports = class Feed {
constructor({sphinx})
{
this.feed = []
this.sphinx = sphinx
this.loaded = false
this.max = 1000
}
constructor({sphinx})
{
this.feed = []
this.sphinx = sphinx
this.loaded = false
this.max = 1000
}
size()
{
return this.feed.length
}
size()
{
return this.feed.length
}
async save() {
if(!this.loaded)
return // feed not loaded on begining, ignore saving
async save() {
if(!this.loaded)
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')
let id = 0
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 load() {
this.feed = await this.sphinx.query('select * from feed limit 1000')
if(this.feed && this.feed.length > 0)
this.feed = this.feed.map(f => JSON.parse(f.data))
else
this.feed = []
async load() {
this.feed = await this.sphinx.query('select * from feed limit 1000')
if(this.feed && this.feed.length > 0)
this.feed = this.feed.map(f => JSON.parse(f.data))
else
this.feed = []
this._order()
this.loaded = true
console.log('lodead feed')
}
this._order()
this.loaded = true
console.log('lodead feed')
}
clear()
{
console.log('clearing feed')
this.feed = []
}
clear()
{
console.log('clearing feed')
this.feed = []
}
add(data) {
let index = -1
if(data.hash)
index = this.feed.findIndex(element => element.hash === data.hash)
add(data) {
let index = -1
if(data.hash)
index = this.feed.findIndex(element => element.hash === data.hash)
if(index >= 0)
this.feed[index] = Object.assign(this.feed[index], data) // just push up element
else
{
if(typeof data == 'object')
{
data.feedDate = Math.floor(Date.now() / 1000)
}
if(index >= 0)
this.feed[index] = Object.assign(this.feed[index], data) // just push up element
else
{
if(typeof data == 'object')
{
data.feedDate = Math.floor(Date.now() / 1000)
}
if(this.feed.length >= this.max)
{
//cleanup
for(let i = this.feed.length - 1; i <= 0; i--)
if(this._compare(this.feed[i]) <= 0)
this.feed.pop()
else
break
if(this.feed.length >= this.max)
{
//cleanup
for(let i = this.feed.length - 1; i <= 0; i--)
if(this._compare(this.feed[i]) <= 0)
this.feed.pop()
else
break
if(this.feed.length >= this.max)
this.feed[this.feed.length - 1] = data // replace last one
else
this.feed.push(data) // insert
}
else
{
this.feed.push(data) // insert
}
}
if(this.feed.length >= this.max)
this.feed[this.feed.length - 1] = data // replace last one
else
this.feed.push(data) // insert
}
else
{
this.feed.push(data) // insert
}
}
this._order()
}
this._order()
}
_order() {
this.feed.sort((a, b) => this._compare(b) - this._compare(a))
}
_order() {
this.feed.sort((a, b) => this._compare(b) - this._compare(a))
}
_compare(x)
{
const rating = (x && x.good) || 0
const comments = 0
const time = Math.floor(Date.now() / 1000) - x.feedDate
_compare(x)
{
const rating = (x && x.good) || 0
const comments = 0
const time = Math.floor(Date.now() / 1000) - x.feedDate
const maxTime = 600000
if(time > maxTime)
time = maxTime
const relativeTime = (maxTime - time) / maxTime
return relativeTime * relativeTime + rating * 1.5 * relativeTime + comments * 4 * relativeTime
}
const maxTime = 600000
if(time > maxTime)
time = maxTime
const relativeTime = (maxTime - time) / maxTime
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) => {
const checker = (index = 0) => {
sphinx.query(`SELECT * FROM ${table} WHERE id > ${index} ${where} LIMIT ${max}`, (err, torrents) => {
const finish = () => {
if(err)
console.log('big table parse error', err)
if(doneCallback)
doneCallback(true)
done(true)
}
const checker = (index = 0) => {
sphinx.query(`SELECT * FROM ${table} WHERE id > ${index} ${where} LIMIT ${max}`, (err, torrents) => {
const finish = () => {
if(err)
console.log('big table parse error', err)
if(doneCallback)
doneCallback(true)
done(true)
}
if(!err && torrents.length > 0)
Promise.all(torrents.map(callback)).then(() => {
if(torrents.length === max)
checker(torrents[torrents.length - 1].id)
else
finish()
})
else
finish()
});
}
checker()
if(!err && torrents.length > 0)
Promise.all(torrents.map(callback)).then(() => {
if(torrents.length === max)
checker(torrents[torrents.length - 1].id)
else
finish()
})
else
finish()
});
}
checker()
})

View File

@ -7,78 +7,78 @@ import { app, BrowserWindow, screen } from "electron";
import jetpack from "fs-jetpack";
export default (name, options) => {
const userDataDir = jetpack.cwd(app.getPath("userData"));
const stateStoreFile = `window-state-${name}.json`;
const defaultSize = {
width: options.width,
height: options.height
};
let state = {};
let win;
const userDataDir = jetpack.cwd(app.getPath("userData"));
const stateStoreFile = `window-state-${name}.json`;
const defaultSize = {
width: options.width,
height: options.height
};
let state = {};
let win;
const restore = () => {
let restoredState = {};
try {
restoredState = userDataDir.read(stateStoreFile, "json");
} catch (err) {
// For some reason json can't be read (might be corrupted).
// No worries, we have defaults.
}
return Object.assign({}, defaultSize, restoredState);
};
const restore = () => {
let restoredState = {};
try {
restoredState = userDataDir.read(stateStoreFile, "json");
} catch (err) {
// For some reason json can't be read (might be corrupted).
// No worries, we have defaults.
}
return Object.assign({}, defaultSize, restoredState);
};
const getCurrentPosition = () => {
const position = win.getPosition();
const size = win.getSize();
return {
x: position[0],
y: position[1],
width: size[0],
height: size[1]
};
};
const getCurrentPosition = () => {
const position = win.getPosition();
const size = win.getSize();
return {
x: position[0],
y: position[1],
width: size[0],
height: size[1]
};
};
const windowWithinBounds = (windowState, bounds) => {
return (
windowState.x >= bounds.x &&
const windowWithinBounds = (windowState, bounds) => {
return (
windowState.x >= bounds.x &&
windowState.y >= bounds.y &&
windowState.x + windowState.width <= bounds.x + bounds.width &&
windowState.y + windowState.height <= bounds.y + bounds.height
);
};
);
};
const resetToDefaults = () => {
const bounds = screen.getPrimaryDisplay().bounds;
return Object.assign({}, defaultSize, {
x: (bounds.width - defaultSize.width) / 2,
y: (bounds.height - defaultSize.height) / 2
});
};
const resetToDefaults = () => {
const bounds = screen.getPrimaryDisplay().bounds;
return Object.assign({}, defaultSize, {
x: (bounds.width - defaultSize.width) / 2,
y: (bounds.height - defaultSize.height) / 2
});
};
const ensureVisibleOnSomeDisplay = windowState => {
const visible = screen.getAllDisplays().some(display => {
return windowWithinBounds(windowState, display.bounds);
});
if (!visible) {
// Window is partially or fully not visible now.
// Reset it to safe defaults.
return resetToDefaults();
}
return windowState;
};
const ensureVisibleOnSomeDisplay = windowState => {
const visible = screen.getAllDisplays().some(display => {
return windowWithinBounds(windowState, display.bounds);
});
if (!visible) {
// Window is partially or fully not visible now.
// Reset it to safe defaults.
return resetToDefaults();
}
return windowState;
};
const saveState = () => {
if (!win.isMinimized() && !win.isMaximized()) {
Object.assign(state, getCurrentPosition());
}
userDataDir.write(stateStoreFile, state, { atomic: true });
};
const saveState = () => {
if (!win.isMinimized() && !win.isMaximized()) {
Object.assign(state, getCurrentPosition());
}
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'
export const aboutMenuTemplateFunc = () => ({
label: __("About"),
submenu: [
{
label: __("Changelog"),
accelerator: "CmdOrCtrl+]",
click: () => {
const win = new BrowserWindow({
parent: BrowserWindow.getFocusedWindow(),
modal: true
})
win.setMenu(null)
win.loadURL(url.format({
pathname: path.join(__dirname, "app.html"),
protocol: "file:",
slashes: true
}))
win.webContents.on('did-finish-load', () => {
setTimeout(() => win.send('url', '/changelog'), 0)
});
label: __("About"),
submenu: [
{
label: __("Changelog"),
accelerator: "CmdOrCtrl+]",
click: () => {
const win = new BrowserWindow({
parent: BrowserWindow.getFocusedWindow(),
modal: true
})
win.setMenu(null)
win.loadURL(url.format({
pathname: path.join(__dirname, "app.html"),
protocol: "file:",
slashes: true
}))
win.webContents.on('did-finish-load', () => {
setTimeout(() => win.send('url', '/changelog'), 0)
});
const handleRedirect = (e, url) => {
if(url != win.webContents.getURL()) {
e.preventDefault()
shell.openExternal(url)
}
}
const handleRedirect = (e, url) => {
if(url != win.webContents.getURL()) {
e.preventDefault()
shell.openExternal(url)
}
}
win.webContents.on('will-navigate', handleRedirect)
win.webContents.on('new-window', handleRedirect)
},
},
{
label: __("Bug Report"),
accelerator: "CmdOrCtrl+[",
click: () => {
shell.openExternal('https://github.com/DEgITx/rats-search/issues')
},
},
{
label: __("Donate"),
accelerator: "CmdOrCtrl+*",
click: () => {
const win = new BrowserWindow({
parent: BrowserWindow.getFocusedWindow(),
modal: true,
width: 1000
})
win.setMenu(null)
win.loadURL(url.format({
pathname: path.join(__dirname, "donate.html"),
protocol: "file:",
slashes: true
}))
win.webContents.on('will-navigate', handleRedirect)
win.webContents.on('new-window', handleRedirect)
},
},
{
label: __("Bug Report"),
accelerator: "CmdOrCtrl+[",
click: () => {
shell.openExternal('https://github.com/DEgITx/rats-search/issues')
},
},
{
label: __("Donate"),
accelerator: "CmdOrCtrl+*",
click: () => {
const win = new BrowserWindow({
parent: BrowserWindow.getFocusedWindow(),
modal: true,
width: 1000
})
win.setMenu(null)
win.loadURL(url.format({
pathname: path.join(__dirname, "donate.html"),
protocol: "file:",
slashes: true
}))
const handleRedirect = (e, url) => {
if(url != win.webContents.getURL()) {
if(!url.includes('patreon'))
return
const handleRedirect = (e, url) => {
if(url != win.webContents.getURL()) {
if(!url.includes('patreon'))
return
e.preventDefault()
shell.openExternal(url)
}
}
e.preventDefault()
shell.openExternal(url)
}
}
win.webContents.on('will-navigate', handleRedirect)
win.webContents.on('new-window', handleRedirect)
},
},
{
label: __("Help (Documentation)"),
accelerator: "CmdOrCtrl+?",
click: () => {
shell.openExternal('https://github.com/DEgITx/rats-search/blob/master/docs/MANUAL.md')
},
},
{
label: __("Support (Discussion)"),
accelerator: "CmdOrCtrl+>",
click: () => {
shell.openExternal('https://discord.gg/t9GQtxA')
},
},
{
label: __("About (GitHub)"),
accelerator: "CmdOrCtrl+<",
click: () => {
shell.openExternal('https://github.com/DEgITx/rats-search')
},
}
]
win.webContents.on('will-navigate', handleRedirect)
win.webContents.on('new-window', handleRedirect)
},
},
{
label: __("Help (Documentation)"),
accelerator: "CmdOrCtrl+?",
click: () => {
shell.openExternal('https://github.com/DEgITx/rats-search/blob/master/docs/MANUAL.md')
},
},
{
label: __("Support (Discussion)"),
accelerator: "CmdOrCtrl+>",
click: () => {
shell.openExternal('https://discord.gg/t9GQtxA')
},
},
{
label: __("About (GitHub)"),
accelerator: "CmdOrCtrl+<",
click: () => {
shell.openExternal('https://github.com/DEgITx/rats-search')
},
}
]
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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