Make Status extend StatusValue and start FileBackend update
[lhc/web/wiklou.git] / includes / filebackend / FileBackendStore.php
index e21aaea..4e25ce7 100644 (file)
@@ -44,7 +44,7 @@ abstract class FileBackendStore extends FileBackend {
        protected $expensiveCache;
 
        /** @var array Map of container names to sharding config */
-       protected $shardViaHashLevels = array();
+       protected $shardViaHashLevels = [];
 
        /** @var callable Method to get the MIME type of files */
        protected $mimeCallback;
@@ -106,23 +106,23 @@ abstract class FileBackendStore extends FileBackend {
         *   - content     : the raw file contents
         *   - dst         : destination storage path
         *   - headers     : HTTP header name/value map
-        *   - async       : Status will be returned immediately if supported.
-        *                   If the status is OK, then its value field will be
+        *   - async       : StatusValue will be returned immediately if supported.
+        *                   If the StatusValue is OK, then its value field will be
         *                   set to a FileBackendStoreOpHandle object.
         *   - dstExists   : Whether a file exists at the destination (optimization).
         *                   Callers can use "false" if no existing file is being changed.
         *
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        final public function createInternal( array $params ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
                if ( strlen( $params['content'] ) > $this->maxFileSizeInternal() ) {
-                       $status = Status::newFatal( 'backend-fail-maxsize',
+                       $status = $this->newStatus( 'backend-fail-maxsize',
                                $params['dst'], $this->maxFileSizeInternal() );
                } else {
                        $status = $this->doCreateInternal( $params );
-                       $this->clearCache( array( $params['dst'] ) );
+                       $this->clearCache( [ $params['dst'] ] );
                        if ( !isset( $params['dstExists'] ) || $params['dstExists'] ) {
                                $this->deleteFileCache( $params['dst'] ); // persistent cache
                        }
@@ -134,7 +134,7 @@ abstract class FileBackendStore extends FileBackend {
        /**
         * @see FileBackendStore::createInternal()
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        abstract protected function doCreateInternal( array $params );
 
@@ -147,23 +147,23 @@ abstract class FileBackendStore extends FileBackend {
         *   - src         : source path on disk
         *   - dst         : destination storage path
         *   - headers     : HTTP header name/value map
-        *   - async       : Status will be returned immediately if supported.
-        *                   If the status is OK, then its value field will be
+        *   - async       : StatusValue will be returned immediately if supported.
+        *                   If the StatusValue is OK, then its value field will be
         *                   set to a FileBackendStoreOpHandle object.
         *   - dstExists   : Whether a file exists at the destination (optimization).
         *                   Callers can use "false" if no existing file is being changed.
         *
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        final public function storeInternal( array $params ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
                if ( filesize( $params['src'] ) > $this->maxFileSizeInternal() ) {
-                       $status = Status::newFatal( 'backend-fail-maxsize',
+                       $status = $this->newStatus( 'backend-fail-maxsize',
                                $params['dst'], $this->maxFileSizeInternal() );
                } else {
                        $status = $this->doStoreInternal( $params );
-                       $this->clearCache( array( $params['dst'] ) );
+                       $this->clearCache( [ $params['dst'] ] );
                        if ( !isset( $params['dstExists'] ) || $params['dstExists'] ) {
                                $this->deleteFileCache( $params['dst'] ); // persistent cache
                        }
@@ -175,7 +175,7 @@ abstract class FileBackendStore extends FileBackend {
        /**
         * @see FileBackendStore::storeInternal()
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        abstract protected function doStoreInternal( array $params );
 
@@ -189,19 +189,19 @@ abstract class FileBackendStore extends FileBackend {
         *   - dst                 : destination storage path
         *   - ignoreMissingSource : do nothing if the source file does not exist
         *   - headers             : HTTP header name/value map
-        *   - async               : Status will be returned immediately if supported.
-        *                           If the status is OK, then its value field will be
+        *   - async               : StatusValue will be returned immediately if supported.
+        *                           If the StatusValue is OK, then its value field will be
         *                           set to a FileBackendStoreOpHandle object.
         *   - dstExists           : Whether a file exists at the destination (optimization).
         *                           Callers can use "false" if no existing file is being changed.
         *
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        final public function copyInternal( array $params ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
                $status = $this->doCopyInternal( $params );
-               $this->clearCache( array( $params['dst'] ) );
+               $this->clearCache( [ $params['dst'] ] );
                if ( !isset( $params['dstExists'] ) || $params['dstExists'] ) {
                        $this->deleteFileCache( $params['dst'] ); // persistent cache
                }
@@ -212,7 +212,7 @@ abstract class FileBackendStore extends FileBackend {
        /**
         * @see FileBackendStore::copyInternal()
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        abstract protected function doCopyInternal( array $params );
 
@@ -223,17 +223,17 @@ abstract class FileBackendStore extends FileBackend {
         * $params include:
         *   - src                 : source storage path
         *   - ignoreMissingSource : do nothing if the source file does not exist
-        *   - async               : Status will be returned immediately if supported.
-        *                           If the status is OK, then its value field will be
+        *   - async               : StatusValue will be returned immediately if supported.
+        *                           If the StatusValue is OK, then its value field will be
         *                           set to a FileBackendStoreOpHandle object.
         *
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        final public function deleteInternal( array $params ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
                $status = $this->doDeleteInternal( $params );
-               $this->clearCache( array( $params['src'] ) );
+               $this->clearCache( [ $params['src'] ] );
                $this->deleteFileCache( $params['src'] ); // persistent cache
                return $status;
        }
@@ -241,7 +241,7 @@ abstract class FileBackendStore extends FileBackend {
        /**
         * @see FileBackendStore::deleteInternal()
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        abstract protected function doDeleteInternal( array $params );
 
@@ -255,19 +255,19 @@ abstract class FileBackendStore extends FileBackend {
         *   - dst                 : destination storage path
         *   - ignoreMissingSource : do nothing if the source file does not exist
         *   - headers             : HTTP header name/value map
-        *   - async               : Status will be returned immediately if supported.
-        *                           If the status is OK, then its value field will be
+        *   - async               : StatusValue will be returned immediately if supported.
+        *                           If the StatusValue is OK, then its value field will be
         *                           set to a FileBackendStoreOpHandle object.
         *   - dstExists           : Whether a file exists at the destination (optimization).
         *                           Callers can use "false" if no existing file is being changed.
         *
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        final public function moveInternal( array $params ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
                $status = $this->doMoveInternal( $params );
-               $this->clearCache( array( $params['src'], $params['dst'] ) );
+               $this->clearCache( [ $params['src'], $params['dst'] ] );
                $this->deleteFileCache( $params['src'] ); // persistent cache
                if ( !isset( $params['dstExists'] ) || $params['dstExists'] ) {
                        $this->deleteFileCache( $params['dst'] ); // persistent cache
@@ -279,7 +279,7 @@ abstract class FileBackendStore extends FileBackend {
        /**
         * @see FileBackendStore::moveInternal()
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        protected function doMoveInternal( array $params ) {
                unset( $params['async'] ); // two steps, won't work here :)
@@ -289,7 +289,7 @@ abstract class FileBackendStore extends FileBackend {
                $status = $this->copyInternal( $params );
                if ( $nsrc !== $ndst && $status->isOK() ) {
                        // Delete source (only fails due to races or network problems)
-                       $status->merge( $this->deleteInternal( array( 'src' => $params['src'] ) ) );
+                       $status->merge( $this->deleteInternal( [ 'src' => $params['src'] ] ) );
                        $status->setResult( true, $status->value ); // ignore delete() errors
                }
 
@@ -303,21 +303,21 @@ abstract class FileBackendStore extends FileBackend {
         * $params include:
         *   - src           : source storage path
         *   - headers       : HTTP header name/value map
-        *   - async         : Status will be returned immediately if supported.
-        *                     If the status is OK, then its value field will be
+        *   - async         : StatusValue will be returned immediately if supported.
+        *                     If the StatusValue is OK, then its value field will be
         *                     set to a FileBackendStoreOpHandle object.
         *
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        final public function describeInternal( array $params ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
                if ( count( $params['headers'] ) ) {
                        $status = $this->doDescribeInternal( $params );
-                       $this->clearCache( array( $params['src'] ) );
+                       $this->clearCache( [ $params['src'] ] );
                        $this->deleteFileCache( $params['src'] ); // persistent cache
                } else {
-                       $status = Status::newGood(); // nothing to do
+                       $status = $this->newStatus(); // nothing to do
                }
 
                return $status;
@@ -326,10 +326,10 @@ abstract class FileBackendStore extends FileBackend {
        /**
         * @see FileBackendStore::describeInternal()
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        protected function doDescribeInternal( array $params ) {
-               return Status::newGood();
+               return $this->newStatus();
        }
 
        /**
@@ -337,15 +337,15 @@ abstract class FileBackendStore extends FileBackend {
         * Do not call this function from places outside FileBackend and FileOp.
         *
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        final public function nullInternal( array $params ) {
-               return Status::newGood();
+               return $this->newStatus();
        }
 
        final public function concatenate( array $params ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                // Try to lock the source files for the scope of this function
                $scopeLockS = $this->getScopedFileLocks( $params['srcs'], LockManager::LOCK_UW, $status );
@@ -366,10 +366,10 @@ abstract class FileBackendStore extends FileBackend {
        /**
         * @see FileBackendStore::concatenate()
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        protected function doConcatenate( array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
                $tmpPath = $params['dst']; // convenience
                unset( $params['latest'] ); // sanity
 
@@ -387,7 +387,7 @@ abstract class FileBackendStore extends FileBackend {
                $fsFiles = $this->getLocalReferenceMulti( $params );
                foreach ( $fsFiles as $path => &$fsFile ) {
                        if ( !$fsFile ) { // chunk failed to download?
-                               $fsFile = $this->getLocalReference( array( 'src' => $path ) );
+                               $fsFile = $this->getLocalReference( [ 'src' => $path ] );
                                if ( !$fsFile ) { // retry failed?
                                        $status->fatal( 'backend-fail-read', $path );
 
@@ -438,7 +438,7 @@ abstract class FileBackendStore extends FileBackend {
 
        final protected function doPrepare( array $params ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
                if ( $dir === null ) {
@@ -465,15 +465,15 @@ abstract class FileBackendStore extends FileBackend {
         * @param string $container
         * @param string $dir
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        protected function doPrepareInternal( $container, $dir, array $params ) {
-               return Status::newGood();
+               return $this->newStatus();
        }
 
        final protected function doSecure( array $params ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
                if ( $dir === null ) {
@@ -500,15 +500,15 @@ abstract class FileBackendStore extends FileBackend {
         * @param string $container
         * @param string $dir
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        protected function doSecureInternal( $container, $dir, array $params ) {
-               return Status::newGood();
+               return $this->newStatus();
        }
 
        final protected function doPublish( array $params ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
                if ( $dir === null ) {
@@ -535,23 +535,23 @@ abstract class FileBackendStore extends FileBackend {
         * @param string $container
         * @param string $dir
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        protected function doPublishInternal( $container, $dir, array $params ) {
-               return Status::newGood();
+               return $this->newStatus();
        }
 
        final protected function doClean( array $params ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                // Recursive: first delete all empty subdirs recursively
                if ( !empty( $params['recursive'] ) && !$this->directoriesAreVirtual() ) {
-                       $subDirsRel = $this->getTopDirectoryList( array( 'dir' => $params['dir'] ) );
+                       $subDirsRel = $this->getTopDirectoryList( [ 'dir' => $params['dir'] ] );
                        if ( $subDirsRel !== null ) { // no errors
                                foreach ( $subDirsRel as $subDirRel ) {
                                        $subDir = $params['dir'] . "/{$subDirRel}"; // full path
-                                       $status->merge( $this->doClean( array( 'dir' => $subDir ) + $params ) );
+                                       $status->merge( $this->doClean( [ 'dir' => $subDir ] + $params ) );
                                }
                                unset( $subDirsRel ); // free directory for rmdir() on Windows (for FS backends)
                        }
@@ -565,7 +565,7 @@ abstract class FileBackendStore extends FileBackend {
                }
 
                // Attempt to lock this directory...
-               $filesLockEx = array( $params['dir'] );
+               $filesLockEx = [ $params['dir'] ];
                $scopedLockE = $this->getScopedFileLocks( $filesLockEx, LockManager::LOCK_EX, $status );
                if ( !$status->isOK() ) {
                        return $status; // abort
@@ -591,10 +591,10 @@ abstract class FileBackendStore extends FileBackend {
         * @param string $container
         * @param string $dir
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        protected function doCleanInternal( $container, $dir, array $params ) {
-               return Status::newGood();
+               return $this->newStatus();
        }
 
        final public function fileExists( array $params ) {
@@ -626,7 +626,7 @@ abstract class FileBackendStore extends FileBackend {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
                $latest = !empty( $params['latest'] ); // use latest data?
                if ( !$latest && !$this->cheapCache->has( $path, 'stat', self::CACHE_TTL ) ) {
-                       $this->primeFileCache( array( $path ) ); // check persistent cache
+                       $this->primeFileCache( [ $path ] ); // check persistent cache
                }
                if ( $this->cheapCache->has( $path, 'stat', self::CACHE_TTL ) ) {
                        $stat = $this->cheapCache->get( $path, 'stat' );
@@ -636,7 +636,7 @@ abstract class FileBackendStore extends FileBackend {
                                if ( !$latest || $stat['latest'] ) {
                                        return $stat;
                                }
-                       } elseif ( in_array( $stat, array( 'NOT_EXIST', 'NOT_EXIST_LATEST' ) ) ) {
+                       } elseif ( in_array( $stat, [ 'NOT_EXIST', 'NOT_EXIST_LATEST' ] ) ) {
                                if ( !$latest || $stat === 'NOT_EXIST_LATEST' ) {
                                        return false;
                                }
@@ -650,17 +650,17 @@ abstract class FileBackendStore extends FileBackend {
                        $this->setFileCache( $path, $stat ); // update persistent cache
                        if ( isset( $stat['sha1'] ) ) { // some backends store SHA-1 as metadata
                                $this->cheapCache->set( $path, 'sha1',
-                                       array( 'hash' => $stat['sha1'], 'latest' => $latest ) );
+                                       [ 'hash' => $stat['sha1'], 'latest' => $latest ] );
                        }
                        if ( isset( $stat['xattr'] ) ) { // some backends store headers/metadata
                                $stat['xattr'] = self::normalizeXAttributes( $stat['xattr'] );
                                $this->cheapCache->set( $path, 'xattr',
-                                       array( 'map' => $stat['xattr'], 'latest' => $latest ) );
+                                       [ 'map' => $stat['xattr'], 'latest' => $latest ] );
                        }
                } elseif ( $stat === false ) { // file does not exist
                        $this->cheapCache->set( $path, 'stat', $latest ? 'NOT_EXIST_LATEST' : 'NOT_EXIST' );
-                       $this->cheapCache->set( $path, 'xattr', array( 'map' => false, 'latest' => $latest ) );
-                       $this->cheapCache->set( $path, 'sha1', array( 'hash' => false, 'latest' => $latest ) );
+                       $this->cheapCache->set( $path, 'xattr', [ 'map' => false, 'latest' => $latest ] );
+                       $this->cheapCache->set( $path, 'sha1', [ 'hash' => false, 'latest' => $latest ] );
                        wfDebug( __METHOD__ . ": File $path does not exist.\n" );
                } else { // an error occurred
                        wfDebug( __METHOD__ . ": Could not stat file $path.\n" );
@@ -689,7 +689,7 @@ abstract class FileBackendStore extends FileBackend {
         * @return array
         */
        protected function doGetFileContentsMulti( array $params ) {
-               $contents = array();
+               $contents = [];
                foreach ( $this->doGetLocalReferenceMulti( $params ) as $path => $fsFile ) {
                        MediaWiki\suppressWarnings();
                        $contents[$path] = $fsFile ? file_get_contents( $fsFile->getPath() ) : false;
@@ -716,7 +716,7 @@ abstract class FileBackendStore extends FileBackend {
                }
                $fields = $this->doGetFileXAttributes( $params );
                $fields = is_array( $fields ) ? self::normalizeXAttributes( $fields ) : false;
-               $this->cheapCache->set( $path, 'xattr', array( 'map' => $fields, 'latest' => $latest ) );
+               $this->cheapCache->set( $path, 'xattr', [ 'map' => $fields, 'latest' => $latest ] );
 
                return $fields;
        }
@@ -726,7 +726,7 @@ abstract class FileBackendStore extends FileBackend {
         * @return bool|string
         */
        protected function doGetFileXAttributes( array $params ) {
-               return array( 'headers' => array(), 'metadata' => array() ); // not supported
+               return [ 'headers' => [], 'metadata' => [] ]; // not supported
        }
 
        final public function getFileSha1Base36( array $params ) {
@@ -745,7 +745,7 @@ abstract class FileBackendStore extends FileBackend {
                        }
                }
                $hash = $this->doGetFileSha1Base36( $params );
-               $this->cheapCache->set( $path, 'sha1', array( 'hash' => $hash, 'latest' => $latest ) );
+               $this->cheapCache->set( $path, 'sha1', [ 'hash' => $hash, 'latest' => $latest ] );
 
                return $hash;
        }
@@ -777,7 +777,7 @@ abstract class FileBackendStore extends FileBackend {
 
                $params = $this->setConcurrencyFlags( $params );
 
-               $fsFiles = array(); // (path => FSFile)
+               $fsFiles = []; // (path => FSFile)
                $latest = !empty( $params['latest'] ); // use latest data?
                // Reuse any files already in process cache...
                foreach ( $params['srcs'] as $src ) {
@@ -799,7 +799,7 @@ abstract class FileBackendStore extends FileBackend {
                        $fsFiles[$path] = $fsFile;
                        if ( $fsFile ) { // update the process cache...
                                $this->expensiveCache->set( $path, 'localRef',
-                                       array( 'object' => $fsFile, 'latest' => $latest ) );
+                                       [ 'object' => $fsFile, 'latest' => $latest ] );
                        }
                }
 
@@ -842,47 +842,47 @@ abstract class FileBackendStore extends FileBackend {
 
        final public function streamFile( array $params ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
-               $info = $this->getFileStat( $params );
-               if ( !$info ) { // let StreamFile handle the 404
-                       $status->fatal( 'backend-fail-notexists', $params['src'] );
-               }
+               // Always set some fields for subclass convenience
+               $params['options'] = isset( $params['options'] ) ? $params['options'] : [];
+               $params['headers'] = isset( $params['headers'] ) ? $params['headers'] : [];
 
-               // Set output buffer and HTTP headers for stream
-               $extraHeaders = isset( $params['headers'] ) ? $params['headers'] : array();
-               $res = StreamFile::prepareForStream( $params['src'], $info, $extraHeaders );
-               if ( $res == StreamFile::NOT_MODIFIED ) {
-                       // do nothing; client cache is up to date
-               } elseif ( $res == StreamFile::READY_STREAM ) {
-                       $status = $this->doStreamFile( $params );
-                       if ( !$status->isOK() ) {
-                               // Per bug 41113, nasty things can happen if bad cache entries get
-                               // stuck in cache. It's also possible that this error can come up
-                               // with simple race conditions. Clear out the stat cache to be safe.
-                               $this->clearCache( array( $params['src'] ) );
-                               $this->deleteFileCache( $params['src'] );
-                               trigger_error( "Bad stat cache or race condition for file {$params['src']}." );
-                       }
-               } else {
+               // Don't stream it out as text/html if there was a PHP error
+               if ( ( empty( $params['headless'] ) || $params['headers'] ) && headers_sent() ) {
+                       print "Headers already sent, terminating.\n";
                        $status->fatal( 'backend-fail-stream', $params['src'] );
+                       return $status;
                }
 
+               $status->merge( $this->doStreamFile( $params ) );
+
                return $status;
        }
 
        /**
         * @see FileBackendStore::streamFile()
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        protected function doStreamFile( array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
+
+               $flags = 0;
+               $flags |= !empty( $params['headless'] ) ? StreamFile::STREAM_HEADLESS : 0;
+               $flags |= !empty( $params['allowOB'] ) ? StreamFile::STREAM_ALLOW_OB : 0;
 
                $fsFile = $this->getLocalReference( $params );
-               if ( !$fsFile ) {
-                       $status->fatal( 'backend-fail-stream', $params['src'] );
-               } elseif ( !readfile( $fsFile->getPath() ) ) {
+
+               if ( $fsFile ) {
+                       $res = StreamFile::stream( $fsFile->getPath(),
+                               $params['headers'], true, $params['options'], $flags );
+               } else {
+                       $res = false;
+                       StreamFile::send404Message( $params['src'], $flags );
+               }
+
+               if ( !$res ) {
                        $status->fatal( 'backend-fail-stream', $params['src'] );
                }
 
@@ -992,11 +992,11 @@ abstract class FileBackendStore extends FileBackend {
         * An exception is thrown if an unsupported operation is requested.
         *
         * @param array $ops Same format as doOperations()
-        * @return array List of FileOp objects
+        * @return FileOp[] List of FileOp objects
         * @throws FileBackendError
         */
        final public function getOperationsInternal( array $ops ) {
-               $supportedOps = array(
+               $supportedOps = [
                        'store' => 'StoreFileOp',
                        'copy' => 'CopyFileOp',
                        'move' => 'MoveFileOp',
@@ -1004,9 +1004,9 @@ abstract class FileBackendStore extends FileBackend {
                        'create' => 'CreateFileOp',
                        'describe' => 'DescribeFileOp',
                        'null' => 'NullFileOp'
-               );
+               ];
 
-               $performOps = array(); // array of FileOp objects
+               $performOps = []; // array of FileOp objects
                // Build up ordered array of FileOps...
                foreach ( $ops as $operation ) {
                        $opName = $operation['op'];
@@ -1036,7 +1036,7 @@ abstract class FileBackendStore extends FileBackend {
         */
        final public function getPathsToLockForOpsInternal( array $performOps ) {
                // Build up a list of files to lock...
-               $paths = array( 'sh' => array(), 'ex' => array() );
+               $paths = [ 'sh' => [], 'ex' => [] ];
                foreach ( $performOps as $fileOp ) {
                        $paths['sh'] = array_merge( $paths['sh'], $fileOp->storagePathsRead() );
                        $paths['ex'] = array_merge( $paths['ex'], $fileOp->storagePathsChanged() );
@@ -1046,13 +1046,13 @@ abstract class FileBackendStore extends FileBackend {
                // Get a shared lock on the parent directory of each path changed
                $paths['sh'] = array_merge( $paths['sh'], array_map( 'dirname', $paths['ex'] ) );
 
-               return array(
+               return [
                        LockManager::LOCK_UW => $paths['sh'],
                        LockManager::LOCK_EX => $paths['ex']
-               );
+               ];
        }
 
-       public function getScopedLocksForOps( array $ops, Status $status ) {
+       public function getScopedLocksForOps( array $ops, StatusValue $status ) {
                $paths = $this->getPathsToLockForOpsInternal( $this->getOperationsInternal( $ops ) );
 
                return $this->getScopedFileLocks( $paths, 'mixed', $status );
@@ -1060,10 +1060,10 @@ abstract class FileBackendStore extends FileBackend {
 
        final protected function doOperationsInternal( array $ops, array $opts ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                // Fix up custom header name/value pairs...
-               $ops = array_map( array( $this, 'sanitizeOpHeaders' ), $ops );
+               $ops = array_map( [ $this, 'sanitizeOpHeaders' ], $ops );
 
                // Build up a list of FileOps...
                $performOps = $this->getOperationsInternal( $ops );
@@ -1086,7 +1086,7 @@ abstract class FileBackendStore extends FileBackend {
                }
 
                // Build the list of paths involved
-               $paths = array();
+               $paths = [];
                foreach ( $performOps as $op ) {
                        $paths = array_merge( $paths, $op->storagePathsRead() );
                        $paths = array_merge( $paths, $op->storagePathsChanged() );
@@ -1098,7 +1098,7 @@ abstract class FileBackendStore extends FileBackend {
                // Load from the persistent container caches
                $this->primeContainerCache( $paths );
                // Get the latest stat info for all the files (having locked them)
-               $ok = $this->preloadFileStat( array( 'srcs' => $paths, 'latest' => true ) );
+               $ok = $this->preloadFileStat( [ 'srcs' => $paths, 'latest' => true ] );
 
                if ( $ok ) {
                        // Actually attempt the operation batch...
@@ -1106,7 +1106,7 @@ abstract class FileBackendStore extends FileBackend {
                        $subStatus = FileOpBatch::attempt( $performOps, $opts, $this->fileJournal );
                } else {
                        // If we could not even stat some files, then bail out...
-                       $subStatus = Status::newFatal( 'backend-fail-internal', $this->name );
+                       $subStatus = $this->newStatus( 'backend-fail-internal', $this->name );
                        foreach ( $ops as $i => $op ) { // mark each op as failed
                                $subStatus->success[$i] = false;
                                ++$subStatus->failCount;
@@ -1115,7 +1115,7 @@ abstract class FileBackendStore extends FileBackend {
                                " stat failure; aborted operations: " . FormatJson::encode( $ops ) );
                }
 
-               // Merge errors into status fields
+               // Merge errors into StatusValue fields
                $status->merge( $subStatus );
                $status->success = $subStatus->success; // not done in merge()
 
@@ -1127,33 +1127,33 @@ abstract class FileBackendStore extends FileBackend {
 
        final protected function doQuickOperationsInternal( array $ops ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                // Fix up custom header name/value pairs...
-               $ops = array_map( array( $this, 'sanitizeOpHeaders' ), $ops );
+               $ops = array_map( [ $this, 'sanitizeOpHeaders' ], $ops );
 
                // Clear any file cache entries
                $this->clearCache();
 
-               $supportedOps = array( 'create', 'store', 'copy', 'move', 'delete', 'describe', 'null' );
+               $supportedOps = [ 'create', 'store', 'copy', 'move', 'delete', 'describe', 'null' ];
                // Parallel ops may be disabled in config due to dependencies (e.g. needing popen())
                $async = ( $this->parallelize === 'implicit' && count( $ops ) > 1 );
                $maxConcurrency = $this->concurrency; // throttle
-
-               $statuses = array(); // array of (index => Status)
-               $fileOpHandles = array(); // list of (index => handle) arrays
-               $curFileOpHandles = array(); // current handle batch
+               /** @var StatusValue[] $statuses */
+               $statuses = []; // array of (index => StatusValue)
+               $fileOpHandles = []; // list of (index => handle) arrays
+               $curFileOpHandles = []; // current handle batch
                // Perform the sync-only ops and build up op handles for the async ops...
                foreach ( $ops as $index => $params ) {
                        if ( !in_array( $params['op'], $supportedOps ) ) {
                                throw new FileBackendError( "Operation '{$params['op']}' is not supported." );
                        }
                        $method = $params['op'] . 'Internal'; // e.g. "storeInternal"
-                       $subStatus = $this->$method( array( 'async' => $async ) + $params );
+                       $subStatus = $this->$method( [ 'async' => $async ] + $params );
                        if ( $subStatus->value instanceof FileBackendStoreOpHandle ) { // async
                                if ( count( $curFileOpHandles ) >= $maxConcurrency ) {
                                        $fileOpHandles[] = $curFileOpHandles; // push this batch
-                                       $curFileOpHandles = array();
+                                       $curFileOpHandles = [];
                                }
                                $curFileOpHandles[$index] = $subStatus->value; // keep index
                        } else { // error or completed
@@ -1184,13 +1184,13 @@ abstract class FileBackendStore extends FileBackend {
 
        /**
         * Execute a list of FileBackendStoreOpHandle handles in parallel.
-        * The resulting Status object fields will correspond
+        * The resulting StatusValue object fields will correspond
         * to the order in which the handles where given.
         *
         * @param FileBackendStoreOpHandle[] $fileOpHandles
         *
         * @throws FileBackendError
-        * @return array Map of Status objects
+        * @return StatusValue[] Map of StatusValue objects
         */
        final public function executeOpHandlesInternal( array $fileOpHandles ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
@@ -1216,14 +1216,14 @@ abstract class FileBackendStore extends FileBackend {
         * @param FileBackendStoreOpHandle[] $fileOpHandles
         *
         * @throws FileBackendError
-        * @return Status[] List of corresponding Status objects
+        * @return StatusValue[] List of corresponding StatusValue objects
         */
        protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
                if ( count( $fileOpHandles ) ) {
                        throw new FileBackendError( "This backend supports no asynchronous operations." );
                }
 
-               return array();
+               return [];
        }
 
        /**
@@ -1238,10 +1238,10 @@ abstract class FileBackendStore extends FileBackend {
         * @return array
         */
        protected function sanitizeOpHeaders( array $op ) {
-               static $longs = array( 'content-disposition' );
+               static $longs = [ 'content-disposition' ];
 
                if ( isset( $op['headers'] ) ) { // op sets HTTP headers
-                       $newHeaders = array();
+                       $newHeaders = [];
                        foreach ( $op['headers'] as $name => $value ) {
                                $name = strtolower( $name );
                                $maxHVLen = in_array( $name, $longs ) ? INF : 255;
@@ -1258,7 +1258,7 @@ abstract class FileBackendStore extends FileBackend {
        }
 
        final public function preloadCache( array $paths ) {
-               $fullConts = array(); // full container names
+               $fullConts = []; // full container names
                foreach ( $paths as $path ) {
                        list( $fullCont, , ) = $this->resolveStoragePath( $path );
                        $fullConts[] = $fullCont;
@@ -1318,20 +1318,20 @@ abstract class FileBackendStore extends FileBackend {
                                $this->setFileCache( $path, $stat ); // update persistent cache
                                if ( isset( $stat['sha1'] ) ) { // some backends store SHA-1 as metadata
                                        $this->cheapCache->set( $path, 'sha1',
-                                               array( 'hash' => $stat['sha1'], 'latest' => $latest ) );
+                                               [ 'hash' => $stat['sha1'], 'latest' => $latest ] );
                                }
                                if ( isset( $stat['xattr'] ) ) { // some backends store headers/metadata
                                        $stat['xattr'] = self::normalizeXAttributes( $stat['xattr'] );
                                        $this->cheapCache->set( $path, 'xattr',
-                                               array( 'map' => $stat['xattr'], 'latest' => $latest ) );
+                                               [ 'map' => $stat['xattr'], 'latest' => $latest ] );
                                }
                        } elseif ( $stat === false ) { // file does not exist
                                $this->cheapCache->set( $path, 'stat',
                                        $latest ? 'NOT_EXIST_LATEST' : 'NOT_EXIST' );
                                $this->cheapCache->set( $path, 'xattr',
-                                       array( 'map' => false, 'latest' => $latest ) );
+                                       [ 'map' => false, 'latest' => $latest ] );
                                $this->cheapCache->set( $path, 'sha1',
-                                       array( 'hash' => false, 'latest' => $latest ) );
+                                       [ 'hash' => false, 'latest' => $latest ] );
                                wfDebug( __METHOD__ . ": File $path does not exist.\n" );
                        } else { // an error occurred
                                $success = false;
@@ -1430,20 +1430,20 @@ abstract class FileBackendStore extends FileBackend {
                                                // Validate and sanitize the container name (backend-specific)
                                                $container = $this->resolveContainerName( "{$container}{$cShard}" );
                                                if ( $container !== null ) {
-                                                       return array( $container, $relPath, $cShard );
+                                                       return [ $container, $relPath, $cShard ];
                                                }
                                        }
                                }
                        }
                }
 
-               return array( null, null, null );
+               return [ null, null, null ];
        }
 
        /**
         * Like resolveStoragePath() except null values are returned if
         * the container is sharded and the shard could not be determined
-        * or if the path ends with '/'. The later case is illegal for FS
+        * or if the path ends with '/'. The latter case is illegal for FS
         * backends and can confuse listings for object store backends.
         *
         * This function is used when resolving paths that must be valid
@@ -1458,10 +1458,10 @@ abstract class FileBackendStore extends FileBackend {
        final protected function resolveStoragePathReal( $storagePath ) {
                list( $container, $relPath, $cShard ) = $this->resolveStoragePath( $storagePath );
                if ( $cShard !== null && substr( $relPath, -1 ) !== '/' ) {
-                       return array( $container, $relPath );
+                       return [ $container, $relPath ];
                }
 
-               return array( null, null );
+               return [ null, null ];
        }
 
        /**
@@ -1491,7 +1491,7 @@ abstract class FileBackendStore extends FileBackend {
                        // Allow certain directories to be above the hash dirs so as
                        // to work with FileRepo (e.g. "archive/a/ab" or "temp/a/ab").
                        // They must be 2+ chars to avoid any hash directory ambiguity.
-                       $m = array();
+                       $m = [];
                        if ( preg_match( "!^(?:[^/]{2,}/)*$hashDirRegex(?:/|$)!", $relPath, $m ) ) {
                                return '.' . implode( '', array_slice( $m, 1 ) );
                        }
@@ -1531,12 +1531,12 @@ abstract class FileBackendStore extends FileBackend {
                        if ( $hashLevels == 1 || $hashLevels == 2 ) {
                                $hashBase = (int)$config['base'];
                                if ( $hashBase == 16 || $hashBase == 36 ) {
-                                       return array( $hashLevels, $hashBase, $config['repeat'] );
+                                       return [ $hashLevels, $hashBase, $config['repeat'] ];
                                }
                        }
                }
 
-               return array( 0, 0, false ); // no sharding
+               return [ 0, 0, false ]; // no sharding
        }
 
        /**
@@ -1546,7 +1546,7 @@ abstract class FileBackendStore extends FileBackend {
         * @return array
         */
        final protected function getContainerSuffixes( $container ) {
-               $shards = array();
+               $shards = [];
                list( $digits, $base ) = $this->getContainerHashLevels( $container );
                if ( $digits > 0 ) {
                        $numShards = pow( $base, $digits );
@@ -1640,8 +1640,8 @@ abstract class FileBackendStore extends FileBackend {
        final protected function primeContainerCache( array $items ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
 
-               $paths = array(); // list of storage paths
-               $contNames = array(); // (cache key => resolved container name)
+               $paths = []; // list of storage paths
+               $contNames = []; // (cache key => resolved container name)
                // Get all the paths/containers from the items...
                foreach ( $items as $item ) {
                        if ( self::isStoragePath( $item ) ) {
@@ -1658,7 +1658,7 @@ abstract class FileBackendStore extends FileBackend {
                        }
                }
 
-               $contInfo = array(); // (resolved container name => cache value)
+               $contInfo = []; // (resolved container name => cache value)
                // Get all cache entries for these container cache keys...
                $values = $this->memCache->getMulti( array_keys( $contNames ) );
                foreach ( $values as $cacheKey => $val ) {
@@ -1702,8 +1702,8 @@ abstract class FileBackendStore extends FileBackend {
                if ( $path === null ) {
                        return; // invalid storage path
                }
-               $age = time() - wfTimestamp( TS_UNIX, $val['mtime'] );
-               $ttl = min( 7 * 86400, max( 300, floor( .1 * $age ) ) );
+               $mtime = wfTimestamp( TS_UNIX, $val['mtime'] );
+               $ttl = $this->memCache->adaptiveTTL( $mtime, 7 * 86400, 300, .1 );
                $key = $this->fileCacheKey( $path );
                // Set the cache unless it is currently salted.
                $this->memCache->set( $key, $val, $ttl );
@@ -1737,8 +1737,8 @@ abstract class FileBackendStore extends FileBackend {
        final protected function primeFileCache( array $items ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
 
-               $paths = array(); // list of storage paths
-               $pathNames = array(); // (cache key => storage path)
+               $paths = []; // list of storage paths
+               $pathNames = []; // (cache key => storage path)
                // Get all the paths/containers from the items...
                foreach ( $items as $item ) {
                        if ( self::isStoragePath( $item ) ) {
@@ -1763,12 +1763,12 @@ abstract class FileBackendStore extends FileBackend {
                                $this->cheapCache->set( $path, 'stat', $val );
                                if ( isset( $val['sha1'] ) ) { // some backends store SHA-1 as metadata
                                        $this->cheapCache->set( $path, 'sha1',
-                                               array( 'hash' => $val['sha1'], 'latest' => false ) );
+                                               [ 'hash' => $val['sha1'], 'latest' => false ] );
                                }
                                if ( isset( $val['xattr'] ) ) { // some backends store headers/metadata
                                        $val['xattr'] = self::normalizeXAttributes( $val['xattr'] );
                                        $this->cheapCache->set( $path, 'xattr',
-                                               array( 'map' => $val['xattr'], 'latest' => false ) );
+                                               [ 'map' => $val['xattr'], 'latest' => false ] );
                                }
                        }
                }
@@ -1782,7 +1782,7 @@ abstract class FileBackendStore extends FileBackend {
         * @since 1.22
         */
        final protected static function normalizeXAttributes( array $xattr ) {
-               $newXAttr = array( 'headers' => array(), 'metadata' => array() );
+               $newXAttr = [ 'headers' => [], 'metadata' => [] ];
 
                foreach ( $xattr['headers'] as $name => $value ) {
                        $newXAttr['headers'][strtolower( $name )] = $value;
@@ -1844,17 +1844,17 @@ abstract class FileBackendStore extends FileBackend {
  * FileBackendStore helper class for performing asynchronous file operations.
  *
  * For example, calling FileBackendStore::createInternal() with the "async"
- * param flag may result in a Status that contains this object as a value.
+ * param flag may result in a StatusValue that contains this object as a value.
  * This class is largely backend-specific and is mostly just "magic" to be
  * passed to FileBackendStore::executeOpHandlesInternal().
  */
 abstract class FileBackendStoreOpHandle {
        /** @var array */
-       public $params = array(); // params to caller functions
+       public $params = []; // params to caller functions
        /** @var FileBackendStore */
        public $backend;
        /** @var array */
-       public $resourcesToClose = array();
+       public $resourcesToClose = [];
 
        public $call; // string; name that identifies the function called
 
@@ -1886,7 +1886,7 @@ abstract class FileBackendStoreShardListIterator extends FilterIterator {
        protected $directory;
 
        /** @var array */
-       protected $multiShardPaths = array(); // (rel path => 1)
+       protected $multiShardPaths = []; // (rel path => 1)
 
        /**
         * @param FileBackendStore $backend
@@ -1928,7 +1928,7 @@ abstract class FileBackendStoreShardListIterator extends FilterIterator {
 
        public function rewind() {
                parent::rewind();
-               $this->multiShardPaths = array();
+               $this->multiShardPaths = [];
        }
 
        /**
@@ -1948,7 +1948,7 @@ class FileBackendStoreShardDirIterator extends FileBackendStoreShardListIterator
                $list = $this->backend->getDirectoryListInternal(
                        $container, $this->directory, $this->params );
                if ( $list === null ) {
-                       return new ArrayIterator( array() );
+                       return new ArrayIterator( [] );
                } else {
                        return is_array( $list ) ? new ArrayIterator( $list ) : $list;
                }
@@ -1963,7 +1963,7 @@ class FileBackendStoreShardFileIterator extends FileBackendStoreShardListIterato
                $list = $this->backend->getFileListInternal(
                        $container, $this->directory, $this->params );
                if ( $list === null ) {
-                       return new ArrayIterator( array() );
+                       return new ArrayIterator( [] );
                } else {
                        return is_array( $list ) ? new ArrayIterator( $list ) : $list;
                }