MediaWiki master
FileBackendDBRepoWrapper.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\FileRepo;
8
9use Closure;
10use InvalidArgumentException;
14use Shellbox\Command\BoxedCommand;
15use StatusValue;
19
36 protected $backend;
38 protected $repoName;
40 protected $dbHandleFunc;
44 protected $dbs;
45 private int $migrationStage;
46
47 public function __construct( array $config ) {
49 $backend = $config['backend'];
50 $config['name'] = $backend->getName();
51 $config['domainId'] = $backend->getDomainId();
52 parent::__construct( $config );
53 $this->backend = $config['backend'];
54 $this->repoName = $config['repoName'];
55 $this->dbHandleFunc = $config['dbHandleFactory'];
56 $this->resolvedPathCache = new MapCacheLRU( 100 );
57 $this->migrationStage = MediaWikiServices::getInstance()->getMainConfig()->get(
59 );
60 }
61
67 public function getInternalBackend() {
68 return $this->backend;
69 }
70
81 public function getBackendPath( $path, $latest = true ) {
82 $paths = $this->getBackendPaths( [ $path ], $latest );
83 return current( $paths );
84 }
85
96 public function getBackendPaths( array $paths, $latest = true ) {
97 $db = $this->getDB( $latest ? DB_PRIMARY : DB_REPLICA );
98
99 // @TODO: batching
100 $resolved = [];
101 foreach ( $paths as $i => $path ) {
102 if ( !$latest && $this->resolvedPathCache->hasField( $path, 'target', 10 ) ) {
103 $resolved[$i] = $this->resolvedPathCache->getField( $path, 'target' );
104 continue;
105 }
106
107 [ , $container ] = FileBackend::splitStoragePath( $path );
108
109 if ( $container === "{$this->repoName}-public" ) {
110 $name = basename( $path );
111 if ( $this->migrationStage & SCHEMA_COMPAT_READ_OLD ) {
112 if ( str_contains( $path, '!' ) ) {
113 $sha1 = $db->newSelectQueryBuilder()
114 ->select( 'oi_sha1' )
115 ->from( 'oldimage' )
116 ->where( [ 'oi_archive_name' => $name ] )
117 ->caller( __METHOD__ )->fetchField();
118 } else {
119 $sha1 = $db->newSelectQueryBuilder()
120 ->select( 'img_sha1' )
121 ->from( 'image' )
122 ->where( [ 'img_name' => $name ] )
123 ->caller( __METHOD__ )->fetchField();
124 }
125 } else {
126 if ( str_contains( $path, '!' ) ) {
127 $sha1 = $db->newSelectQueryBuilder()
128 ->select( 'fr_sha1' )
129 ->from( 'filerevision' )
130 ->where( [ 'fr_archive_name' => $name ] )
131 ->caller( __METHOD__ )->fetchField();
132 } else {
133 $sha1 = $db->newSelectQueryBuilder()
134 ->select( 'fr_sha1' )
135 ->from( 'file' )
136 ->join( 'filerevision', null, 'file_latest = fr_id' )
137 ->where( [ 'file_name' => $name ] )
138 ->caller( __METHOD__ )->fetchField();
139 }
140 }
141
142 if ( !is_string( $sha1 ) || $sha1 === '' ) {
143 $resolved[$i] = $path; // give up
144 continue;
145 }
146 $resolved[$i] = $this->getPathForSHA1( $sha1 );
147 $this->resolvedPathCache->setField( $path, 'target', $resolved[$i] );
148 } elseif ( $container === "{$this->repoName}-deleted" ) {
149 $name = basename( $path ); // <hash>.<ext>
150 $sha1 = substr( $name, 0, strpos( $name, '.' ) ); // ignore extension
151 $resolved[$i] = $this->getPathForSHA1( $sha1 );
152 $this->resolvedPathCache->setField( $path, 'target', $resolved[$i] );
153 } else {
154 $resolved[$i] = $path;
155 }
156 }
157
158 $res = [];
159 foreach ( $paths as $i => $path ) {
160 $res[$i] = $resolved[$i];
161 }
162
163 return $res;
164 }
165
167 protected function doOperationsInternal( array $ops, array $opts ) {
168 return $this->backend->doOperationsInternal( $this->mungeOpPaths( $ops ), $opts );
169 }
170
172 protected function doQuickOperationsInternal( array $ops, array $opts ) {
173 return $this->backend->doQuickOperationsInternal( $this->mungeOpPaths( $ops ), $opts );
174 }
175
177 protected function doPrepare( array $params ) {
178 return $this->backend->doPrepare( $params );
179 }
180
182 protected function doSecure( array $params ) {
183 return $this->backend->doSecure( $params );
184 }
185
187 protected function doPublish( array $params ) {
188 return $this->backend->doPublish( $params );
189 }
190
192 protected function doClean( array $params ) {
193 return $this->backend->doClean( $params );
194 }
195
197 public function concatenate( array $params ) {
198 return $this->translateSrcParams( __FUNCTION__, $params );
199 }
200
202 public function fileExists( array $params ) {
203 return $this->translateSrcParams( __FUNCTION__, $params );
204 }
205
207 public function getFileTimestamp( array $params ) {
208 return $this->translateSrcParams( __FUNCTION__, $params );
209 }
210
212 public function getFileSize( array $params ) {
213 return $this->translateSrcParams( __FUNCTION__, $params );
214 }
215
217 public function getFileStat( array $params ) {
218 return $this->translateSrcParams( __FUNCTION__, $params );
219 }
220
222 public function getFileXAttributes( array $params ) {
223 return $this->translateSrcParams( __FUNCTION__, $params );
224 }
225
227 public function getFileSha1Base36( array $params ) {
228 return $this->translateSrcParams( __FUNCTION__, $params );
229 }
230
232 public function getFileProps( array $params ) {
233 return $this->translateSrcParams( __FUNCTION__, $params );
234 }
235
237 public function streamFile( array $params ) {
238 // The stream methods use the file extension to determine the
239 // Content-Type (as MediaWiki should already validate it on upload).
240 // The translated SHA1 path has no extension, so this needs to use
241 // the untranslated path extension.
242 $type = StreamFile::contentTypeFromPath( $params['src'] );
243 if ( $type && $type != 'unknown/unknown' ) {
244 $params['headers'][] = "Content-type: $type";
245 }
246 return $this->translateSrcParams( __FUNCTION__, $params );
247 }
248
250 public function getFileContentsMulti( array $params ) {
251 return $this->translateArrayResults( __FUNCTION__, $params );
252 }
253
255 public function getLocalReferenceMulti( array $params ) {
256 return $this->translateArrayResults( __FUNCTION__, $params );
257 }
258
260 public function getLocalCopyMulti( array $params ) {
261 return $this->translateArrayResults( __FUNCTION__, $params );
262 }
263
265 public function getFileHttpUrl( array $params ) {
266 return $this->translateSrcParams( __FUNCTION__, $params );
267 }
268
270 public function addShellboxInputFile( BoxedCommand $command, string $boxedName,
271 array $params
272 ) {
273 $params['src'] = $this->getBackendPath( $params['src'], !empty( $params['latest'] ) );
274 return $this->backend->addShellboxInputFile( $command, $boxedName, $params );
275 }
276
278 public function directoryExists( array $params ) {
279 return $this->backend->directoryExists( $params );
280 }
281
283 public function getDirectoryList( array $params ) {
284 return $this->backend->getDirectoryList( $params );
285 }
286
288 public function getFileList( array $params ) {
289 return $this->backend->getFileList( $params );
290 }
291
293 public function getFeatures() {
294 return $this->backend->getFeatures();
295 }
296
297 public function clearCache( ?array $paths = null ) {
298 $this->backend->clearCache( null ); // clear all
299 }
300
301 public function preloadCache( array $paths ) {
302 $paths = $this->getBackendPaths( $paths );
303 $this->backend->preloadCache( $paths );
304 }
305
307 public function preloadFileStat( array $params ) {
308 return $this->translateSrcParams( __FUNCTION__, $params );
309 }
310
312 public function getScopedLocksForOps( array $ops, StatusValue $status ) {
313 return $this->backend->getScopedLocksForOps( $ops, $status );
314 }
315
324 public function getPathForSHA1( $sha1 ) {
325 if ( strlen( $sha1 ) < 3 ) {
326 throw new InvalidArgumentException( "Invalid file SHA-1." );
327 }
328 return $this->backend->getContainerStoragePath( "{$this->repoName}-original" ) .
329 "/{$sha1[0]}/{$sha1[1]}/{$sha1[2]}/{$sha1}";
330 }
331
338 protected function getDB( $index ) {
339 if ( !isset( $this->dbs[$index] ) ) {
340 $func = $this->dbHandleFunc;
341 $this->dbs[$index] = $func( $index );
342 }
343 return $this->dbs[$index];
344 }
345
353 protected function translateSrcParams( $function, array $params ) {
354 $latest = !empty( $params['latest'] );
355
356 if ( isset( $params['src'] ) ) {
357 $params['src'] = $this->getBackendPath( $params['src'], $latest );
358 }
359
360 if ( isset( $params['srcs'] ) ) {
361 $params['srcs'] = $this->getBackendPaths( $params['srcs'], $latest );
362 }
363
364 return $this->backend->$function( $params );
365 }
366
374 protected function translateArrayResults( $function, array $params ) {
375 $origPaths = $params['srcs'];
376 $params['srcs'] = $this->getBackendPaths( $params['srcs'], !empty( $params['latest'] ) );
377 $pathMap = array_combine( $params['srcs'], $origPaths );
378
379 $results = $this->backend->$function( $params );
380
381 $contents = [];
382 foreach ( $results as $path => $result ) {
383 $contents[$pathMap[$path]] = $result;
384 }
385
386 return $contents;
387 }
388
397 protected function mungeOpPaths( array $ops ) {
398 // Ops that use 'src' and do not mutate core file data there
399 static $srcRefOps = [ 'store', 'copy', 'describe' ];
400 foreach ( $ops as &$op ) {
401 if ( isset( $op['src'] ) && in_array( $op['op'], $srcRefOps ) ) {
402 $op['src'] = $this->getBackendPath( $op['src'], true );
403 }
404 if ( isset( $op['srcs'] ) ) {
405 $op['srcs'] = $this->getBackendPaths( $op['srcs'], true );
406 }
407 }
408 return $ops;
409 }
410}
411
413class_alias( FileBackendDBRepoWrapper::class, 'FileBackendDBRepoWrapper' );
const SCHEMA_COMPAT_READ_OLD
Definition Defines.php:294
const DB_REPLICA
Definition defines.php:26
const DB_PRIMARY
Definition defines.php:28
Proxy backend that manages file layout rewriting for FileRepo.
doPublish(array $params)
FileBackend::publish() StatusValue
preloadFileStat(array $params)
Preload file stat information (concurrently if possible) into in-process cache.This should be used wh...
getFileStat(array $params)
Get quick information about a file at a storage path in the backend.If the file does not exist,...
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.FileBackend::TIMESTAMP_FAILstring|false...
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.FileBackend::SHA1_FAILst...
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.If the file does not exists,...
getDirectoryList(array $params)
Get an iterator to list all directories under a storage directory.If the directory is of the form "mw...
directoryExists(array $params)
Check if a directory exists at a given storage path.For backends using key/value stores,...
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.FileBackend::SIZE_FAILint|false File ...
doQuickOperationsInternal(array $ops, array $opts)
FileBackend::doQuickOperations() StatusValue 1.20
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.This returns false if only a directory exists...
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.to overrideint Bitfield of FileB...
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.StatusValue 1.43
doPrepare(array $params)
FileBackend::prepare() StatusValue Good status without value for success, fatal otherwise.
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.The target path should refer to a ...
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.If the directory is of the form "m...
doSecure(array $params)
FileBackend::secure() StatusValue
preloadCache(array $paths)
Preload persistent file stat cache and property cache into in-process cache.
doOperationsInternal(array $ops, array $opts)
FileBackend::doOperations() StatusValue
getScopedLocksForOps(array $ops, StatusValue $status)
Get an array of scoped locks needed for a batch of file operations.Normally, FileBackend::doOperation...
getFileProps(array $params)
Get the properties of the content of the file at a storage path in the backend.This gives the result ...
getFileHttpUrl(array $params)
Return an HTTP URL to a given file that requires no authentication to use.The URL may be pre-authenti...
getFileXAttributes(array $params)
Get metadata about a file at a storage path in the backend.If the file does not exist,...
doClean(array $params)
FileBackend::clean() StatusValue
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.
Store key-value entries in a size-limited in-memory LRU cache.
Interface to a relational database.
Definition IDatabase.php:31