Completed
Pull Request — master (#31)
by Blizzz
13:09 queued 04:36
created

Shared   F

Complexity

Total Complexity 77

Size/Duplication

Total Lines 393
Duplicated Lines 9.16 %

Coupling/Cohesion

Components 1
Dependencies 17

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 36
loc 393
rs 2.1739
wmc 77
lcom 1
cbo 17

33 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 15 1
A init() 0 14 3
A isValid() 0 4 2
A getId() 0 3 1
A getSourceId() 0 3 1
B getPermissions() 0 16 5
A isCreatable() 0 3 1
A isReadable() 0 10 3
A isUpdatable() 0 3 1
A isDeletable() 0 3 1
A isSharable() 0 6 3
D fopen() 0 47 23
B rename() 0 17 6
A getMountPoint() 0 3 1
A setMountPoint() 0 3 1
A getShareType() 0 3 1
A getShareId() 0 3 1
A getSharedFrom() 0 3 1
A getShare() 0 3 1
A getItemType() 0 3 1
A getCache() 0 10 3
A getScanner() 0 6 2
A getPropagator() 0 6 2
A getOwner() 0 3 1
A unshareStorage() 0 4 1
A acquireLock() 10 10 2
A releaseLock() 10 10 2
A changeLock() 0 5 1
A getAvailability() 0 7 1
A setAvailability() 0 3 1
A getSourceStorage() 0 3 1
A file_get_contents() 8 8 1
A file_put_contents() 8 8 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

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 Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Shared 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 Shared, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @author Bart Visscher <[email protected]>
4
 * @author Björn Schießle <[email protected]>
5
 * @author Joas Schilling <[email protected]>
6
 * @author Michael Gapczynski <[email protected]>
7
 * @author Morris Jobke <[email protected]>
8
 * @author Robin Appelman <[email protected]>
9
 * @author Robin McCorkell <[email protected]>
10
 * @author Roeland Jago Douma <[email protected]>
11
 * @author scambra <[email protected]>
12
 * @author Vincent Petry <[email protected]>
13
 *
14
 * @copyright Copyright (c) 2016, ownCloud, Inc.
15
 * @license AGPL-3.0
16
 *
17
 * This code is free software: you can redistribute it and/or modify
18
 * it under the terms of the GNU Affero General Public License, version 3,
19
 * as published by the Free Software Foundation.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24
 * GNU Affero General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Affero General Public License, version 3,
27
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
28
 *
29
 */
30
31
namespace OC\Files\Storage;
32
33
use OC\Files\Filesystem;
34
use OC\Files\Cache\FailedCache;
35
use OCA\Files_Sharing\ISharedStorage;
36
use OCP\Constants;
37
use OCP\Files\Cache\ICacheEntry;
38
use OCP\Files\Storage\IStorage;
39
use OCP\Lock\ILockingProvider;
40
41
/**
42
 * Convert target path to source path and pass the function call to the correct storage provider
43
 */
44
class Shared extends \OC\Files\Storage\Wrapper\Jail implements ISharedStorage {
45
46
	private $share;   // the shared resource
47
48
	/** @var \OCP\Share\IShare */
49
	private $newShare;
50
51
	/**
52
	 * @var \OC\Files\View
53
	 */
54
	private $ownerView;
55
56
	private $initialized = false;
57
58
	/**
59
	 * @var ICacheEntry
60
	 */
61
	private $sourceRootInfo;
62
63
	/**
64
	 * @var IStorage
65
	 */
66
	private $sourceStorage;
67
68
	/** @var string */
69
	private $user;
70
71
	/**
72
	 * @var \OCP\ILogger
73
	 */
74
	private $logger;
75
76
	public function __construct($arguments) {
77
		$this->ownerView = $arguments['ownerView'];
78
		$this->logger = \OC::$server->getLogger();
79
		$this->newShare = $arguments['newShare'];
80
		$this->user = $arguments['user'];
81
82
		Filesystem::initMountPoints($this->newShare->getShareOwner());
83
		$sourcePath = $this->ownerView->getPath($this->newShare->getNodeId());
84
		list($storage, $internalPath) = $this->ownerView->resolvePath($sourcePath);
85
86
		parent::__construct([
87
			'storage' => $storage,
88
			'root' => $internalPath,
89
		]);
90
	}
91
92
	private function init() {
93
		if ($this->initialized) {
94
			return;
95
		}
96
		$this->initialized = true;
97
		try {
98
			Filesystem::initMountPoints($this->newShare->getShareOwner());
99
			$sourcePath = $this->ownerView->getPath($this->newShare->getNodeId());
100
			list($this->sourceStorage, $sourceInternalPath) = $this->ownerView->resolvePath($sourcePath);
101
			$this->sourceRootInfo = $this->sourceStorage->getCache()->get($sourceInternalPath);
102
		} catch (\Exception $e) {
103
			$this->logger->logException($e);
104
		}
105
	}
106
107
	private function isValid() {
108
		$this->init();
109
		return $this->sourceRootInfo && ($this->sourceRootInfo->getPermissions() & Constants::PERMISSION_SHARE) === Constants::PERMISSION_SHARE;
110
	}
111
112
	/**
113
	 * get id of the mount point
114
	 *
115
	 * @return string
116
	 */
117
	public function getId() {
118
		return 'shared::' . $this->getMountPoint();
119
	}
120
121
	/**
122
	 * get file cache of the shared item source
123
	 *
124
	 * @return int
125
	 */
126
	public function getSourceId() {
127
		return $this->newShare->getNodeId();
128
	}
129
130
	/**
131
	 * Get the permissions granted for a shared file
132
	 *
133
	 * @param string $target Shared target file path
134
	 * @return int CRUDS permissions granted
135
	 */
136
	public function getPermissions($target = '') {
137
		if (!$this->isValid()) {
138
			return 0;
139
		}
140
		$permissions = $this->newShare->getPermissions();
141
		// part files and the mount point always have delete permissions
142
		if ($target === '' || pathinfo($target, PATHINFO_EXTENSION) === 'part') {
143
			$permissions |= \OCP\Constants::PERMISSION_DELETE;
144
		}
145
146
		if (\OCP\Util::isSharingDisabledForUser()) {
147
			$permissions &= ~\OCP\Constants::PERMISSION_SHARE;
148
		}
149
150
		return $permissions;
151
	}
152
153
	public function isCreatable($path) {
154
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_CREATE);
155
	}
156
157
	public function isReadable($path) {
158
		if (!$this->isValid()) {
159
			return false;
160
		}
161
		if (!$this->file_exists($path)) {
162
			return false;
163
		}
164
		list($storage, $internalPath) = $this->resolvePath($path);
165
		return $storage->isReadable($internalPath);
166
	}
167
168
	public function isUpdatable($path) {
169
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_UPDATE);
170
	}
