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
31namespace
32{
33static qreal s_defaultDevicePixelRatio = 1.0;
34}
35
36using namespace KIO;
37
38class KIO::PreviewJobPrivate : public KIO::JobPrivate
39{
40public:
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
77private:
78 QDir createTemporaryDir();
79 int maximumWorkers = 1;
80};
81
82void PreviewJob::setDefaultDevicePixelRatio(qreal defaultDevicePixelRatio)
83{
84 s_defaultDevicePixelRatio = defaultDevicePixelRatio;
85}
86
87PreviewJob::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
108PreviewJob::~PreviewJob()
109{
110}
111
112void PreviewJob::setScaleType(ScaleType type)
113{
114 Q_D(PreviewJob);
115 d->scaleType = type;
116}
117
118PreviewJob::ScaleType PreviewJob::scaleType() const
119{
120 Q_D(const PreviewJob);
121 return d->scaleType;
122}
123
124void 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
157void 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
179void KIO::PreviewJob::setSequenceIndex(int index)
180{
181 d_func()->sequenceIndex = index;
182}
183
184int KIO::PreviewJob::sequenceIndex() const
185{
186 return d_func()->sequenceIndex;
187}
188
189float KIO::PreviewJob::sequenceIndexWraparoundPoint() const
190{
191 return d_func()->thumbnailWorkerMetaData.value(QStringLiteral("sequenceIndexWraparoundPoint"), QStringLiteral("-1.0")).toFloat();
192}
193
194bool KIO::PreviewJob::handlesSequences() const
195{
196 return d_func()->thumbnailWorkerMetaData.value(QStringLiteral("handlesSequences")) == QStringLiteral("1");
197}
198
199void KIO::PreviewJob::setDevicePixelRatio(qreal dpr)
200{
201 d_func()->devicePixelRatio = dpr;
202}
203
204void PreviewJob::setIgnoreMaximumSize(bool ignoreSize)
205{
206 d_func()->ignoreMaximumSize = ignoreSize;
207}
208
209void 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
228void 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
255QList<KPluginMetaData> PreviewJob::availableThumbnailerPlugins()
256{
257 return FilePreviewJob::loadAvailablePlugins();
258}
259
260QStringList 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
270QStringList 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
282QStringList 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
292PreviewJob *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

source code of kio/src/gui/previewjob.cpp