MediaWiki  REL1_19
dumpTextPass.php
Go to the documentation of this file.
00001 <?php
00027 $originalDir = getcwd();
00028 
00029 require_once( dirname( __FILE__ ) . '/commandLine.inc' );
00030 require_once( 'backup.inc' );
00031 
00035 class TextPassDumper extends BackupDumper {
00036         var $prefetch = null;
00037         var $input = "php://stdin";
00038         var $history = WikiExporter::FULL;
00039         var $fetchCount = 0;
00040         var $prefetchCount = 0;
00041         var $prefetchCountLast = 0;
00042         var $fetchCountLast = 0;
00043 
00044         var $failures = 0;
00045         var $maxFailures = 5;
00046         var $failedTextRetrievals = 0;
00047         var $maxConsecutiveFailedTextRetrievals = 200;
00048         var $failureTimeout = 5; // Seconds to sleep after db failure
00049 
00050         var $php = "php";
00051         var $spawn = false;
00052         var $spawnProc = false;
00053         var $spawnWrite = false;
00054         var $spawnRead = false;
00055         var $spawnErr = false;
00056 
00057         var $xmlwriterobj = false;
00058 
00059         // when we spend more than maxTimeAllowed seconds on this run, we continue
00060         // processing until we write out the next complete page, then save output file(s),
00061         // rename it/them and open new one(s)
00062         var $maxTimeAllowed = 0;  // 0 = no limit
00063         var $timeExceeded = false;
00064         var $firstPageWritten = false;
00065         var $lastPageWritten = false;
00066         var $checkpointJustWritten = false;
00067         var $checkpointFiles = array();
00068 
00072         protected $db;
00073 
00074         function initProgress( $history ) {
00075                 parent::initProgress();
00076                 $this->timeOfCheckpoint = $this->startTime;
00077         }
00078 
00079         function dump( $history, $text = WikiExporter::TEXT ) {
00080                 // This shouldn't happen if on console... ;)
00081                 header( 'Content-type: text/html; charset=UTF-8' );
00082 
00083                 // Notice messages will foul up your XML output even if they're
00084                 // relatively harmless.
00085                 if ( ini_get( 'display_errors' ) )
00086                         ini_set( 'display_errors', 'stderr' );
00087 
00088                 $this->initProgress( $this->history );
00089 
00090                 $this->db = $this->backupDb();
00091 
00092                 $this->egress = new ExportProgressFilter( $this->sink, $this );
00093 
00094                 // it would be nice to do it in the constructor, oh well. need egress set
00095                 $this->finalOptionCheck();
00096 
00097                 // we only want this so we know how to close a stream :-P
00098                 $this->xmlwriterobj = new XmlDumpWriter();
00099 
00100                 $input = fopen( $this->input, "rt" );
00101                 $result = $this->readDump( $input );
00102 
00103                 if ( WikiError::isError( $result ) ) {
00104                         throw new MWException( $result->getMessage() );
00105                 }
00106 
00107                 if ( $this->spawnProc ) {
00108                         $this->closeSpawn();
00109                 }
00110 
00111                 $this->report( true );
00112         }
00113 
00114         function processOption( $opt, $val, $param ) {
00115                 global $IP;
00116                 $url = $this->processFileOpt( $val, $param );
00117 
00118                 switch( $opt ) {
00119                 case 'prefetch':
00120                         require_once "$IP/maintenance/backupPrefetch.inc";
00121                         $this->prefetch = new BaseDump( $url );
00122                         break;
00123                 case 'stub':
00124                         $this->input = $url;
00125                         break;
00126                 case 'maxtime':
00127                         $this->maxTimeAllowed = intval($val)*60;
00128                         break;
00129                 case 'checkpointfile':
00130                         $this->checkpointFiles[] = $val;
00131                         break;
00132                 case 'current':
00133                         $this->history = WikiExporter::CURRENT;
00134                         break;
00135                 case 'full':
00136                         $this->history = WikiExporter::FULL;
00137                         break;
00138                 case 'spawn':
00139                         $this->spawn = true;
00140                         if ( $val ) {
00141                                 $this->php = $val;
00142                         }
00143                         break;
00144                 }
00145         }
00146 
00147         function processFileOpt( $val, $param ) {
00148                 $fileURIs = explode(';',$param);
00149                 foreach ( $fileURIs as $URI ) {
00150                         switch( $val ) {
00151                                 case "file":
00152                                         $newURI = $URI;
00153                                         break;
00154                                 case "gzip":
00155                                         $newURI = "compress.zlib://$URI";
00156                                         break;
00157                                 case "bzip2":
00158                                         $newURI = "compress.bzip2://$URI";
00159                                         break;
00160                                 case "7zip":
00161                                         $newURI = "mediawiki.compress.7z://$URI";
00162                                         break;
00163                                 default:
00164                                         $newURI = $URI;
00165                         }
00166                         $newFileURIs[] = $newURI;
00167                 }
00168                 $val = implode( ';', $newFileURIs );
00169                 return $val;
00170         }
00171 
00175         function showReport() {
00176                 if ( !$this->prefetch ) {
00177                         parent::showReport();
00178                         return;
00179                 }
00180 
00181                 if ( $this->reporting ) {
00182                         $now = wfTimestamp( TS_DB );
00183                         $nowts = wfTime();
00184                         $deltaAll = wfTime() - $this->startTime;
00185                         $deltaPart = wfTime() - $this->lastTime;
00186                         $this->pageCountPart = $this->pageCount - $this->pageCountLast;
00187                         $this->revCountPart = $this->revCount - $this->revCountLast;
00188 
00189                         if ( $deltaAll ) {
00190                                 $portion = $this->revCount / $this->maxCount;
00191                                 $eta = $this->startTime + $deltaAll / $portion;
00192                                 $etats = wfTimestamp( TS_DB, intval( $eta ) );
00193                                 if ( $this->fetchCount ) {
00194                                         $fetchRate = 100.0 * $this->prefetchCount / $this->fetchCount;
00195                                 } else {
00196                                         $fetchRate = '-';
00197                                 }
00198                                 $pageRate = $this->pageCount / $deltaAll;
00199                                 $revRate = $this->revCount / $deltaAll;
00200                         } else {
00201                                 $pageRate = '-';
00202                                 $revRate = '-';
00203                                 $etats = '-';
00204                                 $fetchRate = '-';
00205                         }
00206                         if ( $deltaPart ) {
00207                                 if ( $this->fetchCountLast ) {
00208                                         $fetchRatePart = 100.0 * $this->prefetchCountLast / $this->fetchCountLast;
00209                                 } else {
00210                                         $fetchRatePart = '-';
00211                                 }
00212                                 $pageRatePart = $this->pageCountPart / $deltaPart;
00213                                 $revRatePart = $this->revCountPart / $deltaPart;
00214 
00215                         } else {
00216                                 $fetchRatePart = '-';
00217                                 $pageRatePart = '-';
00218                                 $revRatePart = '-';
00219                         }
00220                         $this->progress( sprintf( "%s: %s (ID %d) %d pages (%0.1f|%0.1f/sec all|curr), %d revs (%0.1f|%0.1f/sec all|curr), %0.1f%%|%0.1f%% prefetched (all|curr), ETA %s [max %d]",
00221                                         $now, wfWikiID(), $this->ID, $this->pageCount, $pageRate, $pageRatePart, $this->revCount, $revRate, $revRatePart, $fetchRate, $fetchRatePart, $etats, $this->maxCount ) );
00222                         $this->lastTime = $nowts;
00223                         $this->revCountLast = $this->revCount;
00224                         $this->prefetchCountLast = $this->prefetchCount;
00225                         $this->fetchCountLast = $this->fetchCount;
00226                 }
00227         }
00228 
00229         function setTimeExceeded() {
00230                 $this->timeExceeded = True;
00231         }
00232 
00233         function checkIfTimeExceeded() {
00234                 if ( $this->maxTimeAllowed &&  ( $this->lastTime - $this->timeOfCheckpoint  > $this->maxTimeAllowed ) ) {
00235                         return true;
00236                 }
00237                 return false;
00238         }
00239 
00240         function finalOptionCheck() {
00241                 if ( ( $this->checkpointFiles && ! $this->maxTimeAllowed ) ||
00242                         ( $this->maxTimeAllowed && !$this->checkpointFiles ) ) {
00243                         throw new MWException("Options checkpointfile and maxtime must be specified together.\n");
00244                 }
00245                 foreach ($this->checkpointFiles as $checkpointFile) {
00246                         $count = substr_count ( $checkpointFile,"%s" );
00247                         if ( $count != 2 ) {
00248                                 throw new MWException("Option checkpointfile must contain two '%s' for substitution of first and last pageids, count is $count instead, file is $checkpointFile.\n");
00249                         }
00250                 }
00251 
00252                 if ( $this->checkpointFiles ) {
00253                         $filenameList = (array)$this->egress->getFilenames();
00254                         if ( count( $filenameList ) != count( $this->checkpointFiles ) ) {
00255                                 throw new MWException("One checkpointfile must be specified for each output option, if maxtime is used.\n");
00256                         }
00257                 }
00258         }
00259 
00260         function readDump( $input ) {
00261                 $this->buffer = "";
00262                 $this->openElement = false;
00263                 $this->atStart = true;
00264                 $this->state = "";
00265                 $this->lastName = "";
00266                 $this->thisPage = 0;
00267                 $this->thisRev = 0;
00268 
00269                 $parser = xml_parser_create( "UTF-8" );
00270                 xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false );
00271 
00272                 xml_set_element_handler( $parser, array( &$this, 'startElement' ), array( &$this, 'endElement' ) );
00273                 xml_set_character_data_handler( $parser, array( &$this, 'characterData' ) );
00274 
00275                 $offset = 0; // for context extraction on error reporting
00276                 $bufferSize = 512 * 1024;
00277                 do {
00278                         if ($this->checkIfTimeExceeded()) {
00279                                 $this->setTimeExceeded();
00280                         }
00281                         $chunk = fread( $input, $bufferSize );
00282                         if ( !xml_parse( $parser, $chunk, feof( $input ) ) ) {
00283                                 wfDebug( "TextDumpPass::readDump encountered XML parsing error\n" );
00284                                 return new WikiXmlError( $parser, 'XML import parse failure', $chunk, $offset );
00285                         }
00286                         $offset += strlen( $chunk );
00287                 } while ( $chunk !== false && !feof( $input ) );
00288                 if ($this->maxTimeAllowed) {
00289                         $filenameList = (array)$this->egress->getFilenames();
00290                         // we wrote some stuff after last checkpoint that needs renamed
00291                         if (file_exists($filenameList[0])) {
00292                                 $newFilenames = array();
00293                                 # we might have just written the header and footer and had no
00294                                 # pages or revisions written... perhaps they were all deleted
00295                                 # there's no pageID 0 so we use that. the caller is responsible
00296                                 # for deciding what to do with a file containing only the
00297                                 # siteinfo information and the mw tags.
00298                                 if (! $this->firstPageWritten) {
00299                                         $firstPageID = str_pad(0,9,"0",STR_PAD_LEFT);
00300                                         $lastPageID = str_pad(0,9,"0",STR_PAD_LEFT);
00301                                 }
00302                                 else {
00303                                         $firstPageID = str_pad($this->firstPageWritten,9,"0",STR_PAD_LEFT);
00304                                         $lastPageID = str_pad($this->lastPageWritten,9,"0",STR_PAD_LEFT);
00305                                 }
00306                                 for ( $i = 0; $i < count( $filenameList ); $i++ ) {
00307                                         $checkpointNameFilledIn = sprintf( $this->checkpointFiles[$i], $firstPageID, $lastPageID );
00308                                         $fileinfo = pathinfo($filenameList[$i]);
00309                                         $newFilenames[] = $fileinfo['dirname'] . '/' . $checkpointNameFilledIn;
00310                                 }
00311                                 $this->egress->closeAndRename( $newFilenames );
00312                         }
00313                 }
00314                 xml_parser_free( $parser );
00315 
00316                 return true;
00317         }
00318 
00319         function getText( $id ) {
00320                 $this->fetchCount++;
00321                 if ( isset( $this->prefetch ) ) {
00322                         $text = $this->prefetch->prefetch( $this->thisPage, $this->thisRev );
00323                         if ( $text !== null ) { // Entry missing from prefetch dump
00324                                 $dbr = wfGetDB( DB_SLAVE );
00325                                 $revID = intval( $this->thisRev );
00326                                 $revLength = $dbr->selectField( 'revision', 'rev_len', array( 'rev_id' => $revID ) );
00327                                 // if length of rev text in file doesn't match length in db, we reload
00328                                 // this avoids carrying forward broken data from previous xml dumps
00329                                 if( strlen( $text ) == $revLength ) {
00330                                         $this->prefetchCount++;
00331                                         return $text;
00332                                 }
00333                         }
00334                 }
00335                 return $this->doGetText( $id );
00336         }
00337 
00338         private function doGetText( $id ) {
00339                 $id = intval( $id );
00340                 $this->failures = 0;
00341                 $ex = new MWException( "Graceful storage failure" );
00342                 while (true) {
00343                         if ( $this->spawn ) {
00344                                 if ($this->failures) {
00345                                         // we don't know why it failed, could be the child process
00346                                         // borked, could be db entry busted, could be db server out to lunch,
00347                                         // so cover all bases
00348                                         $this->closeSpawn();
00349                                         $this->openSpawn();
00350                                 }
00351                                 $text = $this->getTextSpawned( $id );
00352                         } else {
00353                                 $text = $this->getTextDbSafe( $id );
00354                         }
00355                         if ( $text === false ) {
00356                                 $this->failures++;
00357                                 if ( $this->failures > $this->maxFailures) {
00358                                         $this->progress( "Failed to retrieve revision text for text id ".
00359                                                                          "$id after $this->maxFailures tries, giving up" );
00360                                         // were there so many bad retrievals in a row we want to bail?
00361                                         // at some point we have to declare the dump irretrievably broken
00362                                         $this->failedTextRetrievals++;
00363                                         if ($this->failedTextRetrievals > $this->maxConsecutiveFailedTextRetrievals) {
00364                                                 throw $ex;
00365                                         } else {
00366                                                 // would be nice to return something better to the caller someday,
00367                                                 // log what we know about the failure and about the revision
00368                                                 return "";
00369                                         }
00370                                 } else {
00371                                         $this->progress( "Error $this->failures " .
00372                                                                  "of allowed $this->maxFailures retrieving revision text for text id $id! " .
00373                                                                  "Pausing $this->failureTimeout seconds before retry..." );
00374                                         sleep( $this->failureTimeout );
00375                                 }
00376                         } else {
00377                                 $this->failedTextRetrievals= 0;
00378                                 return $text;
00379                         }
00380                 }
00381                 return '';
00382         }
00383 
00391         private function getTextDbSafe( $id ) {
00392                 while ( true ) {
00393                         try {
00394                                 $text = $this->getTextDb( $id );
00395                         } catch ( DBQueryError $ex ) {
00396                                 $text = false;
00397                         }
00398                         return $text;
00399                 }
00400         }
00401 
00407         private function getTextDb( $id ) {
00408                 global $wgContLang;
00409                 $row = $this->db->selectRow( 'text',
00410                         array( 'old_text', 'old_flags' ),
00411                         array( 'old_id' => $id ),
00412                         __METHOD__ );
00413                 $text = Revision::getRevisionText( $row );
00414                 if ( $text === false ) {
00415                         return false;
00416                 }
00417                 $stripped = str_replace( "\r", "", $text );
00418                 $normalized = $wgContLang->normalize( $stripped );
00419                 return $normalized;
00420         }
00421 
00422         private function getTextSpawned( $id ) {
00423                 wfSuppressWarnings();
00424                 if ( !$this->spawnProc ) {
00425                         // First time?
00426                         $this->openSpawn();
00427                 }
00428                 $text = $this->getTextSpawnedOnce( $id );
00429                 wfRestoreWarnings();
00430                 return $text;
00431         }
00432 
00433         function openSpawn() {
00434                 global $IP;
00435 
00436                 if ( file_exists( "$IP/../multiversion/MWScript.php" ) ) {
00437                         $cmd = implode( " ",
00438                                 array_map( 'wfEscapeShellArg',
00439                                         array(
00440                                                 $this->php,
00441                                                 "$IP/../multiversion/MWScript.php",
00442                                                 "fetchText.php",
00443                                                 '--wiki', wfWikiID() ) ) );
00444                 }
00445                 else {
00446                         $cmd = implode( " ",
00447                                 array_map( 'wfEscapeShellArg',
00448                                         array(
00449                                                 $this->php,
00450                                                 "$IP/maintenance/fetchText.php",
00451                                                 '--wiki', wfWikiID() ) ) );
00452                 }
00453                 $spec = array(
00454                         0 => array( "pipe", "r" ),
00455                         1 => array( "pipe", "w" ),
00456                         2 => array( "file", "/dev/null", "a" ) );
00457                 $pipes = array();
00458 
00459                 $this->progress( "Spawning database subprocess: $cmd" );
00460                 $this->spawnProc = proc_open( $cmd, $spec, $pipes );
00461                 if ( !$this->spawnProc ) {
00462                         // shit
00463                         $this->progress( "Subprocess spawn failed." );
00464                         return false;
00465                 }
00466                 list(
00467                         $this->spawnWrite, // -> stdin
00468                         $this->spawnRead,  // <- stdout
00469                 ) = $pipes;
00470 
00471                 return true;
00472         }
00473 
00474         private function closeSpawn() {
00475                 wfSuppressWarnings();
00476                 if ( $this->spawnRead )
00477                         fclose( $this->spawnRead );
00478                 $this->spawnRead = false;
00479                 if ( $this->spawnWrite )
00480                         fclose( $this->spawnWrite );
00481                 $this->spawnWrite = false;
00482                 if ( $this->spawnErr )
00483                         fclose( $this->spawnErr );
00484                 $this->spawnErr = false;
00485                 if ( $this->spawnProc )
00486                         pclose( $this->spawnProc );
00487                 $this->spawnProc = false;
00488                 wfRestoreWarnings();
00489         }
00490 
00491         private function getTextSpawnedOnce( $id ) {
00492                 global $wgContLang;
00493 
00494                 $ok = fwrite( $this->spawnWrite, "$id\n" );
00495                 // $this->progress( ">> $id" );
00496                 if ( !$ok ) return false;
00497 
00498                 $ok = fflush( $this->spawnWrite );
00499                 // $this->progress( ">> [flush]" );
00500                 if ( !$ok ) return false;
00501 
00502                 // check that the text id they are sending is the one we asked for
00503                 // this avoids out of sync revision text errors we have encountered in the past
00504                 $newId = fgets( $this->spawnRead );
00505                 if ( $newId === false ) {
00506                         return false;
00507                 }
00508                 if ( $id != intval( $newId ) ) {
00509                         return false;
00510                 }
00511 
00512                 $len = fgets( $this->spawnRead );
00513                 // $this->progress( "<< " . trim( $len ) );
00514                 if ( $len === false ) return false;
00515 
00516                 $nbytes = intval( $len );
00517                 // actual error, not zero-length text
00518                 if ($nbytes < 0 ) return false;
00519 
00520                 $text = "";
00521 
00522                 // Subprocess may not send everything at once, we have to loop.
00523                 while ( $nbytes > strlen( $text ) ) {
00524                         $buffer = fread( $this->spawnRead, $nbytes - strlen( $text ) );
00525                         if ( $buffer === false ) break;
00526                         $text .= $buffer;
00527                 }
00528 
00529                 $gotbytes = strlen( $text );
00530                 if ( $gotbytes != $nbytes ) {
00531                         $this->progress( "Expected $nbytes bytes from database subprocess, got $gotbytes " );
00532                         return false;
00533                 }
00534 
00535                 // Do normalization in the dump thread...
00536                 $stripped = str_replace( "\r", "", $text );
00537                 $normalized = $wgContLang->normalize( $stripped );
00538                 return $normalized;
00539         }
00540 
00541         function startElement( $parser, $name, $attribs ) {
00542                 $this->checkpointJustWritten = false;
00543 
00544                 $this->clearOpenElement( null );
00545                 $this->lastName = $name;
00546 
00547                 if ( $name == 'revision' ) {
00548                         $this->state = $name;
00549                         $this->egress->writeOpenPage( null, $this->buffer );
00550                         $this->buffer = "";
00551                 } elseif ( $name == 'page' ) {
00552                         $this->state = $name;
00553                         if ( $this->atStart ) {
00554                                 $this->egress->writeOpenStream( $this->buffer );
00555                                 $this->buffer = "";
00556                                 $this->atStart = false;
00557                         }
00558                 }
00559 
00560                 if ( $name == "text" && isset( $attribs['id'] ) ) {
00561                         $text = $this->getText( $attribs['id'] );
00562                         $this->openElement = array( $name, array( 'xml:space' => 'preserve' ) );
00563                         if ( strlen( $text ) > 0 ) {
00564                                 $this->characterData( $parser, $text );
00565                         }
00566                 } else {
00567                         $this->openElement = array( $name, $attribs );
00568                 }
00569         }
00570 
00571         function endElement( $parser, $name ) {
00572                 $this->checkpointJustWritten = false;
00573 
00574                 if ( $this->openElement ) {
00575                         $this->clearOpenElement( "" );
00576                 } else {
00577                         $this->buffer .= "</$name>";
00578                 }
00579 
00580                 if ( $name == 'revision' ) {
00581                         $this->egress->writeRevision( null, $this->buffer );
00582                         $this->buffer = "";
00583                         $this->thisRev = "";
00584                 } elseif ( $name == 'page' ) {
00585                         if (! $this->firstPageWritten) {
00586                                 $this->firstPageWritten = trim($this->thisPage);
00587                         }
00588                         $this->lastPageWritten = trim($this->thisPage);
00589                         if ($this->timeExceeded) {
00590                                 $this->egress->writeClosePage( $this->buffer );
00591                                 // nasty hack, we can't just write the chardata after the
00592                                 // page tag, it will include leading blanks from the next line
00593                                 $this->egress->sink->write("\n");
00594 
00595                                 $this->buffer = $this->xmlwriterobj->closeStream();
00596                                 $this->egress->writeCloseStream( $this->buffer );
00597 
00598                                 $this->buffer = "";
00599                                 $this->thisPage = "";
00600                                 // this could be more than one file if we had more than one output arg
00601 
00602                                 $filenameList = (array)$this->egress->getFilenames();
00603                                 $newFilenames = array();
00604                                 $firstPageID = str_pad($this->firstPageWritten,9,"0",STR_PAD_LEFT);
00605                                 $lastPageID = str_pad($this->lastPageWritten,9,"0",STR_PAD_LEFT);
00606                                 for ( $i = 0; $i < count( $filenameList ); $i++ ) {
00607                                         $checkpointNameFilledIn = sprintf( $this->checkpointFiles[$i], $firstPageID, $lastPageID );
00608                                         $fileinfo = pathinfo($filenameList[$i]);
00609                                         $newFilenames[] = $fileinfo['dirname'] . '/' . $checkpointNameFilledIn;
00610                                 }
00611                                 $this->egress->closeRenameAndReopen( $newFilenames );
00612                                 $this->buffer = $this->xmlwriterobj->openStream();
00613                                 $this->timeExceeded = false;
00614                                 $this->timeOfCheckpoint = $this->lastTime;
00615                                 $this->firstPageWritten = false;
00616                                 $this->checkpointJustWritten = true;
00617                         }
00618                         else {
00619                                 $this->egress->writeClosePage( $this->buffer );
00620                                 $this->buffer = "";
00621                                 $this->thisPage = "";
00622                         }
00623 
00624                 } elseif ( $name == 'mediawiki' ) {
00625                         $this->egress->writeCloseStream( $this->buffer );
00626                         $this->buffer = "";
00627                 }
00628         }
00629 
00630         function characterData( $parser, $data ) {
00631                 $this->clearOpenElement( null );
00632                 if ( $this->lastName == "id" ) {
00633                         if ( $this->state == "revision" ) {
00634                                 $this->thisRev .= $data;
00635                         } elseif ( $this->state == "page" ) {
00636                                 $this->thisPage .= $data;
00637                         }
00638                 }
00639                 // have to skip the newline left over from closepagetag line of
00640                 // end of checkpoint files. nasty hack!!
00641                 if ($this->checkpointJustWritten) {
00642                         if ($data[0] == "\n") {
00643                                 $data = substr($data,1);
00644                         }
00645                         $this->checkpointJustWritten = false;
00646                 }
00647                 $this->buffer .= htmlspecialchars( $data );
00648         }
00649 
00650         function clearOpenElement( $style ) {
00651                 if ( $this->openElement ) {
00652                         $this->buffer .= Xml::element( $this->openElement[0], $this->openElement[1], $style );
00653                         $this->openElement = false;
00654                 }
00655         }
00656 }
00657 
00658 
00659 $dumper = new TextPassDumper( $argv );
00660 
00661 if ( !isset( $options['help'] ) ) {
00662         $dumper->dump( true );
00663 } else {
00664         $dumper->progress( <<<ENDS
00665 This script postprocesses XML dumps from dumpBackup.php to add
00666 page text which was stubbed out (using --stub).
00667 
00668 XML input is accepted on stdin.
00669 XML output is sent to stdout; progress reports are sent to stderr.
00670 
00671 Usage: php dumpTextPass.php [<options>]
00672 Options:
00673   --stub=<type>:<file> To load a compressed stub dump instead of stdin
00674   --prefetch=<type>:<file> Use a prior dump file as a text source, to save
00675                           pressure on the database.
00676                           (Requires the XMLReader extension)
00677   --maxtime=<minutes> Write out checkpoint file after this many minutes (writing
00678                   out complete page, closing xml file properly, and opening new one
00679                   with header).  This option requires the checkpointfile option.
00680   --checkpointfile=<filenamepattern> Use this string for checkpoint filenames,
00681                       substituting first pageid written for the first %s (required) and the
00682               last pageid written for the second %s if it exists.
00683   --quiet         Don't dump status reports to stderr.
00684   --report=n  Report position and speed after every n pages processed.
00685                           (Default: 100)
00686   --server=h  Force reading from MySQL server h
00687   --current       Base ETA on number of pages in database instead of all revisions
00688   --spawn         Spawn a subprocess for loading text records
00689   --help      Display this help message
00690 ENDS
00691 );
00692 }
00693 
00694