From a5ead96eaf09c064cc6dd23a8d15dc93e98ca167 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 | 4 ++ lib/Listeners/NodeRenamedListener.php | 62 +++++++++++++++++++++++++++ lib/Trash/TrashManager.php | 32 ++++++++++++-- 3 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 lib/Listeners/NodeRenamedListener.php diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 31eb86a5..e858e9bb 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -45,6 +45,7 @@ use OCA\GroupFolders\Helper\LazyFolder; 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; @@ -58,6 +59,8 @@ use OCP\AppFramework\IAppContainer; 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; use OCP\ICacheFactory; @@ -90,6 +93,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->registerServiceAlias('GroupAppFolder', LazyFolder::class); diff --git a/lib/Listeners/NodeRenamedListener.php b/lib/Listeners/NodeRenamedListener.php new file mode 100644 index 00000000..df2f4d7e --- /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 0f6ad187..ec24f3f3 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; } @@ -80,4 +80,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 711d4cbf014166650dd9b2f8fcb08c8447aa95c2 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 df2f4d7e..0068cab5 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 fbf73034ddfc87fc8055d00047fb71947af5bcce 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 3/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 ec24f3f3..7ed06343 100644 --- a/lib/Trash/TrashManager.php +++ b/lib/Trash/TrashManager.php @@ -88,7 +88,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)) From 0e36f901b820a0a870556ea718dc0896c55b35e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 11 Jan 2024 16:00:53 +0100 Subject: [PATCH 4/4] Update psalm baseline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixing the error requires newer psalm which brings in other errors, so for stable27 just silence the warning Signed-off-by: Côme Chilliet --- tests/psalm-baseline.xml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tests/psalm-baseline.xml b/tests/psalm-baseline.xml index dd0f8599..2e3b4e50 100644 --- a/tests/psalm-baseline.xml +++ b/tests/psalm-baseline.xml @@ -17,16 +17,13 @@ - + + NodeRenamedListener::class + + BeforeTemplateRenderedEvent - CircleDestroyedEvent - - - array{id: mixed, mount_point: mixed, groups: array<empty, empty>|mixed, quota: int, size: int|mixed, acl: bool}|false - - $this->__call(__FUNCTION__, func_get_args())