Skip to content

Commit 35bf478

Browse files
committed
Synchronize operation on live photo files
Signed-off-by: Louis Chemineau <[email protected]>
1 parent b213fc7 commit 35bf478

File tree

13 files changed

+477
-19
lines changed

13 files changed

+477
-19
lines changed

apps/files/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
'OCA\\Files\\Helper' => $baseDir . '/../lib/Helper.php',
6060
'OCA\\Files\\Listener\\LoadSidebarListener' => $baseDir . '/../lib/Listener/LoadSidebarListener.php',
6161
'OCA\\Files\\Listener\\RenderReferenceEventListener' => $baseDir . '/../lib/Listener/RenderReferenceEventListener.php',
62+
'OCA\\Files\\Listener\\SyncLivePhotosListener' => $baseDir . '/../lib/Listener/SyncLivePhotosListener.php',
6263
'OCA\\Files\\Migration\\Version11301Date20191205150729' => $baseDir . '/../lib/Migration/Version11301Date20191205150729.php',
6364
'OCA\\Files\\Migration\\Version12101Date20221011153334' => $baseDir . '/../lib/Migration/Version12101Date20221011153334.php',
6465
'OCA\\Files\\Notification\\Notifier' => $baseDir . '/../lib/Notification/Notifier.php',

apps/files/composer/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ class ComposerStaticInitFiles
7474
'OCA\\Files\\Helper' => __DIR__ . '/..' . '/../lib/Helper.php',
7575
'OCA\\Files\\Listener\\LoadSidebarListener' => __DIR__ . '/..' . '/../lib/Listener/LoadSidebarListener.php',
7676
'OCA\\Files\\Listener\\RenderReferenceEventListener' => __DIR__ . '/..' . '/../lib/Listener/RenderReferenceEventListener.php',
77+
'OCA\\Files\\Listener\\SyncLivePhotosListener' => __DIR__ . '/..' . '/../lib/Listener/SyncLivePhotosListener.php',
7778
'OCA\\Files\\Migration\\Version11301Date20191205150729' => __DIR__ . '/..' . '/../lib/Migration/Version11301Date20191205150729.php',
7879
'OCA\\Files\\Migration\\Version12101Date20221011153334' => __DIR__ . '/..' . '/../lib/Migration/Version12101Date20221011153334.php',
7980
'OCA\\Files\\Notification\\Notifier' => __DIR__ . '/..' . '/../lib/Notification/Notifier.php',

