diff options
Diffstat (limited to 'includes/filerepo')
-rw-r--r-- | includes/filerepo/FSRepo.php | 8 | ||||
-rw-r--r-- | includes/filerepo/FileRepo.php | 253 | ||||
-rw-r--r-- | includes/filerepo/ForeignAPIRepo.php | 18 | ||||
-rw-r--r-- | includes/filerepo/ForeignDBRepo.php | 2 | ||||
-rw-r--r-- | includes/filerepo/ForeignDBViaLBRepo.php | 2 | ||||
-rw-r--r-- | includes/filerepo/LocalRepo.php | 53 | ||||
-rw-r--r-- | includes/filerepo/README | 23 | ||||
-rw-r--r-- | includes/filerepo/RepoGroup.php | 24 | ||||
-rw-r--r-- | includes/filerepo/file/ArchivedFile.php | 152 | ||||
-rw-r--r-- | includes/filerepo/file/File.php | 208 | ||||
-rw-r--r-- | includes/filerepo/file/ForeignAPIFile.php | 8 | ||||
-rw-r--r-- | includes/filerepo/file/ForeignDBFile.php | 15 | ||||
-rw-r--r-- | includes/filerepo/file/LocalFile.php | 281 | ||||
-rw-r--r-- | includes/filerepo/file/OldLocalFile.php | 65 | ||||
-rw-r--r-- | includes/filerepo/file/UnregisteredLocalFile.php | 22 |
15 files changed, 743 insertions, 391 deletions
diff --git a/includes/filerepo/FSRepo.php b/includes/filerepo/FSRepo.php index 9c8d85dc..e49f37d2 100644 --- a/includes/filerepo/FSRepo.php +++ b/includes/filerepo/FSRepo.php @@ -24,9 +24,9 @@ /** * A repository for files accessible via the local filesystem. * Does not support database access or registration. - * + * * This is a mostly a legacy class. New uses should not be added. - * + * * @ingroup FileRepo * @deprecated since 1.19 */ @@ -46,6 +46,9 @@ class FSRepo extends FileRepo { $thumbDir = isset( $info['thumbDir'] ) ? $info['thumbDir'] : "{$directory}/thumb"; + $transcodedDir = isset( $info['transcodedDir'] ) + ? $info['transcodedDir'] + : "{$directory}/transcoded"; $fileMode = isset( $info['fileMode'] ) ? $info['fileMode'] : 0644; @@ -59,6 +62,7 @@ class FSRepo extends FileRepo { "{$repoName}-public" => "{$directory}", "{$repoName}-temp" => "{$directory}/temp", "{$repoName}-thumb" => $thumbDir, + "{$repoName}-transcoded" => $transcodedDir, "{$repoName}-deleted" => $deletedDir ), 'fileMode' => $fileMode, 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 ); } /** diff --git a/includes/filerepo/ForeignAPIRepo.php b/includes/filerepo/ForeignAPIRepo.php index 13de9e6b..ba574da1 100644 --- a/includes/filerepo/ForeignAPIRepo.php +++ b/includes/filerepo/ForeignAPIRepo.php @@ -247,10 +247,10 @@ class ForeignAPIRepo extends FileRepo { * If the url has been requested today, get it from cache * Otherwise retrieve remote thumb url, check for local file. * - * @param $name String is a dbkey form of a title + * @param string $name is a dbkey form of a title * @param $width * @param $height - * @param String $params Other rendering parameters (page number, etc) from handler's makeParamString. + * @param string $params Other rendering parameters (page number, etc) from handler's makeParamString. * @return bool|string */ function getThumbUrlFromCache( $name, $width, $height, $params = "" ) { @@ -267,14 +267,14 @@ class ForeignAPIRepo extends FileRepo { $sizekey = "$width:$height:$params"; /* Get the array of urls that we already know */ - $knownThumbUrls = $wgMemc->get($key); + $knownThumbUrls = $wgMemc->get( $key ); if( !$knownThumbUrls ) { /* No knownThumbUrls for this file */ $knownThumbUrls = array(); } else { if( isset( $knownThumbUrls[$sizekey] ) ) { wfDebug( __METHOD__ . ': Got thumburl from local cache: ' . - "{$knownThumbUrls[$sizekey]} \n"); + "{$knownThumbUrls[$sizekey]} \n" ); return $knownThumbUrls[$sizekey]; } /* This size is not yet known */ @@ -294,9 +294,9 @@ class ForeignAPIRepo extends FileRepo { wfDebug( __METHOD__ . " The deduced filename $fileName is not safe\n" ); return false; } - $localPath = $this->getZonePath( 'thumb' ) . "/" . $this->getHashPath( $name ) . $name; + $localPath = $this->getZonePath( 'thumb' ) . "/" . $this->getHashPath( $name ) . $name; $localFilename = $localPath . "/" . $fileName; - $localUrl = $this->getZoneUrl( 'thumb' ) . "/" . $this->getHashPath( $name ) . rawurlencode( $name ) . "/" . rawurlencode( $fileName ); + $localUrl = $this->getZoneUrl( 'thumb' ) . "/" . $this->getHashPath( $name ) . rawurlencode( $name ) . "/" . rawurlencode( $fileName ); if( $backend->fileExists( array( 'src' => $localFilename ) ) && isset( $metadata['timestamp'] ) ) @@ -320,7 +320,6 @@ class ForeignAPIRepo extends FileRepo { return false; } - # @todo FIXME: Delete old thumbs that aren't being used. Maintenance script? $backend->prepare( array( 'dir' => dirname( $localFilename ) ) ); $params = array( 'dst' => $localFilename, 'content' => $thumb ); @@ -337,16 +336,17 @@ class ForeignAPIRepo extends FileRepo { /** * @see FileRepo::getZoneUrl() * @param $zone String + * @param string|null $ext Optional file extension * @return String */ - function getZoneUrl( $zone ) { + function getZoneUrl( $zone, $ext = null ) { switch ( $zone ) { case 'public': return $this->url; case 'thumb': return $this->thumbUrl; default: - return parent::getZoneUrl( $zone ); + return parent::getZoneUrl( $zone, $ext ); } } diff --git a/includes/filerepo/ForeignDBRepo.php b/includes/filerepo/ForeignDBRepo.php index 4b206c3d..18659852 100644 --- a/includes/filerepo/ForeignDBRepo.php +++ b/includes/filerepo/ForeignDBRepo.php @@ -86,7 +86,7 @@ class ForeignDBRepo extends LocalRepo { /** * Get a key on the primary cache for this repository. - * Returns false if the repository's cache is not accessible at this site. + * Returns false if the repository's cache is not accessible at this site. * The parameters are the parts of the key, as for wfMemcKey(). * @return bool|mixed */ diff --git a/includes/filerepo/ForeignDBViaLBRepo.php b/includes/filerepo/ForeignDBViaLBRepo.php index bd76fce7..7951fb13 100644 --- a/includes/filerepo/ForeignDBViaLBRepo.php +++ b/includes/filerepo/ForeignDBViaLBRepo.php @@ -61,7 +61,7 @@ class ForeignDBViaLBRepo extends LocalRepo { /** * Get a key on the primary cache for this repository. - * Returns false if the repository's cache is not accessible at this site. + * Returns false if the repository's cache is not accessible at this site. * The parameters are the parts of the key, as for wfMemcKey(). * @return bool|string */ diff --git a/includes/filerepo/LocalRepo.php b/includes/filerepo/LocalRepo.php index 0954422d..be11b233 100644 --- a/includes/filerepo/LocalRepo.php +++ b/includes/filerepo/LocalRepo.php @@ -103,8 +103,8 @@ class LocalRepo extends FileRepo { /** * Check if a deleted (filearchive) file has this sha1 key * - * @param $key String File storage key (base-36 sha1 key with file extension) - * @param $lock String|null Use "lock" to lock the row via FOR UPDATE + * @param string $key File storage key (base-36 sha1 key with file extension) + * @param string|null $lock Use "lock" to lock the row via FOR UPDATE * @return bool File with this key is in use */ protected function deletedFileHasKey( $key, $lock = null ) { @@ -120,8 +120,8 @@ class LocalRepo extends FileRepo { /** * Check if a hidden (revision delete) file has this sha1 key * - * @param $key String File storage key (base-36 sha1 key with file extension) - * @param $lock String|null Use "lock" to lock the row via FOR UPDATE + * @param string $key File storage key (base-36 sha1 key with file extension) + * @param string|null $lock Use "lock" to lock the row via FOR UPDATE * @return bool File with this key is in use */ protected function hiddenFileHasKey( $key, $lock = null ) { @@ -168,7 +168,7 @@ class LocalRepo extends FileRepo { $expiry = 86400; // has invalidation, 1 day } $cachedValue = $wgMemc->get( $memcKey ); - if ( $cachedValue === ' ' || $cachedValue === '' ) { + if ( $cachedValue === ' ' || $cachedValue === '' ) { // Does not exist return false; } elseif ( strval( $cachedValue ) !== '' ) { @@ -212,12 +212,12 @@ class LocalRepo extends FileRepo { $dbr = $this->getSlaveDB(); $id = $dbr->selectField( 'page', // Table - 'page_id', //Field - array( //Conditions + 'page_id', //Field + array( //Conditions 'page_namespace' => $title->getNamespace(), 'page_title' => $title->getDBkey(), ), - __METHOD__ //Function name + __METHOD__ //Function name ); return $id; } @@ -226,7 +226,7 @@ class LocalRepo extends FileRepo { * Get an array or iterator of file objects for files that have a given * SHA-1 content hash. * - * @param $hash String a sha1 hash to look for + * @param string $hash a sha1 hash to look for * @return Array */ function findBySha1( $hash ) { @@ -238,7 +238,7 @@ class LocalRepo extends FileRepo { __METHOD__, array( 'ORDER BY' => 'img_name' ) ); - + $result = array(); foreach ( $res as $row ) { $result[] = $this->newFileFromRow( $row ); @@ -254,7 +254,7 @@ class LocalRepo extends FileRepo { * * Overrides generic implementation in FileRepo for performance reason * - * @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 */ function findBySha1s( array $hashes ) { @@ -281,6 +281,34 @@ class LocalRepo extends FileRepo { return $result; } + /**
+ * Return an array of files where the name starts with $prefix.
+ *
+ * @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 ) { + $selectOptions = array( 'ORDER BY' => 'img_name', 'LIMIT' => intval( $limit ) ); + + // Query database
+ $dbr = $this->getSlaveDB(); + $res = $dbr->select( + 'image', + LocalFile::selectFields(), + 'img_name ' . $dbr->buildLike( $prefix, $dbr->anyString() ), + __METHOD__, + $selectOptions + ); + + // Build file objects + $files = array(); + foreach ( $res as $row ) { + $files[] = $this->newFileFromRow( $row ); + } + return $files;
+ } + /** * Get a connection to the slave DB * @return DatabaseBase @@ -299,7 +327,7 @@ class LocalRepo extends FileRepo { /** * Get a key on the primary cache for this repository. - * Returns false if the repository's cache is not accessible at this site. + * Returns false if the repository's cache is not accessible at this site. * The parameters are the parts of the key, as for wfMemcKey(). * * @return string @@ -323,4 +351,3 @@ class LocalRepo extends FileRepo { } } } - diff --git a/includes/filerepo/README b/includes/filerepo/README index 885a1ded..1423d359 100644 --- a/includes/filerepo/README +++ b/includes/filerepo/README @@ -18,10 +18,10 @@ repository-specific configuration is needed, or in static members of File or FileRepo, where no such configuration is needed. File objects are generated by a factory function from the repository. The -repository thus has full control over the behaviour of its subsidiary file +repository thus has full control over the behavior of its subsidiary file class, since it can subclass the file class and override functionality at its whim. Thus there is no need for the File subclass to query its parent repository -for information about repository-class-dependent behaviour -- the file subclass +for information about repository-class-dependent behavior -- the file subclass is generally fully aware of the static preferences of its repository. Limited exceptions can be made to this rule to permit sharing of functions, or perhaps even entire classes, between repositories. @@ -39,22 +39,3 @@ LocalRepo.php. LocalRepo provides only file access, and LocalFile provides database access and higher-level functions such as cache management. Tim Starling, June 2007 - -Structure: - -File defines an abstract class File. - ForeignAPIFile extends File. - LocalFile extends File. - ForeignDBFile extends LocalFile - Image extends LocalFile - UnregisteredLocalFile extends File. - UploadStashFile extends UnregisteredLocalFile. -FileRepo defines an abstract class FileRepo. - ForeignAPIRepo extends FileRepo - FSRepo extends FileRepo - LocalRepo extends FSRepo - ForeignDBRepo extends LocalRepo - ForeignDBViaLBRepo extends LocalRepo - NullRepo extends FileRepo - -Russ Nelson, March 2011 diff --git a/includes/filerepo/RepoGroup.php b/includes/filerepo/RepoGroup.php index f9e57599..02dfdad6 100644 --- a/includes/filerepo/RepoGroup.php +++ b/includes/filerepo/RepoGroup.php @@ -79,8 +79,8 @@ class RepoGroup { /** * Construct a group of file repositories. * - * @param $localInfo array Associative array for local repo's info - * @param $foreignInfo Array of repository info arrays. + * @param array $localInfo Associative array for local repo's info + * @param array $foreignInfo of repository info arrays. * Each info array is an associative array with the 'class' member * giving the class name. The entire array is passed to the repository * constructor as the first parameter. @@ -96,7 +96,7 @@ class RepoGroup { * You can also use wfFindFile() to do this. * * @param $title Title|string Title object or string - * @param $options array Associative array of options: + * @param array $options Associative array of options: * time: requested time for an archived image, or false for the * current version. An image object will be returned which was * created at the specified time. @@ -131,7 +131,7 @@ class RepoGroup { $time = isset( $options['time'] ) ? $options['time'] : ''; $dbkey = $title->getDBkey(); if ( isset( $this->cache[$dbkey][$time] ) ) { - wfDebug( __METHOD__.": got File:$dbkey from process cache\n" ); + wfDebug( __METHOD__ . ": got File:$dbkey from process cache\n" ); # Move it to the end of the list so that we can delete the LRU entry later $this->pingCache( $dbkey ); # Return the entry @@ -225,8 +225,8 @@ class RepoGroup { * Find an instance of the file with this key, created at the specified time * Returns false if the file does not exist. * - * @param $hash String base 36 SHA-1 hash - * @param $options array Option array, same as findFile() + * @param string $hash base 36 SHA-1 hash + * @param array $options Option array, same as findFile() * @return File object or false if it is not found */ function findFileFromKey( $hash, $options = array() ) { @@ -247,7 +247,7 @@ class RepoGroup { /** * Find all instances of files with this key * - * @param $hash String base 36 SHA-1 hash + * @param string $hash base 36 SHA-1 hash * @return Array of File objects */ function findBySha1( $hash ) { @@ -266,7 +266,7 @@ class RepoGroup { /** * Find all instances of files with this keys * - * @param $hashes Array base 36 SHA-1 hashes + * @param array $hashes base 36 SHA-1 hashes * @return Array of array of File objects */ function findBySha1s( array $hashes ) { @@ -335,7 +335,7 @@ class RepoGroup { * first parameter. * * @param $callback Callback: the function to call - * @param $params Array: optional additional parameters to pass to the function + * @param array $params optional additional parameters to pass to the function * @return bool */ function forEachForeignRepo( $callback, $params = array() ) { @@ -388,12 +388,12 @@ class RepoGroup { */ function splitVirtualUrl( $url ) { if ( substr( $url, 0, 9 ) != 'mwrepo://' ) { - throw new MWException( __METHOD__.': unknown protocol' ); + throw new MWException( __METHOD__ . ': unknown protocol' ); } $bits = explode( '/', substr( $url, 9 ), 3 ); if ( count( $bits ) != 3 ) { - throw new MWException( __METHOD__.": invalid mwrepo URL: $url" ); + throw new MWException( __METHOD__ . ": invalid mwrepo URL: $url" ); } return $bits; } @@ -433,7 +433,7 @@ class RepoGroup { while ( count( $this->cache ) >= self::MAX_CACHE_SIZE ) { reset( $this->cache ); $key = key( $this->cache ); - wfDebug( __METHOD__.": evicting $key\n" ); + wfDebug( __METHOD__ . ": evicting $key\n" ); unset( $this->cache[$key] ); } } diff --git a/includes/filerepo/file/ArchivedFile.php b/includes/filerepo/file/ArchivedFile.php index c5a0bd1b..3f786197 100644 --- a/includes/filerepo/file/ArchivedFile.php +++ b/includes/filerepo/file/ArchivedFile.php @@ -47,6 +47,7 @@ class ArchivedFile { $timestamp, # time of upload $dataLoaded, # Whether or not all this has been loaded from the database (loadFromXxx) $deleted, # Bitfield akin to rev_deleted + $sha1, # sha1 hash of file content $pageCount, $archive_name; @@ -67,7 +68,7 @@ class ArchivedFile { * @param int $id * @param string $key */ - function __construct( $title, $id=0, $key='' ) { + function __construct( $title, $id = 0, $key = '' ) { $this->id = -1; $this->title = false; $this->name = false; @@ -87,17 +88,18 @@ class ArchivedFile { $this->deleted = 0; $this->dataLoaded = false; $this->exists = false; + $this->sha1 = ''; if( $title instanceof Title ) { $this->title = File::normalizeTitle( $title, 'exception' ); $this->name = $title->getDBkey(); } - if ($id) { + if ( $id ) { $this->id = $id; } - if ($key) { + if ( $key ) { $this->key = $key; } @@ -108,6 +110,7 @@ class ArchivedFile { /** * Loads a file object from the filearchive table + * @throws MWException * @return bool|null True on success or null */ public function load() { @@ -127,64 +130,30 @@ class ArchivedFile { $conds['fa_name'] = $this->title->getDBkey(); } - if( !count($conds)) { + if( !count( $conds ) ) { throw new MWException( "No specific information for retrieving archived file" ); } if( !$this->title || $this->title->getNamespace() == NS_FILE ) { + $this->dataLoaded = true; // set it here, to have also true on miss $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( 'filearchive', - array( - 'fa_id', - 'fa_name', - 'fa_archive_name', - 'fa_storage_key', - 'fa_storage_group', - 'fa_size', - 'fa_bits', - 'fa_width', - 'fa_height', - 'fa_metadata', - 'fa_media_type', - 'fa_major_mime', - 'fa_minor_mime', - 'fa_description', - 'fa_user', - 'fa_user_text', - 'fa_timestamp', - 'fa_deleted' ), + $row = $dbr->selectRow( + 'filearchive', + self::selectFields(), $conds, __METHOD__, - array( 'ORDER BY' => 'fa_timestamp DESC' ) ); - if ( $res == false || $dbr->numRows( $res ) == 0 ) { - // this revision does not exist? + array( 'ORDER BY' => 'fa_timestamp DESC' ) + ); + if ( !$row ) { + // this revision does not exist? return null; } - $ret = $dbr->resultObject( $res ); - $row = $ret->fetchObject(); // initialize fields for filestore image object - $this->id = intval($row->fa_id); - $this->name = $row->fa_name; - $this->archive_name = $row->fa_archive_name; - $this->group = $row->fa_storage_group; - $this->key = $row->fa_storage_key; - $this->size = $row->fa_size; - $this->bits = $row->fa_bits; - $this->width = $row->fa_width; - $this->height = $row->fa_height; - $this->metadata = $row->fa_metadata; - $this->mime = "$row->fa_major_mime/$row->fa_minor_mime"; - $this->media_type = $row->fa_media_type; - $this->description = $row->fa_description; - $this->user = $row->fa_user; - $this->user_text = $row->fa_user_text; - $this->timestamp = $row->fa_timestamp; - $this->deleted = $row->fa_deleted; + $this->loadFromRow( $row ); } else { throw new MWException( 'This title does not correspond to an image page.' ); } - $this->dataLoaded = true; $this->exists = true; return true; @@ -199,26 +168,68 @@ class ArchivedFile { */ public static function newFromRow( $row ) { $file = new ArchivedFile( Title::makeTitle( NS_FILE, $row->fa_name ) ); + $file->loadFromRow( $row ); + return $file; + } - $file->id = intval($row->fa_id); - $file->name = $row->fa_name; - $file->archive_name = $row->fa_archive_name; - $file->group = $row->fa_storage_group; - $file->key = $row->fa_storage_key; - $file->size = $row->fa_size; - $file->bits = $row->fa_bits; - $file->width = $row->fa_width; - $file->height = $row->fa_height; - $file->metadata = $row->fa_metadata; - $file->mime = "$row->fa_major_mime/$row->fa_minor_mime"; - $file->media_type = $row->fa_media_type; - $file->description = $row->fa_description; - $file->user = $row->fa_user; - $file->user_text = $row->fa_user_text; - $file->timestamp = $row->fa_timestamp; - $file->deleted = $row->fa_deleted; + /** + * Fields in the filearchive table + * @return array + */ + static function selectFields() { + return array( + 'fa_id', + 'fa_name', + 'fa_archive_name', + 'fa_storage_key', + 'fa_storage_group', + 'fa_size', + 'fa_bits', + 'fa_width', + 'fa_height', + 'fa_metadata', + 'fa_media_type', + 'fa_major_mime', + 'fa_minor_mime', + 'fa_description', + 'fa_user', + 'fa_user_text', + 'fa_timestamp', + 'fa_deleted', + 'fa_sha1', + ); + } - return $file; + /** + * Load ArchivedFile object fields from a DB row. + * + * @param $row Object database row + * @since 1.21 + */ + public function loadFromRow( $row ) { + $this->id = intval( $row->fa_id ); + $this->name = $row->fa_name; + $this->archive_name = $row->fa_archive_name; + $this->group = $row->fa_storage_group; + $this->key = $row->fa_storage_key; + $this->size = $row->fa_size; + $this->bits = $row->fa_bits; + $this->width = $row->fa_width; + $this->height = $row->fa_height; + $this->metadata = $row->fa_metadata; + $this->mime = "$row->fa_major_mime/$row->fa_minor_mime"; + $this->media_type = $row->fa_media_type; + $this->description = $row->fa_description; + $this->user = $row->fa_user; + $this->user_text = $row->fa_user_text; + $this->timestamp = $row->fa_timestamp; + $this->deleted = $row->fa_deleted; + if( isset( $row->fa_sha1 ) ) { + $this->sha1 = $row->fa_sha1; + } else { + // old row, populate from key + $this->sha1 = LocalRepo::getHashFromKey( $this->key ); + } } /** @@ -381,6 +392,17 @@ class ArchivedFile { } /** + * Get the SHA-1 base 36 hash of the file + * + * @return string + * @since 1.21 + */ + function getSha1() { + $this->load(); + return $this->sha1; + } + + /** * Return the user ID of the uploader. * * @return int diff --git a/includes/filerepo/file/File.php b/includes/filerepo/file/File.php index 557609d4..cecd0aee 100644 --- a/includes/filerepo/file/File.php +++ b/includes/filerepo/file/File.php @@ -40,7 +40,7 @@ * never name a file class explictly outside of the repo class. Instead use the * repo's factory functions to generate file objects, for example: * - * RepoGroup::singleton()->getLocalRepo()->newFile($title); + * RepoGroup::singleton()->getLocalRepo()->newFile( $title ); * * The convenience functions wfLocalFile() and wfFindFile() should be sufficient * in most cases. @@ -54,7 +54,7 @@ abstract class File { const DELETED_RESTRICTED = 8; /** Force rendering in the current process */ - const RENDER_NOW = 1; + const RENDER_NOW = 1; /** * Force rendering even if thumbnail already exist and using RENDER_NOW * I.e. you have to pass both flags: File::RENDER_NOW | File::RENDER_FORCE @@ -152,7 +152,7 @@ abstract class File { * valid Title object with namespace NS_FILE or null * * @param $title Title|string - * @param $exception string|bool Use 'exception' to throw an error on bad titles + * @param string|bool $exception Use 'exception' to throw an error on bad titles * @throws MWException * @return Title|null */ @@ -190,7 +190,7 @@ abstract class File { * Normalize a file extension to the common form, and ensure it's clean. * Extensions with non-alphanumeric characters will be discarded. * - * @param $ext string (without the .) + * @param string $ext (without the .) * @return string */ static function normalizeExtension( $ext ) { @@ -214,7 +214,7 @@ abstract class File { * Checks if file extensions are compatible * * @param $old File Old file - * @param $new string New name + * @param string $new New name * * @return bool|null */ @@ -316,7 +316,8 @@ abstract class File { public function getUrl() { if ( !isset( $this->url ) ) { $this->assertRepoDefined(); - $this->url = $this->repo->getZoneUrl( 'public' ) . '/' . $this->getUrlRel(); + $ext = $this->getExtension(); + $this->url = $this->repo->getZoneUrl( 'public', $ext ) . '/' . $this->getUrlRel(); } return $this->url; } @@ -347,7 +348,7 @@ abstract class File { if ( $this->canRender() ) { return $this->createThumb( $this->getWidth() ); } else { - wfDebug( __METHOD__.': supposed to render ' . $this->getName() . + wfDebug( __METHOD__ . ': supposed to render ' . $this->getName() . ' (' . $this->getMimeType() . "), but can't!\n" ); return $this->getURL(); #hm... return NULL? } @@ -368,7 +369,7 @@ abstract class File { * returns false. * * @return string|bool ForeignAPIFile::getPath can return false - */ + */ public function getPath() { if ( !isset( $this->path ) ) { $this->assertRepoDefined(); @@ -431,7 +432,7 @@ abstract class File { * Returns ID or name of user who uploaded the file * STUB * - * @param $type string 'text' or 'id' + * @param string $type 'text' or 'id' * * @return string|int */ @@ -511,12 +512,12 @@ abstract class File { } /** - * get versioned metadata - * - * @param $metadata Mixed Array or String of (serialized) metadata - * @param $version integer version number. - * @return Array containing metadata, or what was passed to it on fail (unserializing if not array) - */ + * get versioned metadata + * + * @param $metadata Mixed Array or String of (serialized) metadata + * @param $version integer version number. + * @return Array containing metadata, or what was passed to it on fail (unserializing if not array) + */ public function convertMetadataVersion($metadata, $version) { $handler = $this->getHandler(); if ( !is_array( $metadata ) ) { @@ -662,13 +663,13 @@ abstract class File { if ( $this->allowInlineDisplay() ) { return true; } - if ($this->isTrustedFile()) { + if ( $this->isTrustedFile() ) { return true; } $type = $this->getMediaType(); $mime = $this->getMimeType(); - #wfDebug("LocalFile::isSafeFile: type= $type, mime= $mime\n"); + #wfDebug( "LocalFile::isSafeFile: type= $type, mime= $mime\n" ); if ( !$type || $type === MEDIATYPE_UNKNOWN ) { return false; #unknown type, not trusted @@ -766,7 +767,7 @@ abstract class File { * Use File::THUMB_FULL_NAME to always get a name like "<params>-<source>". * Otherwise, the format may be "<params>-<source>" or "<params>-thumbnail.<ext>". * - * @param $params Array: handler-specific parameters + * @param array $params handler-specific parameters * @param $flags integer Bitfield that supports THUMB_* constants * @return string */ @@ -831,8 +832,8 @@ abstract class File { /** * Return either a MediaTransformError or placeholder thumbnail (if $wgIgnoreImageErrors) * - * @param $thumbPath string Thumbnail storage path - * @param $thumbUrl string Thumbnail URL + * @param string $thumbPath Thumbnail storage path + * @param string $thumbUrl Thumbnail URL * @param $params Array * @param $flags integer * @return MediaTransformOutput @@ -841,7 +842,7 @@ abstract class File { global $wgIgnoreImageErrors; if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) { - return $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params ); + return $this->getHandler()->getTransform( $this, $thumbPath, $thumbUrl, $params ); } else { return new MediaTransformError( 'thumbnail_error', $params['width'], 0, wfMessage( 'thumbnail-dest-create' )->text() ); @@ -851,7 +852,7 @@ abstract class File { /** * Transform a media file * - * @param $params Array: an associative array of handler-specific parameters. + * @param array $params an associative array of handler-specific parameters. * Typical keys are width, height and page. * @param $flags Integer: a bitfield, may contain self::RENDER_NOW to force rendering * @return MediaTransformOutput|bool False on failure @@ -872,17 +873,18 @@ abstract class File { $params['descriptionUrl'] = wfExpandUrl( $descriptionUrl, PROTO_CANONICAL ); } + $handler = $this->getHandler(); $script = $this->getTransformScript(); if ( $script && !( $flags & self::RENDER_NOW ) ) { // Use a script to transform on client request, if possible - $thumb = $this->handler->getScriptedTransform( $this, $script, $params ); + $thumb = $handler->getScriptedTransform( $this, $script, $params ); if ( $thumb ) { break; } } $normalisedParams = $params; - $this->handler->normaliseParams( $this, $normalisedParams ); + $handler->normaliseParams( $this, $normalisedParams ); $thumbName = $this->thumbName( $normalisedParams ); $thumbUrl = $this->getThumbUrl( $thumbName ); @@ -895,20 +897,21 @@ abstract class File { // XXX: Pass in the storage path even though we are not rendering anything // and the path is supposed to be an FS path. This is due to getScalerType() // getting called on the path and clobbering $thumb->getUrl() if it's false. - $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params ); + $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params ); break; } // Clean up broken thumbnails as needed $this->migrateThumbFile( $thumbName ); // Check if an up-to-date thumbnail already exists... - wfDebug( __METHOD__.": Doing stat for $thumbPath\n" ); - if ( $this->repo->fileExists( $thumbPath ) && !( $flags & self::RENDER_FORCE ) ) { + wfDebug( __METHOD__ . ": Doing stat for $thumbPath\n" ); + if ( !( $flags & self::RENDER_FORCE ) && $this->repo->fileExists( $thumbPath ) ) { $timestamp = $this->repo->getFileTimestamp( $thumbPath ); if ( $timestamp !== false && $timestamp >= $wgThumbnailEpoch ) { // XXX: Pass in the storage path even though we are not rendering anything // and the path is supposed to be an FS path. This is due to getScalerType() // getting called on the path and clobbering $thumb->getUrl() if it's false. - $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params ); + $thumb = $handler->getTransform( + $this, $thumbPath, $thumbUrl, $params ); $thumb->setStoragePath( $thumbPath ); break; } @@ -935,7 +938,7 @@ abstract class File { // Actually render the thumbnail... wfProfileIn( __METHOD__ . '-doTransform' ); - $thumb = $this->handler->doTransform( $this, $tmpThumbPath, $thumbUrl, $params ); + $thumb = $handler->doTransform( $this, $tmpThumbPath, $thumbUrl, $params ); wfProfileOut( __METHOD__ . '-doTransform' ); $tmpFile->bind( $thumb ); // keep alive with $thumb @@ -945,7 +948,7 @@ abstract class File { $this->lastError = $thumb->toText(); // Ignore errors if requested if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) { - $thumb = $this->handler->getTransform( $this, $tmpThumbPath, $thumbUrl, $params ); + $thumb = $handler->getTransform( $this, $tmpThumbPath, $thumbUrl, $params ); } } elseif ( $this->repo && $thumb->hasFile() && !$thumb->fileIsSource() ) { // Copy the thumbnail from the file system into storage... @@ -975,7 +978,7 @@ abstract class File { } /** - * @param $thumbName string Thumbnail name + * @param string $thumbName Thumbnail name * @return string Content-Disposition header value */ function getThumbDisposition( $thumbName ) { @@ -1048,7 +1051,7 @@ abstract class File { * Purge shared caches such as thumbnails and DB data caching * STUB * Overridden by LocalFile - * @param $options Array Options, which include: + * @param array $options Options, which include: * 'forThumbRefresh' : The purging is only to refresh thumbnails */ function purgeCache( $options = array() ) {} @@ -1088,9 +1091,9 @@ abstract class File { * * STUB * @param $limit integer Limit of rows to return - * @param $start string timestamp Only revisions older than $start will be returned - * @param $end string timestamp Only revisions newer than $end will be returned - * @param $inc bool Include the endpoints of the time range + * @param string $start timestamp Only revisions older than $start will be returned + * @param string $end timestamp Only revisions newer than $end will be returned + * @param bool $inc Include the endpoints of the time range * * @return array */ @@ -1147,7 +1150,7 @@ abstract class File { /** * Get the path of an archived file relative to the public zone root * - * @param $suffix bool|string if not false, the name of an archived thumbnail file + * @param bool|string $suffix if not false, the name of an archived thumbnail file * * @return string */ @@ -1165,7 +1168,7 @@ abstract class File { * Get the path, relative to the thumbnail zone root, of the * thumbnail directory or a particular file if $suffix is specified * - * @param $suffix bool|string if not false, the name of a thumbnail file + * @param bool|string $suffix if not false, the name of a thumbnail file * * @return string */ @@ -1191,8 +1194,8 @@ abstract class File { * Get the path, relative to the thumbnail zone root, for an archived file's thumbs directory * or a specific thumb if the $suffix is given. * - * @param $archiveName string the timestamped name of an archived image - * @param $suffix bool|string if not false, the name of a thumbnail file + * @param string $archiveName the timestamped name of an archived image + * @param bool|string $suffix if not false, the name of a thumbnail file * * @return string */ @@ -1209,7 +1212,7 @@ abstract class File { /** * Get the path of the archived file. * - * @param $suffix bool|string if not false, the name of an archived file. + * @param bool|string $suffix if not false, the name of an archived file. * * @return string */ @@ -1221,8 +1224,8 @@ abstract class File { /** * Get the path of an archived file's thumbs, or a particular thumb if $suffix is specified * - * @param $archiveName string the timestamped name of an archived image - * @param $suffix bool|string if not false, the name of a thumbnail file + * @param string $archiveName the timestamped name of an archived image + * @param bool|string $suffix if not false, the name of a thumbnail file * * @return string */ @@ -1235,7 +1238,7 @@ abstract class File { /** * Get the path of the thumbnail directory, or a particular file if $suffix is specified * - * @param $suffix bool|string if not false, the name of a thumbnail file + * @param bool|string $suffix if not false, the name of a thumbnail file * * @return string */ @@ -1245,15 +1248,28 @@ abstract class File { } /** + * Get the path of the transcoded directory, or a particular file if $suffix is specified + * + * @param bool|string $suffix if not false, the name of a media file + * + * @return string + */ + function getTranscodedPath( $suffix = false ) { + $this->assertRepoDefined(); + return $this->repo->getZonePath( 'transcoded' ) . '/' . $this->getThumbRel( $suffix ); + } + + /** * Get the URL of the archive directory, or a particular file if $suffix is specified * - * @param $suffix bool|string if not false, the name of an archived file + * @param bool|string $suffix if not false, the name of an archived file * * @return string */ function getArchiveUrl( $suffix = false ) { $this->assertRepoDefined(); - $path = $this->repo->getZoneUrl( 'public' ) . '/archive/' . $this->getHashPath(); + $ext = $this->getExtension(); + $path = $this->repo->getZoneUrl( 'public', $ext ) . '/archive/' . $this->getHashPath(); if ( $suffix === false ) { $path = substr( $path, 0, -1 ); } else { @@ -1265,14 +1281,15 @@ abstract class File { /** * Get the URL of the archived file's thumbs, or a particular thumb if $suffix is specified * - * @param $archiveName string the timestamped name of an archived image - * @param $suffix bool|string if not false, the name of a thumbnail file + * @param string $archiveName the timestamped name of an archived image + * @param bool|string $suffix if not false, the name of a thumbnail file * * @return string */ function getArchiveThumbUrl( $archiveName, $suffix = false ) { $this->assertRepoDefined(); - $path = $this->repo->getZoneUrl( 'thumb' ) . '/archive/' . + $ext = $this->getExtension(); + $path = $this->repo->getZoneUrl( 'thumb', $ext ) . '/archive/' . $this->getHashPath() . rawurlencode( $archiveName ) . "/"; if ( $suffix === false ) { $path = substr( $path, 0, -1 ); @@ -1283,15 +1300,17 @@ abstract class File { } /** - * Get the URL of the thumbnail directory, or a particular file if $suffix is specified + * Get the URL of the zone directory, or a particular file if $suffix is specified * - * @param $suffix bool|string if not false, the name of a thumbnail file + * @param string $zone name of requested zone + * @param bool|string $suffix if not false, the name of a file in zone * * @return string path */ - function getThumbUrl( $suffix = false ) { + function getZoneUrl( $zone, $suffix = false ) { $this->assertRepoDefined(); - $path = $this->repo->getZoneUrl( 'thumb' ) . '/' . $this->getUrlRel(); + $ext = $this->getExtension(); + $path = $this->repo->getZoneUrl( $zone, $ext ) . '/' . $this->getUrlRel(); if ( $suffix !== false ) { $path .= '/' . rawurlencode( $suffix ); } @@ -1299,9 +1318,31 @@ abstract class File { } /** + * Get the URL of the thumbnail directory, or a particular file if $suffix is specified + * + * @param bool|string $suffix if not false, the name of a thumbnail file + * + * @return string path + */ + function getThumbUrl( $suffix = false ) { + return $this->getZoneUrl( 'thumb', $suffix ); + } + + /** + * Get the URL of the transcoded directory, or a particular file if $suffix is specified + * + * @param bool|string $suffix if not false, the name of a media file + * + * @return string path + */ + function getTranscodedUrl( $suffix = false ) { + return $this->getZoneUrl( 'transcoded', $suffix ); + } + + /** * Get the public zone virtual URL for a current version source file * - * @param $suffix bool|string if not false, the name of a thumbnail file + * @param bool|string $suffix if not false, the name of a thumbnail file * * @return string */ @@ -1317,7 +1358,7 @@ abstract class File { /** * Get the public zone virtual URL for an archived version source file * - * @param $suffix bool|string if not false, the name of a thumbnail file + * @param bool|string $suffix if not false, the name of a thumbnail file * * @return string */ @@ -1335,7 +1376,7 @@ abstract class File { /** * Get the virtual URL for a thumbnail file or directory * - * @param $suffix bool|string if not false, the name of a thumbnail file + * @param bool|string $suffix if not false, the name of a thumbnail file * * @return string */ @@ -1360,7 +1401,7 @@ abstract class File { * @throws MWException */ function readOnlyError() { - throw new MWException( get_class($this) . ': write operations are not supported' ); + throw new MWException( get_class( $this ) . ': write operations are not supported' ); } /** @@ -1373,8 +1414,12 @@ abstract class File { * @param $copyStatus string * @param $source string * @param $watch bool + * @param $timestamp string|bool + * @param $user User object or null to use $wgUser + * @return bool + * @throws MWException */ - function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '', $watch = false ) { + function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '', $watch = false, $timestamp = false, User $user = null ) { $this->readOnlyError(); } @@ -1386,17 +1431,21 @@ abstract class File { * The archive name should be passed through to recordUpload for database * registration. * - * @param $srcPath String: local filesystem path to the source image + * Options to $options include: + * - headers : name/value map of HTTP headers to use in response to GET/HEAD requests + * + * @param string $srcPath local filesystem path to the source image * @param $flags Integer: a bitwise combination of: * File::DELETE_SOURCE Delete the source file, i.e. move * rather than copy + * @param array $options Optional additional parameters * @return FileRepoStatus object. On success, the value member contains the * archive name, or an empty string if it was a new file. * * STUB * Overridden by LocalFile */ - function publish( $srcPath, $flags = 0 ) { + function publish( $srcPath, $flags = 0, array $options = array() ) { $this->readOnlyError(); } @@ -1490,9 +1539,9 @@ abstract class File { * @param $target Title New file name * @return FileRepoStatus object. */ - function move( $target ) { + function move( $target ) { $this->readOnlyError(); - } + } /** * Delete all versions of the file. @@ -1518,9 +1567,9 @@ abstract class File { * * May throw database exceptions on error. * - * @param $versions array set of record ids of deleted items to restore, + * @param array $versions set of record ids of deleted items to restore, * or empty to restore all revisions. - * @param $unsuppress bool remove restrictions on content upon restoration? + * @param bool $unsuppress remove restrictions on content upon restoration? * @return int|bool the number of file revisions restored if successful, * or false on failure * STUB @@ -1580,7 +1629,7 @@ abstract class File { * Get an image size array like that returned by getImageSize(), or false if it * can't be determined. * - * @param $fileName String: The filename + * @param string $fileName The filename * @return Array */ function getImageSize( $fileName ) { @@ -1617,15 +1666,15 @@ abstract class File { $renderUrl = $this->repo->getDescriptionRenderUrl( $this->getName(), $wgLang->getCode() ); if ( $renderUrl ) { if ( $this->repo->descriptionCacheExpiry > 0 ) { - wfDebug("Attempting to get the description from cache..."); + wfDebug( "Attempting to get the description from cache..." ); $key = $this->repo->getLocalCacheKey( 'RemoteFileDescription', 'url', $wgLang->getCode(), $this->getName() ); - $obj = $wgMemc->get($key); - if ($obj) { - wfDebug("success!\n"); + $obj = $wgMemc->get( $key ); + if ( $obj ) { + wfDebug( "success!\n" ); return $obj; } - wfDebug("miss\n"); + wfDebug( "miss\n" ); } wfDebug( "Fetching shared description from $renderUrl\n" ); $res = Http::get( $renderUrl ); @@ -1704,14 +1753,15 @@ abstract class File { /** * Get an associative array containing information about a file in the local filesystem. * - * @param $path String: absolute local filesystem path + * @param string $path absolute local filesystem path * @param $ext Mixed: the file extension, or true to extract it from the filename. * Set it to false to ignore the extension. * * @return array + * @deprecated since 1.19 */ static function getPropsFromPath( $path, $ext = true ) { - wfDebug( __METHOD__.": Getting file info for $path\n" ); + wfDebug( __METHOD__ . ": Getting file info for $path\n" ); wfDeprecated( __METHOD__, '1.19' ); $fsFile = new FSFile( $path ); @@ -1728,6 +1778,7 @@ abstract class File { * @param $path string * * @return bool|string False on failure + * @deprecated since 1.19 */ static function sha1Base36( $path ) { wfDeprecated( __METHOD__, '1.19' ); @@ -1737,6 +1788,18 @@ abstract class File { } /** + * @return Array HTTP header name/value map to use for HEAD/GET request responses + */ + function getStreamHeaders() { + $handler = $this->getHandler(); + if ( $handler ) { + return $handler->getStreamHeaders( $this->getMetadata() ); + } else { + return array(); + } + } + + /** * @return string */ function getLongDesc() { @@ -1780,7 +1843,7 @@ abstract class File { } /** - * @return Title + * @return Title|null */ function getRedirectedTitle() { if ( $this->redirected ) { @@ -1789,6 +1852,7 @@ abstract class File { } return $this->redirectTitle; } + return null; } /** diff --git a/includes/filerepo/file/ForeignAPIFile.php b/includes/filerepo/file/ForeignAPIFile.php index 56482611..a96c1f3f 100644 --- a/includes/filerepo/file/ForeignAPIFile.php +++ b/includes/filerepo/file/ForeignAPIFile.php @@ -106,7 +106,7 @@ class ForeignAPIFile extends File { } /** - * @param Array $params + * @param array $params * @param int $flags * @return bool|MediaTransformOutput */ @@ -189,7 +189,7 @@ class ForeignAPIFile extends File { * @param string $method * @return int|null|string */ - public function getUser( $method='text' ) { + public function getUser( $method = 'text' ) { return isset( $this->mInfo['user'] ) ? strval( $this->mInfo['user'] ) : null; } @@ -256,7 +256,7 @@ class ForeignAPIFile extends File { */ function getThumbPath( $suffix = '' ) { if ( $this->repo->canCacheThumbs() ) { - $path = $this->repo->getZonePath('thumb') . '/' . $this->getHashPath( $this->getName() ); + $path = $this->repo->getZonePath( 'thumb' ) . '/' . $this->getHashPath( $this->getName() ); if ( $suffix ) { $path = $path . $suffix . '/'; } @@ -293,7 +293,7 @@ class ForeignAPIFile extends File { global $wgMemc, $wgContLang; $url = $this->repo->getDescriptionRenderUrl( $this->getName(), $wgContLang->getCode() ); - $key = $this->repo->getLocalCacheKey( 'RemoteFileDescription', 'url', md5($url) ); + $key = $this->repo->getLocalCacheKey( 'RemoteFileDescription', 'url', md5( $url ) ); $wgMemc->delete( $key ); } diff --git a/includes/filerepo/file/ForeignDBFile.php b/includes/filerepo/file/ForeignDBFile.php index 91f6cb62..ee5883c4 100644 --- a/includes/filerepo/file/ForeignDBFile.php +++ b/includes/filerepo/file/ForeignDBFile.php @@ -57,9 +57,11 @@ class ForeignDBFile extends LocalFile { /** * @param $srcPath String * @param $flags int + * @param $options Array + * @return \FileRepoStatus * @throws MWException */ - function publish( $srcPath, $flags = 0 ) { + function publish( $srcPath, $flags = 0, array $options = array() ) { $this->readOnlyError(); } @@ -71,16 +73,19 @@ class ForeignDBFile extends LocalFile { * @param $source string * @param $watch bool * @param $timestamp bool|string + * @param $user User object or null to use $wgUser + * @return bool * @throws MWException */ function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '', - $watch = false, $timestamp = false ) { + $watch = false, $timestamp = false, User $user = null ) { $this->readOnlyError(); } /** * @param $versions array * @param $unsuppress bool + * @return \FileRepoStatus * @throws MWException */ function restore( $versions = array(), $unsuppress = false ) { @@ -90,6 +95,7 @@ class ForeignDBFile extends LocalFile { /** * @param $reason string * @param $suppress bool + * @return \FileRepoStatus * @throws MWException */ function delete( $reason, $suppress = false ) { @@ -98,6 +104,7 @@ class ForeignDBFile extends LocalFile { /** * @param $target Title + * @return \FileRepoStatus * @throws MWException */ function move( $target ) { @@ -108,7 +115,7 @@ class ForeignDBFile extends LocalFile { * @return string */ function getDescriptionUrl() { - // Restore remote behaviour + // Restore remote behavior return File::getDescriptionUrl(); } @@ -116,7 +123,7 @@ class ForeignDBFile extends LocalFile { * @return string */ function getDescriptionText() { - // Restore remote behaviour + // Restore remote behavior return File::getDescriptionText(); } } diff --git a/includes/filerepo/file/LocalFile.php b/includes/filerepo/file/LocalFile.php index 695c4e9e..639228b9 100644 --- a/includes/filerepo/file/LocalFile.php +++ b/includes/filerepo/file/LocalFile.php @@ -24,7 +24,7 @@ /** * Bump this number when serialized cache records may be incompatible. */ -define( 'MW_FILE_VERSION', 8 ); +define( 'MW_FILE_VERSION', 9 ); /** * Class to represent a local file in the wiki's own database @@ -36,7 +36,7 @@ define( 'MW_FILE_VERSION', 8 ); * never name a file class explictly outside of the repo class. Instead use the * repo's factory functions to generate file objects, for example: * - * RepoGroup::singleton()->getLocalRepo()->newFile($title); + * RepoGroup::singleton()->getLocalRepo()->newFile( $title ); * * The convenience functions wfLocalFile() and wfFindFile() should be sufficient * in most cases. @@ -67,9 +67,11 @@ class LocalFile extends File { $sha1, # SHA-1 base 36 content hash $user, $user_text, # User, who uploaded the file $description, # Description of current revision of the file - $dataLoaded, # Whether or not all this has been loaded from the database (loadFromXxx) + $dataLoaded, # Whether or not core data has been loaded from the database (loadFromXxx) + $extraDataLoaded, # Whether or not lazy-loaded data has been loaded from the database $upgraded, # Whether the row was upgraded on load $locked, # True if the image row is locked + $lockedOwnTrx, # True if the image row is locked with a lock initiated transaction $missing, # True if file is not present in file system. Not to be cached in memcached $deleted; # Bitfield akin to rev_deleted @@ -82,6 +84,8 @@ class LocalFile extends File { protected $repoClass = 'LocalRepo'; + const LOAD_ALL = 1; // integer; load all the lazy fields too (like metadata) + /** * Create a LocalFile from a title * Do not call this except from inside a repo class. @@ -119,7 +123,7 @@ class LocalFile extends File { * Create a LocalFile from a SHA-1 key * Do not call this except from inside a repo class. * - * @param $sha1 string base-36 SHA-1 + * @param string $sha1 base-36 SHA-1 * @param $repo LocalRepo * @param string|bool $timestamp MW_timestamp (optional) * @@ -175,6 +179,7 @@ class LocalFile extends File { $this->historyLine = 0; $this->historyRes = null; $this->dataLoaded = false; + $this->extraDataLoaded = false; $this->assertRepoDefined(); $this->assertTitleDefined(); @@ -200,6 +205,7 @@ class LocalFile extends File { wfProfileIn( __METHOD__ ); $this->dataLoaded = false; + $this->extraDataLoaded = false; $key = $this->getCacheKey(); if ( !$key ) { @@ -210,13 +216,17 @@ class LocalFile extends File { $cachedValues = $wgMemc->get( $key ); // Check if the key existed and belongs to this version of MediaWiki - if ( isset( $cachedValues['version'] ) && ( $cachedValues['version'] == MW_FILE_VERSION ) ) { + if ( isset( $cachedValues['version'] ) && $cachedValues['version'] == MW_FILE_VERSION ) { wfDebug( "Pulling file metadata from cache key $key\n" ); $this->fileExists = $cachedValues['fileExists']; if ( $this->fileExists ) { $this->setProps( $cachedValues ); } $this->dataLoaded = true; + $this->extraDataLoaded = true; + foreach ( $this->getLazyCacheFields( '' ) as $field ) { + $this->extraDataLoaded = $this->extraDataLoaded && isset( $cachedValues[$field] ); + } } if ( $this->dataLoaded ) { @@ -252,7 +262,17 @@ class LocalFile extends File { } } - $wgMemc->set( $key, $cache, 60 * 60 * 24 * 7 ); // A week + // Strip off excessive entries from the subset of fields that can become large. + // If the cache value gets to large it will not fit in memcached and nothing will + // get cached at all, causing master queries for any file access. + foreach ( $this->getLazyCacheFields( '' ) as $field ) { + if ( isset( $cache[$field] ) && strlen( $cache[$field] ) > 100 * 1024 ) { + unset( $cache[$field] ); // don't let the value get too big + } + } + + // Cache presence for 1 week and negatives for 1 day + $wgMemc->set( $key, $cache, $this->fileExists ? 86400 * 7 : 86400 ); } /** @@ -288,6 +308,28 @@ class LocalFile extends File { } /** + * @return array + */ + function getLazyCacheFields( $prefix = 'img_' ) { + static $fields = array( 'metadata' ); + static $results = array(); + + if ( $prefix == '' ) { + return $fields; + } + + if ( !isset( $results[$prefix] ) ) { + $prefixedFields = array(); + foreach ( $fields as $field ) { + $prefixedFields[] = $prefix . $field; + } + $results[$prefix] = $prefixedFields; + } + + return $results[$prefix]; + } + + /** * Load file metadata from the DB */ function loadFromDB() { @@ -297,9 +339,9 @@ class LocalFile extends File { # Unconditionally set loaded=true, we don't want the accessors constantly rechecking $this->dataLoaded = true; + $this->extraDataLoaded = true; $dbr = $this->repo->getMasterDB(); - $row = $dbr->selectRow( 'image', $this->getCacheFields( 'img_' ), array( 'img_name' => $this->getName() ), $fname ); @@ -313,27 +355,70 @@ class LocalFile extends File { } /** - * Decode a row from the database (either object or array) to an array - * with timestamps and MIME types decoded, and the field prefix removed. - * @param $row + * Load lazy file metadata from the DB. + * This covers fields that are sometimes not cached. + */ + protected function loadExtraFromDB() { + # Polymorphic function name to distinguish foreign and local fetches + $fname = get_class( $this ) . '::' . __FUNCTION__; + wfProfileIn( $fname ); + + # Unconditionally set loaded=true, we don't want the accessors constantly rechecking + $this->extraDataLoaded = true; + + $dbr = $this->repo->getSlaveDB(); + // In theory the file could have just been renamed/deleted...oh well + $row = $dbr->selectRow( 'image', $this->getLazyCacheFields( 'img_' ), + array( 'img_name' => $this->getName() ), $fname ); + + if ( !$row ) { // fallback to master + $dbr = $this->repo->getMasterDB(); + $row = $dbr->selectRow( 'image', $this->getLazyCacheFields( 'img_' ), + array( 'img_name' => $this->getName() ), $fname ); + } + + if ( $row ) { + foreach ( $this->unprefixRow( $row, 'img_' ) as $name => $value ) { + $this->$name = $value; + } + } else { + throw new MWException( "Could not find data for image '{$this->getName()}'." ); + } + + wfProfileOut( $fname ); + } + + /** + * @param Row $row * @param $prefix string - * @throws MWException - * @return array + * @return Array */ - function decodeRow( $row, $prefix = 'img_' ) { + protected function unprefixRow( $row, $prefix = 'img_' ) { $array = (array)$row; $prefixLength = strlen( $prefix ); // Sanity check prefix once if ( substr( key( $array ), 0, $prefixLength ) !== $prefix ) { - throw new MWException( __METHOD__ . ': incorrect $prefix parameter' ); + throw new MWException( __METHOD__ . ': incorrect $prefix parameter' ); } $decoded = array(); - foreach ( $array as $name => $value ) { $decoded[substr( $name, $prefixLength )] = $value; } + return $decoded; + } + + /** + * Decode a row from the database (either object or array) to an array + * with timestamps and MIME types decoded, and the field prefix removed. + * @param $row + * @param $prefix string + * @throws MWException + * @return array + */ + function decodeRow( $row, $prefix = 'img_' ) { + $decoded = $this->unprefixRow( $row, $prefix ); $decoded['timestamp'] = wfTimestamp( TS_MW, $decoded['timestamp'] ); @@ -357,6 +442,8 @@ class LocalFile extends File { */ function loadFromRow( $row, $prefix = 'img_' ) { $this->dataLoaded = true; + $this->extraDataLoaded = true; + $array = $this->decodeRow( $row, $prefix ); foreach ( $array as $name => $value ) { @@ -369,8 +456,9 @@ class LocalFile extends File { /** * Load file metadata from cache or DB, unless already loaded + * @param integer $flags */ - function load() { + function load( $flags = 0 ) { if ( !$this->dataLoaded ) { if ( !$this->loadFromCache() ) { $this->loadFromDB(); @@ -378,6 +466,9 @@ class LocalFile extends File { } $this->dataLoaded = true; } + if ( ( $flags & self::LOAD_ALL ) && !$this->extraDataLoaded ) { + $this->loadExtraFromDB(); + } } /** @@ -397,7 +488,7 @@ class LocalFile extends File { } else { $handler = $this->getHandler(); if ( $handler ) { - $validity = $handler->isMetadataValid( $this, $this->metadata ); + $validity = $handler->isMetadataValid( $this, $this->getMetadata() ); if ( $validity === MediaHandler::METADATA_BAD || ( $validity === MediaHandler::METADATA_COMPATIBLE && $wgUpdateCompatibleMetadata ) ) { @@ -464,6 +555,7 @@ class LocalFile extends File { /** * Set properties in this object to be equal to those given in the * associative array $info. Only cacheable fields can be set. + * All fields *must* be set in $info except for getLazyCacheFields(). * * If 'mime' is given, it will be split into major_mime/minor_mime. * If major_mime/minor_mime are given, $this->mime will also be set. @@ -552,7 +644,7 @@ class LocalFile extends File { /** * Returns ID or name of user who uploaded the file * - * @param $type string 'text' or 'id' + * @param string $type 'text' or 'id' * @return int|string */ function getUser( $type = 'text' ) { @@ -570,7 +662,7 @@ class LocalFile extends File { * @return string */ function getMetadata() { - $this->load(); + $this->load( self::LOAD_ALL ); // large metadata is loaded in another step return $this->metadata; } @@ -638,15 +730,14 @@ class LocalFile extends File { * RTT regression for wikis without 404 handling. */ function migrateThumbFile( $thumbName ) { - $thumbDir = $this->getThumbPath(); - /* Old code for bug 2532 + $thumbDir = $this->getThumbPath(); $thumbPath = "$thumbDir/$thumbName"; if ( is_dir( $thumbPath ) ) { // Directory where file should be // This happened occasionally due to broken migration code in 1.5 // Rename to broken-* - for ( $i = 0; $i < 100 ; $i++ ) { + for ( $i = 0; $i < 100; $i++ ) { $broken = $this->repo->getZonePath( 'public' ) . "/broken-$i-$thumbName"; if ( !file_exists( $broken ) ) { rename( $thumbPath, $broken ); @@ -672,7 +763,7 @@ class LocalFile extends File { /** * Get all thumbnail names previously generated for this file - * @param $archiveName string|bool Name of an archive file, default false + * @param string|bool $archiveName Name of an archive file, default false * @return array first element is the base dir, then files in that base dir. */ function getThumbnails( $archiveName = false ) { @@ -736,7 +827,7 @@ class LocalFile extends File { /** * Delete cached transformed files for an archived version only. - * @param $archiveName string name of the archived file + * @param string $archiveName name of the archived file */ function purgeOldThumbnails( $archiveName ) { global $wgUseSquid; @@ -771,8 +862,16 @@ class LocalFile extends File { // Delete thumbnails $files = $this->getThumbnails(); + // Always purge all files from squid regardless of handler filters + if ( $wgUseSquid ) { + $urls = array(); + foreach( $files as $file ) { + $urls[] = $this->getThumbUrl( $file ); + } + array_shift( $urls ); // don't purge directory + } - // Give media handler a chance to filter the purge list + // Give media handler a chance to filter the file purge list if ( !empty( $options['forThumbRefresh'] ) ) { $handler = $this->getHandler(); if ( $handler ) { @@ -788,10 +887,6 @@ class LocalFile extends File { // Purge the squid if ( $wgUseSquid ) { - $urls = array(); - foreach( $files as $file ) { - $urls[] = $this->getThumbUrl( $file ); - } SquidUpdate::purge( $urls ); } @@ -800,13 +895,13 @@ class LocalFile extends File { /** * Delete a list of thumbnails visible at urls - * @param $dir string base dir of the files. - * @param $files array of strings: relative filenames (to $dir) + * @param string $dir base dir of the files. + * @param array $files of strings: relative filenames (to $dir) */ protected function purgeThumbList( $dir, $files ) { $fileListDebug = strtr( var_export( $files, true ), - array("\n"=>'') + array( "\n" => '' ) ); wfDebug( __METHOD__ . ": $fileListDebug\n" ); @@ -814,7 +909,9 @@ class LocalFile extends File { foreach ( $files as $file ) { # Check that the base file name is part of the thumb name # This is a basic sanity check to avoid erasing unrelated directories - if ( strpos( $file, $this->getName() ) !== false ) { + if ( strpos( $file, $this->getName() ) !== false + || strpos( $file, "-thumbnail" ) !== false // "short" thumb name + ) { $purgeList[] = "{$dir}/{$file}"; } } @@ -949,15 +1046,15 @@ class LocalFile extends File { /** * Upload a file and record it in the DB - * @param $srcPath String: source storage path or virtual URL - * @param $comment String: upload description - * @param $pageText String: text to use for the new description page, + * @param string $srcPath source storage path, virtual URL, or filesystem path + * @param string $comment upload description + * @param string $pageText text to use for the new description page, * if a new description page is created * @param $flags Integer|bool: flags for publish() - * @param $props Array|bool: File properties, if known. This can be used to reduce the + * @param array|bool $props File properties, if known. This can be used to reduce the * upload time when uploading virtual URLs for which the file info * is already known - * @param $timestamp String|bool: timestamp for img_timestamp, or false to use the current time + * @param string|bool $timestamp timestamp for img_timestamp, or false to use the current time * @param $user User|null: User object or null to use $wgUser * * @return FileRepoStatus object. On success, the value member contains the @@ -970,11 +1067,34 @@ class LocalFile extends File { return $this->readOnlyFatalStatus(); } + if ( !$props ) { + wfProfileIn( __METHOD__ . '-getProps' ); + if ( $this->repo->isVirtualUrl( $srcPath ) + || FileBackend::isStoragePath( $srcPath ) ) + { + $props = $this->repo->getFileProps( $srcPath ); + } else { + $props = FSFile::getPropsFromPath( $srcPath ); + } + wfProfileOut( __METHOD__ . '-getProps' ); + } + + $options = array(); + $handler = MediaHandler::getHandler( $props['mime'] ); + if ( $handler ) { + $options['headers'] = $handler->getStreamHeaders( $props['metadata'] ); + } else { + $options['headers'] = array(); + } + + // Trim spaces on user supplied text + $comment = trim( $comment ); + // truncate nicely or the DB will do it for us // non-nicely (dangling multi-byte chars, non-truncated version in cache). $comment = $wgContLang->truncate( $comment, 255 ); $this->lock(); // begin - $status = $this->publish( $srcPath, $flags ); + $status = $this->publish( $srcPath, $flags, $options ); if ( $status->successCount > 0 ) { # Essentially we are displacing any existing current file and saving @@ -999,20 +1119,25 @@ class LocalFile extends File { * @param $source string * @param $watch bool * @param $timestamp string|bool + * @param $user User object or null to use $wgUser * @return bool */ function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '', - $watch = false, $timestamp = false ) + $watch = false, $timestamp = false, User $user = null ) { + if ( !$user ) { + global $wgUser; + $user = $wgUser; + } + $pageText = SpecialUpload::getInitialPageText( $desc, $license, $copyStatus, $source ); - if ( !$this->recordUpload2( $oldver, $desc, $pageText ) ) { + if ( !$this->recordUpload2( $oldver, $desc, $pageText, false, $timestamp, $user ) ) { return false; } if ( $watch ) { - global $wgUser; - $wgUser->addWatch( $this->getTitle() ); + $user->addWatch( $this->getTitle() ); } return true; } @@ -1165,7 +1290,7 @@ class LocalFile extends File { $log->getRcComment(), false ); - if (!is_null($nullRevision)) { + if ( !is_null( $nullRevision ) ) { $nullRevision->insertOn( $dbw ); wfRunHooks( 'NewRevisionFromEditComplete', array( $wikiPage, $nullRevision, $latest, $user ) ); @@ -1186,17 +1311,18 @@ class LocalFile extends File { } else { # New file; create the description page. # There's already a log entry, so don't make a second RC entry - # Squid and file cache for the description page are purged by doEdit. - $status = $wikiPage->doEdit( $pageText, $comment, EDIT_NEW | EDIT_SUPPRESS_RC, false, $user ); + # Squid and file cache for the description page are purged by doEditContent. + $content = ContentHandler::makeContent( $pageText, $descTitle ); + $status = $wikiPage->doEditContent( $content, $comment, EDIT_NEW | EDIT_SUPPRESS_RC, false, $user ); if ( isset( $status->value['revision'] ) ) { // XXX; doEdit() uses a transaction - $dbw->begin(); + $dbw->begin( __METHOD__ ); $dbw->update( 'logging', array( 'log_page' => $status->value['revision']->getPage() ), array( 'log_id' => $logId ), __METHOD__ ); - $dbw->commit(); // commit before anything bad can happen + $dbw->commit( __METHOD__ ); // commit before anything bad can happen } } wfProfileOut( __METHOD__ . '-edit' ); @@ -1246,14 +1372,15 @@ class LocalFile extends File { * The archive name should be passed through to recordUpload for database * registration. * - * @param $srcPath String: local filesystem path to the source image + * @param string $srcPath local filesystem path to the source image * @param $flags Integer: a bitwise combination of: * File::DELETE_SOURCE Delete the source file, i.e. move rather than copy + * @param array $options Optional additional parameters * @return FileRepoStatus object. On success, the value member contains the * archive name, or an empty string if it was a new file. */ - function publish( $srcPath, $flags = 0 ) { - return $this->publishTo( $srcPath, $this->getRel(), $flags ); + function publish( $srcPath, $flags = 0, array $options = array() ) { + return $this->publishTo( $srcPath, $this->getRel(), $flags, $options ); } /** @@ -1263,14 +1390,15 @@ class LocalFile extends File { * The archive name should be passed through to recordUpload for database * registration. * - * @param $srcPath String: local filesystem path to the source image - * @param $dstRel String: target relative path + * @param string $srcPath local filesystem path to the source image + * @param string $dstRel target relative path * @param $flags Integer: a bitwise combination of: * File::DELETE_SOURCE Delete the source file, i.e. move rather than copy + * @param array $options Optional additional parameters * @return FileRepoStatus object. On success, the value member contains the * archive name, or an empty string if it was a new file. */ - function publishTo( $srcPath, $dstRel, $flags = 0 ) { + function publishTo( $srcPath, $dstRel, $flags = 0, array $options = array() ) { if ( $this->getRepo()->getReadOnlyReason() !== false ) { return $this->readOnlyFatalStatus(); } @@ -1280,7 +1408,7 @@ class LocalFile extends File { $archiveName = wfTimestamp( TS_MW ) . '!'. $this->getName(); $archiveRel = 'archive/' . $this->getHashPath() . $archiveName; $flags = $flags & File::DELETE_SOURCE ? LocalRepo::DELETE_SOURCE : 0; - $status = $this->repo->publish( $srcPath, $dstRel, $archiveRel, $flags ); + $status = $this->repo->publish( $srcPath, $dstRel, $archiveRel, $flags, $options ); if ( $status->value == 'new' ) { $status->value = ''; @@ -1422,7 +1550,7 @@ class LocalFile extends File { * * May throw database exceptions on error. * - * @param $versions array set of record ids of deleted items to restore, + * @param array $versions set of record ids of deleted items to restore, * or empty to restore all revisions. * @param $unsuppress Boolean * @return FileRepoStatus @@ -1472,12 +1600,11 @@ class LocalFile extends File { * @return bool|mixed */ function getDescriptionText() { - global $wgParser; $revision = Revision::newFromTitle( $this->title, false, Revision::READ_NORMAL ); if ( !$revision ) return false; - $text = $revision->getText(); - if ( !$text ) return false; - $pout = $wgParser->parse( $text, $this->title, new ParserOptions() ); + $content = $revision->getContent(); + if ( !$content ) return false; + $pout = $content->getParserOutput( $this->title, null, new ParserOptions() ); return $pout->getText(); } @@ -1547,7 +1674,10 @@ class LocalFile extends File { $dbw = $this->repo->getMasterDB(); if ( !$this->locked ) { - $dbw->begin( __METHOD__ ); + if ( !$dbw->trxLevel() ) { + $dbw->begin( __METHOD__ ); + $this->lockedOwnTrx = true; + } $this->locked++; } @@ -1562,9 +1692,10 @@ class LocalFile extends File { function unlock() { if ( $this->locked ) { --$this->locked; - if ( !$this->locked ) { + if ( !$this->locked && $this->lockedOwnTrx ) { $dbw = $this->repo->getMasterDB(); $dbw->commit( __METHOD__ ); + $this->lockedOwnTrx = false; } } } @@ -1576,6 +1707,7 @@ class LocalFile extends File { $this->locked = false; $dbw = $this->repo->getMasterDB(); $dbw->rollback( __METHOD__ ); + $this->lockedOwnTrx = false; } /** @@ -1758,7 +1890,7 @@ class LocalFileDeleteBatch { 'fa_deleted_user' => $encUserId, 'fa_deleted_timestamp' => $encTimestamp, 'fa_deleted_reason' => $encReason, - 'fa_deleted' => $this->suppress ? $bitfield : 0, + 'fa_deleted' => $this->suppress ? $bitfield : 0, 'fa_name' => 'img_name', 'fa_archive_name' => 'NULL', @@ -1773,7 +1905,8 @@ class LocalFileDeleteBatch { 'fa_description' => 'img_description', 'fa_user' => 'img_user', 'fa_user_text' => 'img_user_text', - 'fa_timestamp' => 'img_timestamp' + 'fa_timestamp' => 'img_timestamp', + 'fa_sha1' => 'img_sha1', ), $where, __METHOD__ ); } @@ -1805,6 +1938,7 @@ class LocalFileDeleteBatch { 'fa_user' => 'oi_user', 'fa_user_text' => 'oi_user_text', 'fa_timestamp' => 'oi_timestamp', + 'fa_sha1' => 'oi_sha1', ), $where, __METHOD__ ); } } @@ -1836,7 +1970,7 @@ class LocalFileDeleteBatch { $this->file->lock(); // Leave private files alone $privateFiles = array(); - list( $oldRels, $deleteCurrent ) = $this->getOldRels(); + list( $oldRels, ) = $this->getOldRels(); $dbw = $this->file->repo->getMasterDB(); if ( !empty( $oldRels ) ) { @@ -1914,7 +2048,7 @@ class LocalFileDeleteBatch { $files = $newBatch = array(); foreach ( $batch as $batchItem ) { - list( $src, $dest ) = $batchItem; + list( $src, ) = $batchItem; $files[$src] = $this->file->repo->getVirtualUrl( 'public' ) . '/' . rawurlencode( $src ); } @@ -2004,7 +2138,9 @@ class LocalFileRestoreBatch { $conditions[] = 'fa_id IN (' . $dbw->makeList( $this->ids ) . ')'; } - $result = $dbw->select( 'filearchive', '*', + $result = $dbw->select( + 'filearchive', + ArchivedFile::selectFields(), $conditions, __METHOD__, array( 'ORDER BY' => 'fa_timestamp DESC' ) @@ -2037,7 +2173,12 @@ class LocalFileRestoreBatch { $deletedRel = $this->file->repo->getDeletedHashPath( $row->fa_storage_key ) . $row->fa_storage_key; $deletedUrl = $this->file->repo->getVirtualUrl() . '/deleted/' . $deletedRel; - $sha1 = substr( $row->fa_storage_key, 0, strcspn( $row->fa_storage_key, '.' ) ); + if( isset( $row->fa_sha1 ) ) { + $sha1 = $row->fa_sha1; + } else { + // old row, populate from key + $sha1 = LocalRepo::getHashFromKey( $row->fa_storage_key ); + } # Fix leading zero if ( strlen( $sha1 ) == 32 && $sha1[0] == '0' ) { @@ -2251,7 +2392,7 @@ class LocalFileRestoreBatch { /** * Delete unused files in the deleted zone. * This should be called from outside the transaction in which execute() was called. - * @return FileRepoStatus|void + * @return FileRepoStatus */ function cleanup() { if ( !$this->cleanupBatch ) { @@ -2492,7 +2633,7 @@ class LocalFileMoveBatch { */ function getMoveTriplets() { $moves = array_merge( array( $this->cur ), $this->olds ); - $triplets = array(); // The format is: (srcUrl, destZone, destUrl) + $triplets = array(); // The format is: (srcUrl, destZone, destUrl) foreach ( $moves as $move ) { // $move: (oldRelativePath, newRelativePath) diff --git a/includes/filerepo/file/OldLocalFile.php b/includes/filerepo/file/OldLocalFile.php index 40d7dca7..5c505928 100644 --- a/includes/filerepo/file/OldLocalFile.php +++ b/includes/filerepo/file/OldLocalFile.php @@ -42,7 +42,7 @@ class OldLocalFile extends LocalFile { static function newFromTitle( $title, $repo, $time = null ) { # The null default value is only here to avoid an E_STRICT if ( $time === null ) { - throw new MWException( __METHOD__.' got null for $time parameter' ); + throw new MWException( __METHOD__ . ' got null for $time parameter' ); } return new self( $title, $repo, $time, null ); } @@ -73,7 +73,7 @@ class OldLocalFile extends LocalFile { * Create a OldLocalFile from a SHA-1 key * Do not call this except from inside a repo class. * - * @param $sha1 string base-36 SHA-1 + * @param string $sha1 base-36 SHA-1 * @param $repo LocalRepo * @param string|bool $timestamp MW_timestamp (optional) * @@ -123,8 +123,8 @@ class OldLocalFile extends LocalFile { /** * @param $title Title * @param $repo FileRepo - * @param $time String: timestamp or null to load by archive name - * @param $archiveName String: archive name or null to load by timestamp + * @param string $time timestamp or null to load by archive name + * @param string $archiveName archive name or null to load by timestamp * @throws MWException */ function __construct( $title, $repo, $time, $archiveName ) { @@ -132,7 +132,7 @@ class OldLocalFile extends LocalFile { $this->requestedTime = $time; $this->archive_name = $archiveName; if ( is_null( $time ) && is_null( $archiveName ) ) { - throw new MWException( __METHOD__.': must specify at least one of $time or $archiveName' ); + throw new MWException( __METHOD__ . ': must specify at least one of $time or $archiveName' ); } } @@ -164,18 +164,19 @@ class OldLocalFile extends LocalFile { * @return bool */ function isVisible() { - return $this->exists() && !$this->isDeleted(File::DELETED_FILE); + return $this->exists() && !$this->isDeleted( File::DELETED_FILE ); } function loadFromDB() { wfProfileIn( __METHOD__ ); + $this->dataLoaded = true; $dbr = $this->repo->getSlaveDB(); $conds = array( 'oi_name' => $this->getName() ); if ( is_null( $this->requestedTime ) ) { $conds['oi_archive_name'] = $this->archive_name; } else { - $conds[] = 'oi_timestamp = ' . $dbr->addQuotes( $dbr->timestamp( $this->requestedTime ) ); + $conds['oi_timestamp'] = $dbr->timestamp( $this->requestedTime ); } $row = $dbr->selectRow( 'oldimage', $this->getCacheFields( 'oi_' ), $conds, __METHOD__, array( 'ORDER BY' => 'oi_timestamp DESC' ) ); @@ -184,6 +185,42 @@ class OldLocalFile extends LocalFile { } else { $this->fileExists = false; } + + wfProfileOut( __METHOD__ ); + } + + /** + * Load lazy file metadata from the DB + */ + protected function loadExtraFromDB() { + wfProfileIn( __METHOD__ ); + + $this->extraDataLoaded = true; + $dbr = $this->repo->getSlaveDB(); + $conds = array( 'oi_name' => $this->getName() ); + if ( is_null( $this->requestedTime ) ) { + $conds['oi_archive_name'] = $this->archive_name; + } else { + $conds['oi_timestamp'] = $dbr->timestamp( $this->requestedTime ); + } + // In theory the file could have just been renamed/deleted...oh well + $row = $dbr->selectRow( 'oldimage', $this->getLazyCacheFields( 'oi_' ), + $conds, __METHOD__, array( 'ORDER BY' => 'oi_timestamp DESC' ) ); + + if ( !$row ) { // fallback to master + $dbr = $this->repo->getMasterDB(); + $row = $dbr->selectRow( 'oldimage', $this->getLazyCacheFields( 'oi_' ), + $conds, __METHOD__, array( 'ORDER BY' => 'oi_timestamp DESC' ) ); + } + + if ( $row ) { + foreach ( $this->unprefixRow( $row, 'oi_' ) as $name => $value ) { + $this->$name = $value; + } + } else { + throw new MWException( "Could not find data for image '{$this->archive_name}'." ); + } + wfProfileOut( __METHOD__ ); } @@ -218,7 +255,7 @@ class OldLocalFile extends LocalFile { # Don't destroy file info of missing files if ( !$this->fileExists ) { - wfDebug( __METHOD__.": file does not exist, aborting\n" ); + wfDebug( __METHOD__ . ": file does not exist, aborting\n" ); wfProfileOut( __METHOD__ ); return; } @@ -226,7 +263,7 @@ class OldLocalFile extends LocalFile { $dbw = $this->repo->getMasterDB(); list( $major, $minor ) = self::splitMime( $this->mime ); - wfDebug(__METHOD__.': upgrading '.$this->archive_name." to the current schema\n"); + wfDebug( __METHOD__ . ': upgrading ' . $this->archive_name . " to the current schema\n" ); $dbw->update( 'oldimage', array( 'oi_size' => $this->size, // sanity @@ -281,8 +318,8 @@ class OldLocalFile extends LocalFile { /** * Upload a file directly into archive. Generally for Special:Import. * - * @param $srcPath string File system path of the source file - * @param $archiveName string Full archive name of the file, in the form + * @param string $srcPath File system path of the source file + * @param string $archiveName Full archive name of the file, in the form * $timestamp!$filename, where $filename must match $this->getName() * * @param $timestamp string @@ -313,10 +350,10 @@ class OldLocalFile extends LocalFile { /** * Record a file upload in the oldimage table, without adding log entries. * - * @param $srcPath string File system path to the source file - * @param $archiveName string The archive name of the file + * @param string $srcPath File system path to the source file + * @param string $archiveName The archive name of the file * @param $timestamp string - * @param $comment string Upload comment + * @param string $comment Upload comment * @param $user User User who did this upload * @return bool */ diff --git a/includes/filerepo/file/UnregisteredLocalFile.php b/includes/filerepo/file/UnregisteredLocalFile.php index 8d4a3f88..47ba6d6b 100644 --- a/includes/filerepo/file/UnregisteredLocalFile.php +++ b/includes/filerepo/file/UnregisteredLocalFile.php @@ -42,7 +42,7 @@ class UnregisteredLocalFile extends File { var $handler; /** - * @param $path string Storage path + * @param string $path Storage path * @param $mime string * @return UnregisteredLocalFile */ @@ -71,7 +71,7 @@ class UnregisteredLocalFile extends File { */ function __construct( $title = false, $repo = false, $path = false, $mime = false ) { if ( !( $title && $repo ) && !$path ) { - throw new MWException( __METHOD__.': not enough parameters, must specify title and repo, or a full path' ); + throw new MWException( __METHOD__ . ': not enough parameters, must specify title and repo, or a full path' ); } if ( $title instanceof Title ) { $this->title = File::normalizeTitle( $title, 'exception' ); @@ -179,10 +179,18 @@ class UnregisteredLocalFile extends File { */ function getSize() { $this->assertRepoDefined(); - $props = $this->repo->getFileProps( $this->path ); - if ( isset( $props['size'] ) ) { - return $props['size']; - } - return false; // doesn't exist + return $this->repo->getFileSize( $this->path ); + } + + /** + * Optimize getLocalRefPath() by using an existing local reference. + * The file at the path of $fsFile should not be deleted (or at least + * not until the end of the request). This is mostly a performance hack. + * + * @param $fsFile FSFile + * @return void + */ + public function setLocalReference( FSFile $fsFile ) { + $this->fsFile = $fsFile; } } |