File_Bittorrent
[ class tree: File_Bittorrent ] [ index: File_Bittorrent ] [ all elements ]

Source for file MakeTorrent.php

Documentation is available at MakeTorrent.php

  1. <?php
  2.  
  3. // +----------------------------------------------------------------------+
  4. // | Decode and Encode data in Bittorrent format                          |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (C) 2004-2005                                              |
  7. // |   Justin Jones <j.nagash@gmail.com>                                  |
  8. // |   Markus Tacker <m@tacker.org>                                       |
  9. // +----------------------------------------------------------------------+
  10. // | This library is free software; you can redistribute it and/or        |
  11. // | modify it under the terms of the GNU Lesser General Public           |
  12. // | License as published by the Free Software Foundation; either         |
  13. // | version 2.1 of the License, or (at your option) any later version.   |
  14. // |                                                                      |
  15. // | This library is distributed in the hope that it will be useful,      |
  16. // | but WITHOUT ANY WARRANTY; without even the implied warranty of       |
  17. // | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU    |
  18. // | Lesser General Public License for more details.                      |
  19. // |                                                                      |
  20. // | You should have received a copy of the GNU Lesser General Public     |
  21. // | License along with this library; if not, write to the                |
  22. // | Free Software Foundation, Inc.                                       |
  23. // | 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA               |
  24. // +----------------------------------------------------------------------+
  25.  
  26. /**
  27.  * Provides a class for making .torrent files
  28.  * from a file or directory. Produces virtually
  29.  * identical torrent files as btmaketorrent.py
  30.  * from Bram Cohen's original BT client.
  31.  *
  32.  * @author Justin Jones <j.nagash@gmail.com>
  33.  * @author Markus Tacker <m@tacker.org>
  34.  * @version $Id: MakeTorrent.php 56 2006-07-02 17:14:17Z m $
  35.  * @package File_Bittorrent
  36.  * @category File
  37.  */
  38.  
  39. /**
  40.  * Include required classes
  41.  */
  42. require_once 'PEAR.php';
  43. require_once 'File/Bittorrent/Encode.php';
  44.  
  45. /**
  46.  * Provides a class for making .torrent files
  47.  * from a file or directory. Produces virtually
  48.  * identical torrent files as btmaketorrent.py
  49.  * from Bram Cohen's original BT client.
  50.  *
  51.  * @author Justin Jones <j.nagash@gmail.com>
  52.  * @author Markus Tacker <m@tacker.org>
  53.  * @package File_Bittorrent
  54.  * @category File
  55.  */
  56. {
  57.     /**
  58.      * @var string Path to the file or directory to create the torrent from.
  59.      * @access private
  60.      */
  61.     var $_path '';
  62.  
  63.     /**
  64.      * @var bool Whether or not $path is a file
  65.      * @access private
  66.      */
  67.     var $_is_file = false;
  68.  
  69.     /**
  70.      * @var bool Where or not $path is a directory
  71.      */
  72.     var $_is_dir = false;
  73.  
  74.     /**
  75.      * @var string The .torrent announce URL
  76.      * @access private
  77.      */
  78.     var $_announce '';
  79.  
  80.     /**
  81.      * @var array The .torrent announce_list extension
  82.      * @access private
  83.      */
  84.     var $_announce_list = array();
  85.  
  86.     /**
  87.      * @var string The .torrent comment
  88.      * @access private
  89.      */
  90.     var $_comment '';
  91.  
  92.     /**
  93.      * @var string The .torrent created by string
  94.      * @access private
  95.      */
  96.     var $_created_by 'File_Bittorrent_MakeTorrent $Rev: 56 $. http://pear.php.net/package/File_Bittorrent';
  97.  
  98.     /**
  99.      * @var string The .torrent suggested name (file/dir)
  100.      * @access private
  101.      */
  102.     var $_name '';
  103.  
  104.     /**
  105.      * @var string The .torrent packed piece data
  106.      * @access private
  107.      */
  108.     var $_pieces '';
  109.  
  110.     /**
  111.      * @var int The size of each piece in bytes.
  112.      * @access private
  113.      */
  114.     var $_piece_length = 524288;
  115.  
  116.     /**
  117.      * @var array The list of files (if this is a multi-file torrent)
  118.      * @access private
  119.      */
  120.     var $_files = array();
  121.  
  122.     /**
  123.      * @var string|falseThe data gap used to join two files into the same piece. string if it contains data or false
  124.      * @access private
  125.      */
  126.     var $_data_gap = false;
  127.  
  128.     /**
  129.     * @var resource file pointer
  130.     * @access private
  131.     */
  132.     var $_fp;
  133.  
  134.     /**
  135.     * @var mixed    The last error object or null if no error has occurred.
  136.     */
  137.     var $last_error;
  138.  
  139.     /**
  140.      * Constructor
  141.      *
  142.      * Sets up the path to the file/dir to create
  143.      * a torrent from
  144.      *
  145.      * @param string Path to use
  146.      */
  147.     function File_Bittorrent_MakeTorrent($path)
  148.     {
  149.         $this->setPath($path);
  150.     }
  151.  
  152.     /**
  153.      * Function to set the announce URL for
  154.      * the .torrent file
  155.      *
  156.      * @param string announce url
  157.      * @return bool 
  158.      */
  159.     function setAnnounce($announce)
  160.     {
  161.         $this->_announce strval($announce);
  162.         return true;
  163.     }
  164.  
  165.     /**
  166.      * Function to set the announce list for
  167.      * the .torrent file
  168.      *
  169.      * @param array announce list
  170.      * @return bool 
  171.      */
  172.     function setAnnounceList($announce_list)
  173.     {
  174.         if (!is_array($announce_list)) {
  175.             $this->last_error = PEAR::raiseError(__CLASS__ . '::'. __FUNCTION__ . '() - No array given.');
  176.             return false;
  177.         }
  178.         $this->_announce_list $announce_list;
  179.         return true;
  180.     }
  181.  
  182.     /**
  183.      * Function to set the comment for the
  184.      * .torrent file
  185.      *
  186.      * @param string comment
  187.      * @return bool 
  188.      */
  189.     function setComment($comment)
  190.     {
  191.         $this->_comment strval($comment);
  192.         return true;
  193.     }
  194.  
  195.     /**
  196.      * Function to set the path for the
  197.      * file/dir to make the .torrent for
  198.      * Can also be set through the constructor.
  199.      *
  200.      * @param string path to file/dir
  201.      * @return bool 
  202.      */
  203.     function setPath($path)
  204.     {
  205.         $this->_path $path;
  206.         if (is_dir($path)) {
  207.             $this->_is_dir = true;
  208.             $this->_name basename($path);
  209.         else if (is_file($path)) {
  210.             $this->_is_file = true;
  211.             $this->_name basename($path);
  212.         else {
  213.             $this->_path '';
  214.         }
  215.         return true;
  216.     }
  217.  
  218.     /**
  219.      * Function to set the piece length for
  220.      * the .torrent file.
  221.      * min: 32 (32KB), max: 4096 (4MB)
  222.      *
  223.      * @param int piece length in kilobytes
  224.      * @return bool 
  225.      */
  226.     function setPieceLength($piece_length)
  227.     {
  228.         if ($piece_length < 32 or $piece_length > 4096{
  229.             $this->last_error = PEAR::raiseError(__CLASS__ . '::'. __FUNCTION__ . '() - Invalid piece lenth: "' $piece_length '"');
  230.             return false;
  231.         }
  232.         $this->_piece_length $piece_length * 1024;
  233.         return true;
  234.     }
  235.  
  236.     /**
  237.      * Function to build the .torrent file
  238.      * based on the parameters you have set
  239.      * with the set* functions.
  240.      *
  241.      * @return mixed false on failure or a string containing the metainfo
  242.      */
  243.     function buildTorrent()
  244.     {
  245.         if ($this->_is_file{
  246.             if (!$info $this->_addFile($this->_path)) {
  247.                 return false;
  248.             }
  249.             if (!$metainfo $this->_encodeTorrent($info)) {
  250.                 return false;
  251.             }
  252.         else if ($this->_is_dir{
  253.             if (!$diradd_ok $this->_addDir($this->_path)) {
  254.                 return false;
  255.             }
  256.             $metainfo $this->_encodeTorrent();
  257.         else {
  258.             $this->last_error = PEAR::raiseError(__CLASS__ . '::'. __FUNCTION__ . '() - You must provide a file or directory.');
  259.             return false;
  260.         }
  261.         return $metainfo;
  262.     }
  263.  
  264.     /**
  265.      * Internal function which bencodes the data
  266.      * into a valid torrent metainfo string
  267.      *
  268.      * @param array file data
  269.      * @return mixed false on failure or the bencoded metainfo string
  270.      * @access private
  271.      */
  272.     function _encodeTorrent($info = array())
  273.     {
  274.         $bencdata = array();
  275.         $bencdata['info'= array();
  276.         if ($this->_is_file{
  277.             $bencdata['info']['length'$info['length'];
  278.             $bencdata['info']['md5sum'$info['md5sum'];
  279.         else if ($this->_is_dir{
  280.             if ($this->_data_gap !== false{
  281.                 $this->_pieces .= pack('H*'sha1($this->_data_gap));
  282.                 $this->_data_gap = false;
  283.             }
  284.             $bencdata['info']['files'$this->_files;
  285.         else {
  286.             $this->last_error = PEAR::raiseError(__CLASS__ . '::'. __FUNCTION__ . '() - Use ' .  __CLASS__ . '::setPath() to define a file or directory.');
  287.             return false;
  288.         }
  289.         $bencdata['info']['name']         $this->_name;
  290.         $bencdata['info']['piece length'$this->_piece_length;
  291.         $bencdata['info']['pieces']       $this->_pieces;
  292.         $bencdata['announce']             $this->_announce;
  293.         $bencdata['creation date']        time();
  294.         $bencdata['comment']              $this->_comment;
  295.         $bencdata['created by']           $this->_created_by;
  296.         // $bencdata['announce-list'] = array($this->_announce)
  297.         // Encode it
  298.         $Encoder = new File_Bittorrent_Encode;
  299.         return $Encoder->encode_array($bencdata);
  300.     }
  301.  
  302.     /**
  303.      * Internal function which generates
  304.      * metainfo data for a file
  305.      *
  306.      * @param string path to the file
  307.      * @return mixed false on failure or file metainfo data
  308.      * @access private
  309.      */
  310.     function _addFile($file)
  311.     {
  312.         if (!$this->_openFile($file)) {
  313.             $this->last_error = PEAR::raiseError(__CLASS__ . '::'. __FUNCTION__ . "() - Failed to open file '$file'.");
  314.             return false;
  315.         }
  316.  
  317.         $filelength = 0;
  318.         $md5sum md5_file($file);
  319.  
  320.         while (!feof($this->_fp)) {
  321.             $data '';
  322.             $datalength = 0;
  323.  
  324.             if ($this->_is_dir && $this->_data_gap !== false{
  325.                 $data $this->_data_gap;
  326.                 $datalength strlen($data);
  327.                 $this->_data_gap = false;
  328.             }
  329.  
  330.             while (!feof($this->_fp&& ($datalength $this->_piece_length)) {
  331.                 $readlength = 8192;
  332.                 if (($datalength + 8192$this->_piece_length{
  333.                     $readlength $this->_piece_length $datalength;
  334.                 }
  335.  
  336.                 $tmpdata fread($this->_fp$readlength);
  337.                 $actual_readlength strlen($tmpdata);
  338.                 $datalength += $actual_readlength;
  339.                 $filelength += $actual_readlength;
  340.  
  341.                 $data .= $tmpdata;
  342.  
  343.                 flush();
  344.             }
  345.  
  346.             // We've either reached the end of the file, or
  347.             // we have a whole piece, or both.
  348.             if ($datalength == $this->_piece_length{
  349.                 // We have a piece.
  350.                 $this->_pieces .= pack('H*'sha1($data));
  351.             }
  352.             if (($datalength != $this->_piece_length&& feof($this->_fp)) {
  353.                 // We've reached the end of the file, and
  354.                 // we dont have a whole piece.
  355.                 if ($this->_is_dir{
  356.                     $this->_data_gap $data;
  357.                 else {
  358.                     $this->_pieces .= pack('H*'sha1($data));
  359.                 }
  360.             }
  361.         }
  362.  
  363.         // Close the file pointer.
  364.         $this->_closeFile();
  365.         $info = array(
  366.             'length' => $filelength,
  367.             'md5sum' => $md5sum
  368.         );
  369.         return $info;
  370.     }
  371.  
  372.     /**
  373.      * Internal function which iterates through
  374.      * directories and subdirectories, using
  375.      * _addFile for each file it finds.
  376.      *
  377.      * @param string path to the directory
  378.      * @return void 
  379.      * @access private
  380.      */
  381.     function _addDir($path)
  382.     {
  383.         $filelist $this->_dirList($path);
  384.         sort($filelist);
  385.  
  386.         foreach ($filelist as $file{
  387.             $filedata $this->_addFile($file);
  388.             if ($filedata !== false{
  389.                 $filedata['path'= array();
  390.                 $filedata['path'][basename($file);
  391.                 $dirname dirname($file);
  392.                 while (basename($dirname!= $this->_name{
  393.                     $filedata['path'][basename($dirname);
  394.                     $dirname dirname($dirname);
  395.                 }
  396.                 $filedata['path'array_reverse($filedata['path']false);
  397.                 $this->_files[$filedata;
  398.             }
  399.         }
  400.         return true;
  401.     }
  402.  
  403.     /**
  404.      * Internal function which recurses through
  405.      * subdirectory and returns an array of file paths
  406.      *
  407.      * @param string path to the directory
  408.      * @return array file list
  409.      * @access private
  410.      */
  411.     function _dirList($dir)
  412.     {
  413.         $dir realpath($dir);
  414.         $file_list '';
  415.         $stack[$dir;
  416.  
  417.         while ($stack{
  418.             $current_dir array_pop($stack);
  419.             if ($dh opendir($current_dir)) {
  420.                 while ( ($file readdir($dh)) !== false {
  421.                     if ($file{0=='.'continue;
  422.                     $current_file $current_dir '/' $file;
  423.                     if (is_file($current_file)) {
  424.                         $file_list[$current_dir '/' $file;
  425.                     else if (is_dir($current_file)) {
  426.                         $stack[$current_file;
  427.                     }
  428.                 }
  429.             }
  430.         }
  431.         return $file_list;
  432.     }
  433.  
  434.     /**
  435.      * Internal function to get the filesize
  436.      * of a file. Workaround for files >2GB.
  437.      *
  438.      * @param string path to the file
  439.      * @return int the filesize
  440.      * @access private
  441.      */
  442.     function _filesize($file)
  443.     {
  444.         $size @filesize($file);
  445.         if ($size == 0{
  446.             if (PHP_OS != 'Linux'return false;
  447.             $size exec('du -b ' escapeshellarg($file));
  448.         }
  449.         return $size;
  450.     }
  451.  
  452.     /**
  453.      * Internal function to open a file.
  454.      * Workaround for files >2GB using popen
  455.      *
  456.      * @param string path to the file
  457.      * @return bool 
  458.      * @access private
  459.      */
  460.     function _openFile($file)
  461.     {
  462.         $fsize $this->_filesize($file);
  463.         if ($fsize <= 2*1024*1024*1024{
  464.             if (!$this->_fp fopen($file'r')) {
  465.                 $this->last_error = PEAR::raiseError(__CLASS__ . '::'. __FUNCTION__ . '() - Failed to open "' $file '"');
  466.                 return false;
  467.             }
  468.             $this->_fopen = true;
  469.         else {
  470.             if (PHP_OS != 'Linux'{
  471.                 $this->last_error = PEAR::raiseError(__CLASS__ . '::'. __FUNCTION__ . '() - File size is greater than 2GB. This is only supported under Linux.');
  472.                 return false;
  473.             }
  474.             $this->_fp popen('cat ' escapeshellarg($file)'r');
  475.             $this->_fopen = false;
  476.         }
  477.         return true;
  478.     }
  479.  
  480.     /**
  481.      * Internal function to close a file pointer
  482.      *
  483.      * @access private
  484.      */
  485.     function _closeFile()
  486.     {
  487.         if ($this->_fopen{
  488.             fclose($this->_fp);
  489.         else {
  490.             pclose($this->_fp);
  491.         }
  492.     }
  493. }
  494.  
  495. ?>

Documentation generated on Tue, 13 Mar 2007 10:00:14 -0500 by phpDocumentor 1.3.0. PEAR Logo Copyright © PHP Group 2004.