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