Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
83.57% covered (warning)
83.57%
117 / 140
42.86% covered (danger)
42.86%
6 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
MemoryFileBackend
84.17% covered (warning)
84.17%
117 / 139
42.86% covered (danger)
42.86%
6 / 14
61.31
0.00% covered (danger)
0.00%
0 / 1
 getFeatures
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isPathUsableInternal
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 doCreateInternal
80.00% covered (warning)
80.00%
8 / 10
0.00% covered (danger)
0.00%
0 / 1
2.03
 doStoreInternal
75.00% covered (warning)
75.00%
12 / 16
0.00% covered (danger)
0.00%
0 / 1
3.14
 doCopyInternal
72.22% covered (warning)
72.22%
13 / 18
0.00% covered (danger)
0.00%
0 / 1
5.54
 doMoveInternal
70.59% covered (warning)
70.59%
12 / 17
0.00% covered (danger)
0.00%
0 / 1
5.64
 doDeleteInternal
72.73% covered (warning)
72.73%
8 / 11
0.00% covered (danger)
0.00%
0 / 1
4.32
 doGetFileStat
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 doGetLocalCopyMulti
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
0.00%
0 / 1
6.01
 doDirectoryExists
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getDirectoryListInternal
94.74% covered (success)
94.74%
18 / 19
0.00% covered (danger)
0.00%
0 / 1
8.01
 getFileListInternal
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
6.02
 directoriesAreVirtual
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 resolveHashKey
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2/**
3 * Simulation of a backend storage in memory.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 * @ingroup FileBackend
22 */
23
24namespace Wikimedia\FileBackend;
25
26use Wikimedia\AtEase\AtEase;
27use Wikimedia\Timestamp\ConvertibleTimestamp;
28
29/**
30 * Simulation of a backend storage in memory.
31 *
32 * All data in the backend is automatically deleted at the end of PHP execution.
33 * Since the data stored here is volatile, this is only useful for staging or testing.
34 *
35 * @ingroup FileBackend
36 * @since 1.23
37 */
38class MemoryFileBackend extends FileBackendStore {
39    /** @var array Map of (file path => (data,mtime) */
40    protected $files = [];
41
42    public function getFeatures() {
43        return self::ATTR_UNICODE_PATHS;
44    }
45
46    public function isPathUsableInternal( $storagePath ) {
47        return ( $this->resolveHashKey( $storagePath ) !== null );
48    }
49
50    protected function doCreateInternal( array $params ) {
51        $status = $this->newStatus();
52
53        $dst = $this->resolveHashKey( $params['dst'] );
54        if ( $dst === null ) {
55            $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
56
57            return $status;
58        }
59
60        $this->files[$dst] = [
61            'data' => $params['content'],
62            'mtime' => ConvertibleTimestamp::convert( TS_MW, time() )
63        ];
64
65        return $status;
66    }
67
68    protected function doStoreInternal( array $params ) {
69        $status = $this->newStatus();
70
71        $dst = $this->resolveHashKey( $params['dst'] );
72        if ( $dst === null ) {
73            $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
74
75            return $status;
76        }
77
78        AtEase::suppressWarnings();
79        $data = file_get_contents( $params['src'] );
80        AtEase::restoreWarnings();
81        if ( $data === false ) { // source doesn't exist?
82            $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
83
84            return $status;
85        }
86
87        $this->files[$dst] = [
88            'data' => $data,
89            'mtime' => ConvertibleTimestamp::convert( TS_MW, time() )
90        ];
91
92        return $status;
93    }
94
95    protected function doCopyInternal( array $params ) {
96        $status = $this->newStatus();
97
98        $src = $this->resolveHashKey( $params['src'] );
99        if ( $src === null ) {
100            $status->fatal( 'backend-fail-invalidpath', $params['src'] );
101
102            return $status;
103        }
104
105        $dst = $this->resolveHashKey( $params['dst'] );
106        if ( $dst === null ) {
107            $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
108
109            return $status;
110        }
111
112        if ( !isset( $this->files[$src] ) ) {
113            if ( empty( $params['ignoreMissingSource'] ) ) {
114                $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
115            }
116
117            return $status;
118        }
119
120        $this->files[$dst] = [
121            'data' => $this->files[$src]['data'],
122            'mtime' => ConvertibleTimestamp::convert( TS_MW, time() )
123        ];
124
125        return $status;
126    }
127
128    protected function doMoveInternal( array $params ) {
129        $status = $this->newStatus();
130
131        $src = $this->resolveHashKey( $params['src'] );
132        if ( $src === null ) {
133            $status->fatal( 'backend-fail-invalidpath', $params['src'] );
134
135            return $status;
136        }
137
138        $dst = $this->resolveHashKey( $params['dst'] );
139        if ( $dst === null ) {
140            $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
141
142            return $status;
143        }
144
145        if ( !isset( $this->files[$src] ) ) {
146            if ( empty( $params['ignoreMissingSource'] ) ) {
147                $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
148            }
149
150            return $status;
151        }
152
153        $this->files[$dst] = $this->files[$src];
154        unset( $this->files[$src] );
155        $this->files[$dst]['mtime'] = ConvertibleTimestamp::convert( TS_MW, time() );
156
157        return $status;
158    }
159
160    protected function doDeleteInternal( array $params ) {
161        $status = $this->newStatus();
162
163        $src = $this->resolveHashKey( $params['src'] );
164        if ( $src === null ) {
165            $status->fatal( 'backend-fail-invalidpath', $params['src'] );
166
167            return $status;
168        }
169
170        if ( !isset( $this->files[$src] ) ) {
171            if ( empty( $params['ignoreMissingSource'] ) ) {
172                $status->fatal( 'backend-fail-delete', $params['src'] );
173            }
174
175            return $status;
176        }
177
178        unset( $this->files[$src] );
179
180        return $status;
181    }
182
183    protected function doGetFileStat( array $params ) {
184        $src = $this->resolveHashKey( $params['src'] );
185        if ( $src === null ) {
186            return self::RES_ERROR; // invalid path
187        }
188
189        if ( isset( $this->files[$src] ) ) {
190            return [
191                'mtime' => $this->files[$src]['mtime'],
192                'size' => strlen( $this->files[$src]['data'] ),
193            ];
194        }
195
196        return self::RES_ABSENT;
197    }
198
199    protected function doGetLocalCopyMulti( array $params ) {
200        $tmpFiles = []; // (path => TempFSFile)
201        foreach ( $params['srcs'] as $srcPath ) {
202            $src = $this->resolveHashKey( $srcPath );
203            if ( $src === null ) {
204                $fsFile = self::RES_ERROR;
205            } elseif ( !isset( $this->files[$src] ) ) {
206                $fsFile = self::RES_ABSENT;
207            } else {
208                // Create a new temporary file with the same extension...
209                $ext = FileBackend::extensionFromPath( $src );
210                $fsFile = $this->tmpFileFactory->newTempFSFile( 'localcopy_', $ext );
211                if ( $fsFile ) {
212                    $bytes = file_put_contents( $fsFile->getPath(), $this->files[$src]['data'] );
213                    if ( $bytes !== strlen( $this->files[$src]['data'] ) ) {
214                        $fsFile = self::RES_ERROR;
215                    }
216                }
217            }
218            $tmpFiles[$srcPath] = $fsFile;
219        }
220
221        return $tmpFiles;
222    }
223
224    protected function doDirectoryExists( $container, $dir, array $params ) {
225        $prefix = rtrim( "$container/$dir", '/' ) . '/';
226        foreach ( $this->files as $path => $data ) {
227            if ( strpos( $path, $prefix ) === 0 ) {
228                return true;
229            }
230        }
231
232        return false;
233    }
234
235    public function getDirectoryListInternal( $container, $dir, array $params ) {
236        $dirs = [];
237        $prefix = rtrim( "$container/$dir", '/' ) . '/';
238        $prefixLen = strlen( $prefix );
239        foreach ( $this->files as $path => $data ) {
240            if ( strpos( $path, $prefix ) === 0 ) {
241                $relPath = substr( $path, $prefixLen );
242                if ( $relPath === false ) {
243                    continue;
244                } elseif ( strpos( $relPath, '/' ) === false ) {
245                    continue; // just a file
246                }
247                $parts = array_slice( explode( '/', $relPath ), 0, -1 ); // last part is file name
248                if ( !empty( $params['topOnly'] ) ) {
249                    $dirs[$parts[0]] = 1; // top directory
250                } else {
251                    $current = '';
252                    foreach ( $parts as $part ) { // all directories
253                        $dir = ( $current === '' ) ? $part : "$current/$part";
254                        $dirs[$dir] = 1;
255                        $current = $dir;
256                    }
257                }
258            }
259        }
260
261        return array_keys( $dirs );
262    }
263
264    public function getFileListInternal( $container, $dir, array $params ) {
265        $files = [];
266        $prefix = rtrim( "$container/$dir", '/' ) . '/';
267        $prefixLen = strlen( $prefix );
268        foreach ( $this->files as $path => $data ) {
269            if ( strpos( $path, $prefix ) === 0 ) {
270                $relPath = substr( $path, $prefixLen );
271                if ( $relPath === false ) {
272                    continue;
273                } elseif ( !empty( $params['topOnly'] ) && strpos( $relPath, '/' ) !== false ) {
274                    continue;
275                }
276                $files[] = $relPath;
277            }
278        }
279
280        return $files;
281    }
282
283    protected function directoriesAreVirtual() {
284        return true;
285    }
286
287    /**
288     * Get the absolute file system path for a storage path
289     *
290     * @param string $storagePath
291     * @return string|null
292     */
293    protected function resolveHashKey( $storagePath ) {
294        [ $fullCont, $relPath ] = $this->resolveStoragePathReal( $storagePath );
295        if ( $relPath === null ) {
296            return null; // invalid
297        }
298
299        return ( $relPath !== '' ) ? "$fullCont/$relPath" : $fullCont;
300    }
301}
302
303/** @deprecated class alias since 1.43 */
304class_alias( MemoryFileBackend::class, 'MemoryFileBackend' );