KNewStuff
engine.cpp
Go to the documentation of this file.
00001 /* 00002 knewstuff3/engine.cpp 00003 Copyright (c) 2007 Josef Spillner <spillner@kde.org> 00004 Copyright (C) 2007-2010 Frederik Gladhorn <gladhorn@kde.org> 00005 Copyright (c) 2009 Jeremy Whiting <jpwhiting@kde.org> 00006 Copyright (c) 2010 Matthias Fuchs <mat69@gmx.net> 00007 00008 This library is free software; you can redistribute it and/or 00009 modify it under the terms of the GNU Lesser General Public 00010 License as published by the Free Software Foundation; either 00011 version 2.1 of the License, or (at your option) any later version. 00012 00013 This library is distributed in the hope that it will be useful, 00014 but WITHOUT ANY WARRANTY; without even the implied warranty of 00015 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00016 Lesser General Public License for more details. 00017 00018 You should have received a copy of the GNU Lesser General Public 00019 License along with this library. If not, see <http://www.gnu.org/licenses/>. 00020 */ 00021 00022 #include "engine.h" 00023 00024 #include "entry.h" 00025 #include "core/installation.h" 00026 #include "core/xmlloader.h" 00027 #include "ui/imageloader.h" 00028 00029 #include <kaboutdata.h> 00030 #include <kconfig.h> 00031 #include <kconfiggroup.h> 00032 #include <kcomponentdata.h> 00033 #include <kdebug.h> 00034 #include <kstandarddirs.h> 00035 #include <kcodecs.h> 00036 #include <kprocess.h> 00037 #include <kshell.h> 00038 00039 #include <kio/job.h> 00040 #include <kmimetype.h> 00041 #include <krandom.h> 00042 #include <ktoolinvocation.h> 00043 00044 #include <QtCore/QTimer> 00045 #include <QtCore/QDir> 00046 #include <QtXml/qdom.h> 00047 #include <QtCore/Q_PID> 00048 00049 #if defined(Q_OS_WIN) 00050 #include <windows.h> 00051 #define _WIN32_IE 0x0500 00052 #include <shlobj.h> 00053 #endif 00054 00055 // libattica 00056 #include <attica/providermanager.h> 00057 00058 // own 00059 #include "attica/atticaprovider.h" 00060 #include "core/cache.h" 00061 #include "staticxml/staticxmlprovider.h" 00062 00063 using namespace KNS3; 00064 00065 Engine::Engine(QObject* parent) 00066 : QObject(parent) 00067 , m_initialized(false) 00068 , m_installation(new Installation) 00069 , m_searchTimer(new QTimer) 00070 , m_currentPage(-1) 00071 , m_pageSize(20) 00072 , m_numDataJobs(0) 00073 , m_numPictureJobs(0) 00074 , m_numInstallJobs(0) 00075 , m_atticaProviderManager(0) 00076 { 00077 m_searchTimer->setSingleShot(true); 00078 m_searchTimer->setInterval(1000); 00079 connect(m_searchTimer, SIGNAL(timeout()), SLOT(slotSearchTimerExpired())); 00080 connect(m_installation, SIGNAL(signalInstallationFinished()), this, SLOT(slotInstallationFinished())); 00081 connect(m_installation, SIGNAL(signalInstallationFailed(QString)), this, SLOT(slotInstallationFailed(QString))); 00082 00083 } 00084 00085 Engine::~Engine() 00086 { 00087 m_cache->writeRegistry(); 00088 delete m_atticaProviderManager; 00089 delete m_searchTimer; 00090 delete m_installation; 00091 } 00092 00093 bool Engine::init(const QString &configfile) 00094 { 00095 kDebug() << "Initializing KNS3::Engine from '" << configfile << "'"; 00096 00097 emit signalBusy(i18n("Initializing")); 00098 00099 KConfig conf(configfile); 00100 if (conf.accessMode() == KConfig::NoAccess) { 00101 emit signalError(i18n("Configuration file not found: \"%1\"", configfile)); 00102 kError() << "No knsrc file named '" << configfile << "' was found." << endl; 00103 return false; 00104 } 00105 // FIXME: accessMode() doesn't return NoAccess for non-existing files 00106 // - bug in kdecore? 00107 // - this needs to be looked at again until KConfig backend changes for KDE 4 00108 // the check below is a workaround 00109 if (KStandardDirs::locate("config", configfile).isEmpty()) { 00110 emit signalError(i18n("Configuration file not found: \"%1\"", configfile)); 00111 kError() << "No knsrc file named '" << configfile << "' was found." << endl; 00112 return false; 00113 } 00114 00115 KConfigGroup group; 00116 if (conf.hasGroup("KNewStuff3")) { 00117 kDebug() << "Loading KNewStuff3 config: " << configfile; 00118 group = conf.group("KNewStuff3"); 00119 } else if (conf.hasGroup("KNewStuff2")) { 00120 kDebug() << "Loading KNewStuff2 config: " << configfile; 00121 group = conf.group("KNewStuff2"); 00122 } else { 00123 emit signalError(i18n("Configuration file is invalid: \"%1\"", configfile)); 00124 kError() << "A knsrc file was found but it doesn't contain a KNewStuff3 section." << endl; 00125 return false; 00126 } 00127 00128 m_categories = group.readEntry("Categories", QStringList()); 00129 00130 kDebug() << "Categories: " << m_categories; 00131 m_providerFileUrl = group.readEntry("ProvidersUrl", QString()); 00132 m_applicationName = QFileInfo(KStandardDirs::locate("config", configfile)).baseName() + ':'; 00133 00134 // let installation read install specific config 00135 if (!m_installation->readConfig(group)) { 00136 return false; 00137 } 00138 00139 connect(m_installation, SIGNAL(signalEntryChanged(const KNS3::EntryInternal&)), SLOT(slotEntryChanged(const KNS3::EntryInternal&))); 00140 00141 m_cache = Cache::getCache(m_applicationName.split(':')[0]); 00142 connect(this, SIGNAL(signalEntryChanged(const KNS3::EntryInternal&)), m_cache.data(), SLOT(registerChangedEntry(const KNS3::EntryInternal&))); 00143 m_cache->readRegistry(); 00144 00145 m_initialized = true; 00146 00147 // load the providers 00148 loadProviders(); 00149 00150 return true; 00151 } 00152 00153 QStringList Engine::categories() const 00154 { 00155 return m_categories; 00156 } 00157 00158 QStringList Engine::categoriesFilter() const 00159 { 00160 return m_currentRequest.categories; 00161 } 00162 00163 void Engine::loadProviders() 00164 { 00165 if (m_providerFileUrl.isEmpty()) { 00166 // it would be nicer to move the attica stuff into its own class 00167 kDebug(550) << "Using OCS default providers"; 00168 Attica::ProviderManager* m_atticaProviderManager = new Attica::ProviderManager; 00169 connect(m_atticaProviderManager, SIGNAL(providerAdded(Attica::Provider)), this, SLOT(atticaProviderLoaded(Attica::Provider))); 00170 m_atticaProviderManager->loadDefaultProviders(); 00171 } else { 00172 kDebug(550) << "loading providers from " << m_providerFileUrl; 00173 emit signalBusy(i18n("Loading provider information")); 00174 00175 XmlLoader * loader = new XmlLoader(this); 00176 connect(loader, SIGNAL(signalLoaded(const QDomDocument&)), SLOT(slotProviderFileLoaded(const QDomDocument&))); 00177 connect(loader, SIGNAL(signalFailed()), SLOT(slotProvidersFailed())); 00178 00179 loader->load(KUrl(m_providerFileUrl)); 00180 } 00181 } 00182 00183 void Engine::slotProviderFileLoaded(const QDomDocument& doc) 00184 { 00185 kDebug() << "slotProvidersLoaded"; 00186 00187 bool isAtticaProviderFile = false; 00188 00189 // get each provider element, and create a provider object from it 00190 QDomElement providers = doc.documentElement(); 00191 00192 if (providers.tagName() == "providers") { 00193 isAtticaProviderFile = true; 00194 } else if (providers.tagName() != "ghnsproviders" && providers.tagName() != "knewstuffproviders") { 00195 kWarning(550) << "No document in providers.xml."; 00196 emit signalError(i18n("Could not load get hot new stuff providers from file: %1", m_providerFileUrl)); 00197 return; 00198 } 00199 00200 QDomElement n = providers.firstChildElement("provider"); 00201 while (!n.isNull()) { 00202 kDebug() << "Provider attributes: " << n.attribute("type"); 00203 00204 QSharedPointer<KNS3::Provider> provider; 00205 if (isAtticaProviderFile || n.attribute("type").toLower() == "rest") { 00206 provider = QSharedPointer<KNS3::Provider> (new AtticaProvider(m_categories)); 00207 } else { 00208 provider = QSharedPointer<KNS3::Provider> (new StaticXmlProvider); 00209 } 00210 00211 if (provider->setProviderXML(n)) { 00212 addProvider(provider); 00213 } else { 00214 emit signalError(i18n("Error initializing provider.")); 00215 } 00216 n = n.nextSiblingElement(); 00217 } 00218 emit signalBusy(i18n("Loading data")); 00219 } 00220 00221 void Engine::atticaProviderLoaded(const Attica::Provider& atticaProvider) 00222 { 00223 if (!atticaProvider.hasContentService()) { 00224 kDebug() << "Found provider: " << atticaProvider.baseUrl() << " but it does not support content"; 00225 return; 00226 } 00227 QSharedPointer<KNS3::Provider> provider = 00228 QSharedPointer<KNS3::Provider> (new AtticaProvider(atticaProvider, m_categories)); 00229 addProvider(provider); 00230 } 00231 00232 void Engine::addProvider(QSharedPointer<KNS3::Provider> provider) 00233 { 00234 m_providers.insert(provider->id(), provider); 00235 connect(provider.data(), SIGNAL(providerInitialized(KNS3::Provider*)), SLOT(providerInitialized(KNS3::Provider*))); 00236 connect(provider.data(), SIGNAL(loadingFinished(KNS3::Provider::SearchRequest, KNS3::EntryInternal::List)), 00237 SLOT(slotEntriesLoaded(KNS3::Provider::SearchRequest, KNS3::EntryInternal::List))); 00238 connect(provider.data(), SIGNAL(entryDetailsLoaded(KNS3::EntryInternal)), SLOT(slotEntryDetailsLoaded(KNS3::EntryInternal))); 00239 connect(provider.data(), SIGNAL(payloadLinkLoaded(const KNS3::EntryInternal&)), SLOT(downloadLinkLoaded(const KNS3::EntryInternal&))); 00240 connect(provider.data(), SIGNAL(signalError(QString)), this, SIGNAL(signalError(QString))); 00241 connect(provider.data(), SIGNAL(signalInformation(QString)), this, SIGNAL(signalIdle(QString))); 00242 } 00243 00244 void Engine::providerJobStarted ( KJob* job ) 00245 { 00246 emit jobStarted(job, i18n("Loading data from provider")); 00247 } 00248 00249 void Engine::slotProvidersFailed() 00250 { 00251 emit signalError(i18n("Loading of providers from file: %1 failed", m_providerFileUrl)); 00252 } 00253 00254 void Engine::providerInitialized(Provider* p) 00255 { 00256 kDebug() << "providerInitialized" << p->name(); 00257 p->setCachedEntries(m_cache->registryForProvider(p->id())); 00258 updateStatus(); 00259 00260 foreach (const QSharedPointer<KNS3::Provider> &p, m_providers) { 00261 if (!p->isInitialized()) { 00262 return; 00263 } 00264 } 00265 emit signalProvidersLoaded(); 00266 } 00267 00268 void Engine::slotEntriesLoaded(const KNS3::Provider::SearchRequest& request, KNS3::EntryInternal::List entries) 00269 { 00270 m_currentPage = qMax<int>(request.page, m_currentPage); 00271 kDebug() << "loaded page " << request.page << "current page" << m_currentPage; 00272 00273 if (request.sortMode == Provider::Updates) { 00274 emit signalUpdateableEntriesLoaded(entries); 00275 } else { 00276 m_cache->insertRequest(request, entries); 00277 emit signalEntriesLoaded(entries); 00278 } 00279 00280 --m_numDataJobs; 00281 updateStatus(); 00282 } 00283 00284 void Engine::reloadEntries() 00285 { 00286 emit signalResetView(); 00287 m_currentPage = -1; 00288 m_currentRequest.page = 0; 00289 m_numDataJobs = 0; 00290 00291 foreach (const QSharedPointer<KNS3::Provider> &p, m_providers) { 00292 if (p->isInitialized()) { 00293 if (m_currentRequest.sortMode == Provider::Installed) { 00294 // when asking for installed entries, never use the cache 00295 p->loadEntries(m_currentRequest); 00296 } else { 00297 // take entries from cache until there are no more 00298 EntryInternal::List cache = m_cache->requestFromCache(m_currentRequest); 00299 while (!cache.isEmpty()) { 00300 kDebug() << "From cache"; 00301 emit signalEntriesLoaded(cache); 00302 00303 m_currentPage = m_currentRequest.page; 00304 ++m_currentRequest.page; 00305 cache = m_cache->requestFromCache(m_currentRequest); 00306 } 00307 // if the cache was empty, request data from provider 00308 if (m_currentPage == -1) { 00309 kDebug() << "From provider"; 00310 p->loadEntries(m_currentRequest); 00311 00312 ++m_numDataJobs; 00313 updateStatus(); 00314 } 00315 } 00316 } 00317 } 00318 } 00319 00320 void Engine::setCategoriesFilter(const QStringList& categories) 00321 { 00322 m_currentRequest.categories = categories; 00323 reloadEntries(); 00324 } 00325 00326 void Engine::setSortMode(Provider::SortMode mode) 00327 { 00328 if (m_currentRequest.sortMode != mode) { 00329 m_currentRequest.page = -1; 00330 } 00331 m_currentRequest.sortMode = mode; 00332 reloadEntries(); 00333 } 00334 00335 void Engine::setSearchTerm(const QString& searchString) 00336 { 00337 m_searchTimer->stop(); 00338 m_currentRequest.searchTerm = searchString; 00339 EntryInternal::List cache = m_cache->requestFromCache(m_currentRequest); 00340 if (!cache.isEmpty()) { 00341 reloadEntries(); 00342 } else { 00343 m_searchTimer->start(); 00344 } 00345 } 00346 00347 void Engine::slotSearchTimerExpired() 00348 { 00349 reloadEntries(); 00350 } 00351 00352 void Engine::requestMoreData() 00353 { 00354 kDebug() << "Get more data! current page: " << m_currentPage << " requested: " << m_currentRequest.page; 00355 00356 if (m_currentPage < m_currentRequest.page) { 00357 return; 00358 } 00359 00360 m_currentRequest.page++; 00361 doRequest(); 00362 } 00363 00364 void Engine::requestData(int page, int pageSize) 00365 { 00366 m_currentRequest.page = page; 00367 m_currentRequest.pageSize = pageSize; 00368 doRequest(); 00369 } 00370 00371 void Engine::doRequest() 00372 { 00373 foreach (const QSharedPointer<KNS3::Provider> &p, m_providers) { 00374 if (p->isInitialized()) { 00375 p->loadEntries(m_currentRequest); 00376 ++m_numDataJobs; 00377 updateStatus(); 00378 } 00379 } 00380 } 00381 00382 void Engine::install(KNS3::EntryInternal entry, int linkId) 00383 { 00384 if (entry.status() == Entry::Updateable) { 00385 entry.setStatus(Entry::Updating); 00386 } else { 00387 entry.setStatus(Entry::Installing); 00388 } 00389 emit signalEntryChanged(entry); 00390 00391 kDebug() << "Install " << entry.name() 00392 << " from: " << entry.providerId(); 00393 QSharedPointer<Provider> p = m_providers.value(entry.providerId()); 00394 if (p) { 00395 p->loadPayloadLink(entry, linkId); 00396 00397 ++m_numInstallJobs; 00398 updateStatus(); 00399 } 00400 } 00401 00402 void Engine::slotInstallationFinished() 00403 { 00404 --m_numInstallJobs; 00405 updateStatus(); 00406 } 00407 00408 void Engine::slotInstallationFailed(const QString& message) 00409 { 00410 --m_numInstallJobs; 00411 emit signalError(message); 00412 } 00413 00414 void Engine::slotEntryDetailsLoaded(const KNS3::EntryInternal& entry) 00415 { 00416 emit signalEntryDetailsLoaded(entry); 00417 } 00418 00419 void Engine::downloadLinkLoaded(const KNS3::EntryInternal& entry) 00420 { 00421 m_installation->install(entry); 00422 } 00423 00424 void Engine::uninstall(KNS3::EntryInternal entry) 00425 { 00426 // FIXME: change the status? 00427 entry.setStatus(Entry::Installing); 00428 emit signalEntryChanged(entry); 00429 m_installation->uninstall(entry); 00430 } 00431 00432 void Engine::loadDetails(const KNS3::EntryInternal &entry) 00433 { 00434 QSharedPointer<Provider> p = m_providers.value(entry.providerId()); 00435 p->loadEntryDetails(entry); 00436 } 00437 00438 void Engine::loadPreview(const KNS3::EntryInternal& entry, EntryInternal::PreviewType type) 00439 { 00440 kDebug() << "START preview: " << entry.name() << type; 00441 ImageLoader* l = new ImageLoader(entry, type, this); 00442 connect(l, SIGNAL(signalPreviewLoaded(KNS3::EntryInternal,KNS3::EntryInternal::PreviewType)), this, SLOT(slotPreviewLoaded(KNS3::EntryInternal,KNS3::EntryInternal::PreviewType))); 00443 l->start(); 00444 ++m_numPictureJobs; 00445 updateStatus(); 00446 } 00447 00448 void Engine::slotPreviewLoaded(const KNS3::EntryInternal& entry, EntryInternal::PreviewType type) 00449 { 00450 kDebug() << "FINISH preview: " << entry.name() << type; 00451 emit signalEntryPreviewLoaded(entry, type); 00452 --m_numPictureJobs; 00453 updateStatus(); 00454 } 00455 00456 void Engine::contactAuthor(const EntryInternal &entry) 00457 { 00458 if (!entry.author().email().isEmpty()) { 00459 // invoke mail with the address of the author 00460 KToolInvocation::invokeMailer(entry.author().email(), i18n("Re: %1", entry.name())); 00461 } else if (!entry.author().homepage().isEmpty()) { 00462 KToolInvocation::invokeBrowser(entry.author().homepage()); 00463 } 00464 } 00465 00466 void Engine::slotEntryChanged(const KNS3::EntryInternal& entry) 00467 { 00468 emit signalEntryChanged(entry); 00469 } 00470 00471 bool Engine::userCanVote(const EntryInternal& entry) 00472 { 00473 QSharedPointer<Provider> p = m_providers.value(entry.providerId()); 00474 return p->userCanVote(); 00475 } 00476 00477 void Engine::vote(const EntryInternal& entry, uint rating) 00478 { 00479 QSharedPointer<Provider> p = m_providers.value(entry.providerId()); 00480 p->vote(entry, rating); 00481 } 00482 00483 bool Engine::userCanBecomeFan(const EntryInternal& entry) 00484 { 00485 QSharedPointer<Provider> p = m_providers.value(entry.providerId()); 00486 return p->userCanBecomeFan(); 00487 } 00488 00489 void Engine::becomeFan(const EntryInternal& entry) 00490 { 00491 QSharedPointer<Provider> p = m_providers.value(entry.providerId()); 00492 p->becomeFan(entry); 00493 } 00494 00495 void Engine::updateStatus() 00496 { 00497 if (m_numDataJobs > 0) { 00498 emit signalBusy(i18n("Loading data")); 00499 } else if (m_numPictureJobs > 0) { 00500 emit signalBusy(i18np("Loading one preview", "Loading %1 previews", m_numPictureJobs)); 00501 } else if (m_numInstallJobs > 0) { 00502 emit signalBusy(i18n("Installing")); 00503 } else { 00504 emit signalIdle(QString()); 00505 } 00506 } 00507 00508 void Engine::checkForUpdates() 00509 { 00510 foreach(QSharedPointer<Provider> p, m_providers) { 00511 Provider::SearchRequest request(KNS3::Provider::Updates); 00512 p->loadEntries(request); 00513 } 00514 } 00515 00516 #include "engine.moc"
KDE 4.6 API Reference