Passed
Push — develop ( a6815b...1ee6c7 )
by Портнов
05:33
created

WorkerDownloader::start()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 21
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 16
c 1
b 0
f 0
dl 0
loc 21
rs 9.7333
cc 4
nc 5
nop 1
1
<?php
2
/*
3
 * MikoPBX - free phone system for small business
4
 * Copyright (C) 2017-2020 Alexey Portnov and Nikolay Beketov
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License along with this program.
17
 * If not, see <https://www.gnu.org/licenses/>.
18
 */
19
20
namespace MikoPBX\PBXCoreREST\Workers;
21
22
require_once 'Globals.php';
23
24
use MikoPBX\Core\Workers\WorkerBase;
25
use MikoPBX\Core\System\Util;
26
use GuzzleHttp;
27
use GuzzleHttp\Exception\GuzzleException;
28
use Psr\Http\Message\ResponseInterface;
29
30
31
class WorkerDownloader extends WorkerBase
32
{
33
    private string $old_memory_limit;
34
    private int $progress = 0;
35
    private array $settings;
36
    private string $progress_file = '';
37
    private string $error_file = '';
38
    private int $file_size = 0;
39
    private int $lastUpdate = 0;
40
    private int $httpCode = 0;
41
42
    /**
43
     * WorkerDownloader entry point.
44
     *
45
     * @param $params
46
     */
47
    public function start($params): void
48
    {
49
        $this->lastUpdate=time();
50
        $this->old_memory_limit = ini_get('memory_limit');
51
        $filename = $params[2]??'';
52
        if (file_exists($filename)) {
53
            $this->settings = json_decode(file_get_contents($filename), true);
54
        } else {
55
            Util::sysLogMsg(__CLASS__, 'Wrong download settings', LOG_ERR);
56
            return;
57
        }
58
        ini_set('memory_limit', '300M');
59
60
        $temp_dir            = dirname($this->settings['res_file']);
61
        $this->progress_file = $temp_dir . '/progress';
62
        $this->error_file    = $temp_dir . '/error';
63
64
        $result = $this->getFile();
65
        $result = $result && $this->checkFile();
66
        if ( ! $result) {
67
            Util::sysLogMsg(__CLASS__, 'Download error...', LOG_ERR);
68
        }
69
    }
70
71
    /**
72
     * Downloads file from remote resource by link
73
     */
74
    public function getFile(): bool
75
    {
76
        if (empty($this->settings)) {
77
            return false;
78
        }
79
        if (file_exists($this->settings['res_file'])) {
80
            unlink($this->settings['res_file']);
81
        }
82
        if (isset($this->settings['size'])){
83
            $this->file_size = $this->settings['size'];
84
        } else {
85
            $this->file_size = $this->remoteFileSize($this->settings['url']);
86
        }
87
88
        file_put_contents($this->progress_file, 0);
89
        $curl = new GuzzleHttp\Handler\CurlMultiHandler();
90
        $handler = GuzzleHttp\HandlerStack::create($curl);
91
        $client = new GuzzleHttp\Client();
92
        $promise = $client->getAsync($this->settings['url'], [
93
            'handler' => $handler,
94
            'sink'     => $this->settings['res_file'],
95
            'progress' =>  [$this, 'progress'],
96
            'connect_timeout' => 5
97
        ]);
98
        $promise->then(
99
            function (ResponseInterface $res) {
100
                $this->httpCode = $res->getStatusCode();
101
            },
102
            function (GuzzleHttp\Exception\RequestException $e) {
103
                file_put_contents($this->error_file, $e->getMessage(), FILE_APPEND);
104
                $this->httpCode = -1;
105
            }
106
        );
107
        while ($promise->getState() === 'pending'){
108
            $curl->tick();
109
            if(time() - $this->lastUpdate > 30){
110
                $this->httpCode = -1;
111
                file_put_contents($this->error_file, 'Fail download file... No progress for more than 30 seconds.', FILE_APPEND);
112
                break;
113
            }
114
        }
115
        if ($this->httpCode !== 200) {
116
            file_put_contents($this->error_file, "Curl return code $this->httpCode. ", FILE_APPEND);
117
        }
118
119
        return $this->httpCode === 200;
120
    }
121
122
    public function progress( $downloadTotal, $downloadedBytes, $uploadTotal, $uploadedBytes) :void
0 ignored issues
show
Unused Code introduced by
The parameter $uploadTotal is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

122
    public function progress( $downloadTotal, $downloadedBytes, /** @scrutinizer ignore-unused */ $uploadTotal, $uploadedBytes) :void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $uploadedBytes is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

122
    public function progress( $downloadTotal, $downloadedBytes, $uploadTotal, /** @scrutinizer ignore-unused */ $uploadedBytes) :void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
123
    {
124
        if ($downloadedBytes === 0) {
125
            return;
126
        }
127
        $this->lastUpdate = time();
128
        if ($this->file_size < 0) {
129
            $new_progress = $downloadedBytes / $downloadTotal * 100;
130
        } else {
131
            $new_progress = $downloadedBytes / $this->file_size * 100;
132
        }
133
        $delta = $new_progress - $this->progress;
134
        if ($delta > 1) {
135
            // Лимит на работу скрипта. Чтобы исключить "Зависание".
136
            // Если нет прогресса, то завершать работу.
137
            $this->progress = round($new_progress);
138
            $this->progress = min($this->progress, 99);
139
            file_put_contents($this->progress_file, $this->progress);
140
        }
141
    }
142
143
    /**
144
     * Remote File Size Using cURL
145
     *
146
     * @param string $url
147
     *
148
     * @return int
149
     */
150
    private function remoteFileSize(string $url): int
151
    {
152
        $fileSize = -1;
153
        try{
154
            $client    = new GuzzleHttp\Client();
155
            $response  = $client->head($url, ['connect_timeout' => 5]);
156
            $fileSize  = $response->getHeader('Content-Length')[0];
157
        }catch ( GuzzleException  $e){
158
            file_put_contents($this->error_file, $e->getMessage(), FILE_APPEND);
159
        }
160
        return $fileSize;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $fileSize could return the type string which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
161
    }
162
163
    /**
164
     * Checks file md5 sum and size
165
     */
166
    public function checkFile(): bool
167
    {
168
        $result = true;
169
        if ( ! file_exists($this->settings['res_file'])) {
170
            file_put_contents($this->error_file, 'File did not upload', FILE_APPEND);
171
            return false;
172
        }
173
        if (md5_file($this->settings['res_file']) !== $this->settings['md5']) {
174
            unlink($this->settings['res_file']);
175
            file_put_contents($this->error_file, 'Error on comparing MD5 sum', FILE_APPEND);
176
177
            $result = false;
178
        }elseif($this->file_size !== filesize($this->settings['res_file'])) {
179
            unlink($this->settings['res_file']);
180
            file_put_contents($this->error_file, 'Error on comparing file size', FILE_APPEND);
181
            $result = false;
182
        }
183
        file_put_contents($this->progress_file, 100);
184
        return $result;
185
    }
186
187
    /**
188
     * Returns memory_limit to default value.
189
     */
190
    public function __destruct()
191
    {
192
        parent::__destruct();
193
        ini_set('memory_limit', $this->old_memory_limit);
194
    }
195
196
}
197
198
// Start worker process
199
WorkerDownloader::startWorker($argv??null, false);
200