KIO

previewjob.cpp
1 // -*- c++ -*-
2 /*
3  This file is part of the KDE libraries
4  SPDX-FileCopyrightText: 2000 David Faure <[email protected]>
5  SPDX-FileCopyrightText: 2000 Carsten Pfeiffer <[email protected]>
6  SPDX-FileCopyrightText: 2001 Malte Starostik <[email protected]>
7 
8  SPDX-License-Identifier: LGPL-2.0-or-later
9 */
10 
11 #include "previewjob.h"
12 #include "kio_widgets_debug.h"
13 
14 #if defined(Q_OS_UNIX) && !defined(Q_OS_ANDROID)
15 #define WITH_SHM 1
16 #else
17 #define WITH_SHM 0
18 #endif
19 
20 #if WITH_SHM
21 #include <sys/ipc.h>
22 #include <sys/shm.h>
23 #endif
24 
25 #include <limits>
26 #include <set>
27 
28 #include <QDir>
29 #include <QFile>
30 #include <QImage>
31 #include <QPixmap>
32 #include <QRegularExpression>
33 #include <QSaveFile>
34 #include <QTemporaryFile>
35 #include <QTimer>
36 
37 #include <QCryptographicHash>
38 
39 #include <KConfigGroup>
40 #include <KMountPoint>
41 #include <KPluginInfo>
42 #include <KPluginMetaData>
43 #include <KService>
44 #include <KServiceTypeTrader>
45 #include <KSharedConfig>
46 #include <QMimeDatabase>
47 #include <QStandardPaths>
48 #include <Solid/Device>
49 #include <Solid/StorageAccess>
50 #include <kprotocolinfo.h>
51 
52 #include <algorithm>
53 #include <cmath>
54 
55 #include "job_p.h"
56 
57 namespace
58 {
59 static int s_defaultDevicePixelRatio = 1;
60 }
61 
62 namespace KIO
63 {
64 struct PreviewItem;
65 }
66 using namespace KIO;
67 
68 struct KIO::PreviewItem {
69  KFileItem item;
70  KPluginMetaData plugin;
71 };
72 
73 class KIO::PreviewJobPrivate : public KIO::JobPrivate
74 {
75 public:
76  PreviewJobPrivate(const KFileItemList &items, const QSize &size)
77  : initialItems(items)
78  , width(size.width())
79  , height(size.height())
80  , cacheSize(0)
81  , bScale(true)
82  , bSave(true)
83  , ignoreMaximumSize(false)
84  , sequenceIndex(0)
85  , succeeded(false)
86  , maximumLocalSize(0)
87  , maximumRemoteSize(0)
88  , iconSize(0)
89  , iconAlpha(70)
90  , shmid(-1)
91  , shmaddr(nullptr)
92  {
93  // http://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html#DIRECTORY
95  }
96 
97  enum {
98  STATE_STATORIG, // if the thumbnail exists
99  STATE_GETORIG, // if we create it
100  STATE_CREATETHUMB, // thumbnail:/ slave
101  STATE_DEVICE_INFO, // additional state check to get needed device ids
102  } state;
103 
104  KFileItemList initialItems;
105  QStringList enabledPlugins;
106  // Some plugins support remote URLs, <protocol, mimetypes>
107  QHash<QString, QStringList> m_remoteProtocolPlugins;
108  // Our todo list :)
109  // We remove the first item at every step, so use std::list
110  std::list<PreviewItem> items;
111  // The current item
112  PreviewItem currentItem;
113  // The modification time of that URL
114  QDateTime tOrig;
115  // Path to thumbnail cache for the current size
116  QString thumbPath;
117  // Original URL of current item in RFC2396 format
118  // (file:///path/to/a%20file instead of file:/path/to/a file)
119  QByteArray origName;
120  // Thumbnail file name for current item
121  QString thumbName;
122  // Size of thumbnail
123  int width;
124  int height;
125  // Unscaled size of thumbnail (128, 256 or 512 if cache is enabled)
126  short cacheSize;
127  // Whether the thumbnail should be scaled
128  bool bScale;
129  // Whether we should save the thumbnail
130  bool bSave;
131  bool ignoreMaximumSize;
132  int sequenceIndex;
133  bool succeeded;
134  // If the file to create a thumb for was a temp file, this is its name
135  QString tempName;
136  KIO::filesize_t maximumLocalSize;
137  KIO::filesize_t maximumRemoteSize;
138  // the size for the icon overlay
139  int iconSize;
140  // the transparency of the blended MIME type icon
141  int iconAlpha;
142  // Shared memory segment Id. The segment is allocated to a size
143  // of extent x extent x 4 (32 bit image) on first need.
144  int shmid;
145  // And the data area
146  uchar *shmaddr;
147  // Size of the shm segment
148  size_t shmsize;
149  // Root of thumbnail cache
150  QString thumbRoot;
151  // Metadata returned from the KIO thumbnail slave
152  QMap<QString, QString> thumbnailSlaveMetaData;
153  int devicePixelRatio = s_defaultDevicePixelRatio;
154  static const int idUnknown = -1;
155  // Id of a device storing currently processed file
156  int currentDeviceId = 0;
157  // Device ID for each file. Stored while in STATE_DEVICE_INFO state, used later on.
158  QMap<QString, int> deviceIdMap;
159  enum CachePolicy { Prevent, Allow, Unknown } currentDeviceCachePolicy = Unknown;
160 
161  void getOrCreateThumbnail();
162  bool statResultThumbnail();
163  void createThumbnail(const QString &);
164  void cleanupTempFile();
165  void determineNextFile();
166  void emitPreview(const QImage &thumb);
167 
168  void startPreview();
169  void slotThumbData(KIO::Job *, const QByteArray &);
170  // Checks if thumbnail is on encrypted partition different than thumbRoot
171  CachePolicy canBeCached(const QString &path);
172  int getDeviceId(const QString &path);
173 
174  Q_DECLARE_PUBLIC(PreviewJob)
175 
176  static QVector<KPluginMetaData> loadAvailablePlugins()
177  {
178  static QVector<KPluginMetaData> jsonMetaDataPlugins;
179  if (!jsonMetaDataPlugins.isEmpty()) {
180  return jsonMetaDataPlugins;
181  }
182  jsonMetaDataPlugins = KPluginMetaData::findPlugins(QStringLiteral("kf" QT_STRINGIFY(QT_VERSION_MAJOR) "/thumbcreator"));
183  std::set<QString> pluginIds;
184  for (const KPluginMetaData &data : std::as_const(jsonMetaDataPlugins)) {
185  pluginIds.insert(data.pluginId());
186  }
187 #if KSERVICE_ENABLE_DEPRECATED_SINCE(5, 88)
188  QT_WARNING_PUSH
189  QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations")
190  QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations")
191  const KService::List plugins = KServiceTypeTrader::self()->query(QStringLiteral("ThumbCreator"));
192  for (const auto &plugin : plugins) {
193  if (KPluginInfo info(plugin); info.isValid()) {
194  if (auto [it, inserted] = pluginIds.insert(plugin->desktopEntryName()); inserted) {
195  jsonMetaDataPlugins << info.toMetaData();
196  }
197  } else {
198  // Hack for directory thumbnailer: It has a hardcoded plugin id in the kio-slave and not any C++ plugin
199  // Consequently we just use the base name as the plugin file for our KPluginMetaData object
200  const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("kservices5/") + plugin->entryPath());
202  jsonMetaDataPlugins << KPluginMetaData(tmpData.rawData(), QFileInfo(path).baseName(), path);
203  }
204  }
205  QT_WARNING_POP
206 #else
207 #pragma message("TODO: directory thumbnailer needs a non-desktop file solution ")
208 #endif
209  return jsonMetaDataPlugins;
210  }
211 };
212 
213 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 86)
214 void PreviewJob::setDefaultDevicePixelRatio(int defaultDevicePixelRatio)
215 {
216  s_defaultDevicePixelRatio = defaultDevicePixelRatio;
217 }
218 #endif
219 
220 void PreviewJob::setDefaultDevicePixelRatio(qreal defaultDevicePixelRatio)
221 {
222  s_defaultDevicePixelRatio = std::ceil(defaultDevicePixelRatio);
223 }
224 
225 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(4, 7)
226 PreviewJob::PreviewJob(const KFileItemList &items, int width, int height, int iconSize, int iconAlpha, bool scale, bool save, const QStringList *enabledPlugins)
227  : KIO::Job(*new PreviewJobPrivate(items, QSize(width, height ? height : width)))
228 {
229  Q_D(PreviewJob);
230  d->enabledPlugins = enabledPlugins ? *enabledPlugins : availablePlugins();
231  d->iconSize = iconSize;
232  d->iconAlpha = iconAlpha;
233  d->bScale = scale;
234  d->bSave = save && scale;
235 
236  // Return to event loop first, determineNextFile() might delete this;
237  QTimer::singleShot(0, this, [d]() {
238  d->startPreview();
239  });
240 }
241 #endif
242 
243 PreviewJob::PreviewJob(const KFileItemList &items, const QSize &size, const QStringList *enabledPlugins)
244  : KIO::Job(*new PreviewJobPrivate(items, size))
245 {
246  Q_D(PreviewJob);
247 
248  if (enabledPlugins) {
249  d->enabledPlugins = *enabledPlugins;
250  } else {
251  const KConfigGroup globalConfig(KSharedConfig::openConfig(), "PreviewSettings");
252  d->enabledPlugins =
253  globalConfig.readEntry("Plugins",
254  QStringList{QStringLiteral("directorythumbnail"), QStringLiteral("imagethumbnail"), QStringLiteral("jpegthumbnail")});
255  }
256 
257  // Return to event loop first, determineNextFile() might delete this;
258  QTimer::singleShot(0, this, [d]() {
259  d->startPreview();
260  });
261 }
262 
263 PreviewJob::~PreviewJob()
264 {
265 #if WITH_SHM
266  Q_D(PreviewJob);
267  if (d->shmaddr) {
268  shmdt((char *)d->shmaddr);
269  shmctl(d->shmid, IPC_RMID, nullptr);
270  }
271 #endif
272 }
273 
275 {
276  Q_D(PreviewJob);
277  d->iconSize = size;
278 }
279 
281 {
282  Q_D(const PreviewJob);
283  return d->iconSize;
284 }
285 
287 {
288  Q_D(PreviewJob);
289  d->iconAlpha = qBound(0, alpha, 255);
290 }
291 
293 {
294  Q_D(const PreviewJob);
295  return d->iconAlpha;
296 }
297 
299 {
300  Q_D(PreviewJob);
301  switch (type) {
302  case Unscaled:
303  d->bScale = false;
304  d->bSave = false;
305  break;
306  case Scaled:
307  d->bScale = true;
308  d->bSave = false;
309  break;
310  case ScaledAndCached:
311  d->bScale = true;
312  d->bSave = true;
313  break;
314  default:
315  break;
316  }
317 }
318 
320 {
321  Q_D(const PreviewJob);
322  if (d->bScale) {
323  return d->bSave ? ScaledAndCached : Scaled;
324  }
325  return Unscaled;
326 }
327 
328 void PreviewJobPrivate::startPreview()
329 {
330  Q_Q(PreviewJob);
331  // Load the list of plugins to determine which MIME types are supported
332  const QVector<KPluginMetaData> plugins = KIO::PreviewJobPrivate::loadAvailablePlugins();
335 
336  for (const KPluginMetaData &plugin : plugins) {
337  QStringList protocols = plugin.value(QStringLiteral("X-KDE-Protocols"), QStringList());
338  const QString p = plugin.value(QStringLiteral("X-KDE-Protocol"));
339  if (!p.isEmpty()) {
340  protocols.append(p);
341  }
342  for (const QString &protocol : std::as_const(protocols)) {
343  // Add supported MIME type for this protocol
344  QStringList &_ms = m_remoteProtocolPlugins[protocol];
345  const auto mimeTypes = plugin.mimeTypes();
346  for (const QString &_m : mimeTypes) {
347  protocolMap[protocol].insert(_m, plugin);
348  if (!_ms.contains(_m)) {
349  _ms.append(_m);
350  }
351  }
352  }
353  if (enabledPlugins.contains(plugin.pluginId())) {
354  const auto mimeTypes = plugin.mimeTypes();
355  for (const QString &mimeType : mimeTypes) {
356  mimeMap.insert(mimeType, plugin);
357  }
358  }
359  }
360 
361  // Look for images and store the items in our todo list :)
362  bool bNeedCache = false;
363  for (const auto &fileItem : std::as_const(initialItems)) {
364  PreviewItem item;
365  item.item = fileItem;
366 
367  const QString mimeType = item.item.mimetype();
368  KPluginMetaData plugin;
369 
370  // look for protocol-specific thumbnail plugins first
371  auto it = protocolMap.constFind(item.item.url().scheme());
372  if (it != protocolMap.constEnd()) {
373  plugin = it.value().value(mimeType);
374  }
375 
376  if (!plugin.isValid()) {
377  auto pluginIt = mimeMap.constFind(mimeType);
378  if (pluginIt == mimeMap.constEnd()) {
379  // check MIME type inheritance, resolve aliases
380  QMimeDatabase db;
381  const QMimeType mimeInfo = db.mimeTypeForName(mimeType);
382  if (mimeInfo.isValid()) {
383  const QStringList parentMimeTypes = mimeInfo.allAncestors();
384  for (const QString &parentMimeType : parentMimeTypes) {
385  pluginIt = mimeMap.constFind(parentMimeType);
386  if (pluginIt != mimeMap.constEnd()) {
387  break;
388  }
389  }
390  }
391 
392  if (pluginIt == mimeMap.constEnd()) {
393  // Check the wildcards last, see BUG 453480
394  QString groupMimeType = mimeType;
395  const int slashIdx = groupMimeType.indexOf(QLatin1Char('/'));
396  if (slashIdx != -1) {
397  // Replace everything after '/' with '*'
398  groupMimeType.truncate(slashIdx + 1);
399  groupMimeType += QLatin1Char('*');
400  }
401  pluginIt = mimeMap.constFind(groupMimeType);
402  }
403  }
404 
405  if (pluginIt != mimeMap.constEnd()) {
406  plugin = *pluginIt;
407  }
408  }
409 
410  if (plugin.isValid()) {
411  item.plugin = plugin;
412  items.push_back(item);
413  if (!bNeedCache && bSave && plugin.value(QStringLiteral("CacheThumbnail"), true)) {
414  const QUrl url = fileItem.url();
415  if (!url.isLocalFile() || !url.adjusted(QUrl::RemoveFilename).toLocalFile().startsWith(thumbRoot)) {
416  bNeedCache = true;
417  }
418  }
419  } else {
420  Q_EMIT q->failed(fileItem);
421  }
422  }
423 
424  KConfigGroup cg(KSharedConfig::openConfig(), "PreviewSettings");
425  maximumLocalSize = cg.readEntry("MaximumSize", std::numeric_limits<KIO::filesize_t>::max());
426  maximumRemoteSize = cg.readEntry<KIO::filesize_t>("MaximumRemoteSize", 0);
427 
428  if (bNeedCache) {
429  const int longer = std::max(width, height);
430  if (longer <= 128) {
431  cacheSize = 128;
432  } else if (longer <= 256) {
433  cacheSize = 256;
434  } else if (longer <= 512) {
435  cacheSize = 512;
436  } else {
437  cacheSize = 1024;
438  }
439 
440  struct CachePool {
441  QString path;
442  int minSize;
443  };
444 
445  const static auto pools = {
446  CachePool{QStringLiteral("/normal/"), 128},
447  CachePool{QStringLiteral("/large/"), 256},
448  CachePool{QStringLiteral("/x-large/"), 512},
449  CachePool{QStringLiteral("/xx-large/"), 1024},
450  };
451 
452  QString thumbDir;
453  int wants = devicePixelRatio * cacheSize;
454  for (const auto &p : pools) {
455  if (p.minSize < wants) {
456  continue;
457  } else {
458  thumbDir = p.path;
459  break;
460  }
461  }
462  thumbPath = thumbRoot + thumbDir;
463 
464  if (!QDir(thumbPath).exists()) {
465  if (QDir().mkpath(thumbPath)) { // Qt5 TODO: mkpath(dirPath, permissions)
466  QFile f(thumbPath);
467  f.setPermissions(QFile::ReadUser | QFile::WriteUser | QFile::ExeUser); // 0700
468  }
469  }
470  } else {
471  bSave = false;
472  }
473 
474  initialItems.clear();
475  determineNextFile();
476 }
477 
478 void PreviewJob::removeItem(const QUrl &url)
479 {
480  Q_D(PreviewJob);
481 
482  auto it = std::find_if(d->items.cbegin(), d->items.cend(), [&url](const PreviewItem &pItem) {
483  return url == pItem.item.url();
484  });
485  if (it != d->items.cend()) {
486  d->items.erase(it);
487  }
488 
489  if (d->currentItem.item.url() == url) {
490  KJob *job = subjobs().first();
491  job->kill();
492  removeSubjob(job);
493  d->determineNextFile();
494  }
495 }
496 
498 {
499  d_func()->sequenceIndex = index;
500 }
501 
503 {
504  return d_func()->sequenceIndex;
505 }
506 
508 {
509  return d_func()->thumbnailSlaveMetaData.value(QStringLiteral("sequenceIndexWraparoundPoint"), QStringLiteral("-1.0")).toFloat();
510 }
511 
513 {
514  return d_func()->thumbnailSlaveMetaData.value(QStringLiteral("handlesSequences")) == QStringLiteral("1");
515 }
516 
517 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 86)
519 {
520  d_func()->devicePixelRatio = dpr;
521 }
522 #endif
523 
525 {
526  d_func()->devicePixelRatio = std::ceil(dpr);
527 }
528 
529 void PreviewJob::setIgnoreMaximumSize(bool ignoreSize)
530 {
531  d_func()->ignoreMaximumSize = ignoreSize;
532 }
533 
534 void PreviewJobPrivate::cleanupTempFile()
535 {
536  if (!tempName.isEmpty()) {
537  Q_ASSERT((!QFileInfo(tempName).isDir() && QFileInfo(tempName).isFile()) || QFileInfo(tempName).isSymLink());
538  QFile::remove(tempName);
539  tempName.clear();
540  }
541 }
542 
543 void PreviewJobPrivate::determineNextFile()
544 {
545  Q_Q(PreviewJob);
546  if (!currentItem.item.isNull()) {
547  if (!succeeded) {
548  Q_EMIT q->failed(currentItem.item);
549  }
550  }
551  // No more items ?
552  if (items.empty()) {
553  q->emitResult();
554  return;
555  } else {
556  // First, stat the orig file
557  state = PreviewJobPrivate::STATE_STATORIG;
558  currentItem = items.front();
559  items.pop_front();
560  succeeded = false;
561  KIO::Job *job = KIO::statDetails(currentItem.item.url(), StatJob::SourceSide, KIO::StatDefaultDetails | KIO::StatInode, KIO::HideProgressInfo);
562  job->addMetaData(QStringLiteral("thumbnail"), QStringLiteral("1"));
563  job->addMetaData(QStringLiteral("no-auth-prompt"), QStringLiteral("true"));
564  q->addSubjob(job);
565  }
566 }
567 
568 void PreviewJob::slotResult(KJob *job)
569 {
570  Q_D(PreviewJob);
571 
572  removeSubjob(job);
573  Q_ASSERT(!hasSubjobs()); // We should have only one job at a time ...
574  switch (d->state) {
575  case PreviewJobPrivate::STATE_STATORIG: {
576  if (job->error()) { // that's no good news...
577  // Drop this one and move on to the next one
578  d->determineNextFile();
579  return;
580  }
581  const KIO::UDSEntry statResult = static_cast<KIO::StatJob *>(job)->statResult();
582  d->currentDeviceId = statResult.numberValue(KIO::UDSEntry::UDS_DEVICE_ID, 0);
584 
585  bool skipCurrentItem = false;
587  const QUrl itemUrl = d->currentItem.item.mostLocalUrl();
588 
589  if (itemUrl.isLocalFile() || KProtocolInfo::protocolClass(itemUrl.scheme()) == QLatin1String(":local")) {
590  skipCurrentItem = !d->ignoreMaximumSize && size > d->maximumLocalSize && !d->currentItem.plugin.value(QStringLiteral("IgnoreMaximumSize"), false);
591  } else {
592  // For remote items the "IgnoreMaximumSize" plugin property is not respected
593  skipCurrentItem = !d->ignoreMaximumSize && size > d->maximumRemoteSize;
594 
595  // Remote directories are not supported, don't try to do a file_copy on them
596  if (!skipCurrentItem) {
597  // TODO update item.mimeType from the UDS entry, in case it wasn't set initially
598  // But we don't use the MIME type anymore, we just use isDir().
599  if (d->currentItem.item.isDir()) {
600  skipCurrentItem = true;
601  }
602  }
603  }
604  if (skipCurrentItem) {
605  d->determineNextFile();
606  return;
607  }
608 
609  bool pluginHandlesSequences = d->currentItem.plugin.value(QStringLiteral("HandleSequences"), false);
610  if (!d->currentItem.plugin.value(QStringLiteral("CacheThumbnail"), true) || (d->sequenceIndex && pluginHandlesSequences)) {
611  // This preview will not be cached, no need to look for a saved thumbnail
612  // Just create it, and be done
613  d->getOrCreateThumbnail();
614  return;
615  }
616 
617  if (d->statResultThumbnail()) {
618  return;
619  }
620 
621  d->getOrCreateThumbnail();
622  return;
623  }
624  case PreviewJobPrivate::STATE_DEVICE_INFO: {
625  KIO::StatJob *statJob = static_cast<KIO::StatJob *>(job);
626  int id;
627  QString path = statJob->url().toLocalFile();
628  if (job->error()) {
629  // We set id to 0 to know we tried getting it
630  qCWarning(KIO_WIDGETS) << "Cannot read information about filesystem under path" << path;
631  id = 0;
632  } else {
634  }
635  d->deviceIdMap[path] = id;
636  d->createThumbnail(d->currentItem.item.localPath());
637  return;
638  }
639  case PreviewJobPrivate::STATE_GETORIG: {
640  if (job->error()) {
641  d->cleanupTempFile();
642  d->determineNextFile();
643  return;
644  }
645 
646  d->createThumbnail(static_cast<KIO::FileCopyJob *>(job)->destUrl().toLocalFile());
647  return;
648  }
649  case PreviewJobPrivate::STATE_CREATETHUMB: {
650  d->cleanupTempFile();
651  d->determineNextFile();
652  return;
653  }
654  }
655 }
656 
657 bool PreviewJobPrivate::statResultThumbnail()
658 {
659  if (thumbPath.isEmpty()) {
660  return false;
661  }
662 
663  bool isLocal;
664  const QUrl url = currentItem.item.mostLocalUrl(&isLocal);
665  if (isLocal) {
666  const QFileInfo localFile(url.toLocalFile());
667  const QString canonicalPath = localFile.canonicalFilePath();
669  if (origName.isEmpty()) {
670  qCWarning(KIO_WIDGETS) << "Failed to convert" << url << "to canonical path";
671  return false;
672  }
673  } else {
674  // Don't include the password if any
675  origName = url.toEncoded(QUrl::RemovePassword);
676  }
677 
679  md5.addData(origName);
680  thumbName = QString::fromLatin1(md5.result().toHex()) + QLatin1String(".png");
681 
682  QImage thumb;
683  QFile thumbFile(thumbPath + thumbName);
684  if (!thumbFile.open(QIODevice::ReadOnly) || !thumb.load(&thumbFile, "png")) {
685  return false;
686  }
687 
688  if (thumb.text(QStringLiteral("Thumb::URI")) != QString::fromUtf8(origName)
689  || thumb.text(QStringLiteral("Thumb::MTime")).toLongLong() != tOrig.toSecsSinceEpoch()) {
690  return false;
691  }
692 
693  const QString origSize = thumb.text(QStringLiteral("Thumb::Size"));
694  if (!origSize.isEmpty() && origSize.toULongLong() != currentItem.item.size()) {
695  // Thumb::Size is not required, but if it is set it should match
696  return false;
697  }
698 
699  // The DPR of the loaded thumbnail is unspecified (and typically irrelevant).
700  // When a thumbnail is DPR-invariant, use the DPR passed in the request.
701  thumb.setDevicePixelRatio(devicePixelRatio);
702 
703  QString thumbnailerVersion = currentItem.plugin.value(QStringLiteral("ThumbnailerVersion"));
704 
705  if (!thumbnailerVersion.isEmpty() && thumb.text(QStringLiteral("Software")).startsWith(QLatin1String("KDE Thumbnail Generator"))) {
706  // Check if the version matches
707  // The software string should read "KDE Thumbnail Generator pluginName (vX)"
708  QString softwareString = thumb.text(QStringLiteral("Software")).remove(QStringLiteral("KDE Thumbnail Generator")).trimmed();
709  if (softwareString.isEmpty()) {
710  // The thumbnail has been created with an older version, recreating
711  return false;
712  }
713  int versionIndex = softwareString.lastIndexOf(QLatin1String("(v"));
714  if (versionIndex < 0) {
715  return false;
716  }
717 
718  QString cachedVersion = softwareString.remove(0, versionIndex + 2);
719  cachedVersion.chop(1);
720  uint thumbnailerMajor = thumbnailerVersion.toInt();
721  uint cachedMajor = cachedVersion.toInt();
722  if (thumbnailerMajor > cachedMajor) {
723  return false;
724  }
725  }
726 
727  // Found it, use it
728  emitPreview(thumb);
729  succeeded = true;
730  determineNextFile();
731  return true;
732 }
733 
734 void PreviewJobPrivate::getOrCreateThumbnail()
735 {
736  Q_Q(PreviewJob);
737  // We still need to load the orig file ! (This is getting tedious) :)
738  const KFileItem &item = currentItem.item;
739  const QString localPath = item.localPath();
740  if (!localPath.isEmpty()) {
741  createThumbnail(localPath);
742  } else {
743  const QUrl fileUrl = item.url();
744  // heuristics for remote URL support
745  bool supportsProtocol = false;
746  if (m_remoteProtocolPlugins.value(fileUrl.scheme()).contains(item.mimetype())) {
747  // There's a plugin supporting this protocol and MIME type
748  supportsProtocol = true;
749  } else if (m_remoteProtocolPlugins.value(QStringLiteral("KIO")).contains(item.mimetype())) {
750  // Assume KIO understands any URL, ThumbCreator slaves who have
751  // X-KDE-Protocols=KIO will get fed the remote URL directly.
752  supportsProtocol = true;
753  }
754 
755  if (supportsProtocol) {
756  createThumbnail(fileUrl.toString());
757  return;
758  }
759  if (item.isDir()) {
760  // Skip remote dirs (bug 208625)
761  cleanupTempFile();
762  determineNextFile();
763  return;
764  }
765  // No plugin support access to this remote content, copy the file
766  // to the local machine, then create the thumbnail
767  state = PreviewJobPrivate::STATE_GETORIG;
768  QTemporaryFile localFile;
769  localFile.setAutoRemove(false);
770  localFile.open();
771  tempName = localFile.fileName();
772  const QUrl currentURL = item.mostLocalUrl();
773  KIO::Job *job = KIO::file_copy(currentURL, QUrl::fromLocalFile(tempName), -1, KIO::Overwrite | KIO::HideProgressInfo /* No GUI */);
774  job->addMetaData(QStringLiteral("thumbnail"), QStringLiteral("1"));
775  q->addSubjob(job);
776  }
777 }
778 
779 PreviewJobPrivate::CachePolicy PreviewJobPrivate::canBeCached(const QString &path)
780 {
781  // If checked file is directory on a different filesystem than its parent, we need to check it separately
782  int separatorIndex = path.lastIndexOf(QLatin1Char('/'));
783  // special case for root folders
784  const QString parentDirPath = separatorIndex == 0 ? path : path.left(separatorIndex);
785 
786  int parentId = getDeviceId(parentDirPath);
787  if (parentId == idUnknown) {
788  return CachePolicy::Unknown;
789  }
790 
791  bool isDifferentSystem = !parentId || parentId != currentDeviceId;
792  if (!isDifferentSystem && currentDeviceCachePolicy != CachePolicy::Unknown) {
793  return currentDeviceCachePolicy;
794  }
795  int checkedId;
796  QString checkedPath;
797  if (isDifferentSystem) {
798  checkedId = currentDeviceId;
799  checkedPath = path;
800  } else {
801  checkedId = getDeviceId(parentDirPath);
802  checkedPath = parentDirPath;
803  if (checkedId == idUnknown) {
804  return CachePolicy::Unknown;
805  }
806  }
807  // If we're checking different filesystem or haven't checked yet see if filesystem matches thumbRoot
808  int thumbRootId = getDeviceId(thumbRoot);
809  if (thumbRootId == idUnknown) {
810  return CachePolicy::Unknown;
811  }
812  bool shouldAllow = checkedId && checkedId == thumbRootId;
813  if (!shouldAllow) {
815  if (device.isValid()) {
816  shouldAllow = !device.as<Solid::StorageAccess>()->isEncrypted();
817  }
818  }
819  if (!isDifferentSystem) {
820  currentDeviceCachePolicy = shouldAllow ? CachePolicy::Allow : CachePolicy::Prevent;
821  }
822  return shouldAllow ? CachePolicy::Allow : CachePolicy::Prevent;
823 }
824 
825 int PreviewJobPrivate::getDeviceId(const QString &path)
826 {
827  Q_Q(PreviewJob);
828  auto iter = deviceIdMap.find(path);
829  if (iter != deviceIdMap.end()) {
830  return iter.value();
831  }
832  QUrl url = QUrl::fromLocalFile(path);
833  if (!url.isValid()) {
834  qCWarning(KIO_WIDGETS) << "Could not get device id for file preview, Invalid url" << path;
835  return 0;
836  }
837  state = PreviewJobPrivate::STATE_DEVICE_INFO;
839  job->addMetaData(QStringLiteral("no-auth-prompt"), QStringLiteral("true"));
840  q->addSubjob(job);
841 
842  return idUnknown;
843 }
844 
845 void PreviewJobPrivate::createThumbnail(const QString &pixPath)
846 {
847  Q_Q(PreviewJob);
848  state = PreviewJobPrivate::STATE_CREATETHUMB;
849  QUrl thumbURL;
850  thumbURL.setScheme(QStringLiteral("thumbnail"));
851  thumbURL.setPath(pixPath);
852 
853  bool save = bSave && currentItem.plugin.value(QStringLiteral("CacheThumbnail"), true) && !sequenceIndex;
854 
855  bool isRemoteProtocol = currentItem.item.localPath().isEmpty();
856  CachePolicy cachePolicy = isRemoteProtocol ? CachePolicy::Prevent : canBeCached(pixPath);
857 
858  if (cachePolicy == CachePolicy::Unknown) {
859  // If Unknown is returned, creating thumbnail should be called again by slotResult
860  return;
861  }
862 
863  KIO::TransferJob *job = KIO::get(thumbURL, NoReload, HideProgressInfo);
864  q->addSubjob(job);
865  q->connect(job, &KIO::TransferJob::data, q, [this](KIO::Job *job, const QByteArray &data) {
866  slotThumbData(job, data);
867  });
868 
869  int thumb_width = width;
870  int thumb_height = height;
871  int thumb_iconSize = iconSize;
872  if (save) {
873  thumb_width = thumb_height = cacheSize;
874  thumb_iconSize = 64;
875  }
876 
877  job->addMetaData(QStringLiteral("mimeType"), currentItem.item.mimetype());
878  job->addMetaData(QStringLiteral("width"), QString::number(thumb_width));
879  job->addMetaData(QStringLiteral("height"), QString::number(thumb_height));
880  job->addMetaData(QStringLiteral("iconSize"), QString::number(thumb_iconSize));
881  job->addMetaData(QStringLiteral("iconAlpha"), QString::number(iconAlpha));
882  job->addMetaData(QStringLiteral("plugin"), currentItem.plugin.fileName());
883  job->addMetaData(QStringLiteral("enabledPlugins"), enabledPlugins.join(QLatin1Char(',')));
884  job->addMetaData(QStringLiteral("devicePixelRatio"), QString::number(devicePixelRatio));
885  job->addMetaData(QStringLiteral("cache"), QString::number(cachePolicy == CachePolicy::Allow));
886  if (sequenceIndex) {
887  job->addMetaData(QStringLiteral("sequence-index"), QString::number(sequenceIndex));
888  }
889 
890 #if WITH_SHM
891  size_t requiredSize = thumb_width * devicePixelRatio * thumb_height * devicePixelRatio * 4;
892  if (shmid == -1 || shmsize < requiredSize) {
893  if (shmaddr) {
894  // clean previous shared memory segment
895  shmdt((char *)shmaddr);
896  shmaddr = nullptr;
897  shmctl(shmid, IPC_RMID, nullptr);
898  shmid = -1;
899  }
900  if (requiredSize > 0) {
901  shmid = shmget(IPC_PRIVATE, requiredSize, IPC_CREAT | 0600);
902  if (shmid != -1) {
903  shmsize = requiredSize;
904  shmaddr = (uchar *)(shmat(shmid, nullptr, SHM_RDONLY));
905  if (shmaddr == (uchar *)-1) {
906  shmctl(shmid, IPC_RMID, nullptr);
907  shmaddr = nullptr;
908  shmid = -1;
909  }
910  }
911  }
912  }
913  if (shmid != -1) {
914  job->addMetaData(QStringLiteral("shmid"), QString::number(shmid));
915  }
916 #endif
917 }
918 
919 void PreviewJobPrivate::slotThumbData(KIO::Job *job, const QByteArray &data)
920 {
921  thumbnailSlaveMetaData = job->metaData();
922  /* clang-format off */
923  const bool save = bSave
924  && !sequenceIndex
925  && currentDeviceCachePolicy == CachePolicy::Allow
926  && currentItem.plugin.value(QStringLiteral("CacheThumbnail"), true)
927  && (!currentItem.item.url().isLocalFile()
928  || !currentItem.item.url().adjusted(QUrl::RemoveFilename).toLocalFile().startsWith(thumbRoot));
929  /* clang-format on */
930 
931  QImage thumb;
932 #if WITH_SHM
933  if (shmaddr) {
934  // Keep this in sync with kdebase/kioslave/thumbnail.cpp
935  QDataStream str(data);
936  int width;
937  int height;
938  quint8 iFormat;
939  int imgDevicePixelRatio = 1;
940  // TODO KF6: add a version number as first parameter
941  str >> width >> height >> iFormat;
942  if (iFormat & 0x80) {
943  // HACK to deduce if imgDevicePixelRatio is present
944  iFormat &= 0x7f;
945  str >> imgDevicePixelRatio;
946  }
947  QImage::Format format = static_cast<QImage::Format>(iFormat);
948  thumb = QImage(shmaddr, width, height, format).copy();
949  thumb.setDevicePixelRatio(imgDevicePixelRatio);
950  } else {
951  thumb.loadFromData(data);
952  }
953 #else
954  thumb.loadFromData(data);
955 #endif
956 
957  if (thumb.isNull()) {
958  QDataStream s(data);
959  s >> thumb;
960  }
961 
962  if (save) {
963  thumb.setText(QStringLiteral("Thumb::URI"), QString::fromUtf8(origName));
964  thumb.setText(QStringLiteral("Thumb::MTime"), QString::number(tOrig.toSecsSinceEpoch()));
965  thumb.setText(QStringLiteral("Thumb::Size"), number(currentItem.item.size()));
966  thumb.setText(QStringLiteral("Thumb::Mimetype"), currentItem.item.mimetype());
967  QString thumbnailerVersion = currentItem.plugin.value(QStringLiteral("ThumbnailerVersion"));
968  QString signature = QLatin1String("KDE Thumbnail Generator ") + currentItem.plugin.name();
969  if (!thumbnailerVersion.isEmpty()) {
970  signature.append(QLatin1String(" (v") + thumbnailerVersion + QLatin1Char(')'));
971  }
972  thumb.setText(QStringLiteral("Software"), signature);
973  QSaveFile saveFile(thumbPath + thumbName);
974  if (saveFile.open(QIODevice::WriteOnly)) {
975  if (thumb.save(&saveFile, "PNG")) {
976  saveFile.commit();
977  }
978  }
979  }
980  emitPreview(thumb);
981  succeeded = true;
982 }
983 
984 void PreviewJobPrivate::emitPreview(const QImage &thumb)
985 {
986  Q_Q(PreviewJob);
987  QPixmap pix;
988  const qreal ratio = thumb.devicePixelRatio();
989  if (thumb.width() > width * ratio || thumb.height() > height * ratio) {
990  pix = QPixmap::fromImage(thumb.scaled(QSize(width * ratio, height * ratio), Qt::KeepAspectRatio, Qt::SmoothTransformation));
991  } else {
992  pix = QPixmap::fromImage(thumb);
993  }
994  pix.setDevicePixelRatio(ratio);
995  Q_EMIT q->gotPreview(currentItem.item, pix);
996 }
997 
999 {
1000  return PreviewJobPrivate::loadAvailablePlugins();
1001 }
1002 
1004 {
1006  const auto plugins = KIO::PreviewJobPrivate::loadAvailablePlugins();
1007  for (const KPluginMetaData &plugin : plugins) {
1008  result << plugin.pluginId();
1009  }
1010  return result;
1011 }
1012 
1014 {
1015  const QStringList blacklist = QStringList() << QStringLiteral("textthumbnail");
1016 
1018  for (const QString &plugin : blacklist) {
1019  defaultPlugins.removeAll(plugin);
1020  }
1021 
1022  return defaultPlugins;
1023 }
1024 
1026 {
1028  const auto plugins = KIO::PreviewJobPrivate::loadAvailablePlugins();
1029  for (const KPluginMetaData &plugin : plugins) {
1030  result += plugin.mimeTypes();
1031  }
1032  return result;
1033 }
1034 
1035 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(4, 7)
1036 PreviewJob *
1037 KIO::filePreview(const KFileItemList &items, int width, int height, int iconSize, int iconAlpha, bool scale, bool save, const QStringList *enabledPlugins)
1038 {
1039  return new PreviewJob(items, width, height, iconSize, iconAlpha, scale, save, enabledPlugins);
1040 }
1041 #endif
1042 
1043 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(4, 7)
1044 PreviewJob *
1045 KIO::filePreview(const QList<QUrl> &items, int width, int height, int iconSize, int iconAlpha, bool scale, bool save, const QStringList *enabledPlugins)
1046 {
1047  KFileItemList fileItems;
1048  fileItems.reserve(items.size());
1049  for (const QUrl &url : items) {
1050  Q_ASSERT(url.isValid()); // please call us with valid urls only
1051  fileItems.append(KFileItem(url));
1052  }
1053  return new PreviewJob(fileItems, width, height, iconSize, iconAlpha, scale, save, enabledPlugins);
1054 }
1055 #endif
1056 
1057 PreviewJob *KIO::filePreview(const KFileItemList &items, const QSize &size, const QStringList *enabledPlugins)
1058 {
1059  return new PreviewJob(items, size, enabledPlugins);
1060 }
1061 
1062 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(4, 5)
1064 {
1065  KConfigGroup cg(KSharedConfig::openConfig(), "PreviewSettings");
1066  return cg.readEntry("MaximumSize", 5 * 1024 * 1024LL /* 5MB */);
1067 }
1068 #endif
1069 
1070 #include "moc_previewjob.cpp"
@ Overwrite
When set, automatically overwrite the destination if it exists already.
Definition: job_base.h:290
void append(const T &value)
T & first()
QString readEntry(const char *key, const char *aDefault=nullptr) const
static QStringList supportedMimeTypes()
Returns a list of all supported MIME types.
QJsonObject rawData() const
const T value(const Key &key) const const
void truncate(int position)
void setSequenceIndex(int index)
Sets the sequence index given to the thumb creators.
Definition: previewjob.cpp:497
bool isEmpty() const const
QString text(const QString &key) const const
QPixmap fromImage(const QImage &image, Qt::ImageConversionFlags flags)
QString number(int n, int base)
int height() const const
QString fromUtf8(const char *str, int size)
QByteArray toEncoded(QUrl::FormattingOptions options) const const
bool remove()
static QVector< KPluginMetaData > availableThumbnailerPlugins()
Returns all plugins that are considered when a preview is generated The result is internally cached,...
Definition: previewjob.cpp:998
void result(KJob *job)
int removeAll(const T &value)
QString scheme() const const
void setOverlayIconSize(int size)
Sets the size of the MIME-type icon which overlays the preview.
Definition: previewjob.cpp:274
qulonglong filesize_t
64-bit file size
Definition: global.h:39
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
QString trimmed() const const
void clear()
QString url(QUrl::FormattingOptions options) const const
void setAutoRemove(bool b)
static QStringList availablePlugins()
Returns a list of all available preview plugins.
void addMetaData(const QString &key, const QString &value)
Add key/value pair to the meta data that is sent to the slave.
Definition: job.cpp:229
@ Scaled
The preview will be scaled to the size specified when constructing the PreviewJob.
Definition: previewjob.h:50
void chop(int n)
bool isValid() const
QImage scaled(int width, int height, Qt::AspectRatioMode aspectRatioMode, Qt::TransformationMode transformMode) const const
static Device storageAccessFromPath(const QString &path)
QString writableLocation(QStandardPaths::StandardLocation type)
@ StatInode
dev, inode
Definition: global.h:372
KCALUTILS_EXPORT QString mimeType()
KIOCORE_EXPORT TransferJob * get(const QUrl &url, LoadType reload=NoReload, JobFlags flags=DefaultFlags)
Get (means: read).
const QUrl & url() const
Returns the SimpleJob's URL.
Definition: simplejob.cpp:70
int sequenceIndex() const
Returns the currently set sequence index.
Definition: previewjob.cpp:502
bool kill(KillVerbosity verbosity=Quietly)
int lastIndexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
QString locate(QStandardPaths::StandardLocation type, const QString &fileName, QStandardPaths::LocateOptions options)
QMap::const_iterator constFind(const Key &key) const const
bool loadFromData(const uchar *data, int len, const char *format)
bool load(QIODevice *device, const char *format)
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
KIOCORE_EXPORT FileCopyJob * file_copy(const QUrl &src, const QUrl &dest, int permissions=-1, JobFlags flags=DefaultFlags)
Copy a single file.
QHash::iterator insert(const Key &key, const T &value)
void setScaleType(ScaleType type)
Sets the scale type for the generated preview.
Definition: previewjob.cpp:298
void removeItem(const QUrl &url)
Removes an item from preview processing.
Definition: previewjob.cpp:478
virtual QString fileName() const const override
void setScheme(const QString &scheme)
void setDevicePixelRatio(qreal scaleFactor)
void reserve(int alloc)
bool isValid() const const
QMap::iterator insert(const Key &key, const T &value)
bool isValid() const
const QList< KJob * > & subjobs() const
int size() const const
QMap::iterator end()
ScaleType scaleType() const
Definition: previewjob.cpp:319
qulonglong toULongLong(bool *ok, int base) const const
QString toString(QUrl::FormattingOptions options) const const
KeepAspectRatio
QMap::iterator find(const Key &key)
MetaData metaData() const
Get meta data received from the slave.
Definition: job.cpp:213
QStringList mimeTypes() const
RemoveFilename
QMimeType mimeTypeForName(const QString &nameOrAlias) const const
QHash::const_iterator constEnd() const const
FullyEncoded
QMap::const_iterator constEnd() const const
static KIO::filesize_t maximumFileSize()
Returns the default "maximum file size", in bytes, used by PreviewJob.
bool isEmpty() const const
PreviewJob(const KFileItemList &items, int width, int height, int iconSize, int iconAlpha, bool scale, bool save, const QStringList *enabledPlugins)
Creates a new PreviewJob.
Definition: previewjob.cpp:226
QUrl fromLocalFile(const QString &localFile)
DevIface * as()
float sequenceIndexWraparoundPoint() const
Returns the index at which the thumbs of a ThumbSequenceCreator start wrapping around ("looping").
Definition: previewjob.cpp:507
bool isNull() const const
void setDevicePixelRatio(int dpr)
Request preview to use the device pixel ratio dpr.
Definition: previewjob.cpp:518
@ UDS_SIZE
Size of the file.
Definition: udsentry.h:230
void setText(const QString &key, const QString &text)
int toInt(bool *ok, int base) const const
QString toLocalFile() const const
void setIgnoreMaximumSize(bool ignoreSize=true)
If ignoreSize is true, then the preview is always generated regardless of the settings.
Definition: previewjob.cpp:529
@ Unscaled
The original size of the preview will be returned.
Definition: previewjob.h:45
bool isValid() const const
QString join(const QString &separator) const const
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
KIOCORE_EXPORT MkpathJob * mkpath(const QUrl &url, const QUrl &baseUrl=QUrl(), JobFlags flags=DefaultFlags)
Creates a directory, creating parent directories as needed.
Definition: mkpathjob.cpp:148
QHash::const_iterator constFind(const Key &key) const const
bool isValid() const
const UDSEntry & statResult() const
Result of the stat operation.
Definition: statjob.cpp:115
bool handlesSequences() const
Determines whether the ThumbCreator in use is a ThumbSequenceCreator.
Definition: previewjob.cpp:512
bool value(const QString &key, bool defaultValue) const
QString & remove(int position, int n)
static QVector< KPluginMetaData > findPlugins(const QString &directory, std::function< bool(const KPluginMetaData &)> filter, KPluginMetaDataOption option)
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
qint64 toSecsSinceEpoch() const const
bool isEmpty() const const
long long numberValue(uint field, long long defaultValue=0) const
Definition: udsentry.cpp:381
@ UDS_MODIFICATION_TIME
The last time the file was modified. Required time format: seconds since UNIX epoch.
Definition: udsentry.h:259
void setOverlayIconAlpha(int alpha)
Sets the alpha-value for the MIME-type icon which overlays the preview.
Definition: previewjob.cpp:286
KIO Job to get a thumbnail picture.
Definition: previewjob.h:30
QString left(int n) const const
QString fromLatin1(const char *str, int size)
void setPath(const QString &path, QUrl::ParsingMode mode)
const QList< QKeySequence > & save()
bool isLocalFile() const const
QUrl adjusted(QUrl::FormattingOptions options) const const
QDateTime fromSecsSinceEpoch(qint64 secs, Qt::TimeSpec spec, int offsetSeconds)
KIOCORE_EXPORT StatJob * statDetails(const QUrl &url, KIO::StatJob::StatSide side, KIO::StatDetails details=KIO::StatDefaultDetails, JobFlags flags=DefaultFlags)
Find all details for one file or directory.
Definition: statjob.cpp:263
A namespace for KIO globals.
void clear()
QStringList mimeTypes(Mode mode=Writing)
ScaleType
Specifies the type of scaling that is applied to the generated preview.
Definition: previewjob.h:40
@ HideProgressInfo
Hide progress information dialog, i.e. don't show a GUI.
Definition: job_base.h:274
KIOCORE_EXPORT QString number(KIO::filesize_t size)
Converts a size to a string representation Not unlike QString::number(...)
Definition: global.cpp:55
QUrl mostLocalUrl(bool *local=nullptr) const
Tries to return a local URL for this file item if possible.
Definition: kfileitem.cpp:1482
static KPluginMetaData fromDesktopFile(const QString &file, const QStringList &serviceTypes=QStringList())
@ UDS_DEVICE_ID
Device number for this file, used to detect hardlinks.
Definition: udsentry.h:327
int overlayIconSize() const
Definition: previewjob.cpp:280
int error() const
static QStringList defaultPlugins()
Returns a list of plugins that should be enabled by default, which is all plugins Minus the plugins s...
bool hasSubjobs() const
SmoothTransformation
void data(KIO::Job *job, const QByteArray &data)
Data from the slave has arrived.
QString pluginId() const
bool removeSubjob(KJob *job) override
Mark a sub job as being done.
Definition: job.cpp:88
static void setDefaultDevicePixelRatio(int devicePixelRatio)
Sets a default device Pixel Ratio used for Previews.
Definition: previewjob.cpp:214
int overlayIconAlpha() const
Definition: previewjob.cpp:292
bool save(const QString &fileName, const char *format, int quality) const const
KIOWIDGETS_EXPORT PreviewJob * filePreview(const KFileItemList &items, int width, int height=0, int iconSize=0, int iconAlpha=70, bool scale=true, bool save=true, const QStringList *enabledPlugins=nullptr)
Creates a PreviewJob to generate or retrieve a preview image for the given URL.
@ ScaledAndCached
The preview will be scaled to the size specified when constructing the PreviewJob.
Definition: previewjob.h:56
T value(int i) const const
QString & append(QChar ch)
QImage copy(const QRect &rectangle) const const
Q_D(Todo)
@ StatDefaultDetails
Default StatDetail flag when creating a StatJob.
Definition: global.h:382
void setDevicePixelRatio(qreal scaleFactor)
qreal devicePixelRatio() const const
static QString protocolClass(const QString &protocol)
Returns the protocol class for the specified protocol.
int width() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Tue Sep 27 2022 04:22:39 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.