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 }