Предлагаю решение для даунлоада файлов с докачкой

antson

Новичок
Партнер клуба
Предлагаю решение для даунлоада файлов с докачкой

PHP:
<?
// download.class.php
// Класс для даунлоада файла с докачкой
// -----------------------------------------------------
class DownLoadFile{
  var $FileName; // имя файла
  var $size;     // размер
  var $time;     // дата-время модификации
  var $start;    // с какого байта отдавать
  var $NeedRange; // Признак докачки у клиента
  
  function DownLoadFile($FileName){ // конструктор
    $this->FileName=$FileName;
    $range = getenv("HTTP_RANGE");
    if(!Empty($range)){
      $this->NeedRange=true;
      $this->start=intval(substr($range,6)); //откусили bytes
    }else{
      $this->NeedRange=false;
      $this->start=0;
    }
    $this->size=-1; // -1 признак что файла нет
    if(file_exists($this->FileName)){
      $this->size=filesize($this->FileName);
      $this->time=date("D, d M Y H:i:s ", filemtime($this->FileName))."GMT";
    }

  }// конструктор



// private 

  function outHeaderCommon(){
    header("Content-Transfer-Encoding: binary"); 
    header("Content-Disposition: attachment; filename=$this->FileName"); 
    header("Last-modified: ".$this->time);
    header("Content-Length: " . ($this->size - $this->start)); 
  }

  function outHeaderForRange(){
    global $HTTP_SERVER_VARS;
         header("Accept-Ranges: bytes");
         header($HTTP_SERVER_VARS["SERVER_PROTOCOL"] . " 206 Partial Content");
         header("Content-Range: bytes ".$this->start."-".($this->size-1)."/".$this->size);
  }

  function outContent(){
    if($handle = fopen($this->FileName, "rb")){ 
        $this->CalcStatistics();
        fseek($handle, $this->start); 
        fpassthru($handle); 
        fclose($handle);
    }else{
        $this->out403();
    }
  }


// overload 

 function CalcStatistics(){
   // Сохранение в логах факта скачивания файла
   //  Запись в бд или в текстовый файл по вкусу
   // перегрузить в производном классе
 }

  function outContentType(){
    header("Content-Type: application/force-download"); //заставляет всегда сохранять файл
  }

  function out404(){
     header("HTTP/1.0 404 Not Found");  // имхо достаточно
/*
     header($HTTP_SERVER_VARS["SERVER_PROTOCOL"] . " 404 Not Found"); // для эстетов
*/
    echo "404 Not Found!"; 
    exit;

  }

  function out403(){
    header("HTTP/1.0 403 Forbidden");  // теже замечания, что и для 404
    echo "403 Forbidden!";
    exit;
  }



// public 

  function out(){
    if($this->size>0){
       $this->outHeaderCommon();
       $this->outContentType();
       if($this->NeedRange)$this->outHeaderForRange();
       $this->outContent();
    }else{
      $this->out404();
    }
  }


}//DownLoadFile

/* Example */
/*
 $x = new DownLoadFile('20050726.rar');
 $x -> out();
*/

?>
Фанат, раскритикуйте пожалуйста. У Вас отлично это получается :)
 

mani13

Новичок
я конечно не Фанат, но не вижу смысла в классе, так как реализуется _1_ функцией вполне спокойно...
 

whirlwind

TDD infected, paranoid
Класс нужный. Только в таком виде действительно функция смотрелась бы красивее. У меня немного подругому:

PHP:
/*

	HTTP_DL_Object class (PHP5 required)
	// cp-1251

	Базовый класс, реализующий доступ к скачиваемым объектам по протоколу HTTP.
	Поддерживает порционную выдачу файлов. Метод работы с источником реализуется
	производными классами - здесь только выдача файла.

*/

require('mlog.class.php');

class HTTP_DL_Object extends MLog {

	private $content_is_partial;	// признак частичной выдачи файла
	private $content_file_size;		// полный размер файла
	private $content_file_name;		// путь или URL файла
	private $content_range_pos;		// если частичная, то позиция начала в файле
	private $content_range_len;  	// если частичная, то длина части

	public function outFileOrPart(){
    	// выводит файл или часть файла в поток,
    	// предваряя его соответствующими заголовками

    	$this->dumpHash( $_SERVER );

		$content_length = 0;
		$content_filename = $this->getContentFileName();
		$headers = array();

        if ( $this->isPartialContent() ){
            $content_beg_byte = $this->getContentRangePos();
            $content_length = $this->getContentRangeLen();
	        $content_end_byte = $content_length - $content_beg_byte - 1;

			$headers[] = 'HTTP/1.0 206 Partial Content';
			$headers[] = sprintf('Content-Range: bytes %d-%d/%d',
				$content_beg_byte,$content_end_byte,
				$this->getContentFileSize());
        }else{
        	$content_length = $this->getContentFileSize();

			$headers[] = 'HTTP/1.0 200 OK';
			$headers[] = 'Accept-Ranges: bytes';
        }
        $headers[] = 'Content-Type: application/force-download';
		$headers[] = sprintf('Content-Length: %d',$content_length);
        $headers[] = sprintf('Content-Disposition: attachment; filename=%s',
        	basename($content_filename));
        $headers[] = 'Connection: close';

		if ( !$this->_readOut($headers) ){
            return false;
        }
        return true;
    }

