• Skip to content
  • Skip to link menu
KDE 4.1 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • Contact Us
 

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

KNewStuff

Skip menu "KNewStuff"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • KIO
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • Kross
  • KUtils
  • Nepomuk
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.4
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal