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() || !$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
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.