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 AmazonS3 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 AmazonS3, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 48 | class AmazonS3 extends \OC\Files\Storage\Common { |
||
| 49 | use S3ConnectionTrait; |
||
| 50 | use S3ObjectTrait; |
||
| 51 | |||
| 52 | public function needsPartFile() { |
||
| 53 | return false; |
||
| 54 | } |
||
| 55 | |||
| 56 | /** |
||
| 57 | * @var int in seconds |
||
| 58 | */ |
||
| 59 | private $rescanDelay = 10; |
||
| 60 | |||
| 61 | /** @var CappedMemoryCache|Result[] */ |
||
| 62 | private $objectCache; |
||
| 63 | |||
| 64 | public function __construct($parameters) { |
||
| 65 | parent::__construct($parameters); |
||
| 66 | $this->parseParams($parameters); |
||
| 67 | $this->objectCache = new CappedMemoryCache(); |
||
| 68 | } |
||
| 69 | |||
| 70 | /** |
||
| 71 | * @param string $path |
||
| 72 | * @return string correctly encoded path |
||
| 73 | */ |
||
| 74 | private function normalizePath($path) { |
||
| 75 | $path = trim($path, '/'); |
||
| 76 | |||
| 77 | if (!$path) { |
||
| 78 | $path = '.'; |
||
| 79 | } |
||
| 80 | |||
| 81 | return $path; |
||
| 82 | } |
||
| 83 | |||
| 84 | private function isRoot($path) { |
||
| 85 | return $path === '.'; |
||
| 86 | } |
||
| 87 | |||
| 88 | private function cleanKey($path) { |
||
| 89 | if ($this->isRoot($path)) { |
||
| 90 | return '/'; |
||
| 91 | } |
||
| 92 | return $path; |
||
| 93 | } |
||
| 94 | |||
| 95 | private function clearCache() { |
||
| 96 | $this->objectCache = new CappedMemoryCache(); |
||
| 97 | } |
||
| 98 | |||
| 99 | private function invalidateCache($key) { |
||
| 100 | unset($this->objectCache[$key]); |
||
| 101 | $keys = array_keys($this->objectCache->getData()); |
||
| 102 | $keyLength = strlen($key); |
||
| 103 | foreach ($keys as $existingKey) { |
||
| 104 | if (substr($existingKey, 0, $keyLength) === $key) { |
||
| 105 | unset($this->objectCache[$existingKey]); |
||
| 106 | } |
||
| 107 | } |
||
| 108 | } |
||
| 109 | |||
| 110 | /** |
||
| 111 | * @param $key |
||
| 112 | * @return Result|boolean |
||
| 113 | */ |
||
| 114 | private function headObject($key) { |
||
| 115 | if (!isset($this->objectCache[$key])) { |
||
| 116 | try { |
||
| 117 | $this->objectCache[$key] = $this->getConnection()->headObject(array( |
||
| 118 | 'Bucket' => $this->bucket, |
||
| 119 | 'Key' => $key |
||
| 120 | )); |
||
| 121 | } catch (S3Exception $e) { |
||
|
|
|||
| 122 | if ($e->getStatusCode() >= 500) { |
||
| 123 | throw $e; |
||
| 124 | } |
||
| 125 | $this->objectCache[$key] = false; |
||
| 126 | } |
||
| 127 | } |
||
| 128 | |||
| 129 | return $this->objectCache[$key]; |
||
| 130 | } |
||
| 131 | |||
| 132 | /** |
||
| 133 | * Updates old storage ids (v0.2.1 and older) that are based on key and secret to new ones based on the bucket name. |
||
| 134 | * TODO Do this in an update.php. requires iterating over all users and loading the mount.json from their home |
||
| 135 | * |
||
| 136 | * @param array $params |
||
| 137 | */ |
||
| 138 | public function updateLegacyId(array $params) { |
||
| 139 | $oldId = 'amazon::' . $params['key'] . md5($params['secret']); |
||
| 140 | |||
| 141 | // find by old id or bucket |
||
| 142 | $stmt = \OC::$server->getDatabaseConnection()->prepare( |
||
| 143 | 'SELECT `numeric_id`, `id` FROM `*PREFIX*storages` WHERE `id` IN (?, ?)' |
||
| 144 | ); |
||
| 145 | $stmt->execute(array($oldId, $this->id)); |
||
| 146 | while ($row = $stmt->fetch()) { |
||
| 147 | $storages[$row['id']] = $row['numeric_id']; |
||
| 148 | } |
||
| 149 | |||
| 150 | if (isset($storages[$this->id]) && isset($storages[$oldId])) { |
||
| 151 | // if both ids exist, delete the old storage and corresponding filecache entries |
||
| 152 | \OC\Files\Cache\Storage::remove($oldId); |
||
| 153 | } else if (isset($storages[$oldId])) { |
||
| 154 | // if only the old id exists do an update |
||
| 155 | $stmt = \OC::$server->getDatabaseConnection()->prepare( |
||
| 156 | 'UPDATE `*PREFIX*storages` SET `id` = ? WHERE `id` = ?' |
||
| 157 | ); |
||
| 158 | $stmt->execute(array($this->id, $oldId)); |
||
| 159 | } |
||
| 160 | // only the bucket based id may exist, do nothing |
||
| 161 | } |
||
| 162 | |||
| 163 | /** |
||
| 164 | * Remove a file or folder |
||
| 165 | * |
||
| 166 | * @param string $path |
||
| 167 | * @return bool |
||
| 168 | */ |
||
| 169 | protected function remove($path) { |
||
| 170 | // remember fileType to reduce http calls |
||
| 171 | $fileType = $this->filetype($path); |
||
| 172 | if ($fileType === 'dir') { |
||
| 173 | return $this->rmdir($path); |
||
| 174 | } else if ($fileType === 'file') { |
||
| 175 | return $this->unlink($path); |
||
| 176 | } else { |
||
| 177 | return false; |
||
| 178 | } |
||
| 179 | } |
||
| 180 | |||
| 181 | public function mkdir($path) { |
||
| 182 | $path = $this->normalizePath($path); |
||
| 183 | |||
| 184 | if ($this->is_dir($path)) { |
||
| 185 | return false; |
||
| 186 | } |
||
| 187 | |||
| 188 | try { |
||
| 189 | $this->getConnection()->putObject(array( |
||
| 190 | 'Bucket' => $this->bucket, |
||
| 191 | 'Key' => $path . '/', |
||
| 192 | 'Body' => '', |
||
| 193 | 'ContentType' => 'httpd/unix-directory' |
||
| 194 | )); |
||
| 195 | $this->testTimeout(); |
||
| 196 | } catch (S3Exception $e) { |
||
| 197 | \OC::$server->getLogger()->logException($e, ['app' => 'files_external']); |
||
| 198 | return false; |
||
| 199 | } |
||
| 200 | |||
| 201 | $this->invalidateCache($path); |
||
| 202 | |||
| 203 | return true; |
||
| 204 | } |
||
| 205 | |||
| 206 | public function file_exists($path) { |
||
| 209 | |||
| 210 | |||
| 211 | public function rmdir($path) { |
||
| 212 | $path = $this->normalizePath($path); |
||
| 213 | |||
| 214 | if ($this->isRoot($path)) { |
||
| 215 | return $this->clearBucket(); |
||
| 216 | } |
||
| 217 | |||
| 218 | if (!$this->file_exists($path)) { |
||
| 219 | return false; |
||
| 220 | } |
||
| 221 | |||
| 222 | $this->invalidateCache($path); |
||
| 223 | return $this->batchDelete($path); |
||
| 224 | } |
||
| 225 | |||
| 226 | protected function clearBucket() { |
||
| 236 | |||
| 237 | private function batchDelete($path = null) { |
||
| 238 | $params = array( |
||
| 239 | 'Bucket' => $this->bucket |
||
| 240 | ); |
||
| 241 | if ($path !== null) { |
||
| 242 | $params['Prefix'] = $path . '/'; |
||
| 243 | } |
||
| 244 | try { |
||
| 245 | $connection = $this->getConnection(); |
||
| 246 | // Since there are no real directories on S3, we need |
||
| 247 | // to delete all objects prefixed with the path. |
||
| 248 | do { |
||
| 269 | |||
| 270 | public function opendir($path) { |
||
| 271 | $path = $this->normalizePath($path); |
||
| 272 | |||
| 273 | if ($this->isRoot($path)) { |
||
| 274 | $path = ''; |
||
| 275 | } else { |
||
| 276 | $path .= '/'; |
||
| 277 | } |
||
| 278 | |||
| 279 | try { |
||
| 280 | $files = array(); |
||
| 281 | $results = $this->getConnection()->getPaginator('ListObjects', [ |
||
| 282 | 'Bucket' => $this->bucket, |
||
| 283 | 'Delimiter' => '/', |
||
| 284 | 'Prefix' => $path, |
||
| 285 | ]); |
||
| 286 | |||
| 287 | foreach ($results as $result) { |
||
| 288 | // sub folders |
||
| 289 | if (is_array($result['CommonPrefixes'])) { |
||
| 290 | foreach ($result['CommonPrefixes'] as $prefix) { |
||
| 291 | $files[] = substr(trim($prefix['Prefix'], '/'), strlen($path)); |
||
| 292 | } |
||
| 293 | } |
||
| 294 | if (is_array($result['Contents'])) { |
||
| 295 | foreach ($result['Contents'] as $object) { |
||
| 296 | if (isset($object['Key']) && $object['Key'] === $path) { |
||
| 297 | // it's the directory itself, skip |
||
| 298 | continue; |
||
| 299 | } |
||
| 300 | $file = basename( |
||
| 301 | isset($object['Key']) ? $object['Key'] : $object['Prefix'] |
||
| 302 | ); |
||
| 303 | $files[] = $file; |
||
| 304 | } |
||
| 305 | } |
||
| 306 | } |
||
| 307 | |||
| 308 | return IteratorDirectory::wrap($files); |
||
| 309 | } catch (S3Exception $e) { |
||
| 310 | \OC::$server->getLogger()->logException($e, ['app' => 'files_external']); |
||
| 311 | return false; |
||
| 312 | } |
||
| 313 | } |
||
| 314 | |||
| 315 | public function stat($path) { |
||
| 342 | |||
| 343 | public function is_dir($path) { |
||
| 352 | |||
| 353 | public function filetype($path) { |
||
| 374 | |||
| 375 | public function getPermissions($path) { |
||
| 382 | |||
| 383 | public function unlink($path) { |
||
| 400 | |||
| 401 | public function fopen($path, $mode) { |
||
| 402 | $path = $this->normalizePath($path); |
||
| 403 | |||
| 404 | switch ($mode) { |
||
| 405 | case 'r': |
||
| 406 | View Code Duplication | case 'rb': |
|
| 407 | try { |
||
| 408 | return $this->readObject($path); |
||
| 409 | } catch (S3Exception $e) { |
||
| 410 | \OC::$server->getLogger()->logException($e, ['app' => 'files_external']); |
||
| 411 | return false; |
||
| 412 | } |
||
| 413 | case 'w': |
||
| 414 | View Code Duplication | case 'wb': |
|
| 415 | $tmpFile = \OC::$server->getTempManager()->getTemporaryFile(); |
||
| 416 | |||
| 417 | $handle = fopen($tmpFile, 'w'); |
||
| 418 | return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) { |
||
| 419 | $this->writeBack($tmpFile, $path); |
||
| 420 | }); |
||
| 421 | case 'a': |
||
| 422 | case 'ab': |
||
| 423 | case 'r+': |
||
| 424 | case 'w+': |
||
| 425 | case 'wb+': |
||
| 426 | case 'a+': |
||
| 427 | case 'x': |
||
| 428 | case 'x+': |
||
| 429 | case 'c': |
||
| 430 | case 'c+': |
||
| 431 | if (strrpos($path, '.') !== false) { |
||
| 432 | $ext = substr($path, strrpos($path, '.')); |
||
| 433 | } else { |
||
| 434 | $ext = ''; |
||
| 435 | } |
||
| 436 | $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext); |
||
| 437 | if ($this->file_exists($path)) { |
||
| 438 | $source = $this->readObject($path); |
||
| 439 | file_put_contents($tmpFile, $source); |
||
| 440 | } |
||
| 441 | |||
| 442 | $handle = fopen($tmpFile, $mode); |
||
| 443 | return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) { |
||
| 444 | $this->writeBack($tmpFile, $path); |
||
| 445 | }); |
||
| 446 | } |
||
| 447 | return false; |
||
| 448 | } |
||
| 449 | |||
| 450 | public function touch($path, $mtime = null) { |
||
| 495 | |||
| 496 | public function copy($path1, $path2) { |
||
| 545 | |||
| 546 | public function rename($path1, $path2) { |
||
| 574 | |||
| 575 | public function test() { |
||
| 581 | |||
| 582 | public function getId() { |
||
| 585 | |||
| 586 | public function writeBack($tmpFile, $path) { |
||
| 600 | |||
| 601 | /** |
||
| 602 | * check if curl is installed |
||
| 603 | */ |
||
| 604 | public static function checkDependencies() { |
||
| 607 | |||
| 608 | } |
||
| 609 |
Scrutinizer analyzes your
composer.json/composer.lockfile if available to determine the classes, and functions that are defined by your dependencies.It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.