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