<?php
namespace App\Cron\Job;

class TinaBackup extends \App\Cron\Job\Utils\GeneralAbstract
{
	/**
	 * Cronjob category: {system|operational|financial|reporting}
	 * @var string
	 */
	protected $category = 'system';


	/**
	 * Backup folder format
	 */
	private $backupFolderFormat = "Y-m-d H.i.s";

	/**
	 * TINA absolute path
	 */
	private $tinaPath = null;

	/**
	 * Files absolute path
	 */
	private $filesPath = null;

	/**
	 * Root backups directory absolute path
	 */
	private $backupsDir = null;

	/**
	 * Backup size, in bytes
	 */
	private $backupSize = 0;

	/**
	 * Backup duration, in seconds
	 * @var float
	 */
	private $backupDuration = 0;

	/**
	 * The previous backup folder
	 * @var string
	 */
	private $previousBackup = null;

	/**
	 * If the backup takes longer than the number of seconds here, add a Warning message
	 * 18000 seconds = 5 hours
	 */
	private $maxBackupDuration = 18000;

	/**
	 * File directories that will be archived
	 * For each directory the following data can be set in its array:
	 *   'compression' -> compression level 0 - 9
	 *   'splitSize'   -> split archive in multiple parts of this size, ex: 100m
	 *   			NOTE: Extracting a splitted archive is problematic, we'll need to reconstruct it by joining all the parts first
	 *   			@see http://unix.stackexchange.com/questions/40480/how-to-unzip-a-multipart-spanned-zip-on-linux
	 * @var array
	 */
	private $archiveDirs = [
		// 'generatedDocuments'    => array('compression' => 0, 'splitSize'=>'1800m'),
		'config'				=> ['compression' => 3],
		'bsp'					=> ['compression' => 3],
//		'logs'					=> array('compression' => 3),
//		'supplierInvoiceFiles'	=> array('compression' => 3),
		'uploadedContracts'		=> ['compression' => 3],
// 		'exportData'			=> array('compression' => 0),
//		'generatedReports'		=> array('compression' => 3),
		'uploadedImages'		=> ['compression' => 0],
// 		'generatedDocuments'    => array('compressor'=>'backupMonthlyFilesDir'),
// 		'importedReservations'	=> array('compressor'=>'backupMonthlyFilesDir'),


		// GDS's
// 		'amadeus'				=> array('compressor'=>'backupMonthlyFilesDir'),
// 		'sabre'					=> array('compressor'=>'backupMonthlyFilesDir'),
// 		'galileo'				=> array('compressor'=>'backupMonthlyFilesDir'),
// 		'apollo'				=> array('compressor'=>'backupMonthlyFilesDir'),
// 		'worldspan'				=> array('compressor'=>'backupMonthlyFilesDir'),
	];

	/**
	 * Constructor
	 */
	public function __construct()
	{
		$CFG = \Engine\Loader_Cfg::getInstance()->getGlobal();

		// TINA path
		$this->tinaPath = cleanPath(APP_REAL_PATH."../");

		// Files path
		$this->filesPath = cleanPath(APP_REAL_PATH.$CFG['paths']['files']);

		// Backups path
		$this->backupsDir = cleanPath(APP_REAL_PATH.$CFG['paths']['files']."backups/");

		parent::__construct();
	}

	/**
	 * {@inheritDoc}
	 * @see \App\Cron\Job\Utils\GeneralAbstract::getDefaultFormData()
	 */
	public function getDefaultFormData()
	{
		$formData = parent::getDefaultFormData();

		$formData['options']['executeTime'] = '02:00:00';

		$formData['settings']['database'] = true;
		$formData['settings']['files'] = array_keys($this->archiveDirs);
		$formData['settings']['backupDays'] = 7;

		return $formData;
	}

