29use Psr\Log\LoggerInterface;
32use Wikimedia\ScopedCallback;
80 private $haveSourceLock =
false;
83 private $haveTargetLock =
false;
91 $this->oldHash = $this->file->repo->getHashPath( $this->file->getName() );
92 $this->newHash = $this->file->repo->getHashPath( $this->target->getDBkey() );
93 $this->oldName = $this->file->getName();
94 $this->newName = $this->file->repo->getNameFromTitle( $this->target );
99 $this->logger = LoggerFactory::getInstance(
'imagemove' );
109 if ( $status->isOK() ) {
120 $archiveBase =
'archive';
125 $result = $this->db->newSelectQueryBuilder()
126 ->select( [
'oi_archive_name',
'oi_deleted' ] )
129 ->where( [
'oi_name' => $this->oldName ] )
130 ->caller( __METHOD__ )->fetchResultSet();
132 foreach ( $result as $row ) {
133 $archiveNames[] = $row->oi_archive_name;
135 $bits = explode(
'!',
$oldName, 2 );
137 if ( count( $bits ) != 2 ) {
138 $this->logger->debug(
139 'Old file name missing !: {oldName}',
145 [ $timestamp, $filename ] = $bits;
147 if ( $this->oldName != $filename ) {
148 $this->logger->debug(
149 'Old file name does not match: {oldName}',
163 "{$archiveBase}/{$this->oldHash}{$oldName}",
164 "{$archiveBase}/{$this->newHash}{$timestamp}!{$this->newName}"
168 return $archiveNames;
177 if ( $this->haveSourceLock ) {
178 return Status::newGood();
180 $status = $this->file->acquireFileLock();
181 if ( $status->isOK() ) {
182 $this->haveSourceLock =
true;
193 if ( $this->haveTargetLock ) {
194 return Status::newGood();
197 if ( $status->isOK() ) {
198 $this->haveTargetLock =
true;
207 if ( $this->haveSourceLock ) {
208 $this->file->releaseFileLock();
209 $this->haveSourceLock =
false;
211 if ( $this->haveTargetLock ) {
213 $this->haveTargetLock =
false;
223 if ( $this->targetFile ===
null ) {
225 ->newFile( $this->target );
227 return $this->targetFile;
235 $repo = $this->file->repo;
236 $status = $repo->newGood();
239 if ( !$status->isOK() ) {
243 if ( !$status->isOK() ) {
247 $unlockScope =
new ScopedCallback(
function () {
253 if ( !$checkStatus->isGood() ) {
254 $status->merge( $checkStatus );
257 $triplets = $checkStatus->value;
261 if ( !$statusDb->isGood() ) {
262 $statusDb->setOK(
false );
267 if ( !$repo->hasSha1Storage() ) {
273 $this->logger->debug(
274 'Moved files for {fileName}: {successCount} successes, {failCount} failures',
276 'fileName' => $this->file->getName(),
277 'successCount' => $statusMove->successCount,
278 'failCount' => $statusMove->failCount,
282 if ( !$statusMove->isGood() ) {
286 $this->logger->debug(
287 'Error in moving files: {error}',
288 [
'error' => $statusMove->getWikiText(
false,
false,
'en' ) ]
291 $statusMove->setOK(
false );
295 $status->merge( $statusMove );
301 $this->logger->debug(
302 'Renamed {fileName} in database: {successCount} successes, {failCount} failures',
304 'fileName' => $this->file->getName(),
305 'successCount' => $statusDb->successCount,
306 'failCount' => $statusDb->failCount,
314 if ( $this->db->trxLevel() ) {
315 ScopedCallback::cancel( $unlockScope );
316 $this->db->onTransactionResolution(
function () {
320 ScopedCallback::consume( $unlockScope );
323 $status->merge( $statusDb );
335 $repo = $this->file->repo;
336 $status = $repo->newGood();
342 ->where( [
'img_name' => $this->oldName ] )
344 ->caller( __METHOD__ )
348 $oldRowCount = $dbw->newSelectQueryBuilder()
350 ->where( [
'oi_name' => $this->oldName ] )
352 ->caller( __METHOD__ )
356 $status->successCount++;
358 $status->failCount++;
360 $status->successCount += $oldRowCount;
364 $status->failCount += max( 0, $this->oldCount - $oldRowCount );
365 if ( $status->failCount ) {
366 $status->error(
'imageinvalidfilename' );
384 ->select(
'file_id' )
386 ->where( [
'file_name' => $this->newName ] )
387 ->andWhere( [
'file_deleted' => 1 ] )
388 ->caller( __METHOD__ )->fetchField();
393 $dbw->newDeleteQueryBuilder()
394 ->deleteFrom(
'file' )
395 ->where( [
'file_name' => $this->newName ] )
396 ->andWhere( [
'file_deleted' => 1 ] )
397 ->caller( __METHOD__ )->execute();
399 $dbw->newUpdateQueryBuilder()
400 ->update(
'filerevision' )
401 ->set( [
'fr_file' => $this->file->getFileIdFromName() ] )
402 ->where( [
'fr_file' => $deleted ] )
403 ->caller( __METHOD__ )->execute();
405 $dbw->newUpdateQueryBuilder()
407 ->set( [
'file_name' => $this->newName ] )
408 ->where( [
'file_id' => $this->file->getFileIdFromName() ] )
409 ->caller( __METHOD__ )->execute();
412 $dbw->newUpdateQueryBuilder()
414 ->set( [
'img_name' => $this->newName ] )
415 ->where( [
'img_name' => $this->oldName ] )
416 ->caller( __METHOD__ )->execute();
419 $dbw->newUpdateQueryBuilder()
420 ->update(
'oldimage' )
422 'oi_name' => $this->newName,
423 'oi_archive_name' =>
new RawSQLValue( $dbw->strreplace(
425 $dbw->addQuotes( $this->oldName ),
426 $dbw->addQuotes( $this->newName )
429 ->where( [
'oi_name' => $this->oldName ] )
430 ->caller( __METHOD__ )->execute();
438 $moves = array_merge( [ $this->cur ], $this->olds );
441 foreach ( $moves as $move ) {
443 $srcUrl = $this->file->repo->getVirtualUrl() .
'/public/' . rawurlencode( $move[0] );
444 $triplets[] = [ $srcUrl,
'public', $move[1] ];
446 $this->logger->debug(
447 'Generated move triplet for {fileName}: {srcUrl} :: public :: {move1}',
449 'fileName' => $this->file->getName(),
467 foreach ( $triplets as
$file ) {
471 $result = $this->file->repo->fileExistsBatch( $files );
472 if ( in_array(
null, $result,
true ) ) {
473 return Status::newFatal(
'backend-fail-internal',
474 $this->file->repo->getBackend()->getName() );
477 $filteredTriplets = [];
478 foreach ( $triplets as
$file ) {
479 if ( $result[
$file[0]] ) {
480 $filteredTriplets[] =
$file;
482 $this->logger->debug(
483 'File {file} does not exist',
484 [
'file' =>
$file[0] ]
489 return Status::newGood( $filteredTriplets );
500 foreach ( $triplets as $triplet ) {
502 $pairs[] = [ $triplet[1], $triplet[2] ];
505 $this->file->repo->cleanupBatch( $pairs );
516 foreach ( $triplets as $triplet ) {
517 $files[] = $triplet[0];
520 $this->file->repo->cleanupBatch( $files );
525class_alias( LocalFileMoveBatch::class,
'LocalFileMoveBatch' );
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()