Static code analysis.

This commit is contained in:
Jens True 2023-05-31 09:00:20 +00:00
parent 4c8f71d9d8
commit 19f27e6d0c
10 changed files with 1798 additions and 43 deletions

11
Makefile Normal file

@ -0,0 +1,11 @@
analyze-all:
@echo PHPMessDetector
-./vendor/bin/phpmd src text cleancode,codesize,controversial,design,naming,unusedcode
@echo PHPStan
-./vendor/bin/phpstan analyze --level=7 src/ backup
@echo Psalm
-./vendor/bin/psalm
install:
php composer.phar install --no-dev
install-dev:
php composer.phar install

3
backup

@ -22,9 +22,10 @@ if (! $autoload) {
require $autoload; require $autoload;
$package = \Composer\InstalledVersions::getRootPackage();
use Symfony\Component\Console\Application; use Symfony\Component\Console\Application;
$application = new Application(); $application = new Application('backup', $package['version']);
$application->add(new App\CommandBackup()); $application->add(new App\CommandBackup());
$application->add(new App\CommandShow()); $application->add(new App\CommandShow());

@ -1,4 +1,8 @@
{ {
"name": "furyfire/backup",
"description": "Wrapper for Rclone based backup",
"homepage": "https://jcktrue.dk",
"version": "0.1.0",
"require": { "require": {
"symfony/console": "^5.4", "symfony/console": "^5.4",
"symfony/yaml": "^5.4", "symfony/yaml": "^5.4",
@ -13,6 +17,8 @@
}, },
"require-dev": { "require-dev": {
"squizlabs/php_codesniffer": "*", "squizlabs/php_codesniffer": "*",
"phpstan/phpstan": "^1.10" "phpstan/phpstan": "^1.10",
"vimeo/psalm": "^5.12",
"phpmd/phpmd": "^2.13"
} }
} }

1724
composer.lock generated

File diff suppressed because it is too large Load Diff

@ -1,6 +1,8 @@
notification: notification:
domain: ntfy.jcktrue.dk domain: ntfy.jcktrue.dk
topic: backup topic: backup
rclone:
bwlimit: 6M
backup: backup:
- title: Example - title: Example
source: test/src source: test/src

17
psalm.xml Normal file

@ -0,0 +1,17 @@
<?xml version="1.0"?>
<psalm
errorLevel="2"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
findUnusedBaselineEntry="true"
>
<projectFiles>
<directory name="src/" />
<file name="backup" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
</psalm>

@ -5,10 +5,15 @@ use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
use Symfony\Component\Yaml\Exception\ParseException; use Symfony\Component\Yaml\Exception\ParseException;
use Twig\Environment;
use Twig\Loader\ArrayLoader;
use DateTime;
class CommandBackup extends Command class CommandBackup extends Command
{ {
static $defaultName = "backup"; static $defaultName = "backup";
@ -21,38 +26,39 @@ class CommandBackup extends Command
protected function execute(InputInterface $input, OutputInterface $output): int protected function execute(InputInterface $input, OutputInterface $output): int
{ {
$output->writeln('Start backup!'); $io = new SymfonyStyle($input, $output);
$io->title('Start backup process');
$output->writeln('Opening: '.$input->getArgument('config')); $io->info('Opening: '.$input->getArgument('config'));
try { try {
$config = Yaml::parseFile($input->getArgument('config')); $config = Yaml::parseFile($input->getArgument('config'));
} catch (ParseException $e) { } catch (ParseException $e) {
$output->writeln('Unable to parse the YAML string: '. $e->getMessage()); $io->error('Unable to parse the YAML string: '. $e->getMessage());
return Command::FAILURE; return Command::FAILURE;
} }
$rclone = new Rclone\Rclone(); $rclone = new Rclone\Rclone();
$output->writeln("Rclone version: ". $rclone->getVersion()); $io->info("Rclone version: ". $rclone->getVersion());
$ntfy = new Ntfy\Ntfy($config['notification']['domain']); $ntfy = new Ntfy\Ntfy($config['notification']['domain']);
$loader = new \Twig\Loader\ArrayLoader($config['templates']); $loader = new ArrayLoader($config['templates']);
$twig = new \Twig\Environment($loader); $twig = new Environment($loader);
$twig->addExtension(new Twig\AppExtension()); $twig->addExtension(new Twig\AppExtension());
foreach ($config['backup'] as $conf) { foreach($io->progressIterate( $config['backup']) as $conf) {
try { try {
$template = array();
$template['config'] = $conf; $template['config'] = $conf;
$template['start'] = new \DateTime(); $template['start'] = new DateTime();
$template['source_size'] = $rclone->getSize($conf['source']); $template['source_size'] = $rclone->getSize($conf['source']);
$template['destination_size_before'] = $rclone->getSize($conf['destination']); $template['destination_size_before'] = $rclone->getSize($conf['destination']);
$rclone->copy($conf['source'], $conf['destination'], "6M"); $rclone->copy($conf['source'], $conf['destination'], "6M");
$template['destination_size_after'] = $rclone->getSize($conf['destination']); $template['destination_size_after'] = $rclone->getSize($conf['destination']);
$template['end'] = new \DateTime(); $template['end'] = new DateTime();
$message = $twig->render('notify', $template); $message = $twig->render('notify', $template);
echo $message;
} catch (\Throwable $e) { } catch (\Throwable $e) {
$message = $e->getMessage(); $message = $e->getMessage();
@ -60,6 +66,7 @@ class CommandBackup extends Command
$ntfy->send($config['notification']['topic'], $conf['title'], $message); $ntfy->send($config['notification']['topic'], $conf['title'], $message);
} }
$io->success("Complete");
return Command::SUCCESS; return Command::SUCCESS;
} }
} }

@ -5,7 +5,7 @@ use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
use Symfony\Component\Yaml\Exception\ParseException; use Symfony\Component\Yaml\Exception\ParseException;
@ -22,20 +22,20 @@ class CommandShow extends Command
protected function execute(InputInterface $input, OutputInterface $output): int protected function execute(InputInterface $input, OutputInterface $output): int
{ {
$output->writeln('Reading from: '.$input->getArgument('config')); $io = new SymfonyStyle($input, $output);
$io->title('List backup entities');
$io->note('Reading from: '.$input->getArgument('config'));
try { try {
$config = Yaml::parseFile($input->getArgument('config')); $config = Yaml::parseFile($input->getArgument('config'));
} catch (ParseException $e) { } catch (ParseException $e) {
$output->writeln('Unable to parse the YAML string: '. $e->getMessage()); $io->error('Unable to parse the YAML string: '. $e->getMessage());
return Command::FAILURE; return Command::FAILURE;
} }
$table = new Table($output);
$table
->setHeaders(['Description', 'Source', 'Destination'])
->setRows($config['backup']);
;
$table->render();
$io->table(['Description', 'Source', 'Destination'], $config['backup']);
$io->success("Done");
return Command::SUCCESS; return Command::SUCCESS;
} }
} }

@ -4,24 +4,37 @@ namespace App\Rclone;
use Symfony\Component\Process\Process; use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException; use Symfony\Component\Process\Exception\ProcessFailedException;
use Exception;
class Rclone class Rclone
{ {
protected string $rclone_path; protected string $rclonePath;
/** /**
* Global options * Global options
* *
* @var array<string> * @var array<string>
*/ */
protected array $global_options = []; protected array $globalOptions = [];
function __construct(string $rclone_path = "rclone") protected string $version = "";
function __construct(string $rclonePath = "rclone")
{ {
$this->rclone_path = $rclone_path; $this->rclonePath = $rclonePath;
try
{
$version = $this->exec('--version');
$this->version = explode("\n", $version)[0];
}
catch(ProcessFailedException $e)
{
throw new Exception("Check installation of rclone");
}
} }
function getVersion(): string function getVersion(): string
{ {
return $this->exec('--version'); return $this->version;
} }
function getSize(string $path): int function getSize(string $path): int
@ -52,8 +65,8 @@ class Rclone
{ {
$process = new Process( $process = new Process(
array_merge( array_merge(
[$this->rclone_path], [$this->rclonePath],
$this->global_options, $this->globalOptions,
[$command], [$command],
$options $options
) )

@ -24,7 +24,7 @@ class AppExtension extends AbstractExtension
public function formatBytes($bytes, $precision = 2) public function formatBytes($bytes, $precision = 2)
{ {
$size = ['B','kB','MB','GB','TB','PB','EB','ZB','YB']; $size = ['B','kB','MB','GB','TB','PB','EB','ZB','YB'];
$fact = floor((strlen((string)$bytes) - 1) / 3); $fact = (int)(floor((strlen((string)$bytes) - 1) / 3));
return sprintf("%.{$precision}f", $bytes / pow(1024, $fact)) . $size[$fact]; return sprintf("%.{$precision}f", $bytes / pow(1024, $fact)) . $size[$fact];
} }