	/**
	 * The method returns the form which is needed for the the current cronjob settings saving.
	 */
	public function getSettingsDialog(array $fields)
	{
		$form = parent::getSettingsDialog($fields);

		$_LANG = \App\Loader_Lang::getInstance()->getGlobal();
		$CFG = \Engine\Loader_Cfg::getInstance()->getGlobal();

		$formContainer = \Engine\Form\Container\Fieldset::create($_LANG['Admin']['CronJobs']['Management']['TinaBackup']['editOptions']['legend']);
		$form->setStyle('width', '200');
		$form->addContainer($formContainer);

		// label
		$fldLabel = new \Engine\Form\Input\Label();
		$fldLabel->setStyle('noLabel', true);
		$fldLabel->setDetails("<div style='color:red'>".$_LANG['Admin']['CronJobs']['Management']['TinaBackup']['editOptions']['mainLabel']."</div>");
		$formContainer->addElement($fldLabel);

		// database checkbox
		$fldDb = new \Engine\Form\Input\Checkbox($_LANG['Admin']['CronJobs']['Management']['TinaBackup']['editOptions']['database'], 'database', $fields['settings']['database']);
		$fldDb->setTooltip($_LANG['Admin']['CronJobs']['Management']['TinaBackup']['editOptions']['databaseExplain']);
		$formContainer->addElement($fldDb);

		// archive directories
		$values = [];
		foreach ($this->archiveDirs as $dir=>$options) {
			$values[$dir] = $_LANG['Admin']['CronJobs']['Management']['TinaBackup']['filesCheckboxes'][$dir];
		}

		$fldDir = new \Engine\Form\Input\Checkboxes($_LANG['Admin']['CronJobs']['Management']['TinaBackup']['editOptions']['files'], 'files', $fields['settings']['files'], $values);
		$fldDir->setStyle('renderMode', 'aligned');
		$fldDir->setTooltip($_LANG['Admin']['CronJobs']['Management']['TinaBackup']['editOptions']['filesExplain']);
		$formContainer->addElement($fldDir);

		// backup days
		$fldDays = new \Engine\Form\Input\Text($_LANG['Admin']['CronJobs']['Management']['TinaBackup']['editOptions']['backupDays'], 'backupDays',$fields['settings']['backupDays']);
		$fldDays->setMandatory();
		$fldDays->setValidatorParam('type', 'int');
		$fldDays->setTooltip($_LANG['Admin']['CronJobs']['Management']['TinaBackup']['editOptions']['backupDaysExplanation']);
		$fldDays->setWidth(35);
		$fldDays->setCustomHTML($_LANG['Admin']['CronJobs']['Management']['TinaBackup']['editOptions']['backupDaysDetails']);
		$formContainer->addElement($fldDays);

		// archive location
		$labelLocation = new \Engine\Form\Input\Label();
		$labelLocation->setName(sprintf($_LANG['Admin']['CronJobs']['Management']['TinaBackup']['editOptions']['backupPathDescription'], cleanPath(APP_REAL_PATH.$CFG['paths']['files']."backups/")));
		$labelLocation->setCustomHTML(sprintf($_LANG['Admin']['CronJobs']['Management']['TinaBackup']['editOptions']['backupPathDescription'], "<b>".cleanPath(APP_REAL_PATH.$CFG['paths']['files']."backups/"."</b>")));
		$formContainer->addElement($labelLocation);

		$formContainer->groupInputsInArray("settings");

		return $form;
	}

	/**
	 * {@inheritDoc}
	 * @see \App\Cron\Job\Utils\GeneralAbstract::validateSettings()
	 */
	public function validateSettings(\App\Entity\Messages $msgs, array $fields)
	{
		if ($fields['scheduleOptions']['type']!='scheduled') {
			$msgs->addWarningText("The backup process should run at most once per day", 'type');
		}

		if (\Engine\DateInterval::inInterval($fields['scheduleOptions']['executeTime'], '08:00:00', '20:00:00')) {
			$msgs->addWarningText("The backup process should not run during working hours", 'timeInterval');
		}
	}

