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 DAV 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 DAV, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 59 | class DAV extends Common { |
||
| 60 | /** @var string */ |
||
| 61 | protected $password; |
||
| 62 | /** @var string */ |
||
| 63 | protected $user; |
||
| 64 | /** @var string */ |
||
| 65 | protected $authType; |
||
| 66 | /** @var string */ |
||
| 67 | protected $host; |
||
| 68 | /** @var bool */ |
||
| 69 | protected $secure; |
||
| 70 | /** @var string */ |
||
| 71 | protected $root; |
||
| 72 | /** @var string */ |
||
| 73 | protected $certPath; |
||
| 74 | /** @var bool */ |
||
| 75 | protected $ready; |
||
| 76 | /** @var Client */ |
||
| 77 | protected $client; |
||
| 78 | /** @var ArrayCache */ |
||
| 79 | protected $statCache; |
||
| 80 | /** @var \OCP\Http\Client\IClientService */ |
||
| 81 | protected $httpClientService; |
||
| 82 | |||
| 83 | /** |
||
| 84 | * @param array $params |
||
| 85 | * @throws \Exception |
||
| 86 | */ |
||
| 87 | public function __construct($params) { |
||
| 88 | $this->statCache = new ArrayCache(); |
||
| 89 | $this->httpClientService = \OC::$server->getHTTPClientService(); |
||
| 90 | if (isset($params['host']) && isset($params['user']) && isset($params['password'])) { |
||
| 91 | $host = $params['host']; |
||
| 92 | //remove leading http[s], will be generated in createBaseUri() |
||
| 93 | if (substr($host, 0, 8) == "https://") $host = substr($host, 8); |
||
| 94 | else if (substr($host, 0, 7) == "http://") $host = substr($host, 7); |
||
| 95 | $this->host = $host; |
||
| 96 | $this->user = $params['user']; |
||
| 97 | $this->password = $params['password']; |
||
| 98 | if (isset($params['authType'])) { |
||
| 99 | $this->authType = $params['authType']; |
||
| 100 | } |
||
| 101 | if (isset($params['secure'])) { |
||
| 102 | if (is_string($params['secure'])) { |
||
| 103 | $this->secure = ($params['secure'] === 'true'); |
||
| 104 | } else { |
||
| 105 | $this->secure = (bool)$params['secure']; |
||
| 106 | } |
||
| 107 | } else { |
||
| 108 | $this->secure = false; |
||
| 109 | } |
||
| 110 | if ($this->secure === true) { |
||
| 111 | // inject mock for testing |
||
| 112 | $certManager = \OC::$server->getCertificateManager(); |
||
| 113 | if (is_null($certManager)) { //no user |
||
| 114 | $certManager = \OC::$server->getCertificateManager(null); |
||
| 115 | } |
||
| 116 | $certPath = $certManager->getAbsoluteBundlePath(); |
||
| 117 | if (file_exists($certPath)) { |
||
| 118 | $this->certPath = $certPath; |
||
| 119 | } |
||
| 120 | } |
||
| 121 | $this->root = $params['root'] ?? '/'; |
||
| 122 | $this->root = '/' . ltrim($this->root, '/'); |
||
| 123 | $this->root = rtrim($this->root, '/') . '/'; |
||
| 124 | } else { |
||
| 125 | throw new \Exception('Invalid webdav storage configuration'); |
||
| 126 | } |
||
| 127 | } |
||
| 128 | |||
| 129 | protected function init() { |
||
| 130 | if ($this->ready) { |
||
| 131 | return; |
||
| 132 | } |
||
| 133 | $this->ready = true; |
||
| 134 | |||
| 135 | $settings = [ |
||
| 136 | 'baseUri' => $this->createBaseUri(), |
||
| 137 | 'userName' => $this->user, |
||
| 138 | 'password' => $this->password, |
||
| 139 | ]; |
||
| 140 | if (isset($this->authType)) { |
||
| 141 | $settings['authType'] = $this->authType; |
||
| 142 | } |
||
| 143 | |||
| 144 | $proxy = \OC::$server->getConfig()->getSystemValue('proxy', ''); |
||
| 145 | if ($proxy !== '') { |
||
| 146 | $settings['proxy'] = $proxy; |
||
| 147 | } |
||
| 148 | |||
| 149 | $this->client = new Client($settings); |
||
| 150 | $this->client->setThrowExceptions(true); |
||
| 151 | if ($this->secure === true && $this->certPath) { |
||
| 152 | $this->client->addCurlSetting(CURLOPT_CAINFO, $this->certPath); |
||
| 153 | } |
||
| 154 | } |
||
| 155 | |||
| 156 | /** |
||
| 157 | * Clear the stat cache |
||
| 158 | */ |
||
| 159 | public function clearStatCache() { |
||
| 162 | |||
| 163 | /** {@inheritdoc} */ |
||
| 164 | public function getId() { |
||
| 167 | |||
| 168 | /** {@inheritdoc} */ |
||
| 169 | public function createBaseUri() { |
||
| 177 | |||
| 178 | /** {@inheritdoc} */ |
||
| 179 | public function mkdir($path) { |
||
| 188 | |||
| 189 | /** {@inheritdoc} */ |
||
| 190 | View Code Duplication | public function rmdir($path) { |
|
| 200 | |||
| 201 | /** {@inheritdoc} */ |
||
| 202 | public function opendir($path) { |
||
| 203 | $this->init(); |
||
| 204 | $path = $this->cleanPath($path); |
||
| 205 | try { |
||
| 206 | $response = $this->client->propFind( |
||
| 207 | $this->encodePath($path), |
||
| 208 | ['{DAV:}href'], |
||
| 209 | 1 |
||
| 210 | ); |
||
| 211 | if ($response === false) { |
||
| 212 | return false; |
||
| 213 | } |
||
| 214 | $content = []; |
||
| 215 | $files = array_keys($response); |
||
| 216 | array_shift($files); //the first entry is the current directory |
||
| 217 | |||
| 218 | if (!$this->statCache->hasKey($path)) { |
||
| 219 | $this->statCache->set($path, true); |
||
| 220 | } |
||
| 221 | foreach ($files as $file) { |
||
| 222 | $file = urldecode($file); |
||
| 223 | // do not store the real entry, we might not have all properties |
||
| 224 | if (!$this->statCache->hasKey($path)) { |
||
| 225 | $this->statCache->set($file, true); |
||
| 226 | } |
||
| 227 | $file = basename($file); |
||
| 228 | $content[] = $file; |
||
| 229 | } |
||
| 230 | return IteratorDirectory::wrap($content); |
||
| 231 | } catch (\Exception $e) { |
||
| 232 | $this->convertException($e, $path); |
||
| 233 | } |
||
| 234 | return false; |
||
| 235 | } |
||
| 236 | |||
| 237 | /** |
||
| 238 | * Propfind call with cache handling. |
||
| 239 | * |
||
| 240 | * First checks if information is cached. |
||
| 241 | * If not, request it from the server then store to cache. |
||
| 242 | * |
||
| 243 | * @param string $path path to propfind |
||
| 244 | * |
||
| 245 | * @return array|boolean propfind response or false if the entry was not found |
||
| 246 | * |
||
| 247 | * @throws ClientHttpException |
||
| 248 | */ |
||
| 249 | protected function propfind($path) { |
||
| 250 | $path = $this->cleanPath($path); |
||
| 251 | $cachedResponse = $this->statCache->get($path); |
||
| 252 | // we either don't know it, or we know it exists but need more details |
||
| 253 | if (is_null($cachedResponse) || $cachedResponse === true) { |
||
| 254 | $this->init(); |
||
| 255 | try { |
||
| 256 | $response = $this->client->propFind( |
||
| 257 | $this->encodePath($path), |
||
| 258 | array( |
||
| 259 | '{DAV:}getlastmodified', |
||
| 260 | '{DAV:}getcontentlength', |
||
| 261 | '{DAV:}getcontenttype', |
||
| 262 | '{http://owncloud.org/ns}permissions', |
||
| 263 | '{http://open-collaboration-services.org/ns}share-permissions', |
||
| 264 | '{DAV:}resourcetype', |
||
| 265 | '{DAV:}getetag', |
||
| 266 | ) |
||
| 267 | ); |
||
| 268 | $this->statCache->set($path, $response); |
||
| 269 | } catch (ClientHttpException $e) { |
||
|
|
|||
| 270 | View Code Duplication | if ($e->getHttpStatus() === 404) { |
|
| 271 | $this->statCache->clear($path . '/'); |
||
| 272 | $this->statCache->set($path, false); |
||
| 273 | return false; |
||
| 274 | } |
||
| 275 | $this->convertException($e, $path); |
||
| 276 | } catch (\Exception $e) { |
||
| 277 | $this->convertException($e, $path); |
||
| 278 | } |
||
| 279 | } else { |
||
| 280 | $response = $cachedResponse; |
||
| 281 | } |
||
| 282 | return $response; |
||
| 283 | } |
||
| 284 | |||
| 285 | /** {@inheritdoc} */ |
||
| 286 | public function filetype($path) { |
||
| 287 | try { |
||
| 288 | $response = $this->propfind($path); |
||
| 289 | if ($response === false) { |
||
| 290 | return false; |
||
| 291 | } |
||
| 292 | $responseType = []; |
||
| 293 | if (isset($response["{DAV:}resourcetype"])) { |
||
| 294 | /** @var ResourceType[] $response */ |
||
| 295 | $responseType = $response["{DAV:}resourcetype"]->getValue(); |
||
| 296 | } |
||
| 297 | return (count($responseType) > 0 and $responseType[0] == "{DAV:}collection") ? 'dir' : 'file'; |
||
| 298 | } catch (\Exception $e) { |
||
| 299 | $this->convertException($e, $path); |
||
| 300 | } |
||
| 301 | return false; |
||
| 302 | } |
||
| 303 | |||
| 304 | /** {@inheritdoc} */ |
||
| 305 | public function file_exists($path) { |
||
| 306 | try { |
||
| 307 | $path = $this->cleanPath($path); |
||
| 308 | $cachedState = $this->statCache->get($path); |
||
| 309 | if ($cachedState === false) { |
||
| 310 | // we know the file doesn't exist |
||
| 311 | return false; |
||
| 312 | } else if (!is_null($cachedState)) { |
||
| 313 | return true; |
||
| 314 | } |
||
| 315 | // need to get from server |
||
| 316 | return ($this->propfind($path) !== false); |
||
| 317 | } catch (\Exception $e) { |
||
| 318 | $this->convertException($e, $path); |
||
| 319 | } |
||
| 320 | return false; |
||
| 321 | } |
||
| 322 | |||
| 323 | /** {@inheritdoc} */ |
||
| 324 | View Code Duplication | public function unlink($path) { |
|
| 325 | $this->init(); |
||
| 326 | $path = $this->cleanPath($path); |
||
| 327 | $result = $this->simpleResponse('DELETE', $path, null, 204); |
||
| 328 | $this->statCache->clear($path . '/'); |
||
| 329 | $this->statCache->remove($path); |
||
| 330 | return $result; |
||
| 331 | } |
||
| 332 | |||
| 333 | /** {@inheritdoc} */ |
||
| 334 | public function fopen($path, $mode) { |
||
| 335 | $this->init(); |
||
| 336 | $path = $this->cleanPath($path); |
||
| 337 | switch ($mode) { |
||
| 338 | case 'r': |
||
| 339 | case 'rb': |
||
| 340 | try { |
||
| 341 | $response = $this->httpClientService |
||
| 342 | ->newClient() |
||
| 343 | ->get($this->createBaseUri() . $this->encodePath($path), [ |
||
| 344 | 'auth' => [$this->user, $this->password], |
||
| 345 | 'stream' => true |
||
| 346 | ]); |
||
| 347 | } catch (RequestException $e) { |
||
| 348 | if ($e->getResponse() instanceof ResponseInterface |
||
| 349 | && $e->getResponse()->getStatusCode() === 404) { |
||
| 350 | return false; |
||
| 351 | } else { |
||
| 352 | throw $e; |
||
| 353 | } |
||
| 354 | } |
||
| 355 | |||
| 356 | if ($response->getStatusCode() !== Http::STATUS_OK) { |
||
| 357 | if ($response->getStatusCode() === Http::STATUS_LOCKED) { |
||
| 358 | throw new \OCP\Lock\LockedException($path); |
||
| 359 | } else { |
||
| 360 | Util::writeLog("webdav client", 'Guzzle get returned status code ' . $response->getStatusCode(), Util::ERROR); |
||
| 361 | } |
||
| 362 | } |
||
| 363 | |||
| 364 | return $response->getBody(); |
||
| 365 | case 'w': |
||
| 366 | case 'wb': |
||
| 367 | case 'a': |
||
| 368 | case 'ab': |
||
| 369 | case 'r+': |
||
| 370 | case 'w+': |
||
| 371 | case 'wb+': |
||
| 372 | case 'a+': |
||
| 373 | case 'x': |
||
| 374 | case 'x+': |
||
| 375 | case 'c': |
||
| 376 | case 'c+': |
||
| 377 | //emulate these |
||
| 378 | $tempManager = \OC::$server->getTempManager(); |
||
| 379 | if (strrpos($path, '.') !== false) { |
||
| 380 | $ext = substr($path, strrpos($path, '.')); |
||
| 381 | } else { |
||
| 382 | $ext = ''; |
||
| 383 | } |
||
| 384 | if ($this->file_exists($path)) { |
||
| 385 | if (!$this->isUpdatable($path)) { |
||
| 386 | return false; |
||
| 387 | } |
||
| 388 | if ($mode === 'w' or $mode === 'w+') { |
||
| 389 | $tmpFile = $tempManager->getTemporaryFile($ext); |
||
| 390 | } else { |
||
| 391 | $tmpFile = $this->getCachedFile($path); |
||
| 392 | } |
||
| 393 | } else { |
||
| 394 | if (!$this->isCreatable(dirname($path))) { |
||
| 395 | return false; |
||
| 396 | } |
||
| 397 | $tmpFile = $tempManager->getTemporaryFile($ext); |
||
| 398 | } |
||
| 399 | $handle = fopen($tmpFile, $mode); |
||
| 400 | return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) { |
||
| 401 | $this->writeBack($tmpFile, $path); |
||
| 402 | }); |
||
| 403 | } |
||
| 404 | } |
||
| 405 | |||
| 406 | /** |
||
| 407 | * @param string $tmpFile |
||
| 408 | */ |
||
| 409 | public function writeBack($tmpFile, $path) { |
||
| 410 | $this->uploadFile($tmpFile, $path); |
||
| 411 | unlink($tmpFile); |
||
| 412 | } |
||
| 413 | |||
| 414 | /** {@inheritdoc} */ |
||
| 415 | public function free_space($path) { |
||
| 416 | $this->init(); |
||
| 417 | $path = $this->cleanPath($path); |
||
| 418 | try { |
||
| 419 | // TODO: cacheable ? |
||
| 420 | $response = $this->client->propfind($this->encodePath($path), ['{DAV:}quota-available-bytes']); |
||
| 421 | if ($response === false) { |
||
| 422 | return FileInfo::SPACE_UNKNOWN; |
||
| 423 | } |
||
| 424 | if (isset($response['{DAV:}quota-available-bytes'])) { |
||
| 425 | return (int)$response['{DAV:}quota-available-bytes']; |
||
| 426 | } else { |
||
| 427 | return FileInfo::SPACE_UNKNOWN; |
||
| 428 | } |
||
| 429 | } catch (\Exception $e) { |
||
| 430 | return FileInfo::SPACE_UNKNOWN; |
||
| 431 | } |
||
| 432 | } |
||
| 433 | |||
| 434 | /** {@inheritdoc} */ |
||
| 435 | public function touch($path, $mtime = null) { |
||
| 436 | $this->init(); |
||
| 437 | if (is_null($mtime)) { |
||
| 438 | $mtime = time(); |
||
| 439 | } |
||
| 440 | $path = $this->cleanPath($path); |
||
| 441 | |||
| 442 | // if file exists, update the mtime, else create a new empty file |
||
| 443 | if ($this->file_exists($path)) { |
||
| 444 | try { |
||
| 445 | $this->statCache->remove($path); |
||
| 446 | $this->client->proppatch($this->encodePath($path), ['{DAV:}lastmodified' => $mtime]); |
||
| 447 | // non-owncloud clients might not have accepted the property, need to recheck it |
||
| 448 | $response = $this->client->propfind($this->encodePath($path), ['{DAV:}getlastmodified'], 0); |
||
| 449 | if ($response === false) { |
||
| 450 | return false; |
||
| 451 | } |
||
| 452 | if (isset($response['{DAV:}getlastmodified'])) { |
||
| 453 | $remoteMtime = strtotime($response['{DAV:}getlastmodified']); |
||
| 454 | if ($remoteMtime !== $mtime) { |
||
| 455 | // server has not accepted the mtime |
||
| 456 | return false; |
||
| 457 | } |
||
| 458 | } |
||
| 459 | } catch (ClientHttpException $e) { |
||
| 460 | if ($e->getHttpStatus() === 501) { |
||
| 461 | return false; |
||
| 462 | } |
||
| 463 | $this->convertException($e, $path); |
||
| 464 | return false; |
||
| 465 | } catch (\Exception $e) { |
||
| 466 | $this->convertException($e, $path); |
||
| 467 | return false; |
||
| 468 | } |
||
| 469 | } else { |
||
| 470 | $this->file_put_contents($path, ''); |
||
| 471 | } |
||
| 472 | return true; |
||
| 473 | } |
||
| 474 | |||
| 475 | /** |
||
| 476 | * @param string $path |
||
| 477 | * @param string $data |
||
| 478 | * @return int |
||
| 479 | */ |
||
| 480 | public function file_put_contents($path, $data) { |
||
| 486 | |||
| 487 | /** |
||
| 488 | * @param string $path |
||
| 489 | * @param string $target |
||
| 490 | */ |
||
| 491 | protected function uploadFile($path, $target) { |
||
| 508 | |||
| 509 | /** {@inheritdoc} */ |
||
| 510 | public function rename($path1, $path2) { |
||
| 540 | |||
| 541 | /** {@inheritdoc} */ |
||
| 542 | public function copy($path1, $path2) { |
||
| 569 | |||
| 570 | /** {@inheritdoc} */ |
||
| 571 | public function stat($path) { |
||
| 572 | try { |
||
| 573 | $response = $this->propfind($path); |
||
| 574 | if (!$response) { |
||
| 575 | return false; |
||
| 576 | } |
||
| 577 | return [ |
||
| 578 | 'mtime' => strtotime($response['{DAV:}getlastmodified']), |
||
| 579 | 'size' => (int)isset($response['{DAV:}getcontentlength']) ? $response['{DAV:}getcontentlength'] : 0, |
||
| 580 | ]; |
||
| 586 | |||
| 587 | /** {@inheritdoc} */ |
||
| 588 | public function getMimeType($path) { |
||
| 596 | |||
| 597 | public function getMimeTypeFromRemote($path) { |
||
| 620 | |||
| 621 | /** |
||
| 622 | * @param string $path |
||
| 623 | * @return string |
||
| 624 | */ |
||
| 625 | public function cleanPath($path) { |
||
| 633 | |||
| 634 | /** |
||
| 635 | * URL encodes the given path but keeps the slashes |
||
| 636 | * |
||
| 637 | * @param string $path to encode |
||
| 638 | * @return string encoded path |
||
| 639 | */ |
||
| 640 | protected function encodePath($path) { |
||
| 644 | |||
| 645 | /** |
||
| 646 | * @param string $method |
||
| 647 | * @param string $path |
||
| 648 | * @param string|resource|null $body |
||
| 649 | * @param int $expected |
||
| 650 | * @return bool |
||
| 651 | * @throws StorageInvalidException |
||
| 652 | * @throws StorageNotAvailableException |
||
| 653 | */ |
||
| 654 | protected function simpleResponse($method, $path, $body, $expected) { |
||
| 672 | |||
| 673 | /** |
||
| 674 | * check if curl is installed |
||
| 675 | */ |
||
| 676 | public static function checkDependencies() { |
||
| 679 | |||
| 680 | /** {@inheritdoc} */ |
||
| 681 | public function isUpdatable($path) { |
||
| 684 | |||
| 685 | /** {@inheritdoc} */ |
||
| 686 | public function isCreatable($path) { |
||
| 689 | |||
| 690 | /** {@inheritdoc} */ |
||
| 691 | public function isSharable($path) { |
||
| 694 | |||
| 695 | /** {@inheritdoc} */ |
||
| 696 | public function isDeletable($path) { |
||
| 699 | |||
| 700 | /** {@inheritdoc} */ |
||
| 701 | public function getPermissions($path) { |
||
| 718 | |||
| 719 | /** {@inheritdoc} */ |
||
| 720 | public function getETag($path) { |
||
| 732 | |||
| 733 | /** |
||
| 734 | * @param string $permissionsString |
||
| 735 | * @return int |
||
| 736 | */ |
||
| 737 | protected function parsePermissions($permissionsString) { |
||
| 754 | |||
| 755 | /** |
||
| 756 | * check if a file or folder has been updated since $time |
||
| 757 | * |
||
| 758 | * @param string $path |
||
| 759 | * @param int $time |
||
| 760 | * @throws \OCP\Files\StorageNotAvailableException |
||
| 761 | * @return bool |
||
| 762 | */ |
||
| 763 | public function hasUpdated($path, $time) { |
||
| 813 | |||
| 814 | /** |
||
| 815 | * Interpret the given exception and decide whether it is due to an |
||
| 816 | * unavailable storage, invalid storage or other. |
||
| 817 | * This will either throw StorageInvalidException, StorageNotAvailableException |
||
| 818 | * or do nothing. |
||
| 819 | * |
||
| 820 | * @param Exception $e sabre exception |
||
| 821 | * @param string $path optional path from the operation |
||
| 822 | * |
||
| 823 | * @throws StorageInvalidException if the storage is invalid, for example |
||
| 824 | * when the authentication expired or is invalid |
||
| 825 | * @throws StorageNotAvailableException if the storage is not available, |
||
| 826 | * which might be temporary |
||
| 827 | */ |
||
| 828 | protected function convertException(Exception $e, $path = '') { |
||
| 856 | } |
||
| 857 | |||
| 858 |
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.