diff --git a/knewstuff/knewstuff2/core/coreengine.cpp b/knewstuff/knewstuff2/core/coreengine.cpp index 2dad2292fa..5bef4933dc 100644 --- a/knewstuff/knewstuff2/core/coreengine.cpp +++ b/knewstuff/knewstuff2/core/coreengine.cpp @@ -1,1520 +1,1524 @@ /* This file is part of KNewStuff2. Copyright (c) 2007 Josef Spillner Copyright 2007 Frederik Gladhorn This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "coreengine.h" #include "entryhandler.h" #include "providerhandler.h" #include "entryloader.h" #include "providerloader.h" #include "installation.h" #include "security.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KNS; CoreEngine::CoreEngine(QObject* parent) : QObject(parent) { m_initialized = false; m_cachepolicy = CacheNever; m_automationpolicy = AutomationOn; m_uploadedentry = NULL; m_uploadprovider = NULL; m_installation = NULL; m_activefeeds = 0; } CoreEngine::~CoreEngine() { shutdown(); } bool CoreEngine::init(const QString &configfile) { //kDebug() << "Initializing KNS::CoreEngine from '" << configfile << "'"; KConfig conf(configfile); if (conf.accessMode() == KConfig::NoAccess) { kError() << "No knsrc file named '" << configfile << "' was found." << endl; return false; } // FIXME: accessMode() doesn't return NoAccess for non-existing files // - bug in kdecore? // - this needs to be looked at again until KConfig backend changes for KDE 4 // the check below is a workaround if (KStandardDirs::locate("config", configfile).isEmpty()) { kError() << "No knsrc file named '" << configfile << "' was found." << endl; return false; } if (!conf.hasGroup("KNewStuff2")) { kError() << "A knsrc file was found but it doesn't contain a KNewStuff2 section." << endl; return false; } KConfigGroup group = conf.group("KNewStuff2"); m_providersurl = group.readEntry("ProvidersUrl", QString()); //m_componentname = group.readEntry("ComponentName", QString()); m_componentname = QFileInfo(KStandardDirs::locate("config", configfile)).baseName() + ":"; // FIXME: add support for several categories later on // FIXME: read out only when actually installing as a performance improvement? m_installation = new Installation(); m_installation->setUncompression(group.readEntry("Uncompress", QString())); m_installation->setCommand(group.readEntry("InstallationCommand", QString())); m_installation->setStandardResourceDir(group.readEntry("StandardResource", QString())); m_installation->setTargetDir(group.readEntry("TargetDir", QString())); m_installation->setInstallPath(group.readEntry("InstallPath", QString())); m_installation->setCustomName(group.readEntry("CustomName", false)); QString checksumpolicy = group.readEntry("ChecksumPolicy", QString()); if (!checksumpolicy.isEmpty()) { if (checksumpolicy == "never") m_installation->setChecksumPolicy(Installation::CheckNever); else if (checksumpolicy == "ifpossible") m_installation->setChecksumPolicy(Installation::CheckIfPossible); else if (checksumpolicy == "always") m_installation->setChecksumPolicy(Installation::CheckAlways); else { kError() << "The checksum policy '" + checksumpolicy + "' is unknown." << endl; return false; } } QString signaturepolicy = group.readEntry("SignaturePolicy", QString()); if (!signaturepolicy.isEmpty()) { if (signaturepolicy == "never") m_installation->setSignaturePolicy(Installation::CheckNever); else if (signaturepolicy == "ifpossible") m_installation->setSignaturePolicy(Installation::CheckIfPossible); else if (signaturepolicy == "always") m_installation->setSignaturePolicy(Installation::CheckAlways); else { kError() << "The signature policy '" + signaturepolicy + "' is unknown." << endl; return false; } } QString scope = group.readEntry("Scope", QString()); if (!scope.isEmpty()) { if (scope == "user") m_installation->setScope(Installation::ScopeUser); else if (scope == "system") m_installation->setScope(Installation::ScopeSystem); else { kError() << "The scope '" + scope + "' is unknown." << endl; return false; } if (m_installation->scope() == Installation::ScopeSystem) { if (!m_installation->installPath().isEmpty()) { kError() << "System installation cannot be mixed with InstallPath." << endl; return false; } } } QString cachePolicy = group.readEntry("CachePolicy", QString()); if (!cachePolicy.isEmpty()) { if (cachePolicy == "never") { m_cachepolicy = CacheNever; } else if (cachePolicy == "replaceable") { m_cachepolicy = CacheReplaceable; } else if (cachePolicy == "resident") { m_cachepolicy = CacheResident; } else if (cachePolicy == "only") { m_cachepolicy = CacheOnly; } else { kError() << "Cache policy '" + cachePolicy + "' is unknown." << endl; } } m_initialized = true; return true; } void CoreEngine::start() { kDebug() << "starting engine"; if (!m_initialized) { kError() << "Must call KNS::CoreEngine::init() first." << endl; return; } if (m_cachepolicy != CacheNever) { loadProvidersCache(); #if 0 loadEntriesCache(); #endif } loadRegistry(); // FIXME: also return if CacheResident and its conditions fulfilled if (m_cachepolicy == CacheOnly) { emit signalEntriesFinished(); return; } ProviderLoader *provider_loader = new ProviderLoader(this); // make connections before loading, just in case the iojob is very fast connect(provider_loader, SIGNAL(signalProvidersLoaded(KNS::Provider::List)), SLOT(slotProvidersLoaded(KNS::Provider::List))); connect(provider_loader, SIGNAL(signalProvidersFailed()), SLOT(slotProvidersFailed())); provider_loader->load(m_providersurl); } void CoreEngine::loadEntries(Provider *provider) { kDebug() << "loading entries"; if (m_cachepolicy == CacheOnly) { return; } //if (provider != m_provider_index[pid(provider)]) { // // this is the cached provider, and a new provider has been loaded from the internet // // also, this provider's feeds have already been loaded including it's entries // m_provider_cache.removeAll(provider); // just in case it's still in there // return; //} QStringList feeds = provider->feeds(); for (int i = 0; i < feeds.count(); i++) { Feed *feed = provider->downloadUrlFeed(feeds.at(i)); if (feed) { ++m_activefeeds; EntryLoader *entry_loader = new EntryLoader(this); connect(entry_loader, SIGNAL(signalEntriesLoaded(KNS::Entry::List)), SLOT(slotEntriesLoaded(KNS::Entry::List))); connect(entry_loader, SIGNAL(signalEntriesFailed()), SLOT(slotEntriesFailed())); bool worked = connect(entry_loader, SIGNAL(signalProgress(KJob*, unsigned long)), SLOT(slotProgress(KJob*, unsigned long))); kDebug() << "signalprogress on entryloader " << worked; entry_loader->load(provider, feed); } } } void CoreEngine::downloadPreview(Entry *entry) { if (m_previewfiles.contains(entry)) { // FIXME: ensure somewhere else that preview file even exists //kDebug() << "Reusing preview from '" << m_previewfiles[entry] << "'"; emit signalPreviewLoaded(KUrl::fromPath(m_previewfiles[entry])); return; } KUrl source = KUrl(entry->preview().representation()); if (!source.isValid()) { kError() << "The entry doesn't have a preview." << endl; return; } KUrl destination = KGlobal::dirs()->saveLocation("tmp") + KRandom::randomString(10); //kDebug() << "Downloading preview '" << source << "' to '" << destination << "'"; // FIXME: check for validity KIO::FileCopyJob *job = KIO::file_copy(source, destination, -1, KIO::Overwrite | KIO::HideProgressInfo); connect(job, SIGNAL(result(KJob*)), SLOT(slotPreviewResult(KJob*))); bool worked = connect(job, SIGNAL(progress(KJob*, unsigned long)), SLOT(slotProgress(KJob*, unsigned long))); kDebug() << "download payload slotProgress connection: " << worked; m_entry_jobs[job] = entry; } void CoreEngine::downloadPayload(Entry *entry) { KUrl source = KUrl(entry->payload().representation()); if (!source.isValid()) { kError() << "The entry doesn't have a payload." << endl; return; } if (m_installation->isRemote()) { // Remote resource //kDebug() << "Relaying remote payload '" << source << "'"; entry->setStatus(Entry::Installed); + m_payloadfiles[entry] = entry->payload().representation(); emit signalPayloadLoaded(source); // FIXME: we still need registration for eventual deletion return; } KUrl destination = KGlobal::dirs()->saveLocation("tmp") + KRandom::randomString(10); //kDebug() << "Downloading payload '" << source << "' to '" << destination << "'"; // FIXME: check for validity KIO::FileCopyJob *job = KIO::file_copy(source, destination, -1, KIO::Overwrite | KIO::HideProgressInfo); connect(job, SIGNAL(result(KJob*)), SLOT(slotPayloadResult(KJob*))); connect(job, SIGNAL(percent(KJob*, unsigned long)), SLOT(slotProgress(KJob*, unsigned long))); m_entry_jobs[job] = entry; } bool CoreEngine::uploadEntry(Provider *provider, Entry *entry) { //kDebug() << "Uploading " << entry->name().representation() << "..."; if (m_uploadedentry) { kError() << "Another upload is in progress!" << endl; return false; } if (!provider->uploadUrl().isValid()) { kError() << "The provider doesn't support uploads." << endl; return false; // FIXME: support for will go here (file bundle creation etc.) } // FIXME: validate files etc. m_uploadedentry = entry; KUrl sourcepayload = KUrl(entry->payload().representation()); KUrl destfolder = provider->uploadUrl(); destfolder.setFileName(sourcepayload.fileName()); KIO::FileCopyJob *fcjob = KIO::file_copy(sourcepayload, destfolder, -1, KIO::Overwrite | KIO::HideProgressInfo); connect(fcjob, SIGNAL(result(KJob*)), SLOT(slotUploadPayloadResult(KJob*))); return true; } void CoreEngine::slotProvidersLoaded(KNS::Provider::List list) { ProviderLoader *loader = dynamic_cast(sender()); delete loader; mergeProviders(list); } void CoreEngine::slotProvidersFailed() { ProviderLoader *loader = dynamic_cast(sender()); delete loader; emit signalProvidersFailed(); } void CoreEngine::slotEntriesLoaded(KNS::Entry::List list) { EntryLoader *loader = dynamic_cast(sender()); if (!loader) return; const Provider *provider = loader->provider(); Feed *feed = loader->feed(); delete loader; m_activefeeds--; //kDebug() << "entriesloaded m_activefeeds: " << m_activefeeds; //kDebug() << "Provider source " << provider->name().representation(); //kDebug() << "Feed source " << feed->name().representation(); //kDebug() << "Feed data: " << feed; mergeEntries(list, feed, provider); } void CoreEngine::slotEntriesFailed() { EntryLoader *loader = dynamic_cast(sender()); delete loader; m_activefeeds--; emit signalEntriesFailed(); } void CoreEngine::slotProgress(KJob *job, unsigned long percent) { QString url; KIO::FileCopyJob * copyJob = qobject_cast(job); KIO::TransferJob * transferJob = qobject_cast(job); if (copyJob != NULL) { url = copyJob->srcUrl().fileName(); } else if (transferJob != NULL) { url = transferJob->url().fileName(); } QString message = QString("loading %1").arg(url); emit signalProgress(message, percent); } void CoreEngine::slotPayloadResult(KJob *job) { if (job->error()) { kError() << "Cannot load payload file." << endl; kError() << job->errorString() << endl; m_entry_jobs.remove(job); emit signalPayloadFailed(); } else { KIO::FileCopyJob *fcjob = static_cast(job); if (m_entry_jobs.contains(job)) { // FIXME: this is only so exposing the KUrl suffices for downloaded entries Entry *entry = m_entry_jobs[job]; entry->setStatus(Entry::Installed); m_entry_jobs.remove(job); m_payloadfiles[entry] = fcjob->destUrl().path(); } // FIXME: ignore if not? shouldn't happen... emit signalPayloadLoaded(fcjob->destUrl()); } } // FIXME: this should be handled more internally to return a (cached) preview image void CoreEngine::slotPreviewResult(KJob *job) { if (job->error()) { kError() << "Cannot load preview file." << endl; kError() << job->errorString() << endl; m_entry_jobs.remove(job); emit signalPreviewFailed(); } else { KIO::FileCopyJob *fcjob = static_cast(job); if (m_entry_jobs.contains(job)) { // now, assign temporary filename to entry and update entry cache Entry *entry = m_entry_jobs[job]; m_entry_jobs.remove(job); m_previewfiles[entry] = fcjob->destUrl().path(); cacheEntry(entry); } // FIXME: ignore if not? shouldn't happen... emit signalPreviewLoaded(fcjob->destUrl()); } } void CoreEngine::slotUploadPayloadResult(KJob *job) { if (job->error()) { kError() << "Cannot upload payload file." << endl; kError() << job->errorString() << endl; m_uploadedentry = NULL; m_uploadprovider = NULL; emit signalEntryFailed(); return; } if (m_uploadedentry->preview().isEmpty()) { // FIXME: we abuse 'job' here for the shortcut if there's no preview slotUploadPreviewResult(job); return; } KUrl sourcepreview = KUrl(m_uploadedentry->preview().representation()); KUrl destfolder = m_uploadprovider->uploadUrl(); KIO::FileCopyJob *fcjob = KIO::file_copy(sourcepreview, destfolder, -1, KIO::Overwrite | KIO::HideProgressInfo); connect(fcjob, SIGNAL(result(KJob*)), SLOT(slotUploadPreviewResult(KJob*))); } void CoreEngine::slotUploadPreviewResult(KJob *job) { if (job->error()) { kError() << "Cannot upload preview file." << endl; kError() << job->errorString() << endl; m_uploadedentry = NULL; m_uploadprovider = NULL; emit signalEntryFailed(); return; } // FIXME: the following save code is also in cacheEntry() // when we upload, the entry should probably be cached! // FIXME: adhere to meta naming rules as discussed KUrl sourcemeta = KGlobal::dirs()->saveLocation("tmp") + KRandom::randomString(10) + ".meta"; KUrl destfolder = m_uploadprovider->uploadUrl(); EntryHandler eh(*m_uploadedentry); QDomElement exml = eh.entryXML(); QFile f(sourcemeta.path()); if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { kError() << "Cannot write meta information to '" << sourcemeta << "'." << endl; m_uploadedentry = NULL; m_uploadprovider = NULL; emit signalEntryFailed(); return; } QTextStream metastream(&f); metastream << exml; f.close(); KIO::FileCopyJob *fcjob = KIO::file_copy(sourcemeta, destfolder, -1, KIO::Overwrite | KIO::HideProgressInfo); connect(fcjob, SIGNAL(result(KJob*)), SLOT(slotUploadMetaResult(KJob*))); } void CoreEngine::slotUploadMetaResult(KJob *job) { if (job->error()) { kError() << "Cannot upload meta file." << endl; kError() << job->errorString() << endl; m_uploadedentry = NULL; m_uploadprovider = NULL; emit signalEntryFailed(); return; } else { m_uploadedentry = NULL; m_uploadprovider = NULL; //KIO::FileCopyJob *fcjob = static_cast(job); emit signalEntryUploaded(); } } void CoreEngine::loadRegistry() { KStandardDirs d; //kDebug() << "Loading registry of files for the component: " << m_componentname; QString realAppName = m_componentname.split(":")[0]; // this must be same as in registerEntry() QStringList dirs = d.findDirs("data", "knewstuff2-entries.registry"); for (QStringList::Iterator it = dirs.begin(); it != dirs.end(); ++it) { //kDebug() << " + Load from directory '" + (*it) + "'."; QDir dir((*it)); QStringList files = dir.entryList(QDir::Files | QDir::Readable); for (QStringList::iterator fit = files.begin(); fit != files.end(); ++fit) { QString filepath = (*it) + '/' + (*fit); //kDebug() << " + Load from file '" + filepath + "'."; bool ret; QFileInfo info(filepath); QFile f(filepath); // first see if this file is even for this app // because the registry contains entries for all apps QString thisAppName = QString::fromUtf8(QByteArray::fromBase64(info.baseName().toUtf8())); // NOTE: the ":" needs to always coincide with the separator character used in // the id(Entry*) method thisAppName = thisAppName.split(":")[0]; if (thisAppName != realAppName) { continue; } ret = f.open(QIODevice::ReadOnly); if (!ret) { kWarning() << "The file could not be opened."; continue; } QDomDocument doc; ret = doc.setContent(&f); if (!ret) { kWarning() << "The file could not be parsed."; continue; } QDomElement root = doc.documentElement(); if (root.tagName() != "ghnsinstall") { kWarning() << "The file doesn't seem to be of interest."; continue; } QDomElement stuff = root.firstChildElement("stuff"); if (stuff.isNull()) { kWarning() << "Missing GHNS installation metadata."; continue; } EntryHandler handler(stuff); if (!handler.isValid()) { kWarning() << "Invalid GHNS installation metadata."; continue; } Entry *e = handler.entryptr(); e->setStatus(Entry::Installed); QString thisid = id(e); // we must overwrite cache entries with registered entries // and not just append the latter ones if (m_entry_index.contains(thisid)) { // it's in the cache, so replace the cache entry with the registered entry Entry * oldEntry = m_entry_index[thisid]; int index = m_entry_cache.indexOf(oldEntry); m_entry_cache[index] = e; delete oldEntry; } else { m_entry_cache.append(e); } m_entry_index[thisid] = e; } } } void CoreEngine::loadProvidersCache() { KStandardDirs d; // use the componentname so we get the cache specific to this knsrc (kanagram, wallpaper, etc.) QString cachefile = d.findResource("cache", m_componentname + "kns2providers.cache.xml"); if (cachefile.isEmpty()) { kDebug() << "Cache not present, skip loading."; return; } kDebug() << "Loading provider cache from file '" + cachefile + "'."; // make sure we can open and read the file bool ret; QFile f(cachefile); ret = f.open(QIODevice::ReadOnly); if (!ret) { kWarning() << "The file could not be opened."; return; } // make sure it's valid xml QDomDocument doc; ret = doc.setContent(&f); if (!ret) { kWarning() << "The file could not be parsed."; return; } // make sure there's a root tag QDomElement root = doc.documentElement(); if (root.tagName() != "ghnsproviders") { kWarning() << "The file doesn't seem to be of interest."; return; } // get the first provider QDomElement provider = root.firstChildElement("provider"); if (provider.isNull()) { kWarning() << "Missing provider entries in the cache."; return; } // handle each provider while (!provider.isNull()) { ProviderHandler handler(provider); if (!handler.isValid()) { kWarning() << "Invalid provider metadata."; continue; } Provider *p = handler.providerptr(); m_provider_cache.append(p); m_provider_index[pid(p)] = p; emit signalProviderLoaded(p); loadFeedCache(p); // no longer needed because EnginePrivate::slotProviderLoaded calls loadEntries //if (m_automationpolicy == AutomationOn) { // loadEntries(p); //} provider = provider.nextSiblingElement("provider"); } } void CoreEngine::loadFeedCache(Provider *provider) { KStandardDirs d; kDebug() << "Loading feed cache."; QStringList cachedirs = d.findDirs("cache", m_componentname + "kns2feeds.cache"); if (cachedirs.size() == 0) { kDebug() << "Cache directory not present, skip loading."; return; } QString cachedir = cachedirs.first(); QStringList entrycachedirs = d.findDirs("cache", "knewstuff2-entries.cache/"); if (entrycachedirs.size() == 0) { kDebug() << "Cache directory not present, skip loading."; return; } QString entrycachedir = entrycachedirs.first(); kDebug() << "Load from directory '" + cachedir + "'"; QStringList feeds = provider->feeds(); for (int i = 0; i < feeds.count(); i++) { Feed *feed = provider->downloadUrlFeed(feeds.at(i)); QString feedname = feeds.at(i); QString idbase64 = QString(pid(provider).toUtf8().toBase64() + '-' + feedname); QString cachefile = cachedir + '/' + idbase64 + ".xml"; kDebug() << " + Load from file '" + cachefile + "'."; bool ret; QFile f(cachefile); ret = f.open(QIODevice::ReadOnly); if (!ret) { kWarning() << "The file could not be opened."; return; } QDomDocument doc; ret = doc.setContent(&f); if (!ret) { kWarning() << "The file could not be parsed."; return; } QDomElement root = doc.documentElement(); if (root.tagName() != "ghnsfeeds") { kWarning() << "The file doesn't seem to be of interest."; return; } QDomElement entryel = root.firstChildElement("entry-id"); if (entryel.isNull()) { kWarning() << "Missing entries in the cache."; return; } while (!entryel.isNull()) { QString idbase64 = entryel.text(); kDebug() << "loading cache for entry: " << QByteArray::fromBase64(idbase64.toUtf8()); QString filepath = entrycachedir + '/' + idbase64 + ".meta"; kDebug() << "from file '" + filepath + "'."; // FIXME: pass feed and make loadEntryCache return void for consistency? Entry *entry = loadEntryCache(filepath); if (entry) { feed->addEntry(entry); emit signalEntryLoaded(entry, feed, provider); } entryel = entryel.nextSiblingElement("entry-id"); } } } KNS::Entry *CoreEngine::loadEntryCache(const QString& filepath) { bool ret; QFile f(filepath); ret = f.open(QIODevice::ReadOnly); if (!ret) { kWarning() << "The file could not be opened."; return NULL; } QDomDocument doc; ret = doc.setContent(&f); if (!ret) { kWarning() << "The file could not be parsed."; return NULL; } QDomElement root = doc.documentElement(); if (root.tagName() != "ghnscache") { kWarning() << "The file doesn't seem to be of interest."; return NULL; } QDomElement stuff = root.firstChildElement("stuff"); if (stuff.isNull()) { kWarning() << "Missing GHNS cache metadata."; return NULL; } EntryHandler handler(stuff); if (!handler.isValid()) { kWarning() << "Invalid GHNS installation metadata."; return NULL; } Entry *e = handler.entryptr(); e->setStatus(Entry::Downloadable); m_entry_cache.append(e); m_entry_index[id(e)] = e; if (root.hasAttribute("previewfile")) { m_previewfiles[e] = root.attribute("previewfile"); // FIXME: check here for a [ -f previewfile ] } if (root.hasAttribute("payloadfile")) { m_payloadfiles[e] = root.attribute("payloadfile"); // FIXME: check here for a [ -f payloadfile ] } return e; } // FIXME: not needed anymore? #if 0 void CoreEngine::loadEntriesCache() { KStandardDirs d; //kDebug() << "Loading entry cache."; QStringList cachedirs = d.findDirs("cache", "knewstuff2-entries.cache/" + m_componentname); if (cachedirs.size() == 0) { //kDebug() << "Cache directory not present, skip loading."; return; } QString cachedir = cachedirs.first(); //kDebug() << " + Load from directory '" + cachedir + "'."; QDir dir(cachedir); QStringList files = dir.entryList(QDir::Files | QDir::Readable); for (QStringList::iterator fit = files.begin(); fit != files.end(); ++fit) { QString filepath = cachedir + '/' + (*fit); //kDebug() << " + Load from file '" + filepath + "'."; Entry *e = loadEntryCache(filepath); if (e) { // FIXME: load provider/feed information first emit signalEntryLoaded(e, NULL, NULL); } } } #endif void CoreEngine::shutdown() { m_entry_index.clear(); m_provider_index.clear(); qDeleteAll(m_entry_cache); qDeleteAll(m_provider_cache); m_entry_cache.clear(); m_provider_cache.clear(); delete m_installation; } bool CoreEngine::providerCached(Provider *provider) { if (m_cachepolicy == CacheNever) return false; if (m_provider_index.contains(pid(provider))) return true; return false; } bool CoreEngine::providerChanged(Provider *oldprovider, Provider *provider) { QStringList oldfeeds = oldprovider->feeds(); QStringList feeds = provider->feeds(); if (oldfeeds.count() != feeds.count()) return true; for (int i = 0; i < feeds.count(); i++) { Feed *oldfeed = oldprovider->downloadUrlFeed(feeds.at(i)); Feed *feed = provider->downloadUrlFeed(feeds.at(i)); if (!oldfeed) return true; if (feed->feedUrl() != oldfeed->feedUrl()) return true; } return false; } void CoreEngine::mergeProviders(Provider::List providers) { for (Provider::List::Iterator it = providers.begin(); it != providers.end(); ++it) { Provider *p = (*it); if (providerCached(p)) { kDebug() << "CACHE: hit provider " << p->name().representation(); Provider *oldprovider = m_provider_index[pid(p)]; if (providerChanged(oldprovider, p)) { kDebug() << "CACHE: update provider"; cacheProvider(p); emit signalProviderChanged(p); } // oldprovider can now be deleted, see entry hit case // also take it out of m_provider_cache and m_provider_index //m_provider_cache.removeAll(oldprovider); //delete oldprovider; } else { if (m_cachepolicy != CacheNever) { kDebug() << "CACHE: miss provider " << p->name().representation(); cacheProvider(p); } emit signalProviderLoaded(p); // no longer needed, because slotProviderLoaded calls loadEntries() //if (m_automationpolicy == AutomationOn) { // loadEntries(p); //} } m_provider_cache.append(p); m_provider_index[pid(p)] = p; } emit signalProvidersFinished(); } bool CoreEngine::entryCached(Entry *entry) { if (m_cachepolicy == CacheNever) return false; // Direct cache lookup first // FIXME: probably better use URL (changes less frequently) and do iteration if (m_entry_index.contains(id(entry))) return true; // If entry wasn't found, either // - a translation was added which matches our locale better, or // - our locale preferences changed, or both. // In that case we've got to find the old name in the new entry, // since we assume that translations are always added but never removed. for (int i = 0; i < m_entry_cache.count(); i++) { Entry *oldentry = m_entry_cache.at(i); if (id(entry) == id(oldentry)) return true; //QString lang = id(oldentry).section(":", 0, 0); //QString oldname = oldentry->name().translated(lang); //QString name = entry->name().translated(lang); ////kDebug() << "CACHE: compare entry names " << oldname << "/" << name; //if (name == oldname) return true; } return false; } bool CoreEngine::entryChanged(Entry *oldentry, Entry *entry) { if ((!oldentry) || (entry->releaseDate() > oldentry->releaseDate()) || (entry->version() > oldentry->version()) || (entry->release() > oldentry->release())) return true; return false; } void CoreEngine::mergeEntries(Entry::List entries, Feed *feed, const Provider *provider) { //kDebug() << "merging entries: "; for (Entry::List::Iterator it = entries.begin(); it != entries.end(); ++it) { // TODO: find entry in entrycache, replace if needed // don't forget marking as 'updateable' Entry *e = (*it); // set it to Installed if it's in the registry // FIXME: just because it's in m_entry_index, does not mean it's in the registry... if (m_entry_index.contains(id(e))) { // see if the one online is newer (higher version, release, or release date) Entry *oldentry = m_entry_index[id(e)]; e->setInstalledFiles(oldentry->installedFiles()); if (entryChanged(oldentry, e)) { e->setStatus(Entry::Updateable); emit signalEntryChanged(e); } else { e->setStatus(oldentry->status()); } emit signalEntryLoaded(e, feed, provider); feed->removeEntry(oldentry); } else { e->setStatus(Entry::Downloadable); if (entryCached(e)) { //kDebug() << "CACHE: hit entry " << e->name().representation(); // FIXME: separate version updates from server-side translation updates? Entry *cachedentry = m_entry_index[id(e)]; if (entryChanged(cachedentry, e)) { //kDebug() << "CACHE: update entry"; e->setStatus(Entry::Updateable); // entry has changed if (m_cachepolicy != CacheNever) { cacheEntry(e); } emit signalEntryChanged(e); // FIXME: cachedentry can now be deleted, but it's still in the list! // FIXME: better: assigne all values to 'e', keeps refs intact } kDebug() << "removing entry " << cachedentry->name().representation() << " from feed"; // take cachedentry out of the feed feed->removeEntry(cachedentry); } else { if (m_cachepolicy != CacheNever) { //kDebug() << "CACHE: miss entry " << e->name().representation(); cacheEntry(e); } emit signalEntryLoaded(e, feed, provider); } m_entry_cache.append(e); m_entry_index[id(e)] = e; } } if (m_cachepolicy != CacheNever) { // extra code to get the feedname from the provider, we could use feed->name().representation() // but would need to remove spaces, and latinize it since it can be any encoding // besides feeds.size() has a max of 4 currently (unsorted, score, downloads, and latest) QStringList feeds = provider->feeds(); QString feedname; for (int i = 0; i < feeds.size(); ++i) { if (provider->downloadUrlFeed(feeds[i]) == feed) { feedname = feeds[i]; } } cacheFeed(provider, feedname, feed, entries); } emit signalEntriesFeedFinished(feed); if (m_activefeeds == 0) { emit signalEntriesFinished(); } } void CoreEngine::cacheProvider(Provider *provider) { KStandardDirs d; kDebug() << "Caching provider."; QString cachedir = d.saveLocation("cache"); QString cachefile = cachedir + m_componentname + "kns2providers.cache.xml"; kDebug() << " + Save to file '" + cachefile + "'."; QDomDocument doc; QDomElement root = doc.createElement("ghnsproviders"); for (Provider::List::Iterator it = m_provider_cache.begin(); it != m_provider_cache.end(); ++it) { Provider *p = (*it); ProviderHandler ph(*p); QDomElement pxml = ph.providerXML(); root.appendChild(pxml); } ProviderHandler ph(*provider); QDomElement pxml = ph.providerXML(); root.appendChild(pxml); QFile f(cachefile); if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { kError() << "Cannot write meta information to '" << cachedir << "'." << endl; // FIXME: ignore? return; } QTextStream metastream(&f); metastream << root; f.close(); /*QStringList feeds = p->feeds(); for(int i = 0; i < feeds.count(); i++) { Feed *feed = p->downloadUrlFeed(feeds.at(i)); cacheFeed(p, feeds.at(i), feed); }*/ } void CoreEngine::cacheFeed(const Provider *provider, QString feedname, const Feed *feed, Entry::List entries) { // feed cache file is a list of entry-id's that are part of this feed KStandardDirs d; Q_UNUSED(feed); QString cachedir = d.saveLocation("cache", m_componentname + "kns2feeds.cache"); QString idbase64 = QString(pid(provider).toUtf8().toBase64() + '-' + feedname); QString cachefile = idbase64 + ".xml"; kDebug() << "Caching feed to file '" + cachefile + "'."; QDomDocument doc; QDomElement root = doc.createElement("ghnsfeeds"); for (int i = 0; i < entries.count(); i++) { QString idbase64 = id(entries.at(i)).toUtf8().toBase64(); QDomElement entryel = doc.createElement("entry-id"); root.appendChild(entryel); QDomText entrytext = doc.createTextNode(idbase64); entryel.appendChild(entrytext); } QFile f(cachedir + cachefile); if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { kError() << "Cannot write meta information to '" << cachedir + cachefile << "'." << endl; // FIXME: ignore? return; } QTextStream metastream(&f); metastream << root; f.close(); } void CoreEngine::cacheEntry(Entry *entry) { KStandardDirs d; QString cachedir = d.saveLocation("cache", "knewstuff2-entries.cache/"); kDebug() << "Caching entry in directory '" + cachedir + "'."; //FIXME: this must be deterministic, but it could also be an OOB random string //which gets stored into just like preview... QString idbase64 = QString(id(entry).toUtf8().toBase64()); QString cachefile = idbase64 + ".meta"; kDebug() << "Caching to file '" + cachefile + "'."; // FIXME: adhere to meta naming rules as discussed // FIXME: maybe related filename to base64-encoded id(), or the reverse? EntryHandler eh(*entry); QDomElement exml = eh.entryXML(); QDomDocument doc; QDomElement root = doc.createElement("ghnscache"); root.appendChild(exml); if (m_previewfiles.contains(entry)) { root.setAttribute("previewfile", m_previewfiles[entry]); } /*if (m_payloadfiles.contains(entry)) { root.setAttribute("payloadfile", m_payloadfiles[entry]); }*/ QFile f(cachedir + cachefile); if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { kError() << "Cannot write meta information to '" << cachedir + cachefile << "'." << endl; // FIXME: ignore? return; } QTextStream metastream(&f); metastream << root; f.close(); } void CoreEngine::registerEntry(Entry *entry) { KStandardDirs d; //kDebug() << "Registering entry."; // NOTE: this directory must match loadRegistry QString registrydir = d.saveLocation("data", "knewstuff2-entries.registry"); //kDebug() << " + Save to directory '" + registrydir + "'."; // FIXME: see cacheEntry() for naming-related discussion QString registryfile = QString(id(entry).toUtf8().toBase64()) + ".meta"; //kDebug() << " + Save to file '" + registryfile + "'."; EntryHandler eh(*entry); QDomElement exml = eh.entryXML(); QDomDocument doc; QDomElement root = doc.createElement("ghnsinstall"); root.appendChild(exml); if (m_payloadfiles.contains(entry)) { root.setAttribute("payloadfile", m_payloadfiles[entry]); } QFile f(registrydir + registryfile); if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { kError() << "Cannot write meta information to '" << registrydir + registryfile << "'." << endl; // FIXME: ignore? return; } QTextStream metastream(&f); metastream << root; f.close(); } void KNS::CoreEngine::unregisterEntry(Entry * entry) { KStandardDirs d; // NOTE: this directory must match loadRegistry QString registrydir = d.saveLocation("data", "knewstuff2-entries.registry"); // FIXME: see cacheEntry() for naming-related discussion QString registryfile = QString(id(entry).toUtf8().toBase64()) + ".meta"; QFile::remove(registrydir + registryfile); } QString CoreEngine::id(Entry *e) { // This is the primary key of an entry: // A lookup on the name, which must exist but might be translated // This requires some care for comparison since translations might be added return m_componentname + e->name().language() + ':' + e->name().representation(); } QString CoreEngine::pid(const Provider *p) { // This is the primary key of a provider: // The download URL, which is never translated // If no download URL exists, a feed or web service URL must exist // if (p->downloadUrl().isValid()) // return p->downloadUrl().url(); QStringList feeds = p->feeds(); for (int i = 0; i < feeds.count(); i++) { QString feedtype = feeds.at(i); Feed *f = p->downloadUrlFeed(feedtype); if (f->feedUrl().isValid()) return m_componentname + f->feedUrl().url(); } if (p->webService().isValid()) return m_componentname + p->webService().url(); return m_componentname; } bool CoreEngine::install(const QString &payloadfile) { QList entries = m_payloadfiles.keys(payloadfile); if (entries.size() != 1) { // FIXME: shouldn't ever happen - make this an assertion? kError() << "ASSERT: payloadfile is not associated" << endl; return false; } Entry *entry = entries.first(); // FIXME: first of all, do the security stuff here // this means check sum comparison and signature verification // signature verification might take a long time - make async?! if (m_installation->checksumPolicy() != Installation::CheckNever) { if (entry->checksum().isEmpty()) { if (m_installation->checksumPolicy() == Installation::CheckIfPossible) { //kDebug() << "Skip checksum verification"; } else { kError() << "Checksum verification not possible" << endl; return false; } } else { //kDebug() << "Verify checksum..."; } } if (m_installation->signaturePolicy() != Installation::CheckNever) { if (entry->signature().isEmpty()) { if (m_installation->signaturePolicy() == Installation::CheckIfPossible) { //kDebug() << "Skip signature verification"; } else { kError() << "Signature verification not possible" << endl; return false; } } else { //kDebug() << "Verify signature..."; } } //kDebug() << "INSTALL resourceDir " << m_installation->standardResourceDir(); //kDebug() << "INSTALL targetDir " << m_installation->targetDir(); //kDebug() << "INSTALL installPath " << m_installation->installPath(); //kDebug() << "INSTALL + scope " << m_installation->scope(); //kDebug() << "INSTALL + customName" << m_installation->customName(); //kDebug() << "INSTALL + uncompression " << m_installation->uncompression(); //kDebug() << "INSTALL + command " << m_installation->command(); - // installdir is the target directory - QString installdir; - // installpath also contains the file name if it's a single file, otherwise equal to installdir - QString installpath; - int pathcounter = 0; - if (!m_installation->standardResourceDir().isEmpty()) { - if (m_installation->scope() == Installation::ScopeUser) - installdir = KStandardDirs::locateLocal(m_installation->standardResourceDir().toUtf8(), "/"); - else - installdir = KStandardDirs::locate(m_installation->standardResourceDir().toUtf8(), "/"); - pathcounter++; - } - if (!m_installation->targetDir().isEmpty()) { - if (m_installation->scope() == Installation::ScopeUser) - installdir = KStandardDirs::locateLocal("data", m_installation->targetDir() + '/'); - else - installdir = KStandardDirs::locate("data", m_installation->targetDir() + '/'); - pathcounter++; - } - if (!m_installation->installPath().isEmpty()) { - installdir = QDir::home().path() + '/' + m_installation->installPath() + '/'; - pathcounter++; - } - - if (pathcounter != 1) { - kError() << "Wrong number of installation directories given." << endl; - return false; - } - // Collect all files that were installed QStringList installedFiles; - - // respect the uncompress flag in the knsrc - if (!m_installation->uncompression().isEmpty()) { - // this is weird but a decompression is not a single name, so take the path instead - installpath = installdir; - KMimeType::Ptr mimeType = KMimeType::findByPath(payloadfile); - //kDebug() << "Postinstallation: uncompress the file"; - - // FIXME: check for overwriting, malicious archive entries (../foo) etc. - // FIXME: KArchive should provide "safe mode" for this! - KArchive *archive = 0; - - if (mimeType->name() == "application/zip") { - archive = new KZip(payloadfile); - } else if (mimeType->name() == "application/tar" - || mimeType->name() == "application/x-gzip" - || mimeType->name() == "application/x-bzip") { - archive = new KTar(payloadfile); - } else { - delete archive; - kError() << "Could not determine type of archive file '" << payloadfile << "'"; - return false; + QString installpath(payloadfile); + + if (!m_installation->isRemote()) { + // installdir is the target directory + QString installdir; + // installpath also contains the file name if it's a single file, otherwise equal to installdir + int pathcounter = 0; + if (!m_installation->standardResourceDir().isEmpty()) { + if (m_installation->scope() == Installation::ScopeUser) + installdir = KStandardDirs::locateLocal(m_installation->standardResourceDir().toUtf8(), "/"); + else + installdir = KStandardDirs::locate(m_installation->standardResourceDir().toUtf8(), "/"); + pathcounter++; } - - bool success = archive->open(QIODevice::ReadOnly); - if (!success) { - kError() << "Cannot open archive file '" << payloadfile << "'"; + if (!m_installation->targetDir().isEmpty()) { + if (m_installation->scope() == Installation::ScopeUser) + installdir = KStandardDirs::locateLocal("data", m_installation->targetDir() + '/'); + else + installdir = KStandardDirs::locate("data", m_installation->targetDir() + '/'); + pathcounter++; + } + if (!m_installation->installPath().isEmpty()) { + installdir = QDir::home().path() + '/' + m_installation->installPath() + '/'; + pathcounter++; + } + + if (pathcounter != 1) { + kError() << "Wrong number of installation directories given." << endl; return false; } - const KArchiveDirectory *dir = archive->directory(); - dir->copyTo(installdir); - - installedFiles << archiveEntries(installdir, dir); - installedFiles << installdir + "/"; - archive->close(); - QFile::remove(payloadfile); - delete archive; - - } else { - // no decompress but move to target - - /// @todo when using KIO::get the http header can be accessed and it contains a real file name. - // FIXME: make naming convention configurable through *.knsrc? e.g. for kde-look.org image names - KUrl source = KUrl(entry->payload().representation()); - QString installfile; - QString ext = source.fileName().section('.', -1); - if (m_installation->customName()) { - installfile = entry->name().representation(); - installfile += '-' + entry->version(); - if (!ext.isEmpty()) installfile += '.' + ext; + // respect the uncompress flag in the knsrc + if (!m_installation->uncompression().isEmpty()) { + // this is weird but a decompression is not a single name, so take the path instead + installpath = installdir; + KMimeType::Ptr mimeType = KMimeType::findByPath(payloadfile); + //kDebug() << "Postinstallation: uncompress the file"; + + // FIXME: check for overwriting, malicious archive entries (../foo) etc. + // FIXME: KArchive should provide "safe mode" for this! + KArchive *archive = 0; + + if (mimeType->name() == "application/zip") { + archive = new KZip(payloadfile); + } else if (mimeType->name() == "application/tar" + || mimeType->name() == "application/x-gzip" + || mimeType->name() == "application/x-bzip") { + archive = new KTar(payloadfile); + } else { + delete archive; + kError() << "Could not determine type of archive file '" << payloadfile << "'"; + return false; + } + + bool success = archive->open(QIODevice::ReadOnly); + if (!success) { + kError() << "Cannot open archive file '" << payloadfile << "'"; + return false; + } + const KArchiveDirectory *dir = archive->directory(); + dir->copyTo(installdir); + + installedFiles << archiveEntries(installdir, dir); + installedFiles << installdir + "/"; + + archive->close(); + QFile::remove(payloadfile); + delete archive; + } else { - installfile = source.fileName(); - } - installpath = installdir + '/' + installfile; - - //kDebug() << "Install to file " << installpath; - // FIXME: copy goes here (including overwrite checking) - // FIXME: what must be done now is to update the cache *again* - // in order to set the new payload filename (on root tag only) - // - this might or might not need to take uncompression into account - // FIXME: for updates, we might need to force an overwrite (that is, deleting before) - QFile file(payloadfile); - - bool success = file.rename(installpath); - if (!success) { - kError() << "Cannot move file '" << payloadfile << "' to destination '" << installpath << "'"; - return false; + // no decompress but move to target + + /// @todo when using KIO::get the http header can be accessed and it contains a real file name. + // FIXME: make naming convention configurable through *.knsrc? e.g. for kde-look.org image names + KUrl source = KUrl(entry->payload().representation()); + QString installfile; + QString ext = source.fileName().section('.', -1); + if (m_installation->customName()) { + installfile = entry->name().representation(); + installfile += '-' + entry->version(); + if (!ext.isEmpty()) installfile += '.' + ext; + } else { + installfile = source.fileName(); + } + installpath = installdir + '/' + installfile; + + //kDebug() << "Install to file " << installpath; + // FIXME: copy goes here (including overwrite checking) + // FIXME: what must be done now is to update the cache *again* + // in order to set the new payload filename (on root tag only) + // - this might or might not need to take uncompression into account + // FIXME: for updates, we might need to force an overwrite (that is, deleting before) + QFile file(payloadfile); + + bool success = file.rename(installpath); + if (!success) { + kError() << "Cannot move file '" << payloadfile << "' to destination '" << installpath << "'"; + return false; + } + installedFiles << installpath; + installedFiles << installdir + "/"; } - installedFiles << installpath; - installedFiles << installdir + "/"; } + entry->setInstalledFiles(installedFiles); if (!m_installation->command().isEmpty()) { KProcess process; QString command(m_installation->command()); QString fileArg(KShell::quoteArg(installpath)); command.replace("%f", fileArg); //kDebug() << "Postinstallation: execute command"; //kDebug() << "Command is: " << command; process.setShellCommand(command); int exitcode = process.execute(); if (exitcode) { kError() << "Command failed" << endl; } else { //kDebug() << "Command executed successfully"; } } // ==== FIXME: security code below must go above, when async handling is complete ==== // FIXME: security object lifecycle - it is a singleton! Security *sec = Security::ref(); connect(sec, SIGNAL(validityResult(int)), SLOT(slotInstallationVerification(int))); // FIXME: change to accept filename + signature sec->checkValidity(QString()); m_payloadfiles[entry] = installpath; registerEntry(entry); // FIXME: hm, do we need to update the cache really? // only registration is probably needed here emit signalEntryChanged(entry); return true; } bool CoreEngine::uninstall(KNS::Entry *entry) { entry->setStatus(Entry::Deleted); foreach(QString file, entry->installedFiles()) { if (file.endsWith("/")) { QDir dir; bool worked = dir.rmdir(file); if (!worked) { // Maybe directory contains user created files, ignore it continue; } } else { bool worked = QFile::remove(file); if (!worked) { kWarning() << "unable to delete file " << file; return false; } } } entry->setInstalledFiles(QStringList()); unregisterEntry(entry); emit signalEntryChanged(entry); return true; } void CoreEngine::slotInstallationVerification(int result) { //kDebug() << "SECURITY result " << result; if (result & Security::SIGNED_OK) emit signalInstallationFinished(); else emit signalInstallationFailed(); } void CoreEngine::setAutomationPolicy(AutomationPolicy policy) { m_automationpolicy = policy; } void CoreEngine::setCachePolicy(CachePolicy policy) { m_cachepolicy = policy; } QStringList KNS::CoreEngine::archiveEntries(const QString& path, const KArchiveDirectory * dir) { QStringList files; foreach(QString entry, dir->entries()) { QString childPath = path + '/' + entry; if (dir->entry(entry)->isFile()) { files << childPath; } if (dir->entry(entry)->isDirectory()) { const KArchiveDirectory* childDir = static_cast(dir->entry(entry)); files << archiveEntries(childPath, childDir); files << childPath + "/"; } } return files; } #include "coreengine.moc" diff --git a/knewstuff/knewstuff2/ui/kdxsbutton.cpp b/knewstuff/knewstuff2/ui/kdxsbutton.cpp index ec18d60a5d..4229c2df3c 100644 --- a/knewstuff/knewstuff2/ui/kdxsbutton.cpp +++ b/knewstuff/knewstuff2/ui/kdxsbutton.cpp @@ -1,602 +1,602 @@ /* This file is part of KNewStuff2. Copyright (c) 2005 - 2007 Josef Spillner This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kdxsbutton.h" #include "knewstuff2/dxs/dxs.h" #include "knewstuff2/core/entry.h" #include "knewstuff2/core/category.h" #include "downloaddialog.h" #include "kdxsrating.h" #include "kdxscomment.h" #include "kdxscomments.h" #include "kdxschanges.h" #include "kdxstranslation.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KNS; KDXSButton::KDXSButton(QWidget *parent) : QToolButton(parent), d(0) { m_entry = 0; m_provider = 0; m_dxs = 0; m_engine = 0; // FIXME KDE4PORT //setBackgroundColor(QColor(255, 255, 255)); m_p = new KMenu(this); action_install = m_p->addAction(SmallIcon("get-hot-new-stuff"), i18n("Install")); action_uninstall = m_p->addAction(i18n("Uninstall")); action_comments = m_p->addAction(SmallIcon("help-about"), i18n("Comments")); action_changes = m_p->addAction(SmallIcon("help-about"), i18n("Changelog")); m_history = new KMenu(this); m_history->setTitle(i18n("Switch version")); // FIXME KDE4PORT //m_history->insertItem(i18n("(Search...)"), historyinactive); //m_history->setItemEnabled(historyinactive, false); action_historysub = m_p->addMenu(m_history); m_p->addSeparator(); action_info = m_p->addAction(i18n("Provider information")); m_contact = new KMenu(this); m_contact->setIcon(SmallIcon("mail-message-new")); m_contact->setTitle(i18n("Contact author")); KMenu *pcollab = new KMenu(this); pcollab->setTitle(i18n("Collaboration")); action_collabrating = pcollab->addAction(i18n("Add Rating")); action_collabcomment = pcollab->addAction(i18n("Add Comment")); action_collabtranslation = pcollab->addAction(i18n("Translate")); action_collabsubscribe = pcollab->addAction(i18n("Subscribe")); action_collabremoval = pcollab->addAction(i18n("Report bad entry")); pcollab->addMenu(m_contact); m_p->addSeparator(); action_collaboratesub = m_p->addMenu(pcollab); connect(this, SIGNAL(clicked()), SLOT(slotClicked())); connect(m_p, SIGNAL(triggered(QAction*)), SLOT(slotTriggered(QAction*))); connect(m_contact, SIGNAL(triggered(QAction*)), SLOT(slotTriggered(QAction*))); connect(pcollab, SIGNAL(triggered(QAction*)), SLOT(slotTriggered(QAction*))); // FIXME KDE4PORT: dynamic qactions are needed here //connect(m_history, SIGNAL(activated(int)), SLOT(slotVersionsActivated(int))); //connect(m_history, SIGNAL(highlighted(int)), SLOT(slotVersionsHighlighted(int))); setToolButtonStyle(Qt::ToolButtonTextBesideIcon); setPopupMode(QToolButton::MenuButtonPopup); setMenu(m_p); setEnabled(false); show(); } KDXSButton::~KDXSButton() { } void KDXSButton::setEntry(Entry *e) { m_entry = e; if(m_engine) setEnabled(true); Entry::Status status = e->status(); switch (status) { case Entry::Installed: setText(i18n("Uninstall")); action_install->setVisible(false); action_uninstall->setVisible(true); break; case Entry::Updateable: setText(i18n("Update")); action_uninstall->setVisible(false); action_install->setVisible(true); break; case Entry::Deleted: /// @todo Set different button text when string freeze is over? "Install again" setText(i18n("Install")); action_uninstall->setVisible(false); action_install->setVisible(true); break; default: setText(i18n("Install")); action_uninstall->setVisible(false); action_install->setVisible(true); } Author author = e->author(); if(!author.email().isEmpty()) { action_contactbymail = m_contact->addAction(SmallIcon("mail-message-new"), i18n("Send Mail")); } if(!author.jabber().isEmpty()) { action_contactbyjabber = m_contact->addAction(i18n("Contact on Jabber")); } } void KDXSButton::setProvider(const KNS::Provider *provider) { m_provider = provider; if(!provider) return; // FIXME: make it possible to query DxsEngine's DxsPolicy and react here? // FIXME: handle switch-version and collab menus as well if(provider->webService().isValid()) { // the web service url is valid, so enable all the actions action_collabrating->setEnabled(true); action_collabcomment->setEnabled(true); action_collabtranslation->setEnabled(true); action_collabsubscribe->setEnabled(true); action_collabremoval->setEnabled(true); action_comments->setEnabled(true); action_changes->setEnabled(true); m_history->setEnabled(true); } else { action_collabrating->setEnabled(false); action_collabcomment->setEnabled(false); action_collabtranslation->setEnabled(false); action_collabsubscribe->setEnabled(false); action_collabremoval->setEnabled(false); action_comments->setEnabled(false); action_changes->setEnabled(false); m_history->setEnabled(false); } } void KDXSButton::setEngine(DxsEngine *engine) { m_engine = engine; if(m_entry) setEnabled(true); m_dxs = new KNS::Dxs(m_engine); m_dxs->setEndpoint(KUrl("http://new.kstuff.org/cgi-bin/hotstuff-dxs")); // FIXME: use real endpoint as soon as provider is loaded // FIXME: actually we would need a setProvider() here as well // FIXME: another thing: shouldn't dxsengine own the dxs object? connect(m_dxs, SIGNAL(signalInfo(QString, QString, QString)), SLOT(slotInfo(QString, QString, QString))); connect(m_dxs, SIGNAL(signalCategories(QList)), SLOT(slotCategories(QList))); connect(m_dxs, SIGNAL(signalEntries(QList)), SLOT(slotEntries(QList))); connect(m_dxs, SIGNAL(signalComments(QStringList)), SLOT(slotComments(QStringList))); connect(m_dxs, SIGNAL(signalChanges(QStringList)), SLOT(slotChanges(QStringList))); connect(m_dxs, SIGNAL(signalHistory(QStringList)), SLOT(slotHistory(QStringList))); connect(m_dxs, SIGNAL(signalRemoval(bool)), SLOT(slotRemoval(bool))); connect(m_dxs, SIGNAL(signalSubscription(bool)), SLOT(slotSubscription(bool))); connect(m_dxs, SIGNAL(signalComment(bool)), SLOT(slotComment(bool))); connect(m_dxs, SIGNAL(signalRating(bool)), SLOT(slotRating(bool))); connect(m_dxs, SIGNAL(signalFault()), SLOT(slotFault())); connect(m_dxs, SIGNAL(signalError()), SLOT(slotError())); } void KDXSButton::slotInfo(QString provider, QString server, QString version) { QString infostring = i18n("Server: %1", server); infostring += '\n' + i18n("Provider: %1", provider); infostring += '\n' + i18n("Version: %1", version); KMessageBox::information(this, infostring, i18n("Provider information")); } void KDXSButton::slotCategories(QList categories) { for(QList::Iterator it = categories.begin(); it != categories.end(); it++) { KNS::Category *category = (*it); //kDebug() << "Category: " << category->name().representation(); } } void KDXSButton::slotEntries(QList entries) { for(QList::Iterator it = entries.begin(); it != entries.end(); it++) { KNS::Entry *entry = (*it); //kDebug() << "Entry: " << entry->name().representation(); } } void KDXSButton::slotComments(QStringList comments) { KDXSComments commentsdlg(this); for(QStringList::Iterator it = comments.begin(); it != comments.end(); it++) { //kDebug() << "Comment: " << (*it); commentsdlg.addComment("foo", (*it)); } commentsdlg.exec(); } void KDXSButton::slotChanges(QStringList changes) { KDXSChanges changesdlg(this); for(QStringList::Iterator it = changes.begin(); it != changes.end(); it++) { //kDebug() << "Changelog: " << (*it); changesdlg.addChangelog("v???", (*it)); } changesdlg.exec(); } void KDXSButton::slotHistory(QStringList entries) { m_history->clear(); int i = 0; for(QStringList::Iterator it = entries.begin(); it != entries.end(); it++) { //kDebug() << (*it); // FIXME KDE4PORT //m_history->insertItem(SmallIcon("view-history"), // i18n((*it)), historyslots + i); i++; } if(entries.size() == 0) { // FIXME KDE4PORT //m_history->insertItem(i18n("(No history found)"), historydisabled); //m_history->setItemEnabled(historydisabled, false); } m_history->setCursor(Qt::ArrowCursor); } void KDXSButton::slotRemoval(bool success) { if(success) { KMessageBox::information(this, i18n("The removal request was successfully registered."), i18n("Removal of entry")); } else { KMessageBox::error(this, i18n("The removal request failed."), i18n("Removal of entry")); } } void KDXSButton::slotSubscription(bool success) { if(success) { KMessageBox::information(this, i18n("The subscription was successfully completed."), i18n("Subscription to entry")); } else { KMessageBox::error(this, i18n("The subscription request failed."), i18n("Subscription to entry")); } } void KDXSButton::slotRating(bool success) { if(success) { KMessageBox::information(this, i18n("The rating was submitted successfully."), i18n("Rating for entry")); } else { KMessageBox::error(this, i18n("The rating could not be submitted."), i18n("Rating for entry")); } } void KDXSButton::slotComment(bool success) { if(success) { KMessageBox::information(this, i18n("The comment was submitted successfully."), i18n("Comment on entry")); } else { KMessageBox::error(this, i18n("The comment could not be submitted."), i18n("Comment on entry")); } } void KDXSButton::slotFault() { KMessageBox::error(this, i18n("A protocol fault has occurred. The request has failed."), i18n("Desktop Exchange Service")); } void KDXSButton::slotError() { KMessageBox::error(this, i18n("A network error has occurred. The request has failed."), i18n("Desktop Exchange Service")); } void KDXSButton::slotVersionsActivated(int id) { int version = id - historyslots; Q_UNUSED(version); // and now??? } void KDXSButton::slotTriggered(QAction *action) { int ret; if(action == action_info) { // FIXME: consider engine's DxsPolicy if(m_provider->webService().isValid()) { m_dxs->call_info(); } else { slotInfo(m_provider->name().representation(), QString(), QString()); } } if(action == action_comments) { m_dxs->call_comments(0); } if(action == action_changes) { m_dxs->call_changes(2); } if(action == action_contactbymail) { QString address = m_entry->author().email(); KToolInvocation::invokeMailer(address, i18n("KNewStuff contributions"), ""); } if(action == action_contactbyjabber) { new KRun(KUrl(QLatin1String("xmpp:") + m_entry->author().jabber()), this ); } if(action == action_collabtranslation) { if(!authenticate()) return; KDXSTranslation translation(this); ret = translation.exec(); if(ret == QDialog::Accepted) { //QString s = comment.comment(); //if(!s.isEmpty()) //{ // m_dxs->call_comment(0, s); //} } } if(action == action_collabremoval) { if(authenticate()) m_dxs->call_removal(0); } if(action == action_collabsubscribe) { if(authenticate()) m_dxs->call_subscription(0, true); } if(action == action_uninstall) { if (m_engine->uninstall(m_entry)) { setText(i18n("Install")); action_uninstall->setVisible(false); action_install->setVisible(true); } } if(action == action_install) { connect(m_engine, SIGNAL(signalPayloadLoaded(KUrl)), SLOT(slotPayloadLoaded(KUrl))); connect(m_engine, SIGNAL(signalPayloadFailed()), SLOT(slotPayloadFailed())); m_engine->downloadPayload(m_entry); } if(action == action_collabcomment) { if(!authenticate()) return; KDXSComment comment(this); ret = comment.exec(); if(ret == QDialog::Accepted) { QString s = comment.comment(); if(!s.isEmpty()) { m_dxs->call_comment(0, s); } } } if(action == action_collabrating) { if(!authenticate()) return; KDXSRating rating(this); ret = rating.exec(); if(ret == QDialog::Accepted) { int r = rating.rating(); if(r >= 0) { m_dxs->call_rating(0, r); } } } } void KDXSButton::slotVersionsHighlighted(int id) { //kDebug() << "highlighted!"; if(id == historyinactive) { //m_history->setItemEnabled(historyinactive, true); m_history->setCursor(QCursor(Qt::WaitCursor)); //kDebug() << "hourglass!"; m_dxs->call_history(0); // ..... } } void KDXSButton::slotClicked() { if(action_install->isVisible()) slotTriggered(action_install); else slotTriggered(action_uninstall); } bool KDXSButton::authenticate() { if((!m_username.isEmpty()) && (!m_password.isEmpty())) return true; return true; // FIXME: hack during development only KIO::PasswordDialog dlg(i18n("This operation needs authentication"), QString()); int ret = dlg.exec(); if(ret == QDialog::Accepted) { m_username = dlg.username(); m_password = dlg.password(); return true; } return false; } void KDXSButton::slotPayloadLoaded(KUrl url) { //kDebug() << "PAYLOAD: success; try to install"; Entry::Status status = m_entry->status(); if(status == Entry::Installed) { setText(i18n("Uninstall")); action_install->setVisible(false); action_uninstall->setVisible(true); } else { setText(i18n("Install")); action_uninstall->setVisible(false); action_install->setVisible(true); } - m_engine->install(url.path()); + m_engine->install(url.pathOrUrl()); } void KDXSButton::slotPayloadFailed() { //kDebug() << "PAYLOAD: failed"; } #include "kdxsbutton.moc"