MediaWiki  REL1_24
MessageBlobStore.php
Go to the documentation of this file.
00001 <?php
00034 class MessageBlobStore {
00041     public static function getInstance() {
00042         static $instance = null;
00043         if ( $instance === null ) {
00044             $instance = new self;
00045         }
00046 
00047         return $instance;
00048     }
00049 
00058     public function get( ResourceLoader $resourceLoader, $modules, $lang ) {
00059         wfProfileIn( __METHOD__ );
00060         if ( !count( $modules ) ) {
00061             wfProfileOut( __METHOD__ );
00062             return array();
00063         }
00064         // Try getting from the DB first
00065         $blobs = $this->getFromDB( $resourceLoader, array_keys( $modules ), $lang );
00066 
00067         // Generate blobs for any missing modules and store them in the DB
00068         $missing = array_diff( array_keys( $modules ), array_keys( $blobs ) );
00069         foreach ( $missing as $name ) {
00070             $blob = $this->insertMessageBlob( $name, $modules[$name], $lang );
00071             if ( $blob ) {
00072                 $blobs[$name] = $blob;
00073             }
00074         }
00075 
00076         wfProfileOut( __METHOD__ );
00077         return $blobs;
00078     }
00079 
00090     public function insertMessageBlob( $name, ResourceLoaderModule $module, $lang ) {
00091         $blob = $this->generateMessageBlob( $module, $lang );
00092 
00093         if ( !$blob ) {
00094             return false;
00095         }
00096 
00097         try {
00098             $dbw = wfGetDB( DB_MASTER );
00099             $success = $dbw->insert( 'msg_resource', array(
00100                     'mr_lang' => $lang,
00101                     'mr_resource' => $name,
00102                     'mr_blob' => $blob,
00103                     'mr_timestamp' => $dbw->timestamp()
00104                 ),
00105                 __METHOD__,
00106                 array( 'IGNORE' )
00107             );
00108 
00109             if ( $success ) {
00110                 if ( $dbw->affectedRows() == 0 ) {
00111                     // Blob was already present, fetch it
00112                     $blob = $dbw->selectField( 'msg_resource', 'mr_blob', array(
00113                             'mr_resource' => $name,
00114                             'mr_lang' => $lang,
00115                         ),
00116                         __METHOD__
00117                     );
00118                 } else {
00119                     // Update msg_resource_links
00120                     $rows = array();
00121 
00122                     foreach ( $module->getMessages() as $key ) {
00123                         $rows[] = array(
00124                             'mrl_resource' => $name,
00125                             'mrl_message' => $key
00126                         );
00127                     }
00128                     $dbw->insert( 'msg_resource_links', $rows,
00129                         __METHOD__, array( 'IGNORE' )
00130                     );
00131                 }
00132             }
00133         } catch ( Exception $e ) {
00134             wfDebug( __METHOD__ . " failed to update DB: $e\n" );
00135         }
00136         return $blob;
00137     }
00138 
00148     public function updateModule( $name, ResourceLoaderModule $module, $lang ) {
00149         $dbw = wfGetDB( DB_MASTER );
00150         $row = $dbw->selectRow( 'msg_resource', 'mr_blob',
00151             array( 'mr_resource' => $name, 'mr_lang' => $lang ),
00152             __METHOD__
00153         );
00154         if ( !$row ) {
00155             return null;
00156         }
00157 
00158         // Save the old and new blobs for later
00159         $oldBlob = $row->mr_blob;
00160         $newBlob = $this->generateMessageBlob( $module, $lang );
00161 
00162         try {
00163             $newRow = array(
00164                 'mr_resource' => $name,
00165                 'mr_lang' => $lang,
00166                 'mr_blob' => $newBlob,
00167                 'mr_timestamp' => $dbw->timestamp()
00168             );
00169 
00170             $dbw->replace( 'msg_resource',
00171                 array( array( 'mr_resource', 'mr_lang' ) ),
00172                 $newRow, __METHOD__
00173             );
00174 
00175             // Figure out which messages were added and removed
00176             $oldMessages = array_keys( FormatJson::decode( $oldBlob, true ) );
00177             $newMessages = array_keys( FormatJson::decode( $newBlob, true ) );
00178             $added = array_diff( $newMessages, $oldMessages );
00179             $removed = array_diff( $oldMessages, $newMessages );
00180 
00181             // Delete removed messages, insert added ones
00182             if ( $removed ) {
00183                 $dbw->delete( 'msg_resource_links', array(
00184                         'mrl_resource' => $name,
00185                         'mrl_message' => $removed
00186                     ), __METHOD__
00187                 );
00188             }
00189 
00190             $newLinksRows = array();
00191 
00192             foreach ( $added as $message ) {
00193                 $newLinksRows[] = array(
00194                     'mrl_resource' => $name,
00195                     'mrl_message' => $message
00196                 );
00197             }
00198 
00199             if ( $newLinksRows ) {
00200                 $dbw->insert( 'msg_resource_links', $newLinksRows, __METHOD__,
00201                     array( 'IGNORE' ) // just in case
00202                 );
00203             }
00204         } catch ( Exception $e ) {
00205             wfDebug( __METHOD__ . " failed to update DB: $e\n" );
00206         }
00207         return $newBlob;
00208     }
00209 
00215     public function updateMessage( $key ) {
00216         try {
00217             $dbw = wfGetDB( DB_MASTER );
00218 
00219             // Keep running until the updates queue is empty.
00220             // Due to update conflicts, the queue might not be emptied
00221             // in one iteration.
00222             $updates = null;
00223             do {
00224                 $updates = $this->getUpdatesForMessage( $key, $updates );
00225 
00226                 foreach ( $updates as $k => $update ) {
00227                     // Update the row on the condition that it
00228                     // didn't change since we fetched it by putting
00229                     // the timestamp in the WHERE clause.
00230                     $success = $dbw->update( 'msg_resource',
00231                         array(
00232                             'mr_blob' => $update['newBlob'],
00233                             'mr_timestamp' => $dbw->timestamp() ),
00234                         array(
00235                             'mr_resource' => $update['resource'],
00236                             'mr_lang' => $update['lang'],
00237                             'mr_timestamp' => $update['timestamp'] ),
00238                         __METHOD__
00239                     );
00240 
00241                     // Only requeue conflicted updates.
00242                     // If update() returned false, don't retry, for
00243                     // fear of getting into an infinite loop
00244                     if ( !( $success && $dbw->affectedRows() == 0 ) ) {
00245                         // Not conflicted
00246                         unset( $updates[$k] );
00247                     }
00248                 }
00249             } while ( count( $updates ) );
00250 
00251             // No need to update msg_resource_links because we didn't add
00252             // or remove any messages, we just changed their contents.
00253         } catch ( Exception $e ) {
00254             wfDebug( __METHOD__ . " failed to update DB: $e\n" );
00255         }
00256     }
00257 
00258     public function clear() {
00259         // TODO: Give this some more thought
00260         try {
00261             // Not using TRUNCATE, because that needs extra permissions,
00262             // which maybe not granted to the database user.
00263             $dbw = wfGetDB( DB_MASTER );
00264             $dbw->delete( 'msg_resource', '*', __METHOD__ );
00265             $dbw->delete( 'msg_resource_links', '*', __METHOD__ );
00266         } catch ( Exception $e ) {
00267             wfDebug( __METHOD__ . " failed to update DB: $e\n" );
00268         }
00269     }
00270 
00278     private function getUpdatesForMessage( $key, $prevUpdates = null ) {
00279         $dbw = wfGetDB( DB_MASTER );
00280 
00281         if ( is_null( $prevUpdates ) ) {
00282             // Fetch all blobs referencing $key
00283             $res = $dbw->select(
00284                 array( 'msg_resource', 'msg_resource_links' ),
00285                 array( 'mr_resource', 'mr_lang', 'mr_blob', 'mr_timestamp' ),
00286                 array( 'mrl_message' => $key, 'mr_resource=mrl_resource' ),
00287                 __METHOD__
00288             );
00289         } else {
00290             // Refetch the blobs referenced by $prevUpdates
00291 
00292             // Reorganize the (resource, lang) pairs in the format
00293             // expected by makeWhereFrom2d()
00294             $twoD = array();
00295 
00296             foreach ( $prevUpdates as $update ) {
00297                 $twoD[$update['resource']][$update['lang']] = true;
00298             }
00299 
00300             $res = $dbw->select( 'msg_resource',
00301                 array( 'mr_resource', 'mr_lang', 'mr_blob', 'mr_timestamp' ),
00302                 $dbw->makeWhereFrom2d( $twoD, 'mr_resource', 'mr_lang' ),
00303                 __METHOD__
00304             );
00305         }
00306 
00307         // Build the new updates queue
00308         $updates = array();
00309 
00310         foreach ( $res as $row ) {
00311             $updates[] = array(
00312                 'resource' => $row->mr_resource,
00313                 'lang' => $row->mr_lang,
00314                 'timestamp' => $row->mr_timestamp,
00315                 'newBlob' => $this->reencodeBlob( $row->mr_blob, $key, $row->mr_lang )
00316             );
00317         }
00318 
00319         return $updates;
00320     }
00321 
00330     private function reencodeBlob( $blob, $key, $lang ) {
00331         $decoded = FormatJson::decode( $blob, true );
00332         $decoded[$key] = wfMessage( $key )->inLanguage( $lang )->plain();
00333 
00334         return FormatJson::encode( (object)$decoded );
00335     }
00336 
00347     private function getFromDB( ResourceLoader $resourceLoader, $modules, $lang ) {
00348         $config = $resourceLoader->getConfig();
00349         $retval = array();
00350         $dbr = wfGetDB( DB_SLAVE );
00351         $res = $dbr->select( 'msg_resource',
00352             array( 'mr_blob', 'mr_resource', 'mr_timestamp' ),
00353             array( 'mr_resource' => $modules, 'mr_lang' => $lang ),
00354             __METHOD__
00355         );
00356 
00357         foreach ( $res as $row ) {
00358             $module = $resourceLoader->getModule( $row->mr_resource );
00359             if ( !$module ) {
00360                 // This shouldn't be possible
00361                 throw new MWException( __METHOD__ . ' passed an invalid module name' );
00362             }
00363 
00364             // Update the module's blobs if the set of messages changed or if the blob is
00365             // older than the CacheEpoch setting
00366             $keys = array_keys( FormatJson::decode( $row->mr_blob, true ) );
00367             $values = array_values( array_unique( $module->getMessages() ) );
00368             if ( $keys !== $values
00369                 || wfTimestamp( TS_MW, $row->mr_timestamp ) <= $config->get( 'CacheEpoch' )
00370             ) {
00371                 $retval[$row->mr_resource] = $this->updateModule( $row->mr_resource, $module, $lang );
00372             } else {
00373                 $retval[$row->mr_resource] = $row->mr_blob;
00374             }
00375         }
00376 
00377         return $retval;
00378     }
00379 
00387     private function generateMessageBlob( ResourceLoaderModule $module, $lang ) {
00388         $messages = array();
00389 
00390         foreach ( $module->getMessages() as $key ) {
00391             $messages[$key] = wfMessage( $key )->inLanguage( $lang )->plain();
00392         }
00393 
00394         return FormatJson::encode( (object)$messages );
00395     }
00396 }