first commit
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
/**
|
||||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\Engine\Base\Exceptions;
|
||||
|
||||
defined('AKEEBAENGINE') || die();
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* An exception which leads to an error (and complete halt) in the backup process
|
||||
*/
|
||||
class ErrorException extends RuntimeException
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
/**
|
||||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\Engine\Base\Exceptions;
|
||||
|
||||
defined('AKEEBAENGINE') || die();
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* An exception which leads to a warning in the backup process
|
||||
*/
|
||||
class WarningException extends RuntimeException
|
||||
{
|
||||
|
||||
}
|
||||
588
administrator/components/com_akeebabackup/engine/Base/Part.php
Normal file
588
administrator/components/com_akeebabackup/engine/Base/Part.php
Normal file
@@ -0,0 +1,588 @@
|
||||
<?php
|
||||
/**
|
||||
* Akeeba Engine
|
||||
*
|
||||
* @package akeebaengine
|
||||
* @copyright Copyright (c)2006-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace Akeeba\Engine\Base;
|
||||
|
||||
defined('AKEEBAENGINE') || die();
|
||||
|
||||
use Akeeba\Engine\Base\Exceptions\ErrorException;
|
||||
use Akeeba\Engine\Factory;
|
||||
use Exception;
|
||||
use Akeeba\Engine\Psr\Log\LogLevel;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Base class for all Akeeba Engine parts.
|
||||
*
|
||||
* Parts are objects which perform a specific function during the backup process, e.g. backing up files or dumping
|
||||
* database contents. They have a fully defined and controlled lifecycle, from initialization to finalization. The
|
||||
* transition between lifecycle phases is handled by the `tick()` method which is essentially the only public interface
|
||||
* to interacting with an engine part.
|
||||
*/
|
||||
abstract class Part
|
||||
{
|
||||
public const STATE_INIT = 0;
|
||||
public const STATE_PREPARED = 1;
|
||||
public const STATE_RUNNING = 2;
|
||||
public const STATE_POSTRUN = 3;
|
||||
public const STATE_FINISHED = 4;
|
||||
public const STATE_ERROR = 99;
|
||||
|
||||
/**
|
||||
* The current state of this part; see the constants at the top of this class
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $currentState = self::STATE_INIT;
|
||||
|
||||
/**
|
||||
* The name of the engine part (a.k.a. Domain), used in return table
|
||||
* generation.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $activeDomain = "";
|
||||
|
||||
/**
|
||||
* The step this engine part is in. Used verbatim in return table and
|
||||
* should be set by the code in the _run() method.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $activeStep = "";
|
||||
|
||||
/**
|
||||
* A more detailed description of the step this engine part is in. Used
|
||||
* verbatim in return table and should be set by the code in the _run()
|
||||
* method.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $activeSubstep = "";
|
||||
|
||||
/**
|
||||
* Any configuration variables, in the form of an array.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_parametersArray = [];
|
||||
|
||||
/**
|
||||
* The database root key
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $databaseRoot = [];
|
||||
|
||||
/**
|
||||
* Should we log the step nesting?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $nest_logging = false;
|
||||
|
||||
/**
|
||||
* Embedded installer preferences
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected $installerSettings;
|
||||
|
||||
/**
|
||||
* How much milliseconds should we wait to reach the min exec time
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $waitTimeMsec = 0;
|
||||
|
||||
/**
|
||||
* Should I ignore the minimum execution time altogether?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $ignoreMinimumExecutionTime = false;
|
||||
|
||||
/**
|
||||
* The last exception thrown during the tick() method's execution.
|
||||
*
|
||||
* @var null|Exception
|
||||
*/
|
||||
protected $lastException = null;
|
||||
|
||||
public function _onSerialize()
|
||||
{
|
||||
$this->lastException = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public constructor
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// Fetch the installer settings
|
||||
$this->installerSettings = (object) [
|
||||
'installerroot' => 'installation',
|
||||
'sqlroot' => 'installation/sql',
|
||||
'databasesini' => 1,
|
||||
'readme' => 1,
|
||||
'extrainfo' => 1,
|
||||
'password' => 0,
|
||||
];
|
||||
|
||||
$config = Factory::getConfiguration();
|
||||
$installerKey = $config->get('akeeba.advanced.embedded_installer');
|
||||
$installerDescriptors = Factory::getEngineParamsProvider()->getInstallerList();
|
||||
|
||||
// Fall back to default ANGIE installer if the selected installer is not found
|
||||
if (!array_key_exists($installerKey, $installerDescriptors))
|
||||
{
|
||||
$installerKey = 'angie';
|
||||
}
|
||||
|
||||
if (array_key_exists($installerKey, $installerDescriptors))
|
||||
{
|
||||
$this->installerSettings = (object) $installerDescriptors[$installerKey];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Nested logging of exceptions
|
||||
*
|
||||
* The message is logged using the specified log level. The detailed information of the Throwable and its trace are
|
||||
* logged using the DEBUG level.
|
||||
*
|
||||
* If the Throwable is nested, its parents are logged recursively. This should create a thorough trace leading to
|
||||
* the root cause of an error.
|
||||
*
|
||||
* @param Exception|Throwable $exception The Exception or Throwable to log
|
||||
* @param string $logLevel The log level to use, default ERROR
|
||||
*/
|
||||
protected static function logErrorsFromException($exception, $logLevel = LogLevel::ERROR)
|
||||
{
|
||||
$logger = Factory::getLog();
|
||||
|
||||
$logger->log($logLevel, $exception->getMessage());
|
||||
|
||||
$logger->debug(sprintf('[%s] %s(%u) – #%u ‹%s›', get_class($exception), $exception->getFile(), $exception->getLine(), $exception->getCode(), $exception->getMessage()));
|
||||
|
||||
foreach (explode("\n", $exception->getTraceAsString()) as $line)
|
||||
{
|
||||
$logger->debug(rtrim($line));
|
||||
}
|
||||
|
||||
$previous = $exception->getPrevious();
|
||||
|
||||
if (!is_null($previous))
|
||||
{
|
||||
self::logErrorsFromException($previous, $logLevel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The public interface to an engine part. This method takes care for
|
||||
* calling the correct method in order to perform the initialisation -
|
||||
* run - finalisation cycle of operation and return a proper response array.
|
||||
*
|
||||
* @param int $nesting
|
||||
*
|
||||
* @return array A response array
|
||||
*/
|
||||
public function tick($nesting = 0)
|
||||
{
|
||||
$configuration = Factory::getConfiguration();
|
||||
$timer = Factory::getTimer();
|
||||
$this->waitTimeMsec = 0;
|
||||
$this->lastException = null;
|
||||
|
||||
/**
|
||||
* Call the right action method, depending on engine part state.
|
||||
*
|
||||
* The action method may throw an exception to signal failure, hence the try-catch. If there is an exception we
|
||||
* will set the part's state to STATE_ERROR and store the last exception.
|
||||
*/
|
||||
try
|
||||
{
|
||||
switch ($this->getState())
|
||||
{
|
||||
case self::STATE_INIT:
|
||||
$this->_prepare();
|
||||
break;
|
||||
|
||||
case self::STATE_PREPARED:
|
||||
case self::STATE_RUNNING:
|
||||
$this->_run();
|
||||
break;
|
||||
|
||||
case self::STATE_POSTRUN:
|
||||
$this->_finalize();
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
$this->lastException = $e;
|
||||
$this->setState(self::STATE_ERROR);
|
||||
}
|
||||
|
||||
// If there is still time, we are not finished and there is no break flag set, re-run the tick()
|
||||
// method.
|
||||
$breakFlag = $configuration->get('volatile.breakflag', false);
|
||||
|
||||
if (
|
||||
!in_array($this->getState(), [self::STATE_FINISHED, self::STATE_ERROR]) &&
|
||||
($timer->getTimeLeft() > 0) &&
|
||||
!$breakFlag &&
|
||||
($nesting < 20) &&
|
||||
($this->nest_logging)
|
||||
)
|
||||
{
|
||||
// Nesting is only applied if $this->nest_logging == true (currently only Kettenrad has this)
|
||||
$nesting++;
|
||||
|
||||
if ($this->nest_logging)
|
||||
{
|
||||
Factory::getLog()->debug("*** Batching successive steps (nesting level $nesting)");
|
||||
}
|
||||
|
||||
return $this->tick($nesting);
|
||||
}
|
||||
|
||||
// Return the output array
|
||||
$out = $this->makeReturnTable();
|
||||
|
||||
// If it's not a nest-logged part (basically, anything other than Kettenrad) return the output array.
|
||||
if (!$this->nest_logging)
|
||||
{
|
||||
return $out;
|
||||
}
|
||||
|
||||
// From here on: things to do for nest-logged parts (i.e. Kettenrad)
|
||||
if ($breakFlag)
|
||||
{
|
||||
Factory::getLog()->debug("*** Engine steps batching: Break flag detected.");
|
||||
}
|
||||
|
||||
// Reset the break flag
|
||||
$configuration->set('volatile.breakflag', false);
|
||||
|
||||
// Log that we're breaking the step
|
||||
Factory::getLog()->debug("*** Batching of engine steps finished. I will now return control to the caller.");
|
||||
|
||||
// Detect whether I need server-side sleep
|
||||
$serverSideSleep = $this->needsServerSideSleep();
|
||||
|
||||
// Enforce minimum execution time
|
||||
if (!$this->ignoreMinimumExecutionTime)
|
||||
{
|
||||
$timer = Factory::getTimer();
|
||||
$this->waitTimeMsec = (int) $timer->enforce_min_exec_time(true, $serverSideSleep);
|
||||
}
|
||||
|
||||
// Send a Return Table back to the caller
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the class's status array
|
||||
*
|
||||
* @return array The response array
|
||||
*/
|
||||
public function getStatusArray()
|
||||
{
|
||||
return $this->makeReturnTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends any kind of setup information to the engine part. Using this,
|
||||
* we avoid passing parameters to the constructor of the class. These
|
||||
* parameters should be passed as an indexed array and should be taken
|
||||
* into account during the preparation process only. This function will
|
||||
* set the error flag if it's called after the engine part is prepared.
|
||||
*
|
||||
* @param array $parametersArray The parameters to be passed to the engine part.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setup($parametersArray)
|
||||
{
|
||||
if ($this->currentState == self::STATE_PREPARED)
|
||||
{
|
||||
$this->setState(self::STATE_ERROR);
|
||||
|
||||
throw new ErrorException(__CLASS__ . ":: Can't modify configuration after the preparation of " . $this->activeDomain);
|
||||
}
|
||||
|
||||
$this->_parametersArray = $parametersArray;
|
||||
|
||||
if (array_key_exists('root', $parametersArray))
|
||||
{
|
||||
$this->databaseRoot = $parametersArray['root'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the state of this engine part.
|
||||
*
|
||||
* @return int The state of this engine part.
|
||||
*/
|
||||
public function getState()
|
||||
{
|
||||
if (!is_null($this->lastException))
|
||||
{
|
||||
$this->currentState = self::STATE_ERROR;
|
||||
}
|
||||
|
||||
return $this->currentState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate the integer state to a string, used by consumers of the public Engine API.
|
||||
*
|
||||
* @param int $state The part state to translate to string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function stateToString($state)
|
||||
{
|
||||
switch ($state)
|
||||
{
|
||||
case self::STATE_ERROR:
|
||||
return 'error';
|
||||
break;
|
||||
|
||||
case self::STATE_INIT:
|
||||
return 'init';
|
||||
break;
|
||||
|
||||
case self::STATE_PREPARED:
|
||||
return 'prepared';
|
||||
break;
|
||||
|
||||
case self::STATE_RUNNING:
|
||||
return 'running';
|
||||
break;
|
||||
|
||||
case self::STATE_POSTRUN:
|
||||
return 'postrun';
|
||||
break;
|
||||
|
||||
case self::STATE_FINISHED:
|
||||
return 'finished';
|
||||
break;
|
||||
}
|
||||
|
||||
return 'init';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current domain of the engine
|
||||
*
|
||||
* @return string The current domain
|
||||
*/
|
||||
public function getDomain()
|
||||
{
|
||||
return $this->activeDomain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current step of the engine
|
||||
*
|
||||
* @return string The current step
|
||||
*/
|
||||
public function getStep()
|
||||
{
|
||||
return $this->activeStep;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current sub-step of the engine
|
||||
*
|
||||
* @return string The current sub-step
|
||||
*/
|
||||
public function getSubstep()
|
||||
{
|
||||
return $this->activeSubstep;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement this if your Engine Part can return the percentage of its work already complete
|
||||
*
|
||||
* @return float A number from 0 (nothing done) to 1 (all done)
|
||||
*/
|
||||
public function getProgress()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the minimum execution time ignore flag.
|
||||
*
|
||||
* DO NOT REMOVE. It is used by the Engine consumers.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isIgnoreMinimumExecutionTime()
|
||||
{
|
||||
return $this->ignoreMinimumExecutionTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of the minimum execution time ignore flag. When set, the nested logging parts (basically,
|
||||
* Kettenrad) will ignore the minimum execution time parameter.
|
||||
*
|
||||
* DO NOT REMOVE. It is used by the Engine consumers.
|
||||
*
|
||||
* @param boolean $ignoreMinimumExecutionTime
|
||||
*/
|
||||
public function setIgnoreMinimumExecutionTime($ignoreMinimumExecutionTime)
|
||||
{
|
||||
$this->ignoreMinimumExecutionTime = $ignoreMinimumExecutionTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs any initialization code. Must set the state to STATE_PREPARED.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract protected function _prepare();
|
||||
|
||||
/**
|
||||
* Runs any finalisation code. Must set the state to STATE_FINISHED.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract protected function _finalize();
|
||||
|
||||
/**
|
||||
* Performs the main objective of this part. While still processing the state must be set to STATE_RUNNING. When the
|
||||
* main objective is complete and we're ready to proceed to finalization the state must be set to STATE_POSTRUN.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract protected function _run();
|
||||
|
||||
/**
|
||||
* Sets the BREAKFLAG, which instructs this engine part that the current step must break immediately,
|
||||
* in fear of timing out.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function setBreakFlag()
|
||||
{
|
||||
$registry = Factory::getConfiguration();
|
||||
$registry->set('volatile.breakflag', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the engine part's internal state, in an easy to use manner
|
||||
*
|
||||
* @param int $state The part state to set
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function setState($state = self::STATE_INIT)
|
||||
{
|
||||
$this->currentState = $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a Response Array based on the engine part's state.
|
||||
*
|
||||
* @return array The Response Array for the current state
|
||||
*/
|
||||
protected function makeReturnTable()
|
||||
{
|
||||
$errors = [];
|
||||
$e = $this->lastException;
|
||||
|
||||
while (!empty($e))
|
||||
{
|
||||
$errors[] = $e->getMessage();
|
||||
$e = $e->getPrevious();
|
||||
}
|
||||
|
||||
return [
|
||||
'HasRun' => $this->currentState != self::STATE_FINISHED,
|
||||
'Domain' => $this->activeDomain,
|
||||
'Step' => $this->activeStep,
|
||||
'Substep' => $this->activeSubstep,
|
||||
'Error' => implode("\n", $errors),
|
||||
'Warnings' => [],
|
||||
'ErrorException' => $this->lastException,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current domain of the engine
|
||||
*
|
||||
* @param string $new_domain The domain to set
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function setDomain($new_domain)
|
||||
{
|
||||
$this->activeDomain = $new_domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current step of the engine
|
||||
*
|
||||
* @param string $new_step The step to set
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function setStep($new_step)
|
||||
{
|
||||
$this->activeStep = $new_step;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current sub-step of the engine
|
||||
*
|
||||
* @param string $new_substep The sub-step to set
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function setSubstep($new_substep)
|
||||
{
|
||||
$this->activeSubstep = $new_substep;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do I need to apply server-side sleep for the time difference between the elapsed time and the minimum execution
|
||||
* time?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function needsServerSideSleep()
|
||||
{
|
||||
/**
|
||||
* If the part doesn't support tagging, i.e. I can't determine if this is a backend backup or not, I will always
|
||||
* use server-side sleep.
|
||||
*/
|
||||
if (!method_exists($this, 'getTag'))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is not a backend backup I will always use server-side sleep. That is to say that legacy front-end,
|
||||
* remote JSON API and CLI backups must always use server-side sleep since they do not support client-side
|
||||
* sleep.
|
||||
*/
|
||||
if (!in_array($this->getTag(), ['backend']))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return Factory::getConfiguration()->get('akeeba.basic.clientsidewait', 0) == 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user