Merge "Make sure that SQLite uses no prefix"
[lhc/web/wiklou.git] / includes / filerepo / FileRepo.php
index 30d6825..651ee27 100644 (file)
@@ -127,6 +127,9 @@ class FileRepo {
                        if ( !isset( $this->zones[$zone]['directory'] ) ) {
                                $this->zones[$zone]['directory'] = '';
                        }
+                       if ( !isset( $this->zones[$zone]['urlsByExt'] ) ) {
+                               $this->zones[$zone]['urlsByExt'] = array();
+                       }
                }
        }
 
@@ -196,14 +199,17 @@ class FileRepo {
        /**
         * Get the URL corresponding to one of the four basic zones
         *
-        * @param $zone String: one of: public, deleted, temp, thumb
+        * @param $zone String One of: public, deleted, temp, thumb
+        * @param $ext String|null Optional file extension
         * @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
+       public function getZoneUrl( $zone, $ext = null ) {
+               if ( in_array( $zone, array( 'public', 'temp', 'thumb' ) ) ) { // standard public zones
+                       if ( $ext !== null && isset( $this->zones[$zone]['urlsByExt'][$ext] ) ) {
+                               return $this->zones[$zone]['urlsByExt'][$ext]; // custom URL for extension/zone
+                       } elseif ( isset( $this->zones[$zone]['url'] ) ) {
+                               return $this->zones[$zone]['url']; // custom URL for zone
+                       }
                }
                switch ( $zone ) {
                        case 'public':
@@ -696,7 +702,7 @@ class FileRepo {
        /**
         * Store a file to a given destination.
         *
-        * @param $srcPath String: source FS path, storage path, or virtual URL
+        * @param $srcPath String: source file system path, storage path, or virtual URL
         * @param $dstZone String: destination zone
         * @param $dstRel String: destination relative path
         * @param $flags Integer: bitwise combination of the following flags:
@@ -841,7 +847,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 string File system path
+        * @param $src string Source file system path, storage path, or virtual URL
         * @param $dst string Virtual URL or storage path
         * @param $disposition string|null Content-Disposition if given and supported
         * @return FileRepoStatus
@@ -883,22 +889,24 @@ 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.
         *
+        * All path parameters may be a file system path, storage path, or virtual URL.
         * When "dispositions" are given they are used as Content-Disposition if supported.
         *
-        * @param $pairs Array List of tuples (file system path, virtual URL/storage path, disposition)
+        * @param $triples Array List of (source path, destination path, disposition)
         * @return FileRepoStatus
         */
-       public function quickImportBatch( array $pairs ) {
+       public function quickImportBatch( array $triples ) {
                $status = $this->newGood();
                $operations = array();
-               foreach ( $pairs as $pair ) {
-                       list ( $src, $dst ) = $pair;
+               foreach ( $triples as $triple ) {
+                       list( $src, $dst ) = $triple;
+                       $src = $this->resolveToStoragePath( $src );
                        $dst = $this->resolveToStoragePath( $dst );
                        $operations[] = array(
-                               'op'          => 'store',
+                               'op'          => FileBackend::isStoragePath( $src ) ? 'copy' : 'store',
                                'src'         => $src,
                                'dst'         => $dst,
-                               'disposition' => isset( $pair[2] ) ? $pair[2] : null
+                               'disposition' => isset( $triple[2] ) ? $triple[2] : null
                        );
                        $status->merge( $this->initDirectory( dirname( $dst ) ) );
                }
@@ -1014,12 +1022,12 @@ class FileRepo {
 
        /**
         * Copy or move a file either from a storage path, virtual URL,
-        * or FS path, into this repository at the specified destination location.
+        * or file system path, into this repository at the specified destination location.
         *
         * Returns a FileRepoStatus object. On success, the value contains "new" or
         * "archived", to indicate whether the file was new with that name.
         *
-        * @param $srcPath String: the source FS path, storage path, or URL
+        * @param $srcPath String: the source file system path, storage path, or URL
         * @param $dstRel String: the destination relative path
         * @param $archiveRel String: the relative path where the existing file is to
         *        be archived, if there is one. Relative to the public zone root.
@@ -1092,47 +1100,42 @@ class FileRepo {
                                return $this->newFatal( 'directorycreateerror', $archiveDir );
                        }
 
-                       // Archive destination file if it exists
-                       if ( $backend->fileExists( array( 'src' => $dstPath ) ) ) {
-                               // Check if the archive file exists
-                               // This is a sanity check to avoid data loss. In UNIX, the rename primitive
-                               // unlinks the destination file if it exists. DB-based synchronisation in
-                               // publishBatch's caller should prevent races. In Windows there's no
-                               // problem because the rename primitive fails if the destination exists.
-                               if ( $backend->fileExists( array( 'src' => $archivePath ) ) ) {
-                                       $operations[] = array( 'op' => 'null' );
-                                       continue;
-                               } else {
-                                       $operations[] = array(
-                                               'op'           => 'move',
-                                               'src'          => $dstPath,
-                                               'dst'          => $archivePath
-                                       );
-                               }
-                               $status->value[$i] = 'archived';
-                       } else {
-                               $status->value[$i] = 'new';
-                       }
+                       // Archive destination file if it exists.
+                       // This will check if the archive file also exists and fail if does.
+                       // This is a sanity check to avoid data loss. On Windows and Linux,
+                       // copy() will overwrite, so the existence check is vulnerable to
+                       // race conditions unless an functioning LockManager is used.
+                       // LocalFile also uses SELECT FOR UPDATE for synchronization.
+                       $operations[] = array(
+                               'op'                  => 'copy',
+                               'src'                 => $dstPath,
+                               'dst'                 => $archivePath,
+                               'ignoreMissingSource' => true
+                       );
+
                        // Copy (or move) the source file to the destination
                        if ( FileBackend::isStoragePath( $srcPath ) ) {
                                if ( $flags & self::DELETE_SOURCE ) {
                                        $operations[] = array(
                                                'op'           => 'move',
                                                'src'          => $srcPath,
-                                               'dst'          => $dstPath
+                                               'dst'          => $dstPath,
+                                               'overwrite'    => true // replace current
                                        );
                                } else {
                                        $operations[] = array(
                                                'op'           => 'copy',
                                                'src'          => $srcPath,
-                                               'dst'          => $dstPath
+                                               'dst'          => $dstPath,
+                                               'overwrite'    => true // replace current
                                        );
                                }
                        } else { // FS source path
                                $operations[] = array(
                                        'op'           => 'store',
                                        'src'          => $srcPath,
-                                       'dst'          => $dstPath
+                                       'dst'          => $dstPath,
+                                       'overwrite'    => true // replace current
                                );
                                if ( $flags & self::DELETE_SOURCE ) {
                                        $sourceFSFilesToDelete[] = $srcPath;
@@ -1141,8 +1144,17 @@ class FileRepo {
                }
 
                // Execute the operations for each triplet
-               $opts = array( 'force' => true );
-               $status->merge( $backend->doOperations( $operations, $opts ) );
+               $status->merge( $backend->doOperations( $operations ) );
+               // Find out which files were archived...
+               foreach ( $triplets as $i => $triplet ) {
+                       list( $srcPath, $dstRel, $archiveRel ) = $triplet;
+                       $archivePath = $this->getZonePath( 'public' ) . "/$archiveRel";
+                       if ( $this->fileExists( $archivePath ) ) {
+                               $status->value[$i] = 'archived';
+                       } else {
+                               $status->value[$i] = 'new';
+                       }
+               }
                // Cleanup for disk source files...
                foreach ( $sourceFSFilesToDelete as $file ) {
                        wfSuppressWarnings();
@@ -1316,9 +1328,13 @@ class FileRepo {
         * e.g. s/z/a/ for sza251lrxrc1jad41h5mgilp8nysje52.jpg
         *
         * @param $key string
+        * @throws MWException
         * @return string
         */
        public function getDeletedHashPath( $key ) {
+               if ( strlen( $key ) < 31 ) {
+                       throw new MWException( "Invalid storage key '$key'." );
+               }
                $path = '';
                for ( $i = 0; $i < $this->deletedHashLevels; $i++ ) {
                        $path .= $key[$i] . '/';