Completed
Push — master ( a7d308...df23f2 )
by Alejandro
25s queued 11s
created

GeolocationDbUpdater   A

Complexity

Total Complexity 9

Size/Duplication

Total Lines 70
Duplicated Lines 0 %

Test Coverage

Coverage 96.3%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 29
c 2
b 0
f 0
dl 0
loc 70
ccs 26
cts 27
cp 0.963
rs 10
wmc 9

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A buildIsTooOld() 0 5 1
A checkDbUpdate() 0 9 1
A downloadNewDb() 0 10 3
A downloadIfNeeded() 0 10 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Shlinkio\Shlink\CLI\Util;
6
7
use Cake\Chronos\Chronos;
8
use GeoIp2\Database\Reader;
9
use Shlinkio\Shlink\CLI\Exception\GeolocationDbUpdateFailedException;
10
use Shlinkio\Shlink\IpGeolocation\Exception\RuntimeException;
11
use Shlinkio\Shlink\IpGeolocation\GeoLite2\DbUpdaterInterface;
12
use Symfony\Component\Lock\Factory as Locker;
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 35
        } finally {
43 40
            $lock->release();
44
        }
45
    }
46
47
    /**
48
     * @throws GeolocationDbUpdateFailedException
49
     */
50 40
    private function downloadIfNeeded(?callable $mustBeUpdated, ?callable $handleProgress): void
51
    {
52 40
        if (! $this->dbUpdater->databaseFileExists()) {
53 1
            $this->downloadNewDb(false, $mustBeUpdated, $handleProgress);
54
            return;
55
        }
56
57 39
        $meta = $this->geoLiteDbReader->metadata();
58 39
        if ($this->buildIsTooOld($meta->__get('buildEpoch'))) {
59 4
            $this->downloadNewDb(true, $mustBeUpdated, $handleProgress);
60
        }
61
    }
62
63
    /**
64
     * @throws GeolocationDbUpdateFailedException
65
     */
66 5
    private function downloadNewDb(bool $olderDbExists, ?callable $mustBeUpdated, ?callable $handleProgress): void
67
    {
68 5
        if ($mustBeUpdated !== null) {
69 1
            $mustBeUpdated($olderDbExists);
70
        }
71
72
        try {
73 5
            $this->dbUpdater->downloadFreshCopy($handleProgress);
74 5
        } catch (RuntimeException $e) {
75 5
            throw GeolocationDbUpdateFailedException::create($olderDbExists, $e);
76
        }
77
    }
78
79 39
    private function buildIsTooOld(int $buildTimestamp): bool
80
    {
81 39
        $buildDate = Chronos::createFromTimestamp($buildTimestamp);
82 39
        $now = Chronos::now();
83 39
        return $now->gt($buildDate->addDays(35));
84
    }
85
}
86