00001 <?php
00002
00008 interface HistoryBlob
00009 {
00016 public function addItem( $text );
00017
00021 public function getItem( $key );
00022
00031 public function setText( $text );
00032
00036 function getText();
00037 }
00038
00043 class ConcatenatedGzipHistoryBlob implements HistoryBlob
00044 {
00045 public $mVersion = 0, $mCompressed = false, $mItems = array(), $mDefaultHash = '';
00046 public $mSize = 0;
00047 public $mMaxSize = 10000000;
00048 public $mMaxCount = 100;
00049
00051 public function ConcatenatedGzipHistoryBlob() {
00052 if ( !function_exists( 'gzdeflate' ) ) {
00053 throw new MWException( "Need zlib support to read or write this kind of history object (ConcatenatedGzipHistoryBlob)\n" );
00054 }
00055 }
00056
00057 public function addItem( $text ) {
00058 $this->uncompress();
00059 $hash = md5( $text );
00060 if ( !isset( $this->mItems[$hash] ) ) {
00061 $this->mItems[$hash] = $text;
00062 $this->mSize += strlen( $text );
00063 }
00064 return $hash;
00065 }
00066
00067 public function getItem( $hash ) {
00068 $this->uncompress();
00069 if ( array_key_exists( $hash, $this->mItems ) ) {
00070 return $this->mItems[$hash];
00071 } else {
00072 return false;
00073 }
00074 }
00075
00076 public function setText( $text ) {
00077 $this->uncompress();
00078 $this->mDefaultHash = $this->addItem( $text );
00079 }
00080
00081 public function getText() {
00082 $this->uncompress();
00083 return $this->getItem( $this->mDefaultHash );
00084 }
00085
00089 public function removeItem( $hash ) {
00090 $this->mSize -= strlen( $this->mItems[$hash] );
00091 unset( $this->mItems[$hash] );
00092 }
00093
00097 public function compress() {
00098 if ( !$this->mCompressed ) {
00099 $this->mItems = gzdeflate( serialize( $this->mItems ) );
00100 $this->mCompressed = true;
00101 }
00102 }
00103
00107 public function uncompress() {
00108 if ( $this->mCompressed ) {
00109 $this->mItems = unserialize( gzinflate( $this->mItems ) );
00110 $this->mCompressed = false;
00111 }
00112 }
00113
00114
00115 function __sleep() {
00116 $this->compress();
00117 return array( 'mVersion', 'mCompressed', 'mItems', 'mDefaultHash' );
00118 }
00119
00120 function __wakeup() {
00121 $this->uncompress();
00122 }
00123
00128 public function isHappy() {
00129 return $this->mSize < $this->mMaxSize
00130 && count( $this->mItems ) < $this->mMaxCount;
00131 }
00132 }
00133
00134
00141 global $wgBlobCache;
00142 $wgBlobCache = array();
00143
00144
00148 class HistoryBlobStub {
00149 var $mOldId, $mHash, $mRef;
00150
00155 function HistoryBlobStub( $hash = '', $oldid = 0 ) {
00156 $this->mHash = $hash;
00157 }
00158
00163 function setLocation( $id ) {
00164 $this->mOldId = $id;
00165 }
00166
00170 function setReferrer( $id ) {
00171 $this->mRef = $id;
00172 }
00173
00177 function getReferrer() {
00178 return $this->mRef;
00179 }
00180
00181 function getText() {
00182 $fname = 'HistoryBlobStub::getText';
00183 global $wgBlobCache;
00184 if( isset( $wgBlobCache[$this->mOldId] ) ) {
00185 $obj = $wgBlobCache[$this->mOldId];
00186 } else {
00187 $dbr = wfGetDB( DB_SLAVE );
00188 $row = $dbr->selectRow( 'text', array( 'old_flags', 'old_text' ), array( 'old_id' => $this->mOldId ) );
00189 if( !$row ) {
00190 return false;
00191 }
00192 $flags = explode( ',', $row->old_flags );
00193 if( in_array( 'external', $flags ) ) {
00194 $url=$row->old_text;
00195 @list( ,$path)=explode('://',$url,2);
00196 if ($path=="") {
00197 wfProfileOut( $fname );
00198 return false;
00199 }
00200 $row->old_text=ExternalStore::fetchFromUrl($url);
00201
00202 }
00203 if( !in_array( 'object', $flags ) ) {
00204 return false;
00205 }
00206
00207 if( in_array( 'gzip', $flags ) ) {
00208
00209
00210 $obj = unserialize( gzinflate( $row->old_text ) );
00211 } else {
00212 $obj = unserialize( $row->old_text );
00213 }
00214
00215 if( !is_object( $obj ) ) {
00216
00217 $obj = unserialize( $obj );
00218 }
00219
00220
00221
00222 $obj->uncompress();
00223 $wgBlobCache = array( $this->mOldId => $obj );
00224 }
00225 return $obj->getItem( $this->mHash );
00226 }
00227
00231 function getHash() {
00232 return $this->mHash;
00233 }
00234 }
00235
00236
00245 class HistoryBlobCurStub {
00246 var $mCurId;
00247
00251 function HistoryBlobCurStub( $curid = 0 ) {
00252 $this->mCurId = $curid;
00253 }
00254
00259 function setLocation( $id ) {
00260 $this->mCurId = $id;
00261 }
00262
00263 function getText() {
00264 $dbr = wfGetDB( DB_SLAVE );
00265 $row = $dbr->selectRow( 'cur', array( 'cur_text' ), array( 'cur_id' => $this->mCurId ) );
00266 if( !$row ) {
00267 return false;
00268 }
00269 return $row->cur_text;
00270 }
00271 }
00272
00277 class DiffHistoryBlob implements HistoryBlob {
00279 var $mItems = array();
00280
00282 var $mSize = 0;
00283
00292 var $mDiffs;
00293
00295 var $mDiffMap;
00296
00300 var $mDefaultKey;
00301
00305 var $mCompressed;
00306
00310 var $mFrozen = false;
00311
00316 var $mMaxSize = 10000000;
00317
00321 var $mMaxCount = 100;
00322
00324 const XDL_BDOP_INS = 1;
00325 const XDL_BDOP_CPY = 2;
00326 const XDL_BDOP_INSB = 3;
00327
00328 function __construct() {
00329 if ( !function_exists( 'gzdeflate' ) ) {
00330 throw new MWException( "Need zlib support to read or write DiffHistoryBlob\n" );
00331 }
00332 }
00333
00334 function addItem( $text ) {
00335 if ( $this->mFrozen ) {
00336 throw new MWException( __METHOD__.": Cannot add more items after sleep/wakeup" );
00337 }
00338
00339 $this->mItems[] = $text;
00340 $this->mSize += strlen( $text );
00341 $this->mDiffs = null;
00342 return count( $this->mItems ) - 1;
00343 }
00344
00345 function getItem( $key ) {
00346 return $this->mItems[$key];
00347 }
00348
00349 function setText( $text ) {
00350 $this->mDefaultKey = $this->addItem( $text );
00351 }
00352
00353 function getText() {
00354 return $this->getItem( $this->mDefaultKey );
00355 }
00356
00357 function compress() {
00358 if ( !function_exists( 'xdiff_string_rabdiff' ) ){
00359 throw new MWException( "Need xdiff 1.5+ support to write DiffHistoryBlob\n" );
00360 }
00361 if ( isset( $this->mDiffs ) ) {
00362
00363 return;
00364 }
00365 if ( !count( $this->mItems ) ) {
00366
00367 return;
00368 }
00369
00370
00371 $sequences = array(
00372 'small' => array(
00373 'tail' => '',
00374 'diffs' => array(),
00375 'map' => array(),
00376 ),
00377 'main' => array(
00378 'tail' => '',
00379 'diffs' => array(),
00380 'map' => array(),
00381 ),
00382 );
00383 $smallFactor = 0.5;
00384
00385 for ( $i = 0; $i < count( $this->mItems ); $i++ ) {
00386 $text = $this->mItems[$i];
00387 if ( $i == 0 ) {
00388 $seqName = 'main';
00389 } else {
00390 $mainTail = $sequences['main']['tail'];
00391 if ( strlen( $text ) < strlen( $mainTail ) * $smallFactor ) {
00392 $seqName = 'small';
00393 } else {
00394 $seqName = 'main';
00395 }
00396 }
00397 $seq =& $sequences[$seqName];
00398 $tail = $seq['tail'];
00399 $diff = $this->diff( $tail, $text );
00400 $seq['diffs'][] = $diff;
00401 $seq['map'][] = $i;
00402 $seq['tail'] = $text;
00403 }
00404 unset( $seq );
00405
00406
00407 $tail = '';
00408 $this->mDiffs = array();
00409 $this->mDiffMap = array();
00410 foreach ( $sequences as $seq ) {
00411 if ( !count( $seq['diffs'] ) ) {
00412 continue;
00413 }
00414 if ( $tail === '' ) {
00415 $this->mDiffs[] = $seq['diffs'][0];
00416 } else {
00417 $head = $this->patch( '', $seq['diffs'][0] );
00418 $this->mDiffs[] = $this->diff( $tail, $head );
00419 }
00420 $this->mDiffMap[] = $seq['map'][0];
00421 for ( $i = 1; $i < count( $seq['diffs'] ); $i++ ) {
00422 $this->mDiffs[] = $seq['diffs'][$i];
00423 $this->mDiffMap[] = $seq['map'][$i];
00424 }
00425 $tail = $seq['tail'];
00426 }
00427 }
00428
00429 function diff( $t1, $t2 ) {
00430 # Need to do a null concatenation with warnings off, due to bugs in the current version of xdiff
00431 # "String is not zero-terminated"
00432 wfSuppressWarnings();
00433 $diff = xdiff_string_rabdiff( $t1, $t2 ) . '';
00434 wfRestoreWarnings();
00435 return $diff;
00436 }
00437
00438 function patch( $base, $diff ) {
00439 if ( function_exists( 'xdiff_string_bpatch' ) ) {
00440 wfSuppressWarnings();
00441 $text = xdiff_string_bpatch( $base, $diff ) . '';
00442 wfRestoreWarnings();
00443 return $text;
00444 }
00445
00446 # Pure PHP implementation
00447
00448 $header = unpack( 'Vofp/Vcsize', substr( $diff, 0, 8 ) );
00449
00450 # Check the checksum if mhash is available
00451 if ( extension_loaded( 'mhash' ) ) {
00452 $ofp = mhash( MHASH_ADLER32, $base );
00453 if ( $ofp !== substr( $diff, 0, 4 ) ) {
00454 wfDebug( __METHOD__. ": incorrect base checksum\n" );
00455 return false;
00456 }
00457 }
00458 if ( $header['csize'] != strlen( $base ) ) {
00459 wfDebug( __METHOD__. ": incorrect base length\n" );
00460 return false;
00461 }
00462
00463 $p = 8;
00464 $out = '';
00465 while ( $p < strlen( $diff ) ) {
00466 $x = unpack( 'Cop', substr( $diff, $p, 1 ) );
00467 $op = $x['op'];
00468 ++$p;
00469 switch ( $op ) {
00470 case self::XDL_BDOP_INS:
00471 $x = unpack( 'Csize', substr( $diff, $p, 1 ) );
00472 $p++;
00473 $out .= substr( $diff, $p, $x['size'] );
00474 $p += $x['size'];
00475 break;
00476 case self::XDL_BDOP_INSB:
00477 $x = unpack( 'Vcsize', substr( $diff, $p, 4 ) );
00478 $p += 4;
00479 $out .= substr( $diff, $p, $x['csize'] );
00480 $p += $x['csize'];
00481 break;
00482 case self::XDL_BDOP_CPY:
00483 $x = unpack( 'Voff/Vcsize', substr( $diff, $p, 8 ) );
00484 $p += 8;
00485 $out .= substr( $base, $x['off'], $x['csize'] );
00486 break;
00487 default:
00488 wfDebug( __METHOD__.": invalid op\n" );
00489 return false;
00490 }
00491 }
00492 return $out;
00493 }
00494
00495 function uncompress() {
00496 if ( !$this->mDiffs ) {
00497 return;
00498 }
00499 $tail = '';
00500 for ( $diffKey = 0; $diffKey < count( $this->mDiffs ); $diffKey++ ) {
00501 $textKey = $this->mDiffMap[$diffKey];
00502 $text = $this->patch( $tail, $this->mDiffs[$diffKey] );
00503 $this->mItems[$textKey] = $text;
00504 $tail = $text;
00505 }
00506 }
00507
00508 function __sleep() {
00509 $this->compress();
00510 if ( !count( $this->mItems ) ) {
00511
00512 $info = false;
00513 } else {
00514
00515 $map = '';
00516 $prev = 0;
00517 foreach ( $this->mDiffMap as $i ) {
00518 if ( $map !== '' ) {
00519 $map .= ',';
00520 }
00521 $map .= $i - $prev;
00522 $prev = $i;
00523 }
00524 $info = array(
00525 'diffs' => $this->mDiffs,
00526 'map' => $map
00527 );
00528 }
00529 if ( isset( $this->mDefaultKey ) ) {
00530 $info['default'] = $this->mDefaultKey;
00531 }
00532 $this->mCompressed = gzdeflate( serialize( $info ) );
00533 return array( 'mCompressed' );
00534 }
00535
00536 function __wakeup() {
00537
00538 $this->mFrozen = true;
00539 $info = unserialize( gzinflate( $this->mCompressed ) );
00540 unset( $this->mCompressed );
00541
00542 if ( !$info ) {
00543
00544 return;
00545 }
00546
00547 if ( isset( $info['default'] ) ) {
00548 $this->mDefaultKey = $info['default'];
00549 }
00550 $this->mDiffs = $info['diffs'];
00551 if ( isset( $info['base'] ) ) {
00552
00553 $this->mDiffMap = range( 0, count( $this->mDiffs ) - 1 );
00554 array_unshift( $this->mDiffs,
00555 pack( 'VVCV', 0, 0, self::XDL_BDOP_INSB, strlen( $info['base'] ) ) .
00556 $info['base'] );
00557 } else {
00558
00559 $map = explode( ',', $info['map'] );
00560 $cur = 0;
00561 $this->mDiffMap = array();
00562 foreach ( $map as $i ) {
00563 $cur += $i;
00564 $this->mDiffMap[] = $cur;
00565 }
00566 }
00567 $this->uncompress();
00568 }
00569
00574 function isHappy() {
00575 return $this->mSize < $this->mMaxSize
00576 && count( $this->mItems ) < $this->mMaxCount;
00577 }
00578
00579 }