/**
* Bump this number when serialized cache records may be incompatible.
*/
-define( 'MW_FILE_VERSION', 8 );
+define( 'MW_FILE_VERSION', 9 );
/**
* Class to represent a local file in the wiki's own database
$sha1, # SHA-1 base 36 content hash
$user, $user_text, # User, who uploaded the file
$description, # Description of current revision of the file
- $dataLoaded, # Whether or not all this has been loaded from the database (loadFromXxx)
+ $dataLoaded, # Whether or not core data has been loaded from the database (loadFromXxx)
+ $extraDataLoaded, # Whether or not lazy-loaded data has been loaded from the database
$upgraded, # Whether the row was upgraded on load
$locked, # True if the image row is locked
$missing, # True if file is not present in file system. Not to be cached in memcached
protected $repoClass = 'LocalRepo';
+ const LOAD_ALL = 1; // integer; load all the lazy fields too (like metadata)
+
/**
* Create a LocalFile from a title
* Do not call this except from inside a repo class.
$this->historyLine = 0;
$this->historyRes = null;
$this->dataLoaded = false;
+ $this->extraDataLoaded = false;
$this->assertRepoDefined();
$this->assertTitleDefined();
wfProfileIn( __METHOD__ );
$this->dataLoaded = false;
+ $this->extraDataLoaded = false;
$key = $this->getCacheKey();
if ( !$key ) {
$cachedValues = $wgMemc->get( $key );
// Check if the key existed and belongs to this version of MediaWiki
- if ( isset( $cachedValues['version'] ) && ( $cachedValues['version'] == MW_FILE_VERSION ) ) {
+ if ( isset( $cachedValues['version'] ) && $cachedValues['version'] == MW_FILE_VERSION ) {
wfDebug( "Pulling file metadata from cache key $key\n" );
$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] );
+ }
}
if ( $this->dataLoaded ) {
}
}
+ // 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( $cache[$field] ) && strlen( $cache[$field] ) > 100*1024 ) {
+ unset( $cache[$field] ); // don't let the value get too big
+ }
+ }
+
// Cache presence for 1 week and negatives for 1 day
$wgMemc->set( $key, $cache, $this->fileExists ? 86400 * 7 : 86400 );
}
return $results[$prefix];
}
+ /**
+ * @return array
+ */
+ function getLazyCacheFields( $prefix = 'img_' ) {
+ static $fields = array( 'metadata' );
+ static $results = array();
+
+ if ( $prefix == '' ) {
+ return $fields;
+ }
+
+ if ( !isset( $results[$prefix] ) ) {
+ $prefixedFields = array();
+ foreach ( $fields as $field ) {
+ $prefixedFields[] = $prefix . $field;
+ }
+ $results[$prefix] = $prefixedFields;
+ }
+
+ return $results[$prefix];
+ }
+
/**
* Load file metadata from the DB
*/
# Unconditionally set loaded=true, we don't want the accessors constantly rechecking
$this->dataLoaded = true;
+ $this->extraDataLoaded = true;
$dbr = $this->repo->getMasterDB();
-
$row = $dbr->selectRow( 'image', $this->getCacheFields( 'img_' ),
array( 'img_name' => $this->getName() ), $fname );
}
/**
- * Decode a row from the database (either object or array) to an array
- * with timestamps and MIME types decoded, and the field prefix removed.
- * @param $row
+ * Load lazy file metadata from the DB.
+ * 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;
+
+ $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 );
+ }
+
+ if ( $row ) {
+ foreach ( $this->unprefixRow( $row, 'img_' ) as $name => $value ) {
+ $this->$name = $value;
+ }
+ } else {
+ throw new MWException( "Could not find data for image '{$this->getName()}'." );
+ }
+
+ wfProfileOut( $fname );
+ }
+
+ /**
+ * @param Row $row
* @param $prefix string
- * @throws MWException
- * @return array
+ * @return Array
*/
- function decodeRow( $row, $prefix = 'img_' ) {
+ protected function unprefixRow( $row, $prefix = 'img_' ) {
$array = (array)$row;
$prefixLength = strlen( $prefix );
}
$decoded = array();
-
foreach ( $array as $name => $value ) {
$decoded[substr( $name, $prefixLength )] = $value;
}
+ return $decoded;
+ }
+
+ /**
+ * Decode a row from the database (either object or array) to an array
+ * with timestamps and MIME types decoded, and the field prefix removed.
+ * @param $row
+ * @param $prefix string
+ * @throws MWException
+ * @return array
+ */
+ function decodeRow( $row, $prefix = 'img_' ) {
+ $decoded = $this->unprefixRow( $row, $prefix );
$decoded['timestamp'] = wfTimestamp( TS_MW, $decoded['timestamp'] );
*/
function loadFromRow( $row, $prefix = 'img_' ) {
$this->dataLoaded = true;
+ $this->extraDataLoaded = true;
+
$array = $this->decodeRow( $row, $prefix );
foreach ( $array as $name => $value ) {
/**
* Load file metadata from cache or DB, unless already loaded
+ * @param integer $flags
*/
- function load() {
+ function load( $flags = 0 ) {
if ( !$this->dataLoaded ) {
if ( !$this->loadFromCache() ) {
$this->loadFromDB();
}
$this->dataLoaded = true;
}
+ if ( ( $flags & self::LOAD_ALL ) && !$this->extraDataLoaded ) {
+ $this->loadExtraFromDB();
+ }
}
/**
} else {
$handler = $this->getHandler();
if ( $handler ) {
- $validity = $handler->isMetadataValid( $this, $this->metadata );
+ $validity = $handler->isMetadataValid( $this, $this->getMetadata() );
if ( $validity === MediaHandler::METADATA_BAD
|| ( $validity === MediaHandler::METADATA_COMPATIBLE && $wgUpdateCompatibleMetadata )
) {
/**
* Set properties in this object to be equal to those given in the
* associative array $info. Only cacheable fields can be set.
+ * All fields *must* be set in $info except for getLazyCacheFields().
*
* If 'mime' is given, it will be split into major_mime/minor_mime.
* If major_mime/minor_mime are given, $this->mime will also be set.
* @return string
*/
function getMetadata() {
- $this->load();
+ $this->load( self::LOAD_ALL ); // large metadata is loaded in another step
return $this->metadata;
}
function loadFromDB() {
wfProfileIn( __METHOD__ );
+
$this->dataLoaded = true;
$dbr = $this->repo->getSlaveDB();
$conds = array( 'oi_name' => $this->getName() );
if ( is_null( $this->requestedTime ) ) {
$conds['oi_archive_name'] = $this->archive_name;
} else {
- $conds[] = 'oi_timestamp = ' . $dbr->addQuotes( $dbr->timestamp( $this->requestedTime ) );
+ $conds['oi_timestamp'] = $dbr->timestamp( $this->requestedTime );
}
$row = $dbr->selectRow( 'oldimage', $this->getCacheFields( 'oi_' ),
$conds, __METHOD__, array( 'ORDER BY' => 'oi_timestamp DESC' ) );
} else {
$this->fileExists = false;
}
+
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * Load lazy file metadata from the DB
+ */
+ protected function loadExtraFromDB() {
+ wfProfileIn( __METHOD__ );
+
+ $this->extraDataLoaded = true;
+ $dbr = $this->repo->getSlaveDB();
+ $conds = array( 'oi_name' => $this->getName() );
+ if ( is_null( $this->requestedTime ) ) {
+ $conds['oi_archive_name'] = $this->archive_name;
+ } else {
+ $conds['oi_timestamp'] = $dbr->timestamp( $this->requestedTime );
+ }
+ // In theory the file could have just been renamed/deleted...oh well
+ $row = $dbr->selectRow( 'oldimage', $this->getLazyCacheFields( 'oi_' ),
+ $conds, __METHOD__, array( 'ORDER BY' => 'oi_timestamp DESC' ) );
+
+ if ( !$row ) { // fallback to master
+ $dbr = $this->repo->getMasterDB();
+ $row = $dbr->selectRow( 'oldimage', $this->getLazyCacheFields( 'oi_' ),
+ $conds, __METHOD__, array( 'ORDER BY' => 'oi_timestamp DESC' ) );
+ }
+
+ if ( $row ) {
+ foreach ( $this->unprefixRow( $row, 'oi_' ) as $name => $value ) {
+ $this->$name = $value;
+ }
+ } else {
+ throw new MWException( "Could not find data for image '{$this->archive_name}'." );
+ }
+
wfProfileOut( __METHOD__ );
}