Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
87.72% covered (warning)
87.72%
50 / 57
80.00% covered (warning)
80.00%
4 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
MoveFileOp
89.29% covered (warning)
89.29%
50 / 56
80.00% covered (warning)
80.00%
4 / 5
17.36
0.00% covered (danger)
0.00%
0 / 1
 allowedParams
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 doPrecheck
83.33% covered (warning)
83.33%
30 / 36
0.00% covered (danger)
0.00%
0 / 1
9.37
 doAttempt
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
5
 storagePathsRead
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 storagePathsChanged
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Helper class for representing operations with transaction support.
4 *
5 * @license GPL-2.0-or-later
6 * @file
7 * @ingroup FileBackend
8 */
9
10namespace Wikimedia\FileBackend\FileOps;
11
12use StatusValue;
13use Wikimedia\FileBackend\FileBackend;
14
15/**
16 * Move a file from one storage path to another in the backend.
17 * Parameters for this operation are outlined in FileBackend::doOperations().
18 */
19class MoveFileOp extends FileOp {
20    /** @inheritDoc */
21    protected function allowedParams() {
22        return [
23            [ 'src', 'dst' ],
24            [ 'overwrite', 'overwriteSame', 'ignoreMissingSource', 'headers' ],
25            [ 'src', 'dst' ]
26        ];
27    }
28
29    /** @inheritDoc */
30    protected function doPrecheck(
31        FileStatePredicates $opPredicates,
32        FileStatePredicates $batchPredicates
33    ) {
34        $status = StatusValue::newGood();
35
36        if ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
37            $status->fatal( 'backend-fail-usable', $this->params['dst'] );
38
39            return $status;
40        }
41
42        if (
43            !$this->getParam( 'ignoreMissingSource' ) &&
44            !$this->backend->isPathUsableInternal( $this->params['src'] )
45        ) {
46            $status->fatal( 'backend-fail-usable', $this->params['src'] );
47
48            return $status;
49        }
50
51        // Check source file existence
52        $srcExists = $this->resolveFileExistence( $this->params['src'], $opPredicates );
53        if ( $srcExists === false ) {
54            if ( $this->getParam( 'ignoreMissingSource' ) ) {
55                $this->noOp = true; // no-op
56                // Update file existence predicates (cache 404s)
57                $batchPredicates->assumeFileDoesNotExist( $this->params['src'] );
58
59                return $status; // nothing to do
60            } else {
61                $status->fatal( 'backend-fail-notexists', $this->params['src'] );
62
63                return $status;
64            }
65        } elseif ( $srcExists === FileBackend::EXISTENCE_ERROR ) {
66            $status->fatal( 'backend-fail-stat', $this->params['src'] );
67
68            return $status;
69        }
70        // Check if an incompatible destination file exists
71        $srcSize = function () use ( $opPredicates ) {
72            static $size = null;
73            $size ??= $this->resolveFileSize( $this->params['src'], $opPredicates );
74            return $size;
75        };
76        $srcSha1 = function () use ( $opPredicates ) {
77            static $sha1 = null;
78            $sha1 ??= $this->resolveFileSha1Base36( $this->params['src'], $opPredicates );
79            return $sha1;
80        };
81        $status->merge( $this->precheckDestExistence( $opPredicates, $srcSize, $srcSha1 ) );
82        $this->params['dstExists'] = $this->destExists; // see FileBackendStore::setFileCache()
83
84        // Update file existence predicates if the operation is expected to be allowed to run
85        if ( $status->isOK() ) {
86            $batchPredicates->assumeFileExists( $this->params['dst'], $srcSize, $srcSha1 );
87            if ( $this->params['src'] !== $this->params['dst'] ) {
88                $batchPredicates->assumeFileDoesNotExist( $this->params['src'] );
89            }
90        }
91
92        return $status; // safe to call attempt()
93    }
94
95    /** @inheritDoc */
96    protected function doAttempt() {
97        if ( $this->overwriteSameCase ) {
98            if ( $this->params['src'] === $this->params['dst'] ) {
99                // Do nothing to the destination (which is also the source)
100                $status = StatusValue::newGood();
101            } else {
102                // Just delete the source as the destination file needs no changes
103                $status = $this->backend->deleteInternal( $this->setFlags(
104                    [ 'src' => $this->params['src'] ]
105                ) );
106            }
107        } elseif ( $this->params['src'] === $this->params['dst'] ) {
108            // Just update the destination file headers
109            $headers = $this->getParam( 'headers' ) ?: [];
110            $status = $this->backend->describeInternal( $this->setFlags(
111                [ 'src' => $this->params['dst'], 'headers' => $headers ]
112            ) );
113        } else {
114            // Move the file to the destination
115            $status = $this->backend->moveInternal( $this->setFlags( $this->params ) );
116        }
117
118        return $status;
119    }
120
121    /** @inheritDoc */
122    public function storagePathsRead() {
123        return [ $this->params['src'] ];
124    }
125
126    /** @inheritDoc */
127    public function storagePathsChanged() {
128        return [ $this->params['src'], $this->params['dst'] ];
129    }
130}
131
132/** @deprecated class alias since 1.43 */
133class_alias( MoveFileOp::class, 'MoveFileOp' );