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