Passed
Push — master ( 46bb95...b93572 )
by Julius
16:03 queued 15s
created
apps/dav/lib/Upload/ChunkingV2Plugin.php 2 patches
Indentation   +333 added lines, -333 removed lines patch added patch discarded remove patch
@@ -54,337 +54,337 @@
 block discarded – undo
54 54
 use Sabre\Uri;
55 55
 
56 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);
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) {
166
-			/** @var UploadFile $tempTargetFile */
167
-			$tempTargetFile = $this->uploadFolder->getChild(self::TEMP_TARGET);
168
-			[$destinationDir, $destinationName] = Uri\split($this->uploadPath);
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);
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);
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) {
220
-			/** @var ObjectStoreStorage $storage */
221
-			/** @var IObjectStoreMultiPartUpload $objectStore */
222
-			$objectStore = $storage->getObjectStore();
223
-			$parts = $objectStore->getMultipartUploads($storage->getURN($targetFile->getId()), $this->uploadId);
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) {
257
-		try {
258
-			$this->prepareUpload(dirname($request->getPath()));
259
-			$this->checkPrerequisites();
260
-		} catch (StorageInvalidException|BadRequest|NotFound $e) {
261
-			return true;
262
-		}
263
-
264
-		[$storage, $storagePath] = $this->getUploadStorage($this->uploadPath);
265
-		$storage->cancelChunkedWrite($storagePath, $this->uploadId);
266
-		return true;
267
-	}
268
-
269
-	/**
270
-	 * @throws BadRequest
271
-	 * @throws PreconditionFailed
272
-	 * @throws StorageInvalidException
273
-	 */
274
-	private function checkPrerequisites(bool $checkUploadMetadata = true): void {
275
-		if (!$this->uploadFolder instanceof UploadFolder || empty($this->server->httpRequest->getHeader(self::DESTINATION_HEADER))) {
276
-			throw new BadRequest('Skipping chunked file writing as the destination header was not passed');
277
-		}
278
-		if (!$this->uploadFolder->getStorage()->instanceOfStorage(IChunkedFileWrite::class)) {
279
-			throw new StorageInvalidException('Storage does not support chunked file writing');
280
-		}
281
-
282
-		if ($checkUploadMetadata) {
283
-			if ($this->uploadId === null || $this->uploadPath === null) {
284
-				throw new PreconditionFailed('Missing metadata for chunked upload');
285
-			}
286
-		}
287
-	}
288
-
289
-	/**
290
-	 * @return array [IStorage, string]
291
-	 */
292
-	private function getUploadStorage(string $targetPath): array {
293
-		$storage = $this->uploadFolder->getStorage();
294
-		$targetFile = $this->getUploadFile($targetPath);
295
-		return [$storage, $targetFile->getInternalPath()];
296
-	}
297
-
298
-	protected function sanitizeMtime(string $mtimeFromRequest): int {
299
-		if (!is_numeric($mtimeFromRequest)) {
300
-			throw new InvalidArgumentException('X-OC-MTime header must be an integer (unix timestamp).');
301
-		}
302
-
303
-		return (int)$mtimeFromRequest;
304
-	}
305
-
306
-	/**
307
-	 * @throws NotFound
308
-	 */
309
-	public function prepareUpload($path): void {
310
-		$this->uploadFolder = $this->server->tree->getNodeForPath($path);
311
-		$uploadMetadata = $this->cache->get($this->uploadFolder->getName());
312
-		$this->uploadId = $uploadMetadata[self::UPLOAD_ID] ?? null;
313
-		$this->uploadPath = $uploadMetadata[self::UPLOAD_TARGET_PATH] ?? null;
314
-	}
315
-
316
-	private function completeChunkedWrite(string $targetAbsolutePath): void {
317
-		$uploadFile = $this->getUploadFile($this->uploadPath)->getNode();
318
-		[$storage, $storagePath] = $this->getUploadStorage($this->uploadPath);
319
-
320
-		$rootFolder = \OCP\Server::get(IRootFolder::class);
321
-		$exists = $rootFolder->nodeExists($targetAbsolutePath);
322
-
323
-		$uploadFile->lock(ILockingProvider::LOCK_SHARED);
324
-		$this->emitPreHooks($targetAbsolutePath, $exists);
325
-		try {
326
-			$uploadFile->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
327
-			$storage->completeChunkedWrite($storagePath, $this->uploadId);
328
-			$uploadFile->changeLock(ILockingProvider::LOCK_SHARED);
329
-		} catch (Exception $e) {
330
-			$uploadFile->unlock(ILockingProvider::LOCK_EXCLUSIVE);
331
-			throw $e;
332
-		}
333
-
334
-		// If the file was not uploaded to the user storage directly we need to copy/move it
335
-		try {
336
-			$uploadFileAbsolutePath = Filesystem::getRoot() . $uploadFile->getPath();
337
-			if ($uploadFileAbsolutePath !== $targetAbsolutePath) {
338
-				$uploadFile = $rootFolder->get($uploadFile->getFileInfo()->getPath());
339
-				if ($exists) {
340
-					$uploadFile->copy($targetAbsolutePath);
341
-				} else {
342
-					$uploadFile->move($targetAbsolutePath);
343
-				}
344
-			}
345
-			$this->emitPostHooks($targetAbsolutePath, $exists);
346
-		} catch (Exception $e) {
347
-			$uploadFile->unlock(ILockingProvider::LOCK_SHARED);
348
-			throw $e;
349
-		}
350
-	}
351
-
352
-	private function emitPreHooks(string $target, bool $exists): void {
353
-		$hookPath = $this->getHookPath($target);
354
-		if (!$exists) {
355
-			OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_create, [
356
-				Filesystem::signal_param_path => $hookPath,
357
-			]);
358
-		} else {
359
-			OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_update, [
360
-				Filesystem::signal_param_path => $hookPath,
361
-			]);
362
-		}
363
-		OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_write, [
364
-			Filesystem::signal_param_path => $hookPath,
365
-		]);
366
-	}
367
-
368
-	private function emitPostHooks(string $target, bool $exists): void {
369
-		$hookPath = $this->getHookPath($target);
370
-		if (!$exists) {
371
-			OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_create, [
372
-				Filesystem::signal_param_path => $hookPath,
373
-			]);
374
-		} else {
375
-			OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_update, [
376
-				Filesystem::signal_param_path => $hookPath,
377
-			]);
378
-		}
379
-		OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_write, [
380
-			Filesystem::signal_param_path => $hookPath,
381
-		]);
382
-	}
383
-
384
-	private function getHookPath(string $path): ?string {
385
-		if (!Filesystem::getView()) {
386
-			return $path;
387
-		}
388
-		return Filesystem::getView()->getRelativePath($path);
389
-	}
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);
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) {
166
+            /** @var UploadFile $tempTargetFile */
167
+            $tempTargetFile = $this->uploadFolder->getChild(self::TEMP_TARGET);
168
+            [$destinationDir, $destinationName] = Uri\split($this->uploadPath);
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);
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);
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) {
220
+            /** @var ObjectStoreStorage $storage */
221
+            /** @var IObjectStoreMultiPartUpload $objectStore */
222
+            $objectStore = $storage->getObjectStore();
223
+            $parts = $objectStore->getMultipartUploads($storage->getURN($targetFile->getId()), $this->uploadId);
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) {
257
+        try {
258
+            $this->prepareUpload(dirname($request->getPath()));
259
+            $this->checkPrerequisites();
260
+        } catch (StorageInvalidException|BadRequest|NotFound $e) {
261
+            return true;
262
+        }
263
+
264
+        [$storage, $storagePath] = $this->getUploadStorage($this->uploadPath);
265
+        $storage->cancelChunkedWrite($storagePath, $this->uploadId);
266
+        return true;
267
+    }
268
+
269
+    /**
270
+     * @throws BadRequest
271
+     * @throws PreconditionFailed
272
+     * @throws StorageInvalidException
273
+     */
274
+    private function checkPrerequisites(bool $checkUploadMetadata = true): void {
275
+        if (!$this->uploadFolder instanceof UploadFolder || empty($this->server->httpRequest->getHeader(self::DESTINATION_HEADER))) {
276
+            throw new BadRequest('Skipping chunked file writing as the destination header was not passed');
277
+        }
278
+        if (!$this->uploadFolder->getStorage()->instanceOfStorage(IChunkedFileWrite::class)) {
279
+            throw new StorageInvalidException('Storage does not support chunked file writing');
280
+        }
281
+
282
+        if ($checkUploadMetadata) {
283
+            if ($this->uploadId === null || $this->uploadPath === null) {
284
+                throw new PreconditionFailed('Missing metadata for chunked upload');
285
+            }
286
+        }
287
+    }
288
+
289
+    /**
290
+     * @return array [IStorage, string]
291
+     */
292
+    private function getUploadStorage(string $targetPath): array {
293
+        $storage = $this->uploadFolder->getStorage();
294
+        $targetFile = $this->getUploadFile($targetPath);
295
+        return [$storage, $targetFile->getInternalPath()];
296
+    }
297
+
298
+    protected function sanitizeMtime(string $mtimeFromRequest): int {
299
+        if (!is_numeric($mtimeFromRequest)) {
300
+            throw new InvalidArgumentException('X-OC-MTime header must be an integer (unix timestamp).');
301
+        }
302
+
303
+        return (int)$mtimeFromRequest;
304
+    }
305
+
306
+    /**
307
+     * @throws NotFound
308
+     */
309
+    public function prepareUpload($path): void {
310
+        $this->uploadFolder = $this->server->tree->getNodeForPath($path);
311
+        $uploadMetadata = $this->cache->get($this->uploadFolder->getName());
312
+        $this->uploadId = $uploadMetadata[self::UPLOAD_ID] ?? null;
313
+        $this->uploadPath = $uploadMetadata[self::UPLOAD_TARGET_PATH] ?? null;
314
+    }
315
+
316
+    private function completeChunkedWrite(string $targetAbsolutePath): void {
317
+        $uploadFile = $this->getUploadFile($this->uploadPath)->getNode();
318
+        [$storage, $storagePath] = $this->getUploadStorage($this->uploadPath);
319
+
320
+        $rootFolder = \OCP\Server::get(IRootFolder::class);
321
+        $exists = $rootFolder->nodeExists($targetAbsolutePath);
322
+
323
+        $uploadFile->lock(ILockingProvider::LOCK_SHARED);
324
+        $this->emitPreHooks($targetAbsolutePath, $exists);
325
+        try {
326
+            $uploadFile->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
327
+            $storage->completeChunkedWrite($storagePath, $this->uploadId);
328
+            $uploadFile->changeLock(ILockingProvider::LOCK_SHARED);
329
+        } catch (Exception $e) {
330
+            $uploadFile->unlock(ILockingProvider::LOCK_EXCLUSIVE);
331
+            throw $e;
332
+        }
333
+
334
+        // If the file was not uploaded to the user storage directly we need to copy/move it
335
+        try {
336
+            $uploadFileAbsolutePath = Filesystem::getRoot() . $uploadFile->getPath();
337
+            if ($uploadFileAbsolutePath !== $targetAbsolutePath) {
338
+                $uploadFile = $rootFolder->get($uploadFile->getFileInfo()->getPath());
339
+                if ($exists) {
340
+                    $uploadFile->copy($targetAbsolutePath);
341
+                } else {
342
+                    $uploadFile->move($targetAbsolutePath);
343
+                }
344
+            }
345
+            $this->emitPostHooks($targetAbsolutePath, $exists);
346
+        } catch (Exception $e) {
347
+            $uploadFile->unlock(ILockingProvider::LOCK_SHARED);
348
+            throw $e;
349
+        }
350
+    }
351
+
352
+    private function emitPreHooks(string $target, bool $exists): void {
353
+        $hookPath = $this->getHookPath($target);
354
+        if (!$exists) {
355
+            OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_create, [
356
+                Filesystem::signal_param_path => $hookPath,
357
+            ]);
358
+        } else {
359
+            OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_update, [
360
+                Filesystem::signal_param_path => $hookPath,
361
+            ]);
362
+        }
363
+        OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_write, [
364
+            Filesystem::signal_param_path => $hookPath,
365
+        ]);
366
+    }
367
+
368
+    private function emitPostHooks(string $target, bool $exists): void {
369
+        $hookPath = $this->getHookPath($target);
370
+        if (!$exists) {
371
+            OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_create, [
372
+                Filesystem::signal_param_path => $hookPath,
373
+            ]);
374
+        } else {
375
+            OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_update, [
376
+                Filesystem::signal_param_path => $hookPath,
377
+            ]);
378
+        }
379
+        OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_write, [
380
+            Filesystem::signal_param_path => $hookPath,
381
+        ]);
382
+    }
383
+
384
+    private function getHookPath(string $path): ?string {
385
+        if (!Filesystem::getView()) {
386
+            return $path;
387
+        }
388
+        return Filesystem::getView()->getRelativePath($path);
389
+    }
390 390
 }
