Issues (263)

lib/Service/PreviewService.php (6 issues)

1
<?php
2
/**
3
 * Nextcloud - Gallery
4
 *
5
 * This file is licensed under the Affero General Public License version 3 or
6
 * later. See the COPYING file.
7
 *
8
 * @author Olivier Paroz <[email protected]>
9
 *
10
 * @copyright Olivier Paroz 2017
11
 */
12
13
namespace OCA\Gallery\Service;
14
15
use OCP\Files\File;
0 ignored issues
show
The type OCP\Files\File was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
16
use OCP\Image;
0 ignored issues
show
The type OCP\Image was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
use OCP\IPreview;
0 ignored issues
show
The type OCP\IPreview was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
18
use OCP\ILogger;
0 ignored issues
show
The type OCP\ILogger was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
19
20
use OCA\Gallery\Environment\Environment;
21
22
/**
23
 * Generates previews
24
 *
25
 * @package OCA\Gallery\Service
26
 */
27
class PreviewService extends Service {
28
29
	use Base64Encode;
30
31
	/** @var IPreview */
32
	private $previewManager;
33
34
	/**
35
	 * Constructor
36
	 *
37
	 * @param string $appName
38
	 * @param Environment $environment
39
	 * @param IPreview $previewManager
40
	 * @param ILogger $logger
41
	 */
42
	public function __construct(
43
		$appName,
44
		Environment $environment,
45
		IPreview $previewManager,
46
		ILogger $logger
47
	) {
48
		parent::__construct($appName, $environment, $logger);
49
50
		$this->previewManager = $previewManager;
51
	}
52
53
	/**
54
	 * Decides if we should download the file instead of generating a preview
55
	 *
56
	 * @param File $file
57
	 * @param bool $animatedPreview
58
	 *
59
	 * @return bool
60
	 */
61
	public function isPreviewRequired($file, $animatedPreview) {
62
		$mime = $file->getMimeType();
63
64
		if ($mime === 'image/svg+xml') {
65
			return $this->isSvgPreviewRequired();
66
		}
67
		if ($mime === 'image/gif') {
68
			return $this->isGifPreviewRequired($file, $animatedPreview);
69
		}
70
71
		return true;
72
	}
73
74
	/**
75
	 * Returns an array containing everything needed by the client to be able to display a preview
76
	 *
77
	 *    * fileid:  the file's ID
78
	 *    * mimetype: the file's media type
79
	 *    * preview: the preview's content
80
	 *
81
	 * Example logger
82
	 * $this->logger->debug(
83
	 * "[PreviewService] Path : {path} / mime: {mimetype} / fileid: {fileid}",
84
	 * [
85
	 * 'path'     => $preview['data']['path'],
86
	 * 'mimetype' => $preview['data']['mimetype'],
87
	 * 'fileid'   => $preview['fileid']
88
	 * ]
89
	 * );
90
	 *
91
	 * @todo Get the max size from the settings
92
	 *
93
	 * @param File $file
94
	 * @param int $maxX asked width for the preview
95
	 * @param int $maxY asked height for the preview
96
	 * @param bool $keepAspect
97
	 * @param bool $base64Encode
98
	 *
99
	 * @return string|\OC_Image|string|false preview data
0 ignored issues
show
The type OC_Image was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
100
	 * @throws InternalServerErrorServiceException
101
	 */
102
	public function createPreview(
103
		$file, $maxX = 0, $maxY = 0, $keepAspect = true, $base64Encode = false
104
	) {
105
		try {
106
			$preview = $this->previewManager->getPreview($file, $maxX, $maxY, !$keepAspect);
107
			$img = new Image();
108
			$img->loadFromData($preview->getContent());
109
			$mimeType = $img->mimeType();
110
			if ($img && $base64Encode) {
111
				$img = $this->encode($img);
112
			}
113
114
			return [
115
				'preview'  => $img,
116
				'mimetype' => $mimeType
117
			];
118
		} catch (\Exception $exception) {
119
			$this->logger->logException($exception, ['app' => 'gallery']);
120
			throw new InternalServerErrorServiceException('Preview generation has failed');
121
		}
122
	}
123
124
	/**
125
	 * Returns true if the passed mime type is supported
126
	 *
127
	 * In case of a failure, we just return that the media type is not supported
128
	 *
129
	 * @param string $mimeType
130
	 *
131
	 * @return boolean
132
	 */
133
	private function isMimeSupported($mimeType = '*') {
134
		try {
135
			return $this->previewManager->isMimeSupported($mimeType);
136
		} catch (\Exception $exception) {
137
			unset($exception);
138
139
			return false;
140
		}
141
	}
142
143
	/**
144
	 * Decides if we should download the SVG or generate a preview
145
	 *
146
	 * SVGs are downloaded if the SVG converter is disabled
147
	 * Files of any media type are downloaded if requested by the client
148
	 *
149
	 * @return bool
150
	 */
151
	private function isSvgPreviewRequired() {
152
		return $this->isMimeSupported('image/svg+xml');
153
	}
154
155
	/**
156
	 * Decides if we should download the GIF or generate a preview
157
	 *
158
	 * GIFs are downloaded if they're animated and we want to show
159
	 * animations
160
	 *
161
	 * @param File $file
162
	 * @param bool $animatedPreview
163
	 *
164
	 * @return bool
165
	 */
166
	private function isGifPreviewRequired($file, $animatedPreview) {
167
		$gifSupport = $this->isMimeSupported('image/gif');
168
		$animatedGif = $this->isGifAnimated($file);
169
170
		return $gifSupport && !($animatedGif && $animatedPreview);
171
	}
172
173
	/**
174
	 * Tests if a GIF is animated
175
	 *
176
	 * An animated gif contains multiple "frames", with each frame having a
177
	 * header made up of:
178
	 *    * a static 4-byte sequence (\x00\x21\xF9\x04)
179
	 *    * 4 variable bytes
180
	 *    * a static 2-byte sequence (\x00\x2C) (Photoshop uses \x00\x21)
181
	 *
182
	 * We read through the file until we reach the end of the file, or we've
183
	 * found at least 2 frame headers
184
	 *
185
	 * @link http://php.net/manual/en/function.imagecreatefromgif.php#104473
186
	 *
187
	 * @param File $file
188
	 *
189
	 * @return bool
190
	 */
191
	private function isGifAnimated($file) {
192
		$count = 0;
193
		$fileHandle = $this->isFileReadable($file);
194
		if ($fileHandle) {
0 ignored issues
show
$fileHandle is of type resource, thus it always evaluated to false.
Loading history...
195
			while (!feof($fileHandle) && $count < 2) {
196
				$chunk = fread($fileHandle, 1024 * 100); //read 100kb at a time
197
				$count += preg_match_all(
198
					'#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s', $chunk, $matches
199
				);
200
			}
201
			fclose($fileHandle);
202
		}
203
204
		return $count > 1;
205
	}
206
207
	/**
208
	 * Determines if we can read the content of the file and returns a file pointer resource
209
	 *
210
	 * We can't use something like $node->isReadable() as it's too unreliable
211
	 * Some storage classes just check for the presence of the file
212
	 *
213
	 * @param File $file
214
	 *
215
	 * @return resource
216
	 * @throws InternalServerErrorServiceException
217
	 */
218
	private function isFileReadable($file) {
219
		try {
220
			$fileHandle = $file->fopen('rb');
221
			if (!$fileHandle) {
222
				throw new \Exception();
223
			}
224
		} catch (\Exception $exception) {
225
			throw new InternalServerErrorServiceException(
226
				'Something went wrong when trying to read' . $file->getPath()
227
			);
228
		}
229
230
		return $fileHandle;
231
	}
232
233
}
234