if ( !isset( $this->zones[$zone]['directory'] ) ) {
$this->zones[$zone]['directory'] = '';
}
+ if ( !isset( $this->zones[$zone]['urlsByExt'] ) ) {
+ $this->zones[$zone]['urlsByExt'] = array();
+ }
}
}
/**
* 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':
/**
* 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:
* 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
* 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 ) ) );
}
/**
* 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.
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;
}
// 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();
* 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] . '/';