Please login to merge, or discard this patch.
Spacing   +10 added lines, -10 removed lines patch added patch discarded remove patch
@@ -122,7 +122,7 @@  discard block
 block discarded – undo
122 122
 		try {
123 123
 			$this->prepareUpload($request->getPath());
124 124
 			$this->checkPrerequisites(false);
125
-		} catch (BadRequest|StorageInvalidException|NotFound $e) {
125
+		} catch (BadRequest | StorageInvalidException | NotFound $e) {
126 126
 			return true;
127 127
 		}
128 128
 
@@ -146,14 +146,14 @@  discard block
 block discarded – undo
146 146
 		try {
147 147
 			$this->prepareUpload(dirname($request->getPath()));
148 148
 			$this->checkPrerequisites();
149
-		} catch (StorageInvalidException|BadRequest|NotFound $e) {
149
+		} catch (StorageInvalidException | BadRequest | NotFound $e) {
150 150
 			return true;
151 151
 		}
152 152
 
153 153
 		[$storage, $storagePath] = $this->getUploadStorage($this->uploadPath);
154 154
 
155 155
 		$chunkName = basename($request->getPath());
156
-		$partId = is_numeric($chunkName) ? (int)$chunkName : -1;
156
+		$partId = is_numeric($chunkName) ? (int) $chunkName : -1;
157 157
 		if (!($partId >= 1 && $partId <= 10000)) {
158 158
 			throw new BadRequest('Invalid chunk name, must be numeric between 1 and 10000');
159 159
 		}
