You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tellico/src/entryupdater.cpp

274 lines
9.7 KiB

/***************************************************************************
copyright : (C) 2005-2006 by Robby Stephenson
email : robby@periapsis.org
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of version 2 of the GNU General Public License as *
* published by the Free Software Foundation; *
* *
***************************************************************************/
#include "entryupdater.h"
#include "entry.h"
#include "collection.h"
#include "tellico_kernel.h"
#include "tellico_debug.h"
#include "progressmanager.h"
#include "statusbar.h"
#include "gui/richtextlabel.h"
#include "document.h"
#include <kdialogbase.h>
#include <klocale.h>
#include <klistview.h>
#include <kiconloader.h>
#include <tqvbox.h>
#include <tqtimer.h>
namespace {
static const int CHECK_COLLECTION_IMAGES_STEP_SIZE = 10;
}
using Tellico::EntryUpdater;
// for each entry, we loop over all available fetchers
// then we loop over all entries
EntryUpdater::EntryUpdater(Data::CollPtr coll_, Data::EntryVec entries_, TQObject* parent_)
: TQObject(parent_), m_coll(coll_), m_entriesToUpdate(entries_), m_cancelled(false) {
// for now, we're assuming all entries are same collection type
m_fetchers = Fetch::Manager::self()->createUpdateFetchers(m_coll->type());
for(Fetch::FetcherVec::Iterator it = m_fetchers.begin(); it != m_fetchers.end(); ++it) {
connect(it.data(), TQT_SIGNAL(signalResultFound(Tellico::Fetch::SearchResult*)),
TQT_SLOT(slotResult(Tellico::Fetch::SearchResult*)));
connect(it.data(), TQT_SIGNAL(signalDone(Tellico::Fetch::Fetcher::Ptr)),
TQT_SLOT(slotDone()));
}
init();
}
EntryUpdater::EntryUpdater(const TQString& source_, Data::CollPtr coll_, Data::EntryVec entries_, TQObject* parent_)
: TQObject(parent_)
, m_coll(coll_)
, m_entriesToUpdate(entries_)
, m_cancelled(false) {
// for now, we're assuming all entries are same collection type
Fetch::Fetcher::Ptr f = Fetch::Manager::self()->createUpdateFetcher(m_coll->type(), source_);
if(f) {
m_fetchers.append(f);
connect(f, TQT_SIGNAL(signalResultFound(Tellico::Fetch::SearchResult*)),
TQT_SLOT(slotResult(Tellico::Fetch::SearchResult*)));
connect(f, TQT_SIGNAL(signalDone(Tellico::Fetch::Fetcher::Ptr)),
TQT_SLOT(slotDone()));
}
init();
}
EntryUpdater::~EntryUpdater() {
for(ResultList::Iterator res = m_results.begin(); res != m_results.end(); ++res) {
delete (*res).first;
}
}
void EntryUpdater::init() {
m_fetchIndex = 0;
m_origEntryCount = m_entriesToUpdate.count();
TQString label;
if(m_entriesToUpdate.count() == 1) {
label = i18n("Updating %1...").tqarg(m_entriesToUpdate.front()->title());
} else {
label = i18n("Updating entries...");
}
Kernel::self()->beginCommandGroup(i18n("Update Entries"));
ProgressItem& item = ProgressManager::self()->newProgressItem(this, label, true /*canCancel*/);
item.setTotalSteps(m_fetchers.count() * m_origEntryCount);
connect(&item, TQT_SIGNAL(signalCancelled(ProgressItem*)), TQT_SLOT(slotCancel()));
// done if no fetchers available
if(m_fetchers.isEmpty()) {
TQTimer::singleShot(500, this, TQT_SLOT(slotCleanup()));
} else {
slotStartNext(); // starts fetching
}
}
void EntryUpdater::slotStartNext() {
StatusBar::self()->settqStatus(i18n("Updating <b>%1</b>...").tqarg(m_entriesToUpdate.front()->title()));
ProgressManager::self()->setProgress(this, m_fetchers.count() * (m_origEntryCount - m_entriesToUpdate.count()) + m_fetchIndex);
Fetch::Fetcher::Ptr f = m_fetchers[m_fetchIndex];
// myDebug() << "EntryUpdater::slotDone() - starting " << f->source() << endl;
f->updateEntry(m_entriesToUpdate.front());
}
void EntryUpdater::slotDone() {
if(m_cancelled) {
myLog() << "EntryUpdater::slotDone() - cancelled" << endl;
TQTimer::singleShot(500, this, TQT_SLOT(slotCleanup()));
return;
}
if(!m_results.isEmpty()) {
handleResults();
}
m_results.clear();
++m_fetchIndex;
// myDebug() << "EntryUpdater::slotDone() " << m_fetchIndex << endl;
if(m_fetchIndex == static_cast<int>(m_fetchers.count())) {
m_fetchIndex = 0;
// we've gone through the loop for the first entry in the vector
// pop it and move on
m_entriesToUpdate.remove(m_entriesToUpdate.begin());
// if there are no more entries, and this is the last fetcher, time to delete
if(m_entriesToUpdate.isEmpty()) {
TQTimer::singleShot(500, this, TQT_SLOT(slotCleanup()));
return;
}
}
kapp->processEvents();
// so the entry updater can clean up a bit
TQTimer::singleShot(500, this, TQT_SLOT(slotStartNext()));
}
void EntryUpdater::slotResult(Fetch::SearchResult* result_) {
if(!result_ || m_cancelled) {
return;
}
// myDebug() << "EntryUpdater::slotResult() - " << result_->title << " [" << result_->fetcher->source() << "]" << endl;
m_results.append(UpdateResult(result_, m_fetchers[m_fetchIndex]->updateOverwrite()));
Data::EntryPtr e = result_->fetchEntry();
if(e) {
m_fetchedEntries.append(e);
int match = m_coll->sameEntry(m_entriesToUpdate.front(), e);
if(match > Data::Collection::ENTRY_PERFECT_MATCH) {
result_->fetcher->stop();
}
}
kapp->processEvents();
}
void EntryUpdater::slotCancel() {
// myDebug() << "EntryUpdater::slotCancel()" << endl;
m_cancelled = true;
Fetch::Fetcher::Ptr f = m_fetchers[m_fetchIndex];
if(f) {
f->stop(); // ends up calling slotDone();
} else {
slotDone();
}
}
void EntryUpdater::handleResults() {
Data::EntryPtr entry = m_entriesToUpdate.front();
int best = 0;
ResultList matches;
for(ResultList::Iterator res = m_results.begin(); res != m_results.end(); ++res) {
Data::EntryPtr e = (*res).first->fetchEntry();
if(!e) {
continue;
}
m_fetchedEntries.append(e);
int match = m_coll->sameEntry(entry, e);
if(match) {
// myDebug() << e->title() << " matches by " << match << endl;
}
if(match > best) {
best = match;
matches.clear();
matches.append(*res);
} else if(match == best && best > 0) {
matches.append(*res);
}
}
if(best < Data::Collection::ENTRY_GOOD_MATCH) {
if(best > 0) {
myDebug() << "no good match (score > 10), best match = " << best << " (" << matches.count() << " matches)" << endl;
}
return;
}
// myDebug() << "best match = " << best << " (" << matches.count() << " matches)" << endl;
UpdateResult match(0, true);
if(matches.count() == 1) {
match = matches.front();
} else if(matches.count() > 1) {
match = askUser(matches);
}
// askUser() could come back with nil
if(match.first) {
mergeCurrent(match.first->fetchEntry(), match.second);
}
}
Tellico::EntryUpdater::UpdateResult EntryUpdater::askUser(ResultList results) {
KDialogBase dlg(Kernel::self()->widget(), "entry updater dialog",
true, i18n("Select Match"), KDialogBase::Ok|KDialogBase::Cancel);
TQVBox* box = new TQVBox(&dlg);
box->setSpacing(10);
TQHBox* hbox = new TQHBox(box);
hbox->setSpacing(10);
TQLabel* icon = new TQLabel(hbox);
icon->setPixmap(KGlobal::iconLoader()->loadIcon(TQString::tqfromLatin1("network"), KIcon::Panel, 64));
TQString s = i18n("<qt><b>%1</b> returned multiple results which could match <b>%2</b>, "
"the entry currently in the collection. Please select the correct match.</qt>")
.tqarg(m_fetchers[m_fetchIndex]->source())
.tqarg(m_entriesToUpdate.front()->field(TQString::tqfromLatin1("title")));
GUI::RichTextLabel* l = new GUI::RichTextLabel(s, hbox);
hbox->setStretchFactor(l, 100);
KListView* view = new KListView(box);
view->setShowSortIndicator(true);
view->setAllColumnsShowFocus(true);
view->setResizeMode(TQListView::AllColumns);
view->setMinimumWidth(640);
view->addColumn(i18n("Title"));
view->addColumn(i18n("Description"));
TQMap<KListViewItem*, UpdateResult> map;
for(ResultList::Iterator res = results.begin(); res != results.end(); ++res) {
map.insert(new KListViewItem(view, (*res).first->fetchEntry()->title(), (*res).first->desc), *res);
}
dlg.setMainWidget(box);
if(dlg.exec() != TQDialog::Accepted) {
return UpdateResult(0, false);
}
KListViewItem* item = static_cast<KListViewItem*>(view->selectedItem());
if(!item) {
return UpdateResult(0, false);
}
return map[item];
}
void EntryUpdater::mergeCurrent(Data::EntryPtr entry_, bool overWrite_) {
Data::EntryPtr currEntry = m_entriesToUpdate.front();
if(entry_) {
m_matchedEntries.append(entry_);
Kernel::self()->updateEntry(currEntry, entry_, overWrite_);
if(m_entriesToUpdate.count() % CHECK_COLLECTION_IMAGES_STEP_SIZE == 1) {
// I don't want to remove any images in the entries that are getting
// updated since they'll reference them later and the command isn't
// executed until the command history group is finished
// so remove pointers to matched entries
Data::EntryVec nonUpdatedEntries = m_fetchedEntries;
for(Data::EntryVecIt match = m_matchedEntries.begin(); match != m_matchedEntries.end(); ++match) {
nonUpdatedEntries.remove(match);
}
Data::Document::self()->removeImagesNotInCollection(nonUpdatedEntries, m_matchedEntries);
}
}
}
void EntryUpdater::slotCleanup() {
StatusBar::self()->cleartqStatus();
ProgressManager::self()->setDone(this);
Kernel::self()->endCommandGroup();
deleteLater();
}
#include "entryupdater.moc"