root/include/class.bencode.php

Revision 324, 25.8 kB (checked in by Nafania, 1 year ago)

Много мелких изменений. Если используется xbtt, то надо обязательно скачать новую версию на форуме.
Добавлен файл cron.php - если есть возможность, то пускайте cron таски через него. Особенно если используете массовую рассылку почты.

Line 
1 <?php
2 /**</span>
3 <span class="code-comment"> * Torrent
4  *
5  * PHP version 5 only
6  *
7  * LICENSE: This source file is subject to version 3 of the GNU GPL
8  * that is available through the world-wide-web at the following URI:
9  * http://www.gnu.org/licenses/gpl.html. If you did not receive a copy of
10  * the GNU GPL License and are unable to obtain it through the web, please
11  * send a note to adrien.gibrat@gmail.com so I can mail you a copy.
12  *
13  * 1) Features:
14  * - Decode torrent file or data
15  * - Build torrent from source folder/file(s)
16  * - Silent Exception error system
17  *
18  * 2) Usage example
19  * <code>
20     require_once 'Torrent.php';
21
22     // get torrent infos
23     $torrent = new Torrent( './test.torrent' );
24     echo '<br>private: ', $torrent->is_private() ? 'yes' : 'no',
25          '<br>annonce: ';
26     var_dump( $torrent->announce() );
27     echo '<br>name: ', $torrent->name(),
28          '<br>comment: ', $torrent->comment(),
29          '<br>piece_length: ', $torrent->piece_length(),
30          '<br>size: ', $torrent->size( 2 ),
31          '<br>hash info: ', $torrent->hash_info(),
32          '<br>stats: ';
33     var_dump( $torrent->scrape() );
34     echo '<br>content: ';
35     var_dump( $torrent->content() );
36     echo '<br>source: ',
37          $torrent;
38
39     // create torrent
40     $torrent = new Torrent( array( 'test.mp3', 'test.jpg' ), 'http://torrent.tracker/annonce' );
41     $torrent->save('test.torrent'); // save to disk
42
43     // modify torrent
44     $torrent->announce('http://alternate-torrent.tracker/annonce'); // add a tracker
45     $torrent->announce(false); // reset announce trackers
46     $torrent->announce(array('http://torrent.tracker/annonce', 'http://alternate-torrent.tracker/annonce')); // set tracker(s), it also works with a 'one tracker' array...
47     $torrent->announce(array(array('http://torrent.tracker/annonce', 'http://alternate-torrent.tracker/annonce'), 'http://another-torrent.tracker/annonce')); // set tiered trackers
48     $torrent->comment('hello world');
49     $torrent->name('test torrent');
50     $torrent->is_private(true);
51     $torrent->httpseeds('http://file-hosting.domain/path/'); // Bittornado implementation
52     $torrent->url_list(array('http://file-hosting.domain/path/','http://another-file-hosting.domain/path/')); //
53     GetRight implementation
54
55     // print errors
56     if ( $errors = $torrent->errors() )
57         var_dump( $errors );
58
59     // send to user
60     $torrent->send();
61  * </code>
62  *
63  * @author        Adrien Gibrat <adrien.gibrat@gmail.com>
64  * @copyleft    2008 - Just use it!
65  * @license        http://www.gnu.org/licenses/gpl.html GNU General Public License version 3
66  * @version        Release: 0.5
67  */
68 class Torrent {</span>
69 <span class="code-keyword">
70     /**
71     * @var array List of error occured
72     */
73     static public $errors = array();
74
75     /** Read and decode torrent file/data OR build a torrent from source folder/file(s)
76      * Supported signatures:
77      * - Torrent(); // get an instance (usefull to scrape an check errors)
78      * - Torrent( string $torrent ); // analyse a torrent file
79      * - Torrent( string $torrent, string $announce );
80      * - Torrent( string $torrent, array $meta );
81      * - Torrent( string $file_or_folder ); // create a torrent file
82      * - Torrent( string $file_or_folder, string $announce_url, [int $piece_length] );
83      * - Torrent( string $file_or_folder, array $meta, [int $piece_length] );
84      * - Torrent( array $files_list );
85      * - Torrent( array $files_list, string $announce_url, [int $piece_length] );
86      * - Torrent( array $files_list, array $meta, [int $piece_length] );
87      * @param string|array torrent to read or source folder/file(s) (optional, to get an instance)
88      * @param string|array announce url or meta informations (optional)
89      * @param int piece length (optional)
90      */
91     public function __construct ( $data = null, $meta = array(), $piece_length = 256 ) {
92         if ( is_null( $data ) )
93             return false;
94         if ( $piece_length < 32 || $piece_length > 4096 )
95             return ! array_unshift( self::$errors, new Exception( 'Invalid piece lenth, must be between 32 and 4096' ) );
96         if ( is_string( $meta ) )
97             $meta = array( 'announce' => $meta );
98         if ( $this->build( $data, $piece_length * 1024 ) )
99             $this->touch();
100         else
101             $meta = array_merge( $meta, $this->decode( $data ) );
102         foreach( $meta as $key => $value )
103             $this->{$key} = $value;
104     }
105
106     /** Convert the current Torrent instance in torrent format
107      * @return string encoded torrent data
108      */
109     public function __toString() {
110         return $this->encode( $this );
111     }
112
113     /** Return last error message
114      * @return string|boolean error message or false if none
115      */
116     public function error() {
117         return empty( self::$errors ) ?
118             false :
119             self::$errors[0]->getMessage();
120     }
121
122     /** Return Errors
123      * @return array|boolean error list or false if none
124      */
125     public function errors() {
126         return empty( self::$errors ) ?
127             false :
128             self::$errors;
129     }
130
131     /**** Getters and setters ****/
132
133     /** Getter and setter of torrent announce url / list
134      * If the argument is a string, announce url is added to announce list (or set as announce if announce is not set)
135      * 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
136      * If the argument is false announce url & list are unset
137      * @param null|false|string|array announce url / list, reset all if false (optional, if omitted it's a getter)
138      * @return string|array|null announce url / list or null if not set
139      */
140     public function announce ( $announce = null ) {
141         if ( is_null( $announce ) )
142             return ! isset( $this->{'announce-list'} ) ?
143                 isset( $this->announce ) ? $this->announce : null :
144                 $this->{'announce-list'};
145         $this->touch();
146         if ( is_string( $announce ) && isset( $this->announce ) )
147             return $this->{'announce-list'} = self::announce_list( isset( $this->{'announce-list'} ) ? $this->{'announce-list'} : $this->announce, $announce );
148         unset( $this->{'announce-list'} );
149         if ( is_array( $announce ) || is_object( $announce ) )
150             if ( ( $this->announce = self::first_announce( $announce ) ) && count( $announce ) > 1 )
151                 return $this->{'announce-list'} = self::announce_list( $announce );
152             else
153                 return $this->announce;
154         if ( ! isset( $this->announce ) && $announce )
155             return $this->announce = (string) $announce;
156         unset( $this->announce );
157     }
158
159     /** Getter and setter of torrent comment
160      * @param null|string comment (optional, if omitted it's a getter)
161      * @return string|null comment or null if not set
162      */
163     public function comment ( $comment = null ) {
164         return is_null( $comment ) ?
165             isset( $this->comment ) ? $this->comment : null :
166             $this->touch( $this->comment = (string) $comment );
167     }
168
169     /** Getter and setter of torrent name
170      * @param null|string name (optional, if omitted it's a getter)
171      * @return string|null name or null if not set
172      */
173     public function name ( $name = null ) {
174         return is_null( $name ) ?
175             isset( $this->info['name'] ) ? $this->info['name'] : null :
176             $this->touch( $this->info['name'] = (string) $name );
177     }
178
179     /** Getter and setter of private flag
180      * @param null|boolean is private or not (optional, if omitted it's a getter)
181      * @return boolean private flag
182      */
183     public function is_private ( $private = null ) {
184         return is_null( $private ) ?
185             ! empty( $this->info['private'] ) :
186             $this->touch( $this->info['private'] = $private ? 1 : 0 );
187     }
188
189     /** Getter and setter of webseed(s) url list ( GetRight implementation )
190      * @param null|string|array webseed or webseeds mirror list (optional, if omitted it's a getter)
191      * @return string|array|null webseed(s) or null if not set
192      */
193     public function url_list ( $urls = null ) {
194         return is_null( $urls ) ?
195             isset( $this->{'url-list'} ) ? $this->{'url-list'} : null :
196             $this->touch( $this->{'url-list'} = is_string( $urls) ? $urls : (array) $urls );
197     }
198
199     /** Getter and setter of httpseed(s) url list ( Bittornado implementation )
200      * @param null|string|array httpseed or httpseeds mirror list (optional, if omitted it's a getter)
201      * @return array|null httpseed(s) or null if not set
202      */
203     public function httpseeds ( $urls = null ) {
204         return is_null( $urls ) ?
205             isset( $this->httpseeds ) ? $this->httpseeds : null :
206             $this->touch( $this->httpseeds = (array) $urls );
207     }
208
209     /**** Analyze BitTorrent ****/
210
211     /** Get piece length
212      * @return integer piece length or null if not set
213      */
214     public function piece_length () {
215         return isset( $this->info['piece length'] ) ?
216             $this->info['piece length'] :
217             null;
218     }
219
220     /** Compute hash info
221      * @return string hash info or null if info not set
222      */
223     public function hash_info () {
224         return isset( $this->info ) ?
225             pack('H*', sha1( self::encode( $this->info )) ) :
226             null;
227     }
228
229     /** List torrent content
230      * @param integer|null size precision (optional, if omitted returns sizes in bytes)
231      * @return array file(s) and size(s) list, files as keys and sizes as values
232      */
233     public function content ( $precision = null ) {
234         $files = array();
235         if ( is_array( @$this->info['files'] ) )
236             foreach ( $this->info['files'] as $file )
237                 $files[self::path( $file['path'], $this->info['name'] )] = $precision ?
238                     self::format( $file['length'], $precision ) :
239                     $file['length'];
240         elseif ( isset( $this->info['name'] ) )
241                 $files[$this->info['name']] = $precision ?
242                     self::format( $this->info['length'], $precision ) :
243                     $this->info['length'];
244         return $files;
245     }
246
247     /** List torrent content pieces and offset(s)
248      * @return array file(s) and pieces/offset(s) list, file(s) as keys and pieces/offset(s) as values
249      */
250     public function offset () {
251         $files = array();
252         $size = 0;
253         if ( is_array( $this->info['files'] ) )
254             foreach ( $this->info['files'] as $file )
255                 $files[self::path( $file['path'], $this->info['name'] )] = array(
256                     'startpiece'    => floor( $size / $this->info['piece length'] ),
257                     'offset'        => fmod( $size, $this->info['piece length'] ),
258                     'size'            => $size += $file['length'],
259                     'endpiece'        => floor( $size / $this->info['piece length'] )
260                 );
261         elseif ( isset( $this->info['name'] ) )
262                 $files[$this->info['name']] = array(
263                     'startpiece'    => 0,
264                     'offset'        => 0,
265                     'size'            => $this->info['length'],
266                     'endpiece'        => floor( $this->info['length'] / $this->info['piece length'] )
267                 );
268         return $files;
269     }
270
271     /** Sum torrent content size
272      * @param integer|null size precision (optional, if omitted returns size in bytes)
273      * @return integer|string file(s) size
274      */
275     public function size ( $precision = null ) {
276         $size = 0;
277         if ( is_array( $this->info['files'] ) )
278             foreach ( $this->info['files'] as $file )
279                 $size += $file['length'];
280         elseif ( isset( $this->info['name'] ) )
281                 $size = $this->info['length'];
282         return is_null( $precision ) ?
283             $size :
284             self::format( $size, $precision );
285     }
286
287     /** Request torrent statistics from scrape page
288      * @param string announce or scrape page url (optional, to request an alternative tracker BUT mandatory for static call)
289      * @param string torrent hash info (optional: ONLY for static call)
290      * @return array tracker torrent statistics
291      */
292     /* static */ public function scrape ( $announce = null, $hash_info = null ) {
293         if ( ! ini_get( 'allow_url_fopen' ) )
294             return ! array_unshift( self::$errors, new Exception( '"allow_url_fopen" must be enabled' ) );
295         //$packed_hash = pack('H*', $hash_info ? $hash_info : $this->hash_info() );
296         $hash_info = $hash_info ? $hash_info : $this->hash_info();
297         $res = stream_context_create(array(
298             'http' => array(
299                 'timeout' => 5
300             ))
301         );
302         if ( ! $scrape_data = @file_get_contents( str_ireplace( '/announce', '/scrape', $announce ? $announce : $this->announce ) . '?info_hash=' . urlencode( $hash_info ), 0, $res ) )
303             return ! array_unshift( self::$errors, new Exception( 'Tracker request failed' ) );
304         $stats = self::decode_data( $scrape_data );
305         return isset( $stats['files'][$hash_info] ) ?
306             $stats['files'][$hash_info] :
307             ! array_unshift( self::$errors, new Exception( 'Invalid scrape data' ) );
308     }
309
310     /**** Save and Send ****/
311
312     /** Save torrent file to disk
313      * @param null|string name of the file (optional)
314      * @return boolean file has been saved or not
315      */
316     public function save ( $filename = null ) {
317         return file_put_contents( is_null( $filename ) ? $this->info['name'] . '.torrent' : $filename, (string) $this );
318     }
319
320     /** Send torrent file to client
321      * @param null|string name of the file (optional)
322      * @return void script exit
323      */
324     public function send ( $filename = null ) {
325         $data = (string) $this;
326          header( 'Content-Type: application/x-bittorrent' );
327         header( 'Content-Disposition: attachment; filename="' . ( is_null( $filename ) ? $this->info['name'] . '.torrent' : $filename ) . '"' );
328         header( 'Content-transfer-encoding: binary' );
329         header( 'Content-length: ' . strlen( $data ) );
330         gc( $data );
331     }
332
333     /**** Encode BitTorrent ****/
334
335     /** Encode torrent data
336      * @param mixed data to encode
337      * @return string torrent encoded data
338      */
339     static protected function encode ( $mixed ) {
340         switch ( gettype( $mixed ) ) {
341             case 'integer':
342             case 'double':
343                 return self::encode_integer( $mixed );
344             case 'array':
345             case 'object':
346                 return self::encode_array( (array) $mixed );
347             default:
348                 return self::encode_string( (string) $mixed );
349         }
350     }
351
352     /** Encode torrent string
353      * @param string string to encode
354      * @return string encoded string
355      */
356     static private function encode_string ( $string ) {
357         return strlen( $string ) . ':' . $string;
358     }
359
360     /** Encode torrent integer
361      * @param integer integer to encode
362      * @return string encoded integer
363      */
364     static private function encode_integer ( $integer ) {
365         return 'i' . $integer . 'e';
366     }
367
368     /** Encode torrent dictionary or list
369      * @param array array to encode
370      * @return string encoded dictionary or list
371      */
372     static private function encode_array ( $array ) {
373         if ( self::is_list( $array ) ) {
374             $return = 'l';
375             foreach ( $array as $value )
376                 $return .= self::encode( $value );
377         } else {
378             ksort( $array, SORT_STRING );
379             $return = 'd';
380             foreach ( $array as $key => $value )
381                 $return .= self::encode( strval( $key ) ) . self::encode( $value );
382         }
383         return $return . 'e';
384     }
385
386     /**** Decode BitTorrent ****/
387
388     /** Decode torrent data or file
389      * @param string data or file path to decode
390      * @return array decoded torrent data
391      */
392     static protected function decode ( $string ) {
393         $data = is_file( $string ) || self::url_exists( $string ) ?
394             file_get_contents( $string ) :
395             $string;
396         return (array) self::decode_data( $data );
397     }
398
399     /** Decode torrent data
400      * @param string data to decode
401      * @return array decoded torrent data
402      */
403     static private function decode_data ( & $data ) {
404         switch( self::char( $data ) ) {
405         case 'i':
406             $data = substr( $data, 1 );
407             return self::decode_integer( $data );
408         case 'l':
409             $data = substr( $data, 1 );
410             return self::decode_list( $data );
411         case 'd':
412             $data = substr( $data, 1 );
413             return self::decode_dictionary( $data );
414         default:
415             return self::decode_string( $data );
416         }
417     }
418
419     /** Decode torrent dictionary
420      * @param string data to decode
421      * @return array decoded dictionary
422      */
423     static private function decode_dictionary ( & $data ) {
424         $dictionary = array();
425         $previous = null;
426         while ( ( $char = self::char( $data ) ) != 'e' ) {
427             if ( $char === false )
428                 return ! array_unshift( self::$errors, new Exception( 'Unterminated dictionary' ) );
429             if ( ! ctype_digit( $char ) )
430                 return ! array_unshift( self::$errors, new Exception( 'Invalid dictionary key' ) );
431             $key = self::decode_string( $data );
432             if ( isset( $dictionary[$key] ) )
433                 return ! array_unshift( self::$errors, new Exception( 'Duplicate dictionary key' ) );
434             if ( $key < $previous )
435                 return ! array_unshift( self::$errors, new Exception( 'Missorted dictionary key' ) );
436             $dictionary[$key] = self::decode_data( $data );
437             $previous = $key;
438         }
439         $data = substr( $data, 1 );
440         return $dictionary;
441     }
442
443     /** Decode torrent list
444      * @param string data to decode
445      * @return array decoded list
446      */
447     static private function decode_list ( & $data ) {
448         $list = array();
449         while ( ( $char = self::char( $data ) ) != 'e' ) {
450             if ( $char === false )
451                 return ! array_unshift( self::$errors, new Exception( 'Unterminated list' ) );
452             $list[] = self::decode_data( $data );
453         }
454         $data = substr( $data, 1 );
455         return $list;
456     }
457
458     /** Decode torrent string
459      * @param string data to decode
460      * @return string decoded string
461      */
462     static private function decode_string ( & $data ) {
463         if ( self::char( $data ) === '0' && substr( $data, 1, 1 ) != ':' )
464             array_unshift( self::$errors, new Exception( 'Invalid string length, leading zero' ) );
465         if ( ! $colon = @strpos( $data, ':' ) )
466             return ! array_unshift( self::$errors, new Exception( 'Invalid string length, colon not found' ) );
467         $length = intval( substr( $data, 0, $colon ) );
468         if ( $length + $colon + 1 > strlen( $data ) )
469             return ! array_unshift( self::$errors, new Exception( 'Invalid string, input too short for string length' ) );
470         $string = substr( $data, $colon + 1, $length );
471         $data = substr( $data, $colon + $length + 1 );
472         return $string;
473     }
474
475     /** Decode torrent integer
476      * @param string data to decode
477      * @return integer decoded integer
478      */
479     static private function decode_integer ( & $data ) {
480         $start = 0;
481         $end    = strpos( $data, 'e');
482         if ( $end === 0 )
483             array_unshift( self::$errors, new Exception( 'Empty integer' ) );
484         if ( self::char( $data ) == '-' )
485             $start++;
486         if ( substr( $data, $start, 1 ) == '0' && ( $start != 0 || $end > $start + 1 ) )
487             array_unshift( self::$errors, new Exception( 'Leading zero in integer' ) );
488         if ( ! ctype_digit( substr( $data, $start, $end ) ) )
489             array_unshift( self::$errors, new Exception( 'Non-digit characters in integer' ) );
490         $integer = substr( $data, 0, $end );
491         $data = substr( $data, $end + 1 );
492         return $integer + 0;
493     }
494
495     /**** Internal Helpers ****/
496
497     /** Build torrent info
498      * @param string|array source folder/file(s) path
499      * @param integer piece length
500      * @return array|boolean torrent info or false if data isn't folder/file(s)
501      */
502     protected function build ( $data, $piece_length ) {
503         if ( is_null( $data ) )
504             return false;
505         elseif ( is_array( $data ) && self::is_list( $data ) )
506             return $this->info = $this->files( $data, $piece_length );
507         elseif ( is_dir( $data ) )
508             return $this->info = $this->folder( $data, $piece_length );
509         elseif ( ( is_file( $data ) || self::url_exists( $data ) ) && ! self::is_torrent( $data ) )
510             return $this->info = $this->file( $data, $piece_length );
511         else
512             return false;
513     }
514
515     /** Set torrent creator and creation date
516      * @param any param
517      * @return any param
518      */
519     protected function touch ( $void = null ) {
520         //$this->{'created by'}        = 'Torrent PHP Class - Adrien Gibrat';
521         //$this->{'creation date'}    = time();
522         return $void;
523     }
524
525     /** Build announce list
526      * @param string|array announce url / list
527      * @param string|array announce url / list to add (optionnal)
528      * @return array announce list (array of arrays)
529      */
530     static protected function announce_list( $announce, $merge = array() ) {
531         return array_map( create_function( '$a', 'return (array) $a;' ), array_merge( (array) $announce, (array) $merge ) );
532     }
533
534     /** Get the first announce url in a list
535      * @param array announce list (array of arrays if tiered trackers)
536      * @return string first announce url
537      */
538     static protected function first_announce( $announce ) {
539         while ( is_array( $announce ) )
540             $announce = reset( $announce );
541         return $announce;
542     }
543
544     /** Helper to pack data hash
545      * @param string data
546      * @return string packed data hash
547      */
548     static protected function pack ( & $data ) {
549         return pack('H*', sha1( $data ) ) . ( $data = '' );
550     }
551
552     /** Helper to build file path
553      * @param array file path
554      * @param string base folder
555      * @return string real file path
556      */
557     static protected function path ( $path, $folder ) {
558         array_unshift( $path, $folder );
559         return join( DIRECTORY_SEPARATOR, $path );
560     }
561
562     /** Helper to test if an array is a list
563      * @param array array to test
564      * @return boolean is the array a list or not
565      */
566     static protected function is_list ( $array ) {
567         foreach ( array_keys( $array ) as $key )
568             if ( ! is_int( $key ) )
569                 return false;
570         return true;
571     }
572
573     /** Build torrent info from single file
574      * @param string file path
575      * @param integer piece length
576      * @return array torrent info
577      */
578     private function file ( $file, $piece_length ) {
579         if ( ! $handle = self::fopen( $file, $size = self::filesize( $file ) ) )
580             return ! array_unshift( self::$errors, new Exception( 'Failed to open file: "' . $file . '"' ) );
581         $pieces = '';
582         while ( ! feof( $handle ) )
583             $pieces .= self::pack( fread( $handle, $piece_length ) );
584         fclose( $handle );
585         return array(
586             'length'        => $size,
587             'name'            => basename( $file ),
588             'piece length'    => $piece_length,
589             'pieces'        => $pieces
590         );
591     }
592
593     /** Build torrent info from files
594      * @param array file list
595      * @param integer piece length
596      * @return array torrent info
597      */
598     private function files ( $files, $piece_length ) {
599         $files = array_map( 'realpath', $files );
600         sort( $files );
601         usort( $files, create_function( '$a,$b', 'return strrpos($a,DIRECTORY_SEPARATOR)-strrpos($b,DIRECTORY_SEPARATOR);' ) );
602         $path    = explode( DIRECTORY_SEPARATOR, dirname( realpath( current( $files ) ) ) );
603         $length    = $piece_length;
604         $piece    = $pieces = '';
605         foreach ( $files as $i => $file ) {
606             if ( $path != array_intersect_assoc( $file_path = explode( DIRECTORY_SEPARATOR, $file ), $path ) )
607                 continue array_unshift( self::$errors, new Exception( 'Files must be in the same folder: "' . $file . '" discarded' ) );
608             if ( ! $handle = self::fopen( $file, $filesize = self::filesize( $file ) ) )
609                 continue array_unshift( self::$errors, new Exception( 'Failed to open file: "' . $file . '" discarded' ) );
610             while ( ! feof( $handle ) )
611                 if ( ( $length = strlen( $piece .= fread( $handle, $length ) ) ) == $piece_length )
612                     $pieces .= self::pack( $piece );
613                 else
614                     $length = $piece_length - $length;
615             fclose( $handle );
616             $info_files[$i] = array(
617                 'length'    => $filesize,
618                 'path'        => array_diff( $file_path, $path )
619             );
620         }
621         switch ( count( $info_files ) ) {
622             case 0:
623                 return false;
624             case 1:
625                 return $this->file( $files[key( $info_files )], $piece_length );
626             default:
627                 return array(
628                     'files'             => $info_files,
629                     'name'            => end( $path ),
630                     'piece length'    => $piece_length,
631                     'pieces'        => $pieces . ( $piece ? self::pack( $piece ) : '' )
632                 );
633         }
634     }
635
636     /** Build torrent info from folder content
637      * @param string folder path
638      * @param integer piece length
639      * @return array torrent info
640      */
641     private function folder ( $dir, $piece_length ) {
642         return $this->files( self::scandir( $dir ), $piece_length );
643     }
644
645     /** Helper to return the first char of encoded data
646      * @param string encoded data
647      * @return string|boolean first char of encoded data or false if empty data
648      */
649     static private function char ( $data ) {
650         return empty( $data ) ?
651             false :
652             substr( $data, 0, 1 );
653     }
654
655     /**** Public Helpers ****/
656
657     /** Helper to format size in bytes to human readable
658      * @param integer size in bytes
659      * @param integer precision after coma
660      * @return string formated size in appropriate unit
661      */
662     static public function format ( $size, $precision = 2 ) {
663         $units = array ('octets', 'Ko', 'Mo', 'Go', 'To');
664         while( ( $next = next( $units ) ) && $size > 1024 )
665             $size /= 1024;
666         return round( $size, $precision ) . ' ' . ( $next ? prev( $units ) : end( $units ) );
667     }
668
669     /** Helper to return filesize (even bigger than 2Gb -linux only- and distant files size)
670      * @param string file path
671      * @return double|boolean filesize or false if error
672      */
673     static public function filesize ( $file ) {
674         if ( is_file( $file ) )
675             return (double) sprintf( '%u', @filesize( $file ) );
676         else if ( $content_length = preg_grep( $pattern = '#^Content-Length:\s+(\d+)$#i', (array) @get_headers( $file ) ) )
677             return (int) preg_replace( $pattern, '$1', reset( $content_length ) );
678     }
679
680     /** Helper to open file to read (even bigger than 2Gb, linux only)
681      * @param string file path
682      * @param integer|double file size (optional)
683      * @return ressource|boolean file handle or false if error
684      */
685     static public function fopen ( $file, $size = null ) {
686         if ( ( is_null( $size ) ? self::filesize( $file ) : $size ) <= 2 * pow( 1024, 3 ) )
687             return fopen( $file, 'r' );
688         elseif ( PHP_OS != 'Linux' )
689             return ! array_unshift( self::$errors, new Exception( 'File size is greater than 2GB. This is only supported under Linux' ) );
690         elseif ( ! is_readable( $file ) )
691             return false;
692         else
693             return popen( 'cat ' . escapeshellarg( realpath( $file ) ), 'r' );
694     }
695
696     /** Helper to scan directories files and sub directories recursivly
697      * @param string directory path
698      * @return array directory content list
699      */
700     static public function scandir ( $dir ) {
701         $paths = array();
702         foreach ( scandir( $dir ) as $item )
703                 if ( $item != '.' && $item != '..' )
704                     if ( is_dir( $path = realpath( $dir . DIRECTORY_SEPARATOR . $item ) ) )
705                         $paths = array_merge( self::scandir( $path ), $paths );
706                     else
707                         $paths[] = $path;
708         return $paths;
709     }
710
711     /** Helper to check if url exists
712      * @param string url to check
713      * @return boolean does the url exist or not
714      */
715     static public function url_exists ( $file ) {
716         return (bool) preg_grep('#^HTTP/.*\s(200|304)\s#', (array) @get_headers( $file ) );
717     }
718
719     /** Helper to check if a file is a torrent
720      * @param string file location
721      * @return boolean is the file a torrent or not
722      */
723     static public function is_torrent ( $file ) {
724         if ( @file_get_contents( $file, 0, null, 0, 11 ) !== 'd8:announce' ) {
725             return @file_get_contents( $file, 0, null, 0, 17 ) === 'd13:announce-list';
726         }
727         else {
728             return true;
729         }
730     }
731
732 }
733
734 ?>
Note: See TracBrowser for help on using the browser.