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

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
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
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