Skip to content

Commit 917e271

Browse files
authored
Merge pull request #41941 from nextcloud/backport/41924/stable28
[stable28] Add comment in SyncLivePhotosListener
2 parents ed809fd + 263f9e1 commit 917e271

File tree

1 file changed

+131
-93
lines changed

1 file changed

+131
-93
lines changed

apps/files/lib/Listener/SyncLivePhotosListener.php

Lines changed: 131 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
use OCP\Files\Cache\CacheEntryRemovedEvent;
3333
use OCP\Files\Events\Node\BeforeNodeDeletedEvent;
3434
use OCP\Files\Events\Node\BeforeNodeRenamedEvent;
35+
use OCP\Files\File;
3536
use OCP\Files\Folder;
3637
use OCP\Files\Node;
3738
use OCP\Files\NotFoundException;
@@ -74,127 +75,159 @@ public function handle(Event $event): void {
7475
$peerFile = $this->getLivePhotoPeer($event->getNode()->getId());
7576
} elseif ($event instanceof CacheEntryRemovedEvent) {
7677
$peerFile = $this->getLivePhotoPeer($event->getFileId());
77-
} else {
78-
return;
7978
}
8079

8180
if ($peerFile === null) {
8281
return;
8382
}
8483

8584
if ($event instanceof BeforeNodeRenamedEvent) {
86-
$sourceFile = $event->getSource();
87-
$targetFile = $event->getTarget();
88-
$targetParent = $targetFile->getParent();
89-
$sourceExtension = $sourceFile->getExtension();
90-
$peerFileExtension = $peerFile->getExtension();
91-
$targetName = $targetFile->getName();
92-
$targetPath = $targetFile->getPath();
93-
94-
// Prevent rename of the .mov file if the peer file do not have the same path.
95-
if ($sourceFile->getMimetype() === 'video/quicktime') {
96-
$peerFilePath = $this->pendingRenames[$peerFile->getId()] ?? $peerFile->getPath();
97-
$targetPathWithoutExtension = preg_replace("/\.$sourceExtension$/", '', $targetPath);
98-
$peerFilePathWithoutExtension = preg_replace("/\.$peerFileExtension$/", '', $peerFilePath);
99-
100-
if ($targetPathWithoutExtension !== $peerFilePathWithoutExtension) {
101-
$event->abortOperation(new NotPermittedException("The video part of a live photo need to have the same name as the image"));
102-
}
103-
104-
unset($this->pendingRenames[$peerFile->getId()]);
105-
return;
106-
}
107-
108-
if (!str_ends_with($targetName, ".".$sourceExtension)) {
109-
$event->abortOperation(new NotPermittedException("Cannot change the extension of a live photo"));
110-
}
85+
$this->handleMove($event, $peerFile);
86+
} elseif ($event instanceof BeforeNodeDeletedEvent) {
87+
$this->handleDeletion($event, $peerFile);
88+
} elseif ($event instanceof CacheEntryRemovedEvent) {
89+
$peerFile->delete();
90+
} elseif ($event instanceof BeforeNodeRestoredEvent) {
91+
$this->handleRestore($event, $peerFile);
92+
}
93+
}
11194

