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