diff options
author | Joas Schilling <213943+nickvergessen@users.noreply.github.com> | 2023-12-07 10:34:12 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-12-07 10:34:12 +0100 |
commit | 3f9fadc993eed2d06b4d6cf71773cc063cb82493 (patch) | |
tree | a7541fd0464019fd1a066efb0a354a9d9c487101 | |
parent | 96d7813f5cb414ea6a6efd118f5a10ae420877c8 (diff) | |
parent | efdebddb5c2564d7592bdcbc1e7ffc8705d886e8 (diff) |
Merge pull request #11131 from nextcloud/backport/11093/stable28
[stable28] Add metadata to file parameters
-rw-r--r-- | lib/Chat/Parser/SystemMessage.php | 22 | ||||
-rw-r--r-- | lib/Controller/ChatController.php | 14 | ||||
-rw-r--r-- | lib/Share/Helper/FilesMetadataCache.php | 103 | ||||
-rw-r--r-- | tests/php/Chat/Parser/SystemMessageTest.php | 31 | ||||
-rw-r--r-- | tests/php/Controller/ChatControllerTest.php | 5 |
5 files changed, 164 insertions, 11 deletions
diff --git a/lib/Chat/Parser/SystemMessage.php b/lib/Chat/Parser/SystemMessage.php index be9e06ec6..7c3c607dc 100644 --- a/lib/Chat/Parser/SystemMessage.php +++ b/lib/Chat/Parser/SystemMessage.php @@ -34,6 +34,7 @@ use OCA\Talk\Model\Message; use OCA\Talk\Participant; use OCA\Talk\Room; use OCA\Talk\Service\ParticipantService; +use OCA\Talk\Share\Helper\FilesMetadataCache; use OCA\Talk\Share\RoomShareProvider; use OCP\Comments\IComment; use OCP\EventDispatcher\Event; @@ -43,6 +44,7 @@ use OCP\Files\InvalidPathException; use OCP\Files\IRootFolder; use OCP\Files\Node; use OCP\Files\NotFoundException; +use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException; use OCP\IGroup; use OCP\IGroupManager; use OCP\IL10N; @@ -85,6 +87,7 @@ class SystemMessage implements IEventListener { protected IRootFolder $rootFolder, protected ICloudIdManager $cloudIdManager, protected IURLGenerator $url, + protected FilesMetadataCache $metadataCache, ) { } @@ -719,9 +722,12 @@ class SystemMessage implements IEventListener { ]); } + $fileId = $node->getId(); + $isPreviewAvailable = $this->previewManager->isAvailable($node); + $data = [ 'type' => 'file', - 'id' => (string) $node->getId(), + 'id' => (string) $fileId, 'name' => $name, 'size' => $size, 'path' => $path, @@ -729,9 +735,21 @@ class SystemMessage implements IEventListener { 'etag' => $node->getEtag(), 'permissions' => $node->getPermissions(), 'mimetype' => $node->getMimeType(), - 'preview-available' => $this->previewManager->isAvailable($node) ? 'yes' : 'no', + 'preview-available' => $isPreviewAvailable ? 'yes' : 'no', ]; + // If a preview is available, check if we can get the dimensions of the file from the metadata API + if ($isPreviewAvailable && str_starts_with($node->getMimeType(), 'image/')) { + try { + $sizeMetadata = $this->metadataCache->getMetadataPhotosSizeForFileId($fileId); + if (isset($sizeMetadata['width'], $sizeMetadata['height'])) { + $data['width'] = $sizeMetadata['width']; + $data['height'] = $sizeMetadata['height']; + } + } catch (FilesMetadataNotFoundException) { + } + } + if ($node->getMimeType() === 'text/vcard') { $vCard = $node->getContent(); diff --git a/lib/Controller/ChatController.php b/lib/Controller/ChatController.php index 18b21c97c..6c4570667 100644 --- a/lib/Controller/ChatController.php +++ b/lib/Controller/ChatController.php @@ -51,6 +51,7 @@ use OCA\Talk\Service\AvatarService; use OCA\Talk\Service\ParticipantService; use OCA\Talk\Service\ReminderService; use OCA\Talk\Service\SessionService; +use OCA\Talk\Share\Helper\FilesMetadataCache; use OCA\Talk\Share\RoomShareProvider; use OCP\App\IAppManager; use OCP\AppFramework\Db\DoesNotExistException; @@ -73,6 +74,7 @@ use OCP\RichObjectStrings\InvalidObjectExeption; use OCP\RichObjectStrings\IValidator; use OCP\Security\ITrustedDomainHelper; use OCP\Share\Exceptions\ShareNotFound; +use OCP\Share\IShare; use OCP\User\Events\UserLiveStatusEvent; use OCP\UserStatus\IManager as IUserStatusManager; use OCP\UserStatus\IUserStatus; @@ -103,6 +105,7 @@ class ChatController extends AEnvironmentAwareController { private GuestManager $guestManager, private MessageParser $messageParser, protected RoomShareProvider $shareProvider, + protected FilesMetadataCache $filesMetadataCache, private IManager $autoCompleteManager, private IUserStatusManager $statusManager, protected MatterbridgeManager $matterbridgeManager, @@ -308,7 +311,8 @@ class ChatController extends AEnvironmentAwareController { /* * Gather share IDs from the comments and preload share definitions - * to avoid separate database query for each individual share. + * and files metadata to avoid separate database query for each + * individual share/node later on. * * @param IComment[] $comments */ @@ -326,10 +330,14 @@ class ChatController extends AEnvironmentAwareController { } } if (!empty($shareIds)) { - // Ignore the result for now. Retrieved Share objects will be cached by + // Retrieved Share objects will be cached by // the RoomShareProvider and returned from the cache to // the Parser\SystemMessage without additional database queries. - $this->shareProvider->getSharesByIds($shareIds); + $shares = $this->shareProvider->getSharesByIds($shareIds); + + // Preload files metadata as well + $fileIds = array_filter(array_map(static fn (IShare $share) => $share->getNodeId(), $shares)); + $this->filesMetadataCache->preloadMetadata($fileIds); } } diff --git a/lib/Share/Helper/FilesMetadataCache.php b/lib/Share/Helper/FilesMetadataCache.php new file mode 100644 index 000000000..587d2a706 --- /dev/null +++ b/lib/Share/Helper/FilesMetadataCache.php @@ -0,0 +1,103 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com> + * + * @author Joas Schilling <coding@schilljs.com> + * + * @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\Talk\Share\Helper; + +use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException; +use OCP\FilesMetadata\Exceptions\FilesMetadataTypeException; +use OCP\FilesMetadata\IFilesMetadataManager; +use OCP\FilesMetadata\Model\IFilesMetadata; + +class FilesMetadataCache { + /** @var array<int, ?array{width: int, height: int}> */ + protected array $filesSizeData = []; + + public function __construct( + protected IFilesMetadataManager $filesMetadataManager, + ) { + } + + /** + * @param list<int> $fileIds + */ + public function preloadMetadata(array $fileIds): void { + $missingFileIds = array_diff($fileIds, array_keys($this->filesSizeData)); + if (empty($missingFileIds)) { + return; + } + + $data = $this->filesMetadataManager->getMetadataForFiles($missingFileIds); + foreach ($data as $fileId => $metadata) { + $this->cachePhotosSize($fileId, $metadata); + } + } + + /** + * @param int $fileId + * @return array + * @psalm-return array{width: int, height: int} + * @throws FilesMetadataNotFoundException + */ + public function getMetadataPhotosSizeForFileId(int $fileId): array { + if (!array_key_exists($fileId, $this->filesSizeData)) { + try { + $this->cachePhotosSize($fileId, $this->filesMetadataManager->getMetadata($fileId, true)); + } catch (FilesMetadataNotFoundException) { + $this->filesSizeData[$fileId] = null; + } + } + + if ($this->filesSizeData[$fileId] === null) { + throw new FilesMetadataNotFoundException(); + } + + return $this->filesSizeData[$fileId]; + } + + protected function cachePhotosSize(int $fileId, IFilesMetadata $metadata): void { + if ($metadata->hasKey('photos-size')) { + try { + $sizeMetadata = $metadata->getArray('photos-size'); + } catch (FilesMetadataNotFoundException|FilesMetadataTypeException) { + $this->filesSizeData[$fileId] = null; + return; + } + + if (isset($sizeMetadata['width'], $sizeMetadata['height'])) { + $dimensions = [ + 'width' => $sizeMetadata['width'], + 'height' => $sizeMetadata['height'], + ]; + $this->filesSizeData[$fileId] = $dimensions; + } else { + $this->filesSizeData[$fileId] = null; + } + } else { + $this->filesSizeData[$fileId] = null; + } + + } +} diff --git a/tests/php/Chat/Parser/SystemMessageTest.php b/tests/php/Chat/Parser/SystemMessageTest.php index 0df7a9594..1db0029a3 100644 --- a/tests/php/Chat/Parser/SystemMessageTest.php +++ b/tests/php/Chat/Parser/SystemMessageTest.php @@ -32,6 +32,7 @@ use OCA\Talk\Model\Session; use OCA\Talk\Participant; use OCA\Talk\Room; use OCA\Talk\Service\ParticipantService; +use OCA\Talk\Share\Helper\FilesMetadataCache; use OCA\Talk\Share\RoomShareProvider; use OCP\Comments\IComment; use OCP\Federation\ICloudIdManager; @@ -75,6 +76,8 @@ class SystemMessageTest extends TestCase { protected $url; /** @var ICloudIdManager|MockObject */ protected $cloudIdManager; + /** @var FilesMetadataCache|MockObject */ + protected $filesMetadataCache; /** @var IL10N|MockObject */ protected $l; @@ -91,6 +94,7 @@ class SystemMessageTest extends TestCase { $this->rootFolder = $this->createMock(IRootFolder::class); $this->url = $this->createMock(IURLGenerator::class); $this->cloudIdManager = $this->createMock(ICloudIdManager::class); + $this->filesMetadataCache = $this->createMock(FilesMetadataCache::class); $this->l = $this->createMock(IL10N::class); $this->l->method('t') ->willReturnCallback(function ($text, $parameters = []) { @@ -121,6 +125,7 @@ class SystemMessageTest extends TestCase { $this->rootFolder, $this->cloudIdManager, $this->url, + $this->filesMetadataCache, ]) ->onlyMethods($methods) ->getMock(); @@ -137,7 +142,8 @@ class SystemMessageTest extends TestCase { $this->photoCache, $this->rootFolder, $this->cloudIdManager, - $this->url + $this->url, + $this->filesMetadataCache, ); } @@ -611,13 +617,13 @@ class SystemMessageTest extends TestCase { $node = $this->createMock(Node::class); $node->expects($this->once()) ->method('getId') - ->willReturn('54'); + ->willReturn(54); $node->expects($this->once()) ->method('getName') ->willReturn('name'); $node->expects($this->atLeastOnce()) ->method('getMimeType') - ->willReturn('text/plain'); + ->willReturn('image/png'); $node->expects($this->once()) ->method('getSize') ->willReturn(65530); @@ -653,6 +659,11 @@ class SystemMessageTest extends TestCase { ->with($node) ->willReturn(true); + $this->filesMetadataCache->expects($this->once()) + ->method('getMetadataPhotosSizeForFileId') + ->with(54) + ->willReturn(['width' => 1234, 'height' => 4567]); + $participant = $this->createMock(Participant::class); $participant->expects($this->once()) ->method('isGuest') @@ -668,8 +679,10 @@ class SystemMessageTest extends TestCase { 'link' => 'absolute-link', 'etag' => '1872ade88f3013edeb33decd74a4f947', 'permissions' => 27, - 'mimetype' => 'text/plain', + 'mimetype' => 'image/png', 'preview-available' => 'yes', + 'width' => 1234, + 'height' => 4567, ], self::invokePrivate($parser, 'getFileFromShare', [$participant, '23'])); } @@ -677,7 +690,7 @@ class SystemMessageTest extends TestCase { $node = $this->createMock(Node::class); $node->expects($this->exactly(2)) ->method('getId') - ->willReturn('54'); + ->willReturn(54); $node->expects($this->once()) ->method('getName') ->willReturn('name'); @@ -722,6 +735,9 @@ class SystemMessageTest extends TestCase { ]) ->willReturn('absolute-link-owner'); + $this->filesMetadataCache->expects($this->never()) + ->method('getMetadataPhotosSizeForFileId'); + $participant = $this->createMock(Participant::class); $participant->expects($this->once()) ->method('isGuest') @@ -775,7 +791,7 @@ class SystemMessageTest extends TestCase { $file = $this->createMock(Node::class); $file->expects($this->any()) ->method('getId') - ->willReturn('54'); + ->willReturn(54); $file->expects($this->once()) ->method('getName') ->willReturn('different'); @@ -811,6 +827,9 @@ class SystemMessageTest extends TestCase { ->with($file) ->willReturn(false); + $this->filesMetadataCache->expects($this->never()) + ->method('getMetadataPhotosSizeForFileId'); + $this->url->expects($this->once()) ->method('linkToRouteAbsolute') ->with('files.viewcontroller.showFile', [ diff --git a/tests/php/Controller/ChatControllerTest.php b/tests/php/Controller/ChatControllerTest.php index e1120a021..a5bbe6da6 100644 --- a/tests/php/Controller/ChatControllerTest.php +++ b/tests/php/Controller/ChatControllerTest.php @@ -39,6 +39,7 @@ use OCA\Talk\Service\AvatarService; use OCA\Talk\Service\ParticipantService; use OCA\Talk\Service\ReminderService; use OCA\Talk\Service\SessionService; +use OCA\Talk\Share\Helper\FilesMetadataCache; use OCA\Talk\Share\RoomShareProvider; use OCP\App\IAppManager; use OCP\AppFramework\Http; @@ -86,6 +87,8 @@ class ChatControllerTest extends TestCase { protected $messageParser; /** @var RoomShareProvider|MockObject */ protected $roomShareProvider; + /** @var FilesMetadataCache|MockObject */ + protected $filesMetadataCache; /** @var IManager|MockObject */ protected $autoCompleteManager; /** @var IUserStatusManager|MockObject */ @@ -131,6 +134,7 @@ class ChatControllerTest extends TestCase { $this->guestManager = $this->createMock(GuestManager::class); $this->messageParser = $this->createMock(MessageParser::class); $this->roomShareProvider = $this->createMock(RoomShareProvider::class); + $this->filesMetadataCache = $this->createMock(FilesMetadataCache::class); $this->autoCompleteManager = $this->createMock(IManager::class); $this->statusManager = $this->createMock(IUserStatusManager::class); $this->matterbridgeManager = $this->createMock(MatterbridgeManager::class); @@ -171,6 +175,7 @@ class ChatControllerTest extends TestCase { $this->guestManager, $this->messageParser, $this->roomShareProvider, + $this->filesMetadataCache, $this->autoCompleteManager, $this->statusManager, $this->matterbridgeManager, |