Skip to content

Commit

Permalink
Merge pull request #49825 from nextcloud/revert-49650-backport/49293/…
Browse files Browse the repository at this point in the history
…stable30

Revert "[stable30] fix: Handle copy of folders containing live photos"
  • Loading branch information
skjnldsv authored Dec 12, 2024
2 parents d8c3425 + cabedbf commit 9e1b516
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 289 deletions.
8 changes: 1 addition & 7 deletions apps/dav/lib/Connector/Sabre/Directory.php
Original file line number Diff line number Diff line change
Expand Up @@ -445,13 +445,7 @@ public function copyInto($targetName, $sourcePath, INode $sourceNode) {
throw new InvalidPath($ex->getMessage());
}

$copyOkay = $this->fileView->copy($sourcePath, $destinationPath);

if (!$copyOkay) {
throw new \Sabre\DAV\Exception\Forbidden('Copy did not proceed');
}

return true;
return $this->fileView->copy($sourcePath, $destinationPath);
} catch (StorageNotAvailableException $e) {
throw new ServiceUnavailable($e->getMessage(), $e->getCode(), $e);
} catch (ForbiddenException $ex) {
Expand Down
184 changes: 67 additions & 117 deletions apps/files/lib/Listener/SyncLivePhotosListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,18 @@

namespace OCA\Files\Listener;

use Exception;
use OC\Files\Node\NonExistingFile;
use OC\Files\Node\NonExistingFolder;
use OC\Files\View;
use OC\FilesMetadata\Model\FilesMetadata;
use OCA\Files\Service\LivePhotosService;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Exceptions\AbortedEventException;
use OCP\Files\Cache\CacheEntryRemovedEvent;
use OCP\Files\Events\Node\AbstractNodesEvent;
use OCP\Files\Events\Node\BeforeNodeCopiedEvent;
use OCP\Files\Events\Node\BeforeNodeDeletedEvent;
use OCP\Files\Events\Node\BeforeNodeRenamedEvent;
use OCP\Files\Events\Node\NodeCopiedEvent;
use OCP\Files\File;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
use OCP\Files\NotFoundException;
use OCP\FilesMetadata\IFilesMetadataManager;
Expand All @@ -42,8 +37,6 @@ public function __construct(
private ?Folder $userFolder,
private IFilesMetadataManager $filesMetadataManager,
private LivePhotosService $livePhotosService,
private IRootFolder $rootFolder,
private View $view,
) {
}

Expand All @@ -52,47 +45,61 @@ public function handle(Event $event): void {
return;
}

if ($event instanceof BeforeNodeCopiedEvent || $event instanceof NodeCopiedEvent) {
$this->handleCopyRecursive($event, $event->getSource(), $event->getTarget());
} else {
$peerFileId = null;

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

if ($peerFileId === null) {
return; // Not a live photo.
}
if ($event instanceof BeforeNodeRenamedEvent) {
$peerFileId = $this->livePhotosService->getLivePhotoPeerId($event->getSource()->getId());
} elseif ($event instanceof BeforeNodeDeletedEvent) {
$peerFileId = $this->livePhotosService->getLivePhotoPeerId($event->getNode()->getId());
} elseif ($event instanceof CacheEntryRemovedEvent) {
$peerFileId = $this->livePhotosService->getLivePhotoPeerId($event->getFileId());
} elseif ($event instanceof BeforeNodeCopiedEvent || $event instanceof NodeCopiedEvent) {
$peerFileId = $this->livePhotosService->getLivePhotoPeerId($event->getSource()->getId());
}

// Check the user's folder.
$peerFile = $this->userFolder->getFirstNodeById($peerFileId);
if ($peerFileId === null) {
return; // Not a live photo.
}

if ($peerFile === null) {
return; // Peer file not found.
}
// Check the user's folder.
$peerFile = $this->userFolder->getFirstNodeById($peerFileId);

if ($event instanceof BeforeNodeRenamedEvent) {
$this->runMoveOrCopyChecks($event->getSource(), $event->getTarget(), $peerFile);
$this->handleMove($event->getSource(), $event->getTarget(), $peerFile);
} elseif ($event instanceof BeforeNodeDeletedEvent) {
$this->handleDeletion($event, $peerFile);
} elseif ($event instanceof CacheEntryRemovedEvent) {
$peerFile->delete();
}
if ($peerFile === null) {
return; // Peer file not found.
}

if ($event instanceof BeforeNodeRenamedEvent) {
$this->handleMove($event, $peerFile, false);
} elseif ($event instanceof BeforeNodeDeletedEvent) {
$this->handleDeletion($event, $peerFile);
} elseif ($event instanceof CacheEntryRemovedEvent) {
$peerFile->delete();
} elseif ($event instanceof BeforeNodeCopiedEvent) {
$this->handleMove($event, $peerFile, true);
} elseif ($event instanceof NodeCopiedEvent) {
$this->handleCopy($event, $peerFile);
}
}

private function runMoveOrCopyChecks(Node $sourceFile, Node $targetFile, Node $peerFile): void {
/**
* During rename events, which also include move operations,
* we rename the peer file using the same name.
* The event listener being singleton, we can store the current state
* of pending renames inside the 'pendingRenames' property,
* to prevent infinite recursive.
*/
private function handleMove(AbstractNodesEvent $event, Node $peerFile, bool $prepForCopyOnly = false): void {
if (!($event instanceof BeforeNodeCopiedEvent) &&
!($event instanceof BeforeNodeRenamedEvent)) {
return;
}

$sourceFile = $event->getSource();
$targetFile = $event->getTarget();
$targetParent = $targetFile->getParent();
$sourceExtension = $sourceFile->getExtension();
$peerFileExtension = $peerFile->getExtension();
$targetName = $targetFile->getName();
$peerTargetName = substr($targetName, 0, -strlen($sourceExtension)) . $peerFileExtension;

if (!str_ends_with($targetName, "." . $sourceExtension)) {
throw new AbortedEventException('Cannot change the extension of a Live Photo');
Expand All @@ -104,31 +111,15 @@ private function runMoveOrCopyChecks(Node $sourceFile, Node $targetFile, Node $p
} catch (NotFoundException) {
}

if (!($targetParent instanceof NonExistingFolder)) {
try {
$targetParent->get($peerTargetName);
throw new AbortedEventException('A file already exist at destination path of the Live Photo');
} catch (NotFoundException) {
}
}
}

/**
* During rename events, which also include move operations,
* we rename the peer file using the same name.
* The event listener being singleton, we can store the current state
* of pending renames inside the 'pendingRenames' property,
* to prevent infinite recursive.
*/
private function handleMove(Node $sourceFile, Node $targetFile, Node $peerFile): void {
$targetParent = $targetFile->getParent();
$sourceExtension = $sourceFile->getExtension();
$peerFileExtension = $peerFile->getExtension();
$targetName = $targetFile->getName();
$peerTargetName = substr($targetName, 0, -strlen($sourceExtension)) . $peerFileExtension;
try {
$targetParent->get($peerTargetName);
throw new AbortedEventException('A file already exist at destination path of the Live Photo');
} catch (NotFoundException) {
}

// in case the rename was initiated from this listener, we stop right now
if (in_array($peerFile->getId(), $this->pendingRenames)) {
if ($prepForCopyOnly || in_array($peerFile->getId(), $this->pendingRenames)) {
return;
}

Expand All @@ -139,37 +130,39 @@ private function handleMove(Node $sourceFile, Node $targetFile, Node $peerFile):
throw new AbortedEventException($ex->getMessage());
}

$this->pendingRenames = array_diff($this->pendingRenames, [$sourceFile->getId()]);
array_diff($this->pendingRenames, [$sourceFile->getId()]);
}


/**
* handle copy, we already know if it is doable from BeforeNodeCopiedEvent, so we just copy the linked file
*
* @param NodeCopiedEvent $event
* @param Node $peerFile
*/
private function handleCopy(File $sourceFile, File $targetFile, File $peerFile): void {
private function handleCopy(NodeCopiedEvent $event, Node $peerFile): void {
$sourceFile = $event->getSource();
$sourceExtension = $sourceFile->getExtension();
$peerFileExtension = $peerFile->getExtension();
$targetFile = $event->getTarget();
$targetParent = $targetFile->getParent();
$targetName = $targetFile->getName();
$peerTargetName = substr($targetName, 0, -strlen($sourceExtension)) . $peerFileExtension;


if ($targetParent->nodeExists($peerTargetName)) {
// If the copy was a folder copy, then the peer file already exists.
$targetPeerFile = $targetParent->get($peerTargetName);
} else {
// If the copy was a file copy, then we need to create the peer file.
$targetPeerFile = $peerFile->copy($targetParent->getPath() . '/' . $peerTargetName);
}

/**
* let's use freshly set variable.
* we copy the file and get its id. We already have the id of the current copy
* We have everything to update metadata and keep the link between the 2 copies.
*/
$newPeerFile = $peerFile->copy($targetParent->getPath() . '/' . $peerTargetName);
/** @var FilesMetadata $targetMetadata */
$targetMetadata = $this->filesMetadataManager->getMetadata($targetFile->getId(), true);
$targetMetadata->setStorageId($targetFile->getStorage()->getCache()->getNumericStorageId());
$targetMetadata->setString('files-live-photo', (string)$targetPeerFile->getId());
$targetMetadata->setString('files-live-photo', (string)$newPeerFile->getId());
$this->filesMetadataManager->saveMetadata($targetMetadata);
/** @var FilesMetadata $peerMetadata */
$peerMetadata = $this->filesMetadataManager->getMetadata($targetPeerFile->getId(), true);
$peerMetadata->setStorageId($targetPeerFile->getStorage()->getCache()->getNumericStorageId());
$peerMetadata = $this->filesMetadataManager->getMetadata($newPeerFile->getId(), true);
$peerMetadata->setStorageId($newPeerFile->getStorage()->getCache()->getNumericStorageId());
$peerMetadata->setString('files-live-photo', (string)$targetFile->getId());
$this->filesMetadataManager->saveMetadata($peerMetadata);
}
Expand Down Expand Up @@ -200,47 +193,4 @@ private function handleDeletion(BeforeNodeDeletedEvent $event, Node $peerFile):
}
return;
}

/*
* Recursively get all the peer ids of a live photo.
* Needed when coping a folder.
*
* @param BeforeNodeCopiedEvent|NodeCopiedEvent $event
*/
private function handleCopyRecursive(Event $event, Node $sourceNode, Node $targetNode): void {
if ($sourceNode instanceof Folder && $targetNode instanceof Folder) {
foreach ($sourceNode->getDirectoryListing() as $sourceChild) {
if ($event instanceof BeforeNodeCopiedEvent) {
if ($sourceChild instanceof Folder) {
$targetChild = new NonExistingFolder($this->rootFolder, $this->view, $targetNode->getPath() . '/' . $sourceChild->getName(), null, $targetNode);
} else {
$targetChild = new NonExistingFile($this->rootFolder, $this->view, $targetNode->getPath() . '/' . $sourceChild->getName(), null, $targetNode);
}
} elseif ($event instanceof NodeCopiedEvent) {
$targetChild = $targetNode->get($sourceChild->getName());
} else {
throw new Exception('Event is type is not supported');
}

$this->handleCopyRecursive($event, $sourceChild, $targetChild);
}
} elseif ($sourceNode instanceof File && $targetNode instanceof File) {
$peerFileId = $this->livePhotosService->getLivePhotoPeerId($sourceNode->getId());
if ($peerFileId === null) {
return;
}
$peerFile = $this->userFolder->getFirstNodeById($peerFileId);
if ($peerFile === null) {
return;
}

if ($event instanceof BeforeNodeCopiedEvent) {
$this->runMoveOrCopyChecks($sourceNode, $targetNode, $peerFile);
} elseif ($event instanceof NodeCopiedEvent) {
$this->handleCopy($sourceNode, $targetNode, $peerFile);
}
} else {
throw new Exception('Source and target type are not matching');
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

use Exception;
use OC\Files\Node\NonExistingFile;
use OC\Files\Node\NonExistingFolder;
use OCA\Files_Versions\Versions\IVersionBackend;
use OCA\Files_Versions\Versions\IVersionManager;
use OCA\Files_Versions\Versions\IVersionsImporterBackend;
Expand Down Expand Up @@ -131,7 +130,7 @@ private function handleMoveOrCopy(Event $event, IUser $user, File $source, File
}

private function getNodeStorage(Node $node): IStorage {
if ($node instanceof NonExistingFile || $node instanceof NonExistingFolder) {
if ($node instanceof NonExistingFile) {
return $node->getParent()->getStorage();
} else {
return $node->getStorage();
Expand Down
2 changes: 1 addition & 1 deletion cypress/e2e/files/FilesUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export const createFolder = (folderName: string) => {

// TODO: replace by proper data-cy selectors
cy.get('[data-cy-upload-picker] .action-item__menutoggle').first().click()
cy.get('[data-cy-upload-picker-menu-entry="newFolder"] button').click()
cy.contains('.upload-picker__menu-entry button', 'New folder').click()
cy.get('[data-cy-files-new-node-dialog]').should('be.visible')
cy.get('[data-cy-files-new-node-dialog-input]').type(`{selectall}${folderName}`)
cy.get('[data-cy-files-new-node-dialog-submit]').click()
Expand Down
Loading

0 comments on commit 9e1b516

Please sign in to comment.