MediaWiki  REL1_19
SwiftFileBackend.php
Go to the documentation of this file.
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 }