MediaWiki master
RepoGroup.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\FileRepo;
8
9use InvalidArgumentException;
14use MWFileProps;
17use Wikimedia\Mime\MimeAnalyzer;
19use Wikimedia\Timestamp\TimestampFormat as TS;
20
30class RepoGroup {
32 protected $localRepo;
33
35 protected $foreignRepos;
36
38 protected $wanCache;
39
41 protected $reposInitialised = false;
42
47 protected $localInfo;
48
50 protected $foreignInfo;
51
53 protected $cache;
54
56 private const MAX_CACHE_SIZE = 500;
57
59 private $mimeAnalyzer;
60
74 public function __construct(
78 MimeAnalyzer $mimeAnalyzer
79 ) {
80 $this->localInfo = $localInfo;
81 $this->foreignInfo = $foreignInfo;
82 $this->cache = new MapCacheLRU( self::MAX_CACHE_SIZE );
83 $this->wanCache = $wanCache;
84 $this->mimeAnalyzer = $mimeAnalyzer;
85 }
86
105 public function findFile( $title, $options = [] ) {
106 if ( !is_array( $options ) ) {
107 // MW 1.15 compat
108 $options = [ 'time' => $options ];
109 }
110 if ( isset( $options['bypassCache'] ) ) {
111 $options['latest'] = $options['bypassCache']; // b/c
112 }
113 if ( isset( $options['time'] ) && $options['time'] !== false ) {
114 $options['time'] = wfTimestamp( TS::MW, $options['time'] );
115 } else {
116 $options['time'] = false;
117 }
118
119 if ( !$this->reposInitialised ) {
120 $this->initialiseRepos();
121 }
122
123 $title = File::normalizeTitle( $title );
124 if ( !$title ) {
125 return false;
126 }
127
128 # Check the cache
129 $dbkey = $title->getDBkey();
130 $timeKey = is_string( $options['time'] ) ? $options['time'] : '';
131 if ( empty( $options['ignoreRedirect'] )
132 && empty( $options['private'] )
133 && empty( $options['latest'] )
134 ) {
135 if ( $this->cache->hasField( $dbkey, $timeKey, 60 ) ) {
136 return $this->cache->getField( $dbkey, $timeKey );
137 }
138 $useCache = true;
139 } else {
140 $useCache = false;
141 }
142
143 # Check the local repo
144 $image = $this->localRepo->findFile( $title, $options );
145
146 # Check the foreign repos
147 if ( !$image ) {
148 foreach ( $this->foreignRepos as $repo ) {
149 $image = $repo->findFile( $title, $options );
150 if ( $image ) {
151 break;
152 }
153 }
154 }
155
156 $image = $image instanceof File ? $image : false; // type check
157 # Cache file existence or non-existence
158 if ( $useCache && ( !$image || $image->isCacheable() ) ) {
159 $this->cache->setField( $dbkey, $timeKey, $image );
160 }
161
162 return $image;
163 }
164
182 public function findFiles( array $inputItems, $flags = 0 ) {
183 if ( !$this->reposInitialised ) {
184 $this->initialiseRepos();
185 }
186
187 $items = [];
188 foreach ( $inputItems as $item ) {
189 if ( !is_array( $item ) ) {
190 $item = [ 'title' => $item ];
191 }
192 $item['title'] = File::normalizeTitle( $item['title'] );
193 if ( $item['title'] ) {
194 $items[$item['title']->getDBkey()] = $item;
195 }
196 }
197
198 $images = $this->localRepo->findFiles( $items, $flags );
199
200 foreach ( $this->foreignRepos as $repo ) {
201 // Remove found files from $items
202 $items = array_diff_key( $items, $images );
203 $images = array_merge( $images, $repo->findFiles( $items, $flags ) );
204 }
205
206 return $images;
207 }
208
214 public function checkRedirect( $title ) {
215 if ( !$this->reposInitialised ) {
216 $this->initialiseRepos();
217 }
218
219 $title = File::normalizeTitle( $title );
220
221 $redir = $this->localRepo->checkRedirect( $title );
222 if ( $redir ) {
223 return $redir;
224 }
225
226 foreach ( $this->foreignRepos as $repo ) {
227 $redir = $repo->checkRedirect( $title );
228 if ( $redir ) {
229 return $redir;
230 }
231 }
232
233 return false;
234 }
235
244 public function findFileFromKey( $hash, $options = [] ) {
245 if ( !$this->reposInitialised ) {
246 $this->initialiseRepos();
247 }
248
249 $file = $this->localRepo->findFileFromKey( $hash, $options );
250 if ( !$file ) {
251 foreach ( $this->foreignRepos as $repo ) {
252 $file = $repo->findFileFromKey( $hash, $options );
253 if ( $file ) {
254 break;
255 }
256 }
257 }
258
259 return $file;
260 }
261
268 public function findBySha1( $hash ) {
269 if ( !$this->reposInitialised ) {
270 $this->initialiseRepos();
271 }
272
273 $result = $this->localRepo->findBySha1( $hash );
274 foreach ( $this->foreignRepos as $repo ) {
275 $result = array_merge( $result, $repo->findBySha1( $hash ) );
276 }
277 usort( $result, File::compare( ... ) );
278
279 return $result;
280 }
281
288 public function findBySha1s( array $hashes ) {
289 if ( !$this->reposInitialised ) {
290 $this->initialiseRepos();
291 }
292
293 $result = $this->localRepo->findBySha1s( $hashes );
294 foreach ( $this->foreignRepos as $repo ) {
295 $result = array_merge_recursive( $result, $repo->findBySha1s( $hashes ) );
296 }
297 // sort the merged (and presorted) sublist of each hash
298 foreach ( $result as $hash => $files ) {
299 usort( $result[$hash], File::compare( ... ) );
300 }
301
302 return $result;
303 }
304
310 public function getRepo( $index ) {
311 if ( !$this->reposInitialised ) {
312 $this->initialiseRepos();
313 }
314 if ( $index === 'local' ) {
315 return $this->localRepo;
316 }
317 return $this->foreignRepos[$index] ?? false;
318 }
319
325 public function getRepoByName( $name ) {
326 if ( !$this->reposInitialised ) {
327 $this->initialiseRepos();
328 }
329 foreach ( $this->foreignRepos as $repo ) {
330 if ( $repo->name == $name ) {
331 return $repo;
332 }
333 }
334
335 return false;
336 }
337
344 public function getLocalRepo() {
345 // @phan-suppress-next-line PhanTypeMismatchReturnSuperType
346 return $this->getRepo( 'local' );
347 }
348
357 public function forEachForeignRepo( $callback, $params = [] ) {
358 if ( !$this->reposInitialised ) {
359 $this->initialiseRepos();
360 }
361 foreach ( $this->foreignRepos as $repo ) {
362 if ( $callback( $repo, ...$params ) ) {
363 return true;
364 }
365 }
366
367 return false;
368 }
369
374 public function hasForeignRepos() {
375 if ( !$this->reposInitialised ) {
376 $this->initialiseRepos();
377 }
378 return (bool)$this->foreignRepos;
379 }
380
384 public function initialiseRepos() {
385 if ( $this->reposInitialised ) {
386 return;
387 }
388 $this->reposInitialised = true;
389
390 $this->localRepo = $this->newRepo( $this->localInfo );
391 $this->foreignRepos = [];
392 foreach ( $this->foreignInfo as $key => $info ) {
393 $this->foreignRepos[$key] = $this->newRepo( $info );
394 }
395 }
396
403 public function newCustomLocalRepo( $info = [] ) {
404 return $this->newRepo( $info + $this->localInfo );
405 }
406
415 protected function newRepo( $info ) {
416 $class = $info['class'];
417
418 $info['wanCache'] = $this->wanCache;
419
420 return new $class( $info );
421 }
422
428 private function splitVirtualUrl( $url ) {
429 if ( !str_starts_with( $url, 'mwrepo://' ) ) {
430 throw new InvalidArgumentException( __METHOD__ . ': unknown protocol' );
431 }
432
433 $bits = explode( '/', substr( $url, 9 ), 3 );
434 if ( count( $bits ) != 3 ) {
435 throw new InvalidArgumentException( __METHOD__ . ": invalid mwrepo URL: $url" );
436 }
437
438 return $bits;
439 }
440
445 public function getFileProps( $fileName ) {
446 if ( FileRepo::isVirtualUrl( $fileName ) ) {
447 [ $repoName, /* $zone */, /* $rel */ ] = $this->splitVirtualUrl( $fileName );
448 if ( $repoName === '' ) {
449 $repoName = 'local';
450 }
451 $repo = $this->getRepo( $repoName );
452
453 return $repo->getFileProps( $fileName );
454 } else {
455 $mwProps = new MWFileProps( $this->mimeAnalyzer );
456
457 return $mwProps->getPropsFromPath( $fileName, true );
458 }
459 }
460
465 public function clearCache( $title = null ) {
466 if ( $title == null ) {
467 $this->cache->clear();
468 } elseif ( is_string( $title ) ) {
469 $this->cache->clear( $title );
470 } else {
471 $this->cache->clear( $title->getDBkey() );
472 }
473 }
474}
475
477class_alias( RepoGroup::class, 'RepoGroup' );
wfTimestamp( $outputtype=TS::UNIX, $ts=0)
Get a timestamp string in one of various formats.
MimeMagic helper wrapper.
Base class for file repositories.
Definition FileRepo.php:52
static isVirtualUrl( $url)
Determine if a string is an mwrepo:// URL.
Definition FileRepo.php:288
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition File.php:79
Local repository that stores files in the local filesystem and registers them in the wiki's own datab...
Definition LocalRepo.php:45
Prioritized list of file repositories.
Definition RepoGroup.php:30
getRepo( $index)
Get the repo instance with a given key.
forEachForeignRepo( $callback, $params=[])
Call a function for each foreign repo, with the repo object as the first parameter.
clearCache( $title=null)
Clear RepoGroup process cache used for finding a file.
findBySha1s(array $hashes)
Find all instances of files with this keys.
newRepo( $info)
Create a repo class based on an info structure @template T of FileRepo.
findFiles(array $inputItems, $flags=0)
Search repositories for many files at once.
checkRedirect( $title)
Interface for FileRepo::checkRedirect()
findFileFromKey( $hash, $options=[])
Find an instance of the file with this key, created at the specified time Returns false if the file d...
newCustomLocalRepo( $info=[])
Create a local repo with the specified option overrides.
findFile( $title, $options=[])
Search repositories for an image.
initialiseRepos()
Initialise the $repos array.
hasForeignRepos()
Does the installation have any foreign repos set up?
getRepoByName( $name)
Get the repo instance by its name.
getLocalRepo()
Get the local repository, i.e.
findBySha1( $hash)
Find all instances of files with this key.
__construct( $localInfo, $foreignInfo, WANObjectCache $wanCache, MimeAnalyzer $mimeAnalyzer)
Construct a group of file repositories.
Definition RepoGroup.php:74
Represents a title within MediaWiki.
Definition Title.php:69
static compare( $a, $b)
Callback for usort() to do title sorts by (namespace, title)
Definition Title.php:860
Base class for all file backend classes (including multi-write backends).
Store key-value entries in a size-limited in-memory LRU cache.
Multi-datacenter aware caching interface.
Represents the target of a wiki link.
Interface for objects (potentially) representing an editable wiki page.