Completed
Push — master ( bba70d...165832 )
by C
07:28
created

Http::createPremise()   C

Complexity

Conditions 12
Paths 4

Size

Total Lines 113
Code Lines 62

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 30

Importance

Changes 5
Bugs 0 Features 2
Metric Value
c 5
b 0
f 2
dl 0
loc 113
ccs 10
cts 20
cp 0.5
rs 5.034
cc 12
eloc 62
nc 4
nop 1
crap 30

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 37
			catch (\Exception $e)
97
			{
98 37
				$download->setState(Download::STATE_DOWNLOADING_ERROR);
99 37
				$download->setMessage($e->getMessage());
100 37
				$download->setFinishedAt(new \DateTime());
101 37
				$this->handleCommand(new SaveDownloads([
102 37
						$download
103
				]));
104 35
				continue;
105
			}
106
		}
107
108 35
		return $promises;
109
	}
110
111
	/**
112
	 * If none is internaly configured a new instance will be created.
113
	 *
114
	 * @return \GuzzleHttp\ClientInterface
115
	 */
116 40
	public function getClient ()
117
	{
118 40
		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 40
		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 18
	protected function getConfiguration ()
144
	{
145 18
		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
	protected function getHeadersForDownload (Download $download)
213
	{
214
		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 5
	protected function parseFileName (Response $response)
225
	{
226 5
		$dispHeader = $response->getHeader('Content-Disposition');
227 5
		if ($dispHeader && preg_match('/.*filename=([^ ]+)/', $dispHeader[0], $matches))
228
		{
229 3
			return trim($matches[1], '";');
230
		}
231 2
		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
				// Needed to keep the session alive on slow downloads
278
				$options['curl'] = [
0 ignored issues
show
Coding Style Comprehensibility introduced by
$options was never initialized. Although not strictly required by PHP, it is generally a good practice to add $options = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
279
						CURLOPT_TCP_KEEPALIVE => 1,
280
						CURLOPT_TCP_KEEPIDLE => 30,
281
						CURLOPT_TCP_KEEPINTVL => 15
282
				]
283
		];
284
		// @codeCoverageIgnoreEnd
285
286
		if ($this->getConfiguration()->get('speedlimit') > 0)
287
		{
288
			$options['curl'][CURLOPT_MAX_RECV_SPEED_LARGE] = $this->getConfiguration()->get('speedlimit') * 1000;
289
		}
290
291
		$options[RequestOptions::HEADERS] = $this->getHeadersForDownload($download);
292
293
		$request = new Request('get', $url);
294
		$promise = $this->getClient()->sendAsync($request, $options);
295
296
		$promise->then(
297
				function  (Response $resp) use ( $fs, $tmpFileName, $download, $me) {
298
					$originalFileName = $this->parseFileName($resp);
299
					if (empty($download->getFileName()) && ! empty($originalFileName))
300
					{
301
						$download->setFileName($originalFileName);
302
					}
303
304
					if (! empty($download->getFileName()))
305
					{
306
						$fs->rename($tmpFileName, $download->getFileName());
307
					}
308
					else
309
					{
310
						$download->setFileName($tmpFileName);
311
					}
312
313
					// Hash check
314
					if (! empty($download->getHash()))
315
					{
316
						$hash = md5_file($fs->applyPathPrefix($download->getFileName()));
317
						if ($download->getHash() != $hash)
318
						{
319
							$fs->delete($download->getFileName());
320
							$download->setState(Download::STATE_DOWNLOADING_ERROR);
321
							$download->setMessage('TARTANA_DOWNLOAD_MESSAGE_INVALID_HASH');
322
							$download->setFinishedAt(new \DateTime());
323
							$me->handleCommand(new SaveDownloads([
324
									$download
325
							]));
326
							return;
327
						}
328
					}
329
330
					$download->setState(Download::STATE_DOWNLOADING_COMPLETED);
331
					$download->setProgress(100);
332
					$download->setFinishedAt(new \DateTime());
333
					$me->handleCommand(new SaveDownloads([
334
							$download
335
					]));
336
				},
337
				function  (RequestException $e) use ( $download, $me) {
338
					$download->setState(Download::STATE_DOWNLOADING_ERROR);
339
					$download->setMessage($e->getMessage());
340
					$download->setFinishedAt(new \DateTime());
341
					$me->handleCommand(new SaveDownloads([
342
							$download
343
					]));
344
				});
345
		return $promise;
346
	}
347
}
348