Passed
Push — master ( a30145...bc128f )
by Webysther
36s
created

Create::checkPackagesWasChanged()   C

Complexity

Conditions 8
Paths 36

Size

Total Lines 39
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 0
Metric Value
cc 8
eloc 20
nc 36
nop 1
dl 0
loc 39
ccs 0
cts 28
cp 0
crap 72
rs 5.3846
c 0
b 0
f 0
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\Output\OutputInterface;
16
use Symfony\Component\Console\Helper\Table;
17
use Webs\Mirror\Provider;
18
use stdClass;
19
use Generator;
20
use Closure;
21
22
/**
23
 * Create a mirror.
24
 *
25
 * @author Webysther Nunes <[email protected]>
26
 */
27
class Create extends Base
28
{
29
    /**
30
     * @var stdClass
31
     */
32
    protected $providers;
33
34
    /**
35
     * @var array
36
     */
37
    protected $providerIncludes;
38
39
    /**
40
     * @var string
41
     */
42
    protected $currentProvider;
43
44
    /**
45
     * @var array
46
     */
47
    protected $providerPackages;
48
49
    /**
50
     * @var Clean
51
     */
52
    protected $clean;
53
54
    /**
55
     * {@inheritdoc}
56
     */
57
    public function __construct($name = '')
58
    {
59
        parent::__construct('create');
60
        $this->setDescription(
61
            'Create/update packagist mirror'
62
        );
63
    }
64
65
    /**
66
     * {@inheritdoc}
67
     */
68
    public function execute(InputInterface $input, OutputInterface $output):int
69
    {
70
        $this->progressBar->setConsole($input, $output);
71
        $this->package->setConsole($input, $output);
72
        $this->package->setHttp($this->http);
73
        $this->package->setFilesystem($this->filesystem);
74
        $this->provider->setConsole($input, $output);
75
        $this->provider->setHttp($this->http);
76
        $this->provider->setFilesystem($this->filesystem);
77
78
        // Download providers, with repository, is incremental
79
        if ($this->downloadProviders()->stop()) {
80
            return $this->getExitCode();
81
        }
82
83
        // Download packages
84
        if ($this->downloadPackages()->stop()) {
85
            return $this->getExitCode();
86
        }
87
88
        // Move to new location
89
        $this->filesystem->move(self::DOT);
90
91
        // Clean
92
        $this->setExitCode($this->clean->execute($input, $output));
93
94
        if ($this->initialized) {
95
            $this->filesystem->delete(self::INIT);
96
        }
97
98
        return $this->getExitCode();
99
    }
100
101
    /**
102
     * @param Clean $clean
103
     */
104
    public function setClean(Clean $clean):Create
105
    {
106
        $this->clean = $clean;
107
108
        return $this;
109
    }
110
111
    /**
112
     * @return int
113
     */
114
    protected function getExitCode():int
115
    {
116
        $this->generateHtml();
117
118
        return parent::getExitCode();
119
    }
120
121
    /**
122
     * Check if packages.json was changed.
123
     *
124
     * @return bool
125
     */
126
    protected function isEqual():bool
127
    {
128
        // if 'p/...' folder not found
129
        if (!is_dir($this->filesystem->getFullPath(self::TO))) {
130
            $this->filesystem->touch(self::INIT);
131
        }
132
133
        $this->initialized = $this->filesystem->hasFile(self::INIT);
134
135
        $newPackages = json_encode($this->providers, JSON_PRETTY_PRINT);
136
137
        // No provider changed? Just relax...
138
        if ($this->filesystem->has(self::MAIN) && !$this->initialized) {
139
            $old = $this->filesystem->getHashFile(self::MAIN);
140
            $new = $this->filesystem->getHash($newPackages);
141
142
            if ($old == $new) {
143
                $this->output->writeln(self::MAIN.' <info>updated</>');
144
                $this->setExitCode(0);
145
146
                return true;
147
            }
148
        }
149
150
        if (!$this->filesystem->has(self::MAIN)) {
151
            $this->initialized = true;
152
        }
153
154
        $this->provider->setInitialized($this->initialized);
155
        $this->filesystem->write(self::DOT, $newPackages);
156
157
        return false;
158
    }
159
160
    /**
161
     * Download packages.json & provider-xxx$xxx.json.
162
     *
163
     * @return Create
164
     */
165
    protected function downloadProviders():Create
166
    {
167
        $this->output->writeln(
168
            'Loading providers from <info>'.$this->http->getBaseUri().'</>'
169
        );
170
171
        $this->providers = $this->provider->addFullPath(
172
            $this->package->loadMainJson()
173
        );
174
175
        if ($this->isEqual()) {
176
            return $this;
177
        }
178
179
        $this->providerIncludes = $this->provider->normalize($this->providers);
180
        $generator = $this->provider->getGenerator($this->providerIncludes);
181
182
        $this->progressBar->start(count($this->providerIncludes));
183
184
        $success = function ($body, $path) {
185
            $this->provider->setDownloaded($path);
186
            $this->filesystem->write($path, $body);
187
        };
188
189
        $this->http->pool($generator, $success, $this->getClosureComplete());
190
        $this->progressBar->end();
191
        $this->showErrors();
192
193
        // If initialized can have provider downloaded by half
194
        if ($generator->getReturn() && !$this->initialized) {
195
            $this->output->writeln('All providers are <info>updated</>');
196
197
            return $this->setExitCode(0);
198
        }
199
200
        return $this;
201
    }
202
203
    /**
204
     * Show errors.
205
     *
206
     * @return Create
207
     */
208
    protected function showErrors():Create
209
    {
210
        $errors = $this->http->getPoolErrors();
211
212
        if (!$this->isVerbose() || empty($errors)) {
213
            return $this;
214
        }
215
216
        $rows = [];
217
        foreach ($errors as $path => $reason) {
218
            list('code' => $code, 'host' => $host, 'message' => $message) = $reason;
219
220
            $error = $code;
221
            if (!$error) {
222
                $error = $message;
223
            }
224
225
            $rows[] = [
226
                '<info>'.$host.'</>',
227
                '<comment>'.$this->shortname($path).'</>',
228
                '<error>'.$error.'</>',
229
            ];
230
        }
231
232
        $table = new Table($this->output);
233
        $table->setHeaders(['Mirror', 'Path', 'Error']);
234
        $table->setRows($rows);
235
        $table->render();
236
237
        return $this;
238
    }
239
240
    /**
241
     * Disable mirror when due lots of errors.
242
     */
243
    protected function disableDueErrors()
244
    {
245
        $mirrors = $this->http->getMirror()->toArray();
246
247
        foreach ($mirrors as $mirror) {
248
            $total = $this->http->getTotalErrorByMirror($mirror);
249
            if ($total < 1000) {
250
                continue;
251
            }
252
253
            $this->output->write(PHP_EOL);
254
            $this->output->writeln(
255
                'Due to <error>'.$total.
256
                ' errors</> mirror <comment>'.
257
                $mirror.'</> will be disabled'
258
            );
259
            $this->output->write(PHP_EOL);
260
            $this->http->getMirror()->remove($mirror);
261
        }
262
263
        return $this;
264
    }
265
266
    /**
267
     * Download packages listed on provider-*.json on public/p dir.
268
     *
269
     * @return Create
270
     */
271
    protected function downloadPackages():Create
272
    {
273
        $providerIncludes = $this->provider->getDownloaded();
274
        $totalProviders = count($providerIncludes);
275
276
        foreach ($providerIncludes as $counter => $uri) {
277
            $this->currentProvider = $uri;
278
            $shortname = $this->shortname($uri);
279
280
            ++$counter;
281
            $this->output->writeln(
282
                '['.$counter.'/'.$totalProviders.']'.
283
                ' Loading packages from <info>'.$shortname.'</> provider'
284
            );
285
286
            if ($this->initialized) {
287
                $this->http->useMirrors();
288
            }
289
290
            $this->providerPackages = $this->package->getProvider($uri);
291
            $generator = $this->package->getGenerator($this->providerPackages);
292
            $this->progressBar->start(count($this->providerPackages));
293
            $this->poolPackages($generator);
294
            $this->progressBar->end();
295
            $this->showErrors()->disableDueErrors()->fallback();
296
        }
297
298
        return $this;
299
    }
300
301
    /**
302
     * @param Generator $generator
303
     *
304
     * @return Create
305
     */
306
    protected function poolPackages(Generator $generator):Create
307
    {
308
        $this->http->pool(
309
            $generator,
310
            // Success
311
            function ($body, $path) {
312
                $this->filesystem->write($path, $body);
313
                $this->package->setDownloaded($path);
314
            },
315
            // If complete, even failed and success
316
            $this->getClosureComplete()
317
        );
318
319
        return $this;
320
    }
321
322
    /**
323
     * @return Closure
324
     */
325
    protected function getClosureComplete():Closure
326
    {
327
        return function () {
328
            $this->progressBar->progress();
329
        };
330
    }
331
332
    /**
333
     * Fallback to main mirror when other mirrors failed.
334
     *
335
     * @return Create
336
     */
337
    protected function fallback():Create
338
    {
339
        $total = count($this->http->getPoolErrors());
340
341
        if (!$total) {
342
            return $this;
343
        }
344
345
        $shortname = $this->shortname($this->currentProvider);
346
347
        $this->output->writeln(
348
            'Fallback packages from <info>'.$shortname.
349
            '</> provider to main mirror <info>'.$this->http->getBaseUri().'</>'
350
        );
351
352
        $this->providerPackages = $this->http->getPoolErrors();
353
        $generator = $this->package->getGenerator($this->providerPackages);
354
        $this->progressBar->start($total);
355
        $this->poolPackages($generator);
356
        $this->progressBar->end();
357
        $this->showErrors();
358
359
        return $this;
360
    }
361
362
    /**
363
     * Generate HTML of index.html.
364
     */
365
    protected function generateHtml():Create
366
    {
367
        ob_start();
368
        include getcwd().'/resources/index.html.php';
369
        file_put_contents('index.html', ob_get_clean());
370
371
        return $this;
372
    }
373
}
374