Location: PHPKode > scripts > File Exchange Protocol > file-exchange-protocol/FEP_client.php
<?php
/***************************************************************************
 *   Copyright (C) 2007 by Cesar D. Rodas                                  *
 *   hide@address.com                                                     *
 *                                                                         *
 *   Permission is hereby granted, free of charge, to any person obtaining *
 *   a copy of this software and associated documentation files (the       *
 *   "Software"), to deal in the Software without restriction, including   *
 *   without limitation the rights to use, copy, modify, merge, publish,   *
 *   distribute, sublicense, and/or sell copies of the Software, and to    *
 *   permit persons to whom the Software is furnished to do so, subject to *
 *   the following conditions:                                             *
 *                                                                         *
 *   The above copyright notice and this permission notice shall be        *
 *   included in all copies or substantial portions of the Software.       *
 *                                                                         *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       *
 *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    *
 *   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*
 *   IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR     *
 *   OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
 *   ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
 *   OTHER DEALINGS IN THE SOFTWARE.                                       *
 ***************************************************************************/

/**
 *    File Exchange Protocol
 *    
 *    This is a PHP class based on Amazon S3 like transference protocol.
 *    The Idea is to develope a fexible, plataform indepente, file transfer
 *    that works over HTTP(s). Basically for exchange files between servers.
 *
 *    @package File Exchange Protocol
 *    @version 1.0
 *    @author Cesar D. Rodas <hide@address.com>
 *    @license BSD License
 */
require_once( dirname(__FILE__).'/FEP_base.php'); /* include base class*/
require_once( dirname(__FILE__).'/http.php'); /* include http class*/

define('read',1);
define('write',2);
define('append',3);

/**
 *  Cache object.
 *
 *  For better performance, the cache file comunitacion (read-write) 
 *  is handle by an intermediate class that is cache.
 *  
 *
 *    @package File Exchange Protocol
 *    @version 1.0
 *    @author Cesar D. Rodas <hide@address.com>
 *    @license BSD License
 */
class httpCacheObject {
    /**
     *  True if must need to be sync to the server
     *  @var bool
     *  @access public
     */
    var $dirty;
    /**
     *  Size of the cache
     *  @var integer
     *  @access public
     */
    var $size;
    /**
     *  Value of the cache
     *  @var string
     *  @access public
     */
    var $value; 

}



/**
 *  Cliente Class
 *
 *    @package File Exchange Protocol
 *    @version 1.0
 *    @author Cesar D. Rodas <hide@address.com>
 *    @license BSD License
 */
class FEP_client extends FEP_base {
    /**
     *  HTTP Client object
     *  @var object
     *  @acces private 
     */
    var $http;
    
    /**
     *  Opened Mode
     *  
     *  Save in what method is opened a file.
     *  @var int
     *  @access private
     */
    var $mode;
    
    /**
     *  Server URL
     *  @var string
     *  @access private
     */
    var $server;
    
    /**
     *  File (for operation) Path
     *  @var string
     *  @access private
     */
    var $file;
    /**
     *  File Cache (read-write a file);
     *  @var array object
     *  @access private
     */
    var $cache;
    /**
     *  File cache size in bytes.
     *  @var integer
     *  @access private
     */
    var $cacheSize;
    /**
     *  Actual file position
     *  @var integer  
     *  @access private
     */
    var $position;
    /**
     *  Is End Of file?
     *  @access private 
     *  @var bool
     */
    var $eof;
    
    var $stat;
    
    function FEP_client() {
        $this->position=0;
        $this->cacheSize=   16384; /* minimun operation size */
        $this->http = new http_class();
        $http = & $this->http;
        $http->timeout=0;
        $http->data_timeout=0;
        $http->debug=0;
        $http->html_debug=0;
        $http->user_agent="Cesar D. Rodas' FEP Class (+http://cesars.phpclasses.org/fep)";
        $http->follow_redirect=1;
        $http->redirection_limit=5;
        $http->exclude_address="";
        $http->protocol_version="1.1";
        $this->http = &$http;
        $this->contructed = true;
        $this->position=0;
        $this->buffer="";
    }
        
