File indexing completed on 2024-04-28 16:31:58
0001 /*************************************************************************** 0002 Copyright (C) 2005-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 "entryupdater.h" 0026 #include "entry.h" 0027 #include "entrycomparison.h" 0028 #include "collection.h" 0029 #include "tellico_kernel.h" 0030 #include "progressmanager.h" 0031 #include "gui/statusbar.h" 0032 #include "document.h" 0033 #include "fetch/fetchresult.h" 0034 #include "entrymatchdialog.h" 0035 #include "tellico_debug.h" 0036 0037 #include <KLocalizedString> 0038 0039 #include <QTimer> 0040 #include <QApplication> 0041 0042 namespace { 0043 static const int CHECK_COLLECTION_IMAGES_STEP_SIZE = 10; 0044 } 0045 0046 using Tellico::EntryUpdater; 0047 0048 // for each entry, we loop over all available fetchers 0049 // then we loop over all entries 0050 EntryUpdater::EntryUpdater(Tellico::Data::CollPtr coll_, Tellico::Data::EntryList entries_, QObject* parent_) 0051 : QObject(parent_) 0052 , m_coll(coll_) 0053 , m_entriesToUpdate(entries_) 0054 , m_cancelled(false) { 0055 // for now, we're assuming all entries are same collection type 0056 m_fetchers = Fetch::Manager::self()->createUpdateFetchers(m_coll->type()); 0057 foreach(Fetch::Fetcher::Ptr fetcher, m_fetchers) { 0058 connect(fetcher.data(), &Fetch::Fetcher::signalResultFound, 0059 this, &EntryUpdater::slotResult); 0060 connect(fetcher.data(), &Fetch::Fetcher::signalDone, 0061 this, &EntryUpdater::slotDone); 0062 } 0063 init(); 0064 } 0065 0066 EntryUpdater::EntryUpdater(const QString& source_, Tellico::Data::CollPtr coll_, Tellico::Data::EntryList entries_, QObject* parent_) 0067 : QObject(parent_) 0068 , m_coll(coll_) 0069 , m_entriesToUpdate(entries_) 0070 , m_cancelled(false) { 0071 // for now, we're assuming all entries are same collection type 0072 Fetch::Fetcher::Ptr f = Fetch::Manager::self()->createUpdateFetcher(m_coll->type(), source_); 0073 if(f) { 0074 m_fetchers.append(f); 0075 connect(f.data(), &Fetch::Fetcher::signalResultFound, 0076 this, &EntryUpdater::slotResult); 0077 connect(f.data(), &Fetch::Fetcher::signalDone, 0078 this, &EntryUpdater::slotDone); 0079 } 0080 init(); 0081 } 0082 0083 EntryUpdater::~EntryUpdater() { 0084 foreach(const UpdateResult& res, m_results) { 0085 delete res.first; 0086 } 0087 m_results.clear(); 0088 } 0089 0090 void EntryUpdater::init() { 0091 m_fetchIndex = 0; 0092 m_origEntryCount = m_entriesToUpdate.count(); 0093 QString label; 0094 if(m_entriesToUpdate.count() == 1) { 0095 label = i18n("Updating %1...", m_entriesToUpdate.front()->title()); 0096 } else { 0097 label = i18n("Updating entries..."); 0098 } 0099 Kernel::self()->beginCommandGroup(i18n("Update Entries")); 0100 ProgressItem& item = ProgressManager::self()->newProgressItem(this, label, true /*canCancel*/); 0101 item.setTotalSteps(m_fetchers.count() * m_origEntryCount); 0102 connect(&item, &Tellico::ProgressItem::signalCancelled, 0103 this, &Tellico::EntryUpdater::slotCancel); 0104 0105 // done if no fetchers available 0106 if(m_fetchers.isEmpty()) { 0107 QTimer::singleShot(500, this, &EntryUpdater::slotCleanup); 0108 } else { 0109 slotStartNext(); // starts fetching 0110 } 0111 } 0112 0113 void EntryUpdater::slotStartNext() { 0114 StatusBar::self()->setStatus(i18n("Updating <b>%1</b>...", m_entriesToUpdate.front()->title())); 0115 ProgressManager::self()->setProgress(this, m_fetchers.count() * (m_origEntryCount - m_entriesToUpdate.count()) + m_fetchIndex); 0116 0117 Fetch::Fetcher::Ptr f = m_fetchers[m_fetchIndex]; 0118 // myDebug() << "starting update from" << f->source(); 0119 f->startUpdate(m_entriesToUpdate.front()); 0120 } 0121 0122 void EntryUpdater::slotDone() { 0123 if(m_cancelled) { 0124 QTimer::singleShot(500, this, &EntryUpdater::slotCleanup); 0125 return; 0126 } 0127 0128 if(!m_results.isEmpty()) { 0129 handleResults(); 0130 } 0131 0132 m_results.clear(); 0133 ++m_fetchIndex; 0134 // myDebug() << m_fetchIndex; 0135 if(m_fetchIndex == m_fetchers.count()) { 0136 m_fetchIndex = 0; 0137 // we've gone through the loop for the first entry in the vector 0138 // pop it and move on 0139 m_entriesToUpdate.removeAll(m_entriesToUpdate.front()); 0140 // if there are no more entries, and this is the last fetcher, time to delete 0141 if(m_entriesToUpdate.isEmpty()) { 0142 QTimer::singleShot(500, this, &EntryUpdater::slotCleanup); 0143 return; 0144 } 0145 } 0146 qApp->processEvents(); 0147 // so the entry updater can clean up a bit 0148 QTimer::singleShot(500, this, &EntryUpdater::slotStartNext); 0149 } 0150 0151 void EntryUpdater::slotResult(Tellico::Fetch::FetchResult* result_) { 0152 if(!result_ || m_cancelled) { 0153 return; 0154 } 0155 auto fetcher = m_fetchers[m_fetchIndex]; 0156 if(!fetcher || !fetcher->isSearching()) { 0157 return; 0158 } 0159 0160 // myDebug() << "update result:" << result_->title << " [" << fetcher->source() << "]"; 0161 m_results.append(UpdateResult(result_, fetcher->updateOverwrite())); 0162 Data::EntryPtr e = result_->fetchEntry(); 0163 if(e && !m_entriesToUpdate.isEmpty()) { 0164 m_fetchedEntries.append(e); 0165 const int match = m_coll->sameEntry(m_entriesToUpdate.front(), e); 0166 if(match >= EntryComparison::ENTRY_PERFECT_MATCH) { 0167 fetcher->stop(); 0168 } 0169 } 0170 qApp->processEvents(); 0171 } 0172 0173 void EntryUpdater::slotCancel() { 0174 m_cancelled = true; 0175 Fetch::Fetcher::Ptr f = m_fetchers[m_fetchIndex]; 0176 if(f) { 0177 f->stop(); // ends up calling slotDone(); 0178 } else { 0179 slotDone(); 0180 } 0181 } 0182 0183 void EntryUpdater::handleResults() { 0184 Data::EntryPtr entry = m_entriesToUpdate.front(); 0185 int best = 0; 0186 ResultList matches; 0187 foreach(const UpdateResult& res, m_results) { 0188 Data::EntryPtr e = res.first->fetchEntry(); 0189 if(!e) { 0190 continue; 0191 } 0192 m_fetchedEntries.append(e); 0193 int match = m_coll->sameEntry(entry, e); 0194 if(match) { 0195 // myDebug() << e->title() << "matches by" << match; 0196 } 0197 // if the match is GOOD but not PERFECT, keep all of them 0198 if(match >= EntryComparison::ENTRY_PERFECT_MATCH) { 0199 if(match > best) { 0200 best = match; 0201 matches.clear(); 0202 matches.append(res); 0203 } else if(match == best) { 0204 matches.append(res); 0205 } 0206 } else if(match >= EntryComparison::ENTRY_GOOD_MATCH) { 0207 best = qMax(best, match); 0208 // keep all the results that don't exceed the perfect match 0209 matches.append(res); 0210 } else if(match > best) { 0211 best = match; 0212 matches.clear(); 0213 matches.append(res); 0214 } else if(m_results.count() == 1 && best == 0 && entry->title().isEmpty()) { 0215 // special case for updates which may backfire, but let's go with it 0216 // if there is a single result AND the best match is zero AND title is empty 0217 // let's assume it's a case where an entry with a single url or link was updated 0218 myLog() << "Updating entry with 0 score and empty title:" << e->title(); 0219 best = EntryComparison::ENTRY_PERFECT_MATCH; 0220 matches.append(res); 0221 } 0222 } 0223 if(best < EntryComparison::ENTRY_GOOD_MATCH) { 0224 if(best > 0) { 0225 myDebug() << "no good match (score > 10), best match =" << best << "(" << matches.count() << "matches)"; 0226 } 0227 return; 0228 } 0229 // myDebug() << "best match = " << best << " (" << matches.count() << " matches)"; 0230 UpdateResult match(nullptr, true); 0231 if(matches.count() == 1) { 0232 match = matches.front(); 0233 } else if(matches.count() > 1) { 0234 match = askUser(matches); 0235 } 0236 // askUser() could come back with nil 0237 if(match.first) { 0238 mergeCurrent(match.first->fetchEntry(), match.second); 0239 } 0240 } 0241 0242 Tellico::EntryUpdater::UpdateResult EntryUpdater::askUser(const ResultList& results) { 0243 EntryMatchDialog dlg(Kernel::self()->widget(), m_entriesToUpdate.front(), 0244 m_fetchers[m_fetchIndex].data(), results); 0245 0246 if(dlg.exec() != QDialog::Accepted) { 0247 return UpdateResult(nullptr, false); 0248 } 0249 return dlg.updateResult(); 0250 } 0251 0252 void EntryUpdater::mergeCurrent(Tellico::Data::EntryPtr entry_, bool overWrite_) { 0253 Data::EntryPtr currEntry = m_entriesToUpdate.front(); 0254 if(entry_) { 0255 m_matchedEntries.append(entry_); 0256 Kernel::self()->updateEntry(currEntry, entry_, overWrite_); 0257 if(m_entriesToUpdate.count() % CHECK_COLLECTION_IMAGES_STEP_SIZE == 1) { 0258 // I don't want to remove any images in the entries that are getting 0259 // updated since they'll reference them later and the command isn't 0260 // executed until the command history group is finished 0261 // so remove pointers to matched entries 0262 Data::EntryList nonUpdatedEntries = m_fetchedEntries; 0263 foreach(Data::EntryPtr match, m_matchedEntries) { 0264 nonUpdatedEntries.removeAll(match); 0265 } 0266 Data::Document::self()->removeImagesNotInCollection(nonUpdatedEntries, m_matchedEntries); 0267 } 0268 } 0269 } 0270 0271 void EntryUpdater::slotCleanup() { 0272 ProgressManager::self()->setDone(this); 0273 StatusBar::self()->clearStatus(); 0274 Kernel::self()->endCommandGroup(); 0275 deleteLater(); 0276 }