Passed
Push — master ( 691aa8...315510 )
by Blizzz
17:05 queued 13s
created

Imaginary::getCroppedThumbnail()   F

Complexity

Conditions 18
Paths 254

Size

Total Lines 112
Code Lines 75

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 18
eloc 75
c 2
b 1
f 0
nc 254
nop 4
dl 0
loc 112
rs 3.3583

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
/**
3
 * @copyright Copyright (c) 2020, Nextcloud, GmbH.
4
 *
5
 * @author Vincent Petry <[email protected]>
6
 * @author Carl Schwan <[email protected]>
7
 *
8
 * @license AGPL-3.0-or-later
9
 *
10
 * This code is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU Affero General Public License, version 3,
12
 * as published by the Free Software Foundation.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU Affero General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Affero General Public License, version 3,
20
 * along with this program. If not, see <http://www.gnu.org/licenses/>
21
 *
22
 */
23
24
namespace OC\Preview;
25
26
use OCP\Files\File;
27
use OCP\Http\Client\IClientService;
28
use OCP\IConfig;
29
use OCP\IImage;
30
use OCP\Image;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, OC\Preview\Image. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
31
32
use OC\StreamImage;
33
use Psr\Log\LoggerInterface;
34
35
class Imaginary extends ProviderV2 {
36
	/** @var IConfig */
37
	private $config;
38
39
	/** @var IClientService */
40
	private $service;
41
42
	/** @var LoggerInterface */
43
	private $logger;
44
45
	public function __construct(array $config) {
46
		parent::__construct($config);
47
		$this->config = \OC::$server->get(IConfig::class);
48
		$this->service = \OC::$server->get(IClientService::class);
49
		$this->logger = \OC::$server->get(LoggerInterface::class);
50
	}
51
52
	/**
53
	 * {@inheritDoc}
54
	 */
55
	public function getMimeType(): string {
56
		return self::supportedMimeTypes();
57
	}
58
59
	public static function supportedMimeTypes(): string {
60
		return '/(image\/(bmp|x-bitmap|png|jpeg|gif|heic|heif|svg\+xml|tiff|webp)|application\/(pdf|illustrator))/';
61
	}
62
63
	public function getCroppedThumbnail(File $file, int $maxX, int $maxY, bool $crop): ?IImage {
64
		$maxSizeForImages = $this->config->getSystemValue('preview_max_filesize_image', 50);
65
66
		$size = $file->getSize();
67
68
		if ($maxSizeForImages !== -1 && $size > ($maxSizeForImages * 1024 * 1024)) {
69
			return null;
70
		}
71
72
		$imaginaryUrl = $this->config->getSystemValueString('preview_imaginary_url', 'invalid');
73
		if ($imaginaryUrl === 'invalid') {
74
			$this->logger->error('Imaginary preview provider is enabled, but no url is configured. Please provide the url of your imaginary server to the \'preview_imaginary_url\' config variable.');
75
			return null;
76
		}
77
		$imaginaryUrl = rtrim($imaginaryUrl, '/');
78
79
		// Object store
80
		$stream = $file->fopen('r');
81
82
		$httpClient = $this->service->newClient();
83
84
		$convert = false;
85
		$autorotate = true;
86
87
		switch ($file->getMimeType()) {
88
			case 'image/heic':
89
				// Autorotate seems to be broken for Heic so disable for that
90
				$autorotate = false;
91
				$mimeType = 'jpeg';
92
				break;
93
			case 'image/gif':
94
			case 'image/png':
95
				$mimeType = 'png';
96
				break;
97
			case 'image/svg+xml':
98
			case 'application/pdf':
99
			case 'application/illustrator':
100
				$convert = true;
101
				// Converted files do not need to be autorotated
102
				$autorotate = false;
103
				$mimeType = 'png';
104
				break;
105
			default:
106
				$mimeType = 'jpeg';
107
		}
108
		
109
		$operations = [];
110
111
		if ($convert) {
112
			$operations[] = [
113
				'operation' => 'convert',
114
				'params' => [
115
					'type' => $mimeType,
116
				]
117
			];
118
		} elseif ($autorotate) {
119
			$operations[] = [
120
				'operation' => 'autorotate',
121
			];
122
		}
123
124
		$quality = $this->config->getAppValue('preview', 'jpeg_quality', '80');
125
126
		$operations[] = [
127
			'operation' => ($crop ? 'smartcrop' : 'fit'),
128
			'params' => [
129
				'width' => $maxX,
130
				'height' => $maxY,
131
				'stripmeta' => 'true',
132
				'type' => $mimeType,
133
				'norotation' => 'true',
134
				'quality' => $quality,
135
			]
136
		];
137
138
		try {
139
			$response = $httpClient->post(
140
				$imaginaryUrl . '/pipeline', [
141
					'query' => ['operations' => json_encode($operations)],
142
					'stream' => true,
143
					'content-type' => $file->getMimeType(),
144
					'body' => $stream,
145
					'nextcloud' => ['allow_local_address' => true],
146
				]);
147
		} catch (\Exception $e) {
148
			$this->logger->error('Imaginary preview generation failed: ' . $e->getMessage(), [
149
				'exception' => $e,
150
			]);
151
			return null;
152
		}
153
154
		if ($response->getStatusCode() !== 200) {
155
			$this->logger->error('Imaginary preview generation failed: ' . json_decode($response->getBody())['message']);
156
			return null;
157
		}
158
159
		// This is not optimal but previews are distorted if the wrong width and height values are
160
		// used. Both dimension headers are only sent when passing the option "-return-size" to
161
		// Imaginary.
162
		if ($response->getHeader('Image-Width') && $response->getHeader('Image-Height')) {
163
			$image = new StreamImage(
164
				$response->getBody(),
165
				$response->getHeader('Content-Type'),
166
				(int)$response->getHeader('Image-Width'),
167
				(int)$response->getHeader('Image-Height'),
168
			);
169
		} else {
170
			$image = new Image();
171
			$image->loadFromFileHandle($response->getBody());
172
		}
173
174
		return $image->valid() ? $image : null;
175
	}
176
177
	/**
178
	 * {@inheritDoc}
179
	 */
180
	public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage {
181
		return $this->getCroppedThumbnail($file, $maxX, $maxY, false);
182
	}
183
}
184