diff --git a/apps/dav/lib/Connector/Sabre/FilesPlugin.php b/apps/dav/lib/Connector/Sabre/FilesPlugin.php index 7b2f144dfa1d8..cfd99ff9322d3 100644 --- a/apps/dav/lib/Connector/Sabre/FilesPlugin.php +++ b/apps/dav/lib/Connector/Sabre/FilesPlugin.php @@ -9,6 +9,9 @@ use OC\AppFramework\Http\Request; use OC\FilesMetadata\Model\FilesMetadata; +use OC\Preview\Db\Preview; +use OC\Preview\PreviewService; +use OC\Preview\Storage\IPreviewStorage; use OC\User\NoUserException; use OCA\DAV\Connector\Sabre\Exception\InvalidPath; use OCA\Files_Sharing\External\Mount as SharingExternalMount; @@ -30,6 +33,7 @@ use OCP\L10N\IFactory; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\ICollection; use Sabre\DAV\IFile; use Sabre\DAV\PropFind; use Sabre\DAV\PropPatch; @@ -61,6 +65,7 @@ class FilesPlugin extends ServerPlugin { public const CHECKSUMS_PROPERTYNAME = '{http://owncloud.org/ns}checksums'; public const DATA_FINGERPRINT_PROPERTYNAME = '{http://owncloud.org/ns}data-fingerprint'; public const HAS_PREVIEW_PROPERTYNAME = '{http://nextcloud.org/ns}has-preview'; + public const PREVIEW_METADATA_PROPERTYNAME = '{http://nextcloud.org/ns}preview-metadata'; public const MOUNT_TYPE_PROPERTYNAME = '{http://nextcloud.org/ns}mount-type'; public const MOUNT_ROOT_PROPERTYNAME = '{http://nextcloud.org/ns}is-mount-root'; public const IS_FEDERATED_PROPERTYNAME = '{http://nextcloud.org/ns}is-federated'; @@ -77,6 +82,8 @@ class FilesPlugin extends ServerPlugin { /** Reference to main server object */ private ?Server $server = null; + private array $previewMetadataCache = []; + /** * @param Tree $tree * @param IConfig $config @@ -95,6 +102,8 @@ public function __construct( private IUserSession $userSession, private IFilenameValidator $validator, private IAccountManager $accountManager, + private PreviewService $previewService, + private IPreviewStorage $previewStorage, private bool $isPublic = false, private bool $downloadAttachment = true, ) { @@ -127,6 +136,7 @@ public function initialize(Server $server) { $server->protectedProperties[] = self::CHECKSUMS_PROPERTYNAME; $server->protectedProperties[] = self::DATA_FINGERPRINT_PROPERTYNAME; $server->protectedProperties[] = self::HAS_PREVIEW_PROPERTYNAME; + $server->protectedProperties[] = self::PREVIEW_METADATA_PROPERTYNAME; $server->protectedProperties[] = self::MOUNT_TYPE_PROPERTYNAME; $server->protectedProperties[] = self::IS_FEDERATED_PROPERTYNAME; $server->protectedProperties[] = self::SHARE_NOTE; @@ -136,6 +146,7 @@ public function initialize(Server $server) { $server->protectedProperties = array_diff($server->protectedProperties, $allowedProperties); $this->server = $server; + $this->server->on('preloadCollection', $this->preloadCollection(...)); $this->server->on('propFind', [$this, 'handleGetProperties']); $this->server->on('propPatch', [$this, 'handleUpdateProperties']); $this->server->on('afterBind', [$this, 'sendFileIdHeader']); @@ -152,6 +163,23 @@ public function initialize(Server $server) { $this->server->on('beforeCopy', [$this, 'checkCopy']); } + private function preloadCollection(PropFind $propFind, ICollection $collection): void { + if (!($collection instanceof Directory)) { + return; + } + + $requestProperties = $propFind->getRequestedProperties(); + if (in_array(self::PREVIEW_METADATA_PROPERTYNAME, $requestProperties, true)) { + $keys = array_map(static fn (Node $node) => $node->getInternalFileId(), $collection->getChildren()); + $missingKeys = array_diff($keys, array_keys($this->previewMetadataCache)); + if (count($missingKeys) > 0) { + foreach ($this->previewService->getAvailablePreviews($missingKeys) as $fileId => $previews) { + $this->previewMetadataCache[$fileId] = $previews; + } + } + } + } + /** * Plugin that checks if a copy can actually be performed. * @@ -474,29 +502,34 @@ public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node) if ($node instanceof File) { $requestProperties = $propFind->getRequestedProperties(); - if (in_array(self::DOWNLOADURL_PROPERTYNAME, $requestProperties, true) || in_array(self::DOWNLOADURL_EXPIRATION_PROPERTYNAME, $requestProperties, true)) { try { $directDownloadUrl = $node->getDirectDownload(); + if ($directDownloadUrl && isset($directDownloadUrl['url'])) { + $propFind->handle(self::DOWNLOADURL_PROPERTYNAME, $directDownloadUrl['url']); + $propFind->handle(self::DOWNLOADURL_EXPIRATION_PROPERTYNAME, $directDownloadUrl['expiration']); + } } catch (StorageNotAvailableException|ForbiddenException) { - $directDownloadUrl = null; + $propFind->handle(self::DOWNLOADURL_PROPERTYNAME, false); + $propFind->handle(self::DOWNLOADURL_EXPIRATION_PROPERTYNAME, false); } + } - $propFind->handle(self::DOWNLOADURL_PROPERTYNAME, function () use ($node, $directDownloadUrl) { - if ($directDownloadUrl && isset($directDownloadUrl['url'])) { - return $directDownloadUrl['url']; + $propFind->handle(self::PREVIEW_METADATA_PROPERTYNAME, function () use ($node) { + try { + if (isset($this->previewMetadataCache[$node->getInternalFileId()])) { + $previews = $this->previewMetadataCache[$node->getInternalFileId()]; + } else { + [$node->getInternalFileId() => $previews] = $this->previewService->getAvailablePreviews([$node->getInternalFileId()]); } - return false; - }); - $propFind->handle(self::DOWNLOADURL_EXPIRATION_PROPERTYNAME, function () use ($node, $directDownloadUrl) { - if ($directDownloadUrl && isset($directDownloadUrl['expiration'])) { - return $directDownloadUrl['expiration']; - } - return false; - }); - } + $data = array_map(fn (Preview $preview) => Preview::getMetadata($preview, $this->previewStorage), $previews); + return json_encode($data, JSON_THROW_ON_ERROR); + } catch (\Exception $e) { + } + return false; + }); $propFind->handle(self::CHECKSUMS_PROPERTYNAME, function () use ($node) { $checksum = $node->getChecksum(); diff --git a/apps/dav/lib/Connector/Sabre/ServerFactory.php b/apps/dav/lib/Connector/Sabre/ServerFactory.php index 19dd5584c5167..1fcd32234ba24 100644 --- a/apps/dav/lib/Connector/Sabre/ServerFactory.php +++ b/apps/dav/lib/Connector/Sabre/ServerFactory.php @@ -9,6 +9,8 @@ use OC\Files\View; use OC\KnownUser\KnownUserService; +use OC\Preview\PreviewService; +use OC\Preview\Storage\StorageFactory; use OCA\DAV\AppInfo\PluginManager; use OCA\DAV\CalDAV\DefaultCalendarValidator; use OCA\DAV\CalDAV\Proxy\ProxyMapper; @@ -162,6 +164,8 @@ public function createServer( $this->userSession, \OCP\Server::get(IFilenameValidator::class), \OCP\Server::get(IAccountManager::class), + \OCP\Server::get(PreviewService::class), + \OCP\Server::get(StorageFactory::class), $isPublicShare, !$debugEnabled ) diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php index 7398af83377d5..432e7bf97f7de 100644 --- a/apps/dav/lib/Server.php +++ b/apps/dav/lib/Server.php @@ -8,6 +8,8 @@ namespace OCA\DAV; use OC\Files\Filesystem; +use OC\Preview\PreviewService; +use OC\Preview\Storage\StorageFactory; use OCA\DAV\AppInfo\PluginManager; use OCA\DAV\BulkUpload\BulkUploadPlugin; use OCA\DAV\CalDAV\BirthdayCalendar\EnablePlugin; @@ -300,6 +302,8 @@ public function __construct( \OCP\Server::get(IUserSession::class), \OCP\Server::get(IFilenameValidator::class), \OCP\Server::get(IAccountManager::class), + \OCP\Server::get(PreviewService::class), + \OCP\Server::get(StorageFactory::class), false, $config->getSystemValueBool('debug', false) === false, ) diff --git a/apps/files/src/components/FileEntry/FileEntryPreview.vue b/apps/files/src/components/FileEntry/FileEntryPreview.vue index 77b9fb1eadfe6..6bdcb6e793e60 100644 --- a/apps/files/src/components/FileEntry/FileEntryPreview.vue +++ b/apps/files/src/components/FileEntry/FileEntryPreview.vue @@ -52,7 +52,7 @@