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;
- }
-
- $cache = ObjectCache::getMainWANInstance();
- $cachedValues = $cache->get( $key );
+ $this->loadFromDB( self::READ_NORMAL );
- // 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 );
}
/**
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;
}
}
$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 );
}
protected function doDBInserts() {
+ $now = time();
$dbw = $this->file->repo->getMasterDB();
- $encTimestamp = $dbw->addQuotes( $dbw->timestamp() );
+ $encTimestamp = $dbw->addQuotes( $dbw->timestamp( $now ) );
$encUserId = $dbw->addQuotes( $this->user->getId() );
$encReason = $dbw->addQuotes( $this->reason );
$encGroup = $dbw->addQuotes( 'deleted' );
}
if ( $deleteCurrent ) {
- $concat = $dbw->buildConcat( [ "img_sha1", $encExt ] );
- $where = [ 'img_name' => $this->file->getName() ];
- $dbw->insertSelect( 'filearchive', 'image',
+ $dbw->insertSelect(
+ 'filearchive',
+ 'image',
[
'fa_storage_group' => $encGroup,
'fa_storage_key' => $dbw->conditional(
[ 'img_sha1' => '' ],
$dbw->addQuotes( '' ),
- $concat
+ $dbw->buildConcat( [ "img_sha1", $encExt ] )
),
'fa_deleted_user' => $encUserId,
'fa_deleted_timestamp' => $encTimestamp,
'fa_user' => 'img_user',
'fa_user_text' => 'img_user_text',
'fa_timestamp' => 'img_timestamp',
- 'fa_sha1' => 'img_sha1',
- ], $where, __METHOD__ );
+ 'fa_sha1' => 'img_sha1'
+ ],
+ [ 'img_name' => $this->file->getName() ],
+ __METHOD__
+ );
}
if ( count( $oldRels ) ) {
- $concat = $dbw->buildConcat( [ "oi_sha1", $encExt ] );
- $where = [
- 'oi_name' => $this->file->getName(),
- 'oi_archive_name' => array_keys( $oldRels ) ];
- $dbw->insertSelect( 'filearchive', 'oldimage',
+ $res = $dbw->select(
+ 'oldimage',
+ OldLocalFile::selectFields(),
[
- 'fa_storage_group' => $encGroup,
- 'fa_storage_key' => $dbw->conditional(
- [ 'oi_sha1' => '' ],
- $dbw->addQuotes( '' ),
- $concat
- ),
- 'fa_deleted_user' => $encUserId,
- 'fa_deleted_timestamp' => $encTimestamp,
- 'fa_deleted_reason' => $encReason,
- 'fa_deleted' => $this->suppress ? $bitfield : 'oi_deleted',
-
- 'fa_name' => 'oi_name',
- 'fa_archive_name' => 'oi_archive_name',
- 'fa_size' => 'oi_size',
- 'fa_width' => 'oi_width',
- 'fa_height' => 'oi_height',
- 'fa_metadata' => 'oi_metadata',
- 'fa_bits' => 'oi_bits',
- 'fa_media_type' => 'oi_media_type',
- 'fa_major_mime' => 'oi_major_mime',
- 'fa_minor_mime' => 'oi_minor_mime',
- 'fa_description' => 'oi_description',
- 'fa_user' => 'oi_user',
- 'fa_user_text' => 'oi_user_text',
- 'fa_timestamp' => 'oi_timestamp',
- 'fa_sha1' => 'oi_sha1',
- ], $where, __METHOD__ );
+ 'oi_name' => $this->file->getName(),
+ 'oi_archive_name' => array_keys( $oldRels )
+ ],
+ __METHOD__,
+ [ 'FOR UPDATE' ]
+ );
+ $rowsInsert = [];
+ foreach ( $res as $row ) {
+ $rowsInsert[] = [
+ // Deletion-specific fields
+ 'fa_storage_group' => 'deleted',
+ 'fa_storage_key' => ( $row->oi_sha1 === '' )
+ ? ''
+ : "{$row->oi_sha1}{$dotExt}",
+ 'fa_deleted_user' => $this->user->getId(),
+ 'fa_deleted_timestamp' => $dbw->timestamp( $now ),
+ 'fa_deleted_reason' => $this->reason,
+ // Counterpart fields
+ 'fa_deleted' => $this->suppress ? $bitfield : $row->oi_deleted,
+ 'fa_name' => $row->oi_name,
+ 'fa_archive_name' => $row->oi_archive_name,
+ 'fa_size' => $row->oi_size,
+ 'fa_width' => $row->oi_width,
+ 'fa_height' => $row->oi_height,
+ 'fa_metadata' => $row->oi_metadata,
+ 'fa_bits' => $row->oi_bits,
+ 'fa_media_type' => $row->oi_media_type,
+ 'fa_major_mime' => $row->oi_major_mime,
+ 'fa_minor_mime' => $row->oi_minor_mime,
+ 'fa_description' => $row->oi_description,
+ 'fa_user' => $row->oi_user,
+ 'fa_user_text' => $row->oi_user_text,
+ 'fa_timestamp' => $row->oi_timestamp,
+ 'fa_sha1' => $row->oi_sha1
+ ];
+ }
+
+ $dbw->insert( 'filearchive', $rowsInsert, __METHOD__ );
}
}
* @return FileRepoStatus
*/
public function execute() {
+ /** @var Language */
global $wgLang;
$repo = $this->file->getRepo();