Completed
Push — master ( 644e5f...9a3147 )
by Webysther
02:09
created

Create::getClosureComplete()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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