jenkins-bot has submitted this change and it was merged. ( 
https://gerrit.wikimedia.org/r/342845 )

Change subject: Handle duplicate files gracefully
......................................................................


Handle duplicate files gracefully

Bug: T160166
Change-Id: I351049b934f23817f41903292b8c75e69e352286
---
M extension.json
M i18n/en.json
M i18n/qqq.json
A src/Generic/Data/FileRevisions.php
M src/Generic/Data/ImportDetails.php
A src/Generic/Services/DuplicateFileRevisionChecker.php
M src/MediaWiki/ApiDetailRetriever.php
M src/ServiceWiring.php
M src/SpecialImportFile.php
A tests/Generic/Data/FileRevisionsTest.php
10 files changed, 214 insertions(+), 14 deletions(-)

Approvals:
  WMDE-Fisch: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/extension.json b/extension.json
index bc83f2c..0d7c69c 100644
--- a/extension.json
+++ b/extension.json
@@ -28,10 +28,12 @@
                "FileImporter\\Generic\\Services\\Importer": 
"src/Generic/Services/Importer.php",
                "FileImporter\\Generic\\Services\\HttpRequestExecutor": 
"src/Generic/Services/HttpRequestExecutor.php",
                "FileImporter\\Generic\\Services\\RevisionModifier": 
"src/Generic/Services/RevisionModifier.php",
+               
"FileImporter\\Generic\\Services\\DuplicateFileRevisionChecker": 
"src/Generic/Services/DuplicateFileRevisionChecker.php",
                "FileImporter\\Generic\\Data\\ImportTransformations": 
"src/Generic/Data/ImportTransformations.php",
                "FileImporter\\Generic\\Data\\ImportDetails": 
"src/Generic/Data/ImportDetails.php",
                "FileImporter\\Generic\\Data\\TargetUrl": 
"src/Generic/Data/TargetUrl.php",
                "FileImporter\\Generic\\Data\\FileRevision": 
"src/Generic/Data/FileRevision.php",
+               "FileImporter\\Generic\\Data\\FileRevisions": 
"src/Generic/Data/FileRevisions.php",
                "FileImporter\\Generic\\Data\\TextRevision": 
"src/Generic/Data/TextRevision.php",
                "FileImporter\\MediaWiki\\ApiDetailRetriever": 
"src/MediaWiki/ApiDetailRetriever.php",
                "FileImporter\\MediaWiki\\HttpApiLookup": 
"src/MediaWiki/HttpApiLookup.php",
diff --git a/i18n/en.json b/i18n/en.json
index bb18904..bb868d8 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -9,6 +9,8 @@
        "fileimporter-specialpage": "Import file",
        "fileimporter-cantparseurl": "Can't parse the given URL",
        "fileimporter-cantimporturl": "Can't import the given URL",
+       "fileimporter-duplicatefilesdetected" : "The file you are currently 
trying to import already exists on this wiki.",
+       "fileimporter-duplicatefilesdetected-prefix" : "Duplicates",
        "fileimporter-exampleprefix": "Example",
        "fileimporter-textrevisionsprefix": "Text Revisions",
        "fileimporter-filerevisionsprefix": "File Revisions",
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 04f3a17..28c178a 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -10,6 +10,8 @@
        "fileimporter-specialpage": "Title for the File Import special page.",
        "fileimporter-cantparseurl": "Error message shown on the special page 
when the URL entered can not be parsed.",
        "fileimporter-cantimporturl": "Error message shown on the special page 
when the URL entered can not be imported from.",
+       "fileimporter-duplicatefilesdetected" : "Error message shown on the 
special page when the file to be imported has been detected as already existing 
on the local wiki.",
+       "fileimporter-duplicatefilesdetected-prefix" : "Prefix for the 
duplicate file that has been found.",
        "fileimporter-exampleprefix": "Prefix for the example URL contained 
within the URL text box on the special page.\n{{Identical|Example}}",
        "fileimporter-textrevisionsprefix": "Prefix for the number of text 
revisions to be imported on the special page.",
        "fileimporter-filerevisionsprefix": "Prefix for the number of file 
revisions to be imported on the special page.",
diff --git a/src/Generic/Data/FileRevisions.php 
b/src/Generic/Data/FileRevisions.php
new file mode 100644
index 0000000..2b59f95
--- /dev/null
+++ b/src/Generic/Data/FileRevisions.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace FileImporter\Generic\Data;
+
+use Wikimedia\Assert\Assert;
+
+class FileRevisions {
+
+       /**
+        * @var FileRevision[]
+        */
+       private $fileRevisions;
+
+       private $latestKey = null;
+
+       /**
+        * @param FileRevision[] $fileRevisions
+        */
+       public function __construct( array $fileRevisions ) {
+               Assert::parameterElementType( FileRevision::class, 
$fileRevisions, '$fileRevisions' );
+               $this->fileRevisions = $fileRevisions;
+       }
+
+       /**
+        * @return FileRevision[]
+        */
+       public function toArray() {
+               return $this->fileRevisions;
+       }
+
+       /**
+        * @return FileRevision|null
+        */
+       public function getLatest() {
+               if ( $this->latestKey === null ) {
+                       $this->calculateLatestKey();
+               }
+
+               return $this->latestKey !== null ? 
$this->fileRevisions[$this->latestKey] : null;
+       }
+
+       private function calculateLatestKey() {
+               $latestTimestamp = 0;
+               foreach ( $this->fileRevisions as $key => $fileRevision ) {
+                       $fileTimestamp = strtotime( $fileRevision->getField( 
'timestamp' ) );
+                       if ( $latestTimestamp < $fileTimestamp ) {
+                               $latestTimestamp = $fileTimestamp;
+                               $this->latestKey = $key;
+                       }
+               }
+       }
+
+}
diff --git a/src/Generic/Data/ImportDetails.php 
b/src/Generic/Data/ImportDetails.php
index 1482a3a..16b0359 100644
--- a/src/Generic/Data/ImportDetails.php
+++ b/src/Generic/Data/ImportDetails.php
@@ -2,9 +2,6 @@
 
 namespace FileImporter\Generic\Data;
 
-use FileImporter\Generic\Data\FileRevision;
-use FileImporter\Generic\Data\TargetUrl;
-use FileImporter\Generic\Data\TextRevision;
 use Wikimedia\Assert\Assert;
 
 class ImportDetails {
@@ -30,7 +27,7 @@
        private $textRevisions;
 
        /**
-        * @var FileRevision[]
+        * @var FileRevisions
         */
        private $fileRevisions;
 
@@ -39,19 +36,18 @@
         * @param string $titleText
         * @param string $imageDisplayUrl
         * @param TextRevision[] $textRevisions
-        * @param FileRevision[] $fileRevisions
+        * @param FileRevisions $fileRevisions
         */
        public function __construct(
                TargetUrl $targetUrl,
                $titleText,
                $imageDisplayUrl,
                array $textRevisions,
-               array $fileRevisions
+               FileRevisions $fileRevisions
        ) {
                Assert::parameterType( 'string', $titleText, '$titleText' );
                Assert::parameterType( 'string', $imageDisplayUrl, 
'$imageDisplayUrl' );
                Assert::parameterElementType( TextRevision::class, 
$textRevisions, '$textRevisions' );
-               Assert::parameterElementType( FileRevision::class, 
$fileRevisions, '$fileRevisions' );
 
                $this->targetUrl = $targetUrl;
                $this->titleText = $titleText;
@@ -94,14 +90,14 @@
                $hashes = [
                        sha1( $this->targetUrl->getUrl() ),
                        sha1( count( $this->getTextRevisions() ) ),
-                       sha1( count( $this->getFileRevisions() ) ),
+                       sha1( count( $this->getFileRevisions()->toArray() ) ),
                ];
 
                foreach ( $this->getTextRevisions() as $textRevision ) {
                        $hashes[] = $textRevision->getField( 'sha1' );
                }
 
-               foreach ( $this->getFileRevisions() as $fileRevision ) {
+               foreach ( $this->getFileRevisions()->toArray() as $fileRevision 
) {
                        $hashes[] = $fileRevision->getField( 'sha1' );
                }
 
diff --git a/src/Generic/Services/DuplicateFileRevisionChecker.php 
b/src/Generic/Services/DuplicateFileRevisionChecker.php
new file mode 100644
index 0000000..ca01f58
--- /dev/null
+++ b/src/Generic/Services/DuplicateFileRevisionChecker.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace FileImporter\Generic\Services;
+
+use File;
+use FileImporter\Generic\Data\FileRevision;
+use LocalRepo;
+use Wikimedia\Assert\Assert;
+
+/**
+ * Class that can be used to check if a FileRevision already exists on the 
current wiki.
+ * Only current / latest and non deleted files are checked.
+ */
+class DuplicateFileRevisionChecker {
+
+       /**
+        * @var LocalRepo
+        */
+       private $localRepo;
+
+       public function __construct( LocalRepo $localRepo ) {
+               $this->localRepo = $localRepo;
+       }
+
+       /**
+        * @param FileRevision $fileRevision
+        *
+        * @return File[] array of matched files
+        */
+       public function findDuplicates( FileRevision $fileRevision ) {
+               $files = $this->localRepo->findBySha1( $fileRevision->getField( 
'sha1' ) );
+               $files = $this->removeIgnoredFiles( $files );
+
+               return $files;
+       }
+
+       /**
+        * This removed removes files that are either old or deleted.
+        *
+        * @param File[] $files
+        *
+        * @return File[]
+        */
+       private function removeIgnoredFiles( array $files ) {
+               $wantedFiles = [];
+
+               foreach ( $files as $file ) {
+                       if (
+                               $file->isOld() ||
+                               $file->isDeleted( File::DELETED_FILE )
+                       ) {
+                               continue;
+                       }
+                       $wantedFiles[] = $file;
+               }
+
+               return $wantedFiles;
+       }
+
+}
diff --git a/src/MediaWiki/ApiDetailRetriever.php 
b/src/MediaWiki/ApiDetailRetriever.php
index db178b0..80c31a0 100644
--- a/src/MediaWiki/ApiDetailRetriever.php
+++ b/src/MediaWiki/ApiDetailRetriever.php
@@ -2,6 +2,7 @@
 
 namespace FileImporter\MediaWiki;
 
+use FileImporter\Generic\Data\FileRevisions;
 use FileImporter\Generic\Exceptions\HttpRequestException;
 use FileImporter\Generic\Exceptions\ImportException;
 use FileImporter\Generic\Data\FileRevision;
@@ -126,7 +127,7 @@
                $importDetails = new ImportDetails(
                        $targetUrl,
                        $normalizationData['to'],
-                       $fileRevisions[0]->getField( 'thumburl' ),
+                       $fileRevisions->getLatest()->getField( 'thumburl' ),
                        $textRevisions,
                        $fileRevisions
                );
@@ -137,14 +138,21 @@
        /**
         * @param array $imageInfo
         *
-        * @return FileRevision[]
+        * @return FileRevisions
         */
        private function getFileRevisionsFromImageInfo( array $imageInfo ) {
                $revisions = [];
                foreach ( $imageInfo as $revisionInfo ) {
+                       /**
+                        * Convert from API sha1 format to DB sha1 format.
+                        * The conversion can be se inside ApiQueryImageInfo.
+                        *  - API sha1 format is base 16 padded to 40 chars
+                        *  - DB sha1 format is base 36 padded to 31 chars
+                        */
+                       $revisionInfo['sha1'] = \Wikimedia\base_convert( 
$revisionInfo['sha1'], 16, 36, 31 );
                        $revisions[] = new FileRevision( $revisionInfo );
                }
-               return $revisions;
+               return new FileRevisions( $revisions );
        }
 
        /**
diff --git a/src/ServiceWiring.php b/src/ServiceWiring.php
index 38a1e02..4030ea5 100644
--- a/src/ServiceWiring.php
+++ b/src/ServiceWiring.php
@@ -3,10 +3,12 @@
 namespace FileImporter;
 
 use FileImporter\Generic\Services\DispatchingDetailRetriever;
+use FileImporter\Generic\Services\DuplicateFileRevisionChecker;
 use FileImporter\Generic\Services\HttpRequestExecutor;
 use MediaWiki\Logger\LoggerFactory;
 use MediaWiki\MediaWikiServices;
 use Psr\Log\LoggerInterface;
+use RepoGroup;
 
 return [
 
@@ -29,6 +31,11 @@
                return $service;
        },
 
+       'FileImporterDuplicateFileRevisionChecker' => function( 
MediaWikiServices $services ) {
+               $localRepo = RepoGroup::singleton()->getLocalRepo();
+               return new DuplicateFileRevisionChecker( $localRepo );
+       },
+
        // MediaWiki
 
        'FileImporterMediaWikiHttpApiLookup' => function( MediaWikiServices 
$services ) {
diff --git a/src/SpecialImportFile.php b/src/SpecialImportFile.php
index 8b94d0e..08a7d0c 100644
--- a/src/SpecialImportFile.php
+++ b/src/SpecialImportFile.php
@@ -2,9 +2,11 @@
 
 namespace FileImporter;
 
+use File;
 use FileImporter\Generic\Data\ImportTransformations;
 use FileImporter\Generic\Data\ImportDetails;
 use FileImporter\Generic\Services\DetailRetriever;
+use FileImporter\Generic\Services\DuplicateFileRevisionChecker;
 use FileImporter\Generic\Services\Importer;
 use FileImporter\Generic\Data\TargetUrl;
 use Html;
@@ -49,7 +51,15 @@
                        $this->showUrlEntryPage();
                } else {
                        $importDetails = $detailRetriever->getImportDetails( 
$targetUrl );
-                       if ( $wasPosted ) {
+                       /** @var DuplicateFileRevisionChecker 
$duplicateFileChecker */
+                       $duplicateFileChecker = MediaWikiServices::getInstance()
+                               ->getService( 
'FileImporterDuplicateFileRevisionChecker' );
+                       $duplicateFiles = $duplicateFileChecker->findDuplicates(
+                               $importDetails->getFileRevisions()->getLatest()
+                       );
+                       if ( !empty( $duplicateFiles ) ) {
+                               $this->showDuplicateFilesDetectedMessage( 
$duplicateFiles );
+                       } elseif ( $wasPosted ) {
                                $this->doImport( $importDetails );
                        } else {
                                $this->showImportPage( $importDetails );
@@ -97,6 +107,20 @@
                $this->showWarningMessage( ( new Message( 
'fileimporter-cantimporturl' ) )->plain() );
        }
 
+       /**
+        * @param File[] $duplicateFiles
+        */
+       private function showDuplicateFilesDetectedMessage( array 
$duplicateFiles ) {
+               $this->showWarningMessage(
+                       ( new Message( 'fileimporter-duplicatefilesdetected' ) 
)->plain()
+               );
+               $duplicatesMessage = ( new Message( 
'fileimporter-duplicatefilesdetected-prefix' ) )->plain();
+               $this->getOutput()->addWikiText( '\'\'\'' . $duplicatesMessage 
. '\'\'\'' );
+               foreach ( $duplicateFiles as $file ) {
+                       $this->getOutput()->addWikiText( '* [[:' . 
$file->getTitle() . ']]' );
+               }
+       }
+
        private function showWarningMessage( $message ) {
                $this->getOutput()->addHTML(
                        Html::rawElement(
@@ -140,6 +164,7 @@
                                $importDetails->getTitleText()
                        )
                );
+
                $out->addHTML(
                        Html::element(
                                'p',
@@ -153,7 +178,7 @@
                                'p',
                                [],
                                ( new Message( 
'fileimporter-filerevisionsprefix' ) )->plain() . ': ' .
-                                       count( 
$importDetails->getFileRevisions() )
+                                       count( 
$importDetails->getFileRevisions()->toArray() )
                        )
                );
                $out->addHTML(
diff --git a/tests/Generic/Data/FileRevisionsTest.php 
b/tests/Generic/Data/FileRevisionsTest.php
new file mode 100644
index 0000000..9365aa3
--- /dev/null
+++ b/tests/Generic/Data/FileRevisionsTest.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace FileImporter\Generic\Data\Test;
+
+use FileImporter\Generic\Data\FileRevision;
+use FileImporter\Generic\Data\FileRevisions;
+use PHPUnit_Framework_TestCase;
+
+class FileRevisionsTest extends PHPUnit_Framework_TestCase {
+
+       private function getMockFileRevision( $timestamp ) {
+               $mock = $this->getMockBuilder( FileRevision::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $mock->expects( $this->any() )
+                       ->method( 'getField' )
+                       ->with( 'timestamp' )
+                       ->will( $this->returnValue( $timestamp ) );
+               return $mock;
+       }
+
+       public function provideGetLatest() {
+               $firstFileRevision = $this->getMockFileRevision( 
'2013-11-18T13:19:01Z' );
+               $secondFileRevision = $this->getMockFileRevision( 
'2014-11-18T13:19:01Z' );
+               $thirdFileRevision = $this->getMockFileRevision( 
'2015-11-18T13:19:01Z' );
+               return [
+                       [ [], null ],
+                       [ [ $firstFileRevision ], $firstFileRevision ],
+                       [ [ $secondFileRevision ], $secondFileRevision ],
+                       [ [ $firstFileRevision, $secondFileRevision ], 
$secondFileRevision ],
+                       [ [ $secondFileRevision, $firstFileRevision ], 
$secondFileRevision ],
+                       [ [ $secondFileRevision, $firstFileRevision, 
$thirdFileRevision ], $thirdFileRevision ],
+                       [ [ $thirdFileRevision, $firstFileRevision, 
$secondFileRevision ], $thirdFileRevision ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideGetLatest
+        */
+       public function testGetLatest( array $fileRevisions, $expected ) {
+               $fileRevisionsObject = new FileRevisions( $fileRevisions );
+               $this->assertSame( $expected, $fileRevisionsObject->getLatest() 
);
+       }
+
+}

-- 
To view, visit https://gerrit.wikimedia.org/r/342845
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I351049b934f23817f41903292b8c75e69e352286
Gerrit-PatchSet: 8
Gerrit-Project: mediawiki/extensions/FileImporter
Gerrit-Branch: master
Gerrit-Owner: Addshore <[email protected]>
Gerrit-Reviewer: Andrew-WMDE <[email protected]>
Gerrit-Reviewer: Siebrand <[email protected]>
Gerrit-Reviewer: Tobias Gritschacher <[email protected]>
Gerrit-Reviewer: WMDE-Fisch <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to