Completed
Push — master ( 00c767...680d7f )
by Joas
15:12 queued 01:14
created

Manager   B

Complexity

Total Complexity 33

Size/Duplication

Total Lines 432
Duplicated Lines 2.31 %

Coupling/Cohesion

Components 1
Dependencies 16

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 10
loc 432
rs 7.9538
wmc 33
lcom 1
cbo 16

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 15 1
B addShare() 0 57 5
A getShare() 0 9 2
B acceptShare() 0 26 2
A declineShare() 0 16 2
A processNotification() 0 7 1
A sendFeedbackToRemote() 0 23 3
A stripPath() 0 4 1
A getMount() 0 7 1
A mountShare() 0 5 1
A getMountManager() 0 3 1
A setMountPoint() 0 16 1
B removeShare() 4 33 3
A removeReShares() 0 17 1
A removeUserShares() 6 20 3
A getOpenShares() 0 3 1
A getAcceptedShares() 0 3 1
A getShares() 0 16 3

How to fix   Duplicated Code   

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:

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Björn Schießle <[email protected]>
6
 * @author Joas Schilling <[email protected]>
7
 * @author Jörn Friedrich Dreyer <[email protected]>
8
 * @author Lukas Reschke <[email protected]>
9
 * @author Morris Jobke <[email protected]>
10
 * @author Robin Appelman <[email protected]>
11
 * @author Roeland Jago Douma <[email protected]>
12
 * @author Stefan Weil <[email protected]>
13
 *
14
 * @license AGPL-3.0
15
 *
16
 * This code is free software: you can redistribute it and/or modify
17
 * it under the terms of the GNU Affero General Public License, version 3,
18
 * as published by the Free Software Foundation.
19
 *
20
 * This program is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23
 * GNU Affero General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU Affero General Public License, version 3,
26
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
27
 *
28
 */
