View file File name : DupArchiveEngine.php Content :<?php /** * * @package Duplicator * @copyright (c) 2021, Snapcreek LLC */ namespace Duplicator\Libs\DupArchive; use Duplicator\Libs\DupArchive\Headers\DupArchiveDirectoryHeader; use Duplicator\Libs\DupArchive\Headers\DupArchiveFileHeader; use Duplicator\Libs\DupArchive\Headers\DupArchiveHeader; use Duplicator\Libs\DupArchive\Headers\DupArchiveReaderFileHeader; use Duplicator\Libs\DupArchive\Headers\DupArchiveReaderGlobHeader; use Duplicator\Libs\DupArchive\Info\DupArchiveInfo; use Duplicator\Libs\DupArchive\Processors\DupArchiveDirectoryProcessor; use Duplicator\Libs\DupArchive\Processors\DupArchiveFileProcessor; use Duplicator\Libs\DupArchive\Processors\DupArchiveProcessingFailure; use Duplicator\Libs\DupArchive\States\DupArchiveCreateState; use Duplicator\Libs\DupArchive\States\DupArchiveExpandState; use Duplicator\Libs\DupArchive\States\DupArchiveSimpleCreateState; use Duplicator\Libs\DupArchive\States\DupArchiveSimpleExpandState; use Duplicator\Libs\DupArchive\Utils\DupArchiveScanUtil; use Duplicator\Libs\DupArchive\Utils\DupArchiveUtil; use Duplicator\Libs\Snap\Snap32BitSizeLimitException; use Duplicator\Libs\Snap\SnapIO; use Exception; use stdClass; /** * $re = '/\/\/.* /'; * $subst = ''; * * $re = '/(.*^\s*)(namespace.*?)(;)(.*)/sm'; * $subst = '$2 { $4}'; * * $re = '/\/\*.*?\*\//s'; '' * $re = '/\n\s*\n/s'; "\n" */ class DupArchiveEngine extends DupArchive { const EXCEPTION_NON_FATAL = 0; const EXCEPTION_FATAL = 1; /** @var string|null */ public static $targetRootPath = null; /** * Dup archive init * * @param DupArchiveLoggerBase $logger logger object * @param string|null $targetRootPath archive target root path or null not root path * * @return void */ public static function init(DupArchiveLoggerBase $logger, $targetRootPath = null) { DupArchiveUtil::$logger = $logger; self::$targetRootPath = $targetRootPath; } /** * Get local path * * @param string $path item path * @param DupArchiveCreateState $createState base path * * @return string */ protected static function getLocalPath($path, DupArchiveCreateState $createState) { $result = ''; if (self::$targetRootPath === null) { $result = substr($path, $createState->basepathLength); $result = ltrim($result, '/'); if ($createState->newBasePath !== null) { $result = $createState->newBasePath . $result; } } else { $safePath = SnapIO::safePathUntrailingslashit($path); $result = ltrim( $createState->newBasePath . preg_replace('/^' . preg_quote(self::$targetRootPath, '/') . '(.*)/m', '$1', $safePath), '/' ); } return $result; } /** * Get archvie info from path * * @param string $filepath archvie path * * @return DupArchiveInfo */ public static function getArchiveInfo($filepath) { $archiveInfo = new DupArchiveInfo(); DupArchiveUtil::log("archive size=" . filesize($filepath)); $archiveHandle = SnapIO::fopen($filepath, 'rb'); $archiveInfo->archiveHeader = DupArchiveHeader::readFromArchive($archiveHandle); $moreToRead = true; while ($moreToRead) { $headerType = self::getNextHeaderType($archiveHandle); // DupArchiveUtil::log("next header type=$headerType: " . ftell($archiveHandle)); switch ($headerType) { case self::HEADER_TYPE_FILE: $fileHeader = DupArchiveFileHeader::readFromArchive($archiveHandle, true, true); $archiveInfo->fileHeaders[] = $fileHeader; DupArchiveUtil::log("file" . $fileHeader->relativePath); break; case self::HEADER_TYPE_DIR: $directoryHeader = DupArchiveDirectoryHeader::readFromArchive($archiveHandle, true); $archiveInfo->directoryHeaders[] = $directoryHeader; break; case self::HEADER_TYPE_NONE: $moreToRead = false; } } return $archiveInfo; } /** * Add folder to archive * * can't span requests since create state can't store list of files * * @param string $archiveFilepath archive file * @param string $directory folder to add * @param string $basepath base path to consider (?) * @param boolean $includeFiles if true include files * @param string $newBasepath new base path * @param int $globSize global size * * @return stdClass */ public static function addDirectoryToArchiveST( $archiveFilepath, $directory, $basepath, $includeFiles = false, $newBasepath = null, $globSize = DupArchiveCreateState::DEFAULT_GLOB_SIZE ) { if ($includeFiles) { $scan = DupArchiveScanUtil::createScanObject($directory); } else { $scan = new stdClass(); $scan->Files = array(); $scan->Dirs = array(); } $createState = new DupArchiveSimpleCreateState(); $createState->archiveOffset = filesize($archiveFilepath); $createState->archivePath = $archiveFilepath; $createState->basePath = $basepath; $createState->basepathLength = strlen($basepath); $createState->timerEnabled = false; $createState->globSize = $globSize; $createState->newBasePath = $newBasepath; self::addItemsToArchive($createState, $scan); $retVal = new stdClass(); $retVal->numDirsAdded = $createState->currentDirectoryIndex; $retVal->numFilesAdded = $createState->currentFileIndex; if ($createState->skippedFileCount > 0) { throw new Exception("One or more files were were not able to be added when adding {$directory} to {$archiveFilepath}"); } elseif ($createState->skippedDirectoryCount > 0) { throw new Exception("One or more directories were not able to be added when adding {$directory} to {$archiveFilepath}"); } return $retVal; } /** * Add relative file to archive * * @param string $archiveFilepath archive file * @param string $filepath file to add * @param string $relativePath relative path in archive * @param int $globSize global size * * @return void */ public static function addRelativeFileToArchiveST( $archiveFilepath, $filepath, $relativePath, $globSize = DupArchiveCreateState::DEFAULT_GLOB_SIZE ) { $createState = new DupArchiveSimpleCreateState(); $createState->archiveOffset = filesize($archiveFilepath); $createState->archivePath = $archiveFilepath; $createState->basePath = null; $createState->basepathLength = 0; $createState->timerEnabled = false; $createState->globSize = $globSize; $scan = new stdClass(); $scan->Files = array(); $scan->Dirs = array(); $scan->Files[] = $filepath; if ($relativePath != null) { $scan->FileAliases = array(); $scan->FileAliases[$filepath] = $relativePath; } self::addItemsToArchive($createState, $scan); } /** * Add file in archive from src * * @param string|resource $archive Archive path or archive handle * @param string $src source string * @param string $relativeFilePath relative path * @param int $forceSize if 0 size is auto of content is filled of \0 char to size * * @return bool */ public static function addFileFromSrc( $archive, $src, $relativeFilePath, $forceSize = 0 ) { if (is_resource($archive)) { $archiveHandle = $archive; SnapIO::fseek($archiveHandle, 0, SEEK_SET); } else { if (($archiveHandle = SnapIO::fopen($archive, 'r+b')) == false) { throw new Exception('Can\'t open archive'); } } $createState = new DupArchiveSimpleCreateState(); $createState->archiveOffset = SnapIO::ftell($archiveHandle); $createState->basePath = dirname($relativeFilePath); $createState->basepathLength = strlen($createState->basePath); $createState->timerEnabled = false; if ($forceSize == 0) { $archiveHeader = DupArchiveHeader::readFromArchive($archiveHandle); $createState->isCompressed = $archiveHeader->isCompressed; } else { // ff force size is enables the src isn't compress $createState->isCompressed = false; } SnapIO::fseek($archiveHandle, 0, SEEK_END); $result = DupArchiveFileProcessor::writeFileSrcToArchive($createState, $archiveHandle, $src, $relativeFilePath, $forceSize); if (!is_resource($archive)) { SnapIO::fclose($archiveHandle); } return $result; } /** * Add file in archive from src * * @param string $archiveFilepath archive path * @param string $src source string * @param string $relativeFilePath relative path * @param int $offset start search location * @param int $sizeToSearch max size where search * * @return bool */ public static function replaceFileContent( $archiveFilepath, $src, $relativeFilePath, $offset = 0, $sizeToSearch = 0 ) { if (($archiveHandle = SnapIO::fopen($archiveFilepath, 'r+b')) == false) { throw new Exception('Can\'t open archive'); } if (($filePos = self::searchPath($archiveHandle, $relativeFilePath, $offset, $sizeToSearch)) == false) { return false; } $fileHeader = DupArchiveReaderFileHeader::readFromArchive($archiveHandle); $globHeader = DupArchiveReaderGlobHeader::readFromArchive($archiveHandle); SnapIO::fseek($archiveHandle, $filePos); $createState = new DupArchiveSimpleCreateState(); $createState->archivePath = $archiveFilepath; $createState->archiveOffset = $filePos; $createState->basePath = dirname($relativeFilePath); $createState->basepathLength = strlen($createState->basePath); $createState->timerEnabled = false; $createState->isCompressed = false; // replaced content can't be compressed $forceSize = $globHeader->storedSize; $result = DupArchiveFileProcessor::writeFileSrcToArchive($createState, $archiveHandle, $src, $relativeFilePath, $forceSize); SnapIO::fclose($archiveHandle); return $result; } /** * Add file in archive using base dir * * @param string $archiveFilepath archive file * @param string $basePath base path * @param string $filepath file to add * @param int $globSize global size * * @return void */ public static function addFileToArchiveUsingBaseDirST( $archiveFilepath, $basePath, $filepath, $globSize = DupArchiveCreateState::DEFAULT_GLOB_SIZE ) { $createState = new DupArchiveSimpleCreateState(); $createState->archiveOffset = filesize($archiveFilepath); $createState->archivePath = $archiveFilepath; $createState->basePath = $basePath; $createState->basepathLength = strlen($basePath); $createState->timerEnabled = false; $createState->globSize = $globSize; $scan = new stdClass(); $scan->Files = array(); $scan->Dirs = array(); $scan->Files[] = $filepath; self::addItemsToArchive($createState, $scan); } /** * Create archive * * @param string $archivePath archive file path * @param bool $isCompressed is compressed * * @return void */ public static function createArchive($archivePath, $isCompressed) { if (($archiveHandle = SnapIO::fopen($archivePath, 'w+b')) === false) { throw new Exception('Can\t create dup archvie file ' . $archivePath); } $archiveHeader = DupArchiveHeader::create($isCompressed); $archiveHeader->writeToArchive($archiveHandle); //reserver space for index $src = json_encode(array('test')); $src .= str_repeat("\0", self::INDEX_FILE_SIZE - strlen($src)); self::addFileFromSrc($archiveHandle, $src, self::INDEX_FILE_NAME, self::INDEX_FILE_SIZE); // Intentionally do not write build state since if something goes wrong we went it to start over on the archive SnapIO::fclose($archiveHandle); } /** * Add items to archive * * @param DupArchiveCreateState $createState create state info * @param stdClass $scanFSInfo scan if * * @return void */ public static function addItemsToArchive(DupArchiveCreateState $createState, stdClass $scanFSInfo) { if ($createState->globSize == -1) { $createState->globSize = DupArchiveCreateState::DEFAULT_GLOB_SIZE; } DupArchiveUtil::tlogObject("addItemsToArchive start", $createState); $directoryCount = count($scanFSInfo->Dirs); $fileCount = count($scanFSInfo->Files); $createState->startTimer(); $archiveHandle = SnapIO::fopen($createState->archivePath, 'r+b'); DupArchiveUtil::tlog("Archive size=", filesize($createState->archivePath)); DupArchiveUtil::tlog("Archive location is now " . SnapIO::ftell($archiveHandle)); $archiveHeader = DupArchiveHeader::readFromArchive($archiveHandle); $createState->isCompressed = $archiveHeader->isCompressed; if ($createState->archiveOffset == filesize($createState->archivePath)) { DupArchiveUtil::tlog( "Seeking to end of archive location because of offset {$createState->archiveOffset} " . "for file size " . filesize($createState->archivePath) ); SnapIO::fseek($archiveHandle, 0, SEEK_END); } else { DupArchiveUtil::tlog("Seeking archive offset {$createState->archiveOffset} for file size " . filesize($createState->archivePath)); SnapIO::fseek($archiveHandle, $createState->archiveOffset); } while (($createState->currentDirectoryIndex < $directoryCount) && (!$createState->timedOut())) { if ($createState->throttleDelayInUs !== 0) { usleep($createState->throttleDelayInUs); } $directory = $scanFSInfo->Dirs[$createState->currentDirectoryIndex]; try { $relativeDirectoryPath = ''; if (isset($scanFSInfo->DirectoryAliases) && array_key_exists($directory, $scanFSInfo->DirectoryAliases)) { $relativeDirectoryPath = $scanFSInfo->DirectoryAliases[$directory]; } else { $relativeDirectoryPath = self::getLocalPath($directory, $createState); } if ($relativeDirectoryPath !== '') { DupArchiveDirectoryProcessor::writeDirectoryToArchive($createState, $archiveHandle, $directory, $relativeDirectoryPath); } else { $createState->skippedDirectoryCount++; $createState->currentDirectoryIndex++; } } catch (Exception $ex) { DupArchiveUtil::log("Failed to add {$directory} to archive. Error: " . $ex->getMessage(), true); $createState->addFailure(DupArchiveProcessingFailure::TYPE_DIRECTORY, $directory, $ex->getMessage(), false); $createState->currentDirectoryIndex++; $createState->skippedDirectoryCount++; $createState->save(); } } $createState->archiveOffset = SnapIO::ftell($archiveHandle); $workTimestamp = time(); while (($createState->currentFileIndex < $fileCount) && (!$createState->timedOut())) { $filepath = $scanFSInfo->Files[$createState->currentFileIndex]; try { $relativeFilePath = ''; if (isset($scanFSInfo->FileAliases) && array_key_exists($filepath, $scanFSInfo->FileAliases)) { $relativeFilePath = $scanFSInfo->FileAliases[$filepath]; } else { $relativeFilePath = self::getLocalPath($filepath, $createState); } // Uncomment when testing error handling // if((strpos($relativeFilePath, 'dup-installer') !== false) || (strpos($relativeFilePath, 'lib') !== false)) { // Dup_Log::Trace("Was going to do intentional error to {$relativeFilePath} but skipping"); // } else { // throw new Exception("#### intentional file error when writing " . $relativeFilePath); // } // } DupArchiveFileProcessor::writeFilePortionToArchive($createState, $archiveHandle, $filepath, $relativeFilePath); if (($createState->isRobust) && (time() - $workTimestamp >= 1)) { DupArchiveUtil::log("Robust mode create state save"); // When in robustness mode save the state every second $workTimestamp = time(); $createState->working = ($createState->currentDirectoryIndex < $directoryCount) || ($createState->currentFileIndex < $fileCount); $createState->save(); } } catch (Snap32BitSizeLimitException $ex) { throw $ex; } catch (Exception $ex) { DupArchiveUtil::log("Failed to add {$filepath} to archive. Error: " . $ex->getMessage() . $ex->getTraceAsString(), true); $createState->currentFileIndex++; $createState->skippedFileCount++; $createState->addFailure(DupArchiveProcessingFailure::TYPE_FILE, $filepath, $ex->getMessage(), ($ex->getCode() === self::EXCEPTION_FATAL)); $createState->save(); } } $createState->working = ($createState->currentDirectoryIndex < $directoryCount) || ($createState->currentFileIndex < $fileCount); $createState->save(); SnapIO::fclose($archiveHandle); if (!$createState->working) { DupArchiveUtil::log("compress done"); } else { DupArchiveUtil::tlog("compress not done so continuing later"); } } /** * Expand archive * * @param DupArchiveExpandState $expandState expand state * * @return void */ public static function expandArchive(DupArchiveExpandState $expandState) { $expandState->startTimer(); $archiveHandle = SnapIO::fopen($expandState->archivePath, 'rb'); SnapIO::fseek($archiveHandle, $expandState->archiveOffset); if ($expandState->archiveOffset == 0) { $expandState->archiveHeader = DupArchiveHeader::readFromArchive($archiveHandle); $expandState->isCompressed = $expandState->archiveHeader->isCompressed; $expandState->archiveOffset = SnapIO::ftell($archiveHandle); $expandState->save(); } else { DupArchiveUtil::log("#### seeking archive offset {$expandState->archiveOffset}"); } DupArchiveUtil::log('DUP EXPAND OFFSET ' . $expandState->archiveOffset); if ((!$expandState->validateOnly) || ($expandState->validationType == DupArchiveExpandState::VALIDATION_FULL)) { $moreItems = self::expandItems($expandState, $archiveHandle); } else { $moreItems = self::standardValidateItems($expandState, $archiveHandle); } $expandState->working = $moreItems; $expandState->save(); SnapIO::fclose($archiveHandle, false); if (!$expandState->working) { DupArchiveUtil::log("DUP EXPAND DONE"); if (($expandState->expectedFileCount != -1) && ($expandState->expectedFileCount != $expandState->fileWriteCount)) { $expandState->addFailure( DupArchiveProcessingFailure::TYPE_FILE, 'Archive', "Number of files expected ({$expandState->expectedFileCount}) doesn't equal number written ({$expandState->fileWriteCount})." ); } if (($expandState->expectedDirectoryCount != -1) && ($expandState->expectedDirectoryCount != $expandState->directoryWriteCount)) { $expandState->addFailure( DupArchiveProcessingFailure::TYPE_DIRECTORY, 'Archive', "Number of directories expected ({$expandState->expectedDirectoryCount}) " . "doesn't equal number written ({$expandState->directoryWriteCount})." ); } } else { DupArchiveUtil::tlogObject("expand not done so continuing later", $expandState); } } /** * Single-threaded file expansion * * @param string $archiveFilePath archive path * @param string $relativeFilePaths relative file path in archive * @param string $destPath destination path * * @return void */ public static function expandFiles($archiveFilePath, $relativeFilePaths, $destPath) { // Not setting timeout timestamp so it will never timeout DupArchiveUtil::tlog("opening archive {$archiveFilePath}"); $archiveHandle = SnapIO::fopen($archiveFilePath, 'r'); /* @var $expandState DupArchiveSimpleExpandState */ $expandState = new DupArchiveSimpleExpandState(); $expandState->archiveHeader = DupArchiveHeader::readFromArchive($archiveHandle); $expandState->isCompressed = $expandState->archiveHeader->isCompressed; $expandState->archiveOffset = SnapIO::ftell($archiveHandle); $expandState->includedFiles = $relativeFilePaths; $expandState->filteredDirectories = array('*'); $expandState->filteredFiles = array('*'); // $expandState->basePath = $destPath . '/tempExtract'; // RSR remove once extract works $expandState->basePath = $destPath; // RSR remove once extract works // TODO: Filter out all directories/files except those in the list self::expandItems($expandState, $archiveHandle); } /** * Expand dup archive items * * @param DupArchiveExpandState $expandState dup archive expand state * @param resource $archiveHandle dup archvie resource * * @return bool true if more to read */ private static function expandItems(DupArchiveExpandState $expandState, $archiveHandle) { $moreToRead = true; $workTimestamp = time(); while ($moreToRead && (!$expandState->timedOut())) { if ($expandState->throttleDelayInUs !== 0) { usleep($expandState->throttleDelayInUs); } if ($expandState->currentFileHeader != null) { DupArchiveUtil::tlog("Writing file {$expandState->currentFileHeader->relativePath}"); if (self::filePassesFilters($expandState)) { try { $fileCompleted = DupArchiveFileProcessor::writeToFile($expandState, $archiveHandle); } catch (Exception $ex) { DupArchiveUtil::log("Failed to write to {$expandState->currentFileHeader->relativePath}. Error: " . $ex->getMessage(), true); // Reset things - skip over this file within the archive. SnapIO::fseek($archiveHandle, $expandState->lastHeaderOffset); self::skipToNextHeader($archiveHandle, $expandState->currentFileHeader); $expandState->archiveOffset = ftell($archiveHandle); $expandState->addFailure( DupArchiveProcessingFailure::TYPE_FILE, $expandState->currentFileHeader->relativePath, $ex->getMessage(), false ); $expandState->resetForFile(); $expandState->lastHeaderOffset = -1; $expandState->save(); } } else { self::skipFileInArchive($archiveHandle, $expandState->currentFileHeader); $expandState->resetForFile(); } } else { // Header is null so read in the next one $expandState->lastHeaderOffset = @ftell($archiveHandle); $headerType = self::getNextHeaderType($archiveHandle); DupArchiveUtil::tlog('header type ' . $headerType); switch ($headerType) { case self::HEADER_TYPE_FILE: DupArchiveUtil::tlog('File header'); $expandState->currentFileHeader = DupArchiveFileHeader::readFromArchive($archiveHandle, false, true); $expandState->archiveOffset = @ftell($archiveHandle); DupArchiveUtil::tlog('Just read file header from archive'); break; case self::HEADER_TYPE_DIR: DupArchiveUtil::tlog('Directory Header'); $directoryHeader = DupArchiveDirectoryHeader::readFromArchive($archiveHandle, true); if (self::passesDirectoryExclusion($expandState, $directoryHeader->relativePath)) { $createdDirectory = true; if (!$expandState->validateOnly) { $createdDirectory = DupArchiveFileProcessor::createDirectory($expandState, $directoryHeader); } if ($createdDirectory) { $expandState->directoryWriteCount++; } } $expandState->archiveOffset = ftell($archiveHandle); DupArchiveUtil::tlog('Just read directory header ' . $directoryHeader->relativePath . ' from archive'); break; case self::HEADER_TYPE_NONE: $moreToRead = false; } } if (($expandState->isRobust) && (time() - $workTimestamp >= 1)) { DupArchiveUtil::log("Robust mode extract state save for standard validate"); // When in robustness mode save the state every second $workTimestamp = time(); $expandState->save(); } } $expandState->save(); return $moreToRead; } /** * check exclude dir * * @param DupArchiveExpandState $expandState dup archive expand state * @param string $candidate check exclude dir * * @return bool */ private static function passesDirectoryExclusion(DupArchiveExpandState $expandState, $candidate) { foreach ($expandState->filteredDirectories as $directoryFilter) { if ($directoryFilter === '*') { return false; } if (SnapIO::getRelativePath($candidate, $directoryFilter) !== false) { return false; } } if (in_array($candidate, $expandState->excludedDirWithoutChilds)) { return false; } return true; } /** * Check flils filters * * @param DupArchiveExpandState $expandState dup archive expand state * * @return boolean */ private static function filePassesFilters(DupArchiveExpandState $expandState) { $candidate = $expandState->currentFileHeader->relativePath; // Included files trumps all exclusion filters foreach ($expandState->includedFiles as $includedFile) { if ($includedFile === $candidate) { return true; } } if (self::passesDirectoryExclusion($expandState, $candidate)) { foreach ($expandState->filteredFiles as $fileFilter) { if ($fileFilter === '*' || $fileFilter === $candidate) { return false; } } } else { return false; } return true; } /** * Validate items * * @param DupArchiveExpandState $expandState dup archive expan state * @param resource $archiveHandle dup archive resource * * @return bool true if more to read */ private static function standardValidateItems(DupArchiveExpandState $expandState, $archiveHandle) { $moreToRead = true; $to = $expandState->timedOut(); $workTimestamp = time(); while ($moreToRead && (!$to)) { if ($expandState->throttleDelayInUs !== 0) { usleep($expandState->throttleDelayInUs); } if ($expandState->currentFileHeader != null) { try { $fileCompleted = DupArchiveFileProcessor::standardValidateFileEntry($expandState, $archiveHandle); if ($fileCompleted) { $expandState->resetForFile(); } // Expand state taken care of within the write to file to ensure consistency } catch (Exception $ex) { DupArchiveUtil::log("Failed validate file in archive. Error: " . $ex->getMessage(), true); DupArchiveUtil::logObject("expand state", $expandState, true); // $expandState->currentFileIndex++; // RSR TODO: Need way to skip past that file $expandState->addFailure(DupArchiveProcessingFailure::TYPE_FILE, $expandState->currentFileHeader->relativePath, $ex->getMessage()); $expandState->save(); $moreToRead = false; } } else { $headerType = self::getNextHeaderType($archiveHandle); switch ($headerType) { case self::HEADER_TYPE_FILE: $expandState->currentFileHeader = DupArchiveFileHeader::readFromArchive($archiveHandle, false, true); $expandState->archiveOffset = ftell($archiveHandle); break; case self::HEADER_TYPE_DIR: $directoryHeader = DupArchiveDirectoryHeader::readFromArchive($archiveHandle, true); $expandState->directoryWriteCount++; $expandState->archiveOffset = ftell($archiveHandle); break; case self::HEADER_TYPE_NONE: $moreToRead = false; } } if (($expandState->isRobust) && (time() - $workTimestamp >= 1)) { DupArchiveUtil::log("Robust mdoe extract state save for standard validate"); // When in robustness mode save the state every second $workTimestamp = time(); $expandState->save(); } $to = $expandState->timedOut(); } $expandState->save(); return $moreToRead; } }