	/**
	 * (non-PHPdoc)
	 * @see \App\Cron\Job\Utils\GeneralAbstract::executeJob()
	 */
	protected function executeJob()
	{

		// nothing to archive, or archive duration not set
		if (!$this->settings['database'] && !$this->settings['files']
				|| !$this->settings['backupDays'] || !is_numeric($this->settings['backupDays'])) {
			$this->logger->error('Insufficient data to create backup!');
			return;
		}

		$this->updateStatus(['currentStep' => 'initialize', 'progress'=>0.0]);

		// determine which is the last backup
		$x = \Engine\Dir::getContents($this->backupsDir);
		$backups = $x['dirs']??null;
		if ($backups) {
			sort($backups);
			$this->previousBackup = end($backups);
		}

		// backup directory, create if it doesn't exist
		$backupPath = $this->backupsDir.date($this->backupFolderFormat)."/";

		if (!\Engine\Dir::check($backupPath)) {
			$dir = \Engine\Dir::create($backupPath);
			if (!$dir) {
				$this->logger->error('Backup directory cannot be created, aborting');
				\Log::getInstance()->critical("Backup directory '{$backupPath}' cannot be created");
				return;
			}
		}

		$this->updateStatus(['currentStep' => 'cleanup backups', 'progress'=>0.25]);
		// REMOVE OLD BACKUPS
		$this->removeOldBackups();

		// DATABASE BACKUP
		if ($this->settings['database']) {
			$this->updateStatus(['currentStep' => 'backup: database', 'progress'=>0.30]);
			$this->backupDatabase($backupPath);
		}

		// FILES BACKUP
		if ($this->settings['files']) {
			$dirs = array_intersect_key($this->archiveDirs, $this->settings['files']);

			$totalBackups = count($dirs);
			$currentbackupIndex = 0;
			foreach ($dirs as $directory=>$options) {
				$this->updateStatus( ['currentStep' => sprintf('backup: %s', $directory), 'progress'=>0.40+(1.00-0.40)*($currentbackupIndex/$totalBackups)] );

// 				if ($options['compressor']=='backupMonthlyFilesDir') {
// 					$this->backupMonthlyFilesDir($backupPath, $directory, $options);
// 				} else {	// use the default compressor
// 					$this->backupFilesDir($backupPath, $directory, $options);
// 				}
				$this->backupFilesDir($backupPath, $directory, $options);

				$currentbackupIndex++;
			}
		}
	}

	/**
	 * Post execution
	 */
	protected function postExecute()
	{
		// TOTAL BACKUP SIZE
		$this->logger->info(sprintf("Total size: %s", \Engine\File::formatBytes($this->backupSize)));

		// TOTAL BACKUP DURATION
		$this->logger->info(sprintf("Total duration: %s", $this->formatSeconds($this->backupDuration)));

		if ($this->backupDuration>$this->maxBackupDuration) {
			$this->logger->warning(sprintf("Backup took longer than %s hours", $this->maxBackupDuration/60/60));
		}

		parent::postExecute();
	}

	/**
	 * Remove old backups
	 */
	private function removeOldBackups()
	{
		$oldDirs = \Engine\Dir::getContents($this->backupsDir);

		// check that each backupdir did not exceed settings['backupDays'] lifetime, else delete it
		$maxDirTime = strtotime("-{$this->settings['backupDays']} days");
		$maxDirTime = strtotime(date("Y-m-d 23:59:59", $maxDirTime));
		$maxDirName = date($this->backupFolderFormat, $maxDirTime);

		if ($oldDirs['dirs']) {
			foreach ($oldDirs['dirs'] as $oldDir) {
				// check that it's a folder created by our script, else ignore it
				if (!preg_match("/^(\d{4})-(\d{2})-(\d{2})/", $oldDir)) { // search for format yyyy-mm-dd
					$this->logger->info("{$oldDir} is not a valid backup directory. skipping..");
					continue;
				}

				if (\Engine\Dir::check($this->backupsDir.$oldDir)) {
					if ($oldDir<=$maxDirName) {
						// delete backupdir
						$delDir = \Engine\Dir::delete($this->backupsDir.$oldDir);
						if (!$delDir) {
							$this->logger->error("Could not delete directory '{$oldDir}/'");
						}
					}
				}
				else {
					$this->logger->error("Could not access directory '{$oldDir}/'");
				}
			}
		} else {
			$this->logger->info("No backups have been found, in the given time interval, for deleting.");
		}
	}

