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 }