preview.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950
  1. <?php
  2. /**
  3. * @author Björn Schießle <schiessle@owncloud.com>
  4. * @author Frank Karlitschek <frank@owncloud.org>
  5. * @author Georg Ehrke <georg@owncloud.com>
  6. * @author Joas Schilling <nickvergessen@owncloud.com>
  7. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  8. * @author Lukas Reschke <lukas@owncloud.com>
  9. * @author Morris Jobke <hey@morrisjobke.de>
  10. * @author Olivier Paroz <owncloud@interfasys.ch>
  11. * @author Robin Appelman <icewind@owncloud.com>
  12. * @author Robin McCorkell <rmccorkell@karoshi.org.uk>
  13. * @author Thomas Müller <thomas.mueller@tmit.eu>
  14. * @author Tobias Kaminsky <tobias@kaminsky.me>
  15. *
  16. * @copyright Copyright (c) 2015, ownCloud, Inc.
  17. * @license AGPL-3.0
  18. *
  19. * This code is free software: you can redistribute it and/or modify
  20. * it under the terms of the GNU Affero General Public License, version 3,
  21. * as published by the Free Software Foundation.
  22. *
  23. * This program is distributed in the hope that it will be useful,
  24. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  25. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  26. * GNU Affero General Public License for more details.
  27. *
  28. * You should have received a copy of the GNU Affero General Public License, version 3,
  29. * along with this program. If not, see <http://www.gnu.org/licenses/>
  30. *
  31. */
  32. namespace OC;
  33. use OC\Preview\Provider;
  34. use OCP\Files\FileInfo;
  35. use OCP\Files\NotFoundException;
  36. class Preview {
  37. //the thumbnail folder
  38. const THUMBNAILS_FOLDER = 'thumbnails';
  39. //config
  40. private $maxScaleFactor;
  41. private $configMaxX;
  42. private $configMaxY;
  43. //fileview object
  44. private $fileView = null;
  45. private $userView = null;
  46. //vars
  47. private $file;
  48. private $maxX;
  49. private $maxY;
  50. private $scalingUp;
  51. private $mimeType;
  52. private $keepAspect = false;
  53. //filemapper used for deleting previews
  54. // index is path, value is fileinfo
  55. static public $deleteFileMapper = array();
  56. static public $deleteChildrenMapper = array();
  57. /**
  58. * preview images object
  59. *
  60. * @var \OCP\IImage
  61. */
  62. private $preview;
  63. /**
  64. * @var \OCP\Files\FileInfo
  65. */
  66. protected $info;
  67. /**
  68. * check if thumbnail or bigger version of thumbnail of file is cached
  69. * @param string $user userid - if no user is given, OC_User::getUser will be used
  70. * @param string $root path of root
  71. * @param string $file The path to the file where you want a thumbnail from
  72. * @param int $maxX The maximum X size of the thumbnail. It can be smaller depending on the shape of the image
  73. * @param int $maxY The maximum Y size of the thumbnail. It can be smaller depending on the shape of the image
  74. * @param bool $scalingUp Disable/Enable upscaling of previews
  75. * @throws \Exception
  76. * @return mixed (bool / string)
  77. * false if thumbnail does not exist
  78. * path to thumbnail if thumbnail exists
  79. */
  80. public function __construct($user = '', $root = '/', $file = '', $maxX = 1, $maxY = 1, $scalingUp = true) {
  81. //init fileviews
  82. if ($user === '') {
  83. $user = \OC_User::getUser();
  84. }
  85. $this->fileView = new \OC\Files\View('/' . $user . '/' . $root);
  86. $this->userView = new \OC\Files\View('/' . $user);
  87. //set config
  88. $this->configMaxX = \OC::$server->getConfig()->getSystemValue('preview_max_x', 2048);
  89. $this->configMaxY = \OC::$server->getConfig()->getSystemValue('preview_max_y', 2048);
  90. $this->maxScaleFactor = \OC::$server->getConfig()->getSystemValue('preview_max_scale_factor', 2);
  91. //save parameters
  92. $this->setFile($file);
  93. $this->setMaxX($maxX);
  94. $this->setMaxY($maxY);
  95. $this->setScalingUp($scalingUp);
  96. $this->preview = null;
  97. //check if there are preview backends
  98. if (!\OC::$server->getPreviewManager()->hasProviders() && \OC::$server->getConfig()->getSystemValue('enable_previews', true)) {
  99. \OC_Log::write('core', 'No preview providers exist', \OC_Log::ERROR);
  100. throw new \Exception('No preview providers');
  101. }
  102. }
  103. /**
  104. * returns the path of the file you want a thumbnail from
  105. * @return string
  106. */
  107. public function getFile() {
  108. return $this->file;
  109. }
  110. /**
  111. * returns the max width of the preview
  112. * @return integer
  113. */
  114. public function getMaxX() {
  115. return $this->maxX;
  116. }
  117. /**
  118. * returns the max height of the preview
  119. * @return integer
  120. */
  121. public function getMaxY() {
  122. return $this->maxY;
  123. }
  124. /**
  125. * returns whether or not scalingup is enabled
  126. * @return bool
  127. */
  128. public function getScalingUp() {
  129. return $this->scalingUp;
  130. }
  131. /**
  132. * returns the name of the thumbnailfolder
  133. * @return string
  134. */
  135. public function getThumbnailsFolder() {
  136. return self::THUMBNAILS_FOLDER;
  137. }
  138. /**
  139. * returns the max scale factor
  140. * @return string
  141. */
  142. public function getMaxScaleFactor() {
  143. return $this->maxScaleFactor;
  144. }
  145. /**
  146. * returns the max width set in ownCloud's config
  147. * @return string
  148. */
  149. public function getConfigMaxX() {
  150. return $this->configMaxX;
  151. }
  152. /**
  153. * returns the max height set in ownCloud's config
  154. * @return string
  155. */
  156. public function getConfigMaxY() {
  157. return $this->configMaxY;
  158. }
  159. /**
  160. * @return false|Files\FileInfo|\OCP\Files\FileInfo
  161. */
  162. protected function getFileInfo() {
  163. $absPath = $this->fileView->getAbsolutePath($this->file);
  164. $absPath = Files\Filesystem::normalizePath($absPath);
  165. if(array_key_exists($absPath, self::$deleteFileMapper)) {
  166. $this->info = self::$deleteFileMapper[$absPath];
  167. } else if (!$this->info) {
  168. $this->info = $this->fileView->getFileInfo($this->file);
  169. }
  170. return $this->info;
  171. }
  172. /**
  173. * @return array|null
  174. */
  175. private function getChildren() {
  176. $absPath = $this->fileView->getAbsolutePath($this->file);
  177. $absPath = Files\Filesystem::normalizePath($absPath);
  178. if (array_key_exists($absPath, self::$deleteChildrenMapper)) {
  179. return self::$deleteChildrenMapper[$absPath];
  180. }
  181. return null;
  182. }
  183. /**
  184. * set the path of the file you want a thumbnail from
  185. * @param string $file
  186. * @return $this
  187. */
  188. public function setFile($file) {
  189. $this->file = $file;
  190. $this->info = null;
  191. if ($file !== '') {
  192. $this->getFileInfo();
  193. if($this->info instanceof \OCP\Files\FileInfo) {
  194. $this->mimeType = $this->info->getMimetype();
  195. }
  196. }
  197. return $this;
  198. }
  199. /**
  200. * set mime type explicitly
  201. * @param string $mimeType
  202. */
  203. public function setMimetype($mimeType) {
  204. $this->mimeType = $mimeType;
  205. }
  206. /**
  207. * set the the max width of the preview
  208. * @param int $maxX
  209. * @throws \Exception
  210. * @return \OC\Preview $this
  211. */
  212. public function setMaxX($maxX = 1) {
  213. if ($maxX <= 0) {
  214. throw new \Exception('Cannot set width of 0 or smaller!');
  215. }
  216. $configMaxX = $this->getConfigMaxX();
  217. if (!is_null($configMaxX)) {
  218. if ($maxX > $configMaxX) {
  219. \OCP\Util::writeLog('core', 'maxX reduced from ' . $maxX . ' to ' . $configMaxX, \OCP\Util::DEBUG);
  220. $maxX = $configMaxX;
  221. }
  222. }
  223. $this->maxX = $maxX;
  224. return $this;
  225. }
  226. /**
  227. * set the the max height of the preview
  228. * @param int $maxY
  229. * @throws \Exception
  230. * @return \OC\Preview $this
  231. */
  232. public function setMaxY($maxY = 1) {
  233. if ($maxY <= 0) {
  234. throw new \Exception('Cannot set height of 0 or smaller!');
  235. }
  236. $configMaxY = $this->getConfigMaxY();
  237. if (!is_null($configMaxY)) {
  238. if ($maxY > $configMaxY) {
  239. \OCP\Util::writeLog('core', 'maxX reduced from ' . $maxY . ' to ' . $configMaxY, \OCP\Util::DEBUG);
  240. $maxY = $configMaxY;
  241. }
  242. }
  243. $this->maxY = $maxY;
  244. return $this;
  245. }
  246. /**
  247. * set whether or not scalingup is enabled
  248. * @param bool $scalingUp
  249. * @return \OC\Preview $this
  250. */
  251. public function setScalingup($scalingUp) {
  252. if ($this->getMaxScaleFactor() === 1) {
  253. $scalingUp = false;
  254. }
  255. $this->scalingUp = $scalingUp;
  256. return $this;
  257. }
  258. /**
  259. * @param bool $keepAspect
  260. * @return $this
  261. */
  262. public function setKeepAspect($keepAspect) {
  263. $this->keepAspect = $keepAspect;
  264. return $this;
  265. }
  266. /**
  267. * check if all parameters are valid
  268. * @return bool
  269. */
  270. public function isFileValid() {
  271. $file = $this->getFile();
  272. if ($file === '') {
  273. \OCP\Util::writeLog('core', 'No filename passed', \OCP\Util::DEBUG);
  274. return false;
  275. }
  276. if (!$this->fileView->file_exists($file)) {
  277. \OCP\Util::writeLog('core', 'File:"' . $file . '" not found', \OCP\Util::DEBUG);
  278. return false;
  279. }
  280. return true;
  281. }
  282. /**
  283. * deletes previews of a file with specific x and y
  284. * @return bool
  285. */
  286. public function deletePreview() {
  287. $fileInfo = $this->getFileInfo();
  288. if($fileInfo !== null && $fileInfo !== false) {
  289. $fileId = $fileInfo->getId();
  290. $previewPath = $this->buildCachePath($fileId);
  291. return $this->userView->unlink($previewPath);
  292. }
  293. return false;
  294. }
  295. /**
  296. * deletes all previews of a file
  297. */
  298. public function deleteAllPreviews() {
  299. $toDelete = $this->getChildren();
  300. $toDelete[] = $this->getFileInfo();
  301. foreach ($toDelete as $delete) {
  302. if ($delete instanceof FileInfo) {
  303. /** @var \OCP\Files\FileInfo $delete */
  304. $fileId = $delete->getId();
  305. // getId() might return null, e.g. when the file is a
  306. // .ocTransferId*.part file from chunked file upload.
  307. if (!empty($fileId)) {
  308. $previewPath = $this->getPreviewPath($fileId);
  309. $this->userView->deleteAll($previewPath);
  310. $this->userView->rmdir($previewPath);
  311. }
  312. }
  313. }
  314. }
  315. /**
  316. * Checks if thumbnail or bigger version of thumbnail of file is already cached
  317. *
  318. * @param int $fileId fileId of the original image
  319. * @return string|false path to thumbnail if it exists or false
  320. */
  321. public function isCached($fileId) {
  322. if (is_null($fileId)) {
  323. return false;
  324. }
  325. // This gives us a calculated path to a preview of asked dimensions
  326. // thumbnailFolder/fileId/my_image-<maxX>-<maxY>.png
  327. $preview = $this->buildCachePath($fileId);
  328. // This checks if a preview exists at that location
  329. if ($this->userView->file_exists($preview)) {
  330. return $preview;
  331. }
  332. return $this->isCachedBigger($fileId);
  333. }
  334. /**
  335. * Checks if a bigger version of a file preview is cached and if not
  336. * return the preview of max allowed dimensions
  337. *
  338. * @param int $fileId fileId of the original image
  339. *
  340. * @return string|false path to bigger thumbnail if it exists or false
  341. */
  342. private function isCachedBigger($fileId) {
  343. if (is_null($fileId)) {
  344. return false;
  345. }
  346. $maxX = $this->getMaxX();
  347. //array for usable cached thumbnails
  348. // FIXME: Checking only the width could lead to issues
  349. $possibleThumbnails = $this->getPossibleThumbnails($fileId);
  350. foreach ($possibleThumbnails as $width => $path) {
  351. if ($width === 'max' || $width < $maxX) {
  352. continue;
  353. } else {
  354. return $path;
  355. }
  356. }
  357. // At this stage, we didn't find a preview, so if the folder is not empty,
  358. // we return the max preview we generated on the first run
  359. if ($possibleThumbnails) {
  360. return $possibleThumbnails['max'];
  361. }
  362. return false;
  363. }
  364. /**
  365. * get possible bigger thumbnails of the given image
  366. * @param int $fileId fileId of the original image
  367. * @return array an array of paths to bigger thumbnails
  368. */
  369. private function getPossibleThumbnails($fileId) {
  370. if (is_null($fileId)) {
  371. return array();
  372. }
  373. $previewPath = $this->getPreviewPath($fileId);
  374. $wantedAspectRatio = (float)($this->getMaxX() / $this->getMaxY());
  375. //array for usable cached thumbnails
  376. $possibleThumbnails = array();
  377. $allThumbnails = $this->userView->getDirectoryContent($previewPath);
  378. foreach ($allThumbnails as $thumbnail) {
  379. $name = rtrim($thumbnail['name'], '.png');
  380. // Always add the max preview to the array
  381. if (strpos($name, 'max')) {
  382. $possibleThumbnails['max'] = $thumbnail['path'];
  383. continue;
  384. }
  385. list($x, $y, $aspectRatio) = $this->getDimensionsFromFilename($name);
  386. if (abs($aspectRatio - $wantedAspectRatio) >= 0.000001
  387. || $this->unscalable($x, $y)
  388. ) {
  389. continue;
  390. }
  391. $possibleThumbnails[$x] = $thumbnail['path'];
  392. }
  393. ksort($possibleThumbnails);
  394. return $possibleThumbnails;
  395. }
  396. /**
  397. * @param string $name
  398. * @return array
  399. */
  400. private function getDimensionsFromFilename($name) {
  401. $size = explode('-', $name);
  402. $x = (int) $size[0];
  403. $y = (int) $size[1];
  404. $aspectRatio = (float) ($x / $y);
  405. return array($x, $y, $aspectRatio);
  406. }
  407. /**
  408. * @param int $x
  409. * @param int $y
  410. * @return bool
  411. */
  412. private function unscalable($x, $y) {
  413. $maxX = $this->getMaxX();
  414. $maxY = $this->getMaxY();
  415. $scalingUp = $this->getScalingUp();
  416. $maxScaleFactor = $this->getMaxScaleFactor();
  417. if ($x < $maxX || $y < $maxY) {
  418. if ($scalingUp) {
  419. $scalefactor = $maxX / $x;
  420. if ($scalefactor > $maxScaleFactor) {
  421. return true;
  422. }
  423. } else {
  424. return true;
  425. }
  426. }
  427. return false;
  428. }
  429. /**
  430. * Returns a preview of a file
  431. *
  432. * The cache is searched first and if nothing usable was found then a preview is
  433. * generated by one of the providers
  434. *
  435. * @return \OCP\IImage
  436. */
  437. public function getPreview() {
  438. if (!is_null($this->preview) && $this->preview->valid()) {
  439. return $this->preview;
  440. }
  441. $this->preview = null;
  442. $fileInfo = $this->getFileInfo();
  443. if ($fileInfo === null || $fileInfo === false) {
  444. return new \OC_Image();
  445. }
  446. $fileId = $fileInfo->getId();
  447. $cached = $this->isCached($fileId);
  448. if ($cached) {
  449. $this->getCachedPreview($fileId, $cached);
  450. }
  451. if (is_null($this->preview)) {
  452. $this->generatePreview($fileId);
  453. }
  454. // We still don't have a preview, so we generate an empty object which can't be displayed
  455. if (is_null($this->preview)) {
  456. $this->preview = new \OC_Image();
  457. }
  458. return $this->preview;
  459. }
  460. /**
  461. * @param null|string $mimeType
  462. * @throws NotFoundException
  463. */
  464. public function showPreview($mimeType = null) {
  465. // Check if file is valid
  466. if($this->isFileValid() === false) {
  467. throw new NotFoundException('File not found.');
  468. }
  469. \OCP\Response::enableCaching(3600 * 24); // 24 hours
  470. if (is_null($this->preview)) {
  471. $this->getPreview();
  472. }
  473. if ($this->preview instanceof \OCP\IImage) {
  474. $this->preview->show($mimeType);
  475. }
  476. }
  477. /**
  478. * Retrieves the preview from the cache and resizes it if necessary
  479. *
  480. * @param int $fileId fileId of the original image
  481. * @param string $cached the path to the cached preview
  482. */
  483. private function getCachedPreview($fileId, $cached) {
  484. $stream = $this->userView->fopen($cached, 'r');
  485. $this->preview = null;
  486. if ($stream) {
  487. $image = new \OC_Image();
  488. $image->loadFromFileHandle($stream);
  489. $this->preview = $image->valid() ? $image : null;
  490. $maxX = (int)$this->getMaxX();
  491. $maxY = (int)$this->getMaxY();
  492. $previewX = (int)$this->preview->width();
  493. $previewY = (int)$this->preview->height();
  494. if ($previewX !== $maxX && $previewY !== $maxY) {
  495. $this->resizeAndStore($fileId);
  496. }
  497. fclose($stream);
  498. }
  499. }
  500. /**
  501. * Resizes, crops, fixes orientation and stores in the cache
  502. *
  503. * @param int $fileId fileId of the original image
  504. */
  505. private function resizeAndStore($fileId) {
  506. // Resize and store
  507. $this->resizeAndCrop();
  508. // We save a copy in the cache to speed up future calls
  509. $cachePath = $this->buildCachePath($fileId);
  510. $this->userView->file_put_contents($cachePath, $this->preview->data());
  511. }
  512. /**
  513. * resize, crop and fix orientation
  514. *
  515. * @param bool $max
  516. */
  517. private function resizeAndCrop($max = false) {
  518. $image = $this->preview;
  519. list($x, $y, $scalingUp, $maxScaleFactor) = $this->getResizeData($max);
  520. if (!($image instanceof \OCP\IImage)) {
  521. \OCP\Util::writeLog('core', '$this->preview is not an instance of OC_Image', \OCP\Util::DEBUG);
  522. return;
  523. }
  524. $realX = (int)$image->width();
  525. $realY = (int)$image->height();
  526. // compute $maxY and $maxX using the aspect of the generated preview
  527. if ($this->keepAspect) {
  528. $ratio = $realX / $realY;
  529. if($x / $ratio < $y) {
  530. // width restricted
  531. $y = $x / $ratio;
  532. } else {
  533. $x = $y * $ratio;
  534. }
  535. }
  536. // The preview already has the asked dimensions
  537. if ($x === $realX && $y === $realY) {
  538. $this->preview = $image;
  539. return;
  540. }
  541. $factorX = $x / $realX;
  542. $factorY = $y / $realY;
  543. if ($factorX >= $factorY) {
  544. $factor = $factorX;
  545. } else {
  546. $factor = $factorY;
  547. }
  548. if ($scalingUp === false) {
  549. if ($factor > 1) {
  550. $factor = 1;
  551. }
  552. }
  553. if (!is_null($maxScaleFactor)) {
  554. if ($factor > $maxScaleFactor) {
  555. \OCP\Util::writeLog('core', 'scale factor reduced from ' . $factor . ' to ' . $maxScaleFactor, \OCP\Util::DEBUG);
  556. $factor = $maxScaleFactor;
  557. }
  558. }
  559. $newXSize = (int)($realX * $factor);
  560. $newYSize = (int)($realY * $factor);
  561. $image->preciseResize($newXSize, $newYSize);
  562. // The preview has been upscaled and now has the asked dimensions
  563. if ($newXSize === $x && $newYSize === $y) {
  564. $this->preview = $image;
  565. return;
  566. }
  567. // One dimension of the upscaled preview is too big
  568. if ($newXSize >= $x && $newYSize >= $y) {
  569. $cropX = floor(abs($x - $newXSize) * 0.5);
  570. //don't crop previews on the Y axis, this sucks if it's a document.
  571. //$cropY = floor(abs($y - $newYsize) * 0.5);
  572. $cropY = 0;
  573. $image->crop($cropX, $cropY, $x, $y);
  574. $this->preview = $image;
  575. return;
  576. }
  577. // One dimension of the upscaled preview is too small and we're allowed to scale up
  578. if (($newXSize < $x || $newYSize < $y) && $scalingUp) {
  579. if ($newXSize > $x) {
  580. $cropX = floor(($newXSize - $x) * 0.5);
  581. $image->crop($cropX, 0, $x, $newYSize);
  582. }
  583. if ($newYSize > $y) {
  584. $cropY = floor(($newYSize - $y) * 0.5);
  585. $image->crop(0, $cropY, $newXSize, $y);
  586. }
  587. $newXSize = (int)$image->width();
  588. $newYSize = (int)$image->height();
  589. //create transparent background layer
  590. $backgroundLayer = imagecreatetruecolor($x, $y);
  591. $white = imagecolorallocate($backgroundLayer, 255, 255, 255);
  592. imagefill($backgroundLayer, 0, 0, $white);
  593. $image = $image->resource();
  594. $mergeX = floor(abs($x - $newXSize) * 0.5);
  595. $mergeY = floor(abs($y - $newYSize) * 0.5);
  596. imagecopy($backgroundLayer, $image, $mergeX, $mergeY, 0, 0, $newXSize, $newYSize);
  597. //$black = imagecolorallocate(0,0,0);
  598. //imagecolortransparent($transparentlayer, $black);
  599. $image = new \OC_Image($backgroundLayer);
  600. $this->preview = $image;
  601. return;
  602. }
  603. }
  604. /**
  605. * Returns data to be used to resize a preview
  606. *
  607. * @param $max
  608. *
  609. * @return array
  610. */
  611. private function getResizeData($max) {
  612. if (!$max) {
  613. $x = $this->getMaxX();
  614. $y = $this->getMaxY();
  615. $scalingUp = $this->getScalingUp();
  616. $maxScaleFactor = $this->getMaxScaleFactor();
  617. } else {
  618. $x = $this->configMaxX;
  619. $y = $this->configMaxY;
  620. $scalingUp = false;
  621. $maxScaleFactor =1;
  622. }
  623. return [$x, $y, $scalingUp, $maxScaleFactor];
  624. }
  625. /**
  626. * Returns the path to a preview based on its dimensions and aspect
  627. *
  628. * @param int $fileId
  629. *
  630. * @return string
  631. */
  632. private function buildCachePath($fileId) {
  633. $maxX = $this->getMaxX();
  634. $maxY = $this->getMaxY();
  635. $previewPath = $this->getPreviewPath($fileId);
  636. $previewPath = $previewPath . strval($maxX) . '-' . strval($maxY);
  637. if ($this->keepAspect) {
  638. $previewPath .= '-with-aspect';
  639. }
  640. $previewPath .= '.png';
  641. return $previewPath;
  642. }
  643. /**
  644. * @param int $fileId
  645. *
  646. * @return string
  647. */
  648. private function getPreviewPath($fileId) {
  649. return $this->getThumbnailsFolder() . '/' . $fileId . '/';
  650. }
  651. /**
  652. * Asks the provider to send a preview of the file of maximum dimensions
  653. * and after saving it in the cache, it is then resized to the asked dimensions
  654. *
  655. * This is only called once in order to generate a large PNG of dimensions defined in the
  656. * configuration file. We'll be able to quickly resize it later on.
  657. * We never upscale the original conversion as this will be done later by the resizing operation
  658. *
  659. * @param int $fileId fileId of the original image
  660. */
  661. private function generatePreview($fileId) {
  662. $file = $this->getFile();
  663. $preview = null;
  664. $previewProviders = \OC::$server->getPreviewManager()->getProviders();
  665. foreach ($previewProviders as $supportedMimeType => $providers) {
  666. if (!preg_match($supportedMimeType, $this->mimeType)) {
  667. continue;
  668. }
  669. foreach ($providers as $closure) {
  670. $provider = $closure();
  671. if (!($provider instanceof \OCP\Preview\IProvider)) {
  672. continue;
  673. }
  674. \OCP\Util::writeLog(
  675. 'core', 'Generating preview for "' . $file . '" with "' . get_class($provider)
  676. . '"', \OCP\Util::DEBUG
  677. );
  678. /** @var $provider Provider */
  679. $preview = $provider->getThumbnail(
  680. $file, $this->configMaxX, $this->configMaxY, $scalingUp = false, $this->fileView
  681. );
  682. if (!($preview instanceof \OCP\IImage)) {
  683. continue;
  684. }
  685. $this->preview = $preview;
  686. $previewPath = $this->getPreviewPath($fileId);
  687. if ($this->userView->is_dir($this->getThumbnailsFolder() . '/') === false) {
  688. $this->userView->mkdir($this->getThumbnailsFolder() . '/');
  689. }
  690. if ($this->userView->is_dir($previewPath) === false) {
  691. $this->userView->mkdir($previewPath);
  692. }
  693. // This stores our large preview so that it can be used in subsequent resizing requests
  694. $this->storeMaxPreview($previewPath);
  695. break 2;
  696. }
  697. }
  698. // The providers have been kind enough to give us a preview
  699. if ($preview) {
  700. $this->resizeAndStore($fileId);
  701. }
  702. }
  703. /**
  704. * Stores the max preview in the cache
  705. *
  706. * @param string $previewPath path to the preview
  707. */
  708. private function storeMaxPreview($previewPath) {
  709. $maxPreview = false;
  710. $preview = $this->preview;
  711. $allThumbnails = $this->userView->getDirectoryContent($previewPath);
  712. // This is so that the cache doesn't need emptying when upgrading
  713. // Can be replaced by an upgrade script...
  714. foreach ($allThumbnails as $thumbnail) {
  715. $name = rtrim($thumbnail['name'], '.png');
  716. if (strpos($name, 'max')) {
  717. $maxPreview = true;
  718. break;
  719. }
  720. }
  721. // We haven't found the max preview, so we create it
  722. if (!$maxPreview) {
  723. // Most providers don't resize their thumbnails yet
  724. $this->resizeAndCrop(true);
  725. $maxX = $preview->width();
  726. $maxY = $preview->height();
  727. $previewPath = $previewPath . strval($maxX) . '-' . strval($maxY);
  728. $previewPath .= '-max.png';
  729. $this->userView->file_put_contents($previewPath, $preview->data());
  730. }
  731. }
  732. /**
  733. * @param array $args
  734. */
  735. public static function post_write($args) {
  736. self::post_delete($args, 'files/');
  737. }
  738. /**
  739. * @param array $args
  740. */
  741. public static function prepare_delete_files($args) {
  742. self::prepare_delete($args, 'files/');
  743. }
  744. /**
  745. * @param array $args
  746. * @param string $prefix
  747. */
  748. public static function prepare_delete($args, $prefix='') {
  749. $path = $args['path'];
  750. if (substr($path, 0, 1) === '/') {
  751. $path = substr($path, 1);
  752. }
  753. $view = new \OC\Files\View('/' . \OC_User::getUser() . '/' . $prefix);
  754. $absPath = Files\Filesystem::normalizePath($view->getAbsolutePath($path));
  755. self::addPathToDeleteFileMapper($absPath, $view->getFileInfo($path));
  756. if ($view->is_dir($path)) {
  757. $children = self::getAllChildren($view, $path);
  758. self::$deleteChildrenMapper[$absPath] = $children;
  759. }
  760. }
  761. /**
  762. * @param string $absolutePath
  763. * @param \OCP\Files\FileInfo $info
  764. */
  765. private static function addPathToDeleteFileMapper($absolutePath, $info) {
  766. self::$deleteFileMapper[$absolutePath] = $info;
  767. }
  768. /**
  769. * @param \OC\Files\View $view
  770. * @param string $path
  771. * @return array
  772. */
  773. private static function getAllChildren($view, $path) {
  774. $children = $view->getDirectoryContent($path);
  775. $childrensFiles = array();
  776. $fakeRootLength = strlen($view->getRoot());
  777. for ($i = 0; $i < count($children); $i++) {
  778. $child = $children[$i];
  779. $childsPath = substr($child->getPath(), $fakeRootLength);
  780. if ($view->is_dir($childsPath)) {
  781. $children = array_merge(
  782. $children,
  783. $view->getDirectoryContent($childsPath)
  784. );
  785. } else {
  786. $childrensFiles[] = $child;
  787. }
  788. }
  789. return $childrensFiles;
  790. }
  791. /**
  792. * @param array $args
  793. */
  794. public static function post_delete_files($args) {
  795. self::post_delete($args, 'files/');
  796. }
  797. /**
  798. * @param array $args
  799. * @param string $prefix
  800. */
  801. public static function post_delete($args, $prefix='') {
  802. $path = Files\Filesystem::normalizePath($args['path']);
  803. $preview = new Preview(\OC_User::getUser(), $prefix, $path);
  804. $preview->deleteAllPreviews();
  805. }
  806. }