Completed
Pull Request — master (#32545)
by Tom
09:07
created

TransferRequestManager::newTransferRequest()   B

Complexity

Conditions 9
Paths 11

Size

Total Lines 59

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
nc 11
nop 3
dl 0
loc 59
rs 7.3389
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace OCA\Files\Service\TransferOwnership;
4
5
use OC\Command\CommandJob;
6
use OCP\AppFramework\Db\DoesNotExistException;
7
use OCP\AppFramework\Utility\ITimeFactory;
8
use OCP\BackgroundJob\IJobList;
9
use OCP\Files\Folder;
10
use OCP\Files\IRootFolder;
11
use OCP\Files\NotFoundException;
12
use OCP\Files\NotPermittedException;
13
use OCP\Files\Storage\IPersistentLockingStorage;
14
use OCP\IL10N;
15
use OCP\IURLGenerator;
16
use OCP\IUser;
17
use OCP\IUserManager;
18
use OCP\L10N\IFactory;
19
use OCP\Lock\Persistent\ILock;
20
use OCP\Notification\IManager;
21
use OCP\Notification\INotification;
22
use OCP\Notification\INotifier;
23
use OCP\Util;
24
25
class TransferRequestManager implements INotifier {
26
27
	/** @var IRootFolder */
28
	protected $rootFolder;
29
	/** @var IManager */
30
	protected $notificationManager;
31
	/** @var IUserManager  */
32
	protected $userManager;
33
	/** @var ITimeFactory */
34
	protected $timeFactory;
35
	/** @var TransferRequestMapper  */
36
	protected $requestMapper;
37
	/** @var IURLGenerator  */
38
	protected $urlGenerator;
39
	/** @var IFactory */
40
	protected $factory;
41
	/** @var IJobList */
42
	protected $jobList;
43
44
	public function __construct(
45
		IRootFolder $rootFolder,
46
		IManager $notificationManager,
47
		IUserManager $userManager,
48
		ITimeFactory $timeFactory,
49
		TransferRequestMapper $requestMapper,
50
		IURLGenerator $urlGenerator,
51
		IFactory $factory,
52
		IJobList $jobList) {
53
		$this->rootFolder = $rootFolder;
54
		$this->notificationManager = $notificationManager;
55
		$this->userManager = $userManager;
56
		$this->timeFactory = $timeFactory;
57
		$this->requestMapper = $requestMapper;
58
		$this->urlGenerator = $urlGenerator;
59
		$this->factory = $factory;
60
		$this->jobList = $jobList;
61
	}
62
63
	/**
64
	 * @param IUser $sourceUser
65
	 * @param IUser $destinationUser
66
	 * @param $fileId
67
	 * @throws NotFoundException
68
	 * @throws \Exception
69
	 */
70
	public function newTransferRequest(IUser $sourceUser, IUser $destinationUser, $fileId) {
71
		// Cannot give to self
72
		if ($sourceUser->getUID() === $destinationUser->getUID()) {
73
			throw new \Exception('Cannot transfer to self');
74
		}
75
		// Check folder exists
76
		$sourceFolder = $this->rootFolder->getById($fileId)[0];
77
		// Check we are a folder
78
		if (!$sourceFolder instanceof Folder) {
79
			throw new \Exception('Must be a folder');
80
		}
81
		// Check source user owns the file
82
		if ($sourceFolder->getOwner()->getUID() !== $sourceUser->getUID()) {
83
			throw new NotPermittedException('Cannot move a file you dont own');
84
		}
85
		// Check the folder is on persistent lockable storage otherwise we can't do this in the background
86
		if (!$sourceFolder->getStorage() instanceof IPersistentLockingStorage) {
87
			throw new \Exception('Source folder storage not lockable');
88
		}
89
		// Check therer is no request with the same signature
90
		if (count($this->requestMapper->findRequestWithSameSignature($sourceUser->getUID(), $destinationUser->getUID(), $fileId)) > 0) {
91
			// There is
92
			throw new \Exception('There is already a request to transer this file');
93
		}
94
		// Check we are not trying to request a transfer for a folder that is inside a current request
95
		$folder = $sourceFolder;
96
		$fileids = [$folder->getId()];
97
		while($folder->getPath() !== '/') {
98
			$folder = $folder->getParent();
99
			$fileids[] = $folder->getId();
100
		}
101
		if (count($this->requestMapper->findOpenRequestForGivenFiles($fileids)) > 0) {
102
			throw new \Exception('This folder is already pending an existing transfer');
103
		}
104
105
		// Create the transfer request object
106
		$request = new TransferRequest();
107
		$request->setRequestedTime($this->timeFactory->getTime());
108
		$request->setSourceUserId($sourceUser->getUID());
109
		$request->setDestinationUserId($destinationUser->getUID());
110
		$request->setFileId($fileId);
111
		$request = $this->requestMapper->insert($request);
112
113
		/** @var IPersistentLockingStorage $storage */
114
		$storage = $sourceFolder->getStorage();
115
		try {
116
			$storage->lockNodePersistent($sourceFolder->getInternalPath(), [
117
				'depth' => ILock::LOCK_DEPTH_INFINITE,
118
				'token' => $this->getLockTokenFromRequest($request),
0 ignored issues
show
Compatibility introduced by
$request of type object<OCP\AppFramework\Db\Entity> is not a sub-type of object<OCA\Files\Service...ership\TransferRequest>. It seems like you assume a child class of the class OCP\AppFramework\Db\Entity to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
119
				'timeout' => 60*60*2 // 2 hours to allow a cron run
120
			]);
121
		} catch (\Exception $e) {
122
			// Cleanup transfer request and fail
123
			$this->requestMapper->delete($request);
124
			throw $e;
125
		}
126
127
		$this->sendRequestNotification($request);
0 ignored issues
show
Compatibility introduced by
$request of type object<OCP\AppFramework\Db\Entity> is not a sub-type of object<OCA\Files\Service...ership\TransferRequest>. It seems like you assume a child class of the class OCP\AppFramework\Db\Entity to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
128
	}
129
130
	public function acceptRequest(TransferRequest $request) {
131 View Code Duplication
		if ($request->getAcceptedTime() !== null || $request->getActionedTime() !== null || $request->getRejectedTime() !== null) {
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...
132
			throw new \Exception('Already actioned, accepted or rejected');
133
		}
134
		// Create a background job, update accepted time
135
		$request->setAcceptedTime($this->timeFactory->getTime());
136
		$this->requestMapper->update($request);
137
		$sourcePath = $this->rootFolder->getUserFolder(
138
			$request->getSourceUserId())->getById($request->getFileId())[0]->getInternalPath();
139
		$this->jobList->add(CommandJob::class, [
140
			'sourcePath' => $sourcePath,
141
			'sourceUser' => $request->getSourceUserId(),
142
			'destinationUser' => $request->getDestinationUserId()
143
		]);
144
		$notification = $this->notificationManager->createNotification();
145
		$notification->setApp('files')
146
			->setUser($request->getDestinationUserId())
147
			->setObject('transfer_request', $request->getId());
148
		$this->notificationManager->markProcessed($notification);
149
	}
150
151
	public function rejectRequest(TransferRequest $request) {
152
		// Cleanup the lock, save reject timestamp
153 View Code Duplication
		if ($request->getAcceptedTime() !== null || $request->getActionedTime() !== null || $request->getRejectedTime() !== null) {
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...
154
			throw new \Exception('Already actioned, accepted or rejected');
155
		}
156
		// Create a background job, update accepted time
157
		$request->setRejectedTime($this->timeFactory->getTime());
158
		$this->requestMapper->update($request);
159
		$notification = $this->notificationManager->createNotification();
160
		$notification->setApp('files')
161
			->setUser($request->getDestinationUserId())
162
			->setObject('transfer_request', $request->getId());
163
		$this->notificationManager->markProcessed($notification);
164
		$file = $this->rootFolder->getById($request->getFileId())[0];
165
		/** @var IPersistentLockingStorage $storage */
166
		$storage = $file->getStorage();
167
		$storage->unlockNodePersistent($file->getInternalPath(), ['token' => $this->getLockTokenFromRequest($request)]);
168
	}
169
170
	public function deleteRequest(TransferRequest $request) {
171
		// Cleanup the lock and the notification
172
		$this->requestMapper->delete($request);
173
		$notification = $this->notificationManager->createNotification();
174
		$notification->setApp('files')
175
			->setUser($request->getDestinationUserId())
176
			->setObject('transfer_request', $request->getId());
177
		$this->notificationManager->markProcessed($notification);
178
		$file = $this->rootFolder->getById($request->getFileId())[0];
179
		/** @var IPersistentLockingStorage $storage */
180
		$storage = $file->getStorage();
181
		$storage->unlockNodePersistent($file->getInternalPath(), ['token' => $this->getLockTokenFromRequest($request)]);
182
	}
183
184
185
	/**
186
	 * @param TransferRequest $request the request object
187
	 */
188
	protected function sendRequestNotification(TransferRequest $request) {
189
		$time = new \DateTime();
190
		$time->setTimestamp($this->timeFactory->getTime());
191
		$notification = $this->notificationManager->createNotification();
192
		$notification->setApp('files')
193
			->setUser($request->getDestinationUserId())
194
			->setDateTime($time)
195
			->setObject('transfer_request', $request->getId());
196
197
		$notification->setIcon(
198
			$this->urlGenerator->imagePath('core', 'actions/give.svg')
199
		);
200
		$notification->setLink('test.com');
201
202
		$sourceUser = $this->userManager->get($request->getSourceUserId());
203
		$folder = $this->rootFolder->getById($request->getFileId())[0];
204
		$notification->setSubject("new_transfer_request");
205
		$notification->setMessage("new_transfer_request", [$sourceUser->getDisplayName(), $folder->getName(), Util::humanFileSize($folder->getSize())]);
206
207
		$endpoint = $this->urlGenerator->linkToRouteAbsolute(
208
			'files.Transfer.accept',
209
			['requestId' => $request->getId()]
210
		);
211
		$declineAction = $notification->createAction();
212
		$declineAction->setLabel('reject');
213
		$declineAction->setLink($endpoint, 'DELETE');
214
		$notification->addAction($declineAction);
215
216
		$acceptAction = $notification->createAction();
217
		$acceptAction->setLabel('accept');
218
		$acceptAction->setLink($endpoint, 'POST');
219
		$acceptAction->setPrimary(true);
220
		$notification->addAction($acceptAction);
221
222
		$this->notificationManager->notify($notification);
223
	}
224
225
	public function prepare(INotification $notification, $languageCode) {
226
		if ($notification->getApp() !== 'files') {
227
			throw new \InvalidArgumentException();
228
		}
229
230
		// Read the language from the notification
231
		$l = $this->factory->get('files', $languageCode);
232
233
		switch ($notification->getObjectType()) {
234
			case 'transfer_request':
235
				$requestId = $notification->getObjectId();
236
				try {
237
					$this->requestMapper->findById($requestId);
238
				} catch (DoesNotExistException $ex) {
239
					$this->notificationManager->markProcessed($notification);
240
					throw new \InvalidArgumentException();
241
				}
242
				return $this->formatNotification($notification, $l);
243
244
			default:
245
				throw new \InvalidArgumentException();
246
		}
247
	}
248
249
	protected function formatNotification(INotification $notification, IL10N $l) {
250
		$notification->setParsedSubject((string) $l->t('A user would like to transfer a folder to you'));
251
252
		$notification->setParsedMessage(
253
			(string) $l->t(
254
				'"%1$s" requested to transfer "%2$s" to you (%3$s)"',
255
				$notification->getMessageParameters())
256
		);
257
258 View Code Duplication
		foreach ($notification->getActions() as $action) {
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...
259
			switch ($action->getLabel()) {
260
				case 'accept':
261
					$action->setParsedLabel(
262
						(string) $l->t('Accept')
263
					);
264
					break;
265
				case 'reject':
266
					$action->setParsedLabel(
267
						(string) $l->t('Decline')
268
					);
269
					break;
270
			}
271
			$notification->addParsedAction($action);
272
		}
273
274
		return $notification;
275
	}
276
277
	public function cleanup() {
278
		// Delete request that are older than 24 hours
279
		$oldRequests = $this->requestMapper->findOpenRequestsOlderThan(1);
280
		/** @var TransferRequest $request */
281
		foreach ($oldRequests as $request) {
282
			// Remove the lock
283
			try {
284
				$file = $this->rootFolder->getById($request->getFileId())[0];
285
				/** @var IPersistentLockingStorage $storage */
286
				$storage = $file->getStorage();
287
				$storage->unlockNodePersistent($file->getInternalPath(), $this->getLockTokenFromRequest($request));
0 ignored issues
show
Documentation introduced by
$this->getLockTokenFromRequest($request) is of type string, but the function expects a array.

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...
288
			} catch (\Exception $e) {
289
				\OC::$server->getLogger()->logException($e, ['app' => 'files']);
290
			}
291
			// Now remove the request
292
			$this->requestMapper->delete($request);
293
			// And lets remove the notification to save confusion
294
			$notification = $this->notificationManager->createNotification();
295
			$notification->setApp('files')
296
				->setUser($request->getDestinationUserId())
297
				->setObject('transfer_request', $request->getId());
298
			$this->notificationManager->markProcessed($notification);
299
		}
300
	}
301
302
	protected function getLockTokenFromRequest(TransferRequest $request) {
303
		return 'transfer-request-'.$request->getId();
304
	}
305
306
	public function getRequestById($requestId) {
307
		return $this->requestMapper->findById($requestId);
308
	}
309
310
311
}