Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert "[stable30] fix: Handle copy of folders containing live photos" #49825

Merged
merged 1 commit into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading