Completed
Push — master ( cf526e...c4e476 )
by Hiraku
02:24
created

ParallelDownloader::download()   F

Complexity

Conditions 25
Paths 0

Size

Total Lines 132
Code Lines 89

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 650

Importance

Changes 21
Bugs 7 Features 1
Metric Value
c 21
b 7
f 1
dl 0
loc 132
ccs 0
cts 83
cp 0
rs 2
cc 25
eloc 89
nc 0
nop 2
crap 650

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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