Completed
Pull Request — master (#44)
by Hiraku
07:00
created

CurlRemoteFilesystem::exec()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 5
Bugs 2 Features 0
Metric Value
c 5
b 2
f 0
dl 0
loc 20
ccs 3
cts 3
cp 1
rs 9.4285
cc 2
eloc 13
nc 2
nop 2
crap 2
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\Config as CConfig;
10
use Composer\IO;
11
use Composer\Downloader;
12
use Composer\Util;
13
14
/**
15
 * yet another implementation about Composer\Util\RemoteFilesystem
16
 * non thread safe
17
 */
18
class CurlRemoteFilesystem extends Util\RemoteFilesystem
19
{
20
    protected $io;
21
    protected $config;
22
    protected $options;
23
24
    protected $retryAuthFailure = true;
25
26
    protected $pluginConfig;
27
28
    private $_lastHeaders = array();
29
30
    // global flags
31
    private $_retry = false;
32
    private $_degradedMode = false;
33
34
    /** @var Aspects\JoinPoint */
35
    public $onPreDownload;
36
37
    /** @var Aspects\JoinPoint */
38
    public $onPostDownload;
39
40
    /**
41
     * @param IO\IOInterface $io
42
     * @param CConfig $config
43
     * @param array $options
44
     */
45 8
    public function __construct(IO\IOInterface $io, CConfig $config, array $options = array())
46
    {
47 8
        $this->io = $io;
48 8
        $this->config = $config;
49 8
        $this->options = $options;
50 8
    }
51
52 8
    public function setPluginConfig(array $pluginConfig)
53
    {
54 8
        $this->pluginConfig = $pluginConfig;
55 8
    }
56
57
    /**
58
     * Copy the remote file in local.
59
     *
60
     * @param string $origin    host/domain text
61
     * @param string $fileUrl   targeturl
62
     * @param string $fileName  the local filename
63
     * @param bool   $progress  Display the progression
64
     * @param array  $options   Additional context options
65
     *
66
     * @return bool true
67
     */
68 4
    public function copy($origin, $fileUrl, $fileName, $progress = true, $options = array())
69
    {
70 4
        $that = $this; // for PHP5.3
71
72
        return $this->fetch($origin, $fileUrl, $progress, $options, function($ch, $request) use ($that, $fileName) {
73 4
            $outputFile = new OutputFile($fileName);
74 4
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
75 4
            curl_setopt($ch, CURLOPT_FILE, $outputFile->getPointer());
76
77 4
            list(, $response) = $result = $that->exec($ch, $request);
78
79 2
            curl_setopt($ch, CURLOPT_FILE, STDOUT);
80
81 2
            if (200 === $response->info['http_code']) {
82 1
                $outputFile->setSuccess();
83 1
            }
84
85 2
            return $result;
86 4
        });
87
    }
88
89
    /**
90
     * Get the content.
91
     *
92
     * @param string $origin The origin URL
93
     * @param string $fileUrl   The file URL
94
     * @param bool   $progress  Display the progression
95
     * @param array  $options   Additional context options
96
     *
97
     * @return bool|string The content
98
     */
99 1
    public function getContents($origin, $fileUrl, $progress = true, $options = array())
100
    {
101 1
        $that = $this; // for PHP5.3
102
103 1
        return $this->fetch($origin, $fileUrl, $progress, $options, function($ch, $request) use ($that) {
104
            // This order is important.
105 1
            curl_setopt($ch, CURLOPT_FILE, STDOUT);
106 1
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
107
108 1
            return $that->exec($ch, $request);
109 1
        });
110
    }
111
112
    /**
113
     * @param string $origin
114
     * @param string $fileUrl
115
     * @param boolean $progress
116
     * @param \Closure $exec
117
     */
118 5
    protected function fetch($origin, $fileUrl, $progress, $options, $exec)
119
    {
120
        do {
121 5
            $this->_retry = false;
122
123 5
            $request = Factory::getHttpGetRequest($origin, $fileUrl, $this->io, $this->config, $this->pluginConfig);
124 5
            $this->onPreDownload = Factory::getPreEvent($request);
125 5
            $this->onPostDownload = Factory::getPostEvent($request);
126 5
            if ($this->_degradedMode) {
127 5
                $this->onPreDownload->attach(new Aspects\AspectDegradedMode);
128 5
            }
129 5
130 5
            $options += $this->options;
131
            $request->processRFSOption($options);
132
133
            if ($this->io->isDebug()) {
134 5
                $this->io->write('Downloading ' . $fileUrl);
135
            }
136 5
137
            if ($progress) {
138
                $this->io->write("    Downloading: <comment>Connecting...</comment>", false);
139 5
                $request->curlOpts[CURLOPT_NOPROGRESS] = false;
140
                $request->curlOpts[CURLOPT_PROGRESSFUNCTION] = array($this, 'progress');
141
            } else {
142
                $request->curlOpts[CURLOPT_NOPROGRESS] = true;
143 5
                $request->curlOpts[CURLOPT_PROGRESSFUNCTION] = null;
144
            }
145
146
            $this->onPreDownload->notify();
147 5
148 4
            $opts = $request->getCurlOpts();
149 4
            $ch = Factory::getConnection($origin, isset($opts[CURLOPT_USERPWD]));
150 4
151 4
            curl_setopt_array($ch, $opts);
152 1
153 1
            list($execStatus,) = $exec($ch, $request);
154
        } while ($this->_retry);
155
156 5
        if ($progress) {
157
            $this->io->overwrite("    Downloading: <comment>100%</comment>");
158 5
        }
159 5
160
        return $execStatus;
161 5
    }
162
163
    /**
164 5
     * Retrieve the options set in the constructor
165
     *
166
     * @return array Options
167 5
     */
168
    public function getOptions()
169
    {
170
        return $this->options;
171 5
    }
172
173 5
    /**
174 3
     * Returns the headers of the last request
175
     *
176 3
     * @return array
177 2
     */
178 2
    public function getLastHeaders()
179
    {
180 3
        return $this->_lastHeaders;
181
    }
182
183
    /**
184
     * @internal
185
     * @param resource $ch
186
     * @param Aspects\HttpGetRequest $request
187
     * @return array {int, Aspects\HttpGetResponse}
188 1
     */
189
    public function exec($ch, Aspects\HttpGetRequest $request)
190 1
    {
191
        $this->_lastHeaders = array();
192
        curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, 'processHeader'));
193
        $execStatus = curl_exec($ch);
194
195
        $response = new Aspects\HttpGetResponse(
196
            curl_errno($ch),
197
            curl_error($ch),
198 1
            curl_getinfo($ch)
199
        );
200 1
        $this->onPostDownload->setResponse($response);
201
        $this->onPostDownload->notify();
202
203
        if ($response->needAuth()) {
204
            $this->promptAuth($request, $response);
205
        }
206
207
        return array($execStatus, $response);
208
    }
