84 parent::__construct( $info );
86 $this->dbDomain = WikiMap::getCurrentWikiDbDomain();
87 $this->hasAccessibleSharedCache =
true;
89 $this->
hasSha1Storage = ( $info[
'storageLayout'] ?? null ) ===
'sha1';
90 $this->dbProvider = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
94 'backend' => $this->backend,
95 'repoName' => $this->name,
104 'splitMetadataThreshold',
105 'updateCompatibleMetadata',
106 'reserializeMetadata',
109 if ( isset( $info[$option] ) ) {
110 $this->$option = $info[$option];
121 if ( isset( $row->img_name ) ) {
122 return call_user_func( $this->fileFromRowFactory, $row, $this );
123 } elseif ( isset( $row->oi_name ) ) {
124 return call_user_func( $this->oldFileFromRowFactory, $row, $this );
126 throw new MWException( __METHOD__ .
': invalid row' );
136 $title = File::normalizeTitle( $title );
137 return OldLocalFile::newFromArchiveName( $title, $this, $archiveName );
152 wfDebug( __METHOD__ .
": skipped because storage uses sha1 paths" );
153 return Status::newGood();
160 $storageKeys = array_unique( $storageKeys );
161 foreach ( $storageKeys as $key ) {
163 $path =
"$root/$hashPath$key";
164 $dbw->startAtomic( __METHOD__ );
169 if ( !$deleted && !$hidden ) {
170 wfDebug( __METHOD__ .
": deleting $key" );
171 $op = [
'op' =>
'delete',
'src' =>
$path ];
173 $status->error(
'undelete-cleanup-error',
$path );
174 $status->failCount++;
177 wfDebug( __METHOD__ .
": $key still in use" );
178 $status->successCount++;
180 $dbw->endAtomic( __METHOD__ );
194 $queryBuilder = $this->
getPrimaryDB()->newSelectQueryBuilder()
196 ->from(
'filearchive' )
197 ->where( [
'fa_storage_group' =>
'deleted',
'fa_storage_key' => $key ] );
198 if ( $lock ===
'lock' ) {
199 $queryBuilder->forUpdate();
201 return (
bool)$queryBuilder->caller( __METHOD__ )->fetchField();
212 $sha1 = self::getHashFromKey( $key );
213 $ext = File::normalizeExtension( substr( $key, strcspn( $key,
'.' ) + 1 ) );
216 $queryBuilder = $dbw->newSelectQueryBuilder()
221 'oi_archive_name ' . $dbw->buildLike( $dbw->anyString(),
".$ext" ),
222 $dbw->bitAnd(
'oi_deleted', File::DELETED_FILE ) => File::DELETED_FILE,
224 if ( $lock ===
'lock' ) {
225 $queryBuilder->forUpdate();
228 return (
bool)$queryBuilder->caller( __METHOD__ )->fetchField();
238 $sha1 = strtok( $key,
'.' );
239 if ( is_string( $sha1 ) && strlen( $sha1 ) === 32 && $sha1[0] ===
'0' ) {
240 $sha1 = substr( $sha1, 1 );
252 $title = File::normalizeTitle( $title,
'exception' );
254 $memcKey = $this->
getSharedCacheKey(
'file-redirect', md5( $title->getDBkey() ) );
255 if ( $memcKey ===
false ) {
256 $memcKey = $this->
getLocalCacheKey(
'file-redirect', md5( $title->getDBkey() ) );
262 $method = __METHOD__;
263 $redirDbKey = $this->wanCache->getWithSetCallback(
266 function ( $oldValue, &$ttl, array &$setOpts ) use ( $method, $title ) {
269 $setOpts += Database::getCacheSetOptions( $dbr );
271 $row = $dbr->newSelectQueryBuilder()
272 ->select( [
'rd_namespace',
'rd_title' ] )
274 ->join(
'redirect',
null,
'rd_from = page_id' )
275 ->where( [
'page_namespace' => $title->getNamespace(),
'page_title' => $title->getDBkey() ] )
276 ->caller( $method )->fetchRow();
278 return ( $row && $row->rd_namespace ==
NS_FILE )
279 ? Title::makeTitle( $row->rd_namespace, $row->rd_title )->getDBkey()
282 [
'pcTTL' => WANObjectCache::TTL_PROC_LONG ]
286 if ( $redirDbKey !==
' ' && strval( $redirDbKey ) !==
'' ) {
288 return Title::newFromText( $redirDbKey,
NS_FILE );
298 foreach ( $items as $item ) {
299 if ( is_array( $item ) ) {
300 $title = File::normalizeTitle( $item[
'title'] );
302 $searchSet[$title->getDBkey()] = $item;
305 $title = File::normalizeTitle( $item );
307 $searchSet[$title->getDBkey()] = [];
312 $fileMatchesSearch =
static function (
File $file, array $search ) {
318 $contextPerformer = RequestContext::getMain()->getAuthority();
319 $performer = ( !empty( $search[
'private'] ) && $search[
'private'] instanceof
Authority )
326 ( empty( $search[
'time'] ) && !
$file->isOld() ) ||
327 ( !empty( $search[
'time'] ) && $search[
'time'] ===
$file->getTimestamp() )
329 ( !empty( $search[
'private'] ) || !
$file->isDeleted( File::DELETED_FILE ) ) &&
330 $file->userCan( File::DELETED_FILE, $performer )
334 $applyMatchingFiles =
function (
IResultWrapper $res, &$searchSet, &$finalFiles )
335 use ( $fileMatchesSearch, $flags )
337 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
339 foreach ( $res as $row ) {
343 $dbKeysLook = [ strtr(
$file->getName(),
' ',
'_' ) ];
344 if ( !empty( $info[
'initialCapital'] ) ) {
346 $dbKeysLook[] = $contLang->lcfirst(
$file->getName() );
348 foreach ( $dbKeysLook as $dbKey ) {
349 if ( isset( $searchSet[$dbKey] )
350 && $fileMatchesSearch(
$file, $searchSet[$dbKey] )
353 ? [
'title' => $dbKey,
'timestamp' =>
$file->getTimestamp() ]
355 unset( $searchSet[$dbKey] );
365 foreach ( $searchSet as $dbKey => $_ ) {
369 if ( count( $imgNames ) ) {
370 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
371 $res = $queryBuilder->where( [
'img_name' => $imgNames ] )->caller( __METHOD__ )->fetchResultSet();
372 $applyMatchingFiles( $res, $searchSet, $finalFiles );
377 foreach ( $searchSet as $dbKey => $search ) {
378 if ( isset( $search[
'time'] ) ) {
379 $oiConds[] = $dbr->makeList(
382 'oi_timestamp' => $dbr->timestamp( $search[
'time'] )
389 if ( count( $oiConds ) ) {
390 $queryBuilder = FileSelectQueryBuilder::newForOldFile( $dbr );
392 $res = $queryBuilder->where( $dbr->makeList( $oiConds,
LIST_OR ) )
393 ->caller( __METHOD__ )->fetchResultSet();
394 $applyMatchingFiles( $res, $searchSet, $finalFiles );
398 foreach ( $searchSet as $dbKey => $search ) {
399 if ( !empty( $search[
'ignoreRedirect'] ) ) {
403 $title = File::normalizeTitle( $dbKey );
406 if ( $redir && $redir->getNamespace() ===
NS_FILE ) {
408 if (
$file && $fileMatchesSearch(
$file, $search ) ) {
409 $file->redirectedFrom( $title->getDBkey() );
411 $finalFiles[$dbKey] = [
412 'title' =>
$file->getTitle()->getDBkey(),
413 'timestamp' =>
$file->getTimestamp()
416 $finalFiles[$dbKey] =
$file;
433 $queryBuilder = FileSelectQueryBuilder::newForFile( $this->
getReplicaDB() );
434 $res = $queryBuilder->where( [
'img_sha1' => $hash ] )
435 ->orderBy(
'img_name' )
436 ->caller( __METHOD__ )->fetchResultSet();
439 foreach ( $res as $row ) {
457 if ( $hashes === [] ) {
462 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
464 $queryBuilder->where( [
'img_sha1' => $hashes ] )
465 ->orderBy(
'img_name' );
466 $res = $queryBuilder->caller( __METHOD__ )->fetchResultSet();
469 foreach ( $res as $row ) {
487 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
489 $queryBuilder->where(
'img_name ' . $dbr->buildLike( $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();
525 return static function ( $index ) {
537 return $this->hasAccessibleSharedCache;
543 ? $this->wanCache->makeGlobalKey(
544 'filerepo-' . $kClassSuffix,
561 function () use ( $key ) {
562 $this->wanCache->delete( $key );
569 public function store( $srcPath, $dstZone, $dstRel, $flags = 0 ) {
595 public function delete( $srcRel, $archiveRel ) {
614 wfDebug( __METHOD__ .
": skipped because storage uses sha1 paths" );
615 return Status::newGood();
617 return parent::$function( ...$args );
631 return $this->useJsonMetadata;
651 return $this->splitMetadataThreshold;
655 return $this->updateCompatibleMetadata;
659 return $this->reserializeMetadata;
669 if ( !$this->blobStore ) {
670 $this->blobStore = MediaWikiServices::getInstance()->getBlobStoreFactory()
671 ->newBlobStore( $this->dbDomain );
673 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.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
if(!defined('MW_SETUP_CALLBACK'))
Proxy backend that manages file layout rewriting for FileRepo.
doOperation(array $op, array $opts=[])
Same as doOperations() except it takes a single operation.
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...
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.
Interface for objects (potentially) representing an editable wiki page.
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
if(!is_readable( $file)) $ext