MediaWiki master
MemoryFileBackend.php
Go to the documentation of this file.
1<?php
10namespace Wikimedia\FileBackend;
11
12use Wikimedia\AtEase\AtEase;
13use Wikimedia\Timestamp\ConvertibleTimestamp;
14use Wikimedia\Timestamp\TimestampFormat as TS;
15
27 protected $files = [];
28
30 public function getFeatures() {
32 }
33
35 public function isPathUsableInternal( $storagePath ) {
36 return ( $this->resolveHashKey( $storagePath ) !== null );
37 }
38
40 protected function doCreateInternal( array $params ) {
41 $status = $this->newStatus();
42
43 $dst = $this->resolveHashKey( $params['dst'] );
44 if ( $dst === null ) {
45 $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
46
47 return $status;
48 }
49
50 $this->files[$dst] = [
51 'data' => $params['content'],
52 'mtime' => ConvertibleTimestamp::convert( TS::MW, time() )
53 ];
54
55 return $status;
56 }
57
59 protected function doStoreInternal( array $params ) {
60 $status = $this->newStatus();
61
62 $dst = $this->resolveHashKey( $params['dst'] );
63 if ( $dst === null ) {
64 $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
65
66 return $status;
67 }
68
69 AtEase::suppressWarnings();
70 $data = file_get_contents( $params['src'] );
71 AtEase::restoreWarnings();
72 if ( $data === false ) { // source doesn't exist?
73 $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
74
75 return $status;
76 }
77
78 $this->files[$dst] = [
79 'data' => $data,
80 'mtime' => ConvertibleTimestamp::convert( TS::MW, time() )
81 ];
82
83 return $status;
84 }
85
87 protected function doCopyInternal( array $params ) {
88 return $this->copyInMemory( $params, 'copy' );
89 }
90
92 protected function doMoveInternal( array $params ) {
93 return $this->copyInMemory( $params, 'move' );
94 }
95
101 private function copyInMemory( array $params, string $action ) {
102 $status = $this->newStatus();
103
104 $src = $this->resolveHashKey( $params['src'] );
105 if ( $src === null ) {
106 $status->fatal( 'backend-fail-invalidpath', $params['src'] );
107
108 return $status;
109 }
110
111 $dst = $this->resolveHashKey( $params['dst'] );
112 if ( $dst === null ) {
113 $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
114
115 return $status;
116 }
117
118 if ( !isset( $this->files[$src] ) ) {
119 if ( empty( $params['ignoreMissingSource'] ) ) {
120 // Error codes: backend-fail-copy, backend-fail-move
121 $status->fatal( 'backend-fail-' . $action, $params['src'], $params['dst'] );
122 }
123
124 return $status;
125 }
126
127 $this->files[$dst] = [
128 'data' => $this->files[$src]['data'],
129 'mtime' => ConvertibleTimestamp::convert( TS::MW, time() )
130 ];
131
132 if ( $action === 'move' ) {
133 unset( $this->files[$src] );
134 }
135 return $status;
136 }
137
139 protected function doDeleteInternal( array $params ) {
140 $status = $this->newStatus();
141
142 $src = $this->resolveHashKey( $params['src'] );
143 if ( $src === null ) {
144 $status->fatal( 'backend-fail-invalidpath', $params['src'] );
145
146 return $status;
147 }
148
149 if ( !isset( $this->files[$src] ) ) {
150 if ( empty( $params['ignoreMissingSource'] ) ) {
151 $status->fatal( 'backend-fail-delete', $params['src'] );
152 }
153
154 return $status;
155 }
156
157 unset( $this->files[$src] );
158
159 return $status;
160 }
161
163 protected function doGetFileStat( array $params ) {
164 $src = $this->resolveHashKey( $params['src'] );
165 if ( $src === null ) {
166 return self::RES_ERROR; // invalid path
167 }
168
169 if ( isset( $this->files[$src] ) ) {
170 return [
171 'mtime' => $this->files[$src]['mtime'],
172 'size' => strlen( $this->files[$src]['data'] ),
173 ];
174 }
175
176 return self::RES_ABSENT;
177 }
178
180 protected function doGetLocalCopyMulti( array $params ) {
181 $tmpFiles = []; // (path => TempFSFile)
182 foreach ( $params['srcs'] as $srcPath ) {
183 $src = $this->resolveHashKey( $srcPath );
184 if ( $src === null ) {
185 $fsFile = self::RES_ERROR;
186 } elseif ( !isset( $this->files[$src] ) ) {
187 $fsFile = self::RES_ABSENT;
188 } else {
189 // Create a new temporary file with the same extension...
190 $ext = FileBackend::extensionFromPath( $src );
191 $fsFile = $this->tmpFileFactory->newTempFSFile( 'localcopy_', $ext );
192 if ( $fsFile ) {
193 $bytes = file_put_contents( $fsFile->getPath(), $this->files[$src]['data'] );
194 if ( $bytes !== strlen( $this->files[$src]['data'] ) ) {
195 $fsFile = self::RES_ERROR;
196 }
197 }
198 }
199 $tmpFiles[$srcPath] = $fsFile;
200 }
201
202 return $tmpFiles;
203 }
204
206 protected function doDirectoryExists( $fullCont, $dirRel, array $params ) {
207 $prefix = rtrim( "$fullCont/$dirRel", '/' ) . '/';
208 foreach ( $this->files as $path => $data ) {
209 if ( str_starts_with( $path, $prefix ) ) {
210 return true;
211 }
212 }
213
214 return false;
215 }
216
218 public function getDirectoryListInternal( $fullCont, $dirRel, array $params ) {
219 $dirs = [];
220 $prefix = rtrim( "$fullCont/$dirRel", '/' ) . '/';
221 $prefixLen = strlen( $prefix );
222 foreach ( $this->files as $path => $data ) {
223 if ( str_starts_with( $path, $prefix ) ) {
224 $relPath = substr( $path, $prefixLen );
225 if ( !str_contains( $relPath, '/' ) ) {
226 continue; // just a file
227 }
228 $parts = array_slice( explode( '/', $relPath ), 0, -1 ); // last part is file name
229 if ( !empty( $params['topOnly'] ) ) {
230 $dirs[$parts[0]] = 1; // top directory
231 } else {
232 $current = '';
233 foreach ( $parts as $part ) { // all directories
234 $dirRel = ( $current === '' ) ? $part : "$current/$part";
235 $dirs[$dirRel] = 1;
236 $current = $dirRel;
237 }
238 }
239 }
240 }
241
242 return array_keys( $dirs );
243 }
244
246 public function getFileListInternal( $fullCont, $dirRel, array $params ) {
247 $files = [];
248 $prefix = rtrim( "$fullCont/$dirRel", '/' ) . '/';
249 $prefixLen = strlen( $prefix );
250 foreach ( $this->files as $path => $data ) {
251 if ( str_starts_with( $path, $prefix ) ) {
252 $relPath = substr( $path, $prefixLen );
253 if (
254 $relPath === '' ||
255 ( !empty( $params['topOnly'] ) && str_contains( $relPath, '/' ) )
256 ) {
257 continue;
258 }
259 $files[] = $relPath;
260 }
261 }
262
263 return $files;
264 }
265
267 protected function directoriesAreVirtual() {
268 return true;
269 }
270
277 protected function resolveHashKey( $storagePath ) {
278 [ $fullCont, $relPath ] = $this->resolveStoragePathReal( $storagePath );
279 if ( $relPath === null ) {
280 return null; // invalid
281 }
282
283 return ( $relPath !== '' ) ? "$fullCont/$relPath" : $fullCont;
284 }
285}
286
288class_alias( MemoryFileBackend::class, 'MemoryFileBackend' );
Base class for all backends using particular storage medium.
resolveStoragePathReal( $storagePath)
Like resolveStoragePath() except null values are returned if the container is sharded and the shard c...
static extensionFromPath( $path, $case='lowercase')
Get the final extension from a storage or FS path.
newStatus( $message=null,... $params)
Yields the result of the status wrapper callback on either:
Simulation of a backend storage in memory.
directoriesAreVirtual()
Is this a key/value store where directories are just virtual? Virtual directories exists in so much a...
doMoveInternal(array $params)
FileBackendStore::moveInternal() StatusValue
doCopyInternal(array $params)
FileBackendStore::copyInternal() StatusValue
array $files
Map of (file path => (data,mtime)
getFeatures()
Get the a bitfield of extra features supported by the backend medium.to overrideint Bitfield of FileB...
doCreateInternal(array $params)
FileBackendStore::createInternal() StatusValue
isPathUsableInternal( $storagePath)
Check if a file can be created or changed at a given storage path in the backend.FS backends should c...
doDeleteInternal(array $params)
FileBackendStore::deleteInternal() StatusValue
doGetFileStat(array $params)
FileBackendStore::getFileStat() array|false|null
doDirectoryExists( $fullCont, $dirRel, array $params)
FileBackendStore::directoryExists()bool|null
doStoreInternal(array $params)
FileBackendStore::storeInternal() StatusValue
resolveHashKey( $storagePath)
Get the absolute file system path for a storage path.
getFileListInternal( $fullCont, $dirRel, array $params)
Do not call this function from places outside FileBackend.FileBackendStore::getFileList()Traversable|...
doGetLocalCopyMulti(array $params)
FileBackendStore::getLocalCopyMulti() string[]|bool[]|null[] Map of (path => TempFSFile,...
getDirectoryListInternal( $fullCont, $dirRel, array $params)
Do not call this function from places outside FileBackend.FileBackendStore::getDirectoryList()Travers...