File indexing completed on 2025-01-05 03:59:34

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2008 Henry de Valence <hdevalence@gmail.com>
0004 // SPDX-FileCopyrightText: 2010 Dennis Nienhüser <nienhueser@kde.org>
0005 // SPDX-FileCopyrightText: 2010-2013 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
0006 // SPDX-FileCopyrightText: 2011 Thibaut Gridel <tgridel@free.fr>
0007 
0008 #include "SearchRunnerManager.h"
0009 
0010 #include <QString>
0011 #include <QThreadPool>
0012 #include <QTimer>
0013 #include <QMutex>
0014 
0015 #include "MarblePlacemarkModel.h"
0016 #include "MarbleModel.h"
0017 #include "Planet.h"
0018 #include "GeoDataPlacemark.h"
0019 #include "PluginManager.h"
0020 #include "ParseRunnerPlugin.h"
0021 #include "ReverseGeocodingRunnerPlugin.h"
0022 #include "SearchRunnerPlugin.h"
0023 #include "RunnerTask.h"
0024 
0025 #include "digikam_debug.h"
0026 
0027 namespace Marble
0028 {
0029 
0030 class MarbleModel;
0031 
0032 class Q_DECL_HIDDEN SearchRunnerManager::Private
0033 {
0034 public:
0035     Private( SearchRunnerManager *parent, const MarbleModel *marbleModel );
0036 
0037     template<typename T>
0038     QList<T*> plugins( const QList<T*> &plugins ) const;
0039 
0040     void addSearchResult( const QVector<GeoDataPlacemark *> &result );
0041     void cleanupSearchTask( SearchTask *task );
0042     void notifySearchResultChange();
0043     void notifySearchFinished();
0044 
0045     SearchRunnerManager *const q;
0046     const MarbleModel *const m_marbleModel;
0047     const PluginManager* m_pluginManager;
0048     QString m_lastSearchTerm;
0049     GeoDataLatLonBox m_lastPreferredBox;
0050     QMutex m_modelMutex;
0051     MarblePlacemarkModel m_model;
0052     QList<SearchTask *> m_searchTasks;
0053     QVector<GeoDataPlacemark *> m_placemarkContainer;
0054 };
0055 
0056 SearchRunnerManager::Private::Private( SearchRunnerManager *parent, const MarbleModel *marbleModel ) :
0057     q( parent ),
0058     m_marbleModel( marbleModel ),
0059     m_pluginManager( marbleModel->pluginManager() ),
0060     m_model( new MarblePlacemarkModel( parent ) )
0061 {
0062     m_model.setPlacemarkContainer( &m_placemarkContainer );
0063     qRegisterMetaType<QVector<GeoDataPlacemark *> >( "QVector<GeoDataPlacemark*>" );
0064 }
0065 
0066 template<typename T>
0067 QList<T*> SearchRunnerManager::Private::plugins( const QList<T*> &plugins ) const
0068 {
0069     QList<T*> result;
0070     for( T* plugin: plugins ) {
0071         if ( ( m_marbleModel && m_marbleModel->workOffline() && !plugin->canWorkOffline() ) ) {
0072             continue;
0073         }
0074 
0075         if ( !plugin->canWork() ) {
0076             continue;
0077         }
0078 
0079         if ( m_marbleModel && !plugin->supportsCelestialBody( m_marbleModel->planet()->id() ) )
0080         {
0081             continue;
0082         }
0083 
0084         result << plugin;
0085     }
0086 
0087     return result;
0088 }
0089 
0090 void SearchRunnerManager::Private::addSearchResult( const QVector<GeoDataPlacemark *> &result )
0091 {
0092     qCDebug(DIGIKAM_MARBLE_LOG) << "Runner reports" << result.size() << " search results";
0093     if( result.isEmpty() )
0094         return;
0095 
0096     m_modelMutex.lock();
0097     int start = m_placemarkContainer.size();
0098     int count = 0;
0099     bool distanceCompare = m_marbleModel->planet() != nullptr;
0100     for( int i=0; i<result.size(); ++i ) {
0101         bool same = false;
0102         for ( int j=0; j<m_placemarkContainer.size(); ++j ) {
0103             if ( distanceCompare &&
0104                  (result[i]->coordinate().sphericalDistanceTo(m_placemarkContainer[j]->coordinate())
0105                    * m_marbleModel->planet()->radius() < 1 ) ) {
0106                 same = true;
0107             }
0108         }
0109         if ( !same ) {
0110             m_placemarkContainer.append( result[i] );
0111             ++count;
0112         }
0113     }
0114     m_model.addPlacemarks( start, count );
0115     m_modelMutex.unlock();
0116     notifySearchResultChange();
0117 }
0118 
0119 void SearchRunnerManager::Private::cleanupSearchTask( SearchTask *task )
0120 {
0121     m_searchTasks.removeAll( task );
0122     qCDebug(DIGIKAM_MARBLE_LOG) << "removing search task" << m_searchTasks.size() << (quintptr)task;
0123     if ( m_searchTasks.isEmpty() ) {
0124         if( m_placemarkContainer.isEmpty() ) {
0125             notifySearchResultChange();
0126         }
0127         notifySearchFinished();
0128     }
0129 }
0130 
0131 void SearchRunnerManager::Private::notifySearchResultChange()
0132 {
0133     Q_EMIT q->searchResultChanged(&m_model);
0134     Q_EMIT q->searchResultChanged(m_placemarkContainer);
0135 }
0136 
0137 void SearchRunnerManager::Private::notifySearchFinished()
0138 {
0139     Q_EMIT q->searchFinished(m_lastSearchTerm);
0140     Q_EMIT q->placemarkSearchFinished();
0141 }
0142 
0143 SearchRunnerManager::SearchRunnerManager( const MarbleModel *marbleModel, QObject *parent ) :
0144     QObject( parent ),
0145     d( new Private( this, marbleModel ) )
0146 {
0147     if ( QThreadPool::globalInstance()->maxThreadCount() < 4 ) {
0148         QThreadPool::globalInstance()->setMaxThreadCount( 4 );
0149     }
0150 }
0151 
0152 SearchRunnerManager::~SearchRunnerManager()
0153 {
0154     delete d;
0155 }
0156 
0157 void SearchRunnerManager::findPlacemarks( const QString &searchTerm, const GeoDataLatLonBox &preferred )
0158 {
0159     if ( searchTerm == d->m_lastSearchTerm && preferred == d->m_lastPreferredBox ) {
0160       d->notifySearchResultChange();
0161       d->notifySearchFinished();
0162       return;
0163     }
0164 
0165     d->m_lastSearchTerm = searchTerm;
0166     d->m_lastPreferredBox = preferred;
0167 
0168     d->m_searchTasks.clear();
0169 
0170     d->m_modelMutex.lock();
0171     bool placemarkContainerChanged = false;
0172     if (!d->m_placemarkContainer.isEmpty()) {
0173         d->m_model.removePlacemarks( QString::fromUtf8("PlacemarkRunnerManager"), 0, d->m_placemarkContainer.size() );
0174         qDeleteAll( d->m_placemarkContainer );
0175         d->m_placemarkContainer.clear();
0176         placemarkContainerChanged = true;
0177     }
0178     d->m_modelMutex.unlock();
0179     if (placemarkContainerChanged) {
0180         d->notifySearchResultChange();
0181     }
0182 
0183     if ( searchTerm.trimmed().isEmpty() ) {
0184         d->notifySearchFinished();
0185         return;
0186     }
0187 
0188     QList<const SearchRunnerPlugin *> plugins = d->plugins( d->m_pluginManager->searchRunnerPlugins() );
0189     for( const SearchRunnerPlugin *plugin: plugins ) {
0190         SearchTask *task = new SearchTask( plugin->newRunner(), this, d->m_marbleModel, searchTerm, preferred );
0191         connect( task, SIGNAL(finished(SearchTask*)), this, SLOT(cleanupSearchTask(SearchTask*)) );
0192         d->m_searchTasks << task;
0193         qCDebug(DIGIKAM_MARBLE_LOG) << "search task " << plugin->nameId() << " " << (quintptr)task;
0194     }
0195 
0196     for( SearchTask *task: d->m_searchTasks ) {
0197         QThreadPool::globalInstance()->start( task );
0198     }
0199 
0200     if ( plugins.isEmpty() ) {
0201         d->cleanupSearchTask( nullptr );
0202     }
0203 }
0204 
0205 QVector<GeoDataPlacemark *> SearchRunnerManager::searchPlacemarks( const QString &searchTerm, const GeoDataLatLonBox &preferred, int timeout )
0206 {
0207     QEventLoop localEventLoop;
0208     QTimer watchdog;
0209     watchdog.setSingleShot(true);
0210     connect( &watchdog, SIGNAL(timeout()),
0211              &localEventLoop, SLOT(quit()));
0212     connect(this, SIGNAL(placemarkSearchFinished()),
0213             &localEventLoop, SLOT(quit()), Qt::QueuedConnection );
0214 
0215     watchdog.start( timeout );
0216     findPlacemarks( searchTerm, preferred );
0217     localEventLoop.exec();
0218     return d->m_placemarkContainer;
0219 }
0220 
0221 }
0222 
0223 #include "moc_SearchRunnerManager.cpp"