Updater::buildIndexUrl()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace PhpCfdi\RfcLinc\Updater;
6
7
use EngineWorks\ProgressStatus\NullProgress;
8
use EngineWorks\ProgressStatus\ProgressInterface;
9
use PhpCfdi\RfcLinc\DataGateway\FactoryInterface;
10
use PhpCfdi\RfcLinc\Domain\Catalog;
11
use PhpCfdi\RfcLinc\Domain\VersionDate;
12
use PhpCfdi\RfcLinc\Downloader\DownloaderInterface;
13
use PhpCfdi\RfcLinc\Downloader\PhpDownloader;
14
use PhpCfdi\RfcLinc\Util\ReaderInterface;
15
use PhpCfdi\RfcLinc\Util\TemporaryFilename;
16
use Psr\Log\LoggerInterface;
17
use Psr\Log\NullLogger;
18
19
class Updater
20
{
21
    const URL_BLOBS_LIST = 'https://cfdisat.blob.core.windows.net/lco?restype=container&comp=list';
22
23
    /** @var VersionDate */
24
    private $date;
25
26
    /** @var FactoryInterface */
27
    private $gateways;
28
29
    /** @var DownloaderInterface */
30
    private $downloader;
31
32
    /** @var IndexInterpreter */
33
    private $indexInterpreter;
34
35
    /** @var Importer|null */
36
    private $importer;
37
38
    /** @var LoggerInterface */
39
    private $logger;
40
41
    /** @var ProgressInterface */
42
    private $progress;
43
44 12
    public function __construct(VersionDate $date, FactoryInterface $gateways)
45
    {
46 12
        $this->date = $date;
47 12
        $this->gateways = $gateways;
48 12
        $this->downloader = new PhpDownloader();
49 12
        $this->indexInterpreter = new IndexInterpreter();
50 12
        $this->logger = new NullLogger();
51 12
        $this->progress = new NullProgress();
52 12
    }
53
54 1
    public function hasImporter(): bool
55
    {
56 1
        return ($this->importer instanceof Importer);
57
    }
58
59 3
    public function importer(): Importer
60
    {
61 3
        if ($this->importer instanceof Importer) {
62 2
            return $this->importer;
63
        }
64 1
        throw new \LogicException('There is no importer, did you call run() method?');
65
    }
66
67 2
    public function catalog(): Catalog
68
    {
69 2
        return $this->importer()->catalog();
70
    }
71
72 3
    public function progress(): ProgressInterface
73
    {
74 3
        return $this->progress;
75
    }
76
77 1
    public function date(): VersionDate
78
    {
79 1
        return $this->date;
80
    }
81
82 1
    public function gateways(): FactoryInterface
83
    {
84 1
        return $this->gateways;
85
    }
86
87 2
    public function downloader(): DownloaderInterface
88
    {
89 2
        return $this->downloader;
90
    }
91
92 2
    public function indexInterpreter(): IndexInterpreter
93
    {
94 2
        return $this->indexInterpreter;
95
    }
96
97 2
    public function logger(): LoggerInterface
98
    {
99 2
        return $this->logger;
100
    }
101
102 2
    public function setDownloader(DownloaderInterface $downloader)
103
    {
104 2
        $this->downloader = $downloader;
105 2
    }
106
107 1
    public function setIndexInterpreter(IndexInterpreter $indexInterpreter)
108
    {
109 1
        $this->indexInterpreter = $indexInterpreter;
110 1
    }
111
112 2
    public function setLogger(LoggerInterface $logger)
113
    {
114 2
        $this->logger = $logger;
115 2
    }
116
117 2
    public function setProgress(ProgressInterface $progress)
118
    {
119 2
        $this->progress = $progress;
120 2
    }
121
122 1
    public function run(): int
123
    {
124 1
        $indexUrl = $this->indexUrl();
125 1
        $this->logger->info("Processing {$indexUrl}...");
126
127 1
        $this->logger->debug("Downloading {$indexUrl}...");
128 1
        $indexContents = $this->downloader->download($indexUrl);
129
130 1
        $this->logger->debug('Obtaining blobs...');
131 1
        $blobs = $this->indexInterpreter->obtainBlobs($indexContents);
132 1
        $blobsCount = count($blobs);
133
134 1
        $this->logger->debug("Processing $blobsCount blobs...");
135 1
        $processedLines = $this->runBlobs(...$blobs);
136
137 1
        $this->logger->info(sprintf('Processed %s lines', number_format($processedLines)));
138 1
        return $processedLines;
139
    }
140
141 1
    public function runBlobs(Blob ...$blobs): int
142
    {
143 1
        $processedLines = 0;
144 1
        $this->processBegin();
145 1
        foreach ($blobs as $blob) {
146 1
            $processedLines = $processedLines + $this->processBlob($blob);
147
        }
148 1
        $this->processEnd();
149
150 1
        return $processedLines;
151
    }
152
153 2
    public function processBegin()
154
    {
155 2
        $this->logger->notice('Starting general process...');
156
157
        // obtain or create version
158 2
        $gwCatalogs = $this->gateways->catalog();
159 2
        if ($gwCatalogs->exists($this->date)) {
160
            throw new \RuntimeException('The version is already in the catalog, it was not expected to exists');
161
        }
162
        // start optimizations
163 2
        $this->gateways->optimizer()->prepare();
164
165
        // create and store version
166 2
        $catalog = new Catalog($this->date, 0, 0, 0, 0);
167 2
        $gwCatalogs->insert($catalog);
168
169
        // create importer
170 2
        $this->importer = new Importer($catalog, $this->gateways, $this->progress());
171
172
        // set all records as deleted
173 2
        $this->gateways->listedRfc()->markAllAsDeleted();
174
175 2
        $this->logger->debug('General process started');
176 2
    }
177
178 1
    public function processBlob(Blob $blob): int
179
    {
180
        // create temp file
181 1
        $downloaded = new TemporaryFilename();
182 1
        $filename = (string) $downloaded;
183 1
        $url = $blob->url();
184 1
        $expectedMd5 = $blob->md5();
185
186 1
        $this->logger->info("Downloading $url...");
187
188
        // download the resourse
189 1
        $this->logger->debug("Downloading $url into $filename...");
190 1
        $downloadStart = time();
191 1
        $this->downloader->downloadAs($url, $filename);
192 1
        $downloadElapsed = time() - $downloadStart;
193 1
        $this->logger->debug("Download $url takes $downloadElapsed seconds");
194
195
        // check the md5 checksum
196 1
        if ('' !== $expectedMd5) {
197 1
            $this->logger->debug("Checking $expectedMd5 on $filename...");
198 1
            $this->checkFileMd5($filename, $expectedMd5);
199
        }
200
201
        // process file
202 1
        $this->logger->debug("Opening $filename (as packed data)...");
203 1
        $reader = $this->createReaderForPackedFile($filename);
204 1
        $processedLines = $this->processReader($reader);
205 1
        $this->logger->debug("Closing $filename...");
206 1
        $reader->close();
207
208 1
        $this->logger->notice(sprintf('Blob %s process %s lines', $url, number_format($processedLines)));
209
210
        // clear the resource
211 1
        $this->logger->debug("Removing $filename...");
212 1
        $downloaded->unlink();
213
214 1
        return $processedLines;
215
    }
216
217 2
    public function processReader(ReaderInterface $reader): int
218
    {
219 2
        return $this->importer()->importReader($reader);
220
    }
221
222 2
    public function processEnd()
223
    {
224 2
        $importer = $this->importer();
225 2
        $catalog = $importer->catalog();
226 2
        $gwRfc = $this->gateways->listedRfc();
227
228
        // count how many were deleted and log
229 2
        $this->logger->debug('Checking deletes...');
230 2
        foreach ($gwRfc->eachDeleted() as $rfc) {
231 1
            $importer->performDelete($rfc);
232
        }
233 2
        $this->logger->debug(sprintf('Found %s lines deleted', number_format($catalog->deleted())));
234
235 2
        $active = $gwRfc->countDeleted(false);
236 2
        $catalog->setRecords($active);
237 2
        $this->logger->info(sprintf('Found %s RFC active', number_format($active)));
238
239
        // store current version
240 2
        $this->logger->debug('Saving version...');
241 2
        $this->gateways->catalog()->update($catalog);
242
243
        // end optimizations
244 2
        $this->gateways->optimizer()->finish();
245 2
        $this->logger->notice('General process finish');
246 2
    }
247
248 2
    public function checkFileMd5(string $filename, string $expectedMd5)
249
    {
250
        // check md5
251 2
        $md5file = (string) md5_file($filename);
252 2
        if ($md5file !== $expectedMd5) {
253 1
            throw new \RuntimeException(sprintf(
254 1
                'The MD5 from file "%s" does not match with "%s"',
255 1
                $md5file,
256 1
                $expectedMd5
257
            ));
258
        }
259 2
    }
260
261 1
    public function createReaderForPackedFile(string $filename): ReaderInterface
262
    {
263 1
        $reader = new PackedBlobReader();
264 1
        $reader->open($filename);
265 1
        return $reader;
266
    }
267
268 1
    public function indexUrl(): string
269
    {
270 1
        return $this->buildIndexUrl($this->date);
271
    }
272
273 1
    public static function buildIndexUrl(VersionDate $date): string
274
    {
275 1
        return static::URL_BLOBS_LIST . '&prefix=l_RFC_' . $date->format('_');
276
    }
277
}
278