File indexing completed on 2024-05-19 16:18:43

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