<?php

/**
 * Helper class to save downloaded content with HTTP_Request
 *
 * PHP version 5
 *
 * LICENSE:
 *
 * Copyright (c) 2008, Philippe Jausions / 11abacus
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   - Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   - Neither the name of the 11abacus nor the names of its contributors may
 *     be used to endorse or promote products derived from this software
 *     without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @category  HTTP
 * @package   HTTP_Request_SaveAs
 * @author    Philippe Jausions <jausions@php.net>
 * @copyright 2008 by Philippe Jausions / 11abacus
 * @license   New BSD
 */

/**
 * Package dependencies
 */
require_once 'HTTP/Request/Listener.php';
require_once 
'HTTP/Request/SaveAs/Exception.php';
require_once 
'System/Folders.php';

/**
 * Helper class to save downloaded content with HTTP_Request
 *
 * @category  HTTP
 * @package   HTTP_Request_SaveAs
 * @author    Philippe Jausions <jausions@php.net>
 * @copyright 2008 by Philippe Jausions / 11abacus
 * @license   New BSD
 */
class HTTP_Request_SaveAs extends HTTP_Request_Listener
{
    
/**
     * File pointer
     *
     * @var resource
     */
    
protected $fp;

    
/**
     * File path
     *
     * @var string
     */
    
protected $file;

    
/**
     * HTTP_Request the instance is attached to
     *
     * @var HTTP_Request
     */
    
protected $httpRequest;

    
/**
     * Whether to save the body of the response
     *
     * @var boolean
     */
    
protected $saveBody;

    
/**
     * Options
     *
     * @var array
     */
    
protected $options = array(
        
'saveHeaders' => false,
        
'saveBody' => true,
    );

    
/**
     * Class constructor
     *
     * @param string $file    path of file to download to
     * @param array  $options array of options, available indexes:
     * <ul>
     *  <li>saveHeaders (boolean) Whether to save the HTTP headers in the
     *      file</li>
     *  <li>saveBody (boolean) Whether to save the body of the response in the
     *      file</li>
     *  <li>httpStatus (array|integer|string) save the content only when the
     *      HTTP response status is in the list provided. Pass * for all
     *      status. (default is to only save for HTTP Status 200)</li>
     * </ul>
     *
     * @access public
     */
    
public function __construct($file null, array $options = array())
    {
        
$this->HTTP_Request_Listener();
        
$this->file $file;
        if (!
array_key_exists('httpStatus'$options)) {
            
$options['httpStatus'] = array(200);
        } else {
            
settype($options['httpStatus'], 'array');
        }
        
$this->options array_merge($this->options$options);
    }

    
/**
     * Property setter
     *
     * @param string $var   property name
     * @param mixed  $value property value
     *
     * @return void
     */
    
public function __set($var$value)
    {
        switch (
$var) {
        case 
'file':
            
$this->file $value;
            
$this->close();
            break;
        default:
            throw new 
HTTP_Request_SaveAs_Exception('Unknown property "'.$var.'"');
        }
    }

    
/**
     * Property getter
     *
     * @param string $var property name
     *
     * @return mixed
     */
    
public function __get($var)
    {
        switch (
$var) {
        case 
'file':
            return 
$this->file;
            break;
        }
        throw new 
HTTP_Request_SaveAs_Exception('Unknown property "'.$var.'"');
    }

    
/**
     * Initializes the downloader
     *
     * This method is automatically called when the downloader starts
     * receiving data. You can also call it before the HTTP request is sent
     * to ensure you will be able to save the file.
     *
     * @return void
     */
    
public function init()
    {
        if (!
$this->options['saveBody'] && !$this->options['saveHeaders']) {
            return;
        }
        if (
is_resource($this->fp)) {
            return;
        }

        if (!isset(
$this->file) || strlen(trim($this->file)) == 0) {
            static 
$tempDir;
            if (!isset(
$tempDir)) {
                
$sf = new System_Folders();

                
$tempDir $sf->getTemp();
            }
            
$this->file tempnam($tempDir'HTTP_Request__');
        }

        if (!(
$this->fp fopen($this->file'wb'))) {
            throw new 
HTTP_Request_SaveAs_Exception('Could not open destination file '.$this->file);
        }
    }

    
/**
     * Closes the file pointer
     *
     * @return void
     */
    
protected function close()
    {
        if (
is_resource($this->fp)) {
            
fclose($this->fp);
            
$this->fp null;
        }
        
$this->httpRequest null;
        
$this->saveBody    $this->options['saveBody'];
    }

    
/**
     * Receives and saves data to file
     *
     * @param object $subject an object the listener is attached to
     * @param string $event   event name
     * @param mixed  $data    additional data
     *
     * @return void
     */
    
public function update($subject$event$data null)
    {
        switch (
$event) {
        case 
'sentRequest':
        case 
'gotHeaders':
        case 
'tick':
        case 
'gotBody':
            
$this->$event($subject$data);
            break;
        default:
            throw new 
HTTP_Request_SaveAs_Exception('Unhandled HTTP_Request event "'.$event.'"');
        }
    }

    
/**
     * Handles the "sentRequest" event
     *
     * @param object $subject an object the listener is attached to
     * @param mixed  $data    additional data
     *
     * @return void
     */
    
protected function sentRequest($subject$data)
    {
        if (!(
$subject instanceof HTTP_Request)) {
            throw new 
HTTP_Request_SaveAs_Exception('Unexpected subject class');
        }
        
$this->httpRequest $subject;
        
$this->saveBody    $this->options['saveBody'];
    }

    
/**
     * Handles the "gotHeaders" event
     *
     * The first line of the HTTP Header (HTTP/1.x xxx xxx...) may not be
     * exactly what was recieved.
     *
     * @param object $subject an object the listener is attached to
     * @param mixed  $data    additional data
     *
     * @return void
     */
    
protected function gotHeaders($subject$data)
    {
        
$this->init();
        
$httpCode $this->httpRequest->getResponseCode();

        if (
$this->options['saveHeaders']) {
            
$s 'HTTP/1.? '.$httpCode."\n";
            foreach (
$data as $name => $value) {
                
$s .= $name.': '.$value."\n";
            }
            
$s .= "\n";
            if (
fwrite($this->fp$s) === false) {
                throw new 
HTTP_Request_SaveAs_Exception('Could not save HTTP headers');
            }
        }

        if (
$this->saveBody
            
&& !in_array($httpCode$this->options['httpStatus'])
            && !
in_array('*'$this->options['httpStatus'])) {
            
$this->saveBody false;
        }
    }

    
/**
     * Handles the "tick" event
     *
     * @param object $subject an object the listener is attached to
     * @param mixed  $data    additional data
     *
     * @return void
     */
    
protected function tick($subject$data)
    {
        if (
$this->saveBody && (fwrite($this->fp$data) === false)) {
            throw new 
HTTP_Request_SaveAs_Exception('Could not save data');
        }
    }

    
/**
     * Handles the "gotBody" event
     *
     * @param object $subject an object the listener is attached to
     * @param mixed  $data    additional data
     *
     * @return void
     */
    
protected function gotBody($subject$data)
    {
        
$this->close();
    }

    
/**
     * Class destructor
     */
    
public function __destruct()
    {
        
$this->close();
    }
}

?>