	/**
	 * Backup the database
	 * @param string $backupPath
	 */
	private function backupDatabase($backupPath)
	{

		$credentialsObj = new \Engine\Db\Credentials();
		$credentialsObj->loadDefaultCredentials();
		$dbCredentials = $credentialsObj->getCredentials();

		if (!$dbCredentials['host'] || !$dbCredentials['user'] || !$dbCredentials['pass']) {
			$this->logger->error("No database credentials found.");
			return;
		}

		$t1 = microtime(true);

		$outputFile = "database.sql.gz";
		$errFilePath = $backupPath."database.log";
		$outputPath = $backupPath.$outputFile;

		/**
		 * --no-create-db - This option suppresses the CREATE DATABASE statements that are otherwise included in the output
		 * --quick - This option is useful for dumping large tables. It forces mysqldump to retrieve rows for a table from the server a row at a time
		 * 		rather than retrieving the entire row set and buffering it in memory before writing it out.
		 * --single-transaction - This option sends a START TRANSACTION SQL statement to the server before dumping data. It is useful only with transactional
		 * 		tables such as InnoDB, because then it dumps the consistent state of the database at the time when BEGIN was issued without blocking any applications.
		 * --hex-blob - Dump binary columns using hexadecimal notation
		 * --force - Continue even if we get an sql-error (missing report tables basically)
		 *
		 * this command does on-the-fly archiving of the sql data. It depends on gzip to exist
		 * In order to avoid password snooping and the mysq's warning "Using a password on the command line interface can be insecure.", I'm using suggestions from https://stackoverflow.com/questions/20751352/suppress-warning-messages-using-mysql-from-within-terminal-but-password-written
		 * Basically I'm specifying a custom auth file
		 */

		$allTables = \DB::getInstance()->GetCol("SHOW TABLES");
		if (\DB::getInstance()->ErrorNo()) {
			throw new \DBException("Cannot load table list");
		}

		// build the list of regexp for ignored tables
		$regexpsList = [
			"bak_.*", "report_content_.*", "serverStats",
			"errorLogs_archive", "cronJobsLogs", "autoProcess_logs", "exportData_logs ",
			"bspRecords", "bspRecordsData", "bspSettlementRecords",
			"rd_.*", "rdQueue.*", "rdParams",
			"pivotTables.*", "zzUserTraces_.*",
		];
		$regexp = implode("|", $regexpsList);


		// filter existing tables according to the built regexp rules
		$ignoredTables = array_filter($allTables, function($table) use ($regexp) {
			return preg_match("/^({$regexp})$/i", $table);
		});

		$CFG = \Engine\Loader_Cfg::getInstance()->getGlobal();
		$authFilePath = sprintf("%s%s-auth-%s.tmp", $CFG['paths']['temporary'], $this->id, \Engine\RandomGenerator::randomString(15));

		// DON'T use \Enfine\File, as that will give global permissions. We need to keep permissions limited to the current user
		file_put_contents($authFilePath, <<<AUTH
[client]
host="{$dbCredentials['host']}"
user="{$dbCredentials['user']}"
password="{$dbCredentials['pass']}"
AUTH
);
		// some mysqldump versions threw this "World-writable config file" - although the file was not world writable but the OS default access mode
		chmod($authFilePath, 0444);

		try {
			$ignoredTablesArgs = [];

			foreach ($ignoredTables as $table) {
				$ignoredTablesArgs[]= sprintf("--ignore-table=%s", escapeshellarg(sprintf("%s.%s", $dbCredentials['name'], $table)));
			}

			$rawCmd = "mysqldump --defaults-extra-file=__AUTH_FILE__ __IGNORED_TABLES__ --force --no-tablespaces --single-transaction --hex-blob --quick --no-create-db __DB_NAME__ 2>__ERR_FILE__ | gzip -9 > __OUT_FILE__"; // TODO: add --column-statistics=0 also, but this is available only in MySQL 8 or higher (year 2020+)
			$replacements = [
				'__AUTH_FILE__' => 		escapeshellarg($authFilePath),
				'__IGNORED_TABLES__' => implode(" ", $ignoredTablesArgs),
				'__DB_NAME__' => 		escapeshellarg($dbCredentials['name']),
				'__ERR_FILE__' => 		escapeshellarg($errFilePath),
				'__OUT_FILE__' => 		escapeshellarg($outputPath),
			];
			/*
			 * Added --no-tablespaces to supress this error in Itaparica (BR): 'Illegal mix of collations (utf8_general_ci,COERCIBLE) and (utf8_unicode_ci,COERCIBLE) for operation '='' when trying to dump tablespaces mysqldump: Error 1412: Table definition has changed, please retry transaction when dumping table `rdQueueFast` at row: 0 , but it has written 181.5MB in 05m14s
			 */
			$cmd = str_replace(array_keys($replacements), array_values($replacements), $rawCmd);

			$this->execCommand($cmd);

			\Engine\File::setDefaultPermissions($outputPath);
			\Engine\File::setDefaultPermissions($errFilePath);
		}
		catch (\RuntimeException $ex) {
			$this->logger->error($ex->getMessage());
		}

		// do not use \Engine\File::delete, as it checks for permissions
		unlink($authFilePath);

		$time = microtime(true) - $t1;
		$this->backupDuration += $time;

		// check that error file is empty/does not exist
		$errContents = \Engine\File::getContents($errFilePath);
		if ($errContents || !\Engine\File::check($outputPath)) {
			$size = filesize($outputPath);
			$this->backupSize += $size;
			$this->logger->warning(sprintf("Database backup might have failed, mysqldump logged this message: %s, but it has written %s in %s", $errContents, \Engine\File::formatBytes($size), $this->formatSeconds($time)));
		}
		else {
			// database backup size
			$size = filesize($outputPath);
			$this->backupSize += $size;
			$this->logger->success(sprintf("Database successfully backed up. Written %s in %s", \Engine\File::formatBytes($size), $this->formatSeconds($time)));
		}
	}


