79662eeddc4a7cad8273bba28bb19f153ce48bb8
10 * Class for an OpenStack Swift based file backend.
12 * This requires the SwiftCloudFiles MediaWiki extension, which includes
13 * the php-cloudfiles library (https://github.com/rackspace/php-cloudfiles).
14 * php-cloudfiles requires the curl, fileinfo, and mb_string PHP extensions.
16 * Status messages should avoid mentioning the Swift account name.
17 * Likewise, error suppression should be used to avoid path disclosure.
19 * @ingroup FileBackend
22 class SwiftFileBackend
extends FileBackend
{
23 /** @var CF_Authentication */
24 protected $auth; // Swift authentication handler
25 protected $authTTL; // integer seconds
26 protected $swiftAnonUser; // string; username to handle unauthenticated requests
27 protected $maxContCacheSize = 20; // integer; max containers with entries
29 /** @var CF_Connection */
30 protected $conn; // Swift connection handle
31 protected $connStarted = 0; // integer UNIX timestamp
32 protected $connContainers = array(); // container object cache
35 * @see FileBackend::__construct()
36 * Additional $config params include:
37 * swiftAuthUrl : Swift authentication server URL
38 * swiftUser : Swift user used by MediaWiki (account:username)
39 * swiftKey : Swift authentication key for the above user
40 * swiftAuthTTL : Swift authentication TTL (seconds)
41 * swiftAnonUser : Swift user used for end-user requests (account:username)
42 * shardViaHashLevels : Map of container names to the number of hash levels
44 public function __construct( array $config ) {
45 parent
::__construct( $config );
47 $this->auth
= new CF_Authentication(
50 null, // account; unused
51 $config['swiftAuthUrl']
54 $this->authTTL
= isset( $config['swiftAuthTTL'] )
55 ?
$config['swiftAuthTTL']
56 : 120; // some sane number
57 $this->swiftAnonUser
= isset( $config['swiftAnonUser'] )
58 ?
$config['swiftAnonUser']
60 $this->shardViaHashLevels
= isset( $config['shardViaHashLevels'] )
61 ?
$config['shardViaHashLevels']
66 * @see FileBackend::resolveContainerPath()
68 protected function resolveContainerPath( $container, $relStoragePath ) {
69 if ( strlen( urlencode( $relStoragePath ) ) > 1024 ) {
70 return null; // too long for Swift
72 return $relStoragePath;
76 * @see FileBackend::isPathUsableInternal()
78 public function isPathUsableInternal( $storagePath ) {
79 list( $container, $rel ) = $this->resolveStoragePathReal( $storagePath );
80 if ( $rel === null ) {
81 return false; // invalid
85 $this->getContainer( $container );
86 return true; // container exists
87 } catch ( NoSuchContainerException
$e ) {
88 } catch ( InvalidResponseException
$e ) {
89 } catch ( Exception
$e ) { // some other exception?
90 $this->logException( $e, __METHOD__
, array( 'path' => $storagePath ) );
97 * @see FileBackend::doCopyInternal()
99 protected function doCreateInternal( array $params ) {
100 $status = Status
::newGood();
102 list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] );
103 if ( $dstRel === null ) {
104 $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
108 // (a) Check the destination container and object
110 $dContObj = $this->getContainer( $dstCont );
111 if ( empty( $params['overwrite'] ) &&
112 $this->fileExists( array( 'src' => $params['dst'], 'latest' => 1 ) ) )
114 $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
117 } catch ( NoSuchContainerException
$e ) {
118 $status->fatal( 'backend-fail-create', $params['dst'] );
120 } catch ( InvalidResponseException
$e ) {
121 $status->fatal( 'backend-fail-connect', $this->name
);
123 } catch ( Exception
$e ) { // some other exception?
124 $status->fatal( 'backend-fail-internal', $this->name
);
125 $this->logException( $e, __METHOD__
, $params );
129 // (b) Get a SHA-1 hash of the object
130 $sha1Hash = wfBaseConvert( sha1( $params['content'] ), 16, 36, 31 );
132 // (c) Actually create the object
134 // Create a fresh CF_Object with no fields preloaded.
135 // We don't want to preserve headers, metadata, and such.
136 $obj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD
137 // Note: metadata keys stored as [Upper case char][[Lower case char]...]
138 $obj->metadata
= array( 'Sha1base36' => $sha1Hash );
139 // Manually set the ETag (https://github.com/rackspace/php-cloudfiles/issues/59).
140 // The MD5 here will be checked within Swift against its own MD5.
141 $obj->set_etag( md5( $params['content'] ) );
142 // Use the same content type as StreamFile for security
143 $obj->content_type
= StreamFile
::contentTypeFromPath( $params['dst'] );
144 // Actually write the object in Swift
145 $obj->write( $params['content'] );
146 } catch ( BadContentTypeException
$e ) {
147 $status->fatal( 'backend-fail-contenttype', $params['dst'] );
148 } catch ( InvalidResponseException
$e ) {
149 $status->fatal( 'backend-fail-connect', $this->name
);
150 } catch ( Exception
$e ) { // some other exception?
151 $status->fatal( 'backend-fail-internal', $this->name
);
152 $this->logException( $e, __METHOD__
, $params );
159 * @see FileBackend::doStoreInternal()
161 protected function doStoreInternal( array $params ) {
162 $status = Status
::newGood();
164 list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] );
165 if ( $dstRel === null ) {
166 $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
170 // (a) Check the destination container and object
172 $dContObj = $this->getContainer( $dstCont );
173 if ( empty( $params['overwrite'] ) &&
174 $this->fileExists( array( 'src' => $params['dst'], 'latest' => 1 ) ) )
176 $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
179 } catch ( NoSuchContainerException
$e ) {
180 $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
182 } catch ( InvalidResponseException
$e ) {
183 $status->fatal( 'backend-fail-connect', $this->name
);
185 } catch ( Exception
$e ) { // some other exception?
186 $status->fatal( 'backend-fail-internal', $this->name
);
187 $this->logException( $e, __METHOD__
, $params );
191 // (b) Get a SHA-1 hash of the object
192 $sha1Hash = sha1_file( $params['src'] );
193 if ( $sha1Hash === false ) { // source doesn't exist?
194 $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
197 $sha1Hash = wfBaseConvert( $sha1Hash, 16, 36, 31 );
199 // (c) Actually store the object
201 // Create a fresh CF_Object with no fields preloaded.
202 // We don't want to preserve headers, metadata, and such.
203 $obj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD
204 // Note: metadata keys stored as [Upper case char][[Lower case char]...]
205 $obj->metadata
= array( 'Sha1base36' => $sha1Hash );
206 // The MD5 here will be checked within Swift against its own MD5.
207 $obj->set_etag( md5_file( $params['src'] ) );
208 // Use the same content type as StreamFile for security
209 $obj->content_type
= StreamFile
::contentTypeFromPath( $params['dst'] );
210 // Actually write the object in Swift
211 $obj->load_from_filename( $params['src'], True ); // calls $obj->write()
212 } catch ( BadContentTypeException
$e ) {
213 $status->fatal( 'backend-fail-contenttype', $params['dst'] );
214 } catch ( IOException
$e ) {
215 $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
216 } catch ( InvalidResponseException
$e ) {
217 $status->fatal( 'backend-fail-connect', $this->name
);
218 } catch ( Exception
$e ) { // some other exception?
219 $status->fatal( 'backend-fail-internal', $this->name
);
220 $this->logException( $e, __METHOD__
, $params );
227 * @see FileBackend::doCopyInternal()
229 protected function doCopyInternal( array $params ) {
230 $status = Status
::newGood();
232 list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
233 if ( $srcRel === null ) {
234 $status->fatal( 'backend-fail-invalidpath', $params['src'] );
238 list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] );
239 if ( $dstRel === null ) {
240 $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
244 // (a) Check the source/destination containers and destination object
246 $sContObj = $this->getContainer( $srcCont );
247 $dContObj = $this->getContainer( $dstCont );
248 if ( empty( $params['overwrite'] ) &&
249 $this->fileExists( array( 'src' => $params['dst'], 'latest' => 1 ) ) )
251 $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
254 } catch ( NoSuchContainerException
$e ) {
255 $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
257 } catch ( InvalidResponseException
$e ) {
258 $status->fatal( 'backend-fail-connect', $this->name
);
260 } catch ( Exception
$e ) { // some other exception?
261 $status->fatal( 'backend-fail-internal', $this->name
);
262 $this->logException( $e, __METHOD__
, $params );
266 // (b) Actually copy the file to the destination
268 $sContObj->copy_object_to( $srcRel, $dContObj, $dstRel );
269 } catch ( NoSuchObjectException
$e ) { // source object does not exist
270 $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
271 } catch ( InvalidResponseException
$e ) {
272 $status->fatal( 'backend-fail-connect', $this->name
);
273 } catch ( Exception
$e ) { // some other exception?
274 $status->fatal( 'backend-fail-internal', $this->name
);
275 $this->logException( $e, __METHOD__
, $params );
282 * @see FileBackend::doDeleteInternal()
284 protected function doDeleteInternal( array $params ) {
285 $status = Status
::newGood();
287 list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
288 if ( $srcRel === null ) {
289 $status->fatal( 'backend-fail-invalidpath', $params['src'] );
294 $sContObj = $this->getContainer( $srcCont );
295 $sContObj->delete_object( $srcRel );
296 } catch ( NoSuchContainerException
$e ) {
297 $status->fatal( 'backend-fail-delete', $params['src'] );
298 } catch ( NoSuchObjectException
$e ) {
299 if ( empty( $params['ignoreMissingSource'] ) ) {
300 $status->fatal( 'backend-fail-delete', $params['src'] );
302 } catch ( InvalidResponseException
$e ) {
303 $status->fatal( 'backend-fail-connect', $this->name
);
304 } catch ( Exception
$e ) { // some other exception?
305 $status->fatal( 'backend-fail-internal', $this->name
);
306 $this->logException( $e, __METHOD__
, $params );
313 * @see FileBackend::doPrepareInternal()
315 protected function doPrepareInternal( $fullCont, $dir, array $params ) {
316 $status = Status
::newGood();
318 // (a) Check if container already exists
320 $contObj = $this->getContainer( $fullCont );
321 // NoSuchContainerException not thrown: container must exist
322 return $status; // already exists
323 } catch ( NoSuchContainerException
$e ) {
324 // NoSuchContainerException thrown: container does not exist
325 } catch ( InvalidResponseException
$e ) {
326 $status->fatal( 'backend-fail-connect', $this->name
);
328 } catch ( Exception
$e ) { // some other exception?
329 $status->fatal( 'backend-fail-internal', $this->name
);
330 $this->logException( $e, __METHOD__
, $params );
334 // (b) Create container as needed
336 $contObj = $this->createContainer( $fullCont );
337 if ( $this->swiftAnonUser
!= '' ) {
338 // Make container public to end-users...
339 $status->merge( $this->setContainerAccess(
341 array( $this->auth
->username
, $this->swiftAnonUser
), // read
342 array( $this->auth
->username
) // write
345 } catch ( InvalidResponseException
$e ) {
346 $status->fatal( 'backend-fail-connect', $this->name
);
348 } catch ( Exception
$e ) { // some other exception?
349 $status->fatal( 'backend-fail-internal', $this->name
);
350 $this->logException( $e, __METHOD__
, $params );
358 * @see FileBackend::doSecureInternal()
360 protected function doSecureInternal( $fullCont, $dir, array $params ) {
361 $status = Status
::newGood();
363 if ( $this->swiftAnonUser
!= '' ) {
364 // Restrict container from end-users...
366 // doPrepareInternal() should have been called,
367 // so the Swift container should already exist...
368 $contObj = $this->getContainer( $fullCont ); // normally a cache hit
369 // NoSuchContainerException not thrown: container must exist
370 if ( !isset( $contObj->mw_wasSecured
) ) {
371 $status->merge( $this->setContainerAccess(
373 array( $this->auth
->username
), // read
374 array( $this->auth
->username
) // write
376 // @TODO: when php-cloudfiles supports container
377 // metadata, we can make use of that to avoid RTTs
378 $contObj->mw_wasSecured
= true; // avoid useless RTTs
380 } catch ( InvalidResponseException
$e ) {
381 $status->fatal( 'backend-fail-connect', $this->name
);
382 } catch ( Exception
$e ) { // some other exception?
383 $status->fatal( 'backend-fail-internal', $this->name
);
384 $this->logException( $e, __METHOD__
, $params );
392 * @see FileBackend::doCleanInternal()
394 protected function doCleanInternal( $fullCont, $dir, array $params ) {
395 $status = Status
::newGood();
397 // Only containers themselves can be removed, all else is virtual
399 return $status; // nothing to do
402 // (a) Check the container
404 $contObj = $this->getContainer( $fullCont, true );
405 } catch ( NoSuchContainerException
$e ) {
406 return $status; // ok, nothing to do
407 } catch ( InvalidResponseException
$e ) {
408 $status->fatal( 'backend-fail-connect', $this->name
);
410 } catch ( Exception
$e ) { // some other exception?
411 $status->fatal( 'backend-fail-internal', $this->name
);
412 $this->logException( $e, __METHOD__
, $params );
416 // (b) Delete the container if empty
417 if ( $contObj->object_count
== 0 ) {
419 $this->deleteContainer( $fullCont );
420 } catch ( NoSuchContainerException
$e ) {
421 return $status; // race?
422 } catch ( InvalidResponseException
$e ) {
423 $status->fatal( 'backend-fail-connect', $this->name
);
425 } catch ( Exception
$e ) { // some other exception?
426 $status->fatal( 'backend-fail-internal', $this->name
);
427 $this->logException( $e, __METHOD__
, $params );
436 * @see FileBackend::doFileExists()
438 protected function doGetFileStat( array $params ) {
439 list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
440 if ( $srcRel === null ) {
441 return false; // invalid storage path
446 $contObj = $this->getContainer( $srcCont );
447 $srcObj = $contObj->get_object( $srcRel, $this->headersFromParams( $params ) );
448 $this->addMissingMetadata( $srcObj, $params['src'] );
450 // Convert dates like "Tue, 03 Jan 2012 22:01:04 GMT" to TS_MW
451 'mtime' => wfTimestamp( TS_MW
, $srcObj->last_modified
),
452 'size' => $srcObj->content_length
,
453 'sha1' => $srcObj->metadata
['Sha1base36']
455 } catch ( NoSuchContainerException
$e ) {
456 } catch ( NoSuchObjectException
$e ) {
457 } catch ( InvalidResponseException
$e ) {
459 } catch ( Exception
$e ) { // some other exception?
461 $this->logException( $e, __METHOD__
, $params );
468 * Fill in any missing object metadata and save it to Swift
470 * @param $obj CF_Object
471 * @param $path string Storage path to object
472 * @return bool Success
473 * @throws Exception cloudfiles exceptions
475 protected function addMissingMetadata( CF_Object
$obj, $path ) {
476 if ( isset( $obj->metadata
['Sha1base36'] ) ) {
477 return true; // nothing to do
479 $status = Status
::newGood();
480 $scopeLockS = $this->getScopedFileLocks( array( $path ), LockManager
::LOCK_UW
, $status );
481 if ( $status->isOK() ) {
482 $tmpFile = $this->getLocalCopy( array( 'src' => $path, 'latest' => 1 ) );
484 $hash = $tmpFile->getSha1Base36();
485 if ( $hash !== false ) {
486 $obj->metadata
['Sha1base36'] = $hash;
487 $obj->sync_metadata(); // save to Swift
488 return true; // success
492 $obj->metadata
['Sha1base36'] = false;
493 return false; // failed
497 * @see FileBackendBase::getFileContents()
499 public function getFileContents( array $params ) {
500 list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
501 if ( $srcRel === null ) {
502 return false; // invalid storage path
505 if ( !$this->fileExists( $params ) ) {
511 $sContObj = $this->getContainer( $srcCont );
512 $obj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD request
513 $data = $obj->read( $this->headersFromParams( $params ) );
514 } catch ( NoSuchContainerException
$e ) {
515 } catch ( InvalidResponseException
$e ) {
516 } catch ( Exception
$e ) { // some other exception?
517 $this->logException( $e, __METHOD__
, $params );
524 * @see FileBackend::getFileListInternal()
526 public function getFileListInternal( $fullCont, $dir, array $params ) {
527 return new SwiftFileBackendFileList( $this, $fullCont, $dir );
531 * Do not call this function outside of SwiftFileBackendFileList
533 * @param $fullCont string Resolved container name
534 * @param $dir string Resolved storage directory with no trailing slash
535 * @param $after string Storage path of file to list items after
536 * @param $limit integer Max number of items to list
539 public function getFileListPageInternal( $fullCont, $dir, $after, $limit ) {
543 $container = $this->getContainer( $fullCont );
544 $prefix = ( $dir == '' ) ?
null : "{$dir}/";
545 $files = $container->list_objects( $limit, $after, $prefix );
546 } catch ( NoSuchContainerException
$e ) {
547 } catch ( NoSuchObjectException
$e ) {
548 } catch ( InvalidResponseException
$e ) {
549 } catch ( Exception
$e ) { // some other exception?
550 $this->logException( $e, __METHOD__
, array( 'cont' => $fullCont, 'dir' => $dir ) );
557 * @see FileBackend::doGetFileSha1base36()
559 public function doGetFileSha1base36( array $params ) {
560 $stat = $this->getFileStat( $params );
562 return $stat['sha1'];
569 * @see FileBackend::doStreamFile()
571 protected function doStreamFile( array $params ) {
572 $status = Status
::newGood();
574 list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
575 if ( $srcRel === null ) {
576 $status->fatal( 'backend-fail-invalidpath', $params['src'] );
580 $cont = $this->getContainer( $srcCont );
581 } catch ( NoSuchContainerException
$e ) {
582 $status->fatal( 'backend-fail-stream', $params['src'] );
584 } catch ( InvalidResponseException
$e ) {
585 $status->fatal( 'backend-fail-connect', $this->name
);
587 } catch ( Exception
$e ) { // some other exception?
588 $status->fatal( 'backend-fail-stream', $params['src'] );
589 $this->logException( $e, __METHOD__
, $params );
594 $output = fopen( 'php://output', 'w' );
595 // FileBackend::streamFile() already checks existence
596 $obj = new CF_Object( $cont, $srcRel, false, false ); // skip HEAD request
597 $obj->stream( $output, $this->headersFromParams( $params ) );
598 } catch ( InvalidResponseException
$e ) { // 404? connection problem?
599 $status->fatal( 'backend-fail-stream', $params['src'] );
600 } catch ( Exception
$e ) { // some other exception?
601 $status->fatal( 'backend-fail-stream', $params['src'] );
602 $this->logException( $e, __METHOD__
, $params );
609 * @see FileBackend::getLocalCopy()
611 public function getLocalCopy( array $params ) {
612 list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
613 if ( $srcRel === null ) {
617 if ( !$this->fileExists( $params ) ) {
623 $sContObj = $this->getContainer( $srcCont );
624 $obj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD
625 // Get source file extension
626 $ext = FileBackend
::extensionFromPath( $srcRel );
627 // Create a new temporary file...
628 $tmpFile = TempFSFile
::factory( wfBaseName( $srcRel ) . '_', $ext );
630 $handle = fopen( $tmpFile->getPath(), 'wb' );
632 $obj->stream( $handle, $this->headersFromParams( $params ) );
635 $tmpFile = null; // couldn't open temp file
638 } catch ( NoSuchContainerException
$e ) {
640 } catch ( InvalidResponseException
$e ) {
642 } catch ( Exception
$e ) { // some other exception?
644 $this->logException( $e, __METHOD__
, $params );
651 * Get headers to send to Swift when reading a file based
652 * on a FileBackend params array, e.g. that of getLocalCopy().
653 * $params is currently only checked for a 'latest' flag.
655 * @param $params Array
658 protected function headersFromParams( array $params ) {
660 if ( !empty( $params['latest'] ) ) {
661 $hdrs[] = 'X-Newest: true';
667 * Set read/write permissions for a Swift container
669 * @param $contObj CF_Container Swift container
670 * @param $readGrps Array Swift users who can read (account:user)
671 * @param $writeGrps Array Swift users who can write (account:user)
674 protected function setContainerAccess(
675 CF_Container
$contObj, array $readGrps, array $writeGrps
677 $creds = $contObj->cfs_auth
->export_credentials();
679 $url = $creds['storage_url'] . '/' . rawurlencode( $contObj->name
);
681 // Note: 10 second timeout consistent with php-cloudfiles
682 $req = new CurlHttpRequest( $url, array( 'method' => 'POST', 'timeout' => 10 ) );
683 $req->setHeader( 'X-Auth-Token', $creds['auth_token'] );
684 $req->setHeader( 'X-Container-Read', implode( ',', $readGrps ) );
685 $req->setHeader( 'X-Container-Write', implode( ',', $writeGrps ) );
687 return $req->execute(); // should return 204
691 * Get a connection to the Swift proxy
693 * @return CF_Connection|false
694 * @throws InvalidResponseException
696 protected function getConnection() {
697 if ( $this->conn
=== false ) {
698 throw new InvalidResponseException
; // failed last attempt
700 // Session keys expire after a while, so we renew them periodically
701 if ( $this->conn
&& ( time() - $this->connStarted
) > $this->authTTL
) {
702 $this->conn
->close(); // close active cURL connections
705 // Authenticate with proxy and get a session key...
706 if ( $this->conn
=== null ) {
707 $this->connContainers
= array();
709 $this->auth
->authenticate();
710 $this->conn
= new CF_Connection( $this->auth
);
711 $this->connStarted
= time();
712 } catch ( AuthenticationException
$e ) {
713 $this->conn
= false; // don't keep re-trying
714 } catch ( InvalidResponseException
$e ) {
715 $this->conn
= false; // don't keep re-trying
718 if ( !$this->conn
) {
719 throw new InvalidResponseException
; // auth/connection problem
725 * @see FileBackend::doClearCache()
727 protected function doClearCache( array $paths = null ) {
728 $this->connContainers
= array(); // clear container object cache
732 * Get a Swift container object, possibly from process cache.
733 * Use $reCache if the file count or byte count is needed.
735 * @param $container string Container name
736 * @param $reCache bool Refresh the process cache
737 * @return CF_Container
739 protected function getContainer( $container, $reCache = false ) {
740 $conn = $this->getConnection(); // Swift proxy connection
742 unset( $this->connContainers
[$container] ); // purge cache
744 if ( !isset( $this->connContainers
[$container] ) ) {
745 $contObj = $conn->get_container( $container );
746 // NoSuchContainerException not thrown: container must exist
747 if ( count( $this->connContainers
) >= $this->maxContCacheSize
) { // trim cache?
748 reset( $this->connContainers
);
749 $key = key( $this->connContainers
);
750 unset( $this->connContainers
[$key] );
752 $this->connContainers
[$container] = $contObj; // cache it
754 return $this->connContainers
[$container];
758 * Create a Swift container
760 * @param $container string Container name
761 * @return CF_Container
763 protected function createContainer( $container ) {
764 $conn = $this->getConnection(); // Swift proxy connection
765 $contObj = $conn->create_container( $container );
766 $this->connContainers
[$container] = $contObj; // cache it
771 * Delete a Swift container
773 * @param $container string Container name
776 protected function deleteContainer( $container ) {
777 $conn = $this->getConnection(); // Swift proxy connection
778 $conn->delete_container( $container );
779 unset( $this->connContainers
[$container] ); // purge cache
783 * Log an unexpected exception for this backend
785 * @param $e Exception
786 * @param $func string
787 * @param $params Array
790 protected function logException( Exception
$e, $func, array $params ) {
791 wfDebugLog( 'SwiftBackend',
792 get_class( $e ) . " in '{$this->name}': '{$func}' with " . serialize( $params )
798 * SwiftFileBackend helper class to page through object listings.
799 * Swift also has a listing limit of 10,000 objects for sanity.
800 * Do not use this class from places outside SwiftFileBackend.
802 * @ingroup FileBackend
804 class SwiftFileBackendFileList
implements Iterator
{
806 protected $bufferIter = array();
807 protected $bufferAfter = null; // string; list items *after* this path
808 protected $pos = 0; // integer
810 /** @var SwiftFileBackend */
812 protected $container; //
813 protected $dir; // string storage directory
814 protected $suffixStart; // integer
816 const PAGE_SIZE
= 5000; // file listing buffer size
819 * @param $backend SwiftFileBackend
820 * @param $fullCont string Resolved container name
821 * @param $dir string Resolved directory relative to container
823 public function __construct( SwiftFileBackend
$backend, $fullCont, $dir ) {
824 $this->backend
= $backend;
825 $this->container
= $fullCont;
827 if ( substr( $this->dir
, -1 ) === '/' ) {
828 $this->dir
= substr( $this->dir
, 0, -1 ); // remove trailing slash
830 if ( $this->dir
== '' ) { // whole container
831 $this->suffixStart
= 0;
832 } else { // dir within container
833 $this->suffixStart
= strlen( $this->dir
) +
1; // size of "path/to/dir/"
837 public function current() {
838 return substr( current( $this->bufferIter
), $this->suffixStart
);
841 public function key() {
845 public function next() {
846 // Advance to the next file in the page
847 next( $this->bufferIter
);
849 // Check if there are no files left in this page and
850 // advance to the next page if this page was not empty.
851 if ( !$this->valid() && count( $this->bufferIter
) ) {
852 $this->bufferAfter
= end( $this->bufferIter
);
853 $this->bufferIter
= $this->backend
->getFileListPageInternal(
854 $this->container
, $this->dir
, $this->bufferAfter
, self
::PAGE_SIZE
859 public function rewind() {
861 $this->bufferAfter
= null;
862 $this->bufferIter
= $this->backend
->getFileListPageInternal(
863 $this->container
, $this->dir
, $this->bufferAfter
, self
::PAGE_SIZE
867 public function valid() {
868 return ( current( $this->bufferIter
) !== false ); // no paths can have this value