Completed
Pull Request — master (#54)
by Hiraku
02:27
created

CurlMulti::setTargets()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
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
class CurlMulti
10
{
11
    /** @var resource curl_multi */
12
    private $mh;
13
14
    /** @var resource[] curl */
15
    private $unused = array();
16
17
    /** @var resource[] curl */
18
    private $using = array();
19
20
    /** @var array {src: Aspects\HttpGetRequest, dest: OutputFile}*/
21
    private $targets;
22
23
    /** @var array {src: Aspects\HttpGetRequest, dest: OutputFile}*/
24
    private $runningTargets;
25
26
    /**
27
     * @param int $maxConnections
28
     */
29 3
    public function __construct($maxConnections)
30
    {
31 3
        $this->mh = curl_multi_init();
32
33 3
        for ($i = 0; $i < $maxConnections; ++$i) {
34 3
            $this->unused[] = curl_init();
35 3
        }
36 3
    }
37
38 3
    public function __destruct()
39
    {
40 3
        foreach ($this->using as $ch) {
41
            curl_multi_remove_handle($this->mh, $ch);
42
            curl_close($ch);
43 3
        }
44
45 3
        foreach ($this->unused as $ch) {
46 3
            curl_close($ch);
47 3
        }
48
49 3
        curl_multi_close($this->mh);
50 3
    }
51
52
    /**
53
     * @param bool $pipeline
54
     * @codeCoverageIgnore
55
     */
56
    public function setupShareHandler($pipeline)
57
    {
58
        if (function_exists('curl_share_init')) {
59
            $sh = curl_share_init();
60
            curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
61
62
            foreach ($this->unused as $ch) {
63
                curl_setopt($ch, CURLOPT_SHARE, $sh);
64
            }
65
        }
66
67
        if ($pipeline && function_exists('curl_multi_setopt')) {
68
            curl_multi_setopt($this->mh, CURLMOPT_PIPELINING, true);
69
        }
70
    }
71
72
    /**
73
     * @param array $targets {src: Aspects\HttpGetRequest, dest: OutputFile}
74
     */
75 3
    public function setTargets(array $targets)
76
    {
77 3
        $this->targets = $targets;
78 3
    }
79
80 3
    public function setupEventLoop()
81
    {
82 3
        while (count($this->unused) > 0 && count($this->targets) > 0) {
83 2
            $target = array_pop($this->targets);
84 2
            $ch = array_pop($this->unused);
85 2
            $index = (int)$ch;
86
87 2
            $this->using[$index] = $ch;
88 2
            $this->runningTargets[$index] = $target;
89 2
            Factory::getPreEvent($target['src'])->notify();
90
91 2
            $opts = $target['src']->getCurlOpts();
92 2
            unset($opts[CURLOPT_ENCODING], $opts[CURLOPT_USERPWD]);
93 2
            curl_setopt_array($ch, $opts);
94 2
            curl_setopt($ch, CURLOPT_FILE, $target['dest']->getPointer());
95 2
            curl_multi_add_handle($this->mh, $ch);
96 2
        }
97 3
    }
98
99 3
    public function wait()
100
    {
101 3
        $expectRunning = count($this->using);
102 3
        $running = 0;
103 3
        $retryCnt = 0;
104
105
        do {
106
            do {
107 3
                $stat = curl_multi_exec($this->mh, $running);
108 3
            } while ($stat === CURLM_CALL_MULTI_PERFORM);
109 3
            if (-1 === curl_multi_select($this->mh)) {
110
                // @codeCoverageIgnoreStart
111
                if ($retryCnt++ > 10) {
112
                    throw new \RuntimeException('curl_multi_select failure');
113
                }
114
                // @codeCoverageIgnoreEnd
115 2
                usleep(200 * 1000);
116 2
                continue;
117
            }
118 3
        } while ($running > 0 && $running >= $expectRunning);
119
120
        do {
121 3
            $stat = curl_multi_exec($this->mh, $running);
122 3
        } while ($stat === CURLM_CALL_MULTI_PERFORM);
123 3
    }
124
125 3
    public function getFinishedResults()
126
    {
127 3
        $results = array();
128 3
        $successCnt = $failureCnt = 0;
129
        do {
130 3
            if ($raised = curl_multi_info_read($this->mh, $remains)) {
131 2
                $ch = $raised['handle'];
132 2
                $errno = curl_errno($ch);
133 2
                $info = curl_getinfo($ch);
134 2
                curl_setopt($ch, CURLOPT_FILE, STDOUT);
135 2
                $index = (int)$ch;
136 2
                $target = $this->runningTargets[$index];
137 2
                if (CURLE_OK === $errno && 200 === $info['http_code']) {
138 1
                    ++$successCnt;
139 1
                    $target['dest']->setSuccess();
140 1
                    $results[] = $info['url'];
141 1
                } else {
142 1
                    ++$failureCnt;
143
                }
144 2
                unset($this->using[$index], $this->runningTargets[$index], $target);
145 2
                curl_multi_remove_handle($this->mh, $ch);
146 2
                $this->unused[] = $ch;
147 2
            }
148 3
        } while ($remains > 0);
149
150 3
        return compact('successCnt', 'failureCnt', 'results');
151
    }
152
153 3
    public function remain()
154
    {
155 3
        return count($this->runningTargets) > 0;
156
    }
157
}
158