|
1
|
|
|
<?php |
|
2
|
|
|
/* |
|
3
|
|
|
* hirak/prestissimo |
|
4
|
|
|
* @author Hiraku NAKANO |
|
5
|
|
|
* @license MIT https://github.com/hirak/prestissimo |
|
6
|
|
|
*/ |
|
7
|
|
|
namespace Hirak\Prestissimo; |
|
8
|
|
|
|
|
9
|
|
|
use Composer\Package; |
|
10
|
|
|
use Composer\IO; |
|
11
|
|
|
use Composer\Config as CConfig; |
|
12
|
|
|
|
|
13
|
|
|
/** |
|
14
|
|
|
* |
|
15
|
|
|
*/ |
|
16
|
|
|
class ParallelDownloader |
|
17
|
|
|
{ |
|
18
|
|
|
/** @var IO/IOInterface */ |
|
19
|
|
|
protected $io; |
|
20
|
|
|
|
|
21
|
|
|
/** @var CConfig */ |
|
22
|
|
|
protected $config; |
|
23
|
|
|
|
|
24
|
|
|
/** @var int */ |
|
25
|
|
|
protected $totalCnt = 0; |
|
26
|
|
|
protected $successCnt = 0; |
|
27
|
|
|
protected $skippedCnt = 0; |
|
28
|
|
|
protected $failureCnt = 0; |
|
29
|
|
|
|
|
30
|
3 |
|
public function __construct(IO\IOInterface $io, CConfig $config) |
|
31
|
|
|
{ |
|
32
|
3 |
|
$this->io = $io; |
|
33
|
3 |
|
$this->config = $config; |
|
34
|
3 |
|
} |
|
35
|
|
|
|
|
36
|
|
|
/** |
|
37
|
|
|
* @param Package\PackageInterface[] $packages |
|
38
|
|
|
* @param array $pluginConfig |
|
39
|
|
|
* @return void |
|
40
|
|
|
*/ |
|
41
|
2 |
|
public function download(array $packages, array $pluginConfig) |
|
42
|
|
|
{ |
|
43
|
2 |
|
$mh = curl_multi_init(); |
|
44
|
2 |
|
$unused = array(); |
|
45
|
2 |
|
$maxConns = $pluginConfig['maxConnections']; |
|
46
|
2 |
|
for ($i = 0; $i < $maxConns; ++$i) { |
|
47
|
2 |
|
$unused[] = curl_init(); |
|
48
|
2 |
|
} |
|
49
|
|
|
|
|
50
|
2 |
|
$this->setupShareHandler($mh, $unused, $pluginConfig); |
|
51
|
|
|
|
|
52
|
2 |
|
$chFpMap = array(); |
|
53
|
2 |
|
$running = 0; //ref type |
|
54
|
2 |
|
$remains = 0; //ref type |
|
55
|
|
|
|
|
56
|
2 |
|
$this->totalCnt = count($packages); |
|
57
|
2 |
|
$this->successCnt = 0; |
|
58
|
2 |
|
$this->skippedCnt = 0; |
|
59
|
2 |
|
$this->failureCnt = 0; |
|
60
|
2 |
|
$this->io->write(" Prefetch start: total: $this->totalCnt</comment>"); |
|
61
|
|
|
|
|
62
|
2 |
|
$cachedir = rtrim($this->config->get('cache-files-dir'), '\/'); |
|
63
|
|
|
|
|
64
|
|
|
EVENTLOOP: |
|
65
|
|
|
// prepare curl resources |
|
66
|
2 |
|
while (count($unused) > 0 && count($packages) > 0) { |
|
67
|
2 |
|
$package = array_pop($packages); |
|
68
|
2 |
|
$filepath = $cachedir . DIRECTORY_SEPARATOR . static::getCacheKey($package); |
|
69
|
2 |
|
if (file_exists($filepath)) { |
|
70
|
|
|
++$this->skippedCnt; |
|
71
|
|
|
continue; |
|
72
|
|
|
} |
|
73
|
2 |
|
$ch = array_pop($unused); |
|
74
|
|
|
|
|
75
|
|
|
// make file resource |
|
76
|
2 |
|
$chFpMap[(int)$ch] = $outputFile = new OutputFile($filepath); |
|
77
|
|
|
|
|
78
|
|
|
// make url |
|
79
|
2 |
|
$url = $package->getDistUrl(); |
|
80
|
2 |
|
if (! $url) { |
|
81
|
1 |
|
++$this->skippedCnt; |
|
82
|
1 |
|
continue; |
|
83
|
|
|
} |
|
84
|
1 |
|
if ($package->getDistMirrors()) { |
|
85
|
|
|
$url = current($package->getDistUrls()); |
|
86
|
|
|
} |
|
87
|
1 |
|
$host = parse_url($url, PHP_URL_HOST) ?: ''; |
|
88
|
1 |
|
$request = Factory::getHttpGetRequest($host, $url, $this->io, $this->config, $pluginConfig); |
|
89
|
1 |
|
if (in_array($package->getName(), $pluginConfig['privatePackages'])) { |
|
90
|
|
|
$request->maybePublic = false; |
|
91
|
|
|
} else { |
|
92
|
1 |
|
$request->maybePublic = (bool)preg_match('%^(?:https|git)://github\.com%', $package->getSourceUrl()); |
|
93
|
|
|
} |
|
94
|
1 |
|
$onPreDownload = Factory::getPreEvent($request); |
|
95
|
1 |
|
$onPreDownload->notify(); |
|
96
|
|
|
|
|
97
|
1 |
|
$opts = $request->getCurlOpts(); |
|
98
|
1 |
|
unset($opts[CURLOPT_ENCODING]); |
|
99
|
1 |
|
unset($opts[CURLOPT_USERPWD]); // ParallelDownloader doesn't support private packages. |
|
100
|
1 |
|
curl_setopt_array($ch, $opts); |
|
101
|
1 |
|
curl_setopt($ch, CURLOPT_FILE, $outputFile->getPointer()); |
|
102
|
1 |
|
curl_multi_add_handle($mh, $ch); |
|
103
|
1 |
|
} |
|
104
|
|
|
|
|
105
|
|
|
// wait for any event |
|
106
|
|
|
do { |
|
107
|
2 |
|
$runningBefore = $running; |
|
108
|
2 |
|
while (CURLM_CALL_MULTI_PERFORM === curl_multi_exec($mh, $running)) { |
|
|
|
|
|
|
109
|
|
|
|
|
110
|
|
|
} |
|
111
|
|
|
|
|
112
|
|
|
SELECT: |
|
113
|
2 |
|
$eventCount = curl_multi_select($mh, 5); |
|
114
|
|
|
|
|
115
|
2 |
|
if ($eventCount === -1) { |
|
116
|
1 |
|
usleep(200 * 1000); |
|
117
|
1 |
|
continue; |
|
118
|
|
|
} |
|
119
|
|
|
|
|
120
|
1 |
|
while (CURLM_CALL_MULTI_PERFORM === curl_multi_exec($mh, $running)) { |
|
|
|
|
|
|
121
|
|
|
|
|
122
|
|
|
} |
|
123
|
|
|
|
|
124
|
1 |
|
if ($running > 0 && $running === $runningBefore) { |
|
125
|
|
|
goto SELECT; |
|
126
|
|
|
} |
|
127
|
|
|
|
|
128
|
|
|
do { |
|
129
|
1 |
|
if ($raised = curl_multi_info_read($mh, $remains)) { |
|
130
|
1 |
|
$ch = $raised['handle']; |
|
131
|
1 |
|
$errno = curl_errno($ch); |
|
132
|
1 |
|
$info = curl_getinfo($ch); |
|
133
|
1 |
|
curl_setopt($ch, CURLOPT_FILE, STDOUT); |
|
134
|
1 |
|
$index = (int)$ch; |
|
135
|
1 |
|
$outputFile = $chFpMap[$index]; |
|
136
|
1 |
|
unset($chFpMap[$index]); |
|
137
|
1 |
|
if (CURLE_OK === $errno && 200 === $info['http_code']) { |
|
138
|
|
|
++$this->successCnt; |
|
139
|
|
|
$outputFile->setSuccess(); |
|
140
|
|
|
} else { |
|
141
|
1 |
|
++$this->failureCnt; |
|
142
|
|
|
} |
|
143
|
1 |
|
unset($outputFile); |
|
144
|
1 |
|
$this->io->write($this->makeDownloadingText($info['url'])); |
|
145
|
1 |
|
curl_multi_remove_handle($mh, $ch); |
|
146
|
1 |
|
$unused[] = $ch; |
|
147
|
1 |
|
} |
|
148
|
1 |
|
} while ($remains > 0); |
|
149
|
|
|
|
|
150
|
1 |
|
if (count($packages) > 0) { |
|
151
|
|
|
goto EVENTLOOP; |
|
152
|
|
|
} |
|
153
|
2 |
|
} while ($running > 0); |
|
154
|
|
|
|
|
155
|
2 |
|
$this->io->write(" Finished: <comment>success: $this->successCnt, skipped: $this->skippedCnt, failure: $this->failureCnt, total: $this->totalCnt</comment>"); |
|
156
|
|
|
|
|
157
|
2 |
|
foreach ($unused as $ch) { |
|
158
|
2 |
|
curl_close($ch); |
|
159
|
2 |
|
} |
|
160
|
2 |
|
curl_multi_close($mh); |
|
161
|
2 |
|
} |
|
162
|
|
|
|
|
163
|
|
|
/** |
|
164
|
|
|
* @codeCoverageIgnore |
|
165
|
|
|
*/ |
|
166
|
|
|
private function setupShareHandler($mh, array $unused, array $pluginConfig) |
|
167
|
|
|
{ |
|
168
|
|
|
if (function_exists('curl_share_init')) { |
|
169
|
|
|
$sh = curl_share_init(); |
|
170
|
|
|
curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION); |
|
171
|
|
|
|
|
172
|
|
|
foreach ($unused as $ch) { |
|
173
|
|
|
curl_setopt($ch, CURLOPT_SHARE, $sh); |
|
174
|
|
|
} |
|
175
|
|
|
} |
|
176
|
|
|
|
|
177
|
|
|
if (function_exists('curl_multi_setopt')) { |
|
178
|
|
|
if ($pluginConfig['pipeline']) { |
|
179
|
|
|
curl_multi_setopt($mh, CURLMOPT_PIPELINING, true); |
|
180
|
|
|
} |
|
181
|
|
|
} |
|
182
|
|
|
} |
|
183
|
|
|
|
|
184
|
|
|
/** |
|
185
|
|
|
* @param string $url |
|
186
|
|
|
* @return string |
|
187
|
|
|
*/ |
|
188
|
1 |
|
private function makeDownloadingText($url) |
|
189
|
|
|
{ |
|
190
|
1 |
|
$request = new Aspects\HttpGetRequest('example.com', $url, $this->io); |
|
191
|
1 |
|
$request->query = array(); |
|
192
|
1 |
|
return " <comment>$this->successCnt/$this->totalCnt</comment>: {$request->getURL()}"; |
|
193
|
|
|
} |
|
194
|
|
|
|
|
195
|
2 |
|
public static function getCacheKey(Package\PackageInterface $p) |
|
196
|
|
|
{ |
|
197
|
2 |
|
$distRef = $p->getDistReference(); |
|
198
|
2 |
|
if (preg_match('{^[a-f0-9]{40}$}', $distRef)) { |
|
199
|
|
|
return "{$p->getName()}/$distRef.{$p->getDistType()}"; |
|
200
|
|
|
} |
|
201
|
|
|
|
|
202
|
2 |
|
return "{$p->getName()}/{$p->getVersion()}-$distRef.{$p->getDistType()}"; |
|
203
|
|
|
} |
|
204
|
|
|
} |
|
205
|
|
|
|
This check looks for
whileloops that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.Consider removing the loop.