* @file
* @ingroup FileBackend
*/
+
use Wikimedia\AtEase\AtEase;
use Wikimedia\Timestamp\ConvertibleTimestamp;
const CACHE_CHEAP_SIZE = 500; // integer; max entries in "cheap cache"
const CACHE_EXPENSIVE_SIZE = 5; // integer; max entries in "expensive cache"
+ /** @var false Idiom for "no result due to missing file" (since 1.34) */
+ protected static $RES_ABSENT = false;
+ /** @var null Idiom for "no result due to I/O errors" (since 1.34) */
+ protected static $RES_ERROR = null;
+
+ /** @var string File does not exist according to a normal stat query */
+ protected static $ABSENT_NORMAL = 'FNE-N';
+ /** @var string File does not exist according to a "latest"-mode stat query */
+ protected static $ABSENT_LATEST = 'FNE-L';
+
/**
* @see FileBackend::__construct()
* Additional $config params include:
}
/**
- * Check if a file can be created or changed at a given storage path.
- * FS backends should check if the parent directory exists, files can be
- * written under it, and that any file already there is writable.
+ * Check if a file can be created or changed at a given storage path in the backend
+ *
+ * FS backends should check that the parent directory exists, files can be written
+ * under it, and that any file already there is both readable and writable.
* Backends using key/value stores should check if the container exists.
*
* @param string $storagePath
* @return StatusValue
*/
final public function createInternal( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
+
if ( strlen( $params['content'] ) > $this->maxFileSizeInternal() ) {
$status = $this->newStatus( 'backend-fail-maxsize',
$params['dst'], $this->maxFileSizeInternal() );
* @return StatusValue
*/
final public function storeInternal( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
+
if ( filesize( $params['src'] ) > $this->maxFileSizeInternal() ) {
$status = $this->newStatus( 'backend-fail-maxsize',
$params['dst'], $this->maxFileSizeInternal() );
* @return StatusValue
*/
final public function copyInternal( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
+
$status = $this->doCopyInternal( $params );
$this->clearCache( [ $params['dst'] ] );
if ( !isset( $params['dstExists'] ) || $params['dstExists'] ) {
* @return StatusValue
*/
final public function deleteInternal( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
+
$status = $this->doDeleteInternal( $params );
$this->clearCache( [ $params['src'] ] );
$this->deleteFileCache( $params['src'] ); // persistent cache
* @return StatusValue
*/
final public function moveInternal( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
+
$status = $this->doMoveInternal( $params );
$this->clearCache( [ $params['src'], $params['dst'] ] );
$this->deleteFileCache( $params['src'] ); // persistent cache
* @return StatusValue
*/
final public function describeInternal( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
+
if ( count( $params['headers'] ) ) {
$status = $this->doDescribeInternal( $params );
$this->clearCache( [ $params['src'] ] );
}
final public function concatenate( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$status = $this->newStatus();
// Try to lock the source files for the scope of this function
+ /** @noinspection PhpUnusedLocalVariableInspection */
$scopeLockS = $this->getScopedFileLocks( $params['srcs'], LockManager::LOCK_UW, $status );
if ( $status->isOK() ) {
// Actually do the file concatenation...
}
final protected function doPrepare( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$status = $this->newStatus();
}
final protected function doSecure( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$status = $this->newStatus();
}
final protected function doPublish( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$status = $this->newStatus();
}
final protected function doClean( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$status = $this->newStatus();
// Attempt to lock this directory...
$filesLockEx = [ $params['dir'] ];
+ /** @noinspection PhpUnusedLocalVariableInspection */
$scopedLockE = $this->getScopedFileLocks( $filesLockEx, LockManager::LOCK_EX, $status );
if ( !$status->isOK() ) {
return $status; // abort
}
final public function fileExists( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
+
$stat = $this->getFileStat( $params );
+ if ( is_array( $stat ) ) {
+ return true;
+ }
- return ( $stat === null ) ? null : (bool)$stat; // null => failure
+ return ( $stat === self::$RES_ABSENT ) ? false : self::EXISTENCE_ERROR;
}
final public function getFileTimestamp( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
+
$stat = $this->getFileStat( $params );
+ if ( is_array( $stat ) ) {
+ return $stat['mtime'];
+ }
- return $stat ? $stat['mtime'] : false;
+ return self::TIMESTAMP_FAIL; // all failure cases
}
final public function getFileSize( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
+
$stat = $this->getFileStat( $params );
+ if ( is_array( $stat ) ) {
+ return $stat['size'];
+ }
- return $stat ? $stat['size'] : false;
+ return self::SIZE_FAIL; // all failure cases
}
final public function getFileStat( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
+ $ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
+
$path = self::normalizeStoragePath( $params['src'] );
if ( $path === null ) {
- return false; // invalid storage path
+ return self::STAT_ERROR; // invalid storage path
}
- $ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
- $latest = !empty( $params['latest'] ); // use latest data?
- $requireSHA1 = !empty( $params['requireSHA1'] ); // require SHA-1 if file exists?
+ // Whether to bypass cache except for process cache entries loaded directly from
+ // high consistency backend queries (caller handles any cache flushing and locking)
+ $latest = !empty( $params['latest'] );
+ // Whether to ignore cache entries missing the SHA-1 field for existing files
+ $requireSHA1 = !empty( $params['requireSHA1'] );
+ $stat = $this->cheapCache->getField( $path, 'stat', self::CACHE_TTL );
+ // Load the persistent stat cache into process cache if needed
if ( !$latest ) {
- $stat = $this->cheapCache->getField( $path, 'stat', self::CACHE_TTL );
- // Note that some backends, like SwiftFileBackend, sometimes set file stat process
- // cache entries from mass object listings that do not include the SHA-1. In that
- // case, loading the persistent stat cache will likely yield the SHA-1.
if (
+ // File stat is not in process cache
$stat === null ||
+ // Key/value store backends might opportunistically set file stat process
+ // cache entries from object listings that do not include the SHA-1. In that
+ // case, loading the persistent stat cache will likely yield the SHA-1.
( $requireSHA1 && is_array( $stat ) && !isset( $stat['sha1'] ) )
) {
- $this->primeFileCache( [ $path ] ); // check persistent cache
+ $this->primeFileCache( [ $path ] );
+ // Get any newly process-cached entry
+ $stat = $this->cheapCache->getField( $path, 'stat', self::CACHE_TTL );
}
}
- $stat = $this->cheapCache->getField( $path, 'stat', self::CACHE_TTL );
- // If we want the latest data, check that this cached
- // value was in fact fetched with the latest available data.
if ( is_array( $stat ) ) {
if (
( !$latest || $stat['latest'] ) &&
) {
return $stat;
}
- } elseif ( in_array( $stat, [ 'NOT_EXIST', 'NOT_EXIST_LATEST' ], true ) ) {
- if ( !$latest || $stat === 'NOT_EXIST_LATEST' ) {
- return false;
+ } elseif ( $stat === self::$ABSENT_LATEST ) {
+ return self::STAT_ABSENT;
+ } elseif ( $stat === self::$ABSENT_NORMAL ) {
+ if ( !$latest ) {
+ return self::STAT_ABSENT;
}
}
+ // Load the file stat from the backend and update caches
$stat = $this->doGetFileStat( $params );
+ $this->ingestFreshFileStats( [ $path => $stat ], $latest );
- if ( is_array( $stat ) ) { // file exists
- // Strongly consistent backends can automatically set "latest"
- $stat['latest'] = $stat['latest'] ?? $latest;
- $this->cheapCache->setField( $path, 'stat', $stat );
- $this->setFileCache( $path, $stat ); // update persistent cache
- if ( isset( $stat['sha1'] ) ) { // some backends store SHA-1 as metadata
- $this->cheapCache->setField( $path, 'sha1',
- [ 'hash' => $stat['sha1'], 'latest' => $latest ] );
- }
- if ( isset( $stat['xattr'] ) ) { // some backends store headers/metadata
- $stat['xattr'] = self::normalizeXAttributes( $stat['xattr'] );
- $this->cheapCache->setField( $path, 'xattr',
- [ 'map' => $stat['xattr'], 'latest' => $latest ] );
+ if ( is_array( $stat ) ) {
+ return $stat;
+ }
+
+ return ( $stat === self::$RES_ERROR ) ? self::STAT_ERROR : self::STAT_ABSENT;
+ }
+
+ /**
+ * Ingest file stat entries that just came from querying the backend (not cache)
+ *
+ * @param array[]|bool[]|null[] $stats Map of (path => doGetFileStat() stype result)
+ * @param bool $latest Whether doGetFileStat()/doGetFileStatMulti() had the 'latest' flag
+ * @return bool Whether all files have non-error stat replies
+ */
+ final protected function ingestFreshFileStats( array $stats, $latest ) {
+ $success = true;
+
+ foreach ( $stats as $path => $stat ) {
+ if ( is_array( $stat ) ) {
+ // Strongly consistent backends might automatically set this flag
+ $stat['latest'] = $stat['latest'] ?? $latest;
+
+ $this->cheapCache->setField( $path, 'stat', $stat );
+ if ( isset( $stat['sha1'] ) ) {
+ // Some backends store the SHA-1 hash as metadata
+ $this->cheapCache->setField(
+ $path,
+ 'sha1',
+ [ 'hash' => $stat['sha1'], 'latest' => $latest ]
+ );
+ }
+ if ( isset( $stat['xattr'] ) ) {
+ // Some backends store custom headers/metadata
+ $stat['xattr'] = self::normalizeXAttributes( $stat['xattr'] );
+ $this->cheapCache->setField(
+ $path,
+ 'xattr',
+ [ 'map' => $stat['xattr'], 'latest' => $latest ]
+ );
+ }
+ // Update persistent cache (@TODO: set all entries in one batch)
+ $this->setFileCache( $path, $stat );
+ } elseif ( $stat === self::$RES_ABSENT ) {
+ $this->cheapCache->setField(
+ $path,
+ 'stat',
+ $latest ? self::$ABSENT_LATEST : self::$ABSENT_NORMAL
+ );
+ $this->cheapCache->setField(
+ $path,
+ 'xattr',
+ [ 'map' => self::XATTRS_FAIL, 'latest' => $latest ]
+ );
+ $this->cheapCache->setField(
+ $path,
+ 'sha1',
+ [ 'hash' => self::SHA1_FAIL, 'latest' => $latest ]
+ );
+ $this->logger->debug(
+ __METHOD__ . ': File {path} does not exist',
+ [ 'path' => $path ]
+ );
+ } else {
+ $success = false;
+ $this->logger->error(
+ __METHOD__ . ': Could not stat file {path}',
+ [ 'path' => $path ]
+ );
}
- } elseif ( $stat === false ) { // file does not exist
- $this->cheapCache->setField( $path, 'stat', $latest ? 'NOT_EXIST_LATEST' : 'NOT_EXIST' );
- $this->cheapCache->setField( $path, 'xattr', [ 'map' => false, 'latest' => $latest ] );
- $this->cheapCache->setField( $path, 'sha1', [ 'hash' => false, 'latest' => $latest ] );
- $this->logger->debug( __METHOD__ . ': File {path} does not exist', [
- 'path' => $path,
- ] );
- } else { // an error occurred
- $this->logger->warning( __METHOD__ . ': Could not stat file {path}', [
- 'path' => $path,
- ] );
}
- return $stat;
+ return $success;
}
/**
abstract protected function doGetFileStat( array $params );
public function getFileContentsMulti( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$params = $this->setConcurrencyFlags( $params );
$contents = $this->doGetFileContentsMulti( $params );
+ foreach ( $contents as $path => $content ) {
+ if ( !is_string( $content ) ) {
+ $contents[$path] = self::CONTENT_FAIL; // used for all failure cases
+ }
+ }
return $contents;
}
/**
* @see FileBackendStore::getFileContentsMulti()
* @param array $params
- * @return array
+ * @return string[]|bool[]|null[] Map of (path => string, false (missing), or null (error))
*/
protected function doGetFileContentsMulti( array $params ) {
$contents = [];
foreach ( $this->doGetLocalReferenceMulti( $params ) as $path => $fsFile ) {
- AtEase::suppressWarnings();
- $contents[$path] = $fsFile ? file_get_contents( $fsFile->getPath() ) : false;
- AtEase::restoreWarnings();
+ if ( $fsFile instanceof FSFile ) {
+ AtEase::suppressWarnings();
+ $content = file_get_contents( $fsFile->getPath() );
+ AtEase::restoreWarnings();
+ $contents[$path] = is_string( $content ) ? $content : self::$RES_ERROR;
+ } elseif ( $fsFile === self::$RES_ABSENT ) {
+ $contents[$path] = self::$RES_ABSENT;
+ } else {
+ $contents[$path] = self::$RES_ERROR;
+ }
}
return $contents;
}
final public function getFileXAttributes( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
+ $ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
+
$path = self::normalizeStoragePath( $params['src'] );
if ( $path === null ) {
- return false; // invalid storage path
+ return self::XATTRS_FAIL; // invalid storage path
}
- $ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$latest = !empty( $params['latest'] ); // use latest data?
if ( $this->cheapCache->hasField( $path, 'xattr', self::CACHE_TTL ) ) {
$stat = $this->cheapCache->getField( $path, 'xattr' );
}
}
$fields = $this->doGetFileXAttributes( $params );
- $fields = is_array( $fields ) ? self::normalizeXAttributes( $fields ) : false;
- $this->cheapCache->setField( $path, 'xattr', [ 'map' => $fields, 'latest' => $latest ] );
+ if ( is_array( $fields ) ) {
+ $fields = self::normalizeXAttributes( $fields );
+ $this->cheapCache->setField(
+ $path,
+ 'xattr',
+ [ 'map' => $fields, 'latest' => $latest ]
+ );
+ } elseif ( $fields === self::$RES_ABSENT ) {
+ $this->cheapCache->setField(
+ $path,
+ 'xattr',
+ [ 'map' => self::XATTRS_FAIL, 'latest' => $latest ]
+ );
+ } else {
+ $fields = self::XATTRS_FAIL; // used for all failure cases
+ }
return $fields;
}
/**
* @see FileBackendStore::getFileXAttributes()
* @param array $params
- * @return array[][]|false
+ * @return array[][]|false|null Attributes, false (missing file), or null (error)
*/
protected function doGetFileXAttributes( array $params ) {
return [ 'headers' => [], 'metadata' => [] ]; // not supported
}
final public function getFileSha1Base36( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
+ $ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
+
$path = self::normalizeStoragePath( $params['src'] );
if ( $path === null ) {
- return false; // invalid storage path
+ return self::SHA1_FAIL; // invalid storage path
}
- $ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$latest = !empty( $params['latest'] ); // use latest data?
if ( $this->cheapCache->hasField( $path, 'sha1', self::CACHE_TTL ) ) {
$stat = $this->cheapCache->getField( $path, 'sha1' );
return $stat['hash'];
}
}
- $hash = $this->doGetFileSha1Base36( $params );
- $this->cheapCache->setField( $path, 'sha1', [ 'hash' => $hash, 'latest' => $latest ] );
+ $sha1 = $this->doGetFileSha1Base36( $params );
+ if ( is_string( $sha1 ) ) {
+ $this->cheapCache->setField(
+ $path,
+ 'sha1',
+ [ 'hash' => $sha1, 'latest' => $latest ]
+ );
+ } elseif ( $sha1 === self::$RES_ABSENT ) {
+ $this->cheapCache->setField(
+ $path,
+ 'sha1',
+ [ 'hash' => self::SHA1_FAIL, 'latest' => $latest ]
+ );
+ } else {
+ $sha1 = self::SHA1_FAIL; // used for all failure cases
+ }
- return $hash;
+ return $sha1;
}
/**
* @see FileBackendStore::getFileSha1Base36()
* @param array $params
- * @return bool|string
+ * @return bool|string|null SHA1, false (missing file), or null (error)
*/
protected function doGetFileSha1Base36( array $params ) {
$fsFile = $this->getLocalReference( $params );
- if ( !$fsFile ) {
- return false;
- } else {
- return $fsFile->getSha1Base36();
+ if ( $fsFile instanceof FSFile ) {
+ $sha1 = $fsFile->getSha1Base36();
+
+ return is_string( $sha1 ) ? $sha1 : self::$RES_ERROR;
}
+
+ return ( $fsFile === self::$RES_ERROR ) ? self::$RES_ERROR : self::$RES_ABSENT;
}
final public function getFileProps( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
+
$fsFile = $this->getLocalReference( $params );
- $props = $fsFile ? $fsFile->getProps() : FSFile::placeholderProps();
- return $props;
+ return $fsFile ? $fsFile->getProps() : FSFile::placeholderProps();
}
final public function getLocalReferenceMulti( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$params = $this->setConcurrencyFlags( $params );
// Fetch local references of any remaning files...
$params['srcs'] = array_diff( $params['srcs'], array_keys( $fsFiles ) );
foreach ( $this->doGetLocalReferenceMulti( $params ) as $path => $fsFile ) {
- $fsFiles[$path] = $fsFile;
- if ( $fsFile ) { // update the process cache...
- $this->expensiveCache->setField( $path, 'localRef',
- [ 'object' => $fsFile, 'latest' => $latest ] );
+ if ( $fsFile instanceof FSFile ) {
+ $fsFiles[$path] = $fsFile;
+ $this->expensiveCache->setField(
+ $path,
+ 'localRef',
+ [ 'object' => $fsFile, 'latest' => $latest ]
+ );
+ } else {
+ $fsFiles[$path] = null; // used for all failure cases
}
}
/**
* @see FileBackendStore::getLocalReferenceMulti()
* @param array $params
- * @return array
+ * @return string[]|bool[]|null[] Map of (path => FSFile, false (missing), or null (error))
*/
protected function doGetLocalReferenceMulti( array $params ) {
return $this->doGetLocalCopyMulti( $params );
}
final public function getLocalCopyMulti( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$params = $this->setConcurrencyFlags( $params );
$tmpFiles = $this->doGetLocalCopyMulti( $params );
+ foreach ( $tmpFiles as $path => $tmpFile ) {
+ if ( !$tmpFile ) {
+ $tmpFiles[$path] = null; // used for all failure cases
+ }
+ }
return $tmpFiles;
}
/**
* @see FileBackendStore::getLocalCopyMulti()
* @param array $params
- * @return array
+ * @return string[]|bool[]|null[] Map of (path => TempFSFile, false (missing), or null (error))
*/
abstract protected function doGetLocalCopyMulti( array $params );
* @return string|null
*/
public function getFileHttpUrl( array $params ) {
- return null; // not supported
+ return self::TEMPURL_ERROR; // not supported
}
final public function streamFile( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$status = $this->newStatus();
final public function directoryExists( array $params ) {
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
if ( $dir === null ) {
- return false; // invalid storage path
+ return self::EXISTENCE_ERROR; // invalid storage path
}
if ( $shard !== null ) { // confined to a single container/shard
return $this->doDirectoryExists( $fullCont, $dir, $params );
$res = false; // response
foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
$exists = $this->doDirectoryExists( "{$fullCont}{$suffix}", $dir, $params );
- if ( $exists ) {
+ if ( $exists === true ) {
$res = true;
break; // found one!
- } elseif ( $exists === null ) { // error?
- $res = null; // if we don't find anything, it is indeterminate
+ } elseif ( $exists === self::$RES_ERROR ) {
+ $res = self::EXISTENCE_ERROR;
}
}
final public function getDirectoryList( array $params ) {
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
- if ( $dir === null ) { // invalid storage path
- return null;
+ if ( $dir === null ) {
+ return self::EXISTENCE_ERROR; // invalid storage path
}
if ( $shard !== null ) {
// File listing is confined to a single container/shard
* @param string $container Resolved container name
* @param string $dir Resolved path relative to container
* @param array $params
- * @return Traversable|array|null Returns null on failure
+ * @return Traversable|array|null Iterable list or null (error)
*/
abstract public function getDirectoryListInternal( $container, $dir, array $params );
final public function getFileList( array $params ) {
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
- if ( $dir === null ) { // invalid storage path
- return null;
+ if ( $dir === null ) {
+ return self::LIST_ERROR; // invalid storage path
}
if ( $shard !== null ) {
// File listing is confined to a single container/shard
* @param string $container Resolved container name
* @param string $dir Resolved path relative to container
* @param array $params
- * @return Traversable|string[]|null Returns null on failure
+ * @return Traversable|string[]|null Iterable list or null (error)
*/
abstract public function getFileListInternal( $container, $dir, array $params );
}
final protected function doOperationsInternal( array $ops, array $opts ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$status = $this->newStatus();
// Build up a list of files to lock...
$paths = $this->getPathsToLockForOpsInternal( $performOps );
// Try to lock those files for the scope of this function...
-
+ /** @noinspection PhpUnusedLocalVariableInspection */
$scopeLock = $this->getScopedFileLocks( $paths, 'mixed', $status );
if ( !$status->isOK() ) {
return $status; // abort
}
final protected function doQuickOperationsInternal( array $ops ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$status = $this->newStatus();
* @throws FileBackendError
*/
final public function executeOpHandlesInternal( array $fileOpHandles ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
foreach ( $fileOpHandles as $fileOpHandle ) {
*/
protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
if ( count( $fileOpHandles ) ) {
- throw new LogicException( "Backend does not support asynchronous operations." );
+ throw new FileBackendError( "Backend does not support asynchronous operations." );
}
return [];
}
final public function preloadFileStat( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
- $success = true; // no network errors
$params['concurrency'] = ( $this->parallelize !== 'off' ) ? $this->concurrency : 1;
$stats = $this->doGetFileStatMulti( $params );
return true; // not supported
}
- $latest = !empty( $params['latest'] ); // use latest data?
- foreach ( $stats as $path => $stat ) {
- $path = FileBackend::normalizeStoragePath( $path );
- if ( $path === null ) {
- continue; // this shouldn't happen
- }
- if ( is_array( $stat ) ) { // file exists
- // Strongly consistent backends can automatically set "latest"
- $stat['latest'] = $stat['latest'] ?? $latest;
- $this->cheapCache->setField( $path, 'stat', $stat );
- $this->setFileCache( $path, $stat ); // update persistent cache
- if ( isset( $stat['sha1'] ) ) { // some backends store SHA-1 as metadata
- $this->cheapCache->setField( $path, 'sha1',
- [ 'hash' => $stat['sha1'], 'latest' => $latest ] );
- }
- if ( isset( $stat['xattr'] ) ) { // some backends store headers/metadata
- $stat['xattr'] = self::normalizeXAttributes( $stat['xattr'] );
- $this->cheapCache->setField( $path, 'xattr',
- [ 'map' => $stat['xattr'], 'latest' => $latest ] );
- }
- } elseif ( $stat === false ) { // file does not exist
- $this->cheapCache->setField( $path, 'stat',
- $latest ? 'NOT_EXIST_LATEST' : 'NOT_EXIST' );
- $this->cheapCache->setField( $path, 'xattr',
- [ 'map' => false, 'latest' => $latest ] );
- $this->cheapCache->setField( $path, 'sha1',
- [ 'hash' => false, 'latest' => $latest ] );
- $this->logger->debug( __METHOD__ . ': File {path} does not exist', [
- 'path' => $path,
- ] );
- } else { // an error occurred
- $success = false;
- $this->logger->warning( __METHOD__ . ': Could not stat file {path}', [
- 'path' => $path,
- ] );
- }
- }
+ // Whether this queried the backend in high consistency mode
+ $latest = !empty( $params['latest'] );
- return $success;
+ return $this->ingestFreshFileStats( $stats, $latest );
}
/**
* @param array $items
*/
final protected function primeContainerCache( array $items ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$paths = []; // list of storage paths
* @param array $items List of storage paths
*/
final protected function primeFileCache( array $items ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$paths = []; // list of storage paths
$paths[] = FileBackend::normalizeStoragePath( $item );
}
}
- // Get rid of any paths that failed normalization...
+ // Get rid of any paths that failed normalization
$paths = array_filter( $paths, 'strlen' ); // remove nulls
// Get all the corresponding cache keys for paths...
foreach ( $paths as $path ) {
$pathNames[$this->fileCacheKey( $path )] = $path;
}
}
- // Get all cache entries for these file cache keys...
+ // Get all cache entries for these file cache keys.
+ // Note that negatives are not cached by getFileStat()/preloadFileStat().
$values = $this->memCache->getMulti( array_keys( $pathNames ) );
- foreach ( $values as $cacheKey => $val ) {
+ // Load all of the results into process cache...
+ foreach ( array_filter( $values, 'is_array' ) as $cacheKey => $stat ) {
$path = $pathNames[$cacheKey];
- if ( is_array( $val ) ) {
- $val['latest'] = false; // never completely trust cache
- $this->cheapCache->setField( $path, 'stat', $val );
- if ( isset( $val['sha1'] ) ) { // some backends store SHA-1 as metadata
- $this->cheapCache->setField( $path, 'sha1',
- [ 'hash' => $val['sha1'], 'latest' => false ] );
- }
- if ( isset( $val['xattr'] ) ) { // some backends store headers/metadata
- $val['xattr'] = self::normalizeXAttributes( $val['xattr'] );
- $this->cheapCache->setField( $path, 'xattr',
- [ 'map' => $val['xattr'], 'latest' => false ] );
- }
+ // Sanity; this flag only applies to stat info loaded directly
+ // from a high consistency backend query to the process cache
+ unset( $stat['latest'] );
+
+ $this->cheapCache->setField( $path, 'stat', $stat );
+ if ( isset( $stat['sha1'] ) && strlen( $stat['sha1'] ) == 31 ) {
+ // Some backends store SHA-1 as metadata
+ $this->cheapCache->setField(
+ $path,
+ 'sha1',
+ [ 'hash' => $stat['sha1'], 'latest' => false ]
+ );
+ }
+ if ( isset( $stat['xattr'] ) && is_array( $stat['xattr'] ) ) {
+ // Some backends store custom headers/metadata
+ $stat['xattr'] = self::normalizeXAttributes( $stat['xattr'] );
+ $this->cheapCache->setField(
+ $path,
+ 'xattr',
+ [ 'map' => $stat['xattr'], 'latest' => false ]
+ );
}
}
}