src/Backuplay/Artisan/CreateBackup.php
<?php
namespace Astrotomic\Backuplay\Artisan;
use Astrotomic\Backuplay\Events\BackupCreateAfterCommand;
use Astrotomic\Backuplay\Events\BackupCreateAfterScripts;
use Astrotomic\Backuplay\Events\BackupCreateAfterStore;
use Astrotomic\Backuplay\Events\BackupCreateBeforeCommand;
use Astrotomic\Backuplay\Events\BackupCreateBeforeScripts;
use Astrotomic\Backuplay\Events\BackupCreateBeforeStore;
use Astrotomic\Backuplay\Events\BackupCreateFailedScript;
use Astrotomic\Backuplay\Events\BackupCreateFailedStore;
use Astrotomic\Backuplay\Exceptions\FileDoesNotExistException;
use Astrotomic\Backuplay\Helpers\Archive;
use Astrotomic\Backuplay\Helpers\File;
use Astrotomic\Backuplay\Parsers\Filename;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;
/**
* Class CreateBackup.
*/
class CreateBackup extends Command
{
/**
* @var string
*/
protected $name = 'backup:create';
/**
* @var string
*/
protected $description = 'Create and store a new backup';
/**
* @var array
*/
protected $folders;
/**
* @var array
*/
protected $files;
/**
* @return void
* @throws \Astrotomic\Backuplay\Exceptions\EntityIsNoDirectoryException
* @throws \Astrotomic\Backuplay\Exceptions\FileDoesNotExistException
* @throws \Astrotomic\Backuplay\Exceptions\EntityIsNoFileException
* @throws \Symfony\Component\Process\Exception\ProcessFailedException
*/
public function fire()
{
$this->info('start backuplay');
Event::fire(new BackupCreateBeforeCommand($this));
$this->folders = $this->config->getFolders();
$this->comment('backup folders: '.implode(' ', $this->folders));
$this->files = $this->config->getFiles();
$this->comment('backup files: '.implode(' ', $this->files));
if ($this->isValidBackup()) {
$this->runBeforeScripts();
$tempDir = $this->config->getTempDir();
$tempName = md5(uniqid(date('U'))).'.'.$this->config->get('extension');
$tempPath = $tempDir.DIRECTORY_SEPARATOR.$tempName;
$tempMeta = $this->createMetaFile($tempPath);
$zippy = Archive::load();
$archive = $zippy->create($tempPath, [
'backup_info.txt' => $tempMeta,
]);
$this->unlink($tempMeta);
if (count($this->folders) > 0) {
$this->comment('add folders to archive');
foreach ($this->folders as $folder) {
$this->comment('add folder: '.$folder);
$archive->addMembers([
$folder => $folder,
], true);
}
}
if (count($this->files) > 0) {
$this->comment('add files to archive');
foreach ($this->files as $file) {
$this->comment('add file: '.$file);
$archive->addMembers($file, false);
}
}
File::isExisting($tempPath, true);
File::isFile($tempPath, true);
$this->info('created archive');
$this->storeArchive($tempPath);
$this->runAfterScripts();
}
Event::fire(new BackupCreateAfterCommand($this));
$this->info('end backuplay');
}
/**
* @param string $tempPath
* @return string
*/
protected function createMetaFile($tempPath)
{
$tempPath = str_replace($this->config->get('extension'), 'txt', $tempPath);
file_put_contents($tempPath, $this->getMetaContent());
return $tempPath;
}
/**
* @return string
*/
protected function getMetaContent()
{
$content = [];
$content[] = date('Y-m-d H:i:s T');
$content[] = 'Folders:';
if (count($this->folders) > 0) {
foreach ($this->folders as $folder) {
$content[] = '* '.$folder;
}
}
$content[] = 'Files:';
if (count($this->files) > 0) {
foreach ($this->files as $file) {
$content[] = '* '.$file;
}
}
return implode(PHP_EOL, $content);
}
/**
* @param string $tempPath
* @return bool
* @throws \Astrotomic\Backuplay\Exceptions\FileDoesNotExistException
*/
protected function storeArchive($tempPath)
{
$disk = $this->config->get('disk');
if ($disk === false) {
$this->warn('storage is disabled');
return false;
}
$this->comment('store archive on disk: '.$disk);
$filename = new Filename();
foreach ($this->config->get('storage_cycle', []) as $cycle) {
$this->comment('put '.$cycle.' archive in storage');
$filePath = implode(DIRECTORY_SEPARATOR, array_filter([
$this->config->get('storage_path'),
$filename->cycleParse($cycle),
]));
$content = file_get_contents($tempPath);
Event::fire(new BackupCreateBeforeStore($this, $cycle, $filePath, $content));
Storage::disk($disk)->put($filePath, $content);
if (! Storage::disk($disk)->exists($filePath)) {
Event::fire(new BackupCreateFailedStore($this, $cycle, $filePath, $content));
throw new FileDoesNotExistException($filePath);
}
$this->info($cycle.' archive stored');
Event::fire(new BackupCreateAfterStore($this, $cycle, $filePath, $content));
}
$this->unlink($tempPath);
return true;
}
/**
* @return bool
*/
protected function hasFolders()
{
return (bool) (count($this->folders) > 0);
}
/**
* @return bool
*/
protected function hasFiles()
{
return (bool) (count($this->files) > 0);
}
/**
* @return bool
*/
protected function isValidBackup()
{
$valid = (bool) ($this->hasFolders() || $this->hasFiles());
if (! $valid) {
$this->warn('no valid folders or files to backup');
}
return $valid;
}
/**
* @return bool
* @throws \Symfony\Component\Process\Exception\ProcessFailedException
*/
protected function runBeforeScripts()
{
return $this->runScripts('before');
}
/**
* @return bool
* @throws \Symfony\Component\Process\Exception\ProcessFailedException
*/
protected function runAfterScripts()
{
return $this->runScripts('after');
}
/**
* @param string $key
* @return bool
* @throws \Symfony\Component\Process\Exception\ProcessFailedException
*/
protected function runScripts($key)
{
$scripts = $this->config->getScripts($key);
Event::fire(new BackupCreateBeforeScripts($this, $key, $scripts));
if (count($scripts) == 0) {
$this->info("no scripts.{$key} found");
return true;
}
$success = true;
foreach ($scripts as $script) {
$this->info("script.{$key} run: {$script}");
$success = $this->runScript($script) ? $success : false;
}
Event::fire(new BackupCreateAfterScripts($this, $key, $scripts, $success));
return $success;
}
/**
* @param string $script
* @return bool
* @throws \Symfony\Component\Process\Exception\ProcessFailedException
*/
protected function runScript($script)
{
$process = new Process($script);
$process->run();
if (! $process->isSuccessful()) {
Event::fire(new BackupCreateFailedScript($this, $script, $process));
if ($this->config->isStrict()) {
throw new ProcessFailedException($process);
}
$this->error('script failed: '.$script);
return false;
}
$this->comment($process->getOutput());
return true;
}
}