$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 {
wfProfileOut( __METHOD__ . '-getProps' );
}
+ # Imports or such might force a certain timestamp; otherwise we generate
+ # it and can fudge it slightly to keep (name,timestamp) unique on re-upload.
if ( $timestamp === false ) {
$timestamp = $dbw->timestamp();
+ $allowTimeKludge = true;
+ } else {
+ $allowTimeKludge = false;
}
$props['description'] = $comment;
'IGNORE'
);
if ( $dbw->affectedRows() == 0 ) {
+ if ( $allowTimeKludge ) {
+ # Use FOR UPDATE to ignore any transaction snapshotting
+ $ltimestamp = $dbw->selectField( 'image', 'img_timestamp',
+ array( 'img_name' => $this->getName() ), __METHOD__, array( 'FOR UPDATE' ) );
+ $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
+ if ( $lUnixtime && wfTimestamp( TS_UNIX, $timestamp ) <= $lUnixtime ) {
+ sleep( 1 ); // fast enough re-uploads would go far in the future otherwise
+ $timestamp = $dbw->timestamp( $lUnixtime + 1 );
+ $this->timestamp = wfTimestamp( TS_MW, $timestamp ); // DB -> TS_MW
+ }
+ }
+
# (bug 34993) Note: $oldver can be empty here, if the previous
# version of the file was broken. Allow registration of the new
# version to continue anyway, because that's better than having
*
* @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 );
/**
* Start a transaction and lock the image for update
* Increments a reference counter if the lock is already held
- * @throws MWException
- * @return bool True if the image exists, false otherwise
+ * @throws MWException Throws an error if the lock was not acquired
+ * @return bool success
*/
function lock() {
$dbw = $this->repo->getMasterDB();
}
$this->locked++;
// Bug 54736: use simple lock to handle when the file does not exist.
- // SELECT FOR UPDATE only locks records not the gaps where there are none.
- $cache = wfGetMainCache();
- $key = $this->getCacheKey();
- if ( !$cache->lock( $key, 5 ) ) {
+ // SELECT FOR UPDATE prevents changes, not other SELECTs with FOR UPDATE.
+ // Also, that would cause contention on INSERT of similarly named rows.
+ $backend = $this->getRepo()->getBackend();
+ $lockPaths = array( $this->getPath() ); // represents all versions of the file
+ $status = $backend->lockFiles( $lockPaths, LockManager::LOCK_EX, 5 );
+ if ( !$status->isGood() ) {
throw new MWException( "Could not acquire lock for '{$this->getName()}.'" );
}
- $dbw->onTransactionIdle( function () use ( $cache, $key ) {
- $cache->unlock( $key ); // release on commit
+ $dbw->onTransactionIdle( function () use ( $backend, $lockPaths ) {
+ $backend->unlockFiles( $lockPaths, LockManager::LOCK_EX ); // release on commit
} );
}
$this->markVolatile(); // file may change soon
- return $dbw->selectField( 'image', '1',
- array( 'img_name' => $this->getName() ), __METHOD__, array( 'FOR UPDATE' ) );
+ return true;
}
/**
/** @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,
return $this->file->repo->newGood();
}
- $exists = $this->file->lock();
+ $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' ) );
+
// Fetch all or selected archived revisions for the file,
// sorted from the most recent to the oldest.
$conditions = array( 'fa_name' => $this->file->getName() );
$result = $this->db->select( 'oldimage',
array( 'oi_archive_name', 'oi_deleted' ),
array( 'oi_name' => $this->oldName ),
- __METHOD__
+ __METHOD__,
+ array( 'FOR UPDATE' ) // ignore snapshot
);
foreach ( $result as $row ) {