diff --git a/apps/files/composer/composer/autoload_classmap.php b/apps/files/composer/composer/autoload_classmap.php index 0f3e01b5b53f7..d5874228e4662 100644 --- a/apps/files/composer/composer/autoload_classmap.php +++ b/apps/files/composer/composer/autoload_classmap.php @@ -59,6 +59,7 @@ 'OCA\\Files\\Helper' => $baseDir . '/../lib/Helper.php', 'OCA\\Files\\Listener\\LoadSidebarListener' => $baseDir . '/../lib/Listener/LoadSidebarListener.php', 'OCA\\Files\\Listener\\RenderReferenceEventListener' => $baseDir . '/../lib/Listener/RenderReferenceEventListener.php', + 'OCA\\Files\\Listener\\SyncLivePhotosListener' => $baseDir . '/../lib/Listener/SyncLivePhotosListener.php', 'OCA\\Files\\Migration\\Version11301Date20191205150729' => $baseDir . '/../lib/Migration/Version11301Date20191205150729.php', 'OCA\\Files\\Migration\\Version12101Date20221011153334' => $baseDir . '/../lib/Migration/Version12101Date20221011153334.php', 'OCA\\Files\\Notification\\Notifier' => $baseDir . '/../lib/Notification/Notifier.php', diff --git a/apps/files/composer/composer/autoload_static.php b/apps/files/composer/composer/autoload_static.php index 2b152c734f118..d5b1947c1cc9e 100644 --- a/apps/files/composer/composer/autoload_static.php +++ b/apps/files/composer/composer/autoload_static.php @@ -74,6 +74,7 @@ class ComposerStaticInitFiles 'OCA\\Files\\Helper' => __DIR__ . '/..' . '/../lib/Helper.php', 'OCA\\Files\\Listener\\LoadSidebarListener' => __DIR__ . '/..' . '/../lib/Listener/LoadSidebarListener.php', 'OCA\\Files\\Listener\\RenderReferenceEventListener' => __DIR__ . '/..' . '/../lib/Listener/RenderReferenceEventListener.php', + 'OCA\\Files\\Listener\\SyncLivePhotosListener' => __DIR__ . '/..' . '/../lib/Listener/SyncLivePhotosListener.php', 'OCA\\Files\\Migration\\Version11301Date20191205150729' => __DIR__ . '/..' . '/../lib/Migration/Version11301Date20191205150729.php', 'OCA\\Files\\Migration\\Version12101Date20221011153334' => __DIR__ . '/..' . '/../lib/Migration/Version12101Date20221011153334.php', 'OCA\\Files\\Notification\\Notifier' => __DIR__ . '/..' . '/../lib/Notification/Notifier.php', diff --git a/apps/files/composer/composer/installed.php b/apps/files/composer/composer/installed.php index 1a66c7f2416b6..b42781625c734 100644 --- a/apps/files/composer/composer/installed.php +++ b/apps/files/composer/composer/installed.php @@ -3,7 +3,7 @@ 'name' => '__root__', 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => 'b1797842784b250fb01ed5e3bf130705eb94751b', + 'reference' => 'ba1af2b22e5409c62ea2bdf7eb0e13c282ed70e8', 'type' => 'library', 'install_path' => __DIR__ . '/../', 'aliases' => array(), @@ -13,7 +13,7 @@ '__root__' => array( 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => 'b1797842784b250fb01ed5e3bf130705eb94751b', + 'reference' => 'ba1af2b22e5409c62ea2bdf7eb0e13c282ed70e8', 'type' => 'library', 'install_path' => __DIR__ . '/../', 'aliases' => array(), diff --git a/apps/files/lib/AppInfo/Application.php b/apps/files/lib/AppInfo/Application.php index 5934bc1c7ce98..41423a65ca7fe 100644 --- a/apps/files/lib/AppInfo/Application.php +++ b/apps/files/lib/AppInfo/Application.php @@ -43,11 +43,13 @@ use OCA\Files\Event\LoadSidebar; use OCA\Files\Listener\LoadSidebarListener; use OCA\Files\Listener\RenderReferenceEventListener; +use OCA\Files\Listener\SyncLivePhotosListener; use OCA\Files\Notification\Notifier; use OCA\Files\Search\FilesSearchProvider; use OCA\Files\Service\TagService; use OCA\Files\Service\UserConfig; use OCA\Files\Service\ViewConfig; +use OCA\Files_Trashbin\Events\BeforeNodeRestoredEvent; use OCP\Activity\IManager as IActivityManager; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; @@ -56,6 +58,9 @@ use OCP\Collaboration\Reference\RenderReferenceEvent; use OCP\Collaboration\Resources\IProviderManager; use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\Cache\CacheEntryRemovedEvent; +use OCP\Files\Events\Node\BeforeNodeDeletedEvent; +use OCP\Files\Events\Node\BeforeNodeRenamedEvent; use OCP\IConfig; use OCP\IPreview; use OCP\IRequest; @@ -120,6 +125,10 @@ public function register(IRegistrationContext $context): void { $context->registerEventListener(LoadSidebar::class, LoadSidebarListener::class); $context->registerEventListener(RenderReferenceEvent::class, RenderReferenceEventListener::class); + $context->registerEventListener(BeforeNodeRenamedEvent::class, SyncLivePhotosListener::class); + $context->registerEventListener(BeforeNodeDeletedEvent::class, SyncLivePhotosListener::class); + $context->registerEventListener(BeforeNodeRestoredEvent::class, SyncLivePhotosListener::class); + $context->registerEventListener(CacheEntryRemovedEvent::class, SyncLivePhotosListener::class); $context->registerSearchProvider(FilesSearchProvider::class); diff --git a/apps/files/lib/Listener/SyncLivePhotosListener.php b/apps/files/lib/Listener/SyncLivePhotosListener.php new file mode 100644 index 0000000000000..19ca9ed8f2cc1 --- /dev/null +++ b/apps/files/lib/Listener/SyncLivePhotosListener.php @@ -0,0 +1,252 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\Files\Listener; + +use OCA\Files_Trashbin\Events\BeforeNodeRestoredEvent; +use OCA\Files_Trashbin\Trash\ITrashItem; +use OCA\Files_Trashbin\Trash\ITrashManager; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\Files\Cache\CacheEntryRemovedEvent; +use OCP\Files\Events\Node\BeforeNodeDeletedEvent; +use OCP\Files\Events\Node\BeforeNodeRenamedEvent; +use OCP\Files\Folder; +use OCP\Files\Node; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException; +use OCP\FilesMetadata\IFilesMetadataManager; +use OCP\IUserSession; + +/** + * @template-implements IEventListener + */ +class SyncLivePhotosListener implements IEventListener { + /** @var Array */ + private $pendingRenames = []; + /** @var Array */ + private $pendingDeletion = []; + /** @var Array */ + private $pendingRestores = []; + + public function __construct( + private ?Folder $userFolder, + private ?IUserSession $userSession, + private ITrashManager $trashManager, + private IFilesMetadataManager $filesMetadataManager, + ) { + } + + public function handle(Event $event): void { + if ($this->userFolder === null || $this->userSession === null) { + return; + } + + $peerFile = null; + + if ($event instanceof BeforeNodeRenamedEvent) { + $peerFile = $this->getLivePhotoPeer($event->getSource()->getId()); + } elseif ($event instanceof BeforeNodeRestoredEvent) { + $peerFile = $this->getLivePhotoPeer($event->getSource()->getId()); + } elseif ($event instanceof BeforeNodeDeletedEvent) { + $peerFile = $this->getLivePhotoPeer($event->getNode()->getId()); + } elseif ($event instanceof CacheEntryRemovedEvent) { + $peerFile = $this->getLivePhotoPeer($event->getFileId()); + } else { + return; + } + + if ($peerFile === null) { + return; + } + + if ($event instanceof BeforeNodeRenamedEvent) { + $sourceFile = $event->getSource(); + $targetFile = $event->getTarget(); + $targetParent = $targetFile->getParent(); + $sourceExtension = $sourceFile->getExtension(); + $peerFileExtension = $peerFile->getExtension(); + $targetName = $targetFile->getName(); + $targetPath = $targetFile->getPath(); + + // Prevent rename of the .mov file if the peer file do not have the same path. + if ($sourceFile->getMimetype() === 'video/quicktime') { + $peerFilePath = $this->pendingRenames[$peerFile->getId()] ?? $peerFile->getPath(); + $targetPathWithoutExtension = preg_replace("/\.$sourceExtension$/", '', $targetPath); + $peerFilePathWithoutExtension = preg_replace("/\.$peerFileExtension$/", '', $peerFilePath); + + if ($targetPathWithoutExtension !== $peerFilePathWithoutExtension) { + $event->abortOperation(new NotPermittedException("The video part of a live photo need to have the same name as the image")); + } + + unset($this->pendingRenames[$peerFile->getId()]); + return; + } + + if (!str_ends_with($targetName, ".".$sourceExtension)) { + $event->abortOperation(new NotPermittedException("Cannot change the extension of a live photo")); + } + + try { + $targetParent->get($targetName); + $event->abortOperation(new NotPermittedException("A file already exist at destination path")); + } catch (NotFoundException $ex) { + } + try { + $peerTargetName = preg_replace("/\.$sourceExtension$/", '.mov', $targetName); + $targetParent->get($peerTargetName); + $event->abortOperation(new NotPermittedException("A file already exist at destination path")); + } catch (NotFoundException $ex) { + } + + $peerTargetPath = preg_replace("/\.$sourceExtension$/", '.mov', $targetPath); + $this->pendingRenames[$sourceFile->getId()] = $targetPath; + try { + $peerFile->move($peerTargetPath); + } catch (\Throwable $ex) { + $event->abortOperation($ex); + } + return; + } + + if ($event instanceof BeforeNodeDeletedEvent) { + $deletedFile = $event->getNode(); + if ($deletedFile->getMimetype() === 'video/quicktime') { + if (isset($this->pendingDeletion[$peerFile->getId()])) { + unset($this->pendingDeletion[$peerFile->getId()]); + return; + } else { + $event->abortOperation(new NotPermittedException("Cannot delete the video part of a live photo")); + } + } else { + $this->pendingDeletion[$deletedFile->getId()] = true; + try { + $peerFile->delete(); + } catch (\Throwable $ex) { + $event->abortOperation($ex); + } + } + return; + } + + if ($event instanceof CacheEntryRemovedEvent) { + $peerFile->delete(); + } + + if ($event instanceof BeforeNodeRestoredEvent) { + $sourceFile = $event->getSource(); + + if ($sourceFile->getMimetype() === 'video/quicktime') { + if (isset($this->pendingRestores[$peerFile->getId()])) { + unset($this->pendingRestores[$peerFile->getId()]); + return; + } else { + $event->abortOperation(new NotPermittedException("Cannot restore the video part of a live photo")); + } + } else { + $user = $this->userSession->getUser(); + if ($user === null) { + return; + } + + $peerTrashItem = $this->trashManager->getTrashNodeById($user, $peerFile->getId()); + + // Peer file in not in the bin, no need to restore it. + if ($peerTrashItem === null) { + return; + } + + $trashRoot = $this->trashManager->listTrashRoot($user); + $trashItem = $this->getTrashItem($trashRoot, $peerFile->getInternalPath()); + + if ($trashItem === null) { + $event->abortOperation(new NotFoundException("Couldn't find peer file in trashbin")); + } + + $this->pendingRestores[$sourceFile->getId()] = true; + try { + $this->trashManager->restoreItem($trashItem); + } catch (\Throwable $ex) { + $event->abortOperation($ex); + } + } + } + } + + private function getLivePhotoPeer(int $nodeId): ?Node { + if ($this->userFolder === null || $this->userSession === null) { + return null; + } + + try { + $metadata = $this->filesMetadataManager->getMetadata($nodeId); + } catch (FilesMetadataNotFoundException $ex) { + return null; + } + + if (!$metadata->hasKey('files-live-photo')) { + return null; + } + + $peerFileId = (int)$metadata->getString('files-live-photo'); + + // Check the user's folder. + $nodes = $this->userFolder->getById($peerFileId); + if (count($nodes) !== 0) { + return $nodes[0]; + } + + // Check the user's trashbin. + $user = $this->userSession->getUser(); + if ($user !== null) { + $peerFile = $this->trashManager->getTrashNodeById($user, $peerFileId); + if ($peerFile !== null) { + return $peerFile; + } + } + + $metadata->unset('files-live-photo'); + return null; + } + + private function getTrashItem(array $trashFolder, string $path): ?ITrashItem { + foreach($trashFolder as $trashItem) { + if (str_starts_with($path, "files_trashbin/files".$trashItem->getTrashPath())) { + if ($path === "files_trashbin/files".$trashItem->getTrashPath()) { + return $trashItem; + } + + if ($trashItem instanceof Folder) { + $node = $this->getTrashItem($trashItem->getDirectoryListing(), $path); + if ($node !== null) { + return $node; + } + } + } + } + + return null; + } +} diff --git a/apps/files_trashbin/composer/composer/autoload_classmap.php b/apps/files_trashbin/composer/composer/autoload_classmap.php index 88678d8a72abd..c243d1d291cbb 100644 --- a/apps/files_trashbin/composer/composer/autoload_classmap.php +++ b/apps/files_trashbin/composer/composer/autoload_classmap.php @@ -16,7 +16,9 @@ 'OCA\\Files_Trashbin\\Command\\RestoreAllFiles' => $baseDir . '/../lib/Command/RestoreAllFiles.php', 'OCA\\Files_Trashbin\\Command\\Size' => $baseDir . '/../lib/Command/Size.php', 'OCA\\Files_Trashbin\\Controller\\PreviewController' => $baseDir . '/../lib/Controller/PreviewController.php', + 'OCA\\Files_Trashbin\\Events\\BeforeNodeRestoredEvent' => $baseDir . '/../lib/Events/BeforeNodeRestoredEvent.php', 'OCA\\Files_Trashbin\\Events\\MoveToTrashEvent' => $baseDir . '/../lib/Events/MoveToTrashEvent.php', + 'OCA\\Files_Trashbin\\Events\\NodeRestoredEvent' => $baseDir . '/../lib/Events/NodeRestoredEvent.php', 'OCA\\Files_Trashbin\\Exceptions\\CopyRecursiveException' => $baseDir . '/../lib/Exceptions/CopyRecursiveException.php', 'OCA\\Files_Trashbin\\Expiration' => $baseDir . '/../lib/Expiration.php', 'OCA\\Files_Trashbin\\Helper' => $baseDir . '/../lib/Helper.php', diff --git a/apps/files_trashbin/composer/composer/autoload_static.php b/apps/files_trashbin/composer/composer/autoload_static.php index 440dc3ee566d4..ffa3916460b02 100644 --- a/apps/files_trashbin/composer/composer/autoload_static.php +++ b/apps/files_trashbin/composer/composer/autoload_static.php @@ -31,7 +31,9 @@ class ComposerStaticInitFiles_Trashbin 'OCA\\Files_Trashbin\\Command\\RestoreAllFiles' => __DIR__ . '/..' . '/../lib/Command/RestoreAllFiles.php', 'OCA\\Files_Trashbin\\Command\\Size' => __DIR__ . '/..' . '/../lib/Command/Size.php', 'OCA\\Files_Trashbin\\Controller\\PreviewController' => __DIR__ . '/..' . '/../lib/Controller/PreviewController.php', + 'OCA\\Files_Trashbin\\Events\\BeforeNodeRestoredEvent' => __DIR__ . '/..' . '/../lib/Events/BeforeNodeRestoredEvent.php', 'OCA\\Files_Trashbin\\Events\\MoveToTrashEvent' => __DIR__ . '/..' . '/../lib/Events/MoveToTrashEvent.php', + 'OCA\\Files_Trashbin\\Events\\NodeRestoredEvent' => __DIR__ . '/..' . '/../lib/Events/NodeRestoredEvent.php', 'OCA\\Files_Trashbin\\Exceptions\\CopyRecursiveException' => __DIR__ . '/..' . '/../lib/Exceptions/CopyRecursiveException.php', 'OCA\\Files_Trashbin\\Expiration' => __DIR__ . '/..' . '/../lib/Expiration.php', 'OCA\\Files_Trashbin\\Helper' => __DIR__ . '/..' . '/../lib/Helper.php', diff --git a/apps/files_trashbin/lib/Events/BeforeNodeRestoredEvent.php b/apps/files_trashbin/lib/Events/BeforeNodeRestoredEvent.php new file mode 100644 index 0000000000000..019bcae809426 --- /dev/null +++ b/apps/files_trashbin/lib/Events/BeforeNodeRestoredEvent.php @@ -0,0 +1,52 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +namespace OCA\Files_Trashbin\Events; + +use Exception; +use OCP\Files\Events\Node\AbstractNodesEvent; +use OCP\Files\Node; + +/** + * @since 28.0.0 + */ +class BeforeNodeRestoredEvent extends AbstractNodesEvent { + public function __construct(Node $source, Node $target, private bool &$run) { + parent::__construct($source, $target); + } + + /** + * @return never + */ + public function abortOperation(\Throwable $ex = null) { + $this->stopPropagation(); + $this->run = false; + if ($ex !== null) { + throw $ex; + } else { + throw new Exception('Operation aborted'); + } + } +} diff --git a/apps/files_trashbin/lib/Events/NodeRestoredEvent.php b/apps/files_trashbin/lib/Events/NodeRestoredEvent.php new file mode 100644 index 0000000000000..c9979f9cdc9a3 --- /dev/null +++ b/apps/files_trashbin/lib/Events/NodeRestoredEvent.php @@ -0,0 +1,38 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +namespace OCA\Files_Trashbin\Events; + +use OCP\Files\Events\Node\AbstractNodesEvent; +use OCP\Files\Node; + +/** + * @since 28.0.0 + */ +class NodeRestoredEvent extends AbstractNodesEvent { + public function __construct(Node $source, Node $target) { + parent::__construct($source, $target); + } +} diff --git a/apps/files_trashbin/lib/Trashbin.php b/apps/files_trashbin/lib/Trashbin.php index 051665acde1f1..442abc13670c6 100644 --- a/apps/files_trashbin/lib/Trashbin.php +++ b/apps/files_trashbin/lib/Trashbin.php @@ -44,19 +44,27 @@ */ namespace OCA\Files_Trashbin; +use Exception; use OC\Files\Cache\Cache; use OC\Files\Cache\CacheEntry; use OC\Files\Cache\CacheQueryBuilder; use OC\Files\Filesystem; +use OC\Files\Node\File; +use OC\Files\Node\Folder; +use OC\Files\Node\NonExistingFile; +use OC\Files\Node\NonExistingFolder; use OC\Files\ObjectStore\ObjectStoreStorage; use OC\Files\View; use OC_User; use OCA\Files_Trashbin\AppInfo\Application; use OCA\Files_Trashbin\Command\Expire; +use OCA\Files_Trashbin\Events\BeforeNodeRestoredEvent; +use OCA\Files_Trashbin\Events\NodeRestoredEvent; use OCP\App\IAppManager; use OCP\AppFramework\Utility\ITimeFactory; -use OCP\Files\File; -use OCP\Files\Folder; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\IRootFolder; +use OCP\Files\Node; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; use OCP\FilesMetadata\IFilesMetadataManager; @@ -508,6 +516,21 @@ public static function restore($file, $filename, $timestamp) { if (!$view->isCreatable(dirname($target))) { throw new NotPermittedException("Can't restore trash item because the target folder is not writable"); } + + $sourcePath = Filesystem::normalizePath($file); + $targetPath = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename); + + $sourceNode = self::getNodeForPath($sourcePath); + $targetNode = self::getNodeForPath($targetPath); + $run = true; + $event = new BeforeNodeRestoredEvent($sourceNode, $targetNode, $run); + $dispatcher = \OC::$server->get(IEventDispatcher::class); + $dispatcher->dispatchTyped($event); + + if (!$run) { + return false; + } + $restoreResult = $view->rename($source, $target); // handle the restore result @@ -516,8 +539,13 @@ public static function restore($file, $filename, $timestamp) { $view->chroot('/' . $user . '/files'); $view->touch('/' . $location . '/' . $uniqueFilename, $mtime); $view->chroot($fakeRoot); - \OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename), - 'trashPath' => Filesystem::normalizePath($file)]); + \OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => $targetPath, 'trashPath' => $sourcePath]); + + $sourceNode = self::getNodeForPath($sourcePath); + $targetNode = self::getNodeForPath($targetPath); + $event = new NodeRestoredEvent($sourceNode, $targetNode); + $dispatcher = \OC::$server->get(IEventDispatcher::class); + $dispatcher->dispatchTyped($event); self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp); @@ -1151,4 +1179,33 @@ public static function getTrashFilename(string $filename, int $timestamp): strin } return $trashFilename; } + + private static function getNodeForPath(string $path): Node { + $user = OC_User::getUser(); + $rootFolder = \OC::$server->get(IRootFolder::class); + + if ($user !== false) { + $userFolder = $rootFolder->getUserFolder($user); + /** @var Folder */ + $trashFolder = $userFolder->getParent()->get('files_trashbin/files'); + try { + return $trashFolder->get($path); + } catch (NotFoundException $ex) { + } + } + + $view = \OC::$server->get(View::class); + $fsView = Filesystem::getView(); + if ($fsView === null) { + throw new Exception('View should not be null'); + } + + $fullPath = $fsView->getAbsolutePath($path); + + if (Filesystem::is_dir($path)) { + return new NonExistingFolder($rootFolder, $view, $fullPath); + } else { + return new NonExistingFile($rootFolder, $view, $fullPath); + } + } } diff --git a/lib/private/Files/Node/HookConnector.php b/lib/private/Files/Node/HookConnector.php index a8e76d95c22c4..f61eedee66e55 100644 --- a/lib/private/Files/Node/HookConnector.php +++ b/lib/private/Files/Node/HookConnector.php @@ -133,7 +133,7 @@ public function delete($arguments) { $this->root->emit('\OC\Files', 'preDelete', [$node]); $this->dispatcher->dispatch('\OCP\Files::preDelete', new GenericEvent($node)); - $event = new BeforeNodeDeletedEvent($node); + $event = new BeforeNodeDeletedEvent($node, $arguments['run']); $this->dispatcher->dispatchTyped($event); } @@ -171,7 +171,7 @@ public function rename($arguments) { $this->root->emit('\OC\Files', 'preRename', [$source, $target]); $this->dispatcher->dispatch('\OCP\Files::preRename', new GenericEvent([$source, $target])); - $event = new BeforeNodeRenamedEvent($source, $target); + $event = new BeforeNodeRenamedEvent($source, $target, $arguments['run']); $this->dispatcher->dispatchTyped($event); } diff --git a/lib/public/Files/Events/Node/BeforeNodeDeletedEvent.php b/lib/public/Files/Events/Node/BeforeNodeDeletedEvent.php index 316a30fadd83b..dd29a39a27994 100644 --- a/lib/public/Files/Events/Node/BeforeNodeDeletedEvent.php +++ b/lib/public/Files/Events/Node/BeforeNodeDeletedEvent.php @@ -25,8 +25,31 @@ */ namespace OCP\Files\Events\Node; +use Exception; +use OCP\Files\Node; + /** * @since 20.0.0 */ class BeforeNodeDeletedEvent extends AbstractNodeEvent { + /** + * @since 20.0.0 + */ + public function __construct(Node $node, private bool &$run) { + parent::__construct($node); + } + + /** + * @since 28.0.0 + * @return never + */ + public function abortOperation(\Throwable $ex = null) { + $this->stopPropagation(); + $this->run = false; + if ($ex !== null) { + throw $ex; + } else { + throw new Exception('Operation aborted'); + } + } } diff --git a/lib/public/Files/Events/Node/BeforeNodeRenamedEvent.php b/lib/public/Files/Events/Node/BeforeNodeRenamedEvent.php index efbef03e38387..c6876666713e1 100644 --- a/lib/public/Files/Events/Node/BeforeNodeRenamedEvent.php +++ b/lib/public/Files/Events/Node/BeforeNodeRenamedEvent.php @@ -25,8 +25,31 @@ */ namespace OCP\Files\Events\Node; +use Exception; +use OCP\Files\Node; + /** * @since 20.0.0 */ class BeforeNodeRenamedEvent extends AbstractNodesEvent { + /** + * @since 20.0.0 + */ + public function __construct(Node $source, Node $target, private bool &$run) { + parent::__construct($source, $target); + } + + /** + * @since 28.0.0 + * @return never + */ + public function abortOperation(\Throwable $ex = null) { + $this->stopPropagation(); + $this->run = false; + if ($ex !== null) { + throw $ex; + } else { + throw new Exception('Operation aborted'); + } + } }