Completed
Push — master ( 77ebaa...7f75f3 )
by Webysther
02:10
created

Create::moveToPublic()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 3
nop 0
dl 0
loc 8
ccs 4
cts 4
cp 1
crap 3
rs 9.4285
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 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
            $this->moveToPublic();
139
        }
140 1
141
        $this->initialized = $this->filesystem->hasFile(self::INIT);
142 1
143
        $newPackages = json_encode($this->providers, JSON_PRETTY_PRINT);
144
145 1
        // No provider changed? Just relax...
146
        if ($this->filesystem->has(self::MAIN) && !$this->initialized) {
147
            $old = $this->filesystem->getHashFile(self::MAIN);
148
            $new = $this->filesystem->getHash($newPackages);
149
150
            if ($old == $new) {
151
                $this->output->writeln(self::MAIN.' <info>updated</>');
152
                $this->setExitCode(0);
153
154
                return true;
155
            }
156
        }
157 1
158 1
        if (!$this->filesystem->has(self::MAIN)) {
159
            $this->initialized = true;
160
        }
161 1
162 1
        $this->provider->setInitialized($this->initialized);
163
        $this->filesystem->write(self::DOT, $newPackages);
164 1
165
        return false;
166
    }
167
168
    /**
169
     * Copy all public resources to public
170
     *
171
     * @return void
172 1
     */
173
    protected function moveToPublic():void
174 1
    {
175 1
        $from = getcwd().'/resources/public/';
176
        foreach (new \DirectoryIterator($from) as $fileInfo) {
177
            if($fileInfo->isDot()) continue;
178 1
            $file = $fileInfo->getFilename();
179 1
            $to = $this->filesystem->getFullPath($file);
180
            copy($from.$file, $to);
181
        }
182 1
    }
183
184
    /**
185
     * Download packages.json & provider-xxx$xxx.json.
186 1
     *
187 1
     * @return Create
188
     */
189 1
    protected function downloadProviders():Create
190
    {
191 1
        $this->output->writeln(
192 1
            'Loading providers from <info>'.$this->http->getBaseUri().'</>'
193 1
        );
194 1
195
        $this->providers = $this->provider->addFullPath(
196 1
            $this->package->getMainJson()
197 1
        );
198 1
199
        if ($this->isEqual()) {
200
            return $this;
201 1
        }
202
203
        $this->providerIncludes = $this->provider->normalize($this->providers);
204
        $generator = $this->provider->getGenerator($this->providerIncludes);
205
206
        $this->progressBar->start(count($this->providerIncludes));
207 1
208
        $success = function ($body, $path) {
209
            $this->provider->setDownloaded($path);
210
            $this->filesystem->write($path, $body);
211
        };
212
213
        $this->http->pool($generator, $success, $this->getClosureComplete());
214
        $this->progressBar->end();
215 1
        $this->showErrors();
216
217 1
        // If initialized can have provider downloaded by half
218
        if ($generator->getReturn() && !$this->initialized) {
219 1
            $this->output->writeln('All providers are <info>updated</>');
220 1
221
            return $this->setExitCode(0);
222
        }
223
224
        return $this;
225
    }
226
227
    /**
228
     * Show errors.
229
     *
230
     * @return Create
231
     */
232
    protected function showErrors():Create
233
    {
234
        $errors = $this->http->getPoolErrors();
235
236
        if (!$this->isVerbose() || empty($errors)) {
237
            return $this;
238
        }
239
240
        $rows = [];
241
        foreach ($errors as $path => $reason) {
242
            list('code' => $code, 'host' => $host, 'message' => $message) = $reason;
243
244
            $error = $code;
245
            if (!$error) {
246
                $error = $message;
247
            }
248
249
            $rows[] = [
250
                '<info>'.$host.'</>',
251
                '<comment>'.$this->shortname($path).'</>',
252
                '<error>'.$error.'</>',
253
            ];
254
        }
255
256
        $table = new Table($this->output);
257
        $table->setHeaders(['Mirror', 'Path', 'Error']);
258
        $table->setRows($rows);
259
        $table->render();
260
261
        return $this;
262
    }
263
264
    /**
265
     * Disable mirror when due lots of errors.
266
     */
267
    protected function disableDueErrors()
268
    {
269
        $mirrors = $this->http->getMirror()->toArray();
270
271
        foreach ($mirrors as $mirror) {
272
            $total = $this->http->getTotalErrorByMirror($mirror);
273
            if ($total < 1000) {
274
                continue;
275
            }
276
277
            $this->output->write(PHP_EOL);
278 1
            $this->output->writeln(
279
                'Due to <error>'.$total.
280 1
                ' errors</> mirror <comment>'.
281 1
                $mirror.'</> will be disabled'
282
            );
283 1
            $this->output->write(PHP_EOL);
284 1
            $this->http->getMirror()->remove($mirror);
285 1
        }
286
287 1
        return $this;
288 1
    }
