Completed
Push — master ( 31cfba...38a854 )
by Blizzz
50:42 queued 09:40
created
apps/files_versions/lib/Listener/FileEventsListener.php 1 patch
Indentation   +422 added lines, -422 removed lines patch added patch discarded remove patch
@@ -43,426 +43,426 @@
 block discarded – undo
43 43
 
44 44
 /** @template-implements IEventListener<BeforeNodeCopiedEvent|BeforeNodeDeletedEvent|BeforeNodeRenamedEvent|BeforeNodeTouchedEvent|BeforeNodeWrittenEvent|NodeCopiedEvent|NodeCreatedEvent|NodeDeletedEvent|NodeRenamedEvent|NodeTouchedEvent|NodeWrittenEvent> */
45 45
 class FileEventsListener implements IEventListener {
46
-	/**
47
-	 * @var array<int, array>
48
-	 */
49
-	private array $writeHookInfo = [];
50
-	/**
51
-	 * @var array<int, Node>
52
-	 */
53
-	private array $nodesTouched = [];
54
-	/**
55
-	 * @var array<string, Node>
56
-	 */
57
-	private array $versionsDeleted = [];
58
-
59
-	public function __construct(
60
-		private IRootFolder $rootFolder,
61
-		private IVersionManager $versionManager,
62
-		private IMimeTypeLoader $mimeTypeLoader,
63
-		private IUserSession $userSession,
64
-		private LoggerInterface $logger,
65
-	) {
66
-	}
67
-
68
-	public function handle(Event $event): void {
69
-		if ($event instanceof NodeCreatedEvent) {
70
-			$this->created($event->getNode());
71
-		}
72
-
73
-		if ($event instanceof BeforeNodeTouchedEvent) {
74
-			$this->pre_touch_hook($event->getNode());
75
-		}
76
-
77
-		if ($event instanceof NodeTouchedEvent) {
78
-			$this->touch_hook($event->getNode());
79
-		}
80
-
81
-		if ($event instanceof BeforeNodeWrittenEvent) {
82
-			$this->write_hook($event->getNode());
83
-		}
84
-
85
-		if ($event instanceof NodeWrittenEvent) {
86
-			$this->post_write_hook($event->getNode());
87
-		}
88
-
89
-		if ($event instanceof BeforeNodeDeletedEvent) {
90
-			$this->pre_remove_hook($event->getNode());
91
-		}
92
-
93
-		if ($event instanceof NodeDeletedEvent) {
94
-			$this->remove_hook($event->getNode());
95
-		}
96
-
97
-		if ($event instanceof NodeRenamedEvent) {
98
-			$this->rename_hook($event->getSource(), $event->getTarget());
99
-		}
100
-
101
-		if ($event instanceof NodeCopiedEvent) {
102
-			$this->copy_hook($event->getSource(), $event->getTarget());
103
-		}
104
-
105
-		if ($event instanceof BeforeNodeRenamedEvent) {
106
-			$this->pre_renameOrCopy_hook($event->getSource(), $event->getTarget());
107
-		}
108
-
109
-		if ($event instanceof BeforeNodeCopiedEvent) {
110
-			$this->pre_renameOrCopy_hook($event->getSource(), $event->getTarget());
111
-		}
112
-	}
113
-
114
-	public function pre_touch_hook(Node $node): void {
115
-		// Do not handle folders.
116
-		if ($node instanceof Folder) {
117
-			return;
118
-		}
119
-
120
-		// $node is a non-existing on file creation.
121
-		if ($node instanceof NonExistingFile) {
122
-			return;
123
-		}
124
-
125
-		$this->nodesTouched[$node->getId()] = $node;
126
-	}
127
-
128
-	public function touch_hook(Node $node): void {
129
-		// Do not handle folders.
130
-		if ($node instanceof Folder) {
131
-			return;
132
-		}
133
-
134
-		if ($node instanceof NonExistingFile) {
135
-			$this->logger->error(
136
-				'Failed to create or update version for {path}, node does not exist',
137
-				[
138
-					'path' => $node->getPath(),
139
-				]
140
-			);
141
-
142
-			return;
143
-		}
144
-
145
-		$previousNode = $this->nodesTouched[$node->getId()] ?? null;
146
-
147
-		if ($previousNode === null) {
148
-			return;
149
-		}
150
-
151
-		unset($this->nodesTouched[$node->getId()]);
152
-
153
-		try {
154
-			if ($node instanceof File && $this->versionManager instanceof INeedSyncVersionBackend) {
155
-				// We update the timestamp of the version entity associated with the previousNode.
156
-				$this->versionManager->updateVersionEntity($node, $previousNode->getMTime(), ['timestamp' => $node->getMTime()]);
157
-			}
158
-		} catch (DbalException $ex) {
159
-			// Ignore UniqueConstraintViolationException, as we are probably in the middle of a rollback
160
-			// Where the previous node would temporary have the mtime of the old version, so the rollback touches it to fix it.
161
-			if (!($ex->getPrevious() instanceof UniqueConstraintViolationException)) {
162
-				throw $ex;
163
-			}
164
-		} catch (DoesNotExistException $ex) {
165
-			// Ignore DoesNotExistException, as we are probably in the middle of a rollback
166
-			// Where the previous node would temporary have a wrong mtime, so the rollback touches it to fix it.
167
-		}
168
-	}
169
-
170
-	public function created(Node $node): void {
171
-		// Do not handle folders.
172
-		if (!($node instanceof File)) {
173
-			return;
174
-		}
175
-
176
-		if ($node instanceof NonExistingFile) {
177
-			$this->logger->error(
178
-				'Failed to create version for {path}, node does not exist',
179
-				[
180
-					'path' => $node->getPath(),
181
-				]
182
-			);
183
-
184
-			return;
185
-		}
186
-
187
-		if ($this->versionManager instanceof INeedSyncVersionBackend) {
188
-			$this->versionManager->createVersionEntity($node);
189
-		}
190
-	}
191
-
192
-	/**
193
-	 * listen to write event.
194
-	 */
195
-	public function write_hook(Node $node): void {
196
-		// Do not handle folders.
197
-		if ($node instanceof Folder) {
198
-			return;
199
-		}
200
-
201
-		// $node is a non-existing on file creation.
202
-		if ($node instanceof NonExistingFile) {
203
-			return;
204
-		}
205
-
206
-		$path = $this->getPathForNode($node);
207
-		$result = Storage::store($path);
208
-
209
-		// Store the result of the version creation so it can be used in post_write_hook.
210
-		$this->writeHookInfo[$node->getId()] = [
211
-			'previousNode' => $node,
212
-			'versionCreated' => $result !== false
213
-		];
214
-	}
215
-
216
-	/**
217
-	 * listen to post_write event.
218
-	 */
219
-	public function post_write_hook(Node $node): void {
220
-		// Do not handle folders.
221
-		if ($node instanceof Folder) {
222
-			return;
223
-		}
224
-
225
-		if ($node instanceof NonExistingFile) {
226
-			$this->logger->error(
227
-				'Failed to create or update version for {path}, node does not exist',
228
-				[
229
-					'path' => $node->getPath(),
230
-				]
231
-			);
232
-
233
-			return;
234
-		}
235
-
236
-		$writeHookInfo = $this->writeHookInfo[$node->getId()] ?? null;
237
-
238
-		if ($writeHookInfo === null) {
239
-			return;
240
-		}
241
-
242
-		if (
243
-			$writeHookInfo['versionCreated']
244
-			&& $node->getMTime() !== $writeHookInfo['previousNode']->getMTime()
245
-		) {
246
-			// If a new version was created, insert a version in the DB for the current content.
247
-			// If both versions have the same mtime, it means the latest version file simply got overrode,
248
-			// so no need to create a new version.
249
-			$this->created($node);
250
-		} else {
251
-			try {
252
-				// If no new version was stored in the FS, no new version should be added in the DB.
253
-				// So we simply update the associated version.
254
-				if ($node instanceof File && $this->versionManager instanceof INeedSyncVersionBackend) {
255
-					$this->versionManager->updateVersionEntity(
256
-						$node,
257
-						$writeHookInfo['previousNode']->getMtime(),
258
-						[
259
-							'timestamp' => $node->getMTime(),
260
-							'size' => $node->getSize(),
261
-							'mimetype' => $this->mimeTypeLoader->getId($node->getMimetype()),
262
-						],
263
-					);
264
-				}
265
-			} catch (DoesNotExistException $e) {
266
-				// This happens if the versions app was not enabled while the file was created or updated the last time.
267
-				// meaning there is no such revision and we need to create this file.
268
-				if ($writeHookInfo['versionCreated']) {
269
-					$this->created($node);
270
-				} else {
271
-					// Normally this should not happen so we re-throw the exception to not hide any potential issues.
272
-					throw $e;
273
-				}
274
-			} catch (Exception $e) {
275
-				$this->logger->error('Failed to update existing version for ' . $node->getPath(), [
276
-					'exception' => $e,
277
-					'versionCreated' => $writeHookInfo['versionCreated'],
278
-					'previousNode' => [
279
-						'size' => $writeHookInfo['previousNode']->getSize(),
280
-						'mtime' => $writeHookInfo['previousNode']->getMTime(),
281
-					],
282
-					'node' => [
283
-						'size' => $node->getSize(),
284
-						'mtime' => $node->getMTime(),
285
-					]
286
-				]);
287
-				throw $e;
288
-			}
289
-		}
290
-
291
-		unset($this->writeHookInfo[$node->getId()]);
292
-	}
293
-
294
-	/**
295
-	 * Erase versions of deleted file
296
-	 *
297
-	 * This function is connected to the NodeDeletedEvent event
298
-	 * cleanup the versions directory if the actual file gets deleted
299
-	 */
300
-	public function remove_hook(Node $node): void {
301
-		// Need to normalize the path as there is an issue with path concatenation in View.php::getAbsolutePath.
302
-		$path = Filesystem::normalizePath($node->getPath());
303
-		if (!array_key_exists($path, $this->versionsDeleted)) {
304
-			return;
305
-		}
306
-		$node = $this->versionsDeleted[$path];
307
-		$relativePath = $this->getPathForNode($node);
308
-		unset($this->versionsDeleted[$path]);
309
-		Storage::delete($relativePath);
310
-		// If no new version was stored in the FS, no new version should be added in the DB.
311
-		// So we simply update the associated version.
312
-		if ($node instanceof File && $this->versionManager instanceof INeedSyncVersionBackend) {
313
-			$this->versionManager->deleteVersionsEntity($node);
314
-		}
315
-	}
316
-
317
-	/**
318
-	 * mark file as "deleted" so that we can clean up the versions if the file is gone
319
-	 */
320
-	public function pre_remove_hook(Node $node): void {
321
-		$path = $this->getPathForNode($node);
322
-		Storage::markDeletedFile($path);
323
-		$this->versionsDeleted[$node->getPath()] = $node;
324
-	}
325
-
326
-	/**
327
-	 * rename/move versions of renamed/moved files
328
-	 *
329
-	 * This function is connected to the NodeRenamedEvent event and adjust the name and location
330
-	 * of the stored versions along the actual file
331
-	 */
332
-	public function rename_hook(Node $source, Node $target): void {
333
-		$sourceBackend = $this->versionManager->getBackendForStorage($source->getParent()->getStorage());
334
-		$targetBackend = $this->versionManager->getBackendForStorage($target->getStorage());
335
-		// If different backends, do nothing.
336
-		if ($sourceBackend !== $targetBackend) {
337
-			return;
338
-		}
339
-
340
-		$oldPath = $this->getPathForNode($source);
341
-		$newPath = $this->getPathForNode($target);
342
-		Storage::renameOrCopy($oldPath, $newPath, 'rename');
343
-	}
344
-
345
-	/**
346
-	 * copy versions of copied files
347
-	 *
348
-	 * This function is connected to the NodeCopiedEvent event and copies the
349
-	 * the stored versions to the new location
350
-	 */
351
-	public function copy_hook(Node $source, Node $target): void {
352
-		$sourceBackend = $this->versionManager->getBackendForStorage($source->getParent()->getStorage());
353
-		$targetBackend = $this->versionManager->getBackendForStorage($target->getStorage());
354
-		// If different backends, do nothing.
355
-		if ($sourceBackend !== $targetBackend) {
356
-			return;
357
-		}
358
-
359
-		$oldPath = $this->getPathForNode($source);
360
-		$newPath = $this->getPathForNode($target);
361
-		Storage::renameOrCopy($oldPath, $newPath, 'copy');
362
-	}
363
-
364
-	/**
365
-	 * Remember owner and the owner path of the source file.
366
-	 * If the file already exists, then it was a upload of a existing file
367
-	 * over the web interface and we call Storage::store() directly
368
-	 *
369
-	 *
370
-	 */
371
-	public function pre_renameOrCopy_hook(Node $source, Node $target): void {
372
-		$sourceBackend = $this->versionManager->getBackendForStorage($source->getStorage());
373
-		$targetBackend = $this->versionManager->getBackendForStorage($target->getParent()->getStorage());
374
-		// If different backends, do nothing.
375
-		if ($sourceBackend !== $targetBackend) {
376
-			return;
377
-		}
378
-
379
-		// if we rename a movable mount point, then the versions don't have to be renamed
380
-		$oldPath = $this->getPathForNode($source);
381
-		$newPath = $this->getPathForNode($target);
382
-		if ($oldPath === null || $newPath === null) {
383
-			return;
384
-		}
385
-
386
-		$user = $this->userSession->getUser()?->getUID();
387
-		if ($user === null) {
388
-			return;
389
-		}
390
-
391
-		$absOldPath = Filesystem::normalizePath('/' . $user . '/files' . $oldPath);
392
-		$manager = Filesystem::getMountManager();
393
-		$mount = $manager->find($absOldPath);
394
-		$internalPath = $mount->getInternalPath($absOldPath);
395
-		if ($internalPath === '' and $mount instanceof MoveableMount) {
396
-			return;
397
-		}
398
-
399
-		$view = new View($user . '/files');
400
-		if ($view->file_exists($newPath)) {
401
-			Storage::store($newPath);
402
-		} else {
403
-			Storage::setSourcePathAndUser($oldPath);
404
-		}
405
-	}
406
-
407
-	/**
408
-	 * Retrieve the path relative to the current user root folder.
409
-	 * If no user is connected, try to use the node's owner.
410
-	 */
411
-	private function getPathForNode(Node $node): ?string {
412
-		$user = $this->userSession->getUser()?->getUID();
413
-		if ($user) {
414
-			$path = $this->rootFolder
415
-				->getUserFolder($user)
416
-				->getRelativePath($node->getPath());
417
-
418
-			if ($path !== null) {
419
-				return $path;
420
-			}
421
-		}
422
-
423
-		try {
424
-			$owner = $node->getOwner()?->getUid();
425
-		} catch (NotFoundException) {
426
-			$owner = null;
427
-		}
428
-
429
-		// If no owner, extract it from the path.
430
-		// e.g. /user/files/foobar.txt
431
-		if (!$owner) {
432
-			$parts = explode('/', $node->getPath(), 4);
433
-			if (count($parts) === 4) {
434
-				$owner = $parts[1];
435
-			}
436
-		}
437
-
438
-		if ($owner) {
439
-			$path = $this->rootFolder
440
-				->getUserFolder($owner)
441
-				->getRelativePath($node->getPath());
442
-
443
-			if ($path !== null) {
444
-				return $path;
445
-			}
446
-		}
447
-
448
-		if (!($node instanceof NonExistingFile) && !($node instanceof NonExistingFolder)) {
449
-			$this->logger->debug('Failed to compute path for node', [
450
-				'node' => [
451
-					'path' => $node->getPath(),
452
-					'owner' => $owner,
453
-					'fileid' => $node->getId(),
454
-					'size' => $node->getSize(),
455
-					'mtime' => $node->getMTime(),
456
-				]
457
-			]);
458
-		} else {
459
-			$this->logger->debug('Failed to compute path for node', [
460
-				'node' => [
461
-					'path' => $node->getPath(),
462
-					'owner' => $owner,
463
-				]
464
-			]);
465
-		}
466
-		return null;
467
-	}
46
+    /**
47
+     * @var array<int, array>
48
+     */
49
+    private array $writeHookInfo = [];
50
+    /**
51
+     * @var array<int, Node>
52
+     */
53
+    private array $nodesTouched = [];
54
+    /**
55
+     * @var array<string, Node>
56
+     */
57
+    private array $versionsDeleted = [];
58
+
59
+    public function __construct(
60
+        private IRootFolder $rootFolder,
61
+        private IVersionManager $versionManager,
62
+        private IMimeTypeLoader $mimeTypeLoader,
63
+        private IUserSession $userSession,
64
+        private LoggerInterface $logger,
65
+    ) {
66
+    }
67
+
68
+    public function handle(Event $event): void {
69
+        if ($event instanceof NodeCreatedEvent) {
70
+            $this->created($event->getNode());
71
+        }
72
+
73
+        if ($event instanceof BeforeNodeTouchedEvent) {
74
+            $this->pre_touch_hook($event->getNode());
75
+        }
76
+
77
+        if ($event instanceof NodeTouchedEvent) {
78
+            $this->touch_hook($event->getNode());
79
+        }
80
+
81
+        if ($event instanceof BeforeNodeWrittenEvent) {
82
+            $this->write_hook($event->getNode());
83
+        }
84
+
85
+        if ($event instanceof NodeWrittenEvent) {
86
+            $this->post_write_hook($event->getNode());
87
+        }
88
+
89
+        if ($event instanceof BeforeNodeDeletedEvent) {
90
+            $this->pre_remove_hook($event->getNode());
91
+        }
92
+
93
+        if ($event instanceof NodeDeletedEvent) {
94
+            $this->remove_hook($event->getNode());
95
+        }
96
+
97
+        if ($event instanceof NodeRenamedEvent) {
98
+            $this->rename_hook($event->getSource(), $event->getTarget());
99
+        }
100
+
101
+        if ($event instanceof NodeCopiedEvent) {
102
+            $this->copy_hook($event->getSource(), $event->getTarget());
103
+        }
104
+
105
+        if ($event instanceof BeforeNodeRenamedEvent) {
106
+            $this->pre_renameOrCopy_hook($event->getSource(), $event->getTarget());
107
+        }
108
+
109
+        if ($event instanceof BeforeNodeCopiedEvent) {
110
+            $this->pre_renameOrCopy_hook($event->getSource(), $event->getTarget());
111
+        }
112
+    }
113
+
114
+    public function pre_touch_hook(Node $node): void {
115
+        // Do not handle folders.
116
+        if ($node instanceof Folder) {
117
+            return;
118
+        }
119
+
120
+        // $node is a non-existing on file creation.
121
+        if ($node instanceof NonExistingFile) {
122
+            return;
123
+        }
124
+
125
+        $this->nodesTouched[$node->getId()] = $node;
126
+    }
127
+
128
+    public function touch_hook(Node $node): void {
129
+        // Do not handle folders.
130
+        if ($node instanceof Folder) {
131
+            return;
132
+        }
133
+
134
+        if ($node instanceof NonExistingFile) {
135
+            $this->logger->error(
136
+                'Failed to create or update version for {path}, node does not exist',
137
+                [
138
+                    'path' => $node->getPath(),
139
+                ]
140
+            );
141
+
142
+            return;
143
+        }
144
+
145
+        $previousNode = $this->nodesTouched[$node->getId()] ?? null;
146
+
147
+        if ($previousNode === null) {
148
+            return;
149
+        }
150
+
151
+        unset($this->nodesTouched[$node->getId()]);
152
+
153
+        try {
154
+            if ($node instanceof File && $this->versionManager instanceof INeedSyncVersionBackend) {
155
+                // We update the timestamp of the version entity associated with the previousNode.
156
+                $this->versionManager->updateVersionEntity($node, $previousNode->getMTime(), ['timestamp' => $node->getMTime()]);
157
+            }
158
+        } catch (DbalException $ex) {
159
+            // Ignore UniqueConstraintViolationException, as we are probably in the middle of a rollback
160
+            // Where the previous node would temporary have the mtime of the old version, so the rollback touches it to fix it.
161
+            if (!($ex->getPrevious() instanceof UniqueConstraintViolationException)) {
162
+                throw $ex;
163
+            }
164
+        } catch (DoesNotExistException $ex) {
165
+            // Ignore DoesNotExistException, as we are probably in the middle of a rollback
166
+            // Where the previous node would temporary have a wrong mtime, so the rollback touches it to fix it.
167
+        }
168
+    }
169
+
170
+    public function created(Node $node): void {
171
+        // Do not handle folders.
172
+        if (!($node instanceof File)) {
173
+            return;
174
+        }
175
+
176
+        if ($node instanceof NonExistingFile) {
177
+            $this->logger->error(
178
+                'Failed to create version for {path}, node does not exist',
179
+                [
180
+                    'path' => $node->getPath(),
181
+                ]
182
+            );
183
+
184
+            return;
185
+        }
186
+
187
+        if ($this->versionManager instanceof INeedSyncVersionBackend) {
188
+            $this->versionManager->createVersionEntity($node);
189
+        }
190
+    }
191
+
192
+    /**
193
+     * listen to write event.
194
+     */
195
+    public function write_hook(Node $node): void {
196
+        // Do not handle folders.
197
+        if ($node instanceof Folder) {
198
+            return;
199
+        }
200
+
201
+        // $node is a non-existing on file creation.
202
+        if ($node instanceof NonExistingFile) {
203
+            return;
204
+        }
205
+
206
+        $path = $this->getPathForNode($node);
207
+        $result = Storage::store($path);
208
+
209
+        // Store the result of the version creation so it can be used in post_write_hook.
210
+        $this->writeHookInfo[$node->getId()] = [
211
+            'previousNode' => $node,
212
+            'versionCreated' => $result !== false
213
+        ];
214
+    }
215
+
216
+    /**
217
+     * listen to post_write event.
218
+     */
219
+    public function post_write_hook(Node $node): void {
220
+        // Do not handle folders.
221
+        if ($node instanceof Folder) {
222
+            return;
223
+        }
224
+
225
+        if ($node instanceof NonExistingFile) {
226
+            $this->logger->error(
227
+                'Failed to create or update version for {path}, node does not exist',
228
+                [
229
+                    'path' => $node->getPath(),
230
+                ]
231
+            );
232
+
233
+            return;
234
+        }
235
+
236
+        $writeHookInfo = $this->writeHookInfo[$node->getId()] ?? null;
237
+
238
+        if ($writeHookInfo === null) {
239
+            return;
240
+        }
241
+
242
+        if (
243
+            $writeHookInfo['versionCreated']
244
+            && $node->getMTime() !== $writeHookInfo['previousNode']->getMTime()
245
+        ) {
246
+            // If a new version was created, insert a version in the DB for the current content.
247
+            // If both versions have the same mtime, it means the latest version file simply got overrode,
248
+            // so no need to create a new version.
249
+            $this->created($node);
250
+        } else {
251
+            try {
252
+                // If no new version was stored in the FS, no new version should be added in the DB.
253
+                // So we simply update the associated version.
254
+                if ($node instanceof File && $this->versionManager instanceof INeedSyncVersionBackend) {
255
+                    $this->versionManager->updateVersionEntity(
256
+                        $node,
257
+                        $writeHookInfo['previousNode']->getMtime(),
258
+                        [
259
+                            'timestamp' => $node->getMTime(),
260
+                            'size' => $node->getSize(),
261
+                            'mimetype' => $this->mimeTypeLoader->getId($node->getMimetype()),
262
+                        ],
263
+                    );
264
+                }
265
+            } catch (DoesNotExistException $e) {
266
+                // This happens if the versions app was not enabled while the file was created or updated the last time.
267
+                // meaning there is no such revision and we need to create this file.
268
+                if ($writeHookInfo['versionCreated']) {
269
+                    $this->created($node);
270
+                } else {
271
+                    // Normally this should not happen so we re-throw the exception to not hide any potential issues.
272
+                    throw $e;
273
+                }
274
+            } catch (Exception $e) {
275
+                $this->logger->error('Failed to update existing version for ' . $node->getPath(), [
276
+                    'exception' => $e,
277
+                    'versionCreated' => $writeHookInfo['versionCreated'],
278
+                    'previousNode' => [
279
+                        'size' => $writeHookInfo['previousNode']->getSize(),
280
+                        'mtime' => $writeHookInfo['previousNode']->getMTime(),
281
+                    ],
282
+                    'node' => [
283
+                        'size' => $node->getSize(),
284
+                        'mtime' => $node->getMTime(),
285
+                    ]
286
+                ]);
287
+                throw $e;
288
+            }
289
+        }
290
+
291
+        unset($this->writeHookInfo[$node->getId()]);
292
+    }
293
+
294
+    /**
295
+     * Erase versions of deleted file
296
+     *
297
+     * This function is connected to the NodeDeletedEvent event
298
+     * cleanup the versions directory if the actual file gets deleted
299
+     */
300
+    public function remove_hook(Node $node): void {
301
+        // Need to normalize the path as there is an issue with path concatenation in View.php::getAbsolutePath.
302
+        $path = Filesystem::normalizePath($node->getPath());
303
+        if (!array_key_exists($path, $this->versionsDeleted)) {
304
+            return;
305
+        }
306
+        $node = $this->versionsDeleted[$path];
307
+        $relativePath = $this->getPathForNode($node);
308
+        unset($this->versionsDeleted[$path]);
309
+        Storage::delete($relativePath);
310
+        // If no new version was stored in the FS, no new version should be added in the DB.
311
+        // So we simply update the associated version.
312
+        if ($node instanceof File && $this->versionManager instanceof INeedSyncVersionBackend) {
313
+            $this->versionManager->deleteVersionsEntity($node);
314
+        }
315
+    }
316
+
317
+    /**
318
+     * mark file as "deleted" so that we can clean up the versions if the file is gone
319
+     */
320
+    public function pre_remove_hook(Node $node): void {
321
+        $path = $this->getPathForNode($node);
322
+        Storage::markDeletedFile($path);
323
+        $this->versionsDeleted[$node->getPath()] = $node;
324
+    }
325
+
326
+    /**
327
+     * rename/move versions of renamed/moved files
328
+     *
329
+     * This function is connected to the NodeRenamedEvent event and adjust the name and location
330
+     * of the stored versions along the actual file
331
+     */
332
+    public function rename_hook(Node $source, Node $target): void {
333
+        $sourceBackend = $this->versionManager->getBackendForStorage($source->getParent()->getStorage());
334
+        $targetBackend = $this->versionManager->getBackendForStorage($target->getStorage());
335
+        // If different backends, do nothing.
336
+        if ($sourceBackend !== $targetBackend) {
337
+            return;
338
+        }
339
+
340
+        $oldPath = $this->getPathForNode($source);
341
+        $newPath = $this->getPathForNode($target);
342
+        Storage::renameOrCopy($oldPath, $newPath, 'rename');
343
+    }
344
+
345
+    /**
346
+     * copy versions of copied files
347
+     *
348
+     * This function is connected to the NodeCopiedEvent event and copies the
349
+     * the stored versions to the new location
350
+     */
351
+    public function copy_hook(Node $source, Node $target): void {
352
+        $sourceBackend = $this->versionManager->getBackendForStorage($source->getParent()->getStorage());
353
+        $targetBackend = $this->versionManager->getBackendForStorage($target->getStorage());
354
+        // If different backends, do nothing.
355
+        if ($sourceBackend !== $targetBackend) {
356
+            return;
357
+        }
358
+
359
+        $oldPath = $this->getPathForNode($source);
360
+        $newPath = $this->getPathForNode($target);
361
+        Storage::renameOrCopy($oldPath, $newPath, 'copy');
362
+    }
363
+
364
+    /**
365
+     * Remember owner and the owner path of the source file.
366
+     * If the file already exists, then it was a upload of a existing file
367
+     * over the web interface and we call Storage::store() directly
368
+     *
369
+     *
370
+     */
371
+    public function pre_renameOrCopy_hook(Node $source, Node $target): void {
372
+        $sourceBackend = $this->versionManager->getBackendForStorage($source->getStorage());
373
+        $targetBackend = $this->versionManager->getBackendForStorage($target->getParent()->getStorage());
374
+        // If different backends, do nothing.
375
+        if ($sourceBackend !== $targetBackend) {
376
+            return;
377
+        }
378
+
379
+        // if we rename a movable mount point, then the versions don't have to be renamed
380
+        $oldPath = $this->getPathForNode($source);
381
+        $newPath = $this->getPathForNode($target);
382
+        if ($oldPath === null || $newPath === null) {
383
+            return;
384
+        }
385
+
386
+        $user = $this->userSession->getUser()?->getUID();
387
+        if ($user === null) {
388
+            return;
389
+        }
390
+
391
+        $absOldPath = Filesystem::normalizePath('/' . $user . '/files' . $oldPath);
392
+        $manager = Filesystem::getMountManager();
393
+        $mount = $manager->find($absOldPath);
394
+        $internalPath = $mount->getInternalPath($absOldPath);
395
+        if ($internalPath === '' and $mount instanceof MoveableMount) {
396
+            return;
397
+        }
398
+
399
+        $view = new View($user . '/files');
400
+        if ($view->file_exists($newPath)) {
401
+            Storage::store($newPath);
402
+        } else {
403
+            Storage::setSourcePathAndUser($oldPath);
404
+        }
405
+    }
406
+
407
+    /**
408
+     * Retrieve the path relative to the current user root folder.
409
+     * If no user is connected, try to use the node's owner.
410
+     */
411
+    private function getPathForNode(Node $node): ?string {
412
+        $user = $this->userSession->getUser()?->getUID();
413
+        if ($user) {
414
+            $path = $this->rootFolder
415
+                ->getUserFolder($user)
416
+                ->getRelativePath($node->getPath());
417
+
418
+            if ($path !== null) {
419
+                return $path;
420
+            }
421
+        }
422
+
423
+        try {
424
+            $owner = $node->getOwner()?->getUid();
425
+        } catch (NotFoundException) {
426
+            $owner = null;
427
+        }
428
+
429
+        // If no owner, extract it from the path.
430
+        // e.g. /user/files/foobar.txt
431
+        if (!$owner) {
432
+            $parts = explode('/', $node->getPath(), 4);
433
+            if (count($parts) === 4) {
434
+                $owner = $parts[1];
435
+            }
436
+        }
437
+
438
+        if ($owner) {
439
+            $path = $this->rootFolder
440
+                ->getUserFolder($owner)
441
+                ->getRelativePath($node->getPath());
442
+
443
+            if ($path !== null) {
444
+                return $path;
445
+            }
446
+        }
447
+
448
+        if (!($node instanceof NonExistingFile) && !($node instanceof NonExistingFolder)) {
449
+            $this->logger->debug('Failed to compute path for node', [
450
+                'node' => [
451
+                    'path' => $node->getPath(),
452
+                    'owner' => $owner,
453
+                    'fileid' => $node->getId(),
454
+                    'size' => $node->getSize(),
455
+                    'mtime' => $node->getMTime(),
456
+                ]
457
+            ]);
458
+        } else {
459
+            $this->logger->debug('Failed to compute path for node', [
460
+                'node' => [
461
+                    'path' => $node->getPath(),
462
+                    'owner' => $owner,
463
+                ]
464
+            ]);
465
+        }
466
+        return null;
467
+    }
468 468
 }
Please login to merge, or discard this patch.