Warning, file /libraries/baloo-widgets/src/filemetadataprovider.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 SPDX-FileCopyrightText: 2010 Peter Penz <peter.penz@gmx.at> 0003 SPDX-FileCopyrightText: 2012 Vishesh Handa <me@vhanda.in> 0004 SPDX-FileCopyrightText: 2021 Kai Uwe Broulik <kde@broulik.de> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include "filemetadataprovider.h" 0010 #include "filemetadatautil_p.h" 0011 #include "filefetchjob.h" 0012 0013 #include <KFileMetaData/PropertyInfo> 0014 #include <KFormat> 0015 #include <KLocalizedString> 0016 #include <KProtocolInfo> 0017 #include <KShell> 0018 0019 #include <QPair> 0020 0021 // Required includes for subDirectoriesCount(): 0022 #ifdef Q_OS_WIN 0023 #include <QDir> 0024 #else 0025 #include <QFile> 0026 #include <dirent.h> 0027 #endif 0028 0029 using namespace Baloo; 0030 0031 namespace 0032 { 0033 /** 0034 * The standard QMap::unite will contain the key multiple times if both \p v1 and \p v2 0035 * contain the same key. 0036 * 0037 * This will only take the key from \p v2 into account 0038 */ 0039 QVariantMap unite(const QVariantMap &v1, const QVariantMap &v2) 0040 { 0041 QVariantMap v(v1); 0042 QMapIterator<QString, QVariant> it(v2); 0043 while (it.hasNext()) { 0044 it.next(); 0045 0046 v[it.key()] = it.value(); 0047 } 0048 0049 return v; 0050 } 0051 0052 /** 0053 * @return The number of files and hidden files for the directory path. 0054 */ 0055 QPair<int, int> subDirectoriesCount(const QString &path) 0056 { 0057 #ifdef Q_OS_WIN 0058 QDir dir(path); 0059 int count = dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::System).count(); 0060 int hiddenCount = dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::System | QDir::Hidden).count(); 0061 return QPair<int, int>(count, hiddenCount); 0062 #else 0063 // Taken from kdelibs/kio/kio/kdirmodel.cpp 0064 // SPDX-FileCopyrightText: 2006 David Faure <faure@kde.org> 0065 0066 int count = -1; 0067 int hiddenCount = -1; 0068 DIR *dir = ::opendir(QFile::encodeName(path).constData()); 0069 if (dir) { 0070 count = 0; 0071 hiddenCount = 0; 0072 struct dirent *dirEntry = nullptr; 0073 while ((dirEntry = ::readdir(dir))) { // krazy:exclude=syscalls 0074 if (dirEntry->d_name[0] == '.') { 0075 if (dirEntry->d_name[1] == '\0') { 0076 // Skip "." 0077 continue; 0078 } 0079 if (dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0') { 0080 // Skip ".." 0081 continue; 0082 } 0083 // hidden files 0084 hiddenCount++; 0085 } else { 0086 ++count; 0087 } 0088 } 0089 ::closedir(dir); 0090 } 0091 return QPair<int, int>(count, hiddenCount); 0092 #endif 0093 } 0094 0095 /** 0096 * Fill \p data with properties can be derived from others 0097 */ 0098 void extractDerivedProperties(QVariantMap &data) 0099 { 0100 const auto width = data.value(QStringLiteral("width")); 0101 const auto height = data.value(QStringLiteral("height")); 0102 if ((width.type() == QVariant::Double || width.type() == QVariant::Int) && (height.type() == QVariant::Double || height.type() == QVariant::Int)) { 0103 data.insert(QStringLiteral("dimensions"), i18nc("width × height", "%1 × %2", width.toInt(), height.toInt())); 0104 } 0105 0106 bool okLatitude; 0107 const auto gpsLatitude = data.value(QStringLiteral("photoGpsLatitude")).toFloat(&okLatitude); 0108 bool okLongitude; 0109 const auto gpsLongitude = data.value(QStringLiteral("photoGpsLongitude")).toFloat(&okLongitude); 0110 0111 if (okLatitude && okLongitude) { 0112 data.insert(QStringLiteral("gpsLocation"), QVariant::fromValue(QPair<float, float>(gpsLatitude, gpsLongitude))); 0113 } 0114 } 0115 } // anonymous namespace 0116 0117 void FileMetaDataProvider::slotFileFetchFinished(KJob *job) 0118 { 0119 auto fetchJob = static_cast<FileFetchJob *>(job); 0120 QList<QVariantMap> files = fetchJob->data(); 0121 0122 Q_ASSERT(!files.isEmpty()); 0123 0124 if (files.size() > 1) { 0125 Baloo::Private::mergeCommonData(m_data, files); 0126 } else { 0127 m_data = unite(m_data, files.first()); 0128 } 0129 extractDerivedProperties(m_data); 0130 m_readOnly = !fetchJob->canEditAll(); 0131 0132 insertEditableData(); 0133 Q_EMIT loadingFinished(); 0134 } 0135 0136 void FileMetaDataProvider::insertSingleFileBasicData() 0137 { 0138 // TODO: Handle case if remote URLs are used properly. isDir() does 0139 // not work, the modification date needs also to be adjusted... 0140 Q_ASSERT(m_fileItems.count() == 1); 0141 { 0142 const KFileItem &item = m_fileItems.first(); 0143 0144 KFormat format; 0145 if (item.isDir()) { 0146 if (item.isLocalFile() && !item.isSlow()) { 0147 const QPair<int, int> counts = subDirectoriesCount(item.url().path()); 0148 const int count = counts.first; 0149 if (count != -1) { 0150 QString itemCountString = i18ncp("@item:intable", "%1 item", "%1 items", count); 0151 m_data.insert(QStringLiteral("kfileitem#size"), itemCountString); 0152 0153 const int hiddenCount = counts.second; 0154 if (hiddenCount > 0) { 0155 // add hidden items count 0156 QString hiddenCountString = i18ncp("@item:intable", "%1 item", "%1 items", hiddenCount); 0157 m_data.insert(QStringLiteral("kfileitem#hiddenItems"), hiddenCountString); 0158 } 0159 } 0160 } else if (item.entry().contains(KIO::UDSEntry::UDS_SIZE)) { 0161 m_data.insert(QStringLiteral("kfileitem#size"), format.formatByteSize(item.size())); 0162 } 0163 if (item.entry().contains(KIO::UDSEntry::UDS_RECURSIVE_SIZE)) { 0164 m_data.insert(QStringLiteral("kfileitem#totalSize"), format.formatByteSize(item.recursiveSize())); 0165 } 0166 } else { 0167 if (item.entry().contains(KIO::UDSEntry::UDS_SIZE)) { 0168 m_data.insert(QStringLiteral("kfileitem#size"), format.formatByteSize(item.size())); 0169 } 0170 } 0171 0172 m_data.insert(QStringLiteral("kfileitem#type"), item.mimeComment()); 0173 if (item.isLink()) { 0174 m_data.insert(QStringLiteral("kfileitem#linkDest"), item.linkDest()); 0175 } 0176 if (item.entry().contains(KIO::UDSEntry::UDS_TARGET_URL)) { 0177 m_data.insert(QStringLiteral("kfileitem#targetUrl"), KShell::tildeCollapse(item.targetUrl().toDisplayString(QUrl::PreferLocalFile))); 0178 } 0179 QDateTime modificationTime = item.time(KFileItem::ModificationTime); 0180 if (modificationTime.isValid()) { 0181 m_data.insert(QStringLiteral("kfileitem#modified"), modificationTime); 0182 } 0183 QDateTime creationTime = item.time(KFileItem::CreationTime); 0184 if (creationTime.isValid()) { 0185 m_data.insert(QStringLiteral("kfileitem#created"), creationTime); 0186 } 0187 QDateTime accessTime = item.time(KFileItem::AccessTime); 0188 if (accessTime.isValid()) { 0189 m_data.insert(QStringLiteral("kfileitem#accessed"), accessTime); 0190 } 0191 0192 m_data.insert(QStringLiteral("kfileitem#owner"), item.user()); 0193 m_data.insert(QStringLiteral("kfileitem#group"), item.group()); 0194 m_data.insert(QStringLiteral("kfileitem#permissions"), item.permissionsString()); 0195 0196 const auto extraFields = KProtocolInfo::extraFields(item.url()); 0197 for (int i = 0; i < extraFields.count(); ++i) { 0198 const auto &field = extraFields.at(i); 0199 if (field.type == KProtocolInfo::ExtraField::Invalid) { 0200 continue; 0201 } 0202 0203 const QString text = item.entry().stringValue(KIO::UDSEntry::UDS_EXTRA + i); 0204 if (text.isEmpty()) { 0205 continue; 0206 } 0207 0208 const QString key = QStringLiteral("kfileitem#extra_%1_%2").arg(item.url().scheme(), QString::number(i + 1)); 0209 0210 if (field.type == KProtocolInfo::ExtraField::DateTime) { 0211 const QDateTime date = QDateTime::fromString(text, Qt::ISODate); 0212 if (!date.isValid()) { 0213 continue; 0214 } 0215 0216 m_data.insert(key, date); 0217 } else { 0218 m_data.insert(key, text); 0219 } 0220 } 0221 } 0222 } 0223 0224 void FileMetaDataProvider::insertFilesListBasicData() 0225 { 0226 // If all directories 0227 Q_ASSERT(m_fileItems.count() > 1); 0228 bool allDirectories = true; 0229 for (const KFileItem &item : std::as_const(m_fileItems)) { 0230 allDirectories &= item.isDir(); 0231 if (!allDirectories) { 0232 break; 0233 } 0234 } 0235 0236 if (allDirectories) { 0237 int count = 0; 0238 int hiddenCount = 0; 0239 for (const KFileItem &item : std::as_const(m_fileItems)) { 0240 if (!item.isLocalFile() || item.isSlow()) { 0241 return; 0242 } 0243 const QPair<int, int> counts = subDirectoriesCount(item.url().path()); 0244 const int subcount = counts.first; 0245 if (subcount == -1) { 0246 return; 0247 } 0248 count += subcount; 0249 hiddenCount += counts.second; 0250 } 0251 QString itemCountString = i18ncp("@item:intable", "%1 item", "%1 items", count); 0252 if (hiddenCount > 0) { 0253 // add hidden items count 0254 QString hiddenCountString = i18ncp("@item:intable", "%1 item", "%1 items", hiddenCount); 0255 m_data.insert(QStringLiteral("kfileitem#hiddenItems"), hiddenCountString); 0256 } 0257 m_data.insert(QStringLiteral("kfileitem#totalSize"), itemCountString); 0258 0259 } else { 0260 // Calculate the size of all items 0261 quint64 totalSize = 0; 0262 for (const KFileItem &item : std::as_const(m_fileItems)) { 0263 if (!item.isDir() && !item.isLink()) { 0264 totalSize += item.size(); 0265 } 0266 } 0267 KFormat format; 0268 m_data.insert(QStringLiteral("kfileitem#totalSize"), format.formatByteSize(totalSize)); 0269 } 0270 } 0271 0272 void FileMetaDataProvider::insertEditableData() 0273 { 0274 if (!m_readOnly) { 0275 if (!m_data.contains(QStringLiteral("tags"))) { 0276 m_data.insert(QStringLiteral("tags"), QVariant()); 0277 } 0278 if (!m_data.contains(QStringLiteral("rating"))) { 0279 m_data.insert(QStringLiteral("rating"), 0); 0280 } 0281 if (!m_data.contains(QStringLiteral("userComment"))) { 0282 m_data.insert(QStringLiteral("userComment"), QVariant()); 0283 } 0284 } 0285 } 0286 0287 FileMetaDataProvider::FileMetaDataProvider(QObject *parent) 0288 : QObject(parent) 0289 , m_readOnly(false) 0290 { 0291 } 0292 0293 FileMetaDataProvider::~FileMetaDataProvider() = default; 0294 0295 void FileMetaDataProvider::setFileItem() 0296 { 0297 // There are 3 code paths - 0298 // Remote file 0299 // Single local file - 0300 // * Not Indexed 0301 // * Indexed 0302 // 0303 insertSingleFileBasicData(); 0304 const QUrl url = m_fileItems.first().targetUrl(); 0305 if (!url.isLocalFile() || m_fileItems.first().isSlow()) { 0306 // FIXME - are extended attributes supported for remote files? 0307 m_readOnly = true; 0308 Q_EMIT loadingFinished(); 0309 return; 0310 } 0311 0312 const QString filePath = url.toLocalFile(); 0313 FileFetchJob *job; 0314 0315 // Not indexed or only basic file indexing (no content) 0316 if (!m_config.fileIndexingEnabled() || !m_config.shouldBeIndexed(filePath) || m_config.onlyBasicIndexing()) { 0317 job = new FileFetchJob(QStringList{filePath}, true, FileFetchJob::UseRealtimeIndexing::Only, this); 0318 0319 // Fully indexed by Baloo 0320 } else { 0321 job = new FileFetchJob(QStringList{filePath}, true, FileFetchJob::UseRealtimeIndexing::Fallback, this); 0322 } 0323 connect(job, &FileFetchJob::finished, this, &FileMetaDataProvider::slotFileFetchFinished); 0324 job->start(); 0325 } 0326 0327 void FileMetaDataProvider::setFileItems() 0328 { 0329 // Multiple Files - 0330 // * Not Indexed 0331 // * Indexed 0332 0333 QStringList urls; 0334 urls.reserve(m_fileItems.size()); 0335 // Only extract data from indexed files, 0336 // it would be too expensive otherwise. 0337 for (const KFileItem &item : std::as_const(m_fileItems)) { 0338 const QUrl url = item.targetUrl(); 0339 if (url.isLocalFile() && !item.isSlow()) { 0340 urls << url.toLocalFile(); 0341 } 0342 } 0343 0344 insertFilesListBasicData(); 0345 if (!urls.isEmpty()) { 0346 // Editing only if all URLs are local 0347 bool canEdit = (urls.size() == m_fileItems.size()); 0348 0349 auto job = new FileFetchJob(urls, canEdit, FileFetchJob::UseRealtimeIndexing::Disabled, this); 0350 connect(job, &FileFetchJob::finished, this, &FileMetaDataProvider::slotFileFetchFinished); 0351 job->start(); 0352 0353 } else { 0354 // FIXME - are extended attributes supported for remote files? 0355 m_readOnly = true; 0356 Q_EMIT loadingFinished(); 0357 } 0358 } 0359 0360 void FileMetaDataProvider::setItems(const KFileItemList &items) 0361 { 0362 m_fileItems = items; 0363 m_data.clear(); 0364 0365 if (items.isEmpty()) { 0366 Q_EMIT loadingFinished(); 0367 } else if (items.size() == 1) { 0368 setFileItem(); 0369 } else { 0370 setFileItems(); 0371 } 0372 } 0373 0374 QString FileMetaDataProvider::label(const QString &metaDataLabel) const 0375 { 0376 static QHash<QString, QString> hash = { 0377 {QStringLiteral("kfileitem#comment"), i18nc("@label", "Comment")}, 0378 {QStringLiteral("kfileitem#created"), i18nc("@label", "Created")}, 0379 {QStringLiteral("kfileitem#accessed"), i18nc("@label", "Accessed")}, 0380 {QStringLiteral("kfileitem#modified"), i18nc("@label", "Modified")}, 0381 {QStringLiteral("kfileitem#owner"), i18nc("@label", "Owner")}, 0382 {QStringLiteral("kfileitem#group"), i18nc("@label", "Group")}, 0383 {QStringLiteral("kfileitem#permissions"), i18nc("@label", "Permissions")}, 0384 {QStringLiteral("kfileitem#rating"), i18nc("@label", "Rating")}, 0385 {QStringLiteral("kfileitem#size"), i18nc("@label", "Size")}, 0386 {QStringLiteral("kfileitem#tags"), i18nc("@label", "Tags")}, 0387 {QStringLiteral("kfileitem#totalSize"), i18nc("@label", "Total Size")}, 0388 {QStringLiteral("kfileitem#hiddenItems"), i18nc("@label", "Hidden items")}, 0389 {QStringLiteral("kfileitem#type"), i18nc("@label", "Type")}, 0390 {QStringLiteral("kfileitem#linkDest"), i18nc("@label", "Link to")}, 0391 {QStringLiteral("kfileitem#targetUrl"), i18nc("@label", "Points to")}, 0392 {QStringLiteral("tags"), i18nc("@label", "Tags")}, 0393 {QStringLiteral("rating"), i18nc("@label", "Rating")}, 0394 {QStringLiteral("userComment"), i18nc("@label", "Comment")}, 0395 {QStringLiteral("originUrl"), i18nc("@label", "Downloaded From")}, 0396 {QStringLiteral("dimensions"), i18nc("@label", "Dimensions")}, 0397 {QStringLiteral("gpsLocation"), i18nc("@label", "GPS Location")}, 0398 }; 0399 0400 QString value = hash.value(metaDataLabel); 0401 if (value.isEmpty()) { 0402 static const auto extraPrefix = QStringLiteral("kfileitem#extra_"); 0403 if (metaDataLabel.startsWith(extraPrefix)) { 0404 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0405 const auto parts = metaDataLabel.splitRef(QLatin1Char('_')); 0406 #else 0407 const auto parts = metaDataLabel.split(QLatin1Char('_')); 0408 #endif 0409 Q_ASSERT(parts.count() == 3); 0410 const auto protocol = parts.at(1); 0411 const int extraNumber = parts.at(2).toInt() - 1; 0412 0413 // Have to construct a dummy URL for KProtocolInfo::extraFields... 0414 QUrl url; 0415 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0416 url.setScheme(protocol.toString()); 0417 #else 0418 url.setScheme(protocol); 0419 #endif 0420 const auto extraFields = KProtocolInfo::extraFields(url); 0421 auto field = extraFields.value(extraNumber); 0422 if (field.type != KProtocolInfo::ExtraField::Invalid) { 0423 value = field.name; 0424 } 0425 } 0426 } 0427 0428 if (value.isEmpty()) { 0429 value = KFileMetaData::PropertyInfo::fromName(metaDataLabel).displayName(); 0430 } 0431 0432 return value; 0433 } 0434 0435 QString FileMetaDataProvider::group(const QString &label) const 0436 { 0437 static QHash<QString, QString> uriGrouper = { 0438 0439 // KFileItem Data 0440 {QStringLiteral("kfileitem#type"), QStringLiteral("0FileItemA")}, 0441 {QStringLiteral("kfileitem#linkDest"), QStringLiteral("0FileItemB")}, 0442 {QStringLiteral("kfileitem#size"), QStringLiteral("0FileItemC")}, 0443 {QStringLiteral("kfileitem#totalSize"), QStringLiteral("0FileItemC")}, 0444 {QStringLiteral("kfileitem#hiddenItems"), QStringLiteral("0FileItemD")}, 0445 {QStringLiteral("kfileitem#modified"), QStringLiteral("0FileItemE")}, 0446 {QStringLiteral("kfileitem#accessed"), QStringLiteral("0FileItemF")}, 0447 {QStringLiteral("kfileitem#created"), QStringLiteral("0FileItemG")}, 0448 {QStringLiteral("kfileitem#owner"), QStringLiteral("0FileItemH")}, 0449 {QStringLiteral("kfileitem#group"), QStringLiteral("0FileItemI")}, 0450 {QStringLiteral("kfileitem#permissions"), QStringLiteral("0FileItemJ")}, 0451 0452 // Editable Data 0453 {QStringLiteral("tags"), QStringLiteral("1EditableDataA")}, 0454 {QStringLiteral("rating"), QStringLiteral("1EditableDataB")}, 0455 {QStringLiteral("userComment"), QStringLiteral("1EditableDataC")}, 0456 0457 // Image Data 0458 {QStringLiteral("width"), QStringLiteral("2ImageA")}, 0459 {QStringLiteral("height"), QStringLiteral("2ImageB")}, 0460 {QStringLiteral("dimensions"), QStringLiteral("2ImageCA")}, 0461 {QStringLiteral("photoFNumber"), QStringLiteral("2ImageC")}, 0462 {QStringLiteral("photoExposureTime"), QStringLiteral("2ImageD")}, 0463 {QStringLiteral("photoExposureBiasValue"), QStringLiteral("2ImageE")}, 0464 {QStringLiteral("photoISOSpeedRatings"), QStringLiteral("2ImageF")}, 0465 {QStringLiteral("photoFocalLength"), QStringLiteral("2ImageG")}, 0466 {QStringLiteral("photoFocalLengthIn35mmFilm"), QStringLiteral("2ImageH")}, 0467 {QStringLiteral("photoFlash"), QStringLiteral("2ImageI")}, 0468 {QStringLiteral("imageOrientation"), QStringLiteral("2ImageJ")}, 0469 {QStringLiteral("photoGpsLocation"), QStringLiteral("2ImageK")}, 0470 {QStringLiteral("photoGpsLatitude"), QStringLiteral("2ImageL")}, 0471 {QStringLiteral("photoGpsLongitude"), QStringLiteral("2ImageM")}, 0472 {QStringLiteral("photoGpsAltitude"), QStringLiteral("2ImageN")}, 0473 {QStringLiteral("manufacturer"), QStringLiteral("2ImageO")}, 0474 {QStringLiteral("model"), QStringLiteral("2ImageP")}, 0475 0476 // Media Data 0477 {QStringLiteral("title"), QStringLiteral("3MediaA")}, 0478 {QStringLiteral("artist"), QStringLiteral("3MediaB")}, 0479 {QStringLiteral("album"), QStringLiteral("3MediaC")}, 0480 {QStringLiteral("albumArtist"), QStringLiteral("3MediaD")}, 0481 {QStringLiteral("genre"), QStringLiteral("3MediaE")}, 0482 {QStringLiteral("trackNumber"), QStringLiteral("3MediaF")}, 0483 {QStringLiteral("discNumber"), QStringLiteral("3MediaG")}, 0484 {QStringLiteral("releaseYear"), QStringLiteral("3MediaH")}, 0485 {QStringLiteral("duration"), QStringLiteral("3MediaI")}, 0486 {QStringLiteral("sampleRate"), QStringLiteral("3MediaJ")}, 0487 {QStringLiteral("bitRate"), QStringLiteral("3MediaK")}, 0488 0489 // Miscellaneous Data 0490 {QStringLiteral("originUrl"), QStringLiteral("4MiscA")}, 0491 }; 0492 0493 const QString val = uriGrouper.value(label); 0494 if (val.isEmpty()) { 0495 return QStringLiteral("lastGroup"); 0496 } 0497 return val; 0498 } 0499 0500 KFileItemList FileMetaDataProvider::items() const 0501 { 0502 return m_fileItems; 0503 } 0504 0505 void FileMetaDataProvider::setReadOnly(bool readOnly) 0506 { 0507 m_readOnly = readOnly; 0508 } 0509 0510 bool FileMetaDataProvider::isReadOnly() const 0511 { 0512 return m_readOnly; 0513 } 0514 0515 QVariantMap FileMetaDataProvider::data() const 0516 { 0517 return m_data; 0518 }