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

0001 /***************************************************************************
0002     Copyright (C) 2003-2021 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 <config.h>
0026 
0027 #include "fetchmanager.h"
0028 #include "configwidget.h"
0029 #include "messagehandler.h"
0030 #include "../entry.h"
0031 #include "../collection.h"
0032 #include "../utils/tellico_utils.h"
0033 #include "../tellico_debug.h"
0034 
0035 #ifdef HAVE_YAZ
0036 #include "z3950fetcher.h"
0037 #endif
0038 #include "srufetcher.h"
0039 #include "execexternalfetcher.h"
0040 
0041 #include <KLocalizedString>
0042 #include <KIconLoader>
0043 #include <KIO/Job>
0044 #include <KSharedConfig>
0045 
0046 #include <QFileInfo>
0047 #include <QDir>
0048 #include <QTemporaryFile>
0049 
0050 #define LOAD_ICON(name, group, size) \
0051   KIconLoader::global()->loadIcon(name, static_cast<KIconLoader::Group>(group), size_)
0052 
0053 using namespace Tellico;
0054 using Tellico::Fetch::Manager;
0055 
0056 Tellico::Fetch::Manager* Tellico::Fetch::Manager::self() {
0057   static Manager self;
0058   return &self;
0059 }
0060 
0061 Manager::Manager() : QObject(), m_currentFetcherIndex(-1), m_messager(new ManagerMessage()),
0062                      m_count(0), m_loadDefaults(false) {
0063   // no need to load fetchers since the initializer does it for us
0064 
0065 //  m_keyMap.insert(FetchFirst, QString());
0066   m_keyMap.insert(Title,      i18n("Title"));
0067   m_keyMap.insert(Person,     i18n("Person"));
0068   m_keyMap.insert(ISBN,       i18n("ISBN"));
0069   m_keyMap.insert(UPC,        i18n("UPC/EAN"));
0070   m_keyMap.insert(Keyword,    i18n("Keyword"));
0071   m_keyMap.insert(DOI,        i18n("DOI"));
0072   m_keyMap.insert(ArxivID,    i18n("arXiv ID"));
0073   m_keyMap.insert(PubmedID,   i18n("PubMed ID"));
0074   m_keyMap.insert(LCCN,       i18n("LCCN"));
0075   m_keyMap.insert(Raw,        i18n("Raw Query"));
0076 //  m_keyMap.insert(FetchLast,  QString());
0077 }
0078 
0079 Manager::~Manager() {
0080   delete m_messager;
0081 }
0082 
0083 void Manager::registerFunction(int type_, const FetcherFunction& func_) {
0084   functionRegistry.insert(type_, func_);
0085 }
0086 
0087 void Manager::loadFetchers() {
0088   m_fetchers.clear();
0089   m_uuidHash.clear();
0090 
0091   KSharedConfigPtr config = KSharedConfig::openConfig();
0092   if(config->hasGroup(QStringLiteral("Data Sources"))) {
0093     KConfigGroup configGroup(config, QStringLiteral("Data Sources"));
0094     int nSources = configGroup.readEntry("Sources Count", 0);
0095     for(int i = 0; i < nSources; ++i) {
0096       QString group = QStringLiteral("Data Source %1").arg(i);
0097       Fetcher::Ptr f = createFetcher(config, group);
0098       if(f) {
0099         addFetcher(f);
0100       }
0101     }
0102     m_loadDefaults = false;
0103   } else { // add default sources
0104     m_fetchers = defaultFetchers();
0105     m_loadDefaults = true;
0106   }
0107 }
0108 
0109 void Manager::addFetcher(Fetch::Fetcher::Ptr fetcher_) {
0110   Q_ASSERT(fetcher_);
0111   if(fetcher_) {
0112     m_fetchers.append(fetcher_);
0113     if(!fetcher_->messageHandler()) {
0114       fetcher_->setMessageHandler(m_messager);
0115     }
0116     m_uuidHash.insert(fetcher_->uuid(), fetcher_);
0117   }
0118 }
0119 
0120 const Tellico::Fetch::FetcherVec& Manager::fetchers() {
0121   if(m_fetchers.isEmpty()) {
0122     loadFetchers();
0123   }
0124   return m_fetchers;
0125 }
0126 
0127 Tellico::Fetch::FetcherVec Manager::fetchers(int type_) {
0128   FetcherVec vec;
0129   foreach(Fetcher::Ptr fetcher, fetchers()) {
0130     if(fetcher->canFetch(type_)) {
0131       vec.append(fetcher);
0132     }
0133   }
0134   return vec;
0135 }
0136 
0137 Tellico::Fetch::Fetcher::Ptr Manager::fetcherByUuid(const QString& uuid_) {
0138   return m_uuidHash.contains(uuid_) ? m_uuidHash[uuid_] : Fetcher::Ptr();
0139 }
0140 
0141 Tellico::Fetch::KeyMap Manager::keyMap(const QString& source_) {
0142   // an empty string means return all
0143   if(source_.isEmpty()) {
0144     return m_keyMap;
0145   }
0146 
0147   // assume there's only one fetcher match
0148   Fetcher::Ptr foundFetcher;
0149   foreach(Fetcher::Ptr fetcher, fetchers()) {
0150     if(source_ == fetcher->source()) {
0151       foundFetcher = fetcher;
0152       break;
0153     }
0154   }
0155   if(!foundFetcher) {
0156     myWarning() << "no fetcher found!";
0157     return KeyMap();
0158   }
0159 
0160   KeyMap map;
0161   for(KeyMap::ConstIterator it = m_keyMap.constBegin(); it != m_keyMap.constEnd(); ++it) {
0162     if(foundFetcher->canSearch(it.key())) {
0163       map.insert(it.key(), it.value());
0164     }
0165   }
0166   return map;
0167 }
0168 
0169 void Manager::startSearch(const QString& source_, Tellico::Fetch::FetchKey key_, const QString& value_, Tellico::Data::Collection::Type collType_) {
0170   if(value_.isEmpty()) {
0171     emit signalDone();
0172     return;
0173   }
0174 
0175   FetchRequest request(collType_, key_, value_);
0176 
0177   // assume there's only one fetcher match
0178   int i = 0;
0179   m_currentFetcherIndex = -1;
0180   foreach(Fetcher::Ptr fetcher, fetchers()) {
0181     if(source_ == fetcher->source()) {
0182       ++m_count; // Fetcher::search() might emit done(), so increment before calling search()
0183       connect(fetcher.data(), &Fetcher::signalResultFound,
0184               this, &Manager::slotResultFound);
0185       connect(fetcher.data(), &Fetcher::signalDone,
0186               this, &Manager::slotFetcherDone);
0187       myLog() << "Starting search - source:" << source_ << "value:" << value_ << "key:" << key_;
0188       fetcher->startSearch(request);
0189       m_currentFetcherIndex = i;
0190       break;
0191     }
0192     ++i;
0193   }
0194 }
0195 
0196 void Manager::continueSearch() {
0197   if(m_currentFetcherIndex < 0 || m_currentFetcherIndex >= static_cast<int>(m_fetchers.count())) {
0198     myDebug() << "can't continue!";
0199     emit signalDone();
0200     return;
0201   }
0202   Fetcher::Ptr fetcher = m_fetchers[m_currentFetcherIndex];
0203   if(fetcher && fetcher->hasMoreResults()) {
0204     ++m_count;
0205     connect(fetcher.data(), &Fetcher::signalResultFound,
0206             this, &Manager::slotResultFound);
0207     connect(fetcher.data(), &Fetcher::signalDone,
0208             this, &Manager::slotFetcherDone);
0209     myLog() << "Continuing search - source:" << fetcher->source();
0210     fetcher->continueSearch();
0211   } else {
0212     emit signalDone();
0213   }
0214 }
0215 
0216 bool Manager::hasMoreResults() const {
0217   if(m_currentFetcherIndex < 0 || m_currentFetcherIndex >= static_cast<int>(m_fetchers.count())) {
0218     return false;
0219   }
0220   Fetcher::Ptr fetcher = m_fetchers[m_currentFetcherIndex];
0221   return fetcher && fetcher->hasMoreResults();
0222 }
0223 
0224 void Manager::stop() {
0225   foreach(Fetcher::Ptr fetcher, m_fetchers) {
0226     if(fetcher->isSearching()) {
0227       fetcher->stop();
0228       fetcher->saveConfig();
0229     }
0230   }
0231   if(m_count != 0) {
0232     myDebug() << "count should be 0!";
0233   }
0234   m_count = 0;
0235 }
0236 
0237 void Manager::slotResultFound(Tellico::Fetch::FetchResult* result_) {
0238   Q_ASSERT(result_);
0239   myLog() << "Search result - source:" << result_->fetcher()->source() << "result:" << result_->title;
0240   Q_EMIT signalResultFound(result_);
0241 }
0242 
0243 void Manager::slotFetcherDone(Tellico::Fetch::Fetcher* fetcher_) {
0244   Q_ASSERT(fetcher_);
0245   myLog() << "Search done - source:" << fetcher_->source();
0246   fetcher_->disconnect(); // disconnect all signals
0247   fetcher_->saveConfig();
0248   --m_count;
0249   if(m_count <= 0) {
0250     emit signalDone();
0251   }
0252 }
0253 
0254 bool Manager::canFetch(Tellico::Data::Collection::Type collType_) const {
0255   foreach(Fetcher::Ptr fetcher, m_fetchers) {
0256     if(fetcher->canFetch(collType_)) {
0257       return true;
0258     }
0259   }
0260   return false;
0261 }
0262 
0263 Tellico::Fetch::Fetcher::Ptr Manager::createFetcher(KSharedConfigPtr config_, const QString& group_) {
0264   if(!config_->hasGroup(group_)) {
0265     myDebug() << "no config group for " << group_;
0266     return Fetcher::Ptr();
0267   }
0268 
0269   KConfigGroup config(config_, group_);
0270 
0271   int fetchType = config.readEntry("Type", int(Fetch::Unknown));
0272   if(fetchType == Fetch::Unknown) {
0273     myDebug() << "unknown type " << fetchType << ", skipping";
0274     return Fetcher::Ptr();
0275   }
0276 
0277   // special case: the BoardGameGeek fetcher was originally implemented as a Ruby script
0278   // now, it's available with an XML API, so prefer the new version
0279   // so check for fetcher version and switch to the XML if version is missing or lower
0280   if(fetchType == Fetch::ExecExternal &&
0281      config.readPathEntry("ExecPath", QString()).endsWith(QLatin1String("boardgamegeek.rb"))) {
0282     KConfigGroup generalConfig(config_, QStringLiteral("General Options"));
0283     if(generalConfig.readEntry("FetchVersion", 0) < 1) {
0284       fetchType = Fetch::BoardGameGeek;
0285       generalConfig.writeEntry("FetchVersion", 1);
0286     }
0287   }
0288   // special case: the Bedetheque fetcher was originally implemented as a Python script
0289   // now, it's available as a builtin data source, so prefer the new version
0290   // so check for fetcher version and switch to the newer if version is missing or lower
0291   if(fetchType == Fetch::ExecExternal &&
0292      config.readPathEntry("ExecPath", QString()).endsWith(QStringLiteral("bedetheque.py"))) {
0293     KConfigGroup generalConfig(config_, QStringLiteral("General Options"));
0294     if(generalConfig.readEntry("FetchVersion", 0) < 2) {
0295       fetchType = Fetch::Bedetheque;
0296       generalConfig.writeEntry("FetchVersion", 2);
0297     }
0298   }
0299 
0300   Fetcher::Ptr f;
0301   if(functionRegistry.contains(fetchType)) {
0302     f = functionRegistry.value(fetchType).create(this);
0303     f->readConfig(config);
0304   }
0305   return f;
0306 }
0307 
0308 #define FETCHER_ADD(type) \
0309   do { \
0310     if(functionRegistry.contains(type)) { \
0311       vec.append(functionRegistry.value(type).create(this)); \
0312     } \
0313   } while(false)
0314 
0315 // static
0316 Tellico::Fetch::FetcherVec Manager::defaultFetchers() {
0317   FetcherVec vec;
0318 // books
0319   FETCHER_ADD(ISBNdb);
0320   FETCHER_ADD(OpenLibrary);
0321   FETCHER_ADD(GoogleBook);
0322   if(functionRegistry.contains(SRU)) {
0323     vec.append(SRUFetcher::libraryOfCongress(this));
0324   }
0325 // comic books
0326   FETCHER_ADD(Bedetheque);
0327   FETCHER_ADD(ComicVine);
0328 // bibliographic
0329   FETCHER_ADD(Arxiv);
0330   FETCHER_ADD(GoogleScholar);
0331   FETCHER_ADD(BiblioShare);
0332   FETCHER_ADD(DBLP);
0333   FETCHER_ADD(HathiTrust);
0334 // music
0335   FETCHER_ADD(MusicBrainz);
0336   FETCHER_ADD(Itunes);
0337 // video games
0338   FETCHER_ADD(TheGamesDB);
0339   FETCHER_ADD(IGDB);
0340   FETCHER_ADD(VNDB);
0341   FETCHER_ADD(VideoGameGeek);
0342   FETCHER_ADD(VGCollect);
0343 // board games
0344   FETCHER_ADD(BoardGameGeek);
0345 // movies
0346   FETCHER_ADD(TheMovieDB);
0347   FETCHER_ADD(TVmaze);
0348   FETCHER_ADD(IMDB);
0349 // coins and stamps
0350   FETCHER_ADD(Colnect);
0351   FETCHER_ADD(Numista);
0352   QStringList langs = QLocale().uiLanguages();
0353   if(!langs.isEmpty() && langs.first().contains(QLatin1Char('-'))) {
0354     // I'm not sure QT always include two-letter locale codes
0355     langs << langs.first().section(QLatin1Char('-'), 0, 0);
0356   }
0357 // only add IBS if user includes italian
0358   if(langs.contains(QStringLiteral("it"))) {
0359     FETCHER_ADD(IBS);
0360   }
0361   if(langs.contains(QStringLiteral("es"))) {
0362     FETCHER_ADD(FilmAffinity);
0363   }
0364   if(langs.contains(QStringLiteral("fr"))) {
0365     FETCHER_ADD(DVDFr);
0366     FETCHER_ADD(Allocine);
0367   }
0368   if(langs.contains(QStringLiteral("ru"))) {
0369     FETCHER_ADD(KinoPoisk);
0370   }
0371   if(langs.contains(QStringLiteral("ua"))) {
0372     FETCHER_ADD(KinoTeatr);
0373   }
0374   if(langs.contains(QStringLiteral("de"))) {
0375     FETCHER_ADD(Kino);
0376   }
0377   if(langs.contains(QStringLiteral("cn"))) {
0378     FETCHER_ADD(Douban);
0379   }
0380   if(langs.contains(QStringLiteral("dk"))) {
0381     FETCHER_ADD(DBC);
0382   }
0383   return vec;
0384 }
0385 
0386 #undef FETCHER_ADD
0387 
0388 Tellico::Fetch::FetcherVec Manager::createUpdateFetchers(int collType_) {
0389   if(m_loadDefaults) {
0390     return fetchers(collType_);
0391   }
0392 
0393   FetcherVec vec;
0394   KConfigGroup config(KSharedConfig::openConfig(), "Data Sources");
0395   int nSources = config.readEntry("Sources Count", 0);
0396   for(int i = 0; i < nSources; ++i) {
0397     QString group = QStringLiteral("Data Source %1").arg(i);
0398     // needs the KConfig*
0399     Fetcher::Ptr fetcher = createFetcher(KSharedConfig::openConfig(), group);
0400     if(fetcher && fetcher->canFetch(collType_) && fetcher->canUpdate()) {
0401       vec.append(fetcher);
0402     }
0403   }
0404   return vec;
0405 }
0406 
0407 Tellico::Fetch::FetcherVec Manager::createUpdateFetchers(int collType_, Tellico::Fetch::FetchKey key_) {
0408   FetcherVec fetchers;
0409   // creates new fetchers
0410   FetcherVec allFetchers = createUpdateFetchers(collType_);
0411   foreach(Fetcher::Ptr fetcher, allFetchers) {
0412     if(fetcher->canSearch(key_)) {
0413       fetchers.append(fetcher);
0414     }
0415   }
0416   return fetchers;
0417 }
0418 
0419 Tellico::Fetch::Fetcher::Ptr Manager::createUpdateFetcher(int collType_, const QString& source_) {
0420   Fetcher::Ptr newFetcher;
0421   // creates new fetchers
0422   FetcherVec fetchers = createUpdateFetchers(collType_);
0423   foreach(Fetcher::Ptr fetcher, fetchers) {
0424     if(fetcher->source() == source_) {
0425       newFetcher = fetcher;
0426       break;
0427     }
0428   }
0429   return newFetcher;
0430 }
0431 
0432 void Manager::updateStatus(const QString& message_) {
0433   emit signalStatus(message_);
0434 }
0435 
0436 Tellico::Fetch::NameTypeHash Manager::nameTypeHash() {
0437   Fetch::NameTypeHash hash;
0438   FunctionRegistry::const_iterator it = functionRegistry.constBegin();
0439   while(it != functionRegistry.constEnd()) {
0440     hash.insert(functionRegistry.value(it.key()).name(), static_cast<Type>(it.key()));
0441     ++it;
0442   }
0443 
0444   // now find all the scripts distributed with tellico
0445   QStringList files = Tellico::locateAllFiles(QStringLiteral("tellico/data-sources/*.spec"));
0446   foreach(const QString& file, files) {
0447     KConfig spec(file, KConfig::SimpleConfig);
0448     KConfigGroup specConfig(&spec, QString());
0449     QString name = specConfig.readEntry("Name");
0450     if(name.isEmpty()) {
0451       myDebug() << "no name for" << file;
0452       continue;
0453     }
0454 
0455     bool enabled = specConfig.readEntry("Enabled", true);
0456     if(!enabled || !bundledScriptHasExecPath(file, specConfig)) { // no available exec
0457       continue;
0458     }
0459 
0460     hash.insert(name, ExecExternal);
0461     m_scriptMap.insert(name, file);
0462   }
0463   return hash;
0464 }
0465 
0466 // called when creating a new fetcher
0467 Tellico::Fetch::ConfigWidget* Manager::configWidget(QWidget* parent_, Tellico::Fetch::Type type_, const QString& name_) {
0468   ConfigWidget* w = nullptr;
0469   if(functionRegistry.contains(type_)) {
0470     w = functionRegistry.value(type_).configWidget(parent_);
0471   } else {
0472     myWarning() << "no widget defined for type =" << type_;
0473   }
0474   if(w && type_ == ExecExternal) {
0475     if(!name_.isEmpty() && m_scriptMap.contains(name_)) {
0476       // bundledScriptHasExecPath() actually needs to write the exec path
0477       // back to the config so the configWidget can read it. But if the spec file
0478       // is not readable, that doesn't work. So work around it with a copy to a temp file
0479       QTemporaryFile tmpFile;
0480       tmpFile.open();
0481       QUrl from = QUrl::fromLocalFile(m_scriptMap[name_]);
0482       QUrl to = QUrl::fromLocalFile(tmpFile.fileName());
0483       // have to overwrite since QTemporaryFile already created it
0484       KIO::Job* job = KIO::file_copy(from, to, -1, KIO::Overwrite);
0485       if(!job->exec()) {
0486         myDebug() << job->errorString();
0487       }
0488       KConfig spec(to.path(), KConfig::SimpleConfig);
0489       KConfigGroup specConfig(&spec, QString());
0490       // pass actual location of spec file
0491       if(name_ == specConfig.readEntry("Name") && bundledScriptHasExecPath(m_scriptMap[name_], specConfig)) {
0492         w->readConfig(specConfig);
0493       } else {
0494         myWarning() << "Can't read config file for " << to.path();
0495       }
0496     }
0497   }
0498 
0499   return w;
0500 }
0501 
0502 // static
0503 QString Manager::typeName(Tellico::Fetch::Type type_) {
0504   if(self()->functionRegistry.contains(type_)) {
0505     return self()->functionRegistry.value(type_).name();
0506   }
0507   myWarning() << "none found for" << type_;
0508   return QString();
0509 }
0510 
0511 QPixmap Manager::fetcherIcon(Tellico::Fetch::Fetcher* fetcher_, int group_, int size_) {
0512   Q_ASSERT(fetcher_);
0513   if(!fetcher_) {
0514     return QPixmap();
0515   }
0516   if(!fetcher_->icon().isEmpty()) {
0517     return LOAD_ICON(fetcher_->icon(), group_, size_);
0518   }
0519   if(fetcher_->type() == Fetch::Z3950) {
0520 #ifdef HAVE_YAZ
0521     const Fetch::Z3950Fetcher* f = static_cast<const Fetch::Z3950Fetcher*>(fetcher_);
0522     QUrl u;
0523     u.setScheme(QStringLiteral("http"));
0524     u.setHost(f->host());
0525     QString icon = Fetcher::favIcon(u);
0526     if(!icon.isEmpty()) {
0527       return LOAD_ICON(icon, group_, size_);
0528     }
0529 #endif
0530   } else
0531   if(fetcher_->type() == Fetch::ExecExternal) {
0532     const Fetch::ExecExternalFetcher* f = static_cast<const Fetch::ExecExternalFetcher*>(fetcher_);
0533     const QString p = f->execPath();
0534     QUrl u;
0535     if(p.contains(QStringLiteral("allocine"))) {
0536       u = QUrl(QStringLiteral("http://www.allocine.fr"));
0537     } else if(p.contains(QStringLiteral("ministerio_de_cultura"))) {
0538       u = QUrl(QStringLiteral("http://www.mcu.es"));
0539     } else if(p.contains(QStringLiteral("dark_horse_comics"))) {
0540       u = QUrl(QStringLiteral("http://www.darkhorse.com"));
0541     } else if(p.contains(QStringLiteral("boardgamegeek"))) {
0542       u = QUrl(QStringLiteral("http://www.boardgamegeek.com"));
0543     } else if(p.contains(QStringLiteral("supercat"))) {
0544       u = QUrl(QStringLiteral("https://evergreen-ils.org"));
0545     } else if(f->source().contains(QStringLiteral("amarok"), Qt::CaseInsensitive)) {
0546       return LOAD_ICON(QStringLiteral("amarok"), group_, size_);
0547     }
0548     if(!u.isEmpty() && u.isValid()) {
0549       QString icon = Fetcher::favIcon(u);
0550       if(!icon.isEmpty()) {
0551         return LOAD_ICON(icon, group_, size_);
0552       }
0553     }
0554   }
0555   return fetcherIcon(fetcher_->type(), group_, size_);
0556 }
0557 
0558 QPixmap Manager::fetcherIcon(Tellico::Fetch::Type type_, int group_, int size_) {
0559   QString name;
0560   if(self()->functionRegistry.contains(type_)) {
0561     name = self()->functionRegistry.value(type_).icon();
0562   } else {
0563     myWarning() << "no pixmap defined for type =" << type_;
0564   }
0565 
0566   if(name.isEmpty()) {
0567     // use default tellico application icon
0568     name = QStringLiteral("tellico");
0569   }
0570 
0571   QPixmap pix = KIconLoader::global()->loadIcon(name, static_cast<KIconLoader::Group>(group_),
0572                                                 size_, KIconLoader::DefaultState,
0573                                                 QStringList(), nullptr, true);
0574   if(pix.isNull()) {
0575     QIcon icon = QIcon::fromTheme(name);
0576     const int groupSize = KIconLoader::global()->currentSize(static_cast<KIconLoader::Group>(group_));
0577     size_ = size_ == 0 ? groupSize : size_;
0578     pix = icon.pixmap(size_, size_);
0579   }
0580   if(pix.isNull()) {
0581     pix = KIconLoader::global()->loadIcon(name, KIconLoader::Toolbar);
0582   }
0583   return pix;
0584 }
0585 
0586 Tellico::StringHash Manager::optionalFields(Fetch::Type type_) {
0587   if(self()->functionRegistry.contains(type_)) {
0588     return self()->functionRegistry.value(type_).optionalFields();
0589   }
0590   return StringHash();
0591 }
0592 
0593 bool Manager::bundledScriptHasExecPath(const QString& specFile_, KConfigGroup& config_) {
0594   // make sure ExecPath is set and executable
0595   // for the bundled scripts, either the exec name is not set, in which case it is the
0596   // name of the spec file, minus the .spec, or the exec is set, and is local to the dir
0597   // if not, look for it
0598   QFileInfo specInfo(specFile_);
0599   QString exec = config_.readPathEntry("ExecPath", QString());
0600   QFileInfo execInfo(exec);
0601   if(exec.isEmpty() || !execInfo.exists()) {
0602     exec = specInfo.canonicalPath() + QDir::separator() + specInfo.completeBaseName(); // remove ".spec"
0603   } else if(execInfo.isRelative()) {
0604     exec = specInfo.canonicalPath() + QDir::separator() + exec;
0605   } else if(!execInfo.isExecutable()) {
0606     myWarning() << "not executable:" << specFile_;
0607     return false;
0608   }
0609   execInfo.setFile(exec);
0610   if(!execInfo.exists() || !execInfo.isExecutable()) {
0611     myWarning() << "no exec file for" << specFile_;
0612     myWarning() << "exec =" << exec;
0613     return false; // we're not ok
0614   }
0615 
0616   config_.writePathEntry("ExecPath", exec);
0617   config_.sync(); // might be readonly, but that's ok
0618   return true;
0619 }