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 }