Source of file Client.php

Size: 8,405 Bytes - Last Modified: 2020-03-05T10:42:44+00:00

/mnt/sshfs/cdn-01/home/users/stable/server.stable.cz/cdn/web/lib/stable-cz/cdn-client/src/Client/Client.php

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
<?php


namespace Stable\Cdn;

/** 
 * Stable.cz CDN client
 *
 * Basic usage examples:
 * 
 * // construct
 * $client = new \Stable\Cdn\Client(YOUR_API_KEY);
 * 
 * // create some local file
 * file_put_contents('localfile', date('Y-m-d'));
 * 
 * // upload file
 * $client->upload('localfile', 'path/to/remotefile');
 * 
 * // verify file exists
 * $client->ls('path/to/remotefile');
 * 
 * // list files in directory
 * $client->ls('path/to');
 * 
 * // delete remote file
 * $client->delete('path/to/remotefile');
 * 
 * @author Evzen Eiba <evzen.eiba@stable.cz>
 * @copyright Stable.cz
 * 
 */


class Client {
    
    /** 
     * @var string URL given to contructor
     */
    protected $url = 'https://cdn.stable.cz/';
    
    /**
     * @var string API key given to constructor
     */
    protected $apikey;
    
    /**
     * @var string Last curlcall curlinfo
     */
    public $curlLastInfo = null;
    /**
     * @var string Last curlcall result
     */
    public $curlLastResult = null;
    
    /**
     * @var int Maximum size of a upload batch
     */
     public $chunkFileSize = 5 * 1024 * 1024;
     
     
     /**
      * @var array Request/response storage
      */
    public $calls = [];
    
    /**
     * Constructor
     * @param $apikey Your CDN service api key acquired from Stable.cz
     * @param $url Endpoint url (http://domain/, rest is concatenated automatically
     * @return null
     */
    public function __construct($apikey)  {
        $this->apikey = $apikey;
        // $this->url = $url;
    }
    
    /**
     * API request call (via cURL)
     * @param (string) $namespace       - API namespace, currently "files" namespace only
     * @param (string|null) $path       - PATH for request, eg. path for a existing file)
     * @param (array) $params           - call params, eg. metadata for uploaded file
     * @param (string) $method          - HTTP method (GET|POST|DELETE)
     * @throws Exception                - in case of wrong method
     * @return (stdClass)                 - decoded JSON according to API definition or null in case of system fault
     */
    
    protected function call(string $namespace, string $path = null, string $method, array $params = []):? \stdClass {
        $url = rtrim($this->url, '/') . '/api/' . trim($namespace, '/') . '/' . ltrim($path, '/');

        $params['auth'] = $this->apikey;
        $options = [
              CURLOPT_URL               => $url
            , CURLOPT_RETURNTRANSFER    => true
            , CURLINFO_HEADER_OUT       => true
        ];
        $options[CURLOPT_CUSTOMREQUEST]         = strtoupper($method);
        switch (strtoupper($method)) {
            case 'DELETE' : 
            case 'GET' : 
                $options[CURLOPT_URL]                  .= '?' . http_build_query($params);
                break;
            case 'POST' :
                $options[CURLOPT_POST]          = true;
                $options[CURLOPT_POSTFIELDS]    = json_encode($params);
                // $options[CURLOPT_HTTPHEADER]    = [ 'Content-Type: application/json' ]; 
                break;
            default:
                throw new \Exception(sprintf('Unknown method %s', $method));
                break;
        }
        
        $ch = curl_init();
        curl_setopt_array($ch, $options);
        $res = curl_exec($ch);
        $this->curlLastInfo = curl_getinfo($ch);
        $this->curlLastResult = $res;
        // var_dump($this->curlLastResult);

        $ret = null;
        if ($res) {
            $ret = json_decode($res);
        }
        $this->calls[] = [
              'url' => str_replace($this->apikey ,'YOUR_API_KEY', $options[CURLOPT_URL])
            , 'method' => $options[CURLOPT_CUSTOMREQUEST]
            , 'data' => !empty($options[CURLOPT_POSTFIELDS]) ? str_replace($this->apikey ,'YOUR_API_KEY', json_encode($params, JSON_PRETTY_PRINT)) : null
            , 'response' => $ret ? str_replace($this->apikey ,'YOUR_API_KEY', json_encode($ret, JSON_PRETTY_PRINT)) : false
        ];
        
        return $ret;
    }
    
