Completed
Pull Request — master (#51)
by Hiraku
10:33
created

ParallelDownloader::setupShareHandler()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 17
ccs 0
cts 16
cp 0
rs 8.8571
cc 5
eloc 9
nc 6
nop 3
crap 30
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 27 and the first side effect is on line 13.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
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
register_tick_function(function(){
14
    $bt = debug_backtrace(false);
15
    if (count($bt) > 1) {
16
        array_shift($bt);
17
        $b = current($bt);
18
        if (isset($b['line'])) {
19
            echo "$b[file]:$b[line]:$b[function]\n";
20
        }
21
    }
22
});
23
declare(ticks=1) {
24
/**
25
 *
26
 */
27
class ParallelDownloader
28
{
29
    /** @var IO/IOInterface */
30 1
    protected $io;
31
32 1
    /** @var CConfig */
33 1
    protected $config;
34 1
35
    /** @var int */
36
    protected $totalCnt = 0;
37
    protected $successCnt = 0;
38
    protected $skippedCnt = 0;
39
    protected $failureCnt = 0;
40
41
    public function __construct(IO\IOInterface $io, CConfig $config)
42
    {
43
        $this->io = $io;
44
        $this->config = $config;
45
    }
46
47
    /**
48
     * @param Package\PackageInterface[] $packages
49
     * @param array $pluginConfig
50
     * @return void
51
     */
52
    public function download(array $packages, array $pluginConfig)
53
    {
54
        $mh = curl_multi_init();
55
        $unused = array();
56
        for ($i = 0; $i < $pluginConfig['maxConnections']; ++$i) {
57
            $unused[] = curl_init();
58
        }
59
        $this->setupShareHandler($mh, $unused, $pluginConfig);
60
61
        $using = array(); //memory pool
62
        $running = 0;
63
        $remains = 0;
64
        $this->totalCnt = count($packages);
65
        $this->successCnt = 0;
66
        $this->skippedCnt = 0;
67
        $this->failureCnt = 0;
68
        $this->io->write("    Prefetch start: total: $this->totalCnt</comment>");
69
70
        $targets = $this->filterPackages($packages, $pluginConfig);
71
        EVENTLOOP:
72
        // prepare curl resources
73
        while (count($unused) > 0 && count($targets) > 0) {
74
            $using[(int)$ch] = $target = array_pop($targets);
0 ignored issues
show
Bug introduced by
The variable $ch does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
75
            $ch = array_pop($unused);
76
77
            $onPreDownload = Factory::getPreEvent($target['src']);
78
            $onPreDownload->notify();
79
80
            $opts = $target['src']->getCurlOpts();
81
            // ParallelDownloader doesn't support private packages.
82
            unset($opts[CURLOPT_ENCODING], $opts[CURLOPT_USERPWD]);
83
            curl_setopt_array($ch, $opts);
84
            curl_setopt($ch, CURLOPT_FILE, $target['dest']->getPointer());
85
            curl_multi_add_handle($mh, $ch);
86
        }
87
88
        // wait for any event
89
        do {
90
            $runningBefore = $running;
91
            while (CURLM_CALL_MULTI_PERFORM === curl_multi_exec($mh, $running));
92
93
            SELECT:
94
            if (-1 === curl_multi_select($mh, 5)) {
95
                usleep(200 * 1000);
96
                continue;
97
            }
98
99
            while (CURLM_CALL_MULTI_PERFORM === curl_multi_exec($mh, $running));
100
            if ($running > 0 && $running === $runningBefore) {
101
                goto SELECT;
102
            }
103
104
            do {
105
                if ($raised = curl_multi_info_read($mh, $remains)) {
106
                    $ch = $raised['handle'];
107
                    $errno = curl_errno($ch);
108
                    $info = curl_getinfo($ch);
109
                    curl_setopt($ch, CURLOPT_FILE, STDOUT);
110
                    $index = (int)$ch;
111
                    $target = $using[$index];
112
                    unset($using[$index]);
113
                    if (CURLE_OK === $errno && 200 === $info['http_code']) {
114
                        ++$this->successCnt;
115
                        $target['dest']->setSuccess();
116
                    } else {
117
                        ++$this->failureCnt;
118
                    }
119
                    unset($target);
120
                    $this->io->write($this->makeDownloadingText($info['url']));
121
                    curl_multi_remove_handle($mh, $ch);
122
                    $unused[] = $ch;
123
                }
124
            } while ($remains > 0);
125
126
            if (count($packages) > 0) {
127
                goto EVENTLOOP;
128
            }
129
        } while ($running > 0);
130
131
        $this->io->write("    Finished: <comment>success: $this->successCnt, skipped: $this->skippedCnt, failure: $this->failureCnt, total: $this->totalCnt</comment>");
132
133
        foreach ($unused as $ch) {
134
            curl_close($ch);
135
        }
136
        curl_multi_close($mh);
137
    }
138
139
    /**
140
     * @codeCoverageIgnore
141
     */
142
    private function setupShareHandler($mh, array $unused, array $pluginConfig)
143
    {
144
        if (function_exists('curl_share_init')) {
145
            $sh = curl_share_init();
146
            curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
147
148
            foreach ($unused as $ch) {
149
                curl_setopt($ch, CURLOPT_SHARE, $sh);
150
            }
151
        }
152
153
        if (function_exists('curl_multi_setopt')) {
154
            if ($pluginConfig['pipeline']) {
155
                curl_multi_setopt($mh, CURLMOPT_PIPELINING, true);
156
            }
157
        }
158
    }
159
160
    /**
161
     * @param Package\PackageInterface[] $packages
162
     * @param string[] $pluginConfig
163
     * @return [{src: Aspects\HttpGetRequest, dest: OutputFile}]
0 ignored issues
show
Documentation introduced by
The doc-type {src:">[{src: could not be parsed: Unknown type name "[" at position 0. [(view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
164
     */
165
    private function filterPackages(array $packages, array $pluginConfig)
166
    {
167
        $cachedir = rtrim($this->config->get('cache-files-dir'), '\/');
168
        $zips = array();
169
        foreach ($packages as $p) {
170
            $filepath = $cachedir . DIRECTORY_SEPARATOR . static::getCacheKey($p);
171
            if (file_exists($filepath)) {
172
                ++$this->skippedCnt;
173
                continue;
174
            }
175
            $url = $p->getDistUrl();
176
            if (!$url) {
177
                ++$this->skippedCnt;
178
                continue;
179
            }
180
            if ($p->getDistMirrors()) {
181
                $url = current($p->getDistUrls());
182
            }
183
            $host = parse_url($url, PHP_URL_HOST) ?: '';
184
            $src = Factory::getHttpGetRequest($host, $url, $this->io, $this->config, $pluginConfig);
185
            if (in_array($p->getName(), $pluginConfig['privatePackages'])) {
186
                $src->maybePublic = false;
187
            } else {
188
                $src->maybePublic = (bool)preg_match('%^(?:https|git)://github\.com%', $p->getSourceUrl());
189
            }
190
            // make file resource
191
            $dest = new OutputFile($filepath);
192
193
            $zips[] = compact('src', 'dest');
194
        }
195
196
        return $zips;
197
    }
198
199
    /**
200
     * @param string $url
201
     * @return string
202
     */
203
    private function makeDownloadingText($url)
204
    {
205
        $request = new Aspects\HttpGetRequest('example.com', $url, $this->io);
206
        $request->query = array();
207
        return "    <comment>$this->successCnt/$this->totalCnt</comment>:    {$request->getURL()}";
208
    }
209
210
    public static function getCacheKey(Package\PackageInterface $p)
211
    {
212
        $distRef = $p->getDistReference();
213
        if (preg_match('{^[a-f0-9]{40}$}', $distRef)) {
214
            return "{$p->getName()}/$distRef.{$p->getDistType()}";
215
        }
216
217
        return "{$p->getName()}/{$p->getVersion()}-$distRef.{$p->getDistType()}";
218
    }
219
}
220
}
221