Completed
Pull Request — master (#51)
by Hiraku
02:16
created

ParallelDownloader::download()   F

Complexity

Conditions 21
Paths 0

Size

Total Lines 121
Code Lines 82

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 73
CRAP Score 22.5243

Importance

Changes 24
Bugs 7 Features 1
Metric Value
c 24
b 7
f 1
dl 0
loc 121
ccs 73
cts 86
cp 0.8488
rs 2
cc 21
eloc 82
nc 0
nop 2
crap 22.5243

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 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)) {
0 ignored issues
show
Unused Code introduced by
This while loop is empty and can be removed.

This check looks for while loops 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.

Loading history...
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)) {
0 ignored issues
show
Unused Code introduced by
This while loop is empty and can be removed.

This check looks for while loops 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.

Loading history...
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