289 1
290 1
    /**
291
     * Download packages listed on provider-*.json on public/p dir.
292
     *
293 1
     * @return Create
294 1
     */
295
    protected function downloadPackages():Create
296
    {
297 1
        $providerIncludes = $this->provider->getDownloaded();
298 1
        $totalProviders = count($providerIncludes);
299 1
300 1
        foreach ($providerIncludes as $counter => $uri) {
301
            $this->currentProvider = $uri;
302
            $shortname = $this->shortname($uri);
303
304
            ++$counter;
305
            $this->output->writeln(
306
                '['.$counter.'/'.$totalProviders.']'.
307
                ' Loading packages from <info>'.$shortname.'</> provider'
308
            );
309
310
            if ($this->initialized) {
311
                $this->http->useMirrors();
312
            }
313 1
314
            $this->providerPackages = $this->package->getProvider($uri);
315 1
            $generator = $this->package->getGenerator($this->providerPackages);
316 1
            $this->progressBar->start(count($this->providerPackages));
317
            $this->poolPackages($generator);
318 1
            $this->progressBar->end();
319 1
            $this->showErrors()->disableDueErrors()->fallback();
320 1
        }
321 1
322
        return $this;
323 1
    }
324
325
    /**
326
     * @param Generator $generator
327
     *
328
     * @return Create
329
     */
330
    protected function poolPackages(Generator $generator):Create
331
    {
332
        $this->http->pool(
333
            $generator,
334 1
            // Success
335 1
            function ($body, $path) {
336 1
                $this->filesystem->write($path, $body);
337
                $this->package->setDownloaded($path);
338
            },
339
            // If complete, even failed and success
340
            $this->getClosureComplete()
341
        );
342
343
        return $this;
344
    }
345
346
    /**
347
     * @return Closure
348
     */
349
    protected function getClosureComplete():Closure
350
    {
351
        return function () {
352
            $this->progressBar->progress();
353
        };
354
    }
355
356
    /**
357
     * Fallback to main mirror when other mirrors failed.
358
     *
359
     * @return Create
360
     */
361
    protected function fallback():Create
362
    {
363
        $total = count($this->http->getPoolErrors());
364
365
        if (!$total) {
366
            return $this;
367
        }
368
369
        $shortname = $this->shortname($this->currentProvider);
370
371
        $this->output->writeln(
372
            'Fallback packages from <info>'.$shortname.
373
            '</> provider to main mirror <info>'.$this->http->getBaseUri().'</>'
374
        );
375
376
        $this->providerPackages = $this->http->getPoolErrors();
377
        $generator = $this->package->getGenerator($this->providerPackages);
378
        $this->progressBar->start($total);
379
        $this->poolPackages($generator);
380
        $this->progressBar->end();
381
        $this->showErrors();
382
383
        return $this;
384
    }
385
386
    /**
387
     * Generate HTML of index.html.
388
     */
389
    protected function generateHtml():Create
390
    {
391
        ob_start();
392
        $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...
393
        $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...
394
        $maintainerMirror = getenv('MAINTAINER_MIRROR');
1 ignored issue
show
Unused Code introduced by
The assignment to $maintainerMirror is dead and can be removed.
Loading history...
395
        $maintainerProfile = getenv('MAINTAINER_PROFILE');
1 ignored issue
show
Unused Code introduced by
The assignment to $maintainerProfile is dead and can be removed.
Loading history...
396
        $maintainerRepo = getenv('MAINTAINER_REPO');
1 ignored issue
show
Unused Code introduced by
The assignment to $maintainerRepo is dead and can be removed.
Loading history...
397
        $maintainerLicense = getenv('MAINTAINER_LICENSE');
1 ignored issue
show
Unused Code introduced by
The assignment to $maintainerLicense is dead and can be removed.
Loading history...
398
        $tz = getenv('TZ');
1 ignored issue
show
Unused Code introduced by
The assignment to $tz is dead and can be removed.
Loading history...
399
        $synced = getenv('SLEEP');
0 ignored issues
show
Unused Code introduced by
The assignment to $synced is dead and can be removed.
Loading history...
400
        $file = $this->filesystem->getGzName('packages.json');
401
        $exists = $this->filesystem->hasFile($file);
402
        $html = $this->filesystem->getFullPath('index.html');
403
404
        $lastModified = false;
1 ignored issue
show
Unused Code introduced by
The assignment to $lastModified is dead and can be removed.
Loading history...
405
        if ($exists) {
406
            $lastModified = filemtime($html);
407
            unlink($html);
408
        }
409
410
        include_once getcwd().'/resources/index.html.php';
411
        file_put_contents($html, ob_get_clean());
412
        return $this;
413
    }
414
}
415