	/**
	 * Backup a directory from /files/
	 * @param String $directory Directory name
	 */
	private function backupFilesDir($backupPath, $directory, $options)
	{
		$currDir = getcwd();
		$dir = $this->filesPath.$directory."/";

		$outputFile = $directory.".zip";
		$outputPath = $backupPath.$outputFile;

		// ZIP FILES FROM DIRECTORY
		if (\Engine\Dir::check($dir)) {
			chdir($this->tinaPath);
			$t1 = microtime(true);
			try {
				$this->execCommand(sprintf("zip -%sr %s %s %s 2>&1",
					$options['compression'],
					($options['splitSize']) ? sprintf("-s %s", $options['splitSize']) : '',
					escapeshellarg("{$backupPath}{$directory}.zip"),
					escapeshellarg("./files/{$directory}/")
				));

				\Engine\File::create("{$backupPath}{$directory}.md5.txt", md5_file($outputPath));
				\Engine\File::setDefaultPermissions($outputPath);
			}
			catch (\RuntimeException $ex) {
				$this->logger->error($ex->getMessage());
			}

			$time = microtime(true) - $t1;
			$this->backupDuration += $time;

			if (\Engine\File::check($outputPath)) {
				// folder backup size
				$size = filesize($outputPath);
				$this->backupSize += $size;
				$this->logger->success(sprintf("Directory '{$directory}' successfully backed up. Written %s in %s", \Engine\File::formatBytes($size), $this->formatSeconds($time)));
			}
			else {
				$this->logger->error("Directory '{$directory}' backup FAILED");
			}

			chdir($currDir);
		}
		else {
			$this->logger->error("Directory '{$directory}' not found");
		}
	}


