Completed
Push — master ( 165832...677095 )
by C
06:01
created

Http::createPremise()   D

Complexity

Conditions 13
Paths 6

Size

Total Lines 115
Code Lines 63

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 46
CRAP Score 13

Importance

Changes 6
Bugs 0 Features 2
Metric Value
c 6
b 0
f 2
dl 0
loc 115
ccs 46
cts 46
cp 1
rs 4.9922
cc 13
eloc 63
nc 6
nop 1
crap 13

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
namespace Tartana\Host\Common;
3
use GuzzleHttp\Client;
4
use GuzzleHttp\ClientInterface;
5
use GuzzleHttp\Cookie\FileCookieJar;
6
use GuzzleHttp\Exception\RequestException;
7
use GuzzleHttp\Psr7\Request;
8
use GuzzleHttp\Psr7\Response;
9
use GuzzleHttp\RequestOptions;
10
use Joomla\Registry\Registry;
11
use League\Flysystem\Adapter\Local;
12
use League\Flysystem\Config;
13
use Tartana\Domain\Command\SaveDownloads;
14
use Tartana\Entity\Download;
15
use Tartana\Host\HostInterface;
16
use Tartana\Mixins\CommandBusAwareTrait;
17
use Tartana\Mixins\LoggerAwareTrait;
18
19
class Http implements HostInterface
20
{
21
	use LoggerAwareTrait;
22
	use CommandBusAwareTrait;
23
24
	private $configuration = null;
25
26
	private $client = null;
27
28 70
	public function __construct (Registry $configuration, ClientInterface $client = null)
29
	{
30 70
		$this->configuration = $configuration;
31 70
		$this->setClient($client);
32 70
	}
33
34 7
	public function fetchDownloadInfo (array $downloads)
35
	{
36 7
		foreach ($downloads as $download)
37
		{
38
			// Connection check
39
			try
40
			{
41 7
				$originalName = $this->parseFileName($this->getClient()
42 7
					->head($download->getLink()));
43 5
				if (! empty($originalName) && empty($download->getFileName()))
44
				{
45 5
					$download->setFileName($originalName);
46
				}
47
			}
48 2
			catch (\Exception $e)
49
			{
50 2
				$this->log('Exception fetching head for connection test: ' . $e->getMessage());
51 2
				$download->setMessage('TARTANA_DOWNLOAD_MESSAGE_INVALID_URL');
52 7
				$download->setState(Download::STATE_DOWNLOADING_ERROR);
53
			}
54
		}
55 7
	}
56
57 46
	public function download (array $downloads)
58
	{
59 46
		if (empty($downloads))
60
		{
61 2
			return [];
62
		}
63
64
		try
65
		{
66 44
			if (! $this->login())
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
67
			{
68 5
				foreach ($downloads as $download)
69
				{
70 5
					$download->setState(Download::STATE_DOWNLOADING_ERROR);
71 5
					$download->setMessage('TARTANA_DOWNLOAD_MESSAGE_INVALID_LOGIN');
72
				}
73 5
				$this->handleCommand(new SaveDownloads($downloads));
74 42
				return [];
75
			}
76
		}
77 2
		catch (\Exception $e)
78
		{
79 2
			foreach ($downloads as $download)
80
			{
81 2
				$download->setState(Download::STATE_DOWNLOADING_ERROR);
82 2
				$download->setMessage($e->getMessage());
83
			}
84 2
			$this->handleCommand(new SaveDownloads($downloads));
85 2
			return [];
86
		}
87
88 37
		$promises = [];
89 37
		foreach ($downloads as $download)
90
		{
91 37
			$download->setStartedAt(new \DateTime());
92
			try
93
			{
94 37
				$promises[] = $this->createPremise($download);
95
			}
96 11
			catch (\Exception $e)
97
			{
98 11
				$download->setState(Download::STATE_DOWNLOADING_ERROR);
99 11
				$download->setMessage($e->getMessage());
100 11
				$download->setFinishedAt(new \DateTime());
101 11
				$this->handleCommand(new SaveDownloads([
102 11
						$download
103
				]));
104 37
				continue;
105
			}
106
		}
107
108 37
		return $promises;
109
	}
110
111
	/**
112
	 * If none is internaly configured a new instance will be created.
113
	 *
114
	 * @return \GuzzleHttp\ClientInterface
115
	 */
116 56
	public function getClient ()
117
	{
118 56
		if (! $this->client)
119
		{
120 2
			$fs = new Local(TARTANA_PATH_ROOT . '/var/tmp/');
121 2
			$name = strtolower((new \ReflectionClass($this))->getShortName()) . '.cookie';
122 2
			if (! $fs->has($name) || $this->getConfiguration()->get('clearSession', false))
123
			{
124 2
				$fs->write($name, '', new Config());
125
			}
126 2
			$this->client = new Client([
127 2
					'cookies' => new FileCookieJar($fs->applyPathPrefix($name), true)
128
			]);
129
		}
130 56
		return $this->client;
131
	}
132
133 70
	public function setClient (ClientInterface $client = null)
134
	{
135 70
		$this->client = $client;
136 70
	}
137
138
	/**
139
	 * Returns the configuration.
140
	 *
141
	 * @return \Joomla\Registry\Registry
142
	 */
143 35
	protected function getConfiguration ()
144
	{
145 35
		return $this->configuration;
146
	}
147
148
	/**
149
	 * Returns the real url to download, subclasses can do here some
150
	 * preprocessing of the given download.
151
	 * The download will be saved after that operation. If null is returned, the
152
	 * download will not be performed.
153
	 *
154
	 * @param Download $download
155
	 * @return string
156
	 */
157 18
	protected function getUrlToDownload (Download $download)
158
	{
159 18
		return $download->getLink();
160
	}
161
162
	/**
163
	 * Login function which can be used on subclasses to authenticate before the
164
	 * download is done.
165
	 *
166
	 * @return boolean
167
	 */
168 23
	protected function login ()
169
	{
170 23
		return true;
171
	}
172
173
	/**
174
	 * Returns if the local client has a cookie with the given name and is not
175
	 * expired.
176
	 *
177
	 * @param string $name
178
	 * @return \GuzzleHttp\Cookie\SetCookie
179
	 */
180 21
	protected function getCookie ($name)
181
	{
182 21
		$cookies = $this->getClient()->getConfig('cookies');
183
184 21
		if (! $cookies instanceof \Traversable)
185
		{
186 11
			return null;
187
		}
188
189 10
		foreach ($cookies as $cookie)
190
		{
191
			/** @var \GuzzleHttp\Cookie\SetCookie $cookie */
192 10
			if ($cookie->getName() != $name)
193
			{
194 2
				continue;
195
			}
196 8
			if (! $cookie->getExpires() || $cookie->getExpires() > time())
197
			{
198 8
				return $cookie;
199
			}
200
		}
201
202 4
		return null;
203
	}
204
205
	/**
206
	 * Subclasses can define here the headers before the file is downloaded.
207
	 * It must return an array of headers.
208
	 *
209
	 * @param Download $download
210
	 * @return array
211
	 */
212 25
	protected function getHeadersForDownload (Download $download)
213
	{
214 25
		return [];
215
	}
216
217
	/**
218
	 * Parses the file name from a response.
219
	 * Mainly it tryes to analyze the headers.
220
	 *
221
	 * @param Response $response
222
	 * @return string|NULL
223
	 */
224 28
	protected function parseFileName (Response $response)
225
	{
226 28
		$dispHeader = $response->getHeader('Content-Disposition');
227 28
		if ($dispHeader && preg_match('/.*filename=([^ ]+)/', $dispHeader[0], $matches))
228
		{
229 23
			return trim($matches[1], '";');
230
		}
231 5
		return null;
232
	}
233
234 37
	private function createPremise (Download $download)
235
	{
236 37
		$url = $this->getUrlToDownload($download);
237 36
		if (! $url)
238
		{
239 8
			if (! $download->getMessage())
240
			{
241 8
				$download->setMessage('TARTANA_DOWNLOAD_MESSAGE_FAILED_REAL_URL');
242
			}
243 8
			throw new \Exception($download->getMessage());
244
		}
245
246 28
		$tmpFileName = 'tmp-' . $download->getId() . '.bin';
247
248 28
		$me = $this;
249 28
		$fs = new Local($download->getDestination());
250
251 26
		$this->log('Downloading download ' . $download->getId() . ' to ' . $fs->applyPathPrefix($tmpFileName));
252
253
		// @codeCoverageIgnoreStart
254
		$options = [
255
				RequestOptions::SINK => $fs->applyPathPrefix($tmpFileName),
256
				RequestOptions::PROGRESS => function  ($totalSize, $downloadedSize) use ( $download, $me) {
257
					if (! $downloadedSize || ! $totalSize)
258
					{
259
						return;
260
					}
261
					$progress = (100 / $totalSize) * $downloadedSize;
262
263
					if ($progress < $download->getProgress() + (rand(100, 700) / 1000))
264
					{
265
						// Reducing write transactions on the
266
						// repository
267
						return;
268
					}
269
270
					$download->setProgress($progress);
271
					$download->setSize($totalSize);
272
					$me->handleCommand(new SaveDownloads([
273
							$download
274
					]));
275
				}
276
		];
277
		if (defined('CURLOPT_TCP_KEEPALIVE'))
278
		{
279
			// Needed to keep the session alive on slow downloads
280
			$options['curl'] = [
281
					CURLOPT_TCP_KEEPALIVE => 1,
282
					CURLOPT_TCP_KEEPIDLE => 30,
283
					CURLOPT_TCP_KEEPINTVL => 15
284
			];
285
		}
286
		// @codeCoverageIgnoreEnd
287
288 26
		if ($this->getConfiguration()->get('speedlimit') > 0)
289
		{
290 2
			$options['curl'][CURLOPT_MAX_RECV_SPEED_LARGE] = $this->getConfiguration()->get('speedlimit') * 1000;
291
		}
292
293 26
		$options[RequestOptions::HEADERS] = $this->getHeadersForDownload($download);
294
295 26
		$request = new Request('get', $url);
296 26
		$promise = $this->getClient()->sendAsync($request, $options);
297
298 26
		$promise->then(
299
				function  (Response $resp) use ( $fs, $tmpFileName, $download, $me) {
300 24
					$originalFileName = $this->parseFileName($resp);
301 24
					if (empty($download->getFileName()) && ! empty($originalFileName))
302
					{
303 19
						$download->setFileName($originalFileName);
304
					}
305
306 24
					if (! empty($download->getFileName()))
307
					{
308 22
						$fs->rename($tmpFileName, $download->getFileName());
309
					}
310
					else
311
					{
312 2
						$download->setFileName($tmpFileName);
313
					}
314
315
					// Hash check
316 24
					if (! empty($download->getHash()))
317
					{
318 6
						$hash = md5_file($fs->applyPathPrefix($download->getFileName()));
319 6
						if ($download->getHash() != $hash)
320
						{
321 2
							$fs->delete($download->getFileName());
322 2
							$download->setState(Download::STATE_DOWNLOADING_ERROR);
323 2
							$download->setMessage('TARTANA_DOWNLOAD_MESSAGE_INVALID_HASH');
324 2
							$download->setFinishedAt(new \DateTime());
325 2
							$me->handleCommand(new SaveDownloads([
326 2
									$download
327
							]));
328 2
							return;
329
						}
330
					}
331
332 22
					$download->setState(Download::STATE_DOWNLOADING_COMPLETED);
333 22
					$download->setProgress(100);
334 22
					$download->setFinishedAt(new \DateTime());
335 22
					$me->handleCommand(new SaveDownloads([
336 22
							$download
337
					]));
338 26
				},
339 26
				function  (RequestException $e) use ( $download, $me) {
340 2
					$download->setState(Download::STATE_DOWNLOADING_ERROR);
341 2
					$download->setMessage($e->getMessage());
342 2
					$download->setFinishedAt(new \DateTime());
343 2
					$me->handleCommand(new SaveDownloads([
344 2
							$download
345
					]));
346 26
				});
347 26
		return $promise;
348
	}
349
}
350