apps/files/composer/composer/installed.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
'name' => '__root__',
44
'pretty_version' => 'dev-master',
55
'version' => 'dev-master',
6-
'reference' => 'b1797842784b250fb01ed5e3bf130705eb94751b',
6+
'reference' => 'ba1af2b22e5409c62ea2bdf7eb0e13c282ed70e8',
77
'type' => 'library',
88
'install_path' => __DIR__ . '/../',
99
'aliases' => array(),
@@ -13,7 +13,7 @@
1313
'__root__' => array(
1414
'pretty_version' => 'dev-master',
1515
'version' => 'dev-master',
16-
'reference' => 'b1797842784b250fb01ed5e3bf130705eb94751b',
16+
'reference' => 'ba1af2b22e5409c62ea2bdf7eb0e13c282ed70e8',
1717
'type' => 'library',
1818
'install_path' => __DIR__ . '/../',
1919
'aliases' => array(),

apps/files/lib/AppInfo/Application.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,13 @@
4343
use OCA\Files\Event\LoadSidebar;
4444
use OCA\Files\Listener\LoadSidebarListener;
4545
use OCA\Files\Listener\RenderReferenceEventListener;
46+
use OCA\Files\Listener\SyncLivePhotosListener;
4647
use OCA\Files\Notification\Notifier;
4748
use OCA\Files\Search\FilesSearchProvider;
4849
use OCA\Files\Service\TagService;
4950
use OCA\Files\Service\UserConfig;
5051
use OCA\Files\Service\ViewConfig;
52+
use OCA\Files_Trashbin\Events\BeforeNodeRestoredEvent;
5153
use OCP\Activity\IManager as IActivityManager;
5254
use OCP\AppFramework\App;
5355
use OCP\AppFramework\Bootstrap\IBootContext;
@@ -56,6 +58,9 @@
5658
use OCP\Collaboration\Reference\RenderReferenceEvent;
5759
use OCP\Collaboration\Resources\IProviderManager;
5860
use OCP\EventDispatcher\IEventDispatcher;
61+
use OCP\Files\Cache\CacheEntryRemovedEvent;
62+
use OCP\Files\Events\Node\BeforeNodeDeletedEvent;
63+
use OCP\Files\Events\Node\BeforeNodeRenamedEvent;
5964
use OCP\IConfig;
6065
use OCP\IPreview;
6166
use OCP\IRequest;
@@ -120,6 +125,10 @@ public function register(IRegistrationContext $context): void {
120125

121126
$context->registerEventListener(LoadSidebar::class, LoadSidebarListener::class);
122127
$context->registerEventListener(RenderReferenceEvent::class, RenderReferenceEventListener::class);
128+
$context->registerEventListener(BeforeNodeRenamedEvent::class, SyncLivePhotosListener::class);
129+
$context->registerEventListener(BeforeNodeDeletedEvent::class, SyncLivePhotosListener::class);
130+
$context->registerEventListener(BeforeNodeRestoredEvent::class, SyncLivePhotosListener::class);
131+
$context->registerEventListener(CacheEntryRemovedEvent::class, SyncLivePhotosListener::class);
123132

124133
$context->registerSearchProvider(FilesSearchProvider::class);
125134

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* @copyright Copyright (c) 2022 Julius Härtl <[email protected]>
6+
*
7+
* @author Julius Härtl <[email protected]>
8+
*
9+
* @license GNU AGPL version 3 or any later version
10+
*
11+
* This program is free software: you can redistribute it and/or modify
12+
* it under the terms of the GNU Affero General Public License as
13+
* published by the Free Software Foundation, either version 3 of the
14+
* License, or (at your option) any later version.
15+
*
16+
* This program is distributed in the hope that it will be useful,
17+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
18+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19+
* GNU Affero General Public License for more details.
20+
*
21+
* You should have received a copy of the GNU Affero General Public License
22+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
23+
*/
24+
25+
namespace OCA\Files\Listener;
26+
27+
use OCA\Files_Trashbin\Events\BeforeNodeRestoredEvent;
28+
use OCA\Files_Trashbin\Trash\ITrashItem;
29+
use OCA\Files_Trashbin\Trash\ITrashManager;
30+
use OCP\EventDispatcher\Event;
31+
use OCP\EventDispatcher\IEventListener;
32+
use OCP\Files\Cache\CacheEntryRemovedEvent;
33+
use OCP\Files\Events\Node\BeforeNodeDeletedEvent;
34+
use OCP\Files\Events\Node\BeforeNodeRenamedEvent;
35+
use OCP\Files\Folder;
36+
use OCP\Files\Node;
37+
use OCP\Files\NotFoundException;
38+
use OCP\Files\NotPermittedException;
39+
use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException;
40+
use OCP\FilesMetadata\IFilesMetadataManager;
41+
use OCP\IUserSession;
42+
43+
/**
44+
* @template-implements IEventListener<Event>
45+
*/
46+
class SyncLivePhotosListener implements IEventListener {
47+
/** @var Array<int, string> */
48+
private $pendingRenames = [];
49+
/** @var Array<int, bool> */
50+
private $pendingDeletion = [];
51+
/** @var Array<int, bool> */
52+
private $pendingRestores = [];
53+
54+
public function __construct(
55+
private ?Folder $userFolder,
56+
private ?IUserSession $userSession,
57+
private ITrashManager $trashManager,
58+
private IFilesMetadataManager $filesMetadataManager,
59+
) {
60+
}
61+
62+
public function handle(Event $event): void {
63+
if ($this->userFolder === null || $this->userSession === null) {
64+
return;
65+
}
66+
67+
$peerFile = null;
68+
69+
if ($event instanceof BeforeNodeRenamedEvent) {
70+
$peerFile = $this->getLivePhotoPeer($event->getSource()->getId());
71+
} elseif ($event instanceof BeforeNodeRestoredEvent) {
72+
$peerFile = $this->getLivePhotoPeer($event->getSource()->getId());
73+
} elseif ($event instanceof BeforeNodeDeletedEvent) {
74+
$peerFile = $this->getLivePhotoPeer($event->getNode()->getId());
75+
} elseif ($event instanceof CacheEntryRemovedEvent) {
76+
$peerFile = $this->getLivePhotoPeer($event->getFileId());
77+
} else {
78+
return;
79+
}
80+
81+
if ($peerFile === null) {
82+
return;
83+
}
84+
85+
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+
}
111+
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) {
122+
}
123+
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+
}
131+
return;
132+
}
133+
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;
152+
}
153+
154+
if ($event instanceof CacheEntryRemovedEvent) {
155+
$peerFile->delete();
156+
}
157+
158+
if ($event instanceof BeforeNodeRestoredEvent) {
159+
$sourceFile = $event->getSource();
160+
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+
}
168+
} else {
169+
$user = $this->userSession->getUser();
170+
if ($user === null) {
171+
return;
172+
}
173+
174+
$peerTrashItem = $this->trashManager->getTrashNodeById($user, $peerFile->getId());
175+
176+
// Peer file in not in the bin, no need to restore it.
177+
if ($peerTrashItem === null) {
178+
return;
179+
}
180+
181+
$trashRoot = $this->trashManager->listTrashRoot($user);
182+
$trashItem = $this->getTrashItem($trashRoot, $peerFile->getInternalPath());
183+
184+
if ($trashItem === null) {
185+
$event->abortOperation(new NotFoundException("Couldn't find peer file in trashbin"));
186+
}
187+
188+
$this->pendingRestores[$sourceFile->getId()] = true;
189+
try {
190+
$this->trashManager->restoreItem($trashItem);
191+
} catch (\Throwable $ex) {
192+
$event->abortOperation($ex);
193+
}
194+
}
195+
}
196+
}
197+
198+
private function getLivePhotoPeer(int $nodeId): ?Node {
199+
if ($this->userFolder === null || $this->userSession === null) {
200+
return null;
201+
}
202+
203+
try {
204+
$metadata = $this->filesMetadataManager->getMetadata($nodeId);
205+
} catch (FilesMetadataNotFoundException $ex) {
206+
return null;
207+
}
208+
209+
if (!$metadata->hasKey('files-live-photo')) {
210+
return null;
211+
}
212+
213+
$peerFileId = (int)$metadata->getString('files-live-photo');
214+
215+
// Check the user's folder.
216+
$nodes = $this->userFolder->getById($peerFileId);
217+
if (count($nodes) !== 0) {
218+
return $nodes[0];
219+
}
220+
221+
// Check the user's trashbin.
222+
$user = $this->userSession->getUser();
223+
if ($user !== null) {
224+
$peerFile = $this->trashManager->getTrashNodeById($user, $peerFileId);
225+
if ($peerFile !== null) {
226+
return $peerFile;
227+
}
228+
}
229+
230+
$metadata->unset('files-live-photo');
231+
return null;
232+
}
233+
234+
private function getTrashItem(array $trashFolder, string $path): ?ITrashItem {
235+
foreach($trashFolder as $trashItem) {
236+
if (str_starts_with($path, "files_trashbin/files".$trashItem->getTrashPath())) {
237+
if ($path === "files_trashbin/files".$trashItem->getTrashPath()) {
238+
return $trashItem;
239+
}
240+
241+
if ($trashItem instanceof Folder) {
242+
$node = $this->getTrashItem($trashItem->getDirectoryListing(), $path);
243+
if ($node !== null) {
244+
return $node;
245+
}
246+
}
247+
}
248+
}
249+
250+
return null;
251+
}
252+
}

