Completed
Push — master ( 0ec7e8...22630c )
by Alejandro
23s queued 10s
created

GeolocationDbUpdater::checkDbUpdate()   A

Complexity

Conditions 2
Paths 3

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 8
c 2
b 0
f 0
dl 0
loc 11
ccs 8
cts 8
cp 1
rs 10
cc 2
nc 3
nop 2
crap 2
1
<?php
2
declare(strict_types=1);
3
4
namespace Shlinkio\Shlink\CLI\Util;
5
6
use Cake\Chronos\Chronos;
7
use GeoIp2\Database\Reader;
8
use Shlinkio\Shlink\CLI\Exception\GeolocationDbUpdateFailedException;
9
use Shlinkio\Shlink\Common\Exception\RuntimeException;
10
use Shlinkio\Shlink\Common\IpGeolocation\GeoLite2\DbUpdaterInterface;
11
use Symfony\Component\Lock\Factory as Locker;
12
use Throwable;
13
14
class GeolocationDbUpdater implements GeolocationDbUpdaterInterface
15
{
16
    private const LOCK_NAME = 'geolocation-db-update';
17
18
    /** @var DbUpdaterInterface */
19
    private $dbUpdater;
20
    /** @var Reader */
21
    private $geoLiteDbReader;
22
    /** @var Locker */
23
    private $locker;
24
25 40
    public function __construct(DbUpdaterInterface $dbUpdater, Reader $geoLiteDbReader, Locker $locker)
26
    {
27 40
        $this->dbUpdater = $dbUpdater;
28 40
        $this->geoLiteDbReader = $geoLiteDbReader;
29 40
        $this->locker = $locker;
30
    }
31
32
    /**
33
     * @throws GeolocationDbUpdateFailedException
34
     */
35 40
    public function checkDbUpdate(?callable $mustBeUpdated = null, ?callable $handleProgress = null): void
36
    {
37 40
        $lock = $this->locker->createLock(self::LOCK_NAME);
38 40
        $lock->acquire(true); // Block until lock is released
39
40
        try {
41 40
            $this->downloadIfNeeded($mustBeUpdated, $handleProgress);
42 5
        } catch (Throwable $e) {
43 5
            throw $e;
44 35
        } finally {
45 40
            $lock->release();
46
        }
47
    }
48
49
    /**
50
     * @throws GeolocationDbUpdateFailedException
51
     */
52 40
    private function downloadIfNeeded(?callable $mustBeUpdated, ?callable $handleProgress): void
53
    {
54 40
        if (! $this->dbUpdater->databaseFileExists()) {
55 1
            $this->downloadNewDb(false, $mustBeUpdated, $handleProgress);
56
            return;
57
        }
58
59 39
        $meta = $this->geoLiteDbReader->metadata();
60 39
        if ($this->buildIsTooOld($meta->__get('buildEpoch'))) {
61 4
            $this->downloadNewDb(true, $mustBeUpdated, $handleProgress);
62
        }
63
    }
64
65
    /**
66
     * @throws GeolocationDbUpdateFailedException
67
     */
68 5
    private function downloadNewDb(bool $olderDbExists, ?callable $mustBeUpdated, ?callable $handleProgress): void
69
    {
70 5
        if ($mustBeUpdated !== null) {
71 1
            $mustBeUpdated($olderDbExists);
72
        }
73
74
        try {
75 5
            $this->dbUpdater->downloadFreshCopy($handleProgress);
76 5
        } catch (RuntimeException $e) {
77 5
            throw GeolocationDbUpdateFailedException::create($olderDbExists, $e);
78
        }
79
    }
80
81 39
    private function buildIsTooOld(int $buildTimestamp): bool
82
    {
83 39
        $buildDate = Chronos::createFromTimestamp($buildTimestamp);
84 39
        $now = Chronos::now();
85 39
        return $now->gt($buildDate->addDays(35));
86
    }
87
}
88