Merge "filebackend: improve internal use of FileBackend constants"
[lhc/web/wiklou.git] / includes / libs / filebackend / MemoryFileBackend.php
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
24 use Wikimedia\AtEase\AtEase;
25
26 /**
27 * Simulation of a backend storage in memory.
28 *
29 * All data in the backend is automatically deleted at the end of PHP execution.
30 * Since the data stored here is volatile, this is only useful for staging or testing.
31 *
32 * @ingroup FileBackend
33 * @since 1.23
34 */
35 class MemoryFileBackend extends FileBackendStore {
36 /** @var array Map of (file path => (data,mtime) */
37 protected $files = [];
38
39 public function getFeatures() {
40 return self::ATTR_UNICODE_PATHS;
41 }
42
43 public function isPathUsableInternal( $storagePath ) {
44 return ( $this->resolveHashKey( $storagePath ) !== null );
45 }
46
47 protected function doCreateInternal( array $params ) {
48 $status = $this->newStatus();
49
50 $dst = $this->resolveHashKey( $params['dst'] );
51 if ( $dst === null ) {
52 $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
53
54 return $status;
55 }
56
57 $this->files[$dst] = [
58 'data' => $params['content'],
59 'mtime' => wfTimestamp( TS_MW, time() )
60 ];
61
62 return $status;
63 }
64
65 protected function doStoreInternal( array $params ) {
66 $status = $this->newStatus();
67
68 $dst = $this->resolveHashKey( $params['dst'] );
69 if ( $dst === null ) {
70 $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
71
72 return $status;
73 }
74
75 AtEase::suppressWarnings();
76 $data = file_get_contents( $params['src'] );
77 AtEase::restoreWarnings();
78 if ( $data === false ) { // source doesn't exist?
79 $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
80
81 return $status;
82 }
83
84 $this->files[$dst] = [
85 'data' => $data,
86 'mtime' => wfTimestamp( TS_MW, time() )
87 ];
88
89 return $status;
90 }
91
92 protected function doCopyInternal( array $params ) {
93 $status = $this->newStatus();
94
95 $src = $this->resolveHashKey( $params['src'] );
96 if ( $src === null ) {
97 $status->fatal( 'backend-fail-invalidpath', $params['src'] );
98
99 return $status;
100 }
101
102 $dst = $this->resolveHashKey( $params['dst'] );
103 if ( $dst === null ) {
104 $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
105
106 return $status;
107 }
108
109 if ( !isset( $this->files[$src] ) ) {
110 if ( empty( $params['ignoreMissingSource'] ) ) {
111 $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
112 }
113
114 return $status;
115 }
116
117 $this->files[$dst] = [
118 'data' => $this->files[$src]['data'],
119 'mtime' => wfTimestamp( TS_MW, time() )
120 ];
121
122 return $status;
123 }
124
125 protected function doDeleteInternal( array $params ) {
126 $status = $this->newStatus();
127
128 $src = $this->resolveHashKey( $params['src'] );
129 if ( $src === null ) {
130 $status->fatal( 'backend-fail-invalidpath', $params['src'] );
131
132 return $status;
133 }
134
135 if ( !isset( $this->files[$src] ) ) {
136 if ( empty( $params['ignoreMissingSource'] ) ) {
137 $status->fatal( 'backend-fail-delete', $params['src'] );
138 }
139
140 return $status;
141 }
142
143 unset( $this->files[$src] );
144
145 return $status;
146 }
147
148 protected function doGetFileStat( array $params ) {
149 $src = $this->resolveHashKey( $params['src'] );
150 if ( $src === null ) {
151 return self::$RES_ERROR; // invalid path
152 }
153
154 if ( isset( $this->files[$src] ) ) {
155 return [
156 'mtime' => $this->files[$src]['mtime'],
157 'size' => strlen( $this->files[$src]['data'] ),
158 ];
159 }
160
161 return self::$RES_ABSENT;
162 }
163
164 protected function doGetLocalCopyMulti( array $params ) {
165 $tmpFiles = []; // (path => TempFSFile)
166 foreach ( $params['srcs'] as $srcPath ) {
167 $src = $this->resolveHashKey( $srcPath );
168 if ( $src === null ) {
169 $fsFile = self::$RES_ERROR;
170 } elseif ( !isset( $this->files[$src] ) ) {
171 $fsFile = self::$RES_ABSENT;
172 } else {
173 // Create a new temporary file with the same extension...
174 $ext = FileBackend::extensionFromPath( $src );
175 $fsFile = $this->tmpFileFactory->newTempFSFile( 'localcopy_', $ext );
176 if ( $fsFile ) {
177 $bytes = file_put_contents( $fsFile->getPath(), $this->files[$src]['data'] );
178 if ( $bytes !== strlen( $this->files[$src]['data'] ) ) {
179 $fsFile = self::$RES_ERROR;
180 }
181 }
182 }
183 $tmpFiles[$srcPath] = $fsFile;
184 }
185
186 return $tmpFiles;
187 }
188
189 protected function doDirectoryExists( $container, $dir, array $params ) {
190 $prefix = rtrim( "$container/$dir", '/' ) . '/';
191 foreach ( $this->files as $path => $data ) {
192 if ( strpos( $path, $prefix ) === 0 ) {
193 return true;
194 }
195 }
196
197 return false;
198 }
199
200 public function getDirectoryListInternal( $container, $dir, array $params ) {
201 $dirs = [];
202 $prefix = rtrim( "$container/$dir", '/' ) . '/';
203 $prefixLen = strlen( $prefix );
204 foreach ( $this->files as $path => $data ) {
205 if ( strpos( $path, $prefix ) === 0 ) {
206 $relPath = substr( $path, $prefixLen );
207 if ( $relPath === false ) {
208 continue;
209 } elseif ( strpos( $relPath, '/' ) === false ) {
210 continue; // just a file
211 }
212 $parts = array_slice( explode( '/', $relPath ), 0, -1 ); // last part is file name
213 if ( !empty( $params['topOnly'] ) ) {
214 $dirs[$parts[0]] = 1; // top directory
215 } else {
216 $current = '';
217 foreach ( $parts as $part ) { // all directories
218 $dir = ( $current === '' ) ? $part : "$current/$part";
219 $dirs[$dir] = 1;
220 $current = $dir;
221 }
222 }
223 }
224 }
225
226 return array_keys( $dirs );
227 }
228
229 public function getFileListInternal( $container, $dir, array $params ) {
230 $files = [];
231 $prefix = rtrim( "$container/$dir", '/' ) . '/';
232 $prefixLen = strlen( $prefix );
233 foreach ( $this->files as $path => $data ) {
234 if ( strpos( $path, $prefix ) === 0 ) {
235 $relPath = substr( $path, $prefixLen );
236 if ( $relPath === false ) {
237 continue;
238 } elseif ( !empty( $params['topOnly'] ) && strpos( $relPath, '/' ) !== false ) {
239 continue;
240 }
241 $files[] = $relPath;
242 }
243 }
244
245 return $files;
246 }
247
248 protected function directoriesAreVirtual() {
249 return true;
250 }
251
252 /**
253 * Get the absolute file system path for a storage path
254 *
255 * @param string $storagePath Storage path
256 * @return string|null
257 */
258 protected function resolveHashKey( $storagePath ) {
259 list( $fullCont, $relPath ) = $this->resolveStoragePathReal( $storagePath );
260 if ( $relPath === null ) {
261 return null; // invalid
262 }
263
264 return ( $relPath !== '' ) ? "$fullCont/$relPath" : $fullCont;
265 }
266 }