Index: trunk/phase3/includes/filerepo/file/File.php |
— | — | @@ -757,57 +757,63 @@ |
758 | 758 | global $wgIgnoreImageErrors, $wgThumbnailEpoch; |
759 | 759 | |
760 | 760 | $thumbPath = $this->getThumbPath( $thumbName ); // final thumb path |
761 | | - if ( $this->repo && $this->repo->canTransformVia404() && !( $flags & self::RENDER_NOW ) ) { |
762 | | - wfDebug( __METHOD__ . " transformation deferred." ); |
763 | | - // XXX: Pass in the storage path even though we are not rendering anything |
764 | | - // and the path is supposed to be an FS path. This is due to getScalerType() |
765 | | - // getting called on the path and clobbering $thumb->getUrl() if it's false. |
766 | | - return $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params ); |
767 | | - } |
768 | | - |
769 | | - wfDebug( __METHOD__.": Doing stat for $thumbPath\n" ); |
770 | | - $this->migrateThumbFile( $thumbName ); |
771 | | - if ( $this->repo->fileExists( $thumbPath ) && !( $flags & self::RENDER_FORCE ) ) { |
772 | | - $timestamp = $this->repo->getFileTimestamp( $thumbPath ); |
773 | | - if ( $timestamp !== false && $timestamp >= $wgThumbnailEpoch ) { |
| 761 | + if ( $this->repo ) { |
| 762 | + // Defer rendering if a 404 handler is set up... |
| 763 | + if ( $this->repo->canTransformVia404() && !( $flags & self::RENDER_NOW ) ) { |
| 764 | + wfDebug( __METHOD__ . " transformation deferred." ); |
774 | 765 | // XXX: Pass in the storage path even though we are not rendering anything |
775 | 766 | // and the path is supposed to be an FS path. This is due to getScalerType() |
776 | 767 | // getting called on the path and clobbering $thumb->getUrl() if it's false. |
777 | 768 | return $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params ); |
778 | 769 | } |
779 | | - } elseif ( $flags & self::RENDER_FORCE ) { |
780 | | - wfDebug( __METHOD__ . " forcing rendering per flag File::RENDER_FORCE\n" ); |
| 770 | + // Clean up broken thumbnails as needed |
| 771 | + $this->migrateThumbFile( $thumbName ); |
| 772 | + // Check if an up-to-date thumbnail already exists... |
| 773 | + wfDebug( __METHOD__.": Doing stat for $thumbPath\n" ); |
| 774 | + if ( $this->repo->fileExists( $thumbPath ) && !( $flags & self::RENDER_FORCE ) ) { |
| 775 | + $timestamp = $this->repo->getFileTimestamp( $thumbPath ); |
| 776 | + if ( $timestamp !== false && $timestamp >= $wgThumbnailEpoch ) { |
| 777 | + // XXX: Pass in the storage path even though we are not rendering anything |
| 778 | + // and the path is supposed to be an FS path. This is due to getScalerType() |
| 779 | + // getting called on the path and clobbering $thumb->getUrl() if it's false. |
| 780 | + $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params ); |
| 781 | + $thumb->setStoragePath( $thumbPath ); |
| 782 | + return $thumb; |
| 783 | + } |
| 784 | + } elseif ( $flags & self::RENDER_FORCE ) { |
| 785 | + wfDebug( __METHOD__ . " forcing rendering per flag File::RENDER_FORCE\n" ); |
| 786 | + } |
781 | 787 | } |
782 | 788 | |
783 | 789 | // Create a temp FS file with the same extension and the thumbnail |
784 | 790 | $thumbExt = FileBackend::extensionFromPath( $thumbPath ); |
785 | 791 | $tmpFile = TempFSFile::factory( 'transform_', $thumbExt ); |
786 | 792 | if ( !$tmpFile ) { |
787 | | - return new MediaTransformError( 'thumbnail_error', |
788 | | - $params['width'], 0, wfMsg( 'thumbnail-temp-create' ) ); |
| 793 | + return $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags ); |
789 | 794 | } |
790 | 795 | $tmpThumbPath = $tmpFile->getPath(); // path of 0-byte temp file |
791 | 796 | |
792 | | - // Actually render the thumbnail |
| 797 | + // Actually render the thumbnail... |
793 | 798 | $thumb = $this->handler->doTransform( $this, $tmpThumbPath, $thumbUrl, $params ); |
794 | 799 | $tmpFile->bind( $thumb ); // keep alive with $thumb |
795 | 800 | |
796 | | - // Ignore errors if requested |
797 | | - if ( !$thumb ) { |
| 801 | + if ( !$thumb ) { // bad params? |
798 | 802 | $thumb = null; |
799 | | - } elseif ( $thumb->isError() ) { |
| 803 | + } elseif ( $thumb->isError() ) { // transform error |
800 | 804 | $this->lastError = $thumb->toText(); |
| 805 | + // Ignore errors if requested |
801 | 806 | if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) { |
802 | 807 | $thumb = $this->handler->getTransform( $this, $tmpThumbPath, $thumbUrl, $params ); |
803 | 808 | } |
804 | 809 | } elseif ( $thumb->hasFile() && !$thumb->fileIsSource() ) { |
805 | | - // Copy any thumbnail from the FS into storage at $dstpath |
| 810 | + // Copy the thumbnail from the file system into storage |
806 | 811 | $status = $this->repo->store( |
807 | 812 | $tmpThumbPath, 'thumb', $this->getThumbRel( $thumbName ), |
808 | 813 | FileRepo::OVERWRITE | FileRepo::SKIP_LOCKING | FileRepo::ALLOW_STALE ); |
809 | | - if ( !$status->isOK() ) { |
810 | | - return new MediaTransformError( 'thumbnail_error', |
811 | | - $params['width'], 0, wfMsg( 'thumbnail-dest-create' ) ); |
| 814 | + if ( $status->isOK() ) { |
| 815 | + $thumb->setStoragePath( $thumbPath ); |
| 816 | + } else { |
| 817 | + $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags ); |
812 | 818 | } |
813 | 819 | } |
814 | 820 | |
— | — | @@ -815,6 +821,26 @@ |
816 | 822 | } |
817 | 823 | |
818 | 824 | /** |
| 825 | + * Return either a MediaTransformError or placeholder thumbnail (if $wgIgnoreImageErrors) |
| 826 | + * |
| 827 | + * @param $thumbPath string Thumbnail storage path |
| 828 | + * @param $thumbUrl string Thumbnail URL |
| 829 | + * @param $params Array |
| 830 | + * @param $flags integer |
| 831 | + * @return MediaTransformOutput |
| 832 | + */ |
| 833 | + protected function transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags ) { |
| 834 | + global $wgIgnoreImageErrors; |
| 835 | + |
| 836 | + if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) { |
| 837 | + return $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params ); |
| 838 | + } else { |
| 839 | + return new MediaTransformError( 'thumbnail_error', |
| 840 | + $params['width'], 0, wfMsg( 'thumbnail-dest-create' ) ); |
| 841 | + } |
| 842 | + } |
| 843 | + |
| 844 | + /** |
819 | 845 | * Transform a media file |
820 | 846 | * |
821 | 847 | * @param $params Array: an associative array of handler-specific parameters. |
Index: trunk/phase3/includes/media/MediaTransformOutput.php |
— | — | @@ -18,6 +18,7 @@ |
19 | 19 | var $file; |
20 | 20 | |
21 | 21 | var $width, $height, $url, $page, $path; |
| 22 | + protected $storagePath = false; |
22 | 23 | |
23 | 24 | /** |
24 | 25 | * Get the width of the output box |
— | — | @@ -41,6 +42,21 @@ |
42 | 43 | } |
43 | 44 | |
44 | 45 | /** |
| 46 | + * @return string|false The permanent thumbnail storage path |
| 47 | + */ |
| 48 | + public function getStoragePath() { |
| 49 | + return $this->storagePath; |
| 50 | + } |
| 51 | + |
| 52 | + /** |
| 53 | + * @param $storagePath string The permanent storage path |
| 54 | + * @return void |
| 55 | + */ |
| 56 | + public function setStoragePath( $storagePath ) { |
| 57 | + $this->storagePath = $storagePath; |
| 58 | + } |
| 59 | + |
| 60 | + /** |
45 | 61 | * Fetch HTML for this transform output |
46 | 62 | * |
47 | 63 | * @param $options array Associative array of options. Boolean options |
Index: trunk/phase3/includes/media/Bitmap.php |
— | — | @@ -154,7 +154,7 @@ |
155 | 155 | if ( $flags & self::TRANSFORM_LATER ) { |
156 | 156 | wfDebug( __METHOD__ . ": Transforming later per flags.\n" ); |
157 | 157 | return new ThumbnailImage( $image, $dstUrl, $scalerParams['clientWidth'], |
158 | | - $scalerParams['clientHeight'], $dstPath ); |
| 158 | + $scalerParams['clientHeight'], false ); |
159 | 159 | } |
160 | 160 | |
161 | 161 | # Try to make a target path for the thumbnail |
Index: trunk/phase3/includes/specials/SpecialUploadStash.php |
— | — | @@ -166,14 +166,15 @@ |
167 | 167 | } |
168 | 168 | |
169 | 169 | // we should have just generated it locally |
170 | | - if ( ! $thumbnailImage->getPath() ) { |
| 170 | + if ( !$thumbnailImage->getStoragePath() ) { |
171 | 171 | throw new UploadStashFileNotFoundException( "no local path for scaled item" ); |
172 | 172 | } |
173 | 173 | |
174 | 174 | // now we should construct a File, so we can get mime and other such info in a standard way |
175 | 175 | // n.b. mimetype may be different from original (ogx original -> jpeg thumb) |
176 | | - $thumbFile = new UnregisteredLocalFile( false, $this->stash->repo, $thumbnailImage->getPath(), false ); |
177 | | - if ( ! $thumbFile ) { |
| 176 | + $thumbFile = new UnregisteredLocalFile( false, |
| 177 | + $this->stash->repo, $thumbnailImage->getStoragePath(), false ); |
| 178 | + if ( !$thumbFile ) { |
178 | 179 | throw new UploadStashFileNotFoundException( "couldn't create local file object for thumbnail" ); |
179 | 180 | } |
180 | 181 | |
— | — | @@ -238,18 +239,17 @@ |
239 | 240 | /** |
240 | 241 | * Output HTTP response for file |
241 | 242 | * Side effect: writes HTTP response to STDOUT. |
242 | | - * XXX could use wfStreamfile (in includes/Streamfile.php), but for consistency with outputContents() doing it this way. |
243 | | - * XXX is mimeType really enough, or do we need encoding for full Content-Type header? |
244 | 243 | * |
245 | 244 | * @param $file File object with a local path (e.g. UnregisteredLocalFile, LocalFile. Oddly these don't share an ancestor!) |
246 | 245 | */ |
247 | | - private function outputLocalFile( $file ) { |
| 246 | + private function outputLocalFile( File $file ) { |
248 | 247 | if ( $file->getSize() > self::MAX_SERVE_BYTES ) { |
249 | 248 | throw new SpecialUploadStashTooLargeException(); |
250 | 249 | } |
251 | | - self::outputFileHeaders( $file->getMimeType(), $file->getSize() ); |
252 | | - readfile( $file->getPath() ); |
253 | | - return true; |
| 250 | + return $file->getRepo()->streamFile( $file->getPath(), |
| 251 | + array( 'Content-Transfer-Encoding: binary', |
| 252 | + 'Expires: Sun, 17-Jan-2038 19:14:07 GMT' ) |
| 253 | + ); |
254 | 254 | } |
255 | 255 | |
256 | 256 | /** |