23use Psr\Log\LoggerInterface;
89 list( $required, $optional, $paths ) = $this->
allowedParams();
90 foreach ( $required as $name ) {
91 if ( isset(
$params[$name] ) ) {
92 $this->params[$name] =
$params[$name];
94 throw new InvalidArgumentException(
"File operation missing parameter '$name'." );
97 foreach ( $optional as $name ) {
98 if ( isset(
$params[$name] ) ) {
99 $this->params[$name] =
$params[$name];
102 foreach ( $paths as $name ) {
103 if ( isset( $this->params[$name] ) ) {
142 return $this->params[$name] ??
null;
160 return [ self::ASSUMED_EXISTS => [], self::ASSUMED_SHA1 => [], self::ASSUMED_SIZE => [] ];
169 return [
'read' => [],
'write' => [] ];
193 if ( isset( $deps[
'read'][
$path] ) || isset( $deps[
'write'][
$path] ) ) {
198 if ( isset( $deps[
'write'][
$path] ) ) {
214 if ( $this->cancelled ) {
224 'newSha1' => $this->
fileSha1( $path, $oPredicates )
228 if ( $nPredicates[self::ASSUMED_SHA1][
$path] ===
false ) {
236 'op' => $this->
fileExists( $path, $oPredicates ) ?
'update' :
'create',
243 return array_merge( $nullEntries, $updateEntries, $deleteEntries );
254 final public function precheck( array &$predicates ) {
255 if ( $this->state !== self::STATE_NEW ) {
256 return StatusValue::newFatal(
'fileop-fail-state', self::STATE_NEW, $this->state );
260 $status = StatusValue::newGood();
262 if ( !$this->backend->isPathUsableInternal(
$path ) ) {
263 $status->fatal(
'backend-fail-usable',
$path );
266 if ( !$status->isOK() ) {
271 if ( !$status->isOK() ) {
283 return StatusValue::newGood();
292 if ( $this->state !== self::STATE_CHECKED ) {
293 return StatusValue::newFatal(
'fileop-fail-state', self::STATE_CHECKED, $this->state );
294 } elseif ( $this->
failed ) {
295 return StatusValue::newFatal(
'fileop-fail-attempt-precheck' );
298 if ( $this->cancelled ) {
299 $status = StatusValue::newGood();
302 if ( !$status->isOK() ) {
315 return StatusValue::newGood();
326 $this->async =
false;
359 return [ [], [], [] ];
396 return array_values( array_unique(
410 $status = StatusValue::newGood();
413 if ( $this->sourceSize ===
null ) {
414 $this->sourceSize = $this->
fileSize( $this->params[
'src'], $predicates );
418 if ( $this->sourceSha1 ===
null ) {
419 $this->sourceSha1 = $this->
fileSha1( $this->params[
'src'], $predicates );
422 $this->destExists = $this->
fileExists( $this->params[
'dst'], $predicates );
424 $this->overwriteSameCase =
false;
425 if ( $this->destExists ) {
426 if ( $this->
getParam(
'overwrite' ) ) {
428 } elseif ( $this->
getParam(
'overwriteSame' ) ) {
430 $dhash = $this->
fileSha1( $this->params[
'dst'], $predicates );
431 $dsize = $this->
fileSize( $this->params[
'dst'], $predicates );
433 if ( !strlen( $this->sourceSha1 ) || !strlen( $dhash ) ) {
434 $status->fatal(
'backend-fail-hashes' );
435 } elseif ( !is_int( $this->sourceSize ) || !is_int( $dsize ) ) {
436 $status->fatal(
'backend-fail-sizes' );
437 } elseif ( $this->sourceSha1 !== $dhash || $this->sourceSize !== $dsize ) {
439 $status->fatal(
'backend-fail-notsame', $this->params[
'dst'] );
441 $this->overwriteSameCase =
true;
444 $status->fatal(
'backend-fail-alreadyexists', $this->params[
'dst'] );
446 } elseif ( $this->destExists === FileBackend::EXISTENCE_ERROR ) {
447 $status->fatal(
'backend-fail-stat', $this->params[
'dst'] );
484 if ( isset( $predicates[self::ASSUMED_EXISTS][
$source] ) ) {
489 return $this->backend->fileExists(
$params );
505 if ( isset( $predicates[self::ASSUMED_SIZE][
$source] ) ) {
508 isset( $predicates[self::ASSUMED_EXISTS][
$source] ) &&
509 !$predicates[self::ASSUMED_EXISTS][
$source]
515 return $this->backend->getFileSize(
$params );
527 if ( isset( $predicates[self::ASSUMED_SHA1][
$source] ) ) {
530 isset( $predicates[self::ASSUMED_EXISTS][
$source] ) &&
531 !$predicates[self::ASSUMED_EXISTS][
$source]
537 return $this->backend->getFileSha1Base36(
$params );
557 $params[
'failedAction'] = $action;
559 $this->logger->error( static::class .
560 " failed (batch #{$this->batchId}): " . FormatJson::encode(
$params ) );
561 }
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.
setBatchId( $batchId)
Set the batch UUID this operation belongs to.
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.
fileSha1( $source, array $predicates)
Get the SHA-1 of a file in storage when this operation is attempted.
static newPredicates()
Get a new empty predicates array for precheck()
fileExists( $source, array $predicates)
Check if a file will exist in storage when this operation is attempted.
allowedParams()
Get the file operation parameters.
attemptAsync()
Attempt the operation in the background.
doPrecheck(array &$predicates)
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.
getSourceSize()
precheckDestExistence() helper function to get the source file size.
storagePathsChanged()
Get a list of storage paths written to for this operation.
applyDependencies(array $deps)
Update a dependency tracking array to account for this operation.
attempt()
Attempt the operation.
precheck(array &$predicates)
Check preconditions of the operation without writing anything.
setFlags(array $params)
Adjust params to FileBackendStore internal file calls.
fileSize( $source, array $predicates)
Get the size a file in storage will have when this operation is attempted.
getJournalEntries(array $oPredicates, array $nPredicates)
Get the file journal entries for this file operation.
logFailure( $action)
Log a file operation failure and preserve any temp files.
storagePathsRead()
Get a list of storage paths read from for this operation.
getSourceSha1Base36()
precheckDestExistence() helper function to get the source file SHA-1.
attemptQuick()
Attempt the operation without regards to prechecks.
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()
__construct(FileBackendStore $backend, array $params, LoggerInterface $logger)
Build a new batch file operation transaction.
precheckDestExistence(array $predicates)
Check for errors with regards to the destination file already existing.