File indexing completed on 2024-04-21 12:15:18

0001 /*
0002     SPDX-FileCopyrightText: 2019 Stefan BrĂ¼ns <stefan.bruens@rwth-aachen.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "filemetadatautil_p.h"
0008 #include <KFileMetaData/PropertyInfo>
0009 #include <KFileMetaData/UserMetaData>
0010 #include <QSet>
0011 
0012 #include <QCollator>
0013 #include <QString>
0014 #include <QStringList>
0015 
0016 #include <algorithm>
0017 
0018 namespace
0019 {
0020 QVariant intersect(const QVariant &v1, const QVariant &v2)
0021 {
0022     if (!v1.isValid() || !v2.isValid()) {
0023         return {};
0024     }
0025 
0026     // List and String
0027     if (v1.type() == QVariant::StringList && v2.type() == QVariant::String) {
0028         QStringList list = v1.toStringList();
0029         QString str = v2.toString();
0030 
0031         if (!list.contains(str)) {
0032             list << str;
0033         }
0034 
0035         return QVariant(list);
0036     }
0037 
0038     // String and List
0039     if (v1.type() == QVariant::String && v2.type() == QVariant::StringList) {
0040         QStringList list = v2.toStringList();
0041         QString str = v1.toString();
0042 
0043         if (!list.contains(str)) {
0044             list << str;
0045         }
0046 
0047         return QVariant(list);
0048     }
0049 
0050     // List and List
0051     if (v1.type() == QVariant::StringList && v2.type() == QVariant::StringList) {
0052         QSet<QString> s1(v1.toStringList().cbegin(), v1.toStringList().cend());
0053         QSet<QString> s2(v2.toStringList().cbegin(), v2.toStringList().cend());
0054 
0055         return QVariant(s1.intersect(s2).values());
0056     }
0057 
0058     if (v1 == v2) {
0059         return v1;
0060     }
0061 
0062     return {};
0063 }
0064 
0065 // Precondition:
0066 // if commonProperties contains <prop>, all <files> must also provide <prop>
0067 void totalProperties(QVariantMap& target, const QString &prop, const QList<QVariantMap> &files, QList<QString> &commonProperties)
0068 {
0069     auto propIndex = commonProperties.indexOf(prop);
0070 
0071     if (propIndex >= 0) {
0072         int total = 0;
0073         for (const QVariantMap &file : files) {
0074             QVariantMap::const_iterator it = file.constFind(prop);
0075             Q_ASSERT(it != file.constEnd());
0076 
0077             total += it.value().toInt();
0078         }
0079 
0080         target.insert(prop, QVariant(total));
0081 
0082         commonProperties.removeAt(propIndex);
0083     }
0084 }
0085 } // anonymous namespace
0086 
0087 namespace Baloo
0088 {
0089 namespace Private
0090 {
0091 QMap<KFileMetaData::UserMetaData::Attribute, QVariant>
0092 fetchUserMetaData(const KFileMetaData::UserMetaData &metaData, KFileMetaData::UserMetaData::Attributes wantedAttributes)
0093 {
0094     using Attribute = KFileMetaData::UserMetaData::Attribute;
0095 
0096     auto attributes = metaData.queryAttributes(wantedAttributes);
0097 
0098     QMap<Attribute, QVariant> properties;
0099 
0100     if (attributes & Attribute::Tags) {
0101         QStringList tags = metaData.tags();
0102         if (!tags.isEmpty()) {
0103             properties.insert(Attribute::Tags, tags);
0104         }
0105     }
0106 
0107     if (attributes & Attribute::Rating) {
0108         int rating = metaData.rating();
0109         if (rating) {
0110             properties.insert(Attribute::Rating, rating);
0111         }
0112     }
0113 
0114     if (attributes & Attribute::Comment) {
0115         QString comment = metaData.userComment();
0116         if (!comment.isEmpty()) {
0117             properties.insert(Attribute::Comment, comment);
0118         }
0119     }
0120 
0121     if (attributes & Attribute::OriginUrl) {
0122         const QString originUrl = metaData.originUrl().toDisplayString();
0123         if (!originUrl.isEmpty()) {
0124             properties.insert(Attribute::OriginUrl, originUrl);
0125         }
0126     }
0127 
0128     return properties;
0129 }
0130 
0131 QVariantMap convertUserMetaData(const QMap<KFileMetaData::UserMetaData::Attribute, QVariant>& metaData)
0132 {
0133     using Attribute = KFileMetaData::UserMetaData::Attribute;
0134 
0135     QVariantMap properties;
0136     for (auto it = metaData.begin(); it != metaData.end(); ++it) {
0137         if (it.key() == Attribute::Tags) {
0138             properties.insert(QStringLiteral("tags"), it.value());
0139 
0140         } else if (it.key() == Attribute::Rating) {
0141             properties.insert(QStringLiteral("rating"), it.value());
0142 
0143         } else if (it.key() == Attribute::Comment) {
0144             properties.insert(QStringLiteral("userComment"), it.value());
0145 
0146         } else if (it.key() == Attribute::OriginUrl) {
0147             properties.insert(QStringLiteral("originUrl"), it.value());
0148         }
0149     }
0150 
0151     return properties;
0152 }
0153 
0154 QVariantMap convertUserMetaData(const KFileMetaData::UserMetaData &metaData)
0155 {
0156     using Attribute = KFileMetaData::UserMetaData::Attribute;
0157 
0158     auto attributeData = fetchUserMetaData(metaData, Attribute::Tags | Attribute::Rating | Attribute::Comment | Attribute::OriginUrl);
0159 
0160     return convertUserMetaData(attributeData);
0161 }
0162 
0163 QVariantMap toNamedVariantMap(const KFileMetaData::PropertyMultiMap &propMap)
0164 {
0165     QVariantMap map;
0166     if (propMap.isEmpty()) {
0167         return map;
0168     }
0169 
0170     using entry = std::pair<const KFileMetaData::Property::Property &, const QVariant &>;
0171 
0172     auto begin = propMap.constKeyValueBegin();
0173 
0174     while (begin != propMap.constKeyValueEnd()) {
0175         auto key = (*begin).first;
0176         KFileMetaData::PropertyInfo property(key);
0177         auto rangeEnd = std::find_if(begin, propMap.constKeyValueEnd(), [key](const entry &e) {
0178             return e.first != key;
0179         });
0180 
0181         auto distance = std::distance(begin, rangeEnd);
0182         if (distance > 1) {
0183             QVariantList list;
0184             list.reserve(static_cast<int>(distance));
0185             std::for_each(begin, rangeEnd, [&list](const entry &s) {
0186                 list.append(s.second);
0187             });
0188             map.insert(property.name(), list);
0189         } else {
0190             map.insert(property.name(), (*begin).second);
0191         }
0192         begin = rangeEnd;
0193     }
0194 
0195     return map;
0196 }
0197 
0198 void mergeCommonData(QVariantMap& target, const QList<QVariantMap> &files)
0199 {
0200     if (files.empty()) {
0201         target.clear();
0202         return;
0203     }
0204 
0205     if (files.size() == 1) {
0206         target = files[0];
0207         return;
0208     }
0209 
0210     //
0211     // Only report the stuff that is common to all the files
0212     //
0213     QList<QString> commonProperties = files[0].keys();
0214     auto end = commonProperties.end();
0215     for (const QVariantMap &fileData : files) {
0216         end = std::remove_if(commonProperties.begin(), end,
0217                 [&fileData](const QString& prop) { return !fileData.contains(prop); }
0218         );
0219     }
0220     commonProperties.erase(end, commonProperties.end());
0221 
0222     // Special handling for certain properties
0223     totalProperties(target, QStringLiteral("duration"), files, commonProperties);
0224     totalProperties(target, QStringLiteral("characterCount"), files, commonProperties);
0225     totalProperties(target, QStringLiteral("wordCount"), files, commonProperties);
0226     totalProperties(target, QStringLiteral("lineCount"), files, commonProperties);
0227 
0228     for (const QString &propUri : std::as_const(commonProperties)) {
0229         QVariant value = files[0][propUri];
0230         for (const QVariantMap &file : files) {
0231             QVariantMap::const_iterator it = file.find(propUri);
0232             Q_ASSERT(it != file.constEnd());
0233 
0234             value = intersect(it.value(), value);
0235             if (!value.isValid()) {
0236                 break;
0237             }
0238         }
0239 
0240         if (value.isValid())
0241             target[propUri] = value;
0242     }
0243 }
0244 
0245 QStringList sortTags(const QStringList &tags)
0246 {
0247     QCollator coll;
0248     coll.setNumericMode(true);
0249     QStringList sortedTags = tags;
0250     std::sort(sortedTags.begin(), sortedTags.end(), [&](const QString &s1, const QString &s2) {
0251         return coll.compare(s1, s2) < 0;
0252     });
0253     return sortedTags;
0254 }
0255 
0256 } // namespace KFMPrivate
0257 } // namespace Baloo