209 5
210
    /**
211 5
     * @internal
212 5
     */
213 5
    public function progress()
214
    {
215 5
        // @codeCoverageIgnoreStart
216 5
        if (PHP_VERSION_ID >= 50500) {
217 5
            list(, $downBytesMax, $downBytes,,) = func_get_args();
218 5
        } else {
219 5
            list($downBytesMax, $downBytes,,) = func_get_args();
220 5
        }
221 5
        // @codeCoverageIgnoreEnd
222
223 5
        if ($downBytesMax <= 0 || $downBytesMax < $downBytes) {
224 3
            return 0;
225 1
        }
226
227 3
        $progression = intval($downBytes / $downBytesMax * 100);
228
        $this->io->overwrite("    Downloading: <comment>$progression%</comment>", false);
229
        return 0;
230
    }
231
232
    /**
233 4
     * @internal
234
     * @param resource $ch
235
     * @param string $header
236
     * @return int
237
     */
238
    public function processHeader($ch, $header)
239
    {
240
        $this->_lastHeaders[] = trim($header);
241
        return strlen($header);
242
    }
243 4
244 1
    protected function promptAuth(Aspects\HttpGetRequest $req, Aspects\HttpGetResponse $res)
245
    {
246
        $this->_retry = $req->promptAuth($res, $this->config, $this->io);
247 4
    }
248
}
249