23use Psr\Log\LoggerInterface;
24use Wikimedia\RequestTimeout\TimeoutException;
61 private const STATE_NEW = 1;
63 private const STATE_CHECKED = 2;
65 private const STATE_ATTEMPTED = 3;
81 foreach ( $required as $name ) {
82 if ( isset(
$params[$name] ) ) {
83 $this->params[$name] =
$params[$name];
85 throw new InvalidArgumentException(
"File operation missing parameter '$name'." );
88 foreach ( $optional as $name ) {
89 if ( isset(
$params[$name] ) ) {
90 $this->params[$name] =
$params[$name];
93 foreach ( $paths as $name ) {
94 if ( isset( $this->params[$name] ) ) {
111 return $res ??
$path;
124 return $this->params[$name] ??
null;
142 return [
'read' => [],
'write' => [] ];
166 if ( isset( $deps[
'read'][
$path] ) || isset( $deps[
'write'][
$path] ) ) {
171 if ( isset( $deps[
'write'][
$path] ) ) {
188 if ( $this->state !== self::STATE_NEW ) {
189 return StatusValue::newFatal(
'fileop-fail-state', self::STATE_NEW, $this->state );
191 $this->state = self::STATE_CHECKED;
193 $status = StatusValue::newGood();
195 if ( !$this->backend->isPathUsableInternal(
$path ) ) {
196 $status->fatal(
'backend-fail-usable',
$path );
199 if ( !$status->isOK() ) {
204 $status = $this->
doPrecheck( $opPredicates, $predicates );
205 if ( !$status->isOK() ) {
225 return StatusValue::newGood();
234 if ( $this->state !== self::STATE_CHECKED ) {
235 return StatusValue::newFatal(
'fileop-fail-state', self::STATE_CHECKED, $this->state );
236 } elseif ( $this->
failed ) {
237 return StatusValue::newFatal(
'fileop-fail-attempt-precheck' );
239 $this->state = self::STATE_ATTEMPTED;
241 $status = StatusValue::newGood();
244 if ( !$status->isOK() ) {
257 return StatusValue::newGood();
268 $this->async =
false;
279 $this->state = self::STATE_CHECKED;
290 $this->state = self::STATE_CHECKED;
301 return [ [], [], [] ];
338 return array_values( array_unique(
359 $status = StatusValue::newGood();
363 $this->overwriteSameCase =
false;
364 if ( $this->destExists ) {
365 if ( $this->
getParam(
'overwrite' ) ) {
367 } elseif ( $this->
getParam(
'overwriteSame' ) ) {
369 $sourceSize = ( $sourceSize instanceof Closure ) ? $sourceSize() : $sourceSize;
370 $sourceSha1 = ( $sourceSha1 instanceof Closure ) ? $sourceSha1() : $sourceSha1;
372 $dstSize = $this->
resolveFileSize( $this->params[
'dst'], $opPredicates );
374 if ( !strlen( $sourceSha1 ) || !strlen( $dstSha1 ) ) {
375 $status->fatal(
'backend-fail-hashes' );
376 } elseif ( !is_int( $sourceSize ) || !is_int( $dstSize ) ) {
377 $status->fatal(
'backend-fail-sizes' );
378 } elseif ( $sourceSha1 !== $dstSha1 || $sourceSize !== $dstSize ) {
380 $status->fatal(
'backend-fail-notsame', $this->params[
'dst'] );
382 $this->overwriteSameCase =
true;
385 $status->fatal(
'backend-fail-alreadyexists', $this->params[
'dst'] );
387 } elseif ( $this->destExists === FileBackend::EXISTENCE_ERROR ) {
388 $status->fatal(
'backend-fail-stat', $this->params[
'dst'] );
408 return $this->backend->fileExists( [
'src' =>
$path,
'latest' =>
true ] );
428 return $this->backend->getFileSize( [
'src' =>
$path,
'latest' =>
true ] );
444 return $this->backend->getFileSha1Base36( [
'src' =>
$path,
'latest' =>
true ] );
465 $params[
'failedAction'] = $action;
467 $this->logger->error( static::class .
468 " failed: " . FormatJson::encode(
$params ) );
469 }
catch ( TimeoutException $e ) {
471 }
catch ( Exception $e ) {
Base class for all backends using particular storage medium.
static isStoragePath( $path)
Check if a given path is a "mwstore://" path.
static normalizeStoragePath( $storagePath)
Normalize a storage path by cleaning up directory separators.
FileBackend helper class for representing operations.
bool null $overwriteSameCase
static normalizeIfValidStoragePath( $path)
Normalize a string if it is a valid storage path.
static newDependencies()
Get a new empty dependency tracking array for paths read/written to.
resolveFileExistence( $source, FileStatePredicates $opPredicates)
Check if a file will exist in storage when this operation is attempted.
precheckDestExistence(FileStatePredicates $opPredicates, $sourceSize, $sourceSha1)
Check for errors with regards to the destination file already existing.
allowedParams()
Get the file operation parameters.
attemptAsync()
Attempt the operation in the background.
getParam( $name)
Get the value of the parameter with the given name.
storagePathsReadOrChanged()
Get a list of storage paths read from or written to for this operation.
storagePathsChanged()
Get a list of storage paths written to for this operation.
int $state
Stage in the operation life-cycle.
applyDependencies(array $deps)
Update a dependency tracking array to account for this operation.
attempt()
Attempt the operation.
setFlags(array $params)
Adjust params to FileBackendStore internal file calls.
bool $async
Whether the operation is part of a concurrent sub-batch of operation.
doPrecheck(FileStatePredicates $opPredicates, FileStatePredicates $batchPredicates)
Do a dry-run precondition check of the operation in the context of op batch.
logFailure( $action)
Log a file operation failure and preserve any temp files.
storagePathsRead()
Get a list of storage paths read from for this operation.
attemptQuick()
Attempt the operation without regards to prechecks.
resolveFileSha1Base36( $source, FileStatePredicates $opPredicates)
Get the SHA-1 of a file in storage when this operation is attempted.
FileBackendStore $backend
dependsOn(array $deps)
Check if this operation changes files listed in $paths.
getBackend()
Get the backend this operation is for.
attemptAsyncQuick()
Attempt the operation in the background without regards to prechecks.
failed()
Check if this operation failed precheck() or attempt()
resolveFileSize( $source, FileStatePredicates $opPredicates)
Get the size a file in storage will have when this operation is attempted.
__construct(FileBackendStore $backend, array $params, LoggerInterface $logger)
Build a new batch file operation transaction.
bool $noOp
Whether the operation pre-check stage marked the attempt stage as a no-op.
bool $failed
Whether the operation pre-check or attempt stage failed.
precheck(FileStatePredicates $predicates)
Do a dry-run precondition check of the operation in the context of op batch.
Helper class for tracking counterfactual file states when pre-checking file operation batches.
resolveFileSha1Base36(string $path, $curSha1Func)
Get the hypothetical SHA-1 hash of a file given predicated and current state of files.
resolveFileSize(string $path, $curSizeFunc)
Get the hypothetical size of a file given predicated and current state of files.
resolveFileExistence(string $path, $curExistenceFunc)
Get the hypothetical existance a file given predicated and current state of files.