Completed
Push — master ( 719d3a...c8652a )
by C
04:58
created

Http::getCookie()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 24
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 6

Importance

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