171
172
	public function isDeletable($path) {
173
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_DELETE);
174
	}
175
176
	public function isSharable($path) {
177
		if (\OCP\Util::isSharingDisabledForUser() || !\OC\Share\Share::isResharingAllowed()) {
178
			return false;
179
		}
180
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_SHARE);
181
	}
182
183
	public function fopen($path, $mode) {
184
		if ($source = $this->getSourcePath($path)) {
185
			switch ($mode) {
186
				case 'r+':
187
				case 'rb+':
188
				case 'w+':
189
				case 'wb+':
190
				case 'x+':
191
				case 'xb+':
192
				case 'a+':
193
				case 'ab+':
194
				case 'w':
195
				case 'wb':
196
				case 'x':
197
				case 'xb':
198
				case 'a':
199
				case 'ab':
200
					$creatable = $this->isCreatable($path);
201
					$updatable = $this->isUpdatable($path);
202
					// if neither permissions given, no need to continue
203
					if (!$creatable && !$updatable) {
204
						return false;
205
					}
206
207
					$exists = $this->file_exists($path);
208
					// if a file exists, updatable permissions are required
209
					if ($exists && !$updatable) {
210
						return false;
211
					}
212
213
					// part file is allowed if !$creatable but the final file is $updatable
214
					if (pathinfo($path, PATHINFO_EXTENSION) !== 'part') {
215
						if (!$exists && !$creatable) {
216
							return false;
217
						}
218
					}
219
			}
220
			$info = array(
221
				'target' => $this->getMountPoint() . $path,
222
				'source' => $source,
223
				'mode' => $mode,
224
			);
225
			\OCP\Util::emitHook('\OC\Files\Storage\Shared', 'fopen', $info);
226
			return parent::fopen($path, $mode);
227
		}
228
		return false;
229
	}
230
231
	/**
232
	 * see http://php.net/manual/en/function.rename.php
233
	 *
234
	 * @param string $path1
235
	 * @param string $path2
236
	 * @return bool
237
	 */
238
	public function rename($path1, $path2) {
239
		$isPartFile = pathinfo($path1, PATHINFO_EXTENSION) === 'part';
240
		$targetExists = $this->file_exists($path2);
241
		$sameFodler = dirname($path1) === dirname($path2);
242
243
		if ($targetExists || ($sameFodler && !$isPartFile)) {
244
			if (!$this->isUpdatable('')) {
245
				return false;
246
			}
247
		} else {
248
			if (!$this->isCreatable('')) {
249
				return false;
250
			}
251
		}
252
253
		return parent::rename($path1, $path2);
254
	}
255
256
	/**
257
	 * return mount point of share, relative to data/user/files
258
	 *
259
	 * @return string
260
	 */
261
	public function getMountPoint() {
262
		return $this->newShare->getTarget();
263
	}
264
265
	/**
266
	 * @param string $path
267
	 */
268
	public function setMountPoint($path) {
269
		$this->newShare->setTarget($path);
270
	}
271
272
	/**
273
	 * @return int
274
	 */
275
	public function getShareType() {
276
		return $this->newShare->getShareType();
277
	}
278
279
	/**
280
	 * get share ID
281
	 *
282
	 * @return integer unique share ID
283
	 */
284
	public function getShareId() {
285
		return $this->newShare->getId();
286
	}
287
288
	/**
289
	 * get the user who shared the file
290
	 *
291
	 * @return string
292
	 */
