666 lines
31 KiB
PHP
666 lines
31 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Class to create a zip file using PHP ZipArchive
|
|
*
|
|
* Standard: PSR-2 (almost)
|
|
*
|
|
* @link http://www.php-fig.org/psr/psr-2
|
|
*
|
|
* @package DUP_PRO
|
|
* @subpackage classes/package
|
|
* @copyright (c) 2017, Snapcreek LLC
|
|
* @license https://opensource.org/licenses/GPL-3.0 GNU Public License
|
|
* @since 1.0.0
|
|
*
|
|
* @notes: Trace process time
|
|
* $timer01 = DUP_PRO_U::getMicrotime();
|
|
* DUP_PRO_Log::trace("SCAN TIME-B = " . DUP_PRO_U::elapsedTime(DUP_PRO_U::getMicrotime(), $timer01));
|
|
*/
|
|
|
|
defined('ABSPATH') || defined('DUPXABSPATH') || exit;
|
|
|
|
use Duplicator\Libs\Snap\SnapIO;
|
|
use Duplicator\Models\SystemGlobalEntity;
|
|
use Duplicator\Package\Create\BuildProgress;
|
|
use Duplicator\Utils\ZipArchiveExtended;
|
|
|
|
class DUP_PRO_ZipArchive
|
|
{
|
|
/** @var DUP_PRO_Global_Entity */
|
|
private $global = null;
|
|
/** @var bool */
|
|
private $optMaxBuildTimeOn = true;
|
|
/** @var int */
|
|
private $maxBuildTimeFileSize = 100000;
|
|
/** @var int */
|
|
private $throttleDelayInUs = 0;
|
|
/** @var DUP_PRO_Package */
|
|
private $package = null;
|
|
/** @var ZipArchiveExtended */
|
|
private $zipArchive = null;
|
|
|
|
/**
|
|
* Class constructor
|
|
*
|
|
* @param DUP_PRO_Package $package The package to create the zip file for
|
|
*/
|
|
public function __construct(DUP_PRO_Package $package)
|
|
{
|
|
$this->global = DUP_PRO_Global_Entity::getInstance();
|
|
$this->optMaxBuildTimeOn = ($this->global->max_package_runtime_in_min > 0);
|
|
$this->throttleDelayInUs = $this->global->getMicrosecLoadReduction();
|
|
|
|
$this->package = $package;
|
|
$this->zipArchive = new ZipArchiveExtended($this->package->StorePath . '/' . $this->package->Archive->File);
|
|
|
|
$password = $this->package->Archive->getArchivePassword();
|
|
if (strlen($password) > 0) {
|
|
$this->zipArchive->setEncrypt(true, $password);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates the zip file and adds the SQL file to the archive
|
|
*
|
|
* @param BuildProgress $build_progress A copy of the current build progress
|
|
*
|
|
* @return bool Returns true if the process was successful
|
|
*/
|
|
public function create(BuildProgress $build_progress)
|
|
{
|
|
try {
|
|
if (!ZipArchiveExtended::isPhpZipAvailable()) {
|
|
DUP_PRO_Log::trace("Zip archive doesn't exist?");
|
|
return false;
|
|
}
|
|
|
|
$this->package->safe_tmp_cleanup(true);
|
|
if ($this->package->ziparchive_mode == DUP_PRO_ZipArchive_Mode::SingleThread) {
|
|
return $this->createSingleThreaded($build_progress);
|
|
} else {
|
|
return $this->createMultiThreaded($build_progress);
|
|
}
|
|
} catch (Exception $ex) {
|
|
DUP_PRO_Log::error("Runtime error in class-package-archive-zip.php.", "Exception: {$ex}");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates the zip file using a single thread approach
|
|
*
|
|
* @param BuildProgress $build_progress A copy of the current build progress
|
|
*
|
|
* @return bool Returns true if the process was successful
|
|
*/
|
|
private function createSingleThreaded(BuildProgress $build_progress)
|
|
{
|
|
$countFiles = 0;
|
|
$compressDir = rtrim(SnapIO::safePath($this->package->Archive->PackDir), '/');
|
|
$sqlPath = $this->package->StorePath . '/' . $this->package->Database->File;
|
|
$zipPath = $this->package->StorePath . '/' . $this->package->Archive->File;
|
|
$filterDirs = empty($this->package->Archive->FilterDirs) ? 'not set' : rtrim(str_replace(';', "\n\t", $this->package->Archive->FilterDirs));
|
|
$filterFiles = empty($this->package->Archive->FilterFiles) ? 'not set' : rtrim(str_replace(';', "\n\t", $this->package->Archive->FilterFiles));
|
|
$filterExts = empty($this->package->Archive->FilterExts) ? 'not set' : $this->package->Archive->FilterExts;
|
|
$filterOn = ($this->package->Archive->FilterOn) ? 'ON' : 'OFF';
|
|
$validation = ($this->global->ziparchive_validation) ? 'ON' : 'OFF';
|
|
$compression = $build_progress->current_build_compression ? 'ON' : 'OFF';
|
|
|
|
$this->zipArchive->setCompressed($build_progress->current_build_compression);
|
|
//PREVENT RETRIES PAST 3: Default is 10 (DUP_PRO_Constants::MAX_BUILD_RETRIES)
|
|
//since this is ST Mode no reason to keep trying like MT
|
|
if ($build_progress->retries >= 3) {
|
|
$err = __('Package build appears stuck so marking package as failed. Is the PHP or Web Server timeouts too low?', 'duplicator-pro');
|
|
DUP_PRO_Log::error(__('Build Failure', 'duplicator-pro'), $err, false);
|
|
DUP_PRO_Log::trace($err);
|
|
return $build_progress->failed = true;
|
|
} else {
|
|
if ($build_progress->retries > 0) {
|
|
DUP_PRO_Log::infoTrace("**NOTICE: Retry count at: {$build_progress->retries}");
|
|
}
|
|
$build_progress->retries++;
|
|
$this->package->update();
|
|
}
|
|
|
|
//LOAD SCAN REPORT
|
|
try {
|
|
$scanReport = $this->package->getScanReportFromJson(DUPLICATOR_PRO_SSDIR_PATH_TMP . "/{$this->package->NameHash}_scan.json");
|
|
} catch (DUP_PRO_NoScanFileException $ex) {
|
|
DUP_PRO_Log::trace("**** scan file doesn't exist!!");
|
|
DUP_PRO_Log::error($ex->getMessage(), '', false);
|
|
$build_progress->failed = true;
|
|
return true;
|
|
} catch (DUP_PRO_NoFileListException $ex) {
|
|
DUP_PRO_Log::trace("**** list of files doesn't exist!!");
|
|
DUP_PRO_Log::error($ex->getMessage(), '', false);
|
|
$build_progress->failed = true;
|
|
return true;
|
|
} catch (DUP_PRO_NoDirListException $ex) {
|
|
DUP_PRO_Log::trace("**** list of directories doesn't exist!!");
|
|
DUP_PRO_Log::error($ex->getMessage(), '', false);
|
|
$build_progress->failed = true;
|
|
return true;
|
|
}
|
|
|
|
//============================================
|
|
//ST: START ZIP
|
|
//============================================
|
|
if ($build_progress->archive_started === false) {
|
|
DUP_PRO_Log::info("\n********************************************************************************");
|
|
DUP_PRO_Log::info("ARCHIVE ZipArchive Single-Threaded");
|
|
DUP_PRO_Log::info("********************************************************************************");
|
|
DUP_PRO_Log::info("ARCHIVE DIR: " . $compressDir);
|
|
DUP_PRO_Log::info("ARCHIVE FILE: " . basename($zipPath));
|
|
DUP_PRO_Log::info("COMPRESSION: *{$compression}*");
|
|
DUP_PRO_Log::info("VALIDATION: *{$validation}*");
|
|
DUP_PRO_Log::info("FILTERS: *{$filterOn}*");
|
|
DUP_PRO_Log::info("DIRS:\t{$filterDirs}");
|
|
DUP_PRO_Log::info("EXTS: {$filterExts}");
|
|
DUP_PRO_Log::info("FILES: {$filterFiles}");
|
|
DUP_PRO_Log::info("----------------------------------------");
|
|
DUP_PRO_Log::info("COMPRESSING");
|
|
DUP_PRO_Log::info("SIZE:\t" . $scanReport->ARC->Size);
|
|
DUP_PRO_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 == '')) {
|
|
DUP_PRO_Log::error('Invalid Scan Report Detected', 'Invalid Scan Report Detected', false);
|
|
return $build_progress->failed = true;
|
|
}
|
|
$build_progress->archive_started = true;
|
|
$build_progress->archive_start_time = DUP_PRO_U::getMicrotime();
|
|
}
|
|
|
|
//============================================
|
|
//ST: ADD DATABASE FILE
|
|
//============================================
|
|
if ($build_progress->archive_has_database === false) {
|
|
if (!$this->zipArchive->open()) {
|
|
DUP_PRO_Log::error("Couldn't open $zipPath", '', false);
|
|
return $build_progress->failed = true;
|
|
}
|
|
|
|
if ($this->zipArchive->addFile($sqlPath, $this->package->get_sql_ark_file_path())) {
|
|
DUP_PRO_Log::info("SQL ADDED: " . basename($sqlPath));
|
|
} else {
|
|
DUP_PRO_Log::error("Unable to add database.sql to archive.", "SQL File Path [" . $sqlPath . "]", false);
|
|
return $build_progress->failed = true;
|
|
}
|
|
|
|
|
|
if ($this->zipArchive->close()) {
|
|
$build_progress->archive_has_database = true;
|
|
$this->package->update();
|
|
} else {
|
|
$err = 'ZipArchive close failure during database.sql phase.';
|
|
$this->setDupArchiveSwitchFix($err);
|
|
return $build_progress->failed = true;
|
|
}
|
|
}
|
|
|
|
//============================================
|
|
//ST: ZIP DIRECTORIES
|
|
//Keep this loop tight: ZipArchive can handle over 10k+ dir entries in under 0.01 seconds.
|
|
//Its really fast without files so no need to do status pushes or other checks in loop
|
|
//============================================
|
|
if ($build_progress->next_archive_dir_index < count($scanReport->ARC->Dirs)) {
|
|
if (!$this->zipArchive->open()) {
|
|
DUP_PRO_Log::error("Couldn't open $zipPath", '', false);
|
|
return $build_progress->failed = true;
|
|
}
|
|
|
|
foreach ($scanReport->ARC->Dirs as $dir) {
|
|
$emptyDir = $this->package->Archive->getLocalDirPath($dir);
|
|
DUP_PRO_Log::trace("ADD DIR TO ZIP: '{$emptyDir}'");
|
|
if (!$this->zipArchive->addEmptyDir($emptyDir)) {
|
|
if (empty($compressDir) || strpos($dir, rtrim($compressDir, '/')) != 0) {
|
|
DUP_PRO_Log::infoTrace("WARNING: Unable to zip directory: '{$dir}'");
|
|
}
|
|
}
|
|
$build_progress->next_archive_dir_index++;
|
|
}
|
|
|
|
if ($this->zipArchive->close()) {
|
|
$this->package->update();
|
|
} else {
|
|
$err = 'ZipArchive close failure during directory add phase.';
|
|
$this->setDupArchiveSwitchFix($err);
|
|
return $build_progress->failed = true;
|
|
}
|
|
}
|
|
|
|
//============================================
|
|
//ST: ZIP FILES
|
|
//============================================
|
|
if ($build_progress->archive_built === false) {
|
|
if ($this->zipArchive->open() === false) {
|
|
DUP_PRO_Log::error("Can not open zip file at: [{$zipPath}]", '', false);
|
|
return $build_progress->failed = true;
|
|
}
|
|
|
|
// Since we have to estimate progress in Single Thread mode
|
|
// set the status when we start archiving just like Shell Exec
|
|
$this->package->set_status(DUP_PRO_PackageStatus::ARCSTART);
|
|
$total_file_size = 0;
|
|
$total_file_count_trip = ($scanReport->ARC->UFileCount + 1000);
|
|
foreach ($scanReport->ARC->Files as $file) {
|
|
//NON-ASCII check
|
|
if (preg_match('/[^\x20-\x7f]/', $file)) {
|
|
if (!$this->isUTF8FileSafe($file)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ($this->global->ziparchive_validation) {
|
|
if (!is_readable($file)) {
|
|
DUP_PRO_Log::infoTrace("NOTICE: File [{$file}] is unreadable!");
|
|
continue;
|
|
}
|
|
}
|
|
|
|
$local_name = $this->package->Archive->getLocalFilePath($file);
|
|
if (!$this->zipArchive->addFile($file, $local_name)) {
|
|
// Assumption is that we continue?? for some things this would be fatal others it would be ok - leave up to user
|
|
DUP_PRO_Log::info("WARNING: Unable to zip file: {$file}");
|
|
continue;
|
|
}
|
|
|
|
$total_file_size += filesize($file);
|
|
|
|
//ST: SERVER THROTTLE
|
|
if ($this->throttleDelayInUs !== 0) {
|
|
usleep($this->throttleDelayInUs);
|
|
}
|
|
|
|
//Prevent Overflow
|
|
if ($countFiles++ > $total_file_count_trip) {
|
|
DUP_PRO_Log::error("ZipArchive-ST: file loop overflow detected at {$countFiles}", '', false);
|
|
return $build_progress->failed = true;
|
|
}
|
|
}
|
|
|
|
//START ARCHIVE CLOSE
|
|
$total_file_size_easy = DUP_PRO_U::byteSize($total_file_size);
|
|
DUP_PRO_Log::trace("Doing final zip close after adding $total_file_size_easy ({$total_file_size})");
|
|
if ($this->zipArchive->close()) {
|
|
DUP_PRO_Log::trace("Final zip closed.");
|
|
$build_progress->next_archive_file_index = $countFiles;
|
|
$build_progress->archive_built = true;
|
|
$this->package->update();
|
|
} else {
|
|
if ($this->global->ziparchive_validation === false) {
|
|
$this->global->ziparchive_validation = true;
|
|
$this->global->save();
|
|
DUP_PRO_Log::infoTrace("**NOTICE: ZipArchive: validation mode enabled");
|
|
} else {
|
|
$err = 'ZipArchive close failure during file phase with file validation enabled';
|
|
$this->setDupArchiveSwitchFix($err);
|
|
return $build_progress->failed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
//============================================
|
|
//ST: LOG FINAL RESULTS
|
|
//============================================
|
|
if ($build_progress->archive_built) {
|
|
$timerAllEnd = DUP_PRO_U::getMicrotime();
|
|
$timerAllSum = DUP_PRO_U::elapsedTime($timerAllEnd, $build_progress->archive_start_time);
|
|
$zipFileSize = @filesize($zipPath);
|
|
DUP_PRO_Log::info("MEMORY STACK: " . DUP_PRO_Server::getPHPMemory());
|
|
DUP_PRO_Log::info("FINAL SIZE: " . DUP_PRO_U::byteSize($zipFileSize));
|
|
DUP_PRO_Log::info("ARCHIVE RUNTIME: {$timerAllSum}");
|
|
|
|
if ($this->zipArchive->open()) {
|
|
$this->package->Archive->file_count = $this->zipArchive->getNumFiles();
|
|
$this->package->update();
|
|
$this->zipArchive->close();
|
|
} else {
|
|
DUP_PRO_Log::error("ZipArchive open failure.", "Encountered when retrieving final archive file count.", false);
|
|
return $build_progress->failed = true;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Creates the zip file using a multi-thread approach
|
|
*
|
|
* @param BuildProgress $build_progress A copy of the current build progress
|
|
*
|
|
* @return bool Returns true if the process was successful
|
|
*/
|
|
private function createMultiThreaded(BuildProgress $build_progress)
|
|
{
|
|
$timed_out = false;
|
|
$countFiles = 0;
|
|
$compressDir = rtrim(SnapIO::safePath($this->package->Archive->PackDir), '/');
|
|
$sqlPath = $this->package->StorePath . '/' . $this->package->Database->File;
|
|
$zipPath = $this->package->StorePath . '/' . $this->package->Archive->File;
|
|
$filterDirs = empty($this->package->Archive->FilterDirs) ? 'not set' : rtrim(str_replace(';', "\n\t", $this->package->Archive->FilterDirs));
|
|
$filterFiles = empty($this->package->Archive->FilterFiles) ? 'not set' : rtrim(str_replace(';', "\n\t", $this->package->Archive->FilterFiles));
|
|
$filterExts = empty($this->package->Archive->FilterExts) ? 'not set' : $this->package->Archive->FilterExts;
|
|
$filterOn = ($this->package->Archive->FilterOn) ? 'ON' : 'OFF';
|
|
$compression = $build_progress->current_build_compression ? 'ON' : 'OFF';
|
|
$this->zipArchive->setCompressed($build_progress->current_build_compression);
|
|
$scanFilepath = DUPLICATOR_PRO_SSDIR_PATH_TMP . "/{$this->package->NameHash}_scan.json";
|
|
|
|
//LOAD SCAN REPORT
|
|
try {
|
|
$scanReport = $this->package->getScanReportFromJson($scanFilepath);
|
|
} catch (DUP_PRO_NoScanFileException $ex) {
|
|
DUP_PRO_Log::trace("**** scan file $scanFilepath doesn't exist!!");
|
|
DUP_PRO_Log::error($ex->getMessage(), '', false);
|
|
$build_progress->failed = true;
|
|
return true;
|
|
} catch (DUP_PRO_NoFileListException $ex) {
|
|
DUP_PRO_Log::trace("**** list of files doesn't exist!!");
|
|
DUP_PRO_Log::error($ex->getMessage(), '', false);
|
|
$build_progress->failed = true;
|
|
return true;
|
|
} catch (DUP_PRO_NoDirListException $ex) {
|
|
DUP_PRO_Log::trace("**** list of directories doesn't exist!!");
|
|
DUP_PRO_Log::error($ex->getMessage(), '', false);
|
|
$build_progress->failed = true;
|
|
return true;
|
|
}
|
|
|
|
//============================================
|
|
//MT: START ZIP & ADD SQL FILE
|
|
//============================================
|
|
if ($build_progress->archive_started === false) {
|
|
DUP_PRO_Log::info("\n********************************************************************************");
|
|
DUP_PRO_Log::info("ARCHIVE Mode:ZipArchive Multi-Threaded");
|
|
DUP_PRO_Log::info("********************************************************************************");
|
|
DUP_PRO_Log::info("ARCHIVE DIR: " . $compressDir);
|
|
DUP_PRO_Log::info("ARCHIVE FILE: " . basename($zipPath));
|
|
DUP_PRO_Log::info("COMPRESSION: *{$compression}*");
|
|
DUP_PRO_Log::info("FILTERS: *{$filterOn}*");
|
|
DUP_PRO_Log::info("DIRS: {$filterDirs}");
|
|
DUP_PRO_Log::info("EXTS: {$filterExts}");
|
|
DUP_PRO_Log::info("FILES: {$filterFiles}");
|
|
DUP_PRO_Log::info("----------------------------------------");
|
|
DUP_PRO_Log::info("COMPRESSING");
|
|
DUP_PRO_Log::info("SIZE:\t" . $scanReport->ARC->Size);
|
|
DUP_PRO_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 == '')) {
|
|
DUP_PRO_Log::error('Invalid Scan Report Detected', 'Invalid Scan Report Detected', false);
|
|
return $build_progress->failed = true;
|
|
}
|
|
|
|
if (!$this->zipArchive->open()) {
|
|
DUP_PRO_Log::error("Couldn't open $zipPath", '', false);
|
|
return $build_progress->failed = true;
|
|
}
|
|
|
|
if ($this->zipArchive->addFile($sqlPath, $this->package->get_sql_ark_file_path())) {
|
|
DUP_PRO_Log::info("SQL ADDED: " . basename($sqlPath));
|
|
} else {
|
|
DUP_PRO_Log::error("Unable to add database.sql to archive.", "SQL File Path [" . $sqlPath . "]", false);
|
|
return $build_progress->failed = true;
|
|
}
|
|
|
|
if ($this->zipArchive->close()) {
|
|
$build_progress->archive_has_database = true;
|
|
$this->package->update();
|
|
} else {
|
|
$err = 'ZipArchive close failure during database.sql phase.';
|
|
$this->setDupArchiveSwitchFix($err);
|
|
return $build_progress->failed = true;
|
|
}
|
|
}
|
|
|
|
//============================================
|
|
//MT: ZIP DIRECTORIES
|
|
//Keep this loop tight: ZipArchive can handle over 10k dir entries in under 0.01 seconds.
|
|
//Its really fast without files no need to do status pushes or other checks in loop
|
|
//============================================
|
|
if ($this->zipArchive->open()) {
|
|
foreach ($scanReport->ARC->Dirs as $dir) {
|
|
$emptyDir = $this->package->Archive->getLocalDirPath($dir);
|
|
DUP_PRO_Log::trace("ADD DIR TO ZIP: '{$emptyDir}'");
|
|
if (!$this->zipArchive->addEmptyDir($emptyDir)) {
|
|
if (empty($compressDir) || strpos($dir, rtrim($compressDir, '/')) != 0) {
|
|
DUP_PRO_Log::infoTrace("WARNING: Unable to zip directory: '{$dir}'");
|
|
}
|
|
}
|
|
$build_progress->next_archive_dir_index++;
|
|
}
|
|
|
|
$this->package->update();
|
|
if ($build_progress->timedOut($this->global->php_max_worker_time_in_sec)) {
|
|
$timed_out = true;
|
|
$diff = time() - $build_progress->thread_start_time;
|
|
DUP_PRO_Log::trace("Timed out after hitting thread time of $diff {$this->global->php_max_worker_time_in_sec} so quitting zipping early in the directory phase");
|
|
}
|
|
} else {
|
|
DUP_PRO_Log::error("Couldn't open $zipPath", '', false);
|
|
return $build_progress->failed = true;
|
|
}
|
|
|
|
if ($this->zipArchive->close() === false) {
|
|
$err = __('ZipArchive close failure during directory add phase.', 'duplicator-pro');
|
|
$this->setDupArchiveSwitchFix($err);
|
|
return $build_progress->failed = true;
|
|
}
|
|
|
|
//============================================
|
|
//MT: ZIP FILES
|
|
//============================================
|
|
if ($timed_out === false) {
|
|
// PREVENT RETRIES (10x)
|
|
if ($build_progress->retries > DUP_PRO_Constants::MAX_BUILD_RETRIES) {
|
|
$err = __('Zip build appears stuck.', 'duplicator-pro');
|
|
$this->setDupArchiveSwitchFix($err);
|
|
|
|
$error_msg = __('Package build appears stuck so marking package failed. Recommend setting Settings > Packages > Archive Engine to DupArchive', 'duplicator-pro');
|
|
DUP_PRO_Log::error(__('Build Failure', 'duplicator-pro'), $error_msg, false);
|
|
DUP_PRO_Log::trace($error_msg);
|
|
return $build_progress->failed = true;
|
|
} else {
|
|
$build_progress->retries++;
|
|
$this->package->update();
|
|
}
|
|
|
|
$zip_is_open = false;
|
|
$total_file_size = 0;
|
|
$incremental_file_size = 0;
|
|
$used_zip_file_descriptor_count = 0;
|
|
$total_file_count = empty($scanReport->ARC->UFileCount) ? 0 : $scanReport->ARC->UFileCount;
|
|
foreach ($scanReport->ARC->Files as $file) {
|
|
if ($zip_is_open || ($countFiles == $build_progress->next_archive_file_index)) {
|
|
if ($zip_is_open === false) {
|
|
DUP_PRO_Log::trace("resuming archive building at file # $countFiles");
|
|
if ($this->zipArchive->open() !== true) {
|
|
DUP_PRO_Log::error("Couldn't open $zipPath", '', false);
|
|
$build_progress->failed = true;
|
|
return true;
|
|
}
|
|
$zip_is_open = true;
|
|
}
|
|
|
|
//NON-ASCII check
|
|
if (preg_match('/[^\x20-\x7f]/', $file)) {
|
|
if (!$this->isUTF8FileSafe($file)) {
|
|
continue;
|
|
}
|
|
} elseif (!file_exists($file)) {
|
|
DUP_PRO_Log::trace("NOTICE: ASCII file [{$file}] does not exist!");
|
|
continue;
|
|
}
|
|
|
|
$local_name = $this->package->Archive->getLocalFilePath($file);
|
|
$file_size = filesize($file);
|
|
$zip_status = $this->zipArchive->addFile($file, $local_name);
|
|
|
|
if ($zip_status) {
|
|
$total_file_size += $file_size;
|
|
$incremental_file_size += $file_size;
|
|
} else {
|
|
// Assumption is that we continue?? for some things this would be fatal others it would be ok - leave up to user
|
|
DUP_PRO_Log::info("WARNING: Unable to zip file: {$file}");
|
|
}
|
|
|
|
$countFiles++;
|
|
$chunk_size_in_bytes = $this->global->ziparchive_chunk_size_in_mb * 1000000;
|
|
if ($incremental_file_size > $chunk_size_in_bytes) {
|
|
// Only close because of chunk size and file descriptors when in legacy mode
|
|
DUP_PRO_Log::trace("closing zip because ziparchive mode = {$this->global->ziparchive_mode} fd count = $used_zip_file_descriptor_count or incremental file size=$incremental_file_size and chunk size = $chunk_size_in_bytes");
|
|
$incremental_file_size = 0;
|
|
$used_zip_file_descriptor_count = 0;
|
|
if ($this->zipArchive->close() == true) {
|
|
$adjusted_percent = floor(DUP_PRO_PackageStatus::ARCSTART + ((DUP_PRO_PackageStatus::ARCDONE - DUP_PRO_PackageStatus::ARCSTART) * ($countFiles / (float) $total_file_count)));
|
|
$build_progress->next_archive_file_index = $countFiles;
|
|
$build_progress->retries = 0;
|
|
$this->package->Status = $adjusted_percent;
|
|
$this->package->update();
|
|
$zip_is_open = false;
|
|
DUP_PRO_Log::trace("closed zip");
|
|
} else {
|
|
$err = 'ZipArchive close failure during file phase using multi-threaded setting.';
|
|
$this->setDupArchiveSwitchFix($err);
|
|
return $build_progress->failed = true;
|
|
}
|
|
}
|
|
|
|
//MT: SERVER THROTTLE
|
|
if ($this->throttleDelayInUs !== 0) {
|
|
usleep($this->throttleDelayInUs);
|
|
}
|
|
|
|
//MT: MAX WORKER TIME (SECS)
|
|
if ($build_progress->timedOut($this->global->php_max_worker_time_in_sec)) {
|
|
// Only close because of timeout
|
|
$timed_out = true;
|
|
$diff = time() - $build_progress->thread_start_time;
|
|
DUP_PRO_Log::trace("Timed out after hitting thread time of $diff so quitting zipping early in the file phase");
|
|
break;
|
|
}
|
|
|
|
//MT: MAX BUILD TIME (MINUTES)
|
|
//Only stop to check on larger files above 100K to avoid checking every single file
|
|
if ($file_size > $this->maxBuildTimeFileSize && $this->optMaxBuildTimeOn) {
|
|
$elapsed_sec = time() - $this->package->timer_start;
|
|
$elapsed_minutes = $elapsed_sec / 60;
|
|
if ($elapsed_minutes > $this->global->max_package_runtime_in_min) {
|
|
DUP_PRO_Log::trace("ZipArchive: Multi-thread max build time {$this->global->max_package_runtime_in_min} minutes reached killing process.");
|
|
return false;
|
|
}
|
|
}
|
|
} else {
|
|
$countFiles++;
|
|
}
|
|
}
|
|
|
|
DUP_PRO_Log::trace("total file size added to zip = $total_file_size");
|
|
if ($zip_is_open) {
|
|
DUP_PRO_Log::trace("Doing final zip close after adding $incremental_file_size");
|
|
if ($this->zipArchive->close()) {
|
|
DUP_PRO_Log::trace("Final zip closed.");
|
|
$build_progress->next_archive_file_index = $countFiles;
|
|
$build_progress->retries = 0;
|
|
$this->package->update();
|
|
} else {
|
|
$err = __('ZipArchive close failure.', 'duplicator-pro');
|
|
$this->setDupArchiveSwitchFix($err);
|
|
DUP_PRO_Log::error($err);
|
|
return $build_progress->failed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//============================================
|
|
//MT: LOG FINAL RESULTS
|
|
//============================================
|
|
if ($timed_out === false) {
|
|
$build_progress->archive_built = true;
|
|
$build_progress->retries = 0;
|
|
$this->package->update();
|
|
$timerAllEnd = DUP_PRO_U::getMicrotime();
|
|
$timerAllSum = DUP_PRO_U::elapsedTime($timerAllEnd, $build_progress->archive_start_time);
|
|
$zipFileSize = @filesize($zipPath);
|
|
DUP_PRO_Log::info("COMPRESSED SIZE: " . DUP_PRO_U::byteSize($zipFileSize));
|
|
DUP_PRO_Log::info("ARCHIVE RUNTIME: {$timerAllSum}");
|
|
DUP_PRO_Log::info("MEMORY STACK: " . DUP_PRO_Server::getPHPMemory());
|
|
if ($this->zipArchive->open() === true) {
|
|
$this->package->Archive->file_count = $this->zipArchive->getNumFiles();
|
|
$this->package->update();
|
|
$this->zipArchive->close();
|
|
} else {
|
|
DUP_PRO_Log::error("ZipArchive open failure.", "Encountered when retrieving final archive file count.", false);
|
|
return $build_progress->failed = true;
|
|
}
|
|
}
|
|
|
|
return !$timed_out;
|
|
}
|
|
|
|
/**
|
|
* Encodes a UTF8 file and then determines if it is safe to add to an archive
|
|
*
|
|
* @param string $file The file to test
|
|
*
|
|
* @return bool Returns true if the file is readable and safe to add to archive
|
|
*/
|
|
private function isUTF8FileSafe($file)
|
|
{
|
|
$is_safe = true;
|
|
$original_file = $file;
|
|
DUP_PRO_Log::trace("[{$file}] is non ASCII");
|
|
// Necessary for adfron type files
|
|
if (DUP_PRO_STR::hasUTF8($file)) {
|
|
$file = utf8_decode($file);
|
|
}
|
|
|
|
if (file_exists($file) === false) {
|
|
if (file_exists($original_file) === false) {
|
|
DUP_PRO_Log::trace("$file CAN'T BE READ!");
|
|
DUP_PRO_Log::info("WARNING: Unable to zip file: {$file}. Cannot be read");
|
|
$is_safe = false;
|
|
}
|
|
}
|
|
|
|
return $is_safe;
|
|
}
|
|
|
|
/**
|
|
* Wrapper for switching to DupArchive quick fix
|
|
*
|
|
* @param string $message The error message
|
|
*
|
|
* @return void
|
|
*/
|
|
private function setDupArchiveSwitchFix($message)
|
|
{
|
|
$fix_text = __('Click to switch archive engine to DupArchive.', 'duplicator-pro');
|
|
|
|
$this->setFix(
|
|
$message,
|
|
$fix_text,
|
|
array(
|
|
'global' => array(
|
|
'archive_build_mode' => DUP_PRO_Archive_Build_Mode::DupArchive,
|
|
),
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Sends an error to the trace and build logs and sets the UI message
|
|
*
|
|
* @param string $message The error message
|
|
* @param string $fix The details for how to fix the issue
|
|
* @param mixed[] $option The options to set
|
|
*
|
|
* @return void
|
|
*/
|
|
private function setFix($message, $fix, $option)
|
|
{
|
|
DUP_PRO_Log::trace($message);
|
|
DUP_PRO_Log::error("$message **FIX: $fix.", '', false);
|
|
$system_global = SystemGlobalEntity::getInstance();
|
|
$system_global->addQuickFix($message, $fix, $option);
|
|
}
|
|
}
|