Skip to content
Draft
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
61 changes: 47 additions & 14 deletions apps/dav/lib/Connector/Sabre/FilesPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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';
Expand All @@ -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
Expand All @@ -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,
) {
Expand Down Expand Up @@ -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;
Expand All @@ -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']);
Expand All @@ -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.
*
Expand Down Expand Up @@ -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();
Expand Down
4 changes: 4 additions & 0 deletions apps/dav/lib/Connector/Sabre/ServerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
)
Expand Down
4 changes: 4 additions & 0 deletions apps/dav/lib/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
)
Expand Down
10 changes: 9 additions & 1 deletion apps/files/src/components/FileEntry/FileEntryPreview.vue
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
<script lang="ts">
import type { Node } from '@nextcloud/files'
import type { PropType } from 'vue'
import type { UserConfig } from '../../types.ts'
import type { UserConfig, PreviewMetadata } from '../../types.ts'

import { FileType } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
Expand Down Expand Up @@ -164,6 +164,14 @@ export default defineComponent({
return url.href
}

if (this.source.attributes['preview-metadata']) {
let metadata: PreviewMetadata[] = JSON.parse(this.source.attributes['preview-metadata'])
metadata = metadata.filter((preview) => preview.height <= 256 && this.cropPreviews === preview.cropped && preview.preview_url)
if (metadata.length > 0) {
return metadata[0]?.preview_url;
}
}

try {
const previewUrl = this.source.attributes.previewUrl
|| (this.isPublic
Expand Down
3 changes: 3 additions & 0 deletions apps/files/src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,8 @@ registerPreviewServiceWorker()
registerDavProperty('nc:hidden', { nc: 'http://nextcloud.org/ns' })
registerDavProperty('nc:is-mount-root', { nc: 'http://nextcloud.org/ns' })
registerDavProperty('nc:metadata-blurhash', { nc: 'http://nextcloud.org/ns' })
registerDavProperty('nc:preview-metadata', { nc: 'http://nextcloud.org/ns' })
registerDavProperty('nc:download-url-expiration', { nc: 'http://nextcloud.org/ns' })
registerDavProperty('oc:downloadURL', { oc: 'http://owncloud.org/ns' })

initLivePhotos()
8 changes: 8 additions & 0 deletions apps/files/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,11 @@ export type Capabilities = {
versioning: boolean
}
}

export type PreviewMetadata = {
width: int,
height: int,
cropped: boolean,
preview_url: string | null,
preview_url_expiration: int | null,
}
2 changes: 2 additions & 0 deletions lib/private/Files/ObjectStore/S3ConnectionTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ trait S3ConnectionTrait {
private ?ICache $existingBucketsCache = null;
private bool $usePresignedUrl = false;

private bool $usePresignedUrl = false;

protected function parseParams($params) {
if (empty($params['bucket'])) {
throw new \Exception('Bucket has to be configured.');
Expand Down
12 changes: 12 additions & 0 deletions lib/private/Preview/Db/Preview.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

namespace OC\Preview\Db;

use OC\Preview\Storage\IPreviewStorage;
use OCP\AppFramework\Db\Entity;
use OCP\DB\Types;
use OCP\Files\IMimeTypeDetector;
Expand Down Expand Up @@ -179,4 +180,15 @@ public function getSourceMimeType(): string {
public function setSourceMimeType(string $mimeType): void {
$this->sourceMimetype = $mimeType;
}

public static function getMetadata(Preview $preview, IPreviewStorage $storage): mixed {
$data = $storage->getDirectDownload($preview);
return [
'width' => $preview->width,
'height' => $preview->height,
'cropped' => $preview->cropped,
'preview_url' => $data === false ? null : $data['url'],
'preview_url_expiration' => $data === false ? null : $data['expiration'],
];
}
}
5 changes: 5 additions & 0 deletions lib/private/Preview/Storage/IPreviewStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,9 @@ public function migratePreview(Preview $preview, SimpleFile $file): void;
* @throws NotFoundException
*/
public function scan(): int;

/**
* See IStorage::getDirectDownload
*/
public function getDirectDownload(Preview $preview): array|false;
}
5 changes: 5 additions & 0 deletions lib/private/Preview/Storage/LocalPreviewStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -241,4 +241,9 @@ private function deleteParentsFromFileCache(string $dirname): void {
$this->connection->commit();
}
}

#[Override]
public function getDirectDownload(Preview $preview): array|false {
return false;
}
}
16 changes: 16 additions & 0 deletions lib/private/Preview/Storage/ObjectStorePreviewStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,20 @@ public function getUrn(Preview $preview, array $config): string {
public function scan(): int {
return 0;
}

#[Override]
public function getDirectDownload(Preview $preview): array|false {
[
'urn' => $urn,
'store' => $store,
] = $this->getObjectStoreInfoForExistingPreview($preview);

try {
$expiration = new \DateTimeImmutable('+60 minutes');
$url = $store->preSignedUrl($urn, $expiration);
return $url ? ['url' => $url, 'expiration' => $expiration->getTimestamp()] : false;
} catch (\Exception $exception) {
throw new NotPermittedException('Unable to read preview from object store with urn:' . $urn, previous: $exception);
}
}
}
25 changes: 15 additions & 10 deletions lib/private/Preview/Storage/StorageFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,21 @@ public function deletePreview(Preview $preview): void {
$this->getBackend()->deletePreview($preview);
}

#[Override]
public function migratePreview(Preview $preview, SimpleFile $file): void {
$this->getBackend()->migratePreview($preview, $file);
}

#[Override]
public function scan(): int {
return $this->getBackend()->scan();
}

#[Override]
public function getDirectDownload(Preview $preview): array|false {
return $this->getBackend()->getDirectDownload($preview);
}

private function getBackend(): IPreviewStorage {
if ($this->backend) {
return $this->backend;
Expand All @@ -52,14 +67,4 @@ private function getBackend(): IPreviewStorage {

return $this->backend;
}

#[Override]
public function migratePreview(Preview $preview, SimpleFile $file): void {
$this->getBackend()->migratePreview($preview, $file);
}

#[Override]
public function scan(): int {
return $this->getBackend()->scan();
}
}
Loading