| [ Index ] |
PHP Cross Reference of Drupal 6 (yi-drupal) |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Mon Jul 9 18:01:44 2012 | Cross-referenced by PHPXref 0.7 |