apps/files_trashbin/composer/composer/autoload_classmap.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
'OCA\\Files_Trashbin\\Command\\RestoreAllFiles' => $baseDir . '/../lib/Command/RestoreAllFiles.php',
1717
'OCA\\Files_Trashbin\\Command\\Size' => $baseDir . '/../lib/Command/Size.php',
1818
'OCA\\Files_Trashbin\\Controller\\PreviewController' => $baseDir . '/../lib/Controller/PreviewController.php',
19+
'OCA\\Files_Trashbin\\Events\\BeforeNodeRestoredEvent' => $baseDir . '/../lib/Events/BeforeNodeRestoredEvent.php',
1920
'OCA\\Files_Trashbin\\Events\\MoveToTrashEvent' => $baseDir . '/../lib/Events/MoveToTrashEvent.php',
21+
'OCA\\Files_Trashbin\\Events\\NodeRestoredEvent' => $baseDir . '/../lib/Events/NodeRestoredEvent.php',
2022
'OCA\\Files_Trashbin\\Exceptions\\CopyRecursiveException' => $baseDir . '/../lib/Exceptions/CopyRecursiveException.php',
2123
'OCA\\Files_Trashbin\\Expiration' => $baseDir . '/../lib/Expiration.php',
2224
'OCA\\Files_Trashbin\\Helper' => $baseDir . '/../lib/Helper.php',

