Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
FileStatePredicates
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 8
156
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 assumeFileExists
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 assumeFileDoesNotExist
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 resolveFileExistence
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 resolveFileSize
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 resolveFileSha1Base36
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 resolveFileProperty
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 snapshot
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 * @ingroup FileBackend
6 */
7
8namespace Wikimedia\FileBackend\FileOps;
9
10use Closure;
11
12/**
13 * Helper class for tracking counterfactual file states when pre-checking file operation batches
14 *
15 * The file states are represented with (existence,size,sha1) triples. When combined with the
16 * current state of files in the backend, this can be used to simulate how a batch of operations
17 * would play out as a "dry run". This is used in FileBackend::doOperations() to bail out if any
18 * failure can be predicted before modifying any data. This includes file operation batches where
19 * the same file gets modified by different operations within the batch.
20 *
21 * @internal Only for use within FileBackend
22 */
23class FileStatePredicates {
24    protected const EXISTS = 'exists';
25    protected const SIZE = 'size';
26    protected const SHA1 = 'sha1';
27
28    /** @var array<string,array> Map of (storage path => file state map) */
29    private $fileStateByPath;
30
31    public function __construct() {
32        $this->fileStateByPath = [];
33    }
34
35    /**
36     * Predicate that a file exists at the path
37     *
38     * @param string $path Storage path
39     * @param int|false|Closure $size File size or idempotent function yielding the size
40     * @param string|Closure $sha1Base36 File hash, or, idempotent function yielding the hash
41     */
42    public function assumeFileExists( string $path, $size, $sha1Base36 ) {
43        $this->fileStateByPath[$path] = [
44            self::EXISTS => true,
45            self::SIZE => $size,
46            self::SHA1 => $sha1Base36
47        ];
48    }
49
50    /**
51     * Predicate that no file exists at the path
52     *
53     * @param string $path Storage path
54     */
55    public function assumeFileDoesNotExist( string $path ) {
56        $this->fileStateByPath[$path] = [
57            self::EXISTS => false,
58            self::SIZE => false,
59            self::SHA1 => false
60        ];
61    }
62
63    /**
64     * Get the hypothetical existance a file given predicated and current state of files
65     *
66     * @param string $path Storage path
67     * @param Closure $curExistenceFunc Function to compute the current existence for a given path
68     * @return bool|null Whether the file exists; null on error
69     */
70    public function resolveFileExistence( string $path, $curExistenceFunc ) {
71        return self::resolveFileProperty( $path, self::EXISTS, $curExistenceFunc );
72    }
73
74    /**
75     * Get the hypothetical size of a file given predicated and current state of files
76     *
77     * @param string $path Storage path
78     * @param Closure $curSizeFunc Function to compute the current size for a given path
79     * @return int|false Bytes; false on error
80     */
81    public function resolveFileSize( string $path, $curSizeFunc ) {
82        return self::resolveFileProperty( $path, self::SIZE, $curSizeFunc );
83    }
84
85    /**
86     * Get the hypothetical SHA-1 hash of a file given predicated and current state of files
87     *
88     * @param string $path Storage path
89     * @param Closure $curSha1Func Function to compute the current SHA-1 hash for a given path
90     * @return string|false Base 36 SHA-1 hash; false on error
91     */
92    public function resolveFileSha1Base36( string $path, $curSha1Func ) {
93        return self::resolveFileProperty( $path, self::SHA1, $curSha1Func );
94    }
95
96    /**
97     * @param string $path Storage path
98     * @param string $property One of (self::EXISTS, self::SIZE, self::SHA1)
99     * @param Closure $curValueFunc Function to compute the current value for a given path
100     * @return mixed
101     */
102    private function resolveFileProperty( $path, $property, $curValueFunc ) {
103        if ( isset( $this->fileStateByPath[$path] ) ) {
104            // File is predicated to have a counterfactual state
105            $value = $this->fileStateByPath[$path][$property];
106            if ( $value instanceof Closure ) {
107                $value = $value();
108                $this->fileStateByPath[$path][$property] = $value;
109            }
110        } else {
111            // File is not predicated to have a counterfactual state; use the current state
112            $value = $curValueFunc( $path );
113        }
114
115        return $value;
116    }
117
118    /**
119     * @param string[] $paths List of storage paths
120     * @return self Clone predicates limited to the given paths
121     */
122    public function snapshot( array $paths ) {
123        $snapshot = new self();
124        foreach ( $paths as $path ) {
125            if ( isset( $this->fileStateByPath[$path] ) ) {
126                $snapshot->fileStateByPath[$path] = $this->fileStateByPath[$path];
127            }
128        }
129
130        return $snapshot;
131    }
132}
133
134/** @deprecated class alias since 1.43 */
135class_alias( FileStatePredicates::class, 'FileStatePredicates' );