29
30
namespace OCA\Files_Sharing\External;
31
32
use OC\Files\Filesystem;
33
use OCA\FederatedFileSharing\DiscoveryManager;
34
use OCP\Files;
35
use OCP\Http\Client\IClientService;
36
use OCP\Notification\IManager;
37
38
class Manager {
39
	const STORAGE = '\OCA\Files_Sharing\External\Storage';
40
41
	/**
42
	 * @var string
43
	 */
44
	private $uid;
45
46
	/**
47
	 * @var \OCP\IDBConnection
48
	 */
49
	private $connection;
50
51
	/**
52
	 * @var \OC\Files\Mount\Manager
53
	 */
54
	private $mountManager;
55
56
	/**
57
	 * @var \OCP\Files\Storage\IStorageFactory
58
	 */
59
	private $storageLoader;
60
61
	/**
62
	 * @var IClientService
63
	 */
64
	private $clientService;
65
66
	/**
67
	 * @var IManager
68
	 */
69
	private $notificationManager;
70
	/** @var DiscoveryManager */
71
	private $discoveryManager;
72
73
	/**
74
	 * @param \OCP\IDBConnection $connection
75
	 * @param \OC\Files\Mount\Manager $mountManager
76
	 * @param \OCP\Files\Storage\IStorageFactory $storageLoader
77
	 * @param IClientService $clientService
78
	 * @param IManager $notificationManager
79
	 * @param DiscoveryManager $discoveryManager
80
	 * @param string $uid
81
	 */
82
	public function __construct(\OCP\IDBConnection $connection,
83
								\OC\Files\Mount\Manager $mountManager,
84
								\OCP\Files\Storage\IStorageFactory $storageLoader,
85
								IClientService $clientService,
86
								IManager $notificationManager,
87
								DiscoveryManager $discoveryManager,
88
								$uid) {
89
		$this->connection = $connection;
90
		$this->mountManager = $mountManager;
91
		$this->storageLoader = $storageLoader;
92
		$this->clientService = $clientService;
93
		$this->uid = $uid;
94
		$this->notificationManager = $notificationManager;
95
		$this->discoveryManager = $discoveryManager;
96
	}
97
98
	/**
99
	 * add new server-to-server share
100
	 *
101
	 * @param string $remote
102
	 * @param string $token
103
	 * @param string $password
104
	 * @param string $name
105
	 * @param string $owner
106
	 * @param boolean $accepted
107
	 * @param string $user
108
	 * @param int $remoteId
109
	 * @return Mount|null
110
	 */
111
	public function addShare($remote, $token, $password, $name, $owner, $accepted=false, $user = null, $remoteId = -1) {
112
113
		$user = $user ? $user : $this->uid;
114
		$accepted = $accepted ? 1 : 0;
115
		$name = Filesystem::normalizePath('/' . $name);
116
117
		if (!$accepted) {
118
			// To avoid conflicts with the mount point generation later,
119
			// we only use a temporary mount point name here. The real
120
			// mount point name will be generated when accepting the share,
121
			// using the original share item name.
122
			$tmpMountPointName = '{{TemporaryMountPointName#' . $name . '}}';
123
			$mountPoint = $tmpMountPointName;
124
			$hash = md5($tmpMountPointName);
125
			$data = [
126
				'remote'		=> $remote,
127
				'share_token'	=> $token,
128
				'password'		=> $password,
129
				'name'			=> $name,
130
				'owner'			=> $owner,
131
				'user'			=> $user,
132
				'mountpoint'	=> $mountPoint,
133
				'mountpoint_hash'	=> $hash,
134
				'accepted'		=> $accepted,
135
				'remote_id'		=> $remoteId,
136
			];
137
138
			$i = 1;
139
			while (!$this->connection->insertIfNotExist('*PREFIX*share_external', $data, ['user', 'mountpoint_hash'])) {
140
				// The external share already exists for the user
141
				$data['mountpoint'] = $tmpMountPointName . '-' . $i;
142
				$data['mountpoint_hash'] = md5($data['mountpoint']);
143
				$i++;
144
			}
145
			return null;
146
		}
147
148
		$mountPoint = Files::buildNotExistingFileName('/', $name);
149
		$mountPoint = Filesystem::normalizePath('/' . $mountPoint);
150
		$hash = md5($mountPoint);
151
152
		$query = $this->connection->prepare('
153
				INSERT INTO `*PREFIX*share_external`
154
					(`remote`, `share_token`, `password`, `name`, `owner`, `user`, `mountpoint`, `mountpoint_hash`, `accepted`, `remote_id`)
155
				VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
156
			');
157
		$query->execute(array($remote, $token, $password, $name, $owner, $user, $mountPoint, $hash, $accepted, $remoteId));
158
159
		$options = array(
160
			'remote'	=> $remote,
161
			'token'		=> $token,
162
			'password'	=> $password,
163
			'mountpoint'	=> $mountPoint,
164
			'owner'		=> $owner
165
		);
166
		return $this->mountShare($options);
167
	}
168
169
	/**
170
	 * get share
171
	 *
172
	 * @param int $id share id
173
	 * @return mixed share of false
174
	 */
175
	public function getShare($id) {
176
		$getShare = $this->connection->prepare('
177
			SELECT `id`, `remote`, `remote_id`, `share_token`, `name`, `owner`, `user`, `mountpoint`, `accepted`
178
			FROM  `*PREFIX*share_external`
179
			WHERE `id` = ? AND `user` = ?');
180
		$result = $getShare->execute(array($id, $this->uid));
181
182
		return $result ? $getShare->fetch() : false;
183
	}
184
185
	/**
186
	 * accept server-to-server share
187
	 *
188
	 * @param int $id
189
	 * @return bool True if the share could be accepted, false otherwise
190
	 */
191
	public function acceptShare($id) {
192
193
		$share = $this->getShare($id);
194
195
		if ($share) {
196
			$mountPoint = Files::buildNotExistingFileName('/', $share['name']);
197
			$mountPoint = Filesystem::normalizePath('/' . $mountPoint);
198
			$hash = md5($mountPoint);
199
200
			$acceptShare = $this->connection->prepare('
201
				UPDATE `*PREFIX*share_external`
202
				SET `accepted` = ?,
203
					`mountpoint` = ?,
204
					`mountpoint_hash` = ?
205
				WHERE `id` = ? AND `user` = ?');
206
			$acceptShare->execute(array(1, $mountPoint, $hash, $id, $this->uid));
207
			$this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'accept');
208
209
			\OC_Hook::emit('OCP\Share', 'federated_share_added', ['server' => $share['remote']]);
210
211
			$this->processNotification($id);
212
			return true;
213
		}
214
215
		return false;
216
	}
217
218
	/**
219
	 * decline server-to-server share
220
	 *
221
	 * @param int $id
222
	 * @return bool True if the share could be declined, false otherwise
223
	 */
224
	public function declineShare($id) {
225
226
		$share = $this->getShare($id);
227
228
		if ($share) {
229
			$removeShare = $this->connection->prepare('
230
				DELETE FROM `*PREFIX*share_external` WHERE `id` = ? AND `user` = ?');
231
			$removeShare->execute(array($id, $this->uid));
232
			$this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline');
233
234
			$this->processNotification($id);
235
			return true;
236
		}
237
238
		return false;
239
	}
240
241
	/**
242
	 * @param int $remoteShare
243
	 */
244
	public function processNotification($remoteShare) {
245
		$filter = $this->notificationManager->createNotification();
246
		$filter->setApp('files_sharing')
247
			->setUser($this->uid)
248
			->setObject('remote_share', (int) $remoteShare);
249
		$this->notificationManager->markProcessed($filter);
250
	}
251
252
	/**
253
	 * inform remote server whether server-to-server share was accepted/declined
254
	 *
255
	 * @param string $remote
256
	 * @param string $token
257
	 * @param int $remoteId Share id on the remote host
258
	 * @param string $feedback
259
	 * @return boolean
260
	 */
261
	private function sendFeedbackToRemote($remote, $token, $remoteId, $feedback) {
262
263
		$url = rtrim($remote, '/') . $this->discoveryManager->getShareEndpoint($remote) . '/' . $remoteId . '/' . $feedback . '?format=' . \OCP\Share::RESPONSE_FORMAT;
264
		$fields = array('token' => $token);
265
266
		$client = $this->clientService->newClient();
267
268
		try {
269
			$response = $client->post(
270
				$url,
271
				[
272
					'body' => $fields,
273
					'connect_timeout' => 10,
274
				]
275
			);
276
		} catch (\Exception $e) {
277
			return false;
278
		}
279
280
		$status = json_decode($response->getBody(), true);
281
282
		return ($status['ocs']['meta']['statuscode'] === 100 || $status['ocs']['meta']['statuscode'] === 200);
283
	}
284
285
	/**
286
	 * remove '/user/files' from the path and trailing slashes
287
	 *
288
	 * @param string $path
289
	 * @return string
290
	 */
291
	protected function stripPath($path) {
292
		$prefix = '/' . $this->uid . '/files';
293
		return rtrim(substr($path, strlen($prefix)), '/');
294
	}
295
296
	public function getMount($data) {
297
		$data['manager'] = $this;
298
		$mountPoint = '/' . $this->uid . '/files' . $data['mountpoint'];
299
		$data['mountpoint'] = $mountPoint;
300
		$data['certificateManager'] = \OC::$server->getCertificateManager($this->uid);
301
		return new Mount(self::STORAGE, $mountPoint, $data, $this, $this->storageLoader);
0 ignored issues
show
Documentation introduced by
$this->storageLoader is of type object<OCP\Files\Storage\IStorageFactory>, but the function expects a object<OC\Files\Storage\StorageFactory>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
302
	}
303
304
	/**
305
	 * @param array $data
306
	 * @return Mount
307
	 */
308
	protected function mountShare($data) {
309
		$mount = $this->getMount($data);
310
		$this->mountManager->addMount($mount);
311
		return $mount;
312
	}
313
314
	/**
315
	 * @return \OC\Files\Mount\Manager
316
	 */
317
	public function getMountManager() {
318
		return $this->mountManager;
319
	}
320
321
	/**
322
	 * @param string $source
323
	 * @param string $target
324
	 * @return bool
325
	 */
326
	public function setMountPoint($source, $target) {
327
		$source = $this->stripPath($source);
328
		$target = $this->stripPath($target);
329
		$sourceHash = md5($source);
330
		$targetHash = md5($target);
331
332
		$query = $this->connection->prepare('
333
			UPDATE `*PREFIX*share_external`
334
			SET `mountpoint` = ?, `mountpoint_hash` = ?
335
			WHERE `mountpoint_hash` = ?
336
			AND `user` = ?
337
		');
338
		$result = (bool)$query->execute(array($target, $targetHash, $sourceHash, $this->uid));
339
340
		return $result;
341
	}
342
343
	public function removeShare($mountPoint) {
344
345
		$mountPointObj = $this->mountManager->find($mountPoint);
346
		$id = $mountPointObj->getStorage()->getCache()->getId('');
347
348
		$mountPoint = $this->stripPath($mountPoint);
349
		$hash = md5($mountPoint);
350
351
		$getShare = $this->connection->prepare('
352
			SELECT `remote`, `share_token`, `remote_id`
353
			FROM  `*PREFIX*share_external`
354
			WHERE `mountpoint_hash` = ? AND `user` = ?');
355
		$result = $getShare->execute(array($hash, $this->uid));
356
357 View Code Duplication
		if ($result) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
358
			$share = $getShare->fetch();
359
			$this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline');
360
		}
361
		$getShare->closeCursor();
362
363
		$query = $this->connection->prepare('
364
			DELETE FROM `*PREFIX*share_external`
365
			WHERE `mountpoint_hash` = ?
366
			AND `user` = ?
367
		');
368
		$result = (bool)$query->execute(array($hash, $this->uid));
369
370
		if($result) {
371
			$this->removeReShares($id);
372
		}
373
374
		return $result;
375
	}
376
377
	/**
378
	 * remove re-shares from share table and mapping in the federated_reshares table
379
	 * 
380
	 * @param $mountPointId
381
	 */
382
	protected function removeReShares($mountPointId) {
383
		$selectQuery = $this->connection->getQueryBuilder();
384
		$query = $this->connection->getQueryBuilder();
385
		$selectQuery->select('id')->from('share')
386
			->where($selectQuery->expr()->eq('file_source', $query->createNamedParameter($mountPointId)));
387
		$select = $selectQuery->getSQL();
388
389
390
		$query->delete('federated_reshares')
391
			->where($query->expr()->in('share_id', $query->createFunction('(' . $select . ')')));
392
		$query->execute();
393
394
		$deleteReShares = $this->connection->getQueryBuilder();
395
		$deleteReShares->delete('share')
396
			->where($deleteReShares->expr()->eq('file_source', $deleteReShares->createNamedParameter($mountPointId)));
397
		$deleteReShares->execute();
398
	}
399
400
	/**
401
	 * remove all shares for user $uid if the user was deleted
402
	 *
403
	 * @param string $uid
404
	 * @return bool
405
	 */
406
	public function removeUserShares($uid) {
407
		$getShare = $this->connection->prepare('
408
			SELECT `remote`, `share_token`, `remote_id`
409
			FROM  `*PREFIX*share_external`
410
			WHERE `user` = ?');
411
		$result = $getShare->execute(array($uid));
412
413 View Code Duplication
		if ($result) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
414
			$shares = $getShare->fetchAll();
415
			foreach($shares as $share) {
416
				$this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline');
417
			}
418
		}
419
420
		$query = $this->connection->prepare('
421
			DELETE FROM `*PREFIX*share_external`
422
			WHERE `user` = ?
423
		');
424
		return (bool)$query->execute(array($uid));
425
	}
426
427
	/**
428
	 * return a list of shares which are not yet accepted by the user
429
	 *
430
	 * @return array list of open server-to-server shares
431
	 */
432
	public function getOpenShares() {
433
		return $this->getShares(false);
434
	}
435
436
	/**
437
	 * return a list of shares which are accepted by the user
438
	 *
439
	 * @return array list of accepted server-to-server shares
440
	 */
441
	public function getAcceptedShares() {
442
		return $this->getShares(true);
443
	}
444
445
	/**
446
	 * return a list of shares for the user
447
	 *
448
	 * @param bool|null $accepted True for accepted only,
449
	 *                            false for not accepted,
450
	 *                            null for all shares of the user
451
	 * @return array list of open server-to-server shares
452
	 */
453
	private function getShares($accepted) {
454
		$query = 'SELECT `id`, `remote`, `remote_id`, `share_token`, `name`, `owner`, `user`, `mountpoint`, `accepted`
455
		          FROM `*PREFIX*share_external` 
456
				  WHERE `user` = ?';
457
		$parameters = [$this->uid];
458
		if (!is_null($accepted)) {
459
			$query .= ' AND `accepted` = ?';
460
			$parameters[] = (int) $accepted;
461
		}
462
		$query .= ' ORDER BY `id` ASC';
463
464
		$shares = $this->connection->prepare($query);
465
		$result = $shares->execute($parameters);
466
467
		return $result ? $shares->fetchAll() : [];
468
	}
469
}
470