* Do not call this function from places outside FileBackend and FileOp.
*
* $params include:
- * - content : the raw file contents
- * - dst : destination storage path
- * - disposition : Content-Disposition header value for the destination
- * - async : Status will be returned immediately if supported.
- * If the status is OK, then its value field will be
- * set to a FileBackendStoreOpHandle object.
+ * - content : the raw file contents
+ * - dst : destination storage path
+ * - disposition : Content-Disposition header value for the destination
+ * - 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
+ * 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 $params Array
* @return Status
} else {
$status = $this->doCreateInternal( $params );
$this->clearCache( array( $params['dst'] ) );
- $this->deleteFileCache( $params['dst'] ); // persistent cache
+ if ( !isset( $params['dstExists'] ) || $params['dstExists'] ) {
+ $this->deleteFileCache( $params['dst'] ); // persistent cache
+ }
}
wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
/**
* @see FileBackendStore::createInternal()
+ * @return Status
*/
abstract protected function doCreateInternal( array $params );
* Do not call this function from places outside FileBackend and FileOp.
*
* $params include:
- * - src : source path on disk
- * - dst : destination storage path
- * - disposition : Content-Disposition header value for the destination
- * - async : Status will be returned immediately if supported.
- * If the status is OK, then its value field will be
- * set to a FileBackendStoreOpHandle object.
+ * - src : source path on disk
+ * - dst : destination storage path
+ * - disposition : Content-Disposition header value for the destination
+ * - 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
+ * 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 $params Array
* @return Status
} else {
$status = $this->doStoreInternal( $params );
$this->clearCache( array( $params['dst'] ) );
- $this->deleteFileCache( $params['dst'] ); // persistent cache
+ if ( !isset( $params['dstExists'] ) || $params['dstExists'] ) {
+ $this->deleteFileCache( $params['dst'] ); // persistent cache
+ }
}
wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
/**
* @see FileBackendStore::storeInternal()
+ * @return Status
*/
abstract protected function doStoreInternal( array $params );
* - async : Status will be returned immediately if supported.
* If the status 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 $params Array
* @return Status
wfProfileIn( __METHOD__ . '-' . $this->name );
$status = $this->doCopyInternal( $params );
$this->clearCache( array( $params['dst'] ) );
- $this->deleteFileCache( $params['dst'] ); // persistent cache
+ if ( !isset( $params['dstExists'] ) || $params['dstExists'] ) {
+ $this->deleteFileCache( $params['dst'] ); // persistent cache
+ }
wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status;
/**
* @see FileBackendStore::copyInternal()
+ * @return Status
*/
abstract protected function doCopyInternal( array $params );
/**
* @see FileBackendStore::deleteInternal()
+ * @return Status
*/
abstract protected function doDeleteInternal( array $params );
* - async : Status will be returned immediately if supported.
* If the status 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 $params Array
* @return Status
$status = $this->doMoveInternal( $params );
$this->clearCache( array( $params['src'], $params['dst'] ) );
$this->deleteFileCache( $params['src'] ); // persistent cache
- $this->deleteFileCache( $params['dst'] ); // persistent cache
+ if ( !isset( $params['dstExists'] ) || $params['dstExists'] ) {
+ $this->deleteFileCache( $params['dst'] ); // persistent cache
+ }
wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status;
return $status;
}
+ /**
+ * Alter metadata for a file at the storage path.
+ * Do not call this function from places outside FileBackend and FileOp.
+ *
+ * $params include:
+ * - src : source storage path
+ * - disposition : Content-Disposition header value for the destination
+ * - 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
+ * set to a FileBackendStoreOpHandle object.
+ *
+ * @param $params Array
+ * @return Status
+ */
+ final public function describeInternal( array $params ) {
+ wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
+ $status = $this->doDescribeInternal( $params );
+ $this->clearCache( array( $params['src'] ) );
+ $this->deleteFileCache( $params['src'] ); // persistent cache
+ wfProfileOut( __METHOD__ . '-' . $this->name );
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
+ /**
+ * @see FileBackendStore::describeInternal()
+ * @return Status
+ */
+ protected function doDescribeInternal( array $params ) {
+ return Status::newGood();
+ }
+
/**
* No-op file operation that does nothing.
* Do not call this function from places outside FileBackend and FileOp.
if ( $this->cheapCache->has( $path, 'stat', self::CACHE_TTL ) ) {
$stat = $this->cheapCache->get( $path, 'stat' );
// If we want the latest data, check that this cached
- // value was in fact fetched with the latest available data
- // (the process cache is ignored if it contains a negative).
- if ( !$latest || ( is_array( $stat ) && $stat['latest'] ) ) {
- wfProfileOut( __METHOD__ . '-' . $this->name );
- wfProfileOut( __METHOD__ );
- return $stat;
+ // value was in fact fetched with the latest available data.
+ if ( is_array( $stat ) ) {
+ if ( !$latest || $stat['latest'] ) {
+ wfProfileOut( __METHOD__ . '-' . $this->name );
+ wfProfileOut( __METHOD__ );
+ return $stat;
+ }
+ } elseif ( in_array( $stat, array( 'NOT_EXIST', 'NOT_EXIST_LATEST' ) ) ) {
+ if ( !$latest || $stat === 'NOT_EXIST_LATEST' ) {
+ wfProfileOut( __METHOD__ . '-' . $this->name );
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
}
}
wfProfileIn( __METHOD__ . '-miss' );
array( 'hash' => $stat['sha1'], 'latest' => $latest ) );
}
} elseif ( $stat === false ) { // file does not exist
- $this->cheapCache->set( $path, 'stat', false );
+ $this->cheapCache->set( $path, 'stat', $latest ? 'NOT_EXIST_LATEST' : 'NOT_EXIST' );
wfDebug( __METHOD__ . ": File $path does not exist.\n" );
} else { // an error occurred
wfDebug( __METHOD__ . ": Could not stat file $path.\n" );
*/
final public function getOperationsInternal( array $ops ) {
$supportedOps = array(
- 'store' => 'StoreFileOp',
- 'copy' => 'CopyFileOp',
- 'move' => 'MoveFileOp',
- 'delete' => 'DeleteFileOp',
- 'create' => 'CreateFileOp',
- 'null' => 'NullFileOp'
+ 'store' => 'StoreFileOp',
+ 'copy' => 'CopyFileOp',
+ 'move' => 'MoveFileOp',
+ 'delete' => 'DeleteFileOp',
+ 'create' => 'CreateFileOp',
+ 'describe' => 'DescribeFileOp',
+ 'null' => 'NullFileOp'
);
$performOps = array(); // array of FileOp objects
wfProfileIn( __METHOD__ . '-' . $this->name );
$status = Status::newGood();
+ // Fix up custom header name/value pairs...
+ $ops = array_map( array( $this, 'stripInvalidHeadersFromOp' ), $ops );
+
// Build up a list of FileOps...
$performOps = $this->getOperationsInternal( $ops );
wfProfileIn( __METHOD__ . '-' . $this->name );
$status = Status::newGood();
+ // Fix up custom header name/value pairs...
+ $ops = array_map( array( $this, 'stripInvalidHeadersFromOp' ), $ops );
+
+ // Clear any file cache entries
+ $this->clearCache();
+
$supportedOps = array( 'create', 'store', 'copy', 'move', 'delete', 'null' );
$async = ( $this->parallelize === 'implicit' );
$maxConcurrency = $this->concurrency; // throttle
return array();
}
+ /**
+ * Strip long HTTP headers from a file operation
+ *
+ * @param $op array Same format as doOperation()
+ * @return Array
+ */
+ protected function stripInvalidHeadersFromOp( array $op ) {
+ if ( isset( $op['headers'] ) ) {
+ foreach ( $op['headers'] as $name => $value ) {
+ if ( strlen( $name ) > 255 || strlen( $value ) > 255 ) {
+ trigger_error( "Header '$name: $value' is too long." );
+ unset( $op['headers'][$name] );
+ } elseif ( !strlen( $value ) ) {
+ $op['headers'][$name] = ''; // null/false => ""
+ }
+ }
+ }
+ return $op;
+ }
+
/**
* @see FileBackend::preloadCache()
*/
/**
* Delete the cached stat info for a file path.
* The cache key is salted for a while to prevent race conditions.
+ * Since negatives (404s) are not cached, this does not need to be called when
+ * a file is created at a path were there was none before.
*
* @param $path string Storage path
*/