From 26fef90814e66163f5f4b4753a2cabfaff0ad8d9 Mon Sep 17 00:00:00 2001 From: PXgamer Date: Thu, 29 Jun 2017 13:17:08 +0100 Subject: [PATCH 01/20] Added Composer JSON --- composer.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 composer.json diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..17e118c --- /dev/null +++ b/composer.json @@ -0,0 +1,12 @@ +{ + "name": "johncave/phplinuxtrack", + "description": "A simple PHP torrent display mechanism designed to ease and beautify managing the torrent files of a linux distro or other software project.", + "minimum-stability": "stable", + "type": "project", + "license": "GPL-2.0", + "autoload": { + "psr-4": { + "johncave\\PhpLinuxTrack\\": "src/" + } + } +} \ No newline at end of file From 19f5dfd467b2b4aba84df2c059cb263563b31b57 Mon Sep 17 00:00:00 2001 From: PXgamer Date: Thu, 29 Jun 2017 13:37:05 +0100 Subject: [PATCH 02/20] Added requirement for phpdotenv package --- composer.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/composer.json b/composer.json index 17e118c..80f5010 100644 --- a/composer.json +++ b/composer.json @@ -8,5 +8,8 @@ "psr-4": { "johncave\\PhpLinuxTrack\\": "src/" } + }, + "require": { + "vlucas/phpdotenv": "2.4.*" } } \ No newline at end of file From 07c97938a424d6e4807c472cbb4587d68675e345 Mon Sep 17 00:00:00 2001 From: PXgamer Date: Thu, 29 Jun 2017 13:41:32 +0100 Subject: [PATCH 03/20] Added .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f3a8d1d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +vendor/ +config/.env +public/assets/css/styles.css \ No newline at end of file From 30c830c5070f98b93f947ba4141027f9d771fdb0 Mon Sep 17 00:00:00 2001 From: PXgamer Date: Thu, 29 Jun 2017 13:57:19 +0100 Subject: [PATCH 04/20] Added example .env config --- config/.env.example | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 config/.env.example diff --git a/config/.env.example b/config/.env.example new file mode 100644 index 0000000..e4c44b4 --- /dev/null +++ b/config/.env.example @@ -0,0 +1,11 @@ +PROJECT_TITLE = "OpenMandriva Project - BitTorrent Tracker" +LANGUAGE = "en" + +TORRENT_DIRECTORY = "torrents/" +TORRENTS_WEB_DIR = "/torrents/" + +REDIS_HOST = "127.0.0.1" +REDIS_PORT = 6379 + +CACHE_SCRAPE = 900 +CACHE_TABLE = 180 \ No newline at end of file From dcd969f99a31cc2611cd451bf0b1c843c84bcc05 Mon Sep 17 00:00:00 2001 From: PXgamer Date: Thu, 29 Jun 2017 14:10:38 +0100 Subject: [PATCH 05/20] Removed old config file, and moved language files to be JSON --- inc/config.php | 16 ---------------- inc/lang/en.php | 17 ----------------- inc/lang/es.php | 16 ---------------- resources/languages/en.json | 13 +++++++++++++ resources/languages/es.json | 13 +++++++++++++ 5 files changed, 26 insertions(+), 49 deletions(-) delete mode 100755 inc/config.php delete mode 100755 inc/lang/en.php delete mode 100755 inc/lang/es.php create mode 100644 resources/languages/en.json create mode 100644 resources/languages/es.json diff --git a/inc/config.php b/inc/config.php deleted file mode 100755 index 5e23bd4..0000000 --- a/inc/config.php +++ /dev/null @@ -1,16 +0,0 @@ - "OpenMandriva Project - BitTorrent Tracker.", - "lang" => "en", - "langsAvailable" => array("en", "es"), - "tordir" => "torrents/", - "torwebdir" => "/torrents/", - "redisHost" => "127.0.0.1", - "redisPort" => 6379, - "scrapeCache" => 900, #Cache the individual scrapes for this many seconds. - "tableCache" => 180, #Cache the entire table for this many seconds. - "highlightColour" => "#E2266E", - "linkColour" => "#005C9D" -); diff --git a/inc/lang/en.php b/inc/lang/en.php deleted file mode 100755 index 8a02827..0000000 --- a/inc/lang/en.php +++ /dev/null @@ -1,17 +0,0 @@ - "Name", - "headhtml" => "
Welcome to PHPlinuxTrack!
", - "seeders" => "Seeders", - "leechers" => "Downloaders", - "size" => "Size", - "complete" => "Times Completed", - "shared" => "Total Data Transferred", - "get" => "Get Torrent", - "getFile" => "Get Torrent File", - "getMagnet" => "Download by magnet link.", - "loading" => "Checking torrent status. This can take a moment." - -); diff --git a/inc/lang/es.php b/inc/lang/es.php deleted file mode 100755 index 99c81bb..0000000 --- a/inc/lang/es.php +++ /dev/null @@ -1,16 +0,0 @@ - "Nombre", - "headhtml" => "
", - "seeders" => "Subidias", - "leechers" => "Descargandos", - "size" => "Tamaño", - "complete" => "Veces Completado", - "shared" => "Datos Compartidos", - "get" => "Obtener Torrent", - "getFile" => "Obtener .torrent", - "getMagnet" => "Descarger por magnet link.", - "loading" => "Compubando el estado de los torrents. Puede tocar un momento." -); diff --git a/resources/languages/en.json b/resources/languages/en.json new file mode 100644 index 0000000..da6c4f0 --- /dev/null +++ b/resources/languages/en.json @@ -0,0 +1,13 @@ +{ + "name": "Name", + "heading": "
Welcome to PHPlinuxTrack!<\/center>", + "seeders": "Seeders", + "leechers": "Downloaders", + "size": "Size", + "complete": "Times Completed", + "shared": "Total Data Transferred", + "get": "Get Torrent", + "getFile": "Get Torrent File", + "getMagnet": "Download by magnet link.", + "loading": "Checking torrent status. This can take a moment." +} \ No newline at end of file diff --git a/resources/languages/es.json b/resources/languages/es.json new file mode 100644 index 0000000..615467d --- /dev/null +++ b/resources/languages/es.json @@ -0,0 +1,13 @@ +{ + "name": "Nombre", + "heading": "
<\/center>", + "seeders": "Subidias", + "leechers": "Descargandos", + "size": "Tamaño", + "complete": "Veces Completado", + "shared": "Datos Compartidos", + "get": "Obtener Torrent", + "getFile": "Obtener .torrent", + "getMagnet": "Descarger por magnet link.", + "loading": "Compubando el estado de los torrents. Puede tocar un momento." +} \ No newline at end of file From 4709a490effb38cd28dc24a7f04613d1ce42a249 Mon Sep 17 00:00:00 2001 From: PXgamer Date: Thu, 29 Jun 2017 14:53:56 +0100 Subject: [PATCH 06/20] Added additional required packages --- composer.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 80f5010..7ae5028 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,8 @@ } }, "require": { - "vlucas/phpdotenv": "2.4.*" + "vlucas/phpdotenv": "2.4.*", + "coldwinds/torrent-rw": "^1.1", + "johannes85/php-torrent-scraper": "1.0.*" } -} \ No newline at end of file +} From e7c6e53372bd60709f7f3a25744a1d7d1a7e705a Mon Sep 17 00:00:00 2001 From: PXgamer Date: Thu, 29 Jun 2017 15:35:22 +0100 Subject: [PATCH 07/20] Added comments to the config --- config/.env.example | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/config/.env.example b/config/.env.example index e4c44b4..837d037 100644 --- a/config/.env.example +++ b/config/.env.example @@ -1,11 +1,15 @@ +# Project Configuration PROJECT_TITLE = "OpenMandriva Project - BitTorrent Tracker" LANGUAGE = "en" -TORRENT_DIRECTORY = "torrents/" -TORRENTS_WEB_DIR = "/torrents/" +# Torrent File Configuration +TORRENT_DIRECTORY = "torrents/" # Location of the torrents directory +TORRENTS_WEB_DIR = "/torrents/" # Location of the torrents dir, for use in links -REDIS_HOST = "127.0.0.1" -REDIS_PORT = 6379 +# Redis Configuration +REDIS_HOST = "127.0.0.1" # The IP of your Redis instance +REDIS_PORT = 6379 # The port that Redis is running on +# Caching Configuration CACHE_SCRAPE = 900 -CACHE_TABLE = 180 \ No newline at end of file +CACHE_TABLE = 180 # Time to keep the torrent table cached in Redis \ No newline at end of file From 85daeefbb243073cc3e375a5f9ed3337f2e0a345 Mon Sep 17 00:00:00 2001 From: PXgamer Date: Thu, 29 Jun 2017 15:35:42 +0100 Subject: [PATCH 08/20] Removed inc/folder --- inc/bdecode.old.php | 229 ---------- inc/func.php | 144 ------ inc/scrape/httptscraper.php | 96 ---- inc/scrape/lightbenc.php | 163 ------- inc/scrape/torrent.old.php | 815 ---------------------------------- inc/scrape/torrent.php | 860 ------------------------------------ inc/scrape/tscraper.php | 39 -- inc/scrape/udptscraper.php | 95 ---- 8 files changed, 2441 deletions(-) delete mode 100755 inc/bdecode.old.php delete mode 100755 inc/func.php delete mode 100755 inc/scrape/httptscraper.php delete mode 100755 inc/scrape/lightbenc.php delete mode 100755 inc/scrape/torrent.old.php delete mode 100755 inc/scrape/torrent.php delete mode 100755 inc/scrape/tscraper.php delete mode 100755 inc/scrape/udptscraper.php diff --git a/inc/bdecode.old.php b/inc/bdecode.old.php deleted file mode 100755 index c54531b..0000000 --- a/inc/bdecode.old.php +++ /dev/null @@ -1,229 +0,0 @@ -result['my_value_name'] - * - * -- Here is a list of some of the most used properties: - * $torrent->result['announce'] // string - * $torrent->result['announce-list'] // array - * $torrent->result['comment'] // string - * $torrent->result['created by'] // string - * $torrent->result['creation date'] // unix timestamp - * $torrent->result['encoding'] // string - * $torrent->result['info']['files'] // array - * $torrent->result['info']['files'][?]['length'] // integer - * $torrent->result['info']['files'][?]['path'] // string - * $torrent->result['info']['name'] // string - * $torrent->result['info']['piece length'] // integer - * $torrent->result['info']['pieces'] // string - * $torrent->result['info']['private'] // integer - * $torrent->result['modified-by'] // array - * - * See http://wiki.theory.org/BitTorrentSpecification for bittorrent specification - */ - - final class BDecode { - private $content; // string containing contents of file - private $pointer = 0; // current position pointer in content - public $result = array(); // result array containing all decoded elements - - - /************************************************************************** - * Info: Parses bencoded file into array. - * Args: {string} filepath: full or relative path to bencoded file - **************************************************************************/ - function __construct($filepath) { - $this->content = @file_get_contents($filepath); - - if (!$this->content) { - $this->throwException('File does not exist!'); - } else { - if (!isset($this->content)) { - $this->throwException('Error opening file!'); - } else { - $this->result = $this->processElement(); - } - } - unset($this->content); - } - - - /************************************************************************** - * Info: Clear class variables. - * Args: none - **************************************************************************/ - function __destruct() { - unset($this->content); - unset($this->result); - } - - - /************************************************************************** - * Info: Terminates decoding process and returns error. - * Args: {string} error [optional] - error description - **************************************************************************/ - private function throwException($error = 'error parsing file') { - $this->result = array(); - $this->result['error'] = $error; - } - - /************************************************************************** - * Info: Processes element depending on its type. - * Results in error if no valid identifier is found. - * Args: none - **************************************************************************/ - private function processElement() { - switch($this->content[$this->pointer]) { - case 'd': - return $this->processDictionary(); - break; - case 'l': - return $this->processList(); - break; - case 'i': - return $this->processInteger(); - break; - default: - if (is_numeric($this->content[$this->pointer])) { - return $this->processString(); - } else { - $this->throwException('Unknown BEncode element'); - } - break; - } - } - - /************************************************************************** - * Info: Processes dictionary entries. - * Returns array of dictionary entries. - * Args: none - **************************************************************************/ - private function processDictionary() { - if (!$this->isOfType('d')) - $this->throwException(); - - $res = array(); - $this->pointer++; - - while (!$this->isOfType('e')) { - $elemkey = $this->processString(); - - switch($this->content[$this->pointer]) { - case 'd': - $res[$elemkey] = $this->processDictionary(); - break; - case 'l': - $res[$elemkey] = $this->processList(); - break; - case 'i': - $res[$elemkey] = $this->processInteger(); - break; - default: - if (is_numeric($this->content[$this->pointer])) { - $res[$elemkey] = $this->processString(); - } else { - $this->throwException('Unknown BEncode element!'); - } - break; - } - } - - $this->pointer++; - return $res; - } - - /************************************************************************** - * Info: Processes list entries. - * Returns array of list entries found between 'l' and 'e' identifiers. - * Args: none - **************************************************************************/ - private function processList() { - if (!$this->isOfType('l')) - $this->throwException(); - - $res = array(); - $this->pointer++; - - while (!$this->isOfType('e')) - $res[] = $this->processElement(); - - $this->pointer++; - return $res; - } - - /************************************************************************** - * Info: Processes integer value. - * Returns integer value found between 'i' and 'e' identifiers. - * Args: none - **************************************************************************/ - private function processInteger() { - if (!$this->isOfType('e')) - $this->throwException(); - - $this->pointer++; - - $delim_pos = strpos($this->content, 'e', $this->pointer); - $integer = substr($this->content, $this->pointer, $delim_pos - $this->pointer); - if (($integer == '-0') || ((substr($integer, 0, 1) == '0') && (strlen($integer) > 1))) - $this->throwException(); - - $integer = abs(intval($integer)); - $this->pointer = $delim_pos + 1; - return $integer; - } - - /************************************************************************** - * Info: Processes string value. - * Returns string value found after '%:' identifier, where '%' is any - * valid integer. - * Args: none - **************************************************************************/ - private function processString() { - if (!is_numeric($this->content[$this->pointer])) { - $this->throwException(); - } - - - $delim_pos = strpos($this->content, ':', $this->pointer); - $elem_len = intval(substr($this->content, $this->pointer, $delim_pos - $this->pointer)); - $this->pointer = $delim_pos + 1; - - $elem_name = substr($this->content, $this->pointer, $elem_len); - - $this->pointer += $elem_len; - return $elem_name; - } - - /************************************************************************** - * Info: Checks if identifier at current pointer is of supplied type. - * Args: {char} type - character denoting required type. - * Usually one of [d,l,i,e]. - **************************************************************************/ - private function isOfType($type) { - return ($this->content[$this->pointer] == $type); - } - } -?> \ No newline at end of file diff --git a/inc/func.php b/inc/func.php deleted file mode 100755 index e7c9970..0000000 --- a/inc/func.php +++ /dev/null @@ -1,144 +0,0 @@ - connect($CONFIG['redisHost'], $CONFIG['redisPort']); -$offlineTrackers = 0; - -# Define custom functions below # - -function printCSS(){ -global $CONFIG; -#Enter your own CSS here to customise the page. -print <<= 0) && ($bytes < $kilobyte)) { - return $bytes . ' B'; - - } elseif (($bytes >= $kilobyte) && ($bytes < $megabyte)) { - return round($bytes / $kilobyte, $precision) . ' KB'; - - } elseif (($bytes >= $megabyte) && ($bytes < $gigabyte)) { - return round($bytes / $megabyte, $precision) . ' MB'; - - } elseif (($bytes >= $gigabyte) && ($bytes < $terabyte)) { - return round($bytes / $gigabyte, $precision) . ' GB'; - - } elseif ($bytes >= $terabyte) { - return round($bytes / $terabyte, $precision) . ' TB'; - } else { - return $bytes . ' B'; - } -} - - - - -function scrapeTorrent($url,$hash){ - global $redis, $offlineTrackers, $CONFIG; - if($res = $redis->get("plts".$hash)){ - return(json_decode($res, true)); - } else{ - if(is_array($url)){ - $url = $url[0][0]; - } - try{ - $timeout = 2; - #$url = preg_replace("/announce", "", $url); - $url = str_replace("/announce", "", $url); - $scraper = new udptscraper($timeout); - $ret = $scraper->scrape($url,array($hash)); - $redis -> set("plts".$hash, json_encode($ret)); - $redis -> expire ("plts".$hash, $CONFIG['scrapeCache']); - #print "
Scrape results for ".$url." were: ".json_encode($ret); - return($ret); - } - catch(ScraperException $e){ - $ret=array( $hash => array( - "seeders" => "?", - "leechers" => "?", - "completed" => 0 - )); - $redis -> set("plts".$hash, json_encode($ret)); - $redis -> expire ("plts".$hash, $CONFIG['scrapeCache']*3); #The caching length of trackers that seem down is increased to help them to recover if they're overloaded and to increase page load speed (timeouts are expensive to load speed. - $offlineTrackers++; - return $ret; - } - } - -} diff --git a/inc/scrape/httptscraper.php b/inc/scrape/httptscraper.php deleted file mode 100755 index 37dddac..0000000 --- a/inc/scrape/httptscraper.php +++ /dev/null @@ -1,96 +0,0 @@ -scrape('http://tracker.tld:port/announce',array('0000000000000000000000000000000000000000')); - - print_r($ret); - }catch(ScraperException $e){ - echo('Error: ' . $e->getMessage() . "
\n"); - echo('Connection error: ' . ($e->isConnectionError() ? 'yes' : 'no') . "
\n"); - } - */ - - require_once(dirname(__FILE__) . '/tscraper.php'); - require_once(dirname(__FILE__) . '/lightbenc.php'); - - class httptscraper extends tscraper{ - protected $maxreadsize; - - public function __construct($timeout=2,$maxreadsize=4096){ - $this->maxreadsize = $maxreadsize; - parent::__construct($timeout); - } - - /* $url: Tracker url like: http://tracker.tld:port/announce or http://tracker.tld:port/scrape - $infohash: Infohash string or array. 40 char long infohash. - */ - public function scrape($url,$infohash){ - if(!is_array($infohash)){ $infohash = array($infohash); } - foreach($infohash as $hash){ - if(!preg_match('#^[a-f0-9]{40}$#i',$hash)){ throw new ScraperException('Invalid infohash: ' . $hash . '.'); } - } - $url = trim($url); - if (preg_match('%(http://.*?/)announce([^/]*)$%i', $url, $m)){ - $url = $m[1] . 'scrape' . $m[2]; - }else if (preg_match('%(http://.*?/)scrape([^/]*)$%i', $url, $m)){ - }else{ - throw new ScraperException('Invalid tracker url.'); - } - - $sep = preg_match ('/\?.{1,}?/i', $url) ? '&' : '?'; - $requesturl = $url; - foreach($infohash as $hash){ - $requesturl .= $sep . 'info_hash=' . rawurlencode(pack('H*', $hash)); - $sep = '&'; - } - - ini_set('default_socket_timeout',$this->timeout); - $rh = @fopen($requesturl,'r'); - if(!$rh){ throw new ScraperException('Could not open HTTP connection.',0,true); } - stream_set_timeout($rh, $this->timeout); - - $return = ''; - $pos = 0; - while (!feof($rh) && $pos < $this->maxreadsize){ - $return .= fread($rh,1024); - } - fclose($rh); - - if(!substr($return, 0, 1) == 'd'){ throw new ScraperException('Invalid scrape response.'); } - $arr_scrape_data = lightbenc::bdecode($return); - - $torrents = array(); - foreach($infohash as $hash){ - $ehash = pack('H*', $hash); - if (isset($arr_scrape_data['files'][$ehash])){ - $torrents[$hash] = array( 'infohash'=>$hash, - 'seeders'=>(int) $arr_scrape_data['files'][$ehash]['complete'], - 'completed'=>(int) $arr_scrape_data['files'][$ehash]['downloaded'], - 'leechers'=>(int) $arr_scrape_data['files'][$ehash]['incomplete'] - ); - }else{ - $torrents[$hash] = false; - } - } - - return($torrents); - } - } -?> \ No newline at end of file diff --git a/inc/scrape/lightbenc.php b/inc/scrape/lightbenc.php deleted file mode 100755 index 8464e6d..0000000 --- a/inc/scrape/lightbenc.php +++ /dev/null @@ -1,163 +0,0 @@ - - array(1) { - [0]=> - string(8) "a string" - } - ["oneInteger"]=> - int(34) - ["isDct"]=> - bool(true) -} - -The returned value is a nested data type with the following type of elements: - - ints (test type with is_integer($x)) - - strings (test type with is_string($x)) - - lists (test type with is_array($x) && !isset($x[isDct]) - - dicts (test type with is_array($x) && isset($x[isDct]) - -All elements have the native PHP type, except for the dictionary which is an array with an "isDct" key. -This is necessary since PHP makes no distinction between flat and associative arrays. Note that the isDct -key is allways set as a bool, so that even if the dictionary contains an actual "isDct" value, the -functions behave transparently, i.e. they don't strip out or overwrite actual "isDct" keys. - -As such, this implementation is not a drop-in replacement of the TBDev code, hence the new function names -For all practical purposes, it's just as flexible, and very easy to use. For example: - -// decode the torrent file -$dict= bdecode_file($torrentfilename); -// change announce url -$dict['announce']='http://inferno.demonoid.com'; -// add private tracker flag -$dict['info']['private']=1; -// compute infohash -$infohash = pack("H*", sha1(bencode($dict["info"]))); -// recreate the torrent file -$torrentfile=bencode($dict); - -After calling bencode(), the passed nested array will have all it's dictionaries sorted by key. -The bencoded data generated by bencode() will have sorted dictionaries, but bdecode() does not require -this in the input stream, and will keep the order unchanged. - -This implementation is hereby released under the GFYPL, version 1.00. - - - The Go Fuck Yourself Public License, version 1.00 - - Article 1 - You can go fuck yourself. - - END OF ALL TERMS AND CONDITIONS - -*/ -class lightbenc { - function bdecode($s, &$pos=0) { - if($pos>=strlen($s)) { - return null; - } - switch($s[$pos]){ - case 'd': - $pos++; - $retval=array(); - while ($s[$pos]!='e'){ - $key=self::bdecode($s, $pos); - $val=self::bdecode($s, $pos); - if ($key===null || $val===null) - break; - $retval[$key]=$val; - } - $retval["isDct"]=true; - $pos++; - return $retval; - - case 'l': - $pos++; - $retval=array(); - while ($s[$pos]!='e'){ - $val=self::bdecode($s, $pos); - if ($val===null) - break; - $retval[]=$val; - } - $pos++; - return $retval; - - case 'i': - $pos++; - $digits=strpos($s, 'e', $pos)-$pos; - $val=(int)substr($s, $pos, $digits); - $pos+=$digits+1; - return $val; - - // case "0": case "1": case "2": case "3": case "4": - // case "5": case "6": case "7": case "8": case "9": - default: - $digits=strpos($s, ':', $pos)-$pos; - if ($digits<0 || $digits >20) - return null; - $len=(int)substr($s, $pos, $digits); - $pos+=$digits+1; - $str=substr($s, $pos, $len); - $pos+=$len; - //echo "pos: $pos str: [$str] len: $len digits: $digits\n"; - return (string)$str; - } - return null; - } - - function bencode(&$d){ - if(is_array($d)){ - $ret="l"; - if($d["isDct"]){ - $isDict=1; - $ret="d"; - // this is required by the specs, and BitTornado actualy chokes on unsorted dictionaries - ksort($d, SORT_STRING); - } - foreach($d as $key=>$value) { - if($isDict){ - // skip the isDct element, only if it's set by us - if($key=="isDct" and is_bool($value)) continue; - $ret.=strlen($key).":".$key; - } - if (is_string($value)) { - $ret.=strlen($value).":".$value; - } elseif (is_int($value)){ - $ret.="i${value}e"; - } else { - $ret.=self::bencode ($value); - } - } - return $ret."e"; - } elseif (is_string($d)) // fallback if we're given a single bencoded string or int - return strlen($d).":".$d; - elseif (is_int($d)) - return "i${d}e"; - else - return null; - } - - function bdecode_file($filename){ - $f=file_get_contents($filename, FILE_BINARY); - return bdecode($f); - } -} -?> diff --git a/inc/scrape/torrent.old.php b/inc/scrape/torrent.old.php deleted file mode 100755 index 12a3b66..0000000 --- a/inc/scrape/torrent.old.php +++ /dev/null @@ -1,815 +0,0 @@ - -require_once 'Torrent.php'; - -// create torrent -$torrent = new Torrent( './path-to-file-or-folder', 'http://torrent.tracker/annonce' ); -if ( ! $error = $torrent->error() ) // error method return the last error message - $torrent->save('test.torrent'); // save to disk -else - echo '
DEBUG: ',$error; - -// print torrent infos -$torrent = new Torrent( './test.torrent' ); -echo '
private: ', $torrent->is_private() ? 'yes' : 'no',
-	 '
annonce: '; -var_dump( $torrent->announce() ); -echo '
name: ', $torrent->name(), - '
comment: ', $torrent->comment(), - '
piece_length: ', $torrent->piece_length(), - '
size: ', $torrent->size( 2 ), - '
hash info: ', $torrent->hash_info(), - '
stats: '; -var_dump( $torrent->scrape() ); -echo '
content: '; -var_dump( $torrent->content() ); -echo '
source: ', - $torrent; - -// modify torrent -$torrent->announce('http://alternate-torrent.tracker/annonce'); // add a tracker -$torrent->announce(false); // reset announce trackers -$torrent->announce(array('http://torrent.tracker/annonce', 'http://alternate-torrent.tracker/annonce')); // set tracker(s), it also works with a 'one tracker' array... -$torrent->announce(array(array('http://torrent.tracker/annonce', 'http://alternate-torrent.tracker/annonce'), 'http://another-torrent.tracker/annonce')); // set tiered trackers -$torrent->comment('hello world'); -$torrent->name('test torrent'); -$torrent->is_private(true); -$torrent->httpseeds('http://file-hosting.domain/path/'); // Bittornado implementation -$torrent->url_list(array('http://file-hosting.domain/path/','http://another-file-hosting.domain/path/')); // GetRight implementation - -// print errors -if ( $errors = $torrent->errors() ) // errors method return the error stack - var_dump( '
DEBUG: ', $errors ); - -// send to user -//$torrent->send(); - * - * - * @author Adrien Gibrat - * @tester Jeong, official tester ;) Thanks for your precious feedback - * @copyleft 2010 - Just use it! - * @license http://www.gnu.org/licenses/gpl.html GNU General Public License version 3 - * @version Release: 1.2 (6 july 2010) - */ -class Torrent { - - /** - * @const float Default http timeout - */ - const timeout = 30; - - /** - * @var array List of error occured - */ - static protected $errors = array(); - - /** Read and decode torrent file/data OR build a torrent from source folder/file(s) - * Supported signatures: - * - Torrent(); // get an instance (usefull to scrape and check errors) - * - Torrent( string $torrent ); // analyse a torrent file - * - Torrent( string $torrent, string $announce ); - * - Torrent( string $torrent, array $meta ); - * - Torrent( string $file_or_folder ); // create a torrent file - * - Torrent( string $file_or_folder, string $announce_url, [int $piece_length] ); - * - Torrent( string $file_or_folder, array $meta, [int $piece_length] ); - * - Torrent( array $files_list ); - * - Torrent( array $files_list, string $announce_url, [int $piece_length] ); - * - Torrent( array $files_list, array $meta, [int $piece_length] ); - * @param string|array torrent to read or source folder/file(s) (optional, to get an instance) - * @param string|array announce url or meta informations (optional) - * @param int piece length (optional) - */ - public function __construct ( $data = null, $meta = array(), $piece_length = 256 ) { - if ( is_null( $data ) ) - return false; - if ( $piece_length < 32 || $piece_length > 4096 ) - return self::set_error( new Exception( 'Invalid piece lenth, must be between 32 and 4096' ) ); - if ( is_string( $meta ) ) - $meta = array( 'announce' => $meta ); - if ( $this->build( $data, $piece_length * 1024 ) ) - $this->touch(); - else - $meta = array_merge( $meta, $this->decode( $data ) ); - foreach( $meta as $key => $value ) - $this->{$key} = $value; - } - - /** Convert the current Torrent instance in torrent format - * @return string encoded torrent data - */ - public function __toString() { - return $this->encode( $this ); - } - - /** Return last error message - * @return string|boolean last error message or false if none - */ - public function error() { - return empty( self::$errors ) ? - false : - self::$errors[0]->getMessage(); - } - - /** Return Errors - * @return array|boolean error list or false if none - */ - public function errors() { - return empty( self::$errors ) ? - false : - self::$errors; - } - - /**** Getters and setters ****/ - - /** Getter and setter of torrent announce url / list - * If the argument is a string, announce url is added to announce list (or set as announce if announce is not set) - * If the argument is an array/object, set announce url (with first url) and list (if array has more than one url), tiered list supported - * If the argument is false announce url & list are unset - * @param null|false|string|array announce url / list, reset all if false (optional, if omitted it's a getter) - * @return string|array|null announce url / list or null if not set - */ - public function announce ( $announce = null ) { - if ( is_null( $announce ) ) - return ! isset( $this->{'announce-list'} ) ? - isset( $this->announce ) ? $this->announce : null : - $this->{'announce-list'}; - $this->touch(); - if ( is_string( $announce ) && isset( $this->announce ) ) - return $this->{'announce-list'} = self::announce_list( isset( $this->{'announce-list'} ) ? $this->{'announce-list'} : $this->announce, $announce ); - unset( $this->{'announce-list'} ); - if ( is_array( $announce ) || is_object( $announce ) ) - if ( ( $this->announce = self::first_announce( $announce ) ) && count( $announce ) > 1 ) - return $this->{'announce-list'} = self::announce_list( $announce ); - else - return $this->announce; - if ( ! isset( $this->announce ) && $announce ) - return $this->announce = (string) $announce; - unset( $this->announce ); - } - - /** Getter and setter of torrent comment - * @param null|string comment (optional, if omitted it's a getter) - * @return string|null comment or null if not set - */ - public function comment ( $comment = null ) { - return is_null( $comment ) ? - isset( $this->comment ) ? $this->comment : null : - $this->touch( $this->comment = (string) $comment ); - } - - /** Getter and setter of torrent name - * @param null|string name (optional, if omitted it's a getter) - * @return string|null name or null if not set - */ - public function name ( $name = null ) { - return is_null( $name ) ? - isset( $this->info['name'] ) ? $this->info['name'] : null : - $this->touch( $this->info['name'] = (string) $name ); - } - - /** Getter and setter of private flag - * @param null|boolean is private or not (optional, if omitted it's a getter) - * @return boolean private flag - */ - public function is_private ( $private = null ) { - return is_null( $private ) ? - ! empty( $this->info['private'] ) : - $this->touch( $this->info['private'] = $private ? 1 : 0 ); - } - - /** Getter and setter of webseed(s) url list ( GetRight implementation ) - * @param null|string|array webseed or webseeds mirror list (optional, if omitted it's a getter) - * @return string|array|null webseed(s) or null if not set - */ - public function url_list ( $urls = null ) { - return is_null( $urls ) ? - isset( $this->{'url-list'} ) ? $this->{'url-list'} : null : - $this->touch( $this->{'url-list'} = is_string( $urls) ? $urls : (array) $urls ); - } - - /** Getter and setter of httpseed(s) url list ( Bittornado implementation ) - * @param null|string|array httpseed or httpseeds mirror list (optional, if omitted it's a getter) - * @return array|null httpseed(s) or null if not set - */ - public function httpseeds ( $urls = null ) { - return is_null( $urls ) ? - isset( $this->httpseeds ) ? $this->httpseeds : null : - $this->touch( $this->httpseeds = (array) $urls ); - } - - /**** Analyze BitTorrent ****/ - - /** Get piece length - * @return integer piece length or null if not set - */ - public function piece_length () { - return isset( $this->info['piece length'] ) ? - $this->info['piece length'] : - null; - } - - /** Compute hash info - * @return string hash info or null if info not set - */ - public function hash_info () { - return isset( $this->info ) ? - sha1( self::encode( $this->info ) ) : - null; - } - - /** List torrent content - * @param integer|null size precision (optional, if omitted returns sizes in bytes) - * @return array file(s) and size(s) list, files as keys and sizes as values - */ - public function content ( $precision = null ) { - $files = array(); - if ( isset( $this->info['files'] ) && is_array( $this->info['files'] ) ) - foreach ( $this->info['files'] as $file ) - $files[self::path( $file['path'], $this->info['name'] )] = $precision ? - self::format( $file['length'], $precision ) : - $file['length']; - elseif ( isset( $this->info['name'] ) ) - $files[$this->info['name']] = $precision ? - self::format( $this->info['length'], $precision ) : - $this->info['length']; - return $files; - } - - /** List torrent content pieces and offset(s) - * @return array file(s) and pieces/offset(s) list, file(s) as keys and pieces/offset(s) as values - */ - public function offset () { - $files = array(); - $size = 0; - if ( isset( $this->info['files'] ) && is_array( $this->info['files'] ) ) - foreach ( $this->info['files'] as $file ) - $files[self::path( $file['path'], $this->info['name'] )] = array( - 'startpiece' => floor( $size / $this->info['piece length'] ), - 'offset' => fmod( $size, $this->info['piece length'] ), - 'size' => $size += $file['length'], - 'endpiece' => floor( $size / $this->info['piece length'] ) - ); - elseif ( isset( $this->info['name'] ) ) - $files[$this->info['name']] = array( - 'startpiece' => 0, - 'offset' => 0, - 'size' => $this->info['length'], - 'endpiece' => floor( $this->info['length'] / $this->info['piece length'] ) - ); - return $files; - } - - /** Sum torrent content size - * @param integer|null size precision (optional, if omitted returns size in bytes) - * @return integer|string file(s) size - */ - public function size ( $precision = null ) { - $size = 0; - if ( isset( $this->info['files'] ) && is_array( $this->info['files'] ) ) - foreach ( $this->info['files'] as $file ) - $size += $file['length']; - elseif ( isset( $this->info['name'] ) ) - $size = $this->info['length']; - return is_null( $precision ) ? - $size : - self::format( $size, $precision ); - } - - /** Request torrent statistics from scrape page USING CURL!! - * @param string|array announce or scrape page url (optional, to request an alternative tracker BUT requirered for static call) - * @param string torrent hash info (optional, requirered ONLY for static call) - * @param float read timeout in seconds (optional, default to self::timeout 30s) - * @return array tracker torrent statistics - */ - /* static */ public function scrape ( $announce = null, $hash_info = null, $timeout = self::timeout ) { - $packed_hash = urlencode( pack('H*', $hash_info ? $hash_info : $this->hash_info() ) ); - $handles = $scrape = array(); - if ( ! function_exists( 'curl_multi_init' ) ) - return self::set_error( new Exception( 'Install CURL with "curl_multi_init" enabled' ) ); - $curl = curl_multi_init(); - foreach ( (array) ($announce ? $announce : $this->announce()) as $tier ) - foreach ( (array) $tier as $tracker ) { - $tracker = str_ireplace( array( 'udp://', '/announce', ':80/' ), array( 'http://', '/scrape', '/' ), $tracker ); - if ( isset( $handles[$tracker] ) ) - continue; - $handles[$tracker] = curl_init( $tracker . '?info_hash=' . $packed_hash ); - curl_setopt( $handles[$tracker], CURLOPT_RETURNTRANSFER, true ); - curl_setopt( $handles[$tracker], CURLOPT_TIMEOUT, $timeout ); - curl_multi_add_handle( $curl, $handles[$tracker] ); - } - do { - while ( ( $state = curl_multi_exec( $curl, $running ) ) == CURLM_CALL_MULTI_PERFORM ); - if( $state != CURLM_OK ) - continue; - while ( $done = curl_multi_info_read( $curl ) ) { - $info = curl_getinfo( $done['handle'] ); - if ( empty( $info['http_code'] ) ) { - $scrape[$tracker] = self::set_error( new Exception( 'Tracker request timeout (' . $timeout . 's)' ), true ); - continue; - } elseif ( $info['http_code'] != 200 ) { - $scrape[$tracker] = self::set_error( new Exception( 'Tracker request failed (' . $info['http_code'] . ' code)' ), true ); - continue; - } - $stats = self::decode_data( curl_multi_getcontent( $done['handle'] ) ); - curl_multi_remove_handle( $curl, $done['handle'] ); - $scrape[$tracker] = empty( $stats['files'] ) ? - self::set_error( new Exception( 'Empty scrape data' ), true ) : - array_shift( $stats['files'] ) + ( empty( $stats['flags'] ) ? array() : $stats['flags'] ); - } - } while ( $running ); - curl_multi_close( $curl ); - return $scrape; - } - - /**** Save and Send ****/ - - /** Save torrent file to disk - * @param null|string name of the file (optional) - * @return boolean file has been saved or not - */ - public function save ( $filename = null ) { - return file_put_contents( is_null( $filename ) ? $this->info['name'] . '.torrent' : $filename, $this->encode( $this ) ); - } - - /** Send torrent file to client - * @param null|string name of the file (optional) - * @return void script exit - */ - public function send ( $filename = null ) { - $data = $this->encode( $this ); - header( 'Content-type: application/x-bittorrent' ); - header( 'Content-Length: ' . strlen( $data ) ); - header( 'Content-Disposition: attachment; filename="' . ( is_null( $filename ) ? $this->info['name'] . '.torrent' : $filename ) . '"' ); - exit( $data ); - } - - /**** Encode BitTorrent ****/ - - /** Encode torrent data - * @param mixed data to encode - * @return string torrent encoded data - */ - static public function encode ( $mixed ) { - switch ( gettype( $mixed ) ) { - case 'integer': - case 'double': - return self::encode_integer( $mixed ); - case 'object': - $mixed = (array) $mixed; //Thanks to W-Shadow: http://w-shadow.com/blog/2008/11/11/parse-edit-and-create-torrent-files-with-php/ - case 'array': - return self::encode_array( $mixed ); - default: - return self::encode_string( (string) $mixed ); - } - } - - /** Encode torrent string - * @param string string to encode - * @return string encoded string - */ - static private function encode_string ( $string ) { - return strlen( $string ) . ':' . $string; - } - - /** Encode torrent integer - * @param integer integer to encode - * @return string encoded integer - */ - static private function encode_integer ( $integer ) { - return 'i' . $integer . 'e'; - } - - /** Encode torrent dictionary or list - * @param array array to encode - * @return string encoded dictionary or list - */ - static private function encode_array ( $array ) { - if ( self::is_list( $array ) ) { - $return = 'l'; - foreach ( $array as $value ) - $return .= self::encode( $value ); - } else { - ksort( $array, SORT_STRING ); - $return = 'd'; - foreach ( $array as $key => $value ) - $return .= self::encode( strval( $key ) ) . self::encode( $value ); - } - return $return . 'e'; - } - - /**** Decode BitTorrent ****/ - - /** Decode torrent data or file - * @param string data or file path to decode - * @return array decoded torrent data - */ - static protected function decode ( $string ) { - $data = is_file( $string ) || self::url_exists( $string ) ? - self::file_get_contents( $string ) : - $string; - return (array) self::decode_data( $data ); - } - - /** Decode torrent data - * @param string data to decode - * @return array decoded torrent data - */ - static private function decode_data ( & $data ) { - switch( self::char( $data ) ) { - case 'i': - $data = substr( $data, 1 ); - return self::decode_integer( $data ); - case 'l': - $data = substr( $data, 1 ); - return self::decode_list( $data ); - case 'd': - $data = substr( $data, 1 ); - return self::decode_dictionary( $data ); - default: - return self::decode_string( $data ); - } - } - - /** Decode torrent dictionary - * @param string data to decode - * @return array decoded dictionary - */ - static private function decode_dictionary ( & $data ) { - $dictionary = array(); - $previous = null; - while ( ( $char = self::char( $data ) ) != 'e' ) { - if ( $char === false ) - return self::set_error( new Exception( 'Unterminated dictionary' ) ); - if ( ! ctype_digit( $char ) ) - return self::set_error( new Exception( 'Invalid dictionary key' ) ); - $key = self::decode_string( $data ); - if ( isset( $dictionary[$key] ) ) - return self::set_error( new Exception( 'Duplicate dictionary key' ) ); - if ( $key < $previous ) - return self::set_error( new Exception( 'Missorted dictionary key' ) ); - $dictionary[$key] = self::decode_data( $data ); - $previous = $key; - } - $data = substr( $data, 1 ); - return $dictionary; - } - - /** Decode torrent list - * @param string data to decode - * @return array decoded list - */ - static private function decode_list ( & $data ) { - $list = array(); - while ( ( $char = self::char( $data ) ) != 'e' ) { - if ( $char === false ) - return self::set_error( new Exception( 'Unterminated list' ) ); - $list[] = self::decode_data( $data ); - } - $data = substr( $data, 1 ); - return $list; - } - - /** Decode torrent string - * @param string data to decode - * @return string decoded string - */ - static private function decode_string ( & $data ) { - if ( self::char( $data ) === '0' && substr( $data, 1, 1 ) != ':' ) - self::set_error( new Exception( 'Invalid string length, leading zero' ) ); - if ( ! $colon = @strpos( $data, ':' ) ) - return self::set_error( new Exception( 'Invalid string length, colon not found' ) ); - $length = intval( substr( $data, 0, $colon ) ); - if ( $length + $colon + 1 > strlen( $data ) ) - return self::set_error( new Exception( 'Invalid string, input too short for string length' ) ); - $string = substr( $data, $colon + 1, $length ); - $data = substr( $data, $colon + $length + 1 ); - return $string; - } - - /** Decode torrent integer - * @param string data to decode - * @return integer decoded integer - */ - static private function decode_integer ( & $data ) { - $start = 0; - $end = strpos( $data, 'e'); - if ( $end === 0 ) - self::set_error( new Exception( 'Empty integer' ) ); - if ( self::char( $data ) == '-' ) - $start++; - if ( substr( $data, $start, 1 ) == '0' && ( $start != 0 || $end > $start + 1 ) ) - self::set_error( new Exception( 'Leading zero in integer' ) ); - if ( ! ctype_digit( substr( $data, $start, $end ) ) ) - self::set_error( new Exception( 'Non-digit characters in integer' ) ); - $integer = substr( $data, 0, $end ); - $data = substr( $data, $end + 1 ); - return $integer + 0; - } - - /**** Internal Helpers ****/ - - /** Build torrent info - * @param string|array source folder/file(s) path - * @param integer piece length - * @return array|boolean torrent info or false if data isn't folder/file(s) - */ - protected function build ( $data, $piece_length ) { - if ( is_null( $data ) ) - return false; - elseif ( is_array( $data ) && self::is_list( $data ) ) - return $this->info = $this->files( $data, $piece_length ); - elseif ( is_dir( $data ) ) - return $this->info = $this->folder( $data, $piece_length ); - elseif ( ( is_file( $data ) || self::url_exists( $data ) ) && ! self::is_torrent( $data ) ) - return $this->info = $this->file( $data, $piece_length ); - else - return false; - } - - /** Set torrent creator and creation date - * @param any param - * @return any param - */ - protected function touch ( $void = null ) { - $this->{'created by'} = 'Torrent PHP Class - Adrien Gibrat'; - $this->{'creation date'} = time(); - return $void; - } - - /** Add an error to errors stack - * @param Exception error to add - * @param boolean return error message or not (optional, default to false) - * @return boolean|string return false or error message if requested - */ - static protected function set_error ( $exception, $message = false ) { - return ( array_unshift( self::$errors, $exception ) && $message ) ? $exception->getMessage() : false; - } - - /** Build announce list - * @param string|array announce url / list - * @param string|array announce url / list to add (optionnal) - * @return array announce list (array of arrays) - */ - static protected function announce_list( $announce, $merge = array() ) { - return array_map( create_function( '$a', 'return (array) $a;' ), array_merge( (array) $announce, (array) $merge ) ); - } - - /** Get the first announce url in a list - * @param array announce list (array of arrays if tiered trackers) - * @return string first announce url - */ - static protected function first_announce( $announce ) { - while ( is_array( $announce ) ) - $announce = reset( $announce ); - return $announce; - } - - /** Helper to pack data hash - * @param string data - * @return string packed data hash - */ - static protected function pack ( & $data ) { - return pack('H*', sha1( $data ) ) . ( $data = null ); - } - - /** Helper to build file path - * @param array file path - * @param string base folder - * @return string real file path - */ - static protected function path ( $path, $folder ) { - array_unshift( $path, $folder ); - return join( DIRECTORY_SEPARATOR, $path ); - } - - /** Helper to test if an array is a list - * @param array array to test - * @return boolean is the array a list or not - */ - static protected function is_list ( $array ) { - foreach ( array_keys( $array ) as $key ) - if ( ! is_int( $key ) ) - return false; - return true; - } - - /** Build pieces depending on piece length from a file handler - * @param ressource file handle - * @param integer piece length - * @param boolean is last piece - * @return string pieces - */ - private function pieces ( $handle, $piece_length, $last = true ) { - static $piece, $length; - if ( empty( $length ) ) - $length = $piece_length; - $pieces = null; - while ( ! feof( $handle ) ) { - if ( ( $length = strlen( $piece .= fread( $handle, $length ) ) ) == $piece_length ) - $pieces .= self::pack( $piece ); - elseif ( ( $length = $piece_length - $length ) < 0 ) - return self::set_error( new Exception( 'Invalid piece length!' ) ); - } - fclose( $handle ); - return $pieces . ( $last && $piece ? self::pack( $piece ) : null); - } - - /** Build torrent info from single file - * @param string file path - * @param integer piece length - * @return array torrent info - */ - private function file ( $file, $piece_length ) { - if ( ! $handle = self::fopen( $file, $size = self::filesize( $file ) ) ) - return self::set_error( new Exception( 'Failed to open file: "' . $file . '"' ) ); - return array( - 'length' => $size, - 'name' => end( explode( DIRECTORY_SEPARATOR, $file ) ), - 'piece length' => $piece_length, - 'pieces' => $this->pieces( $handle, $piece_length ) - ); - } - - /** Build torrent info from files - * @param array file list - * @param integer piece length - * @return array torrent info - */ - private function files ( $files, $piece_length ) { - $files = array_map( 'realpath', $files ); - sort( $files ); - usort( $files, create_function( '$a,$b', 'return strrpos($a,DIRECTORY_SEPARATOR)-strrpos($b,DIRECTORY_SEPARATOR);' ) ); - $path = explode( DIRECTORY_SEPARATOR, dirname( realpath( current( $files ) ) ) ); - $pieces = null; $info_files = array(); $count = count($files) - 1; - foreach ( $files as $i => $file ) { - if ( $path != array_intersect_assoc( $file_path = explode( DIRECTORY_SEPARATOR, $file ), $path ) ){ - self::set_error( new Exception( 'Files must be in the same folder: "' . $file . '" discarded' ) ); - continue; - } - if ( ! $handle = self::fopen( $file, $filesize = self::filesize( $file ) ) ){ - self::set_error( new Exception( 'Failed to open file: "' . $file . '" discarded' ) ); - continue; - } - $pieces .= $this->pieces( $handle, $piece_length, $count == $i ); - $info_files[] = array( - 'length' => $filesize, - 'path' => array_diff( $file_path, $path ) - ); - } - return array( - 'files' => $info_files, - 'name' => end( $path ), - 'piece length' => $piece_length, - 'pieces' => $pieces - ); - - } - - /** Build torrent info from folder content - * @param string folder path - * @param integer piece length - * @return array torrent info - */ - private function folder ( $dir, $piece_length ) { - return $this->files( self::scandir( $dir ), $piece_length ); - } - - /** Helper to return the first char of encoded data - * @param string encoded data - * @return string|boolean first char of encoded data or false if empty data - */ - static private function char ( $data ) { - return empty( $data ) ? - false : - substr( $data, 0, 1 ); - } - - /**** Public Helpers ****/ - - /** Helper to format size in bytes to human readable - * @param integer size in bytes - * @param integer precision after coma - * @return string formated size in appropriate unit - */ - static public function format ( $size, $precision = 2 ) { - $units = array ('octets', 'Ko', 'Mo', 'Go', 'To'); - while( ( $next = next( $units ) ) && $size > 1024 ) - $size /= 1024; - return round( $size, $precision ) . ' ' . ( $next ? prev( $units ) : end( $units ) ); - } - - /** Helper to return filesize (even bigger than 2Gb -linux only- and distant files size) - * @param string file path - * @return double|boolean filesize or false if error - */ - static public function filesize ( $file ) { - if ( is_file( $file ) ) - return (double) sprintf( '%u', @filesize( $file ) ); - else if ( $content_length = preg_grep( $pattern = '#^Content-Length:\s+(\d+)$#i', (array) @get_headers( $file ) ) ) - return (int) preg_replace( $pattern, '$1', reset( $content_length ) ); - } - - /** Helper to open file to read (even bigger than 2Gb, linux only) - * @param string file path - * @param integer|double file size (optional) - * @return ressource|boolean file handle or false if error - */ - static public function fopen ( $file, $size = null ) { - if ( ( is_null( $size ) ? self::filesize( $file ) : $size ) <= 2 * pow( 1024, 3 ) ) - return fopen( $file, 'r' ); - elseif ( PHP_OS != 'Linux' ) - return self::set_error( new Exception( 'File size is greater than 2GB. This is only supported under Linux' ) ); - elseif ( ! is_readable( $file ) ) - return false; - else - return popen( 'cat ' . escapeshellarg( realpath( $file ) ), 'r' ); - } - - /** Helper to scan directories files and sub directories recursivly - * @param string directory path - * @return array directory content list - */ - static public function scandir ( $dir ) { - $paths = array(); - foreach ( scandir( $dir ) as $item ) - if ( $item != '.' && $item != '..' ) - if ( is_dir( $path = realpath( $dir . DIRECTORY_SEPARATOR . $item ) ) ) - $paths = array_merge( self::scandir( $path ), $paths ); - else - $paths[] = $path; - return $paths; - } - - /** Helper to check if url exists - * @param string url to check - * @return boolean does the url exist or not - */ - static public function url_exists ( $url ) { - return preg_match( '#^http(s)?://[a-z0-9-]+(.[a-z0-9-]+)*(:[0-9]+)?(/.*)?$#i', $url ) ? - (bool) preg_grep( '#^HTTP/.*\s(200|304)\s#', (array) @get_headers( $url ) ) : - false; - } - - /** Helper to check if a file is a torrent - * @param string file location - * @param float http timeout (optional, default to self::timeout 30s) - * @return boolean is the file a torrent or not - */ - static public function is_torrent ( $file, $timeout = self::timeout ) { - return self::file_get_contents( $file, $timeout, 0, 11 ) === 'd8:announce'; - } - - /** Helper to get (distant) file content - * @param string file location - * @param float http timeout (optional, default to self::timeout 30s) - * @param integer starting offset (optional, default to null) - * @param integer content length (optional, default to null) - * @return string|boolean file content or false if error - */ - static public function file_get_contents ( $file, $timeout = self::timeout, $offset = null, $length = null ) { - if ( is_file( $file ) || ini_get( 'allow_url_fopen' ) ) { - $context = ! is_file( $file ) && $timeout ? - stream_context_create( array( 'http' => array( 'timeout' => $timeout ) ) ) : - null; - return ! is_null( $offset ) ? $length ? - @file_get_contents( $file, false, $context, $offset, $length ) : - @file_get_contents( $file, false, $context, $offset ) : - @file_get_contents( $file, false, $context ); - } elseif ( ! function_exists( 'curl_init' ) ) - return self::set_error( new Exception( 'Install CURL or enable "allow_url_fopen"' ) ); - $handle = curl_init( $file ); - if ( $timeout ) - curl_setopt( $handle, CURLOPT_TIMEOUT, $timeout ); - if ( $offset || $length ) - curl_setopt( $handle, CURLOPT_RANGE, $offset . '-' . ( $length ? $offset + $length -1 : null ) ); - curl_setopt( $handle, CURLOPT_RETURNTRANSFER, 1 ); - $content = curl_exec( $handle ); - $size = curl_getinfo( $handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD ); - curl_close( $handle ); - return ( $offset && $size == -1 ) || ( $length && $length != $size ) ? $length ? - substr( $content, $offset, $length) : - substr( $content, $offset) : - $content; - } - -} - -?> diff --git a/inc/scrape/torrent.php b/inc/scrape/torrent.php deleted file mode 100755 index 420f6f8..0000000 --- a/inc/scrape/torrent.php +++ /dev/null @@ -1,860 +0,0 @@ - -require_once 'Torrent.php'; - -// get torrent infos -$torrent = new Torrent( './test.torrent' ); -echo '
private: ', $torrent->is_private() ? 'yes' : 'no', - '
annonce: ', $torrent->announce(), - '
name: ', $torrent->name(), - '
comment: ', $torrent->comment(), - '
piece_length: ', $torrent->piece_length(), - '
size: ', $torrent->size( 2 ), - '
hash info: ', $torrent->hash_info(), - '
stats: '; -var_dump( $torrent->scrape() ); -echo '
content: '; -var_dump( $torrent->content() ); -echo '
source: ', - $torrent; - -// get magnet link -$torrent->magnet(); // use $torrent->magnet( false ); to get non html encoded ampersand - -// create torrent -$torrent = new Torrent( array( 'test.mp3', 'test.jpg' ), 'http://torrent.tracker/annonce' ); -$torrent->save('test.torrent'); // save to disk - -// modify torrent -$torrent->announce('http://alternate-torrent.tracker/annonce'); // add a tracker -$torrent->announce(false); // reset announce trackers -$torrent->announce(array('http://torrent.tracker/annonce', 'http://alternate-torrent.tracker/annonce')); // set tracker(s), it also works with a 'one tracker' array... -$torrent->announce(array(array('http://torrent.tracker/annonce', 'http://alternate-torrent.tracker/annonce'), 'http://another-torrent.tracker/annonce')); // set tiered trackers -$torrent->comment('hello world'); -$torrent->name('test torrent'); -$torrent->is_private(true); -$torrent->httpseeds('http://file-hosting.domain/path/'); // Bittornado implementation -$torrent->url_list(array('http://file-hosting.domain/path/','http://another-file-hosting.domain/path/')); // -GetRight implementation - -// print errors -if ( $errors = $torrent->errors() ) - var_dump( $errors ); - -// send to user -$torrent->send(); - * - * - * @author Adrien Gibrat - * @tester Jeong, Anton, dokcharlie, official testers ;) Thanks for your precious feedback - * @copyleft 2010 - Just use it! - * @license http://www.gnu.org/licenses/gpl.html GNU General Public License version 3 - * @version Release: 1.2 (6 july 2010) - */ - -class Torrent { - - /** - * @const float Default http timeout - */ - const timeout = 30; - - /** - * @var array List of error occured - */ - static protected $_errors = array(); - - /** Read and decode torrent file/data OR build a torrent from source folder/file(s) - * Supported signatures: - * - Torrent(); // get an instance (usefull to scrape and check errors) - * - Torrent( string $torrent ); // analyse a torrent file - * - Torrent( string $torrent, string $announce ); - * - Torrent( string $torrent, array $meta ); - * - Torrent( string $file_or_folder ); // create a torrent file - * - Torrent( string $file_or_folder, string $announce_url, [int $piece_length] ); - * - Torrent( string $file_or_folder, array $meta, [int $piece_length] ); - * - Torrent( array $files_list ); - * - Torrent( array $files_list, string $announce_url, [int $piece_length] ); - * - Torrent( array $files_list, array $meta, [int $piece_length] ); - * @param string|array torrent to read or source folder/file(s) (optional, to get an instance) - * @param string|array announce url or meta informations (optional) - * @param int piece length (optional) - */ - public function __construct ( $data = null, $meta = array(), $piece_length = 256 ) { - if ( is_null( $data ) ) - return false; - if ( $piece_length < 32 || $piece_length > 4096 ) - return self::set_error( new Exception( 'Invalid piece lenth, must be between 32 and 4096' ) ); - if ( is_string( $meta ) ) - $meta = array( 'announce' => $meta ); - if ( $this->build( $data, $piece_length * 1024 ) ) - $this->touch(); - else - $meta = array_merge( $meta, $this->decode( $data ) ); - foreach( $meta as $key => $value ) - $this->{$key} = $value; - } - - /** Convert the current Torrent instance in torrent format - * @return string encoded torrent data - */ - public function __toString() { - return $this->encode( $this ); - } - - /** Return last error message - * @return string|boolean last error message or false if none - */ - public function error() { - return empty( self::$_errors ) ? - false : - self::$_errors[0]->getMessage(); - } - - /** Return Errors - * @return array|boolean error list or false if none - */ - public function errors() { - return empty( self::$_errors ) ? - false : - self::$_errors; - } - - /**** Getters and setters ****/ - - /** Getter and setter of torrent announce url / list - * If the argument is a string, announce url is added to announce list (or set as announce if announce is not set) - * If the argument is an array/object, set announce url (with first url) and list (if array has more than one url), tiered list supported - * If the argument is false announce url & list are unset - * @param null|false|string|array announce url / list, reset all if false (optional, if omitted it's a getter) - * @return string|array|null announce url / list or null if not set - */ - public function announce ( $announce = null ) { - if ( is_null( $announce ) ) - return ! isset( $this->{'announce-list'} ) ? - isset( $this->announce ) ? $this->announce : null : - $this->{'announce-list'}; - $this->touch(); - if ( is_string( $announce ) && isset( $this->announce ) ) - return $this->{'announce-list'} = self::announce_list( isset( $this->{'announce-list'} ) ? $this->{'announce-list'} : $this->announce, $announce ); - unset( $this->{'announce-list'} ); - if ( is_array( $announce ) || is_object( $announce ) ) - if ( ( $this->announce = self::first_announce( $announce ) ) && count( $announce ) > 1 ) - return $this->{'announce-list'} = self::announce_list( $announce ); - else - return $this->announce; - if ( ! isset( $this->announce ) && $announce ) - return $this->announce = (string) $announce; - unset( $this->announce ); - } - - /** Getter and setter of torrent comment - * @param null|string comment (optional, if omitted it's a getter) - * @return string|null comment or null if not set - */ - public function comment ( $comment = null ) { - return is_null( $comment ) ? - isset( $this->comment ) ? $this->comment : null : - $this->touch( $this->comment = (string) $comment ); - } - - /** Getter and setter of torrent name - * @param null|string name (optional, if omitted it's a getter) - * @return string|null name or null if not set - */ - public function name ( $name = null ) { - return is_null( $name ) ? - isset( $this->info['name'] ) ? $this->info['name'] : null : - $this->touch( $this->info['name'] = (string) $name ); - } - - /** Getter and setter of private flag - * @param null|boolean is private or not (optional, if omitted it's a getter) - * @return boolean private flag - */ - public function is_private ( $private = null ) { - return is_null( $private ) ? - ! empty( $this->info['private'] ) : - $this->touch( $this->info['private'] = $private ? 1 : 0 ); - } - - /** Getter and setter of webseed(s) url list ( GetRight implementation ) - * @param null|string|array webseed or webseeds mirror list (optional, if omitted it's a getter) - * @return string|array|null webseed(s) or null if not set - */ - public function url_list ( $urls = null ) { - return is_null( $urls ) ? - isset( $this->{'url-list'} ) ? $this->{'url-list'} : null : - $this->touch( $this->{'url-list'} = is_string( $urls) ? $urls : (array) $urls ); - } - - /** Getter and setter of httpseed(s) url list ( Bittornado implementation ) - * @param null|string|array httpseed or httpseeds mirror list (optional, if omitted it's a getter) - * @return array|null httpseed(s) or null if not set - */ - public function httpseeds ( $urls = null ) { - return is_null( $urls ) ? - isset( $this->httpseeds ) ? $this->httpseeds : null : - $this->touch( $this->httpseeds = (array) $urls ); - } - - /**** Analyze BitTorrent ****/ - - /** Get piece length - * @return integer piece length or null if not set - */ - public function piece_length () { - return isset( $this->info['piece length'] ) ? - $this->info['piece length'] : - null; - } - - /** Compute hash info - * @return string hash info or null if info not set - */ - public function hash_info () { - return isset( $this->info ) ? - sha1( self::encode( $this->info ) ) : - null; - } - - /** List torrent content - * @param integer|null size precision (optional, if omitted returns sizes in bytes) - * @return array file(s) and size(s) list, files as keys and sizes as values - */ - public function content ( $precision = null ) { - $files = array(); - if ( isset( $this->info['files'] ) && is_array( $this->info['files'] ) ) - foreach ( $this->info['files'] as $file ) - $files[self::path( $file['path'], $this->info['name'] )] = $precision ? - self::format( $file['length'], $precision ) : - $file['length']; - elseif ( isset( $this->info['name'] ) ) - $files[$this->info['name']] = $precision ? - self::format( $this->info['length'], $precision ) : - $this->info['length']; - return $files; - } - - /** List torrent content pieces and offset(s) - * @return array file(s) and pieces/offset(s) list, file(s) as keys and pieces/offset(s) as values - */ - public function offset () { - $files = array(); - $size = 0; - if ( isset( $this->info['files'] ) && is_array( $this->info['files'] ) ) - foreach ( $this->info['files'] as $file ) - $files[self::path( $file['path'], $this->info['name'] )] = array( - 'startpiece' => floor( $size / $this->info['piece length'] ), - 'offset' => fmod( $size, $this->info['piece length'] ), - 'size' => $size += $file['length'], - 'endpiece' => floor( $size / $this->info['piece length'] ) - ); - elseif ( isset( $this->info['name'] ) ) - $files[$this->info['name']] = array( - 'startpiece' => 0, - 'offset' => 0, - 'size' => $this->info['length'], - 'endpiece' => floor( $this->info['length'] / $this->info['piece length'] ) - ); - return $files; - } - - /** Sum torrent content size - * @param integer|null size precision (optional, if omitted returns size in bytes) - * @return integer|string file(s) size - */ - public function size ( $precision = null ) { - $size = 0; - if ( isset( $this->info['files'] ) && is_array( $this->info['files'] ) ) - foreach ( $this->info['files'] as $file ) - $size += $file['length']; - elseif ( isset( $this->info['name'] ) ) - $size = $this->info['length']; - return is_null( $precision ) ? - $size : - self::format( $size, $precision ); - } - - /** Request torrent statistics from scrape page USING CURL!! - * @param string|array announce or scrape page url (optional, to request an alternative tracker BUT requirered for static call) - * @param string torrent hash info (optional, requirered ONLY for static call) - * @param float read timeout in seconds (optional, default to self::timeout 30s) - * @return array tracker torrent statistics - */ - /* static */ public function scrape ( $announce = null, $hash_info = null, $timeout = self::timeout ) { - $packed_hash = urlencode( pack('H*', $hash_info ? $hash_info : $this->hash_info() ) ); - $handles = $scrape = array(); - if ( ! function_exists( 'curl_multi_init' ) ) - return self::set_error( new Exception( 'Install CURL with "curl_multi_init" enabled' ) ); - $curl = curl_multi_init(); - foreach ( (array) ($announce ? $announce : $this->announce()) as $tier ) - foreach ( (array) $tier as $tracker ) { - $tracker = str_ireplace( array( 'udp://', '/announce', ':80/' ), array( 'http://', '/scrape', '/' ), $tracker ); - if ( isset( $handles[$tracker] ) ) - continue; - $handles[$tracker] = curl_init( $tracker . '?info_hash=' . $packed_hash ); - curl_setopt( $handles[$tracker], CURLOPT_RETURNTRANSFER, true ); - curl_setopt( $handles[$tracker], CURLOPT_TIMEOUT, $timeout ); - curl_multi_add_handle( $curl, $handles[$tracker] ); - } - do { - while ( ( $state = curl_multi_exec( $curl, $running ) ) == CURLM_CALL_MULTI_PERFORM ); - if( $state != CURLM_OK ) - continue; - while ( $done = curl_multi_info_read( $curl ) ) { - $info = curl_getinfo( $done['handle'] ); - $tracker = explode( '?', $info['url'], 2 ); - $tracker = array_shift( $tracker ); - if ( empty( $info['http_code'] ) ) { - $scrape[$tracker] = self::set_error( new Exception( 'Tracker request timeout (' . $timeout . 's)' ), true ); - continue; - } elseif ( $info['http_code'] != 200 ) { - $scrape[$tracker] = self::set_error( new Exception( 'Tracker request failed (' . $info['http_code'] . ' code)' ), true ); - continue; - } - $data = curl_multi_getcontent( $done['handle'] ); - $stats = self::decode_data( $data ); - curl_multi_remove_handle( $curl, $done['handle'] ); - $scrape[$tracker] = empty( $stats['files'] ) ? - self::set_error( new Exception( 'Empty scrape data' ), true ) : - array_shift( $stats['files'] ) + ( empty( $stats['flags'] ) ? array() : $stats['flags'] ); - } - } while ( $running ); - curl_multi_close( $curl ); - return $scrape; - } - - /**** Save and Send ****/ - - /** Save torrent file to disk - * @param null|string name of the file (optional) - * @return boolean file has been saved or not - */ - public function save ( $filename = null ) { - return file_put_contents( is_null( $filename ) ? $this->info['name'] . '.torrent' : $filename, $this->encode( $this ) ); - } - - /** Send torrent file to client - * @param null|string name of the file (optional) - * @return void script exit - */ - public function send ( $filename = null ) { - $data = $this->encode( $this ); - header( 'Content-type: application/x-bittorrent' ); - header( 'Content-Length: ' . strlen( $data ) ); - header( 'Content-Disposition: attachment; filename="' . ( is_null( $filename ) ? $this->info['name'] . '.torrent' : $filename ) . '"' ); - exit( $data ); - } - - /** Get magnet link - * @param boolean html encode ampersand, default true (optional) - * @return string magnet link - */ - public function magnet ( $html = true ) { - $ampersand = $html ? '&' : '&'; - return sprintf( 'magnet:?xt=urn:btih:%2$s%1$sdn=%3$s%1$sxl=%4$d%1$str=%5$s', $ampersand, $this->hash_info(), urlencode( $this->name() ), $this->size(), implode( $ampersand .'tr=', self::untier( $this->announce() ) ) ); - } - - /**** Encode BitTorrent ****/ - - /** Encode torrent data - * @param mixed data to encode - * @return string torrent encoded data - */ - static public function encode ( $mixed ) { - switch ( gettype( $mixed ) ) { - case 'integer': - case 'double': - return self::encode_integer( $mixed ); - case 'object': - $mixed = get_object_vars( $mixed ); - case 'array': - return self::encode_array( $mixed ); - default: - return self::encode_string( (string) $mixed ); - } - } - - /** Encode torrent string - * @param string string to encode - * @return string encoded string - */ - static private function encode_string ( $string ) { - return strlen( $string ) . ':' . $string; - } - - /** Encode torrent integer - * @param integer integer to encode - * @return string encoded integer - */ - static private function encode_integer ( $integer ) { - return 'i' . $integer . 'e'; - } - - /** Encode torrent dictionary or list - * @param array array to encode - * @return string encoded dictionary or list - */ - static private function encode_array ( $array ) { - if ( self::is_list( $array ) ) { - $return = 'l'; - foreach ( $array as $value ) - $return .= self::encode( $value ); - } else { - ksort( $array, SORT_STRING ); - $return = 'd'; - foreach ( $array as $key => $value ) - $return .= self::encode( strval( $key ) ) . self::encode( $value ); - } - return $return . 'e'; - } - - /**** Decode BitTorrent ****/ - - /** Decode torrent data or file - * @param string data or file path to decode - * @return array decoded torrent data - */ - static protected function decode ( $string ) { - $data = is_file( $string ) || self::url_exists( $string ) ? - self::file_get_contents( $string ) : - $string; - return (array) self::decode_data( $data ); - } - - /** Decode torrent data - * @param string data to decode - * @return array decoded torrent data - */ - static private function decode_data ( & $data ) { - switch( self::char( $data ) ) { - case 'i': - $data = substr( $data, 1 ); - return self::decode_integer( $data ); - case 'l': - $data = substr( $data, 1 ); - return self::decode_list( $data ); - case 'd': - $data = substr( $data, 1 ); - return self::decode_dictionary( $data ); - default: - return self::decode_string( $data ); - } - } - - /** Decode torrent dictionary - * @param string data to decode - * @return array decoded dictionary - */ - static private function decode_dictionary ( & $data ) { - $dictionary = array(); - $previous = null; - while ( ( $char = self::char( $data ) ) != 'e' ) { - if ( $char === false ) - return self::set_error( new Exception( 'Unterminated dictionary' ) ); - if ( ! ctype_digit( $char ) ) - return self::set_error( new Exception( 'Invalid dictionary key' ) ); - $key = self::decode_string( $data ); - if ( isset( $dictionary[$key] ) ) - return self::set_error( new Exception( 'Duplicate dictionary key' ) ); - if ( $key < $previous ) - return self::set_error( new Exception( 'Missorted dictionary key' ) ); - $dictionary[$key] = self::decode_data( $data ); - $previous = $key; - } - $data = substr( $data, 1 ); - return $dictionary; - } - - /** Decode torrent list - * @param string data to decode - * @return array decoded list - */ - static private function decode_list ( & $data ) { - $list = array(); - while ( ( $char = self::char( $data ) ) != 'e' ) { - if ( $char === false ) - return self::set_error( new Exception( 'Unterminated list' ) ); - $list[] = self::decode_data( $data ); - } - $data = substr( $data, 1 ); - return $list; - } - - /** Decode torrent string - * @param string data to decode - * @return string decoded string - */ - static private function decode_string ( & $data ) { - if ( self::char( $data ) === '0' && substr( $data, 1, 1 ) != ':' ) - self::set_error( new Exception( 'Invalid string length, leading zero' ) ); - if ( ! $colon = @strpos( $data, ':' ) ) - return self::set_error( new Exception( 'Invalid string length, colon not found' ) ); - $length = intval( substr( $data, 0, $colon ) ); - if ( $length + $colon + 1 > strlen( $data ) ) - return self::set_error( new Exception( 'Invalid string, input too short for string length' ) ); - $string = substr( $data, $colon + 1, $length ); - $data = substr( $data, $colon + $length + 1 ); - return $string; - } - - /** Decode torrent integer - * @param string data to decode - * @return integer decoded integer - */ - static private function decode_integer ( & $data ) { - $start = 0; - $end = strpos( $data, 'e'); - if ( $end === 0 ) - self::set_error( new Exception( 'Empty integer' ) ); - if ( self::char( $data ) == '-' ) - $start++; - if ( substr( $data, $start, 1 ) == '0' && $end > $start + 1 ) - self::set_error( new Exception( 'Leading zero in integer' ) ); - if ( ! ctype_digit( substr( $data, $start, $start ? $end - 1 : $end ) ) ) - self::set_error( new Exception( 'Non-digit characters in integer' ) ); - $integer = substr( $data, 0, $end ); - $data = substr( $data, $end + 1 ); - return 0 + $integer; - } - - /**** Internal Helpers ****/ - - /** Build torrent info - * @param string|array source folder/file(s) path - * @param integer piece length - * @return array|boolean torrent info or false if data isn't folder/file(s) - */ - protected function build ( $data, $piece_length ) { - if ( is_null( $data ) ) - return false; - elseif ( is_array( $data ) && self::is_list( $data ) ) - return $this->info = $this->files( $data, $piece_length ); - elseif ( is_dir( $data ) ) - return $this->info = $this->folder( $data, $piece_length ); - elseif ( ( is_file( $data ) || self::url_exists( $data ) ) && ! self::is_torrent( $data ) ) - return $this->info = $this->file( $data, $piece_length ); - else - return false; - } - - /** Set torrent creator and creation date - * @param any param - * @return any param - */ - protected function touch ( $void = null ) { - $this->{'created by'} = 'Torrent RW PHP Class - http://github.com/adriengibrat/torrent-rw'; - $this->{'creation date'} = time(); - return $void; - } - - /** Add an error to errors stack - * @param Exception error to add - * @param boolean return error message or not (optional, default to false) - * @return boolean|string return false or error message if requested - */ - static protected function set_error ( $exception, $message = false ) { - return ( array_unshift( self::$_errors, $exception ) && $message ) ? $exception->getMessage() : false; - } - - /** Build announce list - * @param string|array announce url / list - * @param string|array announce url / list to add (optionnal) - * @return array announce list (array of arrays) - */ - static protected function announce_list( $announce, $merge = array() ) { - return array_map( create_function( '$a', 'return (array) $a;' ), array_merge( (array) $announce, (array) $merge ) ); - } - - /** Get the first announce url in a list - * @param array announce list (array of arrays if tiered trackers) - * @return string first announce url - */ - static protected function first_announce( $announce ) { - while ( is_array( $announce ) ) - $announce = reset( $announce ); - return $announce; - } - - /** Helper to pack data hash - * @param string data - * @return string packed data hash - */ - static protected function pack ( & $data ) { - return pack('H*', sha1( $data ) ) . ( $data = null ); - } - - /** Helper to build file path - * @param array file path - * @param string base folder - * @return string real file path - */ - static protected function path ( $path, $folder ) { - array_unshift( $path, $folder ); - return join( DIRECTORY_SEPARATOR, $path ); - } - - /** Helper to test if an array is a list - * @param array array to test - * @return boolean is the array a list or not - */ - static protected function is_list ( $array ) { - foreach ( array_keys( $array ) as $key ) - if ( ! is_int( $key ) ) - return false; - return true; - } - - /** Build pieces depending on piece length from a file handler - * @param ressource file handle - * @param integer piece length - * @param boolean is last piece - * @return string pieces - */ - private function pieces ( $handle, $piece_length, $last = true ) { - static $piece, $length; - if ( empty( $length ) ) - $length = $piece_length; - $pieces = null; - while ( ! feof( $handle ) ) { - if ( ( $length = strlen( $piece .= fread( $handle, $length ) ) ) == $piece_length ) - $pieces .= self::pack( $piece ); - elseif ( ( $length = $piece_length - $length ) < 0 ) - return self::set_error( new Exception( 'Invalid piece length!' ) ); - } - fclose( $handle ); - return $pieces . ( $last && $piece ? self::pack( $piece ) : null); - } - - /** Build torrent info from single file - * @param string file path - * @param integer piece length - * @return array torrent info - */ - private function file ( $file, $piece_length ) { - if ( ! $handle = self::fopen( $file, $size = self::filesize( $file ) ) ) - return self::set_error( new Exception( 'Failed to open file: "' . $file . '"' ) ); - if ( self::is_url( $file ) ) - $this->url_list( $file ); - $path = explode( DIRECTORY_SEPARATOR, $file ); - return array( - 'length' => $size, - 'name' => end( $path ), - 'piece length' => $piece_length, - 'pieces' => $this->pieces( $handle, $piece_length ) - ); - } - - /** Build torrent info from files - * @param array file list - * @param integer piece length - * @return array torrent info - */ - private function files ( $files, $piece_length ) { - if ( ! self::is_url( current( $files ) ) ) - $files = array_map( 'realpath', $files ); - sort( $files ); - usort( $files, create_function( '$a,$b', 'return strrpos($a,DIRECTORY_SEPARATOR)-strrpos($b,DIRECTORY_SEPARATOR);' ) ); - $first = current( $files ); - $root = dirname( $first ); - if ( $url = self::is_url( $first ) ) - $this->url_list( dirname( $root ) . DIRECTORY_SEPARATOR ); - $path = explode( DIRECTORY_SEPARATOR, dirname( $url ? $first : realpath( $first ) ) ); - $pieces = null; $info_files = array(); $count = count( $files ) - 1; - foreach ( $files as $i => $file ) { - if ( $path != array_intersect_assoc( $file_path = explode( DIRECTORY_SEPARATOR, $file ), $path ) ) { - self::set_error( new Exception( 'Files must be in the same folder: "' . $file . '" discarded' ) ); - continue; - } - if ( ! $handle = self::fopen( $file, $filesize = self::filesize( $file ) ) ) { - self::set_error( new Exception( 'Failed to open file: "' . $file . '" discarded' ) ); - continue; - } - $pieces .= $this->pieces( $handle, $piece_length, $count == $i ); - $info_files[] = array( - 'length' => $filesize, - 'path' => array_diff( $file_path, $path ) - ); - } - return array( - 'files' => $info_files, - 'name' => end( $path ), - 'piece length' => $piece_length, - 'pieces' => $pieces - ); - - } - - /** Build torrent info from folder content - * @param string folder path - * @param integer piece length - * @return array torrent info - */ - private function folder ( $dir, $piece_length ) { - return $this->files( self::scandir( $dir ), $piece_length ); - } - - /** Helper to return the first char of encoded data - * @param string encoded data - * @return string|boolean first char of encoded data or false if empty data - */ - static private function char ( $data ) { - return empty( $data ) ? - false : - substr( $data, 0, 1 ); - } - - /**** Public Helpers ****/ - - /** Helper to format size in bytes to human readable - * @param integer size in bytes - * @param integer precision after coma - * @return string formated size in appropriate unit - */ - static public function format ( $size, $precision = 2 ) { - $units = array ('octets', 'Ko', 'Mo', 'Go', 'To'); - while( ( $next = next( $units ) ) && $size > 1024 ) - $size /= 1024; - return round( $size, $precision ) . ' ' . ( $next ? prev( $units ) : end( $units ) ); - } - - /** Helper to return filesize (even bigger than 2Gb -linux only- and distant files size) - * @param string file path - * @return double|boolean filesize or false if error - */ - static public function filesize ( $file ) { - if ( is_file( $file ) ) - return (double) sprintf( '%u', @filesize( $file ) ); - else if ( $content_length = preg_grep( $pattern = '#^Content-Length:\s+(\d+)$#i', (array) @get_headers( $file ) ) ) - return (int) preg_replace( $pattern, '$1', reset( $content_length ) ); - } - - /** Helper to open file to read (even bigger than 2Gb, linux only) - * @param string file path - * @param integer|double file size (optional) - * @return ressource|boolean file handle or false if error - */ - static public function fopen ( $file, $size = null ) { - if ( ( is_null( $size ) ? self::filesize( $file ) : $size ) <= 2 * pow( 1024, 3 ) ) - return fopen( $file, 'r' ); - elseif ( PHP_OS != 'Linux' ) - return self::set_error( new Exception( 'File size is greater than 2GB. This is only supported under Linux' ) ); - elseif ( ! is_readable( $file ) ) - return false; - else - return popen( 'cat ' . escapeshellarg( realpath( $file ) ), 'r' ); - } - - /** Helper to scan directories files and sub directories recursivly - * @param string directory path - * @return array directory content list - */ - static public function scandir ( $dir ) { - $paths = array(); - foreach ( scandir( $dir ) as $item ) - if ( $item != '.' && $item != '..' ) - if ( is_dir( $path = realpath( $dir . DIRECTORY_SEPARATOR . $item ) ) ) - $paths = array_merge( self::scandir( $path ), $paths ); - else - $paths[] = $path; - return $paths; - } - - /** Helper to check if string is an url (http) - * @param string url to check - * @return boolean is string an url - */ - static public function is_url ( $url ) { - return preg_match( '#^http(s)?://[a-z0-9-]+(.[a-z0-9-]+)*(:[0-9]+)?(/.*)?$#i', $url ); - } - - /** Helper to check if url exists - * @param string url to check - * @return boolean does the url exist or not - */ - static public function url_exists ( $url ) { - return self::is_url( $url ) ? - (bool) self::filesize ( $url ) : - false; - } - /** Helper to check if a file is a torrent - * @param string file location - * @param float http timeout (optional, default to self::timeout 30s) - * @return boolean is the file a torrent or not - */ - static public function is_torrent ( $file, $timeout = self::timeout ) { - return ( $start = self::file_get_contents( $file, $timeout, 0, 11 ) ) - && $start === 'd8:announce' - || $start === 'd10:created' - || $start === 'd13:creatio' - || substr($start, 0, 7) === 'd4:info' - || substr($start, 0, 3) === 'd9:'; // @see https://github.com/adriengibrat/torrent-rw/pull/17 - } - - /** Helper to get (distant) file content - * @param string file location - * @param float http timeout (optional, default to self::timeout 30s) - * @param integer starting offset (optional, default to null) - * @param integer content length (optional, default to null) - * @return string|boolean file content or false if error - */ - static public function file_get_contents ( $file, $timeout = self::timeout, $offset = null, $length = null ) { - if ( is_file( $file ) || ini_get( 'allow_url_fopen' ) ) { - $context = ! is_file( $file ) && $timeout ? - stream_context_create( array( 'http' => array( 'timeout' => $timeout ) ) ) : - null; - return ! is_null( $offset ) ? $length ? - @file_get_contents( $file, false, $context, $offset, $length ) : - @file_get_contents( $file, false, $context, $offset ) : - @file_get_contents( $file, false, $context ); - } elseif ( ! function_exists( 'curl_init' ) ) - return self::set_error( new Exception( 'Install CURL or enable "allow_url_fopen"' ) ); - $handle = curl_init( $file ); - if ( $timeout ) - curl_setopt( $handle, CURLOPT_TIMEOUT, $timeout ); - if ( $offset || $length ) - curl_setopt( $handle, CURLOPT_RANGE, $offset . '-' . ( $length ? $offset + $length -1 : null ) ); - curl_setopt( $handle, CURLOPT_RETURNTRANSFER, 1 ); - $content = curl_exec( $handle ); - $size = curl_getinfo( $handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD ); - curl_close( $handle ); - return ( $offset && $size == -1 ) || ( $length && $length != $size ) ? $length ? - substr( $content, $offset, $length) : - substr( $content, $offset) : - $content; - } - - /** Flatten announces list - * @param array announces list - * @return array flattened annonces list - */ - static public function untier( $announces ) { - $list = array(); - foreach ( (array) $announces as $tier ) { - is_array( $tier ) ? - $list = array_merge( $list, self::untier( $tier ) ) : - array_push( $list, $tier ); - } - return $list; - } - -} \ No newline at end of file diff --git a/inc/scrape/tscraper.php b/inc/scrape/tscraper.php deleted file mode 100755 index 1acb4a1..0000000 --- a/inc/scrape/tscraper.php +++ /dev/null @@ -1,39 +0,0 @@ -connectionerror = $connectionerror; - parent::__construct($message, $code); - } - - public function isConnectionError(){ - return($this->connectionerror); - } - } - - abstract class tscraper { - protected $timeout; - - public function __construct($timeout=2){ - $this->timeout = $timeout; - } - } - -?> \ No newline at end of file diff --git a/inc/scrape/udptscraper.php b/inc/scrape/udptscraper.php deleted file mode 100755 index cfa9538..0000000 --- a/inc/scrape/udptscraper.php +++ /dev/null @@ -1,95 +0,0 @@ -scrape('udp://tracker.tld:port',array('0000000000000000000000000000000000000000')); - - print_r($ret); - }catch(ScraperException $e){ - echo('Error: ' . $e->getMessage() . "
\n"); - echo('Connection error: ' . ($e->isConnectionError() ? 'yes' : 'no') . "
\n"); - } - */ - - require_once(dirname(__FILE__) . '/tscraper.php'); - - class udptscraper extends tscraper{ - - /* $url: Tracker url like: udp://tracker.tld:port or udp://tracker.tld:port/announce - $infohash: Infohash string or array (max 74 items). 40 char long infohash. - */ - public function scrape($url,$infohash){ - if(!is_array($infohash)){ $infohash = array($infohash); } - foreach($infohash as $hash){ - if(!preg_match('#^[a-f0-9]{40}$#i',$hash)){ throw new ScraperException('Invalid infohash: ' . $hash . '.'); } - } - if(count($infohash) > 74){ throw new ScraperException('Too many infohashes provided.'); } - if(!preg_match('%udp://([^:/]*)(?::([0-9]*))?(?:/)?%si', $url, $m)){ throw new ScraperException('Invalid tracker url.'); } - $tracker = 'udp://' . $m[1]; - $port = isset($m[2]) ? $m[2] : 80; - - $transaction_id = mt_rand(0,65535); - $fp = fsockopen($tracker, $port, $errno, $errstr); - if(!$fp){ throw new ScraperException('Could not open UDP connection: ' . $errno . ' - ' . $errstr,0,true); } - stream_set_timeout($fp, $this->timeout); - - $current_connid = "\x00\x00\x04\x17\x27\x10\x19\x80"; - - //Connection request - $packet = $current_connid . pack("N", 0) . pack("N", $transaction_id); - fwrite($fp,$packet); - - //Connection response - $ret = fread($fp, 16); - if(strlen($ret) < 1){ throw new ScraperException('No connection response.',0,true); } - if(strlen($ret) < 16){ throw new ScraperException('Too short connection response.'); } - $retd = unpack("Naction/Ntransid",$ret); - if($retd['action'] != 0 || $retd['transid'] != $transaction_id){ - throw new ScraperException('Invalid connection response.'); - } - $current_connid = substr($ret,8,8); - - //Scrape request - $hashes = ''; - foreach($infohash as $hash){ $hashes .= pack('H*', $hash); } - $packet = $current_connid . pack("N", 2) . pack("N", $transaction_id) . $hashes; - fwrite($fp,$packet); - - //Scrape response - $readlength = 8 + (12 * count($infohash)); - $ret = fread($fp, $readlength); - if(strlen($ret) < 1){ throw new ScraperException('No scrape response.',0,true); } - if(strlen($ret) < 8){ throw new ScraperException('Too short scrape response.'); } - $retd = unpack("Naction/Ntransid",$ret); - // Todo check for error string if response = 3 - if($retd['action'] != 2 || $retd['transid'] != $transaction_id){ - throw new ScraperException('Invalid scrape response.'); - } - if(strlen($ret) < $readlength){ throw new ScraperException('Too short scrape response.'); } - $torrents = array(); - $index = 8; - foreach($infohash as $hash){ - $retd = unpack("Nseeders/Ncompleted/Nleechers",substr($ret,$index,12)); - $retd['infohash'] = $hash; - $torrents[$hash] = $retd; - $index = $index + 12; - } - - return($torrents); - } - } -?> \ No newline at end of file From ed0373767a1b56b4c1ba0617132da9f02a5fa822 Mon Sep 17 00:00:00 2001 From: PXgamer Date: Thu, 29 Jun 2017 15:36:01 +0100 Subject: [PATCH 09/20] Moved Redis class to src/ --- inc/redis.php => src/Redis.php | 367 ++++++++++++++++++++------------- 1 file changed, 220 insertions(+), 147 deletions(-) rename inc/redis.php => src/Redis.php (52%) diff --git a/inc/redis.php b/src/Redis.php similarity index 52% rename from inc/redis.php rename to src/Redis.php index bb237ba..3b7471a 100644 --- a/inc/redis.php +++ b/src/Redis.php @@ -13,327 +13,394 @@ * ******************************************************************************/ +namespace johncave\PhpLinuxTrack; -class Redis { +class Redis +{ public $server; public $port; private $_sock; - public function __construct($host='localhost', $port=6379) { + public function __construct($host = 'localhost', $port = 6379) + { $this->host = $host; $this->port = $port; } - - public function connect() { - if ($this->_sock) return; + + public function connect() + { + if ($this->_sock) { + return; + } if ($sock = fsockopen($this->host, $this->port, $errno, $errstr)) { $this->_sock = $sock; return; } $msg = "Cannot open socket to {$this->host}:{$this->port}"; - if ($errno || $errmsg) + if ($errno || $errstr) { $msg .= "," . ($errno ? " error $errno" : "") . - ($errmsg ? " $errmsg" : ""); + ($errstr ? " $errstr" : ""); + } trigger_error("$msg.", E_USER_ERROR); } - - public function disconnect() { - if ($this->_sock) @fclose($this->_sock); + + public function disconnect() + { + if ($this->_sock) { + @fclose($this->_sock); + } $this->_sock = null; } - - public function ping() { + + public function ping() + { $this->connect(); $this->write_cmd("PING"); return $this->get_response(); } - - public function do_echo($s) { + + public function do_echo($s) + { $this->connect(); - $this->write_cmd("ECHO",$s); + $this->write_cmd("ECHO", $s); return $this->get_response(); } - - public function set($name, $value, $preserve=false) { + + public function set($name, $value, $preserve = false) + { $this->connect(); - $this->write_cmd(($preserve ? 'SETNX' : 'SET'),$name,$value); + $this->write_cmd(($preserve ? 'SETNX' : 'SET'), $name, $value); return $this->get_response(); } - - public function get($name) { + + public function get($name) + { $this->connect(); - $this->write_cmd("GET",$name); + $this->write_cmd("GET", $name); return $this->get_response(); } - public function mget($keys) { + public function mget($keys) + { $this->connect(); - $this->write_cmd("MGET",$keys); + $this->write_cmd("MGET", $keys); return $this->get_response(); } - - public function incr($name, $amount=1) { + + public function incr($name, $amount = 1) + { $this->connect(); - if ($amount == 1) - $this->write_cmd("INCR",$name); - else - $this->write_cmd("INCRBY",$name,$amount); + if ($amount == 1) { + $this->write_cmd("INCR", $name); + } else { + $this->write_cmd("INCRBY", $name, $amount); + } return $this->get_response(); } - - public function decr($name, $amount=1) { + + public function decr($name, $amount = 1) + { $this->connect(); - if ($amount == 1) - $this->write_cmd("DECR",$name); - else - $this->write_cmd("DECRBY",$name,$amount); + if ($amount == 1) { + $this->write_cmd("DECR", $name); + } else { + $this->write_cmd("DECRBY", $name, $amount); + } return $this->get_response(); } - - public function exists($name) { + + public function exists($name) + { $this->connect(); - $this->write_cmd("EXISTS",$name); + $this->write_cmd("EXISTS", $name); return $this->get_response(); } - - public function delete($name) { + + public function delete($name) + { $this->connect(); - $this->write_cmd("DEL",$name); + $this->write_cmd("DEL", $name); return $this->get_response(); } - - public function keys($pattern) { + + public function keys($pattern) + { $this->connect(); - $this->write_cmd("KEYS",$pattern); + $this->write_cmd("KEYS", $pattern); return explode(' ', $this->get_response()); } - - public function randomkey() { + + public function randomkey() + { $this->connect(); $this->write_cmd("RANDOMKEY"); return $this->get_response(); } - - public function rename($src, $dst) { + + public function rename($src, $dst) + { $this->connect(); - $this->write_cmd("RENAME",$src,$dst); + $this->write_cmd("RENAME", $src, $dst); return $this->get_response(); } - public function renamenx($src, $dst) { + public function renamenx($src, $dst) + { $this->connect(); - $this->write_cmd("RENAMENX",$src,$dst); + $this->write_cmd("RENAMENX", $src, $dst); return $this->get_response(); } - - public function expire($name, $time) { + + public function expire($name, $time) + { $this->connect(); - $this->write_cmd("EXPIRE",$name,$time); + $this->write_cmd("EXPIRE", $name, $time); return $this->get_response(); } - - public function push($name, $value, $tail=true) { + + public function push($name, $value, $tail = true) + { $this->connect(); - $this->write_cmd(($tail ? 'RPUSH' : 'LPUSH'),$name,$value); + $this->write_cmd(($tail ? 'RPUSH' : 'LPUSH'), $name, $value); return $this->get_response(); } - public function lpush($name, $value) { + public function lpush($name, $value) + { return $this->push($name, $value, false); } - public function rpush($name, $value) { + public function rpush($name, $value) + { return $this->push($name, $value, true); } - public function ltrim($name, $start, $end) { + public function ltrim($name, $start, $end) + { $this->connect(); - $this->write_cmd("LTRIM",$name,$start,$end); + $this->write_cmd("LTRIM", $name, $start, $end); return $this->get_response(); } - - public function lindex($name, $index) { + + public function lindex($name, $index) + { $this->connect(); - $this->write_cmd("LINDEX",$name,$index); + $this->write_cmd("LINDEX", $name, $index); return $this->get_response(); } - - public function pop($name, $tail=true) { + + public function pop($name, $tail = true) + { $this->connect(); - $this->write_cmd(($tail ? 'RPOP' : 'LPOP'),$name); + $this->write_cmd(($tail ? 'RPOP' : 'LPOP'), $name); return $this->get_response(); } - public function lpop($name, $value) { + public function lpop($name, $value) + { return $this->pop($name, $value, false); } - public function rpop($name, $value) { + public function rpop($name, $value) + { return $this->pop($name, $value, true); } - - public function llen($name) { + + public function llen($name) + { $this->connect(); - $this->write_cmd("LLEN",$name); + $this->write_cmd("LLEN", $name); return $this->get_response(); } - - public function lrange($name, $start, $end) { + + public function lrange($name, $start, $end) + { $this->connect(); - $this->write_cmd("LRANGE",$name,$start,$end); + $this->write_cmd("LRANGE", $name, $start, $end); return $this->get_response(); } - public function lset($name, $value, $index) { + public function lset($name, $value, $index) + { $this->connect(); - $this->write_cmd("LSET",$name,$index,$value); + $this->write_cmd("LSET", $name, $index, $value); return $this->get_response(); } - - public function sadd($name, $value) { + + public function sadd($name, $value) + { $this->connect(); - $this->write_cmd("SADD",$name,$value); + $this->write_cmd("SADD", $name, $value); return $this->get_response(); } - - public function srem($name, $value) { + + public function srem($name, $value) + { $this->connect(); - $this->write_cmd("SREM",$name,$value); + $this->write_cmd("SREM", $name, $value); return $this->get_response(); } - - public function sismember($name, $value) { + + public function sismember($name, $value) + { $this->connect(); - $this->write_cmd("SISMEMBER",$name,$value); + $this->write_cmd("SISMEMBER", $name, $value); return $this->get_response(); } - - public function sinter($sets) { + + public function sinter($sets) + { $this->connect(); - $this->write_cmd("SINTER",$sets); + $this->write_cmd("SINTER", $sets); return $this->get_response(); } - - public function smembers($name) { + + public function smembers($name) + { $this->connect(); - $this->write_cmd("SMEMBERS",$name); + $this->write_cmd("SMEMBERS", $name); return $this->get_response(); } - public function scard($name) { + public function scard($name) + { $this->connect(); - $this->write_cmd("SCARD",$name); + $this->write_cmd("SCARD", $name); return $this->get_response(); } - public function zadd($name, $score, $value) { + public function zadd($name, $score, $value) + { $this->connect(); - $this->write_cmd("ZADD",$name,$score,$value); + $this->write_cmd("ZADD", $name, $score, $value); return $this->get_response(); } - public function zincrby($name, $score, $value) { + public function zincrby($name, $score, $value) + { $this->connect(); - $this->write_cmd("ZINCRBY",$name,$score,$value); + $this->write_cmd("ZINCRBY", $name, $score, $value); return $this->get_response(); } - public function zrem($name, $value) { + public function zrem($name, $value) + { $this->connect(); - $this->write_cmd("ZREM",$name,$value); + $this->write_cmd("ZREM", $name, $value); return $this->get_response(); } - public function zscore($name, $value) { + public function zscore($name, $value) + { $this->connect(); - $this->write_cmd("ZSCORE",$name,$value); + $this->write_cmd("ZSCORE", $name, $value); return $this->get_response(); } - public function zrange($name, $first, $last, $opt=false) { + public function zrange($name, $first, $last, $opt = false) + { $this->connect(); - $this->write_cmd("ZRANGE",$name,$first,$last,$opt); + $this->write_cmd("ZRANGE", $name, $first, $last, $opt); return $this->get_response(); } - public function zrevrange($name, $first, $last, $opt=false) { + public function zrevrange($name, $first, $last, $opt = false) + { $this->connect(); - $this->write_cmd("ZREVRANGE",$name,$first,$last,$opt); + $this->write_cmd("ZREVRANGE", $name, $first, $last, $opt); return $this->get_response(); } - public function zremrangebyscore($name, $min, $max) { + public function zremrangebyscore($name, $min, $max) + { $this->connect(); - $this->write_cmd("ZREMRANGEBYSCORE",$name,$min,$max); + $this->write_cmd("ZREMRANGEBYSCORE", $name, $min, $max); return $this->get_response(); } - public function select_db($name) { + public function select_db($name) + { $this->connect(); - $this->write_cmd("SELECT",$name); + $this->write_cmd("SELECT", $name); return $this->get_response(); } - - public function move($name, $db) { + + public function move($name, $db) + { $this->connect(); - $this->write_cmd("MOVE",$name,$db); + $this->write_cmd("MOVE", $name, $db); return $this->get_response(); } - - public function info($raw=false) { + + public function info($raw = false) + { $this->connect(); $this->write_cmd("INFO"); $info = array(); $data =& $this->get_response(); - if ($raw) return $data; + if ($raw) { + return $data; + } foreach (explode("\r\n", $data) as $l) { - if (!$l) + if (!$l) { continue; + } list($k, $v) = explode(':', $l, 2); $_v = strpos($v, '.') !== false ? (float)$v : (int)$v; $info[$k] = (string)$_v == $v ? $_v : $v; } return $info; } - - private function write($s) { + + private function write($s) + { while ($s) { $i = fwrite($this->_sock, $s); if ($i == 0) // || $i == strlen($s)) + { break; + } $s = substr($s, $i); } } - private function write_cmd() { + private function write_cmd() + { $args = func_get_args(); - $argv = Array(); - foreach($args as $a) { - if ($a === false) continue; + $argv = Array(); + foreach ($args as $a) { + if ($a === false) { + continue; + } if (is_array($a)) { - foreach($a as $e) { - if ($e === false) continue; + foreach ($a as $e) { + if ($e === false) { + continue; + } $argv[] = $e; } } else { - $argv[] = $a; + $argv[] = $a; } } - $query = "*".count($argv)."\r\n"; - foreach($argv as $a) { - $query .= "$".strlen($a)."\r\n".$a."\r\n"; + $query = "*" . count($argv) . "\r\n"; + foreach ($argv as $a) { + $query .= "$" . strlen($a) . "\r\n" . $a . "\r\n"; } $this->write($query); } - - private function read($len=1024) { - if ($s = fgets($this->_sock)) + + private function read($len = 1024) + { + if ($s = fgets($this->_sock)) { return $s; + } $this->disconnect(); trigger_error("Cannot read from socket.", E_USER_ERROR); } - - private function get_response() { + + private function get_response() + { $data = trim($this->read()); $c = $data[0]; $data = substr($data, 1); @@ -345,45 +412,51 @@ private function get_response() { return $data; case ':': $i = strpos($data, '.') !== false ? (int)$data : (float)$data; - if ((string)$i != $data) + if ((string)$i != $data) { trigger_error("Cannot convert data '$c$data' to integer", E_USER_ERROR); + } return $i; case '$': return $this->get_bulk_reply($c . $data); case '*': $num = (int)$data; - if ((string)$num != $data) + if ((string)$num != $data) { trigger_error("Cannot convert multi-response header '$data' to integer", E_USER_ERROR); + } $result = array(); - for ($i=0; $i<$num; $i++) + for ($i = 0; $i < $num; $i++) { $result[] =& $this->get_response(); + } return $result; default: trigger_error("Invalid reply type byte: '$c'"); } } - - private function get_bulk_reply($data=null) { - if ($data === null) + + private function get_bulk_reply($data = null) + { + if ($data === null) { $data = trim($this->read()); - if ($data == '$-1') + } + if ($data == '$-1') { return null; + } $c = $data[0]; $data = substr($data, 1); $bulklen = (int)$data; - if ((string)$bulklen != $data) + if ((string)$bulklen != $data) { trigger_error("Cannot convert bulk read header '$c$data' to integer", E_USER_ERROR); - if ($c != '$') + } + if ($c != '$') { trigger_error("Unkown response prefix for '$c$data'", E_USER_ERROR); + } $buffer = ''; while ($bulklen) { - $data = fread($this->_sock,$bulklen); + $data = fread($this->_sock, $bulklen); $bulklen -= strlen($data); $buffer .= $data; } - $crlf = fread($this->_sock,2); + $crlf = fread($this->_sock, 2); return $buffer; } } - -?> From 550a894876396096ed5ec17a1b2a5e679dfdace6 Mon Sep 17 00:00:00 2001 From: PXgamer Date: Thu, 29 Jun 2017 15:36:15 +0100 Subject: [PATCH 10/20] Added Language class to src/ instead of loading PHP files --- src/Language.php | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/Language.php diff --git a/src/Language.php b/src/Language.php new file mode 100644 index 0000000..5be37c2 --- /dev/null +++ b/src/Language.php @@ -0,0 +1,39 @@ +setLanguage(); + } + + public function setLanguage($sLanguageCode = 'en') + { + $this->sLanguage = $sLanguageCode; + + $sLanguageJsonFile = __DIR__ . '/../resources/languages/' . $this->sLanguage . '.json'; + + if (file_exists($sLanguageJsonFile)) { + $sLanguageJsonContents = file_get_contents($sLanguageJsonFile); + $this->aItems = json_decode($sLanguageJsonContents, true); + + return true; + } + + return false; + } + + public function item($sItemName) + { + if (isset($this->aItems[$sItemName])) { + return $this->aItems[$sItemName]; + } + + return false; + } +} \ No newline at end of file From 8ed1cb224e1fa93d5baa14388e27b288e1819fb3 Mon Sep 17 00:00:00 2001 From: PXgamer Date: Thu, 29 Jun 2017 15:36:43 +0100 Subject: [PATCH 11/20] Added styles.less to public/assets/css/ --- public/assets/css/styles.less | 49 +++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 public/assets/css/styles.less diff --git a/public/assets/css/styles.less b/public/assets/css/styles.less new file mode 100644 index 0000000..4f6984b --- /dev/null +++ b/public/assets/css/styles.less @@ -0,0 +1,49 @@ +@highlight_colour: #E2266E; +@link_colour: #005C9D; +@attribution_colour: #800080; + +html { + padding: 7px; +} + +a { + color: @highlight_colour; + &:hover { + color: @link_colour; + text-shadow: 0 0 30px #E2266E; + } +} + +div#attribution { + text-align: center; + margin-top: 5%; + a { + color: @attribution_colour; + } +} + +div#table { + margin-top: 5%; +} + +.alignleft { + text-align: left; + a { + color: @link_colour; + text-shadow: none; + &:hover { + color: @highlight_colour; + } + } +} + +colgroup { + col { + .width-percent-8 { + width: 8%; + } + .width-percent-10 { + width: 10%; + } + } +} \ No newline at end of file From 0f265289461430447815e594f5f75909a356bffd Mon Sep 17 00:00:00 2001 From: PXgamer Date: Thu, 29 Jun 2017 15:37:07 +0100 Subject: [PATCH 12/20] Moved favicon to public/ directory --- favicon.ico => public/favicon.ico | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename favicon.ico => public/favicon.ico (100%) mode change 100755 => 100644 diff --git a/favicon.ico b/public/favicon.ico old mode 100755 new mode 100644 similarity index 100% rename from favicon.ico rename to public/favicon.ico From d04ecb4526acc7b900af28f24df239bc77944908 Mon Sep 17 00:00:00 2001 From: PXgamer Date: Thu, 29 Jun 2017 15:37:28 +0100 Subject: [PATCH 13/20] Moved funcs functions to a new src/Formatting class --- src/Formatting.php | 75 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 src/Formatting.php diff --git a/src/Formatting.php b/src/Formatting.php new file mode 100644 index 0000000..ae50d5c --- /dev/null +++ b/src/Formatting.php @@ -0,0 +1,75 @@ += 0) && ($bytes < $kilobyte)) { + return $bytes . ' B'; + + } elseif (($bytes >= $kilobyte) && ($bytes < $megabyte)) { + return round($bytes / $kilobyte, $precision) . ' KB'; + + } elseif (($bytes >= $megabyte) && ($bytes < $gigabyte)) { + return round($bytes / $megabyte, $precision) . ' MB'; + + } elseif (($bytes >= $gigabyte) && ($bytes < $terabyte)) { + return round($bytes / $gigabyte, $precision) . ' GB'; + + } elseif ($bytes >= $terabyte) { + return round($bytes / $terabyte, $precision) . ' TB'; + } else { + return $bytes . ' B'; + } + } + + public static function totalTime($startTime = null) + { + return round(microtime(true) - $startTime, 3); + } + + public static function scrapeTorrent(Redis $oRedis, $url, $hash) + { + if ($res = $oRedis->get("plts" . $hash)) { + return (json_decode($res, true)); + } else { + if (is_array($url)) { + $url = $url[0][0]; + } + try { + $timeout = 2; + #$url = preg_replace("/announce", "", $url); + $url = str_replace("/announce", "", $url); + $scraper = new Trackers\UdpScraper($timeout); + $ret = $scraper->scrape($url, array($hash)); + $oRedis->set("plts" . $hash, json_encode($ret)); + $oRedis->expire("plts" . $hash, $_ENV['CACHE_SCRAPE']); + #print "
Scrape results for ".$url." were: ".json_encode($ret); + return ($ret); + } catch (Trackers\Exception\ScraperException $e) { + $ret = array( + $hash => array( + "seeders" => "?", + "leechers" => "?", + "completed" => 0 + ) + ); + $oRedis->set("plts" . $hash, json_encode($ret)); + $oRedis->expire("plts" . $hash, + $_ENV['CACHE_SCRAPE'] * 3); #The caching length of trackers that seem down is increased to help them to recover if they're overloaded and to increase page load speed (timeouts are expensive to load speed. + $offlineTrackers++; + return $ret; + } + } + + } +} \ No newline at end of file From 09f387c6c25403ec9e34d9a2a84d45231f6c5e89 Mon Sep 17 00:00:00 2001 From: PXgamer Date: Thu, 29 Jun 2017 15:37:38 +0100 Subject: [PATCH 14/20] Added templates --- table.php | 90 -------------------------- templates/index.html.php | 27 ++++++++ templates/layout.html.php | 12 ++++ templates/table.html.php | 133 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 172 insertions(+), 90 deletions(-) delete mode 100644 table.php create mode 100644 templates/index.html.php create mode 100644 templates/layout.html.php create mode 100644 templates/table.html.php diff --git a/table.php b/table.php deleted file mode 100644 index f1a928c..0000000 --- a/table.php +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - get('pltt'); -#$age = time()-($redis -> get('plttage')); -if($table){ - #print "Served from Redis."; - print $table; -} else{ - #print "Creating new table"; - # Now start building rows for each Torrent file in the torrent directory. # - $files = array_diff(scandir($CONFIG['tordir']), array('..', '.')); - $table = ""; - #var_dump($files); - - - foreach ($files as $file){ - $torrent = new Torrent($CONFIG['tordir'].$file); - $scrape = scrapeTorrent($torrent -> announce(), $torrent->hash_info()); - #print json_encode($scrape); - if($scrape[$torrent->hash_info()]['completed'] == 0){ - $completed = "?"; - } else { - $completed = $scrape[$torrent->hash_info()]['completed']; - } - $table .= ""; - $table .= ""; - $table .= ""; - $table .= ""; - $table .= ""; - $shared = $torrent->size()*$scrape[$torrent->hash_info()]['completed']; - $table .= ""; - $table .= ""; - - - - $table .= "\n"; - - - } - $table .= "
".$torrent->name().""; - $table .= "".$scrape[$torrent->hash_info()]['seeders']."".$scrape[$torrent->hash_info()]['leechers']." ".bytesToSize($torrent->size())." ".$completed." ".bytesToSize($shared)."
"; - #Store the table in Redis then print it. - #print "Generated table"; - $redis -> set('pltt', $table); - $redis -> expire('pltt', $CONFIG['tableCache']); - $redis -> set('pltgt', gmdate("H:i:s")); - #$redis -> set('plttage', time()); - print $table; - -} -/* -foreach ($files as $file){ -print ""; -$torrent = new BDecode($CONFIG['tordir'].$file); -print " ".$torrent->result['info']['name'].""; -print " "; -print " ".bytesToSize($torrent->result['info']['length']).""; -#print "


"; -print ""; - -} -*/ -?> - diff --git a/templates/index.html.php b/templates/index.html.php new file mode 100644 index 0000000..627d01c --- /dev/null +++ b/templates/index.html.php @@ -0,0 +1,27 @@ + +

item('heading') ?>

+ +
+
+ +
+
+ +
+ Powered by PHPlinuxTrack + 0.3, created by + John Cave. +
+ Updated at get('pltgt') ?> UTC. + Generated in seconds. +
+ + + \ No newline at end of file diff --git a/templates/layout.html.php b/templates/layout.html.php new file mode 100644 index 0000000..7bc6c80 --- /dev/null +++ b/templates/layout.html.php @@ -0,0 +1,12 @@ + + + <?= $_ENV['PROJECT_TITLE'] ?> + + + + + + + + + \ No newline at end of file diff --git a/templates/table.html.php b/templates/table.html.php new file mode 100644 index 0000000..87555b4 --- /dev/null +++ b/templates/table.html.php @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + get('pltt'); + + // $age = time()-($redis -> get('plttage')); + if ($sTable) { + echo $sTable; + } else { + // Now start building rows for each Torrent file in the torrent directory. # + $aFiles = array_diff(scandir($_ENV['TORRENT_DIRECTORY']), array('..', '.')); + $sTable = ""; + + foreach ($aFiles as $sFile) { + $oTorrent = new Torrent($_ENV['TORRENT_DIRECTORY'] . $sFile); + + // Skip if not a valid torrent file or no trackers exist + if ($oTorrent->announce() == null && isset($oTorrent->announce()[0][0])) { + continue; + } + + $sFirstAnnounce = $oTorrent->announce()[0][0]; + + if (strpos($sFirstAnnounce, 'http') > -1) { + $oScraper = new Trackers\HttpScraper(); + } elseif (strpos($sFirstAnnounce, 'udp')) { + $oScraper = new Trackers\UdpScraper(); + } else { + continue; + } + + $aUdpResults = $oScraper->scrape($sFirstAnnounce, $oTorrent->hash_info()); + $aUdpScrapeResult = $aUdpResults[$oTorrent->hash_info()]; + + if ($aUdpScrapeResult['completed'] == 0) { + $completed = "?"; + } else { + $completed = $aUdpScrapeResult['completed']; + } + + // Generate size of torrent in bytes + $sSizeInBytes = PhpLinuxTrack\Formatting::bytesToSize($oTorrent->size()); + + // Generate torrent shared size in bytes/plain + $iSharedSize = $oTorrent->size() * $aUdpScrapeResult['completed']; + $sSharedSize = PhpLinuxTrack\Formatting::bytesToSize($iSharedSize); + + $sTable .= << + + + + + + + + +HTML; + + } + + // Store the table in Redis then print it. + $oRedis->set('pltt', $sTable); + $oRedis->expire('pltt', $_ENV['CACHE_TABLE']); + $oRedis->set('pltgt', gmdate("H:i:s")); + } + ?> + + +
item('name') ?> + + + + + + + + + + + +
+ {$oTorrent->name()} + + {$aUdpScrapeResult['seeders']} + + {$aUdpScrapeResult['leechers']} + + {$sSizeInBytes} + + {$completed} + + {$sSharedSize} + + + + + + + + + +
\ No newline at end of file From 78e2b29f38ffbe41838fb1ecd3a94951e01be07b Mon Sep 17 00:00:00 2001 From: PXgamer Date: Thu, 29 Jun 2017 15:38:45 +0100 Subject: [PATCH 15/20] Redone index to load files using Composer + templates It now also loads the config into the environment --- index.php | 42 ------------------------------------------ public/index.php | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 42 deletions(-) delete mode 100755 index.php create mode 100644 public/index.php diff --git a/index.php b/index.php deleted file mode 100755 index ea4ef53..0000000 --- a/index.php +++ /dev/null @@ -1,42 +0,0 @@ - - - -<?=$CONFIG['title']?> - - - - - - - -
-
- - -
-
- - - -
-Powered by PHPlinuxTrack 0.3, created by John Cave.
-Updated at get('pltgt')?> UTC. Generated in seconds. -
- - - - - - diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..7a67e63 --- /dev/null +++ b/public/index.php @@ -0,0 +1,25 @@ +load(); + +$oDotEnv->required(['PROJECT_TITLE', 'LANGUAGE', 'TORRENT_DIRECTORY', 'TORRENTS_WEB_DIR', 'REDIS_HOST'])->notEmpty(); +$oDotEnv->required(['REDIS_PORT', 'CACHE_SCRAPE', 'CACHE_TABLE'])->isInteger()->notEmpty(); + +$oLanguage = new PhpLinuxTrack\Language(); +$oLanguage->setLanguage($_ENV['LANGUAGE']); + +if (!class_exists('Redis')) { + $oRedis = new PhpLinuxTrack\Redis($_ENV['REDIS_HOST'], $_ENV['REDIS_PORT']); +} else { + $oRedis = new Redis(); + $oRedis->connect($_ENV['REDIS_HOST'], $_ENV['REDIS_PORT']); +} + +include __DIR__ . '/../templates/layout.html.php'; \ No newline at end of file From 26dba14b69f6a7a5b37e3e71ae8ec59638b4a827 Mon Sep 17 00:00:00 2001 From: PXgamer Date: Thu, 29 Jun 2017 15:39:41 +0100 Subject: [PATCH 16/20] Added placeholder for default torrents folder --- public/torrents/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 public/torrents/.gitignore diff --git a/public/torrents/.gitignore b/public/torrents/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/public/torrents/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file From e4da0344a59acd6bc4df7877c58424d44701f879 Mon Sep 17 00:00:00 2001 From: PXgamer Date: Thu, 29 Jun 2017 15:45:40 +0100 Subject: [PATCH 17/20] Removed redundant 'center' tags from JSON --- resources/languages/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/languages/en.json b/resources/languages/en.json index da6c4f0..790aff2 100644 --- a/resources/languages/en.json +++ b/resources/languages/en.json @@ -1,6 +1,6 @@ { "name": "Name", - "heading": "
Welcome to PHPlinuxTrack!<\/center>", + "heading": "Welcome to PHPlinuxTrack!", "seeders": "Seeders", "leechers": "Downloaders", "size": "Size", From 7dc773748b917e8b233207b0f80a0a9fbfebf199 Mon Sep 17 00:00:00 2001 From: PXgamer Date: Thu, 29 Jun 2017 15:49:01 +0100 Subject: [PATCH 18/20] Updated README with better formatting, and more information --- README.md | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 9a31455..ecf9673 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,41 @@ # phplinuxtrack -PHPlinuxTrack is a simple PHP torrent statistics script designed to ease and beautify the display of the torrent files of a linux distro or other software project. It allows visitors to see important information about your project's torrents at a glance using its icon-based interface. It was created by [John Cave](https://johncave.co.nz) of the [OpenMandriva](https://www.openmandriva.org) project. -Basically, PHPlinuxTrack makes a pretty display of the seeders, downloaders and total transfers of your torrent files. It does this using Bootstrap as the frontend, PHP as the scripting language, as Redis as a caching data store. +PHPlinuxTrack is a simple PHP torrent statistics script designed to ease and beautify the display of the torrent files of a linux distro or other software project. + +It allows visitors to see important information about your project's torrents at a glance using its icon-based interface. It was created by [John Cave](https://johncave.co.nz) of the [OpenMandriva](https://www.openmandriva.org) project. + +Basically, PHPlinuxTrack makes a pretty display of the seeders, downloaders and total transfers of your torrent files. It does this using [Bootstrap] for the frontend, [PHP] as the scripting language, and [Redis] as a caching data store. ## Features - Attractive, icon-based interface. - Doesn't look like it's seen multiple decades. - Magnet link generation. -- Asynchronous loading of the torrent data table. - Very easy to add new torrents. -- All assets served by CDNJS or my own CDN. +- All assets served by CDNs - Easy to translate (if you feel the icon-based interface needs translating). +- Composer compatible +- PSR-2 and PSR-4 compliant The requirements are: - redis - - PHP5 - - Consider installing [phpredis](https://github.com/phpredis/phpredis) to get an extra speed boost. + - PHP5+ + - (optional) [PhpRedis] for a speed boost ## Installation - - Unzip the files to your webroot, or a subdirectory thereof. - - Upload your torrent files to the torrents/ subdirectory of the PHPlinuxTrack directory. - - Set a suitable header message in inc/lang/en.php. - - Set colours in inc/config.php that fit with your project's website colour scheme. - - If using Redis on localhost:6379, setup should now be complete. If not, you'll have to update the relevant settings in inc/config.php. - - If you want to use a funky directory for your torrents, you'll have to set this up in config.php, along with giving PHPlinuxTrack a suitable web path to link to downloads of the torrent files. + - Unzip the files to your web root, or a subdirectory thereof. + - Upload your torrent files to the `torrents/` subdirectory of the `public` directory. + - Set a suitable header message in `resources/languages/. + - If using Redis on `localhost:6379`, setup should now be complete. If not, you'll have to update the relevant settings in `config/.env`. + - If you want to use a different directory for your torrents, you'll have to set this up in `config/.env`, along with giving PHPlinuxTrack a suitable web path to link to downloads of the torrent files. - You can adjust caching parameters as you see fit. However, I recommend setting a value higher than 300 for the scrapeCache directive, to save the bandwidth of the generous tracker providers out there. -## Wishlist +## To Do - Support for Memcache as well as Redis. -- Changing the link in the Name column (between .torrent file or magnet link) based on number of seeders. +- Changing the link in the Name column (between `.torrent` file or [magnet] link) based on number of seeders. - Prettier loading animation. -## Note -Placing non .torrent files in the torrent directory will cause no end of errors. I consider this software to be a beta, but as it does no writing, it shouldn't corrupt any data. +[PhpRedis]: https://github.com/phpredis/phpredis +[Bootstrap]: https://getbootstrap.com +[PHP]: https://php.net +[Redis]: https://redis.io +[magnet]: https://en.wikipedia.org/wiki/Magnet_URI_scheme From f8b2541fe8f32cc1f744ce7f1cc6b9792755d107 Mon Sep 17 00:00:00 2001 From: PXgamer Date: Thu, 29 Jun 2017 15:50:51 +0100 Subject: [PATCH 19/20] Updated project version to 1.0.0 --- templates/index.html.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/index.html.php b/templates/index.html.php index 627d01c..c060f13 100644 --- a/templates/index.html.php +++ b/templates/index.html.php @@ -16,7 +16,7 @@
Powered by PHPlinuxTrack - 0.3, created by + 1.0.0, created by John Cave.
Updated at get('pltgt') ?> UTC. From 77da2dfc3c80a515f676b6ec7ea7405b761ddb5a Mon Sep 17 00:00:00 2001 From: PXgamer Date: Tue, 11 Jul 2017 16:50:23 +0100 Subject: [PATCH 20/20] Fixed config values mentioned in comment 314411418 --- config/.env.example | 2 +- public/index.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/.env.example b/config/.env.example index 837d037..15ad27e 100644 --- a/config/.env.example +++ b/config/.env.example @@ -4,7 +4,7 @@ LANGUAGE = "en" # Torrent File Configuration TORRENT_DIRECTORY = "torrents/" # Location of the torrents directory -TORRENTS_WEB_DIR = "/torrents/" # Location of the torrents dir, for use in links +TORRENT_WEB_DIRECTORY = "/torrents/" # Location of the torrents dir, for use in links # Redis Configuration REDIS_HOST = "127.0.0.1" # The IP of your Redis instance diff --git a/public/index.php b/public/index.php index 7a67e63..6e0fb52 100644 --- a/public/index.php +++ b/public/index.php @@ -9,7 +9,7 @@ $oDotEnv = new Dotenv(__DIR__ . '/../config'); $oDotEnv->load(); -$oDotEnv->required(['PROJECT_TITLE', 'LANGUAGE', 'TORRENT_DIRECTORY', 'TORRENTS_WEB_DIR', 'REDIS_HOST'])->notEmpty(); +$oDotEnv->required(['PROJECT_TITLE', 'LANGUAGE', 'TORRENT_DIRECTORY', 'TORRENT_WEB_DIRECTORY', 'REDIS_HOST'])->notEmpty(); $oDotEnv->required(['REDIS_PORT', 'CACHE_SCRAPE', 'CACHE_TABLE'])->isInteger()->notEmpty(); $oLanguage = new PhpLinuxTrack\Language();