112-
try {
113-
$targetParent->get($targetName);
114-
$event->abortOperation(new NotPermittedException("A file already exist at destination path"));
115-
} catch (NotFoundException $ex) {
116-
}
117-
try {
118-
$peerTargetName = preg_replace("/\.$sourceExtension$/", '.mov', $targetName);
119-
$targetParent->get($peerTargetName);
120-
$event->abortOperation(new NotPermittedException("A file already exist at destination path"));
121-
} catch (NotFoundException $ex) {
95+
/**
96+
* During rename events, which also include move operations,
97+
* we rename the peer file using the same name.
98+
* This means that a move operation on the .jpg will trigger
99+
* another recursive one for the .mov.
100+
* Move operations on the .mov file directly are currently blocked.
101+
* The event listener being singleton, we can store the current state
102+
* of pending renames inside the 'pendingRenames' property,
103+
* to prevent infinite recursivity.
104+
*/
105+
private function handleMove(BeforeNodeRenamedEvent $event, Node $peerFile): void {
106+
$sourceFile = $event->getSource();
107+
$targetFile = $event->getTarget();
108+
$targetParent = $targetFile->getParent();
109+
$sourceExtension = $sourceFile->getExtension();
110+
$peerFileExtension = $peerFile->getExtension();
111+
$targetName = $targetFile->getName();
112+
$targetPath = $targetFile->getPath();
113+
114+
// Prevent rename of the .mov file if the peer file do not have the same path.
115+
if ($sourceFile->getMimetype() === 'video/quicktime') {
116+
$peerFilePath = $this->pendingRenames[$peerFile->getId()] ?? $peerFile->getPath();
117+
$targetPathWithoutExtension = preg_replace("/\.$sourceExtension$/", '', $targetPath);
118+
$peerFilePathWithoutExtension = preg_replace("/\.$peerFileExtension$/", '', $peerFilePath);
119+
120+
if ($targetPathWithoutExtension !== $peerFilePathWithoutExtension) {
121+
$event->abortOperation(new NotPermittedException("The video part of a live photo need to have the same name as the image"));
122122
}
123123

124-
$peerTargetPath = preg_replace("/\.$sourceExtension$/", '.mov', $targetPath);
125-
$this->pendingRenames[$sourceFile->getId()] = $targetPath;
126-
try {
127-
$peerFile->move($peerTargetPath);
128-
} catch (\Throwable $ex) {
129-
$event->abortOperation($ex);
130-
}
124+
unset($this->pendingRenames[$peerFile->getId()]);
131125
return;
132126
}
133127

134-
if ($event instanceof BeforeNodeDeletedEvent) {
135-
$deletedFile = $event->getNode();
136-
if ($deletedFile->getMimetype() === 'video/quicktime') {
137-
if (isset($this->pendingDeletion[$peerFile->getId()])) {
138-
unset($this->pendingDeletion[$peerFile->getId()]);
139-
return;
140-
} else {
141-
$event->abortOperation(new NotPermittedException("Cannot delete the video part of a live photo"));
142-
}
143-
} else {
144-
$this->pendingDeletion[$deletedFile->getId()] = true;
145-
try {
146-
$peerFile->delete();
147-
} catch (\Throwable $ex) {
148-
$event->abortOperation($ex);
149-
}
150-
}
151-
return;
128+
if (!str_ends_with($targetName, ".".$sourceExtension)) {
129+
$event->abortOperation(new NotPermittedException("Cannot change the extension of a live photo"));
152130
}
153131

154-
if ($event instanceof CacheEntryRemovedEvent) {
155-
$peerFile->delete();
132+
try {
133+
$targetParent->get($targetName);
134+
$event->abortOperation(new NotPermittedException("A file already exist at destination path"));
135+
} catch (NotFoundException $ex) {
136+
}
137+
try {
138+
$peerTargetName = preg_replace("/\.$sourceExtension$/", '.mov', $targetName);
139+
$targetParent->get($peerTargetName);
140+
$event->abortOperation(new NotPermittedException("A file already exist at destination path"));
141+
} catch (NotFoundException $ex) {
156142
}
157143

158-
if ($event instanceof BeforeNodeRestoredEvent) {
159-
$sourceFile = $event->getSource();
144+
$peerTargetPath = preg_replace("/\.$sourceExtension$/", '.mov', $targetPath);
145+
$this->pendingRenames[$sourceFile->getId()] = $targetPath;
146+
try {
147+
$peerFile->move($peerTargetPath);
148+
} catch (\Throwable $ex) {
149+
$event->abortOperation($ex);
150+
}
151+
return;
152+
}
160153

161-
if ($sourceFile->getMimetype() === 'video/quicktime') {
162-
if (isset($this->pendingRestores[$peerFile->getId()])) {
163-
unset($this->pendingRestores[$peerFile->getId()]);
164-
return;
165-
} else {
166-
$event->abortOperation(new NotPermittedException("Cannot restore the video part of a live photo"));
167-
}
154+
/**
155+
* During deletion event, we trigger another recursive delete on the peer file.
156+
* Delete operations on the .mov file directly are currently blocked.
157+
* The event listener being singleton, we can store the current state
158+
* of pending deletions inside the 'pendingDeletions' property,
159+
* to prevent infinite recursivity.
160+
*/
161+
private function handleDeletion(BeforeNodeDeletedEvent $event, Node $peerFile): void {
162+
$deletedFile = $event->getNode();
163+
if ($deletedFile->getMimetype() === 'video/quicktime') {
164+
if (isset($this->pendingDeletion[$peerFile->getId()])) {
165+
unset($this->pendingDeletion[$peerFile->getId()]);
166+
return;
168167
} else {
169-
$user = $this->userSession->getUser();
170-
if ($user === null) {
171-
return;
172-
}
168+
$event->abortOperation(new NotPermittedException("Cannot delete the video part of a live photo"));
169+
}
170+
} else {
171+
$this->pendingDeletion[$deletedFile->getId()] = true;
172+
try {
173+
$peerFile->delete();
174+
} catch (\Throwable $ex) {
175+
$event->abortOperation($ex);
176+
}
177+
}
178+
return;
179+
}
173180

174-
$peerTrashItem = $this->trashManager->getTrashNodeById($user, $peerFile->getId());
181+
/**
182+
* During restore event, we trigger another recursive restore on the peer file.
183+
* Restore operations on the .mov file directly are currently blocked.
184+
* The event listener being singleton, we can store the current state
185+
* of pending restores inside the 'pendingRestores' property,
186+
* to prevent infinite recursivity.
187+
*/
188+
private function handleRestore(BeforeNodeRestoredEvent $event, Node $peerFile): void {
189+
$sourceFile = $event->getSource();
190+
191+
if ($sourceFile->getMimetype() === 'video/quicktime') {
192+
if (isset($this->pendingRestores[$peerFile->getId()])) {
193+
unset($this->pendingRestores[$peerFile->getId()]);
194+
return;
195+
} else {
196+
$event->abortOperation(new NotPermittedException("Cannot restore the video part of a live photo"));
197+
}
198+
} else {
199+
$user = $this->userSession->getUser();
200+
if ($user === null) {
201+
return;
202+
}
175203

176-
// Peer file in not in the bin, no need to restore it.
177-
if ($peerTrashItem === null) {
178-
return;
179-
}
204+
$peerTrashItem = $this->trashManager->getTrashNodeById($user, $peerFile->getId());
205+
// Peer file is not in the bin, no need to restore it.
206+
if ($peerTrashItem === null) {
207+
return;
208+
}
180209

181-
$trashRoot = $this->trashManager->listTrashRoot($user);
182-
$trashItem = $this->getTrashItem($trashRoot, $peerFile->getInternalPath());
210+
$trashRoot = $this->trashManager->listTrashRoot($user);
211+
$trashItem = $this->getTrashItem($trashRoot, $peerFile->getInternalPath());
183212

184-
if ($trashItem === null) {
185-
$event->abortOperation(new NotFoundException("Couldn't find peer file in trashbin"));
186-
}
213+
if ($trashItem === null) {
214+
$event->abortOperation(new NotFoundException("Couldn't find peer file in trashbin"));
215+
}
187216

188-
$this->pendingRestores[$sourceFile->getId()] = true;
189-
try {
190-
$this->trashManager->restoreItem($trashItem);
191-
} catch (\Throwable $ex) {
192-
$event->abortOperation($ex);
193-
}
217+
$this->pendingRestores[$sourceFile->getId()] = true;
218+
try {
219+
$this->trashManager->restoreItem($trashItem);
220+
} catch (\Throwable $ex) {
221+
$event->abortOperation($ex);
194222
}
195223
}
196224
}
197225

226+
/**
227+
* Helper method to get the associated live photo file.
228+
* We first look for it in the user folder, and if we
229+
* cannot find it here, we look for it in the user's trashbin.
230+
*/
198231
private function getLivePhotoPeer(int $nodeId): ?Node {
199232
if ($this->userFolder === null || $this->userSession === null) {
200233
return null;
@@ -231,6 +264,11 @@ private function getLivePhotoPeer(int $nodeId): ?Node {
231264
return null;
232265
}
233266

267+
/**
268+
* There is currently no method to restore a file based on its fileId or path.
269+
* So we have to manually find a ITrashItem from the trash item list.
270+
* TODO: This should be replaced by a proper method in the TrashManager.
271+
*/
234272
private function getTrashItem(array $trashFolder, string $path): ?ITrashItem {
235273
foreach($trashFolder as $trashItem) {
236274
if (str_starts_with($path, "files_trashbin/files".$trashItem->getTrashPath())) {

0 commit comments

Comments
 (0)