Completed
Pull Request — master (#454)
by Alejandro
14:47
created

DbUpdater   A

Complexity

Total Complexity 11

Size/Duplication

Total Lines 87
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 11
eloc 39
dl 0
loc 87
rs 10
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A downloadFreshCopy() 0 9 1
A extractDbFile() 0 13 2
A __construct() 0 5 1
A deleteTempFiles() 0 5 2
A databaseFileExists() 0 3 1
A copyNewDbFile() 0 6 2
A downloadDbFile() 0 12 2
1
<?php
2
declare(strict_types=1);
3
4
namespace Shlinkio\Shlink\IpGeolocation\GeoLite2;
5
6
use Fig\Http\Message\RequestMethodInterface as RequestMethod;
7
use GuzzleHttp\ClientInterface;
8
use GuzzleHttp\Exception\GuzzleException;
9
use GuzzleHttp\RequestOptions;
10
use PharData;
11
use Shlinkio\Shlink\Common\Exception\RuntimeException;
12
use Symfony\Component\Filesystem\Exception as FilesystemException;
13
use Symfony\Component\Filesystem\Filesystem;
14
use Throwable;
15
16
use function sprintf;
17
18
class DbUpdater implements DbUpdaterInterface
19
{
20
    private const DB_COMPRESSED_FILE = 'GeoLite2-City.tar.gz';
21
    private const DB_DECOMPRESSED_FILE = 'GeoLite2-City.mmdb';
22
23
    /** @var ClientInterface */
24
    private $httpClient;
25
    /** @var Filesystem */
26
    private $filesystem;
27
    /** @var GeoLite2Options */
28
    private $options;
29
30
    public function __construct(ClientInterface $httpClient, Filesystem $filesystem, GeoLite2Options $options)
31
    {
32
        $this->httpClient = $httpClient;
33
        $this->filesystem = $filesystem;
34
        $this->options = $options;
35
    }
36
37
    /**
38
     * @throws RuntimeException
39
     */
40
    public function downloadFreshCopy(?callable $handleProgress = null): void
41
    {
42
        $tempDir = $this->options->getTempDir();
43
        $compressedFile = sprintf('%s/%s', $tempDir, self::DB_COMPRESSED_FILE);
44
45
        $this->downloadDbFile($compressedFile, $handleProgress);
46
        $tempFullPath = $this->extractDbFile($compressedFile, $tempDir);
47
        $this->copyNewDbFile($tempFullPath);
48
        $this->deleteTempFiles([$compressedFile, $tempFullPath]);
49
    }
50
51
    private function downloadDbFile(string $dest, ?callable $handleProgress = null): void
52
    {
53
        try {
54
            $this->httpClient->request(RequestMethod::METHOD_GET, $this->options->getDownloadFrom(), [
55
                RequestOptions::SINK => $dest,
56
                RequestOptions::PROGRESS => $handleProgress,
57
            ]);
58
        } catch (Throwable | GuzzleException $e) {
59
            throw new RuntimeException(
60
                'An error occurred while trying to download a fresh copy of the GeoLite2 database',
61
                0,
62
                $e
63
            );
64
        }
65
    }
66
67
    private function extractDbFile(string $compressedFile, string $tempDir): string
68
    {
69
        try {
70
            $phar = new PharData($compressedFile);
71
            $internalPathToDb = sprintf('%s/%s', $phar->getBasename(), self::DB_DECOMPRESSED_FILE);
72
            $phar->extractTo($tempDir, $internalPathToDb, true);
73
74
            return sprintf('%s/%s', $tempDir, $internalPathToDb);
75
        } catch (Throwable $e) {
76
            throw new RuntimeException(
77
                sprintf('An error occurred while trying to extract the GeoLite2 database from %s', $compressedFile),
78
                0,
79
                $e
80
            );
81
        }
82
    }
83
84
    private function copyNewDbFile(string $from): void
85
    {
86
        try {
87
            $this->filesystem->copy($from, $this->options->getDbLocation(), true);
88
        } catch (FilesystemException\FileNotFoundException | FilesystemException\IOException $e) {
89
            throw new RuntimeException('An error occurred while trying to copy GeoLite2 db file to destination', 0, $e);
90
        }
91
    }
92
93
    private function deleteTempFiles(array $files): void
94
    {
95
        try {
96
            $this->filesystem->remove($files);
97
        } catch (FilesystemException\IOException $e) {
98
            // Ignore any error produced when trying to delete temp files
99
        }
100
    }
101
102
    public function databaseFileExists(): bool
103
    {
104
        return $this->filesystem->exists($this->options->getDbLocation());
105
    }
106
}
107