]> source.dussan.org Git - nextcloud-server.git/commitdiff
feat: redirect to the mime icon if no preview available
authorJohn Molakvoæ <skjnldsv@protonmail.com>
Fri, 11 Aug 2023 12:32:42 +0000 (14:32 +0200)
committerJohn Molakvoæ <skjnldsv@protonmail.com>
Thu, 17 Aug 2023 16:56:38 +0000 (18:56 +0200)
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
apps/files/src/components/FileEntry.vue
core/Controller/PreviewController.php
core/openapi.json
lib/composer/composer/autoload_classmap.php
lib/composer/composer/autoload_static.php
lib/private/Preview/MimeIconProvider.php [new file with mode: 0644]
lib/private/Server.php
lib/public/Files/IMimeTypeDetector.php
lib/public/Preview/IMimeIconProvider.php [new file with mode: 0644]

index 98b4dfca07cb121f0539ca371d25738fc40b0d36..6e43d3340c72f2f127da2cd22ed7c6d23a2bc7b3 100644 (file)
                                        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 -->
 </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() {
index c9183466f90d764bbdff7ffe04bfcbd92fc94361..4cfc8143491628d4177bb70e129f19ece21f07ac 100644 (file)
@@ -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);
index d396aa9001a4c476cd9bd200e70f9ee6af32094f..709963c6492352270473e8ddc53c6363820d08c3 100644 (file)
                             "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": {
                                 "schema": {}
                             }
                         }
+                    },
+                    "303": {
+                        "description": "Redirect to the mime icon url if mimeFallback is true",
+                        "headers": {
+                            "Location": {
+                                "schema": {
+                                    "type": "string"
+                                }
+                            }
+                        }
                     }
                 }
             }
                             "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": {
                                 "schema": {}
                             }
                         }
+                    },
+                    "303": {
+                        "description": "Redirect to the mime icon url if mimeFallback is true",
+                        "headers": {
+                            "Location": {
+                                "schema": {
+                                    "type": "string"
+                                }
+                            }
+                        }
                     }
                 }
             }
index 2e5c239b6ed1be8d6351debe87de03bec40280ac..4b99fc1813550a219333bffd4f5ec73ec305ded1 100644 (file)
@@ -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',
index 48c6701c7c60535aaf43a63004e6f62d3d6afb3d..121931b60d06aab178f9574062344003551e9981 100644 (file)
@@ -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 (file)
index 0000000..1e44e8c
--- /dev/null
@@ -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;
+       }
+}
index e9966e83caee9414864b519e5d74ff641898ff8e..e1de1b84e29d22fa29deb1091bb24420379991bf 100644 (file)
@@ -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(
index 11ba5cfc95f91c44ec95e2fd0570446d0ce12e1c..9992c153edc7580d10a9ee2da384d38e89f78ed5 100644 (file)
@@ -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 (file)
index 0000000..cb397dd
--- /dev/null
@@ -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;
+}