    /**
     *  Open a remote file.
     *
     *  $f = $fep->Open("server.com","/file.php","r");
     *
     *  For bandwidth save, the permission aren't checked on 
     *  the Open, only when the operation (read,write) are doing
     *  permission are checked.
     *
     *  @param string $server FEP Server to connect (URL)
     *  @param string $path Path to open
     *  @param string $omode Open Mode
     *
     *  @return bool True if success false on otherwise
     */
    function Open($server, $path,$omode) {
        $mode = & $this->mode; 
        $mode = 0;
        switch( strtolower($omode) )  {
            /* Read */
            case "r":
            case "rb":
                $mode = read;
                break;
            /* Write */
            case "w";
            case "wb":
                $mode = write;
                break;
            /* Append */
            case "r+":
            case "r+b":
            case "a":
            case "ab":
               $mode = append;
               break;
            default:
                return false;
        }
        $this->server = $server;
        $this->SetPath($path);

        return $this->Stat();
    }
    
    /**
     *    File Stat.
     *    
     *    Get information about a given file 
     *  @access public
     *    @param string $path File name, by default an open file.
     *    @return boolean true is success or false on otherwise
     */
    function Stat($path='') {
        if ($path != '') 
            $this->SetPath($path);
        $this->HttpQuery('GET','INFO','',$headers,$response);
        if ( $headers['fep-response'] != 'OK') {
            trigger_error($response,E_USER_WARNING);
            return false;
        }
        $e = & $this->stat;
        list($e['mode'],$e['size'],$e['atime'],$e['mtime'],$e['ctime']) = explode("|",$response);
        return true;
    }
    /**
     *    Write into the open file
     *    
     *    Writes into a cache-section and mark it as dirty for sincronize with the server
     *    as soon as posible.
     *    @param string $txt  Texto to save
     *    @param integer $size Size of the text
     *  @access public
     *    @return integer Size of the writen text
     */
    function Write($txt, $size=-1) {
        $cache = & $this->cache;
        if ($size==-1) $size=strlen($txt);
        $bucket=ceil($this->position/$this->cacheSize) - 1; /* bucket of cache were to search */
        $bucket=$bucket < 0 ? 0 : $bucket;
        $writen=0; /* bytes writed */
        
        /* write in the cache and mark as dirty */
        while ($writen < $size) {
            if ( !isset($cache[$bucket]) ) {
                /* create a new cache object */
                $cache[$bucket] = new httpCacheObject();
                $cache[$bucket]->value = '';
                $cache[$bucket]->size = 0;
            }
            $bucketSize = $cache[$bucket]->size;
            if ( $this->cacheSize == $bucketSize) {
                $bucket++;
                continue;
            }
            $tmp = substr($txt, $writen, $this->cacheSize - $bucketSize);
            $len = strlen($tmp);
            $cache[$bucket]->value .= $tmp; /* increment cache */
            $cache[$bucket]->size   = $bucketSize + $len; /* increment cache size */
            $cache[$bucket]->dirty  = true; /* mark as dirty, need to sync as soon as posible */
            /**
             *  Also change the Filesize in the memory, that is for read from memory
             *  what is yet uncommited. Useful on "a" and "rb" file open.
             */
            $this->filesize = (count($cache)-1) * $this->cacheSize  + $cache[count($cache)-1]->size; 
            $writen += $len;
            $bucket++;
        }
        $this->position += $writen;
        return $size;
    }
    
