Completed
Push — master ( 15dce7...bc61ed )
by C
09:28 queued 06:38
created

Http::fetchDownloadInfo()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5.1158

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 22
ccs 10
cts 12
cp 0.8333
rs 8.6737
cc 5
eloc 12
nc 6
nop 1
crap 5.1158
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 53
	public function __construct (Registry $configuration, ClientInterface $client = null)
29
	{
30 53
		$this->configuration = $configuration;
31 53
		$this->setClient($client);
32 53
	}
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($url));
0 ignored issues
show
Bug introduced by
The variable $url does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
43
				if (! empty($originalName) && empty($download->getFileName()))
44
				{
45
					$download->setFileName($originalName);
46
				}
47
			}
48 7
			catch (\Exception $e)
49 7
			{echo $e;
50 7
				$this->log('Exception fetching head for connection test: ' . $e->getMessage());
51 7
				$download->setMessage('TARTANA_DOWNLOAD_MESSAGE_INVALID_URL');
52 7
				$download->setState(Download::STATE_DOWNLOADING_ERROR);
53
			}
54
		}
55 7
	}
56
57 37
	public function download (array $downloads)
58
	{
59 37
		if (empty($downloads))
60
		{
61 2
			return [];
62
		}
63
64
		try
65
		{
66 35
			if (! $this->login())
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 33
				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 28
		$promises = [];
89 28
		foreach ($downloads as $download)
90
		{
91
			try
92
			{
93 28
				$url = $this->getUrlToDownload($download);
94 27
				if (! $url)
95
				{
96 5
					if (! $download->getMessage())
97
					{
98 2
						$download->setMessage('TARTANA_DOWNLOAD_MESSAGE_INVALID_URL');
99
					}
100 5
					throw new \Exception($download->getMessage());
101
				}
102
103 22
				$tmpFileName = 'tmp-' . $download->getId() . '.bin';
104
105 22
				$me = $this;
106 22
				$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 20
				$options[RequestOptions::HEADERS] = $this->getHeadersForDownload($download);
135
136 20
				$request = new Request('get', $url);
137 20
				$promise = $this->getClient()->sendAsync($request, $options);
138
139 20
				$promise->then(
140
						function  (Response $resp) use ( $fs, $tmpFileName, $download, $me) {
141 18
							$originalFileName = $this->parseFileName($resp);
142 18
							if (empty($download->getFileName()) && $originalFileName)
0 ignored issues
show
Bug Best Practice introduced by
The expression $originalFileName of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
143
							{
144 14
								$download->setFileName($originalFileName);
145
							}
146
147 18
							if (! empty($download->getFileName()))
148
							{
149 16
								$fs->rename($tmpFileName, $download->getFileName());
150
							}
151
							else
152
							{
153 2
								$download->setFileName($tmpFileName);
154
							}
155 18
							$download->setState(Download::STATE_DOWNLOADING_COMPLETED);
156 18
							$download->setProgress(100);
157 18
							$download->setFinishedAt(new \DateTime());
158 18
							$me->handleCommand(new SaveDownloads([
159 18
									$download
160
							]));
161 20
						},
162 20
						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 20
						});
170 20
				$promises[] = $promise;
171
			}
172 8
			catch (\Exception $e)
173
			{
174 8
				$download->setState(Download::STATE_DOWNLOADING_ERROR);
175 8
				$download->setMessage($e->getMessage());
176 8
				$download->setFinishedAt(new \DateTime());
177 8
				$this->handleCommand(new SaveDownloads([
178 8
						$download
179
				]));
180 28
				continue;
181
			}
182
		}
183
184 28
		return $promises;
185
	}
186
187
	/**
188
	 * If none is internaly configured a new instance will be created.
189
	 *
190
	 * @return \GuzzleHttp\ClientInterface
191
	 */
192 40
	public function getClient ()
193
	{
194 40
		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 40
		return $this->client;
207
	}
208
209 53
	public function setClient (ClientInterface $client = null)
210
	{
211 53
		$this->client = $client;
212 53
	}
213
214
	/**
215
	 * Returns the configuration.
216
	 *
217
	 * @return \Joomla\Registry\Registry
218
	 */
219 16
	protected function getConfiguration ()
220
	{
221 16
		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 boolean
255
	 */
256 15
	protected function hasCookie ($name)
257
	{
258 15
		foreach ($this->getClient()->getConfig('cookies') as $cookie)
259
		{
260
			/** @var \GuzzleHttp\Cookie\SetCookie $cookie */
261 6
			if ($cookie->getName() != $name)
262
			{
263 2
				continue;
264
			}
265 4
			if ($cookie->getExpires() > time())
266
			{
267 4
				return true;
268
			}
269
		}
270
271 13
		return false;
272
	}
273
274
	/**
275
	 * Subclasses can define here the headers before the file is downloaded.
276
	 * It must return an array of headers.
277
	 *
278
	 * @param Download $download
279
	 * @return array
280
	 */
281 19
	protected function getHeadersForDownload (Download $download)
282
	{
283 19
		return [];
284
	}
285
286
	/**
287
	 * Parses the file name from a response.
288
	 * Mainly it tryes to analyze the headers.
289
	 * * @param Response $response
290
	 *
291
	 * @return string|NULL
292
	 */
293 17
	protected function parseFileName (Response $response)
294
	{
295 17
		$dispHeader = $response->getHeader('Content-Disposition');
296 17
		if ($dispHeader && preg_match('/.*filename=([^ ]+)/', $dispHeader[0], $matches))
297
		{
298 15
			return trim($matches[1], '";');
299
		}
300 2
		return null;
301
	}
302
}
303