Skip to content

Commit

Permalink
Merge pull request #2304 from acelaya-forks/feature/geolocation-servi…
Browse files Browse the repository at this point in the history
…ces-refactor

Move GeolocationDbUpdater to Core module
  • Loading branch information
acelaya authored Dec 11, 2024
2 parents 5d12b1d + 2ede615 commit 88c2839
Show file tree
Hide file tree
Showing 14 changed files with 169 additions and 113 deletions.
15 changes: 2 additions & 13 deletions module/CLI/config/dependencies.config.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,21 @@
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
use Laminas\ServiceManager\Factory\InvokableFactory;
use Shlinkio\Shlink\Common\Doctrine\NoDbNameConnectionFactory;
use Shlinkio\Shlink\Core\Config\Options\TrackingOptions;
use Shlinkio\Shlink\Core\Config\Options\UrlShortenerOptions;
use Shlinkio\Shlink\Core\Domain\DomainService;
use Shlinkio\Shlink\Core\Geolocation\GeolocationDbUpdater;
use Shlinkio\Shlink\Core\Matomo;
use Shlinkio\Shlink\Core\RedirectRule\ShortUrlRedirectRuleService;
use Shlinkio\Shlink\Core\ShortUrl;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier;
use Shlinkio\Shlink\Core\Tag\TagService;
use Shlinkio\Shlink\Core\Visit;
use Shlinkio\Shlink\Installer\Factory\ProcessHelperFactory;
use Shlinkio\Shlink\IpGeolocation\GeoLite2\DbUpdater;
use Shlinkio\Shlink\IpGeolocation\GeoLite2\GeoLite2ReaderFactory;
use Shlinkio\Shlink\Rest\Service\ApiKeyService;
use Symfony\Component\Console as SymfonyCli;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Process\PhpExecutableFinder;

use const Shlinkio\Shlink\LOCAL_LOCK_FACTORY;

return [

'dependencies' => [
Expand All @@ -34,7 +30,6 @@
SymfonyCli\Helper\ProcessHelper::class => ProcessHelperFactory::class,
PhpExecutableFinder::class => InvokableFactory::class,

GeoLite\GeolocationDbUpdater::class => ConfigAbstractFactory::class,
RedirectRule\RedirectRuleHandler::class => InvokableFactory::class,
Util\ProcessRunner::class => ConfigAbstractFactory::class,

Expand Down Expand Up @@ -82,12 +77,6 @@
],

ConfigAbstractFactory::class => [
GeoLite\GeolocationDbUpdater::class => [
DbUpdater::class,
GeoLite2ReaderFactory::class,
LOCAL_LOCK_FACTORY,
TrackingOptions::class,
],
Util\ProcessRunner::class => [SymfonyCli\Helper\ProcessHelper::class],
ApiKey\RoleResolver::class => [DomainService::class, UrlShortenerOptions::class],

Expand All @@ -107,7 +96,7 @@
Command\ShortUrl\DeleteShortUrlVisitsCommand::class => [ShortUrl\ShortUrlVisitsDeleter::class],
Command\ShortUrl\DeleteExpiredShortUrlsCommand::class => [ShortUrl\DeleteShortUrlService::class],

Command\Visit\DownloadGeoLiteDbCommand::class => [GeoLite\GeolocationDbUpdater::class],
Command\Visit\DownloadGeoLiteDbCommand::class => [GeolocationDbUpdater::class],
Command\Visit\LocateVisitsCommand::class => [
Visit\Geolocation\VisitLocator::class,
Visit\Geolocation\VisitToLocationHelper::class,
Expand Down
42 changes: 25 additions & 17 deletions module/CLI/src/Command/Visit/DownloadGeoLiteDbCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@

namespace Shlinkio\Shlink\CLI\Command\Visit;

use Shlinkio\Shlink\CLI\Exception\GeolocationDbUpdateFailedException;
use Shlinkio\Shlink\CLI\GeoLite\GeolocationDbUpdaterInterface;
use Shlinkio\Shlink\CLI\GeoLite\GeolocationResult;
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;
use Symfony\Component\Console\Input\InputInterface;
Expand All @@ -16,13 +17,14 @@

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();
}
Expand All @@ -39,32 +41,26 @@ protected function configure(): void

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);
}
}

Expand All @@ -86,4 +82,16 @@ private function processGeoLiteUpdateError(GeolocationDbUpdateFailedException $e

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);
}
}
17 changes: 9 additions & 8 deletions module/CLI/test/Command/Visit/DownloadGeoLiteDbCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Shlinkio\Shlink\CLI\Command\Visit\DownloadGeoLiteDbCommand;
use Shlinkio\Shlink\CLI\Exception\GeolocationDbUpdateFailedException;
use Shlinkio\Shlink\CLI\GeoLite\GeolocationDbUpdaterInterface;
use Shlinkio\Shlink\CLI\GeoLite\GeolocationResult;
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 ShlinkioTest\Shlink\CLI\Util\CliTestUtils;
use Symfony\Component\Console\Tester\CommandTester;

