MediaWiki
REL1_23
|
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 try { 00246 // Not using TRUNCATE, because that needs extra permissions, 00247 // which maybe not granted to the database user. 00248 $dbw = wfGetDB( DB_MASTER ); 00249 $dbw->delete( 'msg_resource', '*', __METHOD__ ); 00250 $dbw->delete( 'msg_resource_links', '*', __METHOD__ ); 00251 } catch ( Exception $e ) { 00252 wfDebug( __METHOD__ . " failed to update DB: $e\n" ); 00253 } 00254 } 00255 00263 private static function getUpdatesForMessage( $key, $prevUpdates = null ) { 00264 $dbw = wfGetDB( DB_MASTER ); 00265 00266 if ( is_null( $prevUpdates ) ) { 00267 // Fetch all blobs referencing $key 00268 $res = $dbw->select( 00269 array( 'msg_resource', 'msg_resource_links' ), 00270 array( 'mr_resource', 'mr_lang', 'mr_blob', 'mr_timestamp' ), 00271 array( 'mrl_message' => $key, 'mr_resource=mrl_resource' ), 00272 __METHOD__ 00273 ); 00274 } else { 00275 // Refetch the blobs referenced by $prevUpdates 00276 00277 // Reorganize the (resource, lang) pairs in the format 00278 // expected by makeWhereFrom2d() 00279 $twoD = array(); 00280 00281 foreach ( $prevUpdates as $update ) { 00282 $twoD[$update['resource']][$update['lang']] = true; 00283 } 00284 00285 $res = $dbw->select( 'msg_resource', 00286 array( 'mr_resource', 'mr_lang', 'mr_blob', 'mr_timestamp' ), 00287 $dbw->makeWhereFrom2d( $twoD, 'mr_resource', 'mr_lang' ), 00288 __METHOD__ 00289 ); 00290 } 00291 00292 // Build the new updates queue 00293 $updates = array(); 00294 00295 foreach ( $res as $row ) { 00296 $updates[] = array( 00297 'resource' => $row->mr_resource, 00298 'lang' => $row->mr_lang, 00299 'timestamp' => $row->mr_timestamp, 00300 'newBlob' => self::reencodeBlob( $row->mr_blob, $key, $row->mr_lang ) 00301 ); 00302 } 00303 00304 return $updates; 00305 } 00306 00315 private static function reencodeBlob( $blob, $key, $lang ) { 00316 $decoded = FormatJson::decode( $blob, true ); 00317 $decoded[$key] = wfMessage( $key )->inLanguage( $lang )->plain(); 00318 00319 return FormatJson::encode( (object)$decoded ); 00320 } 00321 00332 private static function getFromDB( ResourceLoader $resourceLoader, $modules, $lang ) { 00333 global $wgCacheEpoch; 00334 $retval = array(); 00335 $dbr = wfGetDB( DB_SLAVE ); 00336 $res = $dbr->select( 'msg_resource', 00337 array( 'mr_blob', 'mr_resource', 'mr_timestamp' ), 00338 array( 'mr_resource' => $modules, 'mr_lang' => $lang ), 00339 __METHOD__ 00340 ); 00341 00342 foreach ( $res as $row ) { 00343 $module = $resourceLoader->getModule( $row->mr_resource ); 00344 if ( !$module ) { 00345 // This shouldn't be possible 00346 throw new MWException( __METHOD__ . ' passed an invalid module name' ); 00347 } 00348 // Update the module's blobs if the set of messages changed or if the blob is 00349 // older than $wgCacheEpoch 00350 if ( array_keys( FormatJson::decode( $row->mr_blob, true ) ) !== array_values( array_unique( $module->getMessages() ) ) || 00351 wfTimestamp( TS_MW, $row->mr_timestamp ) <= $wgCacheEpoch ) { 00352 $retval[$row->mr_resource] = self::updateModule( $row->mr_resource, $module, $lang ); 00353 } else { 00354 $retval[$row->mr_resource] = $row->mr_blob; 00355 } 00356 } 00357 00358 return $retval; 00359 } 00360 00368 private static function generateMessageBlob( ResourceLoaderModule $module, $lang ) { 00369 $messages = array(); 00370 00371 foreach ( $module->getMessages() as $key ) { 00372 $messages[$key] = wfMessage( $key )->inLanguage( $lang )->plain(); 00373 } 00374 00375 return FormatJson::encode( (object)$messages ); 00376 } 00377 }