@@ -161,7 +161,7 @@  discard block
 block discarded – undo
161 161
 		$uploadFile = $this->getUploadFile($this->uploadPath);
162 162
 		$tempTargetFile = null;
163 163
 
164
-		$additionalSize = (int)$request->getHeader('Content-Length');
164
+		$additionalSize = (int) $request->getHeader('Content-Length');
165 165
 		if ($this->uploadFolder->childExists(self::TEMP_TARGET) && $this->uploadPath) {
166 166
 			/** @var UploadFile $tempTargetFile */
167 167
 			$tempTargetFile = $this->uploadFolder->getChild(self::TEMP_TARGET);
@@ -176,7 +176,7 @@  discard block
 block discarded – undo
176 176
 		}
177 177
 
178 178
 		$stream = $request->getBodyAsStream();
179
-		$storage->putChunkedWritePart($storagePath, $this->uploadId, (string)$partId, $stream, $additionalSize);
179
+		$storage->putChunkedWritePart($storagePath, $this->uploadId, (string) $partId, $stream, $additionalSize);
180 180
 
181 181
 		$storage->getCache()->update($uploadFile->getId(), ['size' => $uploadFile->getSize() + $additionalSize]);
182 182
 		if ($tempTargetFile) {
@@ -191,7 +191,7 @@  discard block
 block discarded – undo
191 191
 		try {
192 192
 			$this->prepareUpload(dirname($sourcePath));
193 193
 			$this->checkPrerequisites();
194
-		} catch (StorageInvalidException|BadRequest|NotFound|PreconditionFailed $e) {
194
+		} catch (StorageInvalidException | BadRequest | NotFound | PreconditionFailed $e) {
195 195
 			return true;
196 196
 		}
197 197
 		[$storage, $storagePath] = $this->getUploadStorage($this->uploadPath);
@@ -231,7 +231,7 @@  discard block
 block discarded – undo
231 231
 			}
