KNewStuff
coreengine.cpp
Go to the documentation of this file.
00001 /* 00002 This file is part of KNewStuff2. 00003 Copyright (c) 2007 Josef Spillner <spillner@kde.org> 00004 Copyright 2007 Frederik Gladhorn <frederik.gladhorn@kdemail.net> 00005 00006 This library is free software; you can redistribute it and/or 00007 modify it under the terms of the GNU Lesser General Public 00008 License as published by the Free Software Foundation; either 00009 version 2.1 of the License, or (at your option) any later version. 00010 00011 This library is distributed in the hope that it will be useful, 00012 but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00014 Lesser General Public License for more details. 00015 00016 You should have received a copy of the GNU Lesser General Public 00017 License along with this library. If not, see <http://www.gnu.org/licenses/>. 00018 */ 00019 00020 #include "coreengine.h" 00021 00022 #include "entryhandler.h" 00023 #include "providerhandler.h" 00024 #include "entryloader.h" 00025 #include "providerloader.h" 00026 #include "installation.h" 00027 #include "security.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 <ktar.h> 00043 #include <kzip.h> 00044 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 using namespace KNS; 00056 00057 CoreEngine::CoreEngine(QObject* parent) 00058 : QObject(parent), m_uploadedentry(NULL), m_uploadprovider(NULL), m_installation(NULL), m_activefeeds(0), 00059 m_initialized(false), m_cachepolicy(CacheNever), m_automationpolicy(AutomationOn) 00060 { 00061 } 00062 00063 CoreEngine::~CoreEngine() 00064 { 00065 shutdown(); 00066 } 00067 00068 bool CoreEngine::init(const QString &configfile) 00069 { 00070 kDebug() << "Initializing KNS::CoreEngine from '" << configfile << "'"; 00071 00072 KConfig conf(configfile); 00073 if (conf.accessMode() == KConfig::NoAccess) { 00074 kError() << "No knsrc file named '" << configfile << "' was found." << endl; 00075 return false; 00076 } 00077 // FIXME: accessMode() doesn't return NoAccess for non-existing files 00078 // - bug in kdecore? 00079 // - this needs to be looked at again until KConfig backend changes for KDE 4 00080 // the check below is a workaround 00081 if (KStandardDirs::locate("config", configfile).isEmpty()) { 00082 kError() << "No knsrc file named '" << configfile << "' was found." << endl; 00083 return false; 00084 } 00085 00086 if (!conf.hasGroup("KNewStuff2")) { 00087 kError() << "A knsrc file was found but it doesn't contain a KNewStuff2 section." << endl; 00088 return false; 00089 } 00090 00091 KConfigGroup group = conf.group("KNewStuff2"); 00092 m_providersurl = group.readEntry("ProvidersUrl", QString()); 00093 //m_componentname = group.readEntry("ComponentName", QString()); 00094 m_componentname = QFileInfo(KStandardDirs::locate("config", configfile)).baseName() + ':'; 00095 00096 // FIXME: add support for several categories later on 00097 // FIXME: read out only when actually installing as a performance improvement? 00098 m_installation = new Installation(); 00099 QString uncompresssetting = group.readEntry("Uncompress", QString("never")); 00100 // support old value of true as equivalent of always 00101 if (uncompresssetting == "true") { 00102 uncompresssetting = "always"; 00103 } 00104 if (uncompresssetting != "always" && uncompresssetting != "archive" && uncompresssetting != "never") { 00105 kError() << "invalid Uncompress setting chosen, must be one of: always, archive, or never" << endl; 00106 return false; 00107 } 00108 m_installation->setUncompression(uncompresssetting); 00109 00110 m_installation->setCommand(group.readEntry("InstallationCommand", QString())); 00111 m_installation->setUninstallCommand(group.readEntry("UninstallCommand", QString())); 00112 m_installation->setStandardResourceDir(group.readEntry("StandardResource", QString())); 00113 m_installation->setTargetDir(group.readEntry("TargetDir", QString())); 00114 m_installation->setInstallPath(group.readEntry("InstallPath", QString())); 00115 m_installation->setAbsoluteInstallPath(group.readEntry("AbsoluteInstallPath", QString())); 00116 m_installation->setCustomName(group.readEntry("CustomName", false)); 00117 00118 QString checksumpolicy = group.readEntry("ChecksumPolicy", QString()); 00119 if (!checksumpolicy.isEmpty()) { 00120 if (checksumpolicy == "never") 00121 m_installation->setChecksumPolicy(Installation::CheckNever); 00122 else if (checksumpolicy == "ifpossible") 00123 m_installation->setChecksumPolicy(Installation::CheckIfPossible); 00124 else if (checksumpolicy == "always") 00125 m_installation->setChecksumPolicy(Installation::CheckAlways); 00126 else { 00127 kError() << "The checksum policy '" + checksumpolicy + "' is unknown." << endl; 00128 return false; 00129 } 00130 } 00131 00132 QString signaturepolicy = group.readEntry("SignaturePolicy", QString()); 00133 if (!signaturepolicy.isEmpty()) { 00134 if (signaturepolicy == "never") 00135 m_installation->setSignaturePolicy(Installation::CheckNever); 00136 else if (signaturepolicy == "ifpossible") 00137 m_installation->setSignaturePolicy(Installation::CheckIfPossible); 00138 else if (signaturepolicy == "always") 00139 m_installation->setSignaturePolicy(Installation::CheckAlways); 00140 else { 00141 kError() << "The signature policy '" + signaturepolicy + "' is unknown." << endl; 00142 return false; 00143 } 00144 } 00145 00146 QString scope = group.readEntry("Scope", QString()); 00147 if (!scope.isEmpty()) { 00148 if (scope == "user") 00149 m_installation->setScope(Installation::ScopeUser); 00150 else if (scope == "system") 00151 m_installation->setScope(Installation::ScopeSystem); 00152 else { 00153 kError() << "The scope '" + scope + "' is unknown." << endl; 00154 return false; 00155 } 00156 00157 if (m_installation->scope() == Installation::ScopeSystem) { 00158 if (!m_installation->installPath().isEmpty()) { 00159 kError() << "System installation cannot be mixed with InstallPath." << endl; 00160 return false; 00161 } 00162 } 00163 } 00164 00165 QString cachePolicy = group.readEntry("CachePolicy", QString()); 00166 if (!cachePolicy.isEmpty()) { 00167 if (cachePolicy == "never") { 00168 m_cachepolicy = CacheNever; 00169 } else if (cachePolicy == "replaceable") { 00170 m_cachepolicy = CacheReplaceable; 00171 } else if (cachePolicy == "resident") { 00172 m_cachepolicy = CacheResident; 00173 } else if (cachePolicy == "only") { 00174 m_cachepolicy = CacheOnly; 00175 } else { 00176 kError() << "Cache policy '" + cachePolicy + "' is unknown." << endl; 00177 } 00178 } 00179 kDebug() << "cache policy: " << cachePolicy; 00180 00181 m_initialized = true; 00182 00183 return true; 00184 } 00185 00186 QString CoreEngine::componentName() const 00187 { 00188 if (!m_initialized) { 00189 return QString(); 00190 } 00191 00192 return m_componentname; 00193 } 00194 00195 void CoreEngine::start() 00196 { 00197 //kDebug() << "starting engine"; 00198 00199 if (!m_initialized) { 00200 kError() << "Must call KNS::CoreEngine::init() first." << endl; 00201 return; 00202 } 00203 00204 // first load the registry, so we know which entries are installed 00205 loadRegistry(); 00206 00207 // then load the providersCache if caching is enabled 00208 if (m_cachepolicy != CacheNever) { 00209 loadProvidersCache(); 00210 } 00211 00212 // FIXME: also return if CacheResident and its conditions fulfilled 00213 if (m_cachepolicy == CacheOnly) { 00214 //emit signalEntriesFinished(); 00215 return; 00216 } 00217 00218 ProviderLoader *provider_loader = new ProviderLoader(this); 00219 00220 // make connections before loading, just in case the iojob is very fast 00221 connect(provider_loader, 00222 SIGNAL(signalProvidersLoaded(KNS::Provider::List)), 00223 SLOT(slotProvidersLoaded(KNS::Provider::List))); 00224 connect(provider_loader, 00225 SIGNAL(signalProvidersFailed()), 00226 SLOT(slotProvidersFailed())); 00227 00228 provider_loader->load(m_providersurl); 00229 } 00230 00231 void CoreEngine::loadEntries(Provider *provider) 00232 { 00233 //kDebug() << "loading entries"; 00234 00235 if (m_cachepolicy == CacheOnly) { 00236 return; 00237 } 00238 00239 //if (provider != m_provider_index[pid(provider)]) { 00240 // // this is the cached provider, and a new provider has been loaded from the internet 00241 // // also, this provider's feeds have already been loaded including it's entries 00242 // m_provider_cache.removeAll(provider); // just in case it's still in there 00243 // return; 00244 //} 00245 00246 QStringList feeds = provider->feeds(); 00247 for (int i = 0; i < feeds.count(); i++) { 00248 Feed *feed = provider->downloadUrlFeed(feeds.at(i)); 00249 if (feed) { 00250 ++m_activefeeds; 00251 00252 EntryLoader *entry_loader = new EntryLoader(this); 00253 00254 connect(entry_loader, 00255 SIGNAL(signalEntriesLoaded(KNS::Entry::List)), 00256 SLOT(slotEntriesLoaded(KNS::Entry::List))); 00257 connect(entry_loader, 00258 SIGNAL(signalEntriesFailed()), 00259 SLOT(slotEntriesFailed())); 00260 connect(entry_loader, 00261 SIGNAL(signalProgress(KJob*, unsigned long)), 00262 SLOT(slotProgress(KJob*, unsigned long))); 00263 00264 entry_loader->load(provider, feed); 00265 } 00266 } 00267 } 00268 00269 void CoreEngine::downloadPreview(Entry *entry) 00270 { 00271 if (m_previewfiles.contains(entry)) { 00272 // FIXME: ensure somewhere else that preview file even exists 00273 //kDebug() << "Reusing preview from '" << m_previewfiles[entry] << "'"; 00274 emit signalPreviewLoaded(KUrl::fromPath(m_previewfiles[entry])); 00275 return; 00276 } 00277 00278 KUrl source = KUrl(entry->preview().representation()); 00279 00280 if (!source.isValid()) { 00281 kError() << "The entry doesn't have a preview." << endl; 00282 return; 00283 } 00284 00285 KUrl destination = QString(KGlobal::dirs()->saveLocation("tmp") + KRandom::randomString(10)); 00286 //kDebug() << "Downloading preview '" << source << "' to '" << destination << "'"; 00287 00288 // FIXME: check for validity 00289 KIO::FileCopyJob *job = KIO::file_copy(source, destination, -1, KIO::Overwrite | KIO::HideProgressInfo); 00290 connect(job, 00291 SIGNAL(result(KJob*)), 00292 SLOT(slotPreviewResult(KJob*))); 00293 connect(job, 00294 SIGNAL(progress(KJob*, unsigned long)), 00295 SLOT(slotProgress(KJob*, unsigned long))); 00296 00297 m_entry_jobs[job] = entry; 00298 } 00299 00300 void CoreEngine::downloadPayload(Entry *entry) 00301 { 00302 if(!entry) { 00303 emit signalPayloadFailed(entry); 00304 return; 00305 } 00306 KUrl source = KUrl(entry->payload().representation()); 00307 00308 if (!source.isValid()) { 00309 kError() << "The entry doesn't have a payload." << endl; 00310 emit signalPayloadFailed(entry); 00311 return; 00312 } 00313 00314 if (m_installation->isRemote()) { 00315 // Remote resource 00316 //kDebug() << "Relaying remote payload '" << source << "'"; 00317 entry->setStatus(Entry::Installed); 00318 m_payloadfiles[entry] = entry->payload().representation(); 00319 install(source.pathOrUrl()); 00320 emit signalPayloadLoaded(source); 00321 // FIXME: we still need registration for eventual deletion 00322 return; 00323 } 00324 00325 KUrl destination = QString(KGlobal::dirs()->saveLocation("tmp") + KRandom::randomString(10)); 00326 kDebug() << "Downloading payload '" << source << "' to '" << destination << "'"; 00327 00328 // FIXME: check for validity 00329 KIO::FileCopyJob *job = KIO::file_copy(source, destination, -1, KIO::Overwrite | KIO::HideProgressInfo); 00330 connect(job, 00331 SIGNAL(result(KJob*)), 00332 SLOT(slotPayloadResult(KJob*))); 00333 connect(job, 00334 SIGNAL(percent(KJob*, unsigned long)), 00335 SLOT(slotProgress(KJob*, unsigned long))); 00336 00337 m_entry_jobs[job] = entry; 00338 } 00339 00340 bool CoreEngine::uploadEntry(Provider *provider, Entry *entry) 00341 { 00342 //kDebug() << "Uploading " << entry->name().representation() << "..."; 00343 00344 if (m_uploadedentry) { 00345 kError() << "Another upload is in progress!" << endl; 00346 return false; 00347 } 00348 00349 if (!provider->uploadUrl().isValid()) { 00350 kError() << "The provider doesn't support uploads." << endl; 00351 return false; 00352 00353 // FIXME: support for <noupload> will go here (file bundle creation etc.) 00354 } 00355 00356 // FIXME: validate files etc. 00357 m_uploadprovider = provider; 00358 m_uploadedentry = entry; 00359 00360 KUrl sourcepayload = KUrl(entry->payload().representation()); 00361 KUrl destfolder = provider->uploadUrl(); 00362 00363 destfolder.setFileName(sourcepayload.fileName()); 00364 00365 KIO::FileCopyJob *fcjob = KIO::file_copy(sourcepayload, destfolder, -1, KIO::Overwrite | KIO::HideProgressInfo); 00366 connect(fcjob, 00367 SIGNAL(result(KJob*)), 00368 SLOT(slotUploadPayloadResult(KJob*))); 00369 00370 return true; 00371 } 00372 00373 void CoreEngine::slotProvidersLoaded(KNS::Provider::List list) 00374 { 00375 // note: this is only called from loading the online providers 00376 ProviderLoader *loader = dynamic_cast<ProviderLoader*>(sender()); 00377 delete loader; 00378 00379 mergeProviders(list); 00380 } 00381 00382 void CoreEngine::slotProvidersFailed() 00383 { 00384 kDebug() << "slotProvidersFailed"; 00385 ProviderLoader *loader = dynamic_cast<ProviderLoader*>(sender()); 00386 delete loader; 00387 00388 emit signalProvidersFailed(); 00389 } 00390 00391 void CoreEngine::slotEntriesLoaded(KNS::Entry::List list) 00392 { 00393 EntryLoader *loader = dynamic_cast<EntryLoader*>(sender()); 00394 if (!loader) return; 00395 const Provider *provider = loader->provider(); 00396 Feed *feed = loader->feed(); 00397 delete loader; 00398 m_activefeeds--; 00399 //kDebug() << "entriesloaded m_activefeeds: " << m_activefeeds; 00400 00401 //kDebug() << "Provider source " << provider->name().representation(); 00402 //kDebug() << "Feed source " << feed->name().representation(); 00403 //kDebug() << "Feed data: " << feed; 00404 00405 mergeEntries(list, feed, provider); 00406 } 00407 00408 void CoreEngine::slotEntriesFailed() 00409 { 00410 EntryLoader *loader = dynamic_cast<EntryLoader*>(sender()); 00411 delete loader; 00412 m_activefeeds--; 00413 00414 emit signalEntriesFailed(); 00415 } 00416 00417 void CoreEngine::slotProgress(KJob *job, unsigned long percent) 00418 { 00419 QString url; 00420 KIO::FileCopyJob * copyJob = qobject_cast<KIO::FileCopyJob*>(job); 00421 KIO::TransferJob * transferJob = qobject_cast<KIO::TransferJob*>(job); 00422 if (copyJob != NULL) { 00423 url = copyJob->srcUrl().fileName(); 00424 } else if (transferJob != NULL) { 00425 url = transferJob->url().fileName(); 00426 } 00427 00428 QString message = i18n("loading %1",url); 00429 emit signalProgress(message, percent); 00430 } 00431 00432 void CoreEngine::slotPayloadResult(KJob *job) 00433 { 00434 // for some reason this slot is getting called 3 times on one job error 00435 if (m_entry_jobs.contains(job)) { 00436 Entry *entry = m_entry_jobs[job]; 00437 m_entry_jobs.remove(job); 00438 00439 if (job->error()) { 00440 kError() << "Cannot load payload file." << endl; 00441 kError() << job->errorString() << endl; 00442 00443 emit signalPayloadFailed(entry); 00444 } else { 00445 KIO::FileCopyJob *fcjob = static_cast<KIO::FileCopyJob*>(job); 00446 m_payloadfiles[entry] = fcjob->destUrl().path(); 00447 00448 install(fcjob->destUrl().pathOrUrl()); 00449 00450 emit signalPayloadLoaded(fcjob->destUrl()); 00451 } 00452 } 00453 } 00454 00455 // FIXME: this should be handled more internally to return a (cached) preview image 00456 void CoreEngine::slotPreviewResult(KJob *job) 00457 { 00458 if (job->error()) { 00459 kError() << "Cannot load preview file." << endl; 00460 kError() << job->errorString() << endl; 00461 00462 m_entry_jobs.remove(job); 00463 emit signalPreviewFailed(); 00464 } else { 00465 KIO::FileCopyJob *fcjob = static_cast<KIO::FileCopyJob*>(job); 00466 00467 if (m_entry_jobs.contains(job)) { 00468 // now, assign temporary filename to entry and update entry cache 00469 Entry *entry = m_entry_jobs[job]; 00470 m_entry_jobs.remove(job); 00471 m_previewfiles[entry] = fcjob->destUrl().path(); 00472 cacheEntry(entry); 00473 } 00474 // FIXME: ignore if not? shouldn't happen... 00475 00476 emit signalPreviewLoaded(fcjob->destUrl()); 00477 } 00478 } 00479 00480 void CoreEngine::slotUploadPayloadResult(KJob *job) 00481 { 00482 if (job->error()) { 00483 kError() << "Cannot upload payload file." << endl; 00484 kError() << job->errorString() << endl; 00485 00486 m_uploadedentry = NULL; 00487 m_uploadprovider = NULL; 00488 00489 emit signalEntryFailed(); 00490 return; 00491 } 00492 00493 if (m_uploadedentry->preview().representation().isEmpty()) { 00494 // FIXME: we abuse 'job' here for the shortcut if there's no preview 00495 slotUploadPreviewResult(job); 00496 return; 00497 } 00498 00499 KUrl sourcepreview = KUrl(m_uploadedentry->preview().representation()); 00500 KUrl destfolder = m_uploadprovider->uploadUrl(); 00501 00502 destfolder.setFileName(sourcepreview.fileName()); 00503 00504 KIO::FileCopyJob *fcjob = KIO::file_copy(sourcepreview, destfolder, -1, KIO::Overwrite | KIO::HideProgressInfo); 00505 connect(fcjob, 00506 SIGNAL(result(KJob*)), 00507 SLOT(slotUploadPreviewResult(KJob*))); 00508 } 00509 00510 void CoreEngine::slotUploadPreviewResult(KJob *job) 00511 { 00512 if (job->error()) { 00513 kError() << "Cannot upload preview file." << endl; 00514 kError() << job->errorString() << endl; 00515 00516 m_uploadedentry = NULL; 00517 m_uploadprovider = NULL; 00518 00519 emit signalEntryFailed(); 00520 return; 00521 } 00522 00523 // FIXME: the following save code is also in cacheEntry() 00524 // when we upload, the entry should probably be cached! 00525 00526 // FIXME: adhere to meta naming rules as discussed 00527 KUrl sourcemeta = QString(KGlobal::dirs()->saveLocation("tmp") + KRandom::randomString(10) + ".meta"); 00528 KUrl destfolder = m_uploadprovider->uploadUrl(); 00529 00530 destfolder.setFileName(sourcemeta.fileName()); 00531 00532 EntryHandler eh(*m_uploadedentry); 00533 QDomElement exml = eh.entryXML(); 00534 00535 QDomDocument doc; 00536 QDomElement root = doc.createElement("ghnsupload"); 00537 root.appendChild(exml); 00538 00539 QFile f(sourcemeta.path()); 00540 if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { 00541 kError() << "Cannot write meta information to '" << sourcemeta << "'." << endl; 00542 00543 m_uploadedentry = NULL; 00544 m_uploadprovider = NULL; 00545 00546 emit signalEntryFailed(); 00547 return; 00548 } 00549 QTextStream metastream(&f); 00550 metastream << root; 00551 f.close(); 00552 00553 KIO::FileCopyJob *fcjob = KIO::file_copy(sourcemeta, destfolder, -1, KIO::Overwrite | KIO::HideProgressInfo); 00554 connect(fcjob, 00555 SIGNAL(result(KJob*)), 00556 SLOT(slotUploadMetaResult(KJob*))); 00557 } 00558 00559 void CoreEngine::slotUploadMetaResult(KJob *job) 00560 { 00561 if (job->error()) { 00562 kError() << "Cannot upload meta file." << endl; 00563 kError() << job->errorString() << endl; 00564 00565 m_uploadedentry = NULL; 00566 m_uploadprovider = NULL; 00567 00568 emit signalEntryFailed(); 00569 return; 00570 } else { 00571 m_uploadedentry = NULL; 00572 m_uploadprovider = NULL; 00573 00574 //KIO::FileCopyJob *fcjob = static_cast<KIO::FileCopyJob*>(job); 00575 emit signalEntryUploaded(); 00576 } 00577 } 00578 00579 void CoreEngine::loadRegistry() 00580 { 00581 KStandardDirs d; 00582 00583 //kDebug() << "Loading registry of files for the component: " << m_componentname; 00584 00585 QString realAppName = m_componentname.split(':')[0]; 00586 00587 // this must be same as in registerEntry() 00588 const QStringList dirs = d.findDirs("data", "knewstuff2-entries.registry"); 00589 for (QStringList::ConstIterator it = dirs.begin(); it != dirs.end(); ++it) { 00590 //kDebug() << " + Load from directory '" + (*it) + "'."; 00591 QDir dir((*it)); 00592 const QStringList files = dir.entryList(QDir::Files | QDir::Readable); 00593 for (QStringList::const_iterator fit = files.begin(); fit != files.end(); ++fit) { 00594 QString filepath = (*it) + '/' + (*fit); 00595 //kDebug() << " + Load from file '" + filepath + "'."; 00596 00597 bool ret; 00598 QFileInfo info(filepath); 00599 QFile f(filepath); 00600 00601 // first see if this file is even for this app 00602 // because the registry contains entries for all apps 00603 // FIXMEE: should be able to do this with a filter on the entryList above probably 00604 QString thisAppName = QString::fromUtf8(QByteArray::fromBase64(info.baseName().toUtf8())); 00605 00606 // NOTE: the ":" needs to always coincide with the separator character used in 00607 // the id(Entry*) method 00608 thisAppName = thisAppName.split(':')[0]; 00609 00610 if (thisAppName != realAppName) { 00611 continue; 00612 } 00613 00614 ret = f.open(QIODevice::ReadOnly); 00615 if (!ret) { 00616 kWarning() << "The file could not be opened."; 00617 continue; 00618 } 00619 00620 QDomDocument doc; 00621 ret = doc.setContent(&f); 00622 if (!ret) { 00623 kWarning() << "The file could not be parsed."; 00624 continue; 00625 } 00626 00627 QDomElement root = doc.documentElement(); 00628 if (root.tagName() != "ghnsinstall") { 00629 kWarning() << "The file doesn't seem to be of interest."; 00630 continue; 00631 } 00632 00633 QDomElement stuff = root.firstChildElement("stuff"); 00634 if (stuff.isNull()) { 00635 kWarning() << "Missing GHNS installation metadata."; 00636 continue; 00637 } 00638 00639 EntryHandler handler(stuff); 00640 if (!handler.isValid()) { 00641 kWarning() << "Invalid GHNS installation metadata."; 00642 continue; 00643 } 00644 00645 Entry *e = handler.entryptr(); 00646 e->setStatus(Entry::Installed); 00647 e->setSource(Entry::Registry); 00648 m_entry_registry.insert(id(e), e); 00649 //QString thisid = id(e); 00650 00651 // we must overwrite cache entries with registered entries 00652 // and not just append the latter ones 00653 //if (entryCached(e)) { 00654 // // it's in the cache, so replace the cache entry with the registered entry 00655 // Entry * oldEntry = m_entry_index[thisid]; 00656 // int index = m_entry_cache.indexOf(oldEntry); 00657 // m_entry_cache[index] = e; 00658 // //delete oldEntry; 00659 //} 00660 //else { 00661 // m_entry_cache.append(e); 00662 //} 00663 //m_entry_index[thisid] = e; 00664 } 00665 } 00666 } 00667 00668 void CoreEngine::loadProvidersCache() 00669 { 00670 KStandardDirs d; 00671 00672 // use the componentname so we get the cache specific to this knsrc (kanagram, wallpaper, etc.) 00673 QString cachefile = d.findResource("cache", m_componentname + "kns2providers.cache.xml"); 00674 if (cachefile.isEmpty()) { 00675 kDebug() << "Cache not present, skip loading."; 00676 return; 00677 } 00678 00679 kDebug() << "Loading provider cache from file '" + cachefile + "'."; 00680 00681 // make sure we can open and read the file 00682 bool ret; 00683 QFile f(cachefile); 00684 ret = f.open(QIODevice::ReadOnly); 00685 if (!ret) { 00686 kWarning() << "The file could not be opened."; 00687 return; 00688 } 00689 00690 // make sure it's valid xml 00691 QDomDocument doc; 00692 ret = doc.setContent(&f); 00693 if (!ret) { 00694 kWarning() << "The file could not be parsed."; 00695 return; 00696 } 00697 00698 // make sure there's a root tag 00699 QDomElement root = doc.documentElement(); 00700 if (root.tagName() != "ghnsproviders") { 00701 kWarning() << "The file doesn't seem to be of interest."; 00702 return; 00703 } 00704 00705 // get the first provider 00706 QDomElement provider = root.firstChildElement("provider"); 00707 if (provider.isNull()) { 00708 kWarning() << "Missing provider entries in the cache."; 00709 return; 00710 } 00711 00712 // handle each provider 00713 while (!provider.isNull()) { 00714 ProviderHandler handler(provider); 00715 if (!handler.isValid()) { 00716 kWarning() << "Invalid provider metadata."; 00717 continue; 00718 } 00719 00720 Provider *p = handler.providerptr(); 00721 m_provider_cache.append(p); 00722 m_provider_index[pid(p)] = p; 00723 00724 emit signalProviderLoaded(p); 00725 00726 loadFeedCache(p); 00727 00728 // no longer needed because EnginePrivate::slotProviderLoaded calls loadEntries 00729 //if (m_automationpolicy == AutomationOn) { 00730 // loadEntries(p); 00731 //} 00732 00733 provider = provider.nextSiblingElement("provider"); 00734 } 00735 00736 if (m_cachepolicy == CacheOnly) { 00737 emit signalEntriesFinished(); 00738 } 00739 } 00740 00741 void CoreEngine::loadFeedCache(Provider *provider) 00742 { 00743 KStandardDirs d; 00744 00745 kDebug() << "Loading feed cache."; 00746 00747 QStringList cachedirs = d.findDirs("cache", m_componentname + "kns2feeds.cache"); 00748 if (cachedirs.size() == 0) { 00749 kDebug() << "Cache directory not present, skip loading."; 00750 return; 00751 } 00752 QString cachedir = cachedirs.first(); 00753 00754 QStringList entrycachedirs = d.findDirs("cache", "knewstuff2-entries.cache/"); 00755 if (entrycachedirs.size() == 0) { 00756 kDebug() << "Cache directory not present, skip loading."; 00757 return; 00758 } 00759 QString entrycachedir = entrycachedirs.first(); 00760 00761 kDebug() << "Load from directory: " + cachedir; 00762 00763 QStringList feeds = provider->feeds(); 00764 for (int i = 0; i < feeds.count(); i++) { 00765 Feed *feed = provider->downloadUrlFeed(feeds.at(i)); 00766 QString feedname = feeds.at(i); 00767 00768 QString idbase64 = QString(pid(provider).toUtf8().toBase64() + '-' + feedname); 00769 QString cachefile = cachedir + '/' + idbase64 + ".xml"; 00770 00771 kDebug() << " + Load from file: " + cachefile; 00772 00773 bool ret; 00774 QFile f(cachefile); 00775 ret = f.open(QIODevice::ReadOnly); 00776 if (!ret) { 00777 kWarning() << "The file could not be opened."; 00778 return; 00779 } 00780 00781 QDomDocument doc; 00782 ret = doc.setContent(&f); 00783 if (!ret) { 00784 kWarning() << "The file could not be parsed."; 00785 return; 00786 } 00787 00788 QDomElement root = doc.documentElement(); 00789 if (root.tagName() != "ghnsfeeds") { 00790 kWarning() << "The file doesn't seem to be of interest."; 00791 return; 00792 } 00793 00794 QDomElement entryel = root.firstChildElement("entry-id"); 00795 if (entryel.isNull()) { 00796 kWarning() << "Missing entries in the cache."; 00797 return; 00798 } 00799 00800 while (!entryel.isNull()) { 00801 QString idbase64 = entryel.text(); 00802 //kDebug() << "loading cache for entry: " << QByteArray::fromBase64(idbase64.toUtf8()); 00803 00804 QString filepath = entrycachedir + '/' + idbase64 + ".meta"; 00805 00806 //kDebug() << "from file '" + filepath + "'."; 00807 00808 // FIXME: pass feed and make loadEntryCache return void for consistency? 00809 Entry *entry = loadEntryCache(filepath); 00810 if (entry) { 00811 QString entryid = id(entry); 00812 00813 if (m_entry_registry.contains(entryid)) { 00814 Entry * registryEntry = m_entry_registry.value(entryid); 00815 entry->setStatus(registryEntry->status()); 00816 entry->setInstalledFiles(registryEntry->installedFiles()); 00817 } 00818 00819 feed->addEntry(entry); 00820 //kDebug() << "entry " << entry->name().representation() << " loaded from cache"; 00821 emit signalEntryLoaded(entry, feed, provider); 00822 } 00823 00824 entryel = entryel.nextSiblingElement("entry-id"); 00825 } 00826 } 00827 } 00828 00829 KNS::Entry *CoreEngine::loadEntryCache(const QString& filepath) 00830 { 00831 bool ret; 00832 QFile f(filepath); 00833 ret = f.open(QIODevice::ReadOnly); 00834 if (!ret) { 00835 kWarning() << "The file " << filepath << " could not be opened."; 00836 return NULL; 00837 } 00838 00839 QDomDocument doc; 00840 ret = doc.setContent(&f); 00841 if (!ret) { 00842 kWarning() << "The file could not be parsed."; 00843 return NULL; 00844 } 00845 00846 QDomElement root = doc.documentElement(); 00847 if (root.tagName() != "ghnscache") { 00848 kWarning() << "The file doesn't seem to be of interest."; 00849 return NULL; 00850 } 00851 00852 QDomElement stuff = root.firstChildElement("stuff"); 00853 if (stuff.isNull()) { 00854 kWarning() << "Missing GHNS cache metadata."; 00855 return NULL; 00856 } 00857 00858 EntryHandler handler(stuff); 00859 if (!handler.isValid()) { 00860 kWarning() << "Invalid GHNS installation metadata."; 00861 return NULL; 00862 } 00863 00864 Entry *e = handler.entryptr(); 00865 e->setStatus(Entry::Downloadable); 00866 m_entry_cache.append(e); 00867 m_entry_index[id(e)] = e; 00868 00869 if (root.hasAttribute("previewfile")) { 00870 m_previewfiles[e] = root.attribute("previewfile"); 00871 // FIXME: check here for a [ -f previewfile ] 00872 } 00873 00874 if (root.hasAttribute("payloadfile")) { 00875 m_payloadfiles[e] = root.attribute("payloadfile"); 00876 // FIXME: check here for a [ -f payloadfile ] 00877 } 00878 00879 e->setSource(Entry::Cache); 00880 00881 return e; 00882 } 00883 00884 // FIXME: not needed anymore? 00885 #if 0 00886 void CoreEngine::loadEntriesCache() 00887 { 00888 KStandardDirs d; 00889 00890 //kDebug() << "Loading entry cache."; 00891 00892 QStringList cachedirs = d.findDirs("cache", "knewstuff2-entries.cache/" + m_componentname); 00893 if (cachedirs.size() == 0) { 00894 //kDebug() << "Cache directory not present, skip loading."; 00895 return; 00896 } 00897 QString cachedir = cachedirs.first(); 00898 00899 //kDebug() << " + Load from directory '" + cachedir + "'."; 00900 00901 QDir dir(cachedir); 00902 QStringList files = dir.entryList(QDir::Files | QDir::Readable); 00903 for (QStringList::iterator fit = files.begin(); fit != files.end(); ++fit) { 00904 QString filepath = cachedir + '/' + (*fit); 00905 //kDebug() << " + Load from file '" + filepath + "'."; 00906 00907 Entry *e = loadEntryCache(filepath); 00908 00909 if (e) { 00910 // FIXME: load provider/feed information first 00911 emit signalEntryLoaded(e, NULL, NULL); 00912 } 00913 } 00914 } 00915 #endif 00916 00917 void CoreEngine::shutdown() 00918 { 00919 m_entry_index.clear(); 00920 m_provider_index.clear(); 00921 00922 qDeleteAll(m_entry_cache); 00923 qDeleteAll(m_provider_cache); 00924 00925 m_entry_cache.clear(); 00926 m_provider_cache.clear(); 00927 00928 delete m_installation; 00929 } 00930 00931 bool CoreEngine::providerCached(Provider *provider) 00932 { 00933 if (m_cachepolicy == CacheNever) return false; 00934 00935 if (m_provider_index.contains(pid(provider))) 00936 return true; 00937 return false; 00938 } 00939 00940 bool CoreEngine::providerChanged(Provider *oldprovider, Provider *provider) 00941 { 00942 QStringList oldfeeds = oldprovider->feeds(); 00943 QStringList feeds = provider->feeds(); 00944 if (oldfeeds.count() != feeds.count()) 00945 return true; 00946 for (int i = 0; i < feeds.count(); i++) { 00947 Feed *oldfeed = oldprovider->downloadUrlFeed(feeds.at(i)); 00948 Feed *feed = provider->downloadUrlFeed(feeds.at(i)); 00949 if (!oldfeed) 00950 return true; 00951 if (feed->feedUrl() != oldfeed->feedUrl()) 00952 return true; 00953 } 00954 return false; 00955 } 00956 00957 void CoreEngine::mergeProviders(Provider::List providers) 00958 { 00959 for (Provider::List::Iterator it = providers.begin(); it != providers.end(); ++it) { 00960 Provider *p = (*it); 00961 00962 if (providerCached(p)) { 00963 kDebug() << "CACHE: hit provider " << p->name().representation(); 00964 Provider *oldprovider = m_provider_index[pid(p)]; 00965 if (providerChanged(oldprovider, p)) { 00966 kDebug() << "CACHE: update provider"; 00967 cacheProvider(p); 00968 emit signalProviderChanged(p); 00969 } 00970 // oldprovider can now be deleted, see entry hit case 00971 // also take it out of m_provider_cache and m_provider_index 00972 //m_provider_cache.removeAll(oldprovider); 00973 //delete oldprovider; 00974 } else { 00975 if (m_cachepolicy != CacheNever) { 00976 kDebug() << "CACHE: miss provider " << p->name().representation(); 00977 cacheProvider(p); 00978 } 00979 emit signalProviderLoaded(p); 00980 00981 // no longer needed, because slotProviderLoaded calls loadEntries() 00982 //if (m_automationpolicy == AutomationOn) { 00983 // loadEntries(p); 00984 //} 00985 } 00986 00987 m_provider_cache.append(p); 00988 m_provider_index[pid(p)] = p; 00989 } 00990 00991 emit signalProvidersFinished(); 00992 } 00993 00994 bool CoreEngine::entryCached(Entry *entry) 00995 { 00996 if (m_cachepolicy == CacheNever) return false; 00997 00998 // Direct cache lookup first 00999 // FIXME: probably better use URL (changes less frequently) and do iteration 01000 if (m_entry_index.contains(id(entry)) && m_entry_index[id(entry)]->source() == Entry::Cache) { 01001 return true; 01002 } 01003 01004 // If entry wasn't found, either 01005 // - a translation was added which matches our locale better, or 01006 // - our locale preferences changed, or both. 01007 // In that case we've got to find the old name in the new entry, 01008 // since we assume that translations are always added but never removed. 01009 01010 // BIGFIXME: the code below is incomplete, if we are looking for a translation 01011 // id(entry) will not work, as it uses the current locale to get the id 01012 01013 for (int i = 0; i < m_entry_cache.count(); i++) { 01014 Entry *oldentry = m_entry_cache.at(i); 01015 if (id(entry) == id(oldentry)) return true; 01016 //QString lang = id(oldentry).section(":", 0, 0); 01017 //QString oldname = oldentry->name().translated(lang); 01018 //QString name = entry->name().translated(lang); 01020 //if (name == oldname) return true; 01021 } 01022 01023 return false; 01024 } 01025 01026 bool CoreEngine::entryChanged(Entry *oldentry, Entry *entry) 01027 { 01028 // possibly return true if the status changed? depends on when this is called 01029 if ((!oldentry) || (entry->releaseDate() > oldentry->releaseDate()) 01030 || (entry->version() > oldentry->version()) 01031 || (entry->release() > oldentry->release())) 01032 return true; 01033 return false; 01034 } 01035 01036 void CoreEngine::mergeEntries(Entry::List entries, Feed *feed, const Provider *provider) 01037 { 01038 for (Entry::List::Iterator it = entries.begin(); it != entries.end(); ++it) { 01039 // TODO: find entry in entrycache, replace if needed 01040 // don't forget marking as 'updateable' 01041 Entry *e = (*it); 01042 QString thisId = id(e); 01043 // set it to Installed if it's in the registry 01044 01045 if (m_entry_registry.contains(thisId)) { 01046 // see if the one online is newer (higher version, release, or release date) 01047 Entry *registryentry = m_entry_registry[thisId]; 01048 e->setInstalledFiles(registryentry->installedFiles()); 01049 01050 if (entryChanged(registryentry, e)) { 01051 e->setStatus(Entry::Updateable); 01052 emit signalEntryChanged(e); 01053 } else { 01054 // it hasn't changed, so set the status to that of the registry entry 01055 e->setStatus(registryentry->status()); 01056 } 01057 01058 if (entryCached(e)) { 01059 // in the registry and the cache, so take the cached one out 01060 Entry * cachedentry = m_entry_index[thisId]; 01061 if (entryChanged(cachedentry, e)) { 01062 //kDebug() << "CACHE: update entry"; 01063 cachedentry->setStatus(Entry::Updateable); 01064 // entry has changed 01065 if (m_cachepolicy != CacheNever) { 01066 cacheEntry(e); 01067 } 01068 emit signalEntryChanged(e); 01069 } 01070 01071 // take cachedentry out of the feed 01072 feed->removeEntry(cachedentry); 01073 //emit signalEntryRemoved(cachedentry, feed); 01074 } else { 01075 emit signalEntryLoaded(e, feed, provider); 01076 } 01077 01078 } else { 01079 e->setStatus(Entry::Downloadable); 01080 01081 if (entryCached(e)) { 01082 //kDebug() << "CACHE: hit entry " << e->name().representation(); 01083 // FIXME: separate version updates from server-side translation updates? 01084 Entry *cachedentry = m_entry_index[thisId]; 01085 if (entryChanged(cachedentry, e)) { 01086 //kDebug() << "CACHE: update entry"; 01087 e->setStatus(Entry::Updateable); 01088 // entry has changed 01089 if (m_cachepolicy != CacheNever) { 01090 cacheEntry(e); 01091 } 01092 emit signalEntryChanged(e); 01093 // FIXME: cachedentry can now be deleted, but it's still in the list! 01094 // FIXME: better: assigne all values to 'e', keeps refs intact 01095 } 01096 // take cachedentry out of the feed 01097 feed->removeEntry(cachedentry); 01098 //emit signalEntryRemoved(cachedentry, feed); 01099 } else { 01100 if (m_cachepolicy != CacheNever) { 01101 //kDebug() << "CACHE: miss entry " << e->name().representation(); 01102 cacheEntry(e); 01103 } 01104 emit signalEntryLoaded(e, feed, provider); 01105 } 01106 01107 m_entry_cache.append(e); 01108 m_entry_index[thisId] = e; 01109 } 01110 } 01111 01112 if (m_cachepolicy != CacheNever) { 01113 // extra code to get the feedname from the provider, we could use feed->name().representation() 01114 // but would need to remove spaces, and latinize it since it can be any encoding 01115 // besides feeds.size() has a max of 4 currently (unsorted, score, downloads, and latest) 01116 QStringList feeds = provider->feeds(); 01117 QString feedname; 01118 for (int i = 0; i < feeds.size(); ++i) { 01119 if (provider->downloadUrlFeed(feeds[i]) == feed) { 01120 feedname = feeds[i]; 01121 } 01122 } 01123 cacheFeed(provider, feedname, feed, entries); 01124 } 01125 01126 emit signalEntriesFeedFinished(feed); 01127 if (m_activefeeds == 0) { 01128 emit signalEntriesFinished(); 01129 } 01130 } 01131 01132 void CoreEngine::cacheProvider(Provider *provider) 01133 { 01134 KStandardDirs d; 01135 01136 kDebug() << "Caching provider."; 01137 01138 QString cachedir = d.saveLocation("cache"); 01139 QString cachefile = cachedir + m_componentname + "kns2providers.cache.xml"; 01140 01141 kDebug() << " + Save to file '" + cachefile + "'."; 01142 01143 QDomDocument doc; 01144 QDomElement root = doc.createElement("ghnsproviders"); 01145 01146 for (Provider::List::Iterator it = m_provider_cache.begin(); it != m_provider_cache.end(); ++it) { 01147 Provider *p = (*it); 01148 ProviderHandler ph(*p); 01149 QDomElement pxml = ph.providerXML(); 01150 root.appendChild(pxml); 01151 } 01152 ProviderHandler ph(*provider); 01153 QDomElement pxml = ph.providerXML(); 01154 root.appendChild(pxml); 01155 01156 QFile f(cachefile); 01157 if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { 01158 kError() << "Cannot write meta information to '" << cachedir << "'." << endl; 01159 // FIXME: ignore? 01160 return; 01161 } 01162 QTextStream metastream(&f); 01163 metastream << root; 01164 f.close(); 01165 01166 /*QStringList feeds = p->feeds(); 01167 for(int i = 0; i < feeds.count(); i++) { 01168 Feed *feed = p->downloadUrlFeed(feeds.at(i)); 01169 cacheFeed(p, feeds.at(i), feed); 01170 }*/ 01171 } 01172 01173 void CoreEngine::cacheFeed(const Provider *provider, const QString & feedname, const Feed *feed, Entry::List entries) 01174 { 01175 // feed cache file is a list of entry-id's that are part of this feed 01176 KStandardDirs d; 01177 01178 Q_UNUSED(feed); 01179 01180 QString cachedir = d.saveLocation("cache", m_componentname + "kns2feeds.cache"); 01181 01182 QString idbase64 = QString(pid(provider).toUtf8().toBase64() + '-' + feedname); 01183 QString cachefile = idbase64 + ".xml"; 01184 01185 kDebug() << "Caching feed to file '" + cachefile + "'."; 01186 01187 QDomDocument doc; 01188 QDomElement root = doc.createElement("ghnsfeeds"); 01189 for (int i = 0; i < entries.count(); i++) { 01190 QString idbase64 = id(entries.at(i)).toUtf8().toBase64(); 01191 QDomElement entryel = doc.createElement("entry-id"); 01192 root.appendChild(entryel); 01193 QDomText entrytext = doc.createTextNode(idbase64); 01194 entryel.appendChild(entrytext); 01195 } 01196 01197 QFile f(cachedir + cachefile); 01198 if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { 01199 kError() << "Cannot write meta information to '" << cachedir + cachefile << "'." << endl; 01200 // FIXME: ignore? 01201 return; 01202 } 01203 QTextStream metastream(&f); 01204 metastream << root; 01205 f.close(); 01206 } 01207 01208 void CoreEngine::cacheEntry(Entry *entry) 01209 { 01210 KStandardDirs d; 01211 01212 QString cachedir = d.saveLocation("cache", "knewstuff2-entries.cache/"); 01213 01214 kDebug() << "Caching entry in directory '" + cachedir + "'."; 01215 01216 //FIXME: this must be deterministic, but it could also be an OOB random string 01217 //which gets stored into <ghnscache> just like preview... 01218 QString idbase64 = QString(id(entry).toUtf8().toBase64()); 01219 QString cachefile = idbase64 + ".meta"; 01220 01221 kDebug() << "Caching to file '" + cachefile + "'."; 01222 01223 // FIXME: adhere to meta naming rules as discussed 01224 // FIXME: maybe related filename to base64-encoded id(), or the reverse? 01225 01226 EntryHandler eh(*entry); 01227 QDomElement exml = eh.entryXML(); 01228 01229 QDomDocument doc; 01230 QDomElement root = doc.createElement("ghnscache"); 01231 root.appendChild(exml); 01232 01233 if (m_previewfiles.contains(entry)) { 01234 root.setAttribute("previewfile", m_previewfiles[entry]); 01235 } 01236 /*if (m_payloadfiles.contains(entry)) { 01237 root.setAttribute("payloadfile", m_payloadfiles[entry]); 01238 }*/ 01239 01240 QFile f(cachedir + cachefile); 01241 if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { 01242 kError() << "Cannot write meta information to '" << cachedir + cachefile << "'." << endl; 01243 // FIXME: ignore? 01244 return; 01245 } 01246 QTextStream metastream(&f); 01247 metastream << root; 01248 f.close(); 01249 } 01250 01251 void CoreEngine::registerEntry(Entry *entry) 01252 { 01253 m_entry_registry.insert(id(entry), entry); 01254 KStandardDirs d; 01255 01256 //kDebug() << "Registering entry."; 01257 01258 // NOTE: this directory must match loadRegistry 01259 QString registrydir = d.saveLocation("data", "knewstuff2-entries.registry"); 01260 01261 //kDebug() << " + Save to directory '" + registrydir + "'."; 01262 01263 // FIXME: see cacheEntry() for naming-related discussion 01264 QString registryfile = QString(id(entry).toUtf8().toBase64()) + ".meta"; 01265 01266 //kDebug() << " + Save to file '" + registryfile + "'."; 01267 01268 EntryHandler eh(*entry); 01269 QDomElement exml = eh.entryXML(); 01270 01271 QDomDocument doc; 01272 QDomElement root = doc.createElement("ghnsinstall"); 01273 root.appendChild(exml); 01274 01275 if (m_payloadfiles.contains(entry)) { 01276 root.setAttribute("payloadfile", m_payloadfiles[entry]); 01277 } 01278 01279 QFile f(registrydir + registryfile); 01280 if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { 01281 kError() << "Cannot write meta information to '" << registrydir + registryfile << "'." << endl; 01282 // FIXME: ignore? 01283 return; 01284 } 01285 QTextStream metastream(&f); 01286 metastream << root; 01287 f.close(); 01288 } 01289 01290 void KNS::CoreEngine::unregisterEntry(Entry * entry) 01291 { 01292 KStandardDirs d; 01293 01294 // NOTE: this directory must match loadRegistry 01295 QString registrydir = d.saveLocation("data", "knewstuff2-entries.registry"); 01296 01297 // FIXME: see cacheEntry() for naming-related discussion 01298 QString registryfile = QString(id(entry).toUtf8().toBase64()) + ".meta"; 01299 01300 QFile::remove(registrydir + registryfile); 01301 01302 // remove the entry from m_entry_registry 01303 m_entry_registry.remove(id(entry)); 01304 } 01305 01306 QString CoreEngine::id(Entry *e) 01307 { 01308 // This is the primary key of an entry: 01309 // A lookup on the name, which must exist but might be translated 01310 // This requires some care for comparison since translations might be added 01311 return m_componentname + e->name().language() + ':' + e->name().representation(); 01312 } 01313 01314 QString CoreEngine::pid(const Provider *p) 01315 { 01316 // This is the primary key of a provider: 01317 // The download URL, which is never translated 01318 // If no download URL exists, a feed or web service URL must exist 01319 // if (p->downloadUrl().isValid()) 01320 // return p->downloadUrl().url(); 01321 QStringList feeds = p->feeds(); 01322 for (int i = 0; i < feeds.count(); i++) { 01323 QString feedtype = feeds.at(i); 01324 Feed *f = p->downloadUrlFeed(feedtype); 01325 if (f->feedUrl().isValid()) 01326 return m_componentname + f->feedUrl().url(); 01327 } 01328 if (p->webService().isValid()) 01329 return m_componentname + p->webService().url(); 01330 return m_componentname; 01331 } 01332 01333 bool CoreEngine::install(const QString &payloadfile) 01334 { 01335 QList<Entry*> entries = m_payloadfiles.keys(payloadfile); 01336 if (entries.size() != 1) { 01337 // FIXME: shouldn't ever happen - make this an assertion? 01338 kError() << "ASSERT: payloadfile is not associated" << endl; 01339 return false; 01340 } 01341 Entry *entry = entries.first(); 01342 01343 bool update = (entry->status() == Entry::Updateable); 01344 // FIXME: this is only so exposing the KUrl suffices for downloaded entries 01345 entry->setStatus(Entry::Installed); 01346 01347 // FIXME: first of all, do the security stuff here 01348 // this means check sum comparison and signature verification 01349 // signature verification might take a long time - make async?! 01350 01351 if (m_installation->checksumPolicy() != Installation::CheckNever) { 01352 if (entry->checksum().isEmpty()) { 01353 if (m_installation->checksumPolicy() == Installation::CheckIfPossible) { 01354 //kDebug() << "Skip checksum verification"; 01355 } else { 01356 kError() << "Checksum verification not possible" << endl; 01357 return false; 01358 } 01359 } else { 01360 //kDebug() << "Verify checksum..."; 01361 } 01362 } 01363 if (m_installation->signaturePolicy() != Installation::CheckNever) { 01364 if (entry->signature().isEmpty()) { 01365 if (m_installation->signaturePolicy() == Installation::CheckIfPossible) { 01366 //kDebug() << "Skip signature verification"; 01367 } else { 01368 kError() << "Signature verification not possible" << endl; 01369 return false; 01370 } 01371 } else { 01372 //kDebug() << "Verify signature..."; 01373 } 01374 } 01375 01376 //kDebug() << "INSTALL resourceDir " << m_installation->standardResourceDir(); 01377 //kDebug() << "INSTALL targetDir " << m_installation->targetDir(); 01378 //kDebug() << "INSTALL installPath " << m_installation->installPath(); 01379 //kDebug() << "INSTALL + scope " << m_installation->scope(); 01380 //kDebug() << "INSTALL + customName" << m_installation->customName(); 01381 //kDebug() << "INSTALL + uncompression " << m_installation->uncompression(); 01382 //kDebug() << "INSTALL + command " << m_installation->command(); 01383 01384 // Collect all files that were installed 01385 QStringList installedFiles; 01386 QString installpath(payloadfile); 01387 if (!m_installation->isRemote()) { 01388 // installdir is the target directory 01389 QString installdir; 01390 // installpath also contains the file name if it's a single file, otherwise equal to installdir 01391 int pathcounter = 0; 01392 if (!m_installation->standardResourceDir().isEmpty()) { 01393 if (m_installation->scope() == Installation::ScopeUser) { 01394 installdir = KStandardDirs::locateLocal(m_installation->standardResourceDir().toUtf8(), "/"); 01395 } else { // system scope 01396 installdir = KStandardDirs::installPath(m_installation->standardResourceDir().toUtf8()); 01397 } 01398 pathcounter++; 01399 } 01400 if (!m_installation->targetDir().isEmpty()) { 01401 if (m_installation->scope() == Installation::ScopeUser) { 01402 installdir = KStandardDirs::locateLocal("data", m_installation->targetDir() + '/'); 01403 } else { // system scope 01404 installdir = KStandardDirs::installPath("data") + m_installation->targetDir() + '/'; 01405 } 01406 pathcounter++; 01407 } 01408 if (!m_installation->installPath().isEmpty()) { 01409 #if defined(Q_WS_WIN) 01410 #ifndef _WIN32_WCE 01411 WCHAR wPath[MAX_PATH+1]; 01412 if ( SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, wPath) == S_OK) { 01413 installdir = QString::fromUtf16((const ushort *) wPath) + QLatin1Char('/') + m_installation->installPath() + QLatin1Char('/'); 01414 } else { 01415 #endif 01416 installdir = QDir::home().path() + QLatin1Char('/') + m_installation->installPath() + QLatin1Char('/'); 01417 #ifndef _WIN32_WCE 01418 } 01419 #endif 01420 #else 01421 installdir = QDir::home().path() + '/' + m_installation->installPath() + '/'; 01422 #endif 01423 pathcounter++; 01424 } 01425 if (!m_installation->absoluteInstallPath().isEmpty()) { 01426 installdir = m_installation->absoluteInstallPath() + '/'; 01427 pathcounter++; 01428 } 01429 if (pathcounter != 1) { 01430 kError() << "Wrong number of installation directories given." << endl; 01431 return false; 01432 } 01433 01434 kDebug() << "installdir: " << installdir; 01435 bool isarchive = true; 01436 01437 // respect the uncompress flag in the knsrc 01438 if (m_installation->uncompression() == "always" || m_installation->uncompression() == "archive") { 01439 // this is weird but a decompression is not a single name, so take the path instead 01440 installpath = installdir; 01441 KMimeType::Ptr mimeType = KMimeType::findByPath(payloadfile); 01442 //kDebug() << "Postinstallation: uncompress the file"; 01443 01444 // FIXME: check for overwriting, malicious archive entries (../foo) etc. 01445 // FIXME: KArchive should provide "safe mode" for this! 01446 KArchive *archive = 0; 01447 01448 if (mimeType->name() == "application/zip") { 01449 archive = new KZip(payloadfile); 01450 } else if (mimeType->name() == "application/tar" 01451 || mimeType->name() == "application/x-gzip" 01452 || mimeType->name() == "application/x-bzip" 01453 || mimeType->name() == "application/x-lzma" 01454 || mimeType->name() == "application/x-xz") { 01455 archive = new KTar(payloadfile); 01456 } else { 01457 delete archive; 01458 kError() << "Could not determine type of archive file '" << payloadfile << "'"; 01459 if (m_installation->uncompression() == "always") { 01460 return false; 01461 } 01462 isarchive = false; 01463 } 01464 01465 if (isarchive) { 01466 bool success = archive->open(QIODevice::ReadOnly); 01467 if (!success) { 01468 kError() << "Cannot open archive file '" << payloadfile << "'"; 01469 if (m_installation->uncompression() == "always") { 01470 return false; 01471 } 01472 // otherwise, just copy the file 01473 isarchive = false; 01474 } 01475 01476 if (isarchive) { 01477 const KArchiveDirectory *dir = archive->directory(); 01478 dir->copyTo(installdir); 01479 01480 installedFiles << archiveEntries(installdir, dir); 01481 installedFiles << installdir + '/'; 01482 01483 archive->close(); 01484 QFile::remove(payloadfile); 01485 delete archive; 01486 } 01487 } 01488 } 01489 01490 kDebug() << "isarchive: " << isarchive; 01491 01492 if (m_installation->uncompression() == "never" || (m_installation->uncompression() == "archive" && !isarchive)) { 01493 // no decompress but move to target 01494 01496 // FIXME: make naming convention configurable through *.knsrc? e.g. for kde-look.org image names 01497 KUrl source = KUrl(entry->payload().representation()); 01498 kDebug() << "installing non-archive from " << source.url(); 01499 QString installfile; 01500 QString ext = source.fileName().section('.', -1); 01501 if (m_installation->customName()) { 01502 installfile = entry->name().representation(); 01503 installfile += '-' + entry->version(); 01504 if (!ext.isEmpty()) installfile += '.' + ext; 01505 } else { 01506 installfile = source.fileName(); 01507 } 01508 installpath = installdir + '/' + installfile; 01509 01510 //kDebug() << "Install to file " << installpath; 01511 // FIXME: copy goes here (including overwrite checking) 01512 // FIXME: what must be done now is to update the cache *again* 01513 // in order to set the new payload filename (on root tag only) 01514 // - this might or might not need to take uncompression into account 01515 // FIXME: for updates, we might need to force an overwrite (that is, deleting before) 01516 QFile file(payloadfile); 01517 bool success = true; 01518 01519 if (QFile::exists(installpath) && update) { 01520 success = QFile::remove(installpath); 01521 } 01522 if (success) { 01523 success = file.rename(installpath); 01524 } 01525 if (!success) { 01526 kError() << "Cannot move file '" << payloadfile << "' to destination '" << installpath << "'"; 01527 return false; 01528 } 01529 installedFiles << installpath; 01530 installedFiles << installdir + '/'; 01531 } 01532 } 01533 01534 entry->setInstalledFiles(installedFiles); 01535 01536 if (!m_installation->command().isEmpty()) { 01537 KProcess process; 01538 QString command(m_installation->command()); 01539 QString fileArg(KShell::quoteArg(installpath)); 01540 command.replace("%f", fileArg); 01541 01542 //kDebug() << "Postinstallation: execute command"; 01543 //kDebug() << "Command is: " << command; 01544 01545 process.setShellCommand(command); 01546 int exitcode = process.execute(); 01547 01548 if (exitcode) { 01549 kError() << "Command failed" << endl; 01550 } else { 01551 //kDebug() << "Command executed successfully"; 01552 } 01553 } 01554 01555 // ==== FIXME: security code below must go above, when async handling is complete ==== 01556 01557 // FIXME: security object lifecycle - it is a singleton! 01558 Security *sec = Security::ref(); 01559 01560 connect(sec, 01561 SIGNAL(validityResult(int)), 01562 SLOT(slotInstallationVerification(int))); 01563 01564 // FIXME: change to accept filename + signature 01565 sec->checkValidity(QString()); 01566 01567 m_payloadfiles[entry] = installpath; 01568 registerEntry(entry); 01569 // FIXME: hm, do we need to update the cache really? 01570 // only registration is probably needed here 01571 01572 emit signalEntryChanged(entry); 01573 01574 return true; 01575 } 01576 01577 bool CoreEngine::uninstall(KNS::Entry *entry) 01578 { 01579 entry->setStatus(Entry::Deleted); 01580 01581 if (!m_installation->uninstallCommand().isEmpty()) { 01582 KProcess process; 01583 foreach (const QString& file, entry->installedFiles()) { 01584 QFileInfo info(file); 01585 if (info.isFile()) { 01586 QString fileArg(KShell::quoteArg(file)); 01587 QString command(m_installation->uninstallCommand()); 01588 command.replace("%f", fileArg); 01589 01590 process.setShellCommand(command); 01591 int exitcode = process.execute(); 01592 01593 if (exitcode) { 01594 kError() << "Command failed" << endl; 01595 } else { 01596 //kDebug() << "Command executed successfully"; 01597 } 01598 } 01599 } 01600 } 01601 01602 foreach(const QString &file, entry->installedFiles()) { 01603 if (file.endsWith('/')) { 01604 QDir dir; 01605 bool worked = dir.rmdir(file); 01606 if (!worked) { 01607 // Maybe directory contains user created files, ignore it 01608 continue; 01609 } 01610 } else { 01611 if (QFile::exists(file)) { 01612 bool worked = QFile::remove(file); 01613 if (!worked) { 01614 kWarning() << "unable to delete file " << file; 01615 return false; 01616 } 01617 } else { 01618 kWarning() << "unable to delete file " << file << ". file does not exist."; 01619 } 01620 } 01621 } 01622 entry->setUnInstalledFiles(entry->installedFiles()); 01623 entry->setInstalledFiles(QStringList()); 01624 unregisterEntry(entry); 01625 01626 emit signalEntryChanged(entry); 01627 01628 return true; 01629 } 01630 01631 void CoreEngine::slotInstallationVerification(int result) 01632 { 01633 //kDebug() << "SECURITY result " << result; 01634 01635 if (result & Security::SIGNED_OK) 01636 emit signalInstallationFinished(); 01637 else 01638 emit signalInstallationFailed(); 01639 } 01640 01641 void CoreEngine::setAutomationPolicy(AutomationPolicy policy) 01642 { 01643 m_automationpolicy = policy; 01644 } 01645 01646 void CoreEngine::setCachePolicy(CachePolicy policy) 01647 { 01648 m_cachepolicy = policy; 01649 } 01650 01651 QStringList KNS::CoreEngine::archiveEntries(const QString& path, const KArchiveDirectory * dir) 01652 { 01653 QStringList files; 01654 foreach(const QString &entry, dir->entries()) { 01655 QString childPath = path + '/' + entry; 01656 if (dir->entry(entry)->isFile()) { 01657 files << childPath; 01658 } 01659 01660 if (dir->entry(entry)->isDirectory()) { 01661 const KArchiveDirectory* childDir = static_cast<const KArchiveDirectory*>(dir->entry(entry)); 01662 files << archiveEntries(childPath, childDir); 01663 files << childPath + '/'; 01664 } 01665 } 01666 return files; 01667 } 01668 01669 01670 #include "coreengine.moc"
KDE 4.6 API Reference