87 parent::__construct( $info );
89 $this->dbDomain = WikiMap::getCurrentWikiDbDomain();
90 $this->hasAccessibleSharedCache =
true;
92 $this->
hasSha1Storage = ( $info[
'storageLayout'] ?? null ) ===
'sha1';
93 $this->dbProvider = MediaWikiServices::getInstance()->getConnectionProvider();
97 'backend' => $this->backend,
98 'repoName' => $this->name,
107 'splitMetadataThreshold',
108 'updateCompatibleMetadata',
109 'reserializeMetadata',
112 if ( isset( $info[$option] ) ) {
113 $this->$option = $info[$option];
123 if ( isset( $row->img_name ) ) {
124 return call_user_func( $this->fileFromRowFactory, $row, $this );
125 } elseif ( isset( $row->oi_name ) ) {
126 return call_user_func( $this->oldFileFromRowFactory, $row, $this );
128 throw new InvalidArgumentException( __METHOD__ .
': invalid row' );
138 $title = File::normalizeTitle( $title );
139 return OldLocalFile::newFromArchiveName( $title, $this, $archiveName );
154 wfDebug( __METHOD__ .
": skipped because storage uses sha1 paths" );
155 return Status::newGood();
162 $storageKeys = array_unique( $storageKeys );
163 foreach ( $storageKeys as $key ) {
165 $path =
"$root/$hashPath$key";
166 $dbw->startAtomic( __METHOD__ );
171 if ( !$deleted && !$hidden ) {
172 wfDebug( __METHOD__ .
": deleting $key" );
173 $op = [
'op' =>
'delete',
'src' =>
$path ];
175 $status->error(
'undelete-cleanup-error',
$path );
176 $status->failCount++;
179 wfDebug( __METHOD__ .
": $key still in use" );
180 $status->successCount++;
182 $dbw->endAtomic( __METHOD__ );
196 $queryBuilder = $this->
getPrimaryDB()->newSelectQueryBuilder()
198 ->from(
'filearchive' )
199 ->where( [
'fa_storage_group' =>
'deleted',
'fa_storage_key' => $key ] );
200 if ( $lock ===
'lock' ) {
201 $queryBuilder->forUpdate();
203 return (
bool)$queryBuilder->caller( __METHOD__ )->fetchField();
214 $sha1 = self::getHashFromKey( $key );
215 $ext = File::normalizeExtension( substr( $key, strcspn( $key,
'.' ) + 1 ) );
218 $queryBuilder = $dbw->newSelectQueryBuilder()
223 $dbw->expr(
'oi_archive_name', IExpression::LIKE,
new LikeValue( $dbw->anyString(),
".$ext" ) ),
224 $dbw->bitAnd(
'oi_deleted', File::DELETED_FILE ) => File::DELETED_FILE,
226 if ( $lock ===
'lock' ) {
227 $queryBuilder->forUpdate();
230 return (
bool)$queryBuilder->caller( __METHOD__ )->fetchField();
240 $sha1 = strtok( $key,
'.' );
241 if ( is_string( $sha1 ) && strlen( $sha1 ) === 32 && $sha1[0] ===
'0' ) {
242 $sha1 = substr( $sha1, 1 );
254 $title = File::normalizeTitle( $title,
'exception' );
256 $memcKey = $this->
getSharedCacheKey(
'file-redirect', md5( $title->getDBkey() ) );
257 if ( $memcKey ===
false ) {
258 $memcKey = $this->
getLocalCacheKey(
'file-redirect', md5( $title->getDBkey() ) );
264 $method = __METHOD__;
265 $redirDbKey = $this->wanCache->getWithSetCallback(
268 function ( $oldValue, &$ttl, array &$setOpts ) use ( $method, $title ) {
271 $setOpts += Database::getCacheSetOptions( $dbr );
273 $row = $dbr->newSelectQueryBuilder()
274 ->select( [
'rd_namespace',
'rd_title' ] )
276 ->join(
'redirect',
null,
'rd_from = page_id' )
277 ->where( [
'page_namespace' => $title->getNamespace(),
'page_title' => $title->getDBkey() ] )
278 ->caller( $method )->fetchRow();
280 return ( $row && $row->rd_namespace ==
NS_FILE )
281 ? Title::makeTitle( $row->rd_namespace, $row->rd_title )->getDBkey()
284 [
'pcTTL' => WANObjectCache::TTL_PROC_LONG ]
288 if ( $redirDbKey !==
' ' && strval( $redirDbKey ) !==
'' ) {
290 return Title::newFromText( $redirDbKey,
NS_FILE );
300 foreach ( $items as $item ) {
301 if ( is_array( $item ) ) {
302 $title = File::normalizeTitle( $item[
'title'] );
304 $searchSet[$title->getDBkey()] = $item;
307 $title = File::normalizeTitle( $item );
309 $searchSet[$title->getDBkey()] = [];
314 $fileMatchesSearch =
static function (
File $file, array $search ) {
320 $contextPerformer = RequestContext::getMain()->getAuthority();
321 $performer = ( !empty( $search[
'private'] ) && $search[
'private'] instanceof
Authority )
328 ( empty( $search[
'time'] ) && !$file->
isOld() ) ||
329 ( !empty( $search[
'time'] ) && $search[
'time'] === $file->
getTimestamp() )
331 ( !empty( $search[
'private'] ) || !$file->
isDeleted( File::DELETED_FILE ) ) &&
332 $file->
userCan( File::DELETED_FILE, $performer )
336 $applyMatchingFiles =
function (
IResultWrapper $res, &$searchSet, &$finalFiles )
337 use ( $fileMatchesSearch, $flags )
339 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
341 foreach ( $res as $row ) {
345 $dbKeysLook = [ strtr( $file->
getName(),
' ',
'_' ) ];
346 if ( !empty( $info[
'initialCapital'] ) ) {
348 $dbKeysLook[] = $contLang->lcfirst( $file->
getName() );
350 foreach ( $dbKeysLook as $dbKey ) {
351 if ( isset( $searchSet[$dbKey] )
352 && $fileMatchesSearch( $file, $searchSet[$dbKey] )
355 ? [
'title' => $dbKey,
'timestamp' => $file->
getTimestamp() ]
357 unset( $searchSet[$dbKey] );
367 foreach ( $searchSet as $dbKey => $_ ) {
371 if ( count( $imgNames ) ) {
372 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
373 $res = $queryBuilder->where( [
'img_name' => $imgNames ] )->caller( __METHOD__ )->fetchResultSet();
374 $applyMatchingFiles( $res, $searchSet, $finalFiles );
379 foreach ( $searchSet as $dbKey => $search ) {
380 if ( isset( $search[
'time'] ) ) {
382 ->expr(
'oi_name',
'=', $this->
getNameFromTitle( File::normalizeTitle( $dbKey ) ) )
383 ->and(
'oi_timestamp',
'=', $dbr->timestamp( $search[
'time'] ) );
387 if ( count( $oiConds ) ) {
388 $queryBuilder = FileSelectQueryBuilder::newForOldFile( $dbr );
390 $res = $queryBuilder->where( $dbr->orExpr( $oiConds ) )
391 ->caller( __METHOD__ )->fetchResultSet();
392 $applyMatchingFiles( $res, $searchSet, $finalFiles );
396 foreach ( $searchSet as $dbKey => $search ) {
397 if ( !empty( $search[
'ignoreRedirect'] ) ) {
401 $title = File::normalizeTitle( $dbKey );
404 if ( $redir && $redir->getNamespace() ===
NS_FILE ) {
405 $file = $this->
newFile( $redir );
406 if ( $file && $fileMatchesSearch( $file, $search ) ) {
409 $finalFiles[$dbKey] = [
410 'title' => $file->
getTitle()->getDBkey(),
414 $finalFiles[$dbKey] = $file;
431 $queryBuilder = FileSelectQueryBuilder::newForFile( $this->
getReplicaDB() );
432 $res = $queryBuilder->where( [
'img_sha1' => $hash ] )
433 ->orderBy(
'img_name' )
434 ->caller( __METHOD__ )->fetchResultSet();
437 foreach ( $res as $row ) {
455 if ( $hashes === [] ) {
460 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
462 $queryBuilder->where( [
'img_sha1' => $hashes ] )
463 ->orderBy(
'img_name' );
464 $res = $queryBuilder->caller( __METHOD__ )->fetchResultSet();
467 foreach ( $res as $row ) {
469 $result[$file->getSha1()][] = $file;
485 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
488 ->where( $dbr->expr(
'img_name', IExpression::LIKE,
new LikeValue( $prefix, $dbr->anyString() ) ) )
489 ->orderBy(
'img_name' )
490 ->limit( intval( $limit ) );
491 $res = $queryBuilder->caller( __METHOD__ )->fetchResultSet();
495 foreach ( $res as $row ) {
507 return $this->dbProvider->getReplicaDatabase();
516 return $this->dbProvider->getPrimaryDatabase();
525 return static function ( $index ) {
527 return MediaWikiServices::getInstance()->getConnectionProvider()->getPrimaryDatabase();
529 return MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
541 return $this->hasAccessibleSharedCache;
547 ? $this->wanCache->makeGlobalKey(
548 'filerepo-' . $kClassSuffix,
565 function () use ( $key ) {
566 $this->wanCache->delete( $key );
573 public function store( $srcPath, $dstZone, $dstRel, $flags = 0 ) {
599 public function delete( $srcRel, $archiveRel ) {
618 wfDebug( __METHOD__ .
": skipped because storage uses sha1 paths" );
619 return Status::newGood();
621 return parent::$function( ...$args );
635 return $this->useJsonMetadata;
655 return $this->splitMetadataThreshold;
659 return $this->updateCompatibleMetadata;
663 return $this->reserializeMetadata;
673 if ( !$this->blobStore ) {
674 $this->blobStore = MediaWikiServices::getInstance()->getBlobStoreFactory()
675 ->newBlobStore( $this->dbDomain );
677 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.
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
__construct(array $info=null)
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.