Completed
Push — master ( c8652a...49e598 )
by C
04:38
created

Http::createPremise()   C

Complexity

Conditions 9
Paths 3

Size

Total Lines 81
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 33
CRAP Score 9

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 81
ccs 33
cts 33
cp 1
rs 5.5727
cc 9
eloc 45
nc 3
nop 1
crap 9

How to fix   Long Method   

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 59
	public function __construct (Registry $configuration, ClientInterface $client = null)
29
	{
30 59
		$this->configuration = $configuration;
31 59
		$this->setClient($client);
32 59
	}
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 39
	public function download (array $downloads)
58
	{
59 39
		if (empty($downloads))
60
		{
61 2
			return [];
62
		}
63
64
		try
65
		{
66 37
			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 35
				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 30
		$promises = [];
89 30
		foreach ($downloads as $download)
90
		{
91
			try
92
			{
93 30
				$promises[] = $this->createPremise($download);
94
			}
95 9
			catch (\Exception $e)
96
			{
97 9
				$download->setState(Download::STATE_DOWNLOADING_ERROR);
98 9
				$download->setMessage($e->getMessage());
99 9
				$download->setFinishedAt(new \DateTime());
100 9
				$this->handleCommand(new SaveDownloads([
101 9
						$download
102
				]));
103 30
				continue;
104
			}
105
		}
106
107 30
		return $promises;
108
	}
109
110
	/**
111
	 * If none is internaly configured a new instance will be created.
112
	 *
113
	 * @return \GuzzleHttp\ClientInterface
114
	 */
115 46
	public function getClient ()
116
	{
117 46
		if (! $this->client)
118
		{
119 2
			$fs = new Local(TARTANA_PATH_ROOT . '/var/tmp/');
120 2
			$name = strtolower((new \ReflectionClass($this))->getShortName()) . '.cookie';
121 2
			if (! $fs->has($name) || $this->getConfiguration()->get('clearSession', false))
122
			{
123 2
				$fs->write($name, '', new Config());
124
			}
125 2
			$this->client = new Client([
126 2
					'cookies' => new FileCookieJar($fs->applyPathPrefix($name), true)
127
			]);
128
		}
129 46
		return $this->client;
130
	}
131
132 59
	public function setClient (ClientInterface $client = null)
133
	{
134 59
		$this->client = $client;
135 59
	}
136
137
	/**
138
	 * Returns the configuration.
139
	 *
140
	 * @return \Joomla\Registry\Registry
141
	 */
142 18
	protected function getConfiguration ()
143
	{
144 18
		return $this->configuration;
145
	}
146
147
	/**
148
	 * Returns the real url to download, subclasses can do here some
149
	 * preprocessing of the given download.
150
	 * The download will be saved after that operation. If null is returned, the
151
	 * download will not be performed.
152
	 *
153
	 * @param Download $download
154
	 * @return string
155
	 */
156 14
	protected function getUrlToDownload (Download $download)
157
	{
158 14
		return $download->getLink();
159
	}
160
161
	/**
162
	 * Login function which can be used on subclasses to authenticate before the
163
	 * download is done.
164
	 *
165
	 * @return boolean
166
	 */
167 16
	protected function login ()
168
	{
169 16
		return true;
170
	}
171
172
	/**
173
	 * Returns if the local client has a cookie with the given name and is not
174
	 * expired.
175
	 *
176
	 * @param string $name
177
	 * @return \GuzzleHttp\Cookie\SetCookie
178
	 */
179 21
	protected function getCookie ($name)
180
	{
181 21
		$cookies = $this->getClient()->getConfig('cookies');
182
183 21
		if (! $cookies instanceof \Traversable)
184
		{
185 11
			return null;
186
		}
187
188 10
		foreach ($cookies as $cookie)
189
		{
190
			/** @var \GuzzleHttp\Cookie\SetCookie $cookie */
191 10
			if ($cookie->getName() != $name)
192
			{
193 2
				continue;
194
			}
195 8
			if (! $cookie->getExpires() || $cookie->getExpires() > time())
196
			{
197 8
				return $cookie;
198
			}
199
		}
200
201 4
		return null;
202
	}
203
204
	/**
205
	 * Subclasses can define here the headers before the file is downloaded.
206
	 * It must return an array of headers.
207
	 *
208
	 * @param Download $download
209
	 * @return array
210
	 */
211 20
	protected function getHeadersForDownload (Download $download)
212
	{
213 20
		return [];
214
	}
215
216
	/**
217
	 * Parses the file name from a response.
218
	 * Mainly it tryes to analyze the headers.
219
	 *
220
	 * @param Response $response
221
	 * @return string|NULL
222
	 */
223 23
	protected function parseFileName (Response $response)
224
	{
225 23
		$dispHeader = $response->getHeader('Content-Disposition');
226 23
		if ($dispHeader && preg_match('/.*filename=([^ ]+)/', $dispHeader[0], $matches))
227
		{
228 19
			return trim($matches[1], '";');
229
		}
230 4
		return null;
231
	}
232
233 30
	private function createPremise (Download $download)
234
	{
235 30
		$url = $this->getUrlToDownload($download);
236 29
		if (! $url)
237
		{
238 6
			if (! $download->getMessage())
239
			{
240 3
				$download->setMessage('TARTANA_DOWNLOAD_MESSAGE_INVALID_URL');
241
			}
242 6
			throw new \Exception($download->getMessage());
243
		}
244
245 23
		$tmpFileName = 'tmp-' . $download->getId() . '.bin';
246
247 23
		$me = $this;
248 23
		$fs = new Local($download->getDestination());
249
250
		// @codeCoverageIgnoreStart
251
		$options = [
252
				RequestOptions::SINK => $fs->applyPathPrefix($tmpFileName),
253
				RequestOptions::PROGRESS => function  ($totalSize, $downloadedSize) use ( $download, $me) {
254
					if (! $downloadedSize || ! $totalSize)
255
					{
256
						return;
257
					}
258
					$progress = (100 / $totalSize) * $downloadedSize;
259
260
					if ($progress < $download->getProgress() + (rand(100, 700) / 1000))
261
					{
262
						// Reducing write transactions on the
263
						// repository
264
						return;
265
					}
266
267
					$download->setProgress($progress);
268
					$download->setSize($totalSize);
269
					$me->handleCommand(new SaveDownloads([
270
							$download
271
					]));
272
				}
273
		];
274
		// @codeCoverageIgnoreEnd
275
276 21
		$options[RequestOptions::HEADERS] = $this->getHeadersForDownload($download);
277
278 21
		$request = new Request('get', $url);
279 21
		$promise = $this->getClient()->sendAsync($request, $options);
280
281 21
		$promise->then(
282
				function  (Response $resp) use ( $fs, $tmpFileName, $download, $me) {
283 19
					$originalFileName = $this->parseFileName($resp);
284 19
					if (empty($download->getFileName()) && ! empty($originalFileName))
285
					{
286 15
						$download->setFileName($originalFileName);
287
					}
288
289 19
					if (! empty($download->getFileName()))
290
					{
291 17
						$fs->rename($tmpFileName, $download->getFileName());
292
					}
293
					else
294
					{
295 2
						$download->setFileName($tmpFileName);
296
					}
297 19
					$download->setState(Download::STATE_DOWNLOADING_COMPLETED);
298 19
					$download->setProgress(100);
299 19
					$download->setFinishedAt(new \DateTime());
300 19
					$me->handleCommand(new SaveDownloads([
301 19
							$download
302
					]));
303 21
				},
304 21
				function  (RequestException $e) use ( $download, $me) {
305 2
					$download->setState(Download::STATE_DOWNLOADING_ERROR);
306 2
					$download->setMessage($e->getMessage());
307 2
					$download->setFinishedAt(new \DateTime());
308 2
					$me->handleCommand(new SaveDownloads([
309 2
							$download
310
					]));
311 21
				});
312 21
		return $promise;
313
	}
314
}
315