	/**
	 * Backup a directory from /files/
	 *
	 * This will split the target folder in 2 parts: old and new data.
	 * The "old data" archive will get re-generated at a less freqent rate than the "new data" archive
	 * The old data represents folders older than 12months, and new data represents data newer than 24 months. As you can see, the periods overlap
	 *
	 * time axis ----------------------------------------------------------------------------> all data
	 *           ___________________________________|___________________|                      this represents old data
	 *                                              |________________________________________> this represents new data
	 *
	 * @param String $directory Directory name
	 */
// 	private function backupMonthlyFilesDir($backupPath, $directory, $options)
// 	{
// 		$newdataSliceSize = "P24M";
// 		$olddataSliceSize = "P12M";
// 		$olddataRebuildPeriod = "P4W";

// 		$currDir = getcwd();
// 		$dir = $this->filesPath.$directory."/";

// 		chdir($this->tinaPath);

// 		$outputFile = $directory.".zip";
// 		$outputPath = $backupPath.$outputFile;

// 		// ZIP FILES FROM DIRECTORY
// 		if (\Engine\Dir::check($dir)) {
// 			// get the folders list
// 			$x = \Engine\Dir::getContents($dir);
// 			$folders = [];
// 			if ($x['dirs']) {
// 				$folders = $x['dirs'];
// 				sort($folders);
// 			}

// 			$newFoldersIntervalStart = \Engine\DateTime::create()->sub(new \DateInterval($newdataSliceSize))->format("Ym");
// 			$oldFoldersIntervalStart = \Engine\DateTime::create()->sub(new \DateInterval($olddataSliceSize))->format("Ym");

// 			// extract the list of new folders
// 			$newFolders = array_filter($folders, function($f) use($newFoldersIntervalStart) {
// 				if (preg_match("/^[0-9]{6}$/", $f)) {
// 					return ($f>$newFoldersIntervalStart);
// 				}

// 				return false;
// 			});

// 			// extract the list of old folders
// 			$oldFolders = array_filter($folders, function($f) use($oldFoldersIntervalStart) {
// 				if (preg_match("/^[0-9]{6}$/", $f)) {
// 					return ($f<=$oldFoldersIntervalStart);
// 				}

// 				return true;
// 			});

// 			/**
// 			 * STEP 1: (mandatory)Compress new folders
// 			 */
// 			$t1 = microtime(true);
// 			$outputPath = "{$backupPath}{$directory}(new)";
// 			try {
// 				$o = $options;
// 				$o['compression'] = 0;
// 				$this->compressSubfolders("./files/{$directory}/", $newFolders, $outputPath, $o);
// 			}
// 			catch (\RuntimeException $ex) {
// 				$this->logger->error($ex->getMessage());
// 			}

// 			$time = microtime(true) - $t1;
// 			$this->backupDuration += $time;

// 			if (\Engine\File::check($outputPath.".zip")) {
// 				// folder backup size
// 				$size = filesize($outputPath.".zip");
// 				$this->backupSize += $size;
// 				$this->logger->success(sprintf("Directory '{$directory}' successfully backed up new data. Written %s in %s", \Engine\File::formatBytes($size), $this->formatSeconds($time)));
// 			} else {
// 				$this->logger->error("Directory '{$directory}' backup FAILED");
// 			}



// 			/**
// 			 * STEP2: (optional) Compress old folders, or copy a previous backup if not too old
// 			 */
// 			if ($oldFolders) {
// 				$t1 = microtime(true);
// 				$previousMetadataFile = $this->backupsDir.$this->previousBackup."/{$directory}(old).ser";
// 				$previousMetadata = [];
// 				if (is_file($previousMetadataFile)) {
// 					$previousMetadata = unserialize(file_get_contents($previousMetadataFile));
// 				}

// 				// check if we have a previous backup defined, and check if the previous backup "original" date is no more than 2 weeks old
// 				$usePreviousBackupCopy = false;
// 				if ($previousMetadata && $previousMetadata['backupTime']) {
// 					if (\Engine\DateTime::create()->sub(new \DateInterval($olddataRebuildPeriod))->formatDatetimeIso()<$previousMetadata['backupTime']) {
// 						$usePreviousBackupCopy = true;
// 					}
// 				}

// 				$outputPath = "{$backupPath}{$directory}(old)";
// 				$backupMetadataPath = "{$outputPath}.ser";
// 				if ($usePreviousBackupCopy) {
// 					// dont create a new backup, copy the previous one
// 					\Engine\File::copy($this->backupsDir.$this->previousBackup."/{$directory}(old).zip", "{$outputPath}.zip");
// 					\Engine\File::copy($this->backupsDir.$this->previousBackup."/{$directory}(old).md5.txt", "{$outputPath}.md5.txt");
// 					\Engine\File::copy($this->backupsDir.$this->previousBackup."/{$directory}(old).ser", $backupMetadataPath);
// 				}
// 				else {
// 					// create a new backup of the old files
// 					try {
// 						$o = $options;
// 						$o['compression'] = 0;
// 						$this->compressSubfolders("./files/{$directory}/", $oldFolders, $outputPath, $options);

// 						\Engine\File::create($backupMetadataPath, serialize(Array(
// 							'backupTime' => \Engine\DateTime::create()->formatDatetimeIso(),
// 							'folders' => $oldFolders,
// 						)));
// 					}
// 					catch (\RuntimeException $ex) {
// 						$this->logger->error($ex->getMessage());
// 					}
// 				}

// 				$time = microtime(true) - $t1;
// 				$this->backupDuration += $time;

// 				if (\Engine\File::check($outputPath.".zip")) {
// 					// folder backup size
// 					$size = filesize($outputPath.".zip");
// 					$this->backupSize += $size;

// 					if ($usePreviousBackupCopy) {
// 						$this->logger->info(sprintf("Directory '{$directory}' successfully copied old backup data. Written %s in %s", \Engine\File::formatBytes($size), $this->formatSeconds($time)));
// 					}
// 					else {
// 						$this->logger->info(sprintf("Directory '{$directory}' successfully backed up old data. Written %s in %s", \Engine\File::formatBytes($size), $this->formatSeconds($time)));
// 					}

// 				} else {
// 					$this->logger->error("Directory '{$directory}' backup FAILED");
// 				}
// 			}
// 			else {
// 				$this->logger->info("No old data have been found in directory '{$directory}'.");
// 			}
// 		}
// 		else {
// 			$this->logger->error("Directory '{$directory}' not found");
// 		}

// 		chdir($currDir);
// 	}

