File indexing completed on 2024-05-12 15:55:37

0001 // SPDX-FileCopyrightText: 2003 - 2020 The KPhotoAlbum Development Team
0002 // SPDX-FileCopyrightText: 2021 - 2022 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
0003 // SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de>
0004 //
0005 // SPDX-License-Identifier: GPL-2.0-or-later
0006 
0007 #include "SearchInfo.h"
0008 
0009 #include "Database.h"
0010 
0011 #include <kpabase/FileName.h>
0012 
0013 #include <KLocalizedString>
0014 
0015 /**
0016  * \class Exif::SearchInfo
0017  * This class represents a search for Exif information. It is similar in functionality for category searches which is in the
0018  * class \ref DB::ImageSearchInfo.
0019  *
0020  * The search is build, from \ref Exif::SearchDialog, using the functions addRangeKey(), addSearchKey(), and addCamara().
0021  * The search is stored in an instance of \ref DB::ImageSearchInfo, and may later be executed using search().
0022  * Once a search has been executed, the application may ask if a given image is in the search result using matches()
0023  */
0024 Exif::SearchInfo::SearchInfo()
0025     : m_exifDB(nullptr)
0026 {
0027 }
0028 
0029 Exif::SearchInfo::SearchInfo(const Database *db)
0030     : m_exifDB(db)
0031 {
0032 }
0033 
0034 void Exif::SearchInfo::addSearchKey(const QString &key, const IntList &values)
0035 {
0036     m_intKeys.append(qMakePair(key, values));
0037 }
0038 
0039 QStringList Exif::SearchInfo::buildIntKeyQuery() const
0040 {
0041     QStringList andArgs;
0042     for (IntKeyList::ConstIterator intIt = m_intKeys.begin(); intIt != m_intKeys.end(); ++intIt) {
0043         QStringList orArgs;
0044         const QString key = (*intIt).first;
0045         const IntList values = (*intIt).second;
0046 
0047         for (int value : values) {
0048             orArgs << QString::fromLatin1("(%1 == %2)").arg(key).arg(value);
0049         }
0050         if (orArgs.count() != 0)
0051             andArgs << QString::fromLatin1("(%1)").arg(orArgs.join(QString::fromLatin1(" or ")));
0052     }
0053 
0054     return andArgs;
0055 }
0056 
0057 void Exif::SearchInfo::addRangeKey(const Range &range)
0058 {
0059     m_rangeKeys.append(range);
0060 }
0061 
0062 Exif::SearchInfo::Range::Range(const QString &key)
0063     : key(key)
0064 {
0065 }
0066 
0067 QString Exif::SearchInfo::buildQuery() const
0068 {
0069     QStringList subQueries;
0070     subQueries += buildIntKeyQuery();
0071     subQueries += buildRangeQuery();
0072     QString cameraQuery = buildCameraSearchQuery();
0073     if (!cameraQuery.isEmpty())
0074         subQueries.append(cameraQuery);
0075     QString lensQuery = buildLensSearchQuery();
0076     if (!lensQuery.isEmpty())
0077         subQueries.append(lensQuery);
0078 
0079     if (subQueries.empty())
0080         return QString();
0081     else
0082         return QString::fromLatin1("SELECT filename from exif WHERE %1")
0083             .arg(subQueries.join(QString::fromLatin1(" and ")));
0084 }
0085 
0086 QStringList Exif::SearchInfo::buildRangeQuery() const
0087 {
0088     QStringList result;
0089     for (QList<Range>::ConstIterator it = m_rangeKeys.begin(); it != m_rangeKeys.end(); ++it) {
0090         QString str = sqlForOneRangeItem(*it);
0091         if (!str.isEmpty())
0092             result.append(str);
0093     }
0094     return result;
0095 }
0096 
0097 QString Exif::SearchInfo::sqlForOneRangeItem(const Range &range) const
0098 {
0099     // Notice I multiplied factors on each value to ensure that we do not fail due to rounding errors for say 1/3
0100 
0101     if (range.isLowerMin) {
0102         //  Min to Min  means < x
0103         if (range.isUpperMin)
0104             return QString::fromLatin1("%1 < %2 and %3 > 0").arg(range.key).arg(range.min * 1.01).arg(range.key);
0105 
0106         //  Min to Max means all images
0107         if (range.isUpperMax)
0108             return QString();
0109 
0110         //  Min to y   means <= y
0111         return QString::fromLatin1("%1 <= %2 and %3 > 0").arg(range.key).arg(range.max * 1.01).arg(range.key);
0112     }
0113 
0114     //  MAX to MAX   means >= y
0115     if (range.isLowerMax)
0116         return QString::fromLatin1("%1 > %2").arg(range.key).arg(range.max * 0.99);
0117 
0118     //  x to Max   means >= x
0119     if (range.isUpperMax)
0120         return QString::fromLatin1("%1 >= %2").arg(range.key).arg(range.min * 0.99);
0121 
0122     //  x to y     means >=x and <=y
0123     return QString::fromLatin1("(%1 <= %2 and %2 <= %4)")
0124         .arg(range.min * 0.99)
0125         .arg(range.key)
0126         .arg(range.max * 1.01);
0127 }
0128 
0129 void Exif::SearchInfo::search() const
0130 {
0131     QString queryStr = buildQuery();
0132     m_emptyQuery = queryStr.isEmpty();
0133 
0134     // ensure to do SQL queries as little as possible.
0135     static QString lastQuery;
0136     if (queryStr == lastQuery)
0137         return;
0138     lastQuery = queryStr;
0139 
0140     m_matches.clear();
0141     if (m_emptyQuery)
0142         return;
0143     m_matches = m_exifDB->filesMatchingQuery(queryStr);
0144 }
0145 
0146 bool Exif::SearchInfo::matches(const DB::FileName &fileName) const
0147 {
0148     if (m_emptyQuery)
0149         return true;
0150 
0151     return m_matches.contains(fileName);
0152 }
0153 
0154 bool Exif::SearchInfo::isNull() const
0155 {
0156     return m_exifDB == nullptr;
0157 }
0158 
0159 bool Exif::SearchInfo::isEmpty() const
0160 {
0161     return isNull() || buildQuery().isEmpty();
0162 }
0163 
0164 void Exif::SearchInfo::addCamera(const CameraList &list)
0165 {
0166     m_cameras = list;
0167 }
0168 
0169 void Exif::SearchInfo::addLens(const LensList &list)
0170 {
0171     m_lenses = list;
0172 }
0173 
0174 QString Exif::SearchInfo::buildCameraSearchQuery() const
0175 {
0176     QStringList subResults;
0177     for (CameraList::ConstIterator cameraIt = m_cameras.begin(); cameraIt != m_cameras.end(); ++cameraIt) {
0178         subResults.append(QString::fromUtf8("(Exif_Image_Make='%1' and Exif_Image_Model='%2')")
0179                               .arg((*cameraIt).first, (*cameraIt).second));
0180     }
0181     if (subResults.count() != 0)
0182         return QString::fromUtf8("(%1)").arg(subResults.join(QString::fromLatin1(" or ")));
0183     else
0184         return QString();
0185 }
0186 
0187 QString Exif::SearchInfo::buildLensSearchQuery() const
0188 {
0189     QStringList subResults;
0190     for (LensList::ConstIterator lensIt = m_lenses.begin(); lensIt != m_lenses.end(); ++lensIt) {
0191         if (*lensIt == i18nc("As in No persons, no locations etc.", "None"))
0192             // compare to null (=entry from old db schema) and empty string (=entry w/o exif lens info)
0193             subResults.append(QString::fromUtf8("(nullif(Exif_Photo_LensModel,'') is null)"));
0194         else
0195             subResults.append(QString::fromUtf8("(Exif_Photo_LensModel='%1')")
0196                                   .arg(*lensIt));
0197     }
0198     if (subResults.count() != 0)
0199         return QString::fromUtf8("(%1)").arg(subResults.join(QString::fromLatin1(" or ")));
0200     else
0201         return QString();
0202 }
0203 
0204 // vi:expandtab:tabstop=4 shiftwidth=4: