Baloo Widgets

filemetadataprovider.cpp
1 /*****************************************************************************
2  * Copyright (C) 2010 by Peter Penz <[email protected]> *
3  * Copyright (C) 2012 by Vishesh Handa <[email protected]> *
4  * *
5  * This library is free software; you can redistribute it and/or *
6  * modify it under the terms of the GNU Library General Public *
7  * License as published by the Free Software Foundation; either *
8  * version 2 of the License, or (at your option) any later version. *
9  * *
10  * This library is distributed in the hope that it will be useful, *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
13  * Library General Public License for more details. *
14  * *
15  * You should have received a copy of the GNU Library General Public License *
16  * along with this library; see the file COPYING.LIB. If not, write to *
17  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, *
18  * Boston, MA 02110-1301, USA. *
19  *****************************************************************************/
20 
21 #include "filemetadataprovider.h"
22 #include "filefetchjob.h"
23 
24 #include <KFileMetaData/PropertyInfo>
25 #include <KLocalizedString>
26 #include <KFormat>
27 
28 #include <QTimer>
29 #include <QDebug>
30 
31 // Required includes for subDirectoriesCount():
32 #ifdef Q_OS_WIN
33  #include <QDir>
34 #else
35  #include <dirent.h>
36  #include <QFile>
37 #endif
38 
39 using namespace Baloo;
40 
41 namespace {
42  QVariant intersect(const QVariant& v1, const QVariant& v2) {
43  if (!v1.isValid() || !v2.isValid()) {
44  return QVariant();
45  }
46 
47  // List and String
48  if (v1.type() == QVariant::StringList && v2.type() == QVariant::String) {
50  QString str = v2.toString();
51 
52  if (!list.contains(str)) {
53  list << str;
54  }
55 
56  return QVariant(list);
57  }
58 
59  // String and List
60  if (v1.type() == QVariant::String && v2.type() == QVariant::StringList) {
61  QStringList list = v2.toStringList();
62  QString str = v1.toString();
63 
64  if (!list.contains(str)) {
65  list << str;
66  }
67 
68  return QVariant(list);
69  }
70 
71  // List and List
72  if (v1.type() == QVariant::StringList && v2.type() == QVariant::StringList) {
73  QSet<QString> s1 = v1.toStringList().toSet();
74  QSet<QString> s2 = v2.toStringList().toSet();
75 
76  return QVariant(s1.intersect(s2).values());
77  }
78 
79  if (v1 == v2) {
80  return v1;
81  }
82 
83  return QVariant();
84  }
85 
92  QVariantMap unite(const QVariantMap& v1, const QVariantMap& v2)
93  {
94  QVariantMap v(v1);
96  while (it.hasNext()) {
97  it.next();
98 
99  v[it.key()] = it.value();
100  }
101 
102  return v;
103  }
104 } // anonymous namespace
105 
106 void FileMetaDataProvider::totalPropertyAndInsert(const QString& prop,
107  const QList<QVariantMap>& resources,
108  QSet<QString>& allProperties)
109 {
110  if (allProperties.contains(prop)) {
111  int total = 0;
112  for (const QVariantMap& map : resources) {
113  QVariantMap::const_iterator it = map.constFind(prop);
114  if (it == map.constEnd()) {
115  total = 0;
116  break;
117  } else {
118  total += it.value().toInt();
119  }
120  }
121 
122  if (total) {
123  m_data.insert (prop, QVariant(total));
124  }
125  allProperties.remove(prop);
126  }
127 }
128 
129 void FileMetaDataProvider::slotFileFetchFinished(KJob* job)
130 {
131  FileFetchJob* fetchJob = static_cast<FileFetchJob*>(job);
132  QList<QVariantMap> files = fetchJob->data();
133 
134  Q_ASSERT(!files.isEmpty());
135 
136  if (files.size() > 1) {
137  insertCommonData(files);
138  } else {
139  m_data = unite(m_data, files.first());
140  }
141  m_readOnly = !fetchJob->canEditAll();
142 
143  insertEditableData();
144  emit loadingFinished();
145 }
146 
147 void FileMetaDataProvider::insertSingleFileBasicData()
148 {
149  // TODO: Handle case if remote URLs are used properly. isDir() does
150  // not work, the modification date needs also to be adjusted...
151  Q_ASSERT(m_fileItems.count() <= 1);
152  if (m_fileItems.count() == 1) {
153  const KFileItem& item = m_fileItems.first();
154 
155  KFormat format;
156  if (item.isDir()) {
157  bool isSizeUnknown = !item.isLocalFile();
158  if (!isSizeUnknown) {
159  const QPair<int, int> counts = subDirectoriesCount(item.url().path());
160  const int count = counts.first;
161  isSizeUnknown = count == -1;
162  if (!isSizeUnknown) {
163  QString itemCountString = i18ncp("@item:intable", "%1 item", "%1 items", count);
164  m_data.insert(QStringLiteral("kfileitem#size"), itemCountString);
165 
166  const int hiddenCount = counts.second;
167  if (hiddenCount > 0) {
168  // add hidden items count
169  QString hiddenCountString = i18ncp("@item:intable", "%1 item", "%1 items", hiddenCount);
170  m_data.insert(QStringLiteral("kfileitem#hiddenItems"), hiddenCountString);
171  }
172  }
173  }
174  else if (item.entry().contains(KIO::UDSEntry::UDS_SIZE)) {
175  m_data.insert(QStringLiteral("kfileitem#size"), format.formatByteSize(item.size()));
176  }
178  m_data.insert(QStringLiteral("kfileitem#totalSize"), format.formatByteSize(item.recursiveSize()));
179  }
180  } else {
181  if (item.entry().contains(KIO::UDSEntry::UDS_SIZE)) {
182  m_data.insert(QStringLiteral("kfileitem#size"), format.formatByteSize(item.size()));
183  }
184  }
185 
186  m_data.insert(QStringLiteral("kfileitem#type"), item.mimeComment());
187  if (item.isLink()) {
188  m_data.insert(QStringLiteral("kfileitem#linkDest"), item.linkDest());
189  }
190  QDateTime modificationTime = item.time(KFileItem::ModificationTime);
191  if (modificationTime.isValid()) {
192  m_data.insert(QStringLiteral("kfileitem#modified"), modificationTime);
193  }
194  QDateTime creationTime = item.time(KFileItem::CreationTime);
195  if (creationTime.isValid()) {
196  m_data.insert(QStringLiteral("kfileitem#created"), creationTime);
197  }
198  QDateTime accessTime = item.time(KFileItem::AccessTime);
199  if (accessTime.isValid()) {
200  m_data.insert(QStringLiteral("kfileitem#accessed"), accessTime);
201  }
202 
203  m_data.insert(QStringLiteral("kfileitem#owner"), item.user());
204  m_data.insert(QStringLiteral("kfileitem#group"), item.group());
205  m_data.insert(QStringLiteral("kfileitem#permissions"), item.permissionsString());
206  }
207 }
208 
209 void FileMetaDataProvider::insertFilesListBasicData()
210 {
211  // If all directories
212  Q_ASSERT(m_fileItems.count() > 1);
213  bool allDirectories = true;
214  for (const KFileItem& item : qAsConst(m_fileItems)) {
215  allDirectories &= item.isDir();
216  if (!allDirectories) {
217  break;
218  }
219  }
220 
221  if (allDirectories) {
222  int count = 0;
223  int hiddenCount = 0;
224  bool isSizeKnown = true;
225  for (const KFileItem& item : qAsConst(m_fileItems)) {
226  isSizeKnown = item.isLocalFile();
227  if (!isSizeKnown) {
228  return;
229  }
230  const QPair<int, int> counts = subDirectoriesCount(item.url().path());
231  const int subcount = counts.first;
232  isSizeKnown = subcount != -1;
233  if (!isSizeKnown) {
234  return;
235  }
236  count += subcount;
237  hiddenCount += counts.second;
238  }
239  QString itemCountString = i18ncp("@item:intable", "%1 item", "%1 items", count);
240  if (hiddenCount > 0) {
241  // add hidden items count
242  QString hiddenCountString = i18ncp("@item:intable", "%1 item", "%1 items", hiddenCount);
243  m_data.insert(QStringLiteral("kfileitem#hiddenItems"), hiddenCountString);
244  }
245  m_data.insert(QStringLiteral("kfileitem#totalSize"), itemCountString);
246 
247  } else {
248  // Calculate the size of all items
249  quint64 totalSize = 0;
250  for (const KFileItem& item : qAsConst(m_fileItems)) {
251  if (!item.isDir() && !item.isLink()) {
252  totalSize += item.size();
253  }
254  }
255  KFormat format;
256  m_data.insert(QStringLiteral("kfileitem#totalSize"), format.formatByteSize(totalSize));
257  }
258 }
259 
260 void FileMetaDataProvider::insertEditableData()
261 {
262  if (!m_readOnly) {
263  if (!m_data.contains(QStringLiteral("tags"))) {
264  m_data.insert(QStringLiteral("tags"), QVariant());
265  }
266  if (!m_data.contains(QStringLiteral("rating"))) {
267  m_data.insert(QStringLiteral("rating"), 0);
268  }
269  if (!m_data.contains(QStringLiteral("userComment"))) {
270  m_data.insert(QStringLiteral("userComment"), QVariant());
271  }
272  }
273 }
274 
275 void FileMetaDataProvider::insertCommonData(const QList<QVariantMap>& files)
276 {
277  //
278  // Only report the stuff that is common to all the files
279  //
280  QSet<QString> allProperties;
281  QList<QVariantMap> propertyList;
282  for (const QVariantMap& fileData : files) {
283  propertyList << fileData;
284  allProperties.unite(fileData.uniqueKeys().toSet());
285  }
286 
287  // Special handling for certain properties
288  totalPropertyAndInsert(QStringLiteral("duration"), propertyList, allProperties);
289  totalPropertyAndInsert(QStringLiteral("characterCount"), propertyList, allProperties);
290  totalPropertyAndInsert(QStringLiteral("wordCount"), propertyList, allProperties);
291  totalPropertyAndInsert(QStringLiteral("lineCount"), propertyList, allProperties);
292 
293  for (const QString& propUri : qAsConst(allProperties)) {
294  for (const QVariantMap& map : qAsConst(propertyList)) {
295  QVariantMap::const_iterator it = map.find(propUri);
296  if (it == map.constEnd()) {
297  m_data.remove(propUri);
298  break;
299  }
300 
301  QVariantMap::iterator dit = m_data.find(it.key());
302  if (dit == m_data.end()) {
303  m_data.insert(propUri, it.value());
304  } else {
305  QVariant finalValue = intersect(it.value(), dit.value());
306  if (finalValue.isValid()) {
307  m_data[propUri] = finalValue;
308  } else {
309  m_data.remove(propUri);
310  break;
311  }
312  }
313  }
314  }
315 }
316 
317 FileMetaDataProvider::FileMetaDataProvider(QObject* parent)
318  : QObject(parent)
319  , m_readOnly(false)
320  , m_realTimeIndexing(false)
321 {
322 }
323 
324 FileMetaDataProvider::~FileMetaDataProvider()
325 {
326 }
327 
328 void FileMetaDataProvider::setFileItem()
329 {
330  // There are 3 code paths -
331  // Remote file
332  // Single local file -
333  // * Not Indexed
334  // * Indexed
335  //
336  insertSingleFileBasicData();
337  const QUrl url = m_fileItems.first().targetUrl();
338  if (!url.isLocalFile()) {
339  // FIXME - are extended attributes supported for remote files?
340  m_readOnly = true;
341  emit loadingFinished();
342  return;
343  }
344 
345  const QString filePath = url.toLocalFile();
346  FileFetchJob* job;
347 
348  // Not indexed or only basic file indexing (no content)
349  if (!m_config.fileIndexingEnabled() || !m_config.shouldBeIndexed(filePath)
350  || m_config.onlyBasicIndexing()) {
351  m_realTimeIndexing = true;
352 
353  job = new FileFetchJob(QStringList{filePath}, true,
354  FileFetchJob::UseRealtimeIndexing::Only, this);
355 
356  // Fully indexed by Baloo
357  } else {
358  job = new FileFetchJob(QStringList{filePath}, true,
359  FileFetchJob::UseRealtimeIndexing::Fallback, this);
360  }
361  connect(job, &FileFetchJob::finished, this, &FileMetaDataProvider::slotFileFetchFinished);
362  job->start();
363 }
364 
365 void FileMetaDataProvider::setFileItems()
366 {
367  // Multiple Files -
368  // * Not Indexed
369  // * Indexed
370 
371  QStringList urls;
372  urls.reserve(m_fileItems.size());
373  // Only extract data from indexed files,
374  // it would be too expensive otherwise.
375  for (const KFileItem& item : qAsConst(m_fileItems)) {
376  const QUrl url = item.targetUrl();
377  if (url.isLocalFile()) {
378  urls << url.toLocalFile();
379  }
380  }
381 
382  insertFilesListBasicData();
383  if (!urls.isEmpty()) {
384  // Editing only if all URLs are local
385  bool canEdit = (urls.size() == m_fileItems.size());
386 
387  FileFetchJob* job = new FileFetchJob(urls, canEdit,
388  FileFetchJob::UseRealtimeIndexing::Disabled, this);
389  connect(job, &FileFetchJob::finished, this, &FileMetaDataProvider::slotFileFetchFinished);
390  job->start();
391 
392  } else {
393  // FIXME - are extended attributes supported for remote files?
394  m_readOnly = true;
395  emit loadingFinished();
396  }
397 }
398 
400 {
401  m_fileItems = items;
402  m_data.clear();
403  m_realTimeIndexing = false;
404 
405  if (items.isEmpty()) {
406  emit loadingFinished();
407  } else if (items.size() == 1) {
408  setFileItem();
409  } else {
410  setFileItems();
411  }
412 }
413 
414 QString FileMetaDataProvider::label(const QString& metaDataLabel) const
415 {
416  static QHash<QString, QString> hash = {
417  { QStringLiteral("kfileitem#comment"), i18nc("@label", "Comment") },
418  { QStringLiteral("kfileitem#created"), i18nc("@label", "Created") },
419  { QStringLiteral("kfileitem#accessed"), i18nc("@label", "Accessed") },
420  { QStringLiteral("kfileitem#modified"), i18nc("@label", "Modified") },
421  { QStringLiteral("kfileitem#owner"), i18nc("@label", "Owner") },
422  { QStringLiteral("kfileitem#group"), i18nc("@label", "Group") },
423  { QStringLiteral("kfileitem#permissions"), i18nc("@label", "Permissions") },
424  { QStringLiteral("kfileitem#rating"), i18nc("@label", "Rating") },
425  { QStringLiteral("kfileitem#size"), i18nc("@label", "Size") },
426  { QStringLiteral("kfileitem#tags"), i18nc("@label", "Tags") },
427  { QStringLiteral("kfileitem#totalSize"), i18nc("@label", "Total Size") },
428  { QStringLiteral("kfileitem#hiddenItems"), i18nc("@label", "Hidden items") },
429  { QStringLiteral("kfileitem#type"), i18nc("@label", "Type") },
430  { QStringLiteral("kfileitem#linkDest"), i18nc("@label", "Link to") },
431  { QStringLiteral("tags"), i18nc("@label", "Tags") },
432  { QStringLiteral("rating"), i18nc("@label", "Rating") },
433  { QStringLiteral("userComment"), i18nc("@label", "Comment") },
434  { QStringLiteral("originUrl"), i18nc("@label", "Downloaded From") },
435  };
436 
437  QString value = hash.value(metaDataLabel);
438  if (value.isEmpty()) {
439  value = KFileMetaData::PropertyInfo::fromName(metaDataLabel).displayName();
440  }
441 
442  return value;
443 }
444 
446 {
447  static QHash<QString, QString> uriGrouper = {
448 
449  // KFileItem Data
450  { QStringLiteral("kfileitem#type"), QStringLiteral("0FileItemA") },
451  { QStringLiteral("kfileitem#linkDest"), QStringLiteral("0FileItemB") },
452  { QStringLiteral("kfileitem#size"), QStringLiteral("0FileItemC") },
453  { QStringLiteral("kfileitem#totalSize"), QStringLiteral("0FileItemC") },
454  { QStringLiteral("kfileitem#hiddenItems"), QStringLiteral("0FileItemD") },
455  { QStringLiteral("kfileitem#modified"), QStringLiteral("0FileItemE") },
456  { QStringLiteral("kfileitem#accessed"), QStringLiteral("0FileItemF") },
457  { QStringLiteral("kfileitem#created"), QStringLiteral("0FileItemG") },
458  { QStringLiteral("kfileitem#owner"), QStringLiteral("0FileItemH") },
459  { QStringLiteral("kfileitem#group"), QStringLiteral("0FileItemI") },
460  { QStringLiteral("kfileitem#permissions"), QStringLiteral("0FileItemJ") },
461 
462  // Editable Data
463  { QStringLiteral("tags"), QStringLiteral("1EditableDataA") },
464  { QStringLiteral("rating"), QStringLiteral("1EditableDataB") },
465  { QStringLiteral("userComment"), QStringLiteral("1EditableDataC") },
466 
467  // Image Data
468  { QStringLiteral("width"), QStringLiteral("2ImageA") },
469  { QStringLiteral("height"), QStringLiteral("2ImageB") },
470  { QStringLiteral("photoFNumber"), QStringLiteral("2ImageC") },
471  { QStringLiteral("photoExposureTime"), QStringLiteral("2ImageD") },
472  { QStringLiteral("photoExposureBiasValue"), QStringLiteral("2ImageE") },
473  { QStringLiteral("photoISOSpeedRatings"), QStringLiteral("2ImageF") },
474  { QStringLiteral("photoFocalLength"), QStringLiteral("2ImageG") },
475  { QStringLiteral("photoFocalLengthIn35mmFilm"), QStringLiteral("2ImageH") },
476  { QStringLiteral("photoFlash"), QStringLiteral("2ImageI") },
477  { QStringLiteral("imageOrientation"), QStringLiteral("2ImageJ") },
478  { QStringLiteral("photoGpsLatitude"), QStringLiteral("2ImageK") },
479  { QStringLiteral("photoGpsLongitude"), QStringLiteral("2ImageL") },
480  { QStringLiteral("photoGpsAltitude"), QStringLiteral("2ImageM") },
481  { QStringLiteral("manufacturer"), QStringLiteral("2ImageN") },
482  { QStringLiteral("model"), QStringLiteral("2ImageO") },
483 
484  // Media Data
485  { QStringLiteral("title"), QStringLiteral("3MediaA") },
486  { QStringLiteral("artist"), QStringLiteral("3MediaB") },
487  { QStringLiteral("album"), QStringLiteral("3MediaC") },
488  { QStringLiteral("albumArtist"), QStringLiteral("3MediaD") },
489  { QStringLiteral("genre"), QStringLiteral("3MediaE") },
490  { QStringLiteral("trackNumber"), QStringLiteral("3MediaF") },
491  { QStringLiteral("discNumber"), QStringLiteral("3MediaG") },
492  { QStringLiteral("releaseYear"), QStringLiteral("3MediaH") },
493  { QStringLiteral("duration"), QStringLiteral("3MediaI") },
494  { QStringLiteral("sampleRate"), QStringLiteral("3MediaJ") },
495  { QStringLiteral("bitRate"), QStringLiteral("3MediaK") },
496 
497  // Miscellaneous Data
498  { QStringLiteral("originUrl"), QStringLiteral("4MiscA") },
499  };
500 
501  const QString val = uriGrouper.value(label);
502  if (val.isEmpty()) {
503  return QStringLiteral("lastGroup");
504  }
505  return val;
506 }
507 
508 KFileItemList FileMetaDataProvider::items() const
509 {
510  return m_fileItems;
511 }
512 
514 {
515  m_readOnly = readOnly;
516 }
517 
518 bool FileMetaDataProvider::isReadOnly() const
519 {
520  return m_readOnly;
521 }
522 
523 QVariantMap FileMetaDataProvider::data() const
524 {
525  return m_data;
526 }
527 
528 QPair<int, int> FileMetaDataProvider::subDirectoriesCount(const QString& path)
529 {
530 #ifdef Q_OS_WIN
531  QDir dir(path);
534  return QPair<int, int> (count, hiddenCount);
535 #else
536  // Taken from kdelibs/kio/kio/kdirmodel.cpp
537  // Copyright (C) 2006 David Faure <[email protected]>
538 
539  int count = -1;
540  int hiddenCount = -1;
541  DIR* dir = ::opendir(QFile::encodeName(path).constData());
542  if (dir) {
543  count = 0;
544  hiddenCount = 0;
545  struct dirent *dirEntry = nullptr;
546  while ((dirEntry = ::readdir(dir))) { // krazy:exclude=syscalls
547  if (dirEntry->d_name[0] == '.') {
548  if (dirEntry->d_name[1] == '\0') {
549  // Skip "."
550  continue;
551  }
552  if (dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0') {
553  // Skip ".."
554  continue;
555  }
556  // hidden files
557  hiddenCount++;
558  } else {
559  ++count;
560  }
561  }
562  ::closedir(dir);
563  }
564  return QPair<int, int>(count, hiddenCount);
565 #endif
566 }
567 
569 {
570  return m_realTimeIndexing;
571 }
void clear()
bool isDir() const
QString user() const
void reserve(int alloc)
void setItems(const KFileItemList &items)
Sets the items, where the meta data should be requested.
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
QString displayName() const
QSet< T > toSet() const const
QUrl targetUrl() const
int size() const const
QDateTime time(FileTimes which) const
KIO::filesize_t recursiveSize() const
bool isLocalFile() const
int count(const T &value) const const
static PropertyInfo fromName(const QString &name)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
bool isEmpty() const const
QString linkDest() const
bool isEmpty() const const
QString path(QUrl::ComponentFormattingOptions options) const const
QString mimeComment() const
T & first()
QString group() const
KIO::filesize_t size() const
QString permissionsString() const
QString toLocalFile() const const
const T value(const Key &key) const const
bool isLink() const
QString formatByteSize(double size, int precision=1, KFormat::BinaryUnitDialect dialect=KFormat::DefaultBinaryDialect, KFormat::BinarySizeUnits units=KFormat::DefaultBinaryUnits) const
bool contains(uint field) const
virtual QString label(const QString &metaDataLabel) const
QString i18ncp(const char *context, const char *singular, const char *plural, const TYPE &arg...)
bool contains(const T &value) const const
bool isValid() const const
bool realTimeIndexing()
Returns true if the items do not exist in the database and have just been indexed.
bool remove(const T &value)
QStringList toStringList() const const
QSet< T > & unite(const QSet< T > &other)
QSet< T > & intersect(const QSet< T > &other)
QStringList entryList(QDir::Filters filters, QDir::SortFlags sort) const const
void setReadOnly(bool readOnly)
If set to true, data such as the comment, tag or rating cannot be changed by the user.
bool isValid() const const
QUrl url() const
QVariant::Type type() const const
KIO::UDSEntry entry() const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QString toString() const const
QFuture< void > map(Sequence &sequence, MapFunctor function)
QByteArray encodeName(const QString &fileName)
virtual QString group(const QString &label) const
Meta data items are sorted alphabetically by their translated label per default.
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
bool isLocalFile() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Wed Jan 27 2021 23:04:09 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.