70 private const XDL_BDOP_INS = 1;
71 private const XDL_BDOP_CPY = 2;
72 private const XDL_BDOP_INSB = 3;
75 if ( !function_exists(
'gzdeflate' ) ) {
76 throw new RuntimeException(
"Need zlib support to read or write DiffHistoryBlob\n" );
85 if ( $this->mFrozen ) {
86 throw new BadMethodCallException( __METHOD__ .
": Cannot add more items after sleep/wakeup" );
89 $this->mItems[] = $text;
90 $this->mSize += strlen( $text );
92 return (
string)( count( $this->mItems ) - 1 );
100 return $this->mItems[(int)$key];
107 $this->mDefaultKey = $this->
addItem( $text );
114 return $this->
getItem( $this->mDefaultKey );
117 private function compress() {
118 if ( !function_exists(
'xdiff_string_rabdiff' ) ) {
119 throw new RuntimeException(
"Need xdiff support to write DiffHistoryBlob\n" );
121 if ( isset( $this->mDiffs ) ) {
125 if ( $this->mItems === [] ) {
144 $mItemsCount = count( $this->mItems );
145 for ( $i = 0; $i < $mItemsCount; $i++ ) {
146 $text = $this->mItems[$i];
150 $mainTail = $sequences[
'main'][
'tail'];
151 if ( strlen( $text ) < strlen( $mainTail ) * $smallFactor ) {
158 $tail = $sequences[$seqName][
'tail'];
159 $diff = $this->diff( $tail, $text );
160 $sequences[$seqName][
'diffs'][] = $diff;
161 $sequences[$seqName][
'map'][] = $i;
162 $sequences[$seqName][
'tail'] = $text;
168 $this->mDiffMap = [];
169 foreach ( $sequences as $seq ) {
170 if ( $seq[
'diffs'] === [] ) {
173 if ( $tail ===
'' ) {
174 $this->mDiffs[] = $seq[
'diffs'][0];
176 $head = $this->patch(
'', $seq[
'diffs'][0] );
177 $this->mDiffs[] = $this->diff( $tail, $head );
179 $this->mDiffMap[] = $seq[
'map'][0];
180 $diffsCount = count( $seq[
'diffs'] );
181 for ( $i = 1; $i < $diffsCount; $i++ ) {
182 $this->mDiffs[] = $seq[
'diffs'][$i];
183 $this->mDiffMap[] = $seq[
'map'][$i];
185 $tail = $seq[
'tail'];
194 private function diff( $t1, $t2 ) {
195 return xdiff_string_rabdiff( $t1, $t2 );
203 private function patch( $base, $diff ) {
204 if ( function_exists(
'xdiff_string_bpatch' ) ) {
205 return xdiff_string_bpatch( $base, $diff );
208 # Pure PHP implementation
210 $header = unpack(
'Vofp/Vcsize', substr( $diff, 0, 8 ) );
214 if ( $ofp !== substr( $diff, 0, 4 ) ) {
215 wfDebug( __METHOD__ .
": incorrect base checksum" );
218 if (
$header[
'csize'] != strlen( $base ) ) {
219 wfDebug( __METHOD__ .
": incorrect base length" );
225 while ( $p < strlen( $diff ) ) {
226 $x = unpack(
'Cop', substr( $diff, $p, 1 ) );
230 case self::XDL_BDOP_INS:
231 $x = unpack(
'Csize', substr( $diff, $p, 1 ) );
233 $out .= substr( $diff, $p, $x[
'size'] );
236 case self::XDL_BDOP_INSB:
237 $x = unpack(
'Vcsize', substr( $diff, $p, 4 ) );
239 $out .= substr( $diff, $p, $x[
'csize'] );
242 case self::XDL_BDOP_CPY:
243 $x = unpack(
'Voff/Vcsize', substr( $diff, $p, 8 ) );
245 $out .= substr( $base, $x[
'off'], $x[
'csize'] );
248 wfDebug( __METHOD__ .
": invalid op" );
264 $init ??= str_repeat(
"\xf0", 205 ) .
"\xee" . str_repeat(
"\xf0", 67 ) .
"\x02";
269 return strrev( hash(
'adler32', $init . $s,
true ) );
272 private function uncompress() {
273 if ( !$this->mDiffs ) {
277 $mDiffsCount = count( $this->mDiffs );
278 for ( $diffKey = 0; $diffKey < $mDiffsCount; $diffKey++ ) {
279 $textKey = $this->mDiffMap[$diffKey];
280 $text = $this->patch( $tail, $this->mDiffs[$diffKey] );
281 $this->mItems[$textKey] = $text;
291 if ( $this->mItems === [] ) {
297 foreach ( $this->mDiffMap as $i ) {
309 if ( isset( $this->mDefaultKey ) ) {
312 $this->mCompressed = gzdeflate( serialize( $info ) );
313 return [
'mCompressed' ];
318 $this->mFrozen =
true;
319 $info = HistoryBlobUtils::unserializeArray( gzinflate( $this->mCompressed ) );
320 $this->mCompressed =
null;
327 if ( isset( $info[
'default'] ) ) {
328 $this->mDefaultKey = $info[
'default'];
330 $this->mDiffs = $info[
'diffs'];
331 if ( isset( $info[
'base'] ) ) {
333 $this->mDiffMap = range( 0, count( $this->mDiffs ) - 1 );
334 array_unshift( $this->mDiffs,
335 pack(
'VVCV', 0, 0, self::XDL_BDOP_INSB, strlen( $info[
'base'] ) ) .
339 $map = explode(
',', $info[
'map'] );
341 $this->mDiffMap = [];
342 foreach ( $map as $i ) {
344 $this->mDiffMap[] = $cur;
357 return $this->mSize < $this->mMaxSize
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Diff-based history compression Requires xdiff and zlib.
bool $mFrozen
True if the object is locked against further writes.
xdiffAdler32( $s)
Compute a binary "Adler-32" checksum as defined by LibXDiff, i.e.
int $mMaxSize
The maximum uncompressed size before the object becomes sad Should be less than max_allowed_packet.
int $mMaxCount
The maximum number of text items before the object becomes sad.
isHappy()
Helper function for compression jobs Returns true until the object is "full" and ready to be committe...
Base class for general text storage via the "object" flag in old_flags, or two-part external storage ...