<?php
use Duplicator\Libs\DupArchive\DupArchiveEngine;
use Duplicator\Libs\DupArchive\DupArchiveLoggerBase;
use Duplicator\Libs\DupArchive\States\DupArchiveExpandState;
use Duplicator\Libs\Snap\SnapUtil;
require_once(DUPLICATOR_PLUGIN_PATH . 'classes/package/duparchive/class.pack.archive.duparchive.state.expand.php');
require_once(DUPLICATOR_PLUGIN_PATH . 'classes/package/duparchive/class.pack.archive.duparchive.state.create.php');
class DUP_DupArchive_Logger extends DupArchiveLoggerBase
{
public function log($s, $flush = false, $callingFunctionOverride = null)
{
DUP_Log::Trace($s, true, $callingFunctionOverride);
}
}
class DUP_DupArchive
{
// Using a worker time override since evidence shorter time works much
const WORKER_TIME_IN_SEC = 10;
/**
* CREATE
* Creates the zip file and adds the SQL file to the archive
*/
public static function create(DUP_Archive $archive, $buildProgress, $package)
{
/* @var $buildProgress DUP_Build_Progress */
DUP_LOG::trace("start");
try {
DUP_Log::Open($package->NameHash);
if ($buildProgress->retries > DUPLICATOR_MAX_BUILD_RETRIES) {
$error_msg = __('Backup build appears stuck so marking Backup as failed. Is the Max Worker Time set too high?.', 'duplicator');
DUP_Log::error(esc_html__('Build Failure', 'duplicator'), esc_html($error_msg), Dup_ErrorBehavior::LogOnly);
//$buildProgress->failed = true;
$buildProgress->set_failed($error_msg);
$package->setStatus(DUP_PackageStatus::ERROR);
;
return true;
} else {
// If all goes well retries will be reset to 0 at the end of this function.
$buildProgress->retries++;
$package->update();
}
$done = false;
DupArchiveEngine::init(new DUP_DupArchive_Logger(), $archive->getTargetRootPath());
DUP_Package::safeTmpCleanup(true);
$compressDir = rtrim(DUP_Util::safePath($archive->PackDir), '/');
$sqlPath = DUP_Settings::getSsdirTmpPath() . "/{$package->Database->File}";
$archivePath = DUP_Settings::getSsdirTmpPath() . "/{$archive->File}";
$scanFilepath = DUP_Settings::getSsdirTmpPath() . "/{$package->NameHash}_scan.json";
$skipArchiveFinalization = false;
$json = '';
if (file_exists($scanFilepath)) {
$json = file_get_contents($scanFilepath);
if (empty($json)) {
$errorText = __("Scan file $scanFilepath is empty!", 'duplicator');
$fixText = __("Click on \"Resolve This\" button to fix the JSON settings.", 'duplicator');
DUP_Log::Trace($errorText);
DUP_Log::error(esc_html($errorText) . " **RECOMMENDATION: " . esc_html($fixText) . ".", '', Dup_ErrorBehavior::LogOnly);
//$buildProgress->failed = true;
$buildProgress->set_failed($errorText);
$package->setStatus(DUP_PackageStatus::ERROR);
return true;
}
} else {
DUP_Log::trace("**** scan file $scanFilepath doesn't exist!!");
$errorMessage = sprintf(__("ERROR: Can't find Scanfile %s. Please ensure there no non-English characters in the Backup or schedule name.", 'duplicator'), $scanFilepath);
DUP_Log::error($errorMessage, '', Dup_ErrorBehavior::LogOnly);
//$buildProgress->failed = true;
$buildProgress->set_failed($errorMessage);
$package->setStatus(DUP_PackageStatus::ERROR);
return true;
}
Dup_Log::TraceObject("buildprogress object", $buildProgress, false);
$scanReport = json_decode($json);
if ($buildProgress->archive_started == false) {
$filterDirs = empty($archive->FilterDirs) ? 'not set' : $archive->FilterDirs;
$filterExts = empty($archive->FilterExts) ? 'not set' : $archive->FilterExts;
$filterFiles = empty($archive->FilterFiles) ? 'not set' : $archive->FilterFiles;
$filterOn = ($archive->FilterOn) ? 'ON' : 'OFF';
$filterDirsFormat = rtrim(str_replace(';', "\n\t", $filterDirs));
$filterFilesFormat = rtrim(str_replace(';', "\n\t", $filterFiles));
DUP_Log::info("\n********************************************************************************");
DUP_Log::info("ARCHIVE Type=DUP Mode=DupArchive");
DUP_Log::info("********************************************************************************");
DUP_Log::info("ARCHIVE DIR: " . $compressDir);
DUP_Log::info("ARCHIVE FILE: " . basename($archivePath));
DUP_Log::info("FILTERS: *{$filterOn}*");
DUP_Log::Info("DIRS:\n\t{$filterDirsFormat}");
DUP_Log::Info("FILES:\n\t{$filterFilesFormat}");
DUP_Log::info("EXTS: {$filterExts}");
DUP_Log::info("----------------------------------------");
DUP_Log::info("COMPRESSING");
DUP_Log::info("SIZE:\t" . $scanReport->ARC->Size);
DUP_Log::info("STATS:\tDirs " . $scanReport->ARC->DirCount . " | Files " . $scanReport->ARC->FileCount . " | Total " . $scanReport->ARC->FullCount);
if (($scanReport->ARC->DirCount == '') || ($scanReport->ARC->FileCount == '') || ($scanReport->ARC->FullCount == '')) {
$error_message = 'Invalid Scan Report Detected';
DUP_Log::error($error_message, 'Invalid Scan Report Detected', Dup_ErrorBehavior::LogOnly);
$buildProgress->set_failed($error_message);
$package->setStatus(DUP_PackageStatus::ERROR);
return true;
}
try {
DupArchiveEngine::createArchive($archivePath, true);
$sql_ark_file_path = $package->getSqlArkFilePath();
DupArchiveEngine::addRelativeFileToArchiveST($archivePath, $sqlPath, $sql_ark_file_path);
} catch (Exception $ex) {
$error_message = 'Error adding database.sql to archive';
DUP_Log::error($error_message, $ex->getMessage(), Dup_ErrorBehavior::LogOnly);
$buildProgress->set_failed($error_message);
$package->setStatus(DUP_PackageStatus::ERROR);
return true;
}
$buildProgress->archive_started = true;
$buildProgress->retries = 0;
$createState = DUP_DupArchive_Create_State::createNew($archivePath, $compressDir, self::WORKER_TIME_IN_SEC, true, true);
$createState->throttleDelayInUs = 0;
$createState->save();
$package->Update();
}
try {
$createState = DUP_DupArchive_Create_State::get_instance();
if ($buildProgress->retries > 1) {
$createState->isRobust = true;
$createState->save();
}
if ($createState->working) {
DUP_LOG::Trace("Create state is working");
DupArchiveEngine::addItemsToArchive($createState, $scanReport->ARC);
$buildProgress->set_build_failures($createState->failures);
if ($createState->isCriticalFailurePresent()) {
throw new Exception($createState->getFailureSummary());
}
$totalFileCount = count($scanReport->ARC->Files);
$package->Status = SnapUtil::getWorkPercent(DUP_PackageStatus::ARCSTART, DUP_PackageStatus::ARCVALIDATION, $totalFileCount, $createState->currentFileIndex);
$buildProgress->retries = 0;
$createState->save();
DUP_LOG::TraceObject("Stored Create State", $createState);
DUP_LOG::TraceObject('Stored build_progress', $package->BuildProgress);
if ($createState->working == false) {
// Want it to do the final cleanup work in an entirely new thread so return immediately
$skipArchiveFinalization = true;
DUP_LOG::TraceObject("Done build phase. Create State=", $createState);
}
}
} catch (Exception $ex) {
$message = __('Problem adding items to archive.', 'duplicator') . ' ' . $ex->getMessage();
DUP_Log::error(__('Problems adding items to archive.', 'duplicator'), $message, Dup_ErrorBehavior::LogOnly);
DUP_Log::TraceObject($message . " EXCEPTION:", $ex);
//$buildProgress->failed = true;
$buildProgress->set_failed($message);
$package->setStatus(DUP_PackageStatus::ERROR);
return true;
}
//-- Final Wrapup of the Archive
if ((!$skipArchiveFinalization) && ($createState->working == false)) {
DUP_LOG::Trace("Create state is not working and not skip archive finalization");
if (!$buildProgress->installer_built) {
if ($package->Installer->build($package, false)) {
$package->Runtime = -1;
$package->ExeSize = DUP_Util::byteSize($package->Installer->Size);
$package->ZipSize = DUP_Util::byteSize($package->Archive->Size);
$package->update();
} else {
$package->update();
return;
}
DUP_Log::Trace("Installer has been built so running expand now");
$expandState = DUP_DupArchive_Expand_State::getInstance(true);
$expandState->archivePath = $archivePath;
$expandState->working = true;
$expandState->timeSliceInSecs = self::WORKER_TIME_IN_SEC;
$expandState->basePath = DUP_Settings::getSsdirTmpPath() . '/validate';
$expandState->throttleDelayInUs = 0;
// RSR TODO
$expandState->validateOnly = true;
$expandState->validationType = DupArchiveExpandState::VALIDATION_NONE;
$expandState->working = true;
$expandState->expectedDirectoryCount = count($scanReport->ARC->Dirs) - $createState->skippedDirectoryCount + $package->Installer->numDirsAdded;
$expandState->expectedFileCount = 1 + count($scanReport->ARC->Files) + 1 - $createState->skippedFileCount + $package->Installer->numFilesAdded;
// database.sql will be in there
$expandState->save();
$sfc = count($scanReport->ARC->Files);
$nfa = $package->Installer->numFilesAdded;
Dup_Log::trace("####scan files {$sfc} skipped files {$createState->skippedFileCount} num files added {$nfa}");
DUP_LOG::traceObject("EXPAND STATE AFTER SAVE", $expandState);
} else {
try {
$expandState = DUP_DupArchive_Expand_State::getInstance();
if ($buildProgress->retries > 1) {
// Indicates it had problems before so move into robustness mode
$expandState->isRobust = true;
$expandState->save();
}
DUP_Log::traceObject('Resumed validation expand state', $expandState);
DupArchiveEngine::expandArchive($expandState);
$buildProgress->set_validation_failures($expandState->failures);
$totalFileCount = count($scanReport->ARC->Files);
$archiveSize = @filesize($expandState->archivePath);
$package->Status = SnapUtil::getWorkPercent(
DUP_PackageStatus::ARCVALIDATION,
DUP_PackageStatus::COMPLETE,
$archiveSize,
$expandState->archiveOffset
);
DUP_LOG::TraceObject("package status after expand=", $package->Status);
DUP_LOG::Trace("archive size:{$archiveSize} expand offset:{$expandState->archiveOffset}");
} catch (Exception $ex) {
DUP_Log::Trace('Exception:' . $ex->getMessage() . ':' . $ex->getTraceAsString());
$buildProgress->set_failed('Error validating archive');
$package->setStatus(DUP_PackageStatus::ERROR);
return true;
}
if ($expandState->isCriticalFailurePresent()) {
// Fail immediately if critical failure present - even if havent completed processing the entire archive.
$error_message = __('Critical failure present in validation', 'duplicator');
DUP_Log::error($error_message, $expandState->getFailureSummary(), Dup_ErrorBehavior::LogOnly);
$buildProgress->set_failed($error_message);
return true;
} elseif (!$expandState->working) {
$buildProgress->archive_built = true;
$buildProgress->retries = 0;
$package->update();
$timerAllEnd = DUP_Util::getMicrotime();
$timerAllSum = DUP_Util::elapsedTime($timerAllEnd, $package->TimerStart);
DUP_LOG::traceObject("create state", $createState);
$archiveFileSize = @filesize($archivePath);
DUP_Log::info("COMPRESSED SIZE: " . DUP_Util::byteSize($archiveFileSize));
DUP_Log::info("ARCHIVE RUNTIME: {$timerAllSum}");
DUP_Log::info("MEMORY STACK: " . DUP_Server::getPHPMemory());
DUP_Log::info("CREATE WARNINGS: " . $createState->getFailureSummary(false, true));
DUP_Log::info("VALIDATION WARNINGS: " . $expandState->getFailureSummary(false, true));
$archive->file_count = $expandState->fileWriteCount + $expandState->directoryWriteCount;
$package->update();
$done = true;
} else {
$expandState->save();
}
}
}
} catch (Exception $ex) {
// Have to have a catchall since the main system that calls this function is not prepared to handle exceptions
DUP_Log::trace('Top level create Exception:' . $ex->getMessage() . ':' . $ex->getTraceAsString());
//$buildProgress->failed = true;
$buildProgress->set_failed('Error encoundtered creating archive. See Backup log');
return true;
}
$buildProgress->retries = 0;
return $done;
}
}