enhance filerepo doc structure
[lhc/web/wiklou.git] / includes / filerepo / file / File.php
index 05f1e2c..f74fb67 100644 (file)
@@ -1,9 +1,16 @@
 <?php
+/**
+ * @defgroup FileAbstraction File abstraction
+ * @ingroup FileRepo
+ *
+ * Represents files in a repository.
+ */
+
 /**
  * Base code for files.
  *
  * @file
- * @ingroup FileRepo
+ * @ingroup FileAbstraction
  */
 
 /**
@@ -23,7 +30,7 @@
  * The convenience functions wfLocalFile() and wfFindFile() should be sufficient
  * in most cases.
  *
- * @ingroup FileRepo
+ * @ingroup FileAbstraction
  */
 abstract class File {
        const DELETED_FILE = 1;
@@ -35,7 +42,7 @@ abstract class File {
        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;
 
@@ -99,7 +106,7 @@ abstract class File {
 
        /**
         * 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.
@@ -298,11 +305,12 @@ abstract class File {
         * @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 {
@@ -335,7 +343,7 @@ abstract class File {
         * 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() {
@@ -741,73 +749,23 @@ abstract class File {
        }
 
        /**
-        * 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;
        }
 
        /**
@@ -816,40 +774,105 @@ abstract class 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 | 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
@@ -859,7 +882,7 @@ abstract class File {
                                        SquidUpdate::purge( array( $thumbUrl ) );
                                }
                        }
-               } while (false);
+               } while ( false );
 
                wfProfileOut( __METHOD__ );
                return is_object( $thumb ) ? $thumb : false;
@@ -1010,7 +1033,8 @@ abstract class File {
        }
 
        /**
-        * 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
         */
@@ -1019,16 +1043,7 @@ abstract class File {
        }
 
        /**
-        * 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
         *
@@ -1045,7 +1060,33 @@ abstract class 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
@@ -1076,7 +1117,7 @@ abstract class File {
        }
 
        /**
-        * 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
@@ -1098,11 +1139,7 @@ abstract class 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 );
        }
 
        /**
@@ -1277,8 +1314,7 @@ abstract class File {
         * @return bool
         */
        function isLocal() {
-               $repo = $this->getRepo();
-               return $repo && $repo->isLocal();
+               return $this->repo && $this->repo->isLocal();
        }
 
        /**