	/**
	 * Compress a list of subfodlers from a certain folder
	 * @param string $baseFolder
	 * @param array $subfolders
	 * @param string $destination
	 * @param array $options
	 */
// 	private function compressSubfolders($baseFolder, $subfolders, $destination, $options)
// 	{
// 		$outputFile = $destination.".zip";
// 		$paramsIncludeFolders = [];

// 		if (!$subfolders) {
// 			$subfolders = Array('.inexistent-backup-folder-name');
// 		}

// 		foreach ($subfolders as $f) {
// 			$paramsIncludeFolders[] = sprintf("-i %s", escapeshellarg("{$baseFolder}{$f}/*"));
// 		}

// 		$this->execCommand(sprintf("zip -%dr %s %s %s %s 2>&1",
// 			$options['compression'],
// 			($options['splitSize']) ? sprintf("-s %s", $options['splitSize']) : '',
// 			escapeshellarg($outputFile),
// 			escapeshellarg($baseFolder),
// 			implode(" ", $paramsIncludeFolders)
// 		));

// 		\Engine\File::create("{$destination}.md5.txt", md5_file($outputFile));
// 		\Engine\File::setDefaultPermissions($outputFile);
// 	}

	/**
	 * Execute system command
	 * @param String $command
	 */
	private function execCommand($command)
	{
		$output = [];
		$returnVar = 0;

		exec($command, $output, $returnVar);

		if ($returnVar) {
			throw new \RuntimeException(implode(",", $output), $returnVar);
		}

		return true;
	}

	/**
	 * Format seconds for display
	 * @param float $seconds
	 * @return string
	 */
	private function formatSeconds($seconds)
	{
		$seconds = (int)$seconds;
		if ($seconds > 60*60) return date('H\hi\ms\s', mktime(0, 0, $seconds));
		else if ($seconds > 60) return date('i\ms\s', mktime(0, 0, $seconds));
		else if ($seconds > 5) return sprintf("%ds", $seconds);
		else return sprintf("%.1fs", $seconds);
	}
}
