88 parent::__construct( $info );
90 $this->dbDomain = WikiMap::getCurrentWikiDbDomain();
91 $this->hasAccessibleSharedCache =
true;
93 $this->
hasSha1Storage = ( $info[
'storageLayout'] ?? null ) ===
'sha1';
94 $this->dbProvider = MediaWikiServices::getInstance()->getConnectionProvider();
98 'backend' => $this->backend,
99 'repoName' => $this->name,
108 'splitMetadataThreshold',
109 'updateCompatibleMetadata',
110 'reserializeMetadata',
113 if ( isset( $info[$option] ) ) {
114 $this->$option = $info[$option];
124 if ( isset( $row->img_name ) ) {
125 return call_user_func( $this->fileFromRowFactory, $row, $this );
126 } elseif ( isset( $row->oi_name ) ) {
127 return call_user_func( $this->oldFileFromRowFactory, $row, $this );
129 throw new InvalidArgumentException( __METHOD__ .
': invalid row' );
139 $title = File::normalizeTitle( $title );
140 return OldLocalFile::newFromArchiveName( $title, $this, $archiveName );
155 wfDebug( __METHOD__ .
": skipped because storage uses sha1 paths" );
156 return Status::newGood();
163 $storageKeys = array_unique( $storageKeys );
164 foreach ( $storageKeys as $key ) {
166 $path =
"$root/$hashPath$key";
167 $dbw->startAtomic( __METHOD__ );
172 if ( !$deleted && !$hidden ) {
173 wfDebug( __METHOD__ .
": deleting $key" );
174 $op = [
'op' =>
'delete',
'src' =>
$path ];
176 $status->error(
'undelete-cleanup-error',
$path );
177 $status->failCount++;
180 wfDebug( __METHOD__ .
": $key still in use" );
181 $status->successCount++;
183 $dbw->endAtomic( __METHOD__ );
197 $queryBuilder = $this->
getPrimaryDB()->newSelectQueryBuilder()
199 ->from(
'filearchive' )
200 ->where( [
'fa_storage_group' =>
'deleted',
'fa_storage_key' => $key ] );
201 if ( $lock ===
'lock' ) {
202 $queryBuilder->forUpdate();
204 return (
bool)$queryBuilder->caller( __METHOD__ )->fetchField();
215 $sha1 = self::getHashFromKey( $key );
216 $ext = File::normalizeExtension( substr( $key, strcspn( $key,
'.' ) + 1 ) );
219 $queryBuilder = $dbw->newSelectQueryBuilder()
224 $dbw->expr(
'oi_archive_name', IExpression::LIKE,
new LikeValue( $dbw->anyString(),
".$ext" ) ),
225 $dbw->bitAnd(
'oi_deleted', File::DELETED_FILE ) => File::DELETED_FILE,
227 if ( $lock ===
'lock' ) {
228 $queryBuilder->forUpdate();
231 return (
bool)$queryBuilder->caller( __METHOD__ )->fetchField();
241 $sha1 = strtok( $key,
'.' );
242 if ( is_string( $sha1 ) && strlen( $sha1 ) === 32 && $sha1[0] ===
'0' ) {
243 $sha1 = substr( $sha1, 1 );
255 $title = File::normalizeTitle( $title,
'exception' );
257 $memcKey = $this->
getSharedCacheKey(
'file-redirect', md5( $title->getDBkey() ) );
258 if ( $memcKey ===
false ) {
259 $memcKey = $this->
getLocalCacheKey(
'file-redirect', md5( $title->getDBkey() ) );
265 $method = __METHOD__;
266 $redirDbKey = $this->wanCache->getWithSetCallback(
269 function ( $oldValue, &$ttl, array &$setOpts ) use ( $method, $title ) {
272 $setOpts += Database::getCacheSetOptions( $dbr );
274 $row = $dbr->newSelectQueryBuilder()
275 ->select( [
'rd_namespace',
'rd_title' ] )
277 ->join(
'redirect',
null,
'rd_from = page_id' )
278 ->where( [
'page_namespace' => $title->getNamespace(),
'page_title' => $title->getDBkey() ] )
279 ->caller( $method )->fetchRow();
281 return ( $row && $row->rd_namespace ==
NS_FILE )
282 ? Title::makeTitle( $row->rd_namespace, $row->rd_title )->getDBkey()
285 [
'pcTTL' => WANObjectCache::TTL_PROC_LONG ]
289 if ( $redirDbKey !==
' ' && strval( $redirDbKey ) !==
'' ) {
291 return Title::newFromText( $redirDbKey,
NS_FILE );
301 foreach ( $items as $item ) {
302 if ( is_array( $item ) ) {
303 $title = File::normalizeTitle( $item[
'title'] );
305 $searchSet[$title->getDBkey()] = $item;
308 $title = File::normalizeTitle( $item );
310 $searchSet[$title->getDBkey()] = [];
315 $fileMatchesSearch =
static function (
File $file, array $search ) {
321 $contextPerformer = RequestContext::getMain()->getAuthority();
322 $performer = ( !empty( $search[
'private'] ) && $search[
'private'] instanceof
Authority )
329 ( empty( $search[
'time'] ) && !$file->
isOld() ) ||
330 ( !empty( $search[
'time'] ) && $search[
'time'] === $file->
getTimestamp() )
332 ( !empty( $search[
'private'] ) || !$file->
isDeleted( File::DELETED_FILE ) ) &&
333 $file->
userCan( File::DELETED_FILE, $performer )
337 $applyMatchingFiles =
function (
IResultWrapper $res, &$searchSet, &$finalFiles )
338 use ( $fileMatchesSearch, $flags )
340 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
342 foreach ( $res as $row ) {
346 $dbKeysLook = [ strtr( $file->
getName(),
' ',
'_' ) ];
347 if ( !empty( $info[
'initialCapital'] ) ) {
349 $dbKeysLook[] = $contLang->lcfirst( $file->
getName() );
351 foreach ( $dbKeysLook as $dbKey ) {
352 if ( isset( $searchSet[$dbKey] )
353 && $fileMatchesSearch( $file, $searchSet[$dbKey] )
356 ? [
'title' => $dbKey,
'timestamp' => $file->
getTimestamp() ]
358 unset( $searchSet[$dbKey] );
368 foreach ( $searchSet as $dbKey => $_ ) {
372 if ( count( $imgNames ) ) {
373 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
374 $res = $queryBuilder->where( [
'img_name' => $imgNames ] )->caller( __METHOD__ )->fetchResultSet();
375 $applyMatchingFiles( $res, $searchSet, $finalFiles );
380 foreach ( $searchSet as $dbKey => $search ) {
381 if ( isset( $search[
'time'] ) ) {
383 ->expr(
'oi_name',
'=', $this->
getNameFromTitle( File::normalizeTitle( $dbKey ) ) )
384 ->and(
'oi_timestamp',
'=', $dbr->timestamp( $search[
'time'] ) );
388 if ( count( $oiConds ) ) {
389 $queryBuilder = FileSelectQueryBuilder::newForOldFile( $dbr );
391 $res = $queryBuilder->where( $dbr->orExpr( $oiConds ) )
392 ->caller( __METHOD__ )->fetchResultSet();
393 $applyMatchingFiles( $res, $searchSet, $finalFiles );
397 foreach ( $searchSet as $dbKey => $search ) {
398 if ( !empty( $search[
'ignoreRedirect'] ) ) {
402 $title = File::normalizeTitle( $dbKey );
405 if ( $redir && $redir->getNamespace() ===
NS_FILE ) {
406 $file = $this->
newFile( $redir );
407 if ( $file && $fileMatchesSearch( $file, $search ) ) {
410 $finalFiles[$dbKey] = [
411 'title' => $file->
getTitle()->getDBkey(),
415 $finalFiles[$dbKey] = $file;
432 $queryBuilder = FileSelectQueryBuilder::newForFile( $this->
getReplicaDB() );
433 $res = $queryBuilder->where( [
'img_sha1' => $hash ] )
434 ->orderBy(
'img_name' )
435 ->caller( __METHOD__ )->fetchResultSet();
438 foreach ( $res as $row ) {
456 if ( $hashes === [] ) {
461 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
463 $queryBuilder->where( [
'img_sha1' => $hashes ] )
464 ->orderBy(
'img_name' );
465 $res = $queryBuilder->caller( __METHOD__ )->fetchResultSet();
468 foreach ( $res as $row ) {
470 $result[$file->getSha1()][] = $file;
486 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
489 ->where( $dbr->expr(
'img_name', IExpression::LIKE,
new LikeValue( $prefix, $dbr->anyString() ) ) )
490 ->orderBy(
'img_name' )
491 ->limit( intval( $limit ) );
492 $res = $queryBuilder->caller( __METHOD__ )->fetchResultSet();
496 foreach ( $res as $row ) {
508 return $this->dbProvider->getReplicaDatabase();
517 return $this->dbProvider->getPrimaryDatabase();
526 return static function ( $index ) {
528 return MediaWikiServices::getInstance()->getConnectionProvider()->getPrimaryDatabase();
530 return MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
542 return $this->hasAccessibleSharedCache;
548 ? $this->wanCache->makeGlobalKey(
549 'filerepo-' . $kClassSuffix,
566 function () use ( $key ) {
567 $this->wanCache->delete( $key );
574 public function store( $srcPath, $dstZone, $dstRel, $flags = 0 ) {
600 public function delete( $srcRel, $archiveRel ) {
619 wfDebug( __METHOD__ .
": skipped because storage uses sha1 paths" );
620 return Status::newGood();
622 return parent::$function( ...$args );
636 return $this->useJsonMetadata;
656 return $this->splitMetadataThreshold;
660 return $this->updateCompatibleMetadata;
664 return $this->reserializeMetadata;
674 if ( !$this->blobStore ) {
675 $this->blobStore = MediaWikiServices::getInstance()->getBlobStoreFactory()
676 ->newBlobStore( $this->dbDomain );
678 return $this->blobStore;
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
if(!defined('MW_SETUP_CALLBACK'))
Proxy backend that manages file layout rewriting for FileRepo.
Base class for file repositories.
assertWritableRepo()
Throw an exception if this repo is read-only by design.
newGood( $value=null)
Create a new good result.
getLocalCacheKey( $kClassSuffix,... $components)
Get a site-local, repository-qualified, WAN cache key.
hasSha1Storage()
Returns whether or not storage is SHA-1 based.
getZonePath( $zone)
Get the storage path corresponding to one of the zones.
getDeletedHashPath( $key)
Get a relative path for a deletion archive key, e.g.
getNameFromTitle( $title)
Get the name of a file from its title.
newFile( $title, $time=false)
Create a new File object from the local repository.
getInfo()
Return information about the repository.
Implements some public methods and some protected utility functions which are required by multiple ch...
getTimestamp()
Get the 14-character timestamp of the file upload.
getName()
Return the name of this file.
exists()
Returns true if file exists in the repository.
isOld()
Returns true if the image is an old version STUB.
getTitle()
Return the associated title object.
redirectedFrom(string $from)
isDeleted( $field)
Is this file a "deleted" file in a private archive? STUB.
userCan( $field, Authority $performer)
Determine if the current user is allowed to view a particular field of this file, if it's marked as d...
Local repository that stores files in the local filesystem and registers them in the wiki's own datab...
skipWriteOperationIfSha1( $function, array $args)
Skips the write operation if storage is sha1-based, executes it normally otherwise.
int null $splitMetadataThreshold
getDBFactory()
Get a callback to get a DB handle given an index (DB_REPLICA/DB_PRIMARY)
getSharedCacheKey( $kClassSuffix,... $components)
Get a global, repository-qualified, WAN cache key.
isMetadataUpdateEnabled()
isSplitMetadataEnabled()
Returns true if files should split up large metadata, storing parts of it in the BlobStore.
deletedFileHasKey( $key, $lock=null)
Check if a deleted (filearchive) file has this sha1 key.
callable $oldFileFactoryKey
isJsonMetadataEnabled()
Returns true if files should store metadata in JSON format.
cleanupBatch(array $files, $flags=0)
Deletes a batch of files.
publishBatch(array $ntuples, $flags=0)
Publish a batch of files.
findFiles(array $items, $flags=0)
Find many files at once.
findFilesByPrefix( $prefix, $limit)
Return an array of files where the name starts with $prefix.
findBySha1s(array $hashes)
Get an array of arrays or iterators of file objects for files that have the given SHA-1 content hashe...
getBlobStore()
Get a BlobStore for storing and retrieving large metadata, or null if that can't be done.
IConnectionProvider $dbProvider
callable $oldFileFromRowFactory
string $dbDomain
DB domain of the repo wiki.
invalidateImageRedirect( $title)
Invalidates image redirect cache related to that image.
cleanupDeletedBatch(array $storageKeys)
Delete files in the deleted directory if they are not referenced in the filearchive table.
bool $updateCompatibleMetadata
getPrimaryDB()
Get a connection to the primary DB.
__construct(?array $info=null)
checkRedirect( $title)
Checks if there is a redirect named as $title.
hasAcessibleSharedCache()
Check whether the repo has a shared cache, accessible from the current site context.
bool $hasAccessibleSharedCache
Whether shared cache keys are exposed/accessible.
getReplicaDB()
Get a connection to the replica DB.
store( $srcPath, $dstZone, $dstRel, $flags=0)
Store a file to a given destination.
publish( $src, $dstRel, $archiveRel, $flags=0, array $options=[])
Copy or move a file either from a storage path, virtual URL, or file system path, into this repositor...
storeBatch(array $triplets, $flags=0)
Store a batch of files.
getSplitMetadataThreshold()
Get the threshold above which metadata items should be split into separate storage,...
callable $fileFromRowFactory
deleteBatch(array $sourceDestPairs)
Move a group of files to the deletion archive.
hiddenFileHasKey( $key, $lock=null)
Check if a hidden (revision delete) file has this sha1 key.
static getHashFromKey( $key)
Gets the SHA1 hash from a storage key.
newFromArchiveName( $title, $archiveName)
bool $reserializeMetadata
isMetadataReserializeEnabled()
findBySha1( $hash)
Get an array or iterator of file objects for files that have a given SHA-1 content hash.
Group all the pieces relevant to the context of a request into one instance.
Interface for objects (potentially) representing an editable wiki page.