MediaWiki
master
|
00001 <?php 00039 class SwiftFileBackend extends FileBackendStore { 00041 protected $auth; // Swift authentication handler 00042 protected $authTTL; // integer seconds 00043 protected $swiftTempUrlKey; // string; shared secret value for making temp urls 00044 protected $swiftAnonUser; // string; username to handle unauthenticated requests 00045 protected $swiftUseCDN; // boolean; whether CloudFiles CDN is enabled 00046 protected $swiftCDNExpiry; // integer; how long to cache things in the CDN 00047 protected $swiftCDNPurgable; // boolean; whether object CDN purging is enabled 00048 00050 protected $conn; // Swift connection handle 00051 protected $sessionStarted = 0; // integer UNIX timestamp 00052 00054 protected $connException; 00055 protected $connErrorTime = 0; // UNIX timestamp 00056 00058 protected $srvCache; 00059 00061 protected $connContainerCache; // container object cache 00062 00091 public function __construct( array $config ) { 00092 parent::__construct( $config ); 00093 if ( !MWInit::classExists( 'CF_Constants' ) ) { 00094 throw new MWException( 'SwiftCloudFiles extension not installed.' ); 00095 } 00096 // Required settings 00097 $this->auth = new CF_Authentication( 00098 $config['swiftUser'], 00099 $config['swiftKey'], 00100 null, // account; unused 00101 $config['swiftAuthUrl'] 00102 ); 00103 // Optional settings 00104 $this->authTTL = isset( $config['swiftAuthTTL'] ) 00105 ? $config['swiftAuthTTL'] 00106 : 5 * 60; // some sane number 00107 $this->swiftAnonUser = isset( $config['swiftAnonUser'] ) 00108 ? $config['swiftAnonUser'] 00109 : ''; 00110 $this->swiftTempUrlKey = isset( $config['swiftTempUrlKey'] ) 00111 ? $config['swiftTempUrlKey'] 00112 : ''; 00113 $this->shardViaHashLevels = isset( $config['shardViaHashLevels'] ) 00114 ? $config['shardViaHashLevels'] 00115 : ''; 00116 $this->swiftUseCDN = isset( $config['swiftUseCDN'] ) 00117 ? $config['swiftUseCDN'] 00118 : false; 00119 $this->swiftCDNExpiry = isset( $config['swiftCDNExpiry'] ) 00120 ? $config['swiftCDNExpiry'] 00121 : 12*3600; // 12 hours is safe (tokens last 24 hours per http://docs.openstack.org) 00122 $this->swiftCDNPurgable = isset( $config['swiftCDNPurgable'] ) 00123 ? $config['swiftCDNPurgable'] 00124 : true; 00125 // Cache container information to mask latency 00126 $this->memCache = wfGetMainCache(); 00127 // Process cache for container info 00128 $this->connContainerCache = new ProcessCacheLRU( 300 ); 00129 // Cache auth token information to avoid RTTs 00130 if ( !empty( $config['cacheAuthInfo'] ) ) { 00131 if ( php_sapi_name() === 'cli' ) { 00132 $this->srvCache = wfGetMainCache(); // preferrably memcached 00133 } else { 00134 try { // look for APC, XCache, WinCache, ect... 00135 $this->srvCache = ObjectCache::newAccelerator( array() ); 00136 } catch ( Exception $e ) {} 00137 } 00138 } 00139 $this->srvCache = $this->srvCache ? $this->srvCache : new EmptyBagOStuff(); 00140 } 00141 00146 protected function resolveContainerPath( $container, $relStoragePath ) { 00147 if ( !mb_check_encoding( $relStoragePath, 'UTF-8' ) ) { // mb_string required by CF 00148 return null; // not UTF-8, makes it hard to use CF and the swift HTTP API 00149 } elseif ( strlen( urlencode( $relStoragePath ) ) > 1024 ) { 00150 return null; // too long for Swift 00151 } 00152 return $relStoragePath; 00153 } 00154 00159 public function isPathUsableInternal( $storagePath ) { 00160 list( $container, $rel ) = $this->resolveStoragePathReal( $storagePath ); 00161 if ( $rel === null ) { 00162 return false; // invalid 00163 } 00164 00165 try { 00166 $this->getContainer( $container ); 00167 return true; // container exists 00168 } catch ( NoSuchContainerException $e ) { 00169 } catch ( CloudFilesException $e ) { // some other exception? 00170 $this->handleException( $e, null, __METHOD__, array( 'path' => $storagePath ) ); 00171 } 00172 00173 return false; 00174 } 00175 00180 protected function truncDisp( $disposition ) { 00181 $res = ''; 00182 foreach ( explode( ';', $disposition ) as $part ) { 00183 $part = trim( $part ); 00184 $new = ( $res === '' ) ? $part : "{$res};{$part}"; 00185 if ( strlen( $new ) <= 255 ) { 00186 $res = $new; 00187 } else { 00188 break; // too long; sigh 00189 } 00190 } 00191 return $res; 00192 } 00193 00198 protected function doCreateInternal( array $params ) { 00199 $status = Status::newGood(); 00200 00201 list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] ); 00202 if ( $dstRel === null ) { 00203 $status->fatal( 'backend-fail-invalidpath', $params['dst'] ); 00204 return $status; 00205 } 00206 00207 // (a) Check the destination container and object 00208 try { 00209 $dContObj = $this->getContainer( $dstCont ); 00210 } catch ( NoSuchContainerException $e ) { 00211 $status->fatal( 'backend-fail-create', $params['dst'] ); 00212 return $status; 00213 } catch ( CloudFilesException $e ) { // some other exception? 00214 $this->handleException( $e, $status, __METHOD__, $params ); 00215 return $status; 00216 } 00217 00218 // (b) Get a SHA-1 hash of the object 00219 $sha1Hash = wfBaseConvert( sha1( $params['content'] ), 16, 36, 31 ); 00220 00221 // (c) Actually create the object 00222 try { 00223 // Create a fresh CF_Object with no fields preloaded. 00224 // We don't want to preserve headers, metadata, and such. 00225 $obj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD 00226 $obj->setMetadataValues( array( 'Sha1base36' => $sha1Hash ) ); 00227 // Manually set the ETag (https://github.com/rackspace/php-cloudfiles/issues/59). 00228 // The MD5 here will be checked within Swift against its own MD5. 00229 $obj->set_etag( md5( $params['content'] ) ); 00230 // Use the same content type as StreamFile for security 00231 $obj->content_type = StreamFile::contentTypeFromPath( $params['dst'] ); 00232 if ( !strlen( $obj->content_type ) ) { // special case 00233 $obj->content_type = 'unknown/unknown'; 00234 } 00235 // Set the Content-Disposition header if requested 00236 if ( isset( $params['disposition'] ) ) { 00237 $obj->headers['Content-Disposition'] = $this->truncDisp( $params['disposition'] ); 00238 } 00239 if ( !empty( $params['async'] ) ) { // deferred 00240 $op = $obj->write_async( $params['content'] ); 00241 $status->value = new SwiftFileOpHandle( $this, $params, 'Create', $op ); 00242 $status->value->affectedObjects[] = $obj; 00243 } else { // actually write the object in Swift 00244 $obj->write( $params['content'] ); 00245 $this->purgeCDNCache( array( $obj ) ); 00246 } 00247 } catch ( CDNNotEnabledException $e ) { 00248 // CDN not enabled; nothing to see here 00249 } catch ( BadContentTypeException $e ) { 00250 $status->fatal( 'backend-fail-contenttype', $params['dst'] ); 00251 } catch ( CloudFilesException $e ) { // some other exception? 00252 $this->handleException( $e, $status, __METHOD__, $params ); 00253 } 00254 00255 return $status; 00256 } 00257 00261 protected function _getResponseCreate( CF_Async_Op $cfOp, Status $status, array $params ) { 00262 try { 00263 $cfOp->getLastResponse(); 00264 } catch ( BadContentTypeException $e ) { 00265 $status->fatal( 'backend-fail-contenttype', $params['dst'] ); 00266 } 00267 } 00268 00273 protected function doStoreInternal( array $params ) { 00274 $status = Status::newGood(); 00275 00276 list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] ); 00277 if ( $dstRel === null ) { 00278 $status->fatal( 'backend-fail-invalidpath', $params['dst'] ); 00279 return $status; 00280 } 00281 00282 // (a) Check the destination container and object 00283 try { 00284 $dContObj = $this->getContainer( $dstCont ); 00285 } catch ( NoSuchContainerException $e ) { 00286 $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] ); 00287 return $status; 00288 } catch ( CloudFilesException $e ) { // some other exception? 00289 $this->handleException( $e, $status, __METHOD__, $params ); 00290 return $status; 00291 } 00292 00293 // (b) Get a SHA-1 hash of the object 00294 $sha1Hash = sha1_file( $params['src'] ); 00295 if ( $sha1Hash === false ) { // source doesn't exist? 00296 $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] ); 00297 return $status; 00298 } 00299 $sha1Hash = wfBaseConvert( $sha1Hash, 16, 36, 31 ); 00300 00301 // (c) Actually store the object 00302 try { 00303 // Create a fresh CF_Object with no fields preloaded. 00304 // We don't want to preserve headers, metadata, and such. 00305 $obj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD 00306 $obj->setMetadataValues( array( 'Sha1base36' => $sha1Hash ) ); 00307 // The MD5 here will be checked within Swift against its own MD5. 00308 $obj->set_etag( md5_file( $params['src'] ) ); 00309 // Use the same content type as StreamFile for security 00310 $obj->content_type = StreamFile::contentTypeFromPath( $params['dst'] ); 00311 if ( !strlen( $obj->content_type ) ) { // special case 00312 $obj->content_type = 'unknown/unknown'; 00313 } 00314 // Set the Content-Disposition header if requested 00315 if ( isset( $params['disposition'] ) ) { 00316 $obj->headers['Content-Disposition'] = $this->truncDisp( $params['disposition'] ); 00317 } 00318 if ( !empty( $params['async'] ) ) { // deferred 00319 wfSuppressWarnings(); 00320 $fp = fopen( $params['src'], 'rb' ); 00321 wfRestoreWarnings(); 00322 if ( !$fp ) { 00323 $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] ); 00324 } else { 00325 $op = $obj->write_async( $fp, filesize( $params['src'] ), true ); 00326 $status->value = new SwiftFileOpHandle( $this, $params, 'Store', $op ); 00327 $status->value->resourcesToClose[] = $fp; 00328 $status->value->affectedObjects[] = $obj; 00329 } 00330 } else { // actually write the object in Swift 00331 $obj->load_from_filename( $params['src'], true ); // calls $obj->write() 00332 $this->purgeCDNCache( array( $obj ) ); 00333 } 00334 } catch ( CDNNotEnabledException $e ) { 00335 // CDN not enabled; nothing to see here 00336 } catch ( BadContentTypeException $e ) { 00337 $status->fatal( 'backend-fail-contenttype', $params['dst'] ); 00338 } catch ( IOException $e ) { 00339 $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] ); 00340 } catch ( CloudFilesException $e ) { // some other exception? 00341 $this->handleException( $e, $status, __METHOD__, $params ); 00342 } 00343 00344 return $status; 00345 } 00346 00350 protected function _getResponseStore( CF_Async_Op $cfOp, Status $status, array $params ) { 00351 try { 00352 $cfOp->getLastResponse(); 00353 } catch ( BadContentTypeException $e ) { 00354 $status->fatal( 'backend-fail-contenttype', $params['dst'] ); 00355 } catch ( IOException $e ) { 00356 $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] ); 00357 } 00358 } 00359 00364 protected function doCopyInternal( array $params ) { 00365 $status = Status::newGood(); 00366 00367 list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] ); 00368 if ( $srcRel === null ) { 00369 $status->fatal( 'backend-fail-invalidpath', $params['src'] ); 00370 return $status; 00371 } 00372 00373 list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] ); 00374 if ( $dstRel === null ) { 00375 $status->fatal( 'backend-fail-invalidpath', $params['dst'] ); 00376 return $status; 00377 } 00378 00379 // (a) Check the source/destination containers and destination object 00380 try { 00381 $sContObj = $this->getContainer( $srcCont ); 00382 $dContObj = $this->getContainer( $dstCont ); 00383 } catch ( NoSuchContainerException $e ) { 00384 $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] ); 00385 return $status; 00386 } catch ( CloudFilesException $e ) { // some other exception? 00387 $this->handleException( $e, $status, __METHOD__, $params ); 00388 return $status; 00389 } 00390 00391 // (b) Actually copy the file to the destination 00392 try { 00393 $dstObj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD 00394 $hdrs = array(); // source file headers to override with new values 00395 if ( isset( $params['disposition'] ) ) { 00396 $hdrs['Content-Disposition'] = $this->truncDisp( $params['disposition'] ); 00397 } 00398 if ( !empty( $params['async'] ) ) { // deferred 00399 $op = $sContObj->copy_object_to_async( $srcRel, $dContObj, $dstRel, null, $hdrs ); 00400 $status->value = new SwiftFileOpHandle( $this, $params, 'Copy', $op ); 00401 $status->value->affectedObjects[] = $dstObj; 00402 } else { // actually write the object in Swift 00403 $sContObj->copy_object_to( $srcRel, $dContObj, $dstRel, null, $hdrs ); 00404 $this->purgeCDNCache( array( $dstObj ) ); 00405 } 00406 } catch ( CDNNotEnabledException $e ) { 00407 // CDN not enabled; nothing to see here 00408 } catch ( NoSuchObjectException $e ) { // source object does not exist 00409 if ( empty( $params['ignoreMissingSource'] ) ) { 00410 $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] ); 00411 } 00412 } catch ( CloudFilesException $e ) { // some other exception? 00413 $this->handleException( $e, $status, __METHOD__, $params ); 00414 } 00415 00416 return $status; 00417 } 00418 00422 protected function _getResponseCopy( CF_Async_Op $cfOp, Status $status, array $params ) { 00423 try { 00424 $cfOp->getLastResponse(); 00425 } catch ( NoSuchObjectException $e ) { // source object does not exist 00426 $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] ); 00427 } 00428 } 00429 00434 protected function doMoveInternal( array $params ) { 00435 $status = Status::newGood(); 00436 00437 list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] ); 00438 if ( $srcRel === null ) { 00439 $status->fatal( 'backend-fail-invalidpath', $params['src'] ); 00440 return $status; 00441 } 00442 00443 list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] ); 00444 if ( $dstRel === null ) { 00445 $status->fatal( 'backend-fail-invalidpath', $params['dst'] ); 00446 return $status; 00447 } 00448 00449 // (a) Check the source/destination containers and destination object 00450 try { 00451 $sContObj = $this->getContainer( $srcCont ); 00452 $dContObj = $this->getContainer( $dstCont ); 00453 } catch ( NoSuchContainerException $e ) { 00454 $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] ); 00455 return $status; 00456 } catch ( CloudFilesException $e ) { // some other exception? 00457 $this->handleException( $e, $status, __METHOD__, $params ); 00458 return $status; 00459 } 00460 00461 // (b) Actually move the file to the destination 00462 try { 00463 $srcObj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD 00464 $dstObj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD 00465 $hdrs = array(); // source file headers to override with new values 00466 if ( isset( $params['disposition'] ) ) { 00467 $hdrs['Content-Disposition'] = $this->truncDisp( $params['disposition'] ); 00468 } 00469 if ( !empty( $params['async'] ) ) { // deferred 00470 $op = $sContObj->move_object_to_async( $srcRel, $dContObj, $dstRel, null, $hdrs ); 00471 $status->value = new SwiftFileOpHandle( $this, $params, 'Move', $op ); 00472 $status->value->affectedObjects[] = $srcObj; 00473 $status->value->affectedObjects[] = $dstObj; 00474 } else { // actually write the object in Swift 00475 $sContObj->move_object_to( $srcRel, $dContObj, $dstRel, null, $hdrs ); 00476 $this->purgeCDNCache( array( $srcObj ) ); 00477 $this->purgeCDNCache( array( $dstObj ) ); 00478 } 00479 } catch ( CDNNotEnabledException $e ) { 00480 // CDN not enabled; nothing to see here 00481 } catch ( NoSuchObjectException $e ) { // source object does not exist 00482 if ( empty( $params['ignoreMissingSource'] ) ) { 00483 $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] ); 00484 } 00485 } catch ( CloudFilesException $e ) { // some other exception? 00486 $this->handleException( $e, $status, __METHOD__, $params ); 00487 } 00488 00489 return $status; 00490 } 00491 00495 protected function _getResponseMove( CF_Async_Op $cfOp, Status $status, array $params ) { 00496 try { 00497 $cfOp->getLastResponse(); 00498 } catch ( NoSuchObjectException $e ) { // source object does not exist 00499 $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] ); 00500 } 00501 } 00502 00507 protected function doDeleteInternal( array $params ) { 00508 $status = Status::newGood(); 00509 00510 list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] ); 00511 if ( $srcRel === null ) { 00512 $status->fatal( 'backend-fail-invalidpath', $params['src'] ); 00513 return $status; 00514 } 00515 00516 try { 00517 $sContObj = $this->getContainer( $srcCont ); 00518 $srcObj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD 00519 if ( !empty( $params['async'] ) ) { // deferred 00520 $op = $sContObj->delete_object_async( $srcRel ); 00521 $status->value = new SwiftFileOpHandle( $this, $params, 'Delete', $op ); 00522 $status->value->affectedObjects[] = $srcObj; 00523 } else { // actually write the object in Swift 00524 $sContObj->delete_object( $srcRel ); 00525 $this->purgeCDNCache( array( $srcObj ) ); 00526 } 00527 } catch ( CDNNotEnabledException $e ) { 00528 // CDN not enabled; nothing to see here 00529 } catch ( NoSuchContainerException $e ) { 00530 $status->fatal( 'backend-fail-delete', $params['src'] ); 00531 } catch ( NoSuchObjectException $e ) { 00532 if ( empty( $params['ignoreMissingSource'] ) ) { 00533 $status->fatal( 'backend-fail-delete', $params['src'] ); 00534 } 00535 } catch ( CloudFilesException $e ) { // some other exception? 00536 $this->handleException( $e, $status, __METHOD__, $params ); 00537 } 00538 00539 return $status; 00540 } 00541 00545 protected function _getResponseDelete( CF_Async_Op $cfOp, Status $status, array $params ) { 00546 try { 00547 $cfOp->getLastResponse(); 00548 } catch ( NoSuchContainerException $e ) { 00549 $status->fatal( 'backend-fail-delete', $params['src'] ); 00550 } catch ( NoSuchObjectException $e ) { 00551 if ( empty( $params['ignoreMissingSource'] ) ) { 00552 $status->fatal( 'backend-fail-delete', $params['src'] ); 00553 } 00554 } 00555 } 00556 00561 protected function doPrepareInternal( $fullCont, $dir, array $params ) { 00562 $status = Status::newGood(); 00563 00564 // (a) Check if container already exists 00565 try { 00566 $contObj = $this->getContainer( $fullCont ); 00567 // NoSuchContainerException not thrown: container must exist 00568 return $status; // already exists 00569 } catch ( NoSuchContainerException $e ) { 00570 // NoSuchContainerException thrown: container does not exist 00571 } catch ( CloudFilesException $e ) { // some other exception? 00572 $this->handleException( $e, $status, __METHOD__, $params ); 00573 return $status; 00574 } 00575 00576 // (b) Create container as needed 00577 try { 00578 $contObj = $this->createContainer( $fullCont ); 00579 if ( !empty( $params['noAccess'] ) ) { 00580 // Make container private to end-users... 00581 $status->merge( $this->doSecureInternal( $fullCont, $dir, $params ) ); 00582 } else { 00583 // Make container public to end-users... 00584 $status->merge( $this->doPublishInternal( $fullCont, $dir, $params ) ); 00585 } 00586 if ( $this->swiftUseCDN ) { // Rackspace style CDN 00587 $contObj->make_public( $this->swiftCDNExpiry ); 00588 } 00589 } catch ( CDNNotEnabledException $e ) { 00590 // CDN not enabled; nothing to see here 00591 } catch ( CloudFilesException $e ) { // some other exception? 00592 $this->handleException( $e, $status, __METHOD__, $params ); 00593 return $status; 00594 } 00595 00596 return $status; 00597 } 00598 00603 protected function doSecureInternal( $fullCont, $dir, array $params ) { 00604 $status = Status::newGood(); 00605 if ( empty( $params['noAccess'] ) ) { 00606 return $status; // nothing to do 00607 } 00608 00609 // Restrict container from end-users... 00610 try { 00611 // doPrepareInternal() should have been called, 00612 // so the Swift container should already exist... 00613 $contObj = $this->getContainer( $fullCont ); // normally a cache hit 00614 // NoSuchContainerException not thrown: container must exist 00615 00616 // Make container private to end-users... 00617 $status->merge( $this->setContainerAccess( 00618 $contObj, 00619 array( $this->auth->username ), // read 00620 array( $this->auth->username ) // write 00621 ) ); 00622 if ( $this->swiftUseCDN && $contObj->is_public() ) { // Rackspace style CDN 00623 $contObj->make_private(); 00624 } 00625 } catch ( CDNNotEnabledException $e ) { 00626 // CDN not enabled; nothing to see here 00627 } catch ( CloudFilesException $e ) { // some other exception? 00628 $this->handleException( $e, $status, __METHOD__, $params ); 00629 } 00630 00631 return $status; 00632 } 00633 00638 protected function doPublishInternal( $fullCont, $dir, array $params ) { 00639 $status = Status::newGood(); 00640 00641 // Unrestrict container from end-users... 00642 try { 00643 // doPrepareInternal() should have been called, 00644 // so the Swift container should already exist... 00645 $contObj = $this->getContainer( $fullCont ); // normally a cache hit 00646 // NoSuchContainerException not thrown: container must exist 00647 00648 // Make container public to end-users... 00649 if ( $this->swiftAnonUser != '' ) { 00650 $status->merge( $this->setContainerAccess( 00651 $contObj, 00652 array( $this->auth->username, $this->swiftAnonUser ), // read 00653 array( $this->auth->username, $this->swiftAnonUser ) // write 00654 ) ); 00655 } else { 00656 $status->merge( $this->setContainerAccess( 00657 $contObj, 00658 array( $this->auth->username, '.r:*' ), // read 00659 array( $this->auth->username ) // write 00660 ) ); 00661 } 00662 if ( $this->swiftUseCDN && !$contObj->is_public() ) { // Rackspace style CDN 00663 $contObj->make_public(); 00664 } 00665 } catch ( CDNNotEnabledException $e ) { 00666 // CDN not enabled; nothing to see here 00667 } catch ( CloudFilesException $e ) { // some other exception? 00668 $this->handleException( $e, $status, __METHOD__, $params ); 00669 } 00670 00671 return $status; 00672 } 00673 00678 protected function doCleanInternal( $fullCont, $dir, array $params ) { 00679 $status = Status::newGood(); 00680 00681 // Only containers themselves can be removed, all else is virtual 00682 if ( $dir != '' ) { 00683 return $status; // nothing to do 00684 } 00685 00686 // (a) Check the container 00687 try { 00688 $contObj = $this->getContainer( $fullCont, true ); 00689 } catch ( NoSuchContainerException $e ) { 00690 return $status; // ok, nothing to do 00691 } catch ( CloudFilesException $e ) { // some other exception? 00692 $this->handleException( $e, $status, __METHOD__, $params ); 00693 return $status; 00694 } 00695 00696 // (b) Delete the container if empty 00697 if ( $contObj->object_count == 0 ) { 00698 try { 00699 $this->deleteContainer( $fullCont ); 00700 } catch ( NoSuchContainerException $e ) { 00701 return $status; // race? 00702 } catch ( NonEmptyContainerException $e ) { 00703 return $status; // race? consistency delay? 00704 } catch ( CloudFilesException $e ) { // some other exception? 00705 $this->handleException( $e, $status, __METHOD__, $params ); 00706 return $status; 00707 } 00708 } 00709 00710 return $status; 00711 } 00712 00717 protected function doGetFileStat( array $params ) { 00718 list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] ); 00719 if ( $srcRel === null ) { 00720 return false; // invalid storage path 00721 } 00722 00723 $stat = false; 00724 try { 00725 $contObj = $this->getContainer( $srcCont ); 00726 $srcObj = $contObj->get_object( $srcRel, $this->headersFromParams( $params ) ); 00727 $this->addMissingMetadata( $srcObj, $params['src'] ); 00728 $stat = array( 00729 // Convert dates like "Tue, 03 Jan 2012 22:01:04 GMT" to TS_MW 00730 'mtime' => wfTimestamp( TS_MW, $srcObj->last_modified ), 00731 'size' => (int)$srcObj->content_length, 00732 'sha1' => $srcObj->getMetadataValue( 'Sha1base36' ) 00733 ); 00734 } catch ( NoSuchContainerException $e ) { 00735 } catch ( NoSuchObjectException $e ) { 00736 } catch ( CloudFilesException $e ) { // some other exception? 00737 $stat = null; 00738 $this->handleException( $e, null, __METHOD__, $params ); 00739 } 00740 00741 return $stat; 00742 } 00743 00752 protected function addMissingMetadata( CF_Object $obj, $path ) { 00753 if ( $obj->getMetadataValue( 'Sha1base36' ) !== null ) { 00754 return true; // nothing to do 00755 } 00756 wfProfileIn( __METHOD__ ); 00757 trigger_error( "$path was not stored with SHA-1 metadata.", E_USER_WARNING ); 00758 $status = Status::newGood(); 00759 $scopeLockS = $this->getScopedFileLocks( array( $path ), LockManager::LOCK_UW, $status ); 00760 if ( $status->isOK() ) { 00761 $tmpFile = $this->getLocalCopy( array( 'src' => $path, 'latest' => 1 ) ); 00762 if ( $tmpFile ) { 00763 $hash = $tmpFile->getSha1Base36(); 00764 if ( $hash !== false ) { 00765 $obj->setMetadataValues( array( 'Sha1base36' => $hash ) ); 00766 $obj->sync_metadata(); // save to Swift 00767 wfProfileOut( __METHOD__ ); 00768 return true; // success 00769 } 00770 } 00771 } 00772 $obj->setMetadataValues( array( 'Sha1base36' => false ) ); 00773 wfProfileOut( __METHOD__ ); 00774 return false; // failed 00775 } 00776 00781 protected function doGetFileContentsMulti( array $params ) { 00782 $contents = array(); 00783 00784 $ep = array_diff_key( $params, array( 'srcs' => 1 ) ); // for error logging 00785 // Blindly create tmp files and stream to them, catching any exception if the file does 00786 // not exist. Doing stats here is useless and will loop infinitely in addMissingMetadata(). 00787 foreach ( array_chunk( $params['srcs'], $params['concurrency'] ) as $pathBatch ) { 00788 $cfOps = array(); // (path => CF_Async_Op) 00789 00790 foreach ( $pathBatch as $path ) { // each path in this concurrent batch 00791 list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $path ); 00792 if ( $srcRel === null ) { 00793 $contents[$path] = false; 00794 continue; 00795 } 00796 $data = false; 00797 try { 00798 $sContObj = $this->getContainer( $srcCont ); 00799 $obj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD 00800 // Get source file extension 00801 $ext = FileBackend::extensionFromPath( $path ); 00802 // Create a new temporary memory file... 00803 $handle = fopen( 'php://temp', 'wb' ); 00804 if ( $handle ) { 00805 $headers = $this->headersFromParams( $params ); 00806 if ( count( $pathBatch ) > 1 ) { 00807 $cfOps[$path] = $obj->stream_async( $handle, $headers ); 00808 $cfOps[$path]->_file_handle = $handle; // close this later 00809 } else { 00810 $obj->stream( $handle, $headers ); 00811 rewind( $handle ); // start from the beginning 00812 $data = stream_get_contents( $handle ); 00813 fclose( $handle ); 00814 } 00815 } else { 00816 $data = false; 00817 } 00818 } catch ( NoSuchContainerException $e ) { 00819 $data = false; 00820 } catch ( NoSuchObjectException $e ) { 00821 $data = false; 00822 } catch ( CloudFilesException $e ) { // some other exception? 00823 $data = false; 00824 $this->handleException( $e, null, __METHOD__, array( 'src' => $path ) + $ep ); 00825 } 00826 $contents[$path] = $data; 00827 } 00828 00829 $batch = new CF_Async_Op_Batch( $cfOps ); 00830 $cfOps = $batch->execute(); 00831 foreach ( $cfOps as $path => $cfOp ) { 00832 try { 00833 $cfOp->getLastResponse(); 00834 rewind( $cfOp->_file_handle ); // start from the beginning 00835 $contents[$path] = stream_get_contents( $cfOp->_file_handle ); 00836 } catch ( NoSuchContainerException $e ) { 00837 $contents[$path] = false; 00838 } catch ( NoSuchObjectException $e ) { 00839 $contents[$path] = false; 00840 } catch ( CloudFilesException $e ) { // some other exception? 00841 $contents[$path] = false; 00842 $this->handleException( $e, null, __METHOD__, array( 'src' => $path ) + $ep ); 00843 } 00844 fclose( $cfOp->_file_handle ); // close open handle 00845 } 00846 } 00847 00848 return $contents; 00849 } 00850 00855 protected function doDirectoryExists( $fullCont, $dir, array $params ) { 00856 try { 00857 $container = $this->getContainer( $fullCont ); 00858 $prefix = ( $dir == '' ) ? null : "{$dir}/"; 00859 return ( count( $container->list_objects( 1, null, $prefix ) ) > 0 ); 00860 } catch ( NoSuchContainerException $e ) { 00861 return false; 00862 } catch ( CloudFilesException $e ) { // some other exception? 00863 $this->handleException( $e, null, __METHOD__, 00864 array( 'cont' => $fullCont, 'dir' => $dir ) ); 00865 } 00866 00867 return null; // error 00868 } 00869 00874 public function getDirectoryListInternal( $fullCont, $dir, array $params ) { 00875 return new SwiftFileBackendDirList( $this, $fullCont, $dir, $params ); 00876 } 00877 00882 public function getFileListInternal( $fullCont, $dir, array $params ) { 00883 return new SwiftFileBackendFileList( $this, $fullCont, $dir, $params ); 00884 } 00885 00896 public function getDirListPageInternal( $fullCont, $dir, &$after, $limit, array $params ) { 00897 $dirs = array(); 00898 if ( $after === INF ) { 00899 return $dirs; // nothing more 00900 } 00901 wfProfileIn( __METHOD__ . '-' . $this->name ); 00902 00903 try { 00904 $container = $this->getContainer( $fullCont ); 00905 $prefix = ( $dir == '' ) ? null : "{$dir}/"; 00906 // Non-recursive: only list dirs right under $dir 00907 if ( !empty( $params['topOnly'] ) ) { 00908 $objects = $container->list_objects( $limit, $after, $prefix, null, '/' ); 00909 foreach ( $objects as $object ) { // files and dirs 00910 if ( substr( $object, -1 ) === '/' ) { 00911 $dirs[] = $object; // directories end in '/' 00912 } 00913 } 00914 // Recursive: list all dirs under $dir and its subdirs 00915 } else { 00916 // Get directory from last item of prior page 00917 $lastDir = $this->getParentDir( $after ); // must be first page 00918 $objects = $container->list_objects( $limit, $after, $prefix ); 00919 foreach ( $objects as $object ) { // files 00920 $objectDir = $this->getParentDir( $object ); // directory of object 00921 if ( $objectDir !== false ) { // file has a parent dir 00922 // Swift stores paths in UTF-8, using binary sorting. 00923 // See function "create_container_table" in common/db.py. 00924 // If a directory is not "greater" than the last one, 00925 // then it was already listed by the calling iterator. 00926 if ( strcmp( $objectDir, $lastDir ) > 0 ) { 00927 $pDir = $objectDir; 00928 do { // add dir and all its parent dirs 00929 $dirs[] = "{$pDir}/"; 00930 $pDir = $this->getParentDir( $pDir ); 00931 } while ( $pDir !== false // sanity 00932 && strcmp( $pDir, $lastDir ) > 0 // not done already 00933 && strlen( $pDir ) > strlen( $dir ) // within $dir 00934 ); 00935 } 00936 $lastDir = $objectDir; 00937 } 00938 } 00939 } 00940 if ( count( $objects ) < $limit ) { 00941 $after = INF; // avoid a second RTT 00942 } else { 00943 $after = end( $objects ); // update last item 00944 } 00945 } catch ( NoSuchContainerException $e ) { 00946 } catch ( CloudFilesException $e ) { // some other exception? 00947 $this->handleException( $e, null, __METHOD__, 00948 array( 'cont' => $fullCont, 'dir' => $dir ) ); 00949 } 00950 00951 wfProfileOut( __METHOD__ . '-' . $this->name ); 00952 return $dirs; 00953 } 00954 00955 protected function getParentDir( $path ) { 00956 return ( strpos( $path, '/' ) !== false ) ? dirname( $path ) : false; 00957 } 00958 00969 public function getFileListPageInternal( $fullCont, $dir, &$after, $limit, array $params ) { 00970 $files = array(); 00971 if ( $after === INF ) { 00972 return $files; // nothing more 00973 } 00974 wfProfileIn( __METHOD__ . '-' . $this->name ); 00975 00976 try { 00977 $container = $this->getContainer( $fullCont ); 00978 $prefix = ( $dir == '' ) ? null : "{$dir}/"; 00979 // Non-recursive: only list files right under $dir 00980 if ( !empty( $params['topOnly'] ) ) { // files and dirs 00981 $objects = $container->list_objects( $limit, $after, $prefix, null, '/' ); 00982 foreach ( $objects as $object ) { 00983 if ( substr( $object, -1 ) !== '/' ) { 00984 $files[] = $object; // directories end in '/' 00985 } 00986 } 00987 // Recursive: list all files under $dir and its subdirs 00988 } else { // files 00989 $objects = $container->list_objects( $limit, $after, $prefix ); 00990 $files = $objects; 00991 } 00992 if ( count( $objects ) < $limit ) { 00993 $after = INF; // avoid a second RTT 00994 } else { 00995 $after = end( $objects ); // update last item 00996 } 00997 } catch ( NoSuchContainerException $e ) { 00998 } catch ( CloudFilesException $e ) { // some other exception? 00999 $this->handleException( $e, null, __METHOD__, 01000 array( 'cont' => $fullCont, 'dir' => $dir ) ); 01001 } 01002 01003 wfProfileOut( __METHOD__ . '-' . $this->name ); 01004 return $files; 01005 } 01006 01011 protected function doGetFileSha1base36( array $params ) { 01012 $stat = $this->getFileStat( $params ); 01013 if ( $stat ) { 01014 return $stat['sha1']; 01015 } else { 01016 return false; 01017 } 01018 } 01019 01024 protected function doStreamFile( array $params ) { 01025 $status = Status::newGood(); 01026 01027 list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] ); 01028 if ( $srcRel === null ) { 01029 $status->fatal( 'backend-fail-invalidpath', $params['src'] ); 01030 } 01031 01032 try { 01033 $cont = $this->getContainer( $srcCont ); 01034 } catch ( NoSuchContainerException $e ) { 01035 $status->fatal( 'backend-fail-stream', $params['src'] ); 01036 return $status; 01037 } catch ( CloudFilesException $e ) { // some other exception? 01038 $this->handleException( $e, $status, __METHOD__, $params ); 01039 return $status; 01040 } 01041 01042 try { 01043 $output = fopen( 'php://output', 'wb' ); 01044 $obj = new CF_Object( $cont, $srcRel, false, false ); // skip HEAD 01045 $obj->stream( $output, $this->headersFromParams( $params ) ); 01046 } catch ( NoSuchObjectException $e ) { 01047 $status->fatal( 'backend-fail-stream', $params['src'] ); 01048 } catch ( CloudFilesException $e ) { // some other exception? 01049 $this->handleException( $e, $status, __METHOD__, $params ); 01050 } 01051 01052 return $status; 01053 } 01054 01059 protected function doGetLocalCopyMulti( array $params ) { 01060 $tmpFiles = array(); 01061 01062 $ep = array_diff_key( $params, array( 'srcs' => 1 ) ); // for error logging 01063 // Blindly create tmp files and stream to them, catching any exception if the file does 01064 // not exist. Doing a stat here is useless causes infinite loops in addMissingMetadata(). 01065 foreach ( array_chunk( $params['srcs'], $params['concurrency'] ) as $pathBatch ) { 01066 $cfOps = array(); // (path => CF_Async_Op) 01067 01068 foreach ( $pathBatch as $path ) { // each path in this concurrent batch 01069 list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $path ); 01070 if ( $srcRel === null ) { 01071 $tmpFiles[$path] = null; 01072 continue; 01073 } 01074 $tmpFile = null; 01075 try { 01076 $sContObj = $this->getContainer( $srcCont ); 01077 $obj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD 01078 // Get source file extension 01079 $ext = FileBackend::extensionFromPath( $path ); 01080 // Create a new temporary file... 01081 $tmpFile = TempFSFile::factory( 'localcopy_', $ext ); 01082 if ( $tmpFile ) { 01083 $handle = fopen( $tmpFile->getPath(), 'wb' ); 01084 if ( $handle ) { 01085 $headers = $this->headersFromParams( $params ); 01086 if ( count( $pathBatch ) > 1 ) { 01087 $cfOps[$path] = $obj->stream_async( $handle, $headers ); 01088 $cfOps[$path]->_file_handle = $handle; // close this later 01089 } else { 01090 $obj->stream( $handle, $headers ); 01091 fclose( $handle ); 01092 } 01093 } else { 01094 $tmpFile = null; 01095 } 01096 } 01097 } catch ( NoSuchContainerException $e ) { 01098 $tmpFile = null; 01099 } catch ( NoSuchObjectException $e ) { 01100 $tmpFile = null; 01101 } catch ( CloudFilesException $e ) { // some other exception? 01102 $tmpFile = null; 01103 $this->handleException( $e, null, __METHOD__, array( 'src' => $path ) + $ep ); 01104 } 01105 $tmpFiles[$path] = $tmpFile; 01106 } 01107 01108 $batch = new CF_Async_Op_Batch( $cfOps ); 01109 $cfOps = $batch->execute(); 01110 foreach ( $cfOps as $path => $cfOp ) { 01111 try { 01112 $cfOp->getLastResponse(); 01113 } catch ( NoSuchContainerException $e ) { 01114 $tmpFiles[$path] = null; 01115 } catch ( NoSuchObjectException $e ) { 01116 $tmpFiles[$path] = null; 01117 } catch ( CloudFilesException $e ) { // some other exception? 01118 $tmpFiles[$path] = null; 01119 $this->handleException( $e, null, __METHOD__, array( 'src' => $path ) + $ep ); 01120 } 01121 fclose( $cfOp->_file_handle ); // close open handle 01122 } 01123 } 01124 01125 return $tmpFiles; 01126 } 01127 01132 public function getFileHttpUrl( array $params ) { 01133 if ( $this->swiftTempUrlKey != '' ) { // temp urls enabled 01134 list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] ); 01135 if ( $srcRel === null ) { 01136 return null; // invalid path 01137 } 01138 try { 01139 $sContObj = $this->getContainer( $srcCont ); 01140 $obj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD 01141 return $obj->get_temp_url( $this->swiftTempUrlKey, 86400, "GET" ); 01142 } catch ( NoSuchContainerException $e ) { 01143 } catch ( CloudFilesException $e ) { // some other exception? 01144 $this->handleException( $e, null, __METHOD__, $params ); 01145 } 01146 } 01147 return null; 01148 } 01149 01154 protected function directoriesAreVirtual() { 01155 return true; 01156 } 01157 01166 protected function headersFromParams( array $params ) { 01167 $hdrs = array(); 01168 if ( !empty( $params['latest'] ) ) { 01169 $hdrs[] = 'X-Newest: true'; 01170 } 01171 return $hdrs; 01172 } 01173 01178 protected function doExecuteOpHandlesInternal( array $fileOpHandles ) { 01179 $statuses = array(); 01180 01181 $cfOps = array(); // list of CF_Async_Op objects 01182 foreach ( $fileOpHandles as $index => $fileOpHandle ) { 01183 $cfOps[$index] = $fileOpHandle->cfOp; 01184 } 01185 $batch = new CF_Async_Op_Batch( $cfOps ); 01186 01187 $cfOps = $batch->execute(); 01188 foreach ( $cfOps as $index => $cfOp ) { 01189 $status = Status::newGood(); 01190 try { // catch exceptions; update status 01191 $function = '_getResponse' . $fileOpHandles[$index]->call; 01192 $this->$function( $cfOp, $status, $fileOpHandles[$index]->params ); 01193 $this->purgeCDNCache( $fileOpHandles[$index]->affectedObjects ); 01194 } catch ( CloudFilesException $e ) { // some other exception? 01195 $this->handleException( $e, $status, 01196 __CLASS__ . ":$function", $fileOpHandles[$index]->params ); 01197 } 01198 $statuses[$index] = $status; 01199 } 01200 01201 return $statuses; 01202 } 01203 01230 protected function setContainerAccess( 01231 CF_Container $contObj, array $readGrps, array $writeGrps 01232 ) { 01233 $creds = $contObj->cfs_auth->export_credentials(); 01234 01235 $url = $creds['storage_url'] . '/' . rawurlencode( $contObj->name ); 01236 01237 // Note: 10 second timeout consistent with php-cloudfiles 01238 $req = MWHttpRequest::factory( $url, array( 'method' => 'POST', 'timeout' => 10 ) ); 01239 $req->setHeader( 'X-Auth-Token', $creds['auth_token'] ); 01240 $req->setHeader( 'X-Container-Read', implode( ',', $readGrps ) ); 01241 $req->setHeader( 'X-Container-Write', implode( ',', $writeGrps ) ); 01242 01243 return $req->execute(); // should return 204 01244 } 01245 01253 public function purgeCDNCache( array $objects ) { 01254 if ( $this->swiftUseCDN && $this->swiftCDNPurgable ) { 01255 foreach ( $objects as $object ) { 01256 try { 01257 $object->purge_from_cdn(); 01258 } catch ( CDNNotEnabledException $e ) { 01259 // CDN not enabled; nothing to see here 01260 } catch ( CloudFilesException $e ) { 01261 $this->handleException( $e, null, __METHOD__, 01262 array( 'cont' => $object->container->name, 'obj' => $object->name ) ); 01263 } 01264 } 01265 } 01266 } 01267 01274 protected function getConnection() { 01275 if ( $this->connException instanceof CloudFilesException ) { 01276 if ( ( time() - $this->connErrorTime ) < 60 ) { 01277 throw $this->connException; // failed last attempt; don't bother 01278 } else { // actually retry this time 01279 $this->connException = null; 01280 $this->connErrorTime = 0; 01281 } 01282 } 01283 // Session keys expire after a while, so we renew them periodically 01284 $reAuth = ( ( time() - $this->sessionStarted ) > $this->authTTL ); 01285 // Authenticate with proxy and get a session key... 01286 if ( !$this->conn || $reAuth ) { 01287 $this->sessionStarted = 0; 01288 $this->connContainerCache->clear(); 01289 $cacheKey = $this->getCredsCacheKey( $this->auth->username ); 01290 $creds = $this->srvCache->get( $cacheKey ); // credentials 01291 if ( is_array( $creds ) ) { // cache hit 01292 $this->auth->load_cached_credentials( 01293 $creds['auth_token'], $creds['storage_url'], $creds['cdnm_url'] ); 01294 $this->sessionStarted = time() - ceil( $this->authTTL/2 ); // skew for worst case 01295 } else { // cache miss 01296 try { 01297 $this->auth->authenticate(); 01298 $creds = $this->auth->export_credentials(); 01299 $this->srvCache->add( $cacheKey, $creds, ceil( $this->authTTL/2 ) ); // cache 01300 $this->sessionStarted = time(); 01301 } catch ( CloudFilesException $e ) { 01302 $this->connException = $e; // don't keep re-trying 01303 $this->connErrorTime = time(); 01304 throw $e; // throw it back 01305 } 01306 } 01307 if ( $this->conn ) { // re-authorizing? 01308 $this->conn->close(); // close active cURL handles in CF_Http object 01309 } 01310 $this->conn = new CF_Connection( $this->auth ); 01311 } 01312 return $this->conn; 01313 } 01314 01320 protected function closeConnection() { 01321 if ( $this->conn ) { 01322 $this->conn->close(); // close active cURL handles in CF_Http object 01323 $this->sessionStarted = 0; 01324 $this->connContainerCache->clear(); 01325 } 01326 } 01327 01334 private function getCredsCacheKey( $username ) { 01335 return wfMemcKey( 'backend', $this->getName(), 'usercreds', $username ); 01336 } 01337 01347 protected function getContainer( $container, $bypassCache = false ) { 01348 $conn = $this->getConnection(); // Swift proxy connection 01349 if ( $bypassCache ) { // purge cache 01350 $this->connContainerCache->clear( $container ); 01351 } elseif ( !$this->connContainerCache->has( $container, 'obj' ) ) { 01352 $this->primeContainerCache( array( $container ) ); // check persistent cache 01353 } 01354 if ( !$this->connContainerCache->has( $container, 'obj' ) ) { 01355 $contObj = $conn->get_container( $container ); 01356 // NoSuchContainerException not thrown: container must exist 01357 $this->connContainerCache->set( $container, 'obj', $contObj ); // cache it 01358 if ( !$bypassCache ) { 01359 $this->setContainerCache( $container, // update persistent cache 01360 array( 'bytes' => $contObj->bytes_used, 'count' => $contObj->object_count ) 01361 ); 01362 } 01363 } 01364 return $this->connContainerCache->get( $container, 'obj' ); 01365 } 01366 01374 protected function createContainer( $container ) { 01375 $conn = $this->getConnection(); // Swift proxy connection 01376 $contObj = $conn->create_container( $container ); 01377 $this->connContainerCache->set( $container, 'obj', $contObj ); // cache 01378 return $contObj; 01379 } 01380 01388 protected function deleteContainer( $container ) { 01389 $conn = $this->getConnection(); // Swift proxy connection 01390 $this->connContainerCache->clear( $container ); // purge 01391 $conn->delete_container( $container ); 01392 } 01393 01398 protected function doPrimeContainerCache( array $containerInfo ) { 01399 try { 01400 $conn = $this->getConnection(); // Swift proxy connection 01401 foreach ( $containerInfo as $container => $info ) { 01402 $contObj = new CF_Container( $conn->cfs_auth, $conn->cfs_http, 01403 $container, $info['count'], $info['bytes'] ); 01404 $this->connContainerCache->set( $container, 'obj', $contObj ); 01405 } 01406 } catch ( CloudFilesException $e ) { // some other exception? 01407 $this->handleException( $e, null, __METHOD__, array() ); 01408 } 01409 } 01410 01421 protected function handleException( Exception $e, $status, $func, array $params ) { 01422 if ( $status instanceof Status ) { 01423 if ( $e instanceof AuthenticationException ) { 01424 $status->fatal( 'backend-fail-connect', $this->name ); 01425 } else { 01426 $status->fatal( 'backend-fail-internal', $this->name ); 01427 } 01428 } 01429 if ( $e->getMessage() ) { 01430 trigger_error( "$func: " . $e->getMessage(), E_USER_WARNING ); 01431 } 01432 if ( $e instanceof InvalidResponseException ) { // possibly a stale token 01433 $this->srvCache->delete( $this->getCredsCacheKey( $this->auth->username ) ); 01434 $this->closeConnection(); // force a re-connect and re-auth next time 01435 } 01436 wfDebugLog( 'SwiftBackend', 01437 get_class( $e ) . " in '{$func}' (given '" . FormatJson::encode( $params ) . "')" . 01438 ( $e->getMessage() ? ": {$e->getMessage()}" : "" ) 01439 ); 01440 } 01441 } 01442 01446 class SwiftFileOpHandle extends FileBackendStoreOpHandle { 01448 public $cfOp; 01450 public $affectedObjects = array(); 01451 01452 public function __construct( $backend, array $params, $call, CF_Async_Op $cfOp ) { 01453 $this->backend = $backend; 01454 $this->params = $params; 01455 $this->call = $call; 01456 $this->cfOp = $cfOp; 01457 } 01458 } 01459 01467 abstract class SwiftFileBackendList implements Iterator { 01469 protected $bufferIter = array(); 01470 protected $bufferAfter = null; // string; list items *after* this path 01471 protected $pos = 0; // integer 01473 protected $params = array(); 01474 01476 protected $backend; 01477 protected $container; // string; container name 01478 protected $dir; // string; storage directory 01479 protected $suffixStart; // integer 01480 01481 const PAGE_SIZE = 9000; // file listing buffer size 01482 01489 public function __construct( SwiftFileBackend $backend, $fullCont, $dir, array $params ) { 01490 $this->backend = $backend; 01491 $this->container = $fullCont; 01492 $this->dir = $dir; 01493 if ( substr( $this->dir, -1 ) === '/' ) { 01494 $this->dir = substr( $this->dir, 0, -1 ); // remove trailing slash 01495 } 01496 if ( $this->dir == '' ) { // whole container 01497 $this->suffixStart = 0; 01498 } else { // dir within container 01499 $this->suffixStart = strlen( $this->dir ) + 1; // size of "path/to/dir/" 01500 } 01501 $this->params = $params; 01502 } 01503 01508 public function key() { 01509 return $this->pos; 01510 } 01511 01516 public function next() { 01517 // Advance to the next file in the page 01518 next( $this->bufferIter ); 01519 ++$this->pos; 01520 // Check if there are no files left in this page and 01521 // advance to the next page if this page was not empty. 01522 if ( !$this->valid() && count( $this->bufferIter ) ) { 01523 $this->bufferIter = $this->pageFromList( 01524 $this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE, $this->params 01525 ); // updates $this->bufferAfter 01526 } 01527 } 01528 01533 public function rewind() { 01534 $this->pos = 0; 01535 $this->bufferAfter = null; 01536 $this->bufferIter = $this->pageFromList( 01537 $this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE, $this->params 01538 ); // updates $this->bufferAfter 01539 } 01540 01545 public function valid() { 01546 if ( $this->bufferIter === null ) { 01547 return false; // some failure? 01548 } else { 01549 return ( current( $this->bufferIter ) !== false ); // no paths can have this value 01550 } 01551 } 01552 01563 abstract protected function pageFromList( $container, $dir, &$after, $limit, array $params ); 01564 } 01565 01569 class SwiftFileBackendDirList extends SwiftFileBackendList { 01574 public function current() { 01575 return substr( current( $this->bufferIter ), $this->suffixStart, -1 ); 01576 } 01577 01582 protected function pageFromList( $container, $dir, &$after, $limit, array $params ) { 01583 return $this->backend->getDirListPageInternal( $container, $dir, $after, $limit, $params ); 01584 } 01585 } 01586 01590 class SwiftFileBackendFileList extends SwiftFileBackendList { 01595 public function current() { 01596 return substr( current( $this->bufferIter ), $this->suffixStart ); 01597 } 01598 01603 protected function pageFromList( $container, $dir, &$after, $limit, array $params ) { 01604 return $this->backend->getFileListPageInternal( $container, $dir, $after, $limit, $params ); 01605 } 01606 }