    /**
     *  File read.
     *
     *  First search in cache, is not found, calls CacheRequest and returns.
     *
     *  @param integer $size Size to read.
     *    @return string Content of the file.
     *  @access public
     */
    function Read($size) {
        $cache = & $this->cache;
        $cachesize = & $this->cacheSize;
        $pos = & $this->position;
        $bucket=ceil(($pos+1)/$cachesize) - 1; /* bucket of cache were to search */
        $bucket=$bucket < 0 ? 0 : $bucket;
        $read = 0; /* bytes writed */
        $start = $pos % $cachesize;
        $res = '';
        while ($read < $size &&  !$this->stream_eof() ) {
            if ( !isset($cache[$bucket]) ) {
                /* create a new cache object */
                $cache[$bucket] = new httpCacheObject();
                $cache[$bucket]->size = 0;
                $this->HttpQuery('GET','READ','',$headers,$response,$pos, $cachesize);
                if ( $headers['fep-response'] != 'OK') {
                    /**
                     *  At this moment, the project stop the execution
                     *  by simulation and EOF when the first error is arraised.
                     *  TODO: Make a better system for check the read-error
                     *  and have a system to avoid faults by re-asking to the server
                     *  or stop on FILE_NOT_FOUND or UNAUTH.
                     */
                    trigger_error($response,E_USER_WARNING);
                    $this->eof = true;
                    return;
                }
                $cache[$bucket]->value = $response;
                $cache[$bucket]->size = $headers['fep-length'];
            }   
            $tmp   = substr($cache[$bucket]->value, $start, ($size - $read)  % $cachesize);
            $start = 0;
            $e = strlen($tmp);
            $read += $e;
            $this->position  += $e;
            $res  .= $tmp;
            $bucket++;
        }
        return $res;
    }
    
    /**
     *  Syncronize server
     *  
     *  Sendes dirty cache to the server and those cache
     *  became clean cache until the cache is changed.
     *
     *  The only function that do the HTTP request for write
     *  is this method. No other can write directly
     */
    function Syncronize() {
        $cache = & $this->cache;
        $http  = & $this->http;
        for($i=0; isSet($cache[$i]); $i++) {
            if ($cache[$i]->dirty) {
                $posStart = $i * $this->cacheSize;
                $this->HttpQuery('POST','WRITE',$cache[$i]->value,$headers,$response,$posStart, $cache[$i]->size);
                if ( $headers['fep-response'] != 'OK') {
                    trigger_error($response,E_USER_WARNING);
                }
                $cache[$i]->dirty = false; /* the page is syncronized! */
            }
        }
    }
    
    /**
     *  End object.
     *
     *  Syncronize dirty pages, destroy conections, destroy 
     *  cache.
     *
     */
    function Destroy() {
        $http = & $this->http;
        $this->Syncronize();
        $http->close();     /* destroy http connection */
        unset($this->cache); /* destroy cache*/
    }
    
    function GenAuthCode(&$arguments) {
        $header = &$arguments['Headers'];
        $header['FEP-DATE'] = gmdate("D, d M Y G:i:s T");
        $header['FEP-USER'] = FEP_USER;
        $header['FEP-VERSION'] = "1.0";
        $header['FEP-KEY'] = $this->genAuthKey($header['FEP-USER'], FEP_PASS, $header['FEP-PATH'], $header['FEP-ACTION'], $header['FEP-DATE']);   
    }
    

    function HttpQuery($method,$action, $text, &$gHeaders, &$fepResponse,$starPos=-1, $length=-1) {
        $http = & $this->http;
        $http->request_method= $method; 
        $http->GetRequestArguments($this->server,$arguments);  /* parse arguments */
        $header = & $arguments['Headers'];
        
        $header['FEP-PATH'] = $this->file;
        if ( $starPos != -1) {
            $header['Content-Length'] = $length;
            $header['FEP-START'] = $starPos;
            $header['FEP-LENGTH'] = $length;
        }
        $header['FEP-ACTION'] = $action;
        if ( strlen($text) > 0)
            $arguments['Body'] = $text;
        $this->GenAuthCode($arguments);
        /* open */
        $http->Open($arguments); 
        /* send request */
        $http->SendRequest($arguments);
        /* get response headers */
        $http->ReadReplyHeaders($tmp);
        $gHeaders = $tmp;
        $fepResponse='';
        $http->response_status.=""; //convert to string
        if ($http->response_status[0] != 2) {
            return false;
        }
        /*Something were wrong*/
        for(;;)
        {
            $error=$http->ReadReplyBody($body,1000);
            if($error!="" || strlen($body)==0) break;
            $fepResponse.=($body);
        }    
        $http->Close();
        
        return true;
    }

