Passed
Push — master ( 691aa8...315510 )
by Blizzz
17:05 queued 13s
created

ChunkingV2Plugin::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
/*
5
 * @copyright Copyright (c) 2021 Julius Härtl <[email protected]>
6
 *
7
 * @author Julius Härtl <[email protected]>
8
 *
9
 * @license GNU AGPL version 3 or any later version
10
 *
11
 * This program is free software: you can redistribute it and/or modify
12
 * it under the terms of the GNU Affero General Public License as
13
 * published by the Free Software Foundation, either version 3 of the
14
 * License, or (at your option) any later version.
15
 *
16
 * This program is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
 * GNU Affero General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU Affero General Public License
22
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
23
 *
24
 */
25
26
namespace OCA\DAV\Upload;
27
28
use Exception;
29
use InvalidArgumentException;
30
use OC\Files\Filesystem;
31
use OC\Files\ObjectStore\ObjectStoreStorage;
32
use OC\Files\View;
33
use OC_Hook;
34
use OCA\DAV\Connector\Sabre\Directory;
35
use OCA\DAV\Connector\Sabre\File;
36
use OCP\Files\IMimeTypeDetector;
37
use OCP\Files\IRootFolder;
38
use OCP\Files\ObjectStore\IObjectStoreMultiPartUpload;
39
use OCP\Files\Storage\IChunkedFileWrite;
40
use OCP\Files\StorageInvalidException;
41
use OCP\ICache;
42
use OCP\ICacheFactory;
43
use OCP\Lock\ILockingProvider;
44
use Sabre\DAV\Exception\BadRequest;
45
use Sabre\DAV\Exception\InsufficientStorage;
46
use Sabre\DAV\Exception\NotFound;
47
use Sabre\DAV\Exception\PreconditionFailed;
48
use Sabre\DAV\ICollection;
49
use Sabre\DAV\INode;
50
use Sabre\DAV\Server;
51
use Sabre\DAV\ServerPlugin;
52
use Sabre\HTTP\RequestInterface;
53
use Sabre\HTTP\ResponseInterface;
54
use Sabre\Uri;
55
56
class ChunkingV2Plugin extends ServerPlugin {
57
	/** @var Server */
58
	private $server;
59
	/** @var UploadFolder */
60
	private $uploadFolder;
61
	/** @var ICache */
62
	private $cache;
63
64
	private ?string $uploadId = null;
65
	private ?string $uploadPath = null;
66
67
	private const TEMP_TARGET = '.target';
68
69
	public const CACHE_KEY = 'chunking-v2';
70
	public const UPLOAD_TARGET_PATH = 'upload-target-path';
71
	public const UPLOAD_TARGET_ID = 'upload-target-id';
72
	public const UPLOAD_ID = 'upload-id';
73
74
	private const DESTINATION_HEADER = 'Destination';
75
76
	public function __construct(ICacheFactory $cacheFactory) {
77
		$this->cache = $cacheFactory->createDistributed(self::CACHE_KEY);
78
	}
79
80
	/**
81
	 * @inheritdoc
82
	 */
83
	public function initialize(Server $server) {
84
		$server->on('afterMethod:MKCOL', [$this, 'afterMkcol']);
85
		$server->on('beforeMethod:PUT', [$this, 'beforePut']);
86
		$server->on('beforeMethod:DELETE', [$this, 'beforeDelete']);
87
		$server->on('beforeMove', [$this, 'beforeMove'], 90);
88
89
		$this->server = $server;
90
	}
91
92
	/**
93
	 * @param string $path
94
	 * @param bool $createIfNotExists
95
	 * @return FutureFile|UploadFile|ICollection|INode
96
	 */
97
	private function getUploadFile(string $path, bool $createIfNotExists = false) {
98
		try {
99
			$actualFile = $this->server->tree->getNodeForPath($path);
100
			// Only directly upload to the target file if it is on the same storage
101
			// There may be further potential to optimize here by also uploading
102
			// to other storages directly. This would require to also carefully pick
103
			// the storage/path used in getStorage()
104
			if ($actualFile instanceof File && $this->uploadFolder->getStorage()->getId() === $actualFile->getNode()->getStorage()->getId()) {
105
				return $actualFile;
106
			}
107
		} catch (NotFound $e) {
108
			// If there is no target file we upload to the upload folder first
109
		}
110
111
		// Use file in the upload directory that will be copied or moved afterwards
112
		if ($createIfNotExists) {
113
			$this->uploadFolder->createFile(self::TEMP_TARGET);
114
		}
115
116
		/** @var UploadFile $uploadFile */
117
		$uploadFile = $this->uploadFolder->getChild(self::TEMP_TARGET);
118
		return $uploadFile->getFile();
119
	}
120
121
	public function afterMkcol(RequestInterface $request, ResponseInterface $response): bool {
122
		try {
123
			$this->prepareUpload($request->getPath());
124
			$this->checkPrerequisites(false);
125
		} catch (BadRequest|StorageInvalidException|NotFound $e) {
126
			return true;
127
		}
128
129
		$this->uploadPath = $this->server->calculateUri($this->server->httpRequest->getHeader(self::DESTINATION_HEADER));
130
		$targetFile = $this->getUploadFile($this->uploadPath, true);
131
		[$storage, $storagePath] = $this->getUploadStorage($this->uploadPath);
132
133
		$this->uploadId = $storage->startChunkedWrite($storagePath);
0 ignored issues
show
Bug introduced by
The method startChunkedWrite() does not exist on OCP\Files\Storage\IStorage. It seems like you code against a sub-type of OCP\Files\Storage\IStorage such as OCP\Files\Storage\IChunkedFileWrite or OCA\Files_Sharing\SharedStorage or OCA\Files_Sharing\SharedStorage or OC\Files\ObjectStore\HomeObjectStoreStorage or OC\Files\Storage\Wrapper\Wrapper or OC\Files\ObjectStore\ObjectStoreStorage or OC\Files\Storage\Wrapper\Wrapper or OC\Files\ObjectStore\ObjectStoreStorage. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

133
		/** @scrutinizer ignore-call */ 
134
  $this->uploadId = $storage->startChunkedWrite($storagePath);
Loading history...
134
135
		$this->cache->set($this->uploadFolder->getName(), [
136
			self::UPLOAD_ID => $this->uploadId,
137
			self::UPLOAD_TARGET_PATH => $this->uploadPath,
138
			self::UPLOAD_TARGET_ID => $targetFile->getId(),
139
		], 86400);
140
141
		$response->setStatus(201);
142
		return true;
143
	}
144
145
	public function beforePut(RequestInterface $request, ResponseInterface $response): bool {
146
		try {
147
			$this->prepareUpload(dirname($request->getPath()));
148
			$this->checkPrerequisites();
149
		} catch (StorageInvalidException|BadRequest|NotFound $e) {
150
			return true;
151
		}
152
153
		[$storage, $storagePath] = $this->getUploadStorage($this->uploadPath);
154
155
		$chunkName = basename($request->getPath());
156
		$partId = is_numeric($chunkName) ? (int)$chunkName : -1;
157
		if (!($partId >= 1 && $partId <= 10000)) {
158
			throw new BadRequest('Invalid chunk name, must be numeric between 1 and 10000');
159
		}
160
161
		$uploadFile = $this->getUploadFile($this->uploadPath);
162
		$tempTargetFile = null;
163
164
		$additionalSize = (int)$request->getHeader('Content-Length');
165
		if ($this->uploadFolder->childExists(self::TEMP_TARGET) && $this->uploadPath) {
0 ignored issues
show
Bug introduced by
The method childExists() does not exist on Sabre\DAV\INode. It seems like you code against a sub-type of Sabre\DAV\INode such as Sabre\DAV\ICollection or Sabre\DAV\Collection or Sabre\CalDAV\Principal\User or OCA\DAV\CardDAV\Integration\ExternalAddressBook or OCA\DAV\CalDAV\Trashbin\TrashbinHome or Sabre\CalDAV\Calendar or OCA\DAV\Comments\EntityCollection or Sabre\CardDAV\AddressBook or OCA\DAV\CalDAV\Integration\ExternalCalendar or Sabre\CalDAV\Subscriptions\ISubscription or Sabre\CalDAV\Principal\User or Sabre\CalDAV\SharedCalendar or OCA\DAV\Connector\Sabre\Directory or Sabre\CardDAV\AddressBookHome or Sabre\CalDAV\CalendarHome or Sabre\DAVACL\FS\HomeCollection or OCA\DAV\CalDAV\Trashbin\TrashbinHome or Sabre\CalDAV\ICalendar or Sabre\CalDAV\Schedule\IInbox or Sabre\DAVACL\PrincipalCollection or Sabre\CalDAV\Subscriptions\Subscription or OCA\ContactsInteraction\AddressBook or Sabre\CalDAV\Schedule\IOutbox or Sabre\CalDAV\Notifications\Collection or Sabre\DAVACL\FS\Collection or Sabre\CardDAV\AddressBook or Sabre\CalDAV\Principal\User or Sabre\CalDAV\Principal\User or OCA\DAV\CalDAV\Calendar or OCA\DAV\CardDAV\AddressBook or Sabre\DAV\FSExt\Directory or Sabre\DAV\FS\Directory. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

165
		if ($this->uploadFolder->/** @scrutinizer ignore-call */ childExists(self::TEMP_TARGET) && $this->uploadPath) {
Loading history...
166
			/** @var UploadFile $tempTargetFile */
167
			$tempTargetFile = $this->uploadFolder->getChild(self::TEMP_TARGET);
0 ignored issues
show
Bug introduced by
The method getChild() does not exist on Sabre\DAV\INode. It seems like you code against a sub-type of Sabre\DAV\INode such as Sabre\DAV\ICollection or Sabre\DAV\Collection or Sabre\CalDAV\Principal\User or OCA\DAV\CardDAV\Integration\ExternalAddressBook or OCA\DAV\CalDAV\Trashbin\TrashbinHome or Sabre\CalDAV\Calendar or OCA\DAV\Comments\EntityCollection or Sabre\CardDAV\AddressBook or OCA\DAV\CalDAV\Integration\ExternalCalendar or Sabre\CalDAV\Subscriptions\ISubscription or Sabre\CalDAV\Principal\User or Sabre\CalDAV\SharedCalendar or OCA\DAV\Connector\Sabre\Directory or Sabre\CardDAV\AddressBookHome or Sabre\CalDAV\CalendarHome or Sabre\DAVACL\FS\HomeCollection or OCA\DAV\CalDAV\Trashbin\TrashbinHome or Sabre\CalDAV\ICalendar or Sabre\CalDAV\Schedule\IInbox or Sabre\DAVACL\PrincipalCollection or Sabre\CalDAV\Subscriptions\Subscription or OCA\ContactsInteraction\AddressBook or Sabre\CalDAV\Schedule\IOutbox or Sabre\CalDAV\Notifications\Collection or Sabre\DAVACL\FS\Collection or Sabre\CardDAV\AddressBook or Sabre\CalDAV\Principal\User or Sabre\CalDAV\Principal\User or OCA\DAV\CalDAV\Calendar or OCA\DAV\CardDAV\AddressBook or Sabre\DAV\FSExt\Directory or Sabre\DAV\FS\Directory. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

167
			/** @scrutinizer ignore-call */ 
168
   $tempTargetFile = $this->uploadFolder->getChild(self::TEMP_TARGET);
Loading history...
168
			[$destinationDir, $destinationName] = Uri\split($this->uploadPath);
0 ignored issues
show
Bug introduced by
The call to split() has too few arguments starting with string. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

168
			[$destinationDir, $destinationName] = /** @scrutinizer ignore-call */ Uri\split($this->uploadPath);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
169
			/** @var Directory $destinationParent */
170
			$destinationParent = $this->server->tree->getNodeForPath($destinationDir);
171
			$free = $storage->free_space($destinationParent->getInternalPath());
172
			$newSize = $tempTargetFile->getSize() + $additionalSize;
173
			if ($free >= 0 && ($tempTargetFile->getSize() > $free || $newSize > $free)) {
174
				throw new InsufficientStorage("Insufficient space in $this->uploadPath");
175
			}
176
		}
177
178
		$stream = $request->getBodyAsStream();
179
		$storage->putChunkedWritePart($storagePath, $this->uploadId, (string)$partId, $stream, $additionalSize);
0 ignored issues
show
Bug introduced by
The method putChunkedWritePart() does not exist on OCP\Files\Storage\IStorage. It seems like you code against a sub-type of OCP\Files\Storage\IStorage such as OCP\Files\Storage\IChunkedFileWrite or OCA\Files_Sharing\SharedStorage or OCA\Files_Sharing\SharedStorage or OC\Files\ObjectStore\HomeObjectStoreStorage or OC\Files\Storage\Wrapper\Wrapper or OC\Files\ObjectStore\ObjectStoreStorage or OC\Files\Storage\Wrapper\Wrapper or OC\Files\ObjectStore\ObjectStoreStorage. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

179
		$storage->/** @scrutinizer ignore-call */ 
180
            putChunkedWritePart($storagePath, $this->uploadId, (string)$partId, $stream, $additionalSize);
Loading history...
180
181
		$storage->getCache()->update($uploadFile->getId(), ['size' => $uploadFile->getSize() + $additionalSize]);
182
		if ($tempTargetFile) {
183
			$storage->getPropagator()->propagateChange($tempTargetFile->getInternalPath(), time(), $additionalSize);
184
		}
185
186
		$response->setStatus(201);
187
		return false;
188
	}
189
190
	public function beforeMove($sourcePath, $destination): bool {
191
		try {
192
			$this->prepareUpload(dirname($sourcePath));
193
			$this->checkPrerequisites();
194
		} catch (StorageInvalidException|BadRequest|NotFound|PreconditionFailed $e) {
195
			return true;
196
		}
197
		[$storage, $storagePath] = $this->getUploadStorage($this->uploadPath);
198
199
		$targetFile = $this->getUploadFile($this->uploadPath);
200
201
		[$destinationDir, $destinationName] = Uri\split($destination);
0 ignored issues
show
Bug introduced by
The call to split() has too few arguments starting with string. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

201
		[$destinationDir, $destinationName] = /** @scrutinizer ignore-call */ Uri\split($destination);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
202
		/** @var Directory $destinationParent */
203
		$destinationParent = $this->server->tree->getNodeForPath($destinationDir);
204
		$destinationExists = $destinationParent->childExists($destinationName);
205
206
207
		// allow sync clients to send the modification and creation time along in a header
208
		$updateFileInfo = [];
209
		if ($this->server->httpRequest->getHeader('X-OC-MTime') !== null) {
210
			$updateFileInfo['mtime'] = $this->sanitizeMtime($this->server->httpRequest->getHeader('X-OC-MTime'));
211
			$this->server->httpResponse->setHeader('X-OC-MTime', 'accepted');
212
		}
213
		if ($this->server->httpRequest->getHeader('X-OC-CTime') !== null) {
214
			$updateFileInfo['creation_time'] = $this->sanitizeMtime($this->server->httpRequest->getHeader('X-OC-CTime'));
215
			$this->server->httpResponse->setHeader('X-OC-CTime', 'accepted');
216
		}
217
		$updateFileInfo['mimetype'] = \OCP\Server::get(IMimeTypeDetector::class)->detectPath($destinationName);
218
219
		if ($storage->instanceOfStorage(ObjectStoreStorage::class) && $storage->getObjectStore() instanceof IObjectStoreMultiPartUpload) {
0 ignored issues
show
Bug introduced by
The method getObjectStore() does not exist on OCP\Files\Storage\IStorage. It seems like you code against a sub-type of OCP\Files\Storage\IStorage such as OCA\Files_Sharing\SharedStorage or OCA\Files_Sharing\SharedStorage or OC\Files\ObjectStore\HomeObjectStoreStorage or OC\Files\Storage\Wrapper\Wrapper or OC\Files\ObjectStore\ObjectStoreStorage or OC\Files\Storage\Wrapper\Wrapper or OC\Files\ObjectStore\ObjectStoreStorage or OC\Files\ObjectStore\ObjectStoreStorage. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

219
		if ($storage->instanceOfStorage(ObjectStoreStorage::class) && $storage->/** @scrutinizer ignore-call */ getObjectStore() instanceof IObjectStoreMultiPartUpload) {
Loading history...
220
			/** @var ObjectStoreStorage $storage */
221
			/** @var IObjectStoreMultiPartUpload $objectStore */
222
			$objectStore = $storage->getObjectStore();
223
			$parts = $objectStore->getMultipartUploads($storage->getURN($targetFile->getId()), $this->uploadId);
0 ignored issues
show
Bug introduced by
The method getURN() does not exist on OCP\Files\Storage\IStorage. It seems like you code against a sub-type of OCP\Files\Storage\IStorage such as OCA\Files_Sharing\SharedStorage or OCA\Files_Sharing\SharedStorage or OC\Files\ObjectStore\HomeObjectStoreStorage or OC\Files\Storage\Wrapper\Wrapper or OC\Files\ObjectStore\ObjectStoreStorage or OC\Files\Storage\Wrapper\Wrapper or OC\Files\ObjectStore\ObjectStoreStorage or OC\Files\ObjectStore\ObjectStoreStorage. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

223
			$parts = $objectStore->getMultipartUploads($storage->/** @scrutinizer ignore-call */ getURN($targetFile->getId()), $this->uploadId);
Loading history...
224
			$size = 0;
225
			foreach ($parts as $part) {
226
				$size += $part['Size'];
227
			}
228
			$free = $storage->free_space($destinationParent->getInternalPath());
229
			if ($free >= 0 && ($size > $free)) {
230
				throw new InsufficientStorage("Insufficient space in $this->uploadPath");
231
			}
232
		}
233
234
		$destinationInView = $destinationParent->getFileInfo()->getPath() . '/' . $destinationName;
235
		$this->completeChunkedWrite($destinationInView);
236
237
		$rootView = new View();
238
		$rootView->putFileInfo($destinationInView, $updateFileInfo);
239
240
		$sourceNode = $this->server->tree->getNodeForPath($sourcePath);
241
		if ($sourceNode instanceof FutureFile) {
242
			$this->uploadFolder->delete();
243
		}
244
245
		$this->server->emit('afterMove', [$sourcePath, $destination]);
246
		$this->server->emit('afterUnbind', [$sourcePath]);
247
		$this->server->emit('afterBind', [$destination]);
248
249
		$response = $this->server->httpResponse;
250
		$response->setHeader('Content-Type', 'application/xml; charset=utf-8');
251
		$response->setHeader('Content-Length', '0');
252
		$response->setStatus($destinationExists ? 204 : 201);
253
		return false;
254
	}
255
256
	public function beforeDelete(RequestInterface $request, ResponseInterface $response) {
0 ignored issues
show
Unused Code introduced by
The parameter $response is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

256
	public function beforeDelete(RequestInterface $request, /** @scrutinizer ignore-unused */ ResponseInterface $response) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
257
		try {
258
			$this->prepareUpload($request->getPath());
259
			if (!$this->uploadFolder instanceof UploadFolder) {
260
				return true;
261
			}
262
263
			[$storage, $storagePath] = $this->getUploadStorage($this->uploadPath);
264
			$storage->cancelChunkedWrite($storagePath, $this->uploadId);
0 ignored issues
show
Bug introduced by
The method cancelChunkedWrite() does not exist on OCP\Files\Storage\IStorage. It seems like you code against a sub-type of OCP\Files\Storage\IStorage such as OCP\Files\Storage\IChunkedFileWrite or OCA\Files_Sharing\SharedStorage or OCA\Files_Sharing\SharedStorage or OC\Files\ObjectStore\HomeObjectStoreStorage or OC\Files\Storage\Wrapper\Wrapper or OC\Files\ObjectStore\ObjectStoreStorage or OC\Files\Storage\Wrapper\Wrapper or OC\Files\ObjectStore\ObjectStoreStorage. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

264
			$storage->/** @scrutinizer ignore-call */ 
265
             cancelChunkedWrite($storagePath, $this->uploadId);
Loading history...
265
			return true;
266
		} catch (NotFound $e) {
267
			return true;
268
		}
269
	}
270
271
	/**
272
	 * @throws BadRequest
273
	 * @throws PreconditionFailed
274
	 * @throws StorageInvalidException
275
	 */
276
	private function checkPrerequisites(bool $checkUploadMetadata = true): void {
277
		if (!$this->uploadFolder instanceof UploadFolder || empty($this->server->httpRequest->getHeader(self::DESTINATION_HEADER))) {
0 ignored issues
show
introduced by
$this->uploadFolder is always a sub-type of OCA\DAV\Upload\UploadFolder.
Loading history...
278
			throw new BadRequest('Skipping chunked file writing as the destination header was not passed');
279
		}
280
		if (!$this->uploadFolder->getStorage()->instanceOfStorage(IChunkedFileWrite::class)) {
281
			throw new StorageInvalidException('Storage does not support chunked file writing');
282
		}
283
284
		if ($checkUploadMetadata) {
285
			if ($this->uploadId === null || $this->uploadPath === null) {
286
				throw new PreconditionFailed('Missing metadata for chunked upload');
287
			}
288
		}
289
	}
290
291
	/**
292
	 * @return array [IStorage, string]
293
	 */
294
	private function getUploadStorage(string $targetPath): array {
295
		$storage = $this->uploadFolder->getStorage();
296
		$targetFile = $this->getUploadFile($targetPath);
297
		return [$storage, $targetFile->getInternalPath()];
298
	}
299
300
	protected function sanitizeMtime(string $mtimeFromRequest): int {
301
		if (!is_numeric($mtimeFromRequest)) {
302
			throw new InvalidArgumentException('X-OC-MTime header must be an integer (unix timestamp).');
303
		}
304
305
		return (int)$mtimeFromRequest;
306
	}
307
308
	/**
309
	 * @throws NotFound
310
	 */
311
	public function prepareUpload($path): void {
312
		$this->uploadFolder = $this->server->tree->getNodeForPath($path);
313
		$uploadMetadata = $this->cache->get($this->uploadFolder->getName());
314
		$this->uploadId = $uploadMetadata[self::UPLOAD_ID] ?? null;
315
		$this->uploadPath = $uploadMetadata[self::UPLOAD_TARGET_PATH] ?? null;
316
	}
317
318
	private function completeChunkedWrite(string $targetAbsolutePath): void {
319
		$uploadFile = $this->getUploadFile($this->uploadPath)->getNode();
320
		[$storage, $storagePath] = $this->getUploadStorage($this->uploadPath);
321
322
		$rootFolder = \OCP\Server::get(IRootFolder::class);
323
		$exists = $rootFolder->nodeExists($targetAbsolutePath);
324
325
		$uploadFile->lock(ILockingProvider::LOCK_SHARED);
326
		$this->emitPreHooks($targetAbsolutePath, $exists);
327
		try {
328
			$uploadFile->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
329
			$storage->completeChunkedWrite($storagePath, $this->uploadId);
0 ignored issues
show
Bug introduced by
The method completeChunkedWrite() does not exist on OCP\Files\Storage\IStorage. It seems like you code against a sub-type of OCP\Files\Storage\IStorage such as OCP\Files\Storage\IChunkedFileWrite or OCA\Files_Sharing\SharedStorage or OCA\Files_Sharing\SharedStorage or OC\Files\ObjectStore\HomeObjectStoreStorage or OC\Files\Storage\Wrapper\Wrapper or OC\Files\ObjectStore\ObjectStoreStorage or OC\Files\Storage\Wrapper\Wrapper or OC\Files\ObjectStore\ObjectStoreStorage. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

329
			$storage->/** @scrutinizer ignore-call */ 
330
             completeChunkedWrite($storagePath, $this->uploadId);
Loading history...
330
			$uploadFile->changeLock(ILockingProvider::LOCK_SHARED);
331
		} catch (Exception $e) {
332
			$uploadFile->unlock(ILockingProvider::LOCK_EXCLUSIVE);
333
			throw $e;
334
		}
335
336
		// If the file was not uploaded to the user storage directly we need to copy/move it
337
		try {
338
			$uploadFileAbsolutePath = Filesystem::getRoot() . $uploadFile->getPath();
339
			if ($uploadFileAbsolutePath !== $targetAbsolutePath) {
340
				$uploadFile = $rootFolder->get($uploadFile->getFileInfo()->getPath());
0 ignored issues
show
Bug introduced by
The method getFileInfo() does not exist on OCP\Files\File. Since it exists in all sub-types, consider adding an abstract or default implementation to OCP\Files\File. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

340
				$uploadFile = $rootFolder->get($uploadFile->/** @scrutinizer ignore-call */ getFileInfo()->getPath());
Loading history...
341
				if ($exists) {
342
					$uploadFile->copy($targetAbsolutePath);
343
				} else {
344
					$uploadFile->move($targetAbsolutePath);
345
				}
346
			}
347
			$this->emitPostHooks($targetAbsolutePath, $exists);
348
		} catch (Exception $e) {
349
			$uploadFile->unlock(ILockingProvider::LOCK_SHARED);
350
			throw $e;
351
		}
352
	}
353
354
	private function emitPreHooks(string $target, bool $exists): void {
355
		$hookPath = $this->getHookPath($target);
356
		if (!$exists) {
357
			OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_create, [
358
				Filesystem::signal_param_path => $hookPath,
359
			]);
360
		} else {
361
			OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_update, [
362
				Filesystem::signal_param_path => $hookPath,
363
			]);
364
		}
365
		OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_write, [
366
			Filesystem::signal_param_path => $hookPath,
367
		]);
368
	}
369
370
	private function emitPostHooks(string $target, bool $exists): void {
371
		$hookPath = $this->getHookPath($target);
372
		if (!$exists) {
373
			OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_create, [
374
				Filesystem::signal_param_path => $hookPath,
375
			]);
376
		} else {
377
			OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_update, [
378
				Filesystem::signal_param_path => $hookPath,
379
			]);
380
		}
381
		OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_write, [
382
			Filesystem::signal_param_path => $hookPath,
383
		]);
384
	}
385
386
	private function getHookPath(string $path): ?string {
387
		if (!Filesystem::getView()) {
388
			return $path;
389
		}
390
		return Filesystem::getView()->getRelativePath($path);
391
	}
392
}
393