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';
111 $result = $this->db->newSelectQueryBuilder()
112 ->select( [
'oi_archive_name',
'oi_deleted' ] )
115 ->where( [
'oi_name' => $this->oldName ] )
116 ->caller( __METHOD__ )->fetchResultSet();
118 foreach ( $result as $row ) {
119 $archiveNames[] = $row->oi_archive_name;
121 $bits = explode(
'!',
$oldName, 2 );
123 if ( count( $bits ) != 2 ) {
124 $this->logger->debug(
125 'Old file name missing !: {oldName}',
131 [ $timestamp, $filename ] = $bits;
133 if ( $this->oldName != $filename ) {
134 $this->logger->debug(
135 'Old file name does not match: {oldName}',
149 "{$archiveBase}/{$this->oldHash}{$oldName}",
150 "{$archiveBase}/{$this->newHash}{$timestamp}!{$this->newName}"
154 return $archiveNames;
163 if ( $this->haveSourceLock ) {
164 return Status::newGood();
166 $status = $this->file->acquireFileLock();
167 if ( $status->isOK() ) {
168 $this->haveSourceLock =
true;
179 if ( $this->haveTargetLock ) {
180 return Status::newGood();
183 if ( $status->isOK() ) {
184 $this->haveTargetLock =
true;
193 if ( $this->haveSourceLock ) {
194 $this->file->releaseFileLock();
195 $this->haveSourceLock =
false;
197 if ( $this->haveTargetLock ) {
199 $this->haveTargetLock =
false;
209 if ( $this->targetFile ===
null ) {
211 ->newFile( $this->target );
213 return $this->targetFile;
221 $repo = $this->file->repo;
222 $status = $repo->newGood();
225 if ( !$status->isOK() ) {
229 if ( !$status->isOK() ) {
233 $unlockScope =
new ScopedCallback(
function () {
239 if ( !$checkStatus->isGood() ) {
240 $status->merge( $checkStatus );
243 $triplets = $checkStatus->value;
247 if ( !$statusDb->isGood() ) {
248 $statusDb->setOK(
false );
253 if ( !$repo->hasSha1Storage() ) {
259 $this->logger->debug(
260 'Moved files for {fileName}: {successCount} successes, {failCount} failures',
262 'fileName' => $this->file->getName(),
263 'successCount' => $statusMove->successCount,
264 'failCount' => $statusMove->failCount,
268 if ( !$statusMove->isGood() ) {
272 $this->logger->debug(
273 'Error in moving files: {error}',
274 [
'error' => $statusMove->getWikiText(
false,
false,
'en' ) ]
277 $statusMove->setOK(
false );
281 $status->merge( $statusMove );
287 $this->logger->debug(
288 'Renamed {fileName} in database: {successCount} successes, {failCount} failures',
290 'fileName' => $this->file->getName(),
291 'successCount' => $statusDb->successCount,
292 'failCount' => $statusDb->failCount,
300 if ( $this->db->trxLevel() ) {
301 ScopedCallback::cancel( $unlockScope );
302 $this->db->onTransactionResolution(
function () {
306 ScopedCallback::consume( $unlockScope );
309 $status->merge( $statusDb );
321 $repo = $this->file->repo;
322 $status = $repo->newGood();
328 ->where( [
'img_name' => $this->oldName ] )
330 ->caller( __METHOD__ )
334 $oldRowCount = $dbw->newSelectQueryBuilder()
336 ->where( [
'oi_name' => $this->oldName ] )
338 ->caller( __METHOD__ )
342 $status->successCount++;
344 $status->failCount++;
346 $status->successCount += $oldRowCount;
350 $status->failCount += max( 0, $this->oldCount - $oldRowCount );
351 if ( $status->failCount ) {
352 $status->error(
'imageinvalidfilename' );
370 ->select(
'file_id' )
372 ->where( [
'file_name' => $this->newName ] )
373 ->andWhere( [
'file_deleted' => 1 ] )
374 ->caller( __METHOD__ )->fetchField();
379 $dbw->newDeleteQueryBuilder()
380 ->deleteFrom(
'file' )
381 ->where( [
'file_name' => $this->newName ] )
382 ->andWhere( [
'file_deleted' => 1 ] )
383 ->caller( __METHOD__ )->execute();
385 $dbw->newUpdateQueryBuilder()
386 ->update(
'filerevision' )
387 ->set( [
'fr_file' => $this->file->getFileIdFromName() ] )
388 ->where( [
'fr_file' => $deleted ] )
389 ->caller( __METHOD__ )->execute();
391 $dbw->newUpdateQueryBuilder()
393 ->set( [
'file_name' => $this->newName ] )
394 ->where( [
'file_id' => $this->file->getFileIdFromName() ] )
395 ->caller( __METHOD__ )->execute();
398 $dbw->newUpdateQueryBuilder()
400 ->set( [
'img_name' => $this->newName ] )
401 ->where( [
'img_name' => $this->oldName ] )
402 ->caller( __METHOD__ )->execute();
405 $dbw->newUpdateQueryBuilder()
406 ->update(
'oldimage' )
408 'oi_name' => $this->newName,
409 'oi_archive_name' =>
new RawSQLValue( $dbw->strreplace(
411 $dbw->addQuotes( $this->oldName ),
412 $dbw->addQuotes( $this->newName )
415 ->where( [
'oi_name' => $this->oldName ] )
416 ->caller( __METHOD__ )->execute();
426 foreach ( [ $this->cur, ...$this->olds ] as $move ) {
428 $srcUrl = $this->file->repo->getVirtualUrl() .
'/public/' . rawurlencode( $move[0] );
429 $triplets[] = [ $srcUrl,
'public', $move[1] ];
431 $this->logger->debug(
432 'Generated move triplet for {fileName}: {srcUrl} :: public :: {move1}',
434 'fileName' => $this->file->getName(),
452 foreach ( $triplets as
$file ) {
456 $result = $this->file->repo->fileExistsBatch( $files );
457 if ( in_array(
null, $result,
true ) ) {
458 return Status::newFatal(
'backend-fail-internal',
459 $this->file->repo->getBackend()->getName() );
462 $filteredTriplets = [];
463 foreach ( $triplets as
$file ) {
464 if ( $result[
$file[0]] ) {
465 $filteredTriplets[] =
$file;
467 $this->logger->debug(
468 'File {file} does not exist',
469 [
'file' =>
$file[0] ]
474 return Status::newGood( $filteredTriplets );
485 foreach ( $triplets as $triplet ) {
487 $pairs[] = [ $triplet[1], $triplet[2] ];
490 $this->file->repo->cleanupBatch( $pairs );
501 foreach ( $triplets as $triplet ) {
502 $files[] = $triplet[0];
505 $this->file->repo->cleanupBatch( $files );
510class_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()