24use InvalidArgumentException;
96 parent::__construct( $info );
98 $this->dbDomain = WikiMap::getCurrentWikiDbDomain();
99 $this->hasAccessibleSharedCache =
true;
101 $this->
hasSha1Storage = ( $info[
'storageLayout'] ?? null ) ===
'sha1';
106 'backend' => $this->backend,
107 'repoName' => $this->name,
116 'splitMetadataThreshold',
117 'updateCompatibleMetadata',
118 'reserializeMetadata',
121 if ( isset( $info[$option] ) ) {
122 $this->$option = $info[$option];
132 if ( isset( $row->img_name ) ) {
133 return ( $this->fileFromRowFactory )( $row, $this );
134 } elseif ( isset( $row->oi_name ) ) {
135 return ( $this->oldFileFromRowFactory )( $row, $this );
137 throw new InvalidArgumentException( __METHOD__ .
': invalid row' );
147 $title = File::normalizeTitle( $title );
148 return OldLocalFile::newFromArchiveName( $title, $this, $archiveName );
163 wfDebug( __METHOD__ .
": skipped because storage uses sha1 paths" );
164 return Status::newGood();
171 $storageKeys = array_unique( $storageKeys );
172 foreach ( $storageKeys as $key ) {
174 $path =
"$root/$hashPath$key";
175 $dbw->startAtomic( __METHOD__ );
180 if ( !$deleted && !$hidden ) {
181 wfDebug( __METHOD__ .
": deleting $key" );
182 $op = [
'op' =>
'delete',
'src' =>
$path ];
184 $status->error(
'undelete-cleanup-error',
$path );
185 $status->failCount++;
188 wfDebug( __METHOD__ .
": $key still in use" );
189 $status->successCount++;
191 $dbw->endAtomic( __METHOD__ );
205 $queryBuilder = $this->
getPrimaryDB()->newSelectQueryBuilder()
207 ->from(
'filearchive' )
208 ->where( [
'fa_storage_group' =>
'deleted',
'fa_storage_key' => $key ] );
209 if ( $lock ===
'lock' ) {
210 $queryBuilder->forUpdate();
212 return (
bool)$queryBuilder->caller( __METHOD__ )->fetchField();
224 $ext = File::normalizeExtension( substr( $key, strcspn( $key,
'.' ) + 1 ) );
227 $queryBuilder = $dbw->newSelectQueryBuilder()
232 $dbw->expr(
'oi_archive_name', IExpression::LIKE,
new LikeValue( $dbw->anyString(),
".$ext" ) ),
233 $dbw->bitAnd(
'oi_deleted', File::DELETED_FILE ) => File::DELETED_FILE,
235 if ( $lock ===
'lock' ) {
236 $queryBuilder->forUpdate();
239 return (
bool)$queryBuilder->caller( __METHOD__ )->fetchField();
249 $sha1 = strtok( $key,
'.' );
250 if ( is_string( $sha1 ) && strlen( $sha1 ) === 32 && $sha1[0] ===
'0' ) {
251 $sha1 = substr( $sha1, 1 );
263 $title = File::normalizeTitle( $title,
'exception' );
265 $memcKey = $this->
getSharedCacheKey(
'file-redirect', md5( $title->getDBkey() ) );
266 if ( $memcKey ===
false ) {
267 $memcKey = $this->
getLocalCacheKey(
'file-redirect', md5( $title->getDBkey() ) );
273 $method = __METHOD__;
274 $redirDbKey = $this->wanCache->getWithSetCallback(
277 function ( $oldValue, &$ttl, array &$setOpts ) use ( $method, $title ) {
282 $row = $dbr->newSelectQueryBuilder()
283 ->select( [
'rd_namespace',
'rd_title' ] )
285 ->join(
'redirect',
null,
'rd_from = page_id' )
286 ->where( [
'page_namespace' => $title->getNamespace(),
'page_title' => $title->getDBkey() ] )
287 ->caller( $method )->fetchRow();
289 return ( $row && $row->rd_namespace ==
NS_FILE )
290 ? Title::makeTitle( $row->rd_namespace, $row->rd_title )->getDBkey()
293 [
'pcTTL' => WANObjectCache::TTL_PROC_LONG ]
297 if ( $redirDbKey !==
' ' && strval( $redirDbKey ) !==
'' ) {
299 return Title::newFromText( $redirDbKey,
NS_FILE );
309 foreach ( $items as $item ) {
310 if ( is_array( $item ) ) {
311 $title = File::normalizeTitle( $item[
'title'] );
313 $searchSet[$title->getDBkey()] = $item;
316 $title = File::normalizeTitle( $item );
318 $searchSet[$title->getDBkey()] = [];
323 $fileMatchesSearch =
static function (
File $file, array $search ) {
329 $contextPerformer = RequestContext::getMain()->getAuthority();
330 $performer = ( !empty( $search[
'private'] ) && $search[
'private'] instanceof
Authority )
337 ( empty( $search[
'time'] ) && !$file->
isOld() ) ||
338 ( !empty( $search[
'time'] ) && $search[
'time'] === $file->
getTimestamp() )
340 ( !empty( $search[
'private'] ) || !$file->
isDeleted( File::DELETED_FILE ) ) &&
341 $file->
userCan( File::DELETED_FILE, $performer )
345 $applyMatchingFiles =
function (
IResultWrapper $res, &$searchSet, &$finalFiles )
346 use ( $fileMatchesSearch, $flags )
350 foreach ( $res as $row ) {
354 $dbKeysLook = [ strtr( $file->
getName(),
' ',
'_' ) ];
355 if ( !empty( $info[
'initialCapital'] ) ) {
357 $dbKeysLook[] = $contLang->lcfirst( $file->
getName() );
359 foreach ( $dbKeysLook as $dbKey ) {
360 if ( isset( $searchSet[$dbKey] )
361 && $fileMatchesSearch( $file, $searchSet[$dbKey] )
364 ? [
'title' => $dbKey,
'timestamp' => $file->
getTimestamp() ]
366 unset( $searchSet[$dbKey] );
376 foreach ( $searchSet as $dbKey => $_ ) {
380 if ( count( $imgNames ) ) {
381 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
382 $res = $queryBuilder->where( [
'img_name' => $imgNames ] )->caller( __METHOD__ )->fetchResultSet();
383 $applyMatchingFiles( $res, $searchSet, $finalFiles );
388 foreach ( $searchSet as $dbKey => $search ) {
389 if ( isset( $search[
'time'] ) ) {
391 ->expr(
'oi_name',
'=', $this->
getNameFromTitle( File::normalizeTitle( $dbKey ) ) )
392 ->and(
'oi_timestamp',
'=', $dbr->timestamp( $search[
'time'] ) );
396 if ( count( $oiConds ) ) {
397 $queryBuilder = FileSelectQueryBuilder::newForOldFile( $dbr );
399 $res = $queryBuilder->where( $dbr->orExpr( $oiConds ) )
400 ->caller( __METHOD__ )->fetchResultSet();
401 $applyMatchingFiles( $res, $searchSet, $finalFiles );
405 foreach ( $searchSet as $dbKey => $search ) {
406 if ( !empty( $search[
'ignoreRedirect'] ) ) {
410 $title = File::normalizeTitle( $dbKey );
413 if ( $redir && $redir->getNamespace() ===
NS_FILE ) {
414 $file = $this->
newFile( $redir );
415 if ( $file && $fileMatchesSearch( $file, $search ) ) {
418 $finalFiles[$dbKey] = [
419 'title' => $file->
getTitle()->getDBkey(),
423 $finalFiles[$dbKey] = $file;
440 $queryBuilder = FileSelectQueryBuilder::newForFile( $this->
getReplicaDB() );
441 $res = $queryBuilder->where( [
'img_sha1' => $hash ] )
442 ->orderBy(
'img_name' )
443 ->caller( __METHOD__ )->fetchResultSet();
446 foreach ( $res as $row ) {
464 if ( $hashes === [] ) {
469 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
471 $queryBuilder->where( [
'img_sha1' => $hashes ] )
472 ->orderBy(
'img_name' );
473 $res = $queryBuilder->caller( __METHOD__ )->fetchResultSet();
476 foreach ( $res as $row ) {
478 $result[$file->getSha1()][] = $file;
494 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
497 ->where( $dbr->expr(
'img_name', IExpression::LIKE,
new LikeValue( $prefix, $dbr->anyString() ) ) )
498 ->orderBy(
'img_name' )
499 ->limit( intval( $limit ) );
500 $res = $queryBuilder->caller( __METHOD__ )->fetchResultSet();
504 foreach ( $res as $row ) {
516 return $this->dbProvider->getReplicaDatabase();
525 return $this->dbProvider->getPrimaryDatabase();
534 return static function ( $index ) {
556 ? $this->wanCache->makeGlobalKey(
557 'filerepo-' . $kClassSuffix,
574 function () use ( $key ) {
575 $this->wanCache->delete( $key );
582 public function store( $srcPath, $dstZone, $dstRel, $flags = 0 ) {
608 public function delete( $srcRel, $archiveRel ) {
627 wfDebug( __METHOD__ .
": skipped because storage uses sha1 paths" );
628 return Status::newGood();
630 return parent::$function( ...$args );
680 if ( !$this->blobStore ) {
682 ->newBlobStore( $this->dbDomain );
689class_alias( LocalRepo::class,
'LocalRepo' );
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'))
Group all the pieces relevant to the context of a request into one instance.
Interface for objects (potentially) representing an editable wiki page.