MediaWiki
REL1_24
|
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 }