MediaWiki
REL1_19
|
00001 <?php 00022 class SwiftFileBackend extends FileBackendStore { 00024 protected $auth; // Swift authentication handler 00025 protected $authTTL; // integer seconds 00026 protected $swiftAnonUser; // string; username to handle unauthenticated requests 00027 protected $maxContCacheSize = 100; // integer; max containers with entries 00028 00030 protected $conn; // Swift connection handle 00031 protected $connStarted = 0; // integer UNIX timestamp 00032 protected $connContainers = array(); // container object cache 00033 00048 public function __construct( array $config ) { 00049 parent::__construct( $config ); 00050 // Required settings 00051 $this->auth = new CF_Authentication( 00052 $config['swiftUser'], 00053 $config['swiftKey'], 00054 null, // account; unused 00055 $config['swiftAuthUrl'] 00056 ); 00057 // Optional settings 00058 $this->authTTL = isset( $config['swiftAuthTTL'] ) 00059 ? $config['swiftAuthTTL'] 00060 : 120; // some sane number 00061 $this->swiftAnonUser = isset( $config['swiftAnonUser'] ) 00062 ? $config['swiftAnonUser'] 00063 : ''; 00064 $this->shardViaHashLevels = isset( $config['shardViaHashLevels'] ) 00065 ? $config['shardViaHashLevels'] 00066 : ''; 00067 } 00068 00072 protected function resolveContainerPath( $container, $relStoragePath ) { 00073 if ( strlen( urlencode( $relStoragePath ) ) > 1024 ) { 00074 return null; // too long for Swift 00075 } 00076 return $relStoragePath; 00077 } 00078 00082 public function isPathUsableInternal( $storagePath ) { 00083 list( $container, $rel ) = $this->resolveStoragePathReal( $storagePath ); 00084 if ( $rel === null ) { 00085 return false; // invalid 00086 } 00087 00088 try { 00089 $this->getContainer( $container ); 00090 return true; // container exists 00091 } catch ( NoSuchContainerException $e ) { 00092 } catch ( InvalidResponseException $e ) { 00093 } catch ( Exception $e ) { // some other exception? 00094 $this->logException( $e, __METHOD__, array( 'path' => $storagePath ) ); 00095 } 00096 00097 return false; 00098 } 00099 00103 protected function doCreateInternal( array $params ) { 00104 $status = Status::newGood(); 00105 00106 list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] ); 00107 if ( $dstRel === null ) { 00108 $status->fatal( 'backend-fail-invalidpath', $params['dst'] ); 00109 return $status; 00110 } 00111 00112 // (a) Check the destination container and object 00113 try { 00114 $dContObj = $this->getContainer( $dstCont ); 00115 if ( empty( $params['overwrite'] ) && 00116 $this->fileExists( array( 'src' => $params['dst'], 'latest' => 1 ) ) ) 00117 { 00118 $status->fatal( 'backend-fail-alreadyexists', $params['dst'] ); 00119 return $status; 00120 } 00121 } catch ( NoSuchContainerException $e ) { 00122 $status->fatal( 'backend-fail-create', $params['dst'] ); 00123 return $status; 00124 } catch ( InvalidResponseException $e ) { 00125 $status->fatal( 'backend-fail-connect', $this->name ); 00126 return $status; 00127 } catch ( Exception $e ) { // some other exception? 00128 $status->fatal( 'backend-fail-internal', $this->name ); 00129 $this->logException( $e, __METHOD__, $params ); 00130 return $status; 00131 } 00132 00133 // (b) Get a SHA-1 hash of the object 00134 $sha1Hash = wfBaseConvert( sha1( $params['content'] ), 16, 36, 31 ); 00135 00136 // (c) Actually create the object 00137 try { 00138 // Create a fresh CF_Object with no fields preloaded. 00139 // We don't want to preserve headers, metadata, and such. 00140 $obj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD 00141 // Note: metadata keys stored as [Upper case char][[Lower case char]...] 00142 $obj->metadata = array( 'Sha1base36' => $sha1Hash ); 00143 // Manually set the ETag (https://github.com/rackspace/php-cloudfiles/issues/59). 00144 // The MD5 here will be checked within Swift against its own MD5. 00145 $obj->set_etag( md5( $params['content'] ) ); 00146 // Use the same content type as StreamFile for security 00147 $obj->content_type = StreamFile::contentTypeFromPath( $params['dst'] ); 00148 // Actually write the object in Swift 00149 $obj->write( $params['content'] ); 00150 } catch ( BadContentTypeException $e ) { 00151 $status->fatal( 'backend-fail-contenttype', $params['dst'] ); 00152 } catch ( InvalidResponseException $e ) { 00153 $status->fatal( 'backend-fail-connect', $this->name ); 00154 } catch ( Exception $e ) { // some other exception? 00155 $status->fatal( 'backend-fail-internal', $this->name ); 00156 $this->logException( $e, __METHOD__, $params ); 00157 } 00158 00159 return $status; 00160 } 00161 00165 protected function doStoreInternal( array $params ) { 00166 $status = Status::newGood(); 00167 00168 list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] ); 00169 if ( $dstRel === null ) { 00170 $status->fatal( 'backend-fail-invalidpath', $params['dst'] ); 00171 return $status; 00172 } 00173 00174 // (a) Check the destination container and object 00175 try { 00176 $dContObj = $this->getContainer( $dstCont ); 00177 if ( empty( $params['overwrite'] ) && 00178 $this->fileExists( array( 'src' => $params['dst'], 'latest' => 1 ) ) ) 00179 { 00180 $status->fatal( 'backend-fail-alreadyexists', $params['dst'] ); 00181 return $status; 00182 } 00183 } catch ( NoSuchContainerException $e ) { 00184 $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] ); 00185 return $status; 00186 } catch ( InvalidResponseException $e ) { 00187 $status->fatal( 'backend-fail-connect', $this->name ); 00188 return $status; 00189 } catch ( Exception $e ) { // some other exception? 00190 $status->fatal( 'backend-fail-internal', $this->name ); 00191 $this->logException( $e, __METHOD__, $params ); 00192 return $status; 00193 } 00194 00195 // (b) Get a SHA-1 hash of the object 00196 $sha1Hash = sha1_file( $params['src'] ); 00197 if ( $sha1Hash === false ) { // source doesn't exist? 00198 $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] ); 00199 return $status; 00200 } 00201 $sha1Hash = wfBaseConvert( $sha1Hash, 16, 36, 31 ); 00202 00203 // (c) Actually store the object 00204 try { 00205 // Create a fresh CF_Object with no fields preloaded. 00206 // We don't want to preserve headers, metadata, and such. 00207 $obj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD 00208 // Note: metadata keys stored as [Upper case char][[Lower case char]...] 00209 $obj->metadata = array( 'Sha1base36' => $sha1Hash ); 00210 // The MD5 here will be checked within Swift against its own MD5. 00211 $obj->set_etag( md5_file( $params['src'] ) ); 00212 // Use the same content type as StreamFile for security 00213 $obj->content_type = StreamFile::contentTypeFromPath( $params['dst'] ); 00214 // Actually write the object in Swift 00215 $obj->load_from_filename( $params['src'], True ); // calls $obj->write() 00216 } catch ( BadContentTypeException $e ) { 00217 $status->fatal( 'backend-fail-contenttype', $params['dst'] ); 00218 } catch ( IOException $e ) { 00219 $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] ); 00220 } catch ( InvalidResponseException $e ) { 00221 $status->fatal( 'backend-fail-connect', $this->name ); 00222 } catch ( Exception $e ) { // some other exception? 00223 $status->fatal( 'backend-fail-internal', $this->name ); 00224 $this->logException( $e, __METHOD__, $params ); 00225 } 00226 00227 return $status; 00228 } 00229 00233 protected function doCopyInternal( array $params ) { 00234 $status = Status::newGood(); 00235 00236 list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] ); 00237 if ( $srcRel === null ) { 00238 $status->fatal( 'backend-fail-invalidpath', $params['src'] ); 00239 return $status; 00240 } 00241 00242 list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] ); 00243 if ( $dstRel === null ) { 00244 $status->fatal( 'backend-fail-invalidpath', $params['dst'] ); 00245 return $status; 00246 } 00247 00248 // (a) Check the source/destination containers and destination object 00249 try { 00250 $sContObj = $this->getContainer( $srcCont ); 00251 $dContObj = $this->getContainer( $dstCont ); 00252 if ( empty( $params['overwrite'] ) && 00253 $this->fileExists( array( 'src' => $params['dst'], 'latest' => 1 ) ) ) 00254 { 00255 $status->fatal( 'backend-fail-alreadyexists', $params['dst'] ); 00256 return $status; 00257 } 00258 } catch ( NoSuchContainerException $e ) { 00259 $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] ); 00260 return $status; 00261 } catch ( InvalidResponseException $e ) { 00262 $status->fatal( 'backend-fail-connect', $this->name ); 00263 return $status; 00264 } catch ( Exception $e ) { // some other exception? 00265 $status->fatal( 'backend-fail-internal', $this->name ); 00266 $this->logException( $e, __METHOD__, $params ); 00267 return $status; 00268 } 00269 00270 // (b) Actually copy the file to the destination 00271 try { 00272 $sContObj->copy_object_to( $srcRel, $dContObj, $dstRel ); 00273 } catch ( NoSuchObjectException $e ) { // source object does not exist 00274 $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] ); 00275 } catch ( InvalidResponseException $e ) { 00276 $status->fatal( 'backend-fail-connect', $this->name ); 00277 } catch ( Exception $e ) { // some other exception? 00278 $status->fatal( 'backend-fail-internal', $this->name ); 00279 $this->logException( $e, __METHOD__, $params ); 00280 } 00281 00282 return $status; 00283 } 00284 00288 protected function doDeleteInternal( array $params ) { 00289 $status = Status::newGood(); 00290 00291 list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] ); 00292 if ( $srcRel === null ) { 00293 $status->fatal( 'backend-fail-invalidpath', $params['src'] ); 00294 return $status; 00295 } 00296 00297 try { 00298 $sContObj = $this->getContainer( $srcCont ); 00299 $sContObj->delete_object( $srcRel ); 00300 } catch ( NoSuchContainerException $e ) { 00301 $status->fatal( 'backend-fail-delete', $params['src'] ); 00302 } catch ( NoSuchObjectException $e ) { 00303 if ( empty( $params['ignoreMissingSource'] ) ) { 00304 $status->fatal( 'backend-fail-delete', $params['src'] ); 00305 } 00306 } catch ( InvalidResponseException $e ) { 00307 $status->fatal( 'backend-fail-connect', $this->name ); 00308 } catch ( Exception $e ) { // some other exception? 00309 $status->fatal( 'backend-fail-internal', $this->name ); 00310 $this->logException( $e, __METHOD__, $params ); 00311 } 00312 00313 return $status; 00314 } 00315 00319 protected function doPrepareInternal( $fullCont, $dir, array $params ) { 00320 $status = Status::newGood(); 00321 00322 // (a) Check if container already exists 00323 try { 00324 $contObj = $this->getContainer( $fullCont ); 00325 // NoSuchContainerException not thrown: container must exist 00326 return $status; // already exists 00327 } catch ( NoSuchContainerException $e ) { 00328 // NoSuchContainerException thrown: container does not exist 00329 } catch ( InvalidResponseException $e ) { 00330 $status->fatal( 'backend-fail-connect', $this->name ); 00331 return $status; 00332 } catch ( Exception $e ) { // some other exception? 00333 $status->fatal( 'backend-fail-internal', $this->name ); 00334 $this->logException( $e, __METHOD__, $params ); 00335 return $status; 00336 } 00337 00338 // (b) Create container as needed 00339 try { 00340 $contObj = $this->createContainer( $fullCont ); 00341 if ( $this->swiftAnonUser != '' ) { 00342 // Make container public to end-users... 00343 $status->merge( $this->setContainerAccess( 00344 $contObj, 00345 array( $this->auth->username, $this->swiftAnonUser ), // read 00346 array( $this->auth->username ) // write 00347 ) ); 00348 } 00349 } catch ( InvalidResponseException $e ) { 00350 $status->fatal( 'backend-fail-connect', $this->name ); 00351 return $status; 00352 } catch ( Exception $e ) { // some other exception? 00353 $status->fatal( 'backend-fail-internal', $this->name ); 00354 $this->logException( $e, __METHOD__, $params ); 00355 return $status; 00356 } 00357 00358 return $status; 00359 } 00360 00364 protected function doSecureInternal( $fullCont, $dir, array $params ) { 00365 $status = Status::newGood(); 00366 00367 if ( $this->swiftAnonUser != '' ) { 00368 // Restrict container from end-users... 00369 try { 00370 // doPrepareInternal() should have been called, 00371 // so the Swift container should already exist... 00372 $contObj = $this->getContainer( $fullCont ); // normally a cache hit 00373 // NoSuchContainerException not thrown: container must exist 00374 if ( !isset( $contObj->mw_wasSecured ) ) { 00375 $status->merge( $this->setContainerAccess( 00376 $contObj, 00377 array( $this->auth->username ), // read 00378 array( $this->auth->username ) // write 00379 ) ); 00380 // @TODO: when php-cloudfiles supports container 00381 // metadata, we can make use of that to avoid RTTs 00382 $contObj->mw_wasSecured = true; // avoid useless RTTs 00383 } 00384 } catch ( InvalidResponseException $e ) { 00385 $status->fatal( 'backend-fail-connect', $this->name ); 00386 } catch ( Exception $e ) { // some other exception? 00387 $status->fatal( 'backend-fail-internal', $this->name ); 00388 $this->logException( $e, __METHOD__, $params ); 00389 } 00390 } 00391 00392 return $status; 00393 } 00394 00398 protected function doCleanInternal( $fullCont, $dir, array $params ) { 00399 $status = Status::newGood(); 00400 00401 // Only containers themselves can be removed, all else is virtual 00402 if ( $dir != '' ) { 00403 return $status; // nothing to do 00404 } 00405 00406 // (a) Check the container 00407 try { 00408 $contObj = $this->getContainer( $fullCont, true ); 00409 } catch ( NoSuchContainerException $e ) { 00410 return $status; // ok, nothing to do 00411 } catch ( InvalidResponseException $e ) { 00412 $status->fatal( 'backend-fail-connect', $this->name ); 00413 return $status; 00414 } catch ( Exception $e ) { // some other exception? 00415 $status->fatal( 'backend-fail-internal', $this->name ); 00416 $this->logException( $e, __METHOD__, $params ); 00417 return $status; 00418 } 00419 00420 // (b) Delete the container if empty 00421 if ( $contObj->object_count == 0 ) { 00422 try { 00423 $this->deleteContainer( $fullCont ); 00424 } catch ( NoSuchContainerException $e ) { 00425 return $status; // race? 00426 } catch ( InvalidResponseException $e ) { 00427 $status->fatal( 'backend-fail-connect', $this->name ); 00428 return $status; 00429 } catch ( Exception $e ) { // some other exception? 00430 $status->fatal( 'backend-fail-internal', $this->name ); 00431 $this->logException( $e, __METHOD__, $params ); 00432 return $status; 00433 } 00434 } 00435 00436 return $status; 00437 } 00438 00442 protected function doGetFileStat( array $params ) { 00443 list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] ); 00444 if ( $srcRel === null ) { 00445 return false; // invalid storage path 00446 } 00447 00448 $stat = false; 00449 try { 00450 $contObj = $this->getContainer( $srcCont ); 00451 $srcObj = $contObj->get_object( $srcRel, $this->headersFromParams( $params ) ); 00452 $this->addMissingMetadata( $srcObj, $params['src'] ); 00453 $stat = array( 00454 // Convert dates like "Tue, 03 Jan 2012 22:01:04 GMT" to TS_MW 00455 'mtime' => wfTimestamp( TS_MW, $srcObj->last_modified ), 00456 'size' => $srcObj->content_length, 00457 'sha1' => $srcObj->metadata['Sha1base36'] 00458 ); 00459 } catch ( NoSuchContainerException $e ) { 00460 } catch ( NoSuchObjectException $e ) { 00461 } catch ( InvalidResponseException $e ) { 00462 $stat = null; 00463 } catch ( Exception $e ) { // some other exception? 00464 $stat = null; 00465 $this->logException( $e, __METHOD__, $params ); 00466 } 00467 00468 return $stat; 00469 } 00470 00479 protected function addMissingMetadata( CF_Object $obj, $path ) { 00480 if ( isset( $obj->metadata['Sha1base36'] ) ) { 00481 return true; // nothing to do 00482 } 00483 $status = Status::newGood(); 00484 $scopeLockS = $this->getScopedFileLocks( array( $path ), LockManager::LOCK_UW, $status ); 00485 if ( $status->isOK() ) { 00486 $tmpFile = $this->getLocalCopy( array( 'src' => $path, 'latest' => 1 ) ); 00487 if ( $tmpFile ) { 00488 $hash = $tmpFile->getSha1Base36(); 00489 if ( $hash !== false ) { 00490 $obj->metadata['Sha1base36'] = $hash; 00491 $obj->sync_metadata(); // save to Swift 00492 return true; // success 00493 } 00494 } 00495 } 00496 $obj->metadata['Sha1base36'] = false; 00497 return false; // failed 00498 } 00499 00503 public function getFileContents( array $params ) { 00504 list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] ); 00505 if ( $srcRel === null ) { 00506 return false; // invalid storage path 00507 } 00508 00509 if ( !$this->fileExists( $params ) ) { 00510 return null; 00511 } 00512 00513 $data = false; 00514 try { 00515 $sContObj = $this->getContainer( $srcCont ); 00516 $obj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD request 00517 $data = $obj->read( $this->headersFromParams( $params ) ); 00518 } catch ( NoSuchContainerException $e ) { 00519 } catch ( InvalidResponseException $e ) { 00520 } catch ( Exception $e ) { // some other exception? 00521 $this->logException( $e, __METHOD__, $params ); 00522 } 00523 00524 return $data; 00525 } 00526 00530 public function getFileListInternal( $fullCont, $dir, array $params ) { 00531 return new SwiftFileBackendFileList( $this, $fullCont, $dir ); 00532 } 00533 00543 public function getFileListPageInternal( $fullCont, $dir, $after, $limit ) { 00544 $files = array(); 00545 00546 try { 00547 $container = $this->getContainer( $fullCont ); 00548 $prefix = ( $dir == '' ) ? null : "{$dir}/"; 00549 $files = $container->list_objects( $limit, $after, $prefix ); 00550 } catch ( NoSuchContainerException $e ) { 00551 } catch ( NoSuchObjectException $e ) { 00552 } catch ( InvalidResponseException $e ) { 00553 } catch ( Exception $e ) { // some other exception? 00554 $this->logException( $e, __METHOD__, array( 'cont' => $fullCont, 'dir' => $dir ) ); 00555 } 00556 00557 return $files; 00558 } 00559 00563 public function doGetFileSha1base36( array $params ) { 00564 $stat = $this->getFileStat( $params ); 00565 if ( $stat ) { 00566 return $stat['sha1']; 00567 } else { 00568 return false; 00569 } 00570 } 00571 00575 protected function doStreamFile( array $params ) { 00576 $status = Status::newGood(); 00577 00578 list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] ); 00579 if ( $srcRel === null ) { 00580 $status->fatal( 'backend-fail-invalidpath', $params['src'] ); 00581 } 00582 00583 try { 00584 $cont = $this->getContainer( $srcCont ); 00585 } catch ( NoSuchContainerException $e ) { 00586 $status->fatal( 'backend-fail-stream', $params['src'] ); 00587 return $status; 00588 } catch ( InvalidResponseException $e ) { 00589 $status->fatal( 'backend-fail-connect', $this->name ); 00590 return $status; 00591 } catch ( Exception $e ) { // some other exception? 00592 $status->fatal( 'backend-fail-stream', $params['src'] ); 00593 $this->logException( $e, __METHOD__, $params ); 00594 return $status; 00595 } 00596 00597 try { 00598 $output = fopen( 'php://output', 'wb' ); 00599 $obj = new CF_Object( $cont, $srcRel, false, false ); // skip HEAD request 00600 $obj->stream( $output, $this->headersFromParams( $params ) ); 00601 } catch ( InvalidResponseException $e ) { // 404? connection problem? 00602 $status->fatal( 'backend-fail-stream', $params['src'] ); 00603 } catch ( Exception $e ) { // some other exception? 00604 $status->fatal( 'backend-fail-stream', $params['src'] ); 00605 $this->logException( $e, __METHOD__, $params ); 00606 } 00607 00608 return $status; 00609 } 00610 00614 public function getLocalCopy( array $params ) { 00615 list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] ); 00616 if ( $srcRel === null ) { 00617 return null; 00618 } 00619 00620 if ( !$this->fileExists( $params ) ) { 00621 return null; 00622 } 00623 00624 $tmpFile = null; 00625 try { 00626 $sContObj = $this->getContainer( $srcCont ); 00627 $obj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD 00628 // Get source file extension 00629 $ext = FileBackend::extensionFromPath( $srcRel ); 00630 // Create a new temporary file... 00631 $tmpFile = TempFSFile::factory( wfBaseName( $srcRel ) . '_', $ext ); 00632 if ( $tmpFile ) { 00633 $handle = fopen( $tmpFile->getPath(), 'wb' ); 00634 if ( $handle ) { 00635 $obj->stream( $handle, $this->headersFromParams( $params ) ); 00636 fclose( $handle ); 00637 } else { 00638 $tmpFile = null; // couldn't open temp file 00639 } 00640 } 00641 } catch ( NoSuchContainerException $e ) { 00642 $tmpFile = null; 00643 } catch ( InvalidResponseException $e ) { 00644 $tmpFile = null; 00645 } catch ( Exception $e ) { // some other exception? 00646 $tmpFile = null; 00647 $this->logException( $e, __METHOD__, $params ); 00648 } 00649 00650 return $tmpFile; 00651 } 00652 00661 protected function headersFromParams( array $params ) { 00662 $hdrs = array(); 00663 if ( !empty( $params['latest'] ) ) { 00664 $hdrs[] = 'X-Newest: true'; 00665 } 00666 return $hdrs; 00667 } 00668 00677 protected function setContainerAccess( 00678 CF_Container $contObj, array $readGrps, array $writeGrps 00679 ) { 00680 $creds = $contObj->cfs_auth->export_credentials(); 00681 00682 $url = $creds['storage_url'] . '/' . rawurlencode( $contObj->name ); 00683 00684 // Note: 10 second timeout consistent with php-cloudfiles 00685 $req = new CurlHttpRequest( $url, array( 'method' => 'POST', 'timeout' => 10 ) ); 00686 $req->setHeader( 'X-Auth-Token', $creds['auth_token'] ); 00687 $req->setHeader( 'X-Container-Read', implode( ',', $readGrps ) ); 00688 $req->setHeader( 'X-Container-Write', implode( ',', $writeGrps ) ); 00689 00690 return $req->execute(); // should return 204 00691 } 00692 00699 protected function getConnection() { 00700 if ( $this->conn === false ) { 00701 throw new InvalidResponseException; // failed last attempt 00702 } 00703 // Session keys expire after a while, so we renew them periodically 00704 if ( $this->conn && ( time() - $this->connStarted ) > $this->authTTL ) { 00705 $this->conn->close(); // close active cURL connections 00706 $this->conn = null; 00707 } 00708 // Authenticate with proxy and get a session key... 00709 if ( $this->conn === null ) { 00710 $this->connContainers = array(); 00711 try { 00712 $this->auth->authenticate(); 00713 $this->conn = new CF_Connection( $this->auth ); 00714 $this->connStarted = time(); 00715 } catch ( AuthenticationException $e ) { 00716 $this->conn = false; // don't keep re-trying 00717 } catch ( InvalidResponseException $e ) { 00718 $this->conn = false; // don't keep re-trying 00719 } 00720 } 00721 if ( !$this->conn ) { 00722 throw new InvalidResponseException; // auth/connection problem 00723 } 00724 return $this->conn; 00725 } 00726 00730 protected function doClearCache( array $paths = null ) { 00731 $this->connContainers = array(); // clear container object cache 00732 } 00733 00742 protected function getContainer( $container, $reCache = false ) { 00743 $conn = $this->getConnection(); // Swift proxy connection 00744 if ( $reCache ) { 00745 unset( $this->connContainers[$container] ); // purge cache 00746 } 00747 if ( !isset( $this->connContainers[$container] ) ) { 00748 $contObj = $conn->get_container( $container ); 00749 // NoSuchContainerException not thrown: container must exist 00750 if ( count( $this->connContainers ) >= $this->maxContCacheSize ) { // trim cache? 00751 reset( $this->connContainers ); 00752 $key = key( $this->connContainers ); 00753 unset( $this->connContainers[$key] ); 00754 } 00755 $this->connContainers[$container] = $contObj; // cache it 00756 } 00757 return $this->connContainers[$container]; 00758 } 00759 00766 protected function createContainer( $container ) { 00767 $conn = $this->getConnection(); // Swift proxy connection 00768 $contObj = $conn->create_container( $container ); 00769 $this->connContainers[$container] = $contObj; // cache it 00770 return $contObj; 00771 } 00772 00779 protected function deleteContainer( $container ) { 00780 $conn = $this->getConnection(); // Swift proxy connection 00781 $conn->delete_container( $container ); 00782 unset( $this->connContainers[$container] ); // purge cache 00783 } 00784 00793 protected function logException( Exception $e, $func, array $params ) { 00794 wfDebugLog( 'SwiftBackend', 00795 get_class( $e ) . " in '{$func}' (given '" . serialize( $params ) . "')" . 00796 ( $e instanceof InvalidResponseException 00797 ? ": {$e->getMessage()}" 00798 : "" 00799 ) 00800 ); 00801 } 00802 } 00803 00811 class SwiftFileBackendFileList implements Iterator { 00813 protected $bufferIter = array(); 00814 protected $bufferAfter = null; // string; list items *after* this path 00815 protected $pos = 0; // integer 00816 00818 protected $backend; 00819 protected $container; // 00820 protected $dir; // string storage directory 00821 protected $suffixStart; // integer 00822 00823 const PAGE_SIZE = 5000; // file listing buffer size 00824 00830 public function __construct( SwiftFileBackend $backend, $fullCont, $dir ) { 00831 $this->backend = $backend; 00832 $this->container = $fullCont; 00833 $this->dir = $dir; 00834 if ( substr( $this->dir, -1 ) === '/' ) { 00835 $this->dir = substr( $this->dir, 0, -1 ); // remove trailing slash 00836 } 00837 if ( $this->dir == '' ) { // whole container 00838 $this->suffixStart = 0; 00839 } else { // dir within container 00840 $this->suffixStart = strlen( $this->dir ) + 1; // size of "path/to/dir/" 00841 } 00842 } 00843 00844 public function current() { 00845 return substr( current( $this->bufferIter ), $this->suffixStart ); 00846 } 00847 00848 public function key() { 00849 return $this->pos; 00850 } 00851 00852 public function next() { 00853 // Advance to the next file in the page 00854 next( $this->bufferIter ); 00855 ++$this->pos; 00856 // Check if there are no files left in this page and 00857 // advance to the next page if this page was not empty. 00858 if ( !$this->valid() && count( $this->bufferIter ) ) { 00859 $this->bufferAfter = end( $this->bufferIter ); 00860 $this->bufferIter = $this->backend->getFileListPageInternal( 00861 $this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE 00862 ); 00863 } 00864 } 00865 00866 public function rewind() { 00867 $this->pos = 0; 00868 $this->bufferAfter = null; 00869 $this->bufferIter = $this->backend->getFileListPageInternal( 00870 $this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE 00871 ); 00872 } 00873 00874 public function valid() { 00875 return ( current( $this->bufferIter ) !== false ); // no paths can have this value 00876 } 00877 }