Completed
Push — master ( d0aa7d...1ba86d )
by Webysther
02:09
created

Create::downloadProviders()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 46
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 27.3373

Importance

Changes 7
Bugs 0 Features 0
Metric Value
cc 6
eloc 25
c 7
b 0
f 0
nc 6
nop 0
dl 0
loc 46
ccs 4
cts 25
cp 0.16
crap 27.3373
rs 8.8977
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Packagist Mirror.
7
 *
8
 * For the full license information, please view the LICENSE.md
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Webs\Mirror\Command;
13
14
use Symfony\Component\Console\Input\InputInterface;
15
use Symfony\Component\Console\Input\InputOption;
16
use Symfony\Component\Console\Output\OutputInterface;
17
use Symfony\Component\Console\Helper\Table;
18
use Webs\Mirror\Provider;
19
use stdClass;
20
use Generator;
21
use Closure;
22
23
/**
24
 * Create a mirror.
25
 *
26
 * @author Webysther Nunes <[email protected]>
27
 */
28
class Create extends Base
29
{
30
    /**
31
     * @var stdClass
32
     */
33
    protected $providers;
34
35
    /**
36
     * @var array
37
     */
38
    protected $providerIncludes;
39
40
    /**
41
     * @var string
42
     */
43
    protected $currentProvider;
44
45
    /**
46
     * @var array
47
     */
48
    protected $providerPackages;
49
50
    /**
51
     * @var Clean
52
     */
53
    protected $clean;
54
55
    /**
56
     * @var array
57 1
     */
58
    protected $latestErrorsShowed = [];
59 1
60 1
    /**
61 1
     * {@inheritdoc}
62
     */
63 1
    public function __construct($name = '')
64
    {
65
        parent::__construct('create');
66
        $this->setDescription(
67
            'Create/update packagist mirror'
68 1
        );
69
    }
70 1
71 1
    /**
72
     * {@inheritdoc}
73
     */
74 1
    protected function configure()
75
    {
76
        parent::configure();
77 1
        $this->addOption(
78
            'no-clean',
79
            null,
80
            InputOption::VALUE_NONE,
81
            "Don't search for deleted packages from metadata: php bin/mirror clean --help"
82
        );
83
    }
84
85
    /**
86
     * {@inheritdoc}
87
     */
88
    public function execute(InputInterface $input, OutputInterface $output):int
89
    {
90
        $this->initialize($input, $output);
91
        $this->bootstrap();
92
93
        // Download providers
94
        $this->downloadProviders();
95
96
        // Download packages
97 1
        if ($this->stop() || $this->downloadPackages()->stop()) {
98
            return $this->getExitCode();
99 1
        }
100 1
101 1
        // Move to new location
102 1
        $this->filesystem->move(self::DOT);
103 1
104 1
        // Clean
105 1
        if(!$this->input->getOption('no-clean')){
106 1
            $this->setExitCode($this->clean->execute($input, $output));
107
        }
108
109
        if ($this->initialized) {
110
            $this->filesystem->delete(self::INIT);
111 1
        }
112
113 1
        return $this->getExitCode();
114
    }
115 1
116
    /**
117
     * @return void
118
     */
119
    public function bootstrap():void
120
    {
121
        $this->progressBar->setConsole($this->input, $this->output);
122
        $this->package->setConsole($this->input, $this->output);
123
        $this->package->setHttp($this->http);
124
        $this->package->setFilesystem($this->filesystem);
125
        $this->provider->setConsole($this->input, $this->output);
126
        $this->provider->setHttp($this->http);
127
        $this->provider->setFilesystem($this->filesystem);
128
    }
129
130
    /**
131
     * @param Clean $clean
132
     */
133 1
    public function setClean(Clean $clean):Create
134
    {
135
        $this->clean = $clean;
136 1
137 1
        return $this;
138
    }
139
140 1
    /**
141
     * @return int
142 1
     */
143
    protected function getExitCode():int
144
    {
145 1
        $this->generateHtml();
146
147
        return parent::getExitCode();
148
    }
149
150
    /**
151
     * Check if packages.json was changed.
152
     *
153
     * @return bool
154
     */
155
    protected function isEqual():bool
156
    {
157 1
        // if 'p/...' folder not found
158 1
        if (!is_dir($this->filesystem->getFullPath(self::TO))) {
159
            $this->filesystem->touch(self::INIT);
160
            $this->moveToPublic();
161 1
        }
162 1
163
        //usefull on dev frontend (move the frontend files every time):
164 1
        //$this->moveToPublic();
165
        $this->initialized = $this->filesystem->hasFile(self::INIT);
166
167
        $newPackages = json_encode($this->providers, JSON_PRETTY_PRINT);
168
169
        // No provider changed? Just relax...
170
        if ($this->filesystem->has(self::MAIN) && !$this->initialized) {
171
            $old = $this->filesystem->getHashFile(self::MAIN);
172 1
            $new = $this->filesystem->getHash($newPackages);
173
174 1
            if ($old == $new) {
175 1
                $this->output->writeln(self::MAIN.' <info>updated</>');
176
                $this->setExitCode(0);
177
178 1
                return true;
179 1
            }
180
        }
181
182 1
        if (!$this->filesystem->has(self::MAIN)) {
183
            $this->initialized = true;
184
        }
185
186 1
        $this->provider->setInitialized($this->initialized);
187 1
        $this->filesystem->write(self::DOT, $newPackages);
188
189 1
        return false;
190
    }
191 1
192 1
    /**
193 1
     * Copy all public resources to public
194 1
     *
195
     * @return void
196 1
     */
197 1
    protected function moveToPublic():void
198 1
    {
199
        $from = getcwd().'/resources/public/';
200
        foreach (new \DirectoryIterator($from) as $fileInfo) {
201 1
            if($fileInfo->isDot()) continue;
202
            $file = $fileInfo->getFilename();
203
            $to = $this->filesystem->getFullPath($file);
204
            copy($from.$file, $to);
205
        }
206
    }
207 1
208
    /**
209
     * Download packages.json & provider-xxx$xxx.json.
210
     *
211
     * @return Create
212
     */
213
    protected function downloadProviders():Create
214
    {
215 1
        $uri = $this->http->getBaseUri();
216
217 1
        $this->output->writeln(
218
            'Loading providers from <info>'.$uri.'</>'
219 1
        );
220 1
221
        if (!filter_var($uri, FILTER_VALIDATE_URL)) {
222
            $this->output->writeln('<error>The main mirror url is invalid!</>');
223
            return $this->setExitCode(1);
224
        }
225
226
        $this->providers = $this->provider->addFullPath(
227
            $this->package->getMainJson()
228
        );
229
230
        if ($this->isEqual()) {
231
            return $this;
232
        }
233
234
        $this->providerIncludes = $this->provider->normalize($this->providers);
235
        $generator = $this->provider->getGenerator($this->providerIncludes);
236
237
        $this->progressBar->start(count($this->providerIncludes));
238
239
        $success = function ($body, $path) {
240
            $this->provider->setDownloaded($path);
241
            $this->filesystem->write($path, $body);
242
        };
243
244
        $this->http->pool($generator, $success, $this->getClosureComplete());
245
        $this->progressBar->end();
246
        if(!$this->progressBar->isDisabled()){
247
            $this->output->write(PHP_EOL);
248
        }
249
        $this->showErrors();
250
251
        // If initialized can have provider downloaded by half
252
        if ($generator->getReturn() && !$this->initialized) {
253
            $this->output->writeln('All providers are <info>updated</>');
254
255
            return $this->setExitCode(0);
256
        }
257
258
        return $this;
259
    }
260
261
    /**
262
     * Show errors.
263
     *
264
     * @return Create
265
     */
266
    protected function showErrors():Create
267
    {
268
        if (!$this->isDebug()) {
269
            return $this;
270
        }
271
272
        $errors = $this->http->getPoolErrors();
273
        $rows = [];
274
        foreach ($errors as $path => $reason) {
275
            list('code' => $code, 'host' => $host, 'message' => $message) = $reason;
276
277
            $error = $code;
278 1
            if (!$error) {
279
                $error = $message;
280 1
            }
281 1
282
            $rows[] = [
283 1
                '<info>'.$host.'</>',
284 1
                '<comment>'.$this->shortname($path).'</>',
285 1
                '<error>'.$error.'</>',
286
            ];
287 1
        }
288 1
289 1
        if(!count($rows)){
290 1
            return $this;
291
        }
292
293 1
        $table = new Table($this->output);
294 1
        $table->setHeaders(['Mirror', 'Path', 'Error']);
295
        $table->setRows($rows);
296
        $table->render();
297 1
298 1
        return $this;
299 1
    }
300 1
301
    /**
302
     * Disable mirror when due lots of errors.
303
     */
304
    protected function disableDueErrors()
305
    {
306
        $mirrors = $this->http->getMirror()->toArray();
307
308
        foreach ($mirrors as $mirror) {
309
            $total = $this->http->getTotalErrorByMirror($mirror);
310
311
            if(!isset($this->latestErrorsShowed[$mirror])){
312
                $this->latestErrorsShowed[$mirror] = 0;
313 1
            }
314
315 1
            if ($total < 100) {
316 1
                if ($this->isDebug() && $total > 1) {
317
                    if($this->latestErrorsShowed[$mirror] == $total){
318 1
                        continue;
319 1
                    }
320 1
321 1
                    $this->latestErrorsShowed[$mirror] = $total;
322
                    $softError = '<error>'.$total.' errors</> mirror <comment>';
323 1
                    $softError = $softError.$mirror.'</>';
324
                    $this->output->writeln($softError);
325
                }
326
                
327
                continue;
328
            }
329
330
            $this->output->write(PHP_EOL);
331
            $this->output->writeln(
332
                'Due to <error>'.$total.
333
                ' errors</> mirror <comment>'.
334 1
                $mirror.'</> will be disabled'
335 1
            );
336 1
            $this->output->write(PHP_EOL);
337
            $this->http->getMirror()->remove($mirror);
338
        }
339
340
        return $this;
341
    }
342
343
    /**
344
     * Download packages listed on provider-*.json on public/p dir.
345
     *
346
     * @return Create
347
     */
348
    protected function downloadPackages():Create
349
    {
350
        $providerIncludes = $this->provider->getDownloaded();
351
        $totalProviders = count($providerIncludes);
352
353
        foreach ($providerIncludes as $counter => $uri) {
354
            $this->currentProvider = $uri;
355
            $shortname = $this->shortname($uri);
356
357
            ++$counter;
358
            $this->output->writeln(
359
                '['.$counter.'/'.$totalProviders.']'.
360
                ' Loading packages from <info>'.$shortname.'</> provider'
361
            );
362
363
            if ($this->initialized) {
364
                $this->http->useMirrors();
365
            }
366
367
            $this->providerPackages = $this->package->getProvider($uri);
368
            $generator = $this->package->getGenerator($this->providerPackages);
369
            $this->progressBar->start(count($this->providerPackages));
370
            $this->poolPackages($generator);
371
            $this->progressBar->end();
372
            if(!$this->progressBar->isDisabled()){
373
                $this->output->write(PHP_EOL);
374
            }
375
            $this->showErrors()->disableDueErrors()->fallback();
376
        }
377
378
        return $this;
379
    }
380
381
    /**
382
     * @param Generator $generator
383
     *
384
     * @return Create
385
     */
386
    protected function poolPackages(Generator $generator):Create
387
    {
388
        $this->http->pool(
389
            $generator,
390
            // Success
391
            function ($body, $path) {
392
                $this->filesystem->write($path, $body);
393
                $this->package->setDownloaded($path);
394
            },
395
            // If complete, even failed and success
396
            $this->getClosureComplete()
397
        );
398
399
        return $this;
400
    }
401
402
    /**
403
     * @return Closure
404
     */
405
    protected function getClosureComplete():Closure
406
    {
407
        return function () {
408
            $this->progressBar->progress();
409
        };
410
    }
411
412
    /**
413
     * Fallback to main mirror when other mirrors failed.
414
     *
415
     * @return Create
416
     */
417
    protected function fallback():Create
418
    {
419
        $total = count($this->http->getPoolErrors());
420
421
        if (!$total) {
422
            return $this;
423
        }
424
425
        $shortname = $this->shortname($this->currentProvider);
426
427
        $this->output->writeln(
428
            'Fallback packages from <info>'.$shortname.
429
            '</> provider to main mirror <info>'.$this->http->getBaseUri().'</>'
430
        );
431
432
        $this->providerPackages = $this->http->getPoolErrors();
433
        $generator = $this->package->getGenerator($this->providerPackages);
434
        $this->progressBar->start($total);
435
        $this->poolPackages($generator);
436
        $this->progressBar->end();
437
        if(!$this->progressBar->isDisabled()){
438
            $this->output->write(PHP_EOL);
439
        }
440
        $this->showErrors();
441
442
        return $this;
443
    }
444
445
    /**
446
     * Generate HTML of index.html.
447
     */
448
    protected function generateHtml():Create
449
    {
450
        ob_start();
451
        $countryName = getenv('APP_COUNTRY_NAME');
1 ignored issue
show
Unused Code introduced by
The assignment to $countryName is dead and can be removed.
Loading history...
452
        $countryCode = getenv('APP_COUNTRY_CODE');
1 ignored issue
show
Unused Code introduced by
The assignment to $countryCode is dead and can be removed.
Loading history...
453
        $maintainerMirror = getenv('MAINTAINER_MIRROR');
1 ignored issue
show
Unused Code introduced by
The assignment to $maintainerMirror is dead and can be removed.
Loading history...
454
        $maintainerProfile = getenv('MAINTAINER_PROFILE');
1 ignored issue
show
Unused Code introduced by
The assignment to $maintainerProfile is dead and can be removed.
Loading history...
455
        $maintainerRepo = getenv('MAINTAINER_REPO');
1 ignored issue
show
Unused Code introduced by
The assignment to $maintainerRepo is dead and can be removed.
Loading history...
456
        $maintainerLicense = getenv('MAINTAINER_LICENSE');
1 ignored issue
show
Unused Code introduced by
The assignment to $maintainerLicense is dead and can be removed.
Loading history...
457
        $tz = getenv('TZ');
1 ignored issue
show
Unused Code introduced by
The assignment to $tz is dead and can be removed.
Loading history...
458
        $synced = getenv('SLEEP');
1 ignored issue
show
Unused Code introduced by
The assignment to $synced is dead and can be removed.
Loading history...
459
        $googleAnalyticsId = getenv('GOOGLE_ANALYTICS_ID');
1 ignored issue
show
Unused Code introduced by
The assignment to $googleAnalyticsId is dead and can be removed.
Loading history...
460
        $googleAnalyticsMainId = getenv('GOOGLE_ANALYTICS_MAIN_ID');
1 ignored issue
show
Unused Code introduced by
The assignment to $googleAnalyticsMainId is dead and can be removed.
Loading history...
461
        $file = $this->filesystem->getGzName('packages.json');
1 ignored issue
show
Unused Code introduced by
The assignment to $file is dead and can be removed.
Loading history...
462
        $exists = $this->filesystem->hasFile('index.html');
463
        $html = $this->filesystem->getFullPath('index.html');
464
465
        $lastModified = false;
1 ignored issue
show
Unused Code introduced by
The assignment to $lastModified is dead and can be removed.
Loading history...
466
        if ($exists) {
467
            $lastModified = filemtime($html);
468
            unlink($html);
469
        }
470
471
        include_once getcwd().'/resources/index.html.php';
472
        file_put_contents($html, ob_get_clean());
473
        return $this;
474
    }
475
}
476