15use Psr\Log\LoggerInterface;
18use Wikimedia\ScopedCallback;
66 private $haveSourceLock =
false;
69 private $haveTargetLock =
false;
77 $this->oldHash = $this->file->repo->getHashPath( $this->file->getName() );
78 $this->newHash = $this->file->repo->getHashPath( $this->target->getDBkey() );
79 $this->oldName = $this->file->getName();
80 $this->newName = $this->file->repo->getNameFromTitle( $this->target );
85 $this->logger = LoggerFactory::getInstance(
'imagemove' );
95 if ( $status->isOK() ) {
106 $archiveBase =
'archive';
115 $result = $this->db->newSelectQueryBuilder()
116 ->select( [
'oi_archive_name',
'oi_deleted' ] )
119 ->where( [
'oi_name' => $this->oldName ] )
120 ->caller( __METHOD__ )->fetchResultSet();
122 $result = $this->db->newSelectQueryBuilder()
124 'oi_archive_name' =>
'fr_archive_name',
125 'oi_deleted' =>
'fr_deleted',
128 ->from(
'filerevision' )
129 ->join(
'file',
null,
'fr_file = file_id' )
130 ->where( [
'file_name' => $this->oldName,
'file_deleted' => 0,
'file_latest != fr_id' ] )
131 ->caller( __METHOD__ )->fetchResultSet();
134 foreach ( $result as $row ) {
135 $archiveNames[] = $row->oi_archive_name;
137 $bits = explode(
'!',
$oldName, 2 );
139 if ( count( $bits ) != 2 ) {
140 $this->logger->debug(
141 'Old file name missing !: {oldName}',
147 [ $timestamp, $filename ] = $bits;
149 if ( $this->oldName != $filename ) {
150 $this->logger->debug(
151 'Old file name does not match: {oldName}',
165 "{$archiveBase}/{$this->oldHash}{$oldName}",
166 "{$archiveBase}/{$this->newHash}{$timestamp}!{$this->newName}"
170 return $archiveNames;
179 if ( $this->haveSourceLock ) {
180 return Status::newGood();
182 $status = $this->file->acquireFileLock();
183 if ( $status->isOK() ) {
184 $this->haveSourceLock =
true;
195 if ( $this->haveTargetLock ) {
196 return Status::newGood();
199 if ( $status->isOK() ) {
200 $this->haveTargetLock =
true;
209 if ( $this->haveSourceLock ) {
210 $this->file->releaseFileLock();
211 $this->haveSourceLock =
false;
213 if ( $this->haveTargetLock ) {
215 $this->haveTargetLock =
false;
225 if ( $this->targetFile ===
null ) {
227 ->newFile( $this->target );
229 return $this->targetFile;
237 $repo = $this->file->repo;
238 $status = $repo->newGood();
241 if ( !$status->isOK() ) {
245 if ( !$status->isOK() ) {
249 $unlockScope =
new ScopedCallback(
function () {
255 if ( !$checkStatus->isGood() ) {
256 $status->merge( $checkStatus );
259 $triplets = $checkStatus->value;
263 if ( !$statusDb->isGood() ) {
264 $statusDb->setOK(
false );
269 if ( !$repo->hasSha1Storage() ) {
275 $this->logger->debug(
276 'Moved files for {fileName}: {successCount} successes, {failCount} failures',
278 'fileName' => $this->file->getName(),
279 'successCount' => $statusMove->successCount,
280 'failCount' => $statusMove->failCount,
284 if ( !$statusMove->isGood() ) {
288 $this->logger->debug(
289 'Error in moving files: {error}',
290 [
'error' => $statusMove->getWikiText(
false,
false,
'en' ) ]
293 $statusMove->setOK(
false );
297 $status->merge( $statusMove );
303 $this->logger->debug(
304 'Renamed {fileName} in database: {successCount} successes, {failCount} failures',
306 'fileName' => $this->file->getName(),
307 'successCount' => $statusDb->successCount,
308 'failCount' => $statusDb->failCount,
316 if ( $this->db->trxLevel() ) {
317 ScopedCallback::cancel( $unlockScope );
318 $this->db->onTransactionResolution(
function () {
322 ScopedCallback::consume( $unlockScope );
325 $status->merge( $statusDb );
337 $repo = $this->file->repo;
338 $status = $repo->newGood();
348 ->where( [
'img_name' => $this->oldName ] )
350 ->caller( __METHOD__ )
354 $oldRowCount = $dbw->newSelectQueryBuilder()
356 ->where( [
'oi_name' => $this->oldName ] )
358 ->caller( __METHOD__ )
361 $status->successCount++;
363 $status->failCount++;
365 $status->successCount += $oldRowCount;
369 $status->failCount += max( 0, $this->oldCount - $oldRowCount );
370 if ( $status->failCount ) {
371 $status->error(
'imageinvalidfilename' );
374 $status->successCount += $this->db->newSelectQueryBuilder()
376 ->from(
'filerevision' )
377 ->join(
'file',
null,
'fr_file = file_id' )
378 ->where( [
'file_name' => $this->oldName,
'file_deleted' => 0 ] )
379 ->caller( __METHOD__ )->fetchRowCount();
397 ->select(
'file_id' )
399 ->where( [
'file_name' => $this->newName ] )
400 ->andWhere( [
'file_deleted' => 1 ] )
401 ->caller( __METHOD__ )->fetchField();
406 $dbw->newDeleteQueryBuilder()
407 ->deleteFrom(
'file' )
408 ->where( [
'file_name' => $this->newName ] )
409 ->andWhere( [
'file_deleted' => 1 ] )
410 ->caller( __METHOD__ )->execute();
412 $dbw->newUpdateQueryBuilder()
413 ->update(
'filerevision' )
414 ->set( [
'fr_file' => $this->file->getFileIdFromName() ] )
415 ->where( [
'fr_file' => $deleted ] )
416 ->caller( __METHOD__ )->execute();
418 $dbw->newUpdateQueryBuilder()
420 ->set( [
'file_name' => $this->newName ] )
421 ->where( [
'file_id' => $this->file->getFileIdFromName() ] )
422 ->caller( __METHOD__ )->execute();
426 $dbw->newUpdateQueryBuilder()
428 ->set( [
'img_name' => $this->newName ] )
429 ->where( [
'img_name' => $this->oldName ] )
430 ->caller( __METHOD__ )->execute();
433 $dbw->newUpdateQueryBuilder()
434 ->update(
'oldimage' )
436 'oi_name' => $this->newName,
437 'oi_archive_name' =>
new RawSQLValue( $dbw->strreplace(
439 $dbw->addQuotes( $this->oldName ),
440 $dbw->addQuotes( $this->newName )
443 ->where( [
'oi_name' => $this->oldName ] )
444 ->caller( __METHOD__ )->execute();
455 foreach ( [ $this->cur, ...$this->olds ] as $move ) {
457 $srcUrl = $this->file->repo->getVirtualUrl() .
'/public/' . rawurlencode( $move[0] );
458 $triplets[] = [ $srcUrl,
'public', $move[1] ];
460 $this->logger->debug(
461 'Generated move triplet for {fileName}: {srcUrl} :: public :: {move1}',
463 'fileName' => $this->file->getName(),
481 foreach ( $triplets as
$file ) {
485 $result = $this->file->repo->fileExistsBatch( $files );
486 if ( in_array(
null, $result,
true ) ) {
487 return Status::newFatal(
'backend-fail-internal',
488 $this->file->repo->getBackend()->getName() );
491 $filteredTriplets = [];
492 foreach ( $triplets as
$file ) {
493 if ( $result[
$file[0]] ) {
494 $filteredTriplets[] =
$file;
496 $this->logger->debug(
497 'File {file} does not exist',
498 [
'file' =>
$file[0] ]
503 return Status::newGood( $filteredTriplets );
514 foreach ( $triplets as $triplet ) {
516 $pairs[] = [ $triplet[1], $triplet[2] ];
519 $this->file->repo->cleanupBatch( $pairs );
530 foreach ( $triplets as $triplet ) {
531 $files[] = $triplet[0];
534 $this->file->repo->cleanupBatch( $files );
539class_alias( LocalFileMoveBatch::class,
'LocalFileMoveBatch' );
const SCHEMA_COMPAT_WRITE_OLD
const SCHEMA_COMPAT_WRITE_NEW
A class containing constants representing the names of configuration variables.
const FileSchemaMigrationStage
Name constant for the FileSchemaMigrationStage setting, for use with Config::get()