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