    function SetPath($path) {
        $this->file = substr($path,6);
    }
}

/**
 *  Cliente Class, Stream Wrapper version
 *
 *    @package File Exchange Protocol
 *    @version 1.0
 *    @author Cesar D. Rodas <hide@address.com>
 *    @license BSD License
 */
class FEP_client_PHP_STREAM_WRAPPER extends FEP_client {
    var $actualDir;
    var $dirList;
    
    function FEP_client_PHP_STREAM_WRAPPER() {
        $this->FEP_client();
        $this->server = FEP_SERVER;
    }
    
    function stream_tell() {
        return $this->position;
    }
    
    function stream_open($path, $mode, $options, &$opened_path) {
        if ( !defined('FEP_SERVER') ) return false;
        return $this->Open(FEP_SERVER,$path,$mode);
    }
    
    function stream_read ( $count ) {
        return $this->Read($count);
    }
    
    function stream_stat() {
        return $this->stat;
    }
    
    function rmdir($path) {
        return $this->GeneralQuery($path, 'GET','RMDIR',$head,$resp);
    }
    
    function mkdir($path) {
        return $this->GeneralQuery($path, 'GET','MKDIR',$head,$resp);
    }
    
    function unlink($path) {
        return $this->GeneralQuery($path, 'GET','UNLINK',$head,$resp);
    }
    
    function  url_stat ( $path )    {
        $this->FEP_client_PHP_STREAM_WRAPPER();
        return $this->stat($path) ? $this->stat : false ;
    }
    
    function dir_opendir($path ) {
        $this->dir_rewinddir();
        if ( $this->GeneralQuery($path, 'GET','LIST',$head,$resp) )
        {
            $this->dirList=explode("\r\n",$resp);
            return true;
        }
        return false;
    }
    
    function dir_rewinddir() {
        $this->actualDir=0; 
    }
    
    function dir_closedir() {
        $this->dir_rewinddir();
        $this->dirList = array();
    }
    
    function dir_readdir() {
        return (count($this->dirList) > $this->actualDir) ? $this->dirList[ $this->actualDir++ ] : false;
    }
    
    function stream_flush() {
        return $this->Syncronize();
    }
    
    function stream_write($data) {
        return $this->Write($data);
    }
    
    function stream_close() {
        return $this->Destroy();
        
    }
    
    function stream_eof() {
        return $this->eof || $this->stat['size'] <= $this->position;
    }
    
    
    function stream_seek($offset, $whence)
    {
        switch ($whence) {
            case SEEK_SET:
                if ($offset < $this->stat['size'] && $offset >= 0) {
                     $this->position = $offset;
                     return true;
                } else {
                     return false;
                }
                break;
               
            case SEEK_CUR:
                if ( $this->position + $offset <  $this->stat['size']) {
                     $this->position += $offset;
                     return true;
                } else {
                     return false;
                }
                break;
               
            case SEEK_END:
                if ($this->stat['size'] + $offset <  $this->stat['size']) {
                     $this->position = $this->stat['size'] + $offset;
                     return true;
                } else {
                     return false;
                }
                break;
               
            default:
                return false;
        }
    }
    
    
    function GeneralQuery($path, $type,$command, &$head, &$resp) {
        $this->FEP_client_PHP_STREAM_WRAPPER();
        $this->SetPath($path);
        $this->HttpQuery($type,$command,'',$headers,$response);
        $head = $headers;
        $resp = $response;
        $this->Destroy();
        if ( $headers['fep-response'] != 'OK') {
            trigger_error($response,E_USER_WARNING);
            return false;
        }
        
        return true;
    } 
}

stream_wrapper_register("fep","FEP_client_PHP_STREAM_WRAPPER") or die("Failed to register protocol fep"); 


?>
Return current item: File Exchange Protocol