diff --git a/core/libs/database/item/imagelister.cpp b/core/libs/database/item/imagelister.cpp index c28264c8d9..fd9477e805 100644 --- a/core/libs/database/item/imagelister.cpp +++ b/core/libs/database/item/imagelister.cpp @@ -1,1149 +1,1149 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2007-03-20 * Description : Listing information from database. * * Copyright (C) 2005 by Renchi Raju * Copyright (C) 2007-2012 by Marcel Wiesweg * Copyright (C) 2007-2018 by Gilles Caulier * Copyright (C) 2015 by Mohamed_Anwer * Copyright (C) 2018 by Mario Frank * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "imagelister.h" // C++ includes #include #include #include #include #include // Qt includes #include #include #include #include #include // Local includes #include "digikam_debug.h" #include "coredb.h" #include "coredbaccess.h" #include "coredbbackend.h" #include "collectionmanager.h" #include "collectionlocation.h" #include "imagequerybuilder.h" #include "dmetadata.h" #include "haariface.h" #include "dbenginesqlquery.h" #include "tagscache.h" #include "imagetagpair.h" #include "dbjobsthread.h" #include "dbjobinfo.h" #include "similaritydbaccess.h" #include "similaritydb.h" namespace Digikam { /** * Used by QSet */ uint qHash(const ImageListerRecord& key) { return key.imageID; } /* * The binary field for file size is only 32 bit. * If the value fits, we pass it. If it does not, we pass -1, * and the receiver shall get the full number itself */ static inline int toInt32BitSafe(const QList::const_iterator& it) { qlonglong v = (*it).toLongLong(); if (v > std::numeric_limits::max() || v < 0) { return -1; } return (int)v; } // --------------------------------------------------------------------------------- class ImageLister::Private { public: explicit Private() { recursive = true; listOnlyAvailableImages = true; allowExtraValues = false; } bool recursive; bool listOnlyAvailableImages; bool allowExtraValues; }; ImageLister::ImageLister() : d(new Private) { } ImageLister::~ImageLister() { delete d; } void ImageLister::setRecursive(bool recursive) { d->recursive = recursive; } void ImageLister::setListOnlyAvailable(bool listOnlyAvailable) { d->listOnlyAvailableImages = listOnlyAvailable; } void ImageLister::setAllowExtraValues(bool useExtraValue) { d->allowExtraValues = useExtraValue; } void ImageLister::list(ImageListerReceiver* const receiver, const CoreDbUrl& url) { if (url.isAlbumUrl()) { int albumRootId = url.albumRootId(); QString album = url.album(); listAlbum(receiver, albumRootId, album); } else if (url.isTagUrl()) { listTag(receiver, url.tagIds()); } else if (url.isDateUrl()) { listDateRange(receiver, url.startDate(), url.endDate()); } else if (url.isMapImagesUrl()) { double lat1, lat2, lon1, lon2; url.areaCoordinates(&lat1, &lat2, &lon1, &lon2); listAreaRange(receiver, lat1, lat2, lon1, lon2); } } void ImageLister::listAlbum(ImageListerReceiver* const receiver, int albumRootId, const QString& album) { if (d->listOnlyAvailableImages) { if (!CollectionManager::instance()->locationForAlbumRootId(albumRootId).isAvailable()) { return; } } QList albumIds; if (d->recursive) { QList intAlbumIds = CoreDbAccess().db()->getAlbumAndSubalbumsForPath(albumRootId, album); if (intAlbumIds.isEmpty()) { return; } foreach(int id, intAlbumIds) { albumIds << id; } } else { int albumId = CoreDbAccess().db()->getAlbumForPath(albumRootId, album, false); if (albumId == -1) { return; } albumIds << albumId; } QList values; QString query = QString::fromUtf8("SELECT DISTINCT Images.id, Images.name, Images.album, " " ImageInformation.rating, Images.category, " " ImageInformation.format, ImageInformation.creationDate, " " Images.modificationDate, Images.fileSize, " " ImageInformation.width, ImageInformation.height " " FROM Images " " LEFT JOIN ImageInformation ON Images.id=ImageInformation.imageid " " WHERE Images.status=1 AND "); if (d->recursive) { // SQLite allows no more than 999 parameters const int maxParams = CoreDbAccess().backend()->maximumBoundValues(); for (int i = 0 ; i < albumIds.size() ; i++) { QString q = query; QList ids = (albumIds.size() <= maxParams) ? albumIds : albumIds.mid(i, maxParams); i += ids.count(); QList v; CoreDbAccess access; q += QString::fromUtf8("Images.album IN ("); access.db()->addBoundValuePlaceholders(q, ids.size()); q += QString::fromUtf8(");"); access.backend()->execSql(q, ids, &v); values += v; } } else { CoreDbAccess access; query += QString::fromUtf8("Images.album = ?;"); access.backend()->execSql(query, albumIds, &values); } int width, height; for (QList::const_iterator it = values.constBegin(); it != values.constEnd();) { ImageListerRecord record; record.imageID = (*it).toLongLong(); ++it; record.name = (*it).toString(); ++it; record.albumID = (*it).toInt(); ++it; record.rating = (*it).toInt(); ++it; record.category = (DatabaseItem::Category)(*it).toInt(); ++it; record.format = (*it).toString(); ++it; record.creationDate = (*it).isNull() ? QDateTime() : QDateTime::fromString((*it).toString(), Qt::ISODate); ++it; record.modificationDate = (*it).isNull() ? QDateTime() : QDateTime::fromString((*it).toString(), Qt::ISODate); ++it; record.fileSize = toInt32BitSafe(it); ++it; width = (*it).toInt(); ++it; height = (*it).toInt(); ++it; record.imageSize = QSize(width, height); record.albumRootID = albumRootId; receiver->receive(record); } } -void ImageLister::listTag(ImageListerReceiver* const receiver, QList tagIds) +void ImageLister::listTag(ImageListerReceiver* const receiver, const QList& tagIds) { QSet records; - QList::iterator it; + QList::const_iterator it; - for(it = tagIds.begin() ; it != tagIds.end() ; ++it) + for (it = tagIds.constBegin() ; it != tagIds.constEnd() ; ++it) { QList values; QMap parameters; parameters.insert(QLatin1String(":tagPID"), *it); parameters.insert(QLatin1String(":tagID"), *it); CoreDbAccess access; if (d->recursive) { access.backend()->execDBAction(access.backend()->getDBAction(QLatin1String("listTagRecursive")), parameters, &values); } else { access.backend()->execDBAction(access.backend()->getDBAction(QLatin1String("listTag")), parameters, &values); } QSet albumRoots = albumRootsToList(); int width, height; - for (QList::const_iterator it = values.constBegin(); it != values.constEnd();) + for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { ImageListerRecord record; record.imageID = (*it).toLongLong(); ++it; record.name = (*it).toString(); ++it; record.albumID = (*it).toInt(); ++it; record.albumRootID = (*it).toInt(); ++it; record.rating = (*it).toInt(); ++it; record.category = (DatabaseItem::Category)(*it).toInt(); ++it; record.format = (*it).toString(); ++it; record.creationDate = (*it).isNull() ? QDateTime() : QDateTime::fromString((*it).toString(), Qt::ISODate); ++it; record.modificationDate = (*it).isNull() ? QDateTime() : QDateTime::fromString((*it).toString(), Qt::ISODate); ++it; record.fileSize = toInt32BitSafe(it); ++it; width = (*it).toInt(); ++it; height = (*it).toInt(); ++it; if (d->listOnlyAvailableImages && !albumRoots.contains(record.albumRootID)) { continue; } record.imageSize = QSize(width, height); records.insert(record); } } - for(QSet::iterator it = records.begin(); it != records.end(); ++it) + for (QSet::iterator it = records.begin(); it != records.end(); ++it) { receiver->receive(*it); } } void ImageLister::listFaces(ImageListerReceiver* const receiver, int personId) { QList list; QList values; CoreDbAccess access; access.backend()->execSql(QString::fromUtf8("SELECT Images.id " " FROM Images " " LEFT JOIN ImageInformation ON Images.id=ImageInformation.imageid " " INNER JOIN Albums ON Albums.id=")+ QString::number(personId)+ QString::fromUtf8(" WHERE Images.status=1 " " ORDER BY Albums.id;"), &values); QListIterator it(values); while (it.hasNext()) { TagsCache* const cache = TagsCache::instance(); ImageTagPair pair(list.last(), cache->tagForPath(QLatin1String("/People/Unknown"))); QList nameList = pair.values(QLatin1String("face")); // push the image into the list every time a face with the name is found in the image int count = nameList.count(cache->tagName(personId)); - for (int i = 0; i < count; ++i) + for (int i = 0 ; i < count ; ++i) { list += it.next().toLongLong(); } } listFromIdList(receiver, list); } void ImageLister::listDateRange(ImageListerReceiver* const receiver, const QDate& startDate, const QDate& endDate) { QList values; { CoreDbAccess access; access.backend()->execSql(QString::fromUtf8("SELECT DISTINCT Images.id, Images.name, Images.album, " " Albums.albumRoot, " " ImageInformation.rating, Images.category, " " ImageInformation.format, ImageInformation.creationDate, " " Images.modificationDate, Images.fileSize, " " ImageInformation.width, ImageInformation.height " " FROM Images " " LEFT JOIN ImageInformation ON Images.id=ImageInformation.imageid " " INNER JOIN Albums ON Albums.id=Images.album " " WHERE Images.status=1 " " AND ImageInformation.creationDate < ? " " AND ImageInformation.creationDate >= ? " " ORDER BY Images.album;"), QDateTime(endDate).toString(Qt::ISODate), QDateTime(startDate).toString(Qt::ISODate), &values); } QSet albumRoots = albumRootsToList(); int width, height; - for (QList::const_iterator it = values.constBegin(); it != values.constEnd();) + for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { ImageListerRecord record; record.imageID = (*it).toLongLong(); ++it; record.name = (*it).toString(); ++it; record.albumID = (*it).toInt(); ++it; record.albumRootID = (*it).toInt(); ++it; record.rating = (*it).toInt(); ++it; record.category = (DatabaseItem::Category)(*it).toInt(); ++it; record.format = (*it).toString(); ++it; record.creationDate = (*it).isNull() ? QDateTime() : QDateTime::fromString((*it).toString(), Qt::ISODate); ++it; record.modificationDate = (*it).isNull() ? QDateTime() : QDateTime::fromString((*it).toString(), Qt::ISODate); ++it; record.fileSize = toInt32BitSafe(it); ++it; width = (*it).toInt(); ++it; height = (*it).toInt(); ++it; if (d->listOnlyAvailableImages && !albumRoots.contains(record.albumRootID)) { continue; } record.imageSize = QSize(width, height); receiver->receive(record); } } void ImageLister::listAreaRange(ImageListerReceiver* const receiver, double lat1, double lat2, double lon1, double lon2) { QList values; QList boundValues; boundValues << lat1 << lat2 << lon1 << lon2; qCDebug(DIGIKAM_DATABASE_LOG) << "Listing area" << lat1 << lat2 << lon1 << lon2; CoreDbAccess access; access.backend()->execSql(QString::fromUtf8("SELECT DISTINCT Images.id, " " Albums.albumRoot, ImageInformation.rating, ImageInformation.creationDate, " " ImagePositions.latitudeNumber, ImagePositions.longitudeNumber " " FROM Images " " LEFT JOIN ImageInformation ON Images.id=ImageInformation.imageid " " INNER JOIN Albums ON Albums.id=Images.album " " INNER JOIN ImagePositions ON Images.id=ImagePositions.imageid " " WHERE Images.status=1 " " AND (ImagePositions.latitudeNumber>? AND ImagePositions.latitudeNumber? AND ImagePositions.longitudeNumber albumRoots = albumRootsToList(); double lat, lon; - for (QList::const_iterator it = values.constBegin(); it != values.constEnd();) + for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { ImageListerRecord record(d->allowExtraValues ? ImageListerRecord::ExtraValueFormat : ImageListerRecord::TraditionalFormat); record.imageID = (*it).toLongLong(); ++it; record.albumRootID = (*it).toInt(); ++it; record.rating = (*it).toInt(); ++it; record.creationDate = (*it).toDateTime(); ++it; lat = (*it).toDouble(); ++it; lon = (*it).toDouble(); ++it; if (d->listOnlyAvailableImages && !albumRoots.contains(record.albumRootID)) { continue; } record.extraValues << lat << lon; receiver->receive(record); } } void ImageLister::listSearch(ImageListerReceiver* const receiver, const QString& xml, int limit, qlonglong referenceImageId) { if (xml.isEmpty()) { return; } QList boundValues; QList values; QString sqlQuery; QString errMsg; // query head sqlQuery = QString::fromUtf8( "SELECT DISTINCT Images.id, Images.name, Images.album, " " Albums.albumRoot, " " ImageInformation.rating, Images.category, " " ImageInformation.format, ImageInformation.creationDate, " " Images.modificationDate, Images.fileSize, " " ImageInformation.width, ImageInformation.height, " " ImagePositions.latitudeNumber, ImagePositions.longitudeNumber " " FROM Images " " LEFT JOIN ImageInformation ON Images.id=ImageInformation.imageid " " LEFT JOIN ImageMetadata ON Images.id=ImageMetadata.imageid " " LEFT JOIN VideoMetadata ON Images.id=VideoMetadata.imageid " " LEFT JOIN ImagePositions ON Images.id=ImagePositions.imageid " " INNER JOIN Albums ON Albums.id=Images.album " "WHERE Images.status=1 AND ( "); // query body ImageQueryBuilder builder; ImageQueryPostHooks hooks; sqlQuery += builder.buildQuery(xml, &boundValues, &hooks); if (limit > 0) { sqlQuery += QString::fromUtf8(" ) LIMIT %1; ").arg(limit); } else { sqlQuery += QString::fromUtf8(" );"); } qCDebug(DIGIKAM_DATABASE_LOG) << "Search query:\n" << sqlQuery << "\n" << boundValues; bool executionSuccess; { CoreDbAccess access; executionSuccess = access.backend()->execSql(sqlQuery, boundValues, &values); if (!executionSuccess) { errMsg = access.backend()->lastError(); } } if (!executionSuccess) { receiver->error(errMsg); return; } qCDebug(DIGIKAM_DATABASE_LOG) << "Search result:" << values.size(); QSet albumRoots = albumRootsToList(); int width, height; double lat,lon; CoreDbAccess access; - for (QList::const_iterator it = values.constBegin(); it != values.constEnd();) + for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { ImageListerRecord record; record.imageID = (*it).toLongLong(); ++it; record.name = (*it).toString(); ++it; record.albumID = (*it).toInt(); ++it; record.albumRootID = (*it).toInt(); ++it; record.rating = (*it).toInt(); ++it; record.category = (DatabaseItem::Category)(*it).toInt(); ++it; record.format = (*it).toString(); ++it; record.creationDate = (*it).isNull() ? QDateTime() : QDateTime::fromString((*it).toString(), Qt::ISODate); ++it; record.modificationDate = (*it).isNull() ? QDateTime() : QDateTime::fromString((*it).toString(), Qt::ISODate); ++it; record.fileSize = toInt32BitSafe(it); ++it; width = (*it).toInt(); ++it; height = (*it).toInt(); ++it; lat = (*it).toDouble(); ++it; lon = (*it).toDouble(); ++it; record.currentSimilarity = SimilarityDbAccess().db()->getImageSimilarity(record.imageID, referenceImageId); if (record.currentSimilarity < 0) { // Ignore nonexistence and invalid db entry. record.currentSimilarity = 0.0; } record.currentFuzzySearchReferenceImage = referenceImageId; if (d->listOnlyAvailableImages && !albumRoots.contains(record.albumRootID)) { continue; } if (!hooks.checkPosition(lat, lon)) { continue; } record.imageSize = QSize(width, height); receiver->receive(record); } } void ImageLister::listImageTagPropertySearch(ImageListerReceiver* const receiver, const QString& xml) { if (xml.isEmpty()) { return; } QList boundValues; QList values; QString sqlQuery; QString errMsg; // Currently, for optimization, this does not allow a general-purpose search, // ImageMetadata and ImagePositions are not joined and hooks are ignored. // query head sqlQuery = QString::fromUtf8( "SELECT DISTINCT Images.id, Images.name, Images.album, " " Albums.albumRoot, " " ImageInformation.rating, Images.category, " " ImageInformation.format, ImageInformation.creationDate, " " Images.modificationDate, Images.fileSize, " " ImageInformation.width, ImageInformation.height, " " ImageTagProperties.value, ImageTagProperties.property, ImageTagProperties.tagid " " FROM Images " " INNER JOIN ImageTagProperties ON ImageTagProperties.imageid=Images.id " " LEFT JOIN ImageInformation ON Images.id=ImageInformation.imageid " " INNER JOIN Albums ON Albums.id=Images.album " "WHERE Images.status=1 AND ( "); // query body ImageQueryBuilder builder; ImageQueryPostHooks hooks; builder.setImageTagPropertiesJoined(true); // ImageTagProperties added by INNER JOIN sqlQuery += builder.buildQuery(xml, &boundValues, &hooks); sqlQuery += QString::fromUtf8(" );"); qCDebug(DIGIKAM_DATABASE_LOG) << "Search query:\n" << sqlQuery << "\n" << boundValues; bool executionSuccess; { CoreDbAccess access; executionSuccess = access.backend()->execSql(sqlQuery, boundValues, &values); if (!executionSuccess) { errMsg = access.backend()->lastError(); } } if (!executionSuccess) { receiver->error(errMsg); return; } qCDebug(DIGIKAM_DATABASE_LOG) << "Search result:" << values.size(); QSet albumRoots = albumRootsToList(); int width, height; - for (QList::const_iterator it = values.constBegin(); it != values.constEnd();) + for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { ImageListerRecord record(d->allowExtraValues ? ImageListerRecord::ExtraValueFormat : ImageListerRecord::TraditionalFormat); record.imageID = (*it).toLongLong(); ++it; record.name = (*it).toString(); ++it; record.albumID = (*it).toInt(); ++it; record.albumRootID = (*it).toInt(); ++it; record.rating = (*it).toInt(); ++it; record.category = (DatabaseItem::Category)(*it).toInt(); ++it; record.format = (*it).toString(); ++it; record.creationDate = (*it).isNull() ? QDateTime() : QDateTime::fromString((*it).toString(), Qt::ISODate); ++it; record.modificationDate = (*it).isNull() ? QDateTime() : QDateTime::fromString((*it).toString(), Qt::ISODate); ++it; record.fileSize = toInt32BitSafe(it); ++it; width = (*it).toInt(); ++it; height = (*it).toInt(); ++it; // sync the following order with the places where it's read, e.g., FaceTagsIface QVariant value = (*it); ++it; QVariant property = (*it); ++it; QVariant tagId = (*it); ++it; // If the property is the autodetected person, get the original image tag properties if (property.toString().compare(ImageTagPropertyName::autodetectedPerson()) == 0) { // If we split the value by ',' we must have the segments tagId, property, region // Set the values. QStringList values = value.toString().split(QLatin1Char(',')); if (values.size() == 3) { value = values.at(2); property = values.at(1); tagId = values.at(0); } } record.extraValues << value; record.extraValues << property; record.extraValues << tagId; if (d->listOnlyAvailableImages && !albumRoots.contains(record.albumRootID)) { continue; } record.imageSize = QSize(width, height); receiver->receive(record); } } void ImageLister::listHaarSearch(ImageListerReceiver* const receiver, const QString& xml) { //qCDebug(DIGIKAM_GENERAL_LOG) << "Query: " << xml; // ------------------------------------------------ // read basic info SearchXmlReader reader(xml); reader.readToFirstField(); if (reader.fieldName() != QLatin1String("similarity")) { receiver->error(QLatin1String("Unsupported field name \"") + reader.fieldName() + QLatin1String("\" in Haar search")); return; } QStringRef type = reader.attributes().value(QLatin1String("type")); QStringRef numResultsString = reader.attributes().value(QLatin1String("numberofresults")); QStringRef thresholdString = reader.attributes().value(QLatin1String("threshold")); QStringRef maxThresholdString = reader.attributes().value(QLatin1String("maxthreshold")); QStringRef sketchTypeString = reader.attributes().value(QLatin1String("sketchtype")); double threshold = 0.9; double maxThreshold = 1.0; int numberOfResults = 20; HaarIface::SketchType sketchType = HaarIface::ScannedSketch; QList targetAlbums; // ------------------------------------------------ // read target albums SearchXmlReader albumsReader(xml); SearchXml::Element element; while ((element = albumsReader.readNext()) != SearchXml::End) { // Get the target albums, i.e. the albums in which the similar images must be located. if ((element == SearchXml::Field) && (albumsReader.fieldName().compare(QLatin1String("noeffect_targetAlbums")) == 0)) { targetAlbums = albumsReader.valueToIntList(); //qCDebug(DIGIKAM_GENERAL_LOG) << "Searching in " << targetAlbums.size() << " Albums"; break; } } // ------------------------------------------------- if (!numResultsString.isNull()) { numberOfResults = numResultsString.toString().toInt(); //qCDebug(DIGIKAM_GENERAL_LOG) << "Returning " << numberOfResults << " results"; } if (!thresholdString.isNull()) { threshold = qMax(thresholdString.toString().toDouble(), 0.1); //qCDebug(DIGIKAM_GENERAL_LOG) << "Minimum threshold: " << threshold; } if (!maxThresholdString.isNull()) { maxThreshold = qMax(maxThresholdString.toString().toDouble(), threshold); //qCDebug(DIGIKAM_GENERAL_LOG) << "Maximum threshold: " << maxThreshold; } if (!sketchTypeString.isNull() && sketchTypeString == QLatin1String("handdrawn")) { sketchType = HaarIface::HanddrawnSketch; } QMap imageSimilarityMap; if (type == QLatin1String("signature")) { QString sig = reader.value(); HaarIface iface; if (d->listOnlyAvailableImages) { iface.setAlbumRootsToSearch(albumRootsToList()); } imageSimilarityMap = iface.bestMatchesForSignature(sig, targetAlbums, numberOfResults, sketchType); } else if (type == QLatin1String("imageid")) { qlonglong id = reader.valueToLongLong(); HaarIface iface; if (d->listOnlyAvailableImages) { iface.setAlbumRootsToSearch(albumRootsToList()); } imageSimilarityMap = iface.bestMatchesForImageWithThreshold(id, threshold, maxThreshold, targetAlbums, HaarIface::DuplicatesSearchRestrictions::None, sketchType).second; } else if (type == QLatin1String("image")) { // If the given SAlbum contains a dropped image, get all images which are similar to this one. QString path = reader.value(); HaarIface iface; if (d->listOnlyAvailableImages) { iface.setAlbumRootsToSearch(albumRootsToList()); } imageSimilarityMap = iface.bestMatchesForImageWithThreshold(path, threshold,maxThreshold, targetAlbums, HaarIface::DuplicatesSearchRestrictions::None, sketchType).second; } listFromHaarSearch(receiver, imageSimilarityMap); } void ImageLister::listFromHaarSearch(ImageListerReceiver* const receiver, const QMap& imageSimilarityMap) { QList values; QString errMsg; bool executionSuccess = true; { // Generate the query that returns the similarity as constant for a given image id. CoreDbAccess access; DbEngineSqlQuery query = access.backend()->prepareQuery(QString::fromUtf8( "SELECT DISTINCT Images.id, Images.name, Images.album, " " Albums.albumRoot, " " ImageInformation.rating, Images.category, " " ImageInformation.format, ImageInformation.creationDate, " " Images.modificationDate, Images.fileSize, " " ImageInformation.width, ImageInformation.height " " FROM Images " " LEFT JOIN ImageInformation ON Images.id=ImageInformation.imageid " " LEFT JOIN Albums ON Albums.id=Images.album " " WHERE Images.status=1 AND Images.id = ?;")); qlonglong imageId; double similarity; // Iterate over the image similarity map and bind the image id and similarity to the query. - for (QMap::const_iterator it = imageSimilarityMap.constBegin(); it != imageSimilarityMap.constEnd(); ++it) + for (QMap::const_iterator it = imageSimilarityMap.constBegin() ; it != imageSimilarityMap.constEnd() ; ++it) { similarity = it.value(); imageId = it.key(); query.bindValue(0, imageId); executionSuccess = access.backend()->exec(query); if (!executionSuccess) { errMsg = access.backend()->lastError(); break; } // Add the similarity to the table row. QList tableRow = access.backend()->readToList(query); tableRow.append(similarity); // append results to list values << tableRow; } } if (!executionSuccess) { receiver->error(errMsg); return; } int width, height; - for (QList::const_iterator it = values.constBegin(); it != values.constEnd();) + for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { ImageListerRecord record; record.imageID = (*it).toLongLong(); ++it; record.name = (*it).toString(); ++it; record.albumID = (*it).toInt(); ++it; record.albumRootID = (*it).toInt(); ++it; record.rating = (*it).toInt(); ++it; record.category = (DatabaseItem::Category)(*it).toInt(); ++it; record.format = (*it).toString(); ++it; record.creationDate = (*it).isNull() ? QDateTime() : QDateTime::fromString((*it).toString(), Qt::ISODate); ++it; record.modificationDate = (*it).isNull() ? QDateTime() : QDateTime::fromString((*it).toString(), Qt::ISODate); ++it; record.fileSize = toInt32BitSafe(it); ++it; width = (*it).toInt(); ++it; height = (*it).toInt(); ++it; record.imageSize = QSize(width, height); record.currentSimilarity = (*it).toDouble(); ++it; receiver->receive(record); } } void ImageLister::listFromIdList(ImageListerReceiver* const receiver, const QList& imageIds) { QList values; QString errMsg; bool executionSuccess = true; { /* // Unfortunately, we need to convert to QVariant QList variantIdList; foreach(const qlonglong& id, imageIds) { variantIdList << id; } CoreDbAccess access; QSqlQuery query = access.backend()->prepareQuery(QString::fromUtf8( "SELECT DISTINCT Images.id, Images.name, Images.album, " " ImageInformation.rating, ImageInformation.creationDate, " " Images.modificationDate, Images.fileSize, " " ImageInformation.width, ImageInformation.height " " FROM Images " " LEFT JOIN ImageInformation ON Images.id=ImageInformation.imageid " " WHERE Images.id = ?;")); query.addBindValue(variantIdList); executionSuccess = query.execBatch */ CoreDbAccess access; DbEngineSqlQuery query = access.backend()->prepareQuery(QString::fromUtf8( "SELECT DISTINCT Images.id, Images.name, Images.album, " " Albums.albumRoot, " " ImageInformation.rating, Images.category, " " ImageInformation.format, ImageInformation.creationDate, " " Images.modificationDate, Images.fileSize, " " ImageInformation.width, ImageInformation.height " " FROM Images " " LEFT JOIN ImageInformation ON Images.id=ImageInformation.imageid " " LEFT JOIN Albums ON Albums.id=Images.album " " WHERE Images.status=1 AND Images.id = ?;")); foreach(const qlonglong& id, imageIds) { query.bindValue(0, id); executionSuccess = access.backend()->exec(query); if (!executionSuccess) { errMsg = access.backend()->lastError(); break; } // append results to list values << access.backend()->readToList(query); } } if (!executionSuccess) { receiver->error(errMsg); return; } int width, height; - for (QList::const_iterator it = values.constBegin(); it != values.constEnd();) + for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { ImageListerRecord record; record.imageID = (*it).toLongLong(); ++it; record.name = (*it).toString(); ++it; record.albumID = (*it).toInt(); ++it; record.albumRootID = (*it).toInt(); ++it; record.rating = (*it).toInt(); ++it; record.category = (DatabaseItem::Category)(*it).toInt(); ++it; record.format = (*it).toString(); ++it; record.creationDate = (*it).isNull() ? QDateTime() : QDateTime::fromString((*it).toString(), Qt::ISODate); ++it; record.modificationDate = (*it).isNull() ? QDateTime() : QDateTime::fromString((*it).toString(), Qt::ISODate); ++it; record.fileSize = toInt32BitSafe(it); ++it; width = (*it).toInt(); ++it; height = (*it).toInt(); ++it; record.imageSize = QSize(width, height); receiver->receive(record); } } QSet ImageLister::albumRootsToList() const { if (!d->listOnlyAvailableImages) { return QSet(); // invalid value, all album roots shall be listed } QList locations = CollectionManager::instance()->allAvailableLocations(); QSet ids; foreach(const CollectionLocation& location, locations) { ids << location.id(); } return ids; } QString ImageLister::tagSearchXml(int tagId, const QString& type, bool includeChildTags) const { if (type == QLatin1String("faces")) { SearchXmlWriter writer; writer.writeGroup(); writer.setDefaultFieldOperator(SearchXml::Or); QStringList properties; properties << ImageTagPropertyName::autodetectedPerson(); properties << ImageTagPropertyName::autodetectedFace(); properties << ImageTagPropertyName::tagRegion(); foreach(const QString& property, properties) { writer.writeField(QLatin1String("imagetagproperty"), includeChildTags ? SearchXml::InTree : SearchXml::Equal); if (tagId != -1) { writer.writeAttribute(QLatin1String("tagid"), QString::number(tagId)); } writer.writeValue(property); writer.finishField(); } /* if (flags & TagAssigned && tagId) { writer.writeField("tagid", SearchXml::Equal); writer.writeValue(tagId); writer.finishField(); } */ writer.finishGroup(); return writer.xml(); } else { return QString(); } } } // namespace Digikam diff --git a/core/libs/database/item/imagelister.h b/core/libs/database/item/imagelister.h index 1020877b84..7a6e2adb41 100644 --- a/core/libs/database/item/imagelister.h +++ b/core/libs/database/item/imagelister.h @@ -1,153 +1,153 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2007-03-20 * Description : Listing information from database. * * Copyright (C) 2005 by Renchi Raju * Copyright (C) 2007-2012 by Marcel Wiesweg * Copyright (C) 2007-2018 by Gilles Caulier * Copyright (C) 2015 by Mohamed_Anwer * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_IMAGE_LISTER_H #define DIGIKAM_IMAGE_LISTER_H // Qt includes #include // Local includes #include "digikam_export.h" #include "imageinfo.h" #include "imageinfolist.h" #include "coredbaccess.h" #include "coredburl.h" #include "imagelisterrecord.h" #include "imagelisterreceiver.h" #include "dbjobinfo.h" namespace Digikam { class DIGIKAM_DATABASE_EXPORT ImageLister { public: explicit ImageLister(); ~ImageLister(); /** * Adjust the setting if album or tags will be listed recursively (i.e. including subalbums / subtags) */ void setRecursive(bool recursive); /** * Adjust the setting if images from collections that are currently not in the state "available" * will be included in the listing. Default: true. */ void setListOnlyAvailable(bool listOnlyAvailable); /** * Allow the binary protocol of ImageListerRecord using an extra value. * Currently used for AreaRand and TagPropertySearch. */ void setAllowExtraValues(bool useExtraValue); /** * Convenience method for Album, Tag and Date URLs, _not_ for Search URLs. */ void list(ImageListerReceiver* const receiver, const CoreDbUrl& url); /** * List images in the Album (physical album) specified by albumRoot, album. * The results will be fed to the specified receiver. */ void listAlbum(ImageListerReceiver* const receiver, int albumRootId, const QString& album); /** * List the images which have assigned the tags specified by tagIds * Updated to support multiple tags */ - void listTag(ImageListerReceiver* receiver, QList tagIds); + void listTag(ImageListerReceiver* const receiver, const QList& tagIds); /** * List the images which have faces. An image with n faces will be listed n times. * FIXME: Obviously an ugly way. Should be trashed later in favor of a better method. */ void listFaces(ImageListerReceiver* const receiver, int personId); /** * List those images whose date lies in the range beginning with startDate (inclusive) * and ending before endDate (exclusive). */ void listDateRange(ImageListerReceiver* const receiver, const QDate& startDate, const QDate& endDate); /** * List the images whose coordinates are between coordinates contained in areaCoordinates(lat1, lat2, lng1, lng2) */ void listAreaRange(ImageListerReceiver* const receiver, double lat1, double lat2, double lon1, double lon2); /** * Execute the search specified by search XML * @param receiver receiver for the searches * @param xml SearchXml describing the query * @param limit limit the count of the result set. If limit = 0, then no limit is set. * @param referenceImageId the id of a reference image in the search query. */ void listSearch(ImageListerReceiver* const receiver, const QString& xml, int limit = 0, qlonglong referenceImageId = -1); /** * Execute the search specified by search XML describing a Tag Properties search. * Two special add-ons: Non-unique by image id; if enabled, uses the extended ImageRecord protocol * to pass the property value in the record's extraValue. * @param receiver receiver for the searches * @param xml SearchXml describing the query */ void listImageTagPropertySearch(ImageListerReceiver* const receiver, const QString& xml); /** * Execute the search specified by search XML describing a Haar search * @param receiver receiver for the searches * @param xml SearchXml describing the query */ void listHaarSearch(ImageListerReceiver* const receiver, const QString& xml); QString tagSearchXml(int tagId, const QString& type, bool includeChildTags) const; private: /** * This method generates image records for the receiver that contain the similarities. * @param receiver for the searches * @param imageSimilarityMap the map of image ids and their similarities in the HAAR search */ - void listFromHaarSearch(ImageListerReceiver* const receiver, const QMap& imageSimilarityMap); + void listFromHaarSearch(ImageListerReceiver* const receiver, const QMap& imageSimilarityMap); void listFromIdList(ImageListerReceiver* const receiver, const QList& imageIds); QSet albumRootsToList() const; private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_IMAGE_LISTER_H diff --git a/core/libs/database/item/imagelisterreceiver.cpp b/core/libs/database/item/imagelisterreceiver.cpp index 9841e8deb4..58cb8ce43a 100644 --- a/core/libs/database/item/imagelisterreceiver.cpp +++ b/core/libs/database/item/imagelisterreceiver.cpp @@ -1,108 +1,108 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2007-03-20 * Description : Simple virtual interface for ImageLister * * Copyright (C) 2007-2011 by Marcel Wiesweg * Copyright (C) 2015 by Mohamed_Anwer * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "imagelisterreceiver.h" // Qt includes #include namespace Digikam { ImageListerValueListReceiver::ImageListerValueListReceiver() : hasError(false) { } void ImageListerValueListReceiver::error(const QString&) { hasError = true; } void ImageListerValueListReceiver::receive(const ImageListerRecord& record) { records << record; } // ---------------------------------------------- -ImageListerJobReceiver::ImageListerJobReceiver(DBJob *const job) +ImageListerJobReceiver::ImageListerJobReceiver(DBJob* const job) : m_job(job) { } void ImageListerJobReceiver::sendData() { emit m_job->data(records); records.clear(); } void ImageListerJobReceiver::error(const QString& errMsg) { m_job->error(errMsg); ImageListerValueListReceiver::error(errMsg); } // ---------------------------------------------- -ImageListerJobPartsSendingReceiver::ImageListerJobPartsSendingReceiver(DBJob *const job, int limit) +ImageListerJobPartsSendingReceiver::ImageListerJobPartsSendingReceiver(DBJob* const job, int limit) : ImageListerJobReceiver(job), m_limit(limit), m_count(0) { } void ImageListerJobPartsSendingReceiver::receive(const ImageListerRecord &record) { ImageListerJobReceiver::receive(record); if (++m_count > m_limit) { sendData(); m_count = 0; } } // ---------------------------------------------- ImageListerJobGrowingPartsSendingReceiver:: - ImageListerJobGrowingPartsSendingReceiver(DBJob* job, int start, int end, int increment) + ImageListerJobGrowingPartsSendingReceiver(DBJob* const job, int start, int end, int increment) : ImageListerJobPartsSendingReceiver(job, start), m_maxLimit(end), m_increment(increment) { } void ImageListerJobGrowingPartsSendingReceiver::receive(const ImageListerRecord& record) { ImageListerJobPartsSendingReceiver::receive(record); // limit was reached? if (m_count == 0) { m_limit = qMin(m_limit + m_increment, m_maxLimit); } } } // namespace Digikam diff --git a/core/libs/database/item/imagelisterreceiver.h b/core/libs/database/item/imagelisterreceiver.h index 5c95884c24..fa377c8d03 100644 --- a/core/libs/database/item/imagelisterreceiver.h +++ b/core/libs/database/item/imagelisterreceiver.h @@ -1,122 +1,122 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2007-03-20 * Description : Simple virtual interface for ImageLister * * Copyright (C) 2005 by Renchi Raju * Copyright (C) 2007-2011 by Marcel Wiesweg * Copyright (C) 2015 by Mohamed_Anwer * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_IMAGE_LISTER_RECEIVER_H #define DIGIKAM_IMAGE_LISTER_RECEIVER_H // Qt includes #include #include // Local includes #include "digikam_export.h" #include "imagelisterrecord.h" #include "dbjob.h" namespace Digikam { //TODO: Docs class DIGIKAM_DATABASE_EXPORT ImageListerReceiver { public: virtual ~ImageListerReceiver() {}; virtual void receive(const ImageListerRecord& record) = 0; virtual void error(const QString& /*errMsg*/) {}; }; // ------------------------------------------------------------------------------------------------ class DIGIKAM_DATABASE_EXPORT ImageListerValueListReceiver : public ImageListerReceiver { public: explicit ImageListerValueListReceiver(); QList records; bool hasError; virtual void receive(const ImageListerRecord& record); virtual void error(const QString& errMsg); }; // ------------------------------------------------------------------------------------------------ class DIGIKAM_DATABASE_EXPORT ImageListerJobReceiver : public ImageListerValueListReceiver { public: - explicit ImageListerJobReceiver(DBJob *const job); + explicit ImageListerJobReceiver(DBJob* const job); virtual void error(const QString& errMsg); void sendData(); protected: DBJob *const m_job; }; // ------------------------------------------------------------------------------------------------ class DIGIKAM_DATABASE_EXPORT ImageListerJobPartsSendingReceiver : public ImageListerJobReceiver { public: - explicit ImageListerJobPartsSendingReceiver(DBJob *const job, int limit); + explicit ImageListerJobPartsSendingReceiver(DBJob* const job, int limit); virtual void receive(const ImageListerRecord &record); protected: int m_limit; int m_count; }; // ------------------------------------------------------------------------------------------------ class DIGIKAM_DATABASE_EXPORT ImageListerJobGrowingPartsSendingReceiver : public ImageListerJobPartsSendingReceiver { public: - explicit ImageListerJobGrowingPartsSendingReceiver(DBJob* job, int start, int end, int increment); + explicit ImageListerJobGrowingPartsSendingReceiver(DBJob* const job, int start, int end, int increment); virtual void receive(const ImageListerRecord& record); protected: int m_maxLimit; int m_increment; }; } // namespace Digikam #endif // DIGIKAM_IMAGE_LISTER_RECEIVER_H