Completed
Push — master ( a27b17...016cb0 )
by Webysther
02:20
created

Create   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 361
Duplicated Lines 0 %

Test Coverage

Coverage 52.9%

Importance

Changes 0
Metric Value
dl 0
loc 361
ccs 82
cts 155
cp 0.529
rs 9.2
c 0
b 0
f 0
wmc 34

14 Methods

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