use \MediaWiki\Logger\LoggerFactory;
-/**
- * Bump this number when serialized cache records may be incompatible.
- */
-define( 'MW_FILE_VERSION', 9 );
-
/**
* Class to represent a local file in the wiki's own database
*
* @ingroup FileAbstraction
*/
class LocalFile extends File {
+ const VERSION = 10; // cache version
+
const CACHE_FIELD_MAX_LEN = 1000;
/** @var bool Does the file exist on disk? (loadFromXxx) */
* @return string|bool
*/
function getCacheKey() {
- $hashedName = md5( $this->getName() );
-
- return $this->repo->getSharedCacheKey( 'file', $hashedName );
+ return $this->repo->getSharedCacheKey( 'file', sha1( $this->getName() ) );
}
/**
- * Try to load file metadata from memcached. Returns true on success.
- * @return bool
+ * Try to load file metadata from memcached, falling back to the database
*/
private function loadFromCache() {
$this->dataLoaded = false;
$this->extraDataLoaded = false;
- $key = $this->getCacheKey();
+ $key = $this->getCacheKey();
if ( !$key ) {
- return false;
- }
+ $this->loadFromDB( self::READ_NORMAL );
- $cache = ObjectCache::getMainWANInstance();
- $cachedValues = $cache->get( $key );
-
- // Check if the key existed and belongs to this version of MediaWiki
- if ( is_array( $cachedValues ) && $cachedValues['version'] == MW_FILE_VERSION ) {
- $this->fileExists = $cachedValues['fileExists'];
- if ( $this->fileExists ) {
- $this->setProps( $cachedValues );
- }
- $this->dataLoaded = true;
- $this->extraDataLoaded = true;
- foreach ( $this->getLazyCacheFields( '' ) as $field ) {
- $this->extraDataLoaded = $this->extraDataLoaded && isset( $cachedValues[$field] );
- }
+ return;
}
- return $this->dataLoaded;
- }
-
- /**
- * Save the file metadata to memcached
- */
- private function saveToCache() {
- $this->load();
+ $cache = ObjectCache::getMainWANInstance();
+ $cachedValues = $cache->getWithSetCallback(
+ $key,
+ $cache::TTL_WEEK,
+ function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
+ $setOpts += Database::getCacheSetOptions( $this->repo->getSlaveDB() );
+
+ $this->loadFromDB( self::READ_NORMAL );
+
+ $fields = $this->getCacheFields( '' );
+ $cacheVal['fileExists'] = $this->fileExists;
+ if ( $this->fileExists ) {
+ foreach ( $fields as $field ) {
+ $cacheVal[$field] = $this->$field;
+ }
+ }
+ // Strip off excessive entries from the subset of fields that can become large.
+ // If the cache value gets to large it will not fit in memcached and nothing will
+ // get cached at all, causing master queries for any file access.
+ foreach ( $this->getLazyCacheFields( '' ) as $field ) {
+ if ( isset( $cacheVal[$field] )
+ && strlen( $cacheVal[$field] ) > 100 * 1024
+ ) {
+ unset( $cacheVal[$field] ); // don't let the value get too big
+ }
+ }
- $key = $this->getCacheKey();
- if ( !$key ) {
- return;
- }
+ if ( $this->fileExists ) {
+ $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->timestamp ), $ttl );
+ } else {
+ $ttl = $cache::TTL_DAY;
+ }
- $fields = $this->getCacheFields( '' );
- $cacheVal = [ 'version' => MW_FILE_VERSION ];
- $cacheVal['fileExists'] = $this->fileExists;
+ return $cacheVal;
+ },
+ [ 'version' => self::VERSION ]
+ );
+ $this->fileExists = $cachedValues['fileExists'];
if ( $this->fileExists ) {
- foreach ( $fields as $field ) {
- $cacheVal[$field] = $this->$field;
- }
+ $this->setProps( $cachedValues );
}
- // Strip off excessive entries from the subset of fields that can become large.
- // If the cache value gets to large it will not fit in memcached and nothing will
- // get cached at all, causing master queries for any file access.
+ $this->dataLoaded = true;
+ $this->extraDataLoaded = true;
foreach ( $this->getLazyCacheFields( '' ) as $field ) {
- if ( isset( $cacheVal[$field] ) && strlen( $cacheVal[$field] ) > 100 * 1024 ) {
- unset( $cacheVal[$field] ); // don't let the value get too big
- }
+ $this->extraDataLoaded = $this->extraDataLoaded && isset( $cachedValues[$field] );
}
-
- // Cache presence for 1 week and negatives for 1 day
- $ttl = $this->fileExists ? 86400 * 7 : 86400;
- $opts = Database::getCacheSetOptions( $this->repo->getSlaveDB() );
- ObjectCache::getMainWANInstance()->set( $key, $cacheVal, $ttl, $opts );
}
/**
return;
}
- $this->repo->getMasterDB()->onTransactionPreCommitOrIdle( function() use ( $key ) {
- ObjectCache::getMainWANInstance()->delete( $key );
- } );
+ $this->repo->getMasterDB()->onTransactionPreCommitOrIdle(
+ function () use ( $key ) {
+ ObjectCache::getMainWANInstance()->delete( $key );
+ },
+ __METHOD__
+ );
}
/**
private function loadFieldsWithTimestamp( $dbr, $fname ) {
$fieldMap = false;
- $row = $dbr->selectRow( 'image', $this->getLazyCacheFields( 'img_' ),
- [ 'img_name' => $this->getName(), 'img_timestamp' => $this->getTimestamp() ],
- $fname );
+ $row = $dbr->selectRow( 'image', $this->getLazyCacheFields( 'img_' ), [
+ 'img_name' => $this->getName(),
+ 'img_timestamp' => $dbr->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_' ),
- [ 'oi_name' => $this->getName(), 'oi_timestamp' => $this->getTimestamp() ],
- $fname );
+ $row = $dbr->selectRow( 'oldimage', $this->getLazyCacheFields( 'oi_' ), [
+ 'oi_name' => $this->getName(),
+ 'oi_timestamp' => $dbr->timestamp( $this->getTimestamp() )
+ ], $fname );
if ( $row ) {
$fieldMap = $this->unprefixRow( $row, 'oi_' );
}
*/
function load( $flags = 0 ) {
if ( !$this->dataLoaded ) {
- if ( ( $flags & self::READ_LATEST ) || !$this->loadFromCache() ) {
+ if ( $flags & self::READ_LATEST ) {
$this->loadFromDB( $flags );
- $this->saveToCache();
+ } else {
+ $this->loadFromCache();
}
- $this->dataLoaded = true;
}
+
if ( ( $flags & self::LOAD_ALL ) && !$this->extraDataLoaded ) {
// @note: loads on name/timestamp to reduce race condition problems
$this->loadExtraFromDB();
if ( $type == 'text' ) {
return $this->user_text;
- } elseif ( $type == 'id' ) {
+ } else { // id
return (int)$this->user;
}
}
) {
$props = $this->repo->getFileProps( $srcPath );
} else {
- $props = FSFile::getPropsFromPath( $srcPath );
+ $mwProps = new MWFileProps( MimeMagic::singleton() );
+ $props = $mwProps->getPropsFromPath( $srcPath, true );
}
}
);
if ( isset( $status->value['revision'] ) ) {
+ /** @var $rev Revision */
+ $rev = $status->value['revision'];
// Associate new page revision id
- $logEntry->setAssociatedRevId( $status->value['revision']->getId() );
+ $logEntry->setAssociatedRevId( $rev->getId() );
}
// This relies on the resetArticleID() call in WikiPage::insertOn(),
// which is triggered on $descTitle by doEditContent() above.
$sha1 = $repo->isVirtualUrl( $srcPath )
? $repo->getFileSha1( $srcPath )
: FSFile::getSha1Base36FromPath( $srcPath );
- $dst = $repo->getBackend()->getPathForSHA1( $sha1 );
+ /** @var FileBackendDBRepoWrapper $wrapperBackend */
+ $wrapperBackend = $repo->getBackend();
+ $dst = $wrapperBackend->getPathForSHA1( $sha1 );
$status = $repo->quickImport( $src, $dst );
if ( $flags & File::DELETE_SOURCE ) {
unlink( $srcPath );
}
// Release the lock *after* commit to avoid row-level contention.
// Make sure it triggers on rollback() as well as commit() (T132921).
- $dbw->onTransactionResolution( function () use ( $logger ) {
- $status = $this->releaseFileLock();
- if ( !$status->isGood() ) {
- $logger->error( "Failed to unlock '{file}'", [ 'file' => $this->name ] );
- }
- } );
+ $dbw->onTransactionResolution(
+ function () use ( $logger ) {
+ $status = $this->releaseFileLock();
+ if ( !$status->isGood() ) {
+ $logger->error( "Failed to unlock '{file}'", [ 'file' => $this->name ] );
+ }
+ },
+ __METHOD__
+ );
// Callers might care if the SELECT snapshot is safely fresh
$this->lockedOwnTrx = $makesTransaction;
}
* @return FileRepoStatus
*/
public function execute() {
+ /** @var Language */
global $wgLang;
$repo = $this->file->getRepo();
// Even if some files could be copied, fail entirely as that is the
// easiest thing to do without data loss
$this->cleanupFailedBatch( $storeStatus, $storeBatch );
- $status->ok = false;
+ $status->setOK( false );
$this->file->unlock();
return $status;
if ( !$statusDb->isGood() ) {
$destFile->unlock();
$this->file->unlock();
- $statusDb->ok = false;
+ $statusDb->setOK( false );
return $statusDb;
}
$this->file->unlock();
wfDebugLog( 'imagemove', "Error in moving files: "
. $statusMove->getWikiText( false, false, 'en' ) );
- $statusMove->ok = false;
+ $statusMove->setOK( false );
return $statusMove;
}