23use Psr\Log\LoggerInterface;
25use Wikimedia\ScopedCallback;
72 private $haveSourceLock =
false;
75 private $haveTargetLock =
false;
87 $this->oldHash = $this->file->repo->getHashPath( $this->file->getName() );
88 $this->newHash = $this->file->repo->getHashPath( $this->target->getDBkey() );
89 $this->oldName = $this->file->getName();
90 $this->newName = $this->file->repo->getNameFromTitle( $this->target );
95 $this->logger = LoggerFactory::getInstance(
'imagemove' );
105 if ( $status->isOK() ) {
116 $archiveBase =
'archive';
121 $result = $this->db->select(
'oldimage',
122 [
'oi_archive_name',
'oi_deleted' ],
123 [
'oi_name' => $this->oldName ],
128 foreach ( $result as $row ) {
129 $archiveNames[] = $row->oi_archive_name;
131 $bits = explode(
'!',
$oldName, 2 );
133 if ( count( $bits ) != 2 ) {
134 $this->logger->debug(
135 'Old file name missing !: {oldName}',
141 list( $timestamp, $filename ) = $bits;
143 if ( $this->oldName != $filename ) {
144 $this->logger->debug(
145 'Old file name does not match: {oldName}',
154 if ( $row->oi_deleted & File::DELETED_FILE ) {
159 "{$archiveBase}/{$this->oldHash}{$oldName}",
160 "{$archiveBase}/{$this->newHash}{$timestamp}!{$this->newName}"
164 return $archiveNames;
173 if ( $this->haveSourceLock ) {
174 return Status::newGood();
176 $status = $this->file->acquireFileLock();
177 if ( $status->isOK() ) {
178 $this->haveSourceLock =
true;
189 if ( $this->haveTargetLock ) {
190 return Status::newGood();
193 if ( $status->isOK() ) {
194 $this->haveTargetLock =
true;
203 if ( $this->haveSourceLock ) {
204 $this->file->releaseFileLock();
205 $this->haveSourceLock =
false;
207 if ( $this->haveTargetLock ) {
209 $this->haveTargetLock =
false;
219 if ( $this->targetFile ===
null ) {
220 $this->targetFile = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo()
221 ->newFile( $this->target );
223 return $this->targetFile;
231 $repo = $this->file->repo;
232 $status = $repo->newGood();
235 if ( !$status->isOK() ) {
239 if ( !$status->isOK() ) {
243 $unlockScope =
new ScopedCallback(
function () {
249 if ( !$checkStatus->isGood() ) {
250 $status->merge( $checkStatus );
253 $triplets = $checkStatus->value;
257 if ( !$statusDb->isGood() ) {
258 $statusDb->setOK(
false );
263 if ( !$repo->hasSha1Storage() ) {
269 $this->logger->debug(
270 'Moved files for {fileName}: {successCount} successes, {failCount} failures',
272 'fileName' => $this->file->getName(),
273 'successCount' => $statusMove->successCount,
274 'failCount' => $statusMove->failCount,
278 if ( !$statusMove->isGood() ) {
282 $this->logger->debug(
283 'Error in moving files: {error}',
284 [
'error' => $statusMove->getWikiText(
false,
false,
'en' ) ]
287 $statusMove->setOK(
false );
291 $status->merge( $statusMove );
297 $this->logger->debug(
298 'Renamed {fileName} in database: {successCount} successes, {failCount} failures',
300 'fileName' => $this->file->getName(),
301 'successCount' => $statusDb->successCount,
302 'failCount' => $statusDb->failCount,
310 if ( $this->db->trxLevel() ) {
311 $unlockScope->cancel();
312 $this->db->onTransactionResolution(
function () {
316 ScopedCallback::consume( $unlockScope );
319 $status->merge( $statusDb );
331 $repo = $this->file->repo;
332 $status = $repo->newGood();
337 [
'img_name' => $this->oldName ],
340 $oldRowCount = $dbw->lockForUpdate(
342 [
'oi_name' => $this->oldName ],
347 $status->successCount++;
349 $status->failCount++;
351 $status->successCount += $oldRowCount;
355 $status->failCount += max( 0, $this->oldCount - $oldRowCount );
356 if ( $status->failCount ) {
357 $status->error(
'imageinvalidfilename' );
373 [
'img_name' => $this->newName ],
374 [
'img_name' => $this->oldName ],
382 'oi_name' => $this->newName,
383 'oi_archive_name = ' . $dbw->strreplace(
'oi_archive_name',
384 $dbw->addQuotes( $this->oldName ), $dbw->addQuotes( $this->newName ) ),
386 [
'oi_name' => $this->oldName ],
396 $moves = array_merge( [ $this->cur ], $this->olds );
399 foreach ( $moves as $move ) {
401 $srcUrl = $this->file->repo->getVirtualUrl() .
'/public/' . rawurlencode( $move[0] );
402 $triplets[] = [ $srcUrl,
'public', $move[1] ];
404 $this->logger->debug(
405 'Generated move triplet for {fileName}: {srcUrl} :: public :: {move1}',
407 'fileName' => $this->file->getName(),
425 foreach ( $triplets as
$file ) {
429 $result = $this->file->repo->fileExistsBatch( $files );
430 if ( in_array(
null, $result,
true ) ) {
431 return Status::newFatal(
'backend-fail-internal',
432 $this->file->repo->getBackend()->getName() );
435 $filteredTriplets = [];
436 foreach ( $triplets as
$file ) {
437 if ( $result[
$file[0]] ) {
438 $filteredTriplets[] =
$file;
440 $this->logger->debug(
441 'File {file} does not exist',
442 [
'file' =>
$file[0] ]
447 return Status::newGood( $filteredTriplets );
458 foreach ( $triplets as $triplet ) {
460 $pairs[] = [ $triplet[1], $triplet[2] ];
463 $this->file->repo->cleanupBatch( $pairs );
474 foreach ( $triplets as $triplet ) {
475 $files[] = $triplet[0];
478 $this->file->repo->cleanupBatch( $files );
Helper class for file movement.
getTargetFile()
Get the target file.
releaseLocks()
Release both file locks.
cleanupTarget( $triplets)
Cleanup a partially moved array of triplets by deleting the target files.
addOlds()
Add the old versions of the image to the batch.
doDBUpdates()
Do the database updates and return a new Status indicating how many rows where updated.
acquireSourceLock()
Acquire the source file lock, if it has not been acquired already.
getMoveTriplets()
Generate triplets for FileRepo::storeBatch().
execute()
Perform the move.
verifyDBUpdates()
Verify the database updates and return a new Status indicating how many rows would be updated.
removeNonexistentFiles( $triplets)
Removes non-existent files from move batch.
__construct(LocalFile $file, Title $target)
acquireTargetLock()
Acquire the target file lock, if it has not been acquired already.
addCurrent()
Add the current image to the batch.
cleanupSource( $triplets)
Cleanup a fully moved array of triplets by deleting the source files.
Local file in the wiki's own database.
Represents a title within MediaWiki.