| 1 | /* |
| 2 | * This file is part of the KDE libraries |
| 3 | * SPDX-FileCopyrightText: 2025 Akseli Lahtinen <akselmo@akselmo.dev> |
| 4 | * |
| 5 | * SPDX-License-Identifier: LGPL-2.0-or-later |
| 6 | */ |
| 7 | |
| 8 | #ifndef KIO_FILEPREVIEWJOB_H |
| 9 | #define KIO_FILEPREVIEWJOB_H |
| 10 | |
| 11 | #include "previewjob.h" |
| 12 | #include <KPluginMetaData> |
| 13 | #include <QDir> |
| 14 | #include <QImage> |
| 15 | #include <QSize> |
| 16 | #include <kfileitem.h> |
| 17 | #include <kio/job.h> |
| 18 | |
| 19 | namespace KIO |
| 20 | { |
| 21 | |
| 22 | struct PreviewItem { |
| 23 | KFileItem item; |
| 24 | QSize size = QSize(); |
| 25 | qreal devicePixelRatio = 1.0; |
| 26 | bool ignoreMaximumSize = false; |
| 27 | int sequenceIndex = 0; |
| 28 | PreviewJob::ScaleType scaleType = PreviewJob::ScaleType::ScaledAndCached; |
| 29 | int cacheSize = 0; |
| 30 | QMap<QString, int> deviceIdMap; |
| 31 | }; |
| 32 | |
| 33 | class SHM |
| 34 | { |
| 35 | public: |
| 36 | SHM(int id, uchar *address); |
| 37 | ~SHM(); |
| 38 | |
| 39 | static std::unique_ptr<SHM> create(int size); |
| 40 | |
| 41 | Q_DISABLE_COPY(SHM); |
| 42 | |
| 43 | int id() const; |
| 44 | uchar *address() const; |
| 45 | |
| 46 | private: |
| 47 | // Shared memory segment Id. The segment is allocated to a size |
| 48 | // of extent x extent x 4 (32 bit image) on first need. |
| 49 | int m_id; |
| 50 | // And the data area |
| 51 | uchar *m_address; |
| 52 | }; |
| 53 | |
| 54 | // Time (in milliseconds) to wait for kio-fuse in a PreviewJob before giving up. |
| 55 | static constexpr int s_kioFuseMountTimeout = 10000; |
| 56 | |
| 57 | /* |
| 58 | * This job does multiple small chained jobs to get the thumbnail for an item, |
| 59 | * and returns the result. |
| 60 | * |
| 61 | * First, it attempts to get the device id's for the given paths: thumbRoot and item.localUrl |
| 62 | * However if past jobs have already done this, it can reuse the old deviceId table and skip |
| 63 | * getting the device id's again. |
| 64 | * |
| 65 | * Device id's are required for checking if the thumbnail can be cached. |
| 66 | * |
| 67 | * After getting all this information, if the item has sequenceId higher than 0, |
| 68 | * we just get the next item in the sequence and return that result. |
| 69 | * |
| 70 | * If we're not sequencing, first we try to pull the thumbnail from the cache. |
| 71 | * If that is succesful, we just return the file and end the job. |
| 72 | * |
| 73 | * If not succesful, it's likely we do not have thumbnail for this item, so |
| 74 | * we generate one, either by using thumbnailerPlugin or standardThumbnailer. |
| 75 | * |
| 76 | * We then return the result, whatever it may be. |
| 77 | */ |
| 78 | class FilePreviewJob : public KIO::Job |
| 79 | { |
| 80 | Q_OBJECT |
| 81 | public: |
| 82 | FilePreviewJob(const PreviewItem &item, const QString &thumbRoot, const QMap<QString, KPluginMetaData> &mimeMap); |
| 83 | ~FilePreviewJob(); |
| 84 | |
| 85 | void start() override; |
| 86 | |
| 87 | QMap<QString, QString> thumbnailWorkerMetaData() const; |
| 88 | QMap<QString, int> deviceIdMap() const; |
| 89 | QImage previewImage() const; |
| 90 | PreviewItem item() const; |
| 91 | static QList<KPluginMetaData> loadAvailablePlugins(); |
| 92 | static QList<KPluginMetaData> standardThumbnailers(); |
| 93 | |
| 94 | private Q_SLOTS: |
| 95 | void slotStatFile(KJob *job); |
| 96 | void slotGetOrCreateThumbnail(KJob *job); |
| 97 | |
| 98 | private: |
| 99 | enum CachePolicy { |
| 100 | Prevent, |
| 101 | Allow, |
| 102 | Unknown |
| 103 | } m_currentDeviceCachePolicy = Unknown; |
| 104 | |
| 105 | QStringList m_enabledPlugins; |
| 106 | // The current item |
| 107 | KIO::PreviewItem m_item; |
| 108 | // The modification time of that URL |
| 109 | QDateTime m_tOrig; |
| 110 | // Path to thumbnail cache for the current size |
| 111 | QString m_thumbPath; |
| 112 | // Original URL of current item in RFC2396 format |
| 113 | // (file:///path/to/a%20file instead of file:/path/to/a file) |
| 114 | QByteArray m_origName; |
| 115 | // Thumbnail file name for current item |
| 116 | QString m_thumbName; |
| 117 | // Size of thumbnail |
| 118 | QSize m_size; |
| 119 | // Unscaled size of thumbnail (128, 256 or 512 if cache is enabled) |
| 120 | short m_cacheSize; |
| 121 | // Whether the thumbnail should be scaled and/or saved |
| 122 | PreviewJob::ScaleType m_scaleType; |
| 123 | bool m_ignoreMaximumSize; |
| 124 | int m_sequenceIndex; |
| 125 | // If the file to create a thumb for was a temp file, this is its name |
| 126 | QString m_tempName; |
| 127 | // The shared memory |
| 128 | std::unique_ptr<SHM> m_shm; |
| 129 | // Root of thumbnail cache |
| 130 | QString m_thumbRoot; |
| 131 | // Metadata returned from the KIO thumbnail worker |
| 132 | QMap<QString, QString> m_thumbnailWorkerMetaData; |
| 133 | qreal m_devicePixelRatio; |
| 134 | static const int m_idUnknown = -1; |
| 135 | // Id of a device storing currently processed file |
| 136 | int m_currentDeviceId = 0; |
| 137 | // Device ID for each file. Stored while in STATE_DEVICE_INFO state, used later on. |
| 138 | QMap<QString, int> m_deviceIdMap; |
| 139 | // the path of a unique temporary directory |
| 140 | QString m_tempDirPath; |
| 141 | // Whether to try using KIOFuse to resolve files. Set to false if KIOFuse is not available. |
| 142 | bool m_tryKioFuse = true; |
| 143 | // The preview image. If when emitting return this is empty, job can be considered as failed. |
| 144 | QImage m_preview; |
| 145 | |
| 146 | bool m_standardThumbnailer = false; |
| 147 | KPluginMetaData m_plugin; |
| 148 | |
| 149 | const QMap<QString, KPluginMetaData> m_mimeMap; |
| 150 | |
| 151 | void statFile(); |
| 152 | void getOrCreateThumbnail(); |
| 153 | static QImage loadThumbnailFromCache(const QString &url, qreal dpr); |
| 154 | bool isCacheValid(const QImage &thumb); |
| 155 | void createThumbnail(const QString &); |
| 156 | void createThumbnailViaFuse(const QUrl &, const QUrl &); |
| 157 | void createThumbnailViaLocalCopy(const QUrl &); |
| 158 | QString parentDirPath(const QString &path) const; |
| 159 | |
| 160 | void emitPreview(const QImage &thumb); |
| 161 | void slotThumbData(KIO::Job *, const QByteArray &); |
| 162 | void slotStandardThumbData(KIO::Job *, const QImage &); |
| 163 | // Checks if thumbnail is on encrypted partition different than thumbRoot |
| 164 | CachePolicy canBeCached(const QString &path); |
| 165 | int getDeviceId(const QString &path); |
| 166 | void saveThumbnailData(QImage &thumb); |
| 167 | void preparePluginForMimetype(const QString &mimeType); |
| 168 | static void saveThumbnailToCache(const QImage &thumb, const QString &path); |
| 169 | }; |
| 170 | |
| 171 | inline FilePreviewJob *filePreviewJob(const PreviewItem &item, const QString &thumbRoot, const QMap<QString, KPluginMetaData> &mimeMap) |
| 172 | { |
| 173 | auto job = new FilePreviewJob(item, thumbRoot, mimeMap); |
| 174 | return job; |
| 175 | } |
| 176 | } |
| 177 | |
| 178 | #endif |
| 179 | |