232 232
 		}
233 233
 
234
-		$destinationInView = $destinationParent->getFileInfo()->getPath() . '/' . $destinationName;
234
+		$destinationInView = $destinationParent->getFileInfo()->getPath().'/'.$destinationName;
235 235
 		$this->completeChunkedWrite($destinationInView);
236 236
 
237 237
 		$rootView = new View();
@@ -257,7 +257,7 @@  discard block
 block discarded – undo
257 257
 		try {
258 258
 			$this->prepareUpload(dirname($request->getPath()));
259 259
 			$this->checkPrerequisites();
260
-		} catch (StorageInvalidException|BadRequest|NotFound $e) {
260
+		} catch (StorageInvalidException | BadRequest | NotFound $e) {
261 261
 			return true;
262 262
 		}
263 263
 
@@ -300,7 +300,7 @@  discard block
 block discarded – undo
300 300
 			throw new InvalidArgumentException('X-OC-MTime header must be an integer (unix timestamp).');
301 301
 		}
302 302
 
303
-		return (int)$mtimeFromRequest;
303
+		return (int) $mtimeFromRequest;
304 304
 	}
305 305
 
306 306
 	/**
@@ -333,7 +333,7 @@  discard block
 block discarded – undo
333 333
 
334 334
 		// If the file was not uploaded to the user storage directly we need to copy/move it
335 335
 		try {
336
-			$uploadFileAbsolutePath = Filesystem::getRoot() . $uploadFile->getPath();
336
+			$uploadFileAbsolutePath = Filesystem::getRoot().$uploadFile->getPath();
337 337
 			if ($uploadFileAbsolutePath !== $targetAbsolutePath) {
338 338
 				$uploadFile = $rootFolder->get($uploadFile->getFileInfo()->getPath());
339 339
 				if ($exists) {
Please login to merge, or discard this patch.