File indexing completed on 2024-05-12 05:09:39

0001 /***************************************************************************
0002     Copyright (C) 2009 Robby Stephenson <robby@periapsis.org>
0003  ***************************************************************************/
0004 
0005 /***************************************************************************
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or         *
0008  *   modify it under the terms of the GNU General Public License as        *
0009  *   published by the Free Software Foundation; either version 2 of        *
0010  *   the License or (at your option) version 3 or any later version        *
0011  *   accepted by the membership of KDE e.V. (or its successor approved     *
0012  *   by the membership of KDE e.V.), which shall act as a proxy            *
0013  *   defined in Section 14 of version 3 of the license.                    *
0014  *                                                                         *
0015  *   This program is distributed in the hope that it will be useful,       *
0016  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0017  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0018  *   GNU General Public License for more details.                          *
0019  *                                                                         *
0020  *   You should have received a copy of the GNU General Public License     *
0021  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0022  *                                                                         *
0023  ***************************************************************************/
0024 
0025 #include "multifetcher.h"
0026 #include "fetchmanager.h"
0027 #include "../entrycomparison.h"
0028 #include "../utils/mergeconflictresolver.h"
0029 #include "../gui/collectiontypecombo.h"
0030 #include "../tellico_debug.h"
0031 
0032 #include <KLocalizedString>
0033 #include <KConfigGroup>
0034 
0035 #include <QLabel>
0036 #include <QVBoxLayout>
0037 #include <QHBoxLayout>
0038 
0039 using namespace Tellico;
0040 using Tellico::Fetch::MultiFetcher;
0041 
0042 MultiFetcher::MultiFetcher(QObject* parent_)
0043     : Fetcher(parent_), m_collType(0), m_fetcherIndex(0), m_resultIndex(0), m_started(false) {
0044 }
0045 
0046 MultiFetcher::~MultiFetcher() {
0047 }
0048 
0049 QString MultiFetcher::source() const {
0050   return m_name.isEmpty() ? defaultName() : m_name;
0051 }
0052 
0053 bool MultiFetcher::canFetch(int type) const {
0054   return type == m_collType;
0055 }
0056 
0057 bool MultiFetcher::canSearch(Fetch::FetchKey k) const {
0058   // can fetch anything supported by the first data source
0059   // ensure we populate the child fetcher list before querying
0060   readSources();
0061   return !m_fetchers.isEmpty() && m_fetchers.front()->canSearch(k);
0062 }
0063 
0064 void MultiFetcher::readConfigHook(const KConfigGroup& config_) {
0065   m_collType = config_.readEntry("CollectionType", -1);
0066   m_uuids = config_.readEntry("Sources", QStringList());
0067 }
0068 
0069 void MultiFetcher::readSources() const {
0070   if(!m_fetchers.isEmpty()) {
0071     return;
0072   }
0073   foreach(const QString& uuid, m_uuids) {
0074     Fetcher::Ptr fetcher = Manager::self()->fetcherByUuid(uuid);
0075     if(fetcher) {
0076 //      myDebug() << "Adding fetcher:" << fetcher->source();
0077       m_fetchers.append(fetcher);
0078       // in the event we have multiple instances of same fetcher, only need to connect once
0079       if(m_fetchers.count(fetcher) == 1) {
0080         connect(fetcher.data(), &Fetcher::signalResultFound,
0081                 this, &MultiFetcher::slotResult);
0082         connect(fetcher.data(), &Fetcher::signalDone,
0083                 this, &MultiFetcher::slotDone);
0084       }
0085     }
0086   }
0087 }
0088 
0089 void MultiFetcher::search() {
0090   m_started = true;
0091   readSources();
0092   if(m_fetchers.isEmpty()) {
0093 //    myDebug() << source() << "has no sources";
0094     slotDone();
0095     return;
0096   }
0097   m_matches.clear();
0098 //  myDebug() << "Starting" << m_fetchers.front()->source();
0099   m_fetchers.front()->startSearch(request());
0100 }
0101 
0102 void MultiFetcher::stop() {
0103   if(!m_started) {
0104     return;
0105   }
0106   m_started = false;
0107   foreach(Fetcher::Ptr fetcher, m_fetchers) {
0108     fetcher->stop();
0109   }
0110   // no need to emit done, since slotDone() will get called
0111 }
0112 
0113 void MultiFetcher::slotResult(Tellico::Fetch::FetchResult* result) {
0114   Data::EntryPtr newEntry = result->fetchEntry();
0115   // if fetcher index is 0, this is the first set of results, save them all
0116   if(m_fetcherIndex == 0) {
0117 //    myDebug() << "...found new result:" << newEntry->title();
0118     m_entries.append(newEntry);
0119     return;
0120   }
0121 
0122   // otherwise, keep the entry to compare later
0123   m_matches.append(newEntry);
0124 }
0125 
0126 void MultiFetcher::slotDone() {
0127   if(m_fetcherIndex == 0) {
0128     m_fetcherIndex++;
0129     m_resultIndex = 0;
0130   } else {
0131     // iterate over all the matches from this data source and figure out which one is the best match to the existing result
0132     Data::EntryPtr entry = m_entries.at(m_resultIndex);
0133     int bestScore = -1;
0134     int bestIndex = -1;
0135     for(int idx = 0; idx < m_matches.count(); ++idx) {
0136       auto match = m_matches.at(idx);
0137       const int score = entry->collection()->sameEntry(entry, match);
0138       if(score > bestScore) {
0139         bestScore = score;
0140         bestIndex = idx;
0141       }
0142       if(score >= EntryComparison::ENTRY_PERFECT_MATCH) {
0143         // no need to compare further
0144         break;
0145       }
0146     }
0147 //    myDebug() << "best score" << bestScore  << "; index:" << bestIndex;
0148     if(bestIndex > -1 && bestScore >= EntryComparison::ENTRY_GOOD_MATCH) {
0149       auto newEntry = m_matches.at(bestIndex);
0150 //      myDebug() << "...merging from" << newEntry->title() << "into" << entry->title();
0151       Merge::mergeEntry(entry, newEntry);
0152     } else {
0153 //      myDebug() << "___no match for" << entry->title();
0154     }
0155 
0156     // now, bump to next result and continue trying to match
0157     m_resultIndex++;
0158     if(m_resultIndex >= m_entries.count()) {
0159       m_fetcherIndex++;
0160       m_resultIndex = 0;
0161     }
0162   }
0163 
0164   if(m_fetcherIndex < m_fetchers.count() && m_resultIndex < m_entries.count()) {
0165     Fetcher::Ptr fetcher = m_fetchers.at(m_fetcherIndex);
0166     Q_ASSERT(fetcher);
0167 //    myDebug() << "updating entry#" << m_resultIndex << "from" << fetcher->source();
0168     fetcher->startUpdate(m_entries.at(m_resultIndex));
0169     return;
0170   }
0171 
0172   // at this point, all the fetchers have run through all the results, so we're
0173   // done so emit all results
0174   foreach(Data::EntryPtr entry, m_entries) {
0175     FetchResult* r = new FetchResult(this, entry);
0176     m_entryHash.insert(r->uid, entry);
0177     emit signalResultFound(r);
0178   }
0179   emit signalDone(this);
0180 }
0181 
0182 Tellico::Data::EntryPtr MultiFetcher::fetchEntryHook(uint uid_) {
0183   Data::EntryPtr entry = m_entryHash[uid_];
0184   if(!entry) {
0185     myWarning() << "no entry in hash";
0186     return Data::EntryPtr();
0187   }
0188   return entry;
0189 }
0190 
0191 Tellico::Fetch::FetchRequest MultiFetcher::updateRequest(Data::EntryPtr entry_) {
0192   const QString isbn = entry_->field(QStringLiteral("isbn"));
0193   if(!isbn.isEmpty()) {
0194     return FetchRequest(ISBN, isbn);
0195   }
0196   const QString title = entry_->field(QStringLiteral("title"));
0197   if(!title.isEmpty()) {
0198     return FetchRequest(Title, title);
0199   }
0200   return FetchRequest();
0201 }
0202 
0203 Tellico::Fetch::ConfigWidget* MultiFetcher::configWidget(QWidget* parent_) const {
0204   return new MultiFetcher::ConfigWidget(parent_, this);
0205 }
0206 
0207 QString MultiFetcher::defaultName() {
0208   return i18n("Multiple Sources");
0209 }
0210 
0211 QString MultiFetcher::defaultIcon() {
0212   return QStringLiteral("folder-favorites");
0213 }
0214 
0215 Tellico::StringHash MultiFetcher::allOptionalFields() {
0216   StringHash hash;
0217   return hash;
0218 }
0219 
0220 MultiFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const MultiFetcher* fetcher_)
0221     : Fetch::ConfigWidget(parent_) {
0222   QVBoxLayout* l = new QVBoxLayout(optionsWidget());
0223 
0224   QWidget* hbox = new QWidget(optionsWidget());
0225   QHBoxLayout* hboxHBoxLayout = new QHBoxLayout(hbox);
0226   hboxHBoxLayout->setMargin(0);
0227   l->addWidget(hbox);
0228 
0229   QLabel* label = new QLabel(i18n("Collection &type:"), hbox);
0230   hboxHBoxLayout->addWidget(label);
0231   m_collCombo = new GUI::CollectionTypeCombo(hbox);
0232   void (GUI::CollectionTypeCombo::* activatedInt)(int) = &GUI::CollectionTypeCombo::activated;
0233   connect(m_collCombo, activatedInt, this, &ConfigWidget::slotSetModified);
0234   connect(m_collCombo, activatedInt, this, &ConfigWidget::slotTypeChanged);
0235   label->setBuddy(m_collCombo);
0236   hboxHBoxLayout->addWidget(m_collCombo);
0237 
0238   m_listWidget = new FetcherListWidget(optionsWidget());
0239   l->addWidget(m_listWidget);
0240 
0241   if(fetcher_) {
0242     if(fetcher_->m_collType > -1) {
0243       m_collCombo->setCurrentType(fetcher_->m_collType);
0244     } else {
0245       m_collCombo->setCurrentType(fetcher_->collectionType());
0246     }
0247   } else {
0248     // default to Book for now
0249     m_collCombo->setCurrentType(Data::Collection::Book);
0250   }
0251   slotTypeChanged();
0252   // set the source after the type was changed so the fetcher list is already populated
0253   if(fetcher_) {
0254     fetcher_->readSources();
0255     m_listWidget->setSources(fetcher_->m_fetchers);
0256   }
0257 }
0258 
0259 void MultiFetcher::ConfigWidget::slotTypeChanged() {
0260   const int collType = m_collCombo->currentType();
0261   m_listWidget->setFetchers(Fetch::Manager::self()->fetchers(collType));
0262 }
0263 
0264 void MultiFetcher::ConfigWidget::saveConfigHook(KConfigGroup& config_) {
0265   config_.writeEntry("CollectionType", m_collCombo->currentType());
0266   config_.writeEntry("Sources", m_listWidget->uuids());
0267 }
0268 
0269 QString MultiFetcher::ConfigWidget::preferredName() const {
0270   return MultiFetcher::defaultName();
0271 }
0272 
0273 MultiFetcher::FetcherItemWidget::FetcherItemWidget(QWidget* parent_)
0274     : QFrame(parent_) {
0275   QHBoxLayout* layout = new QHBoxLayout(this);
0276   layout->setSpacing(0);
0277   layout->setMargin(0);
0278   setLayout(layout);
0279 
0280   QLabel* label = new QLabel(i18n("Data source:"), this);
0281   layout->addWidget(label);
0282   m_fetcherCombo = new GUI::ComboBox(this);
0283   layout->addWidget(m_fetcherCombo);
0284   void (GUI::ComboBox::* activatedInt)(int) = &GUI::ComboBox::activated;
0285   connect(m_fetcherCombo, activatedInt, this, &FetcherItemWidget::signalModified);
0286   label->setBuddy(m_fetcherCombo);
0287 }
0288 
0289 void MultiFetcher::FetcherItemWidget::setFetchers(const QList<Fetcher::Ptr>& fetchers_) {
0290   m_fetcherCombo->clear();
0291   m_fetcherCombo->addItem(QString(), QVariant());
0292   foreach(Fetcher::Ptr fetcher, fetchers_) {
0293     // can't contain another multiple fetcher
0294     if(fetcher->type() == Multiple) {
0295       continue;
0296     }
0297     m_fetcherCombo->addItem(Manager::self()->fetcherIcon(fetcher.data()), fetcher->source(), fetcher->uuid());
0298   }
0299 }
0300 
0301 void MultiFetcher::FetcherItemWidget::setSource(Fetch::Fetcher::Ptr fetcher_) {
0302   m_fetcherCombo->setCurrentData(fetcher_->uuid());
0303 }
0304 
0305 QString MultiFetcher::FetcherItemWidget::fetcherUuid() const {
0306   return m_fetcherCombo->currentData().toString();
0307 }
0308 
0309 MultiFetcher::FetcherListWidget::FetcherListWidget(QWidget* parent_)
0310     : KWidgetLister(1, 8, parent_) {
0311   connect(this, &KWidgetLister::clearWidgets, this, &FetcherListWidget::signalModified);
0312   // start with at least 1
0313   setNumberOfShownWidgetsTo(1);
0314 }
0315 
0316 void MultiFetcher::FetcherListWidget::setFetchers(const QList<Fetcher::Ptr>& fetchers_) {
0317   m_fetchers = fetchers_;
0318   foreach(QWidget* widget, mWidgetList) {
0319     FetcherItemWidget* item = static_cast<FetcherItemWidget*>(widget);
0320     item->setFetchers(fetchers_);
0321   }
0322 }
0323 
0324 void MultiFetcher::FetcherListWidget::setSources(const QList<Fetcher::Ptr>& fetchers_) {
0325   setNumberOfShownWidgetsTo(fetchers_.count());
0326   Q_ASSERT(fetchers_.count() == mWidgetList.count());
0327   int i = 0;
0328   foreach(QWidget* widget, mWidgetList) {
0329     FetcherItemWidget* item = static_cast<FetcherItemWidget*>(widget);
0330     item->setSource(fetchers_.at(i));
0331     ++i;
0332   }
0333 }
0334 
0335 QStringList MultiFetcher::FetcherListWidget::uuids() const {
0336   QStringList uuids;
0337   foreach(QWidget* widget, mWidgetList) {
0338     FetcherItemWidget* item = static_cast<FetcherItemWidget*>(widget);
0339     QString uuid = item->fetcherUuid();
0340     if(!uuid.isEmpty()) {
0341       uuids << uuid;
0342     }
0343   }
0344   return uuids;
0345 }
0346 
0347 QWidget* MultiFetcher::FetcherListWidget::createWidget(QWidget* parent_) {
0348   FetcherItemWidget* w = new FetcherItemWidget(parent_);
0349   w->setFetchers(m_fetchers);
0350   connect(w, &FetcherItemWidget::signalModified, this, &FetcherListWidget::signalModified);
0351   return w;
0352 }