<?php
+/**
+ * @defgroup FileAbstraction File abstraction
+ * @ingroup FileRepo
+ *
+ * Represents files in a repository.
+ */
+
/**
* Base code for files.
*
* @file
- * @ingroup FileRepo
+ * @ingroup FileAbstraction
*/
/**
* The convenience functions wfLocalFile() and wfFindFile() should be sufficient
* in most cases.
*
- * @ingroup FileRepo
+ * @ingroup FileAbstraction
*/
abstract class File {
const DELETED_FILE = 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
+ * I.e. you have to pass both flags: File::RENDER_NOW | File::RENDER_FORCE
*/
const RENDER_FORCE = 2;
/**
* Call this constructor from child classes.
- *
+ *
* Both $title and $repo are optional, though some functions
* may return false or throw exceptions if they are not set.
* Most subclasses will want to call assertRepoDefined() here.
* @return string
*/
function getViewURL() {
- if( $this->mustRender()) {
- if( $this->canRender() ) {
+ if ( $this->mustRender() ) {
+ if ( $this->canRender() ) {
return $this->createThumb( $this->getWidth() );
} else {
- wfDebug(__METHOD__.': supposed to render '.$this->getName().' ('.$this->getMimeType()."), but can't!\n");
+ wfDebug( __METHOD__.': supposed to render ' . $this->getName() .
+ ' (' . $this->getMimeType() . "), but can't!\n" );
return $this->getURL(); #hm... return NULL?
}
} else {
* Get an FS copy or original of this file and return the path.
* Returns false on failure. Callers must not alter the file.
* Temporary files are cleared automatically.
- *
+ *
* @return string|false
*/
public function getLocalRefPath() {
}
/**
- * Do the work of a transform (from an original into a thumb).
- * Contains filesystem-specific functions.
- *
- * @param $thumbName string: the name of the thumbnail file.
- * @param $thumbUrl string: the URL of the thumbnail file.
- * @param $params Array: 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|null
+ * Return either a MediaTransformError or placeholder thumbnail (if $wgIgnoreImageErrors)
+ *
+ * @param $thumbPath string Thumbnail storage path
+ * @param $thumbUrl string Thumbnail URL
+ * @param $params Array
+ * @param $flags integer
+ * @return MediaTransformOutput
*/
- protected function maybeDoTransform( $thumbName, $thumbUrl, $params, $flags = 0 ) {
- global $wgIgnoreImageErrors, $wgThumbnailEpoch;
+ protected function transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags ) {
+ global $wgIgnoreImageErrors;
- $thumbPath = $this->getThumbPath( $thumbName ); // final thumb path
- if ( $this->repo && $this->repo->canTransformVia404() && !( $flags & self::RENDER_NOW ) ) {
- wfDebug( __METHOD__ . " transformation deferred." );
- // 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 the $thumb->getUrl() if it's false.
+ if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) {
return $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
- }
-
- wfDebug( __METHOD__.": Doing stat for $thumbPath\n" );
- $this->migrateThumbFile( $thumbName );
- if ( $this->repo->fileExists( $thumbPath ) && !( $flags & self::RENDER_FORCE ) ) {
- $timestamp = $this->repo->getFileTimestamp( $thumbPath );
- if ( $timestamp !== false && $timestamp >= $wgThumbnailEpoch ) {
- return $this->handler->getTransform( $this, false, $thumbUrl, $params );
- }
- } elseif ( $flags & self::RENDER_FORCE ) {
- wfDebug( __METHOD__ . " forcing rendering per flag File::RENDER_FORCE\n" );
- }
-
- // Create a temp FS file with the same extension
- $tmpFile = TempFSFile::factory( 'transform_', $this->getExtension() );
- if ( !$tmpFile ) {
+ } else {
return new MediaTransformError( 'thumbnail_error',
- $params['width'], 0, wfMsg( 'thumbnail-temp-create' ) );
+ $params['width'], 0, wfMsg( 'thumbnail-dest-create' ) );
}
- $tmpThumbPath = $tmpFile->getPath(); // path of 0-byte temp file
-
- // Actually render the thumbnail
- $thumb = $this->handler->doTransform( $this, $tmpThumbPath, $thumbUrl, $params );
- $tmpFile->bind( $thumb ); // keep alive with $thumb
-
- // Ignore errors if requested
- if ( !$thumb ) {
- $thumb = null;
- } elseif ( $thumb->isError() ) {
- $this->lastError = $thumb->toText();
- if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) {
- $thumb = $this->handler->getTransform( $this, $tmpThumbPath, $thumbUrl, $params );
- }
- } elseif ( $thumb->hasFile() && !$thumb->fileIsSource() ) {
- // @TODO: use a FileRepo store function
- $op = array( 'op' => 'store',
- 'src' => $tmpThumbPath, 'dst' => $thumbPath, 'overwriteDest' => true );
- // Copy any thumbnail from the FS into storage at $dstpath
- $opts = array( 'ignoreErrors' => true, 'nonLocking' => true ); // performance
- if ( !$this->getRepo()->getBackend()->doOperation( $op, $opts )->isOK() ) {
- return new MediaTransformError( 'thumbnail_error',
- $params['width'], 0, wfMsg( 'thumbnail-dest-create' ) );
- }
- }
-
- return $thumb;
}
/**
* @param $params Array: 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 | false
+ * @return MediaTransformOutput|false
*/
function transform( $params, $flags = 0 ) {
- global $wgUseSquid;
+ global $wgUseSquid, $wgIgnoreImageErrors, $wgThumbnailEpoch;
wfProfileIn( __METHOD__ );
do {
if ( !$this->canRender() ) {
- // not a bitmap or renderable image, don't try.
$thumb = $this->iconThumb();
- break;
+ break; // not a bitmap or renderable image, don't try
}
// Get the descriptionUrl to embed it as comment into the thumbnail. Bug 19791.
- $descriptionUrl = $this->getDescriptionUrl();
+ $descriptionUrl = $this->getDescriptionUrl();
if ( $descriptionUrl ) {
$params['descriptionUrl'] = wfExpandUrl( $descriptionUrl, PROTO_CANONICAL );
}
$script = $this->getTransformScript();
- if ( $script && !($flags & self::RENDER_NOW) ) {
+ if ( $script && !( $flags & self::RENDER_NOW ) ) {
// Use a script to transform on client request, if possible
$thumb = $this->handler->getScriptedTransform( $this, $script, $params );
- if( $thumb ) {
+ if ( $thumb ) {
break;
}
}
$normalisedParams = $params;
$this->handler->normaliseParams( $this, $normalisedParams );
+
$thumbName = $this->thumbName( $normalisedParams );
$thumbUrl = $this->getThumbUrl( $thumbName );
+ $thumbPath = $this->getThumbPath( $thumbName ); // final thumb path
- $thumb = $this->maybeDoTransform( $thumbName, $thumbUrl, $params, $flags );
+ if ( $this->repo ) {
+ // Defer rendering if a 404 handler is set up...
+ if ( $this->repo->canTransformVia404() && !( $flags & self::RENDER_NOW ) ) {
+ wfDebug( __METHOD__ . " transformation deferred." );
+ // 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 );
+ 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 ) ) {
+ $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->setStoragePath( $thumbPath );
+ break;
+ }
+ } elseif ( $flags & self::RENDER_FORCE ) {
+ wfDebug( __METHOD__ . " forcing rendering per flag File::RENDER_FORCE\n" );
+ }
+ }
+
+ // Create a temp FS file with the same extension and the thumbnail
+ $thumbExt = FileBackend::extensionFromPath( $thumbPath );
+ $tmpFile = TempFSFile::factory( 'transform_', $thumbExt );
+ if ( !$tmpFile ) {
+ $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
+ break;
+ }
+ $tmpThumbPath = $tmpFile->getPath(); // path of 0-byte temp file
+
+ // Actually render the thumbnail...
+ $thumb = $this->handler->doTransform( $this, $tmpThumbPath, $thumbUrl, $params );
+ $tmpFile->bind( $thumb ); // keep alive with $thumb
+
+ if ( !$thumb ) { // bad params?
+ $thumb = null;
+ } elseif ( $thumb->isError() ) { // transform error
+ $this->lastError = $thumb->toText();
+ // Ignore errors if requested
+ if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) {
+ $thumb = $this->handler->getTransform( $this, $tmpThumbPath, $thumbUrl, $params );
+ }
+ } elseif ( $this->repo && $thumb->hasFile() && !$thumb->fileIsSource() ) {
+ $backend = $this->repo->getBackend();
+ // Copy the thumbnail from the file system into storage. This avoids using
+ // FileRepo::store(); getThumbPath() uses a different zone in some subclasses.
+ $backend->prepare( array( 'dir' => dirname( $thumbPath ) ) );
+ $status = $backend->store(
+ array( 'src' => $tmpThumbPath, 'dst' => $thumbPath, 'overwrite' => 1 ),
+ array( 'force' => 1, 'nonLocking' => 1, 'allowStale' => 1 )
+ );
+ if ( $status->isOK() ) {
+ $thumb->setStoragePath( $thumbPath );
+ } else {
+ $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
+ }
+ }
// Purge. Useful in the event of Core -> Squid connection failure or squid
// purge collisions from elsewhere during failure. Don't keep triggering for
SquidUpdate::purge( array( $thumbUrl ) );
}
}
- } while (false);
+ } while ( false );
wfProfileOut( __METHOD__ );
return is_object( $thumb ) ? $thumb : false;
}
/**
- * Get the path of the file relative to the public zone root
+ * Get the path of the file relative to the public zone root.
+ * This function is overriden in OldLocalFile to be like getArchiveRel().
*
* @return string
*/
}
/**
- * Get urlencoded relative path of the file
- *
- * @return string
- */
- function getUrlRel() {
- return $this->getHashPath() . rawurlencode( $this->getName() );
- }
-
- /**
- * Get the relative path for an archived 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
*
}
/**
- * Get the relative path for an archived file's thumbs directory
+ * 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
+ *
+ * @return string
+ */
+ function getThumbRel( $suffix = false ) {
+ $path = $this->getRel();
+ if ( $suffix !== false ) {
+ $path .= '/' . $suffix;
+ }
+ return $path;
+ }
+
+ /**
+ * Get urlencoded path of the file relative to the public zone root.
+ * This function is overriden in OldLocalFile to be like getArchiveUrl().
+ *
+ * @return string
+ */
+ function getUrlRel() {
+ return $this->getHashPath() . rawurlencode( $this->getName() );
+ }
+
+ /**
+ * 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
}
/**
- * Get the path of the archived file's thumbs, or a particular thumb if $suffix is specified
+ * 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
*/
function getThumbPath( $suffix = false ) {
$this->assertRepoDefined();
- $path = $this->repo->getZonePath( 'thumb' ) . '/' . $this->getRel();
- if ( $suffix !== false ) {
- $path .= '/' . $suffix;
- }
- return $path;
+ return $this->repo->getZonePath( 'thumb' ) . '/' . $this->getThumbRel( $suffix );
}
/**
* @return bool
*/
function isLocal() {
- $repo = $this->getRepo();
- return $repo && $repo->isLocal();
+ return $this->repo && $this->repo->isLocal();
}
/**