Passed
Push — master ( 5e0187...08d2a7 )
by Webysther
02:13
created

Create::configure()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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