diff options
Diffstat (limited to 'includes/filerepo/FileRepo.php')
-rw-r--r-- | includes/filerepo/FileRepo.php | 253 |
1 files changed, 157 insertions, 96 deletions
diff --git a/includes/filerepo/FileRepo.php b/includes/filerepo/FileRepo.php index a31b148a..366dd8a5 100644 --- a/includes/filerepo/FileRepo.php +++ b/includes/filerepo/FileRepo.php @@ -120,13 +120,16 @@ class FileRepo { $this->isPrivate = !empty( $info['isPrivate'] ); // Give defaults for the basic zones... $this->zones = isset( $info['zones'] ) ? $info['zones'] : array(); - foreach ( array( 'public', 'thumb', 'temp', 'deleted' ) as $zone ) { + foreach ( array( 'public', 'thumb', 'transcoded', 'temp', 'deleted' ) as $zone ) { if ( !isset( $this->zones[$zone]['container'] ) ) { $this->zones[$zone]['container'] = "{$this->name}-{$zone}"; } if ( !isset( $this->zones[$zone]['directory'] ) ) { $this->zones[$zone]['directory'] = ''; } + if ( !isset( $this->zones[$zone]['urlsByExt'] ) ) { + $this->zones[$zone]['urlsByExt'] = array(); + } } } @@ -152,7 +155,7 @@ class FileRepo { /** * Check if a single zone or list of zones is defined for usage * - * @param $doZones Array Only do a particular zones + * @param array $doZones Only do a particular zones * @throws MWException * @return Status */ @@ -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 string $zone One of: public, deleted, temp, thumb + * @param string|null $ext 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', 'transcoded' ) ) ) { // 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': @@ -214,6 +220,8 @@ class FileRepo { return false; // no public URL case 'thumb': return $this->thumbUrl; + case 'transcoded': + return "{$this->url}/transcoded"; default: return false; } @@ -229,12 +237,12 @@ class FileRepo { * from the URL path, one can configure thumb_handler.php to recognize a special path on the * same host name as the wiki that is used for viewing thumbnails. * - * @param $zone String: one of: public, deleted, temp, thumb + * @param string $zone one of: public, deleted, temp, thumb * @return String or false */ public function getZoneHandlerUrl( $zone ) { if ( isset( $this->zones[$zone]['handlerUrl'] ) - && in_array( $zone, array( 'public', 'temp', 'thumb' ) ) ) + && in_array( $zone, array( 'public', 'temp', 'thumb', 'transcoded' ) ) ) { return $this->zones[$zone]['handlerUrl']; } @@ -332,7 +340,7 @@ class FileRepo { * version control should return false if the time is specified. * * @param $title Mixed: Title object or string - * @param $options array Associative array of options: + * @param array $options Associative array of options: * time: requested time for a specific file version, or false for the * current version. An image object will be returned which was * created at the specified time (which may be archived or current). @@ -391,7 +399,7 @@ class FileRepo { /** * Find many files at once. * - * @param $items array An array of titles, or an array of findFile() options with + * @param array $items An array of titles, or an array of findFile() options with * the "title" option giving the title. Example: * * $findItem = array( 'title' => $title, 'private' => true ); @@ -423,8 +431,8 @@ class FileRepo { * Returns false if the file does not exist. Repositories not supporting * version control should return false if the time is specified. * - * @param $sha1 String base 36 SHA-1 hash - * @param $options array Option array, same as findFile(). + * @param string $sha1 base 36 SHA-1 hash + * @param array $options Option array, same as findFile(). * @return File|bool False on failure */ public function findFileFromKey( $sha1, $options = array() ) { @@ -468,7 +476,7 @@ class FileRepo { * 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 + * @param array $hashes 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 ) { @@ -483,6 +491,18 @@ class FileRepo { } /** + * Return an array of files where the name starts with $prefix. + * + * STUB + * @param string $prefix The prefix to search for + * @param int $limit The maximum amount of files to return + * @return array + */ + public function findFilesByPrefix( $prefix, $limit ) { + return array(); + } + + /** * Get the public root URL of the repository * * @deprecated since 1.20 @@ -542,7 +562,7 @@ class FileRepo { * Get a relative path including trailing slash, e.g. f/fa/ * If the repo is not hashed, returns an empty string * - * @param $name string Name of file + * @param string $name Name of file * @return string */ public function getHashPath( $name ) { @@ -553,7 +573,7 @@ class FileRepo { * Get a relative path including trailing slash, e.g. f/fa/ * If the repo is not hashed, returns an empty string * - * @param $suffix string Basename of file from FileRepo::storeTemp() + * @param string $suffix Basename of file from FileRepo::storeTemp() * @return string */ public function getTempHashPath( $suffix ) { @@ -602,7 +622,7 @@ class FileRepo { * Make an url to this repo * * @param $query mixed Query string to append - * @param $entry string Entry point; defaults to index + * @param string $entry Entry point; defaults to index * @return string|bool False on failure */ public function makeUrl( $query = '', $entry = 'index' ) { @@ -656,8 +676,8 @@ class FileRepo { * repository's file class, since it may return invalid results. User code * should use File::getDescriptionText(). * - * @param $name String: name of image to fetch - * @param $lang String: language to fetch it in, if any. + * @param string $name name of image to fetch + * @param string $lang language to fetch it in, if any. * @return string */ public function getDescriptionRenderUrl( $name, $lang = null ) { @@ -688,7 +708,7 @@ class FileRepo { public function getDescriptionStylesheetUrl() { if ( isset( $this->scriptDirUrl ) ) { return $this->makeUrl( 'title=MediaWiki:Filepage.css&' . - wfArrayToCGI( Skin::getDynamicStylesheetQuery() ) ); + wfArrayToCgi( Skin::getDynamicStylesheetQuery() ) ); } return false; } @@ -696,9 +716,9 @@ class FileRepo { /** * Store a file to a given destination. * - * @param $srcPath String: source file system path, storage path, or virtual URL - * @param $dstZone String: destination zone - * @param $dstRel String: destination relative path + * @param string $srcPath source file system path, storage path, or virtual URL + * @param string $dstZone destination zone + * @param string $dstRel destination relative path * @param $flags Integer: bitwise combination of the following flags: * self::DELETE_SOURCE Delete the source file after upload * self::OVERWRITE Overwrite an existing destination file instead of failing @@ -721,7 +741,7 @@ class FileRepo { /** * Store a batch of files * - * @param $triplets Array: (src, dest zone, dest rel) triplets as per store() + * @param array $triplets (src, dest zone, dest rel) triplets as per store() * @param $flags Integer: bitwise combination of the following flags: * self::DELETE_SOURCE Delete the source file after upload * self::OVERWRITE Overwrite an existing destination file instead of failing @@ -755,7 +775,7 @@ class FileRepo { throw new MWException( 'Validation error in $dstRel' ); } $dstPath = "$root/$dstRel"; - $dstDir = dirname( $dstPath ); + $dstDir = dirname( $dstPath ); // Create destination directories for this triplet if ( !$this->initDirectory( $dstDir )->isOK() ) { return $this->newFatal( 'directorycreateerror', $dstDir ); @@ -803,7 +823,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 $files array List of files to delete + * @param array $files 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,9 +861,9 @@ 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 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 + * @param string $src Source file system path, storage path, or virtual URL + * @param string $dst Virtual URL or storage path + * @param string|null $disposition Content-Disposition if given and supported * @return FileRepoStatus */ final public function quickImport( $src, $dst, $disposition = null ) { @@ -855,7 +875,7 @@ class FileRepo { * This function can be used to write to otherwise read-only foreign repos. * This is intended for purging thumbnails. * - * @param $path string Virtual URL or storage path + * @param string $path Virtual URL or storage path * @return FileRepoStatus */ final public function quickPurge( $path ) { @@ -866,7 +886,7 @@ class FileRepo { * Deletes a directory if empty. * This function can be used to write to otherwise read-only foreign repos. * - * @param $dir string Virtual URL (or storage path) of directory to clean + * @param string $dir Virtual URL (or storage path) of directory to clean * @return Status */ public function quickCleanDir( $dir ) { @@ -886,7 +906,7 @@ class FileRepo { * 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 $triples Array List of (source path, destination path, disposition) + * @param array $triples List of (source path, destination path, disposition) * @return FileRepoStatus */ public function quickImportBatch( array $triples ) { @@ -914,7 +934,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 $paths Array List of virtual URLs or storage paths + * @param array $paths List of virtual URLs or storage paths * @return FileRepoStatus */ public function quickPurgeBatch( array $paths ) { @@ -937,19 +957,18 @@ class FileRepo { * Returns a FileRepoStatus object with the file Virtual URL in the value, * file can later be disposed using FileRepo::freeTemp(). * - * @param $originalName String: the base name of the file as specified + * @param string $originalName the base name of the file as specified * by the user. The file extension will be maintained. - * @param $srcPath String: the current location of the file. + * @param string $srcPath the current location of the file. * @return FileRepoStatus object with the URL in the value. */ public function storeTemp( $originalName, $srcPath ) { $this->assertWritableRepo(); // fail out if read-only - $date = gmdate( "YmdHis" ); - $hashPath = $this->getHashPath( $originalName ); - $dstRel = "{$hashPath}{$date}!{$originalName}"; - $dstUrlRel = $hashPath . $date . '!' . rawurlencode( $originalName ); - $virtualUrl = $this->getVirtualUrl( 'temp' ) . '/' . $dstUrlRel; + $date = gmdate( "YmdHis" ); + $hashPath = $this->getHashPath( $originalName ); + $dstUrlRel = $hashPath . $date . '!' . rawurlencode( $originalName ); + $virtualUrl = $this->getVirtualUrl( 'temp' ) . '/' . $dstUrlRel; $result = $this->quickImport( $srcPath, $virtualUrl ); $result->value = $virtualUrl; @@ -960,7 +979,7 @@ class FileRepo { /** * Remove a temporary file or mark it for garbage collection * - * @param $virtualUrl String: the virtual URL returned by FileRepo::storeTemp() + * @param string $virtualUrl the virtual URL returned by FileRepo::storeTemp() * @return Boolean: true on success, false on failure */ public function freeTemp( $virtualUrl ) { @@ -978,8 +997,8 @@ class FileRepo { /** * Concatenate a list of temporary files into a target file location. * - * @param $srcPaths Array Ordered list of source virtual URLs/storage paths - * @param $dstPath String Target file system path + * @param array $srcPaths Ordered list of source virtual URLs/storage paths + * @param string $dstPath Target file system path * @param $flags Integer: bitwise combination of the following flags: * self::DELETE_SOURCE Delete the source files * @return FileRepoStatus @@ -1021,18 +1040,25 @@ class FileRepo { * 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 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 + * Options to $options include: + * - headers : name/value map of HTTP headers to use in response to GET/HEAD requests + * + * @param string $srcPath the source file system path, storage path, or URL + * @param string $dstRel the destination relative path + * @param string $archiveRel the relative path where the existing file is to * be archived, if there is one. Relative to the public zone root. * @param $flags Integer: bitfield, may be FileRepo::DELETE_SOURCE to indicate * that the source file should be deleted if possible + * @param array $options Optional additional parameters * @return FileRepoStatus */ - public function publish( $srcPath, $dstRel, $archiveRel, $flags = 0 ) { + public function publish( + $srcPath, $dstRel, $archiveRel, $flags = 0, array $options = array() + ) { $this->assertWritableRepo(); // fail out if read-only - $status = $this->publishBatch( array( array( $srcPath, $dstRel, $archiveRel ) ), $flags ); + $status = $this->publishBatch( + array( array( $srcPath, $dstRel, $archiveRel, $options ) ), $flags ); if ( $status->successCount == 0 ) { $status->ok = false; } @@ -1048,13 +1074,14 @@ class FileRepo { /** * Publish a batch of files * - * @param $triplets Array: (source, dest, archive) triplets as per publish() + * @param array $ntuples (source, dest, archive) triplets or + * (source, dest, archive, options) 4-tuples 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 ) { + public function publishBatch( array $ntuples, $flags = 0 ) { $this->assertWritableRepo(); // fail out if read-only $backend = $this->backend; // convenience @@ -1069,8 +1096,9 @@ class FileRepo { $operations = array(); $sourceFSFilesToDelete = array(); // cleanup for disk source files // Validate each triplet and get the store operation... - foreach ( $triplets as $i => $triplet ) { - list( $srcPath, $dstRel, $archiveRel ) = $triplet; + foreach ( $ntuples as $ntuple ) { + list( $srcPath, $dstRel, $archiveRel ) = $ntuple; + $options = isset( $ntuple[3] ) ? $ntuple[3] : array(); // Resolve source to a storage path if virtual $srcPath = $this->resolveToStoragePath( $srcPath ); if ( !$this->validateFilename( $dstRel ) ) { @@ -1090,51 +1118,52 @@ class FileRepo { if ( !$this->initDirectory( $dstDir )->isOK() ) { return $this->newFatal( 'directorycreateerror', $dstDir ); } - if ( !$this->initDirectory($archiveDir )->isOK() ) { + if ( !$this->initDirectory( $archiveDir )->isOK() ) { 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'; - } + // Set any desired headers to be use in GET/HEAD responses + $headers = isset( $options['headers'] ) ? $options['headers'] : array(); + + // 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 + 'op' => 'move', + 'src' => $srcPath, + 'dst' => $dstPath, + 'overwrite' => true, // replace current + 'headers' => $headers ); } else { $operations[] = array( - 'op' => 'copy', - 'src' => $srcPath, - 'dst' => $dstPath + 'op' => 'copy', + 'src' => $srcPath, + 'dst' => $dstPath, + 'overwrite' => true, // replace current + 'headers' => $headers ); } } else { // FS source path $operations[] = array( - 'op' => 'store', - 'src' => $srcPath, - 'dst' => $dstPath + 'op' => 'store', + 'src' => $srcPath, + 'dst' => $dstPath, + 'overwrite' => true, // replace current + 'headers' => $headers ); if ( $flags & self::DELETE_SOURCE ) { $sourceFSFilesToDelete[] = $srcPath; @@ -1143,8 +1172,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 ( $ntuples as $i => $ntuple ) { + list( , , $archiveRel ) = $ntuple; + $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(); @@ -1159,12 +1197,12 @@ class FileRepo { * 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 + * @param string $dir 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 ); + list( , $container, ) = FileBackend::splitStoragePath( $path ); $params = array( 'dir' => $path ); if ( $this->isPrivate || $container === $this->zones['deleted']['container'] ) { @@ -1179,7 +1217,7 @@ class FileRepo { /** * Deletes a directory if empty. * - * @param $dir string Virtual URL (or storage path) of directory to clean + * @param string $dir Virtual URL (or storage path) of directory to clean * @return Status */ public function cleanDir( $dir ) { @@ -1195,7 +1233,7 @@ class FileRepo { /** * Checks existence of a a file * - * @param $file string Virtual URL (or storage path) of file to check + * @param string $file Virtual URL (or storage path) of file to check * @return bool */ public function fileExists( $file ) { @@ -1206,7 +1244,7 @@ class FileRepo { /** * Checks existence of an array of files. * - * @param $files Array: Virtual URLs (or storage paths) of files to check + * @param array $files Virtual URLs (or storage paths) of files to check * @return array|bool Either array of files and existence flags, or false */ public function fileExistsBatch( array $files ) { @@ -1244,7 +1282,7 @@ class FileRepo { * assumes a naming scheme in the deleted zone based on content hash, as * opposed to the public zone which is assumed to be unique. * - * @param $sourceDestPairs Array of source/destination pairs. Each element + * @param array $sourceDestPairs of source/destination pairs. Each element * 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. @@ -1318,9 +1356,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] . '/'; @@ -1392,6 +1434,17 @@ class FileRepo { } /** + * Get the size of a file with a given virtual URL/storage path + * + * @param $virtualUrl string + * @return integer|bool False on failure + */ + public function getFileSize( $virtualUrl ) { + $path = $this->resolveToStoragePath( $virtualUrl ); + return $this->backend->getFileSize( array( 'src' => $path ) ); + } + + /** * Get the sha1 (base 36) of a file with a given virtual URL/storage path * * @param $virtualUrl string @@ -1406,7 +1459,7 @@ class FileRepo { * Attempt to stream a file with the given virtual URL/storage path * * @param $virtualUrl string - * @param $headers Array Additional HTTP headers to send on success + * @param array $headers Additional HTTP headers to send on success * @return bool Success */ public function streamFile( $virtualUrl, $headers = array() ) { @@ -1568,7 +1621,7 @@ class FileRepo { */ public function nameForThumb( $name ) { if ( strlen( $name ) > $this->abbrvThreshold ) { - $ext = FileBackend::extensionFromPath( $name ); + $ext = FileBackend::extensionFromPath( $name ); $name = ( $ext == '' ) ? 'thumbnail' : "thumbnail.$ext"; } return $name; @@ -1630,10 +1683,17 @@ class FileRepo { 'directory' => ( $this->zones['thumb']['directory'] == '' ) ? 'temp' : $this->zones['thumb']['directory'] . '/temp' + ), + 'transcoded' => array( + 'container' => $this->zones['transcoded']['container'], + 'directory' => ( $this->zones['transcoded']['directory'] == '' ) + ? 'temp' + : $this->zones['transcoded']['directory'] . '/temp' ) ), 'url' => $this->getZoneUrl( 'temp' ), 'thumbUrl' => $this->getZoneUrl( 'thumb' ) . '/temp', + 'transcodedUrl' => $this->getZoneUrl( 'transcoded' ) . '/temp', 'hashLevels' => $this->hashLevels // performance ) ); } @@ -1641,10 +1701,11 @@ class FileRepo { /** * Get an UploadStash associated with this repo. * + * @param $user User * @return UploadStash */ - public function getUploadStash() { - return new UploadStash( $this ); + public function getUploadStash( User $user = null ) { + return new UploadStash( $this, $user ); } /** |