From b9b85843572bf283f48285001e276ba7e61b63f6 Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Sun, 22 Feb 2009 13:37:51 +0100 Subject: updated to MediaWiki 1.14.0 --- includes/filerepo/ArchivedFile.php | 44 +++++--- includes/filerepo/FSRepo.php | 10 +- includes/filerepo/File.php | 26 +++-- includes/filerepo/FileCache.php | 156 ++++++++++++++++++++++++++++ includes/filerepo/FileRepo.php | 80 +++++++------- includes/filerepo/ForeignAPIFile.php | 83 +++++++++++++-- includes/filerepo/ForeignAPIRepo.php | 77 ++++++++++++-- includes/filerepo/ForeignDBFile.php | 2 +- includes/filerepo/Image.php | 2 +- includes/filerepo/LocalFile.php | 99 +++++++++++------- includes/filerepo/LocalRepo.php | 7 +- includes/filerepo/OldLocalFile.php | 3 +- includes/filerepo/RepoGroup.php | 24 +++-- includes/filerepo/UnregisteredLocalFile.php | 2 +- 14 files changed, 482 insertions(+), 133 deletions(-) create mode 100644 includes/filerepo/FileCache.php (limited to 'includes/filerepo') diff --git a/includes/filerepo/ArchivedFile.php b/includes/filerepo/ArchivedFile.php index 646256bb..3919cfbc 100644 --- a/includes/filerepo/ArchivedFile.php +++ b/includes/filerepo/ArchivedFile.php @@ -30,12 +30,9 @@ class ArchivedFile /**#@-*/ function ArchivedFile( $title, $id=0, $key='' ) { - if( !is_object($title) ) { - throw new MWException( 'ArchivedFile constructor given bogus title.' ); - } $this->id = -1; - $this->title = $title; - $this->name = $title->getDBkey(); + $this->title = false; + $this->name = false; $this->group = ''; $this->key = ''; $this->size = 0; @@ -51,6 +48,20 @@ class ArchivedFile $this->timestamp = NULL; $this->deleted = 0; $this->dataLoaded = false; + + if( is_object($title) ) { + $this->title = $title; + $this->name = $title->getDBkey(); + } + + if ($id) + $this->id = $id; + + if ($key) + $this->key = $key; + + if (!$id && !$key && !is_object($title)) + throw new MWException( "No specifications provided to ArchivedFile constructor." ); } /** @@ -61,8 +72,19 @@ class ArchivedFile if ( $this->dataLoaded ) { return true; } - $conds = ($this->id) ? "fa_id = {$this->id}" : "fa_storage_key = '{$this->key}'"; - if( $this->title->getNamespace() == NS_IMAGE ) { + $conds = array(); + + if ($this->id>0) + $conds['fa_id'] = $this->id; + if ($this->key) + $conds['fa_storage_key'] = $this->key; + if ($this->title) + $conds['fa_name'] = $this->title->getDBkey(); + + if (!count($conds)) + throw new MWException( "No specific information for retrieving archived file" ); + + if( !$this->title || $this->title->getNamespace() == NS_FILE ) { $dbr = wfGetDB( DB_SLAVE ); $res = $dbr->select( 'filearchive', array( @@ -84,9 +106,7 @@ class ArchivedFile 'fa_user_text', 'fa_timestamp', 'fa_deleted' ), - array( - 'fa_name' => $this->title->getDBkey(), - $conds ), + $conds, __METHOD__, array( 'ORDER BY' => 'fa_timestamp DESC' ) ); @@ -129,7 +149,7 @@ class ArchivedFile * @return ResultWrapper */ public static function newFromRow( $row ) { - $file = new ArchivedFile( Title::makeTitle( NS_IMAGE, $row->fa_name ) ); + $file = new ArchivedFile( Title::makeTitle( NS_FILE, $row->fa_name ) ); $file->id = intval($row->fa_id); $file->name = $row->fa_name; @@ -251,7 +271,7 @@ class ArchivedFile */ public function getTimestamp() { $this->load(); - return $this->timestamp; + return wfTimestamp( TS_MW, $this->timestamp ); } /** diff --git a/includes/filerepo/FSRepo.php b/includes/filerepo/FSRepo.php index eb8df0f5..d561e61b 100644 --- a/includes/filerepo/FSRepo.php +++ b/includes/filerepo/FSRepo.php @@ -6,7 +6,7 @@ * @ingroup FileRepo */ class FSRepo extends FileRepo { - var $directory, $deletedDir, $url, $hashLevels, $deletedHashLevels; + var $directory, $deletedDir, $url, $deletedHashLevels; var $fileFactory = array( 'UnregisteredLocalFile', 'newFromTitle' ); var $oldFileFactory = false; var $pathDisclosureProtection = 'simple'; @@ -451,14 +451,6 @@ class FSRepo extends FileRepo { return $status; } - /** - * Get a relative path including trailing slash, e.g. f/fa/ - * If the repo is not hashed, returns an empty string - */ - function getHashPath( $name ) { - return FileRepo::getHashPathForLevel( $name, $this->hashLevels ); - } - /** * Get a relative path for a deletion archive key, * e.g. s/z/a/ for sza251lrxrc1jad41h5mgilp8nysje52.jpg diff --git a/includes/filerepo/File.php b/includes/filerepo/File.php index 64b48e0a..4f0990af 100644 --- a/includes/filerepo/File.php +++ b/includes/filerepo/File.php @@ -264,7 +264,14 @@ abstract class File { * Overridden by LocalFile, UnregisteredLocalFile * STUB */ - function getMetadata() { return false; } + public function getMetadata() { return false; } + + /** + * Return the bit depth of the file + * Overridden by LocalFile + * STUB + */ + public function getBitDepth() { return 0; } /** * Return the size of the image file, in bytes @@ -499,8 +506,7 @@ abstract class File { * * @param integer $width maximum width of the generated thumbnail * @param integer $height maximum height of the image (optional) - * @param boolean $render True to render the thumbnail if it doesn't exist, - * false to just return the URL + * @param boolean $render Deprecated * * @return ThumbnailImage or null on failure * @@ -511,8 +517,7 @@ abstract class File { if ( $height != -1 ) { $params['height'] = $height; } - $flags = $render ? self::RENDER_NOW : 0; - return $this->transform( $params, $flags ); + return $this->transform( $params, 0 ); } /** @@ -575,7 +580,7 @@ abstract class File { // Purge. Useful in the event of Core -> Squid connection failure or squid // purge collisions from elsewhere during failure. Don't keep triggering for // "thumbs" which have the main image URL though (bug 13776) - if ( $wgUseSquid && ($thumb->isError() || $thumb->getUrl() != $this->getURL()) ) { + if ( $wgUseSquid && ( !$thumb || $thumb->isError() || $thumb->getUrl() != $this->getURL()) ) { SquidUpdate::purge( array( $thumbUrl ) ); } } while (false); @@ -678,8 +683,9 @@ abstract class File { * @param $limit integer Limit of rows to return * @param $start timestamp Only revisions older than $start will be returned * @param $end timestamp Only revisions newer than $end will be returned + * @param $inc bool Include the endpoints of the time range */ - function getHistory($limit = null, $start = null, $end = null) { + function getHistory($limit = null, $start = null, $end = null, $inc=true) { return array(); } @@ -1212,7 +1218,7 @@ abstract class File { if ( $handler ) { return $handler->getLongDesc( $this ); } else { - return MediaHandler::getLongDesc( $this ); + return MediaHandler::getGeneralLongDesc( $this ); } } @@ -1221,7 +1227,7 @@ abstract class File { if ( $handler ) { return $handler->getShortDesc( $this ); } else { - return MediaHandler::getShortDesc( $this ); + return MediaHandler::getGeneralShortDesc( $this ); } } @@ -1241,7 +1247,7 @@ abstract class File { function getRedirectedTitle() { if ( $this->redirected ) { if ( !$this->redirectTitle ) - $this->redirectTitle = Title::makeTitle( NS_IMAGE, $this->redirected ); + $this->redirectTitle = Title::makeTitle( NS_FILE, $this->redirected ); return $this->redirectTitle; } } diff --git a/includes/filerepo/FileCache.php b/includes/filerepo/FileCache.php new file mode 100644 index 00000000..7840d1a3 --- /dev/null +++ b/includes/filerepo/FileCache.php @@ -0,0 +1,156 @@ +repoGroup = $repoGroup; + } + + + /** + * Add some files to the cache. This is a fairly low-level function, + * which most users should not need to call. Note that any existing + * entries for the same keys will not be replaced. Call clearFiles() + * first if you need that. + * @param array $files array of File objects, indexed by DB key + */ + function addFiles( $files ) { + wfDebug( "FileCache adding ".count( $files )." files\n" ); + $this->cache += $files; + } + + /** + * Remove some files from the cache, so that their existence will be + * rechecked. This is a fairly low-level function, which most users + * should not need to call. + * @param array $remove array indexed by DB keys to remove (the values are ignored) + */ + function clearFiles( $remove ) { + wfDebug( "FileCache clearing data for ".count( $remove )." files\n" ); + $this->cache = array_diff_keys( $this->cache, $remove ); + $this->notFound = array_diff_keys( $this->notFound, $remove ); + } + + /** + * Mark some DB keys as nonexistent. This is a fairly low-level + * function, which most users should not need to call. + * @param array $dbkeys array of DB keys + */ + function markNotFound( $dbkeys ) { + wfDebug( "FileCache marking ".count( $dbkeys )." files as not found\n" ); + $this->notFound += array_fill_keys( $dbkeys, true ); + } + + + /** + * Search the cache for a file. + * @param mixed $title Title object or string + * @return File object or false if it is not found + * @todo Implement searching for old file versions(?) + */ + function findFile( $title ) { + if( !( $title instanceof Title ) ) { + $title = Title::makeTitleSafe( NS_FILE, $title ); + } + if( !$title ) { + return false; // invalid title? + } + + $dbkey = $title->getDBkey(); + if( array_key_exists( $dbkey, $this->cache ) ) { + wfDebug( "FileCache HIT for $dbkey\n" ); + return $this->cache[$dbkey]; + } + if( array_key_exists( $dbkey, $this->notFound ) ) { + wfDebug( "FileCache negative HIT for $dbkey\n" ); + return false; + } + + // Not in cache, fall back to a direct query + $file = $this->repoGroup->findFile( $title ); + if( $file ) { + wfDebug( "FileCache MISS for $dbkey\n" ); + $this->cache[$dbkey] = $file; + } else { + wfDebug( "FileCache negative MISS for $dbkey\n" ); + $this->notFound[$dbkey] = true; + } + return $file; + } + + /** + * Search the cache for multiple files. + * @param array $titles Title objects or strings to search for + * @return array of File objects, indexed by DB key + */ + function findFiles( $titles ) { + $titleObjs = array(); + foreach ( $titles as $title ) { + if ( !( $title instanceof Title ) ) { + $title = Title::makeTitleSafe( NS_FILE, $title ); + } + if ( $title ) { + $titleObjs[$title->getDBkey()] = $title; + } + } + + $result = array_intersect_key( $this->cache, $titleObjs ); + + $unsure = array_diff_key( $titleObjs, $result, $this->notFound ); + if( $unsure ) { + wfDebug( "FileCache MISS for ".count( $unsure )." files out of ".count( $titleObjs )."...\n" ); + // XXX: We assume the array returned by findFiles() is + // indexed by DBkey; this appears to be true, but should + // be explicitly documented. + $found = $this->repoGroup->findFiles( $unsure ); + $result += $found; + $this->addFiles( $found ); + $this->markNotFound( array_keys( array_diff_key( $unsure, $found ) ) ); + } + + wfDebug( "FileCache found ".count( $result )." files out of ".count( $titleObjs )."\n" ); + return $result; + } +} diff --git a/includes/filerepo/FileRepo.php b/includes/filerepo/FileRepo.php index edfc2a99..5beac732 100644 --- a/includes/filerepo/FileRepo.php +++ b/includes/filerepo/FileRepo.php @@ -15,6 +15,7 @@ abstract class FileRepo { var $thumbScriptUrl, $transformVia404; var $descBaseUrl, $scriptDirUrl, $articleUrl, $fetchDescription, $initialCapital; var $pathDisclosureProtection = 'paranoid'; + var $descriptionCacheExpiry, $apiThumbCacheExpiry, $hashLevels; /** * Factory functions for creating new files @@ -30,7 +31,8 @@ abstract class FileRepo { // Optional settings $this->initialCapital = true; // by default foreach ( array( 'descBaseUrl', 'scriptDirUrl', 'articleUrl', 'fetchDescription', - 'thumbScriptUrl', 'initialCapital', 'pathDisclosureProtection', 'descriptionCacheExpiry' ) as $var ) + 'thumbScriptUrl', 'initialCapital', 'pathDisclosureProtection', + 'descriptionCacheExpiry', 'apiThumbCacheExpiry', 'hashLevels' ) as $var ) { if ( isset( $info[$var] ) ) { $this->$var = $info[$var]; @@ -57,7 +59,7 @@ abstract class FileRepo { */ function newFile( $title, $time = false ) { if ( !($title instanceof Title) ) { - $title = Title::makeTitleSafe( NS_IMAGE, $title ); + $title = Title::makeTitleSafe( NS_FILE, $title ); if ( !is_object( $title ) ) { return null; } @@ -83,7 +85,7 @@ abstract class FileRepo { */ function findFile( $title, $time = false, $flags = 0 ) { if ( !($title instanceof Title) ) { - $title = Title::makeTitleSafe( NS_IMAGE, $title ); + $title = Title::makeTitleSafe( NS_FILE, $title ); if ( !is_object( $title ) ) { return false; } @@ -99,7 +101,7 @@ abstract class FileRepo { # Now try an old version of the file if ( $time !== false ) { $img = $this->newFile( $title, $time ); - if ( $img->exists() ) { + if ( $img && $img->exists() ) { if ( !$img->isDeleted(File::DELETED_FILE) ) { return $img; } else if ( ($flags & FileRepo::FIND_PRIVATE) && $img->userCan(File::DELETED_FILE) ) { @@ -113,7 +115,7 @@ abstract class FileRepo { return false; } $redir = $this->checkRedirect( $title ); - if( $redir && $redir->getNamespace() == NS_IMAGE) { + if( $redir && $redir->getNamespace() == NS_FILE) { $img = $this->newFile( $redir ); if( !$img ) { return false; @@ -129,12 +131,12 @@ abstract class FileRepo { /* * Find many files at once. * @param array $titles, an array of titles - * @param int $flags + * @todo Think of a good way to optionally pass timestamps to this function. */ - function findFiles( $titles, $flags ) { + function findFiles( $titles ) { $result = array(); foreach ( $titles as $index => $title ) { - $file = $this->findFile( $title, $flags ); + $file = $this->findFile( $title ); if ( $file ) $result[$file->getTitle()->getDBkey()] = $file; } @@ -236,6 +238,14 @@ abstract class FileRepo { return $path; } } + + /** + * Get a relative path including trailing slash, e.g. f/fa/ + * If the repo is not hashed, returns an empty string + */ + function getHashPath( $name ) { + return self::getHashPathForLevel( $name, $this->hashLevels ); + } /** * Get the name of this repository, as specified by $info['name]' to the constructor @@ -244,25 +254,6 @@ abstract class FileRepo { return $this->name; } - /** - * Get the file description page base URL, or false if there isn't one. - * @private - */ - function getDescBaseUrl() { - if ( is_null( $this->descBaseUrl ) ) { - if ( !is_null( $this->articleUrl ) ) { - $this->descBaseUrl = str_replace( '$1', - wfUrlencode( MWNamespace::getCanonicalName( NS_IMAGE ) ) . ':', $this->articleUrl ); - } elseif ( !is_null( $this->scriptDirUrl ) ) { - $this->descBaseUrl = $this->scriptDirUrl . '/index.php?title=' . - wfUrlencode( MWNamespace::getCanonicalName( NS_IMAGE ) ) . ':'; - } else { - $this->descBaseUrl = false; - } - } - return $this->descBaseUrl; - } - /** * Get the URL of an image description page. May return false if it is * unknown or not applicable. In general this should only be called by the @@ -273,12 +264,29 @@ abstract class FileRepo { * constructor, whereas local repositories use the local Title functions. */ function getDescriptionUrl( $name ) { - $base = $this->getDescBaseUrl(); - if ( $base ) { - return $base . wfUrlencode( $name ); - } else { - return false; + $encName = wfUrlencode( $name ); + if ( !is_null( $this->descBaseUrl ) ) { + # "http://example.com/wiki/Image:" + return $this->descBaseUrl . $encName; + } + if ( !is_null( $this->articleUrl ) ) { + # "http://example.com/wiki/$1" + # + # We use "Image:" as the canonical namespace for + # compatibility across all MediaWiki versions. + return str_replace( '$1', + "Image:$encName", $this->articleUrl ); } + if ( !is_null( $this->scriptDirUrl ) ) { + # "http://example.com/w" + # + # We use "Image:" as the canonical namespace for + # compatibility across all MediaWiki versions, + # and just sort of hope index.php is right. ;) + return $this->scriptDirUrl . + "/index.php?title=Image:$encName"; + } + return false; } /** @@ -290,12 +298,12 @@ abstract class FileRepo { function getDescriptionRenderUrl( $name ) { if ( isset( $this->scriptDirUrl ) ) { return $this->scriptDirUrl . '/index.php?title=' . - wfUrlencode( MWNamespace::getCanonicalName( NS_IMAGE ) . ':' . $name ) . + wfUrlencode( 'Image:' . $name ) . '&action=render'; } else { - $descBase = $this->getDescBaseUrl(); - if ( $descBase ) { - return wfAppendQuery( $descBase . wfUrlencode( $name ), 'action=render' ); + $descUrl = $this->getDescriptionUrl( $name ); + if ( $descUrl ) { + return wfAppendQuery( $descUrl, 'action=render' ); } else { return false; } diff --git a/includes/filerepo/ForeignAPIFile.php b/includes/filerepo/ForeignAPIFile.php index aaf92204..d9fb85d0 100644 --- a/includes/filerepo/ForeignAPIFile.php +++ b/includes/filerepo/ForeignAPIFile.php @@ -7,15 +7,19 @@ * @ingroup FileRepo */ class ForeignAPIFile extends File { - function __construct( $title, $repo, $info ) { + + private $mExists; + + function __construct( $title, $repo, $info, $exists = false ) { parent::__construct( $title, $repo ); $this->mInfo = $info; + $this->mExists = $exists; } static function newFromTitle( $title, $repo ) { $info = $repo->getImageInfo( $title ); if( $info ) { - return new ForeignAPIFile( $title, $repo, $info ); + return new ForeignAPIFile( $title, $repo, $info, true ); } else { return null; } @@ -23,7 +27,7 @@ class ForeignAPIFile extends File { // Dummy functions... public function exists() { - return true; + return $this->mExists; } public function getPath() { @@ -31,12 +35,15 @@ class ForeignAPIFile extends File { } function transform( $params, $flags = 0 ) { - $thumbUrl = $this->repo->getThumbUrl( - $this->getName(), - isset( $params['width'] ) ? $params['width'] : -1, - isset( $params['height'] ) ? $params['height'] : -1 ); + if( !$this->canRender() ) { + // show icon + return parent::transform( $params, $flags ); + } + $thumbUrl = $this->repo->getThumbUrlFromCache( + $this->getName(), + isset( $params['width'] ) ? $params['width'] : -1, + isset( $params['height'] ) ? $params['height'] : -1 ); if( $thumbUrl ) { - wfDebug( __METHOD__ . " got remote thumb $thumbUrl\n" ); return $this->handler->getTransform( $this, 'bogus', $thumbUrl, $params );; } return false; @@ -98,4 +105,64 @@ class ForeignAPIFile extends File { ? $this->mInfo['descriptionurl'] : false; } + + /** + * Only useful if we're locally caching thumbs anyway... + */ + function getThumbPath( $suffix = '' ) { + if ( $this->repo->canCacheThumbs() ) { + global $wgUploadDirectory; + $path = $wgUploadDirectory . '/thumb/' . $this->getHashPath( $this->getName() ); + if ( $suffix ) { + $path = $path . $suffix . '/'; + } + return $path; + } + else { + return null; + } + } + + function getThumbnails() { + $files = array(); + $dir = $this->getThumbPath( $this->getName() ); + if ( is_dir( $dir ) ) { + $handle = opendir( $dir ); + if ( $handle ) { + while ( false !== ( $file = readdir($handle) ) ) { + if ( $file{0} != '.' ) { + $files[] = $file; + } + } + closedir( $handle ); + } + } + return $files; + } + + function purgeCache() { + $this->purgeThumbnails(); + $this->purgeDescriptionPage(); + } + + function purgeDescriptionPage() { + global $wgMemc; + $url = $this->repo->getDescriptionRenderUrl( $this->getName() ); + $key = wfMemcKey( 'RemoteFileDescription', 'url', md5($url) ); + $wgMemc->delete( $key ); + } + + function purgeThumbnails() { + global $wgMemc; + $key = wfMemcKey( 'ForeignAPIRepo', 'ThumbUrl', $this->getName() ); + $wgMemc->delete( $key ); + $files = $this->getThumbnails(); + $dir = $this->getThumbPath( $this->getName() ); + foreach ( $files as $file ) { + unlink( $dir . $file ); + } + if ( is_dir( $dir ) ) { + rmdir( $dir ); // Might have already gone away, spews errors if we don't. + } + } } diff --git a/includes/filerepo/ForeignAPIRepo.php b/includes/filerepo/ForeignAPIRepo.php index 0dee699f..6fc9c465 100644 --- a/includes/filerepo/ForeignAPIRepo.php +++ b/includes/filerepo/ForeignAPIRepo.php @@ -19,6 +19,7 @@ */ class ForeignAPIRepo extends FileRepo { var $fileFactory = array( 'ForeignAPIFile', 'newFromTitle' ); + var $apiThumbCacheExpiry = 0; protected $mQueryCache = array(); function __construct( $info ) { @@ -30,10 +31,12 @@ class ForeignAPIRepo extends FileRepo { } } +/** + * No-ops + */ function storeBatch( $triplets, $flags = 0 ) { return false; } - function storeTemp( $originalName, $srcPath ) { return false; } @@ -69,14 +72,16 @@ class ForeignAPIRepo extends FileRepo { array_merge( $query, array( 'format' => 'json', - 'action' => 'query', - 'prop' => 'imageinfo' ) ) ); + 'action' => 'query' ) ) ); if( !isset( $this->mQueryCache[$url] ) ) { - $key = wfMemcKey( 'ForeignAPIRepo', $url ); + $key = wfMemcKey( 'ForeignAPIRepo', 'Metadata', md5( $url ) ); $data = $wgMemc->get( $key ); if( !$data ) { $data = Http::get( $url ); + if ( !$data ) { + return null; + } $wgMemc->set( $key, $data, 3600 ); } @@ -92,7 +97,22 @@ class ForeignAPIRepo extends FileRepo { function getImageInfo( $title, $time = false ) { return $this->queryImage( array( 'titles' => 'Image:' . $title->getText(), - 'iiprop' => 'timestamp|user|comment|url|size|sha1|metadata|mime' ) ); + 'iiprop' => 'timestamp|user|comment|url|size|sha1|metadata|mime', + 'prop' => 'imageinfo' ) ); + } + + function findBySha1( $hash ) { + $results = $this->fetchImageQuery( array( + 'aisha1base36' => $hash, + 'aiprop' => 'timestamp|user|comment|url|size|sha1|metadata|mime', + 'list' => 'allimages', ) ); + $ret = array(); + if ( isset( $results['query']['allimages'] ) ) { + foreach ( $results['query']['allimages'] as $img ) { + $ret[] = new ForeignAPIFile( Title::makeTitle( NS_IMAGE, $img['name'] ), $this, $img ); + } + } + return $ret; } function getThumbUrl( $name, $width=-1, $height=-1 ) { @@ -100,11 +120,56 @@ class ForeignAPIRepo extends FileRepo { 'titles' => 'Image:' . $name, 'iiprop' => 'url', 'iiurlwidth' => $width, - 'iiurlheight' => $height ) ); + 'iiurlheight' => $height, + 'prop' => 'imageinfo' ) ); if( $info ) { + wfDebug( __METHOD__ . " got remote thumb " . $info['thumburl'] . "\n" ); return $info['thumburl']; } else { return false; } } + + function getThumbUrlFromCache( $name, $width, $height ) { + global $wgMemc, $wgUploadPath, $wgServer, $wgUploadDirectory; + + if ( !$this->canCacheThumbs() ) { + return $this->getThumbUrl( $name, $width, $height ); + } + + $key = wfMemcKey( 'ForeignAPIRepo', 'ThumbUrl', $name ); + if ( $thumbUrl = $wgMemc->get($key) ) { + wfDebug("Got thumb from local cache. $thumbUrl \n"); + return $thumbUrl; + } + else { + $foreignUrl = $this->getThumbUrl( $name, $width, $height ); + + // We need the same filename as the remote one :) + $fileName = ltrim( substr( $foreignUrl, strrpos( $foreignUrl, '/' ) ), '/' ); + $path = 'thumb/' . $this->getHashPath( $name ) . $name . "/"; + if ( !is_dir($wgUploadDirectory . '/' . $path) ) { + wfMkdirParents($wgUploadDirectory . '/' . $path); + } + if ( !is_writable( $wgUploadDirectory . '/' . $path . $fileName ) ) { + wfDebug( __METHOD__ . " could not write to thumb path\n" ); + return $foreignUrl; + } + $localUrl = $wgServer . $wgUploadPath . '/' . $path . $fileName; + $thumb = Http::get( $foreignUrl ); + # FIXME: Delete old thumbs that aren't being used. Maintenance script? + file_put_contents($wgUploadDirectory . '/' . $path . $fileName, $thumb ); + $wgMemc->set( $key, $localUrl, $this->apiThumbCacheExpiry ); + wfDebug( __METHOD__ . " got local thumb $localUrl, saving to cache \n" ); + return $localUrl; + } + } + + /** + * Are we locally caching the thumbnails? + * @return bool + */ + public function canCacheThumbs() { + return ( $this->apiThumbCacheExpiry > 0 ); + } } diff --git a/includes/filerepo/ForeignDBFile.php b/includes/filerepo/ForeignDBFile.php index eed26048..5fb432c8 100644 --- a/includes/filerepo/ForeignDBFile.php +++ b/includes/filerepo/ForeignDBFile.php @@ -13,7 +13,7 @@ class ForeignDBFile extends LocalFile { * Do not call this except from inside a repo class. */ static function newFromRow( $row, $repo ) { - $title = Title::makeTitle( NS_IMAGE, $row->img_name ); + $title = Title::makeTitle( NS_FILE, $row->img_name ); $file = new self( $title, $repo ); $file->loadFromRow( $row ); return $file; diff --git a/includes/filerepo/Image.php b/includes/filerepo/Image.php index 665dd4bf..5207bb4b 100644 --- a/includes/filerepo/Image.php +++ b/includes/filerepo/Image.php @@ -36,7 +36,7 @@ class Image extends LocalFile { */ static function newFromName( $name ) { wfDeprecated( __METHOD__ ); - $title = Title::makeTitleSafe( NS_IMAGE, $name ); + $title = Title::makeTitleSafe( NS_FILE, $name ); if ( is_object( $title ) ) { $img = wfFindFile( $title ); if ( !$img ) { diff --git a/includes/filerepo/LocalFile.php b/includes/filerepo/LocalFile.php index 57c0703d..6fd6de72 100644 --- a/includes/filerepo/LocalFile.php +++ b/includes/filerepo/LocalFile.php @@ -68,7 +68,7 @@ class LocalFile extends File * Do not call this except from inside a repo class. */ static function newFromRow( $row, $repo ) { - $title = Title::makeTitle( NS_IMAGE, $row->img_name ); + $title = Title::makeTitle( NS_FILE, $row->img_name ); $file = new self( $title, $repo ); $file->loadFromRow( $row ); return $file; @@ -453,6 +453,11 @@ class LocalFile extends File return $this->metadata; } + function getBitDepth() { + $this->load(); + return $this->bits; + } + /** * Return the size of the image file, in bytes * @public @@ -619,31 +624,38 @@ class LocalFile extends File /** purgeDescription inherited */ /** purgeEverything inherited */ - function getHistory($limit = null, $start = null, $end = null) { + function getHistory($limit = null, $start = null, $end = null, $inc = true) { $dbr = $this->repo->getSlaveDB(); $tables = array('oldimage'); - $join_conds = array(); $fields = OldLocalFile::selectFields(); - $conds = $opts = array(); + $conds = $opts = $join_conds = array(); + $eq = $inc ? "=" : ""; $conds[] = "oi_name = " . $dbr->addQuotes( $this->title->getDBKey() ); - if( $start !== null ) { - $conds[] = "oi_timestamp <= " . $dbr->addQuotes( $dbr->timestamp( $start ) ); + if( $start ) { + $conds[] = "oi_timestamp <$eq " . $dbr->addQuotes( $dbr->timestamp( $start ) ); } - if( $end !== null ) { - $conds[] = "oi_timestamp >= " . $dbr->addQuotes( $dbr->timestamp( $end ) ); + if( $end ) { + $conds[] = "oi_timestamp >$eq " . $dbr->addQuotes( $dbr->timestamp( $end ) ); } if( $limit ) { $opts['LIMIT'] = $limit; } - $opts['ORDER BY'] = 'oi_timestamp DESC'; + // Search backwards for time > x queries + $order = (!$start && $end !== null) ? "ASC" : "DESC"; + $opts['ORDER BY'] = "oi_timestamp $order"; + $opts['USE INDEX'] = array('oldimage' => 'oi_name_timestamp'); - wfRunHooks( 'LocalFile::getHistory', array( &$this, &$tables, &$fields, &$conds, &$opts, &$join_conds ) ); + wfRunHooks( 'LocalFile::getHistory', array( &$this, &$tables, &$fields, + &$conds, &$opts, &$join_conds ) ); $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $opts, $join_conds ); $r = array(); while( $row = $dbr->fetchObject($res) ) { $r[] = OldLocalFile::newFromRow($row, $this->repo); } + if( $order == "ASC" ) { + $r = array_reverse( $r ); // make sure it ends up descending + } return $r; } @@ -732,11 +744,11 @@ class LocalFile extends File * @return FileRepoStatus object. On success, the value member contains the * archive name, or an empty string if it was a new file. */ - function upload( $srcPath, $comment, $pageText, $flags = 0, $props = false, $timestamp = false ) { + function upload( $srcPath, $comment, $pageText, $flags = 0, $props = false, $timestamp = false, $user = null ) { $this->lock(); $status = $this->publish( $srcPath, $flags ); if ( $status->ok ) { - if ( !$this->recordUpload2( $status->value, $comment, $pageText, $props, $timestamp ) ) { + if ( !$this->recordUpload2( $status->value, $comment, $pageText, $props, $timestamp, $user ) ) { $status->fatal( 'filenotfound', $srcPath ); } } @@ -766,18 +778,22 @@ class LocalFile extends File /** * Record a file upload in the upload log and the image table */ - function recordUpload2( $oldver, $comment, $pageText, $props = false, $timestamp = false ) + function recordUpload2( $oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null ) { - global $wgUser; + if( is_null( $user ) ) { + global $wgUser; + $user = $wgUser; + } $dbw = $this->repo->getMasterDB(); + $dbw->begin(); if ( !$props ) { $props = $this->repo->getFileProps( $this->getVirtualUrl() ); } $props['description'] = $comment; - $props['user'] = $wgUser->getId(); - $props['user_text'] = $wgUser->getName(); + $props['user'] = $user->getId(); + $props['user_text'] = $user->getName(); $props['timestamp'] = wfTimestamp( TS_MW ); $this->setProps( $props ); @@ -812,8 +828,8 @@ class LocalFile extends File 'img_minor_mime' => $this->minor_mime, 'img_timestamp' => $timestamp, 'img_description' => $comment, - 'img_user' => $wgUser->getId(), - 'img_user_text' => $wgUser->getName(), + 'img_user' => $user->getId(), + 'img_user_text' => $user->getName(), 'img_metadata' => $this->metadata, 'img_sha1' => $this->sha1 ), @@ -858,8 +874,8 @@ class LocalFile extends File 'img_minor_mime' => $this->minor_mime, 'img_timestamp' => $timestamp, 'img_description' => $comment, - 'img_user' => $wgUser->getId(), - 'img_user_text' => $wgUser->getName(), + 'img_user' => $user->getId(), + 'img_user_text' => $user->getName(), 'img_metadata' => $this->metadata, 'img_sha1' => $this->sha1 ), array( /* WHERE */ @@ -874,19 +890,22 @@ class LocalFile extends File } $descTitle = $this->getTitle(); - $article = new Article( $descTitle ); + $article = new ImagePage( $descTitle ); + $article->setFile( $this ); # Add the log entry $log = new LogPage( 'upload' ); $action = $reupload ? 'overwrite' : 'upload'; - $log->addEntry( $action, $descTitle, $comment ); + $log->addEntry( $action, $descTitle, $comment, array(), $user ); if( $descTitle->exists() ) { # Create a null revision - $nullRevision = Revision::newNullRevision( $dbw, $descTitle->getArticleId(), $log->getRcComment(), false ); + $latest = $descTitle->getLatestRevID(); + $nullRevision = Revision::newNullRevision( $dbw, $descTitle->getArticleId(), + $log->getRcComment(), false ); $nullRevision->insertOn( $dbw ); - wfRunHooks( 'NewRevisionFromEditComplete', array($article, $nullRevision, false) ); + wfRunHooks( 'NewRevisionFromEditComplete', array($article, $nullRevision, $latest, $user) ); $article->updateRevisionOn( $dbw, $nullRevision ); # Invalidate the cache for the description page @@ -1109,8 +1128,8 @@ class LocalFile extends File if ( !$revision ) return false; $text = $revision->getText(); if ( !$text ) return false; - $html = $wgParser->parse( $text, new ParserOptions ); - return $html; + $pout = $wgParser->parse( $text, $this->title, new ParserOptions() ); + return $pout->getText(); } function getDescription() { @@ -1128,7 +1147,7 @@ class LocalFile extends File // Initialise now if necessary if ( $this->sha1 == '' && $this->fileExists ) { $this->sha1 = File::sha1Base36( $this->getPath() ); - if ( strval( $this->sha1 ) != '' ) { + if ( !wfReadOnly() && strval( $this->sha1 ) != '' ) { $dbw = $this->repo->getMasterDB(); $dbw->update( 'image', array( 'img_sha1' => $this->sha1 ), @@ -1355,7 +1374,7 @@ class LocalFileDeleteBatch { $dbw->delete( 'oldimage', array( 'oi_name' => $this->file->getName(), - 'oi_archive_name IN (' . $dbw->makeList( array_keys( $oldRels ) ) . ')' + 'oi_archive_name' => array_keys( $oldRels ) ), __METHOD__ ); } if ( $deleteCurrent ) { @@ -1509,7 +1528,8 @@ class LocalFileRestoreBatch { $result = $dbw->select( 'filearchive', '*', $conditions, __METHOD__, - array( 'ORDER BY' => 'fa_timestamp DESC' ) ); + array( 'ORDER BY' => 'fa_timestamp DESC' ) + ); $idsPresent = array(); $storeBatch = array(); @@ -1554,15 +1574,11 @@ class LocalFileRestoreBatch { 'minor_mime' => $row->fa_minor_mime, 'major_mime' => $row->fa_major_mime, 'media_type' => $row->fa_media_type, - 'metadata' => $row->fa_metadata ); + 'metadata' => $row->fa_metadata + ); } if ( $first && !$exists ) { - // The live (current) version cannot be hidden! - if( !$this->unsuppress && $row->fa_deleted ) { - $this->file->unlock(); - return $status; - } // This revision will be published as the new current version $destRel = $this->file->getRel(); $insertCurrent = array( @@ -1579,7 +1595,13 @@ class LocalFileRestoreBatch { 'img_user' => $row->fa_user, 'img_user_text' => $row->fa_user_text, 'img_timestamp' => $row->fa_timestamp, - 'img_sha1' => $sha1); + 'img_sha1' => $sha1 + ); + // The live (current) version cannot be hidden! + if( !$this->unsuppress && $row->fa_deleted ) { + $storeBatch[] = array( $deletedUrl, 'public', $destRel ); + $this->cleanupBatch[] = $row->fa_storage_key; + } } else { $archiveName = $row->fa_archive_name; if( $archiveName == '' ) { @@ -1616,6 +1638,7 @@ class LocalFileRestoreBatch { $deleteIds[] = $row->fa_id; if( !$this->unsuppress && $row->fa_deleted & File::DELETED_FILE ) { // private files can stay where they are + $status->successCount++; } else { $storeBatch[] = array( $deletedUrl, 'public', $destRel ); $this->cleanupBatch[] = $row->fa_storage_key; @@ -1705,7 +1728,7 @@ class LocalFileMoveBatch { $this->file = $file; $this->target = $target; $this->oldHash = $this->file->repo->getHashPath( $this->file->getName() ); - $this->newHash = $this->file->repo->getHashPath( $this->target->getDbKey() ); + $this->newHash = $this->file->repo->getHashPath( $this->target->getDBKey() ); $this->oldName = $this->file->getName(); $this->newName = $this->file->repo->getNameFromTitle( $this->target ); $this->oldRel = $this->oldHash . $this->oldName; @@ -1751,7 +1774,7 @@ class LocalFileMoveBatch { continue; } $this->olds[] = array( - "{$archiveBase}/{$this->oldHash}{$oldname}", + "{$archiveBase}/{$this->oldHash}{$oldName}", "{$archiveBase}/{$this->newHash}{$timestamp}!{$this->newName}" ); } diff --git a/includes/filerepo/LocalRepo.php b/includes/filerepo/LocalRepo.php index 90b198c8..5eb1a11c 100644 --- a/includes/filerepo/LocalRepo.php +++ b/includes/filerepo/LocalRepo.php @@ -94,7 +94,7 @@ class LocalRepo extends FSRepo { 'page_id', //Field array( //Conditions 'page_namespace' => $title->getNamespace(), - 'page_title' => $title->getDbKey(), + 'page_title' => $title->getDBKey(), ), __METHOD__ //Function name ); @@ -108,7 +108,7 @@ class LocalRepo extends FSRepo { $title = Title::newFromTitle( $title ); } if( $title instanceof Title && $title->getNamespace() == NS_MEDIA ) { - $title = Title::makeTitle( NS_IMAGE, $title->getText() ); + $title = Title::makeTitle( NS_FILE, $title->getText() ); } $memcKey = $this->getMemcKey( "image_redirect:" . md5( $title->getPrefixedDBkey() ) ); @@ -164,8 +164,7 @@ class LocalRepo extends FSRepo { /* * Find many files using one query */ - function findFiles( $titles, $flags ) { - // FIXME: Comply with $flags + function findFiles( $titles ) { // FIXME: Only accepts a $titles array where the keys are the sanitized // file names. diff --git a/includes/filerepo/OldLocalFile.php b/includes/filerepo/OldLocalFile.php index 89e49c4c..46c35bd9 100644 --- a/includes/filerepo/OldLocalFile.php +++ b/includes/filerepo/OldLocalFile.php @@ -23,7 +23,7 @@ class OldLocalFile extends LocalFile { } static function newFromRow( $row, $repo ) { - $title = Title::makeTitle( NS_IMAGE, $row->oi_name ); + $title = Title::makeTitle( NS_FILE, $row->oi_name ); $file = new self( $title, $repo, null, $row->oi_archive_name ); $file->loadFromRow( $row, 'oi_' ); return $file; @@ -64,6 +64,7 @@ class OldLocalFile extends LocalFile { 'oi_user', 'oi_user_text', 'oi_timestamp', + 'oi_deleted', 'oi_sha1', ); } diff --git a/includes/filerepo/RepoGroup.php b/includes/filerepo/RepoGroup.php index 7cb837b3..2303f581 100644 --- a/includes/filerepo/RepoGroup.php +++ b/includes/filerepo/RepoGroup.php @@ -82,7 +82,7 @@ class RepoGroup { } return false; } - function findFiles( $titles, $flags = 0 ) { + function findFiles( $titles ) { if ( !$this->reposInitialised ) { $this->initialiseRepos(); } @@ -90,11 +90,12 @@ class RepoGroup { $titleObjs = array(); foreach ( $titles as $title ) { if ( !( $title instanceof Title ) ) - $title = Title::makeTitleSafe( NS_IMAGE, $title ); - $titleObjs[$title->getDBkey()] = $title; + $title = Title::makeTitleSafe( NS_FILE, $title ); + if ( $title ) + $titleObjs[$title->getDBkey()] = $title; } - $images = $this->localRepo->findFiles( $titleObjs, $flags ); + $images = $this->localRepo->findFiles( $titleObjs ); foreach ( $this->foreignRepos as $repo ) { // Remove found files from $titleObjs @@ -102,7 +103,7 @@ class RepoGroup { if ( isset( $titleObjs[$name] ) ) unset( $titleObjs[$name] ); - $images = array_merge( $images, $repo->findFiles( $titleObjs, $flags ) ); + $images = array_merge( $images, $repo->findFiles( $titleObjs ) ); } return $images; } @@ -176,6 +177,13 @@ class RepoGroup { return $this->getRepo( 'local' ); } + /** + * Call a function for each foreign repo, with the repo object as the + * first parameter. + * + * @param $callback callback The function to call + * @param $params array Optional additional parameters to pass to the function + */ function forEachForeignRepo( $callback, $params = array() ) { foreach( $this->foreignRepos as $repo ) { $args = array_merge( array( $repo ), $params ); @@ -186,8 +194,12 @@ class RepoGroup { return false; } + /** + * Does the installation have any foreign repos set up? + * @return bool + */ function hasForeignRepos() { - return !empty( $this->foreignRepos ); + return (bool)$this->foreignRepos; } /** diff --git a/includes/filerepo/UnregisteredLocalFile.php b/includes/filerepo/UnregisteredLocalFile.php index c687ef6e..6f63cb0b 100644 --- a/includes/filerepo/UnregisteredLocalFile.php +++ b/includes/filerepo/UnregisteredLocalFile.php @@ -32,7 +32,7 @@ class UnregisteredLocalFile extends File { $this->name = $repo->getNameFromTitle( $title ); } else { $this->name = basename( $path ); - $this->title = Title::makeTitleSafe( NS_IMAGE, $this->name ); + $this->title = Title::makeTitleSafe( NS_FILE, $this->name ); } $this->repo = $repo; if ( $path ) { -- cgit v1.2.2