    /**
     * Uploads a file to target
     * @param (string) $file    - source file path
     * @param (string) $target  - target file path
     * @param (callable) $callback  - callback for upload progress
     * @return (stdClass)         - decoded JSON according to API definition or null in case of system fault
     */
    public function upload(string $file, string $target, callable $callback = null):? \stdClass {
        $filesize = filesize($file);

        if ($filesize > $this->chunkFileSize) {

            $i = 0;
            $progress = 0;
            $start = microtime(true);

            $parts = $filesize / $this->chunkFileSize;

            $f = fOpen($file, 'r');
            
            if ($callback) {
                $callback((object) [
                      'chunk' => -1
                    , 'progress' => $progress
                    , 'total_filesize' => $filesize
                    , 'chunkresult' => null]);
            }

            while ($chunk = fread($f, $this->chunkFileSize)) {

                $res = $this->call('files', null, 'POST', 
                    [ 'data' => [ 
                          'filename' => $target
                        , 'chunk' => $i
                        , 'content' => base64_encode($chunk) ] ] );
                $progress += strlen($chunk);
                $eta = ( (microtime(true) - $start) / ($i+1) * $parts ) - (microtime(true) - $start);
                $d1 = new \DateTime; $d1->setTimestamp(0);
                $d2 = new \DateTime; $d2->setTimestamp((int) $eta);
                $eta_formatted = $d2->diff($d1)->format('%H:%I:%S');

                if ($callback) {
                    $callback((object) [
                                  'chunk' => $i
                                , 'progress' => $progress
                                , 'total_filesize' => $filesize
                                , 'eta' => $eta
                                , 'eta_formatted' => $eta_formatted
                                , 'chunkresult' => $res]);
                }
                $i++;
            }
            
            fClose($f);
            return $res; // return last result;
            
        } else {
            $res = $this->call('files', null, 'POST', 
                [ 'data' => [ 'filename' => $target, 'content' => base64_encode(file_get_contents($file)) ] ] );
            if ($callback) {
                $callback((object) [
                                  'chunk' => 0
                                , 'progress' => $filesize
                                , 'total_filesize' => $filesize
                                , 'eta' => 0
                                , 'eta_formatted' => 0
                                , 'chunkresult' => $res]);
            }
            return $res;
        }
    }
    
    /**
     * Delete an existing file
     * @param (string) $file    - path to existing file
     * @return (stdClass)         - decoded JSON according to API definition or null in case of system fault
     */
     

    public function delete(string $file):? \stdClass {
        $res = $this->call('files', $file, 'DELETE');
        return $res;
    }
    
    /**
     * List an existing file or directory - get a file info or a directory and its files
     * @param (string) $path    - path to a file or directory
     * @return (stdClass)         - decoded JSON according to API definition or null in case of system fault
     */

    public function ls(string $path):? \stdClass {
        $res = $this->call('files', $path, 'GET');
        return $res;
        
    }
    
    /**
     * Configure CDN (currently resizer sizes only)
     * @param (string) $what - configuration part ('sizes');
     * @param (string) $action - one of get|add|delete action
     * @param $value - optional value
     * @return (stdClass)         - decoded JSON according to API definition or null in case of system fault
     */
    
    public function configure(string $what, string $action, $value = null):? \stdClass {
        switch(strtolower($action)) {
            case 'get' : return $this->call($what, $value, 'GET');
            case 'add' : return $this->call($what, null, 'POST', [ 'data' => $value ]);
            case 'delete' : return $this->call($what, $value, 'DELETE');
        }
    }
}