896 lines
20 KiB
PHP
896 lines
20 KiB
PHP
<?php
|
|
/**
|
|
* @package awf
|
|
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
|
|
* @license GNU GPL version 3 or later
|
|
*/
|
|
|
|
namespace Awf\Database;
|
|
|
|
use Awf\Container\Container;
|
|
use Awf\Text\Text;
|
|
use Awf\Timer\Timer;
|
|
|
|
if (!defined('DATA_CHUNK_LENGTH'))
|
|
{
|
|
define('DATA_CHUNK_LENGTH', 65536); // How many bytes to read per step
|
|
define('MAX_QUERY_LINES', 300); // How many lines may be considered to be one query (except text lines)
|
|
}
|
|
|
|
/**
|
|
* Database restoration class. This is a generic SQL script execution handler which takes a (possibly segmented in
|
|
* multiple files), generalised (prefixes written as #__) SQL script and runs it against the database in many small
|
|
* steps.
|
|
*/
|
|
abstract class Restore
|
|
{
|
|
|
|
/**
|
|
* A list of error codes (numbers) which should not block cause the
|
|
* restoration to halt. Used for soft errors and warnings which do not cause
|
|
* problems with the restored site.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $allowedErrorCodes = array();
|
|
|
|
/**
|
|
* A list of comment line delimiters. Lines starting with these strings are
|
|
* skipped over during restoration.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $comment = array();
|
|
|
|
/**
|
|
* A list of the part files of the database dump we are importing
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $partsMap = array();
|
|
|
|
/**
|
|
* The total size of all database dump files
|
|
*
|
|
* @var integer
|
|
*/
|
|
protected $totalSize = 0;
|
|
|
|
/**
|
|
* The part file currently being processed
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $currentPart = null;
|
|
|
|
/**
|
|
* The offset into the part file being processed
|
|
*
|
|
* @var integer
|
|
*/
|
|
protected $fileOffset = 0;
|
|
|
|
/**
|
|
* The total size of all database dump files processed so far
|
|
*
|
|
* @var integer
|
|
*/
|
|
protected $runSize = 0;
|
|
|
|
/**
|
|
* The file pointer to the SQL file currently being restored
|
|
*
|
|
* @var resource
|
|
*/
|
|
protected $file = null;
|
|
|
|
/**
|
|
* The filename of the SQL file currently being restored
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $filename = null;
|
|
|
|
/**
|
|
* The starting line number of processing the current file
|
|
*
|
|
* @var integer
|
|
*/
|
|
protected $start = null;
|
|
|
|
/**
|
|
* The Timer object used to guard against timeouts
|
|
*
|
|
* @var Timer
|
|
*/
|
|
protected $timer = null;
|
|
|
|
/**
|
|
* The database file key used to determine which dump we're restoring
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $dbKey = null;
|
|
|
|
/**
|
|
* The database driver used to connect to this database
|
|
*
|
|
* @var Driver
|
|
*/
|
|
protected $db = null;
|
|
|
|
/**
|
|
* Total queries run so far
|
|
*
|
|
* @var integer
|
|
*/
|
|
protected $totalQueries = null;
|
|
|
|
/**
|
|
* Line number in the current file being processed
|
|
*
|
|
* @var integer
|
|
*/
|
|
protected $lineNumber = null;
|
|
|
|
/**
|
|
* Number of queries run in this restoration step
|
|
*
|
|
* @var integer
|
|
*/
|
|
protected $queries = null;
|
|
|
|
/**
|
|
* The size of the file being processed
|
|
*
|
|
* @var int
|
|
*/
|
|
protected $fileSize = 0;
|
|
|
|
/**
|
|
* Total size of SQL already read from the dump files
|
|
*
|
|
* @var int
|
|
*/
|
|
protected $totalSizeRead = 0;
|
|
|
|
/**
|
|
* The container this database restoration class is attached to
|
|
*
|
|
* @var Container
|
|
*/
|
|
protected $container = null;
|
|
|
|
/** @var array Internal cache for Restore instances */
|
|
protected static $instances = array();
|
|
|
|
/**
|
|
* Public constructor. Initialises the database restoration engine.
|
|
*
|
|
* @param Container $container The container we are attached to. You need a dbrestore array in it.
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
public function __construct(Container $container)
|
|
{
|
|
if (!isset($container['dbrestore']))
|
|
{
|
|
throw new \Exception(Text::_('AWF_RESTORE_ERROR_NORESTOREDATAINCONTAINER'), 500);
|
|
}
|
|
|
|
$this->container = $container;
|
|
|
|
if(!isset($container['dbrestore']['dbkey']))
|
|
{
|
|
throw new \Exception(Text::_('AWF_RESTORE_ERROR_NORESTOREDBKEYINCONTAINER'), 500);
|
|
}
|
|
|
|
$this->dbKey = $container['dbrestore']['dbkey'];
|
|
|
|
$maxExecTime = isset($container['dbrestore']['maxexectime']) ? (int)$container['dbrestore']['maxexectime'] : 5;
|
|
$runTimeBias = isset($container['dbrestore']['runtimebias']) ? (int)$container['dbrestore']['runtimebias'] : 75;
|
|
|
|
$maxExecTime = ($maxExecTime < 1) ? 1 : $maxExecTime;
|
|
$runTimeBias = ($runTimeBias < 10) ? 10 : $runTimeBias;
|
|
|
|
$this->timer = new Timer($maxExecTime, $runTimeBias);
|
|
|
|
$this->populatePartsMap();
|
|
}
|
|
|
|
/**
|
|
* Public destructor. Closes open handlers.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function __destruct()
|
|
{
|
|
if (is_object($this->db))
|
|
{
|
|
if ($this->db instanceof Driver)
|
|
{
|
|
try
|
|
{
|
|
$this->db->disconnect();
|
|
}
|
|
catch (\Exception $exc)
|
|
{
|
|
// Nothing. We just never want to fail when closing the
|
|
// database connection.
|
|
}
|
|
}
|
|
}
|
|
|
|
if (is_resource($this->file))
|
|
{
|
|
@fclose($this->file);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets an instance of the database restoration class based on the container.
|
|
*
|
|
* @staticvar array $instances The array of \Awf\Database\Restore instances
|
|
*
|
|
* @param Container $container The container the class is attached to
|
|
*
|
|
* @return Restore
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
public static function getInstance(Container $container)
|
|
{
|
|
if (!isset($container['dbrestore']))
|
|
{
|
|
throw new \Exception(Text::_('AWF_RESTORE_ERROR_NORESTOREDATAINCONTAINER'), 500);
|
|
}
|
|
|
|
if(!isset($container['dbrestore']['dbkey']))
|
|
{
|
|
throw new \Exception(Text::_('AWF_RESTORE_ERROR_NORESTOREDBKEYINCONTAINER'), 500);
|
|
}
|
|
|
|
$dbkey = $container['dbrestore']['dbkey'];
|
|
|
|
if (!array_key_exists($dbkey, self::$instances))
|
|
{
|
|
if(!isset($container['dbrestore']['dbtype']))
|
|
{
|
|
throw new \Exception(Text::_('AWF_RESTORE_ERROR_NORESTOREDBTYPEINCONTAINER'), 500);
|
|
}
|
|
|
|
$class = '\\Awf\\Database\\Restore\\' . ucfirst($container['dbrestore']['dbtype']);
|
|
|
|
if(!class_exists($class, true))
|
|
{
|
|
throw new \Exception(Text::_('AWF_RESTORE_ERROR_RESTORECLASSNOTEXISTS'), 500);
|
|
}
|
|
|
|
self::$instances[$dbkey] = new $class($container);
|
|
}
|
|
|
|
return self::$instances[$dbkey];
|
|
}
|
|
|
|
/**
|
|
* Remove all cached information from the session storage
|
|
*/
|
|
protected function removeInformationFromStorage()
|
|
{
|
|
$variables = array(
|
|
'start', 'foffset', 'totalqueries', 'curpart',
|
|
'partsmap', 'totalsize', 'runsize'
|
|
);
|
|
|
|
$session = $this->container->segment;
|
|
|
|
foreach ($variables as $var)
|
|
{
|
|
$key = 'restore_' . $this->dbKey . '_' . $var;
|
|
$session->$key = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return a value from the session storage
|
|
*
|
|
* @param string $var The name of the variable
|
|
* @param mixed $default The default value (null if ommitted)
|
|
*
|
|
* @return mixed The variable's value
|
|
*/
|
|
protected function getFromStorage($var, $default = null)
|
|
{
|
|
$session = $this->container->segment;
|
|
|
|
$key = 'restore_' . $this->dbKey . '_' . $var;
|
|
|
|
if (!isset($session->$key))
|
|
{
|
|
$session->$key = $default;
|
|
}
|
|
|
|
return $session->$key;
|
|
}
|
|
|
|
/**
|
|
* Sets a value to the session storage
|
|
*
|
|
* @param string $var The name of the variable
|
|
* @param mixed $value The value to store
|
|
*/
|
|
protected function setToStorage($var, $value)
|
|
{
|
|
$session = $this->container->segment;
|
|
|
|
$key = 'restore_' . $this->dbKey . '_' . $var;
|
|
|
|
$session->$key = $value;
|
|
}
|
|
|
|
/**
|
|
* Gets a database configuration variable as cached in the container
|
|
*
|
|
* @param string $key The name of the variable to get
|
|
* @param mixed $default Default value (null if skipped)
|
|
*
|
|
* @return mixed The configuration variable's value
|
|
*/
|
|
protected function getParam($key, $default = null)
|
|
{
|
|
if (array_key_exists($key, $this->container['dbrestore']))
|
|
{
|
|
return $this->container['dbrestore'][$key];
|
|
}
|
|
else
|
|
{
|
|
return $default;
|
|
}
|
|
}
|
|
|
|
protected function populatePartsMap()
|
|
{
|
|
// Nothing to do if it's already populated, right?
|
|
if (!empty($this->partsMap))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// First, try to fetch from the session storage
|
|
$this->totalSize = $this->getFromStorage('totalsize', 0);
|
|
$this->runSize = $this->getFromStorage('runsize', 0);
|
|
$this->partsMap = $this->getFromStorage('partsmap', array());
|
|
$this->currentPart = $this->getFromStorage('curpart', 0);
|
|
$this->fileOffset = $this->getFromStorage('foffset', 0);
|
|
$this->start = $this->getFromStorage('start', 0);
|
|
$this->totalQueries = $this->getFromStorage('totalqueries', 0);
|
|
|
|
// If that didn't work try a full initalisation
|
|
if (empty($this->partsMap))
|
|
{
|
|
if(!isset($this->container['dbrestore']['sqlfile']))
|
|
{
|
|
throw new \RuntimeException('AWF_RESTORE_ERROR_NORESTOREFILEINCONTAINER', 500);
|
|
}
|
|
|
|
$sqlfile = $this->container['dbrestore']['sqlfile'];
|
|
|
|
$parts = $this->getParam('parts', 1);
|
|
|
|
$this->partsMap = array();
|
|
$path = $this->container->sqlPath;
|
|
$this->totalSize = 0;
|
|
$this->runSize = 0;
|
|
$this->currentPart = 0;
|
|
$this->fileOffset = 0;
|
|
|
|
for ($index = 0; $index <= $parts; $index++)
|
|
{
|
|
if ($index == 0)
|
|
{
|
|
$basename = $sqlfile;
|
|
}
|
|
else
|
|
{
|
|
$basename = substr($sqlfile, 0, -4) . '.s' . sprintf('%02u', $index);
|
|
}
|
|
|
|
$file = $path . '/' . $basename;
|
|
|
|
if (!file_exists($file))
|
|
{
|
|
$file = 'sql/' . $basename;
|
|
}
|
|
|
|
$filesize = @filesize($file);
|
|
$this->totalSize += intval($filesize);
|
|
$this->partsMap[] = $file;
|
|
}
|
|
|
|
$this->setToStorage('totalsize', $this->totalSize);
|
|
$this->setToStorage('runsize', $this->runSize);
|
|
$this->setToStorage('partsmap', $this->partsMap);
|
|
$this->setToStorage('curpart', $this->currentPart);
|
|
$this->setToStorage('foffset', $this->fileOffset);
|
|
$this->setToStorage('start', $this->start);
|
|
$this->setToStorage('totalqueries', $this->totalQueries);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Proceeds to opening the next SQL part file
|
|
*
|
|
* @return bool True on success
|
|
*/
|
|
protected function getNextFile()
|
|
{
|
|
$parts = $this->getParam('parts', 1);
|
|
|
|
if ($this->currentPart >= ($parts - 1))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
$this->currentPart++;
|
|
$this->fileOffset = 0;
|
|
|
|
$this->setToStorage('curpart', $this->currentPart);
|
|
$this->setToStorage('foffset', $this->fileOffset);
|
|
|
|
return $this->openFile();
|
|
}
|
|
|
|
/**
|
|
* Opens the SQL part file whose ID is specified in the $curpart variable
|
|
* and updates the $file, $start and $foffset variables.
|
|
*
|
|
* @return boolean True on success
|
|
*
|
|
* @throws \Exception When an error occurs
|
|
*/
|
|
protected function openFile()
|
|
{
|
|
if (!is_numeric($this->currentPart))
|
|
{
|
|
$this->currentPart = 0;
|
|
}
|
|
$this->filename = $this->partsMap[$this->currentPart];
|
|
|
|
if (!$this->file = @fopen($this->filename, "r"))
|
|
{
|
|
throw new \Exception(Text::sprintf('AWF_RESTORE_ERROR_CANTOPENDUMPFILE', $this->filename));
|
|
}
|
|
else
|
|
{
|
|
// Get the file size
|
|
if (fseek($this->file, 0, SEEK_END) == 0)
|
|
{
|
|
$this->fileSize = ftell($this->file);
|
|
}
|
|
else
|
|
{
|
|
throw new \Exception(Text::_('AWF_RESTORE_ERROR_UNKNOWNFILESIZE'));
|
|
}
|
|
}
|
|
|
|
// Check start and foffset are numeric values
|
|
if (!is_numeric($this->start) || !is_numeric($this->fileOffset))
|
|
{
|
|
throw new \Exception(Text::_('AWF_RESTORE_ERROR_INVALIDPARAMETERS'));
|
|
}
|
|
|
|
$this->start = floor($this->start);
|
|
$this->fileOffset = floor($this->fileOffset);
|
|
|
|
// Check $foffset upon $filesize
|
|
if ($this->fileOffset > $this->fileSize)
|
|
{
|
|
throw new \Exception(Text::_('AWF_RESTORE_ERROR_AFTEREOF'));
|
|
}
|
|
|
|
// Set file pointer to $foffset
|
|
if (fseek($this->file, $this->fileOffset) != 0)
|
|
{
|
|
throw new \Exception(Text::_('AWF_RESTORE_ERROR_CANTSETOFFSET'));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns the instance of the database driver, creating it if it doesn't
|
|
* exist.
|
|
*
|
|
* @return Driver
|
|
*
|
|
* @throws \RuntimeException
|
|
*/
|
|
protected function getDatabase()
|
|
{
|
|
if (!is_object($this->db))
|
|
{
|
|
$options = array(
|
|
'driver' => $this->container['dbrestore']['dbtype'],
|
|
'database' => $this->container['dbrestore']['dbname'],
|
|
'select' => 0,
|
|
'host' => $this->container['dbrestore']['dbhost'],
|
|
'user' => $this->container['dbrestore']['dbuser'],
|
|
'password' => $this->container['dbrestore']['dbpass'],
|
|
'prefix' => $this->container['dbrestore']['prefix'],
|
|
);
|
|
|
|
$class = '\\Awf\\Database\\Driver\\' . ucfirst(strtolower($options['driver']));
|
|
|
|
$this->db = new $class($options);
|
|
$this->db->setUTF();
|
|
}
|
|
|
|
return $this->db;
|
|
}
|
|
|
|
/**
|
|
* Executes a SQL statement, ignoring errors in the $allowedErrorCodes list.
|
|
*
|
|
* @param string $sql The SQL statement to execute
|
|
*
|
|
* @return mixed A database cursor on success, false on failure
|
|
*
|
|
* @throws \Exception On error
|
|
*/
|
|
protected function execute($sql)
|
|
{
|
|
$db = $this->getDatabase();
|
|
|
|
try
|
|
{
|
|
$db->setQuery($sql);
|
|
$result = $db->execute();
|
|
}
|
|
catch (\Exception $exc)
|
|
{
|
|
$result = false;
|
|
if (!in_array($exc->getCode(), $this->allowedErrorCodes))
|
|
{
|
|
// Format the error message and throw it again
|
|
$message = '<h2>' . Text::sprintf('AWF_RESTORE_ERROR_ERRORATLINE', $this->lineNumber) . '</h2>' . "\n";
|
|
$message .= '<p>' . Text::_('AWF_RESTORE_ERROR_MYSQLERROR') . '</p>' . "\n";
|
|
$message .= '<code>ErrNo #' . htmlspecialchars($exc->getCode()) . '</code>' . "\n";
|
|
$message .= '<pre>' . htmlspecialchars($exc->getMessage()) . '</pre>' . "\n";
|
|
$message .= '<p>' . Text::_('AWF_RESTORE_ERROR_RAWQUERY') . '</p>' . "\n";
|
|
$message .= '<pre>' . htmlspecialchars($sql) . '</pre>' . "\n";
|
|
|
|
// Rethrow the exception if we're not supposed to handle it
|
|
throw new \Exception($message);
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Read the next line from the database dump
|
|
*
|
|
* @return string The query string
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
protected function readNextLine()
|
|
{
|
|
$parts = $this->getParam('parts', 1);
|
|
|
|
$query = "";
|
|
|
|
while (!feof($this->file) && (strpos($query, "\n") === false))
|
|
{
|
|
$query .= fgets($this->file, DATA_CHUNK_LENGTH);
|
|
}
|
|
|
|
// An empty query is EOF. Are we done or should I skip to the next file?
|
|
if (empty($query) || ($query === false))
|
|
{
|
|
if ($this->currentPart >= ($parts - 1))
|
|
{
|
|
throw new \Exception('All done', 200);
|
|
}
|
|
else
|
|
{
|
|
// Register the bytes read
|
|
$current_foffset = @ftell($this->file);
|
|
|
|
if (is_null($this->fileOffset))
|
|
{
|
|
$this->fileOffset = 0;
|
|
}
|
|
|
|
$this->runSize = (is_null($this->runSize) ? 0 : $this->runSize) + ($current_foffset - $this->fileOffset);
|
|
|
|
// Get the next file
|
|
$this->getNextFile();
|
|
|
|
// Rerun the fetcher
|
|
throw new \Exception('Continue', 201);
|
|
}
|
|
}
|
|
|
|
if (substr($query, -1) != "\n")
|
|
{
|
|
// We read more data than we should. Roll back the file...
|
|
$newLinePos = strpos($query, "\n");
|
|
|
|
if ($newLinePos !== false)
|
|
{
|
|
$queryLength = strlen($query);
|
|
$rollback = $queryLength - $newLinePos;
|
|
fseek($this->file, -$rollback, SEEK_CUR);
|
|
// ...and chop the line
|
|
$query = substr($query, 0, $rollback);
|
|
}
|
|
}
|
|
|
|
// Handle DOS linebreaks
|
|
$query = str_replace("\r\n", "\n", $query);
|
|
$query = str_replace("\r", "\n", $query);
|
|
|
|
// Skip comments and blank lines only if NOT in parentheses
|
|
$skipline = false;
|
|
reset($this->comment);
|
|
|
|
foreach ($this->comment as $comment_value)
|
|
{
|
|
if (trim($query) == "" || strpos($query, $comment_value) === 0)
|
|
{
|
|
$skipline = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ($skipline)
|
|
{
|
|
$this->lineNumber++;
|
|
throw new \Exception('Continue', 201);
|
|
}
|
|
|
|
$query = trim($query, " \n");
|
|
$query = rtrim($query, ';');
|
|
|
|
return $query;
|
|
}
|
|
|
|
/**
|
|
* Runs a restoration step and returns an array to be used in the response.
|
|
*
|
|
* @return array
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
public function stepRestoration()
|
|
{
|
|
$parts = $this->getParam('parts', 1);
|
|
$this->openFile();
|
|
|
|
$this->lineNumber = $this->start;
|
|
$this->totalSizeRead = 0;
|
|
$this->queries = 0;
|
|
|
|
while ($this->timer->getTimeLeft() > 0)
|
|
{
|
|
$query = '';
|
|
|
|
// Get the next query line
|
|
try
|
|
{
|
|
$query = $this->readNextLine();
|
|
}
|
|
catch (\Exception $exc)
|
|
{
|
|
if ($exc->getCode() == 200)
|
|
{
|
|
break;
|
|
}
|
|
elseif ($exc->getCode() == 201)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (empty($query))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Update variables
|
|
$this->totalSizeRead += strlen($query);
|
|
$this->totalQueries++;
|
|
$this->queries++;
|
|
$this->lineNumber++;
|
|
|
|
// Process the query line, running drop/rename queries as necessary
|
|
$this->processQueryLine($query);
|
|
}
|
|
|
|
// Get the current file position
|
|
$current_foffset = ftell($this->file);
|
|
|
|
if ($current_foffset === false)
|
|
{
|
|
if (is_resource($this->file))
|
|
{
|
|
@fclose($this->file);
|
|
}
|
|
|
|
throw new \Exception(Text::_('AWF_RESTORE_ERROR_CANTREADPOINTER'));
|
|
}
|
|
else
|
|
{
|
|
if (is_null($this->fileOffset))
|
|
{
|
|
$this->fileOffset = 0;
|
|
}
|
|
|
|
$bytes_in_step = $current_foffset - $this->fileOffset;
|
|
$this->runSize = (is_null($this->runSize) ? 0 : $this->runSize) + $bytes_in_step;
|
|
$this->fileOffset = $current_foffset;
|
|
}
|
|
|
|
// Return statistics
|
|
$bytes_togo = $this->totalSize - $this->runSize;
|
|
|
|
// Check for global EOF
|
|
if (($this->currentPart >= ($parts - 1)) && feof($this->file))
|
|
{
|
|
$bytes_togo = 0;
|
|
}
|
|
|
|
// Save variables in storage
|
|
$this->setToStorage('start', $this->start);
|
|
$this->setToStorage('foffset', $this->fileOffset);
|
|
$this->setToStorage('totalqueries', $this->totalQueries);
|
|
$this->setToStorage('runsize', $this->runSize);
|
|
|
|
if ($bytes_togo == 0)
|
|
{
|
|
// Clear stored variables if we're finished
|
|
$this->removeInformationFromStorage();
|
|
}
|
|
|
|
// Calculate estimated time
|
|
$bytesPerSecond = $bytes_in_step / $this->timer->getRunningTime();
|
|
|
|
if ($bytesPerSecond <= 0.01)
|
|
{
|
|
$remainingSeconds = 120;
|
|
}
|
|
else
|
|
{
|
|
$remainingSeconds = round($bytes_togo / $bytesPerSecond, 0);
|
|
}
|
|
|
|
// Return meaningful data
|
|
return array(
|
|
'percent' => round(100 * ($this->runSize / $this->totalSize), 1),
|
|
'restored' => $this->sizeformat($this->runSize),
|
|
'total' => $this->sizeformat($this->totalSize),
|
|
'queries_restored' => $this->totalQueries,
|
|
'current_line' => $this->lineNumber,
|
|
'current_part' => $this->currentPart,
|
|
'total_parts' => $parts,
|
|
'eta' => $this->etaformat($remainingSeconds),
|
|
'error' => '',
|
|
'done' => ($bytes_togo == 0) ? '1' : '0'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Processes the query line in the best way each restoration engine sees
|
|
* fit. This method is supposed to take care of backing up and dropping
|
|
* tables, changing table collation if requested and converting INSERT to
|
|
* REPLACE if requested. It is also supposed to execute $query against the
|
|
* database, replacing the metaprefix #__ with the real prefix.
|
|
*
|
|
* @param string $query
|
|
*
|
|
* @return string The processed query
|
|
*/
|
|
abstract protected function processQueryLine($query);
|
|
|
|
/**
|
|
* Format a raw time in seconds as a human readable string
|
|
*
|
|
* @param integer $Raw Time in seconds
|
|
* @param string $measureby Unit of measurement, leave blank to auto-detect
|
|
*
|
|
* @return string Human readable time string
|
|
*/
|
|
private function etaformat($Raw, $measureby = '')
|
|
{
|
|
$Clean = abs($Raw);
|
|
|
|
$calcNum = array(
|
|
array('s', 60),
|
|
array('m', 60 * 60),
|
|
array('h', 60 * 60 * 60),
|
|
array('d', 60 * 60 * 60 * 24),
|
|
array('y', 60 * 60 * 60 * 24 * 365)
|
|
);
|
|
|
|
$calc = array(
|
|
's' => array(1, 'second'),
|
|
'm' => array(60, 'minute'),
|
|
'h' => array(60 * 60, 'hour'),
|
|
'd' => array(60 * 60 * 24, 'day'),
|
|
'y' => array(60 * 60 * 24 * 365, 'year')
|
|
);
|
|
|
|
if ($measureby == '')
|
|
{
|
|
$usemeasure = 's';
|
|
|
|
for ($i = 0; $i < count($calcNum); $i++)
|
|
{
|
|
if ($Clean <= $calcNum[$i][1])
|
|
{
|
|
$usemeasure = $calcNum[$i][0];
|
|
$i = count($calcNum);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$usemeasure = $measureby;
|
|
}
|
|
|
|
$datedifference = floor($Clean / $calc[$usemeasure][0]);
|
|
|
|
if ($datedifference == 1)
|
|
{
|
|
return $datedifference . ' ' . $calc[$usemeasure][1];
|
|
}
|
|
else
|
|
{
|
|
return $datedifference . ' ' . $calc[$usemeasure][1] . 's';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the cached total size of the SQL dump.
|
|
*
|
|
* @param boolean $use_units Should I automatically figure out the unit of measurement
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getTotalSize($use_units = false)
|
|
{
|
|
$size = $this->totalSize;
|
|
|
|
if ($use_units)
|
|
{
|
|
$size = $this->sizeformat($size);
|
|
}
|
|
|
|
return $size;
|
|
}
|
|
|
|
/**
|
|
* Format a size in bytes into a human readable format
|
|
*
|
|
* @param string $size The size in bytes
|
|
*
|
|
* @return string The human readable size string
|
|
*/
|
|
private function sizeformat($size)
|
|
{
|
|
if ($size < 0)
|
|
{
|
|
return 0;
|
|
}
|
|
$unit = array('b', 'KB', 'MB', 'GB', 'TB', 'PB');
|
|
$i = floor(log($size, 1024));
|
|
if (($i < 0) || ($i > 5))
|
|
{
|
|
$i = 0;
|
|
}
|
|
|
|
return @round($size / pow(1024, ($i)), 2) . ' ' . $unit[$i];
|
|
}
|
|
}
|