mirror of
https://github.com/shlinkio/shlink.git
synced 2026-02-28 04:03:12 +08:00
Decouple LocateVisitsCommand from AbstractLockedCommand
This commit is contained in:
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace Shlinkio\Shlink\CLI\Command\Db;
|
||||
|
||||
use Shlinkio\Shlink\CLI\Command\Util\AbstractLockedCommand;
|
||||
use Shlinkio\Shlink\CLI\Command\Util\LockedCommandConfig;
|
||||
use Shlinkio\Shlink\CLI\Command\Util\LockConfig;
|
||||
use Shlinkio\Shlink\CLI\Util\ProcessRunnerInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Lock\LockFactory;
|
||||
@@ -30,8 +30,8 @@ abstract class AbstractDatabaseCommand extends AbstractLockedCommand
|
||||
$this->processRunner->run($output, $command);
|
||||
}
|
||||
|
||||
protected function getLockConfig(): LockedCommandConfig
|
||||
protected function getLockConfig(): LockConfig
|
||||
{
|
||||
return LockedCommandConfig::blocking($this->getName() ?? static::class);
|
||||
return LockConfig::blocking($this->getName() ?? static::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,5 +39,5 @@ abstract class AbstractLockedCommand extends Command
|
||||
|
||||
abstract protected function lockedExecute(InputInterface $input, OutputInterface $output): int;
|
||||
|
||||
abstract protected function getLockConfig(): LockedCommandConfig;
|
||||
abstract protected function getLockConfig(): LockConfig;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ namespace Shlinkio\Shlink\CLI\Command\Util;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Lock\LockFactory;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
class CommandUtils
|
||||
{
|
||||
@@ -25,4 +28,31 @@ class CommandUtils
|
||||
|
||||
return $callback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a callback with a lock, making sure the lock is released after running the callback, and the callback does
|
||||
* not run if the lock is already acquired.
|
||||
*
|
||||
* @param callable(): int $callback
|
||||
*/
|
||||
public static function executeWithLock(
|
||||
LockFactory $locker,
|
||||
LockConfig $lockConfig,
|
||||
SymfonyStyle $io,
|
||||
callable $callback,
|
||||
): int {
|
||||
$lock = $locker->createLock($lockConfig->lockName, $lockConfig->ttl, $lockConfig->isBlocking);
|
||||
if (! $lock->acquire($lockConfig->isBlocking)) {
|
||||
$io->writeln(
|
||||
sprintf('<comment>Command "%s" is already in progress. Skipping.</comment>', $lockConfig->lockName),
|
||||
);
|
||||
return Command::INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
return $callback();
|
||||
} finally {
|
||||
$lock->release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,24 +4,24 @@ declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\CLI\Command\Util;
|
||||
|
||||
final class LockedCommandConfig
|
||||
final readonly class LockConfig
|
||||
{
|
||||
public const float DEFAULT_TTL = 600.0; // 10 minutes
|
||||
|
||||
private function __construct(
|
||||
public readonly string $lockName,
|
||||
public readonly bool $isBlocking,
|
||||
public readonly float $ttl = self::DEFAULT_TTL,
|
||||
public string $lockName,
|
||||
public bool $isBlocking,
|
||||
public float $ttl = self::DEFAULT_TTL,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function blocking(string $lockName): self
|
||||
{
|
||||
return new self($lockName, true);
|
||||
return new self($lockName, isBlocking: true);
|
||||
}
|
||||
|
||||
public static function nonBlocking(string $lockName): self
|
||||
{
|
||||
return new self($lockName, false);
|
||||
return new self($lockName, isBlocking: false);
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\CLI\Command\Visit;
|
||||
|
||||
use Shlinkio\Shlink\CLI\Command\Util\AbstractLockedCommand;
|
||||
use Shlinkio\Shlink\CLI\Command\Util\LockedCommandConfig;
|
||||
use Shlinkio\Shlink\CLI\Command\Util\CommandUtils;
|
||||
use Shlinkio\Shlink\CLI\Command\Util\LockConfig;
|
||||
use Shlinkio\Shlink\Common\Util\IpAddress;
|
||||
use Shlinkio\Shlink\Core\Exception\IpCannotBeLocatedException;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
@@ -15,6 +15,7 @@ use Shlinkio\Shlink\Core\Visit\Geolocation\VisitLocatorInterface;
|
||||
use Shlinkio\Shlink\Core\Visit\Geolocation\VisitToLocationHelperInterface;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\UnlocatableIpType;
|
||||
use Shlinkio\Shlink\IpGeolocation\Model\Location;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
@@ -26,7 +27,7 @@ use Throwable;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
class LocateVisitsCommand extends AbstractLockedCommand implements VisitGeolocationHelperInterface
|
||||
class LocateVisitsCommand extends Command implements VisitGeolocationHelperInterface
|
||||
{
|
||||
public const string NAME = 'visit:locate';
|
||||
|
||||
@@ -35,9 +36,9 @@ class LocateVisitsCommand extends AbstractLockedCommand implements VisitGeolocat
|
||||
public function __construct(
|
||||
private readonly VisitLocatorInterface $visitLocator,
|
||||
private readonly VisitToLocationHelperInterface $visitToLocation,
|
||||
LockFactory $locker,
|
||||
private readonly LockFactory $locker,
|
||||
) {
|
||||
parent::__construct($locker);
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
@@ -97,7 +98,17 @@ class LocateVisitsCommand extends AbstractLockedCommand implements VisitGeolocat
|
||||
return $this->io->confirm('Do you want to proceed?', false);
|
||||
}
|
||||
|
||||
protected function lockedExecute(InputInterface $input, OutputInterface $output): int
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
return CommandUtils::executeWithLock(
|
||||
$this->locker,
|
||||
LockConfig::nonBlocking(self::NAME),
|
||||
new SymfonyStyle($input, $output),
|
||||
fn () => $this->runStuff($input),
|
||||
);
|
||||
}
|
||||
|
||||
private function runStuff(InputInterface $input): int
|
||||
{
|
||||
$retry = $input->getOption('retry');
|
||||
$all = $retry && $input->getOption('all');
|
||||
@@ -174,9 +185,4 @@ class LocateVisitsCommand extends AbstractLockedCommand implements VisitGeolocat
|
||||
throw new RuntimeException('It is not possible to locate visits without a GeoLite2 db file.');
|
||||
}
|
||||
}
|
||||
|
||||
protected function getLockConfig(): LockedCommandConfig
|
||||
{
|
||||
return LockedCommandConfig::nonBlocking(self::NAME);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace Shlinkio\Shlink\CLI\Util;
|
||||
|
||||
use Closure;
|
||||
use Shlinkio\Shlink\CLI\Command\Util\LockedCommandConfig;
|
||||
use Shlinkio\Shlink\CLI\Command\Util\LockConfig;
|
||||
use Symfony\Component\Console\Helper\DebugFormatterHelper;
|
||||
use Symfony\Component\Console\Helper\ProcessHelper;
|
||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||
@@ -24,7 +24,7 @@ class ProcessRunner implements ProcessRunnerInterface
|
||||
{
|
||||
$this->createProcess = $createProcess !== null
|
||||
? $createProcess(...)
|
||||
: static fn (array $cmd) => new Process($cmd, timeout: LockedCommandConfig::DEFAULT_TTL);
|
||||
: static fn (array $cmd) => new Process($cmd, timeout: LockConfig::DEFAULT_TTL);
|
||||
}
|
||||
|
||||
public function run(OutputInterface $output, array $cmd): void
|
||||
|
||||
Reference in New Issue
Block a user