Completed
Push — master ( 79120e...417d38 )
by Olivier
101:25 queued 98:44
created

PreviewService::isGifAnimated()   B

Complexity

Conditions 5
Paths 11

Size

Total Lines 23
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 5.0042

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 23
ccs 17
cts 18
cp 0.9444
rs 8.5906
cc 5
eloc 16
nc 11
nop 1
crap 5.0042
1
<?php
2
/**
3
 * ownCloud - galleryplus
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 2014-2015
11
 */
12
13
namespace OCA\GalleryPlus\Service;
14
15
use OCP\Files\File;
16
use OCP\ILogger;
17
18
use OCA\GalleryPlus\Environment\Environment;
19
use OCA\GalleryPlus\Preview\Preview;
20
21
/**
22
 * Generates previews
23
 *
24
 * @package OCA\GalleryPlus\Service
25
 */
26
class PreviewService extends Service {
27
28
	use Base64Encode;
29
30
	/** @var Preview */
31
	private $previewManager;
32
33
	/**
34
	 * Constructor
35
	 *
36
	 * @param string $appName
37
	 * @param Environment $environment
38
	 * @param Preview $previewManager
39
	 * @param ILogger $logger
40
	 */
41 27
	public function __construct(
42
		$appName,
43
		Environment $environment,
44
		Preview $previewManager,
45
		ILogger $logger
46
	) {
47 27
		parent::__construct($appName, $environment, $logger);
48
49 27
		$this->previewManager = $previewManager;
50 27
	}
51
52
	/**
53
	 * Decides if we should download the file instead of generating a preview
54
	 *
55
	 * @param File $file
56
	 * @param bool $animatedPreview
57
	 *
58
	 * @return bool
59
	 */
60 16
	public function isPreviewRequired($file, $animatedPreview) {
61 16
		$mime = $file->getMimeType();
62
63 16
		if ($mime === 'image/svg+xml') {
64 2
			return $this->isSvgPreviewRequired();
65
		}
66 14
		if ($mime === 'image/gif') {
67 10
			return $this->isGifPreviewRequired($file, $animatedPreview);
68
		}
69
70 6
		return true;
71
	}
72
73
	/**
74
	 * Returns an array containing everything needed by the client to be able to display a preview
75
	 *
76
	 *    * fileid:  the file's ID
77
	 *    * mimetype: the file's media type
78
	 *    * preview: the preview's content
79
	 *
80
	 * Example logger
81
	 * $this->logger->debug(
82
	 * "[PreviewService] Path : {path} / mime: {mimetype} / fileid: {fileid}",
83
	 * [
84
	 * 'path'     => $preview['data']['path'],
85
	 * 'mimetype' => $preview['data']['mimetype'],
86
	 * 'fileid'   => $preview['fileid']
87
	 * ]
88
	 * );
89
	 *
90
	 * @todo Get the max size from the settings
91
	 *
92
	 * @param File $file
93
	 * @param int $maxX asked width for the preview
94
	 * @param int $maxY asked height for the preview
95
	 * @param bool $keepAspect
96
	 * @param bool $base64Encode
97
	 *
98
	 * @return array <string,\OC_Image|string>|false preview data
99
	 * @throws InternalServerErrorServiceException
100
	 */
101 8
	public function createPreview(
102
		$file, $maxX = 0, $maxY = 0, $keepAspect = true, $base64Encode = false
103
	) {
104
		try {
105 8
			$userId = $this->environment->getUserId();
106 7
			$imagePathFromFolder = $this->environment->getPathFromUserFolder($file);
107 7
			$this->previewManager->setupView($userId, $file, $imagePathFromFolder);
108 7
			$preview = $this->previewManager->preparePreview($maxX, $maxY, $keepAspect);
109 7
			if ($preview && $base64Encode) {
110 3
				$preview['preview'] = $this->encode($preview['preview']);
111
			}
112
113 7
			return $preview;
114 1
		} catch (\Exception $exception) {
115 1
			throw new InternalServerErrorServiceException('Preview generation has failed');
116
		}
117
	}
118
119
	/**
120
	 * Makes sure we return previews of the asked dimensions and fix the cache
121
	 * if necessary
122
	 *
123
	 * @param bool $square
124
	 * @param bool $base64Encode
125
	 *
126
	 * @return \OC_Image|string
127
	 * @throws InternalServerErrorServiceException
128
	 */
129 8
	public function previewValidator($square, $base64Encode) {
130
		try {
131 8
			$preview = $this->previewManager->previewValidator($square);
132 7
			if ($base64Encode) {
133 5
				$preview = $this->encode($preview);
134 2
			}
135
136 7
			return $preview;
137 1
		} catch (\Exception $exception) {
138 1
			throw new InternalServerErrorServiceException(
139
				'There was an error while trying to fix the preview'
140 1
			);
141
		}
142
	}
143
144
	/**
145
	 * Returns true if the passed mime type is supported
146
	 *
147
	 * In case of a failure, we just return that the media type is not supported
148
	 *
149
	 * @param string $mimeType
150
	 *
151
	 * @return boolean
152
	 */
153 12
	private function isMimeSupported($mimeType = '*') {
154
		try {
155 12
			return $this->previewManager->isMimeSupported($mimeType);
156 1
		} catch (\Exception $exception) {
157 1
			unset($exception);
158
159 1
			return false;
160
		}
161
	}
162
163
	/**
164
	 * Decides if we should download the SVG or generate a preview
165
	 *
166
	 * SVGs are downloaded if the SVG converter is disabled
167
	 * Files of any media type are downloaded if requested by the client
168
	 *
169
	 * @return bool
170
	 */
171 2
	private function isSvgPreviewRequired() {
172 2
		return $this->isMimeSupported('image/svg+xml');
173
	}
174
175
	/**
176
	 * Decides if we should download the GIF or generate a preview
177
	 *
178
	 * GIFs are downloaded if they're animated and we want to show
179
	 * animations
180
	 *
181
	 * @param File $file
182
	 * @param bool $animatedPreview
183
	 *
184
	 * @return bool
185
	 */
186 10
	private function isGifPreviewRequired($file, $animatedPreview) {
187 10
		$gifSupport = $this->isMimeSupported('image/gif');
188 10
		$animatedGif = $this->isGifAnimated($file);
189
190 9
		return $gifSupport && !($animatedGif && $animatedPreview);
191
	}
192
193
	/**
194
	 * Tests if a GIF is animated
195
	 *
196
	 * An animated gif contains multiple "frames", with each frame having a
197
	 * header made up of:
198
	 *    * a static 4-byte sequence (\x00\x21\xF9\x04)
199
	 *    * 4 variable bytes
200
	 *    * a static 2-byte sequence (\x00\x2C) (Photoshop uses \x00\x21)
201
	 *
202
	 * We read through the file until we reach the end of the file, or we've
203
	 * found at least 2 frame headers
204
	 *
205
	 * @link http://php.net/manual/en/function.imagecreatefromgif.php#104473
206
	 *
207
	 * @param File $file
208
	 *
209
	 * @return bool
210
	 * @throws InternalServerErrorServiceException
211
	 */
212 10
	private function isGifAnimated($file) {
213 10
		$count = 0;
214
		try {
215 10
			$fileHandle = $file->fopen('rb');
216 9
			if ($fileHandle) {
217 9
				while (!feof($fileHandle) && $count < 2) {
218 9
					$chunk = fread($fileHandle, 1024 * 100); //read 100kb at a time
219 9
					$count += preg_match_all(
220 9
						'#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s', $chunk, $matches
221 9
					);
222 9
				}
223 9
				fclose($fileHandle);
224 9
			} else {
225
				throw new \Exception();
226
			}
227 6
		} catch (\Exception $exception) {
228 1
			throw new InternalServerErrorServiceException(
229 1
				'Something went wrong when trying to read' . $file->getPath()
230 1
			);
231
		}
232
233 9
		return $count > 1;
234
	}
235
236
}
237