(bug 27567) Add file repo support to prop=duplicatefiles
[lhc/web/wiklou.git] / includes / filerepo / FileRepo.php
index 6533dae..5a55096 100644 (file)
@@ -60,7 +60,11 @@ class FileRepo {
        var $oldFileFactory = false;
        var $fileFactoryKey = false, $oldFileFactoryKey = false;
 
-       function __construct( array $info = null ) {
+       /**
+        * @param $info array|null
+        * @throws MWException
+        */
+       public function __construct( array $info = null ) {
                // Verify required settings presence
                if(
                        $info === null
@@ -114,11 +118,11 @@ class FileRepo {
                        : array();
                // Give defaults for the basic zones...
                foreach ( array( 'public', 'thumb', 'temp', 'deleted' ) as $zone ) {
-                       if ( !isset( $this->zones[$zone] ) ) {
-                               $this->zones[$zone] = array(
-                                       'container' => "{$this->name}-{$zone}",
-                                       'directory' => '' // container root
-                               );
+                       if ( !isset( $this->zones[$zone]['container'] ) ) {
+                               $this->zones[$zone]['container'] = "{$this->name}-{$zone}";
+                       }
+                       if ( !isset( $this->zones[$zone]['directory'] ) ) {
+                               $this->zones[$zone]['directory'] = '';
                        }
                }
        }
@@ -146,6 +150,7 @@ class FileRepo {
         * Check if a single zone or list of zones is defined for usage
         *
         * @param $doZones Array Only do a particular zones
+        * @throws MWException
         * @return Status
         */
        protected function initZones( $doZones = array() ) {
@@ -159,18 +164,6 @@ class FileRepo {
                return $status;
        }
 
-       /**
-        * Take all available measures to prevent web accessibility of new deleted
-        * directories, in case the user has not configured offline storage
-        *
-        * @param $dir string
-        * @return void
-        */
-       protected function initDeletedDir( $dir ) {
-               $this->backend->secure( // prevent web access & dir listings
-                       array( 'dir' => $dir, 'noAccess' => true, 'noListing' => true ) );
-       }
-
        /**
         * Determine if a string is an mwrepo:// URL
         *
@@ -186,7 +179,7 @@ class FileRepo {
         * The suffix, if supplied, is considered to be unencoded, and will be
         * URL-encoded before being returned.
         *
-        * @param $suffix string
+        * @param $suffix string|bool
         * @return string
         */
        public function getVirtualUrl( $suffix = false ) {
@@ -204,6 +197,11 @@ class FileRepo {
         * @return String or false
         */
        public function getZoneUrl( $zone ) {
+               if ( isset( $this->zones[$zone]['url'] )
+                       && in_array( $zone, array( 'public', 'temp', 'thumb' ) ) )
+               {
+                       return $this->zones[$zone]['url']; // custom URL
+               }
                switch ( $zone ) {
                        case 'public':
                                return $this->url;
@@ -223,6 +221,7 @@ class FileRepo {
         * Use this function wisely.
         *
         * @param $url string
+        * @throws MWException
         * @return string
         */
        public function resolveVirtualUrl( $url ) {
@@ -433,19 +432,39 @@ class FileRepo {
         * SHA-1 content hash.
         *
         * STUB
+        * @param $hash
         * @return array
         */
        public function findBySha1( $hash ) {
                return array();
        }
 
+       /**
+        * Get an array of arrays or iterators of file objects for files that
+        * have the given SHA-1 content hashes.
+        *
+        * @param $hashes array An array of hashes
+        * @return array An Array of arrays or iterators of file objects and the hash as key
+        */
+       public function findBySha1s( array $hashes ) {
+               $result = array();
+               foreach ( $hashes as $hash ) {
+                       $files = $this->findBySha1( $hash );
+                       if ( count( $files ) ) {
+                               $result[$hash] = $files;
+                       }
+               }
+               return $result;
+       }
+
        /**
         * Get the public root URL of the repository
         *
+        * @deprecated since 1.20
         * @return string
         */
        public function getRootUrl() {
-               return $this->url;
+               return $this->getZoneUrl( 'public' );
        }
 
        /**
@@ -684,6 +703,7 @@ class FileRepo {
         *     self::OVERWRITE_SAME    Overwrite the file if the destination exists and has the
         *                             same contents as the source
         *     self::SKIP_LOCKING      Skip any file locking when doing the store
+        * @throws MWException
         * @return FileRepoStatus
         */
        public function storeBatch( array $triplets, $flags = 0 ) {
@@ -712,14 +732,10 @@ class FileRepo {
                        $dstPath = "$root/$dstRel";
                        $dstDir  = dirname( $dstPath );
                        // Create destination directories for this triplet
-                       if ( !$backend->prepare( array( 'dir' => $dstDir ) )->isOK() ) {
+                       if ( !$this->initDirectory( $dstDir )->isOK() ) {
                                return $this->newFatal( 'directorycreateerror', $dstDir );
                        }
 
-                       if ( $dstZone == 'deleted' ) {
-                               $this->initDeletedDir( $dstDir );
-                       }
-
                        // Resolve source to a storage path if virtual
                        $srcPath = $this->resolveToStoragePath( $srcPath );
 
@@ -762,7 +778,7 @@ class FileRepo {
         * Each file can be a (zone, rel) pair, virtual url, storage path.
         * It will try to delete each file, but ignores any errors that may occur.
         *
-        * @param $pairs array List of files to delete
+        * @param $files array List of files to delete
         * @param $flags Integer: bitwise combination of the following flags:
         *     self::SKIP_LOCKING      Skip any file locking when doing the deletions
         * @return FileRepoStatus
@@ -841,7 +857,7 @@ class FileRepo {
         * This function can be used to write to otherwise read-only foreign repos.
         * This is intended for copying generated thumbnails into the repo.
         *
-        * @param $src Array List of tuples (file system path, virtual URL or storage path)
+        * @param $pairs Array List of tuples (file system path, virtual URL or storage path)
         * @return FileRepoStatus
         */
        public function quickImportBatch( array $pairs ) {
@@ -849,17 +865,15 @@ class FileRepo {
                $operations = array();
                foreach ( $pairs as $pair ) {
                        list ( $src, $dst ) = $pair;
+                       $dst = $this->resolveToStoragePath( $dst );
                        $operations[] = array(
                                'op'        => 'store',
                                'src'       => $src,
-                               'dst'       => $this->resolveToStoragePath( $dst ),
-                               'overwrite' => true
+                               'dst'       => $dst
                        );
-                       $this->backend->prepare( array( 'dir' => dirname( $dst ) ) );
+                       $status->merge( $this->initDirectory( dirname( $dst ) ) );
                }
-               $status->merge( $this->backend->doOperations( $operations,
-                       array( 'force' => 1, 'nonLocking' => 1, 'allowStale' => 1, 'nonJournaled' => 1 )
-               ) );
+               $status->merge( $this->backend->doQuickOperations( $operations ) );
 
                return $status;
        }
@@ -869,7 +883,7 @@ class FileRepo {
         * This function can be used to write to otherwise read-only foreign repos.
         * This does no locking nor journaling and is intended for purging thumbnails.
         *
-        * @param $path Array List of virtual URLs or storage paths
+        * @param $paths Array List of virtual URLs or storage paths
         * @return FileRepoStatus
         */
        public function quickPurgeBatch( array $paths ) {
@@ -882,9 +896,7 @@ class FileRepo {
                                'ignoreMissingSource' => true
                        );
                }
-               $status->merge( $this->backend->doOperations( $operations,
-                       array( 'force' => 1, 'nonLocking' => 1, 'allowStale' => 1, 'nonJournaled' => 1 )
-               ) );
+               $status->merge( $this->backend->doQuickOperations( $operations ) );
 
                return $status;
        }
@@ -1013,6 +1025,7 @@ class FileRepo {
         * @param $triplets Array: (source, dest, archive) triplets as per publish()
         * @param $flags Integer: bitfield, may be FileRepo::DELETE_SOURCE to indicate
         *        that the source files should be deleted if possible
+        * @throws MWException
         * @return FileRepoStatus
         */
        public function publishBatch( array $triplets, $flags = 0 ) {
@@ -1048,10 +1061,10 @@ class FileRepo {
                        $dstDir = dirname( $dstPath );
                        $archiveDir = dirname( $archivePath );
                        // Abort immediately on directory creation errors since they're likely to be repetitive
-                       if ( !$backend->prepare( array( 'dir' => $dstDir ) )->isOK() ) {
+                       if ( !$this->initDirectory( $dstDir )->isOK() ) {
                                return $this->newFatal( 'directorycreateerror', $dstDir );
                        }
-                       if ( !$backend->prepare( array( 'dir' => $archiveDir ) )->isOK() ) {
+                       if ( !$this->initDirectory($archiveDir )->isOK() ) {
                                return $this->newFatal( 'directorycreateerror', $archiveDir );
                        }
 
@@ -1116,6 +1129,27 @@ class FileRepo {
                return $status;
        }
 
+       /**
+        * Creates a directory with the appropriate zone permissions.
+        * Callers are responsible for doing read-only and "writable repo" checks.
+        *
+        * @param $dir string Virtual URL (or storage path) of directory to clean
+        * @return Status
+        */
+       protected function initDirectory( $dir ) {
+               $path = $this->resolveToStoragePath( $dir );
+               list( $b, $container, $r ) = FileBackend::splitStoragePath( $path );
+
+               $params = array( 'dir' => $path );
+               if ( $container === $this->zones['deleted']['container'] ) {
+                       # Take all available measures to prevent web accessibility of new deleted
+                       # directories, in case the user has not configured offline storage
+                       $params = array( 'noAccess' => true, 'noListing' => true ) + $params;
+               }
+
+               return $this->backend->prepare( $params );
+       }
+
        /**
         * Deletes a directory if empty.
         *
@@ -1188,6 +1222,7 @@ class FileRepo {
         *        is a two-element array containing the source file path relative to the
         *        public root in the first element, and the archive file path relative
         *        to the deleted zone root in the second element.
+        * @throws MWException
         * @return FileRepoStatus
         */
        public function deleteBatch( array $sourceDestPairs ) {
@@ -1220,10 +1255,9 @@ class FileRepo {
                        $archiveDir = dirname( $archivePath ); // does not touch FS
 
                        // Create destination directories
-                       if ( !$backend->prepare( array( 'dir' => $archiveDir ) )->isOK() ) {
+                       if ( !$this->initDirectory( $archiveDir )->isOK() ) {
                                return $this->newFatal( 'directorycreateerror', $archiveDir );
                        }
-                       $this->initDeletedDir( $archiveDir );
 
                        $operations[] = array(
                                'op'            => 'move',
@@ -1257,6 +1291,7 @@ class FileRepo {
         * Get a relative path for a deletion archive key,
         * e.g. s/z/a/ for sza251lrxrc1jad41h5mgilp8nysje52.jpg
         *
+        * @param $key string
         * @return string
         */
        public function getDeletedHashPath( $key ) {
@@ -1460,6 +1495,7 @@ class FileRepo {
        /**
         * Create a new good result
         *
+        * @param $value null|string
         * @return FileRepoStatus
         */
        public function newGood( $value = null ) {