apps/files_trashbin/composer/composer/autoload_static.php

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,22 @@
44

55
namespace Composer\Autoload;
66

7-
class ComposerStaticInitFiles_Trashbin
8-
{
9-
public static $prefixLengthsPsr4 = array (
10-
'O' =>
11-
array (
7+
class ComposerStaticInitFiles_Trashbin {
8+
public static $prefixLengthsPsr4 = array(
9+
'O' =>
10+
array(
1211
'OCA\\Files_Trashbin\\' => 19,
1312
),
1413
);
1514

16-
public static $prefixDirsPsr4 = array (
17-
'OCA\\Files_Trashbin\\' =>
18-
array (
15+
public static $prefixDirsPsr4 = array(
16+
'OCA\\Files_Trashbin\\' =>
17+
array(
1918
0 => __DIR__ . '/..' . '/../lib',
2019
),
2120
);
2221

23-
public static $classMap = array (
22+
public static $classMap = array(
2423
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
2524
'OCA\\Files_Trashbin\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php',
2625
'OCA\\Files_Trashbin\\BackgroundJob\\ExpireTrash' => __DIR__ . '/..' . '/../lib/BackgroundJob/ExpireTrash.php',
@@ -31,7 +30,9 @@ class ComposerStaticInitFiles_Trashbin
3130
'OCA\\Files_Trashbin\\Command\\RestoreAllFiles' => __DIR__ . '/..' . '/../lib/Command/RestoreAllFiles.php',
3231
'OCA\\Files_Trashbin\\Command\\Size' => __DIR__ . '/..' . '/../lib/Command/Size.php',
3332
'OCA\\Files_Trashbin\\Controller\\PreviewController' => __DIR__ . '/..' . '/../lib/Controller/PreviewController.php',
33+
'OCA\\Files_Trashbin\\Events\\BeforeNodeRestoredEvent' => __DIR__ . '/..' . '/../lib/Events/BeforeNodeRestoredEvent.php',
3434
'OCA\\Files_Trashbin\\Events\\MoveToTrashEvent' => __DIR__ . '/..' . '/../lib/Events/MoveToTrashEvent.php',
35+
'OCA\\Files_Trashbin\\Events\\NodeRestoredEvent' => __DIR__ . '/..' . '/../lib/Events/NodeRestoredEvent.php',
3536
'OCA\\Files_Trashbin\\Exceptions\\CopyRecursiveException' => __DIR__ . '/..' . '/../lib/Exceptions/CopyRecursiveException.php',
3637
'OCA\\Files_Trashbin\\Expiration' => __DIR__ . '/..' . '/../lib/Expiration.php',
3738
'OCA\\Files_Trashbin\\Helper' => __DIR__ . '/..' . '/../lib/Helper.php',
@@ -63,8 +64,7 @@ class ComposerStaticInitFiles_Trashbin
6364
'OCA\\Files_Trashbin\\UserMigration\\TrashbinMigrator' => __DIR__ . '/..' . '/../lib/UserMigration/TrashbinMigrator.php',
6465
);
6566

66-
public static function getInitializer(ClassLoader $loader)
67-
{
67+
public static function getInitializer(ClassLoader $loader) {
6868
return \Closure::bind(function () use ($loader) {
6969
$loader->prefixLengthsPsr4 = ComposerStaticInitFiles_Trashbin::$prefixLengthsPsr4;
7070
$loader->prefixDirsPsr4 = ComposerStaticInitFiles_Trashbin::$prefixDirsPsr4;

0 commit comments

Comments
 (0)