[ Index ]

PHP Cross Reference of Drupal 6 (yi-drupal)

title

Body

[close]

/sites/all/modules/video/plugins/video_s3/ -> video_s3.lib.inc (source)

   1  <?php
   2  
   3  /*
   4   * @file
   5   * Class file to handle amazon s3 transfers.
   6   *
   7   */
   8  
   9  define('VIDEO_S3_PENDING', 1);
  10  define('VIDEO_S3_ACTIVE', 5);
  11  define('VIDEO_S3_COMPLETE', 10);
  12  define('VIDEO_S3_FAILED', 20);
  13  
  14  class video_amazon_s3 {
  15    private $access_key;
  16    private $secret_key;
  17    private $ssl;
  18    private $limit;
  19    private $bucket;
  20    private $libfile;
  21    
  22    /**
  23     * @var AmazonS3
  24     */
  25    public $s3;
  26  
  27    public function __construct() {
  28      // Create the Zencoder class
  29      $libpath = libraries_get_path('awssdk');
  30      $this->libfile = $libpath .'/sdk.class.php';
  31      
  32      if (!file_exists($this->libfile)) {
  33        drupal_set_message(t('The Amazon SDK for PHP has not been installed correctly. See the <a href="@drupal-status-page">Drupal status page</a> for more information.', array('@drupal-status-page' => url('admin/reports/status'))), 'error');
  34        return;
  35      }
  36      
  37      $this->access_key = variable_get('amazon_s3_access_key', '');
  38      $this->secret_key = variable_get('amazon_s3_secret_access_key', '');
  39      $this->ssl = variable_get('amazon_s3_ssl', FALSE);
  40      $this->limit = variable_get('amazon_s3_limit', 5);
  41      $this->bucket = variable_get('amazon_s3_bucket', '');
  42    }
  43  
  44    public function connect($access_key = '', $secret_key = '', $ssl = FALSE) {
  45      $access_key = $access_key ? $access_key : $this->access_key;
  46      $secret_key = $secret_key ? $secret_key : $this->secret_key;
  47      $ssl = $ssl ? $ssl : $this->ssl;
  48  
  49      // Make our connection to Amazon.
  50      require_once $this->libfile;
  51      
  52      $credential = new stdClass();
  53      $credential->key = $access_key;
  54      $credential->secret = $secret_key;
  55      $credential->token = NULL;
  56      
  57      CFCredentials::set(array($credential));
  58      
  59      $this->s3 = new AmazonS3();
  60      $this->s3->use_ssl = $ssl;
  61    }
  62  
  63    /*
  64     * Verifies the existence of a file id, returns the row or false if none found.
  65     */
  66    public function verify($fid) {
  67      $sql = db_query('SELECT * FROM {video_s3} WHERE fid = %d', $fid);
  68      return db_fetch_object($sql);
  69    }
  70  
  71    /*
  72     * Gets a video object from the database.
  73     */
  74    public function get($fid) {
  75      $sql = db_query('SELECT * FROM {video_s3} WHERE fid = %d AND status = %d', $fid, VIDEO_S3_COMPLETE);
  76      return db_fetch_object($sql);
  77    }
  78  
  79    /*
  80     * Inserts file object into the database.
  81     */
  82    public function insert($fid) {
  83      db_query('INSERT INTO {video_s3} (fid, status) VALUES (%d, %d)', $fid, VIDEO_S3_PENDING);
  84    }
  85  
  86    /*
  87     * Updates the database after a successful transfer to amazon.
  88     */
  89    public function update($video) {
  90      return db_query("UPDATE {video_s3} SET bucket='%s', filename='%s', filepath='%s', filemime='%s', filesize='%s', status=%d, completed=%d WHERE vid=%d",
  91              $video->bucket, $video->filename, $video->filepath, $video->filemime, $video->filesize, VIDEO_S3_COMPLETE, time(), $video->vid);
  92    }
  93  
  94    public function working($vid) {
  95      db_query("UPDATE {video_s3} SET status=%d WHERE vid=%d", VIDEO_S3_ACTIVE, $vid);
  96    }
  97  
  98    public function failed($vid) {
  99      db_query("UPDATE {video_s3} SET status=%d WHERE vid=%d", VIDEO_S3_FAILED, $vid);
 100    }
 101  
 102    public function delete($fid) {
 103      // Lets get our file no matter the status and delete it.
 104      if ($video = $this->verify($fid)) {
 105        if ($video->bucket) {
 106          // It has been pushed to amazon so lets remove it.
 107          $filepath = $video->filepath;
 108          // For old video's (pre-4.5), the filename property is actually the path we need
 109          if (strpos('/', $video->filename) !== FALSE) {
 110            $filepath = $video->filename;
 111          }
 112          $this->s3->delete_object($video->bucket, $filepath);
 113  
 114          // Remove ffmpeg converted files
 115          $row = db_fetch_object(db_query('SELECT data FROM {video_files} WHERE data IS NOT NULL AND fid = %d', $fid));
 116          if ($row) {
 117            foreach (unserialize($row->data) as $subvideo) {
 118              $this->s3->delete_object($video->bucket, $subvideo->filepath);
 119            }
 120          }
 121  
 122          // TODO: move this to video_zencoder
 123          if (module_exists('video_zencoder')) {
 124            // Delete converted files added by Zencoder
 125            $row = db_fetch_object(db_query('SELECT data FROM {video_zencoder} WHERE data IS NOT NULL AND fid = %d', $fid));
 126            if ($row) {
 127              foreach (unserialize($row->data) as $subvideo) {
 128                $parts = parse_url($subvideo->url);
 129                $this->s3->delete_object($video->bucket, ltrim($parts['path'], '/'));
 130              }
 131            }
 132  
 133            // Delete thumbnails added by Zencoder
 134            $thumb_folder = video_thumb_path($video, FALSE) .'/';
 135            $thumbfilenames = $this->s3->get_object_list($video->bucket, array('prefix' => $thumb_folder));
 136            if (!empty($thumbfilenames)) {
 137              $objects = array();
 138              foreach ($thumbfilenames as $thumb) {
 139                $objects[] = array('key' => $thumb);
 140              }
 141              $this->s3->delete_objects($video->bucket, array('objects' => $objects));
 142            }
 143          }
 144        }
 145  
 146        // Lets delete our record from the database.
 147        db_query('DELETE FROM {video_s3} WHERE vid = %d', $video->vid);
 148      }
 149    }
 150  
 151    /*
 152     * Selects the pending queue to be transfered to amazon.
 153     */
 154    public function queue() {
 155      $sql = db_query("SELECT vid, fid FROM {video_s3} WHERE status = %d LIMIT %d", VIDEO_S3_PENDING, $this->limit);
 156      while ($row = db_fetch_object($sql)) {
 157        module_load_include('inc', 'video', '/includes/conversion');
 158        
 159        // We need to check if this file id exists in our transcoding table.
 160        // Skip unfinished ffmpeg jobs
 161        $result = db_query('SELECT f.*, vf.data FROM {files} f LEFT JOIN {video_files} vf USING(fid) WHERE f.fid = %d AND (vf.status IS NULL OR vf.status = %d)', $row->fid, VIDEO_RENDERING_COMPLETE);
 162        if (!($video = db_fetch_object($result))) {
 163          $this->watchdog('Could not find the file id %fid or it is still queued for ffmpeg processing.', array('%fid' => $row->fid), WATCHDOG_DEBUG, $row);
 164          continue;
 165        }
 166  
 167        // Update our status to working.
 168        $this->working($row->vid);
 169  
 170        $success = true;
 171  
 172        // Add all ffmpeg converted variants
 173        if (!empty($video->data)) {
 174          foreach (unserialize($video->data) as $file) {
 175            if (!$this->pushFile($file)) {
 176              $success = false;
 177            }
 178          }
 179        }
 180  
 181        if ($success) {
 182          // Upload the original
 183          if ($video = $this->pushFile($video)) {
 184            $video->vid = $row->vid;
 185            $this->update($video);
 186          }
 187          else {
 188            $success = false;
 189          }
 190        }
 191  
 192        if (!$success) {
 193          $this->failed($row->vid);
 194        }
 195      }
 196      
 197      // Update Expires headers on currently-uploaded files.
 198      // First, make sure this is a good time to do this. It doesn't make much
 199      // sense to do this more often than the Expires offset.
 200      $expires_offset = variable_get('amazon_s3_expires_offset', 604800);
 201      if ($expires_offset !== 'none' && $expires_offset != 0 && variable_get('amazon_s3_expires_last_cron', 0) + $expires_offset < time()) {
 202        $active = db_query('SELECT bucket, filename, filepath, filemime FROM {video_s3} WHERE status = %d', VIDEO_S3_COMPLETE);
 203        $permission = variable_get('amazon_s3_private', FALSE) ? AmazonS3::ACL_PRIVATE : AmazonS3::ACL_PUBLIC;
 204        
 205        $headers = array('Expires' => gmdate('r', time() + $expires_offset));
 206        // Note that Cache-Control headers are always relative values (X seconds
 207        // in the future from the point they are sent), so we don't need to update
 208        // them regularly like we do with Expires headers. However, if we don't
 209        // send one, the one that is set (if any) will be deleted. (Also, if the
 210        // human has changed this setting on the administration page, we want to
 211        // update video info accordingly.)
 212        // @todo: Logic problems: This only updates when expires headers update…
 213        // Might want to find a way so that these update immediately when the
 214        // admin settings form is submitted.
 215        $cc = variable_get('amazon_s3_cache_control_max_age', 'none');
 216        if ($cc !== 'none') {
 217          $headers['Cache-Control'] = 'max-age=' . $cc;
 218        }
 219        while ($file = db_fetch_object($active)) {
 220          $this->update_headers($file, $permission, $headers);
 221        }
 222        
 223        variable_set('amazon_s3_expires_last_cron', time());
 224      }
 225    }
 226  
 227    private function pushFile(stdClass $file) {
 228      $expires_offset = variable_get('amazon_s3_expires_offset', 604800);
 229      $perm = variable_get('amazon_s3_private', FALSE) ? AmazonS3::ACL_PRIVATE : AmazonS3::ACL_PUBLIC;
 230      $cc = variable_get('amazon_s3_cache_control_max_age', 'none');
 231  
 232      $headers = array();
 233      
 234      if ($expires_offset !== 'none') {
 235        $headers['Expires'] = gmdate('r', $expires_offset == 0 ? 0 : (time() + $expires_offset));
 236      }
 237      if ($cc !== 'none') {
 238        $headers['Cache-Control'] = 'max-age='. $cc;
 239      }
 240  
 241      // Increase the database timeout to prevent database errors after a long upload
 242      _video_db_increase_timeout();
 243  
 244      $response = $this->s3->create_object($this->bucket, $file->filepath, array(
 245        'fileUpload' => $file->filepath,
 246        'acl' => $perm,
 247        'contentType' => $file->filemime,
 248        'headers' => $headers,
 249      ));
 250      
 251      if ($response->isOK()) {
 252        // If private, set permissions for Zencoder to read
 253        if ($perm == AmazonS3::ACL_PRIVATE) {
 254          $this->setZencoderAccessPolicy($this->bucket, $file->filepath);
 255        }
 256  
 257        $file->bucket = $this->bucket;
 258        $s3url = 'http://'. $file->bucket .'.s3.amazonaws.com/'. drupal_urlencode($file->filepath);
 259  
 260        // remove local file
 261        if (variable_get('amazon_s3_delete_local', FALSE)) {
 262          $this->replaceLocalFile($file->filepath, $s3url);
 263        }
 264  
 265        $this->watchdog(
 266          'Successfully uploaded %file to Amazon S3 location <a href="@s3-url">@s3-path</a> and permission %permission.',
 267          array(
 268            '%file' => $file->filename,
 269            '@s3-url' => $s3url,
 270            '@s3-path' => $file->filepath,
 271            '%permission' => $perm,
 272          ), WATCHDOG_INFO, $file);
 273  
 274        return $file;
 275      }
 276  
 277      $this->watchdog('Failed to upload %file to Amazon S3.', array('%file' => $file->filepath), WATCHDOG_ERROR, $file);
 278  
 279      return NULL;
 280    }
 281  
 282    public function getVideoUrl($filepath) {
 283      if (variable_get('amazon_s3_private', FALSE)) {
 284        return $this->get_authenticated_url($filepath);
 285      }
 286      else {
 287        $cfdomain = variable_get('amazon_s3_cf_domain', FALSE);
 288  
 289        return
 290          ($this->ssl ? 'https://' : 'http://') .
 291          ($cfdomain ? $cfdomain .'/' : ($this->bucket .'.s3.amazonaws.com/')) .
 292          drupal_urlencode($filepath); // Escape spaces
 293      }
 294    }
 295  
 296    public function get_object_info($filepath) {
 297      return $this->s3->get_object_headers($this->bucket, $filepath)->isOK();
 298    }
 299  
 300    public function get_authenticated_url($filepath) {
 301      $lifetime = (int)variable_get('amazon_s3_lifetime', 1800);
 302      $url = $this->s3->get_object_url($this->bucket, $filepath, time() + $lifetime);
 303  
 304      if ($this->ssl) {
 305        $url = 'https://'. substr($url, 7);
 306      }
 307  
 308      return $url;
 309    }
 310  
 311    public function get_object($filepath, $saveTo = false) {
 312      return $this->s3->get_object($this->bucket, $filepath, array(
 313        'fileDownload' => $saveTo,
 314      ));
 315    }
 316  
 317    private function update_headers($file, $permission, $headers) {
 318      $filepath = $file->filepath;
 319      // For old video's (pre-4.5), the filename property is actually the path we need
 320      if (strpos('/', $file->filename) !== FALSE) {
 321        $filepath = $file->filename;
 322      }
 323      
 324      // Reset the Content-Type header usually sent when the S3 library puts a
 325      // file - we'll lose it otherwise.
 326      $headers['Content-Type'] = $file->filemime;
 327      
 328      $item = array(
 329        'bucket' => $file->bucket,
 330        'filename' => $filepath,
 331      );
 332      
 333      return $this->s3->copy_object($item, $item, array(
 334        'acl' => $permission,
 335        'headers' => $headers,
 336      ))->isOK();
 337    }
 338  
 339    /**
 340    * Set access control policy to zencoder if module is enabled
 341    *
 342    * @todo Add this to video_zencoder module
 343    * @param string $bucket
 344    * @param string $filepath
 345    * @param string $perm WRITE, READ or auto. If auto, $perm is set to READ for files and WRITE for buckets
 346    * @return bool|null True if the settings have been applied, false on error, NULL when settings already set or zencoder not enabled.
 347    */
 348    public function setZencoderAccessPolicy($bucket, $filepath = '', $perm = 'auto') {
 349      if (!module_exists('video_zencoder')) {
 350        return NULL;
 351      }
 352  
 353      if ($perm == 'auto') {
 354        $perm = empty($filepath) ? AmazonS3::GRANT_WRITE : AmazonS3::GRANT_READ;
 355      }
 356  
 357      if (empty($filepath)) {
 358        $acp = $this->s3->get_bucket_acl($bucket);
 359      }
 360      else {
 361        $acp = $this->s3->get_object_acl($bucket, $filepath);
 362      }
 363  
 364      // Store existing ACLs to preserve them when adding zencoder
 365      $acl = array();
 366      
 367      // check if the acl is already present
 368      foreach ($acp->body->AccessControlList->Grant as $grant) {
 369        if ($grant->Grantee->DisplayName == 'zencodertv' && $grant->Permission == $perm) {
 370          return NULL;
 371        }
 372        $acl[] = array(
 373          'id' => isset($grant->Grantee->URI) ? (string)$grant->Grantee->URI : (string)$grant->Grantee->ID,
 374          'permission' => (string)$grant->Permission,
 375        );
 376      }
 377  
 378      // Add the Zencoder credentials
 379      $acl[] = array('id' => 'aws@zencoder.com', 'permission' => $perm);
 380  
 381      if (empty($filepath)) {
 382        return $this->s3->set_bucket_acl($bucket, $acl)->isOK();
 383      }
 384      else {
 385        return $this->s3->set_object_acl($bucket, $filepath, $acl)->isOK();
 386      }
 387    }
 388    
 389    private function watchdog($message, $variables = array(), $severity = WATCHDOG_NOTICE, $nid = NULL) {
 390      $link = NULL;
 391  
 392      if (is_object($nid)) {
 393        if (isset($nid->nid) && $nid->nid > 0) {
 394          $link = l(t('view node'), 'node/'. intval($nid->nid));
 395        }
 396      }
 397      elseif($nid > 0) {
 398        $link = l(t('view node'), 'node/'. intval($nid));
 399      }
 400  
 401      watchdog('amazon_s3', $message, $variables, $severity, $link);
 402    }
 403    
 404    /**
 405     * Replace a file with a text file to reduce disk space usage.
 406     * 
 407     * @param string $filepath
 408     * @param string $s3url
 409     */
 410    private function replaceLocalFile($filepath, $s3url) {
 411      if (file_delete($filepath) !== FALSE) {
 412        $fp = fopen($filepath, 'w+');
 413        if (!$fp) {
 414          return FALSE;
 415        }
 416        
 417        fwrite($fp, 'Moved to '. $s3url);
 418        fclose($fp);
 419        chmod($filepath, 0644);
 420      }
 421    }
 422  }


Generated: Mon Jul 9 18:01:44 2012 Cross-referenced by PHPXref 0.7