diff options
author | John Molakvoæ <skjnldsv@protonmail.com> | 2023-08-11 14:32:42 +0200 |
---|---|---|
committer | John Molakvoæ <skjnldsv@protonmail.com> | 2023-08-17 18:56:38 +0200 |
commit | 28725c46a84db6e733c42e38cbc5e1b7f0e3854b (patch) | |
tree | 6915d025434815635017439555006a3d2e5e0ee8 | |
parent | 9df5212a40f5a5011755f987eaf9b63652ef6848 (diff) | |
download | nextcloud-server-28725c46a84db6e733c42e38cbc5e1b7f0e3854b.tar.gz nextcloud-server-28725c46a84db6e733c42e38cbc5e1b7f0e3854b.zip |
feat: redirect to the mime icon if no preview available
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
-rw-r--r-- | apps/files/src/components/FileEntry.vue | 17 | ||||
-rw-r--r-- | core/Controller/PreviewController.php | 33 | ||||
-rw-r--r-- | core/openapi.json | 38 | ||||
-rw-r--r-- | lib/composer/composer/autoload_classmap.php | 2 | ||||
-rw-r--r-- | lib/composer/composer/autoload_static.php | 2 | ||||
-rw-r--r-- | lib/private/Preview/MimeIconProvider.php | 98 | ||||
-rw-r--r-- | lib/private/Server.php | 3 | ||||
-rw-r--r-- | lib/public/Files/IMimeTypeDetector.php | 6 | ||||
-rw-r--r-- | lib/public/Preview/IMimeIconProvider.php | 37 |
9 files changed, 213 insertions, 23 deletions
diff --git a/apps/files/src/components/FileEntry.vue b/apps/files/src/components/FileEntry.vue index 98b4dfca07c..6e43d3340c7 100644 --- a/apps/files/src/components/FileEntry.vue +++ b/apps/files/src/components/FileEntry.vue @@ -45,10 +45,6 @@ class="files-list__row-icon-preview" :style="{ backgroundImage }" /> - <span v-else-if="mimeIconUrl" - class="files-list__row-icon-preview files-list__row-icon-preview--mime" - :style="{ backgroundImage: mimeIconUrl }" /> - <FileIcon v-else /> <!-- Favorite icon --> @@ -155,17 +151,16 @@ </template> <script lang='ts'> +import { CancelablePromise } from 'cancelable-promise' import { debounce } from 'debounce' import { emit } from '@nextcloud/event-bus' import { extname } from 'path' import { formatFileSize, Permission } from '@nextcloud/files' -import { Fragment } from 'vue-frag' import { generateUrl } from '@nextcloud/router' import { showError, showSuccess } from '@nextcloud/dialogs' import { translate } from '@nextcloud/l10n' import { vOnClickOutside } from '@vueuse/components' import axios from '@nextcloud/axios' -import CancelablePromise from 'cancelable-promise' import FileIcon from 'vue-material-design-icons/File.vue' import FolderIcon from 'vue-material-design-icons/Folder.vue' import moment from '@nextcloud/moment' @@ -205,7 +200,6 @@ export default Vue.extend({ FavoriteIcon, FileIcon, FolderIcon, - Fragment, NcActionButton, NcActions, NcCheckboxRadioSwitch, @@ -394,6 +388,7 @@ export default Vue.extend({ // Request tiny previews url.searchParams.set('x', '32') url.searchParams.set('y', '32') + url.searchParams.set('mimeFallback', 'true') // Handle cropping url.searchParams.set('a', this.cropPreviews === true ? '0' : '1') @@ -402,14 +397,6 @@ export default Vue.extend({ return null } }, - mimeIconUrl() { - const mimeType = this.source.mime || 'application/octet-stream' - const mimeIconUrl = window.OC?.MimeType?.getIconUrl?.(mimeType) - if (mimeIconUrl) { - return `url(${mimeIconUrl})` - } - return '' - }, // Sorted actions that are enabled for this node enabledActions() { diff --git a/core/Controller/PreviewController.php b/core/Controller/PreviewController.php index c9183466f90..4cfc8143491 100644 --- a/core/Controller/PreviewController.php +++ b/core/Controller/PreviewController.php @@ -32,12 +32,14 @@ use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\FileDisplayResponse; +use OCP\AppFramework\Http\RedirectResponse; use OCP\Files\File; use OCP\Files\IRootFolder; use OCP\Files\Node; use OCP\Files\NotFoundException; use OCP\IPreview; use OCP\IRequest; +use OCP\Preview\IMimeIconProvider; class PreviewController extends Controller { public function __construct( @@ -46,6 +48,7 @@ class PreviewController extends Controller { private IPreview $preview, private IRootFolder $root, private ?string $userId, + private IMimeIconProvider $mimeIconProvider, ) { parent::__construct($appName, $request); } @@ -62,9 +65,11 @@ class PreviewController extends Controller { * @param bool $a Whether to not crop the preview * @param bool $forceIcon Force returning an icon * @param string $mode How to crop the image - * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, array<empty>, array{}> + * @param bool $mimeFallback Whether to fallback to the mime icon if no preview is available + * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, array<empty>, array{}>|RedirectResponse<Http::STATUS_SEE_OTHER, array{}> * * 200: Preview returned + * 303: Redirect to the mime icon url if mimeFallback is true * 400: Getting preview is not possible * 403: Getting preview is not allowed * 404: Preview not found @@ -75,7 +80,8 @@ class PreviewController extends Controller { int $y = 32, bool $a = false, bool $forceIcon = true, - string $mode = 'fill'): Http\Response { + string $mode = 'fill', + bool $mimeFallback): Http\Response { if ($file === '' || $x === 0 || $y === 0) { return new DataResponse([], Http::STATUS_BAD_REQUEST); } @@ -87,7 +93,7 @@ class PreviewController extends Controller { return new DataResponse([], Http::STATUS_NOT_FOUND); } - return $this->fetchPreview($node, $x, $y, $a, $forceIcon, $mode); + return $this->fetchPreview($node, $x, $y, $a, $forceIcon, $mode, $mimeFallback); } /** @@ -102,9 +108,11 @@ class PreviewController extends Controller { * @param bool $a Whether to not crop the preview * @param bool $forceIcon Force returning an icon * @param string $mode How to crop the image - * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, array<empty>, array{}> + * @param bool $mimeFallback Whether to fallback to the mime icon if no preview is available + * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, array<empty>, array{}>|RedirectResponse<Http::STATUS_SEE_OTHER, array{}> * * 200: Preview returned + * 303: Redirect to the mime icon url if mimeFallback is true * 400: Getting preview is not possible * 403: Getting preview is not allowed * 404: Preview not found @@ -115,7 +123,8 @@ class PreviewController extends Controller { int $y = 32, bool $a = false, bool $forceIcon = true, - string $mode = 'fill') { + string $mode = 'fill', + bool $mimeFallback = false) { if ($fileId === -1 || $x === 0 || $y === 0) { return new DataResponse([], Http::STATUS_BAD_REQUEST); } @@ -129,11 +138,11 @@ class PreviewController extends Controller { $node = array_pop($nodes); - return $this->fetchPreview($node, $x, $y, $a, $forceIcon, $mode); + return $this->fetchPreview($node, $x, $y, $a, $forceIcon, $mode, $mimeFallback); } /** - * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, array<empty>, array{}> + * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, array<empty>, array{}>|RedirectResponse<Http::STATUS_SEE_OTHER, array{}> */ private function fetchPreview( Node $node, @@ -141,7 +150,8 @@ class PreviewController extends Controller { int $y, bool $a, bool $forceIcon, - string $mode) : Http\Response { + string $mode, + bool $mimeFallback = false) : Http\Response { if (!($node instanceof File) || (!$forceIcon && !$this->preview->isAvailable($node))) { return new DataResponse([], Http::STATUS_NOT_FOUND); } @@ -167,6 +177,13 @@ class PreviewController extends Controller { $response->cacheFor(3600 * 24, false, true); return $response; } catch (NotFoundException $e) { + // If we have no preview enabled, we can redirect to the mime icon if any + if ($mimeFallback) { + if ($url = $this->mimeIconProvider->getMimeIconUrl($node->getMimeType())) { + return new RedirectResponse($url); + } + } + return new DataResponse([], Http::STATUS_NOT_FOUND); } catch (\InvalidArgumentException $e) { return new DataResponse([], Http::STATUS_BAD_REQUEST); diff --git a/core/openapi.json b/core/openapi.json index d396aa9001a..709963c6492 100644 --- a/core/openapi.json +++ b/core/openapi.json @@ -939,6 +939,15 @@ "type": "string", "default": "fill" } + }, + { + "name": "mimeFallback", + "in": "query", + "description": "Whether to fallback to the mime icon if no preview is available", + "schema": { + "type": "integer", + "default": 0 + } } ], "responses": { @@ -976,6 +985,16 @@ "schema": {} } } + }, + "303": { + "description": "Redirect to the mime icon url if mimeFallback is true", + "headers": { + "Location": { + "schema": { + "type": "string" + } + } + } } } } @@ -1051,6 +1070,15 @@ "type": "string", "default": "fill" } + }, + { + "name": "mimeFallback", + "in": "query", + "description": "Whether to fallback to the mime icon if no preview is available", + "required": true, + "schema": { + "type": "integer" + } } ], "responses": { @@ -1088,6 +1116,16 @@ "schema": {} } } + }, + "303": { + "description": "Redirect to the mime icon url if mimeFallback is true", + "headers": { + "Location": { + "schema": { + "type": "string" + } + } + } } } } diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 2e5c239b6ed..4b99fc18135 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -533,6 +533,7 @@ return array( 'OCP\\OCS\\IDiscoveryService' => $baseDir . '/lib/public/OCS/IDiscoveryService.php', 'OCP\\PreConditionNotMetException' => $baseDir . '/lib/public/PreConditionNotMetException.php', 'OCP\\Preview\\BeforePreviewFetchedEvent' => $baseDir . '/lib/public/Preview/BeforePreviewFetchedEvent.php', + 'OCP\\Preview\\IMimeIconProvider' => $baseDir . '/lib/public/Preview/IMimeIconProvider.php', 'OCP\\Preview\\IProvider' => $baseDir . '/lib/public/Preview/IProvider.php', 'OCP\\Preview\\IProviderV2' => $baseDir . '/lib/public/Preview/IProviderV2.php', 'OCP\\Preview\\IVersionedPreviewFile' => $baseDir . '/lib/public/Preview/IVersionedPreviewFile.php', @@ -1479,6 +1480,7 @@ return array( 'OC\\Preview\\MSOffice2007' => $baseDir . '/lib/private/Preview/MSOffice2007.php', 'OC\\Preview\\MSOfficeDoc' => $baseDir . '/lib/private/Preview/MSOfficeDoc.php', 'OC\\Preview\\MarkDown' => $baseDir . '/lib/private/Preview/MarkDown.php', + 'OC\\Preview\\MimeIconProvider' => $baseDir . '/lib/private/Preview/MimeIconProvider.php', 'OC\\Preview\\Movie' => $baseDir . '/lib/private/Preview/Movie.php', 'OC\\Preview\\Office' => $baseDir . '/lib/private/Preview/Office.php', 'OC\\Preview\\OpenDocument' => $baseDir . '/lib/private/Preview/OpenDocument.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 48c6701c7c6..121931b60d0 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -566,6 +566,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\OCS\\IDiscoveryService' => __DIR__ . '/../../..' . '/lib/public/OCS/IDiscoveryService.php', 'OCP\\PreConditionNotMetException' => __DIR__ . '/../../..' . '/lib/public/PreConditionNotMetException.php', 'OCP\\Preview\\BeforePreviewFetchedEvent' => __DIR__ . '/../../..' . '/lib/public/Preview/BeforePreviewFetchedEvent.php', + 'OCP\\Preview\\IMimeIconProvider' => __DIR__ . '/../../..' . '/lib/public/Preview/IMimeIconProvider.php', 'OCP\\Preview\\IProvider' => __DIR__ . '/../../..' . '/lib/public/Preview/IProvider.php', 'OCP\\Preview\\IProviderV2' => __DIR__ . '/../../..' . '/lib/public/Preview/IProviderV2.php', 'OCP\\Preview\\IVersionedPreviewFile' => __DIR__ . '/../../..' . '/lib/public/Preview/IVersionedPreviewFile.php', @@ -1512,6 +1513,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Preview\\MSOffice2007' => __DIR__ . '/../../..' . '/lib/private/Preview/MSOffice2007.php', 'OC\\Preview\\MSOfficeDoc' => __DIR__ . '/../../..' . '/lib/private/Preview/MSOfficeDoc.php', 'OC\\Preview\\MarkDown' => __DIR__ . '/../../..' . '/lib/private/Preview/MarkDown.php', + 'OC\\Preview\\MimeIconProvider' => __DIR__ . '/../../..' . '/lib/private/Preview/MimeIconProvider.php', 'OC\\Preview\\Movie' => __DIR__ . '/../../..' . '/lib/private/Preview/Movie.php', 'OC\\Preview\\Office' => __DIR__ . '/../../..' . '/lib/private/Preview/Office.php', 'OC\\Preview\\OpenDocument' => __DIR__ . '/../../..' . '/lib/private/Preview/OpenDocument.php', diff --git a/lib/private/Preview/MimeIconProvider.php b/lib/private/Preview/MimeIconProvider.php new file mode 100644 index 00000000000..1e44e8ca80a --- /dev/null +++ b/lib/private/Preview/MimeIconProvider.php @@ -0,0 +1,98 @@ +<?php +/** + * @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com> + * + * @author John Molakvoæ <skjnldsv@protonmail.com> + * + * @license AGPL-3.0-or-later + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ +namespace OC\Preview; + +use OCA\Theming\ThemingDefaults; +use OCP\App\IAppManager; +use OCP\Files\IMimeTypeDetector; +use OCP\IConfig; +use OCP\IURLGenerator; +use OCP\Preview\IMimeIconProvider; + +class MimeIconProvider implements IMimeIconProvider { + public function __construct( + protected IMimeTypeDetector $mimetypeDetector, + protected IConfig $config, + protected IURLGenerator $urlGenerator, + protected IAppManager $appManager, + protected ThemingDefaults $themingDefaults, + ) { + } + + public function getMimeIconUrl(string $mime): null|string { + if (!$mime) { + return null; + } + + // Fetch all the aliases + $aliases = $this->mimetypeDetector->getAllAliases(); + + // Remove comments + $aliases = array_filter($aliases, static function ($key) { + return !($key === '' || $key[0] === '_'); + }, ARRAY_FILTER_USE_KEY); + + // Map all the aliases recursively + foreach ($aliases as $alias => $value) { + if ($alias === $mime) { + $mime = $value; + } + } + + $fileName = str_replace('/', '-', $mime); + if ($url = $this->searchfileName($fileName)) { + return $url; + } + + $mimeType = explode('/', $mime)[0]; + if ($url = $this->searchfileName($mimeType)) { + return $url; + } + + return null; + } + + private function searchfileName(string $fileName): null|string { + // If the file exists in the current enabled legacy + // custom theme, let's return it + $theme = $this->config->getSystemValue('theme', ''); + if (!empty($theme)) { + $path = "/themes/$theme/core/img/filetypes/$fileName.svg"; + if (file_exists(\OC::$SERVERROOT . $path)) { + return $this->urlGenerator->getAbsoluteURL($path); + } + } + + // Previously, we used to pass thi through Theming + // But it was only used to colour icons containing + // 0082c9. Since with vue we moved to inline svg icons, + // we can just use the default core icons. + + // Finally, if the file exists in core, let's return it + $path = "/core/img/filetypes/$fileName.svg"; + if (file_exists(\OC::$SERVERROOT . $path)) { + return $this->urlGenerator->getAbsoluteURL($path); + } + + return null; + } +} diff --git a/lib/private/Server.php b/lib/private/Server.php index e9966e83cae..e1de1b84e29 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -127,6 +127,7 @@ use OC\Notification\Manager; use OC\OCS\DiscoveryService; use OC\Preview\GeneratorHelper; use OC\Preview\IMagickSupport; +use OC\Preview\MimeIconProvider; use OC\Remote\Api\ApiFactory; use OC\Remote\InstanceFactory; use OC\RichObjectStrings\Validator; @@ -262,6 +263,7 @@ use OCA\Files_External\Service\GlobalStoragesService; use OCA\Files_External\Service\BackendService; use OCP\Profiler\IProfiler; use OC\Profiler\Profiler; +use OCP\Preview\IMimeIconProvider; /** * Class Server @@ -337,6 +339,7 @@ class Server extends ServerContainer implements IServerContainer { }); /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('PreviewManager', IPreview::class); + $this->registerAlias(IMimeIconProvider::class, MimeIconProvider::class); $this->registerService(\OC\Preview\Watcher::class, function (ContainerInterface $c) { return new \OC\Preview\Watcher( diff --git a/lib/public/Files/IMimeTypeDetector.php b/lib/public/Files/IMimeTypeDetector.php index 11ba5cfc95f..9992c153edc 100644 --- a/lib/public/Files/IMimeTypeDetector.php +++ b/lib/public/Files/IMimeTypeDetector.php @@ -82,4 +82,10 @@ interface IMimeTypeDetector { * @since 8.2.0 */ public function mimeTypeIcon($mimeType); + + /** + * @return string[] + * @since 28.0.0 + */ + public function getAllAliases(): array; } diff --git a/lib/public/Preview/IMimeIconProvider.php b/lib/public/Preview/IMimeIconProvider.php new file mode 100644 index 00000000000..cb397ddfda7 --- /dev/null +++ b/lib/public/Preview/IMimeIconProvider.php @@ -0,0 +1,37 @@ +<?php +/** + * @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com> + * + * @author John Molakvoæ <skjnldsv@protonmail.com> + * + * @license AGPL-3.0-or-later + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ +namespace OCP\Preview; + +/** + * Interface IMimeIconProvider + * + * @since 28.0.0 + */ +interface IMimeIconProvider { + /** + * Get the URL to the icon for the given mime type + * Used by the preview provider to show a mime icon + * if no preview is available. + * @since 28.0.0 + */ + public function getMimeIconUrl(string $mime): string|null; +} |