MediaWiki master
FileBackendDBRepoWrapper.php
Go to the documentation of this file.
1<?php
21namespace MediaWiki\FileRepo;
22
23use Closure;
24use InvalidArgumentException;
25use MapCacheLRU;
29use Shellbox\Command\BoxedCommand;
30use StatusValue;
33
50 protected $backend;
52 protected $repoName;
54 protected $dbHandleFunc;
58 protected $dbs;
59 private int $migrationStage;
60
61 public function __construct( array $config ) {
63 $backend = $config['backend'];
64 $config['name'] = $backend->getName();
65 $config['domainId'] = $backend->getDomainId();
66 parent::__construct( $config );
67 $this->backend = $config['backend'];
68 $this->repoName = $config['repoName'];
69 $this->dbHandleFunc = $config['dbHandleFactory'];
70 $this->resolvedPathCache = new MapCacheLRU( 100 );
71 $this->migrationStage = MediaWikiServices::getInstance()->getMainConfig()->get(
73 );
74 }
75
81 public function getInternalBackend() {
82 return $this->backend;
83 }
84
95 public function getBackendPath( $path, $latest = true ) {
96 $paths = $this->getBackendPaths( [ $path ], $latest );
97 return current( $paths );
98 }
99
110 public function getBackendPaths( array $paths, $latest = true ) {
111 $db = $this->getDB( $latest ? DB_PRIMARY : DB_REPLICA );
112
113 // @TODO: batching
114 $resolved = [];
115 foreach ( $paths as $i => $path ) {
116 if ( !$latest && $this->resolvedPathCache->hasField( $path, 'target', 10 ) ) {
117 $resolved[$i] = $this->resolvedPathCache->getField( $path, 'target' );
118 continue;
119 }
120
121 [ , $container ] = FileBackend::splitStoragePath( $path );
122
123 if ( $container === "{$this->repoName}-public" ) {
124 $name = basename( $path );
125 if ( $this->migrationStage & SCHEMA_COMPAT_READ_OLD ) {
126 if ( str_contains( $path, '!' ) ) {
127 $sha1 = $db->newSelectQueryBuilder()
128 ->select( 'oi_sha1' )
129 ->from( 'oldimage' )
130 ->where( [ 'oi_archive_name' => $name ] )
131 ->caller( __METHOD__ )->fetchField();
132 } else {
133 $sha1 = $db->newSelectQueryBuilder()
134 ->select( 'img_sha1' )
135 ->from( 'image' )
136 ->where( [ 'img_name' => $name ] )
137 ->caller( __METHOD__ )->fetchField();
138 }
139 } else {
140 if ( str_contains( $path, '!' ) ) {
141 $sha1 = $db->newSelectQueryBuilder()
142 ->select( 'fr_sha1' )
143 ->from( 'filerevision' )
144 ->where( [ 'fr_archive_name' => $name ] )
145 ->caller( __METHOD__ )->fetchField();
146 } else {
147 $sha1 = $db->newSelectQueryBuilder()
148 ->select( 'fr_sha1' )
149 ->from( 'file' )
150 ->join( 'filerevision', null, 'file_latest = fr_id' )
151 ->where( [ 'file_name' => $name ] )
152 ->caller( __METHOD__ )->fetchField();
153 }
154 }
155
156 if ( !is_string( $sha1 ) || $sha1 === '' ) {
157 $resolved[$i] = $path; // give up
158 continue;
159 }
160 $resolved[$i] = $this->getPathForSHA1( $sha1 );
161 $this->resolvedPathCache->setField( $path, 'target', $resolved[$i] );
162 } elseif ( $container === "{$this->repoName}-deleted" ) {
163 $name = basename( $path ); // <hash>.<ext>
164 $sha1 = substr( $name, 0, strpos( $name, '.' ) ); // ignore extension
165 $resolved[$i] = $this->getPathForSHA1( $sha1 );
166 $this->resolvedPathCache->setField( $path, 'target', $resolved[$i] );
167 } else {
168 $resolved[$i] = $path;
169 }
170 }
171
172 $res = [];
173 foreach ( $paths as $i => $path ) {
174 $res[$i] = $resolved[$i];
175 }
176
177 return $res;
178 }
179
180 protected function doOperationsInternal( array $ops, array $opts ) {
181 return $this->backend->doOperationsInternal( $this->mungeOpPaths( $ops ), $opts );
182 }
183
184 protected function doQuickOperationsInternal( array $ops, array $opts ) {
185 return $this->backend->doQuickOperationsInternal( $this->mungeOpPaths( $ops ), $opts );
186 }
187
188 protected function doPrepare( array $params ) {
189 return $this->backend->doPrepare( $params );
190 }
191
192 protected function doSecure( array $params ) {
193 return $this->backend->doSecure( $params );
194 }
195
196 protected function doPublish( array $params ) {
197 return $this->backend->doPublish( $params );
198 }
199
200 protected function doClean( array $params ) {
201 return $this->backend->doClean( $params );
202 }
203
204 public function concatenate( array $params ) {
205 return $this->translateSrcParams( __FUNCTION__, $params );
206 }
207
208 public function fileExists( array $params ) {
209 return $this->translateSrcParams( __FUNCTION__, $params );
210 }
211
212 public function getFileTimestamp( array $params ) {
213 return $this->translateSrcParams( __FUNCTION__, $params );
214 }
215
216 public function getFileSize( array $params ) {
217 return $this->translateSrcParams( __FUNCTION__, $params );
218 }
219
220 public function getFileStat( array $params ) {
221 return $this->translateSrcParams( __FUNCTION__, $params );
222 }
223
224 public function getFileXAttributes( array $params ) {
225 return $this->translateSrcParams( __FUNCTION__, $params );
226 }
227
228 public function getFileSha1Base36( array $params ) {
229 return $this->translateSrcParams( __FUNCTION__, $params );
230 }
231
232 public function getFileProps( array $params ) {
233 return $this->translateSrcParams( __FUNCTION__, $params );
234 }
235
236 public function streamFile( array $params ) {
237 // The stream methods use the file extension to determine the
238 // Content-Type (as MediaWiki should already validate it on upload).
239 // The translated SHA1 path has no extension, so this needs to use
240 // the untranslated path extension.
241 $type = StreamFile::contentTypeFromPath( $params['src'] );
242 if ( $type && $type != 'unknown/unknown' ) {
243 $params['headers'][] = "Content-type: $type";
244 }
245 return $this->translateSrcParams( __FUNCTION__, $params );
246 }
247
248 public function getFileContentsMulti( array $params ) {
249 return $this->translateArrayResults( __FUNCTION__, $params );
250 }
251
252 public function getLocalReferenceMulti( array $params ) {
253 return $this->translateArrayResults( __FUNCTION__, $params );
254 }
255
256 public function getLocalCopyMulti( array $params ) {
257 return $this->translateArrayResults( __FUNCTION__, $params );
258 }
259
260 public function getFileHttpUrl( array $params ) {
261 return $this->translateSrcParams( __FUNCTION__, $params );
262 }
263
264 public function addShellboxInputFile( BoxedCommand $command, string $boxedName,
265 array $params
266 ) {
267 $params['src'] = $this->getBackendPath( $params['src'], !empty( $params['latest'] ) );
268 return $this->backend->addShellboxInputFile( $command, $boxedName, $params );
269 }
270
271 public function directoryExists( array $params ) {
272 return $this->backend->directoryExists( $params );
273 }
274
275 public function getDirectoryList( array $params ) {
276 return $this->backend->getDirectoryList( $params );
277 }
278
279 public function getFileList( array $params ) {
280 return $this->backend->getFileList( $params );
281 }
282
283 public function getFeatures() {
284 return $this->backend->getFeatures();
285 }
286
287 public function clearCache( ?array $paths = null ) {
288 $this->backend->clearCache( null ); // clear all
289 }
290
291 public function preloadCache( array $paths ) {
292 $paths = $this->getBackendPaths( $paths );
293 $this->backend->preloadCache( $paths );
294 }
295
296 public function preloadFileStat( array $params ) {
297 return $this->translateSrcParams( __FUNCTION__, $params );
298 }
299
300 public function getScopedLocksForOps( array $ops, StatusValue $status ) {
301 return $this->backend->getScopedLocksForOps( $ops, $status );
302 }
303
312 public function getPathForSHA1( $sha1 ) {
313 if ( strlen( $sha1 ) < 3 ) {
314 throw new InvalidArgumentException( "Invalid file SHA-1." );
315 }
316 return $this->backend->getContainerStoragePath( "{$this->repoName}-original" ) .
317 "/{$sha1[0]}/{$sha1[1]}/{$sha1[2]}/{$sha1}";
318 }
319
326 protected function getDB( $index ) {
327 if ( !isset( $this->dbs[$index] ) ) {
328 $func = $this->dbHandleFunc;
329 $this->dbs[$index] = $func( $index );
330 }
331 return $this->dbs[$index];
332 }
333
341 protected function translateSrcParams( $function, array $params ) {
342 $latest = !empty( $params['latest'] );
343
344 if ( isset( $params['src'] ) ) {
345 $params['src'] = $this->getBackendPath( $params['src'], $latest );
346 }
347
348 if ( isset( $params['srcs'] ) ) {
349 $params['srcs'] = $this->getBackendPaths( $params['srcs'], $latest );
350 }
351
352 return $this->backend->$function( $params );
353 }
354
362 protected function translateArrayResults( $function, array $params ) {
363 $origPaths = $params['srcs'];
364 $params['srcs'] = $this->getBackendPaths( $params['srcs'], !empty( $params['latest'] ) );
365 $pathMap = array_combine( $params['srcs'], $origPaths );
366
367 $results = $this->backend->$function( $params );
368
369 $contents = [];
370 foreach ( $results as $path => $result ) {
371 $contents[$pathMap[$path]] = $result;
372 }
373
374 return $contents;
375 }
376
385 protected function mungeOpPaths( array $ops ) {
386 // Ops that use 'src' and do not mutate core file data there
387 static $srcRefOps = [ 'store', 'copy', 'describe' ];
388 foreach ( $ops as &$op ) {
389 if ( isset( $op['src'] ) && in_array( $op['op'], $srcRefOps ) ) {
390 $op['src'] = $this->getBackendPath( $op['src'], true );
391 }
392 if ( isset( $op['srcs'] ) ) {
393 $op['srcs'] = $this->getBackendPaths( $op['srcs'], true );
394 }
395 }
396 return $ops;
397 }
398}
399
401class_alias( FileBackendDBRepoWrapper::class, 'FileBackendDBRepoWrapper' );
const SCHEMA_COMPAT_READ_OLD
Definition Defines.php:304
array $params
The job parameters.
Store key-value entries in a size-limited in-memory LRU cache.
Proxy backend that manages file layout rewriting for FileRepo.
preloadFileStat(array $params)
Preload file stat information (concurrently if possible) into in-process cache.
getFileStat(array $params)
Get quick information about a file at a storage path in the backend.
getDB( $index)
Get a connection to the repo file registry DB.
getLocalReferenceMulti(array $params)
Like getLocalReference() except it takes an array of storage paths and yields an order-preserved map ...
getFileTimestamp(array $params)
Get the last-modified timestamp of the file at a storage path.
getInternalBackend()
Get the underlying FileBackend that is being wrapped.
getFileSha1Base36(array $params)
Get a SHA-1 hash of the content of the file at a storage path in the backend.
getBackendPath( $path, $latest=true)
Translate a legacy "title" path to its "sha1" counterpart.
streamFile(array $params)
Stream the content of the file at a storage path in the backend.
getDirectoryList(array $params)
Get an iterator to list all directories under a storage directory.
directoryExists(array $params)
Check if a directory exists at a given storage path.
clearCache(?array $paths=null)
Invalidate any in-process file stat and property cache.
getFileSize(array $params)
Get the size (bytes) of a file at a storage path in the backend.
getLocalCopyMulti(array $params)
Like getLocalCopy() except it takes an array of storage paths and yields an order preserved-map of st...
translateSrcParams( $function, array $params)
Translates paths found in the "src" or "srcs" keys of a params array.
getPathForSHA1( $sha1)
Get the ultimate original storage path for a file.
fileExists(array $params)
Check if a file exists at a storage path in the backend.
getBackendPaths(array $paths, $latest=true)
Translate legacy "title" paths to their "sha1" counterparts.
getFeatures()
Get the a bitfield of extra features supported by the backend medium.
getFileContentsMulti(array $params)
Like getFileContents() except it takes an array of storage paths and returns an order preserved map o...
__construct(array $config)
Create a new backend instance from configuration.
addShellboxInputFile(BoxedCommand $command, string $boxedName, array $params)
Add a file to a Shellbox command as an input file.
translateArrayResults( $function, array $params)
Translates paths when the backend function returns results keyed by paths.
concatenate(array $params)
Concatenate a list of storage files into a single file system file.
mungeOpPaths(array $ops)
Translate legacy "title" source paths to their "sha1" counterparts.
getFileList(array $params)
Get an iterator to list all stored files under a storage directory.
preloadCache(array $paths)
Preload persistent file stat cache and property cache into in-process cache.
getScopedLocksForOps(array $ops, StatusValue $status)
Get an array of scoped locks needed for a batch of file operations.
getFileProps(array $params)
Get the properties of the content of the file at a storage path in the backend.
getFileHttpUrl(array $params)
Return an HTTP URL to a given file that requires no authentication to use.
getFileXAttributes(array $params)
Get metadata about a file at a storage path in the backend.
A class containing constants representing the names of configuration variables.
const FileSchemaMigrationStage
Name constant for the FileSchemaMigrationStage setting, for use with Config::get()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
Functions related to the output of file content.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Base class for all file backend classes (including multi-write backends).
string $name
Unique backend name.
static splitStoragePath( $storagePath)
Split a storage path into a backend name, a container name, and a relative file path.
getDomainId()
Get the domain identifier used for this backend (possibly empty).
getName()
Get the unique backend name.
Interface to a relational database.
Definition IDatabase.php:45
const DB_REPLICA
Definition defines.php:26
const DB_PRIMARY
Definition defines.php:28