summaryrefslogtreecommitdiff
path: root/includes/filerepo
diff options
context:
space:
mode:
Diffstat (limited to 'includes/filerepo')
-rw-r--r--includes/filerepo/FileRepo.php64
-rw-r--r--includes/filerepo/FileRepoStatus.php36
-rw-r--r--includes/filerepo/ForeignAPIRepo.php2
-rw-r--r--includes/filerepo/RepoGroup.php6
-rw-r--r--includes/filerepo/file/ArchivedFile.php12
-rw-r--r--includes/filerepo/file/File.php51
-rw-r--r--includes/filerepo/file/LocalFile.php194
-rw-r--r--includes/filerepo/file/OldLocalFile.php14
-rw-r--r--includes/filerepo/file/UnregisteredLocalFile.php12
9 files changed, 169 insertions, 222 deletions
diff --git a/includes/filerepo/FileRepo.php b/includes/filerepo/FileRepo.php
index 59295257..5b42c2c6 100644
--- a/includes/filerepo/FileRepo.php
+++ b/includes/filerepo/FileRepo.php
@@ -114,6 +114,9 @@ class FileRepo {
/** @var string The URL of the repo's favicon, if any */
protected $favicon;
+ /** @var bool Whether all zones should be private (e.g. private wiki repo) */
+ protected $isPrivate;
+
/**
* Factory functions for creating new files
* Override these in the base class
@@ -269,7 +272,7 @@ class FileRepo {
* @return string|bool
*/
public function getZoneUrl( $zone, $ext = null ) {
- if ( in_array( $zone, array( 'public', 'temp', 'thumb', 'transcoded' ) ) ) {
+ if ( in_array( $zone, array( 'public', 'thumb', 'transcoded' ) ) ) {
// standard public zones
if ( $ext !== null && isset( $this->zones[$zone]['urlsByExt'][$ext] ) ) {
// custom URL for extension/zone
@@ -283,7 +286,6 @@ class FileRepo {
case 'public':
return $this->url;
case 'temp':
- return "{$this->url}/temp";
case 'deleted':
return false; // no public URL
case 'thumb':
@@ -404,6 +406,7 @@ class FileRepo {
* private: If true, return restricted (deleted) files if the current
* user is allowed to view them. Otherwise, such files will not
* be found. If a User object, use that user instead of the current.
+ * latest: If true, load from the latest available data into File objects
* @return File|bool False on failure
*/
public function findFile( $title, $options = array() ) {
@@ -411,18 +414,24 @@ class FileRepo {
if ( !$title ) {
return false;
}
+ if ( isset( $options['bypassCache'] ) ) {
+ $options['latest'] = $options['bypassCache']; // b/c
+ }
$time = isset( $options['time'] ) ? $options['time'] : false;
+ $flags = !empty( $options['latest'] ) ? File::READ_LATEST : 0;
# First try the current version of the file to see if it precedes the timestamp
$img = $this->newFile( $title );
if ( !$img ) {
return false;
}
+ $img->load( $flags );
if ( $img->exists() && ( !$time || $img->getTimestamp() == $time ) ) {
return $img;
}
# Now try an old version of the file
if ( $time !== false ) {
$img = $this->newFile( $title, $time );
+ $img->load( $flags );
if ( $img && $img->exists() ) {
if ( !$img->isDeleted( File::DELETED_FILE ) ) {
return $img; // always OK
@@ -443,6 +452,7 @@ class FileRepo {
$redir = $this->checkRedirect( $title );
if ( $redir && $title->getNamespace() == NS_FILE ) {
$img = $this->newFile( $redir );
+ $img->load( $flags );
if ( !$img ) {
return false;
}
@@ -1305,7 +1315,10 @@ class FileRepo {
list( , $container, ) = FileBackend::splitStoragePath( $path );
$params = array( 'dir' => $path );
- if ( $this->isPrivate || $container === $this->zones['deleted']['container'] ) {
+ if ( $this->isPrivate
+ || $container === $this->zones['deleted']['container']
+ || $container === $this->zones['temp']['container']
+ ) {
# Take all available measures to prevent web accessibility of new deleted
# directories, in case the user has not configured offline storage
$params = array( 'noAccess' => true, 'noListing' => true ) + $params;
@@ -1676,23 +1689,26 @@ class FileRepo {
* Create a new fatal error
*
* @param string $message
- * @return FileRepoStatus
+ * @return Status
*/
public function newFatal( $message /*, parameters...*/ ) {
- $params = func_get_args();
- array_unshift( $params, $this );
+ $status = call_user_func_array( array( 'Status', 'newFatal' ), func_get_args() );
+ $status->cleanCallback = $this->getErrorCleanupFunction();
- return call_user_func_array( array( 'FileRepoStatus', 'newFatal' ), $params );
+ return $status;
}
/**
* Create a new good result
*
* @param null|string $value
- * @return FileRepoStatus
+ * @return Status
*/
public function newGood( $value = null ) {
- return FileRepoStatus::newGood( $this, $value );
+ $status = Status::newGood( $value );
+ $status->cleanCallback = $this->getErrorCleanupFunction();
+
+ return $status;
}
/**
@@ -1785,9 +1801,9 @@ class FileRepo {
}
/**
- * Get an temporary FileRepo associated with this repo.
- * Files will be created in the temp zone of this repo and
- * thumbnails in a /temp subdirectory in thumb zone of this repo.
+ * Get a temporary private FileRepo associated with this repo.
+ *
+ * Files will be created in the temp zone of this repo.
* It will have the same backend as this repo.
*
* @return TempFileRepo
@@ -1798,26 +1814,26 @@ class FileRepo {
'backend' => $this->backend,
'zones' => array(
'public' => array(
+ // Same place storeTemp() uses in the base repo, though
+ // the path hashing is mismatched, which is annoying.
'container' => $this->zones['temp']['container'],
'directory' => $this->zones['temp']['directory']
),
'thumb' => array(
- 'container' => $this->zones['thumb']['container'],
- 'directory' => $this->zones['thumb']['directory'] == ''
- ? 'temp'
- : $this->zones['thumb']['directory'] . '/temp'
+ 'container' => $this->zones['temp']['container'],
+ 'directory' => $this->zones['temp']['directory'] == ''
+ ? 'thumb'
+ : $this->zones['temp']['directory'] . '/thumb'
),
'transcoded' => array(
- 'container' => $this->zones['transcoded']['container'],
- 'directory' => $this->zones['transcoded']['directory'] == ''
- ? 'temp'
- : $this->zones['transcoded']['directory'] . '/temp'
+ 'container' => $this->zones['temp']['container'],
+ 'directory' => $this->zones['temp']['directory'] == ''
+ ? 'transcoded'
+ : $this->zones['temp']['directory'] . '/transcoded'
)
),
- 'url' => $this->getZoneUrl( 'temp' ),
- 'thumbUrl' => $this->getZoneUrl( 'thumb' ) . '/temp',
- 'transcodedUrl' => $this->getZoneUrl( 'transcoded' ) . '/temp',
- 'hashLevels' => $this->hashLevels // performance
+ 'hashLevels' => $this->hashLevels, // performance
+ 'isPrivate' => true // all in temp zone
) );
}
diff --git a/includes/filerepo/FileRepoStatus.php b/includes/filerepo/FileRepoStatus.php
index 56848df2..67080b6f 100644
--- a/includes/filerepo/FileRepoStatus.php
+++ b/includes/filerepo/FileRepoStatus.php
@@ -24,41 +24,7 @@
/**
* Generic operation result class for FileRepo-related operations
* @ingroup FileRepo
+ * @deprecated 1.25
*/
class FileRepoStatus extends Status {
- /**
- * Factory function for fatal errors
- *
- * @param FileRepo $repo
- * @return FileRepoStatus
- */
- static function newFatal( $repo /*, parameters...*/ ) {
- $params = array_slice( func_get_args(), 1 );
- $result = new self( $repo );
- call_user_func_array( array( &$result, 'error' ), $params );
- $result->ok = false;
-
- return $result;
- }
-
- /**
- * @param FileRepo|bool $repo Default: false
- * @param mixed $value
- * @return FileRepoStatus
- */
- static function newGood( $repo = false, $value = null ) {
- $result = new self( $repo );
- $result->value = $value;
-
- return $result;
- }
-
- /**
- * @param bool|FileRepo $repo
- */
- function __construct( $repo = false ) {
- if ( $repo ) {
- $this->cleanCallback = $repo->getErrorCleanupFunction();
- }
- }
}
diff --git a/includes/filerepo/ForeignAPIRepo.php b/includes/filerepo/ForeignAPIRepo.php
index 6924f0a6..7ead968f 100644
--- a/includes/filerepo/ForeignAPIRepo.php
+++ b/includes/filerepo/ForeignAPIRepo.php
@@ -514,7 +514,7 @@ class ForeignAPIRepo extends FileRepo {
$options['timeout'] = 'default';
}
- $req = MWHttpRequest::factory( $url, $options );
+ $req = MWHttpRequest::factory( $url, $options, __METHOD__ );
$req->setUserAgent( ForeignAPIRepo::getUserAgent() );
$status = $req->execute();
diff --git a/includes/filerepo/RepoGroup.php b/includes/filerepo/RepoGroup.php
index fab42162..050c4290 100644
--- a/includes/filerepo/RepoGroup.php
+++ b/includes/filerepo/RepoGroup.php
@@ -114,7 +114,7 @@ class RepoGroup {
* private: If true, return restricted (deleted) files if the current
* user is allowed to view them. Otherwise, such files will not
* be found.
- * bypassCache: If true, do not use the process-local cache of File objects
+ * latest: If true, load from the latest available data into File objects
* @return File|bool False if title is not found
*/
function findFile( $title, $options = array() ) {
@@ -122,6 +122,10 @@ class RepoGroup {
// MW 1.15 compat
$options = array( 'time' => $options );
}
+ if ( isset( $options['bypassCache'] ) ) {
+ $options['latest'] = $options['bypassCache']; // b/c
+ }
+
if ( !$this->reposInitialised ) {
$this->initialiseRepos();
}
diff --git a/includes/filerepo/file/ArchivedFile.php b/includes/filerepo/file/ArchivedFile.php
index 5b0d8e2b..1d454283 100644
--- a/includes/filerepo/file/ArchivedFile.php
+++ b/includes/filerepo/file/ArchivedFile.php
@@ -100,8 +100,9 @@ class ArchivedFile {
* @param Title $title
* @param int $id
* @param string $key
+ * @param string $sha1
*/
- function __construct( $title, $id = 0, $key = '' ) {
+ function __construct( $title, $id = 0, $key = '', $sha1 = '' ) {
$this->id = -1;
$this->title = false;
$this->name = false;
@@ -136,7 +137,11 @@ class ArchivedFile {
$this->key = $key;
}
- if ( !$id && !$key && !( $title instanceof Title ) ) {
+ if ( $sha1 ) {
+ $this->sha1 = $sha1;
+ }
+
+ if ( !$id && !$key && !( $title instanceof Title ) && !$sha1 ) {
throw new MWException( "No specifications provided to ArchivedFile constructor." );
}
}
@@ -162,6 +167,9 @@ class ArchivedFile {
if ( $this->title ) {
$conds['fa_name'] = $this->title->getDBkey();
}
+ if ( $this->sha1 ) {
+ $conds['fa_sha1'] = $this->sha1;
+ }
if ( !count( $conds ) ) {
throw new MWException( "No specific information for retrieving archived file" );
diff --git a/includes/filerepo/file/File.php b/includes/filerepo/file/File.php
index b574c5e7..6edd6fcc 100644
--- a/includes/filerepo/file/File.php
+++ b/includes/filerepo/file/File.php
@@ -47,7 +47,7 @@
*
* @ingroup FileAbstraction
*/
-abstract class File {
+abstract class File implements IDBAccessObject {
// Bitfield values akin to the Revision deletion constants
const DELETED_FILE = 1;
const DELETED_COMMENT = 2;
@@ -138,10 +138,10 @@ abstract class File {
/** @var Title */
protected $redirectTitle;
- /** @var bool Wether the output of transform() for this file is likely to be valid. */
+ /** @var bool Whether the output of transform() for this file is likely to be valid. */
protected $canRender;
- /** @var bool Wether this media file is in a format that is unlikely to
+ /** @var bool Whether this media file is in a format that is unlikely to
* contain viruses or malicious content
*/
protected $isSafeFile;
@@ -490,7 +490,7 @@ abstract class File {
sort( $sortedBuckets );
foreach ( $sortedBuckets as $bucket ) {
- if ( $bucket > $imageWidth ) {
+ if ( $bucket >= $imageWidth ) {
return false;
}
@@ -837,6 +837,18 @@ abstract class File {
}
/**
+ * Load any lazy-loaded file object fields from source
+ *
+ * This is only useful when setting $flags
+ *
+ * Overridden by LocalFile to actually query the DB
+ *
+ * @param integer $flags Bitfield of File::READ_* constants
+ */
+ public function load( $flags = 0 ) {
+ }
+
+ /**
* Returns true if file exists in the repository.
*
* Overridden by LocalFile to avoid unnecessary stat calls.
@@ -996,7 +1008,6 @@ abstract class File {
function transform( $params, $flags = 0 ) {
global $wgThumbnailEpoch;
- wfProfileIn( __METHOD__ );
do {
if ( !$this->canRender() ) {
$thumb = $this->iconThumb();
@@ -1069,8 +1080,6 @@ abstract class File {
}
} while ( false );
- wfProfileOut( __METHOD__ );
-
return is_object( $thumb ) ? $thumb : false;
}
@@ -1100,9 +1109,7 @@ abstract class File {
}
// Actually render the thumbnail...
- wfProfileIn( __METHOD__ . '-doTransform' );
$thumb = $handler->doTransform( $this, $tmpThumbPath, $thumbUrl, $transformParams );
- wfProfileOut( __METHOD__ . '-doTransform' );
$tmpFile->bind( $thumb ); // keep alive with $thumb
if ( !$thumb ) { // bad params?
@@ -1123,7 +1130,7 @@ abstract class File {
$thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $transformParams, $flags );
}
// Give extensions a chance to do something with this thumbnail...
- wfRunHooks( 'FileTransformed', array( $this, $thumb, $tmpThumbPath, $thumbPath ) );
+ Hooks::run( 'FileTransformed', array( $this, $thumb, $tmpThumbPath, $thumbPath ) );
}
// Purge. Useful in the event of Core -> Squid connection failure or squid
@@ -1237,7 +1244,7 @@ abstract class File {
$that = $this;
$work = new PoolCounterWorkViaCallback( 'GetLocalFileCopy', sha1( $this->getName() ),
array(
- 'doWork' => function() use ( $that ) {
+ 'doWork' => function () use ( $that ) {
return $that->getLocalRefPath();
}
)
@@ -1458,7 +1465,7 @@ abstract class File {
/**
* Get the path of the file relative to the public zone root.
- * This function is overriden in OldLocalFile to be like getArchiveRel().
+ * This function is overridden in OldLocalFile to be like getArchiveRel().
*
* @return string
*/
@@ -1502,7 +1509,7 @@ abstract class File {
/**
* Get urlencoded path of the file relative to the public zone root.
- * This function is overriden in OldLocalFile to be like getArchiveUrl().
+ * This function is overridden in OldLocalFile to be like getArchiveUrl().
*
* @return string
*/
@@ -1770,14 +1777,15 @@ abstract class File {
}
/**
+ * @param bool|IContextSource $context Context to use (optional)
* @return bool
*/
- function formatMetadata() {
+ function formatMetadata( $context = false ) {
if ( !$this->getHandler() ) {
return false;
}
- return $this->getHandler()->formatMetadata( $this, $this->getMetadata() );
+ return $this->getHandler()->formatMetadata( $this, $context );
}
/**
@@ -2013,7 +2021,7 @@ abstract class File {
wfDebug( "miss\n" );
}
wfDebug( "Fetching shared description from $renderUrl\n" );
- $res = Http::get( $renderUrl );
+ $res = Http::get( $renderUrl, array(), __METHOD__ );
if ( $res && $this->repo->descriptionCacheExpiry > 0 ) {
$wgMemc->set( $key, $res, $this->repo->descriptionCacheExpiry );
}
@@ -2052,6 +2060,17 @@ abstract class File {
}
/**
+ * Returns the timestamp (in TS_MW format) of the last change of the description page.
+ * Returns false if the file does not have a description page, or retrieving the timestamp
+ * would be expensive.
+ * @since 1.25
+ * @return string|bool
+ */
+ public function getDescriptionTouched() {
+ return false;
+ }
+
+ /**
* Get the SHA-1 base 36 hash of the file
*
* @return string
diff --git a/includes/filerepo/file/LocalFile.php b/includes/filerepo/file/LocalFile.php
index 8824b669..b4cced38 100644
--- a/includes/filerepo/file/LocalFile.php
+++ b/includes/filerepo/file/LocalFile.php
@@ -109,6 +109,9 @@ class LocalFile extends File {
/** @var string Description of current revision of the file */
private $description;
+ /** @var string TS_MW timestamp of the last change of the file description */
+ private $descriptionTouched;
+
/** @var bool Whether the row was upgraded on load */
private $upgraded;
@@ -121,13 +124,8 @@ class LocalFile extends File {
/** @var bool True if file is not present in file system. Not to be cached in memcached */
private $missing;
- /** @var int UNIX timestamp of last markVolatile() call */
- private $lastMarkedVolatile = 0;
-
- const LOAD_ALL = 1; // integer; load all the lazy fields too (like metadata)
- const LOAD_VIA_SLAVE = 2; // integer; use a slave to load the data
-
- const VOLATILE_TTL = 300; // integer; seconds
+ // @note: higher than IDBAccessObject constants
+ const LOAD_ALL = 16; // integer; load all the lazy fields too (like metadata)
/**
* Create a LocalFile from a title
@@ -247,13 +245,11 @@ class LocalFile extends File {
function loadFromCache() {
global $wgMemc;
- wfProfileIn( __METHOD__ );
$this->dataLoaded = false;
$this->extraDataLoaded = false;
$key = $this->getCacheKey();
if ( !$key ) {
- wfProfileOut( __METHOD__ );
return false;
}
@@ -280,8 +276,6 @@ class LocalFile extends File {
wfIncrStats( 'image_cache_miss' );
}
- wfProfileOut( __METHOD__ );
-
return $this->dataLoaded;
}
@@ -382,17 +376,15 @@ class LocalFile extends File {
* @param int $flags
*/
function loadFromDB( $flags = 0 ) {
- # 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->dataLoaded = true;
$this->extraDataLoaded = true;
- $dbr = ( $flags & self::LOAD_VIA_SLAVE )
- ? $this->repo->getSlaveDB()
- : $this->repo->getMasterDB();
+ $dbr = ( $flags & self::READ_LATEST )
+ ? $this->repo->getMasterDB()
+ : $this->repo->getSlaveDB();
$row = $dbr->selectRow( 'image', $this->getCacheFields( 'img_' ),
array( 'img_name' => $this->getName() ), $fname );
@@ -402,8 +394,6 @@ class LocalFile extends File {
} else {
$this->fileExists = false;
}
-
- wfProfileOut( $fname );
}
/**
@@ -411,9 +401,7 @@ class LocalFile extends File {
* 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;
@@ -428,11 +416,8 @@ class LocalFile extends File {
$this->$name = $value;
}
} else {
- wfProfileOut( $fname );
throw new MWException( "Could not find data for image '{$this->getName()}'." );
}
-
- wfProfileOut( $fname );
}
/**
@@ -540,13 +525,14 @@ class LocalFile extends File {
*/
function load( $flags = 0 ) {
if ( !$this->dataLoaded ) {
- if ( !$this->loadFromCache() ) {
- $this->loadFromDB( $this->isVolatile() ? 0 : self::LOAD_VIA_SLAVE );
+ if ( ( $flags & self::READ_LATEST ) || !$this->loadFromCache() ) {
+ $this->loadFromDB( $flags );
$this->saveToCache();
}
$this->dataLoaded = true;
}
if ( ( $flags & self::LOAD_ALL ) && !$this->extraDataLoaded ) {
+ // @note: loads on name/timestamp to reduce race condition problems
$this->loadExtraFromDB();
}
}
@@ -587,7 +573,6 @@ class LocalFile extends File {
* Fix assorted version-related problems with the image row by reloading it from the file
*/
function upgradeRow() {
- wfProfileIn( __METHOD__ );
$this->lock(); // begin
@@ -597,7 +582,6 @@ class LocalFile extends File {
if ( !$this->fileExists ) {
$this->unlock();
wfDebug( __METHOD__ . ": file does not exist, aborting\n" );
- wfProfileOut( __METHOD__ );
return;
}
@@ -607,7 +591,6 @@ class LocalFile extends File {
if ( wfReadOnly() ) {
$this->unlock();
- wfProfileOut( __METHOD__ );
return;
}
@@ -633,7 +616,6 @@ class LocalFile extends File {
$this->unlock(); // done
- wfProfileOut( __METHOD__ );
}
/**
@@ -672,7 +654,7 @@ class LocalFile extends File {
/** getURL inherited */
/** getViewURL inherited */
/** getPath inherited */
- /** isVisible inhereted */
+ /** isVisible inherited */
/**
* @return bool
@@ -860,7 +842,7 @@ class LocalFile extends File {
* Refresh metadata in memcached, but don't touch thumbnails or squid
*/
function purgeMetadataCache() {
- $this->loadFromDB();
+ $this->loadFromDB( File::READ_LATEST );
$this->saveToCache();
$this->purgeHistory();
}
@@ -889,7 +871,6 @@ class LocalFile extends File {
* @note This used to purge old thumbnails by default as well, but doesn't anymore.
*/
function purgeCache( $options = array() ) {
- wfProfileIn( __METHOD__ );
// Refresh metadata cache
$this->purgeMetadataCache();
@@ -898,7 +879,6 @@ class LocalFile extends File {
// Purge squid cache for this file
SquidUpdate::purge( array( $this->getURL() ) );
- wfProfileOut( __METHOD__ );
}
/**
@@ -907,13 +887,12 @@ class LocalFile extends File {
*/
function purgeOldThumbnails( $archiveName ) {
global $wgUseSquid;
- wfProfileIn( __METHOD__ );
// Get a list of old thumbnails and URLs
$files = $this->getThumbnails( $archiveName );
// Purge any custom thumbnail caches
- wfRunHooks( 'LocalFilePurgeThumbnails', array( $this, $archiveName ) );
+ Hooks::run( 'LocalFilePurgeThumbnails', array( $this, $archiveName ) );
$dir = array_shift( $files );
$this->purgeThumbList( $dir, $files );
@@ -927,7 +906,6 @@ class LocalFile extends File {
SquidUpdate::purge( $urls );
}
- wfProfileOut( __METHOD__ );
}
/**
@@ -936,7 +914,6 @@ class LocalFile extends File {
*/
function purgeThumbnails( $options = array() ) {
global $wgUseSquid;
- wfProfileIn( __METHOD__ );
// Delete thumbnails
$files = $this->getThumbnails();
@@ -958,7 +935,7 @@ class LocalFile extends File {
}
// Purge any custom thumbnail caches
- wfRunHooks( 'LocalFilePurgeThumbnails', array( $this, false ) );
+ Hooks::run( 'LocalFilePurgeThumbnails', array( $this, false ) );
$dir = array_shift( $files );
$this->purgeThumbList( $dir, $files );
@@ -968,7 +945,6 @@ class LocalFile extends File {
SquidUpdate::purge( $urls );
}
- wfProfileOut( __METHOD__ );
}
/**
@@ -1035,7 +1011,7 @@ class LocalFile extends File {
$opts['ORDER BY'] = "oi_timestamp $order";
$opts['USE INDEX'] = array( 'oldimage' => 'oi_name_timestamp' );
- wfRunHooks( 'LocalFile::getHistory', array( &$this, &$tables, &$fields,
+ Hooks::run( 'LocalFile::getHistory', array( &$this, &$tables, &$fields,
&$conds, &$opts, &$join_conds ) );
$res = $dbr->select( $tables, $fields, $conds, __METHOD__, $opts, $join_conds );
@@ -1146,7 +1122,6 @@ class LocalFile extends File {
}
if ( !$props ) {
- wfProfileIn( __METHOD__ . '-getProps' );
if ( $this->repo->isVirtualUrl( $srcPath )
|| FileBackend::isStoragePath( $srcPath )
) {
@@ -1154,7 +1129,6 @@ class LocalFile extends File {
} else {
$props = FSFile::getPropsFromPath( $srcPath );
}
- wfProfileOut( __METHOD__ . '-getProps' );
}
$options = array();
@@ -1236,7 +1210,6 @@ class LocalFile extends File {
function recordUpload2( $oldver, $comment, $pageText, $props = false, $timestamp = false,
$user = null
) {
- wfProfileIn( __METHOD__ );
if ( is_null( $user ) ) {
global $wgUser;
@@ -1247,9 +1220,7 @@ class LocalFile extends File {
$dbw->begin( __METHOD__ );
if ( !$props ) {
- wfProfileIn( __METHOD__ . '-getProps' );
$props = $this->repo->getFileProps( $this->getVirtualUrl() );
- wfProfileOut( __METHOD__ . '-getProps' );
}
# Imports or such might force a certain timestamp; otherwise we generate
@@ -1271,7 +1242,6 @@ class LocalFile extends File {
if ( !$this->fileExists ) {
wfDebug( __METHOD__ . ": File " . $this->getRel() . " went missing!\n" );
$dbw->rollback( __METHOD__ );
- wfProfileOut( __METHOD__ );
return false;
}
@@ -1303,9 +1273,11 @@ class LocalFile extends File {
);
if ( $dbw->affectedRows() == 0 ) {
if ( $allowTimeKludge ) {
- # Use FOR UPDATE to ignore any transaction snapshotting
+ # Use LOCK IN SHARE MODE to ignore any transaction snapshotting
$ltimestamp = $dbw->selectField( 'image', 'img_timestamp',
- array( 'img_name' => $this->getName() ), __METHOD__, array( 'FOR UPDATE' ) );
+ array( 'img_name' => $this->getName() ),
+ __METHOD__,
+ array( 'LOCK IN SHARE MODE' ) );
$lUnixtime = $ltimestamp ? wfTimestamp( TS_UNIX, $ltimestamp ) : false;
# Avoid a timestamp that is not newer than the last version
# TODO: the image/oldimage tables should be like page/revision with an ID field
@@ -1404,12 +1376,14 @@ class LocalFile extends File {
// Page exists, do RC entry now (otherwise we wait for later).
$logEntry->publish( $logId );
}
- wfProfileIn( __METHOD__ . '-edit' );
if ( $exists ) {
# Create a null revision
$latest = $descTitle->getLatestRevID();
- $editSummary = LogFormatter::newFromEntry( $logEntry )->getPlainActionText();
+ // Use own context to get the action text in content language
+ $formatter = LogFormatter::newFromEntry( $logEntry );
+ $formatter->setContext( RequestContext::newExtraneousContext( $descTitle ) );
+ $editSummary = $formatter->getPlainActionText();
$nullRevision = Revision::newNullRevision(
$dbw,
@@ -1421,7 +1395,7 @@ class LocalFile extends File {
if ( !is_null( $nullRevision ) ) {
$nullRevision->insertOn( $dbw );
- wfRunHooks( 'NewRevisionFromEditComplete', array( $wikiPage, $nullRevision, $latest, $user ) );
+ Hooks::run( 'NewRevisionFromEditComplete', array( $wikiPage, $nullRevision, $latest, $user ) );
$wikiPage->updateRevisionOn( $dbw, $nullRevision );
}
}
@@ -1468,22 +1442,16 @@ class LocalFile extends File {
$dbw->commit( __METHOD__ ); // commit before anything bad can happen
}
- wfProfileOut( __METHOD__ . '-edit' );
-
if ( $reupload ) {
# Delete old thumbnails
- wfProfileIn( __METHOD__ . '-purge' );
$this->purgeThumbnails();
- wfProfileOut( __METHOD__ . '-purge' );
# Remove the old file from the squid cache
SquidUpdate::purge( array( $this->getURL() ) );
}
# Hooks, hooks, the magic of hooks...
- wfProfileIn( __METHOD__ . '-hooks' );
- wfRunHooks( 'FileUpload', array( $this, $reupload, $descTitle->exists() ) );
- wfProfileOut( __METHOD__ . '-hooks' );
+ Hooks::run( 'FileUpload', array( $this, $reupload, $descTitle->exists() ) );
# Invalidate cache for all pages using this file
$update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' );
@@ -1492,8 +1460,6 @@ class LocalFile extends File {
LinksUpdate::queueRecursiveJobsForTable( $this->getTitle(), 'imagelinks' );
}
- wfProfileOut( __METHOD__ );
-
return true;
}
@@ -1811,6 +1777,25 @@ class LocalFile extends File {
}
/**
+ * @return bool|string
+ */
+ public function getDescriptionTouched() {
+ // The DB lookup might return false, e.g. if the file was just deleted, or the shared DB repo
+ // itself gets it from elsewhere. To avoid repeating the DB lookups in such a case, we
+ // need to differentiate between null (uninitialized) and false (failed to load).
+ if ( $this->descriptionTouched === null ) {
+ $cond = array(
+ 'page_namespace' => $this->title->getNamespace(),
+ 'page_title' => $this->title->getDBkey()
+ );
+ $touched = $this->repo->getSlaveDB()->selectField( 'page', 'page_touched', $cond, __METHOD__ );
+ $this->descriptionTouched = $touched ? wfTimestamp( TS_MW, $touched ) : false;
+ }
+
+ return $this->descriptionTouched;
+ }
+
+ /**
* @return string
*/
function getSha1() {
@@ -1850,7 +1835,7 @@ class LocalFile extends File {
* Start a transaction and lock the image for update
* Increments a reference counter if the lock is already held
* @throws MWException Throws an error if the lock was not acquired
- * @return bool Success
+ * @return bool Whether the file lock owns/spawned the DB transaction
*/
function lock() {
$dbw = $this->repo->getMasterDB();
@@ -1875,9 +1860,7 @@ class LocalFile extends File {
} );
}
- $this->markVolatile(); // file may change soon
-
- return true;
+ return $this->lockedOwnTrx;
}
/**
@@ -1896,48 +1879,6 @@ class LocalFile extends File {
}
/**
- * Mark a file as about to be changed
- *
- * This sets a cache key that alters master/slave DB loading behavior
- *
- * @return bool Success
- */
- protected function markVolatile() {
- global $wgMemc;
-
- $key = $this->repo->getSharedCacheKey( 'file-volatile', md5( $this->getName() ) );
- if ( $key ) {
- $this->lastMarkedVolatile = time();
- return $wgMemc->set( $key, $this->lastMarkedVolatile, self::VOLATILE_TTL );
- }
-
- return true;
- }
-
- /**
- * Check if a file is about to be changed or has been changed recently
- *
- * @see LocalFile::isVolatile()
- * @return bool Whether the file is volatile
- */
- protected function isVolatile() {
- global $wgMemc;
-
- $key = $this->repo->getSharedCacheKey( 'file-volatile', md5( $this->getName() ) );
- if ( !$key ) {
- // repo unavailable; bail.
- return false;
- }
-
- if ( $this->lastMarkedVolatile === 0 ) {
- $this->lastMarkedVolatile = $wgMemc->get( $key ) ?: 0;
- }
-
- $volatileDuration = time() - $this->lastMarkedVolatile;
- return $volatileDuration <= self::VOLATILE_TTL;
- }
-
- /**
* Roll back the DB transaction and mark the image unlocked
*/
function unlockAndRollback() {
@@ -1985,7 +1926,7 @@ class LocalFileDeleteBatch {
/** @var array Items to be processed in the deletion batch */
private $deletionBatch;
- /** @var bool Wether to suppress all suppressable fields when deleting */
+ /** @var bool Whether to suppress all suppressable fields when deleting */
private $suppress;
/** @var FileRepoStatus */
@@ -2238,26 +2179,9 @@ class LocalFileDeleteBatch {
* @return FileRepoStatus
*/
function execute() {
- wfProfileIn( __METHOD__ );
$this->file->lock();
- // Leave private files alone
- $privateFiles = array();
- list( $oldRels, ) = $this->getOldRels();
- $dbw = $this->file->repo->getMasterDB();
-
- if ( !empty( $oldRels ) ) {
- $res = $dbw->select( 'oldimage',
- array( 'oi_archive_name' ),
- array( 'oi_name' => $this->file->getName(),
- 'oi_archive_name' => array_keys( $oldRels ),
- $dbw->bitAnd( 'oi_deleted', File::DELETED_FILE ) => File::DELETED_FILE ),
- __METHOD__ );
- foreach ( $res as $row ) {
- $privateFiles[$row->oi_archive_name] = 1;
- }
- }
// Prepare deletion batch
$hashes = $this->getHashes();
$this->deletionBatch = array();
@@ -2265,9 +2189,8 @@ class LocalFileDeleteBatch {
$dotExt = $ext === '' ? '' : ".$ext";
foreach ( $this->srcRels as $name => $srcRel ) {
- // Skip files that have no hash (missing source).
- // Keep private files where they are.
- if ( isset( $hashes[$name] ) && !array_key_exists( $name, $privateFiles ) ) {
+ // Skip files that have no hash (e.g. missing DB record, or sha1 field and file source)
+ if ( isset( $hashes[$name] ) ) {
$hash = $hashes[$name];
$key = $hash . $dotExt;
$dstRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
@@ -2284,6 +2207,7 @@ class LocalFileDeleteBatch {
$this->doDBInserts();
// Removes non-existent file from the batch, so we don't get errors.
+ // This also handles files in the 'deleted' zone deleted via revision deletion.
$checkStatus = $this->removeNonexistentFiles( $this->deletionBatch );
if ( !$checkStatus->isGood() ) {
$this->status->merge( $checkStatus );
@@ -2303,7 +2227,6 @@ class LocalFileDeleteBatch {
// Roll back inserts, release lock and abort
// TODO: delete the defunct filearchive rows if we are using a non-transactional DB
$this->file->unlockAndRollback();
- wfProfileOut( __METHOD__ );
return $this->status;
}
@@ -2313,7 +2236,6 @@ class LocalFileDeleteBatch {
// Commit and return
$this->file->unlock();
- wfProfileOut( __METHOD__ );
return $this->status;
}
@@ -2366,7 +2288,7 @@ class LocalFileRestoreBatch {
/** @var bool Add all revisions of the file */
private $all;
- /** @var bool Wether to remove all settings for suppressed fields */
+ /** @var bool Whether to remove all settings for suppressed fields */
private $unsuppress = false;
/**
@@ -2419,13 +2341,19 @@ class LocalFileRestoreBatch {
return $this->file->repo->newGood();
}
- $this->file->lock();
+ $lockOwnsTrx = $this->file->lock();
$dbw = $this->file->repo->getMasterDB();
$status = $this->file->repo->newGood();
$exists = (bool)$dbw->selectField( 'image', '1',
- array( 'img_name' => $this->file->getName() ), __METHOD__, array( 'FOR UPDATE' ) );
+ array( 'img_name' => $this->file->getName() ),
+ __METHOD__,
+ // The lock() should already prevents changes, but this still may need
+ // to bypass any transaction snapshot. However, if lock() started the
+ // trx (which it probably did) then snapshot is post-lock and up-to-date.
+ $lockOwnsTrx ? array() : array( 'LOCK IN SHARE MODE' )
+ );
// Fetch all or selected archived revisions for the file,
// sorted from the most recent to the oldest.
@@ -2797,7 +2725,7 @@ class LocalFileMoveBatch {
array( 'oi_archive_name', 'oi_deleted' ),
array( 'oi_name' => $this->oldName ),
__METHOD__,
- array( 'FOR UPDATE' ) // ignore snapshot
+ array( 'LOCK IN SHARE MODE' ) // ignore snapshot
);
foreach ( $result as $row ) {
diff --git a/includes/filerepo/file/OldLocalFile.php b/includes/filerepo/file/OldLocalFile.php
index 710058fb..fd92e11a 100644
--- a/includes/filerepo/file/OldLocalFile.php
+++ b/includes/filerepo/file/OldLocalFile.php
@@ -175,11 +175,12 @@ class OldLocalFile extends LocalFile {
}
function loadFromDB( $flags = 0 ) {
- wfProfileIn( __METHOD__ );
-
$this->dataLoaded = true;
- $dbr = $this->repo->getSlaveDB();
+ $dbr = ( $flags & self::READ_LATEST )
+ ? $this->repo->getMasterDB()
+ : $this->repo->getSlaveDB();
+
$conds = array( 'oi_name' => $this->getName() );
if ( is_null( $this->requestedTime ) ) {
$conds['oi_archive_name'] = $this->archive_name;
@@ -194,14 +195,12 @@ class OldLocalFile extends LocalFile {
$this->fileExists = false;
}
- wfProfileOut( __METHOD__ );
}
/**
* Load lazy file metadata from the DB
*/
protected function loadExtraFromDB() {
- wfProfileIn( __METHOD__ );
$this->extraDataLoaded = true;
$dbr = $this->repo->getSlaveDB();
@@ -226,11 +225,9 @@ class OldLocalFile extends LocalFile {
$this->$name = $value;
}
} else {
- wfProfileOut( __METHOD__ );
throw new MWException( "Could not find data for image '{$this->archive_name}'." );
}
- wfProfileOut( __METHOD__ );
}
/**
@@ -260,13 +257,11 @@ class OldLocalFile extends LocalFile {
}
function upgradeRow() {
- wfProfileIn( __METHOD__ );
$this->loadFromFile();
# Don't destroy file info of missing files
if ( !$this->fileExists ) {
wfDebug( __METHOD__ . ": file does not exist, aborting\n" );
- wfProfileOut( __METHOD__ );
return;
}
@@ -291,7 +286,6 @@ class OldLocalFile extends LocalFile {
'oi_archive_name' => $this->archive_name ),
__METHOD__
);
- wfProfileOut( __METHOD__ );
}
/**
diff --git a/includes/filerepo/file/UnregisteredLocalFile.php b/includes/filerepo/file/UnregisteredLocalFile.php
index 5a3e4e9c..4c11341b 100644
--- a/includes/filerepo/file/UnregisteredLocalFile.php
+++ b/includes/filerepo/file/UnregisteredLocalFile.php
@@ -166,6 +166,18 @@ class UnregisteredLocalFile extends File {
}
/**
+ * @return int
+ */
+ function getBitDepth() {
+ $gis = $this->getImageSize( $this->getLocalRefPath() );
+
+ if ( !$gis || !isset( $gis['bits'] ) ) {
+ return 0;
+ }
+ return $gis['bits'];
+ }
+
+ /**
* @return bool
*/
function getMetadata() {