Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like Preview often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Preview, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 46 | class Preview { |
||
| 47 | //the thumbnail folder |
||
| 48 | const THUMBNAILS_FOLDER = 'thumbnails'; |
||
| 49 | |||
| 50 | const MODE_FILL = 'fill'; |
||
| 51 | const MODE_COVER = 'cover'; |
||
| 52 | |||
| 53 | //config |
||
| 54 | private $maxScaleFactor; |
||
| 55 | /** @var int maximum width allowed for a preview */ |
||
| 56 | private $configMaxWidth; |
||
| 57 | /** @var int maximum height allowed for a preview */ |
||
| 58 | private $configMaxHeight; |
||
| 59 | |||
| 60 | //fileview object |
||
| 61 | private $fileView = null; |
||
| 62 | private $userView = null; |
||
| 63 | |||
| 64 | //vars |
||
| 65 | /** @var File */ |
||
| 66 | private $file; |
||
| 67 | private $maxX; |
||
| 68 | private $maxY; |
||
| 69 | private $scalingUp; |
||
| 70 | private $mimeType; |
||
| 71 | private $keepAspect = false; |
||
| 72 | private $mode = self::MODE_FILL; |
||
| 73 | |||
| 74 | //used to calculate the size of the preview to generate |
||
| 75 | /** @var int $maxPreviewWidth max width a preview can have */ |
||
| 76 | private $maxPreviewWidth; |
||
| 77 | /** @var int $maxPreviewHeight max height a preview can have */ |
||
| 78 | private $maxPreviewHeight; |
||
| 79 | /** @var int $previewWidth calculated width of the preview we're looking for */ |
||
| 80 | private $previewWidth; |
||
| 81 | /** @var int $previewHeight calculated height of the preview we're looking for */ |
||
| 82 | private $previewHeight; |
||
| 83 | |||
| 84 | // filemapper used for deleting previews |
||
| 85 | // index is path, value is fileinfo |
||
| 86 | public static $deleteFileMapper = []; |
||
| 87 | public static $deleteChildrenMapper = []; |
||
| 88 | |||
| 89 | /** |
||
| 90 | * preview images object |
||
| 91 | * |
||
| 92 | * @var IImage |
||
| 93 | */ |
||
| 94 | private $preview; |
||
| 95 | /** @var string */ |
||
| 96 | private $versionId; |
||
| 97 | |||
| 98 | /** |
||
| 99 | * check if thumbnail or bigger version of thumbnail of file is cached |
||
| 100 | * |
||
| 101 | * @param string $user userid - if no user is given, OC_User::getUser will be used |
||
| 102 | * @param string $root path of root |
||
| 103 | * @param Node $file The path to the file where you want a thumbnail from |
||
| 104 | * @param int $maxX The maximum X size of the thumbnail. It can be smaller depending on the |
||
| 105 | * shape of the image |
||
| 106 | * @param int $maxY The maximum Y size of the thumbnail. It can be smaller depending on the |
||
| 107 | * shape of the image |
||
| 108 | * @param bool $scalingUp Disable/Enable up-scaling of previews |
||
| 109 | * @param string $versionId |
||
| 110 | * @throws \Exception |
||
| 111 | */ |
||
| 112 | public function __construct( |
||
| 153 | |||
| 154 | /** |
||
| 155 | * returns the path of the file you want a thumbnail from |
||
| 156 | * |
||
| 157 | * @return File |
||
| 158 | */ |
||
| 159 | public function getFile() { |
||
| 162 | |||
| 163 | /** |
||
| 164 | * returns the max width of the preview |
||
| 165 | * |
||
| 166 | * @return integer |
||
| 167 | */ |
||
| 168 | public function getMaxX() { |
||
| 171 | |||
| 172 | /** |
||
| 173 | * returns the max height of the preview |
||
| 174 | * |
||
| 175 | * @return integer |
||
| 176 | */ |
||
| 177 | public function getMaxY() { |
||
| 180 | |||
| 181 | /** |
||
| 182 | * returns whether or not scalingup is enabled |
||
| 183 | * |
||
| 184 | * @return bool |
||
| 185 | */ |
||
| 186 | public function getScalingUp() { |
||
| 189 | |||
| 190 | /** |
||
| 191 | * returns the name of the thumbnailfolder |
||
| 192 | * |
||
| 193 | * @return string |
||
| 194 | */ |
||
| 195 | public function getThumbnailsFolder() { |
||
| 198 | |||
| 199 | /** |
||
| 200 | * returns the max scale factor |
||
| 201 | * |
||
| 202 | * @return string |
||
| 203 | */ |
||
| 204 | public function getMaxScaleFactor() { |
||
| 207 | |||
| 208 | /** |
||
| 209 | * returns the max width set in ownCloud's config |
||
| 210 | * |
||
| 211 | * @return integer |
||
| 212 | */ |
||
| 213 | public function getConfigMaxX() { |
||
| 216 | |||
| 217 | /** |
||
| 218 | * returns the max height set in ownCloud's config |
||
| 219 | * |
||
| 220 | * @return integer |
||
| 221 | */ |
||
| 222 | public function getConfigMaxY() { |
||
| 225 | |||
| 226 | /** |
||
| 227 | * Returns the FileInfo object associated with the file to preview |
||
| 228 | * |
||
| 229 | * @return false|Files\FileInfo|\OCP\Files\FileInfo |
||
| 230 | */ |
||
| 231 | protected function getFileInfo() { |
||
| 234 | |||
| 235 | /** |
||
| 236 | * @return array|null |
||
| 237 | */ |
||
| 238 | private function getChildren() { |
||
| 248 | |||
| 249 | /** |
||
| 250 | * Sets the path of the file you want a preview of |
||
| 251 | * |
||
| 252 | * @param Node $file |
||
| 253 | * @param string $versionId |
||
| 254 | * |
||
| 255 | * @return Preview |
||
| 256 | */ |
||
| 257 | public function setFile(Node $file, $versionId = null) { |
||
| 264 | |||
| 265 | /** |
||
| 266 | * Forces the use of a specific media type |
||
| 267 | * |
||
| 268 | * @param string $mimeType |
||
| 269 | */ |
||
| 270 | public function setMimetype($mimeType) { |
||
| 273 | |||
| 274 | /** |
||
| 275 | * Sets the max width of the preview. It's capped by the maximum allowed size set in the |
||
| 276 | * configuration |
||
| 277 | * |
||
| 278 | * @param int $maxX |
||
| 279 | * |
||
| 280 | * @throws \Exception |
||
| 281 | * @return \OC\Preview |
||
| 282 | */ |
||
| 283 | public function setMaxX($maxX = 1) { |
||
| 293 | |||
| 294 | /** |
||
| 295 | * Sets the max height of the preview. It's capped by the maximum allowed size set in the |
||
| 296 | * configuration |
||
| 297 | * |
||
| 298 | * @param int $maxY |
||
| 299 | * |
||
| 300 | * @throws \Exception |
||
| 301 | * @return \OC\Preview |
||
| 302 | */ |
||
| 303 | public function setMaxY($maxY = 1) { |
||
| 313 | |||
| 314 | /** |
||
| 315 | * Sets whether we're allowed to scale up when generating a preview. It's capped by the maximum |
||
| 316 | * allowed scale factor set in the configuration |
||
| 317 | * |
||
| 318 | * @param bool $scalingUp |
||
| 319 | * |
||
| 320 | * @return \OC\Preview |
||
| 321 | */ |
||
| 322 | public function setScalingup($scalingUp) { |
||
| 330 | |||
| 331 | /** |
||
| 332 | * Set whether to cover or fill the specified dimensions |
||
| 333 | * |
||
| 334 | * @param string $mode |
||
| 335 | * |
||
| 336 | * @return \OC\Preview |
||
| 337 | */ |
||
| 338 | public function setMode($mode) { |
||
| 343 | |||
| 344 | /** |
||
| 345 | * Sets whether we need to generate a preview which keeps the aspect ratio of the original file |
||
| 346 | * |
||
| 347 | * @param bool $keepAspect |
||
| 348 | * |
||
| 349 | * @return \OC\Preview |
||
| 350 | */ |
||
| 351 | public function setKeepAspect($keepAspect) { |
||
| 356 | |||
| 357 | /** |
||
| 358 | * Makes sure we were given a file to preview and that it exists in the filesystem |
||
| 359 | * |
||
| 360 | * @return bool |
||
| 361 | */ |
||
| 362 | public function isFileValid() { |
||
| 378 | |||
| 379 | /** |
||
| 380 | * Deletes the preview of a file with specific width and height |
||
| 381 | * |
||
| 382 | * This should never delete the max preview, use deleteAllPreviews() instead |
||
| 383 | * |
||
| 384 | * @return bool |
||
| 385 | */ |
||
| 386 | public function deletePreview() { |
||
| 397 | |||
| 398 | /** |
||
| 399 | * Deletes all previews of a file |
||
| 400 | */ |
||
| 401 | public function deleteAllPreviews() { |
||
| 425 | |||
| 426 | /** |
||
| 427 | * Checks if a preview matching the asked dimensions or a bigger version is already cached |
||
| 428 | * |
||
| 429 | * * We first retrieve the size of the max preview since this is what we be used to create |
||
| 430 | * all our preview. If it doesn't exist we return false, so that it can be generated |
||
| 431 | * * Using the dimensions of the max preview, we calculate what the size of the new |
||
| 432 | * thumbnail should be |
||
| 433 | * * And finally, we look for a suitable candidate in the cache |
||
| 434 | * |
||
| 435 | * @return string|false path to the cached preview if it exists or false |
||
| 436 | */ |
||
| 437 | public function isCached() { |
||
| 498 | |||
| 499 | /** |
||
| 500 | * Returns the dimensions of the max preview |
||
| 501 | * |
||
| 502 | * @param FileInfo[] $allThumbnails the list of all our cached thumbnails |
||
| 503 | * |
||
| 504 | * @return int[] |
||
| 505 | */ |
||
| 506 | private function getMaxPreviewSize($allThumbnails) { |
||
| 520 | |||
| 521 | /** |
||
| 522 | * Check if a specific thumbnail size is cached |
||
| 523 | * |
||
| 524 | * @param FileInfo[] $allThumbnails the list of all our cached thumbnails |
||
| 525 | * @param string $name |
||
| 526 | * @return bool |
||
| 527 | */ |
||
| 528 | private function thumbnailSizeExists(array $allThumbnails, $name) { |
||
| 537 | |||
| 538 | /** |
||
| 539 | * Determines the size of the preview we should be looking for in the cache |
||
| 540 | * |
||
| 541 | * @return integer[] |
||
| 542 | */ |
||
| 543 | private function simulatePreviewDimensions() { |
||
| 556 | |||
| 557 | /** |
||
| 558 | * Resizes the boundaries to match the aspect ratio |
||
| 559 | * |
||
| 560 | * @param int $askedWidth |
||
| 561 | * @param int $askedHeight |
||
| 562 | * |
||
| 563 | * @param int $originalWidth |
||
| 564 | * @param int $originalHeight |
||
| 565 | * @return integer[] |
||
| 566 | */ |
||
| 567 | private function applyAspectRatio($askedWidth, $askedHeight, $originalWidth = 0, $originalHeight = 0) { |
||
| 589 | |||
| 590 | /** |
||
| 591 | * Resizes the boundaries to cover the area |
||
| 592 | * |
||
| 593 | * @param int $askedWidth |
||
| 594 | * @param int $askedHeight |
||
| 595 | * @param int $previewWidth |
||
| 596 | * @param int $previewHeight |
||
| 597 | * @return integer[] |
||
| 598 | */ |
||
| 599 | private function applyCover($askedWidth, $askedHeight, $previewWidth, $previewHeight) { |
||
| 615 | |||
| 616 | /** |
||
| 617 | * Makes sure an upscaled preview doesn't end up larger than the max dimensions defined in the |
||
| 618 | * config |
||
| 619 | * |
||
| 620 | * @param int $askedWidth |
||
| 621 | * @param int $askedHeight |
||
| 622 | * |
||
| 623 | * @return integer[] |
||
| 624 | */ |
||
| 625 | private function fixSize($askedWidth, $askedHeight) { |
||
| 633 | |||
| 634 | /** |
||
| 635 | * Checks if a bigger version of a file preview is cached and if not |
||
| 636 | * return the preview of max allowed dimensions |
||
| 637 | * |
||
| 638 | * @param FileInfo[] $allThumbnails the list of all our cached thumbnails |
||
| 639 | * |
||
| 640 | * @return string path to bigger thumbnail |
||
| 641 | */ |
||
| 642 | private function isCachedBigger($allThumbnails) { |
||
| 660 | |||
| 661 | /** |
||
| 662 | * Get possible bigger thumbnails of the given image with the proper aspect ratio |
||
| 663 | * |
||
| 664 | * @param FileInfo[] $allThumbnails the list of all our cached thumbnails |
||
| 665 | * |
||
| 666 | * @return string[] an array of paths to bigger thumbnails |
||
| 667 | */ |
||
| 668 | private function getPossibleThumbnails($allThumbnails) { |
||
| 692 | |||
| 693 | /** |
||
| 694 | * Looks at the preview filename from the cache and extracts the size of the preview |
||
| 695 | * |
||
| 696 | * @param string $name |
||
| 697 | * |
||
| 698 | * @return array<int,int,float> |
||
| 699 | */ |
||
| 700 | private function getDimensionsFromFilename($name) { |
||
| 708 | |||
| 709 | /** |
||
| 710 | * @param int $x |
||
| 711 | * @param int $y |
||
| 712 | * |
||
| 713 | * @return bool |
||
| 714 | */ |
||
| 715 | private function unscalable($x, $y) { |
||
| 734 | |||
| 735 | /** |
||
| 736 | * Returns a preview of a file |
||
| 737 | * |
||
| 738 | * The cache is searched first and if nothing usable was found then a preview is |
||
| 739 | * generated by one of the providers |
||
| 740 | * |
||
| 741 | * @return IImage |
||
| 742 | */ |
||
| 743 | public function getPreview() { |
||
| 770 | |||
| 771 | /** |
||
| 772 | * Sends the preview, including the headers to client which requested it |
||
| 773 | * |
||
| 774 | * @param null|string $mimeTypeForHeaders the media type to use when sending back the reply |
||
| 775 | * |
||
| 776 | * @throws NotFoundException |
||
| 777 | */ |
||
| 778 | public function showPreview($mimeTypeForHeaders = null) { |
||
| 796 | |||
| 797 | /** |
||
| 798 | * Retrieves the preview from the cache and resizes it if necessary |
||
| 799 | * |
||
| 800 | * @param string $cached the path to the cached preview |
||
| 801 | */ |
||
| 802 | private function getCachedPreview($cached) { |
||
| 828 | |||
| 829 | /** |
||
| 830 | * Resizes, crops, fixes orientation and stores in the cache |
||
| 831 | */ |
||
| 832 | private function resizeAndStore() { |
||
| 900 | |||
| 901 | /** |
||
| 902 | * Calculates the new dimensions of the preview |
||
| 903 | * |
||
| 904 | * The new dimensions can be larger or smaller than the ones of the preview we have to resize |
||
| 905 | * |
||
| 906 | * @param IImage $image |
||
| 907 | * @param int $askedWidth |
||
| 908 | * @param int $askedHeight |
||
| 909 | * @param int $previewWidth |
||
| 910 | * @param int $previewHeight |
||
| 911 | * |
||
| 912 | * @return int[] |
||
| 913 | */ |
||
| 914 | private function scale($image, $askedWidth, $askedHeight, $previewWidth, $previewHeight) { |
||
| 952 | |||
| 953 | /** |
||
| 954 | * Crops a preview which is larger than the dimensions we've received |
||
| 955 | * |
||
| 956 | * @param IImage $image |
||
| 957 | * @param int $askedWidth |
||
| 958 | * @param int $askedHeight |
||
| 959 | * @param int $previewWidth |
||
| 960 | * @param int $previewHeight |
||
| 961 | */ |
||
| 962 | private function crop($image, $askedWidth, $askedHeight, $previewWidth, $previewHeight = null) { |
||
| 970 | |||
| 971 | /** |
||
| 972 | * Crops an image if it's larger than the dimensions we've received and fills the empty space |
||
| 973 | * with a transparent background |
||
| 974 | * |
||
| 975 | * @param IImage $image |
||
| 976 | * @param int $askedWidth |
||
| 977 | * @param int $askedHeight |
||
| 978 | * @param int $previewWidth |
||
| 979 | * @param int $previewHeight |
||
| 980 | */ |
||
| 981 | private function cropAndFill($image, $askedWidth, $askedHeight, $previewWidth, $previewHeight) { |
||
| 1016 | |||
| 1017 | /** |
||
| 1018 | * Saves a preview in the cache to speed up future calls |
||
| 1019 | * |
||
| 1020 | * Do not nullify the preview as it might send the whole process in a loop |
||
| 1021 | * |
||
| 1022 | * @param int $previewWidth |
||
| 1023 | * @param int $previewHeight |
||
| 1024 | */ |
||
| 1025 | private function storePreview($previewWidth, $previewHeight) { |
||
| 1036 | |||
| 1037 | /** |
||
| 1038 | * Returns the path to a preview based on its dimensions and aspect |
||
| 1039 | * |
||
| 1040 | * @param int|null $maxX |
||
| 1041 | * @param int|null $maxY |
||
| 1042 | * |
||
| 1043 | * @return string |
||
| 1044 | */ |
||
| 1045 | private function buildCachePath($maxX = null, $maxY = null) { |
||
| 1070 | |||
| 1071 | /** |
||
| 1072 | * Returns the path to the folder where the previews are stored, identified by the fileId |
||
| 1073 | * |
||
| 1074 | * @return string |
||
| 1075 | */ |
||
| 1076 | private function getPreviewPath($fileId = null) { |
||
| 1086 | |||
| 1087 | /** |
||
| 1088 | * Asks the provider to send a preview of the file which respects the maximum dimensions |
||
| 1089 | * defined in the configuration and after saving it in the cache, it is then resized to the |
||
| 1090 | * asked dimensions |
||
| 1091 | * |
||
| 1092 | * This is only called once in order to generate a large PNG of dimensions defined in the |
||
| 1093 | * configuration file. We'll be able to quickly resize it later on. |
||
| 1094 | * We never upscale the original conversion as this will be done later by the resizing |
||
| 1095 | * operation |
||
| 1096 | * |
||
| 1097 | */ |
||
| 1098 | private function generatePreview() { |
||
| 1149 | |||
| 1150 | /** |
||
| 1151 | * Defines the media icon, for the media type of the original file, as the preview |
||
| 1152 | */ |
||
| 1153 | private function getMimeIcon() { |
||
| 1165 | |||
| 1166 | /** |
||
| 1167 | * Stores the max preview in the cache |
||
| 1168 | * |
||
| 1169 | * @param string $previewPath path to the preview |
||
| 1170 | */ |
||
| 1171 | private function storeMaxPreview($previewPath) { |
||
| 1196 | |||
| 1197 | /** |
||
| 1198 | * Limits a dimension to the maximum dimension provided as argument |
||
| 1199 | * |
||
| 1200 | * @param int $dim |
||
| 1201 | * @param int $maxDim |
||
| 1202 | * @param string $dimName |
||
| 1203 | * |
||
| 1204 | * @return integer |
||
| 1205 | */ |
||
| 1206 | private function limitMaxDim($dim, $maxDim, $dimName) { |
||
| 1218 | |||
| 1219 | /** |
||
| 1220 | * @param array $args |
||
| 1221 | */ |
||
| 1222 | public static function post_write($args) { |
||
| 1225 | |||
| 1226 | /** |
||
| 1227 | * @param array $args |
||
| 1228 | */ |
||
| 1229 | public static function prepare_delete_files($args) { |
||
| 1232 | |||
| 1233 | /** |
||
| 1234 | * @param array $args |
||
| 1235 | * @param string $prefix |
||
| 1236 | */ |
||
| 1237 | public static function prepare_delete(array $args, $prefix = '') { |
||
| 1256 | |||
| 1257 | /** |
||
| 1258 | * @param string $absolutePath |
||
| 1259 | * @param \OCP\Files\FileInfo $info |
||
| 1260 | */ |
||
| 1261 | private static function addPathToDeleteFileMapper($absolutePath, $info) { |
||
| 1264 | |||
| 1265 | /** |
||
| 1266 | * @param Folder $node |
||
| 1267 | * |
||
| 1268 | * @return array |
||
| 1269 | */ |
||
| 1270 | private static function getAllChildren($node) { |
||
| 1287 | |||
| 1288 | /** |
||
| 1289 | * @param array $args |
||
| 1290 | */ |
||
| 1291 | public static function post_delete_files($args) { |
||
| 1294 | |||
| 1295 | /** |
||
| 1296 | * @param array $args |
||
| 1297 | */ |
||
| 1298 | public static function post_delete_versions($args) { |
||
| 1301 | |||
| 1302 | /** |
||
| 1303 | * @param array $args |
||
| 1304 | * @param string $prefix |
||
| 1305 | */ |
||
| 1306 | public static function post_delete($args, $prefix = '') { |
||
| 1328 | } |
||
| 1329 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.
Either this assignment is in error or an instanceof check should be added for that assignment.