MediaWiki  REL1_20
MessageBlobStore.php
Go to the documentation of this file.
00001 <?php
00034 class MessageBlobStore {
00035 
00044         public static function get( ResourceLoader $resourceLoader, $modules, $lang ) {
00045                 wfProfileIn( __METHOD__ );
00046                 if ( !count( $modules ) ) {
00047                         wfProfileOut( __METHOD__ );
00048                         return array();
00049                 }
00050                 // Try getting from the DB first
00051                 $blobs = self::getFromDB( $resourceLoader, array_keys( $modules ), $lang );
00052 
00053                 // Generate blobs for any missing modules and store them in the DB
00054                 $missing = array_diff( array_keys( $modules ), array_keys( $blobs ) );
00055                 foreach ( $missing as $name ) {
00056                         $blob = self::insertMessageBlob( $name, $modules[$name], $lang );
00057                         if ( $blob ) {
00058                                 $blobs[$name] = $blob;
00059                         }
00060                 }
00061 
00062                 wfProfileOut( __METHOD__ );
00063                 return $blobs;
00064         }
00065 
00076         public static function insertMessageBlob( $name, ResourceLoaderModule $module, $lang ) {
00077                 $blob = self::generateMessageBlob( $module, $lang );
00078 
00079                 if ( !$blob ) {
00080                         return false;
00081                 }
00082 
00083                 try {
00084                         $dbw = wfGetDB( DB_MASTER );
00085                         $success = $dbw->insert( 'msg_resource', array(
00086                                         'mr_lang' => $lang,
00087                                         'mr_resource' => $name,
00088                                         'mr_blob' => $blob,
00089                                         'mr_timestamp' => $dbw->timestamp()
00090                                 ),
00091                                 __METHOD__,
00092                                 array( 'IGNORE' )
00093                         );
00094 
00095                         if ( $success ) {
00096                                 if ( $dbw->affectedRows() == 0 ) {
00097                                         // Blob was already present, fetch it
00098                                         $blob = $dbw->selectField( 'msg_resource', 'mr_blob', array(
00099                                                         'mr_resource' => $name,
00100                                                         'mr_lang' => $lang,
00101                                                 ),
00102                                                 __METHOD__
00103                                         );
00104                                 } else {
00105                                         // Update msg_resource_links
00106                                         $rows = array();
00107 
00108                                         foreach ( $module->getMessages() as $key ) {
00109                                                 $rows[] = array(
00110                                                         'mrl_resource' => $name,
00111                                                         'mrl_message' => $key
00112                                                 );
00113                                         }
00114                                         $dbw->insert( 'msg_resource_links', $rows,
00115                                                 __METHOD__, array( 'IGNORE' )
00116                                         );
00117                                 }
00118                         }
00119                 } catch ( Exception $e ) {
00120                         wfDebug( __METHOD__ . " failed to update DB: $e\n" );
00121                 }
00122                 return $blob;
00123         }
00124 
00133         public static function updateModule( $name, ResourceLoaderModule $module, $lang ) {
00134                 $dbw = wfGetDB( DB_MASTER );
00135                 $row = $dbw->selectRow( 'msg_resource', 'mr_blob',
00136                         array( 'mr_resource' => $name, 'mr_lang' => $lang ),
00137                         __METHOD__
00138                 );
00139                 if ( !$row ) {
00140                         return null;
00141                 }
00142 
00143                 // Save the old and new blobs for later
00144                 $oldBlob = $row->mr_blob;
00145                 $newBlob = self::generateMessageBlob( $module, $lang );
00146 
00147                 try {
00148                         $newRow = array(
00149                                 'mr_resource' => $name,
00150                                 'mr_lang' => $lang,
00151                                 'mr_blob' => $newBlob,
00152                                 'mr_timestamp' => $dbw->timestamp()
00153                         );
00154 
00155                         $dbw->replace( 'msg_resource',
00156                                 array( array( 'mr_resource', 'mr_lang' ) ),
00157                                 $newRow, __METHOD__
00158                         );
00159 
00160                         // Figure out which messages were added and removed
00161                         $oldMessages = array_keys( FormatJson::decode( $oldBlob, true ) );
00162                         $newMessages = array_keys( FormatJson::decode( $newBlob, true ) );
00163                         $added = array_diff( $newMessages, $oldMessages );
00164                         $removed = array_diff( $oldMessages, $newMessages );
00165 
00166                         // Delete removed messages, insert added ones
00167                         if ( $removed ) {
00168                                 $dbw->delete( 'msg_resource_links', array(
00169                                                 'mrl_resource' => $name,
00170                                                 'mrl_message' => $removed
00171                                         ), __METHOD__
00172                                 );
00173                         }
00174 
00175                         $newLinksRows = array();
00176 
00177                         foreach ( $added as $message ) {
00178                                 $newLinksRows[] = array(
00179                                         'mrl_resource' => $name,
00180                                         'mrl_message' => $message
00181                                 );
00182                         }
00183 
00184                         if ( $newLinksRows ) {
00185                                 $dbw->insert( 'msg_resource_links', $newLinksRows, __METHOD__,
00186                                         array( 'IGNORE' ) // just in case
00187                                 );
00188                         }
00189                 } catch ( Exception $e ) {
00190                         wfDebug( __METHOD__ . " failed to update DB: $e\n" );
00191                 }
00192                 return $newBlob;
00193         }
00194 
00200         public static function updateMessage( $key ) {
00201                 try {
00202                         $dbw = wfGetDB( DB_MASTER );
00203 
00204                         // Keep running until the updates queue is empty.
00205                         // Due to update conflicts, the queue might not be emptied
00206                         // in one iteration.
00207                         $updates = null;
00208                         do {
00209                                 $updates = self::getUpdatesForMessage( $key, $updates );
00210 
00211                                 foreach ( $updates as $k => $update ) {
00212                                         // Update the row on the condition that it
00213                                         // didn't change since we fetched it by putting
00214                                         // the timestamp in the WHERE clause.
00215                                         $success = $dbw->update( 'msg_resource',
00216                                                 array(
00217                                                         'mr_blob' => $update['newBlob'],
00218                                                         'mr_timestamp' => $dbw->timestamp() ),
00219                                                 array(
00220                                                         'mr_resource' => $update['resource'],
00221                                                         'mr_lang' => $update['lang'],
00222                                                         'mr_timestamp' => $update['timestamp'] ),
00223                                                 __METHOD__
00224                                         );
00225 
00226                                         // Only requeue conflicted updates.
00227                                         // If update() returned false, don't retry, for
00228                                         // fear of getting into an infinite loop
00229                                         if ( !( $success && $dbw->affectedRows() == 0 ) ) {
00230                                                 // Not conflicted
00231                                                 unset( $updates[$k] );
00232                                         }
00233                                 }
00234                         } while ( count( $updates ) );
00235 
00236                         // No need to update msg_resource_links because we didn't add
00237                         // or remove any messages, we just changed their contents.
00238                 } catch ( Exception $e ) {
00239                         wfDebug( __METHOD__ . " failed to update DB: $e\n" );
00240                 }
00241         }
00242 
00243         public static function clear() {
00244                 // TODO: Give this some more thought
00245                 // TODO: Is TRUNCATE better?
00246                 try {
00247                         $dbw = wfGetDB( DB_MASTER );
00248                         $dbw->delete( 'msg_resource', '*', __METHOD__ );
00249                         $dbw->delete( 'msg_resource_links', '*', __METHOD__ );
00250                 } catch ( Exception $e ) {
00251                         wfDebug( __METHOD__ . " failed to update DB: $e\n" );
00252                 }
00253         }
00254 
00262         private static function getUpdatesForMessage( $key, $prevUpdates = null ) {
00263                 $dbw = wfGetDB( DB_MASTER );
00264 
00265                 if ( is_null( $prevUpdates ) ) {
00266                         // Fetch all blobs referencing $key
00267                         $res = $dbw->select(
00268                                 array( 'msg_resource', 'msg_resource_links' ),
00269                                 array( 'mr_resource', 'mr_lang', 'mr_blob', 'mr_timestamp' ),
00270                                 array( 'mrl_message' => $key, 'mr_resource=mrl_resource' ),
00271                                 __METHOD__
00272                         );
00273                 } else {
00274                         // Refetch the blobs referenced by $prevUpdates
00275 
00276                         // Reorganize the (resource, lang) pairs in the format
00277                         // expected by makeWhereFrom2d()
00278                         $twoD = array();
00279 
00280                         foreach ( $prevUpdates as $update ) {
00281                                 $twoD[$update['resource']][$update['lang']] = true;
00282                         }
00283 
00284                         $res = $dbw->select( 'msg_resource',
00285                                 array( 'mr_resource', 'mr_lang', 'mr_blob', 'mr_timestamp' ),
00286                                 $dbw->makeWhereFrom2d( $twoD, 'mr_resource', 'mr_lang' ),
00287                                 __METHOD__
00288                         );
00289                 }
00290 
00291                 // Build the new updates queue
00292                 $updates = array();
00293 
00294                 foreach ( $res as $row ) {
00295                         $updates[] = array(
00296                                 'resource' => $row->mr_resource,
00297                                 'lang' => $row->mr_lang,
00298                                 'timestamp' => $row->mr_timestamp,
00299                                 'newBlob' => self::reencodeBlob( $row->mr_blob, $key, $row->mr_lang )
00300                         );
00301                 }
00302 
00303                 return $updates;
00304         }
00305 
00314         private static function reencodeBlob( $blob, $key, $lang ) {
00315                 $decoded = FormatJson::decode( $blob, true );
00316                 $decoded[$key] = wfMessage( $key )->inLanguage( $lang )->plain();
00317 
00318                 return FormatJson::encode( (object)$decoded );
00319         }
00320 
00330         private static function getFromDB( ResourceLoader $resourceLoader, $modules, $lang ) {
00331                 global $wgCacheEpoch;
00332                 $retval = array();
00333                 $dbr = wfGetDB( DB_SLAVE );
00334                 $res = $dbr->select( 'msg_resource',
00335                         array( 'mr_blob', 'mr_resource', 'mr_timestamp' ),
00336                         array( 'mr_resource' => $modules, 'mr_lang' => $lang ),
00337                         __METHOD__
00338                 );
00339 
00340                 foreach ( $res as $row ) {
00341                         $module = $resourceLoader->getModule( $row->mr_resource );
00342                         if ( !$module ) {
00343                                 // This shouldn't be possible
00344                                 throw new MWException( __METHOD__ . ' passed an invalid module name' );
00345                         }
00346                         // Update the module's blobs if the set of messages changed or if the blob is
00347                         // older than $wgCacheEpoch
00348                         if ( array_keys( FormatJson::decode( $row->mr_blob, true ) ) !== array_values( array_unique( $module->getMessages() ) ) ||
00349                                         wfTimestamp( TS_MW, $row->mr_timestamp ) <= $wgCacheEpoch ) {
00350                                 $retval[$row->mr_resource] = self::updateModule( $row->mr_resource, $module, $lang );
00351                         } else {
00352                                 $retval[$row->mr_resource] = $row->mr_blob;
00353                         }
00354                 }
00355 
00356                 return $retval;
00357         }
00358 
00366         private static function generateMessageBlob( ResourceLoaderModule $module, $lang ) {
00367                 $messages = array();
00368 
00369                 foreach ( $module->getMessages() as $key ) {
00370                         $messages[$key] = wfMessage( $key )->inLanguage( $lang )->plain();
00371                 }
00372 
00373                 return FormatJson::encode( (object)$messages );
00374         }
00375 }