	public function initFromHTTPHeaders(){
		// имя файла-источника д.б. предварительно
		// установлено посредством ->setContentFileName()

		$file_size = $this->_calculateContentFileSize();
		if ( !isset($file_size) ){
			return false;
        }

		// длина источника должна быть определена
		// эта задача возложена на производные классы
		$this->_setContentFileSize($file_size);

		$txt_range = $_SERVER['HTTP_RANGE'];

		if ( isset($txt_range) ){
        	// выдаем файл порциями
        	$this->_setIsPartialContent(true);

	        if ( !preg_match("/^bytes=(\d*)-(\d*)$/",$txt_range,$matches) ){
	            $this->_setLastError(
	            	sprintf("Invalid range specification: %s",$txt_range));
	            return false;
	        }
	        $beg_byte = $matches[1] ? $matches[1] : 0;
	        $end_byte = $matches[2] ? $matches[2] : ($file_size - 1);

	        if ( $end_byte >= $file_size ){
	            // глюка, при которой запрошено больше, чем содержит файл
	            // в этом случае обрезаем запрошенное до размера файла
	            $end_byte = $file_size - 1;
	        }

	        if ( $beg_byte > $end_byte ){
	            // глюка, при которой начало сегмента находится выше
	            // чем его завершение
	            // в этом случае меняем местами
	            $temp_var = $beg_byte;
	            $beg_byte = $end_byte;
	            $end_byte = $temp_var;
	        }

	        $this->_setContentRangePos($beg_byte);
	        $this->_setContentRangeLen($end_byte - $beg_byte + 1);
	    }else{
            // не порционный вариант выдачи
            $this->_setIsPartialContent(false);
            $this->_setContentRangeLen($file_size);
        }

    	return true;
	}

	public function __construct(){
		parent::__construct();

		$this->_setIsPartialContent(false);
    	$this->_setContentRangePos(0);
    	$this->_setContentRangeLen(0);
    	$this->_setContentFileSize(0);
    	$this->setContentFileName('');
	}

	public function __destruct(){

	}

// getters/setters

	public function isPartialContent(){
    	return $this->content_is_partial ? true : false;
	}

	private function _setIsPartialContent($partial_flag=true){
		$this->content_is_partial = $partial_flag ? true : false;
		return;
	}

	public function getContentRangePos(){
    	return $this->content_range_pos;
	}

	private function _setContentRangePos($range_pos){
		$this->content_range_pos = $range_pos;
		return;
	}

	public function getContentRangeLen(){
		return $this->content_range_len;
	}

	private function _setContentRangeLen($range_len){
    	$this->content_range_len = $range_len;
    	return;
	}

	public function getContentFileName(){
    	return $this->content_file_name;
	}

	public function setContentFileName($file_name){
    	$this->content_file_name = $file_name;
    	return;
	}

	public function getContentFileSize(){
    	return $this->content_file_size;
    }

    private function _setContentFileSize($file_size){
    	$this->content_file_size = $file_size;
    	return;
	}
// для перекрытия производными классами

	public function _calculateContentFileSize(){
        $this->_setLastError(sprintf(
        	"Pure-virtual function call: %s::%s",__CLASS__,__FUNCTION__));
        return;	// возвращает неопределенное, если неудача
    }

    public function _readOut($headers){
    	$this->_setLastError(sprintf(
    		"Pure-virtual function call: %s::%s",__CLASS__,__FUNCTION__));
    	return false;
    }



// private

}
Производные могут реализовывать различные протоколы доступа к источнику. Таким образом, можно сделать хоть туннель для перекачки, хоть генерацию файлов на лету. Доступ к файлам локальной ФС может выглядеть так:

PHP:
/*

	HTTP_DL_File class (PHP5 required)
	// cp-1251

    Наследник класса HTTP_DL_Object, реализующий механизм доступа к
    файлу-источнику, расположенному в локальных файловых системах.

*/

require('http_dl_object.class.php');

class HTTP_DL_File extends HTTP_DL_Object {

	public function _calculateContentFileSize(){
    	$file_name = parent::getContentFileName();

        if ( !$file_name ){
            parent::_setLastError("No source file specified");
            return;
		}

		if ( !file_exists($file_name) ){
        	parent::_setLastError("File not exists: $file_name");
			return;
        }

		return filesize($file_name);
    }

    public function _readOut($headers){
		$read_buffer_size = 4096;
        $file_name = $this->getContentFileName();

        if ( !($fh = fopen($file_name,'rb')) ){
        	$this->_setLastError("Couldn't open file for reading: $file_name");
        	return false;
        }

        $begpos = $this->getContentRangePos();
        $length = $this->getContentRangeLen();
        if ( -1 == fseek($fh,$begpos,SEEK_SET) ){
        	$this->_setLastError("Seek file failed: $file_name");
        	return false;
        }

        foreach ($headers as $head){
        	header($head);
        }

        while ( $length >= $read_buffer_size && !feof($fh) ){
        	$buffer = fread($fh,$read_buffer_size);
        	echo $buffer;
        	$length -= $read_buffer_size;
        }

        // если еще что то осталось, то дочитываем остаток
        if ( $length > 0 && !feof($fh)){
			$buffer = fread($fh,$length);
			echo $buffer;
        }

        fclose($fh);
        unset($fh);

    	return true;
    }


}
пример как это работает

PHP:
$file_id = $_REQUEST['id'];

$download_object = new HTTP_DL_File;
$download_object->setContentFileName($file_id);
if ( !$download_object->initFromHTTPHeaders() ||
	!$download_object->outFileOrPart() )
{
    header("HTTP/1.0 404 Not found");
    echo "<h1>404 Not found</h1>";
    echo $download_object->getLastError();
    exit;
}

ЗЫ. Такой вопрос. Кто как определяет факт скачивания файла?
 
Сверху