MediaWiki  REL1_22
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 
00331     private static function getFromDB( ResourceLoader $resourceLoader, $modules, $lang ) {
00332         global $wgCacheEpoch;
00333         $retval = array();
00334         $dbr = wfGetDB( DB_SLAVE );
00335         $res = $dbr->select( 'msg_resource',
00336             array( 'mr_blob', 'mr_resource', 'mr_timestamp' ),
00337             array( 'mr_resource' => $modules, 'mr_lang' => $lang ),
00338             __METHOD__
00339         );
00340 
00341         foreach ( $res as $row ) {
00342             $module = $resourceLoader->getModule( $row->mr_resource );
00343             if ( !$module ) {
00344                 // This shouldn't be possible
00345                 throw new MWException( __METHOD__ . ' passed an invalid module name' );
00346             }
00347             // Update the module's blobs if the set of messages changed or if the blob is
00348             // older than $wgCacheEpoch
00349             if ( array_keys( FormatJson::decode( $row->mr_blob, true ) ) !== array_values( array_unique( $module->getMessages() ) ) ||
00350                     wfTimestamp( TS_MW, $row->mr_timestamp ) <= $wgCacheEpoch ) {
00351                 $retval[$row->mr_resource] = self::updateModule( $row->mr_resource, $module, $lang );
00352             } else {
00353                 $retval[$row->mr_resource] = $row->mr_blob;
00354             }
00355         }
00356 
00357         return $retval;
00358     }
00359 
00367     private static function generateMessageBlob( ResourceLoaderModule $module, $lang ) {
00368         $messages = array();
00369 
00370         foreach ( $module->getMessages() as $key ) {
00371             $messages[$key] = wfMessage( $key )->inLanguage( $lang )->plain();
00372         }
00373 
00374         return FormatJson::encode( (object)$messages );
00375     }
00376 }