/** @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
/**
* Create a LocalFile from a title
/**
* Get the memcached key for the main data for this file, or false if
* there is no access to the shared cache.
- * @return bool
+ * @return string|bool
*/
function getCacheKey() {
$hashedName = md5( $this->getName() );
/**
* Load file metadata from the DB
*/
- function loadFromDB() {
+ function loadFromDB( $flags = 0 ) {
# Polymorphic function name to distinguish foreign and local fetches
$fname = get_class( $this ) . '::' . __FUNCTION__;
wfProfileIn( $fname );
$this->dataLoaded = true;
$this->extraDataLoaded = true;
- $dbr = $this->repo->getMasterDB();
+ $dbr = ( $flags & self::LOAD_VIA_SLAVE )
+ ? $this->repo->getSlaveDB()
+ : $this->repo->getMasterDB();
+
$row = $dbr->selectRow( 'image', $this->getCacheFields( 'img_' ),
array( 'img_name' => $this->getName() ), $fname );
# Unconditionally set loaded=true, we don't want the accessors constantly rechecking
$this->extraDataLoaded = true;
- $dbr = $this->repo->getSlaveDB();
- // In theory the file could have just been renamed/deleted...oh well
- $row = $dbr->selectRow( 'image', $this->getLazyCacheFields( 'img_' ),
- array( 'img_name' => $this->getName() ), $fname );
-
- if ( !$row ) { // fallback to master
- $dbr = $this->repo->getMasterDB();
- $row = $dbr->selectRow( 'image', $this->getLazyCacheFields( 'img_' ),
- array( 'img_name' => $this->getName() ), $fname );
+ $fieldMap = $this->loadFieldsWithTimestamp( $this->repo->getSlaveDB(), $fname );
+ if ( !$fieldMap ) {
+ $fieldMap = $this->loadFieldsWithTimestamp( $this->repo->getMasterDB(), $fname );
}
- if ( $row ) {
- foreach ( $this->unprefixRow( $row, 'img_' ) as $name => $value ) {
+ if ( $fieldMap ) {
+ foreach ( $fieldMap as $name => $value ) {
$this->$name = $value;
}
} else {
wfProfileOut( $fname );
}
+ /**
+ * @param DatabaseBase $dbr
+ * @param string $fname
+ * @return array|false
+ */
+ private function loadFieldsWithTimestamp( $dbr, $fname ) {
+ $fieldMap = false;
+
+ $row = $dbr->selectRow( 'image', $this->getLazyCacheFields( 'img_' ),
+ array( 'img_name' => $this->getName(), 'img_timestamp' => $this->getTimestamp() ),
+ $fname );
+ if ( $row ) {
+ $fieldMap = $this->unprefixRow( $row, 'img_' );
+ } else {
+ # File may have been uploaded over in the meantime; check the old versions
+ $row = $dbr->selectRow( 'oldimage', $this->getLazyCacheFields( 'oi_' ),
+ array( 'oi_name' => $this->getName(), 'oi_timestamp' => $this->getTimestamp() ),
+ $fname );
+ if ( $row ) {
+ $fieldMap = $this->unprefixRow( $row, 'oi_' );
+ }
+ }
+
+ return $fieldMap;
+ }
+
/**
* @param array $row Row
* @param string $prefix
$decoded['timestamp'] = wfTimestamp( TS_MW, $decoded['timestamp'] );
+ $decoded['metadata'] = $this->repo->getSlaveDB()->decodeBlob( $decoded['metadata'] );
+
if ( empty( $decoded['major_mime'] ) ) {
$decoded['mime'] = 'unknown/unknown';
} else {
function load( $flags = 0 ) {
if ( !$this->dataLoaded ) {
if ( !$this->loadFromCache() ) {
- $this->loadFromDB();
+ $this->loadFromDB( $this->isVolatile() ? 0 : self::LOAD_VIA_SLAVE );
$this->saveToCache();
}
$this->dataLoaded = true;
/** createThumb inherited */
/** transform inherited */
- /**
- * Fix thumbnail files from 1.4 or before, with extreme prejudice
- * @todo Do we still care about this? Perhaps a maintenance script
- * can be made instead. Enabling this code results in a serious
- * RTT regression for wikis without 404 handling.
- *
- * @param string $thumbName
- */
- function migrateThumbFile( $thumbName ) {
- /* Old code for bug 2532
- $thumbDir = $this->getThumbPath();
- $thumbPath = "$thumbDir/$thumbName";
- if ( is_dir( $thumbPath ) ) {
- // Directory where file should be
- // This happened occasionally due to broken migration code in 1.5
- // Rename to broken-*
- for ( $i = 0; $i < 100; $i++ ) {
- $broken = $this->repo->getZonePath( 'public' ) . "/broken-$i-$thumbName";
- if ( !file_exists( $broken ) ) {
- rename( $thumbPath, $broken );
- break;
- }
- }
- // Doesn't exist anymore
- clearstatcache();
- }
- */
- /*
- if ( $this->repo->fileExists( $thumbDir ) ) {
- // Delete file where directory should be
- $this->repo->cleanupBatch( array( $thumbDir ) );
- }
- */
- }
-
/** getHandler inherited */
/** iconThumb inherited */
/** getLastError inherited */
$dbw,
$descTitle->getArticleID(),
$editSummary,
- false
+ false,
+ $user
);
if ( !is_null( $nullRevision ) ) {
$nullRevision->insertOn( $dbw );
wfProfileOut( __METHOD__ . '-edit' );
-
if ( $reupload ) {
# Delete old thumbnails
wfProfileIn( __METHOD__ . '-purge' );
*
* @param string $reason
* @param bool $suppress
+ * @param User|null $user
* @return FileRepoStatus
*/
- function delete( $reason, $suppress = false ) {
+ function delete( $reason, $suppress = false, $user = null ) {
if ( $this->getRepo()->getReadOnlyReason() !== false ) {
return $this->readOnlyFatalStatus();
}
- $batch = new LocalFileDeleteBatch( $this, $reason, $suppress );
+ $batch = new LocalFileDeleteBatch( $this, $reason, $suppress, $user );
$this->lock(); // begin
$batch->addCurrent();
* @param string $archiveName
* @param string $reason
* @param bool $suppress
+ * @param User|null $user
* @throws MWException Exception on database or file store failure
* @return FileRepoStatus
*/
- function deleteOld( $archiveName, $reason, $suppress = false ) {
+ function deleteOld( $archiveName, $reason, $suppress = false, $user = null ) {
global $wgUseSquid;
if ( $this->getRepo()->getReadOnlyReason() !== false ) {
return $this->readOnlyFatalStatus();
}
- $batch = new LocalFileDeleteBatch( $this, $reason, $suppress );
+ $batch = new LocalFileDeleteBatch( $this, $reason, $suppress, $user );
$this->lock(); // begin
$batch->addOld( $archiveName );
} );
}
+ $this->markVolatile(); // file may change soon
+
return $dbw->selectField( 'image', '1',
array( 'img_name' => $this->getName() ), __METHOD__, array( 'FOR UPDATE' ) );
}
}
}
+ /**
+ * 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 FileRepoStatus */
private $status;
+ /** @var User */
+ private $user;
+
/**
* @param File $file
* @param string $reason
* @param bool $suppress
+ * @param User|null $user
*/
- function __construct( File $file, $reason = '', $suppress = false ) {
+ function __construct( File $file, $reason = '', $suppress = false, $user = null ) {
$this->file = $file;
$this->reason = $reason;
$this->suppress = $suppress;
+ if ( $user ) {
+ $this->user = $user;
+ } else {
+ global $wgUser;
+ $this->user = $wgUser;
+ }
$this->status = $file->repo->newGood();
}
}
function doDBInserts() {
- global $wgUser;
-
$dbw = $this->file->repo->getMasterDB();
$encTimestamp = $dbw->addQuotes( $dbw->timestamp() );
- $encUserId = $dbw->addQuotes( $wgUser->getId() );
+ $encUserId = $dbw->addQuotes( $this->user->getId() );
$encReason = $dbw->addQuotes( $this->reason );
$encGroup = $dbw->addQuotes( 'deleted' );
$ext = $this->file->getExtension();
$dbw->insertSelect( 'filearchive', 'image',
array(
'fa_storage_group' => $encGroup,
- 'fa_storage_key' => "CASE WHEN img_sha1='' THEN '' ELSE $concat END",
+ 'fa_storage_key' => $dbw->conditional(
+ array( 'img_sha1' => '' ),
+ $dbw->addQuotes( '' ),
+ $concat
+ ),
'fa_deleted_user' => $encUserId,
'fa_deleted_timestamp' => $encTimestamp,
'fa_deleted_reason' => $encReason,
$dbw->insertSelect( 'filearchive', 'oldimage',
array(
'fa_storage_group' => $encGroup,
- 'fa_storage_key' => "CASE WHEN oi_sha1='' THEN '' ELSE $concat END",
+ 'fa_storage_key' => $dbw->conditional(
+ array( 'oi_sha1' => '' ),
+ $dbw->addQuotes( '' ),
+ $concat
+ ),
'fa_deleted_user' => $encUserId,
'fa_deleted_timestamp' => $encTimestamp,
'fa_deleted_reason' => $encReason,