X-Git-Url: http://git.cyclocoop.org/?a=blobdiff_plain;f=includes%2FMessageCache.php;h=35ea4d53d5dd9d99c35c7b6bcdf68c2d880fab35;hb=71ffa3756a8214ac0ded721faa93ae6bbc886abd;hp=b260f4cb34e5294fc7981a8a61c18b78856bcd80;hpb=41470111f1dd8c0d83fd73f9239f7c5f84734535;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/MessageCache.php b/includes/MessageCache.php old mode 100755 new mode 100644 index b260f4cb34..35ea4d53d5 --- a/includes/MessageCache.php +++ b/includes/MessageCache.php @@ -2,6 +2,7 @@ /** * * @package MediaWiki + * @subpackage Cache */ /** */ @@ -20,11 +21,10 @@ define( 'MSG_WAIT_TIMEOUT', 10); * * @package MediaWiki */ -class MessageCache -{ +class MessageCache { var $mCache, $mUseCache, $mDisable, $mExpiry; var $mMemcKey, $mKeys, $mParserOptions, $mParser; - var $mExtensionMessages; + var $mExtensionMessages = array(); var $mInitialised = false; var $mDeferred = true; @@ -53,20 +53,125 @@ class MessageCache # can return a cache hit, this saves # some extra milliseconds $this->mDeferred = true; - + wfProfileOut( $fname ); } + /** + * Try to load the cache from a local file + */ + function loadFromLocal( $hash ) { + global $wgLocalMessageCache, $wgDBname; + + $this->mCache = false; + if ( $wgLocalMessageCache === false ) { + return; + } + + $filename = "$wgLocalMessageCache/messages-$wgDBname"; + + wfSuppressWarnings(); + $file = fopen( $filename, 'r' ); + wfRestoreWarnings(); + if ( !$file ) { + return; + } + + // Check to see if the file has the hash specified + $localHash = fread( $file, 32 ); + if ( $hash == $localHash ) { + // All good, get the rest of it + $serialized = fread( $file, 1000000 ); + $this->mCache = unserialize( $serialized ); + } + fclose( $file ); + } + + /** + * Save the cache to a local file + */ + function saveToLocal( $serialized, $hash ) { + global $wgLocalMessageCache, $wgDBname; + + if ( $wgLocalMessageCache === false ) { + return; + } + + $filename = "$wgLocalMessageCache/messages-$wgDBname"; + $oldUmask = umask( 0 ); + wfMkdirParents( $wgLocalMessageCache, 0777 ); + umask( $oldUmask ); + + $file = fopen( $filename, 'w' ); + if ( !$file ) { + wfDebug( "Unable to open local cache file for writing\n" ); + return; + } + + fwrite( $file, $hash . $serialized ); + fclose( $file ); + @chmod( $filename, 0666 ); + } + + function loadFromScript( $hash ) { + global $wgLocalMessageCache, $wgDBname; + if ( $wgLocalMessageCache === false ) { + return; + } + + $filename = "$wgLocalMessageCache/messages-$wgDBname"; + + wfSuppressWarnings(); + $file = fopen( $filename, 'r' ); + wfRestoreWarnings(); + if ( !$file ) { + return; + } + $localHash=substr(fread($file,40),8); + fclose($file); + if ($hash!=$localHash) { + return; + } + require("$wgLocalMessageCache/messages-$wgDBname"); + } + + function saveToScript($array, $hash) { + global $wgLocalMessageCache, $wgDBname; + if ( $wgLocalMessageCache === false ) { + return; + } + + $filename = "$wgLocalMessageCache/messages-$wgDBname"; + $oldUmask = umask( 0 ); + wfMkdirParents( $wgLocalMessageCache, 0777 ); + umask( $oldUmask ); + $file = fopen( $filename.'.tmp', 'w'); + fwrite($file,"mCache = array("); + + $re="/(? $message) { + fwrite($file, "'". preg_replace($re, "\'", $key). + "' => '" . preg_replace( $re, "\'", $message) . "',\n"); + } + fwrite($file,");\n?>"); + fclose($file); + rename($filename.'.tmp',$filename); + } + /** * Loads messages either from memcached or the database, if not disabled * On error, quietly switches to a fallback mode * Returns false for a reportable error, true otherwise */ function load() { - global $wgAllMessagesEn; + global $wgLocalMessageCache, $wgLocalMessageCacheSerialized; if ( $this->mDisable ) { - wfDebug( "MessageCache::load(): disabled\n" ); + static $shownDisabled = false; + if ( !$shownDisabled ) { + wfDebug( "MessageCache::load(): disabled\n" ); + $shownDisabled = true; + } return true; } $fname = 'MessageCache::load'; @@ -74,29 +179,77 @@ class MessageCache $success = true; if ( $this->mUseCache ) { - wfProfileIn( $fname.'-fromcache' ); - $this->mCache = $this->mMemc->get( $this->mMemcKey ); - wfProfileOut( $fname.'-fromcache' ); + $this->mCache = false; + + # Try local cache + wfProfileIn( $fname.'-fromlocal' ); + $hash = $this->mMemc->get( "{$this->mMemcKey}-hash" ); + if ( $hash ) { + if ($wgLocalMessageCacheSerialized) { + $this->loadFromLocal( $hash ); + } else { + $this->loadFromScript( $hash ); + } + } + wfProfileOut( $fname.'-fromlocal' ); + + # Try memcached + if ( !$this->mCache ) { + wfProfileIn( $fname.'-fromcache' ); + $this->mCache = $this->mMemc->get( $this->mMemcKey ); + + # Save to local cache + if ( $wgLocalMessageCache !== false ) { + $serialized = serialize( $this->mCache ); + if ( !$hash ) { + $hash = md5( $serialized ); + $this->mMemc->set( "{$this->mMemcKey}-hash", $hash, $this->mExpiry ); + } + if ($wgLocalMessageCacheSerialized) { + $this->saveToLocal( $serialized,$hash ); + } else { + $this->saveToScript( $this->mCache, $hash ); + } + } + wfProfileOut( $fname.'-fromcache' ); + } + # If there's nothing in memcached, load all the messages from the database if ( !$this->mCache ) { wfDebug( "MessageCache::load(): loading all messages\n" ); $this->lock(); # Other threads don't need to load the messages if another thread is doing it. - $success = $this->mMemc->add( $this->mMemcKey, "loading", MSG_LOAD_TIMEOUT ); + $success = $this->mMemc->add( $this->mMemcKey.'-status', "loading", MSG_LOAD_TIMEOUT ); if ( $success ) { wfProfileIn( $fname.'-load' ); $this->loadFromDB(); wfProfileOut( $fname.'-load' ); + # Save in memcached # Keep trying if it fails, this is kind of important wfProfileIn( $fname.'-save' ); - for ( $i=0; $i<20 && !$this->mMemc->set( $this->mMemcKey, $this->mCache, $this->mExpiry ); $i++ ) { + for ($i=0; $i<20 && + !$this->mMemc->set( $this->mMemcKey, $this->mCache, $this->mExpiry ); + $i++ ) { usleep(mt_rand(500000,1500000)); } + + # Save to local cache + if ( $wgLocalMessageCache !== false ) { + $serialized = serialize( $this->mCache ); + $hash = md5( $serialized ); + $this->mMemc->set( "{$this->mMemcKey}-hash", $hash, $this->mExpiry ); + if ($wgLocalMessageCacheSerialized) { + $this->saveToLocal( $serialized,$hash ); + } else { + $this->saveToScript( $this->mCache, $hash ); + } + } + wfProfileOut( $fname.'-save' ); if ( $i == 20 ) { - $this->mMemc->set( $this->mMemcKey, 'error', 60*5 ); + $this->mMemc->set( $this->mMemcKey.'-status', 'error', 60*5 ); wfDebug( "MemCached set error in MessageCache: restart memcached server!\n" ); } } @@ -130,23 +283,18 @@ class MessageCache * Loads all or main part of cacheable messages from the database */ function loadFromDB() { - global $wgPartialMessageCache; + global $wgAllMessagesEn, $wgLang; + $fname = 'MessageCache::loadFromDB'; $dbr =& wfGetDB( DB_SLAVE ); - $conditions = array( 'page_is_redirect' => 0, - 'page_namespace' => NS_MEDIAWIKI); - if ($wgPartialMessageCache) { - wfDebugDieBacktrace( "Confused about how this works." ); - if (is_array($wgPartialMessageCache)) { - $conditions['page_title']=$wgPartialMessageCache; - } else { - require_once("MessageCacheHints.php"); - $conditions['page_title']=MessageCacheHints::get(); - } + if ( !$dbr ) { + wfDebugDieBacktrace( 'Invalid database object' ); } - $res = $dbr->select( array( 'page', 'text' ), + $conditions = array( 'page_is_redirect' => 0, + 'page_namespace' => NS_MEDIAWIKI); + $res = $dbr->select( array( 'page', 'revision', 'text' ), array( 'page_title', 'old_text', 'old_flags' ), - 'page_is_redirect=0 AND page_namespace = '.NS_MEDIAWIKI.' AND page_latest = old_id', + 'page_is_redirect=0 AND page_namespace='.NS_MEDIAWIKI.' AND page_latest=rev_id AND rev_text_id=old_id', $fname ); @@ -155,6 +303,22 @@ class MessageCache $this->mCache[$row->page_title] = Revision::getRevisionText( $row ); } + # Negative caching + # Go through the language array and the extension array and make a note of + # any keys missing from the cache + foreach ( $wgAllMessagesEn as $key => $value ) { + $uckey = $wgLang->ucfirst( $key ); + if ( !array_key_exists( $uckey, $this->mCache ) ) { + $this->mCache[$uckey] = false; + } + } + foreach ( $this->mExtensionMessages as $key => $value ) { + $uckey = $wgLang->ucfirst( $key ); + if ( !array_key_exists( $uckey, $this->mCache ) ) { + $this->mCache[$uckey] = false; + } + } + $dbr->freeResult( $res ); } @@ -174,18 +338,35 @@ class MessageCache } /** - * Obsolete + * @deprecated */ function isCacheable( $key ) { return true; } function replace( $title, $text ) { + global $wgLocalMessageCache, $wgLocalMessageCacheSerialized, $parserMemc, $wgDBname; + $this->lock(); $this->load(); + $parserMemc->delete("$wgDBname:sidebar"); if ( is_array( $this->mCache ) ) { $this->mCache[$title] = $text; $this->mMemc->set( $this->mMemcKey, $this->mCache, $this->mExpiry ); + + # Save to local cache + if ( $wgLocalMessageCache !== false ) { + $serialized = serialize( $this->mCache ); + $hash = md5( $serialized ); + $this->mMemc->set( "{$this->mMemcKey}-hash", $hash, $this->mExpiry ); + if ($wgLocalMessageCacheSerialized) { + $this->saveToLocal( $serialized,$hash ); + } else { + $this->saveToScript( $this->mCache, $hash ); + } + } + + } $this->unlock(); } @@ -229,10 +410,10 @@ class MessageCache } # If uninitialised, someone is trying to call this halfway through Setup.php if( !$this->mInitialised ) { - return "<$key>"; + return '<' . htmlspecialchars($key) . '>'; } # If cache initialization was deferred, start it now. - if( $this->mDeferred ) { + if( $this->mDeferred && !$this->mDisable && $useDB ) { $this->load(); } @@ -242,62 +423,43 @@ class MessageCache if(!$isfullkey && ($langcode != $wgContLanguageCode) ) { $title .= '/' . $langcode; } - - # Try the cache - if( $this->mUseCache && is_array( $this->mCache ) && array_key_exists( $title, $this->mCache ) ) { - $message = $this->mCache[$title]; - } - - if ( !$message && $this->mUseCache ) { - $message = $this->mMemc->get( $this->mMemcKey . ':' . $title ); - if( $message ) { - $this->mCache[$title] = $message; - } - } - - # If it wasn't in the cache, load each message from the DB individually - if ( !$message ) { - $dbr =& wfGetDB( DB_SLAVE ); - $result = $dbr->selectRow( array( 'page', 'text' ), - array( 'old_flags', 'old_text' ), - 'page_namespace=' . NS_MEDIAWIKI . - ' AND page_title=' . $dbr->addQuotes( $title ) . - ' AND page_latest=old_id', - 'MessageCache::get' ); - if ( $result ) { - $message = Revision::getRevisionText( $result ); - if ($this->mUseCache) { - $this->mCache[$title]=$message; - /* individual messages may be often - recached until proper purge code exists - */ - $this->mMemc->set( $this->mMemcKey . ':' . $title, $message, 300 ); - } - } - } + $message = $this->getFromCache( $title ); } # Try the extension array - if( !$message ) { - $message = @$this->mExtensionMessages[$key]; + if( $message === false && array_key_exists( $key, $this->mExtensionMessages ) ) { + $message = $this->mExtensionMessages[$key]; } # Try the array in the language object - if( !$message ) { + if( $message === false ) { wfSuppressWarnings(); $message = $lang->getMessage( $key ); wfRestoreWarnings(); + if ( is_null( $message ) ) { + $message = false; + } } # Try the English array - if( !$message && $langcode != 'en' ) { + if( $message === false && $langcode != 'en' ) { wfSuppressWarnings(); $message = Language::getMessage( $key ); wfRestoreWarnings(); + if ( is_null( $message ) ) { + $message = false; + } + } + + # Is this a custom message? Try the default language in the db... + if( $message === false && + !$this->mDisable && $useDB && + !$isfullkey && ($langcode != $wgContLanguageCode) ) { + $message = $this->getFromCache( $lang->ucfirst( $key ) ); } # Final fallback - if( !$message ) { - $message = "<$key>"; + if( $message === false ) { + return '<' . htmlspecialchars($key) . '>'; } # Replace brace tags @@ -305,6 +467,50 @@ class MessageCache return $message; } + function getFromCache( $title ) { + $message = false; + + # Try the cache + if( $this->mUseCache && is_array( $this->mCache ) && array_key_exists( $title, $this->mCache ) ) { + return $this->mCache[$title]; + } + + # Try individual message cache + if ( $this->mUseCache ) { + $message = $this->mMemc->get( $this->mMemcKey . ':' . $title ); + if ( $message == '###NONEXISTENT###' ) { + return false; + } elseif( !is_null( $message ) ) { + $this->mCache[$title] = $message; + return $message; + } else { + $message = false; + } + } + + # Call message Hooks, in case they are defined + wfRunHooks('MessagesPreLoad',array($title,&$message)); + + # If it wasn't in the cache, load each message from the DB individually + $revision = Revision::newFromTitle( Title::makeTitle( NS_MEDIAWIKI, $title ) ); + if( $revision ) { + $message = $revision->getText(); + if ($this->mUseCache) { + $this->mCache[$title]=$message; + /* individual messages may be often + recached until proper purge code exists + */ + $this->mMemc->set( $this->mMemcKey . ':' . $title, $message, 300 ); + } + } else { + # Negative caching + # Use some special text instead of false, because false gets converted to '' somewhere + $this->mMemc->set( $this->mMemcKey . ':' . $title, '###NONEXISTENT###', $this->mExpiry ); + } + + return $message; + } + function transform( $message ) { if( !$this->mDisableTransform ) { if( strpos( $message, '{{' ) !== false ) { @@ -318,14 +524,27 @@ class MessageCache function enable() { $this->mDisable = false; } function disableTransform() { $this->mDisableTransform = true; } function enableTransform() { $this->mDisableTransform = false; } + function setTransform( $x ) { $this->mDisableTransform = $x; } + function getTransform() { return $this->mDisableTransform; } + /** + * Add a message to the cache + * + * @param mixed $key + * @param mixed $value + */ function addMessage( $key, $value ) { $this->mExtensionMessages[$key] = $value; } + /** + * Add an associative array of message to the cache + * + * @param array $messages An associative array of key => values to be added + */ function addMessages( $messages ) { foreach ( $messages as $key => $value ) { - $this->mExtensionMessages[$key] = $value; + $this->addMessage( $key, $value ); } }