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_cache(0) 00070 , m_searchTimer(new QTimer) 00071 , m_currentPage(-1) 00072 , m_pageSize(20) 00073 , m_numDataJobs(0) 00074 , m_numPictureJobs(0) 00075 , m_numInstallJobs(0) 00076 , m_atticaProviderManager(0) 00077 { 00078 m_searchTimer->setSingleShot(true); 00079 m_searchTimer->setInterval(1000); 00080 connect(m_searchTimer, SIGNAL(timeout()), SLOT(slotSearchTimerExpired())); 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 if (m_cache) { 00089 m_cache->writeRegistry(); 00090 } 00091 delete m_atticaProviderManager; 00092 delete m_searchTimer; 00093 delete m_installation; 00094 } 00095 00096 bool Engine::init(const QString &configfile) 00097 { 00098 kDebug() << "Initializing KNS3::Engine from '" << configfile << "'"; 00099 00100 emit signalBusy(i18n("Initializing")); 00101 00102 KConfig conf(configfile); 00103 if (conf.accessMode() == KConfig::NoAccess) { 00104 emit signalError(i18n("Configuration file not found: \"%1\"", configfile)); 00105 kError() << "No knsrc file named '" << configfile << "' was found." << endl; 00106 return false; 00107 } 00108 // FIXME: accessMode() doesn't return NoAccess for non-existing files 00109 // - bug in kdecore? 00110 // - this needs to be looked at again until KConfig backend changes for KDE 4 00111 // the check below is a workaround 00112 if (KStandardDirs::locate("config", configfile).isEmpty()) { 00113 emit signalError(i18n("Configuration file not found: \"%1\"", configfile)); 00114 kError() << "No knsrc file named '" << configfile << "' was found." << endl; 00115 return false; 00116 } 00117 00118 KConfigGroup group; 00119 if (conf.hasGroup("KNewStuff3")) { 00120 kDebug() << "Loading KNewStuff3 config: " << configfile; 00121 group = conf.group("KNewStuff3"); 00122 } else if (conf.hasGroup("KNewStuff2")) { 00123 kDebug() << "Loading KNewStuff2 config: " << configfile; 00124 group = conf.group("KNewStuff2"); 00125 } else { 00126 emit signalError(i18n("Configuration file is invalid: \"%1\"", configfile)); 00127 kError() << "A knsrc file was found but it doesn't contain a KNewStuff3 section." << endl; 00128 return false; 00129 } 00130 00131 m_categories = group.readEntry("Categories", QStringList()); 00132 00133 kDebug() << "Categories: " << m_categories; 00134 m_providerFileUrl = group.readEntry("ProvidersUrl", QString()); 00135 m_applicationName = QFileInfo(KStandardDirs::locate("config", configfile)).baseName() + ':'; 00136 00137 // let installation read install specific config 00138 if (!m_installation->readConfig(group)) { 00139 return false; 00140 } 00141 00142 connect(m_installation, SIGNAL(signalEntryChanged(const KNS3::EntryInternal&)), SLOT(slotEntryChanged(const KNS3::EntryInternal&))); 00143 00144 m_cache = Cache::getCache(m_applicationName.split(':')[0]); 00145 connect(this, SIGNAL(signalEntryChanged(const KNS3::EntryInternal&)), m_cache.data(), SLOT(registerChangedEntry(const KNS3::EntryInternal&))); 00146 m_cache->readRegistry(); 00147 00148 m_initialized = true; 00149 00150 // load the providers 00151 loadProviders(); 00152 00153 return true; 00154 } 00155 00156 QStringList Engine::categories() const 00157 { 00158 return m_categories; 00159 } 00160 00161 QStringList Engine::categoriesFilter() const 00162 { 00163 return m_currentRequest.categories; 00164 } 00165 00166 void Engine::loadProviders() 00167 { 00168 if (m_providerFileUrl.isEmpty()) { 00169 // it would be nicer to move the attica stuff into its own class 00170 kDebug(550) << "Using OCS default providers"; 00171 Attica::ProviderManager* m_atticaProviderManager = new Attica::ProviderManager; 00172 connect(m_atticaProviderManager, SIGNAL(providerAdded(Attica::Provider)), this, SLOT(atticaProviderLoaded(Attica::Provider))); 00173 m_atticaProviderManager->loadDefaultProviders(); 00174 } else { 00175 kDebug(550) << "loading providers from " << m_providerFileUrl; 00176 emit signalBusy(i18n("Loading provider information")); 00177 00178 XmlLoader * loader = new XmlLoader(this); 00179 connect(loader, SIGNAL(signalLoaded(const QDomDocument&)), SLOT(slotProviderFileLoaded(const QDomDocument&))); 00180 connect(loader, SIGNAL(signalFailed()), SLOT(slotProvidersFailed())); 00181 00182 loader->load(KUrl(m_providerFileUrl)); 00183 } 00184 } 00185 00186 void Engine::slotProviderFileLoaded(const QDomDocument& doc) 00187 { 00188 kDebug() << "slotProvidersLoaded"; 00189 00190 bool isAtticaProviderFile = false; 00191 00192 // get each provider element, and create a provider object from it 00193 QDomElement providers = doc.documentElement(); 00194 00195 if (providers.tagName() == "providers") { 00196 isAtticaProviderFile = true; 00197 } else if (providers.tagName() != "ghnsproviders" && providers.tagName() != "knewstuffproviders") { 00198 kWarning(550) << "No document in providers.xml."; 00199 emit signalError(i18n("Could not load get hot new stuff providers from file: %1", m_providerFileUrl)); 00200 return; 00201 } 00202 00203 QDomElement n = providers.firstChildElement("provider"); 00204 while (!n.isNull()) { 00205 kDebug() << "Provider attributes: " << n.attribute("type"); 00206 00207 QSharedPointer<KNS3::Provider> provider; 00208 if (isAtticaProviderFile || n.attribute("type").toLower() == "rest") { 00209 provider = QSharedPointer<KNS3::Provider> (new AtticaProvider(m_categories)); 00210 } else { 00211 provider = QSharedPointer<KNS3::Provider> (new StaticXmlProvider); 00212 } 00213 00214 if (provider->setProviderXML(n)) { 00215 addProvider(provider); 00216 } else { 00217 emit signalError(i18n("Error initializing provider.")); 00218 } 00219 n = n.nextSiblingElement(); 00220 } 00221 emit signalBusy(i18n("Loading data")); 00222 } 00223 00224 void Engine::atticaProviderLoaded(const Attica::Provider& atticaProvider) 00225 { 00226 if (!atticaProvider.hasContentService()) { 00227 kDebug() << "Found provider: " << atticaProvider.baseUrl() << " but it does not support content"; 00228 return; 00229 } 00230 QSharedPointer<KNS3::Provider> provider = 00231 QSharedPointer<KNS3::Provider> (new AtticaProvider(atticaProvider, m_categories)); 00232 addProvider(provider); 00233 } 00234 00235 void Engine::addProvider(QSharedPointer<KNS3::Provider> provider) 00236 { 00237 m_providers.insert(provider->id(), provider); 00238 connect(provider.data(), SIGNAL(providerInitialized(KNS3::Provider*)), SLOT(providerInitialized(KNS3::Provider*))); 00239 connect(provider.data(), SIGNAL(loadingFinished(KNS3::Provider::SearchRequest, KNS3::EntryInternal::List)), 00240 SLOT(slotEntriesLoaded(KNS3::Provider::SearchRequest, KNS3::EntryInternal::List))); 00241 connect(provider.data(), SIGNAL(entryDetailsLoaded(KNS3::EntryInternal)), SLOT(slotEntryDetailsLoaded(KNS3::EntryInternal))); 00242 connect(provider.data(), SIGNAL(payloadLinkLoaded(const KNS3::EntryInternal&)), SLOT(downloadLinkLoaded(const KNS3::EntryInternal&))); 00243 connect(provider.data(), SIGNAL(signalError(QString)), this, SIGNAL(signalError(QString))); 00244 connect(provider.data(), SIGNAL(signalInformation(QString)), this, SIGNAL(signalIdle(QString))); 00245 } 00246 00247 void Engine::providerJobStarted ( KJob* job ) 00248 { 00249 emit jobStarted(job, i18n("Loading data from provider")); 00250 } 00251 00252 void Engine::slotProvidersFailed() 00253 { 00254 emit signalError(i18n("Loading of providers from file: %1 failed", m_providerFileUrl)); 00255 } 00256 00257 void Engine::providerInitialized(Provider* p) 00258 { 00259 kDebug() << "providerInitialized" << p->name(); 00260 p->setCachedEntries(m_cache->registryForProvider(p->id())); 00261 updateStatus(); 00262 00263 foreach (const QSharedPointer<KNS3::Provider> &p, m_providers) { 00264 if (!p->isInitialized()) { 00265 return; 00266 } 00267 } 00268 emit signalProvidersLoaded(); 00269 } 00270 00271 void Engine::slotEntriesLoaded(const KNS3::Provider::SearchRequest& request, KNS3::EntryInternal::List entries) 00272 { 00273 m_currentPage = qMax<int>(request.page, m_currentPage); 00274 kDebug() << "loaded page " << request.page << "current page" << m_currentPage; 00275 00276 if (request.sortMode == Provider::Updates) { 00277 emit signalUpdateableEntriesLoaded(entries); 00278 } else { 00279 m_cache->insertRequest(request, entries); 00280 emit signalEntriesLoaded(entries); 00281 } 00282 00283 --m_numDataJobs; 00284 updateStatus(); 00285 } 00286 00287 void Engine::reloadEntries() 00288 { 00289 emit signalResetView(); 00290 m_currentPage = -1; 00291 m_currentRequest.page = 0; 00292 m_numDataJobs = 0; 00293 00294 foreach (const QSharedPointer<KNS3::Provider> &p, m_providers) { 00295 if (p->isInitialized()) { 00296 if (m_currentRequest.sortMode == Provider::Installed) { 00297 // when asking for installed entries, never use the cache 00298 p->loadEntries(m_currentRequest); 00299 } else { 00300 // take entries from cache until there are no more 00301 EntryInternal::List cache = m_cache->requestFromCache(m_currentRequest); 00302 while (!cache.isEmpty()) { 00303 kDebug() << "From cache"; 00304 emit signalEntriesLoaded(cache); 00305 00306 m_currentPage = m_currentRequest.page; 00307 ++m_currentRequest.page; 00308 cache = m_cache->requestFromCache(m_currentRequest); 00309 } 00310 // if the cache was empty, request data from provider 00311 if (m_currentPage == -1) { 00312 kDebug() << "From provider"; 00313 p->loadEntries(m_currentRequest); 00314 00315 ++m_numDataJobs; 00316 updateStatus(); 00317 } 00318 } 00319 } 00320 } 00321 } 00322 00323 void Engine::setCategoriesFilter(const QStringList& categories) 00324 { 00325 m_currentRequest.categories = categories; 00326 reloadEntries(); 00327 } 00328 00329 void Engine::setSortMode(Provider::SortMode mode) 00330 { 00331 if (m_currentRequest.sortMode != mode) { 00332 m_currentRequest.page = -1; 00333 } 00334 m_currentRequest.sortMode = mode; 00335 reloadEntries(); 00336 } 00337 00338 void Engine::setSearchTerm(const QString& searchString) 00339 { 00340 m_searchTimer->stop(); 00341 m_currentRequest.searchTerm = searchString; 00342 EntryInternal::List cache = m_cache->requestFromCache(m_currentRequest); 00343 if (!cache.isEmpty()) { 00344 reloadEntries(); 00345 } else { 00346 m_searchTimer->start(); 00347 } 00348 } 00349 00350 void Engine::slotSearchTimerExpired() 00351 { 00352 reloadEntries(); 00353 } 00354 00355 void Engine::requestMoreData() 00356 { 00357 kDebug() << "Get more data! current page: " << m_currentPage << " requested: " << m_currentRequest.page; 00358 00359 if (m_currentPage < m_currentRequest.page) { 00360 return; 00361 } 00362 00363 m_currentRequest.page++; 00364 doRequest(); 00365 } 00366 00367 void Engine::requestData(int page, int pageSize) 00368 { 00369 m_currentRequest.page = page; 00370 m_currentRequest.pageSize = pageSize; 00371 doRequest(); 00372 } 00373 00374 void Engine::doRequest() 00375 { 00376 foreach (const QSharedPointer<KNS3::Provider> &p, m_providers) { 00377 if (p->isInitialized()) { 00378 p->loadEntries(m_currentRequest); 00379 ++m_numDataJobs; 00380 updateStatus(); 00381 } 00382 } 00383 } 00384 00385 void Engine::install(KNS3::EntryInternal entry, int linkId) 00386 { 00387 if (entry.status() == Entry::Updateable) { 00388 entry.setStatus(Entry::Updating); 00389 } else { 00390 entry.setStatus(Entry::Installing); 00391 } 00392 emit signalEntryChanged(entry); 00393 00394 kDebug() << "Install " << entry.name() 00395 << " from: " << entry.providerId(); 00396 QSharedPointer<Provider> p = m_providers.value(entry.providerId()); 00397 if (p) { 00398 p->loadPayloadLink(entry, linkId); 00399 00400 ++m_numInstallJobs; 00401 updateStatus(); 00402 } 00403 } 00404 00405 void Engine::slotInstallationFinished() 00406 { 00407 --m_numInstallJobs; 00408 updateStatus(); 00409 } 00410 00411 void Engine::slotInstallationFailed(const QString& message) 00412 { 00413 --m_numInstallJobs; 00414 emit signalError(message); 00415 } 00416 00417 void Engine::slotEntryDetailsLoaded(const KNS3::EntryInternal& entry) 00418 { 00419 emit signalEntryDetailsLoaded(entry); 00420 } 00421 00422 void Engine::downloadLinkLoaded(const KNS3::EntryInternal& entry) 00423 { 00424 m_installation->install(entry); 00425 } 00426 00427 void Engine::uninstall(KNS3::EntryInternal entry) 00428 { 00429 // FIXME: change the status? 00430 entry.setStatus(Entry::Installing); 00431 emit signalEntryChanged(entry); 00432 m_installation->uninstall(entry); 00433 } 00434 00435 void Engine::loadDetails(const KNS3::EntryInternal &entry) 00436 { 00437 QSharedPointer<Provider> p = m_providers.value(entry.providerId()); 00438 p->loadEntryDetails(entry); 00439 } 00440 00441 void Engine::loadPreview(const KNS3::EntryInternal& entry, EntryInternal::PreviewType type) 00442 { 00443 kDebug() << "START preview: " << entry.name() << type; 00444 ImageLoader* l = new ImageLoader(entry, type, this); 00445 connect(l, SIGNAL(signalPreviewLoaded(KNS3::EntryInternal,KNS3::EntryInternal::PreviewType)), this, SLOT(slotPreviewLoaded(KNS3::EntryInternal,KNS3::EntryInternal::PreviewType))); 00446 l->start(); 00447 ++m_numPictureJobs; 00448 updateStatus(); 00449 } 00450 00451 void Engine::slotPreviewLoaded(const KNS3::EntryInternal& entry, EntryInternal::PreviewType type) 00452 { 00453 kDebug() << "FINISH preview: " << entry.name() << type; 00454 emit signalEntryPreviewLoaded(entry, type); 00455 --m_numPictureJobs; 00456 updateStatus(); 00457 } 00458 00459 void Engine::contactAuthor(const EntryInternal &entry) 00460 { 00461 if (!entry.author().email().isEmpty()) { 00462 // invoke mail with the address of the author 00463 KToolInvocation::invokeMailer(entry.author().email(), i18n("Re: %1", entry.name())); 00464 } else if (!entry.author().homepage().isEmpty()) { 00465 KToolInvocation::invokeBrowser(entry.author().homepage()); 00466 } 00467 } 00468 00469 void Engine::slotEntryChanged(const KNS3::EntryInternal& entry) 00470 { 00471 emit signalEntryChanged(entry); 00472 } 00473 00474 bool Engine::userCanVote(const EntryInternal& entry) 00475 { 00476 QSharedPointer<Provider> p = m_providers.value(entry.providerId()); 00477 return p->userCanVote(); 00478 } 00479 00480 void Engine::vote(const EntryInternal& entry, uint rating) 00481 { 00482 QSharedPointer<Provider> p = m_providers.value(entry.providerId()); 00483 p->vote(entry, rating); 00484 } 00485 00486 bool Engine::userCanBecomeFan(const EntryInternal& entry) 00487 { 00488 QSharedPointer<Provider> p = m_providers.value(entry.providerId()); 00489 return p->userCanBecomeFan(); 00490 } 00491 00492 void Engine::becomeFan(const EntryInternal& entry) 00493 { 00494 QSharedPointer<Provider> p = m_providers.value(entry.providerId()); 00495 p->becomeFan(entry); 00496 } 00497 00498 void Engine::updateStatus() 00499 { 00500 if (m_numDataJobs > 0) { 00501 emit signalBusy(i18n("Loading data")); 00502 } else if (m_numPictureJobs > 0) { 00503 emit signalBusy(i18np("Loading one preview", "Loading %1 previews", m_numPictureJobs)); 00504 } else if (m_numInstallJobs > 0) { 00505 emit signalBusy(i18n("Installing")); 00506 } else { 00507 emit signalIdle(QString()); 00508 } 00509 } 00510 00511 void Engine::checkForUpdates() 00512 { 00513 foreach(QSharedPointer<Provider> p, m_providers) { 00514 Provider::SearchRequest request(KNS3::Provider::Updates); 00515 p->loadEntries(request); 00516 } 00517 } 00518 00519 #include "engine.moc"
KDE 4.7 API Reference