Expand All @@ -36,9 +37,9 @@ public function showsProperMessageWhenGeoLiteUpdateFails(
int $expectedExitCode,
): void {
$this->dbUpdater->expects($this->once())->method('checkDbUpdate')->withAnyParameters()->willReturnCallback(
function (callable $beforeDownload, callable $handleProgress) use ($olderDbExists): void {
$beforeDownload($olderDbExists);
$handleProgress(100, 50);
function (GeolocationDownloadProgressHandlerInterface $handler) use ($olderDbExists): void {
$handler->beforeDownload($olderDbExists);
$handler->handleProgress(100, 50, $olderDbExists);

throw $olderDbExists
? GeolocationDbUpdateFailedException::withOlderDb()
Expand Down Expand Up @@ -105,8 +106,8 @@ public function printsExpectedMessageWhenNoErrorOccurs(callable $checkUpdateBeha
public static function provideSuccessParams(): iterable
{
yield 'up to date db' => [fn () => GeolocationResult::CHECK_SKIPPED, '[INFO] GeoLite2 db file is up to date.'];
yield 'outdated db' => [function (callable $beforeDownload): GeolocationResult {
$beforeDownload(true);
yield 'outdated db' => [function (GeolocationDownloadProgressHandlerInterface $handler): GeolocationResult {
$handler->beforeDownload(true);
return GeolocationResult::DB_CREATED;
}, '[OK] GeoLite2 db file properly downloaded.'];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use RuntimeException;
use Shlinkio\Shlink\CLI\Exception\GeolocationDbUpdateFailedException;
use Shlinkio\Shlink\Core\Exception\GeolocationDbUpdateFailedException;
use Throwable;

class GeolocationDbUpdateFailedExceptionTest extends TestCase
Expand Down
11 changes: 11 additions & 0 deletions module/Core/config/dependencies.config.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@
use Psr\EventDispatcher\EventDispatcherInterface;
use Shlinkio\Shlink\Common\Doctrine\EntityRepositoryFactory;
use Shlinkio\Shlink\Core\Config\Options\NotFoundRedirectOptions;
use Shlinkio\Shlink\Core\Geolocation\GeolocationDbUpdater;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier;
use Shlinkio\Shlink\Importer\ImportedLinksProcessorInterface;
use Shlinkio\Shlink\IpGeolocation\GeoLite2\DbUpdater;
use Shlinkio\Shlink\IpGeolocation\GeoLite2\GeoLite2ReaderFactory;
use Shlinkio\Shlink\IpGeolocation\Resolver\IpLocationResolverInterface;
use Symfony\Component\Lock;

use const Shlinkio\Shlink\LOCAL_LOCK_FACTORY;

return [

'dependencies' => [
Expand Down Expand Up @@ -103,6 +107,7 @@

EventDispatcher\PublishingUpdatesGenerator::class => ConfigAbstractFactory::class,

Geolocation\GeolocationDbUpdater::class => ConfigAbstractFactory::class,
Geolocation\Middleware\IpGeolocationMiddleware::class => ConfigAbstractFactory::class,

Importer\ImportedLinksProcessor::class => ConfigAbstractFactory::class,
Expand Down Expand Up @@ -240,6 +245,12 @@

EventDispatcher\PublishingUpdatesGenerator::class => [ShortUrl\Transformer\ShortUrlDataTransformer::class],

GeolocationDbUpdater::class => [
DbUpdater::class,
GeoLite2ReaderFactory::class,
LOCAL_LOCK_FACTORY,
Config\Options\TrackingOptions::class,
],
Geolocation\Middleware\IpGeolocationMiddleware::class => [
IpLocationResolverInterface::class,
DbUpdater::class,
Expand Down
2 changes: 1 addition & 1 deletion module/Core/config/event_dispatcher.config.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@

use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
use Psr\EventDispatcher\EventDispatcherInterface;
use Shlinkio\Shlink\CLI\GeoLite\GeolocationDbUpdater;
use Shlinkio\Shlink\Common\Cache\RedisPublishingHelper;
use Shlinkio\Shlink\Common\Mercure\MercureHubPublishingHelper;
use Shlinkio\Shlink\Common\Mercure\MercureOptions;
use Shlinkio\Shlink\Common\RabbitMq\RabbitMqPublishingHelper;
use Shlinkio\Shlink\Core\Geolocation\GeolocationDbUpdater;
use Shlinkio\Shlink\Core\Matomo\MatomoOptions;
use Shlinkio\Shlink\Core\Visit\Geolocation\VisitLocator;
use Shlinkio\Shlink\Core\Visit\Geolocation\VisitToLocationHelper;
Expand Down
45 changes: 30 additions & 15 deletions module/Core/src/EventDispatcher/UpdateGeoLiteDb.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@

use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Log\LoggerInterface;
use Shlinkio\Shlink\CLI\GeoLite\GeolocationDbUpdaterInterface;
use Shlinkio\Shlink\CLI\GeoLite\GeolocationResult;
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;

use function sprintf;
Expand All @@ -24,21 +25,35 @@ public function __construct(

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;
}
try {
$result = $this->dbUpdater->checkDbUpdate(
new class ($this->logger) implements GeolocationDownloadProgressHandlerInterface {
public function __construct(
private readonly LoggerInterface $logger,
private bool $messageLogged = false,
) {
}

$messageLogged = true;
$this->logger->notice(sprintf('Finished %s GeoLite2 db file', $olderDbExists ? 'updating' : 'downloading'));
};
public function beforeDownload(bool $olderDbExists): void
{
$this->logger->notice(
sprintf('%s GeoLite2 db file...', $olderDbExists ? 'Updating' : 'Downloading'),
);
}

try {
$result = $this->dbUpdater->checkDbUpdate($beforeDownload, $handleProgress);
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());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace Shlinkio\Shlink\CLI\Exception;
namespace Shlinkio\Shlink\Core\Exception;

use RuntimeException;
use Throwable;
Expand Down
Loading

0 comments on commit 88c2839

Please sign in to comment.