DependencyLib::download()   A
last analyzed

Complexity

Conditions 5
Paths 3

Size

Total Lines 33
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 21
c 0
b 0
f 0
dl 0
loc 33
rs 9.2728
cc 5
nc 3
nop 1
1
<?php
2
3
/*
4
 * Pickle
5
 *
6
 *
7
 * @license
8
 *
9
 * New BSD License
10
 *
11
 * Copyright © 2015-2015, Pickle community. All rights reserved.
12
 *
13
 * Redistribution and use in source and binary forms, with or without
14
 * modification, are permitted provided that the following conditions are met:
15
 *     * Redistributions of source code must retain the above copyright
16
 *       notice, this list of conditions and the following disclaimer.
17
 *     * Redistributions in binary form must reproduce the above copyright
18
 *       notice, this list of conditions and the following disclaimer in the
19
 *       documentation and/or other materials provided with the distribution.
20
 *     * Neither the name of the Hoa nor the names of its contributors may be
21
 *       used to endorse or promote products derived from this software without
22
 *       specific prior written permission.
23
 *
24
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
28
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34
 * POSSIBILITY OF SUCH DAMAGE.
35
 */
36
37
namespace Pickle\Package\Util\Windows;
38
39
use Exception;
40
use Pickle\Base\Archive;
41
use Pickle\Base\Util;
42
use Pickle\Base\Util\FileOps;
43
use RuntimeException;
44
use Symfony\Component\Console\Output\OutputInterface as OutputInterface;
45
46
class DependencyLib
47
{
48
    use FileOps;
49
50
    public const DLL_MAP_URL = 'https://windows.php.net/downloads/pecl/deps/dllmapping.json';
51
52
    public const DEPLISTER_URL = 'https://windows.php.net/downloads/pecl/tools/deplister.exe';
53
54
    public const DEPS_URL = 'https://windows.php.net/downloads/pecl/deps';
55
56
    private $dllMap;
57
58
    private $php;
59
60
    private $progress;
61
62
    private $output;
63
64
    private $fetchedZips = [];
65
66
    public function __construct(\Pickle\Base\Interfaces\Engine $php)
67
    {
68
        $this->php = $php;
69
        $this->checkDepListerExe();
70
        $this->fetchDllMap();
71
    }
72
73
    public function getZipUrlsForDll($binary, $ignore_installed = false)
74
    {
75
        $dll = $this->getDllsForBinary($binary);
76
        $packages = [];
77
        foreach ($this->dllMap as $pkg_name => $pkg) {
78
            foreach ($dll as $dll_name => $dll_installed) {
79
                if (in_array($dll_name, $pkg)) {
80
                    if ($ignore_installed && $dll_installed) {
81
                        continue;
82
                    }
83
                    $packages[] = $pkg_name;
84
                    continue 2;
85
                }
86
            }
87
        }
88
89
        return $packages;
90
    }
91
92
    public function resolveForBin($dll, $resolve_multiple_cb = null)
93
    {
94
        $dep_zips = $this->getZipUrlsForDll($dll, false);
95
96
        if (count($dep_zips) == 1) {
97
            $dep_zip = $dep_zips[0];
98
99
            if (in_array($dep_zip, $this->fetchedZips)) {
100
                return true;
101
            }
102
        } elseif (count($dep_zips) > 1) {
103
            foreach ($dep_zips as $dep_zip) {
104
                /* The user has already picked one here, ignore it. */
105
                if (in_array($dep_zip, $this->fetchedZips)) {
106
                    return true;
107
                }
108
            }
109
            if ($resolve_multiple_cb !== null) {
110
                $dep_zip = $resolve_multiple_cb($dep_zips);
111
            } else {
112
                throw new Exception("Multiple choice for dependencies, couldn't resolve");
113
            }
114
        } else {
115
            /* That might be not quite true, as we might just not have the
116
           corresponding dependency package. However it's fetched from
117
           the PECL build dependencies, no extension build should have
118
           been exist if there's no dependency package uploaded. */
119
            return true;
120
        }
121
122
        return $this->resolveForZip($dep_zip, $resolve_multiple_cb);
123
    }
124
125
    public function resolveForZip($zip_name, $resolve_multiple_cb = null)
126
    {
127
        if (in_array($zip_name, $this->fetchedZips)) {
128
            return true;
129
        }
130
131
        $url = self::DEPS_URL . "/{$zip_name}";
132
        $path = $this->download($url);
133
        try {
134
            $this->uncompress($path);
135
            $lst = $this->copyFiles();
136
        } catch (Exception $e) {
137
            $this->cleanup();
138
            throw new Exception($e->getMessage());
139
        }
140
        $this->cleanup();
141
        $this->fetchedZips[] = $zip_name;
142
143
        foreach ($lst as $bin) {
144
            $this->resolveForBin($bin, $resolve_multiple_cb);
145
        }
146
147
        return true;
148
    }
149
150
    public function setProgress($progress)
151
    {
152
        $this->progress = $progress;
153
    }
154
155
    public function setOutput(OutputInterface $output)
156
    {
157
        $this->output = $output;
158
    }
159
160
    private function fetchDllMap()
161
    {
162
        $dllMap = null;
163
164
        if ($this->dllMap === null) {
165
            $opts = [
166
                'http' => [
167
                    'header' => 'User-Agent: pickle',
168
                ],
169
            ];
170
            $context = stream_context_create($opts);
171
            $data = @file_get_contents(self::DLL_MAP_URL, false, $context);
172
            if (!$data) {
173
                throw new RuntimeException('Cannot fetch the DLL mapping file');
174
            }
175
            $dllMap = json_decode($data);
176
            if (!$dllMap) {
177
                throw new RuntimeException('Cannot parse the DLL mapping file');
178
            }
179
        }
180
        $compiler = $this->php->getCompiler();
181
        $architecture = $this->php->getArchitecture();
182
        if (!isset($dllMap->{$compiler}->{$architecture})) {
183
            /* Just for the case the given compiler/arch set isn't defined in the dllmap,
184
           or we've got a corrupted file, or ...
185
           The dllMap property should be ensured an array. */
186
            $this->dllMap = [];
187
        } else {
188
            $this->dllMap = $dllMap->{$compiler}->{$architecture};
189
        }
190
191
        return true;
192
    }
193
194
    private function checkDepListerExe()
195
    {
196
        $ret = exec('deplister.exe ' . $this->php->getPath() . ' .');
197
        if (empty($ret)) {
198
            $depexe = @file_get_contents(self::DEPLISTER_URL);
199
            if (!$depexe) {
200
                throw new RuntimeException('Cannot fetch deplister.exe');
201
            }
202
            $dir = dirname($this->php->getPath());
203
            $path = $dir . DIRECTORY_SEPARATOR . 'deplister.exe';
204
            if (!@file_put_contents($path, $depexe)) {
205
                throw new RuntimeException('Cannot copy deplister.exe to ' . $dir);
206
            }
207
        }
208
    }
209
210
    private function getDllsForBinary($binary)
211
    {
212
        $out = [];
213
        $ret = exec('deplister.exe ' . escapeshellarg($binary) . ' .', $out);
214
        if (empty($ret) || !$ret) {
215
            throw new RuntimeException('Error while running deplister.exe');
216
        }
217
        $dlls = [];
218
        foreach ((array) $out as $l) {
219
            [$dllname, $found] = explode(',', $l);
220
            $found = trim($found);
221
            $dllname = trim($dllname);
222
            $dlls[$dllname] = $found == 'OK' ? true : false;
223
        }
224
225
        return $dlls;
226
    }
227
228
    private function copyFiles()
229
    {
230
        $ret = [];
231
        $DLLs = glob($this->tempDir . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . '*.dll');
232
233
        /* Copying ALL files from the zip, not just required. */
234
        foreach ($DLLs as $dll) {
235
            $dll = realpath($dll);
236
            $basename = basename($dll);
237
            $dest = dirname($this->php->getPath()) . DIRECTORY_SEPARATOR . $basename;
238
            $success = @copy($dll, dirname($this->php->getPath()) . '/' . $basename);
239
            if (!$success) {
240
                throw new Exception('Cannot copy DLL <' . $dll . '> to <' . $dest . '>');
241
            }
242
243
            $ret[] = $dest;
244
        }
245
246
        return $ret;
247
    }
248
249
    private function download($url)
250
    {
251
        $output = $this->output;
252
        $progress = $this->progress;
253
254
        $ctx = stream_context_create(
255
            [],
256
            [
257
                'notification' => function ($notificationCode, $severity, $message, $messageCode, $bytesTransferred, $bytesMax) use ($output, $progress) {
258
                    switch ($notificationCode) {
259
                        case STREAM_NOTIFY_FILE_SIZE_IS:
260
                            $progress->start($output, $bytesMax);
261
                            break;
262
                        case STREAM_NOTIFY_PROGRESS:
263
                            $progress->setCurrent($bytesTransferred);
264
                            break;
265
                    }
266
                },
267
            ]
268
        );
269
        $output->writeln("downloading {$url} ");
270
        $fileContents = file_get_contents($url, false, $ctx);
271
        $progress->finish();
272
        if (!$fileContents) {
273
            throw new Exception('Cannot fetch <' . $url . '>');
274
        }
275
        $tmpdir = Util\TmpDir::get();
276
        $path = $tmpdir . DIRECTORY_SEPARATOR . basename($url);
277
        if (!file_put_contents($path, $fileContents)) {
278
            throw new Exception('Cannot save temporary file <' . $path . '>');
279
        }
280
281
        return $path;
282
    }
283
284
    private function uncompress($zipFile)
285
    {
286
        $this->createTempDir();
287
        $this->cleanup();
288
        $zipArchiveClass = Archive\Factory::getUnzipperClassName();
289
        $zipArchive = new $zipArchiveClass($zipFile);
290
        /** @var \Pickle\Base\Interfaces\Archive\Unzipper $zipArchive */
291
        $this->output->writeln('Extracting archives...');
292
        $zipArchive->extractTo($this->tempDir);
293
    }
294
}
295
296
/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
297