293
	public function getSharedFrom() {
294
		return $this->newShare->getShareOwner();
295
	}
296
297
	/**
298
	 * @return \OCP\Share\IShare
299
	 */
300
	public function getShare() {
301
		return $this->newShare;
302
	}
303
304
	/**
305
	 * return share type, can be "file" or "folder"
306
	 *
307
	 * @return string
308
	 */
309
	public function getItemType() {
310
		return $this->newShare->getNodeType();
311
	}
312
313
	public function getCache($path = '', $storage = null) {
314
		$this->init();
315
		if (is_null($this->sourceStorage)) {
316
			return new FailedCache(false);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new \OC\Files\Cache\FailedCache(false); (OC\Files\Cache\FailedCache) is incompatible with the return type declared by the interface OC\Files\Storage\Storage::getCache of type OC\Files\Cache\Cache.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
317
		}
318
		if (!$storage) {
319
			$storage = $this;
320
		}
321
		return new \OCA\Files_Sharing\Cache($storage, $this->sourceStorage, $this->sourceRootInfo);
322
	}
323
324
	public function getScanner($path = '', $storage = null) {
325
		if (!$storage) {
326
			$storage = $this;
327
		}
328
		return new \OCA\Files_Sharing\Scanner($storage);
329
	}
330
331
	public function getPropagator($storage = null) {
332
		if (!$storage) {
333
			$storage = $this;
334
		}
335
		return new \OCA\Files_Sharing\SharedPropagator($storage, \OC::$server->getDatabaseConnection());
336
	}
337
338
	public function getOwner($path) {
339
		return $this->newShare->getShareOwner();
340
	}
341
342
	/**
343
	 * unshare complete storage, also the grouped shares
344
	 *
345
	 * @return bool
346
	 */
347
	public function unshareStorage() {
348
		\OC::$server->getShareManager()->deleteFromSelf($this->newShare, $this->user);
349
		return true;
350
	}
351
352
	/**
353
	 * @param string $path
354
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
355
	 * @param \OCP\Lock\ILockingProvider $provider
356
	 * @throws \OCP\Lock\LockedException
357
	 */
358 View Code Duplication
	public function acquireLock($path, $type, ILockingProvider $provider) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
359
		/** @var \OCP\Files\Storage $targetStorage */
360
		list($targetStorage, $targetInternalPath) = $this->resolvePath($path);
361
		$targetStorage->acquireLock($targetInternalPath, $type, $provider);
362
		// lock the parent folders of the owner when locking the share as recipient
363
		if ($path === '') {
364
			$sourcePath = $this->ownerView->getPath($this->newShare->getNodeId());
365
			$this->ownerView->lockFile(dirname($sourcePath), ILockingProvider::LOCK_SHARED, true);
366
		}
367
	}
368
369
	/**
370
	 * @param string $path
371
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
372
	 * @param \OCP\Lock\ILockingProvider $provider
373
	 */
374 View Code Duplication
	public function releaseLock($path, $type, ILockingProvider $provider) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
375
		/** @var \OCP\Files\Storage $targetStorage */
376
		list($targetStorage, $targetInternalPath) = $this->resolvePath($path);
377
		$targetStorage->releaseLock($targetInternalPath, $type, $provider);
378
		// unlock the parent folders of the owner when unlocking the share as recipient
379
		if ($path === '') {
380
			$sourcePath = $this->ownerView->getPath($this->newShare->getNodeId());
381
			$this->ownerView->unlockFile(dirname($sourcePath), ILockingProvider::LOCK_SHARED, true);
382
		}
383
	}
384
385
	/**
386
	 * @param string $path
387
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
388
	 * @param \OCP\Lock\ILockingProvider $provider
389
	 */
390
	public function changeLock($path, $type, ILockingProvider $provider) {
391
		/** @var \OCP\Files\Storage $targetStorage */
392
		list($targetStorage, $targetInternalPath) = $this->resolvePath($path);
393
		$targetStorage->changeLock($targetInternalPath, $type, $provider);
394
	}
395
396
	/**
397
	 * @return array [ available, last_checked ]
398
	 */
399
	public function getAvailability() {
400
		// shares do not participate in availability logic
401
		return [
402
			'available' => true,
403
			'last_checked' => 0
404
		];
405
	}
406
407
	/**
408
	 * @param bool $available
409
	 */
410
	public function setAvailability($available) {
411
		// shares do not participate in availability logic
412
	}
413
414
	public function getSourceStorage() {
415
		return $this->sourceStorage;
416
	}
417
418 View Code Duplication
	public function file_get_contents($path) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
419
		$info = [
420
			'target' => $this->getMountPoint() . '/' . $path,
421
			'source' => $this->getSourcePath($path),
422
		];
423
		\OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_get_contents', $info);
424
		return parent::file_get_contents($path);
425
	}
426
427 View Code Duplication
	public function file_put_contents($path, $data) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
428
		$info = [
429
			'target' => $this->getMountPoint() . '/' . $path,
430
			'source' => $this->getSourcePath($path),
431
		];
432
		\OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_put_contents', $info);
433
		return parent::file_put_contents($path, $data);
434
	}
435
436
}
437