Skip to content

Commit

Permalink
fix(files): Do not require files_trashbin in live photo sync listener
Browse files Browse the repository at this point in the history
Fix #43299

Signed-off-by: Louis Chemineau <[email protected]>
  • Loading branch information
artonge committed Apr 3, 2024
1 parent 927fd65 commit b760db9
Show file tree
Hide file tree
Showing 9 changed files with 223 additions and 131 deletions.
1 change: 1 addition & 0 deletions apps/files/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
'OCA\\Files\\ResponseDefinitions' => $baseDir . '/../lib/ResponseDefinitions.php',
'OCA\\Files\\Search\\FilesSearchProvider' => $baseDir . '/../lib/Search/FilesSearchProvider.php',
'OCA\\Files\\Service\\DirectEditingService' => $baseDir . '/../lib/Service/DirectEditingService.php',
'OCA\\Files\\Service\\LivePhotosService' => $baseDir . '/../lib/Service/LivePhotosService.php',
'OCA\\Files\\Service\\OwnershipTransferService' => $baseDir . '/../lib/Service/OwnershipTransferService.php',
'OCA\\Files\\Service\\TagService' => $baseDir . '/../lib/Service/TagService.php',
'OCA\\Files\\Service\\UserConfig' => $baseDir . '/../lib/Service/UserConfig.php',
Expand Down
1 change: 1 addition & 0 deletions apps/files/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ class ComposerStaticInitFiles
'OCA\\Files\\ResponseDefinitions' => __DIR__ . '/..' . '/../lib/ResponseDefinitions.php',
'OCA\\Files\\Search\\FilesSearchProvider' => __DIR__ . '/..' . '/../lib/Search/FilesSearchProvider.php',
'OCA\\Files\\Service\\DirectEditingService' => __DIR__ . '/..' . '/../lib/Service/DirectEditingService.php',
'OCA\\Files\\Service\\LivePhotosService' => __DIR__ . '/..' . '/../lib/Service/LivePhotosService.php',
'OCA\\Files\\Service\\OwnershipTransferService' => __DIR__ . '/..' . '/../lib/Service/OwnershipTransferService.php',
'OCA\\Files\\Service\\TagService' => __DIR__ . '/..' . '/../lib/Service/TagService.php',
'OCA\\Files\\Service\\UserConfig' => __DIR__ . '/..' . '/../lib/Service/UserConfig.php',
Expand Down
2 changes: 0 additions & 2 deletions apps/files/lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
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;
Expand Down Expand Up @@ -127,7 +126,6 @@ public function register(IRegistrationContext $context): void {
$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);
Expand Down
144 changes: 15 additions & 129 deletions apps/files/lib/Listener/SyncLivePhotosListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@

namespace OCA\Files\Listener;

use OCA\Files_Trashbin\Events\BeforeNodeRestoredEvent;
use OCA\Files_Trashbin\Trash\ITrashItem;
use OCA\Files_Trashbin\Trash\ITrashManager;
use OCA\Files\Service\LivePhotosService;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Files\Cache\CacheEntryRemovedEvent;
Expand All @@ -36,9 +34,7 @@
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<Event>
Expand All @@ -48,36 +44,38 @@ class SyncLivePhotosListener implements IEventListener {
private array $pendingRenames = [];
/** @var Array<int, bool> */
private array $pendingDeletion = [];
/** @var Array<int, bool> */
private array $pendingRestores = [];

public function __construct(
private ?Folder $userFolder,
private ?IUserSession $userSession,
private ITrashManager $trashManager,
private IFilesMetadataManager $filesMetadataManager,
private LivePhotosService $livePhotosService,
) {
}

public function handle(Event $event): void {
if ($this->userFolder === null || $this->userSession === null) {
if ($this->userFolder === null) {
return;
}

$peerFile = null;
$peerFileId = null;

if ($event instanceof BeforeNodeRenamedEvent) {
$peerFile = $this->getLivePhotoPeer($event->getSource()->getId());
} elseif ($event instanceof BeforeNodeRestoredEvent) {
$peerFile = $this->getLivePhotoPeer($event->getSource()->getId());
$peerFileId = $this->livePhotosService->getLivePhotoPeerId($event->getSource()->getId());
} elseif ($event instanceof BeforeNodeDeletedEvent) {
$peerFile = $this->getLivePhotoPeer($event->getNode()->getId());
$peerFileId = $this->livePhotosService->getLivePhotoPeerId($event->getNode()->getId());
} elseif ($event instanceof CacheEntryRemovedEvent) {
$peerFile = $this->getLivePhotoPeer($event->getFileId());
$peerFileId = $this->livePhotosService->getLivePhotoPeerId($event->getFileId());
}

if ($peerFileId === null) {
return; // Not a live photo.
}

// Check the user's folder.
$peerFile = $this->userFolder->getById($peerFileId)[0];

if ($peerFile === null) {
return; // not a Live Photo
return; // Peer file not found.
}

if ($event instanceof BeforeNodeRenamedEvent) {
Expand All @@ -86,8 +84,6 @@ public function handle(Event $event): void {
$this->handleDeletion($event, $peerFile);
} elseif ($event instanceof CacheEntryRemovedEvent) {
$peerFile->delete();
} elseif ($event instanceof BeforeNodeRestoredEvent) {
$this->handleRestore($event, $peerFile);
}
}

Expand Down Expand Up @@ -164,114 +160,4 @@ private function handleDeletion(BeforeNodeDeletedEvent $event, Node $peerFile):
}
return;
}

/**
* 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);
}
}
}

/**
* Helper method to get the associated live photo file.
* We first look for it in the user folder, and if we
* cannot find it here, we look for it in the user's trashbin.
*/
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;
}

/**
* 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;
}
}
52 changes: 52 additions & 0 deletions apps/files/lib/Service/LivePhotosService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

declare(strict_types=1);
/**
* @copyright Copyright (c) 2024 Louis Chemineau <[email protected]>
*
* @author Louis Chemineau <[email protected]>
*
* @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\Service;

use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException;
use OCP\FilesMetadata\IFilesMetadataManager;

class LivePhotosService {
public function __construct(
private IFilesMetadataManager $filesMetadataManager,
) {
}

/**
* Get the associated live photo for a given file id
*/
public function getLivePhotoPeerId(int $fileId): ?int {
try {
$metadata = $this->filesMetadataManager->getMetadata($fileId);
} catch (FilesMetadataNotFoundException $ex) {
return null;
}

if (!$metadata->hasKey('files-live-photo')) {
return null;
}

return (int)$metadata->getString('files-live-photo');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
'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',
Expand Down
1 change: 1 addition & 0 deletions apps/files_trashbin/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
4 changes: 4 additions & 0 deletions apps/files_trashbin/lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@
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;
Expand Down Expand Up @@ -62,6 +64,8 @@ public function register(IRegistrationContext $context): void {
LoadAdditionalScriptsEvent::class,
LoadAdditionalScripts::class
);

$context->registerEventListener(BeforeNodeRestoredEvent::class, SyncLivePhotosListener::class);
}

public function boot(IBootContext $context): void {
Expand Down
Loading

0 comments on commit b760db9

Please sign in to comment.