aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files_trashbin
diff options
context:
space:
mode:
authorLouis Chemineau <louis@chmn.me>2024-03-12 18:47:14 +0100
committerLouis Chemineau <louis@chmn.me>2024-03-14 10:55:44 +0100
commit2de9880d79adeeab0e0a01ce3992de404a0c274d (patch)
tree1665d95b3fe23d64a2cd111acd64c90cc4e2658e /apps/files_trashbin
parentd9e09d5883e6cca4c61c874e1518dc92dd0a1bde (diff)
downloadnextcloud-server-2de9880d79adeeab0e0a01ce3992de404a0c274d.tar.gz
nextcloud-server-2de9880d79adeeab0e0a01ce3992de404a0c274d.zip
fix(files): Do not require files_trashbin in live photo sync listener
Fix https://github.com/nextcloud/server/issues/43299 Signed-off-by: Louis Chemineau <louis@chmn.me>
Diffstat (limited to 'apps/files_trashbin')
-rw-r--r--apps/files_trashbin/composer/composer/autoload_classmap.php1
-rw-r--r--apps/files_trashbin/composer/composer/autoload_static.php1
-rw-r--r--apps/files_trashbin/lib/AppInfo/Application.php4
-rw-r--r--apps/files_trashbin/lib/Listeners/SyncLivePhotosListener.php148
4 files changed, 154 insertions, 0 deletions
diff --git a/apps/files_trashbin/composer/composer/autoload_classmap.php b/apps/files_trashbin/composer/composer/autoload_classmap.php
index c243d1d291c..f1bc38f1492 100644
--- a/apps/files_trashbin/composer/composer/autoload_classmap.php
+++ b/apps/files_trashbin/composer/composer/autoload_classmap.php
@@ -24,6 +24,7 @@ return array(
'OCA\\Files_Trashbin\\Helper' => $baseDir . '/../lib/Helper.php',
'OCA\\Files_Trashbin\\Hooks' => $baseDir . '/../lib/Hooks.php',
'OCA\\Files_Trashbin\\Listeners\\LoadAdditionalScripts' => $baseDir . '/../lib/Listeners/LoadAdditionalScripts.php',
+ 'OCA\\Files_Trashbin\\Listeners\\SyncLivePhotosListener' => $baseDir . '/../lib/Listeners/SyncLivePhotosListener.php',
'OCA\\Files_Trashbin\\Migration\\Version1010Date20200630192639' => $baseDir . '/../lib/Migration/Version1010Date20200630192639.php',
'OCA\\Files_Trashbin\\Sabre\\AbstractTrash' => $baseDir . '/../lib/Sabre/AbstractTrash.php',
'OCA\\Files_Trashbin\\Sabre\\AbstractTrashFile' => $baseDir . '/../lib/Sabre/AbstractTrashFile.php',
diff --git a/apps/files_trashbin/composer/composer/autoload_static.php b/apps/files_trashbin/composer/composer/autoload_static.php
index ffa3916460b..4c4b33574af 100644
--- a/apps/files_trashbin/composer/composer/autoload_static.php
+++ b/apps/files_trashbin/composer/composer/autoload_static.php
@@ -39,6 +39,7 @@ class ComposerStaticInitFiles_Trashbin
'OCA\\Files_Trashbin\\Helper' => __DIR__ . '/..' . '/../lib/Helper.php',
'OCA\\Files_Trashbin\\Hooks' => __DIR__ . '/..' . '/../lib/Hooks.php',
'OCA\\Files_Trashbin\\Listeners\\LoadAdditionalScripts' => __DIR__ . '/..' . '/../lib/Listeners/LoadAdditionalScripts.php',
+ 'OCA\\Files_Trashbin\\Listeners\\SyncLivePhotosListener' => __DIR__ . '/..' . '/../lib/Listeners/SyncLivePhotosListener.php',
'OCA\\Files_Trashbin\\Migration\\Version1010Date20200630192639' => __DIR__ . '/..' . '/../lib/Migration/Version1010Date20200630192639.php',
'OCA\\Files_Trashbin\\Sabre\\AbstractTrash' => __DIR__ . '/..' . '/../lib/Sabre/AbstractTrash.php',
'OCA\\Files_Trashbin\\Sabre\\AbstractTrashFile' => __DIR__ . '/..' . '/../lib/Sabre/AbstractTrashFile.php',
diff --git a/apps/files_trashbin/lib/AppInfo/Application.php b/apps/files_trashbin/lib/AppInfo/Application.php
index 0f36fe37d29..e12a6ca8af9 100644
--- a/apps/files_trashbin/lib/AppInfo/Application.php
+++ b/apps/files_trashbin/lib/AppInfo/Application.php
@@ -28,8 +28,10 @@ namespace OCA\Files_Trashbin\AppInfo;
use OCA\DAV\Connector\Sabre\Principal;
use OCA\Files\Event\LoadAdditionalScriptsEvent;
use OCA\Files_Trashbin\Capabilities;
+use OCA\Files_Trashbin\Events\BeforeNodeRestoredEvent;
use OCA\Files_Trashbin\Expiration;
use OCA\Files_Trashbin\Listeners\LoadAdditionalScripts;
+use OCA\Files_Trashbin\Listeners\SyncLivePhotosListener;
use OCA\Files_Trashbin\Trash\ITrashManager;
use OCA\Files_Trashbin\Trash\TrashManager;
use OCA\Files_Trashbin\UserMigration\TrashbinMigrator;
@@ -62,6 +64,8 @@ class Application extends App implements IBootstrap {
LoadAdditionalScriptsEvent::class,
LoadAdditionalScripts::class
);
+
+ $context->registerEventListener(BeforeNodeRestoredEvent::class, SyncLivePhotosListener::class);
}
public function boot(IBootContext $context): void {
diff --git a/apps/files_trashbin/lib/Listeners/SyncLivePhotosListener.php b/apps/files_trashbin/lib/Listeners/SyncLivePhotosListener.php
new file mode 100644
index 00000000000..8226f7e40a6
--- /dev/null
+++ b/apps/files_trashbin/lib/Listeners/SyncLivePhotosListener.php
@@ -0,0 +1,148 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2023 Louis Chemineau <louis@chmn.me>
+ *
+ * @author Louis Chemineau <louis@chmn.me>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\Files_Trashbin\Listeners;
+
+use OCA\Files\Service\LivePhotosService;
+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\Folder;
+use OCP\Files\Node;
+use OCP\Files\NotFoundException;
+use OCP\Files\NotPermittedException;
+use OCP\IUserSession;
+
+/**
+ * @template-implements IEventListener<BeforeNodeRestoredEvent>
+ */
+class SyncLivePhotosListener implements IEventListener {
+ /** @var Array<int, bool> */
+ private array $pendingRestores = [];
+
+ public function __construct(
+ private ?IUserSession $userSession,
+ private ITrashManager $trashManager,
+ private LivePhotosService $livePhotosService,
+ ) {
+ }
+
+ public function handle(Event $event): void {
+ if ($this->userSession === null) {
+ return;
+ }
+
+ /** @var BeforeNodeRestoredEvent $event */
+ $peerFileId = $this->livePhotosService->getLivePhotoPeerId($event->getSource()->getId());
+
+ if ($peerFileId === null) {
+ return; // Not a live photo.
+ }
+
+ // Check the user's trashbin.
+ $user = $this->userSession->getUser();
+ if ($user === null) {
+ return;
+ }
+
+ $peerFile = $this->trashManager->getTrashNodeById($user, $peerFileId);
+
+ if ($peerFile === null) {
+ return; // Peer file not found.
+ }
+
+ $this->handleRestore($event, $peerFile);
+ }
+
+ /**
+ * During restore event, we trigger another recursive restore on the peer file.
+ * Restore operations on the .mov file directly are currently blocked.
+ * The event listener being singleton, we can store the current state
+ * of pending restores inside the 'pendingRestores' property,
+ * to prevent infinite recursivity.
+ */
+ private function handleRestore(BeforeNodeRestoredEvent $event, Node $peerFile): void {
+ $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 is 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);
+ }
+ }
+ }
+
+ /**
+ * There is currently no method to restore a file based on its fileId or path.
+ * So we have to manually find a ITrashItem from the trash item list.
+ * TODO: This should be replaced by a proper method in the TrashManager.
+ */
+ 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;
+ }
+}