/** @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;
/** @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
function loadFromCache() {
global $wgMemc;
- wfProfileIn( __METHOD__ );
$this->dataLoaded = false;
$this->extraDataLoaded = false;
$key = $this->getCacheKey();
if ( !$key ) {
- wfProfileOut( __METHOD__ );
return false;
}
wfIncrStats( 'image_cache_miss' );
}
- wfProfileOut( __METHOD__ );
-
return $this->dataLoaded;
}
* @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 );
} else {
$this->fileExists = false;
}
-
- wfProfileOut( $fname );
}
/**
* 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;
$this->$name = $value;
}
} else {
- wfProfileOut( $fname );
throw new MWException( "Could not find data for image '{$this->getName()}'." );
}
-
- wfProfileOut( $fname );
}
/**
*/
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();
}
}
* Fix assorted version-related problems with the image row by reloading it from the file
*/
function upgradeRow() {
- wfProfileIn( __METHOD__ );
$this->lock(); // begin
if ( !$this->fileExists ) {
$this->unlock();
wfDebug( __METHOD__ . ": file does not exist, aborting\n" );
- wfProfileOut( __METHOD__ );
return;
}
if ( wfReadOnly() ) {
$this->unlock();
- wfProfileOut( __METHOD__ );
return;
}
$this->unlock(); // done
- wfProfileOut( __METHOD__ );
}
/**
/** getURL inherited */
/** getViewURL inherited */
/** getPath inherited */
- /** isVisible inhereted */
+ /** isVisible inherited */
/**
* @return bool
* @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();
// Purge squid cache for this file
SquidUpdate::purge( array( $this->getURL() ) );
- wfProfileOut( __METHOD__ );
}
/**
*/
function purgeOldThumbnails( $archiveName ) {
global $wgUseSquid;
- wfProfileIn( __METHOD__ );
// Get a list of old thumbnails and URLs
$files = $this->getThumbnails( $archiveName );
SquidUpdate::purge( $urls );
}
- wfProfileOut( __METHOD__ );
}
/**
*/
function purgeThumbnails( $options = array() ) {
global $wgUseSquid;
- wfProfileIn( __METHOD__ );
// Delete thumbnails
$files = $this->getThumbnails();
SquidUpdate::purge( $urls );
}
- wfProfileOut( __METHOD__ );
}
/**
}
if ( !$props ) {
- wfProfileIn( __METHOD__ . '-getProps' );
if ( $this->repo->isVirtualUrl( $srcPath )
|| FileBackend::isStoragePath( $srcPath )
) {
} else {
$props = FSFile::getPropsFromPath( $srcPath );
}
- wfProfileOut( __METHOD__ . '-getProps' );
}
$options = array();
function recordUpload2( $oldver, $comment, $pageText, $props = false, $timestamp = false,
$user = null
) {
- wfProfileIn( __METHOD__ );
if ( is_null( $user ) ) {
global $wgUser;
$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
if ( !$this->fileExists ) {
wfDebug( __METHOD__ . ": File " . $this->getRel() . " went missing!\n" );
$dbw->rollback( __METHOD__ );
- wfProfileOut( __METHOD__ );
return false;
}
// 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,
$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' );
Hooks::run( 'FileUpload', array( $this, $reupload, $descTitle->exists() ) );
- wfProfileOut( __METHOD__ . '-hooks' );
# Invalidate cache for all pages using this file
$update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' );
LinksUpdate::queueRecursiveJobsForTable( $this->getTitle(), 'imagelinks' );
}
- wfProfileOut( __METHOD__ );
-
return true;
}
return $this->timestamp;
}
+ /**
+ * @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
*/
} );
}
- $this->markVolatile(); // file may change soon
-
return $this->lockedOwnTrx;
}
}
}
- /**
- * 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
*/
/** @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 */
* @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();
$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;
$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 );
// 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;
}
// Commit and return
$this->file->unlock();
- wfProfileOut( __METHOD__ );
return $this->status;
}
/** @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;
/**