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

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 "execexternalfetcher.h"
0026 #include "fetchmanager.h"
0027 #include "../collection.h"
0028 #include "../entry.h"
0029 #include "../fieldformat.h"
0030 #include "../derivedvalue.h"
0031 #include "../tellico_debug.h"
0032 #include "../gui/combobox.h"
0033 #include "../gui/lineedit.h"
0034 #include "../gui/collectiontypecombo.h"
0035 #include "../utils/cursorsaver.h"
0036 #include "../newstuff/manager.h"
0037 #include "../translators/translators.h"
0038 #include "../translators/tellicoimporter.h"
0039 #include "../translators/bibteximporter.h"
0040 #include "../translators/xsltimporter.h"
0041 #include "../translators/risimporter.h"
0042 #include "../utils/datafileregistry.h"
0043 
0044 #include <KLocalizedString>
0045 #include <KProcess>
0046 #include <KUrlRequester>
0047 #include <KAcceleratorManager>
0048 #include <KShell>
0049 #include <KConfigGroup>
0050 
0051 #include <QLabel>
0052 #include <QGroupBox>
0053 #include <QGridLayout>
0054 #include <QStandardItemModel>
0055 
0056 using Tellico::Fetch::ExecExternalFetcher;
0057 
0058 QStringList ExecExternalFetcher::parseArguments(const QString& str_) {
0059   // matching escaped quotes is too hard... :(
0060   static const QRegularExpression quotes(QLatin1String("(['\"])(.*?)\\1"));
0061   static const QRegularExpression spaces(QLatin1String("\\s+?"));
0062 
0063   QStringList args;
0064   QRegularExpressionMatch match;
0065   int pos = 0;
0066   for(int nextPos = str_.indexOf(quotes, pos, &match); nextPos > -1; pos = nextPos+1, nextPos = str_.indexOf(quotes, pos, &match)) {
0067     // a non-quotes arguments runs from pos to nextPos
0068 #if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0))
0069     args += str_.mid(pos, nextPos-pos).split(spaces, QString::SkipEmptyParts);
0070 #else
0071     args += str_.mid(pos, nextPos-pos).split(spaces, Qt::SkipEmptyParts);
0072 #endif
0073     // move nextpos marker to end of match
0074     nextPos += match.capturedLength();
0075     args += match.captured(2);
0076   }
0077   // catch the end stuff
0078 #if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0))
0079   args += str_.mid(pos).split(spaces, QString::SkipEmptyParts);
0080 #else
0081   args += str_.mid(pos).split(spaces, Qt::SkipEmptyParts);
0082 #endif
0083 
0084   return args;
0085 }
0086 
0087 ExecExternalFetcher::ExecExternalFetcher(QObject* parent_) : Fetcher(parent_),
0088     m_started(false), m_collType(-1), m_formatType(-1), m_canUpdate(false), m_process(nullptr), m_deleteOnRemove(false) {
0089 }
0090 
0091 ExecExternalFetcher::~ExecExternalFetcher() {
0092   if(m_process) {
0093     m_process->kill();
0094     m_process->deleteLater();
0095   }
0096 }
0097 
0098 QString ExecExternalFetcher::source() const {
0099   return m_name;
0100 }
0101 
0102 bool ExecExternalFetcher::canSearch(Fetch::FetchKey k) const {
0103   return m_args.contains(k) || (m_canUpdate && k == ExecUpdate);
0104 }
0105 
0106 bool ExecExternalFetcher::canFetch(int type_) const {
0107   return m_collType == -1 ? false : m_collType == type_;
0108 }
0109 
0110 void ExecExternalFetcher::readConfigHook(const KConfigGroup& config_) {
0111   QString s = config_.readPathEntry("ExecPath", QString());
0112   if(!s.isEmpty()) {
0113     m_path = s;
0114   }
0115   QList<int> argKeys;
0116   if(config_.hasKey("ArgumentKeys")) {
0117     argKeys = config_.readEntry("ArgumentKeys", argKeys);
0118   } else {
0119     myDebug() << "appending default keyword argument";
0120     argKeys.append(Keyword);
0121   }
0122   QStringList args = config_.readEntry("Arguments", QStringList());
0123   if(argKeys.count() != args.count()) {
0124     myWarning() << "unequal number of arguments and keys";
0125   }
0126   int n = qMin(argKeys.count(), args.count());
0127   for(int i = 0; i < n; ++i) {
0128     m_args.insert(static_cast<FetchKey>(argKeys[i]), args[i]);
0129   }
0130   if(config_.hasKey("UpdateArgs")) {
0131     m_canUpdate = true;
0132     m_updateArgs = config_.readEntry("UpdateArgs");
0133   } else {
0134     m_canUpdate = false;
0135   }
0136   m_collType = config_.readEntry("CollectionType", -1);
0137   m_formatType = config_.readEntry("FormatType", -1);
0138   m_deleteOnRemove = config_.readEntry("DeleteOnRemove", false);
0139   m_newStuffName = config_.readEntry("NewStuffName");
0140 }
0141 
0142 void ExecExternalFetcher::search() {
0143   m_started = true;
0144 
0145   if(request().key() != ExecUpdate && !m_args.contains(request().key())) {
0146     myDebug() << "stopping: not an update and no matching argument for search key";
0147     stop();
0148     return;
0149   }
0150 
0151   if(request().key() == ExecUpdate) {
0152     // because the rowDelimiterString() is used below
0153     QStringList args = FieldFormat::splitTable(request().value());
0154     startSearch(args);
0155     return;
0156   }
0157 
0158   // should KShell::quoteArg() be used?
0159   // %1 gets replaced by the search value, but since the arguments are going to be split
0160   // the search value needs to be enclosed in quotation marks
0161   // but first check to make sure the user didn't do that already
0162   // AND the "%1" wasn't used in the settings
0163   QString value = request().value();
0164   if(request().key() == ISBN) {
0165     value.remove(QLatin1Char('-')); // remove hyphens from isbn values
0166     // shouldn't hurt and might keep from confusing stupid search sources
0167   }
0168   bool hasQuotes = value.startsWith(QLatin1Char('"')) && value.endsWith(QLatin1Char('"'));
0169   if(!hasQuotes) {
0170     hasQuotes = value.startsWith(QLatin1Char('\'')) && value.endsWith(QLatin1Char('\''));
0171   }
0172   if(!hasQuotes) {
0173     value = QLatin1Char('"') + value + QLatin1Char('"');
0174   }
0175   QString args = m_args.value(request().key());
0176   static const QRegularExpression rx(QLatin1String("(['\"])%1\\1"));
0177   args.replace(rx, QStringLiteral("%1"));
0178   startSearch(parseArguments(args.arg(value))); // replace %1 with search value
0179 }
0180 
0181 void ExecExternalFetcher::startSearch(const QStringList& args_) {
0182   if(m_path.isEmpty()) {
0183     Q_ASSERT(!m_path.isEmpty());
0184     stop();
0185     return;
0186   }
0187 
0188   m_process = new KProcess();
0189   connect(m_process, &QProcess::readyReadStandardOutput, this, &ExecExternalFetcher::slotData);
0190   connect(m_process, &QProcess::readyReadStandardError, this, &ExecExternalFetcher::slotError);
0191   void (QProcess::* finished)(int, QProcess::ExitStatus) = &QProcess::finished;
0192   connect(m_process, finished, this, &ExecExternalFetcher::slotProcessExited);
0193   m_process->setOutputChannelMode(KProcess::SeparateChannels);
0194   m_process->setProgram(m_path, args_);
0195   if(m_process && m_process->execute() < 0) {
0196     myDebug() << "process failed to start";
0197     stop();
0198   }
0199 }
0200 
0201 void ExecExternalFetcher::stop() {
0202   if(!m_started) {
0203     return;
0204   }
0205   if(m_process) {
0206     m_process->kill();
0207     m_process->deleteLater();
0208     m_process = nullptr;
0209   }
0210   m_data.clear();
0211   m_started = false;
0212   m_errors.clear();
0213   emit signalDone(this);
0214 }
0215 
0216 void ExecExternalFetcher::slotData() {
0217   m_data.append(m_process->readAllStandardOutput());
0218 }
0219 
0220 void ExecExternalFetcher::slotError() {
0221   GUI::CursorSaver cs(Qt::ArrowCursor);
0222   QString msg = QString::fromLocal8Bit(m_process->readAllStandardError());
0223   msg.prepend(source() + QLatin1String(": "));
0224   if(msg.endsWith(QChar::fromLatin1('\n'))) {
0225     msg.truncate(msg.length()-1);
0226   }
0227   myDebug() << msg;
0228   m_errors << msg;
0229 }
0230 
0231 void ExecExternalFetcher::slotProcessExited() {
0232 //  DEBUG_LINE;
0233   if(m_process->exitStatus() != QProcess::NormalExit || m_process->exitCode() != 0) {
0234     myDebug() << source() << ": process did not exit successfully";
0235     if(!m_errors.isEmpty()) {
0236       message(m_errors.join(QChar::fromLatin1('\n')), MessageHandler::Error);
0237     }
0238     stop();
0239     return;
0240   }
0241   if(!m_errors.isEmpty()) {
0242     message(m_errors.join(QChar::fromLatin1('\n')), MessageHandler::Warning);
0243   }
0244 
0245   if(m_data.isEmpty()) {
0246     myDebug() << source() << ": no data";
0247     stop();
0248     return;
0249   }
0250 
0251   const QString text = QString::fromUtf8(m_data.constData(), m_data.size());
0252 #if 0
0253   myWarning() << "Remove debug from ExecExternalFetcher.cpp";
0254   QFile f(QStringLiteral("/tmp/test-exec.txt"));
0255   if(f.open(QIODevice::WriteOnly)) {
0256     QTextStream t(&f);
0257     t.setCodec("UTF-8");
0258     t << m_data;
0259   }
0260   f.close();
0261 #endif
0262   Import::Format format = static_cast<Import::Format>(m_formatType > -1 ? m_formatType : Import::TellicoXML);
0263   Import::Importer* imp = nullptr;
0264   // only 4 formats re supported here
0265   switch(format) {
0266     case Import::TellicoXML:
0267       imp = new Import::TellicoImporter(text);
0268       break;
0269 
0270     case Import::Bibtex:
0271       imp = new Import::BibtexImporter(text);
0272       break;
0273 
0274     case Import::MODS:
0275       imp = new Import::XSLTImporter(text);
0276       {
0277         QString xsltFile = DataFileRegistry::self()->locate(QStringLiteral("mods2tellico.xsl"));
0278         if(!xsltFile.isEmpty()) {
0279           QUrl u = QUrl::fromLocalFile(xsltFile);
0280           static_cast<Import::XSLTImporter*>(imp)->setXSLTURL(u);
0281         } else {
0282           myWarning() << "unable to find mods2tellico.xml!";
0283           delete imp;
0284           imp = nullptr;
0285         }
0286       }
0287       break;
0288 
0289     case Import::RIS:
0290       imp = new Import::RISImporter(text);
0291       break;
0292 
0293     default:
0294       break;
0295   }
0296   if(!imp) {
0297     stop();
0298     return;
0299   }
0300 
0301   Data::CollPtr coll = imp->collection();
0302   if(!coll) {
0303     if(!imp->statusMessage().isEmpty()) {
0304       message(imp->statusMessage(), MessageHandler::Status);
0305     }
0306     myDebug() << source() << ": no collection pointer";
0307     delete imp;
0308     stop();
0309     return;
0310   }
0311 
0312   delete imp;
0313   if(coll->entryCount() == 0) {
0314 //    myDebug() << "no results";
0315     stop();
0316     return;
0317   }
0318 
0319   Data::EntryList entries = coll->entries();
0320   foreach(Data::EntryPtr entry, entries) {
0321     FetchResult* r = new FetchResult(this, entry);
0322     m_entries.insert(r->uid, entry);
0323     emit signalResultFound(r);
0324   }
0325   stop(); // be sure to call this
0326 }
0327 
0328 Tellico::Data::EntryPtr ExecExternalFetcher::fetchEntryHook(uint uid_) {
0329   return m_entries[uid_];
0330 }
0331 
0332 Tellico::Fetch::FetchRequest ExecExternalFetcher::updateRequest(Data::EntryPtr entry_) {
0333   if(!m_canUpdate) {
0334     return FetchRequest();
0335   }
0336 
0337   QStringList args = parseArguments(m_updateArgs);
0338   for(QStringList::Iterator it = args.begin(); it != args.end(); ++it) {
0339     Data::DerivedValue dv(*it);
0340     *it = dv.value(entry_, false);
0341   }
0342   return FetchRequest(ExecUpdate, args.join(FieldFormat::rowDelimiterString()));
0343 }
0344 
0345 Tellico::Fetch::ConfigWidget* ExecExternalFetcher::configWidget(QWidget* parent_) const {
0346   return new ExecExternalFetcher::ConfigWidget(parent_, this);
0347 }
0348 
0349 QString ExecExternalFetcher::defaultName() {
0350   return i18n("External Application");
0351 }
0352 
0353 QString ExecExternalFetcher::defaultIcon() {
0354   return QStringLiteral("application-x-executable");
0355 }
0356 
0357 ExecExternalFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const ExecExternalFetcher* fetcher_/*=0*/)
0358     : Fetch::ConfigWidget(parent_), m_deleteOnRemove(false) {
0359   QGridLayout* l = new QGridLayout(optionsWidget());
0360   l->setSpacing(4);
0361   l->setColumnStretch(1, 10);
0362 
0363   int row = -1;
0364 
0365   QLabel* label = new QLabel(i18n("Collection &type:"), optionsWidget());
0366   l->addWidget(label, ++row, 0);
0367   m_collCombo = new GUI::CollectionTypeCombo(optionsWidget());
0368   void (GUI::ComboBox::* activatedInt)(int) = &GUI::ComboBox::activated;
0369   connect(m_collCombo, activatedInt, this, &ConfigWidget::slotSetModified);
0370   l->addWidget(m_collCombo, row, 1);
0371   QString w = i18n("Set the collection type of the data returned from the external application.");
0372   label->setWhatsThis(w);
0373   m_collCombo->setWhatsThis(w);
0374   label->setBuddy(m_collCombo);
0375 
0376   label = new QLabel(i18n("&Result type: "), optionsWidget());
0377   l->addWidget(label, ++row, 0);
0378   m_formatCombo = new GUI::ComboBox(optionsWidget());
0379   m_formatCombo->addItem(QStringLiteral("Tellico"), Import::TellicoXML);
0380   m_formatCombo->addItem(QStringLiteral("Bibtex"), Import::Bibtex);
0381   m_formatCombo->addItem(QStringLiteral("MODS"), Import::MODS);
0382   m_formatCombo->addItem(QStringLiteral("RIS"), Import::RIS);
0383   connect(m_formatCombo, activatedInt, this, &ExecExternalFetcher::ConfigWidget::slotSetModified);
0384   l->addWidget(m_formatCombo, row, 1);
0385   w = i18n("Set the result type of the data returned from the external application.");
0386   label->setWhatsThis(w);
0387   m_formatCombo->setWhatsThis(w);
0388   label->setBuddy(m_formatCombo);
0389 #ifndef ENABLE_BTPARSE
0390   // disable the option for bibtex
0391   auto formatModel = qobject_cast<const QStandardItemModel*>(m_formatCombo->model());
0392   auto matchList = formatModel->match(formatModel->index(0, 0), Qt::UserRole, Import::Bibtex);
0393   if(!matchList.isEmpty()) {
0394     auto item = formatModel->itemFromIndex(matchList.front());
0395     item->setEnabled(false);
0396   }
0397 #endif
0398 
0399   label = new QLabel(i18n("Application &path: "), optionsWidget());
0400   l->addWidget(label, ++row, 0);
0401   m_pathEdit = new KUrlRequester(optionsWidget());
0402   connect(m_pathEdit, &KUrlRequester::textChanged, this, &ConfigWidget::slotSetModified);
0403   l->addWidget(m_pathEdit, row, 1);
0404   w = i18n("Set the path of the application to run that should output a valid Tellico data file.");
0405   label->setWhatsThis(w);
0406   m_pathEdit->setWhatsThis(w);
0407   label->setBuddy(m_pathEdit);
0408 
0409   w = i18n("Select the search keys supported by the data source.");
0410   // in this string, the %1 is not a placeholder, it's an example
0411   QString w2 = i18n("Add any arguments that may be needed. <b>%1</b> will be replaced by the search term."); // krazy:exclude=i18ncheckarg
0412   QGroupBox* gbox = new QGroupBox(i18n("Arguments"), optionsWidget());
0413   ++row;
0414   l->addWidget(gbox, row, 0, 1, 2);
0415   QGridLayout* gridLayout = new QGridLayout(gbox);
0416   gridLayout->setSpacing(2);
0417   row = -1;
0418   const Fetch::KeyMap keyMap = Fetch::Manager::self()->keyMap();
0419   for(Fetch::KeyMap::ConstIterator it = keyMap.begin(); it != keyMap.end(); ++it) {
0420     FetchKey key = it.key();
0421     if(key == Raw) {
0422       continue;
0423     }
0424     QCheckBox* cb = new QCheckBox(it.value(), gbox);
0425     gridLayout->addWidget(cb, ++row, 0);
0426     m_cbDict.insert(key, cb);
0427     GUI::LineEdit* le = new GUI::LineEdit(gbox);
0428     le->setPlaceholderText(QStringLiteral("%1")); // for example
0429     le->completionObject()->addItem(QStringLiteral("%1"));
0430     gridLayout->addWidget(le, row, 1);
0431     m_leDict.insert(key, le);
0432     if(fetcher_ && fetcher_->m_args.contains(key)) {
0433       cb->setChecked(true);
0434       le->setEnabled(true);
0435       le->setText(fetcher_->m_args.value(key));
0436     } else {
0437       cb->setChecked(false);
0438       le->setEnabled(false);
0439     }
0440     connect(cb, &QAbstractButton::toggled, le, &QWidget::setEnabled);
0441     cb->setWhatsThis(w);
0442     le->setWhatsThis(w2);
0443   }
0444   m_cbUpdate = new QCheckBox(i18n("Update"), gbox);
0445   gridLayout->addWidget(m_cbUpdate, ++row, 0);
0446   m_leUpdate = new GUI::LineEdit(gbox);
0447   m_leUpdate->setPlaceholderText(QStringLiteral("%{title}")); // for example
0448   m_leUpdate->completionObject()->addItem(QStringLiteral("%{title}"));
0449   m_leUpdate->completionObject()->addItem(QStringLiteral("%{isbn}"));
0450   gridLayout->addWidget(m_leUpdate, row, 1);
0451   /* TRANSLATORS: Do not translate %{author}. */
0452   w2 = i18n("<p>Enter the arguments which should be used to search for available updates to an entry.</p><p>"
0453            "The format is the same as for fields with derived values, where field names "
0454            "are contained inside braces, such as <i>%{author}</i>. See the documentation for details.</p>");
0455   m_cbUpdate->setWhatsThis(w);
0456   m_leUpdate->setWhatsThis(w2);
0457   if(fetcher_ && fetcher_->m_canUpdate) {
0458     m_cbUpdate->setChecked(true);
0459     m_leUpdate->setEnabled(true);
0460     m_leUpdate->setText(fetcher_->m_updateArgs);
0461   } else {
0462     m_cbUpdate->setChecked(false);
0463     m_leUpdate->setEnabled(false);
0464   }
0465   connect(m_cbUpdate, &QAbstractButton::toggled, m_leUpdate, &QWidget::setEnabled);
0466 
0467   l->setRowStretch(++row, 1);
0468 
0469   if(fetcher_) {
0470     m_pathEdit->setUrl(QUrl::fromLocalFile(fetcher_->m_path));
0471     m_newStuffName = fetcher_->m_newStuffName;
0472   }
0473   if(fetcher_ && fetcher_->m_collType > -1) {
0474     m_collCombo->setCurrentType(fetcher_->m_collType);
0475   } else {
0476     m_collCombo->setCurrentType(Data::Collection::Book);
0477   }
0478   if(fetcher_ && fetcher_->m_formatType > -1) {
0479     m_formatCombo->setCurrentData(fetcher_->m_formatType);
0480   } else {
0481     m_formatCombo->setCurrentData(Import::TellicoXML);
0482   }
0483   m_deleteOnRemove = fetcher_ && fetcher_->m_deleteOnRemove;
0484   KAcceleratorManager::manage(optionsWidget());
0485 }
0486 
0487 ExecExternalFetcher::ConfigWidget::~ConfigWidget() {
0488 }
0489 
0490 void ExecExternalFetcher::ConfigWidget::readConfig(const KConfigGroup& config_) {
0491   m_pathEdit->setUrl(QUrl::fromLocalFile(config_.readPathEntry("ExecPath", QString())));
0492   QList<int> argKeys = config_.readEntry("ArgumentKeys", QList<int>());
0493   QStringList argValues = config_.readEntry("Arguments", QStringList());
0494   if(argKeys.count() != argValues.count()) {
0495     myWarning() << "unequal number of arguments and keys";
0496   }
0497   int n = qMin(argKeys.count(), argValues.count());
0498   QMap<FetchKey, QString> args;
0499   for(int i = 0; i < n; ++i) {
0500     args[static_cast<FetchKey>(argKeys[i])] = argValues[i];
0501   }
0502   for(QList<int>::Iterator it = argKeys.begin(); it != argKeys.end(); ++it) {
0503     if(*it == Raw) {
0504       continue;
0505     }
0506     FetchKey key = static_cast<FetchKey>(*it);
0507     QCheckBox* cb = m_cbDict[key];
0508     QLineEdit* le = m_leDict[key];
0509     if(cb && le) {
0510       if(args.contains(key)) {
0511         cb->setChecked(true);
0512         le->setEnabled(true);
0513         le->setText(args[key]);
0514       } else {
0515         cb->setChecked(false);
0516         le->setEnabled(false);
0517         le->clear();
0518       }
0519     }
0520   }
0521 
0522   if(config_.hasKey("UpdateArgs")) {
0523     m_cbUpdate->setChecked(true);
0524     m_leUpdate->setEnabled(true);
0525     m_leUpdate->setText(config_.readEntry("UpdateArgs"));
0526   } else {
0527     m_cbUpdate->setChecked(false);
0528     m_leUpdate->setEnabled(false);
0529     m_leUpdate->clear();
0530   }
0531 
0532   int collType = config_.readEntry("CollectionType", -1);
0533   m_collCombo->setCurrentType(collType);
0534 
0535   int formatType = config_.readEntry("FormatType", -1);
0536   m_formatCombo->setCurrentData(static_cast<Import::Format>(formatType));
0537   m_deleteOnRemove = config_.readEntry("DeleteOnRemove", false);
0538   m_name = config_.readEntry("Name");
0539   m_newStuffName = config_.readEntry("NewStuffName");
0540 }
0541 
0542 void ExecExternalFetcher::ConfigWidget::saveConfigHook(KConfigGroup& config_) {
0543   QUrl u = m_pathEdit->url();
0544   if(!u.isEmpty()) {
0545     config_.writePathEntry("ExecPath", u.path());
0546   }
0547   QList<int> keys;
0548   QStringList args;
0549   QHash<int, QCheckBox*>::const_iterator it = m_cbDict.constBegin();
0550   for( ; it != m_cbDict.constEnd(); ++it) {
0551     if(it.value()->isChecked()) {
0552       keys << it.key();
0553       args << m_leDict[it.key()]->text();
0554     }
0555   }
0556   config_.writeEntry("ArgumentKeys", keys);
0557   config_.writeEntry("Arguments", args);
0558 
0559   if(m_cbUpdate->isChecked()) {
0560     config_.writeEntry("UpdateArgs", m_leUpdate->text());
0561   } else {
0562     config_.deleteEntry("UpdateArgs");
0563   }
0564 
0565   config_.writeEntry("CollectionType", m_collCombo->currentType());
0566   config_.writeEntry("FormatType", m_formatCombo->currentData().toInt());
0567   config_.writeEntry("DeleteOnRemove", m_deleteOnRemove);
0568   if(!m_newStuffName.isEmpty()) {
0569     config_.writeEntry("NewStuffName", m_newStuffName);
0570   }
0571 }
0572 
0573 void ExecExternalFetcher::ConfigWidget::removed() {
0574   if(!m_deleteOnRemove) {
0575     return;
0576   }
0577   if(!m_newStuffName.isEmpty()) {
0578     NewStuff::Manager::self()->removeScript(m_newStuffName);
0579   }
0580 }
0581 
0582 QString ExecExternalFetcher::ConfigWidget::preferredName() const {
0583   return m_name.isEmpty() ? ExecExternalFetcher::defaultName() : m_name;
0584 }