2008-11-04 00:08:39 +01:00
|
|
|
/******************************************************************************
|
|
|
|
* Copyright 2007 by Aaron Seigo <aseigo@kde.org> *
|
|
|
|
* Copyright 2007 by Riccardo Iaconelli <riccardo@kde.org> *
|
|
|
|
* *
|
|
|
|
* 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 "package.h"
|
|
|
|
|
|
|
|
#include <QDir>
|
|
|
|
#include <QFile>
|
|
|
|
#include <QRegExp>
|
|
|
|
|
2008-11-04 03:20:46 +01:00
|
|
|
#include <karchive.h>
|
2008-11-04 03:04:34 +01:00
|
|
|
#include <kcomponentdata.h>
|
|
|
|
#include <kdesktopfile.h>
|
|
|
|
#include <kio/copyjob.h>
|
|
|
|
#include <kio/deletejob.h>
|
2008-11-04 03:20:46 +01:00
|
|
|
#include <kio/jobclasses.h>
|
2008-11-04 03:04:34 +01:00
|
|
|
#include <kio/job.h>
|
|
|
|
#include <kplugininfo.h>
|
|
|
|
#include <kstandarddirs.h>
|
|
|
|
#include <ktempdir.h>
|
|
|
|
#include <ktemporaryfile.h>
|
|
|
|
#include <kzip.h>
|
|
|
|
#include <kdebug.h>
|
2008-11-04 00:08:39 +01:00
|
|
|
|
|
|
|
#include "packagemetadata.h"
|
|
|
|
|
|
|
|
namespace Plasma
|
|
|
|
{
|
|
|
|
|
|
|
|
class PackagePrivate
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
PackagePrivate(const PackageStructure::Ptr st, const QString &p)
|
2009-04-07 07:42:30 +02:00
|
|
|
: structure(st)
|
2008-11-04 00:08:39 +01:00
|
|
|
{
|
2009-04-07 07:42:30 +02:00
|
|
|
structure->setPath(p);
|
|
|
|
valid = !structure->path().isEmpty();
|
2008-11-04 00:08:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
~PackagePrivate()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
PackageStructure::Ptr structure;
|
|
|
|
bool valid;
|
|
|
|
};
|
|
|
|
|
|
|
|
Package::Package(const QString &packageRoot, const QString &package,
|
|
|
|
PackageStructure::Ptr structure)
|
|
|
|
: d(new PackagePrivate(structure, packageRoot + '/' + package))
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
Package::Package(const QString &packagePath, PackageStructure::Ptr structure)
|
|
|
|
: d(new PackagePrivate(structure, packagePath))
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
Package::~Package()
|
|
|
|
{
|
|
|
|
delete d;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Package::isValid() const
|
|
|
|
{
|
|
|
|
if (!d->valid) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach (const char *dir, d->structure->requiredDirectories()) {
|
2009-04-07 07:42:30 +02:00
|
|
|
if (!QFile::exists(d->structure->path() + d->structure->contentsPrefix() + d->structure->path(dir))) {
|
2008-11-04 00:08:39 +01:00
|
|
|
kWarning() << "Could not find required directory" << dir;
|
|
|
|
d->valid = false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach (const char *file, d->structure->requiredFiles()) {
|
2009-04-07 07:42:30 +02:00
|
|
|
if (!QFile::exists(d->structure->path() + d->structure->contentsPrefix() + d->structure->path(file))) {
|
2008-11-04 00:08:39 +01:00
|
|
|
kWarning() << "Could not find required file" << file << ", look in"
|
2009-04-07 07:42:30 +02:00
|
|
|
<< d->structure->path() + d->structure->contentsPrefix() + d->structure->path(file) << endl;
|
2008-11-04 00:08:39 +01:00
|
|
|
d->valid = false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString Package::filePath(const char *fileType, const QString &filename) const
|
|
|
|
{
|
|
|
|
if (!d->valid) {
|
|
|
|
kDebug() << "package is not valid";
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
|
|
|
QString path = d->structure->path(fileType);
|
|
|
|
|
|
|
|
if (path.isEmpty()) {
|
2009-01-13 20:49:36 +01:00
|
|
|
kDebug() << "no matching path came of it, while looking for" << fileType << filename;
|
2008-11-04 00:08:39 +01:00
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
2009-04-07 07:42:30 +02:00
|
|
|
path.prepend(d->structure->path() + d->structure->contentsPrefix());
|
2008-11-04 00:08:39 +01:00
|
|
|
|
|
|
|
if (!filename.isEmpty()) {
|
|
|
|
path.append("/").append(filename);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (QFile::exists(path)) {
|
2009-01-16 02:02:35 +01:00
|
|
|
if (d->structure->allowExternalPaths()) {
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
2008-11-04 00:08:39 +01:00
|
|
|
// ensure that we don't return files outside of our base path
|
|
|
|
// due to symlink or ../ games
|
|
|
|
QDir dir(path);
|
|
|
|
QString canonicalized = dir.canonicalPath() + QDir::separator();
|
2009-04-07 07:42:30 +02:00
|
|
|
if (canonicalized.startsWith(d->structure->path())) {
|
2008-11-04 00:08:39 +01:00
|
|
|
return path;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
kDebug() << path << "does not exist";
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
|
|
|
QString Package::filePath(const char *fileType) const
|
|
|
|
{
|
|
|
|
return filePath(fileType, QString());
|
|
|
|
}
|
|
|
|
|
|
|
|
QStringList Package::entryList(const char *fileType) const
|
|
|
|
{
|
|
|
|
if (!d->valid) {
|
|
|
|
return QStringList();
|
|
|
|
}
|
|
|
|
|
2009-04-07 07:42:30 +02:00
|
|
|
return d->structure->entryList(fileType);
|
2008-11-04 00:08:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
PackageMetadata Package::metadata() const
|
|
|
|
{
|
|
|
|
return d->structure->metadata();
|
|
|
|
}
|
|
|
|
|
|
|
|
const QString Package::path() const
|
|
|
|
{
|
2009-04-07 07:42:30 +02:00
|
|
|
return d->structure->path();
|
2008-11-04 00:08:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const PackageStructure::Ptr Package::structure() const
|
|
|
|
{
|
|
|
|
return d->structure;
|
|
|
|
}
|
|
|
|
|
|
|
|
//TODO: provide a version of this that allows one to ask for certain types of packages, etc?
|
|
|
|
// should we be using KService here instead/as well?
|
|
|
|
QStringList Package::listInstalled(const QString &packageRoot) // static
|
|
|
|
{
|
|
|
|
QDir dir(packageRoot);
|
|
|
|
|
|
|
|
if (!dir.exists()) {
|
|
|
|
return QStringList();
|
|
|
|
}
|
|
|
|
|
|
|
|
QStringList packages;
|
|
|
|
|
|
|
|
foreach (const QString &sdir, dir.entryList(QDir::AllDirs | QDir::Readable)) {
|
|
|
|
QString metadata = packageRoot + '/' + sdir + "/metadata.desktop";
|
|
|
|
if (QFile::exists(metadata)) {
|
|
|
|
PackageMetadata m(metadata);
|
|
|
|
packages << m.pluginName();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return packages;
|
|
|
|
}
|
|
|
|
|
2009-01-05 19:49:54 +01:00
|
|
|
QStringList Package::listInstalledPaths(const QString &packageRoot) // static
|
|
|
|
{
|
|
|
|
QDir dir(packageRoot);
|
|
|
|
|
|
|
|
if (!dir.exists()) {
|
|
|
|
return QStringList();
|
|
|
|
}
|
|
|
|
|
|
|
|
QStringList packages;
|
|
|
|
|
|
|
|
foreach (const QString &sdir, dir.entryList(QDir::AllDirs | QDir::Readable)) {
|
|
|
|
QString metadata = packageRoot + '/' + sdir + "/metadata.desktop";
|
|
|
|
if (QFile::exists(metadata)) {
|
|
|
|
packages << sdir;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return packages;
|
|
|
|
}
|
|
|
|
|
2008-11-04 00:08:39 +01:00
|
|
|
bool Package::installPackage(const QString &package,
|
|
|
|
const QString &packageRoot,
|
|
|
|
const QString &servicePrefix) // static
|
|
|
|
{
|
|
|
|
//TODO: report *what* failed if something does fail
|
|
|
|
QDir root(packageRoot);
|
|
|
|
|
|
|
|
if (!root.exists()) {
|
|
|
|
KStandardDirs::makeDir(packageRoot);
|
|
|
|
if (!root.exists()) {
|
|
|
|
kWarning() << "Could not create package root directory:" << packageRoot;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QFileInfo fileInfo(package);
|
|
|
|
if (!fileInfo.exists()) {
|
|
|
|
kWarning() << "No such file:" << package;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString path;
|
|
|
|
KTempDir tempdir;
|
|
|
|
bool archivedPackage = false;
|
|
|
|
|
|
|
|
if (fileInfo.isDir()) {
|
|
|
|
// we have a directory, so let's just install what is in there
|
|
|
|
path = package;
|
|
|
|
|
|
|
|
// make sure we end in a slash!
|
|
|
|
if (path[path.size() - 1] != '/') {
|
|
|
|
path.append('/');
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
KZip archive(package);
|
|
|
|
if (!archive.open(QIODevice::ReadOnly)) {
|
|
|
|
kWarning() << "Could not open package file:" << package;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
archivedPackage = true;
|
|
|
|
const KArchiveDirectory *source = archive.directory();
|
|
|
|
const KArchiveEntry *metadata = source->entry("metadata.desktop");
|
|
|
|
|
|
|
|
if (!metadata) {
|
|
|
|
kWarning() << "No metadata file in package" << package;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
path = tempdir.name();
|
|
|
|
source->copyTo(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
QString metadataPath = path + "metadata.desktop";
|
|
|
|
if (!QFile::exists(metadataPath)) {
|
|
|
|
kWarning() << "No metadata file in package" << package;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
PackageMetadata meta(metadataPath);
|
|
|
|
QString targetName = meta.pluginName();
|
|
|
|
|
|
|
|
if (targetName.isEmpty()) {
|
|
|
|
kWarning() << "Package plugin name not specified";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure that package names are safe so package uninstall can't inject
|
|
|
|
// bad characters into the paths used for removal.
|
2009-03-17 17:43:21 +01:00
|
|
|
QRegExp validatePluginName("^[\\w-\\.]+$"); // Only allow letters, numbers, underscore and period.
|
2008-11-04 00:08:39 +01:00
|
|
|
if (!validatePluginName.exactMatch(targetName)) {
|
|
|
|
kWarning() << "Package plugin name " << targetName << "contains invalid characters";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
targetName = packageRoot + '/' + targetName;
|
|
|
|
if (QFile::exists(targetName)) {
|
|
|
|
kWarning() << targetName << "already exists";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (archivedPackage) {
|
|
|
|
// it's in a temp dir, so just move it over.
|
|
|
|
KIO::CopyJob *job = KIO::move(KUrl(path), KUrl(targetName), KIO::HideProgressInfo);
|
|
|
|
if (!job->exec()) {
|
|
|
|
kWarning() << "Could not move package to destination:" << targetName << " : " << job->errorString();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// it's a directory containing the stuff, so copy the contents rather
|
|
|
|
// than move them
|
|
|
|
KIO::CopyJob *job = KIO::copy(KUrl(path), KUrl(targetName), KIO::HideProgressInfo);
|
|
|
|
if (!job->exec()) {
|
|
|
|
kWarning() << "Could not copy package to destination:" << targetName << " : " << job->errorString();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (archivedPackage) {
|
|
|
|
// no need to remove the temp dir (which has been successfully moved if it's an archive)
|
|
|
|
tempdir.setAutoRemove(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
// and now we register it as a service =)
|
|
|
|
QString metaPath = targetName + "/metadata.desktop";
|
|
|
|
KDesktopFile df(metaPath);
|
|
|
|
KConfigGroup cg = df.desktopGroup();
|
|
|
|
|
|
|
|
// Q: should not installing it as a service disqualify it?
|
|
|
|
// Q: i don't think so since KServiceTypeTrader may not be
|
|
|
|
// used by the installing app in any case, and the
|
|
|
|
// package is properly installed - aseigo
|
|
|
|
|
|
|
|
//TODO: reduce code duplication with registerPackage below
|
|
|
|
|
|
|
|
QString serviceName = servicePrefix + meta.pluginName();
|
|
|
|
|
|
|
|
QString service = KStandardDirs::locateLocal("services", serviceName + ".desktop");
|
|
|
|
KIO::FileCopyJob *job = KIO::file_copy(metaPath, service, -1, KIO::HideProgressInfo);
|
|
|
|
if (job->exec()) {
|
|
|
|
// the icon in the installed file needs to point to the icon in the
|
|
|
|
// installation dir!
|
|
|
|
QString iconPath = targetName + '/' + cg.readEntry("Icon");
|
|
|
|
QFile icon(iconPath);
|
|
|
|
if (icon.exists()) {
|
|
|
|
KDesktopFile df(service);
|
|
|
|
KConfigGroup cg = df.desktopGroup();
|
|
|
|
cg.writeEntry("Icon", iconPath);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Package::uninstallPackage(const QString &pluginName,
|
|
|
|
const QString &packageRoot,
|
|
|
|
const QString &servicePrefix) // static
|
|
|
|
{
|
|
|
|
// We need to remove the package directory and its metadata file.
|
|
|
|
QString targetName = pluginName;
|
|
|
|
targetName = packageRoot + '/' + targetName;
|
|
|
|
|
|
|
|
if (!QFile::exists(targetName)) {
|
|
|
|
kWarning() << targetName << "does not exist";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString serviceName = servicePrefix + pluginName;
|
|
|
|
|
|
|
|
QString service = KStandardDirs::locateLocal("services", serviceName + ".desktop");
|
|
|
|
kDebug() << "Removing service file " << service;
|
|
|
|
bool ok = QFile::remove(service);
|
|
|
|
|
|
|
|
if (!ok) {
|
|
|
|
kWarning() << "Unable to remove " << service;
|
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
|
|
|
|
KIO::DeleteJob *job = KIO::del(KUrl(targetName));
|
|
|
|
if (!job->exec()) {
|
|
|
|
kWarning() << "Could not delete package from:" << targetName << " : " << job->errorString();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Package::registerPackage(const PackageMetadata &data, const QString &iconPath)
|
|
|
|
{
|
|
|
|
QString serviceName("plasma-applet-" + data.pluginName());
|
|
|
|
QString service = KStandardDirs::locateLocal("services", serviceName + ".desktop");
|
|
|
|
|
|
|
|
if (data.pluginName().isEmpty()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
data.write(service);
|
|
|
|
|
|
|
|
KDesktopFile config(service);
|
|
|
|
KConfigGroup cg = config.desktopGroup();
|
|
|
|
const QString type = data.type().isEmpty() ? "Service" : data.type();
|
|
|
|
cg.writeEntry("Type", type);
|
|
|
|
const QString serviceTypes = data.serviceType().isNull() ? "Plasma/Applet,Plasma/Containment" : data.serviceType();
|
|
|
|
cg.writeEntry("X-KDE-ServiceTypes", serviceTypes);
|
|
|
|
cg.writeEntry("X-KDE-PluginInfo-EnabledByDefault", true);
|
|
|
|
|
|
|
|
QFile icon(iconPath);
|
|
|
|
if (icon.exists()) {
|
|
|
|
//FIXME: the '/' search will break on non-UNIX. do we care?
|
|
|
|
QString installedIcon("plasma_applet_" + data.pluginName() +
|
|
|
|
iconPath.right(iconPath.length() - iconPath.lastIndexOf("/")));
|
|
|
|
cg.writeEntry("Icon", installedIcon);
|
|
|
|
installedIcon = KStandardDirs::locateLocal("icon", installedIcon);
|
|
|
|
KIO::FileCopyJob *job = KIO::file_copy(iconPath, installedIcon, -1, KIO::HideProgressInfo);
|
|
|
|
job->exec();
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Package::createPackage(const PackageMetadata &metadata,
|
|
|
|
const QString &source,
|
|
|
|
const QString &destination,
|
|
|
|
const QString &icon) // static
|
|
|
|
{
|
|
|
|
Q_UNUSED(icon)
|
|
|
|
if (!metadata.isValid()) {
|
|
|
|
kWarning() << "Metadata file is not complete";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// write metadata in a temporary file
|
|
|
|
KTemporaryFile metadataFile;
|
|
|
|
if (!metadataFile.open()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
metadata.write(metadataFile.fileName());
|
|
|
|
|
|
|
|
// put everything into a zip archive
|
|
|
|
KZip creation(destination);
|
|
|
|
creation.setCompression(KZip::NoCompression);
|
|
|
|
if (!creation.open(QIODevice::WriteOnly)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
creation.addLocalFile(metadataFile.fileName(), "metadata.desktop");
|
|
|
|
creation.addLocalDirectory(source, "contents");
|
|
|
|
creation.close();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // Namespace
|