summaryrefslogtreecommitdiffstats
path: root/src/fetch/execexternalfetcher.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/fetch/execexternalfetcher.cpp')
-rw-r--r--src/fetch/execexternalfetcher.cpp561
1 files changed, 561 insertions, 0 deletions
diff --git a/src/fetch/execexternalfetcher.cpp b/src/fetch/execexternalfetcher.cpp
new file mode 100644
index 0000000..07b99d8
--- /dev/null
+++ b/src/fetch/execexternalfetcher.cpp
@@ -0,0 +1,561 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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 "execexternalfetcher.h"
+#include "messagehandler.h"
+#include "fetchmanager.h"
+#include "../collection.h"
+#include "../entry.h"
+#include "../importdialog.h"
+#include "../translators/tellicoimporter.h"
+#include "../tellico_debug.h"
+#include "../gui/combobox.h"
+#include "../gui/lineedit.h"
+#include "../gui/collectiontypecombo.h"
+#include "../tellico_utils.h"
+#include "../newstuff/manager.h"
+
+#include <klocale.h>
+#include <kconfig.h>
+#include <kprocess.h>
+#include <kurlrequester.h>
+#include <kaccelmanager.h>
+
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qwhatsthis.h>
+#include <qregexp.h>
+#include <qvgroupbox.h>
+#include <qfile.h> // needed for QFile::remove
+
+using Tellico::Fetch::ExecExternalFetcher;
+
+QStringList ExecExternalFetcher::parseArguments(const QString& str_) {
+ // matching escaped quotes is too hard... :(
+// QRegExp quotes(QString::fromLatin1("[^\\\\](['\"])(.*[^\\\\])\\1"));
+ QRegExp quotes(QString::fromLatin1("(['\"])(.*)\\1"));
+ quotes.setMinimal(true);
+ QRegExp spaces(QString::fromLatin1("\\s+"));
+ spaces.setMinimal(true);
+
+ QStringList args;
+ int pos = 0;
+ for(int nextPos = quotes.search(str_); nextPos > -1; pos = nextPos+1, nextPos = quotes.search(str_, pos)) {
+ // a non-quotes arguments runs from pos to nextPos
+ args += QStringList::split(spaces, str_.mid(pos, nextPos-pos));
+ // move nextpos marker to end of match
+ pos = quotes.pos(2); // skip quotation mark
+ nextPos += quotes.matchedLength();
+ args += str_.mid(pos, nextPos-pos-1);
+ }
+ // catch the end stuff
+ args += QStringList::split(spaces, str_.mid(pos));
+
+#if 0
+ for(QStringList::ConstIterator it = args.begin(); it != args.end(); ++it) {
+ myDebug() << *it << endl;
+ }
+#endif
+
+ return args;
+}
+
+ExecExternalFetcher::ExecExternalFetcher(QObject* parent_, const char* name_/*=0*/) : Fetcher(parent_, name_),
+ m_started(false), m_collType(-1), m_formatType(-1), m_canUpdate(false), m_process(0), m_deleteOnRemove(false) {
+}
+
+ExecExternalFetcher::~ExecExternalFetcher() {
+ stop();
+}
+
+QString ExecExternalFetcher::defaultName() {
+ return i18n("External Application");
+}
+
+QString ExecExternalFetcher::source() const {
+ return m_name;
+}
+
+bool ExecExternalFetcher::canFetch(int type_) const {
+ return m_collType == -1 ? false : m_collType == type_;
+}
+
+void ExecExternalFetcher::readConfigHook(const KConfigGroup& config_) {
+ QString s = config_.readPathEntry("ExecPath");
+ if(!s.isEmpty()) {
+ m_path = s;
+ }
+ QValueList<int> il;
+ if(config_.hasKey("ArgumentKeys")) {
+ il = config_.readIntListEntry("ArgumentKeys");
+ } else {
+ il.append(Keyword);
+ }
+ QStringList sl = config_.readListEntry("Arguments");
+ if(il.count() != sl.count()) {
+ kdWarning() << "ExecExternalFetcher::readConfig() - unequal number of arguments and keys" << endl;
+ }
+ int n = QMIN(il.count(), sl.count());
+ for(int i = 0; i < n; ++i) {
+ m_args[static_cast<FetchKey>(il[i])] = sl[i];
+ }
+ if(config_.hasKey("UpdateArgs")) {
+ m_canUpdate = true;
+ m_updateArgs = config_.readEntry("UpdateArgs");
+ } else {
+ m_canUpdate = false;
+ }
+ m_collType = config_.readNumEntry("CollectionType", -1);
+ m_formatType = config_.readNumEntry("FormatType", -1);
+ m_deleteOnRemove = config_.readBoolEntry("DeleteOnRemove", false);
+ m_newStuffName = config_.readEntry("NewStuffName");
+}
+
+void ExecExternalFetcher::search(FetchKey key_, const QString& value_) {
+ m_started = true;
+
+ if(!m_args.contains(key_)) {
+ stop();
+ return;
+ }
+
+ // should KProcess::quote() be used?
+ // %1 gets replaced by the search value, but since the arguments are going to be split
+ // the search value needs to be enclosed in quotation marks
+ // but first check to make sure the user didn't do that already
+ // AND the "%1" wasn't used in the settings
+ QString value = value_;
+ if(key_ == ISBN) {
+ value.remove('-'); // remove hyphens from isbn values
+ // shouldn't hurt and might keep from confusing stupid search sources
+ }
+ QRegExp rx1(QString::fromLatin1("['\"].*\\1"));
+ if(!rx1.exactMatch(value)) {
+ value.prepend('"').append('"');
+ }
+ QString args = m_args[key_];
+ QRegExp rx2(QString::fromLatin1("['\"]%1\\1"));
+ args.replace(rx2, QString::fromLatin1("%1"));
+ startSearch(parseArguments(args.arg(value))); // replace %1 with search value
+}
+
+void ExecExternalFetcher::startSearch(const QStringList& args_) {
+ if(m_path.isEmpty()) {
+ stop();
+ return;
+ }
+
+#if 0
+ myDebug() << m_path << endl;
+ for(QStringList::ConstIterator it = args_.begin(); it != args_.end(); ++it) {
+ myDebug() << " " << *it << endl;
+ }
+#endif
+
+ m_process = new KProcess();
+ connect(m_process, SIGNAL(receivedStdout(KProcess*, char*, int)), SLOT(slotData(KProcess*, char*, int)));
+ connect(m_process, SIGNAL(receivedStderr(KProcess*, char*, int)), SLOT(slotError(KProcess*, char*, int)));
+ connect(m_process, SIGNAL(processExited(KProcess*)), SLOT(slotProcessExited(KProcess*)));
+ *m_process << m_path << args_;
+ if(!m_process->start(KProcess::NotifyOnExit, KProcess::AllOutput)) {
+ myDebug() << "ExecExternalFetcher::startSearch() - process failed to start" << endl;
+ stop();
+ }
+}
+
+void ExecExternalFetcher::stop() {
+ if(!m_started) {
+ return;
+ }
+ if(m_process) {
+ m_process->kill();
+ delete m_process;
+ m_process = 0;
+ }
+ m_data.truncate(0);
+ m_started = false;
+ m_errors.clear();
+ emit signalDone(this);
+}
+
+void ExecExternalFetcher::slotData(KProcess*, char* buffer_, int len_) {
+ QDataStream stream(m_data, IO_WriteOnly | IO_Append);
+ stream.writeRawBytes(buffer_, len_);
+}
+
+void ExecExternalFetcher::slotError(KProcess*, char* buffer_, int len_) {
+ GUI::CursorSaver cs(Qt::arrowCursor);
+ QString msg = QString::fromLocal8Bit(buffer_, len_);
+ msg.prepend(source() + QString::fromLatin1(": "));
+ if(msg.endsWith(QChar('\n'))) {
+ msg.truncate(msg.length()-1);
+ }
+ myDebug() << "ExecExternalFetcher::slotError() - " << msg << endl;
+ m_errors << msg;
+}
+
+void ExecExternalFetcher::slotProcessExited(KProcess*) {
+// myDebug() << "ExecExternalFetcher::slotProcessExited()" << endl;
+ if(!m_process->normalExit() || m_process->exitStatus()) {
+ myDebug() << "ExecExternalFetcher::slotProcessExited() - "<< source() << ": process did not exit successfully" << endl;
+ if(!m_errors.isEmpty()) {
+ message(m_errors.join(QChar('\n')), MessageHandler::Error);
+ }
+ stop();
+ return;
+ }
+ if(!m_errors.isEmpty()) {
+ message(m_errors.join(QChar('\n')), MessageHandler::Warning);
+ }
+
+ if(m_data.isEmpty()) {
+ myDebug() << "ExecExternalFetcher::slotProcessExited() - "<< source() << ": no data" << endl;
+ stop();
+ return;
+ }
+
+ Import::Format format = static_cast<Import::Format>(m_formatType > -1 ? m_formatType : Import::TellicoXML);
+ Import::Importer* imp = ImportDialog::importer(format, KURL::List());
+ if(!imp) {
+ stop();
+ return;
+ }
+
+ imp->setText(QString::fromUtf8(m_data, m_data.size()));
+ Data::CollPtr coll = imp->collection();
+ if(!coll) {
+ if(!imp->statusMessage().isEmpty()) {
+ message(imp->statusMessage(), MessageHandler::Status);
+ }
+ myDebug() << "ExecExternalFetcher::slotProcessExited() - "<< source() << ": no collection pointer" << endl;
+ delete imp;
+ stop();
+ return;
+ }
+
+ delete imp;
+ if(coll->entryCount() == 0) {
+// myDebug() << "ExecExternalFetcher::slotProcessExited() - no results" << endl;
+ stop();
+ return;
+ }
+
+ Data::EntryVec entries = coll->entries();
+ for(Data::EntryVec::Iterator entry = entries.begin(); entry != entries.end(); ++entry) {
+ QString desc;
+ switch(coll->type()) {
+ case Data::Collection::Book:
+ case Data::Collection::Bibtex:
+ desc = entry->field(QString::fromLatin1("author"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("publisher"));
+ if(!entry->field(QString::fromLatin1("cr_year")).isEmpty()) {
+ desc += QChar('/') + entry->field(QString::fromLatin1("cr_year"));
+ } else if(!entry->field(QString::fromLatin1("pub_year")).isEmpty()){
+ desc += QChar('/') + entry->field(QString::fromLatin1("pub_year"));
+ }
+ break;
+
+ case Data::Collection::Video:
+ desc = entry->field(QString::fromLatin1("studio"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("director"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("year"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("medium"));
+ break;
+
+ case Data::Collection::Album:
+ desc = entry->field(QString::fromLatin1("artist"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("label"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("year"));
+ break;
+
+ case Data::Collection::Game:
+ desc = entry->field(QString::fromLatin1("platform"));
+ break;
+
+ case Data::Collection::ComicBook:
+ desc = entry->field(QString::fromLatin1("publisher"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("pub_year"));
+ break;
+
+ case Data::Collection::BoardGame:
+ desc = entry->field(QString::fromLatin1("designer"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("publisher"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("year"));
+ break;
+
+ default:
+ break;
+ }
+ SearchResult* r = new SearchResult(this, entry->title(), desc, entry->field(QString::fromLatin1("isbn")));
+ m_entries.insert(r->uid, entry);
+ emit signalResultFound(r);
+ }
+ stop(); // be sure to call this
+}
+
+Tellico::Data::EntryPtr ExecExternalFetcher::fetchEntry(uint uid_) {
+ return m_entries[uid_];
+}
+
+void ExecExternalFetcher::updateEntry(Data::EntryPtr entry_) {
+ if(!m_canUpdate) {
+ emit signalDone(this); // must do this
+ }
+
+ m_started = true;
+
+ Data::ConstEntryPtr e(entry_.data());
+ QStringList args = parseArguments(m_updateArgs);
+ for(QStringList::Iterator it = args.begin(); it != args.end(); ++it) {
+ *it = Data::Entry::dependentValue(e, *it, false);
+ }
+ startSearch(args);
+}
+
+Tellico::Fetch::ConfigWidget* ExecExternalFetcher::configWidget(QWidget* parent_) const {
+ return new ExecExternalFetcher::ConfigWidget(parent_, this);
+}
+
+ExecExternalFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const ExecExternalFetcher* fetcher_/*=0*/)
+ : Fetch::ConfigWidget(parent_), m_deleteOnRemove(false) {
+ QGridLayout* l = new QGridLayout(optionsWidget(), 5, 2);
+ l->setSpacing(4);
+ l->setColStretch(1, 10);
+
+ int row = -1;
+
+ QLabel* label = new QLabel(i18n("Collection &type:"), optionsWidget());
+ l->addWidget(label, ++row, 0);
+ m_collCombo = new GUI::CollectionTypeCombo(optionsWidget());
+ connect(m_collCombo, SIGNAL(activated(int)), SLOT(slotSetModified()));
+ l->addWidget(m_collCombo, row, 1);
+ QString w = i18n("Set the collection type of the data returned from the external application.");
+ QWhatsThis::add(label, w);
+ QWhatsThis::add(m_collCombo, w);
+ label->setBuddy(m_collCombo);
+
+ label = new QLabel(i18n("&Result type: "), optionsWidget());
+ l->addWidget(label, ++row, 0);
+ m_formatCombo = new GUI::ComboBox(optionsWidget());
+ Import::FormatMap formatMap = ImportDialog::formatMap();
+ for(Import::FormatMap::Iterator it = formatMap.begin(); it != formatMap.end(); ++it) {
+ if(ImportDialog::formatImportsText(it.key())) {
+ m_formatCombo->insertItem(it.data(), it.key());
+ }
+ }
+ connect(m_formatCombo, SIGNAL(activated(int)), SLOT(slotSetModified()));
+ l->addWidget(m_formatCombo, row, 1);
+ w = i18n("Set the result type of the data returned from the external application.");
+ QWhatsThis::add(label, w);
+ QWhatsThis::add(m_formatCombo, w);
+ label->setBuddy(m_formatCombo);
+
+ label = new QLabel(i18n("Application &path: "), optionsWidget());
+ l->addWidget(label, ++row, 0);
+ m_pathEdit = new KURLRequester(optionsWidget());
+ connect(m_pathEdit, SIGNAL(textChanged(const QString&)), SLOT(slotSetModified()));
+ l->addWidget(m_pathEdit, row, 1);
+ w = i18n("Set the path of the application to run that should output a valid Tellico data file.");
+ QWhatsThis::add(label, w);
+ QWhatsThis::add(m_pathEdit, w);
+ label->setBuddy(m_pathEdit);
+
+ w = i18n("Select the search keys supported by the data source.");
+ QString w2 = i18n("Add any arguments that may be needed. <b>%1</b> will be replaced by the search term.");
+ QVGroupBox* box = new QVGroupBox(i18n("Arguments"), optionsWidget());
+ ++row;
+ l->addMultiCellWidget(box, row, row, 0, 1);
+ QWidget* grid = new QWidget(box);
+ QGridLayout* gridLayout = new QGridLayout(grid);
+ gridLayout->setSpacing(2);
+ row = -1;
+ const Fetch::KeyMap keyMap = Fetch::Manager::self()->keyMap();
+ for(Fetch::KeyMap::ConstIterator it = keyMap.begin(); it != keyMap.end(); ++it) {
+ FetchKey key = it.key();
+ if(key == Raw) {
+ continue;
+ }
+ QCheckBox* cb = new QCheckBox(it.data(), grid);
+ gridLayout->addWidget(cb, ++row, 0);
+ m_cbDict.insert(key, cb);
+ GUI::LineEdit* le = new GUI::LineEdit(grid);
+ le->setHint(QString::fromLatin1("%1")); // for example
+ le->completionObject()->addItem(QString::fromLatin1("%1"));
+ gridLayout->addWidget(le, row, 1);
+ m_leDict.insert(key, le);
+ if(fetcher_ && fetcher_->m_args.contains(key)) {
+ cb->setChecked(true);
+ le->setEnabled(true);
+ le->setText(fetcher_->m_args[key]);
+ } else {
+ cb->setChecked(false);
+ le->setEnabled(false);
+ }
+ connect(cb, SIGNAL(toggled(bool)), le, SLOT(setEnabled(bool)));
+ QWhatsThis::add(cb, w);
+ QWhatsThis::add(le, w2);
+ }
+ m_cbUpdate = new QCheckBox(i18n("Update"), grid);
+ gridLayout->addWidget(m_cbUpdate, ++row, 0);
+ m_leUpdate = new GUI::LineEdit(grid);
+ m_leUpdate->setHint(QString::fromLatin1("%{title}")); // for example
+ m_leUpdate->completionObject()->addItem(QString::fromLatin1("%{title}"));
+ m_leUpdate->completionObject()->addItem(QString::fromLatin1("%{isbn}"));
+ gridLayout->addWidget(m_leUpdate, row, 1);
+ /* TRANSLATORS: Do not translate %{author}. */
+ w2 = i18n("<p>Enter the arguments which should be used to search for available updates to an entry.</p><p>"
+ "The format is the same as for <i>Dependent</i> fields, where field values "
+ "are contained inside braces, such as <i>%{author}</i>. See the documentation for details.</p>");
+ QWhatsThis::add(m_cbUpdate, w);
+ QWhatsThis::add(m_leUpdate, w2);
+ if(fetcher_ && fetcher_->m_canUpdate) {
+ m_cbUpdate->setChecked(true);
+ m_leUpdate->setEnabled(true);
+ m_leUpdate->setText(fetcher_->m_updateArgs);
+ } else {
+ m_cbUpdate->setChecked(false);
+ m_leUpdate->setEnabled(false);
+ }
+ connect(m_cbUpdate, SIGNAL(toggled(bool)), m_leUpdate, SLOT(setEnabled(bool)));
+
+ l->setRowStretch(++row, 1);
+
+ if(fetcher_) {
+ m_pathEdit->setURL(fetcher_->m_path);
+ m_newStuffName = fetcher_->m_newStuffName;
+ }
+ if(fetcher_ && fetcher_->m_collType > -1) {
+ m_collCombo->setCurrentType(fetcher_->m_collType);
+ } else {
+ m_collCombo->setCurrentType(Data::Collection::Book);
+ }
+ if(fetcher_ && fetcher_->m_formatType > -1) {
+ m_formatCombo->setCurrentItem(formatMap[static_cast<Import::Format>(fetcher_->m_formatType)]);
+ } else {
+ m_formatCombo->setCurrentItem(formatMap[Import::TellicoXML]);
+ }
+ m_deleteOnRemove = fetcher_ && fetcher_->m_deleteOnRemove;
+ KAcceleratorManager::manage(optionsWidget());
+}
+
+ExecExternalFetcher::ConfigWidget::~ConfigWidget() {
+}
+
+void ExecExternalFetcher::ConfigWidget::readConfig(KConfig* config_) {
+ m_pathEdit->setURL(config_->readPathEntry("ExecPath"));
+ QValueList<int> argKeys = config_->readIntListEntry("ArgumentKeys");
+ QStringList argValues = config_->readListEntry("Arguments");
+ if(argKeys.count() != argValues.count()) {
+ kdWarning() << "ExecExternalFetcher::ConfigWidget::readConfig() - unequal number of arguments and keys" << endl;
+ }
+ int n = QMIN(argKeys.count(), argValues.count());
+ QMap<FetchKey, QString> args;
+ for(int i = 0; i < n; ++i) {
+ args[static_cast<FetchKey>(argKeys[i])] = argValues[i];
+ }
+ for(QValueList<int>::Iterator it = argKeys.begin(); it != argKeys.end(); ++it) {
+ if(*it == Raw) {
+ continue;
+ }
+ FetchKey key = static_cast<FetchKey>(*it);
+ QCheckBox* cb = m_cbDict[key];
+ KLineEdit* le = m_leDict[key];
+ if(cb && le) {
+ if(args.contains(key)) {
+ cb->setChecked(true);
+ le->setEnabled(true);
+ le->setText(args[key]);
+ } else {
+ cb->setChecked(false);
+ le->setEnabled(false);
+ le->clear();
+ }
+ }
+ }
+
+ if(config_->hasKey("UpdateArgs")) {
+ m_cbUpdate->setChecked(true);
+ m_leUpdate->setEnabled(true);
+ m_leUpdate->setText(config_->readEntry("UpdateArgs"));
+ } else {
+ m_cbUpdate->setChecked(false);
+ m_leUpdate->setEnabled(false);
+ m_leUpdate->clear();
+ }
+
+ int collType = config_->readNumEntry("CollectionType");
+ m_collCombo->setCurrentType(collType);
+
+ Import::FormatMap formatMap = ImportDialog::formatMap();
+ int formatType = config_->readNumEntry("FormatType");
+ m_formatCombo->setCurrentItem(formatMap[static_cast<Import::Format>(formatType)]);
+ m_deleteOnRemove = config_->readBoolEntry("DeleteOnRemove", false);
+ m_name = config_->readEntry("Name");
+ m_newStuffName = config_->readEntry("NewStuffName");
+}
+
+void ExecExternalFetcher::ConfigWidget::saveConfig(KConfigGroup& config_) {
+ QString s = m_pathEdit->url();
+ if(!s.isEmpty()) {
+ config_.writePathEntry("ExecPath", s);
+ }
+ QValueList<int> keys;
+ QStringList args;
+ for(QIntDictIterator<QCheckBox> it(m_cbDict); it.current(); ++it) {
+ if(it.current()->isChecked()) {
+ keys << it.currentKey();
+ args << m_leDict[it.currentKey()]->text();
+ }
+ }
+ config_.writeEntry("ArgumentKeys", keys);
+ config_.writeEntry("Arguments", args);
+
+ if(m_cbUpdate->isChecked()) {
+ config_.writeEntry("UpdateArgs", m_leUpdate->text());
+ } else {
+ config_.deleteEntry("UpdateArgs");
+ }
+
+ config_.writeEntry("CollectionType", m_collCombo->currentType());
+ config_.writeEntry("FormatType", m_formatCombo->currentData().toInt());
+ config_.writeEntry("DeleteOnRemove", m_deleteOnRemove);
+ if(!m_newStuffName.isEmpty()) {
+ config_.writeEntry("NewStuffName", m_newStuffName);
+ }
+ slotSetModified(false);
+}
+
+void ExecExternalFetcher::ConfigWidget::removed() {
+ if(!m_deleteOnRemove) {
+ return;
+ }
+ if(!m_newStuffName.isEmpty()) {
+ NewStuff::Manager man(this);
+ man.removeScript(m_newStuffName);
+ }
+}
+
+QString ExecExternalFetcher::ConfigWidget::preferredName() const {
+ return m_name.isEmpty() ? ExecExternalFetcher::defaultName() : m_name;
+}
+
+#include "execexternalfetcher.moc"