File indexing completed on 2024-05-12 16:45:49

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