| 1 | // -*- c++ -*- |
| 2 | /* |
| 3 | This file is part of the KDE libraries |
| 4 | SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org> |
| 5 | SPDX-FileCopyrightText: 2000 Carsten Pfeiffer <pfeiffer@kde.org> |
| 6 | SPDX-FileCopyrightText: 2001 Malte Starostik <malte.starostik@t-online.de> |
| 7 | |
| 8 | SPDX-License-Identifier: LGPL-2.0-or-later |
| 9 | */ |
| 10 | |
| 11 | #include "previewjob.h" |
| 12 | #include "filepreviewjob.h" |
| 13 | #include "kiogui_debug.h" |
| 14 | #include "kprotocolinfo.h" |
| 15 | |
| 16 | #include <KConfigGroup> |
| 17 | #include <KSharedConfig> |
| 18 | #include <QMetaMethod> |
| 19 | #include <QMimeDatabase> |
| 20 | #include <QPixmap> |
| 21 | #include <QStandardPaths> |
| 22 | |
| 23 | #include "job_p.h" |
| 24 | |
| 25 | #ifdef WITH_QTDBUS |
| 26 | #include <QDBusConnection> |
| 27 | #include <QDBusError> |
| 28 | |
| 29 | #endif |
| 30 | |
| 31 | namespace |
| 32 | { |
| 33 | static qreal s_defaultDevicePixelRatio = 1.0; |
| 34 | } |
| 35 | |
| 36 | using namespace KIO; |
| 37 | |
| 38 | class KIO::PreviewJobPrivate : public KIO::JobPrivate |
| 39 | { |
| 40 | public: |
| 41 | PreviewJobPrivate(const KFileItemList &items, const QSize &size) |
| 42 | : initialItems(items) |
| 43 | , size(size) |
| 44 | , scaleType(PreviewJob::ScaleType::ScaledAndCached) |
| 45 | , ignoreMaximumSize(false) |
| 46 | , sequenceIndex(0) |
| 47 | { |
| 48 | // https://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html#DIRECTORY |
| 49 | thumbRoot = QStandardPaths::writableLocation(type: QStandardPaths::GenericCacheLocation) + QLatin1String("/thumbnails/" ); |
| 50 | } |
| 51 | |
| 52 | KFileItemList initialItems; |
| 53 | QStringList enabledPlugins; |
| 54 | // Our todo list :) |
| 55 | // We remove the first item at every step, so use std::list |
| 56 | QSize size; |
| 57 | std::list<PreviewItem> items; |
| 58 | // Whether the thumbnail should be scaled ando/or saved |
| 59 | PreviewJob::ScaleType scaleType; |
| 60 | bool ignoreMaximumSize; |
| 61 | int sequenceIndex; |
| 62 | // Root of thumbnail cache |
| 63 | QString thumbRoot; |
| 64 | // Metadata returned from the KIO thumbnail worker |
| 65 | QMap<QString, QString> thumbnailWorkerMetaData; |
| 66 | qreal devicePixelRatio = s_defaultDevicePixelRatio; |
| 67 | // Cache the deviceIdMap so we dont need to stat the files every time |
| 68 | QMap<QString, int> deviceIdMap; |
| 69 | |
| 70 | QMap<QString, KPluginMetaData> mimeMap; |
| 71 | |
| 72 | void determineNextFile(); |
| 73 | void startPreview(); |
| 74 | |
| 75 | Q_DECLARE_PUBLIC(PreviewJob) |
| 76 | |
| 77 | private: |
| 78 | QDir createTemporaryDir(); |
| 79 | int maximumWorkers = 1; |
| 80 | }; |
| 81 | |
| 82 | void PreviewJob::setDefaultDevicePixelRatio(qreal defaultDevicePixelRatio) |
| 83 | { |
| 84 | s_defaultDevicePixelRatio = defaultDevicePixelRatio; |
| 85 | } |
| 86 | |
| 87 | PreviewJob::PreviewJob(const KFileItemList &items, const QSize &size, const QStringList *enabledPlugins) |
| 88 | : KIO::Job(*new PreviewJobPrivate(items, size)) |
| 89 | { |
| 90 | Q_D(PreviewJob); |
| 91 | |
| 92 | const KConfigGroup globalConfig(KSharedConfig::openConfig(), QStringLiteral("PreviewSettings" )); |
| 93 | if (enabledPlugins) { |
| 94 | d->enabledPlugins = *enabledPlugins; |
| 95 | } else { |
| 96 | d->enabledPlugins = |
| 97 | globalConfig.readEntry(key: "Plugins" , |
| 98 | aDefault: QStringList{QStringLiteral("directorythumbnail" ), QStringLiteral("imagethumbnail" ), QStringLiteral("jpegthumbnail" )}); |
| 99 | } |
| 100 | |
| 101 | d->maximumWorkers = KProtocolInfo::maxWorkers(QStringLiteral("thumbnail" )); |
| 102 | // Return to event loop first, determineNextFile() might delete this; |
| 103 | QTimer::singleShot(interval: 0, receiver: this, slot: [d]() { |
| 104 | d->startPreview(); |
| 105 | }); |
| 106 | } |
| 107 | |
| 108 | PreviewJob::~PreviewJob() |
| 109 | { |
| 110 | } |
| 111 | |
| 112 | void PreviewJob::setScaleType(ScaleType type) |
| 113 | { |
| 114 | Q_D(PreviewJob); |
| 115 | d->scaleType = type; |
| 116 | } |
| 117 | |
| 118 | PreviewJob::ScaleType PreviewJob::scaleType() const |
| 119 | { |
| 120 | Q_D(const PreviewJob); |
| 121 | return d->scaleType; |
| 122 | } |
| 123 | |
| 124 | void PreviewJobPrivate::startPreview() |
| 125 | { |
| 126 | Q_Q(PreviewJob); |
| 127 | |
| 128 | // Load the list of plugins to determine which MIME types are supported |
| 129 | const QList<KPluginMetaData> plugins = KIO::FilePreviewJob::loadAvailablePlugins(); |
| 130 | |
| 131 | for (const KPluginMetaData &plugin : plugins) { |
| 132 | bool pluginIsEnabled = enabledPlugins.contains(str: plugin.pluginId()); |
| 133 | const auto mimeTypes = plugin.mimeTypes(); |
| 134 | for (const QString &mimeType : mimeTypes) { |
| 135 | if (pluginIsEnabled && !mimeMap.contains(key: mimeType)) { |
| 136 | mimeMap.insert(key: mimeType, value: plugin); |
| 137 | } |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | for (const auto &fileItem : std::as_const(t&: initialItems)) { |
| 142 | PreviewItem previewItem; |
| 143 | previewItem.item = fileItem; |
| 144 | previewItem.devicePixelRatio = devicePixelRatio; |
| 145 | previewItem.sequenceIndex = sequenceIndex; |
| 146 | previewItem.ignoreMaximumSize = ignoreMaximumSize; |
| 147 | previewItem.scaleType = scaleType; |
| 148 | previewItem.size = size; |
| 149 | previewItem.deviceIdMap = deviceIdMap; |
| 150 | items.push_back(x: previewItem); |
| 151 | } |
| 152 | |
| 153 | initialItems.clear(); |
| 154 | determineNextFile(); |
| 155 | } |
| 156 | |
| 157 | void PreviewJob::removeItem(const QUrl &url) |
| 158 | { |
| 159 | Q_D(PreviewJob); |
| 160 | |
| 161 | auto it = std::find_if(first: d->items.cbegin(), last: d->items.cend(), pred: [&url](const PreviewItem &pItem) { |
| 162 | return url == pItem.item.url(); |
| 163 | }); |
| 164 | if (it != d->items.cend()) { |
| 165 | d->items.erase(position: it); |
| 166 | } |
| 167 | |
| 168 | for (auto subjob : subjobs()) { |
| 169 | FilePreviewJob *previewJob = static_cast<KIO::FilePreviewJob *>(subjob); |
| 170 | if (previewJob && previewJob->item().item.url() == url) { |
| 171 | subjob->kill(); |
| 172 | removeSubjob(job: subjob); |
| 173 | d->determineNextFile(); |
| 174 | break; |
| 175 | } |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | void KIO::PreviewJob::setSequenceIndex(int index) |
| 180 | { |
| 181 | d_func()->sequenceIndex = index; |
| 182 | } |
| 183 | |
| 184 | int KIO::PreviewJob::sequenceIndex() const |
| 185 | { |
| 186 | return d_func()->sequenceIndex; |
| 187 | } |
| 188 | |
| 189 | float KIO::PreviewJob::sequenceIndexWraparoundPoint() const |
| 190 | { |
| 191 | return d_func()->thumbnailWorkerMetaData.value(QStringLiteral("sequenceIndexWraparoundPoint" ), QStringLiteral("-1.0" )).toFloat(); |
| 192 | } |
| 193 | |
| 194 | bool KIO::PreviewJob::handlesSequences() const |
| 195 | { |
| 196 | return d_func()->thumbnailWorkerMetaData.value(QStringLiteral("handlesSequences" )) == QStringLiteral("1" ); |
| 197 | } |
| 198 | |
| 199 | void KIO::PreviewJob::setDevicePixelRatio(qreal dpr) |
| 200 | { |
| 201 | d_func()->devicePixelRatio = dpr; |
| 202 | } |
| 203 | |
| 204 | void PreviewJob::setIgnoreMaximumSize(bool ignoreSize) |
| 205 | { |
| 206 | d_func()->ignoreMaximumSize = ignoreSize; |
| 207 | } |
| 208 | |
| 209 | void PreviewJobPrivate::determineNextFile() |
| 210 | { |
| 211 | Q_Q(PreviewJob); |
| 212 | |
| 213 | if (q->subjobs().count() == 0 && items.empty()) { |
| 214 | q->emitResult(); |
| 215 | return; |
| 216 | } |
| 217 | |
| 218 | const int jobsToRun = qMin(a: (int)items.size(), b: maximumWorkers - q->subjobs().count()); |
| 219 | for (int i = 0; i < jobsToRun; i++) { |
| 220 | auto item = items.front(); |
| 221 | items.pop_front(); |
| 222 | FilePreviewJob *job = KIO::filePreviewJob(item, thumbRoot, mimeMap); |
| 223 | q->addSubjob(job); |
| 224 | job->start(); |
| 225 | } |
| 226 | } |
| 227 | |
| 228 | void PreviewJob::slotResult(KJob *job) |
| 229 | { |
| 230 | Q_D(PreviewJob); |
| 231 | FilePreviewJob *previewJob = static_cast<KIO::FilePreviewJob *>(job); |
| 232 | if (previewJob) { |
| 233 | auto fileItem = previewJob->item().item; |
| 234 | if (!previewJob->previewImage().isNull()) { |
| 235 | d->thumbnailWorkerMetaData = previewJob->thumbnailWorkerMetaData(); |
| 236 | d->deviceIdMap = previewJob->deviceIdMap(); |
| 237 | auto previewImage = previewJob->previewImage(); |
| 238 | Q_EMIT generated(item: fileItem, preview: previewImage); |
| 239 | if (isSignalConnected(signal: QMetaMethod::fromSignal(signal: &PreviewJob::gotPreview))) { |
| 240 | QPixmap pixmap = QPixmap::fromImage(image: previewImage); |
| 241 | pixmap.setDevicePixelRatio(d->devicePixelRatio); |
| 242 | Q_EMIT gotPreview(item: fileItem, preview: pixmap); |
| 243 | } |
| 244 | } else { |
| 245 | Q_EMIT failed(item: fileItem); |
| 246 | } |
| 247 | } |
| 248 | removeSubjob(job); |
| 249 | if (job->error() > 0) { |
| 250 | qCWarning(KIO_GUI) << "PreviewJob subjob had an error:" << job->errorString(); |
| 251 | } |
| 252 | d->determineNextFile(); |
| 253 | } |
| 254 | |
| 255 | QList<KPluginMetaData> PreviewJob::availableThumbnailerPlugins() |
| 256 | { |
| 257 | return FilePreviewJob::loadAvailablePlugins(); |
| 258 | } |
| 259 | |
| 260 | QStringList PreviewJob::availablePlugins() |
| 261 | { |
| 262 | QStringList result; |
| 263 | const auto plugins = KIO::FilePreviewJob::loadAvailablePlugins(); |
| 264 | for (const KPluginMetaData &plugin : plugins) { |
| 265 | result << plugin.pluginId(); |
| 266 | } |
| 267 | return result; |
| 268 | } |
| 269 | |
| 270 | QStringList PreviewJob::defaultPlugins() |
| 271 | { |
| 272 | const QStringList exclusionList = QStringList() << QStringLiteral("textthumbnail" ); |
| 273 | |
| 274 | QStringList defaultPlugins = availablePlugins(); |
| 275 | for (const QString &plugin : exclusionList) { |
| 276 | defaultPlugins.removeAll(t: plugin); |
| 277 | } |
| 278 | |
| 279 | return defaultPlugins; |
| 280 | } |
| 281 | |
| 282 | QStringList PreviewJob::supportedMimeTypes() |
| 283 | { |
| 284 | QStringList result; |
| 285 | const auto plugins = KIO::FilePreviewJob::loadAvailablePlugins(); |
| 286 | for (const KPluginMetaData &plugin : plugins) { |
| 287 | result += plugin.mimeTypes(); |
| 288 | } |
| 289 | return result; |
| 290 | } |
| 291 | |
| 292 | PreviewJob *KIO::filePreview(const KFileItemList &items, const QSize &size, const QStringList *enabledPlugins) |
| 293 | { |
| 294 | return new PreviewJob(items, size, enabledPlugins); |
| 295 | } |
| 296 | |
| 297 | #include "moc_previewjob.cpp" |
| 298 | |