From 426af68dce06dc2e241bb8e3d0b13a54f46ae9ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Tue, 28 Nov 2023 14:28:16 +0100 Subject: [PATCH 1/4] Add a listener to update trashed items when parent is renamed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- lib/AppInfo/Application.php | 3 ++ lib/Listeners/NodeRenamedListener.php | 62 +++++++++++++++++++++++++++ lib/Trash/TrashManager.php | 32 ++++++++++++-- 3 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 lib/Listeners/NodeRenamedListener.php diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 3b1d22f3a..a06bde607 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -43,6 +43,7 @@ use OCA\GroupFolders\Folder\FolderManager; use OCA\GroupFolders\Listeners\CircleDestroyedEventListener; use OCA\GroupFolders\Listeners\LoadAdditionalScriptsListener; +use OCA\GroupFolders\Listeners\NodeRenamedListener; use OCA\GroupFolders\Mount\MountProvider; use OCA\GroupFolders\Trash\TrashBackend; use OCA\GroupFolders\Trash\TrashManager; @@ -55,6 +56,7 @@ use OCP\AppFramework\Bootstrap\IRegistrationContext; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Files\Config\IMountProviderCollection; +use OCP\Files\Events\Node\NodeRenamedEvent; use OCP\Files\Folder; use OCP\Files\IMimeTypeLoader; use OCP\Files\IRootFolder; @@ -90,6 +92,7 @@ public function register(IRegistrationContext $context): void { $context->registerEventListener(LoadAdditionalScriptsEvent::class, LoadAdditionalScriptsListener::class); $context->registerEventListener(BeforeTemplateRenderedEvent::class, LoadAdditionalScriptsListener::class); $context->registerEventListener(CircleDestroyedEvent::class, CircleDestroyedEventListener::class); + $context->registerEventListener(NodeRenamedEvent::class, NodeRenamedListener::class); $context->registerService('GroupAppFolder', function (ContainerInterface $c): Folder { /** @var IRootFolder $rootFolder */ diff --git a/lib/Listeners/NodeRenamedListener.php b/lib/Listeners/NodeRenamedListener.php new file mode 100644 index 000000000..df2f4d7ef --- /dev/null +++ b/lib/Listeners/NodeRenamedListener.php @@ -0,0 +1,62 @@ + + * + * @author Côme Chilliet + * + * @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\GroupFolders\Listeners; + +use OCA\GroupFolders\Mount\GroupFolderStorage; +use OCA\GroupFolders\Trash\TrashManager; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\Files\Events\Node\NodeRenamedEvent; +use OCP\Files\Folder; +use Psr\Log\LoggerInterface; + +/** + * @template-implements IEventListener + */ +class NodeRenamedListener implements IEventListener { + public function __construct( + private TrashManager $trashManager, + private LoggerInterface $logger, + ) { + } + + public function handle(Event $event): void { + $source = $event->getSource(); + $target = $event->getTarget(); + // Look at the parent because the node itself is not existing anymore + $sourceStorage = $source->getParent()->getStorage(); + $targetStorage = $target->getStorage(); + + if (($target instanceof Folder) && + $sourceStorage->instanceOfStorage(GroupFolderStorage::class) && + $targetStorage->instanceOfStorage(GroupFolderStorage::class)) { + $sourcePath = preg_replace('/^'.preg_quote($source->getParent()->getMountPoint()->getMountPoint(), '/').'/', '', $source->getPath()); + $targetPath = preg_replace('/^'.preg_quote($target->getMountPoint()->getMountPoint(), '/').'/', '', $target->getPath()); + $this->trashManager->updateTrashedChildren($sourceStorage->getFolderId(), $targetStorage->getFolderId(), $sourcePath, $targetPath); + } + } +} diff --git a/lib/Trash/TrashManager.php b/lib/Trash/TrashManager.php index a769da298..f1e1e7670 100644 --- a/lib/Trash/TrashManager.php +++ b/lib/Trash/TrashManager.php @@ -25,9 +25,9 @@ use OCP\IDBConnection; class TrashManager { - private IDBConnection $connection; - - public function __construct(IDBConnection $connection) { + public function __construct( + private IDBConnection $connection, + ) { $this->connection = $connection; } @@ -90,4 +90,30 @@ public function emptyTrashbin(int $folderId): void { ->where($query->expr()->eq('folder_id', $query->createNamedParameter($folderId, IQueryBuilder::PARAM_INT))); $query->executeStatement(); } + + public function updateTrashedChildren(int $fromFolderId, int $toFolderId, string $fromLocation, string $toLocation): void { + // Update deep children + $query = $this->connection->getQueryBuilder(); + $fun = $query->func(); + $sourceLength = mb_strlen($fromLocation); + $newPathFunction = $fun->concat( + $query->createNamedParameter($toLocation), + $fun->substring('original_location', $query->createNamedParameter($sourceLength + 1, IQueryBuilder::PARAM_INT))// +1 for the leading slash + ); + $query->update('group_folders_trash') + ->set('folder_id', $query->createNamedParameter($toFolderId, IQueryBuilder::PARAM_INT)) + ->set('original_location', $newPathFunction) + ->where($query->expr()->eq('folder_id', $query->createNamedParameter($fromFolderId, IQueryBuilder::PARAM_INT))) + ->andWhere($query->expr()->like('original_location', $query->createNamedParameter($this->connection->escapeLikeParameter($fromLocation) . '/%'))); + $query->executeStatement(); + + // Update direct children + $query = $this->connection->getQueryBuilder(); + $query->update('group_folders_trash') + ->set('folder_id', $query->createNamedParameter($toFolderId, IQueryBuilder::PARAM_INT)) + ->set('original_location', $query->createNamedParameter($toLocation)) + ->where($query->expr()->eq('folder_id', $query->createNamedParameter($fromFolderId, IQueryBuilder::PARAM_INT))) + ->andWhere($query->expr()->eq('original_location', $query->createNamedParameter($fromLocation, IQueryBuilder::PARAM_STR))); + $query->executeStatement(); + } } From bd8fdf07e6688c09a2a42b1133f669fd309a9d7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Tue, 28 Nov 2023 16:55:44 +0100 Subject: [PATCH 2/4] Use getInternalPath instead of replacement regex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- lib/Listeners/NodeRenamedListener.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/Listeners/NodeRenamedListener.php b/lib/Listeners/NodeRenamedListener.php index df2f4d7ef..0068cab52 100644 --- a/lib/Listeners/NodeRenamedListener.php +++ b/lib/Listeners/NodeRenamedListener.php @@ -54,8 +54,13 @@ public function handle(Event $event): void { if (($target instanceof Folder) && $sourceStorage->instanceOfStorage(GroupFolderStorage::class) && $targetStorage->instanceOfStorage(GroupFolderStorage::class)) { - $sourcePath = preg_replace('/^'.preg_quote($source->getParent()->getMountPoint()->getMountPoint(), '/').'/', '', $source->getPath()); - $targetPath = preg_replace('/^'.preg_quote($target->getMountPoint()->getMountPoint(), '/').'/', '', $target->getPath()); + // Get internal path on parent to avoid NotFoundException + $sourcePath = $source->getParent()->getInternalPath(); + if ($sourcePath !== '') { + $sourcePath .= '/'; + } + $sourcePath .= $source->getName(); + $targetPath = $target->getInternalPath(); $this->trashManager->updateTrashedChildren($sourceStorage->getFolderId(), $targetStorage->getFolderId(), $sourcePath, $targetPath); } } From 4c8616af9dfef62604e6d6e15103251baa343c52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Tue, 12 Dec 2023 18:09:26 +0100 Subject: [PATCH 3/4] Enable tests related to renaming parent of trashed items MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- cypress/e2e/groupfolders.cy.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cypress/e2e/groupfolders.cy.ts b/cypress/e2e/groupfolders.cy.ts index 7f511e9b4..5b5b73c39 100644 --- a/cypress/e2e/groupfolders.cy.ts +++ b/cypress/e2e/groupfolders.cy.ts @@ -201,7 +201,7 @@ describe('Groupfolders ACLs and trashbin behavior', () => { fileOrFolderDoesNotExistInTrashbin('subfolder1') }) - it.skip('Delete, rename parent and restore', () => { + it('Delete, rename parent and restore', () => { // Create a subfolders and a file as manager cy.login(managerUser) cy.mkdir(managerUser, `/${groupFolderName}/subfolder1`) @@ -238,7 +238,7 @@ describe('Groupfolders ACLs and trashbin behavior', () => { fileOrFolderExists('file1.txt') }) - it.skip('Delete, rename parent and check ACL', () => { + it('Delete, rename parent and check ACL', () => { // Create a subfolders and a file as manager cy.login(managerUser) cy.mkdir(managerUser, `/${groupFolderName}/subfolder1`) From e177b55284400374d1cbfc085a4775f7f4da6a7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= <91878298+come-nc@users.noreply.github.com> Date: Tue, 2 Jan 2024 15:38:21 +0100 Subject: [PATCH 4/4] Fix comment in lib/Trash/TrashManager.php MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Louis Signed-off-by: Côme Chilliet <91878298+come-nc@users.noreply.github.com> --- lib/Trash/TrashManager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Trash/TrashManager.php b/lib/Trash/TrashManager.php index f1e1e7670..4664117fd 100644 --- a/lib/Trash/TrashManager.php +++ b/lib/Trash/TrashManager.php @@ -98,7 +98,7 @@ public function updateTrashedChildren(int $fromFolderId, int $toFolderId, string $sourceLength = mb_strlen($fromLocation); $newPathFunction = $fun->concat( $query->createNamedParameter($toLocation), - $fun->substring('original_location', $query->createNamedParameter($sourceLength + 1, IQueryBuilder::PARAM_INT))// +1 for the leading slash + $fun->substring('original_location', $query->createNamedParameter($sourceLength + 1, IQueryBuilder::PARAM_INT))// +1 for the ending slash ); $query->update('group_folders_trash') ->set('folder_id', $query->createNamedParameter($toFolderId, IQueryBuilder::PARAM_INT))