File indexing completed on 2025-01-19 03:53:32

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2015-06-01
0007  * Description : DB Jobs thread for listing and scanning
0008  *
0009  * SPDX-FileCopyrightText: 2015 by Mohamed_Anwer <m_dot_anwer at gmx dot com>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 #include "dbjobsthread.h"
0016 
0017 // Local includes
0018 
0019 #include "coredbaccess.h"
0020 #include "duplicatesprogressobserver.h"
0021 #include "digikam_debug.h"
0022 
0023 namespace Digikam
0024 {
0025 
0026 DBJobsThread::DBJobsThread(QObject* const parent)
0027     : ActionThreadBase(parent)
0028 {
0029     setObjectName(QLatin1String("DBJobsThread"));
0030 }
0031 
0032 DBJobsThread::~DBJobsThread()
0033 {
0034 }
0035 
0036 bool DBJobsThread::hasErrors()
0037 {
0038     return !m_errorsList.isEmpty();
0039 }
0040 
0041 QList<QString>& DBJobsThread::errorsList()
0042 {
0043     return m_errorsList;
0044 }
0045 
0046 void DBJobsThread::connectFinishAndErrorSignals(DBJob* const j)
0047 {
0048     connect(j, SIGNAL(signalDone()),
0049             this, SIGNAL(finished()));
0050 
0051     connect(j, SIGNAL(error(QString)),
0052             this, SLOT(error(QString)));
0053 }
0054 
0055 void DBJobsThread::error(const QString& errString)
0056 {
0057     m_errorsList.append(errString);
0058 }
0059 
0060 // -------------------------------------------------
0061 
0062 AlbumsDBJobsThread::AlbumsDBJobsThread(QObject* const parent)
0063     : DBJobsThread(parent)
0064 {
0065 }
0066 
0067 AlbumsDBJobsThread::~AlbumsDBJobsThread()
0068 {
0069 }
0070 
0071 void AlbumsDBJobsThread::albumsListing(const AlbumsDBJobInfo& info)
0072 {
0073     AlbumsJob* const j = new AlbumsJob(info);
0074 
0075     connectFinishAndErrorSignals(j);
0076 
0077     if (info.isFoldersJob())
0078     {
0079         connect(j, SIGNAL(foldersData(QHash<int,int>)),
0080                 this, SIGNAL(foldersData(QHash<int,int>)));
0081     }
0082     else
0083     {
0084         connect(j, SIGNAL(data(QList<ItemListerRecord>)),
0085                 this, SIGNAL(data(QList<ItemListerRecord>)));
0086     }
0087 
0088     ActionJobCollection collection;
0089     collection.insert(j, 0);
0090 
0091     appendJobs(collection);
0092 }
0093 
0094 // -------------------------------------------------
0095 
0096 TagsDBJobsThread::TagsDBJobsThread(QObject* const parent)
0097     : DBJobsThread(parent)
0098 {
0099 }
0100 
0101 TagsDBJobsThread::~TagsDBJobsThread()
0102 {
0103 }
0104 
0105 void TagsDBJobsThread::tagsListing(const TagsDBJobInfo& info)
0106 {
0107     TagsJob* const j = new TagsJob(info);
0108 
0109     connectFinishAndErrorSignals(j);
0110 
0111     if (info.isFoldersJob())
0112     {
0113         connect(j, SIGNAL(foldersData(QHash<int,int>)),
0114                 this, SIGNAL(foldersData(QHash<int,int>)));
0115     }
0116     else if (info.isFaceFoldersJob())
0117     {
0118         connect(j, SIGNAL(faceFoldersData(QMap<QString,QHash<int,int> >)),       // krazy:exclude=normalize
0119                 this, SIGNAL(faceFoldersData(QMap<QString,QHash<int,int> >)));   // krazy:exclude=normalize
0120     }
0121     else
0122     {
0123         connect(j, SIGNAL(data(QList<ItemListerRecord>)),
0124                 this, SIGNAL(data(QList<ItemListerRecord>)));
0125     }
0126 
0127     ActionJobCollection collection;
0128     collection.insert(j, 0);
0129 
0130     appendJobs(collection);
0131 }
0132 
0133 // -------------------------------------------------
0134 
0135 DatesDBJobsThread::DatesDBJobsThread(QObject* const parent)
0136     : DBJobsThread(parent)
0137 {
0138 }
0139 
0140 DatesDBJobsThread::~DatesDBJobsThread()
0141 {
0142 }
0143 
0144 void DatesDBJobsThread::datesListing(const DatesDBJobInfo& info)
0145 {
0146     DatesJob* const j = new DatesJob(info);
0147 
0148     connectFinishAndErrorSignals(j);
0149 
0150     if (info.isFoldersJob())
0151     {
0152         connect(j, SIGNAL(foldersData(QHash<QDateTime,int>)),
0153                 this, SIGNAL(foldersData(QHash<QDateTime,int>)));
0154     }
0155     else
0156     {
0157         connect(j, SIGNAL(data(QList<ItemListerRecord>)),
0158                 this, SIGNAL(data(QList<ItemListerRecord>)));
0159     }
0160 
0161     ActionJobCollection collection;
0162     collection.insert(j, 0);
0163 
0164     appendJobs(collection);
0165 }
0166 
0167 // -------------------------------------------------
0168 
0169 GPSDBJobsThread::GPSDBJobsThread(QObject* const parent)
0170     : DBJobsThread(parent)
0171 {
0172 }
0173 
0174 GPSDBJobsThread::~GPSDBJobsThread()
0175 {
0176 }
0177 
0178 void GPSDBJobsThread::GPSListing(const GPSDBJobInfo& info)
0179 {
0180     GPSJob* const j = new GPSJob(info);
0181 
0182     connectFinishAndErrorSignals(j);
0183 
0184     if (info.isDirectQuery())
0185     {
0186         connect(j, SIGNAL(directQueryData(QList<QVariant>)),
0187                 this, SIGNAL(directQueryData(QList<QVariant>)));
0188     }
0189     else
0190     {
0191         connect(j, SIGNAL(data(QList<ItemListerRecord>)),
0192                 this, SIGNAL(data(QList<ItemListerRecord>)));
0193     }
0194 
0195     ActionJobCollection collection;
0196     collection.insert(j, 0);
0197 
0198     appendJobs(collection);
0199 }
0200 
0201 // -------------------------------------------------
0202 
0203 SearchesDBJobsThread::SearchesDBJobsThread(QObject* const parent)
0204     : DBJobsThread(parent),
0205       m_isAlbumUpdate(false),
0206       m_processedImages(0),
0207       m_totalImages2Scan(0)
0208 {
0209 }
0210 
0211 SearchesDBJobsThread::~SearchesDBJobsThread()
0212 {
0213 }
0214 
0215 void SearchesDBJobsThread::searchesListing(const SearchesDBJobInfo& info)
0216 {
0217     ActionJobCollection collection;
0218 
0219     if (info.isDuplicatesJob())
0220     {
0221         m_results.clear();
0222         m_haarIface.reset(new HaarIface(info.imageIds()));
0223         m_isAlbumUpdate    = info.isAlbumUpdate();
0224         m_processedImages  = 0;
0225         m_totalImages2Scan = info.imageIds().count();
0226 
0227         const int threadsCount         = (m_totalImages2Scan < 200) ? 1 : qMax(1, maximumNumberOfThreads());
0228         const int images2ScanPerThread = m_totalImages2Scan / threadsCount;
0229 
0230         QSet<qlonglong>::const_iterator begin = info.imageIds().constBegin();
0231         QSet<qlonglong>::const_iterator end   = info.imageIds().constBegin();
0232 
0233         // Split job on multiple threads
0234         for (int i = 0; i < threadsCount; ++i)
0235         {
0236             // The last thread should read until the end of the list.
0237             if (i == threadsCount - 1)
0238             {
0239                 end = info.imageIds().constEnd();
0240             }
0241             else
0242             {
0243                 // TODO: port to std::advance https://en.cppreference.com/w/cpp/iterator/advance
0244                 for (int j = 0; end != info.imageIds().constEnd() && j < images2ScanPerThread; ++j, ++end);
0245             }
0246 
0247             SearchesJob* const job = new SearchesJob(info, begin, end, m_haarIface.data());
0248 
0249             begin = end;
0250 
0251             connect(job, &SearchesJob::signalDuplicatesResults,
0252                     this, &SearchesDBJobsThread::slotDuplicatesResults);
0253 
0254             connect(job, &SearchesJob::signalImageProcessed,
0255                     this, &SearchesDBJobsThread::slotImageProcessed);
0256 
0257             collection.insert(job, 0);
0258         }
0259     }
0260     else
0261     {
0262         SearchesJob* const job = new SearchesJob(info);
0263         connectFinishAndErrorSignals(job);
0264 
0265         connect(job, SIGNAL(data(QList<ItemListerRecord>)),
0266                 this, SIGNAL(data(QList<ItemListerRecord>)));
0267 
0268         collection.insert(job, 0);
0269     }
0270 
0271     appendJobs(collection);
0272 }
0273 
0274 void SearchesDBJobsThread::slotImageProcessed()
0275 {
0276     Q_EMIT signalProgress((++m_processedImages * 100) / m_totalImages2Scan);
0277 }
0278 
0279 void SearchesDBJobsThread::slotDuplicatesResults(const HaarIface::DuplicatesResultsMap& incoming)
0280 {
0281     auto searchResults = [&](qlonglong imageId) -> HaarIface::DuplicatesResultsMap::iterator
0282     {
0283         for (auto it = m_results.begin() ; it != m_results.end() ; ++it)
0284         {
0285             if ((imageId == it.key()) || (it->second.contains(imageId)))
0286             {
0287                 return it;
0288             }
0289         }
0290 
0291         return m_results.end();
0292     };
0293 
0294     for (const auto& referenceImage : incoming.keys())
0295     {
0296         if (searchResults(referenceImage) == m_results.end())
0297         {
0298             m_results.insert(referenceImage, incoming.value(referenceImage));
0299         }
0300     }
0301 
0302     if (m_processedImages != m_totalImages2Scan)
0303     {
0304         return;
0305     }
0306 
0307     HaarIface::rebuildDuplicatesAlbums(m_results, m_isAlbumUpdate);
0308 
0309     Q_EMIT finished();
0310 }
0311 
0312 } // namespace Digikam
0313 
0314 #include "moc_dbjobsthread.cpp"