mirror of
https://github.com/shlinkio/shlink.git
synced 2025-02-25 18:45:27 -06:00
Add more strict parameter for GeolocationDbUpdater
This commit is contained in:
@@ -7,6 +7,7 @@ namespace Shlinkio\Shlink\CLI\Command\Visit;
|
||||
use Shlinkio\Shlink\CLI\Util\ExitCode;
|
||||
use Shlinkio\Shlink\Core\Exception\GeolocationDbUpdateFailedException;
|
||||
use Shlinkio\Shlink\Core\Geolocation\GeolocationDbUpdaterInterface;
|
||||
use Shlinkio\Shlink\Core\Geolocation\GeolocationDownloadProgressHandlerInterface;
|
||||
use Shlinkio\Shlink\Core\Geolocation\GeolocationResult;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\ProgressBar;
|
||||
@@ -16,13 +17,14 @@ use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
class DownloadGeoLiteDbCommand extends Command
|
||||
class DownloadGeoLiteDbCommand extends Command implements GeolocationDownloadProgressHandlerInterface
|
||||
{
|
||||
public const string NAME = 'visit:download-db';
|
||||
|
||||
private ProgressBar|null $progressBar = null;
|
||||
private SymfonyStyle $io;
|
||||
|
||||
public function __construct(private GeolocationDbUpdaterInterface $dbUpdater)
|
||||
public function __construct(private readonly GeolocationDbUpdaterInterface $dbUpdater)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
@@ -39,32 +41,26 @@ class DownloadGeoLiteDbCommand extends Command
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$this->io = new SymfonyStyle($input, $output);
|
||||
|
||||
try {
|
||||
$result = $this->dbUpdater->checkDbUpdate(function (bool $olderDbExists) use ($io): void {
|
||||
$io->text(sprintf('<fg=blue>%s GeoLite2 db file...</>', $olderDbExists ? 'Updating' : 'Downloading'));
|
||||
$this->progressBar = new ProgressBar($io);
|
||||
}, function (int $total, int $downloaded): void {
|
||||
$this->progressBar?->setMaxSteps($total);
|
||||
$this->progressBar?->setProgress($downloaded);
|
||||
});
|
||||
$result = $this->dbUpdater->checkDbUpdate($this);
|
||||
|
||||
if ($result === GeolocationResult::LICENSE_MISSING) {
|
||||
$io->warning('It was not possible to download GeoLite2 db, because a license was not provided.');
|
||||
$this->io->warning('It was not possible to download GeoLite2 db, because a license was not provided.');
|
||||
return ExitCode::EXIT_WARNING;
|
||||
}
|
||||
|
||||
if ($this->progressBar === null) {
|
||||
$io->info('GeoLite2 db file is up to date.');
|
||||
$this->io->info('GeoLite2 db file is up to date.');
|
||||
} else {
|
||||
$this->progressBar->finish();
|
||||
$io->success('GeoLite2 db file properly downloaded.');
|
||||
$this->io->success('GeoLite2 db file properly downloaded.');
|
||||
}
|
||||
|
||||
return ExitCode::EXIT_SUCCESS;
|
||||
} catch (GeolocationDbUpdateFailedException $e) {
|
||||
return $this->processGeoLiteUpdateError($e, $io);
|
||||
return $this->processGeoLiteUpdateError($e, $this->io);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,4 +82,16 @@ class DownloadGeoLiteDbCommand extends Command
|
||||
|
||||
return $olderDbExists ? ExitCode::EXIT_WARNING : ExitCode::EXIT_FAILURE;
|
||||
}
|
||||
|
||||
public function beforeDownload(bool $olderDbExists): void
|
||||
{
|
||||
$this->io->text(sprintf('<fg=blue>%s GeoLite2 db file...</>', $olderDbExists ? 'Updating' : 'Downloading'));
|
||||
$this->progressBar = new ProgressBar($this->io);
|
||||
}
|
||||
|
||||
public function handleProgress(int $total, int $downloaded, bool $olderDbExists): void
|
||||
{
|
||||
$this->progressBar?->setMaxSteps($total);
|
||||
$this->progressBar?->setProgress($downloaded);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Core\Config\Options\TrackingOptions;
|
||||
use Shlinkio\Shlink\Core\Exception\GeolocationDbUpdateFailedException;
|
||||
use Shlinkio\Shlink\Core\Geolocation\GeolocationDbUpdater;
|
||||
use Shlinkio\Shlink\Core\Geolocation\GeolocationDownloadProgressHandlerInterface;
|
||||
use Shlinkio\Shlink\Core\Geolocation\GeolocationResult;
|
||||
use Shlinkio\Shlink\IpGeolocation\Exception\DbUpdateException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Exception\MissingLicenseException;
|
||||
@@ -29,6 +30,8 @@ class GeolocationDbUpdaterTest extends TestCase
|
||||
private MockObject & DbUpdaterInterface $dbUpdater;
|
||||
private MockObject & Reader $geoLiteDbReader;
|
||||
private MockObject & Lock\LockInterface $lock;
|
||||
/** @var GeolocationDownloadProgressHandlerInterface&object{beforeDownloadCalled: bool, handleProgressCalled: bool} */
|
||||
private GeolocationDownloadProgressHandlerInterface $progressHandler;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -36,6 +39,23 @@ class GeolocationDbUpdaterTest extends TestCase
|
||||
$this->geoLiteDbReader = $this->createMock(Reader::class);
|
||||
$this->lock = $this->createMock(Lock\SharedLockInterface::class);
|
||||
$this->lock->method('acquire')->with($this->isTrue())->willReturn(true);
|
||||
$this->progressHandler = new class implements GeolocationDownloadProgressHandlerInterface {
|
||||
public function __construct(
|
||||
public bool $beforeDownloadCalled = false,
|
||||
public bool $handleProgressCalled = false,
|
||||
) {
|
||||
}
|
||||
|
||||
public function beforeDownload(bool $olderDbExists): void
|
||||
{
|
||||
$this->beforeDownloadCalled = true;
|
||||
}
|
||||
|
||||
public function handleProgress(int $total, int $downloaded, bool $olderDbExists): void
|
||||
{
|
||||
$this->handleProgressCalled = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[Test]
|
||||
@@ -47,12 +67,9 @@ class GeolocationDbUpdaterTest extends TestCase
|
||||
);
|
||||
$this->geoLiteDbReader->expects($this->never())->method('metadata');
|
||||
|
||||
$isCalled = false;
|
||||
$result = $this->geolocationDbUpdater()->checkDbUpdate(function () use (&$isCalled): void {
|
||||
$isCalled = true;
|
||||
});
|
||||
$result = $this->geolocationDbUpdater()->checkDbUpdate($this->progressHandler);
|
||||
|
||||
self::assertTrue($isCalled);
|
||||
self::assertTrue($this->progressHandler->beforeDownloadCalled);
|
||||
self::assertEquals(GeolocationResult::LICENSE_MISSING, $result);
|
||||
}
|
||||
|
||||
@@ -67,17 +84,14 @@ class GeolocationDbUpdaterTest extends TestCase
|
||||
)->willThrowException($prev);
|
||||
$this->geoLiteDbReader->expects($this->never())->method('metadata');
|
||||
|
||||
$isCalled = false;
|
||||
try {
|
||||
$this->geolocationDbUpdater()->checkDbUpdate(function () use (&$isCalled): void {
|
||||
$isCalled = true;
|
||||
});
|
||||
$this->geolocationDbUpdater()->checkDbUpdate($this->progressHandler);
|
||||
self::fail();
|
||||
} catch (Throwable $e) {
|
||||
self::assertInstanceOf(GeolocationDbUpdateFailedException::class, $e);
|
||||
self::assertSame($prev, $e->getPrevious());
|
||||
self::assertFalse($e->olderDbExists());
|
||||
self::assertTrue($isCalled);
|
||||
self::assertTrue($this->progressHandler->beforeDownloadCalled);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\Event\GeoLiteDbCreated;
|
||||
use Shlinkio\Shlink\Core\Geolocation\GeolocationDbUpdaterInterface;
|
||||
use Shlinkio\Shlink\Core\Geolocation\GeolocationDownloadProgressHandlerInterface;
|
||||
use Shlinkio\Shlink\Core\Geolocation\GeolocationResult;
|
||||
use Throwable;
|
||||
|
||||
@@ -24,21 +25,35 @@ readonly class UpdateGeoLiteDb
|
||||
|
||||
public function __invoke(): void
|
||||
{
|
||||
$beforeDownload = fn (bool $olderDbExists) => $this->logger->notice(
|
||||
sprintf('%s GeoLite2 db file...', $olderDbExists ? 'Updating' : 'Downloading'),
|
||||
);
|
||||
$messageLogged = false;
|
||||
$handleProgress = function (int $total, int $downloaded, bool $olderDbExists) use (&$messageLogged): void {
|
||||
if ($messageLogged || $total > $downloaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
$messageLogged = true;
|
||||
$this->logger->notice(sprintf('Finished %s GeoLite2 db file', $olderDbExists ? 'updating' : 'downloading'));
|
||||
};
|
||||
|
||||
try {
|
||||
$result = $this->dbUpdater->checkDbUpdate($beforeDownload, $handleProgress);
|
||||
$result = $this->dbUpdater->checkDbUpdate(
|
||||
new class ($this->logger) implements GeolocationDownloadProgressHandlerInterface {
|
||||
public function __construct(
|
||||
private readonly LoggerInterface $logger,
|
||||
private bool $messageLogged = false,
|
||||
) {
|
||||
}
|
||||
|
||||
public function beforeDownload(bool $olderDbExists): void
|
||||
{
|
||||
$this->logger->notice(
|
||||
sprintf('%s GeoLite2 db file...', $olderDbExists ? 'Updating' : 'Downloading'),
|
||||
);
|
||||
}
|
||||
|
||||
public function handleProgress(int $total, int $downloaded, bool $olderDbExists): void
|
||||
{
|
||||
if ($this->messageLogged || $total > $downloaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->messageLogged = true;
|
||||
$this->logger->notice(
|
||||
sprintf('Finished %s GeoLite2 db file', $olderDbExists ? 'updating' : 'downloading'),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
if ($result === GeolocationResult::DB_CREATED) {
|
||||
$this->eventDispatcher->dispatch(new GeoLiteDbCreated());
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ use Shlinkio\Shlink\Core\Config\Options\TrackingOptions;
|
||||
use Shlinkio\Shlink\Core\Exception\GeolocationDbUpdateFailedException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Exception\DbUpdateException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Exception\MissingLicenseException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\IpGeolocation\GeoLite2\DbUpdaterInterface;
|
||||
use Symfony\Component\Lock\LockFactory;
|
||||
|
||||
@@ -41,8 +40,7 @@ class GeolocationDbUpdater implements GeolocationDbUpdaterInterface
|
||||
* @throws GeolocationDbUpdateFailedException
|
||||
*/
|
||||
public function checkDbUpdate(
|
||||
callable|null $beforeDownload = null,
|
||||
callable|null $handleProgress = null,
|
||||
GeolocationDownloadProgressHandlerInterface|null $downloadProgressHandler = null,
|
||||
): GeolocationResult {
|
||||
if (! $this->trackingOptions->isGeolocationRelevant()) {
|
||||
return GeolocationResult::CHECK_SKIPPED;
|
||||
@@ -52,7 +50,7 @@ class GeolocationDbUpdater implements GeolocationDbUpdaterInterface
|
||||
$lock->acquire(true); // Block until lock is released
|
||||
|
||||
try {
|
||||
return $this->downloadIfNeeded($beforeDownload, $handleProgress);
|
||||
return $this->downloadIfNeeded($downloadProgressHandler);
|
||||
} finally {
|
||||
$lock->release();
|
||||
}
|
||||
@@ -61,15 +59,16 @@ class GeolocationDbUpdater implements GeolocationDbUpdaterInterface
|
||||
/**
|
||||
* @throws GeolocationDbUpdateFailedException
|
||||
*/
|
||||
private function downloadIfNeeded(callable|null $beforeDownload, callable|null $handleProgress): GeolocationResult
|
||||
{
|
||||
private function downloadIfNeeded(
|
||||
GeolocationDownloadProgressHandlerInterface|null $downloadProgressHandler,
|
||||
): GeolocationResult {
|
||||
if (! $this->dbUpdater->databaseFileExists()) {
|
||||
return $this->downloadNewDb($beforeDownload, $handleProgress, olderDbExists: false);
|
||||
return $this->downloadNewDb($downloadProgressHandler, olderDbExists: false);
|
||||
}
|
||||
|
||||
$meta = ($this->geoLiteDbReaderFactory)()->metadata();
|
||||
if ($this->buildIsTooOld($meta)) {
|
||||
return $this->downloadNewDb($beforeDownload, $handleProgress, olderDbExists: true);
|
||||
return $this->downloadNewDb($downloadProgressHandler, olderDbExists: true);
|
||||
}
|
||||
|
||||
return GeolocationResult::DB_IS_UP_TO_DATE;
|
||||
@@ -106,32 +105,23 @@ class GeolocationDbUpdater implements GeolocationDbUpdaterInterface
|
||||
* @throws GeolocationDbUpdateFailedException
|
||||
*/
|
||||
private function downloadNewDb(
|
||||
callable|null $beforeDownload,
|
||||
callable|null $handleProgress,
|
||||
GeolocationDownloadProgressHandlerInterface|null $downloadProgressHandler,
|
||||
bool $olderDbExists,
|
||||
): GeolocationResult {
|
||||
if ($beforeDownload !== null) {
|
||||
$beforeDownload($olderDbExists);
|
||||
}
|
||||
$downloadProgressHandler?->beforeDownload($olderDbExists);
|
||||
|
||||
try {
|
||||
$this->dbUpdater->downloadFreshCopy($this->wrapHandleProgressCallback($handleProgress, $olderDbExists));
|
||||
$this->dbUpdater->downloadFreshCopy(
|
||||
static fn (int $total, int $downloaded)
|
||||
=> $downloadProgressHandler?->handleProgress($total, $downloaded, $olderDbExists),
|
||||
);
|
||||
return $olderDbExists ? GeolocationResult::DB_UPDATED : GeolocationResult::DB_CREATED;
|
||||
} catch (MissingLicenseException) {
|
||||
return GeolocationResult::LICENSE_MISSING;
|
||||
} catch (DbUpdateException | WrongIpException $e) {
|
||||
} catch (DbUpdateException $e) {
|
||||
throw $olderDbExists
|
||||
? GeolocationDbUpdateFailedException::withOlderDb($e)
|
||||
: GeolocationDbUpdateFailedException::withoutOlderDb($e);
|
||||
}
|
||||
}
|
||||
|
||||
private function wrapHandleProgressCallback(callable|null $handleProgress, bool $olderDbExists): callable|null
|
||||
{
|
||||
if ($handleProgress === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return static fn (int $total, int $downloaded) => $handleProgress($total, $downloaded, $olderDbExists);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ interface GeolocationDbUpdaterInterface
|
||||
* @throws GeolocationDbUpdateFailedException
|
||||
*/
|
||||
public function checkDbUpdate(
|
||||
callable|null $beforeDownload = null,
|
||||
callable|null $handleProgress = null,
|
||||
GeolocationDownloadProgressHandlerInterface|null $downloadProgressHandler = null,
|
||||
): GeolocationResult;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Geolocation;
|
||||
|
||||
interface GeolocationDownloadProgressHandlerInterface
|
||||
{
|
||||
/**
|
||||
* Invoked right before starting to download a geolocation DB file, and only if it needs to be downloaded.
|
||||
* @param $olderDbExists - Indicates if an older DB file already exists when this method is called
|
||||
*/
|
||||
public function beforeDownload(bool $olderDbExists): void;
|
||||
|
||||
/**
|
||||
* Invoked every time a new chunk of the new DB file is downloaded, with the total size of the file and how much has
|
||||
* already been downloaded.
|
||||
* @param $olderDbExists - Indicates if an older DB file already exists when this method is called
|
||||
*/
|
||||
public function handleProgress(int $total, int $downloaded, bool $olderDbExists): void;
|
||||
}
|
||||
Reference in New Issue
Block a user